Skip to content
Back to skills

Release

ASecurity

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, records the released requirements on the board, and creates an annotated git tag. Does not push.

  • 5 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 2, 2026
ai-agentsbashsqlgit

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add HirogaKatageri/hirokata --skill release --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Release?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Release
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hirogakatageri-release/badge)](https://www.skillsdirectory.com/skills/hirogakatageri-release)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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, records the released
  requirements on the board, and creates an annotated git tag. Does not push.
version: 5.0.0
user-invocable: true
allowed-tools: Bash(tursodb *), Bash(git *)
---

# Guild Release — stamp the changelog and tag a version

Finalize completed guild requirements into a versioned release: stamp `CHANGELOG.md`'s
Unreleased section with a version, record the release on the board, and create an annotated
git tag.

Status is a **column**. There are no status directories, no ticket files, and no per-requirement
snapshot — a release copies nothing and moves nothing; the board and `CHANGELOG.md` are the whole
record. Load `guild:warehouse` before the first query.

```bash
export PATH="$HOME/.turso:$PATH"
DB=.guild/guild.db
```

## Arguments

- `--dry-run` — print the full plan, write nothing, run no git command
- `--only REQ-NNN[,REQ-MMM]` — release only the named requirements (default: every `done`
  requirement not already recorded in a previous release)

## Step 1 — preconditions

Run in parallel:
- `git rev-parse --is-inside-work-tree` — confirm a repo
- `git status --short` — uncommitted changes
- `git tag --list --sort=-v:refname` — existing tags
- `[ -f .guild/config.yaml ]` — confirm the guild exists

Stop conditions:
- Not a git repo → `Not inside a git repository. Guild release requires git.`
- No `.guild/config.yaml` → `No guild found. Nothing to release.`
- Uncommitted changes → ask, and default to stopping on "no":
  ```
  You have uncommitted changes. A release should be a clean point in history.
  Continue anyway? (yes / no)
  ```

## Step 2 — what is in scope

A release is **recorded on the board**, in `guild_state` under `release:<version>`, so "what has
already shipped" is a query rather than a directory scan:

```sql
SELECT r.id, r.title
  FROM requirement r
 WHERE r.status = 'done'
   AND NOT EXISTS (SELECT 1 FROM guild_state gs, json_each(gs.value, '$.requirements') j
                    WHERE gs.key LIKE 'release:%' AND json_valid(gs.value) AND j.value = r.id)
 ORDER BY r.id;
```

With `--only`, use exactly the named ids instead — and verify each is `done` before including it.

Empty set → stop with `No completed requirements to release since the last release.`

**Exclude the standing QA umbrella.** It is `in-progress` forever by design, so the query above
already skips it; do not add it by hand under `--only`.

## Step 3 — the pre-release gate

For every requirement in scope, one query answers the whole gate:

```sql
SELECT id, status, tasks_total, tasks_done, tasks_open, tasks_blocked, tasks_failed,
       replace(replace(title, char(10), ' '), '|', '!') AS title
  FROM v_requirement_progress
 WHERE id IN ('REQ-007','REQ-008');
```

**Warn, never block.** Two different warnings, and the difference matters:

- `tasks_failed > 0` — a human already ruled on these. Distinguish the waived ones, because a
  waiver is a decision and a bare failure is not:

  ```sql
  SELECT id, waived, replace(replace(COALESCE(reason,'-'), char(10),' '), '|','!') AS reason, title
    FROM v_failed_tasks WHERE requirement_id IN ('REQ-007');
  ```
  ```
  These tasks for included requirements failed:
    TASK-013: {title}  (waived by you)
    TASK-021: {title}  (failed, not waived — {reason})
  The release will note them. Continue? (yes / no)
  ```

- `tasks_open > 0` — still `todo`, `in-progress`, **or `blocked`**. A blocked one is the loud
  case: it means nobody on the roster could take it, so the requirement is shipping work
  nobody ever attempted. Name it as such:

  ```
  These tasks for included requirements are not done:
    TASK-030: {title} (todo)
    TASK-031: {title} (BLOCKED — no eligible agent)
  They stay on the board after release. Continue? (yes / no)
  ```

## Step 4 — the version

1. Find the latest semver tag (`vX.Y.Z`). None → suggest `v0.1.0`.
2. Show current version, the requirements in scope with their titles, and ask `What version?`
3. Validate against `v\d+\.\d+\.\d+(-[A-Za-z0-9.]+)?`. Re-prompt on invalid.
4. Reject a version whose tag already exists, **and** one already present as a
   `release:<version>` key on the board — either means this version was already cut.

## Step 5 — CHANGELOG.md

The changelog lives at **repo root**, not inside `.guild/`.

If it does not exist, create it with the Keep-a-Changelog skeleton and an empty
`## [Unreleased]`. If it exists without an `## [Unreleased]`, insert one after the preamble,
before the first `## [version]` heading. If `[Unreleased]` is empty and `--only` was not used,
warn: `The [Unreleased] section is empty. Release anyway? (yes / no)`

Then transform it:

1. Capture everything under `## [Unreleased]` up to the next `## ` heading or EOF —
   `UNRELEASED_BODY`.
2. With `--only`, keep only the bullets referencing the included REQ ids; move the rest back
   under `## [Unreleased]`.
3. For each in-scope requirement not already mentioned, append `- REQ-NNN: {title}`. Flatten the
   title first (`replace(replace(title, char(10),' '), '|','!')`) — **a newline inside a title
   would forge a changelog bullet, or worse, a heading.**
4. Replace the heading block with a fresh empty `## [Unreleased]`, then
   `## [{version}] - {today}` carrying `UNRELEASED_BODY`.

## Step 6 — record the release on the board

This is what makes Step 2 a query next time, and it is the only board write a release makes:

```sql
PRAGMA foreign_keys = ON;
UPDATE guild_state SET value = 'orchestrator' WHERE key = 'actor';

INSERT INTO guild_state (key, value)
VALUES ('release:v1.2.0',
        json_object('released', date('now'),
                    'requirements', json_array('REQ-007','REQ-008')))
ON CONFLICT(key) DO UPDATE SET value = excluded.value
RETURNING key, value;
```

**A release never changes a requirement's status and never deletes a row.** `done` requirements
cost nothing on the board — `v_next_task` only ever looks at open tasks — and deleting them would
orphan every work log, finding and event that explains how the release was built. Nor is there
anywhere to hand that job off to: **nothing in the guild deletes records** (G11), so a board that
reads as crowded is one to narrow with `guild:brief` and priorities, not one to empty.

## Step 7 — commit and tag

```bash
git add CHANGELOG.md
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 board row is already recorded, but
nothing was moved or deleted — re-running the release after fixing the hook is safe, because
Step 6's write is an upsert and Step 2 will then find the scope already released and say so.

## Step 8 — report

```
Released {version}
==================

Changelog: CHANGELOG.md (new [{version}] section)
Board:     guild_state['release:{version}'] records what shipped
  {N} requirement(s) — plans, tasks, work logs, findings and bugs stay on the board

  Released requirements stay on the live board as `done`. Nothing was deleted.

Git:
  Commit {short-hash}: chore(release): {version}
  Tag: {version}

Not pushed. Push with:
  git push && git push --tags
```

## Step 9 — verify against §9

Run `guild:validate release`. §9 of `docs/expectations.md` follows from one sentence — a
release *records*, it does not retire: §9.a fingerprints the board before and after and the
**only** difference permitted is the one `guild_state` row, §9.b that the release record is
well-formed and no requirement was released twice or released while not done. **Report every
failure with its rows.** Under `--dry-run`, §9.a's diff must be empty.

## Dry-run mode

With `--dry-run`, run steps 1–5 to build the plan and print it: the version, the requirements in
scope with their titles, the CHANGELOG transformation, and the git actions that would run — plus
every warning from the pre-release gate. **Write no file, run no git command, and make no board
write.** The step-2, step-3 and step-4 queries are all reads and are safe to run; step 6's upsert
is not, and must not run.

## Rules

- **Never push** — the user decides when to publish a tag.
- **Never skip hooks** — no `--no-verify`.
- **Never delete or restatus a released requirement.** A release records; it does not retire.
- **`-m list`, always.** The default `pretty` output mode truncates long values with an ellipsis,
  and a truncated title is a lie that outlives the release.
- **Flatten free text before it becomes structure.** A newline in a title forges a changelog
  bullet or a heading; `replace(replace(x, char(10),' '), '|','!')` in SQL, before it leaves the
  engine.
- **Check every query's exit code.** Errors arrive on stdout and would otherwise be read as
  content.
- **IDs are derived, never counters** — nothing is reset at a release.
- **CHANGELOG.md lives at repo root**, not inside `.guild/`.
- **`doc`, `doc_revision`, `knowledge_edge`, `coverage`, `.guild/docs/` and `.guild/qa/` are
  evergreen** — a release never touches them. Plans, tasks, work logs, findings and bugs stay on
  the board; a release names requirements and decision slugs, it does not copy their contents.
- **One commit, one tag**, both created in step 7.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…