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.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
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.
user-invocable
true
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.md when 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 .codex/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 the use_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 cases section.
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
.codex/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.
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 (no
scaffold.json) → tell the user to run /jig:migrate. It adopts the
existing layout into jig structure.
You don't have to decide the state yourself: workflow.py new (step 2)
classifies and routes for you (spec 063-01) — a scaffold.json-bearing
project proceeds; a greenfield project is refused naming
/jig:scaffold-init; an adoptable spec-driven project is refused naming
/jig:migrate. The deterministic gate and this human-readable
precondition agree by construction, so don't restate the detection
heuristic here — run the helper and let it route. (Bypass for a
deliberate out-of-band flow: JIG_SCAFFOLD_PRECONDITION=0.)
The anti-pattern this step exists to kill: an auto-triggered
spec-workflow run improvising a loose slices/ folder (or any ad-hoc
docs/specs/ skeleton) because /jig:scaffold-init was 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.
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 cases section as framing — before drafting
(spec 068-02 / ADR-0025).
If the project's docs/product-vision.md carries a ## Use cases section
(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 cases section, 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's use_cases: frontmatter (step 2a).
1b. Cross-check tracked bugs before drafting ACs. For feedback/triage
specs, read docs/bugs/README.md and any overlapping docs/bugs/NNN-*.md
record before turning reported symptoms into acceptance criteria. If an
item is a tracked defect with a bug record, route it through bug-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.
Reserve the next free number on origin/main:
python3 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" new <slug>
The helper computes max(NNN) + 1 across docs/specs/, writes a
minimum stub docs/specs/NNN-<slug>/spec.md (frontmatter + Overview
SPIDR-analysis headers), commits it as
docs(specs): reserve NNN-<slug>, and pushes to origin/main. If
the push is refused by branch protection / permissions, the helper
automatically falls back to a reserve/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 main it runs the proven
in-place flow (clean tree required, since the commit lands on local
main); off main — a feature branch or a linked .codex/worktrees/*
worktree — it builds the reservation commit in an ephemeral detached
worktree checked out at origin/main and pushes it by SHA, never
touching your branch, cwd, or working tree. You no longer need to
switch to main (and a linked worktree can't, anyway).
Flags: --no-push for 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); --pr to 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 empty use_cases: frontmatter list. Fill it with the
UC-N id(s) (from the vision ## Use cases section, step 1a) this spec
serves — the dependencies:-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 — the
classify_spec predicate in
skills/_common/use_cases.py computes one of
no_section / empty / resolved / unresolvable for this spec — not a
voluntary "is this new?" self-report. Whenever classify_spec returns empty
or unresolvable (the spec cites nothing, or cites a UC-N with 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-N id(s) in use_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 free UC-N (use_cases.next_use_case_id allocates max + 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 in
use_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_spec returns no_section
(the project has no## Use cases section — 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 on empty /
unresolvable, which presuppose the section exists.
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.
For each new slice, use the template at
templates/docs/specs/slice-template.md — it ships the canonical
frontmatter shape (status, dependencies, last_verified) plus
DoR / AC / DoD / Close-out sections. Set status: DRAFT in 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 ## Assumptions section, 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 ## Assumptions you surface here has
downstream value — slice 064-04 derives the frame_review trigger
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## Assumptions A1–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 ## Assumptions you
just surfaced decide it, mechanically. Set the slice's frame_review flag
from workflow.py frame-review-needed:
The rule is a derivation, not a judgment call: true iff the slice's
## Assumptions section 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 (## Assumptions absent or just "None") stays
default-off. ADRs are always-on (OQ3): any ADR gets frame_review: true unconditionally — the deriver returns true for any adr-*.md
path. When the value is true, set frame_review: true in the slice
frontmatter so the gate + session-plan dispatch the pass.
Add rows to docs/specs/README.md (or regenerate via workflow.py status-board).
Picking up a slice
Read the automatic jig hint: project-orientation headline injected at
SessionStart, or refresh it manually before choosing work:
The headline is computed from scaffold.json and lifecycle artifacts. Treat
docs/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.md for the next slice in READY_FOR_IMPLEMENTATION
(or DRAFT for a slice you intend to plan now).
Claim-on-IN_PROGRESS (spec 049-01). On a frontmatter (file-per-slice)
slice this stamps claimed_by: (the current branch name, or
JIG_CLAIM_ID) so parallel worktrees don't both pick up the same slice.
It refuses if the slice is already claimed by a different identifier and
still IN_PROGRESS — naming the holder and pointing at --release. The
claim is local by default; add --push (direct) or --pr (via PR) to
reserve it on origin/main so other worktrees see it (race / protected-
branch handling mirrors workflow.py new). The claim is cleared on the
forward move to REVIEWED and on any back-transition to
READY_FOR_IMPLEMENTATION / DRAFT. To force-release a stale claim:
transition <spec> <slice> READY_FOR_IMPLEMENTATION --release --reason "<why>" (clears claimed_by:, logs to the slice's ## Release log).
Fill in / refresh plan.md and tasks.md for the slice.
Spawn the implementer subagent with the spec path. Prefix the Task prompt
with [jig:phase=implementation] [jig:spec=NNN] [jig:slice=NNN-NN] so
jig-telemetry.sh can 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 the
reviewer subagent against the deliverable using the prompt built by
review.py implementation. Reviewer is read-only; it evaluates each
acceptance criterion and returns
pass | fail | needs-changes.
Craft pass — pr-review (always). After the compliance pass
returns, build the craft-pass prompt with review.py pr-review and
spawn a second reviewer-shaped subagent. The reviewer is read-only
(Read/Glob/Grep, no Skill tool), so it cannot route to a skill
via Codex's skill router; instead review.py detects a user-installed
pr-review skill on disk ($HOME/.agents/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 same VERDICT / REASONING / SPECIFIC ISSUES / RECONCILIATION NOTES envelope 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's arch_review: frontmatter flag via
workflow.py arch-review-needed. When the helper prints true,
build the arch-pass prompt with review.py arch-review and spawn a
third reviewer-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.py detects $HOME/.agents/skills/arch-review/,
else inlines jig's baseline buckets). When the helper prints false, skip this
pass entirely. Slice authors flip the flag by uncommenting the
arch_review: true line 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's code_health_review: frontmatter flag via
workflow.py code-health-review-needed. When it prints true, run
health.py yourself (the orchestrator / CI), capture its tight
summary, and feed THAT summary into review.py code-health … --summary-file
(or via stdin). Then spawn a reviewer-shaped subagent. The reviewer
is read-only (Read/Glob/Grep, no Bash) — it must NOT run health.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 with code_health_review: true. The evidence file is
reviews/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 fail verdict from any pass blocks the transition.
needs-changes from the compliance pass blocks (the implementer
addresses findings and re-runs).
needs-changes from 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 and
needs-changes become reconciliation-log items.
The code-health pass follows the same rule: [blocker]-tagged entries
block the REVIEWED transition; [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 (writes
docs/specs/NNN-<slug>/reviews/slice-NN-<pass>.md — see the
independent-review SKILL.md § "Recording and checking review
evidence"). The REVIEWED transition is gated on this evidence, so it
is not optional.
Transition: transition <spec.md> <slice> REVIEWED. The gate
re-validates the recorded compliance + 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 "${PLUGIN_ROOT}/skills/independent-review/review.py" \
implementation "docs/specs/NNN-<slug>/spec.md""<slice-fragment>" \
"<deliverable-path-1>" ...)
SUBAGENT=$(python3 "${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 "${PLUGIN_ROOT}/skills/independent-review/review.py" \
pr-review "docs/specs/NNN-<slug>/spec.md""<slice-fragment>" \
"<deliverable-path-1>" ...)
SUBAGENT=$(python3 "${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 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \
arch-review-needed "docs/specs/NNN-<slug>/spec.md""<slice-fragment>"); thenecho"arch-review-needed failed — aborting" >&2
exit 2
fiif [ "$NEED_ARCH" = "true" ]; then
PROMPT=$(python3 "${PLUGIN_ROOT}/skills/independent-review/review.py" \
arch-review "docs/specs/NNN-<slug>/spec.md""<slice-fragment>" \
"<deliverable-path-1>" ...)
SUBAGENT=$(python3 "${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 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \
code-health-review-needed "docs/specs/NNN-<slug>/spec.md""<slice-fragment>"); thenecho"code-health-review-needed failed — aborting" >&2
exit 2
fiif [ "$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 "${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 "${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, then
transition <spec.md> <slice> RECONCILED. That move is gated on the
recorded reconciliation verdict (pass) and### Deviation log
plus ### Reconciliation sweep subsections under the slice heading
(ADR-0014 §5 + ADR-0029).
Commit the work.
After commit: transition <spec.md> <slice> DONE. DONE re-validates
the whole evidence set — compliance + craft (+ arch,
code-health) + reconciliation — plus the deviation log and
reconciliation sweep, on top of the existing
dependencies: check.
Regenerate the board: workflow.py status-board <project-dir>.
Run /jig:memory-sync (or memory.py) to consolidate any new learnings.
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 → DEFERRED is 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-DONE state → ABANDONED is allowed.
DONE → ABANDONED is 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-DONE work is a different, unbuilt concept.
ABANDONED → DRAFT (re-open) is allowed.
ABANDONED → any other state is refused — re-open via DRAFT first,
mirroring DEFERRED'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 written from templates/docs/specs/slice-template.md 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. Legacy specs that embed ## Slice sections inside spec.md
(heading-first, frontmatter-after) remain supported by every helper —
no forced migration.
status — current lifecycle state. workflow.py transition updates
this when present.
dependencies — flow-style list of slice fragments (e.g. 007-02)
and ADR IDs (e.g. adr-0004). transition <slice> DONE refuses if
any listed dependency is not DONE / accepted.
last_verified — date the slice was last reconciled. transition
stamps this automatically on → RECONCILED. Used by stale.
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 / deferred dispositions. 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.md AND write an ADR.
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.
Conventions impact — did this slice introduce or change a rule worth
recording? If yes, edit docs/conventions.md (requires
JIG_CONVENTIONS_APPROVED=1).
Inbox triage — sweep docs/inbox.md for 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: AGENTS.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 (which
workflow.py status-board preserves across regen), memory, or the
reconciled spec/slice record.
Memory-sync — run /jig:memory-sync (or invoke memory.py directly)
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 (see agents/reviewer.md).
Closed-spec drift — if reconciliation surfaces a prior
closed-spec inaccuracy (a DONE / SUPERSEDED spec/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 ## Amendments entry 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.
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 block RECONCILED / 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 cases section.
Commit — only after all gates pass.
Auditing staleness (workflow.py stale)
Slice 015-03 added a read-only freshness audit:
python3 "${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 by dependencies was modified
since last_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 transition uses substring matching on slice names — 001-01
matches ## Slice 001-01 — greenfield-scaffold. If you have multiple slices
whose names share a fragment, the helper refuses with an ambiguous error;
use a more specific fragment.
workflow.py status-board preserves the preamble before the | Spec table
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 slices table below the active table; only the active table preserves Notes.
workflow.py status-board refuses to overwrite on a mid-regen race (slice
028-03). The helper captures a SHA256 of docs/specs/README.md at the start of
regen and re-checksums right before the write; if another writer mutated the file
in the gap, it raises StatusBoardRaceError and exits 4 with the message
status board changed during regen — another writer may have run. Re-run workflow.py status-board to retry.. Pass --force to 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.py ignores ## Spike headers. 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 of docs/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 with DEFERRED → DRAFT first, then advance through the normal
lifecycle. This prevents silently skipping review gates when a parked
slice is picked back up.
transition <slice> DONE validates dependencies:. 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 / missing dependencies: skips the check.