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.
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.