witness-decompose — recap → sliced specs → gate
Ground rules (every witness skill)
Resolve the CLI once per session:
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.4}"
- Render the CLI's decision output verbatim and in full — every line, unmodified. Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.
- The human decides; you may type it. Run a
witness decide verb when the human names an option — its number or its verb — or gives a bare affirmation ("y", "ok", "go") while a CLI-rendered decision block carrying a recommended option is on screen: the affirmation selects the recommended option, and you append --via affirmation to the printed command. Otherwise the string is run byte-for-byte: never recomposed, never reformatted, never with a placeholder you resolved yourself. The moment you compose a --note or resolve an id, you are authoring their decision. A nod never takes --approve --override, --stop, a trust grant, or witness abandon — those require naming, and the CLI refuses them with nod-cannot. A block with no recommendation also requires naming. Questions you authored yourself (the brainstorm interview, the design converge step) are conversation, not a block: an affirmation there accepts your stated recommendation, with no selection entry, no journal claim and no flag. A selection does not survive session death: killed and re-run, render the block again and ask again.
- Never edit
specs/**, plans/**, or designs/** (the canon dirs — paths: in witness.config.yaml may relocate them) — not with an edit tool, not with a write tool, not with Bash redirection. The CLI is the sole writer of state; you author in scratch files under $(mktemp -d) and hand them to the CLI. (The canon guard blocks you; the trailer audit catches what it can't.)
- Read canon with
witness read <id>, never by path. Canon lives at the primary root; inside a worktree the files are absent by design, so a path read finds nothing and a stale copy cannot be mistaken for the contract. Fat artifacts: witness read <spec-id> --design --outline, then --lines <a>-<b>.
- Read a file before your first edit of it in this session. Relay boundaries,
verify-red's stash cycle, and worktree re-attach all change files under you — an edit against a remembered copy is how "modified since read" and partial applies happen. The CLI now prints stale-reads: when it churns the tree; treat that list as unread.
- Never invoke gate reviewers or relay verdicts.
witness gate runs reviewers itself and journals what they said; your summary of a verdict is not evidence.
- Refusal repair loop: a
witness verb exiting 2 prints structured violations (field · rule · got · want). Fix your input and retry — 3 total attempts per artifact, then stop, show the human the violation list verbatim, and end your turn.
- A refused or hook-blocked command is a stop, not a step to drop. Re-issue it on its own; if it still refuses, tell the human what was blocked and why. Never proceed by deleting the refused half of a compound command — a dropped step is silent, and silence is how a skipped check becomes a shipped defect.
- Re-entrancy: derive position from CLI output (
$WITNESS next, the dashboard, log, index) — never from conversation memory. Killed and re-run, you must converge.
Inputs (rebuild them, never remember them)
$WITNESS log <effort> # the latest recap entry is your contract: class, goals g*, non-goals n*, constraints c*
$WITNESS index # live canon: id · summary · status · depends, grouped by dir
$WITNESS decide decompose <effort> --show # ONLY when re-entered after a revise
The class comes from the recap. Never ask for it again.
Route by class
- feature — slice (next section). Expect the gate to stop for scope approval afterwards: that is the standing stop working, not a failure.
- fix — find THE one spec to amend: match the broken behavior against
witness index summaries; when summaries tie, grep the specs dir read-only at the primary root (searching across canon is not an artifact read, and there is no verb for it — the read rule governs reading an artifact you have already identified). Amend exactly one spec. If the fix genuinely needs a brand-new spec, write it — the gate's tripwire stops for a human, which is the designed check on your routing (on a young canon this fires often; say so rather than fighting it).
- chore — write NO specs (a chore writing spec content is refused at write time, by definition of the class). There is nothing here for you:
next routes a chore straight to the plan stage, because the decompose gate refuses nothing-to-gate without written specs and write refuses spec content from a chore — the stage is unsatisfiable in both directions. The parent for the coming plan is chosen when that plan is authored. Hand back immediately if you were invoked for one.
Slicing rules (feature)
- Thin vertical slices: each spec independently shippable, observable end-to-end. Never layers ("the schema", "the client").
- Each spec answers exactly one question; no two overlap; one fact, one home. A fact needed by two slices gets extracted into its own spec, and both
depends on it.
- Amending a living spec is the same write with the existing id — its status resets to
draft (re-approval owed). If the CLI warns the spec has an in-flight child plan, surface that warning at the gate stop.
- Boundary changes (split, merge, extraction out of a live spec) → the new spec's manifest carries
supersedes: <old-id>; rewrite every dependent's depends in the same effort — the CLI refuses dangling edges.
- Cross-cutting changes → sequenced per-slice amendments ordered via
depends, expand-contract style (accept both → switch → drop old), each step independently shippable.
- Self-check totality before gating: every goal covered by ≥ 1 spec, every spec covers ≥ 1 goal.
Author each spec
Manifest + body in scratch, then the write:
DIR=$(mktemp -d)
cat > "$DIR/meta.json" <<'EOF'
{
"type": "spec",
"summary": "Refresh tokens rotate before expiry",
"depends": ["auth-login"],
"needs": [],
"criteria": [
{ "id": "ac-rotate", "test": "@spec:auth-refresh" },
{ "id": "ac-smoke", "cmd": "npm run smoke:auth" }
],
"covers": ["g1"]
}
EOF
cat > "$DIR/body.md" <<'EOF'
## Motivation
Why this slice exists — the recap goal in context, one short paragraph.
## Behavior
What must observably be true, public surface only. State every `cmd:`
criterion's expected behavior and thresholds HERE — a fact that lives only
inside a script has a hidden home and drifts invisibly.
EOF
$WITNESS write auth-refresh --effort <effort> --meta "$DIR/meta.json" --body "$DIR/body.md"
Get these right the first time — the dashboard trends your first-try valid rate:
summary ≤ 120 chars: what the slice is (Motivation carries the why).
criteria: ≥ 1 entry, unique ids, each exactly one of test (value must be @spec:<this-spec-id>) or cmd. A criterion that cannot fail is not a criterion.
covers: the recap goal ids this spec honestly advances — the critic judges the mapping's quality.
- Body: exactly the
## Motivation and ## Behavior headings; behavior-only — internal interfaces never appear in a spec.
id: [a-z0-9-]+; new ids land at specs/<id>.md (or the repo's configured specs dir).
needs: external prerequisites only, machine-checkable (env: / cmd: / manual: + satisfied).
ui: set ui: true in the manifest iff the slice changes browser-rendered or interactive behavior (a screen, a form, client-side interaction). A browser-behavior slice without the flag, or a flag on a non-UI slice, is a slicing-critic finding. A ui spec earns a design stage before its plan — the look is approved before code (Decision 69).
Gate
$WITNESS gate decompose --effort <effort> # append --manual when the run asked for it
- Auto-pass (green path) → done; hand back to /witness.
- Stop (standing stop, blocking findings, fix-created-spec tripwire) → render the gate output verbatim and in full, including its ranked options and
run: line, and END YOUR TURN. You never decide.
- Re-entered after
--revise → decide --show reconstructs the verdict + human note; findings anchor to spec headings. Fix via new witness write calls (same ids amend in place), self-check totality, re-gate. The 3-round bound is the CLI's — surface it, never fight it. --show also emits state: and exits: — a reopened or settled state means the verdict above it is history, so act on the exits: line, not on remembered findings.
- Findings implicate the scope itself (goals wrong, not slicing wrong)? Tell the human that
--revise --upstream on the stop screen routes back to re-interview via witness recap --amend.
1---2name: witness-decompose3description: Slice a witness effort's confirmed recap into vertical spec slices, or route a fix to the one spec it amends — manifests handed to witness write, then the decompose gate. Chores never reach this stage. Normally invoked by /witness with the effort slug.4---56# witness-decompose — recap → sliced specs → gate78## Ground rules (every witness skill)910Resolve the CLI once per session:1112```bash13WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.4}"14```1516- **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.17- **The human decides; you may type it.** Run a `witness decide` verb when the human **names an option** — its number or its verb — or gives a bare affirmation ("y", "ok", "go") while a **CLI-rendered decision block carrying a recommended option** is on screen: the affirmation **selects the recommended option**, and you append `--via affirmation` to the printed command. Otherwise the string is run **byte-for-byte**: never recomposed, never reformatted, never with a placeholder you resolved yourself. The moment you compose a `--note` or resolve an id, you are authoring their decision. **A nod never takes** `--approve --override`, `--stop`, a trust grant, or `witness abandon` — those require naming, and the CLI refuses them with `nod-cannot`. A block with **no** recommendation also requires naming. **Questions you authored yourself** (the brainstorm interview, the design converge step) are conversation, not a block: an affirmation there accepts your stated recommendation, with no selection entry, no journal claim and no flag. A selection does not survive session death: killed and re-run, render the block again and ask again.18- **Never edit `specs/**`, `plans/**`, or `designs/**`** (the canon dirs — `paths:` in witness.config.yaml may relocate them) — not with an edit tool, not with a write tool, not with Bash redirection. The CLI is the sole writer of state; you author in scratch files under `$(mktemp -d)` and hand them to the CLI. (The canon guard blocks you; the trailer audit catches what it can't.)19- **Read canon with `witness read <id>`, never by path.** Canon lives at the primary root; inside a worktree the files are **absent by design**, so a path read finds nothing and a stale copy cannot be mistaken for the contract. Fat artifacts: `witness read <spec-id> --design --outline`, then `--lines <a>-<b>`.20- **Read a file before your first edit of it in this session.** Relay boundaries, `verify-red`'s stash cycle, and worktree re-attach all change files under you — an edit against a remembered copy is how "modified since read" and partial applies happen. The CLI now prints `stale-reads:` when it churns the tree; treat that list as unread.21- **Never invoke gate reviewers or relay verdicts.** `witness gate` runs reviewers itself and journals what they said; your summary of a verdict is not evidence.22- **Refusal repair loop:** a `witness` verb exiting 2 prints structured violations (`field · rule · got · want`). Fix your input and retry — **3 total attempts** per artifact, then stop, show the human the violation list verbatim, and end your turn.23- **A refused or hook-blocked command is a stop, not a step to drop.** Re-issue it on its own; if it still refuses, tell the human what was blocked and why. Never proceed by deleting the refused half of a compound command — a dropped step is silent, and silence is how a skipped check becomes a shipped defect.24- **Re-entrancy:** derive position from CLI output (`$WITNESS next`, the dashboard, `log`, `index`) — never from conversation memory. Killed and re-run, you must converge.2526## Inputs (rebuild them, never remember them)2728```bash29$WITNESS log <effort> # the latest recap entry is your contract: class, goals g*, non-goals n*, constraints c*30$WITNESS index # live canon: id · summary · status · depends, grouped by dir31$WITNESS decide decompose <effort> --show # ONLY when re-entered after a revise32```3334The class comes from the recap. Never ask for it again.3536## Route by class3738- **feature** — slice (next section). Expect the gate to stop for scope approval afterwards: that is the standing stop working, not a failure.39- **fix** — find **THE one spec** to amend: match the broken behavior against `witness index` summaries; when summaries tie, grep the specs dir read-only **at the primary root** (searching across canon is not an artifact read, and there is no verb for it — the read rule governs reading an artifact you have already identified). Amend exactly one spec. If the fix genuinely needs a brand-new spec, write it — the gate's tripwire stops for a human, which is the designed check on your routing (on a young canon this fires often; say so rather than fighting it).40- **chore** — **write NO specs** (a chore writing spec content is refused at write time, by definition of the class). There is nothing here for you: `next` routes a chore straight to the plan stage, because the decompose gate refuses `nothing-to-gate` without written specs and `write` refuses spec content from a chore — the stage is unsatisfiable in both directions. The parent for the coming plan is chosen when that plan is authored. Hand back immediately if you were invoked for one.4142## Slicing rules (feature)4344- Thin **vertical** slices: each spec independently shippable, observable end-to-end. Never layers ("the schema", "the client").45- Each spec answers exactly one question; no two overlap; **one fact, one home**. A fact needed by two slices gets extracted into its own spec, and both `depends` on it.46- Amending a living spec is the same write with the existing id — its status resets to `draft` (re-approval owed). If the CLI warns the spec has an in-flight child plan, surface that warning at the gate stop.47- Boundary changes (split, merge, extraction out of a live spec) → the new spec's manifest carries `supersedes: <old-id>`; rewrite every dependent's `depends` in the same effort — the CLI refuses dangling edges.48- Cross-cutting changes → sequenced per-slice amendments ordered via `depends`, expand-contract style (accept both → switch → drop old), each step independently shippable.49- Self-check totality before gating: every goal covered by ≥ 1 spec, every spec covers ≥ 1 goal.5051## Author each spec5253Manifest + body in scratch, then the write:5455```bash56DIR=$(mktemp -d)57cat > "$DIR/meta.json" <<'EOF'58{59 "type": "spec",60 "summary": "Refresh tokens rotate before expiry",61 "depends": ["auth-login"],62 "needs": [],63 "criteria": [64 { "id": "ac-rotate", "test": "@spec:auth-refresh" },65 { "id": "ac-smoke", "cmd": "npm run smoke:auth" }66 ],67 "covers": ["g1"]68}69EOF70cat > "$DIR/body.md" <<'EOF'71## Motivation7273Why this slice exists — the recap goal in context, one short paragraph.7475## Behavior7677What must observably be true, public surface only. State every `cmd:`78criterion's expected behavior and thresholds HERE — a fact that lives only79inside a script has a hidden home and drifts invisibly.80EOF81$WITNESS write auth-refresh --effort <effort> --meta "$DIR/meta.json" --body "$DIR/body.md"82```8384Get these right the first time — the dashboard trends your first-try valid rate:8586- `summary` ≤ 120 chars: what the slice **is** (Motivation carries the why).87- `criteria`: ≥ 1 entry, unique ids, each exactly one of `test` (value must be `@spec:<this-spec-id>`) or `cmd`. A criterion that cannot fail is not a criterion.88- `covers`: the recap goal ids this spec honestly advances — the critic judges the mapping's quality.89- Body: exactly the `## Motivation` and `## Behavior` headings; behavior-only — internal interfaces never appear in a spec.90- `id`: `[a-z0-9-]+`; new ids land at `specs/<id>.md` (or the repo's configured specs dir).91- `needs`: external prerequisites only, machine-checkable (`env:` / `cmd:` / `manual:` + `satisfied`).92- `ui`: set `ui: true` in the manifest **iff** the slice changes browser-rendered or interactive behavior (a screen, a form, client-side interaction). A browser-behavior slice **without** the flag, or a flag on a non-UI slice, is a slicing-critic finding. A `ui` spec earns a **design stage** before its plan — the look is approved before code (Decision 69).9394## Gate9596```bash97$WITNESS gate decompose --effort <effort> # append --manual when the run asked for it98```99100- **Auto-pass** (green path) → done; hand back to /witness.101- **Stop** (standing stop, blocking findings, fix-created-spec tripwire) → render the gate output verbatim and in full, including its ranked options and `run:` line, and END YOUR TURN. You never decide.102- **Re-entered after `--revise`** → `decide --show` reconstructs the verdict + human note; findings anchor to spec headings. Fix via new `witness write` calls (same ids amend in place), self-check totality, re-gate. The 3-round bound is the CLI's — surface it, never fight it. `--show` also emits `state:` and `exits:` — a `reopened` or `settled` state means the verdict above it is history, so act on the `exits:` line, not on remembered findings.103- Findings implicate the **scope itself** (goals wrong, not slicing wrong)? Tell the human that `--revise --upstream` on the stop screen routes back to re-interview via `witness recap --amend`.