From jig
Scaffold, accept, index, and link Architectural Decision Records (ADRs). Use when the user says "write an ADR", "record this decision", "resolve [deferred item] with an ADR", "supersede ADR-NNNN", or otherwise wants to capture a hard-to-reverse decision in `docs/decisions/`. Also use when a refinement-todo entry needs to be marked RESOLVED with a link back to the ADR. Do NOT use for ad-hoc design discussion that hasn't crystallized into a decision yet — wait until the choice is firm.
How this skill is triggered — by the user, by Claude, or both
Slash command
/jig:adr-workflowThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Spec 005 created this skill from scratch. The mechanics live in `adr.py`;
Spec 005 created this skill from scratch. The mechanics live in
adr.py; Claude owns the judgment (what the decision actually says).
Codifies the ADR lifecycle that ADR-0001 and ADR-0002 were written by hand to exercise. Five deterministic operations:
new — scaffold docs/decisions/adr-NNNN-<slug>.md from the template, with
auto-numbering and a slug-collision check. The scaffold carries
status: Proposed in frontmatter (from the template) alongside the prose
Proposed (date) line (spec 073-02 / ADR-0026).accept — flip Status from Proposed (YYYY-MM-DD) to
Accepted (YYYY-MM-DD), and stamp the canonical status: Accepted
frontmatter field in the same atomic write (spec 073-02 / ADR-0026 —
frontmatter is the canonical status home; prose and frontmatter cannot
diverge).supersede — append Superseded by [ADR-NNNN](./adr-NNNN-<slug>.md) (date)
to an Accepted ADR's Status block and Supersedes ADR-NNNN to the replacement's
Status block, and stamp the old ADR's status: Superseded frontmatter field
in the same atomic write (the replacement retains status: Accepted). This is
the one edit allowed on an immutable ADR per the Nygard convention. Atomic
write on both files.index — regenerate the ## Index section of docs/decisions/README.md
from the actual ADR files present. Idempotent.resolve-todo — strike through a ### Decision: ... heading in
docs/refinement-todo.md and append **Resolved by:** [ADR-NNNN: ...](...).The script does file mutation deterministically. Claude is responsible for the prose inside the ADR (Context, Options Considered, Recommended Decision, Consequences, Open questions).
Step 0 — confirm the project is scaffolded (spec 066 / ADR-0011).
BEFORE reserving an ADR number or drafting ANY docs/decisions/ structure,
confirm this project is a scaffolded jig project. If it isn't, route — do
not hand-roll directories:
/jig:scaffold-init. It lays down conventions, templates, hooks, the
status board, and the docs/decisions/ tree (with its README).docs/decisions/ layout, but not jig-scaffolded (no
scaffold.json) → tell the user to run /jig:migrate. It adopts the
existing layout into jig structure.You don't have to decide the state yourself: adr.py new (below)
classifies and routes for you (spec 066-01) — a scaffold.json-bearing
project proceeds; a greenfield project is refused naming
/jig:scaffold-init; an adoptable spec-driven project is refused naming
/jig:migrate. The deterministic gate and this human-readable precondition
agree by construction, so don't restate the detection heuristic here —
run the helper and let it route. (Bypass for a deliberate out-of-band flow:
JIG_SCAFFOLD_PRECONDITION=0.)
The anti-pattern this step exists to kill: an auto-triggered
adr-workflow run improvising a loose docs/decisions/ skeleton (folder +
README, or just dropping an adr-NNNN-*.md into a hand-made directory)
because /jig:scaffold-init was skipped. That produces a non-jig layout
that then needs migrating — the ADR-side of the reported failure. When in
doubt, route to setup first; never invent the structure by hand.
python3 "${CLAUDE_PLUGIN_ROOT}/skills/adr-workflow/adr.py" new <slug> \
[--title "<Title>"] [--project-dir DIR] [--no-push | --pr]
Run from the project root (the script looks for ./docs/decisions/,
or use --project-dir DIR to target a different root). The slug is
kebab-case (my-decision). --title is optional — defaults to the
title-cased slug.
Reserve-on-origin/main is the default (slice 028-01). The helper
fetches origin/main, computes the next free NNNN from the
just-fetched view, scaffolds the file, commits as
docs(decisions): reserve adr-NNNN-<slug>, and pushes to
origin/main. If the push is refused by branch protection /
permissions, the helper automatically falls back to a
reserve/adr-NNNN-<slug> branch + gh pr create. This locks the
ADR number team-wide before any drafting begins, killing the
parallel-worktree numbering-collision failure mode that motivated
spec 028.
Works from any branch or worktree (ADR-0015 / spec 051, mirroring
workflow.py new). The helper routes on the current branch: on main
it runs the proven in-place flow (clean tree required); off main — a
feature branch or a linked .claude/worktrees/* worktree — it reserves
via an ephemeral detached worktree at origin/main, never touching
your branch, cwd, or working tree. No need to switch to main (a linked
worktree can't, anyway).
Flags:
--no-push — commit locally only; skip fetch / push entirely. On
main it commits on main; off main it commits a provisional
reservation on the current branch (the number is local-view and may
collide at merge — treat it as provisional). Pathspec-scoped, so
unrelated staged work is not swept into the reservation commit.--pr — skip the direct-push attempt; go straight to branch + PR.
Useful when you already know main is protection-locked. Mutually
exclusive with --no-push.Race-on-push (someone advanced origin/main while you were
reserving) surfaces as race-on-push: ... and drops the stranded
local commit + the stranded ADR file from your working tree. Re-run
the same adr.py new <slug> to pick the next free number — there
is no auto-renumber.
Then Claude fills in Context / Options Considered / Recommended Decision / Consequences. Keep it tight: one decision per ADR.
Once the prose is settled and the human (or the workflow gate) approves:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/adr-workflow/adr.py" accept <NNNN>
This flips Proposed (date) to Accepted (date) and stamps the canonical
status: Accepted frontmatter field in the same atomic write (spec 073-02 /
ADR-0026). Refuses if the Status is already Accepted (ADRs are immutable;
supersede instead — see below).
Frame-critique gate (spec 064-05 / ADR-0020 OQ2/OQ3). accept also gates
the flip on a passing adversarial frame-critique verdict — the ADR's
pre-commitment moment to catch a wrong premise (the ADR-0011 / ADR-0008 failure
mode). It applies iff the ADR carries a truthy frame_review flag: new
stamps frame_review: true on every ADR it creates (OQ3 — ADRs always-on), so
new ADRs are gated; a legacy markerless Proposed ADR is grandfathered (no
refusal). To clear it: build the prompt with review.py frame-critique docs/decisions/adr-NNNN-*.md, run a reviewer, then review.py record-review --adr NNNN --pass frame-critique --verdict pass … (writes
docs/decisions/reviews/adr-NNNN-frame-critique.md). Soft / bypassable with
JIG_REVIEW_EVIDENCE_GATE=0 (a deliberateness signal, ADR-0011 — not human-only
enforcement).
When a previously-Accepted decision is replaced by a newer one, don't edit
the old ADR's prose — write a new ADR (per new above), accept it, then run:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/adr-workflow/adr.py" \
supersede <old-NNNN> <new-NNNN>
Both ADRs must already be Accepted. The helper:
Superseded by [ADR-<new>](./adr-<new>-<slug>.md) (today) to the
old ADR's ## Status block,Supersedes ADR-<old> (plain text, no link, no date) to the new
ADR's ## Status block,Accepted (date) lines (this is the one edit allowed on
an immutable ADR per the Nygard convention),status: Superseded frontmatter field in the same
atomic write (the replacement keeps status: Accepted) — spec 073-02 /
ADR-0026, so a dependency on a superseded ADR correctly fails,Refuses (exit 2) if either ADR is Proposed (accept it first), if either
ADR is already Superseded, if <old> == <new> (self-supersession), or if
either NNNN is malformed. Re-run adr.py index docs/decisions after to
refresh the index entries.
python3 "${CLAUDE_PLUGIN_ROOT}/skills/adr-workflow/adr.py" index docs/decisions
Reads every adr-NNNN-*.md (skipping README.md) and rewrites only the
## Index section of docs/decisions/README.md. Everything else in the README
(header, format spec, "When to write" section) is preserved. Re-running on a
current README is a no-op.
If the new ADR resolves a ### Decision: ... entry in
docs/refinement-todo.md:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/adr-workflow/adr.py" \
resolve-todo <NNNN> "<heading fragment>"
The fragment is a case-insensitive substring (same lenient style as
workflow.py's slice fragment). The helper:
~~strikethrough~~, — RESOLVED YYYY-MM-DD,**Deferred:** line in strikethrough,**Resolved by:** [ADR-NNNN: ...](decisions/adr-...) line at the end of
the section.Refuses (exit 2) if the fragment is ambiguous, the section is already struck through, or the ADR hasn't been Accepted yet.
Never edit an Accepted ADR. The Nygard convention treats ADRs as
historical record — if the decision changes, write a new ADR that supersedes
the old one. The supersession lines (one on each side) are the only
edit allowed on an immutable ADR. Use the supersede subcommand
(section "3. Supersede an Accepted ADR" above) — adr.py supersede <old-NNNN> <new-NNNN> writes those lines deterministically on both ADRs
and refuses self-supersession, Proposed-ADR inputs, or double-supersession.
What the helper does (so you can spot-check the result):
## Status block gains a line appended after the existing
Accepted (date) line:
Superseded by [ADR-<new>](./adr-<new>-<slug>.md) (today).## Status block gains a plain-text line after its
Accepted (date): Supersedes ADR-<old> (no link, no date).Accepted (date) lines are preserved.If a user asks to "supersede ADR-NNNN", route them to the supersede
subcommand — both ADRs must already be Accepted; if the replacement
isn't drafted yet, walk them through new + accept for the new ADR
first.
The canonical lifecycle is new → edit → index (preview) → accept → index (final).
The preview-index pass surfaces a truncated or ugly first-bullet line
while the ADR is still mutable (per ADR-0006).
Skip the preview only if the Context first sentence is known to be
index-friendly (no abbreviations outside the helper's allowlist,
fits in one short clause).
# 1. Identify the deferred decision in docs/refinement-todo.md.
# Fragment: "scaffold-stable" (matches "### Decision: scaffold-stable …").
# 2. Scaffold the ADR.
python3 .../adr.py new scaffold-stable --title "scaffold-stable trigger"
# → docs/decisions/adr-0003-scaffold-stable.md
# 3. Claude edits the file: fills Context, Options, Recommended, Consequences.
# 4. Preview the index BEFORE accept, while the ADR is still mutable.
python3 .../adr.py index docs/decisions
# Inspect the new bullet line. If it looks wrong (truncated mid-
# abbreviation, missing the key noun, etc.), edit the ADR's first
# Context sentence and re-run this command. Iterate freely.
# 5. Accept.
python3 .../adr.py accept 0003
# → flips Proposed → Accepted (today).
# 6. Final index regen (idempotent re-run; updates only the Accepted line).
python3 .../adr.py index docs/decisions
# 7. Mark the refinement-todo entry resolved.
python3 .../adr.py resolve-todo 0003 "scaffold-stable"
A PostToolUse hook (jig-boundary-change-warn) fires on
Edit/Write/MultiEdit of a canonical external-interface
contract-artifact file (OpenAPI openapi.yaml/.yml/.json, AsyncAPI
asyncapi.yaml/.yml/.json, *.proto, *.graphql/*.graphqls, or
*.schema.json). It emits a soft additionalContext nudge pointing
the author at /jig:adr-workflow new <slug> (capture the rationale if
the change is breaking) plus the surface-appropriate breaking-change
ecosystem tool (buf breaking, graphql-inspector diff,
redocly diff / spectral, AsyncAPI parser diff, JSON-Schema diff).
The nudge is informational, never a gate — set
JIG_BOUNDARY_CHECK=0 to silence it. The filename + tool list is sourced
manually from the contracts skill's per-surface table;
the contracts skill is the source of truth for which artifact governs
which surface.
0001 and 0003 exist (no
0002), the next new ADR is 0004. The gap is preserved as historical
record.adr-0001-foo.md exists →
adr.py new foo exits 2. Either pick a different slug or write a
superseding ADR.**Deferred:**
line, and one new **Resolved by:** line appended at the section end.
Other fields (**Resolution trigger:**, **Mitigation idea:**,
**Watch-list:**, etc.) are left intact. If a section needs more
intricate updates, edit it by hand.## Context, truncating at the
first sentence-ending punctuation when the paragraph is multi-line or
120 chars. Common abbreviations (
e.g.,i.e.,etc.,Mr.,Dr., …) are skipped by an explicit allowlist; abbreviations outside that list may still cause a mid-word cut. If the resulting bullet reads oddly, edit the ADR's first Context sentence to be index-friendly. Per ADR-0006, edits to Context-section prose to fix index-rendering are NOT decision-content and do not violate the immutability rule. Edits to Status, Recommended Decision, or Consequences DO violate immutability and require a superseding ADR. RunindexBEFOREacceptas a preview pass to catch this while the ADR is still freely mutable.
workflow.py status-board afterward, writing commit messages,
invoking the reviewer subagent).workflow.py. 0001-01 does not collide
with 0001 since ADR numbers are matched as exact 4-digit prefixes,
not free-form substrings (unlike slice fragments).adr.py new is worktree-aware (ADR-0015 / spec 051). It routes on
the current branch: on main it runs the proven 028-01 in-place flow
(clean tree required); off main — a feature branch or a linked
worktree — it reserves via an ephemeral detached worktree at
origin/main (push mode) or commits a provisional reservation on the
current branch (--no-push), without disturbing your branch or tree.
You no longer need to git checkout main first (and a linked worktree
can't, since the primary worktree holds main). The earlier
off-main/dirty-tree refusal — and its impossible git checkout main
workaround — is gone.After using this skill in a real session:
adr.py index.resolve-todo apply to the wrong section?
Verify before committing.accept? If
not, walk back to the Proposed state by editing the Status line
(this is the one situation where editing a not-yet-Accepted ADR
is fine — it's not yet immutable).npx claudepluginhub ramboz/jig --plugin jigGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.