| name | release |
| description | This skill should be used when the user asks to "cut a release", "release the guild", "ship it", "create a release", "tag a version", "publish a release", "guild release", or wants to finalize completed requirements into a versioned release. Renames CHANGELOG Unreleased to a version, archives completed REQs, and creates an annotated git tag. Does not push.
|
| version | 2.0.0 |
| user-invocable | true |
Guild Release — Archive and Tag a Version
Finalize completed guild requirements into a versioned release: stamp the CHANGELOG.md Unreleased section with a version, move completed requirement artifacts into a dated archive, and create an annotated git tag.
Status is encoded by the subdirectory an artifact lives in — there is no status: frontmatter field. All board lookups and moves go through the guild CLI. Bind it once and reuse:
GUILD="${CLAUDE_PLUGIN_ROOT}/scripts/guild"
Arguments
--dry-run — print the full plan without making any changes
--only REQ-NNN[,REQ-MMM] — release only the named requirements (default: all requirements in .guild/requirements/done/ since last release)
Steps
1. Preconditions
Run in parallel:
git rev-parse --is-inside-work-tree — confirm git repo
git status --short — check for uncommitted changes
git tag --list --sort=-v:refname — list existing tags
- Read
.guild/state.yaml — confirm the guild exists
Stop conditions:
2. Determine Scope
Find requirements to include:
- If
--only REQ-NNN,... provided: use exactly those.
- Otherwise: list the done requirements with
"$GUILD" list req done — include every requirement in .guild/requirements/done/ that is NOT already present in any .guild/archive/*/requirements/ directory.
If the resulting set is empty, stop with:
No completed requirements to release since the last release.
3. Pre-release Gate
A task's status is the subdirectory it lives in (tasks/{todo,in-progress,done,failed}/), not a frontmatter field. guild list task prints <ID> <status> <agent> <requirement>, so a REQ's tasks come from one awk filter:
"$GUILD" list task | awk '$4=="REQ-NNN"'
For each requirement in scope:
-
Find all its tasks with the awk filter above.
-
Warn (do not block) if ANY task for an included requirement is:
- in
.guild/tasks/failed/ — these are user-waived (the user chose "skip" when the task
failed; the waiver is noted in the ticket's Work Log). List them and ask:
These tasks for included requirements were waived (failed, user chose not to retry):
TASK-NNN: {title}
The release will note them. Continue? (yes / no)
- in
.guild/tasks/in-progress/ or .guild/tasks/todo/ → list them and ask:
These tasks for included requirements are not yet done:
TASK-NNN: {title} ({status})
They will remain on the board after release. Continue? (yes / no)
4. Prompt for Version
Read existing tags and suggest the next version:
- Find the latest semver tag (
vX.Y.Z). If none, suggest v0.1.0.
- Display:
Current version: {latest tag or "none"}
Requirements in this release: {N}
REQ-NNN: {title}
REQ-MMM: {title}
What version? (e.g. v1.2.0)
- Validate the user's input matches
v\d+\.\d+\.\d+(-[A-Za-z0-9.]+)?. Re-prompt on invalid.
- Reject the version if a tag with that name already exists.
5. Prepare CHANGELOG.md
The changelog lives at repo root (CHANGELOG.md), NOT inside .guild/.
If CHANGELOG.md does not exist, create it with this skeleton:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
If CHANGELOG.md exists but has no ## [Unreleased] section, insert one immediately after the preamble (before the first ## [version] heading).
If the ## [Unreleased] section is empty (no bullet points) AND --only was not used, warn:
The [Unreleased] section is empty. Release anyway? (yes / no)
6. Build the Release Section
Transform CHANGELOG.md:
-
Capture all content under ## [Unreleased] up to the next ## heading or EOF — call this UNRELEASED_BODY.
-
If --only was used, filter UNRELEASED_BODY to keep only bullet points referencing the included REQ IDs; move the rest back under ## [Unreleased].
-
For each requirement in scope that is NOT already in UNRELEASED_BODY, append a bullet:
- REQ-NNN: {requirement title}
(This catches requirements completed before the check-in skill started maintaining [Unreleased].)
-
Replace the ## [Unreleased] heading block with:
## [Unreleased]
## [{version}] - {today's date}
{UNRELEASED_BODY}
The new ## [Unreleased] section is deliberately empty — ready for future work.
7. Archive Requirements
Create archive directory .guild/archive/{version}/ with subdirectories requirements/, plans/, tasks/, reviews/.
File moves come FROM the status subdirectories. Use "$GUILD" path <ID> to locate an artifact wherever it currently lives rather than hardcoding a status dir.
For each requirement in scope:
- Move the done requirement → archive:
.guild/requirements/done/REQ-NNN.md → .guild/archive/{version}/requirements/REQ-NNN.md
- For each plan with matching
requirement: REQ-NNN in its frontmatter (glob .guild/plans/*/PLAN-*.md and read frontmatter, or resolve with "$GUILD" path PLAN-NNN):
- Move
.guild/plans/<status>/PLAN-NNN.md → .guild/archive/{version}/plans/PLAN-NNN.md
- Move the sibling slice directory
.guild/plans/<status>/PLAN-NNN/ (if it exists) → .guild/archive/{version}/plans/PLAN-NNN/ — the slice dir sits beside the overview in the same status dir.
- For each done task with matching
requirement: REQ-NNN (glob .guild/tasks/done/TASK-*.md and read frontmatter):
- Move
.guild/tasks/done/TASK-NNN.md → .guild/archive/{version}/tasks/TASK-NNN.md
- If
.guild/reviews/REQ-NNN.md exists:
- Move it →
.guild/archive/{version}/reviews/REQ-NNN.md
Leave any todo / in-progress / failed tasks in place (their files stay in .guild/tasks/<status>/).
Never archive .guild/docs/ — the knowledge base is evergreen. Researcher findings persist across releases so future architects and researchers can reuse them. Docs are not versioned alongside releases.
Never archive .guild/qa/ — the QA discipline's charter, ledger, regression
manifest, sessions, and missions are evergreen and span releases. The standing
"Product QA & E2E Regression" umbrella requirement stays in-progress and is
never included in a release.
8. Snapshot Board State
There are no ID counters in the new model — IDs are derived from the filesystem (max(existing across all status dirs + archive) + 1) and stay continuous across releases because archived files keep their IDs. state.yaml holds only last-checkin; do NOT touch it here.
Write .guild/archive/{version}/STATE-snapshot.md recording the release and the requirements it covers. Optionally capture the highest ID per kind at release time as "$GUILD" next-id <kind> minus 1 (these are derived, not stored counters); omit them if you prefer:
---
released: {today's date}
version: {version}
highest-req-at-release: {next-id req - 1}
highest-task-at-release: {next-id task - 1}
highest-plan-at-release: {next-id plan - 1}
requirements:
- REQ-NNN: {title}
- REQ-MMM: {title}
---
# Release {version} Snapshot
Requirements included in this release are archived alongside this file.
IDs are derived from the filesystem (status dirs + archive) and remain
continuous across releases — there are no counters to reset.
9. No Board Table to Update
There is no BOARD.md. Once a released requirement's REQ file is moved into the archive (step 7), it
no longer appears in the live requirements view (guild board scans the status dirs) — nothing else
to update. Leave the completed task files for that requirement archived in step 7; any unfinished
tasks stay in .guild/tasks/<status>/.
10. Create Git Tag
Run:
git add CHANGELOG.md .guild/
git commit -m "$(cat <<'EOF'
chore(release): {version}
Release {N} requirement(s):
{one bullet per REQ}
EOF
)"
git tag -a {version} -m "$(cat <<'EOF'
Release {version}
{UNRELEASED_BODY content}
EOF
)"
Do NOT push. Do NOT pass --no-verify.
If pre-commit hooks fail, surface the error and stop. The archive moves have already happened on disk — instruct the user to resolve the hook issue and commit manually.
11. Report Result
Released {version}
==================
Changelog: CHANGELOG.md (new [{version}] section added)
Archived: .guild/archive/{version}/
{N} requirement(s)
{N} plan(s)
{N} task(s)
Git:
Commit {short-hash}: chore(release): {version}
Tag: {version}
Not pushed. Push with:
git push && git push --tags
Dry-run Mode
If --dry-run is set, execute steps 1–6 to build the plan, then print:
Dry run — no changes would be made.
Version: {version}
Requirements to release ({N}):
REQ-NNN: {title}
REQ-MMM: {title}
CHANGELOG.md changes:
## [Unreleased] → ## [{version}] - {today's date}
New [Unreleased] section would be created empty
Files to move:
.guild/requirements/done/REQ-NNN.md → .guild/archive/{version}/requirements/REQ-NNN.md
...
Git actions:
Commit: chore(release): {version}
Tag: {version} (annotated)
Push: NOT executed
Warnings:
{any warnings from the pre-release gate}
Do not create files, move anything, or run any git commands.
Rules
- Never push — the user decides when to push commits and tags
- Never skip hooks — do not pass
--no-verify
- IDs are derived, not counters — there is nothing to reset; archived files keep their IDs so the sequence stays continuous
- Never delete files — archive (move) only
- CHANGELOG.md lives at repo root — not inside
.guild/
.guild/docs/ is evergreen — never archive or touch the knowledge base during a release
.guild/reviews/REQ-NNN.md archives alongside its requirement — unlike docs/, review
reports are per-requirement history, not cross-cutting knowledge
- Pre-release gate only warns, never blocks — user-waived (
failed/) and not-yet-done tasks
warn and ask; there's no automatic-fix-loop escalation token to block on anymore
- In-progress tasks stay on the board — they are not included in the archive
- One commit, one tag — both are created atomically at step 10