Skip to content

Commit 7caa978

Browse files
NagyViktclaude
andcommitted
v1.18: AGENTS.md release-policy enforcement + audit script
Codifies the "every vX.Y commit gets a matching tag + release before the turn ends" rule that's already implicit in the .github/workflows/ release.yml flow, and ships AGENTS-release-check.sh so the rule is testable in one command. What changed in AGENTS.md (§ Versioning + releases): - Made the rule explicit for parallel-session work: when the user commits in parallel, agents must check at end-of-turn that no untagged version commit is sitting on main. - Added a TL;DR pointing at the new check script. - Clarified that the auto-release workflow does the heavy lifting — agents only need to tag+push, not run gh release create themselves. - Added a backfill policy: do NOT retroactively tag v1.10–v1.14 (they predate the workflow + packaging; auto-build would fail). Explicit user request only. - Added hot-spot reminders: fetch origin before tagging, wait for the workflow to finish, don't bump again to "fix" a missing release. AGENTS-release-check.sh: - Walks every commit whose subject matches ^v[0-9]+\.[0-9]+(\.[0-9]+)?: Verifies (a) a tag of the same name exists locally and (b) points at the same SHA; if gh is available, also (c) verifies the GitHub release exists. Exits 0 on a clean repo, 1 when anything is missing. - Quiet by default; -v / --verbose prints OK rows too. - Pulls origin/refs/tags before checking so a tag pushed but not fetched doesn't false-positive. Verified: against the current main, the script correctly reports v1.10–v1.14 missing (the pre-workflow versions) and v1.15–v1.17 clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d54e833 commit 7caa978

2 files changed

Lines changed: 112 additions & 13 deletions

File tree

AGENTS-release-check.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
# AGENTS.md end-of-turn check: every `v1.X:` commit on main must have
3+
# a matching annotated tag, and every tag must have a published GitHub
4+
# release. Exit code:
5+
# 0 — everything in sync
6+
# 1 — at least one version commit is missing a tag or release
7+
# 2 — environment unusable (no git, no gh, not in this repo)
8+
#
9+
# Quiet by default; pass --verbose to also print the GOOD lines.
10+
# This is a CHECK, not a fixer. It prints what's broken; you decide how
11+
# to repair (usually: tag + push the missing commit, let the workflow
12+
# build the release).
13+
set -u
14+
15+
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
16+
cd "$REPO_DIR" || { echo "cannot cd to $REPO_DIR" >&2; exit 2; }
17+
command -v git >/dev/null 2>&1 || { echo "git not in PATH" >&2; exit 2; }
18+
19+
VERBOSE=0
20+
[ "${1:-}" = "--verbose" ] || [ "${1:-}" = "-v" ] && VERBOSE=1
21+
22+
# Pull latest tag state from origin so we don't false-positive on a tag
23+
# the user pushed but our local clone hasn't fetched.
24+
git fetch origin --tags --quiet 2>/dev/null || true
25+
26+
missing_tags=0
27+
missing_releases=0
28+
29+
# Walk every commit whose subject starts with `vX.Y[.Z]:`.
30+
while IFS=$'\t' read -r sha subject; do
31+
tag=$(printf '%s' "$subject" | awk '{print $1}' | tr -d ':')
32+
# The tag must (a) exist locally and (b) point at this same commit.
33+
if ! git rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; then
34+
printf 'MISSING TAG %s %s\n' "$tag" "$sha"
35+
missing_tags=$((missing_tags + 1))
36+
continue
37+
fi
38+
tagged_sha=$(git rev-list -n 1 "refs/tags/$tag")
39+
if [ "$tagged_sha" != "$sha" ]; then
40+
printf 'TAG MISPOINTS %s expected %s got %s\n' "$tag" "$sha" "$tagged_sha"
41+
missing_tags=$((missing_tags + 1))
42+
continue
43+
fi
44+
if command -v gh >/dev/null 2>&1; then
45+
if ! gh release view "$tag" >/dev/null 2>&1; then
46+
printf 'MISSING RELEASE %s %s\n' "$tag" "$sha"
47+
missing_releases=$((missing_releases + 1))
48+
continue
49+
fi
50+
fi
51+
[ "$VERBOSE" = "1" ] && printf 'OK %s %s\n' "$tag" "$sha"
52+
done < <(git log --format='%H%x09%s' | awk -F'\t' '$2 ~ /^v[0-9]+\.[0-9]+(\.[0-9]+)?:/ {print}')
53+
54+
if [ "$missing_tags" -eq 0 ] && [ "$missing_releases" -eq 0 ]; then
55+
[ "$VERBOSE" = "1" ] && echo "all version commits tagged + released"
56+
exit 0
57+
fi
58+
59+
echo
60+
echo "summary: $missing_tags missing tag(s), $missing_releases missing release(s)"
61+
echo "see AGENTS.md § Versioning + releases for the fix workflow"
62+
exit 1

AGENTS.md

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,81 @@ Working rules for agents (Claude, Codex, etc.) editing this repository.
44

55
## Versioning + releases — non-negotiable
66

7-
**Every push that bumps a version MUST be accompanied by a matching GitHub release.** Pushing a commit titled `v1.X` without a release is a defect.
7+
**Every commit titled `vX.Y` MUST be tagged and have a matching GitHub release before you end your turn.** Pushing a `v1.X` commit without a tag is a defect; pushing a tag without a release is a defect. This rule applies to any agent (Claude, Codex, etc.) and to the user themselves — when committing in parallel, agents must check at end-of-turn that there's no untagged version commit on `main`.
88

