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
- 5 stars5 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
58.7 KB, as published. Nobody here has run it
Working posture (ADR-0056). Adversarial review is a named, bounded operation. This workflow invokes review passes (frame-critique / craft), whose skeptical register belongs inside those isolated reviewer subagents. Outside a review, default to collaborative and solution-forward; don't carry the adversarial stance into ordinary conversation.
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. - Guards the status board via
workflow.py check-board <project-dir>— a read-only audit that exits non-zero whendocs/specs/README.mdno longer matches the spec records, or when two spec directories claim one number. Writes nothing, so it is safe to run in CI against a checkout. - 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. 5a. Design-fidelity authoring nudge (spec 104-02 / ADR-0049). When a slice you just wrote in step 5 ships visual design (a mockup, a design system spec, a screen with colours/spacing/sizes/layout rules to hit), don't let "doesn't match the mockup" live only in a picture:- (a) Extract the design values into checkable ACs. Pull the concrete values — colours, spacing, sizes, layout rules — out of the mockup and write them as this slice's acceptance criteria, the same way any other observable behavior becomes an AC. This is what turns a fuzzy "looks right" into something a reviewer (or an eval) can actually check.
- (b) When fidelity must gate, wire the servo rail. If a screen's
visual fidelity needs to be a hard, enforced condition of
DONE— not just an eyeballed check — setdesign_review: truein the slice frontmatter and wire a servodesign-eval(screenshots the running app against the reference, scores it with a pinned vision judge) as the done-condition.design_review: trueis attested, read-only, atREVIEWEDby spec 071's design-review pass (the deriverslice_needs_design_reviewinworkflow.pyreads the flag) — jig never re-derives the eval score itself.
Graduated, not mandatory — jig offers, never forces, servo. Not every screen earns a frozen eval:
- Low-stakes visual polish → design-values-in-ACs plus
attest-by-eyeball at review time is enough; no servo
design-evalrequired. - A hard fidelity gate (fidelity must not regress, or is a stated
product requirement) → servo
design-eval+design_review: true.
Pick the tier that matches the stakes; when in doubt, start with (a) and add (b) only when eyeballing genuinely isn't enough. See spec 071 and ADR-0049 for the full routing rationale; this step adds no new mechanism — teeth stay anchored to the existing
design_reviewflag. -
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. A universal or negative claim ("the only", "never", "always", "one-way", "nothing reads", "otherwise clean") is established by an enumeration — a search you can show returns the complete set — not a single positive citation (ADR-0052): one true example proves nothing about the rest of the set, and these are the highest-value claims a future reader relies on. To claim enumeration, state why the search is exhaustive — what closes the set so nothing escapes. Some sets are closed by syntax and this is easy (imports in a package, call-sites in a repo); many only lookgrep-bounded — a "nothing reads this" search misses dynamic / reflective / ORM / string-built / config-wired / codegen'd / cross-repo access (illustrative, not a checklist to clear: the burden is to show the search captures every member, not to rule out named escapes). When you cannot show the search is exhaustive, an empty result is absence of evidence, not an enumeration: weaken the claim, tighten the boundary until the search genuinely closes the set, or move it to## Assumptions. Never dress an empty search as an enumeration — the frame-critique reviewer treats "I searched and it was empty" as ungrounded until you have shown what closes the set. 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.pyhands it a concrete richer-skill path to read-and-apply, falling back to jig's inlined baseline buckets (scope / blockers / nits / strengths). The pass returns the sameVERDICT / REASONING / SPECIFIC ISSUES / RECONCILIATION NOTESenvelope as the compliance pass, with SPECIFIC ISSUES entries tagged[blocker]/[nit]/[strength].Select the richer skill first (spec 096-03 / ADR-0040 D3).
--richer-skillis a required argument onpr-review(andarch-review/code-health). Before building the craft prompt:- Run the candidate step:
review.py candidates pr_review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" --pass craft. It prints a tiered list —[high-confidence](with descriptions) and[speculative](names only) — and writes the shown set to a sidecar. - Pick the single best HIGH-CONFIDENCE candidate for the category and
pass it as
--richer-skill <name>. If several are genuine, pick one — do not refuse; the alternatives are recorded as the shown-and-declined set. If none fits, pass--richer-skill nonefor jig's baseline. - The pick is a heuristic, not a guarantee — an explicit
review.<category>_skillinscaffold.json(096-01) overrides it, andcandidatesnever picks for you (a[speculative]false positive like a briefing skill must not be selected on lexical grounds alone). - CI / no orchestrator: pass
--richer-skill none --non-interactive; config remains the reproducible path. Omitting--richer-skillentirely, or invoking a pass with no sidecar + no config + no--non-interactive, fails fast (naming the missingcandidatesstep) rather than silently using the baseline.
- Run the candidate step:
-
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 candidate-channel resolution as the craft pass (096-03): runreview.py candidates arch_review …, pick the best, pass--richer-skill; config (review.arch_review_skill) overrides, else jig's baseline buckets are inlined. 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).
When a review retracts a claim, sweep the corpus before re-recording.
The recovery above is written for a code-shaped finding, which is local to one
file. A finding about content is not: a retracted assertion is usually
copied by design into CHANGELOG.md, the slice record, the inbox, and
cross-referenced docs. Fixing only the reviewed deliverable leaves the
retracted version authoritative in every sibling artifact — and the stale
copy is frequently the one the next session reads first (the project's own
rules make CHANGELOG.md a read-before-you-fix record), so the pass re-fails
round after round on a document you never touched. Before you record-review
the new verdict, grep the retracted phrasing across the docs root and
CHANGELOG.md, and reconcile every hit — the plain sweep is what reaches
the changelog, the inbox, and arbitrary cross-referenced files. Within the
spec itself, /jig:analyze is the structured complement: its Duplication
and Terminology Drift categories catch a retracted claim surviving across
the spec's own slice files and the docs it cross-references (product-vision,
accepted ADRs, the glossary, architecture.md) — but it audits one spec's
files plus that fixed set, not the whole corpus, so it sharpens the sweep
rather than replacing it. Distinguish surviving assertions (the claim
still stated as true — must fix) from explicit retractions (the claim
named as withdrawn in a changelog or history entry — correct, and worth
keeping).
# 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) — spec 096-03: select the richer skill first.
# 1. Show the tiered candidates + write the sidecar:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
candidates pr_review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
--pass craft
# 2. Read the [high-confidence] tier, pick the single best (or `none`), then
# build the prompt with the REQUIRED --richer-skill (config overrides it):
PROMPT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
pr-review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
"<deliverable-path-1>" ... --richer-skill "<name-or-none>")
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 …
# (CI / no orchestrator: --richer-skill none --non-interactive.)
# 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
# 096-03: show candidates for arch_review, then pick (config overrides).
python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
candidates arch_review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
--pass arch
PROMPT=$(python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
arch-review "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
"<deliverable-path-1>" ... --richer-skill "<name-or-none>")
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.)
# 096-03: show candidates for code_health, then pick (config overrides).
python3 "${CLAUDE_PLUGIN_ROOT}/skills/independent-review/review.py" \
candidates code_health "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" \
--pass code-health
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 \
--richer-skill "<name-or-none>")
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.
Before landing, audit the board. docs/specs/README.md is derived — every
column is computed from the spec records, and the curated Notes column is
carried across regens — so it is regenerated, never hand-edited. A merge
conflict on it is resolved by re-running status-board, not by picking a side:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \
check-board <project-dir>
Read-only; exits non-zero on either problem it can find. Stale board — the
spec records changed and status-board wasn't re-run. Duplicate spec
number — two spec directories claim one number, which is what parallel
branches produce when the number was never reserved on the trunk. The renderer
emits both without complaint and a staleness check can't see it (both are
faithfully derived), so it needs its own detector; the message names both
directories so you know which to renumber.
Notes-column text is not drift — it is hand-written by design and survives regen. What is drift is any other cell edited by hand: it will be overwritten on the next regen, so change the slice record instead.
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. - Leanness sweep — did the implementation over-build? Confirm nothing was added beyond what the acceptance criteria required — no over-engineering, premature abstraction, or speculative generality (indirection, config knobs, or extension points with no current caller). Anchored to the spec's needs (added-beyond-need), never a push to strip behavior the spec required. Complements the arch-pass leanness lens (spec 109). Non-blocking nudge; not a gate.
- 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: 4 files
662.9 KB alongside SKILL.md, 3 of them executable
- test_spec_workflow_skill_surface.pyruns9.8 KB
- test_workflow.pyruns434.6 KB
- worked-example-spidr-split.md5.5 KB
- workflow.pyruns213.0 KB
Gives 0 of the 12 instructions most plan spec skills give
Counted across 1,360 of the 2,617 authors here whose files we hold, read 2026-09-06
- Ask one question at a timein 73 of 1360
- Write the spec using the templatein 22 of 1360
- Ask clarifying questions if neededin 19 of 1360, across 18 files
- Wait for user confirmation before proceedingin 19 of 1360
- Save plans to the plans directoryin 17 of 1360, across 13 files
- Check for product marketing context firstin 16 of 1360, across 5 files
- Read the plan file completelyin 16 of 1360
- Order tasks by dependencyin 16 of 1360
- Gather context from the conversationin 15 of 1360, across 9 files
- Explore the codebase instead of askingin 15 of 1360, across 13 files
- Wait for explicit user approvalin 14 of 1360, across 13 files
- Quiz the user on the breakdownin 13 of 1360, across 7 files
Said here and by no other author read
- SPIDR-split every non-trivial spec into vertical slices
- Reserve the next free number on origin main
- Record the use-case trace link in frontmatter
- Set design review true when visual fidelity must gate
- Ground load-bearing factual claims by executed probe
- Run independent review before advancing status
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.