| name | release |
| description | Log a release from a repo's changelog and optionally finalize the included tickets across backends |
| risk_class | hard |
| confirm_action | release.finalize |
Release
Log a release from a repository's changelog, write a release section to the
repo's wiki page, and offer to finalize (close) the included tickets across
every backend the workspace is connected to.
When to invoke
Trigger on any of:
/release <repo-name> — latest version from the changelog
/release <repo-name> <version> — a specific version
- "log the release of "
- " just shipped — log it"
Arguments
$ARGUMENTS — the repo name (matches wiki/repos/<name>.md) and an
optional version. Examples: storefront-web, storefront-web 1.38.0.
Behavior
Step 1 — Resolve the repo page
Parse the argument into <repo-name> and optional <version>. Read
wiki/repos/<repo-name>.md:
rubber-ducky frontmatter get wiki/repos/<repo-name>.md --json
If the file doesn't exist, offer to create it. Ask one question at a time
with progress markers:
Question 1 of 2. What's the upstream repo in owner/repo form?
(e.g., acme/storefront-web.)
Question 2 of 2. Where does this repo store its changelog? (Default:
CHANGELOG.md on the default branch. Say "default" to accept.)
Then create the page:
rubber-ducky page create repo "<repo-name>" --repo <owner/repo> --changelog-path <path>
Re-read the resulting frontmatter and proceed.
Step 2 — Check the release-reference pattern
The skill needs to know how ticket references appear in the changelog so it
can extract them. Look at the repo page's release_reference_pattern field.
- Set → use as-is (it's a regex string).
- Unset → infer from the workspace's primary backend:
-
Run rubber-ducky workspace intake show --json and look at
primary_backend.
-
Fall back further to rubber-ducky backend list --json if intake is
unset; if there's a single Jira / GitHub backend, use its
ref_pattern.
-
If multiple backends remain plausible, ask once:
Question 1 of 1. What pattern should I use to extract ticket
references from this repo's changelog? Examples: WEB-\d+ (Jira-style),
acme/storefront-web#\d+ (GitHub-style). Say "skip" to skip
extraction and just log the version + notes.
Persist the answer to the repo page's frontmatter so future runs are silent:
rubber-ducky frontmatter set wiki/repos/<repo-name>.md release_reference_pattern "<pattern>"
Step 3 — Fetch the changelog
/release depends on gh independently of the workspace's backend setup —
the changelog lives on GitHub regardless of whether the workspace's
intake is GitHub, Asana, or Jira. Before pulling, verify gh is
installed and authenticated:
gh auth status
If gh is missing or unauthenticated, ask the user to install it and run
gh auth login, then stop. Don't fall back to a raw fetch — the user
might have a private repo.
Then pull the changelog:
gh api repos/<owner>/<repo>/contents/<changelog-path>?ref=<default_branch> --jq '.content' | base64 -d
Step 4 — Find the version entry
Parse the changelog into version entries. The expected shape — common across
keepachangelog, Changesets, and most hand-rolled formats — is:
## X.Y.Z (YYYY-MM-DD) # or "## X.Y.Z - YYYY-MM-DD"
<section markers>
<body>
- If
$ARGUMENTS named a version, find that entry; if absent, error and
stop.
- If no version was named, use the most recent entry (top of the file
after the first
# Changelog heading, or just the first ## block).
- If the format doesn't match, show the user the first ~200 lines of the
changelog and ask them to point at the right block.
Step 5 — Extract ticket references
Apply release_reference_pattern (or whatever was decided in Step 2) as a
regex against the version entry's body. Dedupe matches preserving order.
For each match, locate the corresponding wiki task page:
rubber-ducky wiki search "<ref>" --json
(Or fall back to a grep on wiki/tasks/ looking at the ref, jira_ref,
asana_ref, and gh_ref frontmatter fields.) For each ticket, capture:
- Wiki page path (if found) — for the wikilink
- Title — from the page or the changelog body if no page exists
- Backend refs in frontmatter — to know which backends to finalize on later
- A one-line description pulled from the nearest body bullet in the changelog
References with no wiki page get listed but flagged with ⚠ no wiki page.
Continue — they're real shipments even if the user didn't ingest them.
Step 6 — Check whether the release was already logged
Read the body of the repo page and look for an existing ### <version>
header under ## Releases. If present, ask:
The repo page already has a section for . Overwrite, skip the
write, or rename it? (overwrite / skip / rename)
Don't silently double-write.
Step 7 — Build and preview the release section
### X.Y.Z — YYYY-MM-DD
| Ref | Task | Notes |
| :-------- | :-------------------------------- | :-------------------------- |
| WEB-NNN | [[<wiki page>]] | <one-line from changelog> |
**Notes:**
- <any sub-bullets pulled from the changelog body>
Show it inline and ask:
Write to repo page? (yes / no / edit)
Step 8 — Write the release section
On yes, prepend the new section under ## Releases (newest first) in the
repo page and bump the updated: frontmatter:
rubber-ducky frontmatter set wiki/repos/<repo-name>.md updated "<now-iso>"
(The body edit itself is a direct file write — there's no CLI primitive for
"insert section into body", so use Read + Edit + Write the tail of the file
preserving structure.)
Step 9 — Offer to finalize the included tickets
After the section is written, ask:
This release covered N tickets. Finalize them?
Finalize = close across every backend each ticket is linked to:
- Wiki: status → done, closed = today, activity-log entry
- Linked backends: transition to their "done" / "complete" state
- Single shared closing comment posted to each backend
Options: Finalize all · Finalize some · Skip for now
For each ticket the user chose to finalize, draft a single shared closing
comment once and reuse it across backends:
Shipped in <repo-name> <version>. Release included: <list of refs>.
Show the comment + a batch confirmation table:
| Ref | Wiki | <backend-a> | <backend-b> |
| WEB-289 | → done | → Done | → Complete |
Step 9a — Per-ticket gating via the primitive CLI verbs
The finalize batch is risk_class: hard. There is no single
"finalize all" gate — each ticket's transition and comment is its own
write, and each rides on the primitive CLI verbs (rubber-ducky transition and rubber-ducky comment). That way the batch fails
gracefully per-ticket and every external call passes through the
CLI-enforced gate.
For each ticket the user chose to finalize:
-
Wiki side first — soft write, no token needed:
rubber-ducky task close <wiki-page>
(sets status: done, closed, logs to daily, appends to activity log).
-
For each backend ref present in the task's frontmatter, issue a
confirm-token and invoke rubber-ducky transition (target = done),
then a separate token + rubber-ducky comment for the shared closing
line. Each external call goes through the CLI-enforced gate; nothing
is bundled.
TOKEN_T=$(rubber-ducky --no-json confirm request \
--action <backend>.transition \
--preview "Finalize: $REF → done")
rubber-ducky --json transition <wiki-page> \
--backend <backend> --to done --confirm-token "$TOKEN_T"
TOKEN_C=$(rubber-ducky --no-json confirm request \
--action <backend>.comment \
--preview "Closing comment on $REF")
rubber-ducky --json comment <wiki-page> \
--backend <backend> --text "$CLOSING_COMMENT" --confirm-token "$TOKEN_C"
Set confirm.<backend>.transition and confirm.<backend>.comment
to auto in settings.json ahead of time if the user wants a
batch-style "no prompts" finalize.
If any single backend write fails, log the failure but keep going on
the remaining tickets and report all failures at the end. The wiki
side is already updated by task close; partial-finalize across
backends is better than no-finalize.
Step 10 — Daily and log update
Add a release block to today's ## Work log section (highlighted, not just a
plain bullet):
- **Release: [[<repo-name>]] <version>** — N tickets shipped:
- [[<task1>]] ✓
- [[<task2>]] ✓
For each finalized ticket, also add a line to today's ## Completed today
section with the shipped in <repo> <version> annotation.
Append to log.md:
rubber-ducky log append "[release] <repo-name> <version> — N tickets shipped, M finalized."
Step 11 — Redirect to active task
If all active tasks just got closed, ask the user what to work on next.
Otherwise, read active_task on today's daily page and end with the
standard one-line redirect.
Rules
- Nothing external is written before Step 7 confirmation (release-section
body write) and Step 9 confirmation (finalize batch). The two are
separate — the user can log the release without finalizing tickets.
- One closing comment covers all tickets in the batch. Don't ask the
user to compose a comment per ticket; share the same line.
- Newer releases go first (prepend, not append) under
## Releases so
the page reads top-down by recency.
- Repo page frontmatter is the source of truth for repo config. On
first run, gather and persist (
repo, changelog_path,
release_reference_pattern); on subsequent runs, read and use silently.
- Don't fight changelog format. If the parser can't find the version,
show the user the raw text and let them point. Don't write a guessed
release section.
- References without wiki pages are reported, not skipped. A ref the
user never ingested is a real shipment too; surface it with a
⚠ no wiki page note so they can ingest later if they want.
Output
A confirmation summary: release section written to the repo page, count of
tickets finalized vs. skipped vs. failed, optional one-line redirect.