9-
Workflow for any version bump:
9+
### TL;DR for AI agents
10+
11+
Run this at the end of every turn that touched this repo, **before claiming done**:
12+
13+
```bash
14+
bash AGENTS-release-check.sh # or paste-inline:
15+
for sha in $(git log --format='%H %s' | awk '$2 ~ /^v[0-9]+\.[0-9]+/ {print $1}'); do
16+
tag=$(git log -1 --format=%s "$sha" | awk '{print $1}' | tr -d ':')
17+
if ! git tag -l "$tag" | grep -q "^$tag$"; then
18+
echo "MISSING TAG: $tag at $sha"
19+
fi
20+
done
21+
```
22+
23+
If anything prints, you have unfinished work. Tag it, push it, verify the release.
24+
25+
### The full workflow
26+
27+
The repo has `.github/workflows/release.yml` that auto-builds the .deb and publishes a GitHub release on **every `v*` tag push**. So in practice the rule reduces to: **never push a `vX.Y` commit without immediately pushing the matching tag.**
1028

1129
1. **Bump in this order:**
1230
- `README.md` — if a version string appears in the tagline or quick-start
13-
- `bin/tmux-paste-dispatch.sh` header `WORKING VERSION: v1.X — <date>` comment
14-
- Any other file with a `v1.X` literal — search with `git grep '^# WORKING VERSION'` and `git grep -F 'v1.'`
31+
- `bin/tmux-paste-dispatch.sh` header `WORKING VERSION: v1.X — <date>` comment (note: historically not updated past v1.0; safe to leave alone unless the rest of the file is touched)
32+
- Any other file with a `v1.X` literal — `git grep -F 'v1.'`
1533

1634
2. **Commit message format:**
1735
```
1836
v1.X: <one-line summary>
1937
2038
<multi-paragraph body explaining what changed and why,
21-
matching the style of v1.10–v1.14 in the existing history>
39+
matching the style of v1.10–v1.17 in the existing history.>
2240
2341
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2442
```
2543
Use `git log --pretty=fuller` to confirm the trailer is preserved.
2644

27-
3. **Tag + push:**
45+
3. **Tag + push — same turn as the commit, never deferred:**
2846
```bash
29-
git tag -a v1.X -m "v1.X: <one-line summary>"
3047
git push origin main
48+
git tag -a v1.X -m "v1.X: <one-line summary>" <commit-sha>
3149
git push origin v1.X
3250
```
51+
The `<commit-sha>` is explicit so you tag the exact commit you just pushed, not HEAD (HEAD may have moved if the user committed in parallel).
3352

34-
4. **Create the GitHub release immediately after pushing:**
53+
4. **The workflow handles the release.** Confirm with:
54+
```bash
55+
gh run watch $(gh run list --workflow=release.yml --limit 1 --json databaseId -q '.[0].databaseId')
56+
gh release view v1.X
57+
```
58+
Workflow runs ~3 minutes (cargo build dominates). If the workflow fails, **investigate before ending the turn** — a failed release isn't optional cleanup, it's part of the bump.
59+
60+
5. **If the workflow is absent** (early commits, or the file got deleted), fall back to manual:
3561
```bash
3662
gh release create v1.X \
37-
--title "v1.X: <one-line summary>" \
63+
--title "flashpaste v1.X" \
3864
--notes "$(git log -1 --pretty=%B v1.X | tail -n +3)"
3965
```
40-
The `tail -n +3` skips the title line + blank line so the body is the multi-paragraph description.
41-
- If this is a **breaking** change, add `--latest=true` and prepend a `### Breaking` block to the notes.
42-
- If experimental / not ready for general use, add `--prerelease`.
66+
Add `--prerelease` for experimental builds; add the .deb as an asset arg when you have one built.
67+
68+
### Backfill policy
69+
70+
If you find untagged version commits in history (`v1.10``v1.14` are this case — they predate the .deb workflow), **do not retroactively tag them by default**. Reasons:
71+
- The workflow doesn't exist on those commits → tag push fails the build job.
72+
- Their build-deb.sh / Rust workspace may not exist or compile.
73+
- Auto-generated release notes for ancient tags add noise to the Releases page.
74+
75+
If the user explicitly asks for backfill, push tags one-by-one and use `gh release create --notes` manually (no workflow) per tag. Verify each .deb (if any) is correct before moving on.
76+
77+
### Hot-spot reminders
4378

44-
5. **Verify** with `gh release view v1.X` and `gh release list --limit 5`. The new release MUST appear in the list before considering the bump complete.
79+
- The user often commits in parallel with the agent. Always `git fetch origin && git log origin/main..HEAD` and `git log HEAD..origin/main` before tagging — your local HEAD may not be the version commit.
80+
- After `git push origin main` and `git push origin v1.X`, the workflow can take 2–5 minutes. Don't claim done before `gh release view v1.X` succeeds.
81+
- If the workflow fails for environmental reasons (transient apt fetch failure, runner restart), retry with `gh run rerun`. Don't push a v1.X+1 to "fix" a missing v1.X release.
4582

4683
## Version-number policy
4784

0 commit comments

Comments
 (0)