Spec workflow
Drive the spec-driven lifecycle for any non-trivial work item: SPIDR-split a new spec into vertical slices, transition state markers (DRAFT → READY_FOR_REVIEW → READY_FOR_IMPLEMENTATION → IN_PROGRESS → REVIEWED → RECONCILED → DONE; also DEFERRED for parked slices with a stated resolution trigger), enforce the reconciliation checklist before commit, and surface stale specs/ADRs whose `last_verified` date has aged past dependency changes. Use when starting non-trivial new work, creating a spec, transitioning a slice's state, parking a slice as DEFERRED, reconciling a reviewed slice, or auditing doc freshness. Do not use for quick one-off fixes that don't need a spec, or for bug-shaped work where `jig:bug-fix` is the better fit.From its SKILL.md
npx -y skills add ramboz/jig --skill spec-workflowAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
49.2 KB, ~12.7k tokens by cl100k_base, as published. Nobody here has run it
Spec 003 promoted this skill from stub to active. The deterministic state mutations live in
workflow.py; this SKILL.md drives the judgment layer.
What this skill does
- Guides SPIDR-splitting a new spec into vertical slices (Spike last, not first — try Rules / Data / Interface / Path first).
- Flags slices that look like horizontal phasing (no user-facing layer touched).
- Drives the spec lifecycle state transitions via
workflow.py. - Coordinates implementer + reviewer subagent invocations at the right points.
- Enforces the reconciliation checklist before a slice goes DONE.
- Consults
docs/memory/glossary.mdwhen drafting ACs to surface unknown domain terms. - Surfaces skill-routing observability via
workflow.py routing-stats [--days N]— a read-only histogram of which skills fired (jig baseline vs. richer/"other" skill per category) from.claude/skill-usage.jsonl(slice 041-02). - Surfaces use-case coverage via
workflow.py coverage [--project-dir DIR]— a read-only, advisory (non-blocking), project-wide bidirectional check (slice 068-03): a deterministic set-difference over theuse_cases:trace links that reports use cases with no implementing spec (coverage gap) and specs citing no parent use case (scope creep). No-op when the project has no## Use casessection. - Surfaces gate-bypass telemetry via
workflow.py gate-stats [--days N]— a read-only per-gate histogram of how often each gate honored its env-var override (an override-frequency audit trail, not a gate-value verdict) from.claude/skill-usage.jsonl(slice 078-02).
SPIDR splitting
All non-trivial specs are SPIDR-split into vertical slices before implementation begins. Spike is the last resort — try Path / Interface / Data / Rules first.
- S — Spike: research/learning activity. Only when none of P/I/D/R apply. AI agents default to spiking too eagerly — resist.
- P — Path: split by alternative paths through the story (happy path first, edge paths later).
- I — Interface: split by UI / platform / channel (minimal first, polish later).
- D — Data: split by data subset or format (less data first).
- R — Rules: split by business rules (simple first, edge cases later).
Anti-horizontal-phasing rule: every slice must touch the user-facing layer and deliver end-to-end value. A slice that touches only the DB or only the parser is horizontal phasing — re-split.
See worked-example-spidr-split.md
for one applied example per axis plus a jig-native dogfood case (spec
017's three-axis split). The canonical primer for all five axes lives
at docs/spec-workflow/spidr-primer.md.
Spike slices
When SPIDR's S axis fires during decomposition (none of P / I / D / R
apply because the team doesn't yet know enough to pick), the
resulting slice is marked kind: spike in its frontmatter — the
typed enum that spec_lint.py validates.
When to introduce a spike during decomposition. Reach for S only after trying R / D / I / P. The bias to resist is "let me research this first" as a prelude to "now let me build it as one big slab" — that is horizontal phasing in a trench coat. If the spike would conclude with "now ship the implementation," the implementation IS the slice, and the research goes inside it.
Body shape (four labelled blocks). A kind: spike slice carries
four blocks alongside the standard Goal / DoR / AC / DoD scaffolding.
Each label must be written with the trailing colon (**Question:**,
etc.) — that is what spec_lint.py matches against.
- Question: — one sentence stating the open question. Set at DRAFT.
- Time-box: — explicit budget (e.g., "1 day", "4 hours"). Set at DRAFT.
- Findings: — bullet evidence collected during the spike. Filled during IN_PROGRESS.
- Outcome: — one of
ADR-NNNN created/spec NNN-NN unblocked/abandoned (reason). Multiple outcomes separated by;(e.g.,ADR-0007 created; spec 030-02 unblocked). Set at DONE.
spec_lint.py soft-warns when a kind: spike slice is missing any of
the four labels — mid-flight spikes legitimately have empty Findings /
Outcome, so this is a warning, not a hard error.
Always nested, never standalone. Spike slices live inside a real
spec — never as a standalone docs/spikes/ artifact. The
1-slice-spec case (no clear downstream spec yet, just an
investigation) collapses to "spawn a normal spec where the only slice
is kind: spike." This forces the investigator to articulate the
downstream change up front and keeps jig at two numbered families
(specs+slices, ADRs).
Abandoned-spike manual-reshape failure mode. When a spike's
Outcome is abandoned (reason), dependents are NOT automatically
cascade-flagged. The human (or the next session) audits each
dependent slice and decides whether the original design still holds.
Automation here over-fires: "approach A abandoned" often means
"approach B from the same findings still satisfies the dependents."
workflow.py deliberately stays out of the cascade business; the
SKILL.md hand-off is the documented gate.
How to use
Creating a new spec
-
Step 0 — confirm the project is scaffolded (spec 063 / ADR-0011). BEFORE reserving a number or drafting ANY
docs/specs/or slice structure, confirm this project is a scaffolded jig project. If it isn't, route — do not hand-roll directories:- Greenfield (no jig structure yet) → tell the user to run
/jig:scaffold-init. It lays down conventions, templates, hooks, the status board, and a seed reference spec. - Existing spec/
slices/layout, but not jig-scaffolded (noscaffold.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:
workflow.py new(step 2) classifies and routes for you (spec 063-01) — ascaffold.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
spec-workflowrun improvising a looseslices/folder (or any ad-hocdocs/specs/skeleton) because/jig:scaffold-initwas skipped. That produces a non-jig layout that then needs migrating — exactly the reported failure. When in doubt, route to setup first; never invent the structure by hand. - Greenfield (no jig structure yet) → tell the user to run
-
Confirm the work needs a spec. Trivial fixes don't.
- Reference moved? Reframe first. If a load-bearing reference changed
from outside the system (a design system, vendor / API contract, test infra,
compliance regime, platform, or product-positioning / strategic-vision
shift), reach for
/jig:reframebefore drafting — re-baseline the corpus onto the new reference so you draft against it, not the dead premise (spec 067 / ADR-0024). 1a. Read the vision## Use casessection as framing — before drafting (spec 068-02 / ADR-0025). If the project'sdocs/product-vision.mdcarries a## Use casessection (the breadth-layer behaviors captured at init), read it first as framing context for this spec — which captured behavior does this work serve? The section is the shared frame specs anchor against; reading it before you draft is what keeps behavior-dense projects from each spec inventing its own slice of the world. (If there is no## Use casessection, the layer isn't adopted for this project — skip this step; nothing here applies.) You record the answer as a trace link in the spec'suse_cases:frontmatter (step 2a). 1b. Cross-check tracked bugs before drafting ACs. For feedback/triage specs, readdocs/bugs/README.mdand any overlappingdocs/bugs/NNN-*.mdrecord before turning reported symptoms into acceptance criteria. If an item is a tracked defect with a bug record, route it throughbug-fix(or link to that bug as an explicit dependency) instead of copying the defect into the spec as a second owner. Keep polish/design/new-behaviour items in the spec.
- Reference moved? Reframe first. If a load-bearing reference changed
from outside the system (a design system, vendor / API contract, test infra,
compliance regime, platform, or product-positioning / strategic-vision
shift), reach for
-
Reserve the next free number on origin/main:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" new <slug>The helper computes
max(NNN) + 1acrossdocs/specs/, writes a minimum stubdocs/specs/NNN-<slug>/spec.md(frontmatter + Overview- SPIDR-analysis headers), commits it as
docs(specs): reserve NNN-<slug>, and pushes toorigin/main. If the push is refused by branch protection / permissions, the helper automatically falls back to areserve/NNN-<slug>branch +gh pr create. This locks the number team-wide before any drafting begins, killing the parallel-worktree spec-number-collision failure mode logged across specs 014/015/016/017.
Works from any branch or worktree (ADR-0015 / spec 051). The helper routes on the current branch: on
mainit runs the proven in-place flow (clean tree required, since the commit lands on localmain); offmain— a feature branch or a linked.claude/worktrees/*worktree — it builds the reservation commit in an ephemeral detached worktree checked out atorigin/mainand pushes it by SHA, never touching your branch, cwd, or working tree. You no longer need to switch tomain(and a linked worktree can't, anyway).Flags:
--no-pushfor solo machines without a remote, or for an off-main provisional reservation committed on the current branch (the number is local-view and may collide at merge — treat it as provisional);--prto skip the direct-push attempt on protection-locked main. 2a. Record the use-case trace link — and grow the vision on discovery (spec 068-02 / ADR-0025). The stub seeds an emptyuse_cases:frontmatter list. Fill it with theUC-Nid(s) (from the vision## Use casessection, step 1a) this spec serves — thedependencies:-style flow-list shape, e.g.use_cases: [UC-1, UC-3]. This is the machine-resolvable trace link the reconcile-phase coverage check (slice 03) reads.The discipline is soft — an empty/absent
use_cases:never blocks a transition (AC4 / ADR-0011). But it is not silent. The trigger is mechanical and deterministic — theclassify_specpredicate inskills/_common/use_cases.pycomputes one ofno_section/empty/resolved/unresolvablefor this spec — not a voluntary "is this new?" self-report. Wheneverclassify_specreturnsemptyorunresolvable(the spec cites nothing, or cites aUC-Nwith no match in the vision) at draft/framing, surface a three-path prompt — every path is one step and none blocks drafting:- (a) cite an existing use case — this spec serves a behavior already in
the vision: put its
UC-Nid(s) inuse_cases:. Done. - (b) grow the vision — this spec serves a behavior not yet captured:
reuse
vision-elicitation's capture loop, seeded with the existing entries (so the author sees the current set), → normalize → confirm, then write additively (append, never discard-and-replace) and assign the next freeUC-N(use_cases.next_use_case_idallocatesmax + 1; retired numbers are never reused). The confirm step guards grow quality so a reachable trigger can't silently bloat the section: (i) enforce goal-level grain — reject spec-shaped / requirements-level phrasing, re-running slice 01's normalize ("[actor] can [goal]"); (ii) run a near-duplicate check against the seeded existing entries (use_cases.is_near_duplicate) — on an apparent match, route back to path (a)-cite rather than minting a duplicate. Then record the new id inuse_cases:. - (c) decline — legitimately untraced (infra / refactor / no user-facing
behavior) or defer: leave
use_cases: []. The vision is unchanged, and any resulting gap is slice 03's advisory coverage backstop. No-op.
CRITICAL — the no-section no-op. When
classify_specreturnsno_section(the project has no## Use casessection — the breadth layer is not adopted, e.g. jig's own repo), the prompt is suppressed entirely: nothing prompts and nothing errors. A project with specs but no use-case layer is wholly unaffected. The trigger fires only onempty/unresolvable, which presuppose the section exists. - SPIDR-analysis headers), commits it as
-
Create
docs/specs/NNN-<slug>/{spec.md,plan.md,tasks.md}with the conventional structure: status frontmatter, overview, SPIDR analysis, ordered slices. -
SPIDR-split: for each slice, the goal is one vertical piece that delivers end-to-end value. Spike is the last resort, not the first reach.
-
Each slice is a whole-file document with the canonical frontmatter shape (
status,dependencies,last_verified) plus DoR / AC / DoD / Close-out sections.workflow.py new(step 2) already emitted a well-formed starterslice-01-tbd.mdfrom the packaged slice template, so you never hand-resolve a template path; add any further slices in that same shape. For the structural reference — what a filled-in slice looks like — mirror the in-project worked example that scaffolding installs at project root:docs/specs/001-adopt-jig/(spec.md+slice-01-bootstrap.md), the first spec, which your project'sCLAUDE.mdnames as the worked example to imitate. Setstatus: DRAFTin the frontmatter. Legacy slices that use prose**STATUS: DRAFT**markers still work (lazy migration); no need to rewrite them. -
Ground your factual claims (spec 064-02 / ADR-0020 §1–§2). Any load-bearing factual claim about a runnable surface — library/API capability, version/perf behavior, behavior of existing code — must be backed by an executed probe (run the command, read the source /
node_modules) or a citation. Everything you cannot verify goes in the spec stub's risk-gated## Assumptionssection, marked explicitly — never asserted as fact. This makes mandatory + derived the existing informal "Current state (verified …)" discipline that the 064-01 retro found jig already half-practices by hand: it was grounding-by-probe all along, just reliant on author diligence. The## Assumptionsyou surface here has downstream value — slice 064-04 derives theframe_reviewtrigger mechanically from it, so honest framing now is what decides later whether the adversarial frame-critique pass fires. The section is risk-gated: write "None" / omit when there are no unverified load-bearing assumptions; don't pad with boilerplate. (For a worked example of marked assumptions plus probe-grounded claims, see ADR-0020## AssumptionsA1–A4 +## Kill criteria, and the spec 064-01 retro, which probe-verified its three most load-bearing claims before recording them.) -
Let the assumptions decide
frame_review(spec 064-04 / ADR-0020). You are not asked "is frame-review needed?" — the## Assumptionsyou just surfaced decide it, mechanically. Set the slice'sframe_reviewflag fromworkflow.py frame-review-needed:python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \ frame-review-needed "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>"The rule is a derivation, not a judgment call:
trueiff the slice's## Assumptionssection carries ≥1 real (non-placeholder) assumption — so honest framing in step 6 is exactly what fires (or silences) the adversarial frame-critique pass. An inline-mirror / refactor slice with no unverified assumptions (## Assumptionsabsent or just "None") stays default-off. ADRs are always-on (OQ3): any ADR getsframe_review: trueunconditionally — the deriver returnstruefor anyadr-*.mdpath. When the value istrue, setframe_review: truein the slice frontmatter so the gate +session-plandispatch the pass. -
Add rows to
docs/specs/README.md(or regenerate viaworkflow.py status-board).
Picking up a slice
-
Read the automatic
jig hint:project-orientation headline injected atSessionStart, or refresh it manually before choosing work:python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" orient \ --project-dir .The headline is computed from
scaffold.jsonand lifecycle artifacts. Treatdocs/architecture.md, the spec corpus, and the status board as authoritative; a shallow source-tree listing is not evidence that a scaffolded project is greenfield or that recorded stack decisions are absent. -
Check
docs/specs/README.mdfor the next slice inREADY_FOR_IMPLEMENTATION(orDRAFTfor a slice you intend to plan now). -
Run:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" transition \ "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" IN_PROGRESSClaim-on-working-state (spec 049-01, amended by ADR-0045). On a frontmatter (file-per-slice) slice, a transition into a working state —
READY_FOR_REVIEW/IN_PROGRESS/REVIEWED/RECONCILED— stampsclaimed_by:(the current branch name, orJIG_CLAIM_ID), so spec-level work is marked too, not just implementation. Entering a release point clears it: the two pickup-queue statesDRAFT/READY_FOR_IMPLEMENTATION(step 2 above tells you to choose work from exactly those, so a leftover owner there would mark a free slice as occupied), plus the terminalDONE/DEFERRED/ABANDONED.It refuses only when the slice is already
IN_PROGRESSunder a different identifier and you are moving it toIN_PROGRESS(naming the holder, pointing at--release); any other foreign claim — on your copy or onorigin/main— is a loud non-blocking warning, because two sessions working one spec can be legitimate. The claim is local by default; add--push(direct) or--pr(via PR) to reserve it onorigin/mainso other worktrees see it, at any working state, though only anIN_PROGRESSreservation also publishesstatus:there (race / protected-branch handling mirrorsworkflow.py new). At a working state that reservation is best-effort (for a target other thanIN_PROGRESS): if the trunk copy is alreadystatus: IN_PROGRESSunder someone else's claim or none, it warns and pushes nothing, because that state is what the start-of-build guard hard-blocks on — stamping a claim over it would move a live lock, or manufacture the enforced pair on an unclaimed copy. Your own trunk claim just reports a benign no-op. To force-release a stale claim:transition <spec> <slice> <state> --release --reason "<why>"(clearsclaimed_by:, logs to## Release log).Do not read a blank
claimed_by:as "free". It means no claim is recorded: claims are local unless pushed, so another worktree's unpushed claim is invisible, and a plainEdit-tool write to a slice takes no claim at all. A claim that IS present names the session that last moved the slice into a working state — a presence hint, not a live lock. When it matters, ask rather than assume — see bug 014. -
Fill in / refresh
plan.mdandtasks.mdfor the slice. -
Spawn the
implementersubagent with the spec path. Prefix the Task prompt with[jig:phase=implementation] [jig:spec=NNN] [jig:slice=NNN-NN]sojig-telemetry.shcan attribute implementation-phase cost. Implementer writes the deliverable to disk (TDD — failing tests first).
After implementation
Slices 031-01 + 031-02 + 060-05 wired a multi-pass review flow into the
post-implementation step. Every slice runs through two passes before the
IN_PROGRESS → REVIEWED transition; two further passes fire on demand —
the arch pass when the slice declares arch_review: true, and the
code-health pass when it declares code_health_review: true.
The orchestrator runs the passes in this order:
-
Compliance pass —
jig:independent-review(always). Spawn thereviewersubagent against the deliverable using the prompt built byreview.py implementation. Reviewer is read-only; it evaluates each acceptance criterion and returnspass | fail | needs-changes. -
Craft pass —
pr-review(always). After the compliance pass returns, build the craft-pass prompt withreview.py pr-reviewand spawn a secondreviewer-shaped subagent. The reviewer is read-only (Read/Glob/Grep, noSkilltool), so it cannot route to a skill via Claude's skill router; insteadreview.pydetects a user-installedpr-reviewskill on disk (~/.claude/skills/pr-review/) and the prompt points the reviewer at that concrete path to read-and-apply, falling back to jig's inlined baseline buckets (scope / blockers / nits / strengths) when none is installed. (File-read dispatch — spec 031 Open-question-#1 option (b); a live probe showed the original prose-router dispatch was inert on the no-Skill-tool subagent path.) The pass returns the sameVERDICT / REASONING / SPECIFIC ISSUES / RECONCILIATION NOTESenvelope as the compliance pass, with SPECIFIC ISSUES entries tagged[blocker]/[nit]/[strength]. -
Arch pass —
arch-review(on-demand). Before running this pass, query the slice'sarch_review:frontmatter flag viaworkflow.py arch-review-needed. When the helper printstrue, build the arch-pass prompt withreview.py arch-reviewand spawn a thirdreviewer-shaped subagent. The pass produces the four canonical arch buckets (summary / strengths / concerns / open questions) wrapped in the same verdict envelope, using the same file-read dispatch (review.pydetects~/.claude/skills/arch-review/, else inlines jig's baseline buckets). When the helper printsfalse, skip this pass entirely. Slice authors flip the flag by uncommenting thearch_review: trueline in the slice template's frontmatter — set it when the slice changes module boundaries, public contracts, or architecture-shaped concerns. -
Code-health pass —
jig:code-health(on-demand, gated). Before running, query the slice'scode_health_review:frontmatter flag viaworkflow.py code-health-review-needed. When it printstrue, runhealth.pyyourself (the orchestrator / CI), capture its tight summary, and feed THAT summary intoreview.py code-health … --summary-file(--summary-file -to pipe it in). Then spawn areviewer-shaped subagent. The reviewer is read-only (Read/Glob/Grep, no Bash) — it must NOT runhealth.py; it judges the summary you provide. The reviewer renders the judgment a tool can't: is duplication within the ADR-0002 inline-mirror budget? is a complex function inherent or fixable? are the lint findings worth blocking on? The pass returns the same verdict envelope, with SPECIFIC ISSUES tagged[blocker]/[nit]/[strength]. Why gated, not always-on: ADR-0017 flags the per-slice review cost (specs 055/057 context-cost discipline) and recommends gating it like arch-review — so it defaults off and slice authors opt in withcode_health_review: true. The evidence file isreviews/slice-NN-code-health.md.
When spawning any reviewer Task above, prefix the Task prompt with telemetry
tags before the review.py body: [jig:phase=<phase>] [jig:spec=NNN] [jig:slice=NNN-NN]. Use compliance for review.py implementation,
craft for pr-review, arch for arch-review, code-health for
code-health, and reconciliation for the final reconciliation review.
Block rule for the REVIEWED transition. All required passes
(compliance + craft, plus arch when arch_review: true, plus code-health
when code_health_review: true) must pass before
transition <slice> REVIEWED:
- Any
failverdict from any pass blocks the transition. needs-changesfrom the compliance pass blocks (the implementer addresses findings and re-runs).needs-changesfrom the craft pass does NOT block — the[nit]-tagged entries become reconciliation-log items (the implementer captures them in the deviation log during reconciliation). Only[blocker]-tagged entries from the craft pass block the transition.- The arch pass follows the same rule as the craft pass:
[blocker]-tagged entries block;[nit]-tagged entries andneeds-changesbecome reconciliation-log items. - The code-health pass follows the same rule:
[blocker]-tagged entries block theREVIEWEDtransition;[nit]-tagged entries become reconciliation-log items.
Measurement tag (non-gating). The craft/arch/code-health passes also
self-classify each SPECIFIC ISSUES entry [spec] (an acceptance-criteria /
spec-frame issue a smarter clarify pass could have prevented) or [impl]
(implementation-discipline — untested edge, brittle fixture, drift). This
carries no gating consequence — it exists purely so a future rollup can
count [spec]-tagged themes across specs (docs/refinement-todo.md
"Instrument the review→learnings→clarify loop before building it").
The gate is mechanical, not advisory (slice 045-03 / ADR-0014 §5).
workflow.py transition now refuses the REVIEWED / RECONCILED /
DONE moves unless the required review evidence — recorded with
review.py record-review as docs/specs/NNN-<slug>/reviews/slice-NN-<pass>.md
— exists and clears (verdict: pass). REVIEWED requires
compliance + craft (+ arch when the slice declares
arch_review: true, + code-health when it declares
code_health_review: true); RECONCILED requires the reconciliation verdict
and ### Deviation log plus ### Reconciliation sweep subsections;
DONE re-validates the post-implementation and reconciliation evidence set
(in addition to the existing dependencies: check). A refusal names
the missing/invalid artifact and the record-review command to produce
it. The gate enforces evidence consistency, not human sign-off (it
lives in the agent's trust boundary per ADR-0011).
Bypass it for a deliberate out-of-band flow by setting
JIG_REVIEW_EVIDENCE_GATE=0 (also false/off/no) — the status still
transitions and the DONE dependency check still runs; only the evidence
check is skipped.
After all required passes pass:
- Address any reviewer findings, adding regression tests for any real bugs found.
- Record each pass's verdict as durable evidence with
review.py record-review(writesdocs/specs/NNN-<slug>/reviews/slice-NN-<pass>.md— see the independent-review SKILL.md § "Recording and checking review evidence"). TheREVIEWEDtransition is gated on this evidence, so it is not optional. - Transition:
transition <spec.md> <slice> REVIEWED. The gate re-validates the recordedcompliance+craft(+arch,code-health) verdicts before the status flips (and before the 003-04 auto-tick).
Recovering from a failed review. A fail/needs-changes verdict — or
a [blocker]-tagged craft/arch finding, which is recorded as a non-pass
verdict — blocks the REVIEWED transition. To recover: address the
findings, re-run the pass against the updated deliverable, record-review
the new verdict (it overwrites in place the earlier file for that
(slice, pass); git history keeps the prior one), then re-run
transition … REVIEWED. With every required pass now pass, the gate
clears. A non-pass artifact never overwritten by a later pass keeps
blocking — the "superseded without a later pass" case (ADR-0014 §4).
# Compliance pass (always)
PROMPT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
implementation "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
"<deliverable-path-1>" ...)
SUBAGENT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
subagent-type implementation)
# … feed "[jig:phase=compliance] [jig:spec=NNN] [jig:slice=NNN-NN]\n\n$PROMPT"
# … to Task with subagent_type: $SUBAGENT, wait for pass …
# Craft pass (always)
PROMPT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
pr-review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
"<deliverable-path-1>" ...)
SUBAGENT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
subagent-type pr-review)
# … feed "[jig:phase=craft] [jig:spec=NNN] [jig:slice=NNN-NN]\n\n$PROMPT"
# … to Task with subagent_type: $SUBAGENT, wait for pass …
# Arch pass (only when slice frontmatter has `arch_review: true`)
# IMPORTANT: capture the helper exit code — a non-zero exit means the
# slice lookup failed (missing spec / unknown fragment / ambiguous),
# not "no arch pass needed." Surface the error rather than silently
# skipping the pass.
if ! NEED_ARCH=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \
arch-review-needed "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>"); then
echo "arch-review-needed failed — aborting" >&2
exit 2
fi
if [ "$NEED_ARCH" = "true" ]; then
PROMPT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
arch-review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
"<deliverable-path-1>" ...)
SUBAGENT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
subagent-type arch-review)
# … feed "[jig:phase=arch] [jig:spec=NNN] [jig:slice=NNN-NN]\n\n$PROMPT"
# … to Task with subagent_type: $SUBAGENT, wait for pass …
fi
# Code-health pass (only when slice frontmatter has `code_health_review: true`)
# The orchestrator runs health.py and feeds its summary IN — the read-only
# reviewer never runs the tool (no Bash).
if ! NEED_CH=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \
code-health-review-needed "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>"); then
echo "code-health-review-needed failed — aborting" >&2
exit 2
fi
if [ "$NEED_CH" = "true" ]; then
# Run the jig:code-health runner yourself (health.py check .) and capture
# its tight summary to /tmp/health-summary.txt — the read-only reviewer
# MUST NOT run it. (The runner ships with the Tier-1 jig:code-health skill;
# if it isn't installed, note "summary unavailable" and judge on the
# deliverables.)
PROMPT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
code-health "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
"<deliverable-path-1>" ... --summary-file /tmp/health-summary.txt)
SUBAGENT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
subagent-type code-health)
# … feed "[jig:phase=code-health] [jig:spec=NNN] [jig:slice=NNN-NN]\n\n$PROMPT"
# … to Task with subagent_type: $SUBAGENT, wait for pass …
fi
Reconciliation (REVIEWED → RECONCILED)
Walk the Reconciliation checklist below. Every item is a gate.
Closing the slice
- After the reconciliation review passes, record its verdict with
review.py record-review … --pass reconciliation, thentransition <spec.md> <slice> RECONCILED. That move is gated on the recordedreconciliationverdict (pass) and### Deviation logplus### Reconciliation sweepsubsections under the slice heading (ADR-0014 §5 + ADR-0029). - Commit the work.
- After commit:
transition <spec.md> <slice> DONE.DONEre-validates the whole evidence set —compliance+craft(+arch,code-health) +reconciliation— plus the deviation log and reconciliation sweep, on top of the existingdependencies:check.
- Regenerate the board:
workflow.py status-board <project-dir>. - Run
/jig:memory-sync(ormemory.py) to consolidate any new learnings.
Spec lifecycle states
DRAFT → READY_FOR_REVIEW → READY_FOR_IMPLEMENTATION → IN_PROGRESS
→ REVIEWED → RECONCILED → DONE
DEFERRED ⇄ DRAFT (parked slices with a stated resolution trigger)
ABANDONED ⇄ DRAFT (permanently dropped slices, pre-DONE only)
Status transitions are mutations on either spec.md's frontmatter status:
field (new convention, slice 015-01) or the prose **STATUS: ...** line
(legacy — still supported via lazy migration), AND the matching row in
docs/specs/README.md. Use workflow.py transition for the spec mutation
and workflow.py status-board to re-sync the board.
Spec-level status: is derived, not authored (slice 030-01; widened by
slice 085-01). The frontmatter status: at the top of each spec.md
overview file is computed by compute_spec_status(spec_path) from its
slices: DONE when every slice, excluding DEFERRED/ABANDONED ones, is
DONE (a mix of DONE + DEFERRED and/or ABANDONED still rolls up to
DONE); ABANDONED when every slice is ABANDONED (the spec's entire
scope was dropped); DRAFT when no slices exist, every slice is
DEFERRED, every non-DEFERRED slice is DRAFT, or the only non-DONE
slices are a DEFERRED+ABANDONED mix with no live work; otherwise
IN_PROGRESS. The rollup write happens automatically inside workflow.py transition (after the slice mutation) and inside workflow.py status-board (during regen). Don't set spec.md's status: by hand —
it'll be overwritten on the next transition or regen anyway.
DEFERRED state
A slice is DEFERRED when scoped but parked — the work is identified but
not the current priority. Different from DRAFT which means "not yet
fleshed out." Transitions:
- Any state →
DEFERREDis allowed. DEFERRED→DRAFT(re-open) is allowed.DEFERRED→ any other state is refused — re-open via DRAFT first so review gates aren't silently skipped. This is the first FROM-state-restricted transition in jig's lifecycle.
When transitioning a slice to DEFERRED, add a **Resolution trigger:**
line in the slice body (same convention docs/refinement-todo.md uses).
The status-board renders deferred slices in a separate ## Deferred slices
section with that trigger as the per-row context.
ABANDONED state
A slice is ABANDONED when it's permanently dropped — scoped, sometimes
even fully specced, and deliberately decided against with no intent to
ever resume. Different from DEFERRED, which means "parked, with a stated
resolution trigger that will resurface it." Added in slice 085-01 (filed as
GitHub issue #72). Transitions:
- Any pre-
DONEstate →ABANDONEDis allowed. DONE→ABANDONEDis refused — "never attempted" and "shipped, then deliberately removed" are different events with different audit value; overloading one bucket for both would erase that distinction where an auditor most needs it (see spec 085 Non-goals). Un-shipping already-DONEwork is a different, unbuilt concept.ABANDONED→DRAFT(re-open) is allowed.ABANDONED→ any other state is refused — re-open via DRAFT first, mirroringDEFERRED's restriction.
When transitioning a slice to ABANDONED, add a **Abandonment reason:**
line in the slice body (same convention shape as **Resolution trigger:**). The status-board renders abandoned slices in a separate
## Abandoned slices section with that reason as the per-row context.
The transition also prints a one-time, non-blocking warning naming any
other slice, anywhere in the project, whose dependencies: names the
now-abandoned slice and whose own status isn't already DONE/ABANDONED
— advisory only, it never blocks the transition, modifies the dependent,
or cascades (a human decides what a live dependent should do next).
Slice frontmatter (slice 015-01 convention, file shape per 018-03)
New slices are whole-file templates — frontmatter at the top, ## Slice ...
heading immediately following the closing frontmatter delimiter. workflow.py new emits a starter slice-01-tbd.md alongside spec.md in this
shape (from the packaged slice template); for a filled-in reference, mirror the
scaffolded worked example docs/specs/001-adopt-jig/slice-01-bootstrap.md.
Legacy specs that embed ## Slice sections inside spec.md
(heading-first, frontmatter-after) remain supported by every helper —
no forced migration.
---
status: DRAFT
dependencies: [007-02, adr-0004]
last_verified:
---
status— current lifecycle state.workflow.py transitionupdates this when present.dependencies— flow-style list of slice fragments (e.g.007-02) and ADR IDs (e.g.adr-0004).transition <slice> DONErefuses if any listed dependency is not DONE / accepted.last_verified— date the slice was last reconciled.transitionstamps this automatically on→ RECONCILED. Used bystale.
Legacy slices using prose **STATUS:** markers still work — the
transition helper writes to whichever shape is present. No retroactive
mass migration; new slices use the template, old slices stay as-is.
Reconciliation checklist
When a slice transitions REVIEWED → RECONCILED, walk this checklist before the
status flip is allowed. Each item is a gate.
- Deviation log — write what changed during implementation and why,
under a "Deviation log (after reconciliation)" subsection of the slice
in
spec.md. Original ACs preserved above; deviations append, not overwrite. - Reconciliation sweep — write which drift-prone surfaces were checked,
using
updated/no-op/deferreddispositions. The transition gate checks the subsection exists; the reconciliation reviewer judges coverage and rationale quality. - Lightweight decisions — did this session's review or implementation
settle any non-spec decisions (UI strings, visual choices, translation
corrections, scoped brand/icon calls)? If yes, record them in
docs/decisions/lightweight-decisions.md. (Non-blocking nudge; not a gate.) - Architecture impact — did module boundaries or public contracts change?
If yes, update
docs/architecture.mdAND write an ADR. Ground what you write (ADR-0020 §1, same rule as spec-authoring step 6). Reconciliation rewrites long-lived front-door prose that everyone reads and nobody re-derives, so any load-bearing factual claim about a runnable surface — library/API capability, version/perf behavior, behavior of existing code — must be backed by an executed probe or afile:linecitation. Anything you cannot verify is marked as an assumption, never asserted as fact. (A prose claim naming a code symbol but citing no line is a candidate warning.) - Load-bearing decision (ADR trigger, judgment) — beyond a boundary change, was a load-bearing design choice with rejected alternatives made? Canonical wording — single-sourced from ADR-0031, drift-tested verbatim across all four surfaces: A load-bearing design choice with rejected alternatives — one a future agent would need to know about to avoid undoing it — warrants an ADR even when it changes no module boundary or public contract.
- Revised a recorded decision? (spec 100 / ADR-0042)
Routing is asked once at first write and never again, so a decision
re-priced during this slice can stay misfiled. If a revised entry now
clears the trigger above, promote it (
decisions.py promote --title "<title>" --no-push— push mode reserves the ADR onorigin/mainfrom an ephemeral worktree, so offmainit never reaches your working copy andpromoterefuses); if it is still settled, local and bounded, revise it (decisions.py update). Never hand-editlightweight-decisions.md. - Conventions impact — did this slice introduce or change a rule worth
recording? If yes, edit
docs/conventions.md(requiresJIG_CONVENTIONS_APPROVED=1). - Inbox triage — sweep
docs/inbox.mdfor items resolved by this slice; move them to the relevant memory file or strike them through. - Primer hygiene — if this slice closes the spec (all non-deferred
slices DONE), apply the spec 025 compress-on-close-out rule per the slice
template's
### Close-out (post-DONE)section. Check every primer surface present in this project:CLAUDE.md,AGENTS.md, and scaffold templates. Active-spec sections should only carry in-flight work; load-bearing per-slice invariants migrate to the status board Notes column (whichworkflow.py status-boardpreserves across regen), memory, or the reconciled spec/slice record. - Memory-sync — run
/jig:memory-sync(or invokememory.pydirectly) to persist any new domain terms, dead-end learnings, or tool decisions that emerged during implementation. This is where slice 002-04's integration lives: the reconciliation phase explicitly surfaces memory-worthy items for persistence. The reviewer subagent reads from memory but never writes to it (seeagents/reviewer.md). - Closed-spec drift — if reconciliation surfaces a prior
closed-spec inaccuracy (a
DONE/SUPERSEDEDspec/slice, or load-bearing skill/router/workflow prose that no longer matches reality), follow the policy in ADR-0010 (supersedes ADR-0008). Records (closed specs/slices): append a dated## Amendmentsentry preserving the original. Live prose (SKILL.md / workflow.md / README): fix it inline — git history is the audit trail. New ADR (or superseding spec) only for decision-content changes. Authorisation to amend (issue #125). Amending a closed record requires explicit owner approval — a separate grant from approval of a behaviour. When two canon artifacts disagree, surface the conflict and stop: propose the amendment as text in the conversation and write it only after the owner agrees. Never write the resolution in the same turn as discovering the conflict — including when the owner has already approved the underlying behaviour (approving what the app does is not authority to rewrite what the spec says). And before asserting that artifact X contradicts criterion Y, read all of Y's sibling criteria: another may already satisfy X — for a cross-cutting question the unit of reading is the whole criteria block, not the item that appears to speak to it. This authorisation rule governs records only; correcting live operational prose inline (per the split above) is git-history-audited and needs no sign-off. - Reconciliation review — spawn a second reviewer subagent with a
reconciliation-review prompt prefixed by
[jig:phase=reconciliation] [jig:spec=NNN] [jig:slice=NNN-NN]: are the doc changes faithful? Is the deviation log honest? Is scope appropriate (no scope creep in docs)? - Use-case coverage (advisory) — run
workflow.py coverage [--project-dir DIR]and review any coverage gap (a use case with no implementing spec) or scope creep (a spec citing no resolvable use case). Non-blocking — unlike the gates above, a finding here does not blockRECONCILED/DONE(ADR-0025 OQ3 / ADR-0011); it is the reconcile-time backstop to slice 02's framing-time grow prompt. No-op when the project has no## Use casessection. - Commit — only after all gates pass.
Auditing staleness (workflow.py stale)
Slice 015-03 added a read-only freshness audit:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" stale \
[--project-dir DIR] [--days N]
Walks docs/specs/*/spec.md and docs/decisions/adr-*.md, extracts
last_verified + dependencies from frontmatter, and lists items
meeting the conjunctive criterion:
An item is stale iff (a)
today - last_verified > --days(default 90) AND (b) at least one file referenced bydependencieswas modified sincelast_verified.
Pure age isn't enough — a verified-2-years-ago ADR for an unchanged decision shouldn't fire. Pure recency-of-dep isn't either — a doc verified yesterday with old deps is fine. Both conditions must hold.
The check uses git log -1 --format=%cs <path> for committed-state
authority and falls back to filesystem mtime when git is unavailable
or the file isn't tracked. Read-only: it lists, doesn't transition.
Bumping last_verified is a deliberate human/agent action — edit the
file, or re-run transition <slice> RECONCILED after re-verifying.
Gotchas
- Spike is the LAST SPIDR technique to reach for, not the first. AI agents default to spiking too eagerly; try Rules / Data / Interface / Path first.
- Every slice must be vertical (crosses all layers, delivers end-to-end value). A slice that touches only the DB or only the parser is horizontal phasing — flag it.
- The reviewer subagent must NOT be invoked with prior implementation context.
Write the deliverable to disk first; reviewer reads only the spec + deliverable
- acceptance criteria.
- The reviewer is read-only on
docs/memory/— memory-sync runs as a separate step during reconciliation, never as part of review. workflow.py transitionuses substring matching on slice names —001-01matches## Slice 001-01 — greenfield-scaffold. If you have multiple slices whose names share a fragment, the helper refuses with anambiguouserror; use a more specific fragment.workflow.py status-boardpreserves the preamble before the| Spectable header. Custom intro text survives regen. Idempotent: no churn if the board is already current. Notes column also survives regen (the helper parses existing Notes and re-emits them). Deferred slices appear in a separate## Deferred slicestable below the active table; only the active table preserves Notes.workflow.py status-boardrefuses to overwrite on a mid-regen race (slice 028-03). The helper captures a SHA256 ofdocs/specs/README.mdat the start of regen and re-checksums right before the write; if another writer mutated the file in the gap, it raisesStatusBoardRaceErrorand exits 4 with the messagestatus board changed during regen — another writer may have run. Re-run workflow.py status-board to retry.. Pass--forceto bypass the guard and overwrite anyway (use only when you've manually reconciled the conflict). Identical-content rewrites do NOT trigger a refusal (checksum is content-based, not mtime-based).workflow.pyignores## Spikeheaders. Spikes are research artifacts, not lifecycle-managed work items. They don't have a STATUS marker the helper can transition. If you need a spike to be tracked in the status board, model it as a## Slice Nnna — <name>instead, or update the board's Notes column manually.- Avoid raw
|characters in the Notes column ofdocs/specs/README.md. Markdown tables use pipes as cell separators; raw pipes in a Note value would truncate the cell during regen's preservation step. Use HTML-entity|or rephrase if you really need a pipe. DEFERRED → DONE(or any non-DRAFT state) is refused. Re-open the slice withDEFERRED → DRAFTfirst, then advance through the normal lifecycle. This prevents silently skipping review gates when a parked slice is picked back up.transition <slice> DONEvalidatesdependencies:. If any listed dep slice isn't DONE or any listed ADR isn't Accepted, the helper refuses with a structured error naming each unsatisfied dep. Empty / missingdependencies:skips the check.
What ships with it: 2 files
218.5 KB alongside SKILL.md, 1 of them executable
- worked-example-spidr-split.md5.5 KB
- workflow.pyruns213.0 KB
Gives 0 of the 12 instructions most plan spec skills give in ~12.7k tokens
Counted across 1,099 of the 1,860 authors here whose files we hold, read 2026-08-07
- Ask one question at a timein 51 of 1099
- Break plans into vertical slicesin 29 of 1099, across 11 files
- Publish issues in dependency orderin 27 of 1099, across 9 files
- Iterate until user approves the breakdownin 25 of 1099, across 7 files
- Explore the repository to understand the codebase statein 24 of 1099, across 7 files
- Use domain glossary vocabularyin 23 of 1099, across 5 files
- Apply correct triage labels to published issuesin 23 of 1099, across 5 files
- Prefer AFK slices over HITLin 22 of 1099, across 7 files
- Write a specification before writing any codein 22 of 1099, across 14 files
- Write failing tests before implementation codein 22 of 1099, across 20 files
- Ask clarifying questions until requirements are concretein 21 of 1099, across 13 files
- Respect existing architecture decision recordsin 20 of 1099, across 5 files
Said here and by no other author read
- split specs into vertical slices using spidr
- try path interface data and rules before spike
- ensure every slice touches user-facing layer
- re-split horizontal slices that lack end-to-end value
- confirm project is scaffolded before creating specs
- read product vision use cases before drafting
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.