/release
Synced from
factory-kit/commands/release.mdat v0.1.2. The source of truth is the factory-kit repo.
You’re cutting a new release. The user owns the final word at every gate, and edits the release notes directly in their editor (Cursor) — not by prompting you to make changes.
Argument: $ARGUMENTS — patch, minor, or major. If empty, ask via AskUserQuestion.
What to do
Section titled “What to do”-
Pre-flight. Run in parallel:
git status --porcelain— must be clean. If dirty, list the files and ask whether to abort or stash.git rev-parse --abbrev-ref HEAD— confirm we’re on the default branch (typicallymain). If not, warn and ask before proceeding.- Read
VERSIONat repo root if present. If absent, fall back togit describe --tags --abbrev=0(strip leadingv). If neither exists, treat current as0.0.0.
-
Compute the new version:
patch:X.Y.Z → X.Y.(Z+1)minor:X.Y.Z → X.(Y+1).0major:X.Y.Z → (X+1).0.0
-
Check for tag collision.
git rev-parse v<new-version> 2>/dev/null— if it resolves, fail with a clear message and stop. Don’t overwrite an existing tag. -
Gather commits since last tag.
- Previous tag:
git describe --tags --abbrev=0. If no tags exist, use the initial commit. - Commit list:
git log <prevTag>..HEAD --pretty=format:"%h %s". - Group by Conventional Commits type:
feat,fix,refactor,chore,docs,test. Anything that doesn’t fit the format goes under**other:**with a flag for the user to rewrite. - Identify the dominant theme — largest group, weighted toward user-facing types (
feat>fix>refactor>chore). - Extract any Linear IDs (
<TEAM>-<NUM>) referenced in commit subjects or bodies.
- Previous tag:
-
Write the draft to a file the user can actually edit. Use the
Writetool to create/tmp/factory-kit-release-notes-v<new-version>.mdwith this content (auto-fill what you can, leave clear markers where the user needs to refine):**Outcome:** v<new-version> — <one-line summary of the dominant change>**Why:** <best-guess from dominant commit type — REFINE THIS>**Changes:****feat:**- <bulleted list>**fix:**- <bulleted list>**chore:**- <bulleted list><omit any empty group entirely>**Refs:** <Linear IDs from commits — omit line if none> -
Open it in Cursor. Run
cursor /tmp/factory-kit-release-notes-v<new-version>.mdvia Bash. Ifcursorisn’t on PATH, fall back to$EDITORor tell the user the path and ask them to open it manually. -
Gate 1 — wait for the user. Print:
Draft is open in Cursor:
/tmp/factory-kit-release-notes-v<new-version>.md. Edit anything you want —Why,Outcome, the bullets, all of it. Save the file and replydone(orcancelto abort).Wait for the user’s reply. On
cancel, delete the temp file and stop. -
Read back what they saved. Use the
Readtool on the temp file. Print a brief diff summary (or just the final content if the diff would be longer than the file). Extract the Outcome line’s summary fragment (the text after—) to use as the commit subject. Ask one final confirmation:Final notes shown above. Confirm to write the commit + tag, or reply with another round of edits and I’ll wait again.
-
Gate 2 — write. On confirmation:
- Write the new version to
VERSION(only if the file existed before). git add VERSION(only if applicable).git commit -m "release: v<new-version> — <outcome-summary>"— the summary comes from the Outcome line in the notes. Keep the full header ≤ 72 chars (truncate the summary if needed, full text is in the tag annotation).git tag -a v<new-version> -F /tmp/factory-kit-release-notes-v<new-version>.md— pass the file directly so multi-line formatting is preserved.
- Write the new version to
-
Gate 3 — push? Show local state (
git log -1 --oneline,git tag --list 'v*' | tail -3) and ask explicitly whether to push. Don’t pre-authorize. On yes:git push origin HEADgit push origin v<new-version>
-
Gate 4 — publish GitHub Release? Only runs if Gate 3 was approved (no tag on origin means nothing to release against). Pre-checks:
git remote get-url origin | grep -q github.com— if origin isn’t GitHub, skip this gate entirely (don’t ask).command -v gh— ifghisn’t on PATH, print the manual one-liner and the Releases URL, then skip. Don’t fail the run.
Otherwise ask explicitly:
Publish a GitHub Release for v
? Notes come from the tag annotation. (y/n) On yes, run:
gh release create v<new-version> \--title "v<new-version> — <outcome-summary>" \--notes-file <(git tag -l v<new-version> --format='%(contents)')<outcome-summary>is the same fragment used for the commit subject (step 8). Print the returned Release URL.Rationale: tag and GitHub Release should be 1:1. Tags are the source of truth for “what shipped”; the Release page is the discoverable changelog and the feed that Renovate/Dependabot watch. Skipping it for patches creates “did this ship?” gaps on the Releases page.
-
Clean up and confirm. Delete
/tmp/factory-kit-release-notes-v<new-version>.md. Print:Released v
. Tag pushed (if approved). GitHub Release published (if approved). Run git show v<new-version>to verify the annotation.
Follow factory-voice.md. Four explicit gates — notes (edited in Cursor), write (commit + tag), push, publish GitHub Release — none batched, none skipped. Gate 4 auto-skips when origin isn’t GitHub or gh isn’t installed; it never asks a question the environment can’t answer. The auto-grouped Conventional Commits list IS the changelog; don’t editorialize it. Architect judgment goes on the Outcome and Why lines, which the user will rewrite in their editor. If a commit was pushed with --no-verify and doesn’t fit the conventional format, surface it under **other:** so the user can decide how to characterize it.
Related
Section titled “Related”factory-commits.md— the Conventional Commits format this command parsesfactory-voice.md— the shape used for release notes