agentsclimarketplace

Spec workflow

Skill ramboz/jig/hosts/codex/plugins/jig/skills/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

Install
npx -y skills add ramboz/jig --skill spec-workflow

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

44.7 KB, ~11.6k tokens by cl100k_base, as published. Nobody here has run it

Spec 003 promoted this skill from stub to active. The deterministic state mutations live in workflow.py; this SKILL.md drives the judgment layer.

What this skill does

  • Guides SPIDR-splitting a new spec into vertical slices (Spike last, not first — try Rules / Data / Interface / Path first).
  • Flags slices that look like horizontal phasing (no user-facing layer touched).
  • Drives the spec lifecycle state transitions via workflow.py.
  • Coordinates implementer + reviewer subagent invocations at the right points.
  • Enforces the reconciliation checklist before a slice goes DONE.
  • Consults docs/memory/glossary.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.

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

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

  2. 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:reframe before 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.
  3. 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.

  4. Create docs/specs/NNN-<slug>/{spec.md,plan.md,tasks.md} with the conventional structure: status frontmatter, overview, SPIDR analysis, ordered slices.

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

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

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

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

    python3 "${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: 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.

  9. Add rows to docs/specs/README.md (or regenerate via workflow.py status-board).

Picking up a slice

  1. Read the automatic jig hint: project-orientation headline injected at SessionStart, or refresh it manually before choosing work:
    python3 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" orient \
      --project-dir .
    
    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.
  2. Check docs/specs/README.md for the next slice in READY_FOR_IMPLEMENTATION (or DRAFT for a slice you intend to plan now).
  3. Run:
    python3 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" transition \
      "docs/specs/NNN-<slug>/spec.md" "<slice-fragment>" IN_PROGRESS
    
    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).
  4. Fill in / refresh plan.md and tasks.md for the slice.
  5. 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:

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

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

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

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

  1. Address any reviewer findings, adding regression tests for any real bugs found.
  2. 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.
  3. 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>"); then
  echo "arch-review-needed failed — aborting" >&2
  exit 2
fi
if [ "$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>"); then
  echo "code-health-review-needed failed — aborting" >&2
  exit 2
fi
if [ "$NEED_CH" = "true" ]; then
  # Run the jig:code-health runner yourself (health.py check .) and capture
  # its tight summary to /tmp/health-summary.txt — the read-only reviewer
  # MUST NOT run it. (The runner ships with the Tier-1 jig:code-health skill;
  # if it isn't installed, note "summary unavailable" and judge on the
  # deliverables.)
  PROMPT=$(python3 "${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

  1. 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).
  2. Commit the work.
  3. 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.
  4. Regenerate the board: workflow.py status-board <project-dir>.
  5. Run /jig:memory-sync (or memory.py) to consolidate any new learnings.

Spec lifecycle states

DRAFT → READY_FOR_REVIEW → READY_FOR_IMPLEMENTATION → IN_PROGRESS
  → REVIEWED → RECONCILED → DONE

         DEFERRED ⇄ DRAFT   (parked slices with a stated resolution trigger)
         ABANDONED ⇄ DRAFT  (permanently dropped slices, pre-DONE only)

Status transitions are mutations on either spec.md's frontmatter status: field (new convention, slice 015-01) or the prose **STATUS: ...** line (legacy — still supported via lazy migration), AND the matching row in docs/specs/README.md. Use workflow.py transition for the spec mutation and workflow.py status-board to re-sync the board.

Spec-level status: is derived, not authored (slice 030-01; widened by slice 085-01). The frontmatter status: at the top of each spec.md overview file is computed by compute_spec_status(spec_path) from its slices: DONE when every slice, excluding DEFERRED/ABANDONED ones, is DONE (a mix of DONE + DEFERRED and/or ABANDONED still rolls up to DONE); ABANDONED when every slice is ABANDONED (the spec's entire scope was dropped); DRAFT when no slices exist, every slice is DEFERRED, every non-DEFERRED slice is DRAFT, or the only non-DONE slices are a DEFERRED+ABANDONED mix with no live work; otherwise IN_PROGRESS. The rollup write happens automatically inside workflow.py transition (after the slice mutation) and inside workflow.py status-board (during regen). Don't set spec.md's status: by hand — it'll be overwritten on the next transition or regen anyway.

DEFERRED state

A slice is DEFERRED when scoped but parked — the work is identified but not the current priority. Different from DRAFT which means "not yet fleshed out." Transitions:

  • Any state → DEFERRED is allowed.
  • DEFERREDDRAFT (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.
  • DONEABANDONED 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.
  • ABANDONEDDRAFT (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: DRAFT
dependencies: [007-02, adr-0004]
last_verified:
---
  • 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 names001-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 &#124; 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.

What ships with it: 2 files

218.5 KB alongside SKILL.md, 1 of them executable

Keep looking

Skills are one crate of 326,861. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.