--- name: spec description: Upstream planning-discipline — brainstorm a feature into spec.md + plan.md under .agent/plans//, then route to plan approval. Enforced by the spec-gate tool boundary, not prompt coercion. NOT for trivial/test/doc-only edits (the gate scopes those out), and NOT for executing an already-approved plan (that is /supervise). when_to_use: Before starting substantive implementation on a non-trivial feature or change — "spec this", "/spec ", or when spec-gate asks you to plan first before an edit. Add --interview when the request is fuzzy enough that a wrong guess would commit the spec to the wrong shape (structured question loop before writing). tools: Bash, Read, Write, Edit, Grep, Glob --- # /spec ## Goal Turn a fuzzy request into two committed artifacts — a **spec** (what/why) and a **plan** (how, in waves) — before any implementation edit, then hand the plan to the user for approval. Approval is what unlocks editing: `plan-gate.py` writes the plan-approval flag, and `spec-gate.py` (a PreToolUse gate) stops asking once that flag exists. This skill supplies the *methodology*; the *enforcement* is a tool boundary. You cannot prompt your way past `spec-gate` — you produce the artifacts and get the plan approved, or you flip the mode. That separation is the point. ## Steps ### 1. Brainstorm Explore the request before committing to a shape: a. Restate the request in one sentence and name the success criterion. b. Surface the 2-3 plausible approaches and the tradeoff between them; pick one and say why. If the request is ambiguous, ask — don't guess. c. List what's explicitly **out** of scope (prevents scope creep later). #### `--interview` — opt-in deep-interview submode The default brainstorm is a single pass. `/spec --interview` replaces step 1b's one-shot "ask if ambiguous" with a structured question loop for requests fuzzy enough that a wrong guess commits the whole spec to the wrong shape. It is opt-in by design: simple requests keep the single pass, and the enforcement story is untouched — the spec-gate tool boundary neither knows nor cares which submode produced the artifacts. 1. **Unknowns table.** List every unknown in the request; mark each **decision-changing (Y/N)** — would different answers produce different specs? N-unknowns are implementation details; leave them to the plan. 2. **Batch-question the Y rows only** — at most **4 questions per round**, each naming the options and your recommended default. 3. **Re-score after the answers.** Answers resolve rows and surface new unknowns; add those to the table and mark them Y/N. This is the decision-tree pruning: each round should shrink the Y set. 4. **Terminate on either condition** (whichever comes first): - the table has **zero open decision-changing unknowns**, or - **3 rounds** have run. Unresolved rows carry into `spec.md` under `## Open questions` — named and deferred beats silently guessed. The interview's Q/A trail is recorded in `spec.md` under `## Interview log` (one line per question: the question, the answer, the decision it settled), so the spec shows *why* it has its shape, not just the shape. #### `--score-candidates` — opt-in candidate-scoring submode Step 1b picks an approach in prose. When the harder question is *which problem or approach to commit to* — a wide field where the pick should be earned, not asserted — `/spec --score-candidates` replaces 1b's one-line "pick one and say why" with an explicit numeric pass. Like `--interview`, it is opt-in and touches only the brainstorm; the spec-gate boundary is indifferent to it. 1. **List the candidates.** Enumerate the plausible problems/approaches — the field before pruning (the reference workflow scored 18, kept 3). A field of one means you don't need this submode. 2. **Name the dimensions.** 3–5 criteria that actually discriminate (e.g. *impact*, *effort*, *risk*, *fit*). Reuse the repo's scoring idiom — each dimension scored **0–5**, aggregated per candidate — rather than coining a new scale (`docs/scoring-convention.md`, `core/infra/supervisor-goal-audit.sh`). 3. **Score and rank.** Build a candidates × dimensions table, aggregate, and take the **top-K** (usually K=1, sometimes a short list the plan sequences). The score **ranks; it never gates** — a low-scoring candidate is dropped by judgment, not auto-rejected by a threshold. 4. **Record the winner *and the cut*.** The chosen candidate becomes step 1b's pick; the runners-up and *why they lost* are the audit trail. The scoring table is recorded in `spec.md` under `## Candidate scoring`, so the spec shows the field it chose from, not just the choice. ### 2. Write `spec.md` Pick a short kebab-case `` and write `.agent/plans//spec.md`: ```markdown # — spec ## Problem ## Success criteria - ## Approach ## Out of scope - ## Candidate scoring | candidate | | | | total | verdict | |---|---|---|---|---|---| | | 5 | 4 | 5 | 14 | chosen | | | 3 | 5 | 2 | 10 | cut: | ## Interview log - Q: → A: → settled: ## Open questions - ``` The trailing sections are submode-specific — `## Candidate scoring` on `--score-candidates` runs, `## Interview log` / `## Open questions` on `--interview` runs; a single-pass spec omits them. ### 3. Write `plan.md` Write `.agent/plans//plan.md` as ordered waves — the shape `/supervise` consumes: ```markdown # — plan ## Wave 1: - → verify: ## Wave 2: - → verify: ``` Every step names its verification. A plan whose steps aren't checkable isn't done. Default each `→ verify:` to an **executable check** — a command whose exit code decides (`bash core/tests/.sh`, `grep -q `); prose verification is allowed only with a stated reason why no command can check it. ### 4. Route to approval Present the plan to the user and **exit plan mode** (ExitPlanMode). User approval is what triggers `plan-gate.py` to write `/tmp/agent-plan-approved`. From that point `spec-gate` allows edits for the rest of the session (the flag is the dedup — approve once, not per-edit). SessionStart/Stop clear the flag, so each new session starts unapproved. This skill is complete when `spec.md` and `plan.md` both exist under `.agent/plans//` and the plan has been routed through ExitPlanMode — approval itself is the user's move, not yours. ## Enforcement — the tool boundary, not prompt coercion `core/hooks/spec-gate.py` runs on every `Write|Edit|MultiEdit`. When no plan is approved this session and the target is substantive impl code (matches the scope regex, not a test/type/config/doc/meta file, not a risk area), it acts: | `AGENT_SPEC_GATE_MODE` | Behavior | |---|---| | `off` | Gate disabled — no-op. | | `dryrun` (default) | Logs the verdict to `.agent/logs/spec-gate.jsonl`; advisory only, never stops an edit. | | `block` | Emits `permissionDecision: "ask"` — surfaces the missing plan; the user approves to proceed or dispatches this skill. | **Deny vs ask.** The gate uses `ask`, not `deny`. A planning-discipline gate is reversible — the edit isn't destructive and the escape is trivial — so `ask` (the harness default for reversible gates) is more defensible than a hard `deny`, and it de-risks a false positive by leaving the user in control. ## Escapes Two, both named in the gate's own reason message: 1. **Approve a plan** via ExitPlanMode (this skill's normal path) → the flag is written and edits are allowed for the session. 2. **Set `AGENT_SPEC_GATE_MODE=off`** → disable the gate entirely. ## Hard rules - **Spec and plan before impl.** The artifacts come first; editing is what they unlock. - **Never hand-write the approval flag.** It's written by `plan-gate.py` on real approval — forging it defeats the gate. - **The scope is substantive impl code only.** Tests, types, config, docs, and `.agent/` meta are out of scope by design; risk-area paths defer to their own hooks. Don't widen the gate to nag on trivial edits.