# Spec Workflow

> >

- **Type:** Skill
- **Install:** `agentstack add skill-ramboz-jig-spec-workflow`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ramboz](https://agentstack.voostack.com/s/ramboz)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ramboz](https://github.com/ramboz)
- **Source:** https://github.com/ramboz/jig/tree/main/hosts/codex/plugins/jig/skills/spec-workflow

## Install

```sh
agentstack add skill-ramboz-jig-spec-workflow
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

> 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`](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`](../../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

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

1. 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](../../docs/decisions/adr-0024-reference-reframe.md)).
1a. **Read the vision `## Use cases` section as framing — before drafting
   (spec 068-02 / [ADR-0025](../../docs/decisions/adr-0025-use-cases-breadth-layer.md)).**
   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.
2. **Reserve the next free number on origin/main:**

   ```bash
   python3 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" new 
   ```

   The helper computes `max(NNN) + 1` across `docs/specs/`, writes a
   minimum stub `docs/specs/NNN-/spec.md` (frontmatter + Overview
   + SPIDR-analysis headers), commits it as
   `docs(specs): reserve NNN-`, and pushes to `origin/main`. If
   the push is refused by branch protection / permissions, the helper
   automatically falls back to a `reserve/NNN-` 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](../../docs/decisions/adr-0025-use-cases-breadth-layer.md)).**
   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](../../docs/decisions/adr-0011-spec-gate-model.md)).
   But it is not silent.** The trigger is **mechanical and deterministic** — the
   `classify_spec` predicate in
   [`skills/_common/use_cases.py`](../_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.
3. Create `docs/specs/NNN-/{spec.md,plan.md,tasks.md}` with the conventional
   structure: status frontmatter, overview, SPIDR analysis, ordered slices.
4. 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.
5. 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.
6. **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](../../docs/decisions/adr-0020-spec-frame-hardening.md)
   `## Assumptions` A1–A4 + `## Kill criteria`, and the
   [spec 064-01 retro](../../docs/specs/064-spec-frame-hardening/retro.md),
   which probe-verified its three most load-bearing claims before recording
   them.)
7. **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`:

   ```bash
   python3 "${PLUGIN_ROOT}/skills/spec-workflow/workflow.py" \
     frame-review-needed "docs/specs/NNN-/spec.md" ""
   ```

   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 (`#

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ramboz](https://github.com/ramboz)
- **Source:** [ramboz/jig](https://github.com/ramboz/jig)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-ramboz-jig-spec-workflow
- Seller: https://agentstack.voostack.com/s/ramboz
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
