/release
The single permitted path to a version tag, for any target declared in the project's .codearbiter/release-targets.md. Routed to when the user invokes /release on a non-default branch with a green suite. Takes the declared target as its one argument, derives the SemVer bump from
$ npx -y skills add arbiterForge/codeArbiter --skill release --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/release
Context preview
The summary Claude sees to decide when to auto-load this skill.
The single permitted path to a version tag, for any target declared in the project's .codearbiter/release-targets.md. Routed to when the user invokes /release on a non-default branch with a green suite. Takes the declared target as its one argument, derives the SemVer bump from
SKILL.md
release.SKILL.mdname: release
description: The single permitted path to a version tag, for any target declared in the project's .codearbiter/release-targets.md. Routed to when the user invokes /release on a non-default branch with a green suite. Takes the declared target as its one argument, derives the SemVer bump from Conventional-Commits history since that target's last tag, rolls the commits into that target's CHANGELOG, writes an annotated tag in that target's namespace, and on authorization publishes it as a GitHub Release with the changelog section as its notes. A release commit, if needed, routes through commit-gate; the tag and Release are never published without explicit authorization.
release
The single permitted path to a version tag. Routed to when the user invokes `/release [target]`. Derive the bump from the commit log, update the changelog, tag — nothing more.
**One command, any number of declared targets.** A project declares one or more release targets in `{{PROJECT_DIR}}/.codearbiter/release-targets.md` (grammar and parser contract: `{{PLUGIN_ROOT}}/hooks/_releaselib.py`'s module docstring). `/release` takes the target's name as its only argument. When `$TARGET` is omitted and the declared file names exactly one target, that target is used — a single-target project's bare `/release` behaves exactly as it always has. When more than one target is declared, `$TARGET` is required; STOP and ask rather than guessing which one a bare invocation meant. **Resolve the omitted-single-target case mechanically, never by assumption** (MEDIUM, adversarial review 2026-07-31: `tag-prefix` itself takes `$TARGET` as a REQUIRED positional argument and has no way to express "the implicit one", so naming it here was not itself enough — the mechanical step that turns an omitted target into a concrete name before `tag-prefix` is ever called has to be spelled out too): run `"$PY" "{{PLUGIN_ROOT}}/hooks/_releaselib.py" list-targets` first — the sanctioned enumeration, through the same tested grammar `tag-prefix` already reads, rather than a by-eye scan of the delimiter block. Exactly one printed line confirms which name `$TARGET` is; more than one is the multi-target STOP above, restated by the tool rather than assumed. There is deliberately no second command per target: N commands would be N public surfaces to govern, catalog, and carry, for one operation whose only difference is which declared row it reads.
Every phase below is written once, against that row. Nothing in this skill is per-target prose.
**Interpreter convention, stated once and applying to every helper invocation in this file** (A-3.6). `python3` is not universally present — a Windows consumer commonly has `python` on PATH and no `python3` at all, and a literal `python3` spelling fails on every invocation at once there.
**Resolve the interpreter ONCE, by presence, before the first invocation:**
PY=python3; command -v python3 >/dev/null 2>&1 || PY=python
Every helper invocation below is then spelled `"$PY" "{{PLUGIN_ROOT}}/hooks/<script>" <args>` literally, one spelling throughout — including the inline `"$PY" -c "…"` snippets. Two spellings for one thing invites reading the difference as meaningful (blind exercise run 14 flagged exactly that when only three steps used `"$PY"` and fifteen still said `python3`).
**Both quotes are load-bearing, and the second one is the easier to lose** (HIGH-1, blind exercise run 16). Quoting only the interpreter — `"$PY" {{PLUGIN_ROOT}}/hooks/<script>` — leaves the script path exposed to word splitting, and a plugin root containing a space is an ordinary Windows install (`C:\Users\First Last\.claude\plugins\…`, since an account name with a space is unremarkable). On such a host the path splits at the space, Python is handed a truncated filename, and EVERY step of this lane fails at once: target resolution, `last-tag`, `classify-window`, `check-manifests`, `classify`, `notes-match`. The operator's only diagnostic is `can't open file '…\First'`, which names nothing recognisable. The same applies to any `{{PROJECT_DIR}}`-rooted path passed as an argument. The one deliberate exception is `$PAYLOAD`, which is a git pathspec that MUST word-split — see its own note under "Targets".
**MUST NOT spell them `python3 "<script>" … || python "<script>" …`.** `||` branches on the EXIT CODE, and it cannot distinguish "no such interpreter" from "the helper ran and told you something". This lane's helpers answer in exit codes by design — `run-pre-tag` returns 5 for drift and 6 for a mutating check, `semver-greater` and `check-manifests` each separate "no" from "could not compare" — so the `||` form re-runs the whole command on every one of those answers and then reports the SECOND run's code. For `run-pre-tag` that means executing the project's declared pre-tag commands twice and losing the verdict of the first. The fallback must key on whether the interpreter EXISTS, which is what `command -v` tests, not on what it said.
Targets
Resolve `$TARGET`'s row from the declared file FIRST and use it throughout — never a hardcoded table. An unparseable declared file (any parser-contract violation on a file that DOES exist — including one that exists but carries no delimiter block at all, `FileExistsNoBlockError`) → STOP and surface the parse error; never guess a row's shape, and never treat an existing-but-broken file as an opportunity to back-fill it (see "Back-fill" below for why that distinction is mechanical, not a judgment call). A genuinely ABSENT declared file — nothing on disk at all, the one state `AbsentBlockError` alone names — enters the "Back-fill" lane below instead of stopping outright; that lane never runs against a file that already exists, in any state. Resolve `$TAG_PREFIX` through the shared mechanism, never typed from memory: `TAG_PREFIX=$("$PY" "{{PLUGIN_ROOT}}/hooks/_releaselib.py" tag-prefix $TARGET)`. Where a hosted publish lane's own namespace resolution is ALSO wired to read
Read more
name: release description: The single permitted path to a version tag, for any target declared in the project's .codearbiter/release-targets.md. Routed to when the user invokes /release on a non-default branch with a green suite. Takes the declared target as its one argument, derives the SemVer bump from Conventional-Commits history since that target's last tag, rolls the commits into that target's CHANGELOG, writes an annotated tag in that target's namespace, and on authorization publishes it as a GitHub Release with the changelog section as its notes. A release commit, if needed, routes through commit-gate; the tag and Release are never published without explicit authorization.
release
The single permitted path to a version tag. Routed to when the user invokes `/release [target]`. Derive the bump from the commit log, update the changelog, tag — nothing more.
**One command, any number of declared targets.** A project declares one or more release targets in `{{PROJECT_DIR}}/.codearbiter/release-targets.md` (grammar and parser contract: `{{PLUGIN_ROOT}}/hooks/_releaselib.py`'s module docstring). `/release` takes the target's name as its only argument. When `$TARGET` is omitted and the declared file names exactly one target, that target is used — a single-target project's bare `/release` behaves exactly as it always has. When more than one target is declared, `$TARGET` is required; STOP and ask rather than guessing which one a bare invocation meant. **Resolve the omitted-single-target case mechanically, never by assumption** (MEDIUM, adversarial review 2026-07-31: `tag-prefix` itself takes `$TARGET` as a REQUIRED positional argument and has no way to express "the implicit one", so naming it here was not itself enough — the mechanical step that turns an omitted target into a concrete name before `tag-prefix` is ever called has to be spelled out too): run `"$PY" "{{PLUGIN_ROOT}}/hooks/_releaselib.py" list-targets` first — the sanctioned enumeration, through the same tested grammar `tag-prefix` already reads, rather than a by-eye scan of the delimiter block. Exactly one printed line confirms which name `$TARGET` is; more than one is the multi-target STOP above, restated by the tool rather than assumed. There is deliberately no second command per target: N commands would be N public surfaces to govern, catalog, and carry, for one operation whose only difference is which declared row it reads.
Every phase below is written once, against that row. Nothing in this skill is per-target prose.
**Interpreter convention, stated once and applying to every helper invocation in this file** (A-3.6). `python3` is not universally present — a Windows consumer commonly has `python` on PATH and no `python3` at all, and a literal `python3` spelling fails on every invocation at once there.
**Resolve the interpreter ONCE, by presence, before the first invocation:**
PY=python3; command -v python3 >/dev/null 2>&1 || PY=python
Every helper invocation below is then spelled `"$PY" "{{PLUGIN_ROOT}}/hooks/<script>" <args>` literally, one spelling throughout — including the inline `"$PY" -c "…"` snippets. Two spellings for one thing invites reading the difference as meaningful (blind exercise run 14 flagged exactly that when only three steps used `"$PY"` and fifteen still said `python3`).
**Both quotes are load-bearing, and the second one is the easier to lose** (HIGH-1, blind exercise run 16). Quoting only the interpreter — `"$PY" {{PLUGIN_ROOT}}/hooks/<script>` — leaves the script path exposed to word splitting, and a plugin root containing a space is an ordinary Windows install (`C:\Users\First Last\.claude\plugins\…`, since an account name with a space is unremarkable). On such a host the path splits at the space, Python is handed a truncated filename, and EVERY step of this lane fails at once: target resolution, `last-tag`, `classify-window`, `check-manifests`, `classify`, `notes-match`. The operator's only diagnostic is `can't open file '…\First'`, which names nothing recognisable. The same applies to any `{{PROJECT_DIR}}`-rooted path passed as an argument. The one deliberate exception is `$PAYLOAD`, which is a git pathspec that MUST word-split — see its own note under "Targets".
**MUST NOT spell them `python3 "<script>" … || python "<script>" …`.** `||` branches on the EXIT CODE, and it cannot distinguish "no such interpreter" from "the helper ran and told you something". This lane's helpers answer in exit codes by design — `run-pre-tag` returns 5 for drift and 6 for a mutating check, `semver-greater` and `check-manifests` each separate "no" from "could not compare" — so the `||` form re-runs the whole command on every one of those answers and then reports the SECOND run's code. For `run-pre-tag` that means executing the project's declared pre-tag commands twice and losing the verdict of the first. The fallback must key on whether the interpreter EXISTS, which is what `command -v` tests, not on what it said.
Targets
Resolve `$TARGET`'s row from the declared file FIRST and use it throughout — never a hardcoded table. An unparseable declared file (any parser-contract violation on a file that DOES exist — including one that exists but carries no delimiter block at all, `FileExistsNoBlockError`) → STOP and surface the parse error; never guess a row's shape, and never treat an existing-but-broken file as an opportunity to back-fill it (see "Back-fill" below for why that distinction is mechanical, not a judgment call). A genuinely ABSENT declared file — nothing on disk at all, the one state `AbsentBlockError` alone names — enters the "Back-fill" lane below instead of stopping outright; that lane never runs against a file that already exists, in any state. Resolve `$TAG_PREFIX` through the shared mechanism, never typed from memory: `TAG_PREFIX=$("$PY" "{{PLUGIN_ROOT}}/hooks/_releaselib.py" tag-prefix $TARGET)`. Where a hosted publish lane's own namespace resolution is ALSO wired to read
Showing the first part of this file.
When you can't trust yourself with your code base, trust Arbiter.
Repo: arbiterForge/codeArbiter
Other skills on codearbiter.
- /brainstorming
The Socratic spec-refinement front of /feature, and the planning front of /sprint. Routed to BEFORE any code — it takes a one-line idea and drives it to an approved, concrete spec with testable acceptance criteria. Four gated phases — frame, refine, write, approve. No
Open skill - /commit-gate
The only path to a commit. Routed to when the user invokes /commit or otherwise instructs codeArbiter to persist staged changes. Nine gated phases — permission, branch, classification, verification (test/lint/secrets), behavioral proof, diff review, selective stage, message,
Open skill - /context-check
Optional manual drift audit — report stale provenance-tracked docs (via _provenancelib drift detection across .codearbiter/.provenance/), then per stale doc offer re-scout / re-baseline / defer. Not the daily loop; commit-gate auto-heal owns routine maintenance.
Open skill - /context-creation
The brownfield back-fill. Routed to by /create-context, and by startup when .codearbiter/CONTEXT.md lacks the <!--INITIALIZED--> body marker but source code exists. Six gated phases — pre-flight, scout dispatch, synthesis, gap interview, write, lock. Reads the existing codebase
Open skill - /crypto-compliance
The banned-primitive gate. Routed to when changed code hashes, signs, encrypts, derives keys, generates security-relevant randomness, configures TLS, or imports a crypto library. Rejects broken primitives, disabled TLS verification, and home-rolled crypto; the approved-primitive
Open skill - /debug
Investigate-then-decide root-cause analysis for a defect whose cause is unknown (distinct from /fix, which assumes a known bug). Five gated phases: capture, hypothesize, gather, decide, hand off. Investigation only, no code changes; exits to /fix, /adr, or a no-action close.
Open skill

