release-aid
Cut a release of THIS repo (the AID methodology repo) end-to-end: bump the version, update the release notes, gate, tag, publish to every channel, and verify all checks green. One level argument decid
SKILL.md.claude/skills/release-aid/SKILL.md
GitHubRelease AID
Maintainer-only skill — outside
canonical/. Likegenerate-profile, this lives only at.claude/skills/release-aid/— it is NOT incanonical/skills/, is NOT rendered toprofiles/, and is NEVER shipped to adopters. It operates on the AID repo itself. Edit it directly. It is a runnable procedure, not a spec — for the format/architecture of a release see.aid/knowledge/infrastructure.md; for the deep runbook (recovery,release.shflags) seedocs/release.md. This skill EXECUTES the process and closes the gap those two leave open: it always updates the release notes as part of the release.
Cuts one release of the AID repo — beta or stable — from the command to artifacts published in the right places with every check green. Runs the whole sequence autonomously, pausing only at the irreversible human-gated points: the PR merge, the tag push, and confirming the publish.
Argument — REQUIRED
/release-aid <level> where <level> is exactly one of major | minor | patch | beta.
The argument is required. If it is missing or not one of the four values, STOP and ask (one clear question) which level to release — do NOT default, guess, or proceed. Only continue once a valid level is chosen.
Step 0 — Resolve the target version
Read the current version: cat VERSION (source of truth; format M.m.p optionally -beta.N).
Never trust a version hard-coded in prose. Compute the target by the level, bumping that level
and zeroing every level below it:
| level | current is stable M.m.p |
current is beta M.m.p-beta.N |
|---|---|---|
major |
(M+1).0.0 (stable) |
(M+1).0.0 (stable) |
minor |
M.(m+1).0 (stable) |
M.(m+1).0 (stable) |
patch |
M.m.(p+1) (stable) |
M.m.p (stable — PROMOTE: drop the -beta.N, do not bump) |
beta |
M.m.(p+1)-beta.1 (start the next patch's beta line) |
M.m.p-beta.(N+1) (next beta of the same target) |
Worked from 2.2.3-beta.1: beta→2.2.3-beta.2 · patch→2.2.3 · minor→2.3.0 ·
major→3.0.0. From stable 2.2.3: patch→2.2.4 · beta→2.2.4-beta.1.
Rationale for patch-from-beta = promote: a X.Y.Z-beta.N is the release candidate for
X.Y.Z, so finalizing that beta line ships X.Y.Z (never skips to X.Y.Z+1).
Print current -> target and the resulting channel plan (see Step 7's table), then proceed.
Step 1 — Preconditions (STOP on any failure; do not force)
git rev-parse --abbrev-ref HEAD # must be master
git fetch origin && git status -sb # clean worktree; up to date with origin/master (pull if behind)
git tag -l "v<target>" # must print nothing (no tag collision)
gh run list --workflow test.yml --branch master --limit 3 # latest master run must be green
python .claude/skills/generate-profile/scripts/run_generator.py && git diff --exit-code -- profiles/ # render-drift clean
- If the worktree shows only the unrelated
.aid/.aid-manifest.jsonlocal mod,git checkout --it (it is not a release artifact) — otherwise a dirty tree STOPs the release. - If
test.ymlis not green on the target commit, STOP — fix master first.
Step 2 — Bump the version carriers
The four carriers are VERSION, packages/npm/package.json, packages/pypi/pyproject.toml, and
the git tag v<target>. The gate job runs check-version-sync.sh and fails unless the present
carriers agree.
- stable (
major/minor/patch): set all three files to<target>(the tag in Step 6 makes the fourth). - beta: set
VERSION+pyproject.tomlto the SemVer form<target>(e.g.2.2.3-beta.2), and leavepackages/npm/package.jsonat the last stable — the version-sync gate detects a pre-release and exempts the npm carrier (npm ships no betas).
CRITICAL — beta version format: use SemVer
X.Y.Z-beta.N(with the dash), NOT PEP 440X.Y.ZbN. The CLI tool-bundle name regex needs the separator; PyPI normalizes-beta.N→bNon publish. A PEP-440 form makesaid add/update --from-bundlereject the bundle and CI go red.
Step 3 — Update documentation & release notes (the step the runbook omits — never skip it)
Docs drift precisely because no release step audits them; the release is NOT done until every documentation surface reflects the released state. Two parts, both landing in the release PR.
3.1 — Release notes / changelog ledger
- Drain
.aid/knowledge/backlog.md§## Next Releaseinto.aid/knowledge/release-tracking.md— that section is the committed slice for this tag and is the drain's only source. Open a NEW## v<target> - <YYYY-MM-DD>section (today, UTC) at the top ofrelease-tracking.mdand write one bullet per row of## Next Release: the row'sTag([NEW]/[CHANGE]/[FIX]) verbatim — the drain re-tags nothing — led by itsTitle, with itsDefinition & done-conditionas the bullet's text.Titleis the key the drain matches on, since a release-note bullet carries no id.- It is a move, not a copy. Delete each drained row from
backlog.md§## Next Releasein the SAME run that adds it to the new version section; an item left in both documents is a checkable defect.## Next Releaseis empty when the drain is done. - Sanity-check first: only items that genuinely ship in THIS version belong. If the committed
slice has fallen behind (
## Next Releaseholds already-shipped items, or intermediate versions have no section), reconcile against the published GitHub Releases +git log <last-tag>..HEADbefore draining — never stamp a version with the wrong items.
- It is a move, not a copy. Delete each drained row from
3.2 — Documentation sync (audit EVERY affected surface)
Per-feature docs are updated during each feature's own work; THIS is the release-level sweep that
(a) makes the release-spanning updates (version references, highlights) and (b) catches anything a
feature's work missed or that the release makes stale. Derive "what changed" from this version's
release-tracking.md items + git log <last-tag>..HEAD (new/changed commands, behaviors,
capabilities, and the version bump itself), then audit and update each surface it affects:
README.md— keep a short "recent releases -> see the changelog / GitHub Releases" pointer (no per-version "What's New in vX" block — that is the shape that froze at v1.1.0; OD-2 resolved = pointer only). The pointer carries no version-specific text, so it never goes stale. Also update any command/feature enumeration the release changed.- KB —
.aid/knowledge/— any doc describing a capability/command/behavior/channel that changed:capability-inventory.md+module-map.md(a new/changed command or module),infrastructure.md(release/channel/version facts — e.g. which distribution channels are enabled),technology-stack.md, and any other affected doc. docs/—install.md(command surface),release.md, and any versioned reference.- Docs site —
site/src/content/docs/—reference/cli.mdx(command reference), guides, reference pages. (The site's release changelog page lives separately atsite/src/pages/releases/changelog.astro— it is auto-sourced from GitHub Releases, so leave it.) - Methodology content — if the release changes a methodology skill/template/agent surface, the
SOURCE is
canonical/(rendered toprofiles/); editcanonical/and re-render via thegenerate-profileskill — never hand-editprofiles/. The render-drift gate (Step 1 + the release gate) catches a missed re-render.
Discovery aid: grep the doc surfaces for the outgoing version string and for enumerations of any command/feature the release touches — every stale hit is a doc to fix. Keep every edit TRUTHFUL: never add an unreleased/newer feature to a historical, version-frozen section (a "What's New in vX" block describes vX; cumulative history lives in the changelog / GitHub Releases).
Related: the
aid-document-changelogskill authors adopter-project changelogs; the AID-repo ledger drain (3.1) and this documentation sweep (3.2) are this skill's own responsibility.
Step 4 — PR the bump + notes ⏸ (human-gated merge)
Branch off master, commit all of Steps 2-3 — the version carriers, the ledger drain, and
every documentation file touched by the 3.2 sweep (do NOT include .aid/.aid-manifest.json,
which is never a release carrier) — push, open a PR.
gh auth switch --user AndreVianna # pushes/PRs to AndreVianna/aid-methodology need this account
git checkout -b release-<target>
git add VERSION packages/pypi/pyproject.toml # carriers (Step 2)
[ stable ] git add packages/npm/package.json # npm carrier (stable only)
git add .aid/knowledge/release-tracking.md .aid/knowledge/backlog.md # ledger drain (Step 3.1)
git add README.md docs/ site/ .aid/knowledge/ canonical/ profiles/ # doc sweep (Step 3.2) — stage ONLY the files you actually changed
git status # confirm ONLY intended files are staged; .aid/.aid-manifest.json must NOT be
git commit -m "chore(release): <target>"
git push -u origin release-<target>
gh pr create --base master --title "chore(release): <target>" --body "..."
gh auth switch --user AndreVianna-Ross # restore
⏸ PAUSE — hand the merge to the user. The auto-mode classifier BLOCKS gh pr merge; print the
command for the human to run and wait for confirmation it merged:
! gh pr merge <N> --repo AndreVianna/aid-methodology --merge --delete-branch
Verify the PR's CI is green (each check individually — see Step 7) before handing off the merge.
Step 5 — Sync + dry-run the release workflow
After the merge: git checkout master && git pull origin master. Then dry-run (publishes
nothing — npm does npm pack --dry-run, pypi skips upload; catches version-sync/render/pack
issues before the tag):
gh auth switch --user AndreVianna
gh workflow run release.yml --ref master -f ref=master -f dry_run=true
gh run watch # then verify the dry-run run is fully green (each job)
Use
-f ref=master(NOT-f ref="v<target>") — the tag does not exist yet, and the gate's version step falls back to theVERSIONfile.
Step 6 — Tag + push ⏸ (irreversible — confirm first)
⏸ PAUSE for explicit confirmation before tagging — a published version can never be reused.
gh auth switch --user AndreVianna # re-verify active account right before the tag push
git tag "v<target>" && git push origin "v<target>" # triggers release.yml end-to-end
Step 7 — Verify the release workflow (every check, individually)
gh run watch # convenience only
gh run list --workflow release.yml --limit 1 # then inspect the run's jobs one by one
gh run watch/gh pr checks --watchexiting 0 is NOT proof every job passed — verify each job's conclusion individually.
Expected jobs by channel:
| level | gate | github-release | pypi-publish | npm-publish |
|---|---|---|---|---|
| stable | pass | pass (release = latest) | pass | pass |
| beta | pass | pass (release marked prerelease, carries skill tarballs) | pass | skipped |
For a beta, confirm on the run that npm-publish shows skipped, github-release created a
pre-release, and pypi-publish is green. If NM08 (npm-pack file-listing) fails on a stable,
it is a known flake — gh run rerun --failed (a version-only bump cannot drop a packed file).
Step 8 — Confirm the artifacts landed ⏸ (publish confirmation — third human-gated point)
Confirm every channel published what it should:
- PyPI:
curl -s https://pypi.org/simple/aid-installer/ | grep <target-normalized>— use/simple/(the/pypi/<pkg>/jsoninfo.versionlags a minute+ after upload). Beta normalizes-beta.N→bN. - GitHub Release:
gh release view "v<target>"— beta must showPre-release: trueand NOT be/releases/latest; stable must be latest. Assets =aid-shared-v<target>.tar.gz, five agents-onlyaid-<tool>-v<target>.tar.gzartifacts, andaid-cli-v<target>.tar.gz, plus the two installer libraries andSHA256SUMS. - npm (stable only):
npm view aid-installer@<target> version.
⏸ PAUSE — present the confirmed-publish report to the user and await acknowledgment before close-out. This is the third and final human-gated point: the version is now live and can never be reused, so the skill STOPs here with the per-channel confirmation (version shipped, channels, release URL) instead of silently finishing. Proceed to Step 9 only after the human acknowledges — and if any channel failed to publish, surface it here rather than closing out.
Step 9 — Close out
gh auth switch --user AndreVianna-Ross # restore the default account
Verify the Step 3.1 drain landed, before reporting — for every Title drained:
grep -F "<title>" .aid/knowledge/backlog.md # must print NOTHING (row deleted)
grep -F "<title>" .aid/knowledge/release-tracking.md # must match, inside the new ## v<target> section
A title still present in .aid/knowledge/backlog.md, or missing from the new version section,
means the drain half-ran — finish it before closing out.
Report: the version shipped, the channels it went to, the release URL, and a one-line note that
release-tracking.md, .aid/knowledge/backlog.md and README were updated.
Autonomy
Run Steps 0-9 straight through without per-step check-ins, EXCEPT the three ⏸ points:
(1) the PR merge — hand the gh pr merge to the human; (2) the tag push (Step 6) — confirm before
the irreversible publish; (3) the publish confirmation (Step 8) — present the confirmed-publish
report and await the human's acknowledgment before close-out. Everything else (version math,
carrier bumps, notes rewrite, branch/PR, dry-run, verification) proceeds autonomously. Stop
and surface any precondition failure, red check, or version-math ambiguity rather than working
around it.
Gotchas (learned the hard way — all embedded above)
ghactive account silently reverts toAndreVianna-Ross; rungh auth switch --user AndreViannaimmediately before EACH push/PR/tag and restore after.- Classifier BLOCKS
gh pr mergein auto-mode — hand it to the human. - Beta = SemVer
X.Y.Z-beta.N(not PEP 440bN); npm carrier exempt for betas. gh ... --watchexit 0 ≠ all-pass — verify each job.- PyPI
/jsonlags; use/simple/.NM08npm-pack is flaky; rerun. - Never bump/commit
.aid/.aid-manifest.jsonas part of a release (not a gate carrier). - Always dry-run (
-f ref=master -f dry_run=true) before the tag.