---
name: planning-workflow
description: "Extracted section: the planning half — intake → feature create → AC generation → feature check gate → decomposition → batch-create gate → refine. The full step-by-step procedure for turning a vague description into a validated, decomposed feature."
see_also:
  - spur-dev
  - decomposition
  - ac-style-guide
---

# Planning Workflow

```
vague description
  → intake (clarify scope, constraints — prompt work)
  → spur feature create … ; AC authored/generated (spur agent run, bdd templates)
  → GATE: spur feature check   (BDD validator; loop until clean)
  → decomposition (prompt work) → task-batch JSON
  → GATE: task-batch.schema.json + spur task batch-create (atomic: all-or-nothing)
  → design doc (conditional: seam heuristic under default; skip with --skip-design) → docs/design/<slug>.md + 04 index
```


The planning half transforms a description into a validated feature file with acceptance
criteria, then decomposes it into a batch of tasks. Two CLI gates make LLM regressions
unable to corrupt the corpus: `spur feature check` validates the AC, and
`task-batch.schema.json` validates the decomposition shape before `spur task batch-create`
writes anything.

## Step 1: Intake

When given a feature description, clarify before creating:

1. **Scope** — what is in, what is explicitly out. Record both.
2. **Constraints** — existing systems it must work with, performance/security boundaries.
3. **Success criteria** — how will we know it's done? (Feeds AC generation.)

Ask only what is ambiguous. A crisp description needs no Q&A loop — proceed directly to feature
creation.

## Step 2: Feature creation + AC generation

```bash
spur feature create "<name>" [--parent <id>]
```

Without `--parent`, choose one before creating: list the tree (`spur feature list --json`) and nest the
feature under the existing feature that owns the surface or its design satellite. A new root feature
is a last resort that needs an explicit operator decision. When a parent is full (the 9-child limit),
nest under the closest owning child, and use `spur feature move <id> --parent <id> --dry-run` to
relocate an existing feature.

The feature file lands in `docs/features/<ID>_<slug>.md`. Immediately author the `## Goal`
(single sentence) and `## Scope` (in/out bullets).

Then generate **BDD acceptance criteria** in the `## Acceptance Criteria` section using the
Gherkin template. Conventions:

- **R-numbered scenarios:** each scenario carries an `R1, R2, …` prefix in its title for
  cheap, human-readable traceability. (Coverage matching is by **normalized scenario title** —
  the R-prefix is stripped before matching — so keep the title text stable, not just the number.)
- **Two AC tiers:** core scenarios (the must-pass gate) and edge-case scenarios (advisory
  warnings — the permissive start, per DD-06). Mark edge-case scenarios explicitly.
- **Scenario-title mapping:** the scenario title is the identity key for traceability edges
  to task files — keep them stable and unique.
- Generate AC inline (current session) with the BDD template
  (`templates/bdd/gherkin.md`), or escalate to `spur agent run` only when a
  [subprocess trigger](cross-cutting.md#inline-default-execution-surface) applies. **Thread
  `--agent` through** when the command forwarded one. See the
  [inline-default execution-surface contract](cross-cutting.md#inline-default-execution-surface).

Full authoring conventions: see [ac-style-guide.md](ac-style-guide.md).

## Step 3: Feature check gate (loop)

```bash
spur feature check <id> --json
```

The BDD validator gate. A non-zero exit means findings:

1. Read each finding from the JSON output.
2. Fix the **specific** AC issue — never restructure unrelated scenarios.
3. Re-run. Loop until exit 0.

This gate catches: malformed Gherkin, missing required scenario elements, traceability gaps.
A skill regression can never corrupt the corpus — worst case is a rejected write with findings
the skill can react to.

## Step 4: Decomposition

**Default: do not decompose.** A clean feature does not automatically become many tasks. The
default outcome is one task whose `## Plan` carries the implementation steps; splitting is the
exception you justify with the rubric. See the `sp:spec-decomposition` skill ("Default to NOT
decomposing") — read it before this step.

1. **Score the feature with the rubric first.** Compute E/D/L/C/R for the *whole* feature. Record
   the assessment (this is a required artifact, not optional):
   - **Score 0–2 → keep as one task.** Stop. Author one task (Background + Requirements from the
     feature, the implementation steps as its `## Plan`). Do NOT call `batch-create`. You're done
     with decomposition.
   - **Score 3–4 → decomposition optional.** Prefer one task with a rationale unless a clear
     deliverable boundary justifies a split.
   - **Score 5+, or a force-decompose override fires → decompose.** Continue to step 2.
2. **Only now design the split** — by deliverable boundary, never by phase or by list-item. Read
   the feature's scenarios (the AC); each resulting task maps to ≥1 scenario by title.
3. **Score each candidate child.** Any candidate that scores 0–2 on its own is a `## Plan` step of
   a sibling, not a task — merge it. This is the check that stops a 6-item finding list becoming 6
   tiny tasks.
4. Produce a **task-batch JSON** document conforming to `task-batch.schema.json` — a top-level
   JSON **array** of strict task items (no `tasks` wrapper, only documented fields).

Decomposition heuristics (apply only after deciding to decompose):
- **One task = one atomic unit of work** a single agent can complete (target 2–8h; never <2h).
- **Scenario coverage:** every core scenario maps to ≥1 task; edge-case scenarios may map
  or be deferred.
- **Sub-tasks:** record `parent_wbs` (quoted, e.g. `"0042"`) for sub-tasks; note ordering in
  `background` prose (the item schema has no `dependencies` field).
- **Template variants:** choose `feature-impl` for implementation tasks (pulls Goal →
  Background from the linked feature, per B09).
- **Record the rubric score** in each child's `background` (a trailing line, e.g.
  *"Rubric: E2 D1 L1 C1 R2 = 7 → decompose (force: R=high)."*) — the assessment artifact the
  gate below checks for.

The batch JSON is the LLM→CLI contract — see the `sp:spec-decomposition` skill for the
full schema and conventions.

## Step 5: Batch-create gate

**Pre-check (soundness, before the CLI gate).** Re-read each item in the batch JSON against the
rubric before submitting. Specifically:

- Would this item score 0–2 on its own? → it is a Plan step; merge it into a sibling or back into
  the parent. Do not submit it as a task.
- Is the batch one-item-per-list-entry (one finding → one task, one scenario → one task)? → you
  are reflex-decomposing. Re-score and merge.
- Does each item carry its rubric-score line in `background`? → if not, the assessment is missing;
  score it before submitting.

Only then run the CLI gate:

```bash
spur task batch-create --file <batch.json>
```

Atomic: all-or-nothing. If `task-batch.schema.json` validation fails, **nothing is written**
and findings are returned. The schema validates shape only (it cannot reject over-decomposition —
that is the pre-check's job above). Common shape failures:

- Missing required fields per the template variant's section matrix.
- Invalid status values (must be lowercase canonical).
- `feature_id` referencing a non-existent feature.
- WBS collisions (already-allocated range).

Loop until the command exits 0 — then the batch is created and each task appears in the
feature's `## Tasks` block on next `spur feature refresh --feature <id>`.

**If you decomposed a parent task into sub-task files** (the children carry `parent_wbs`), the
sub-task roster is **auto-generated** — `spur task batch-create` invokes `spur task refresh-roster`
for each distinct `parent_wbs` after the atomic create lands (task 0178 F1), and transitions
decomposed parents from `todo` to `wip` (task 0178 F2). You do **not** hand-write the roster;
re-run `spur task refresh-roster <parent-wbs>` to re-emit it after a child status change outside
`batch-create`. See `sp:spec-decomposition` ("Parent (umbrella) tasks") for the roster format and
the parent-completion rule.

## Step 5.5: Design package (unified `--skip-design` only)

Two design surfaces, **one operator opt-out** (aligned with `/sp:dev-idea`):

| Surface | What | Default |
|---------|------|---------|
| **Feature satellite** | `docs/design/<slug>.md` + `04_DESIGN.md` index | On when seam heuristic fires; **ties lean design**; off only with `--skip-design` |
| **Task `### Design`** | Per-task WHAT/WHY in each batch item's `design` field | **On by default** at decompose/batch-create; off with `--skip-design` |

There is **no** `--design` force flag. Design is **on by default**; **`--skip-design`** is the only
opt-out.

**`--skip-design` (unified):** skip the feature satellite **and** leave task `design` empty (scaffold
only). Refine is the **fallback** that fills blank Design before implement.

| Flags | Feature satellite | Task `### Design` in batch |
|-------|-------------------|----------------------------|
| (default / `--auto`) | seam heuristic (ties lean **design**) | **author `design` on each item** |
| `--skip-design` | skip | **omit `design`** (refine later) |

A task's in-file `### Design` is code-level and narrow. The feature satellite is cross-cutting. Both
are part of the same planning "design package" controlled by `--skip-design`.

**The seam heuristic (default / `--auto` decision).** A design doc is warranted when the feature
introduces an **ADR-worthy** change — anything that shifts a boundary another engineer must reason
about:

- a **new command** or a new flag that changes a command's contract,
- a **new module / package / service** (a new `apps/*` or `packages/*`, a new app-layer service),
- a **new schema** — a DB table/migration, a Zod config key, a DTO/contract shape,
- a **new transport / boundary** — an oRPC seam, an auth boundary, a job-queue or EventBus topic.

If the work is clearly internal to one module, a bug fix, a doc/chore, or a refactor with no boundary
change, **skip** — note the skip in the report. **When in doubt, lean design** (same ties rule as
idea-path `needs_design`). Use `--skip-design` only when the operator wants no satellite and blank
task Design.

**Authoring (skill-prose — no CLI verb).** The `04` index is a hand-curated derived doc
(constitution §4.5 rule 4 / §6.5), so write it directly, in the fixed **detail-first then index**
order (§4.5 rule 5 / sync trigger **T9**):

1. **Satellite first.** Write/update `docs/design/<slug>.md`. `<slug>` is the stable grep anchor —
   derive it from the feature name (kebab-case), and **reuse the existing slug** on re-runs. For a
   new file use [document-authoring.md](document-authoring.md) and its design template; for an
   existing file preserve its headings and anchors. Capture the chosen approach + one-line reason,
   rejected alternatives, key interface/type **signatures** (not bodies), invariants, and the
   surface it touches.
2. **Index second.** Add or update the satellite's row in `docs/04_DESIGN.md §0` (the `| Satellite |
   Area | Status |` table) — pointer + one-line area + status only, never a restatement of the body.

**Idempotency (re-runnable).** `/sp:dev-plan` may run many times for one feature. If the
satellite already exists: **update in place** — merge new design content into its sections, refresh
its `updated_at`, and leave its existing `04` index row alone (or adjust only its status). **Never**
overwrite the whole file, create a second satellite, or add a duplicate index row. The invariant
(§4.5 rule 1): exactly one `04 §0` row per satellite, every satellite reachable from exactly one row.

**Report (no confirmation pause).** Under `--auto`, generation is autonomous — when the heuristic
fires, author the doc and **report** the chosen slug and a one-line rationale ("authored
`docs/design/<slug>.md` — new `spur <noun>` command + config key"); when it does not fire, report the
skip and why. Do not pause to ask; the operator reviews the satellite afterward.

## Step 5.6: Idea pipeline (sp:dev-idea) — planning handoff contracts

`/sp:dev-idea` runs the same planning half through the idea-pipeline workflow definition
(`idea-pipeline.yaml` — resolved from an explicit project path or the bundled tree). Two
artifacts are the contract between the workflow states and the operator:

**Goal/Scope intent (feature-create).** The agent writes body-only intent files
`.spur/run/<runId>-idea-goal.md` and `.spur/run/<runId>-idea-scope.md`, then persists both through
the corpus CLI (a missing/empty artifact stops the state before decomposition can begin):

```bash
spur feature update <id> --section Goal  --from-file .spur/run/<runId>-idea-goal.md
spur feature update <id> --section Scope --from-file .spur/run/<runId>-idea-scope.md
```

- **Goal is intent only** — a short statement of what the feature achieves. Task breakdowns,
  checklists, and how-to steps **never** enter Goal.
- **Scope carries explicit boundaries** — in-scope and out-of-scope bullets.

**Design review artifact (system-design / design-approval).** One run-scoped file,
`.spur/run/<runId>-idea-design-review.md`, with fixed headings `## Proposed design`,
`## Operator feedback`, and `## Reconciliation`:

- **First pass:** system-design writes the proposed design under `## Proposed design`.
- **Rejection:** before answering `no` at the design-approval gate, the operator edits
  `## Operator feedback` with the concrete issue(s).
- **Retry:** system-design reads that feedback, revises the design/ADR artifacts, records the
  changes under `## Reconciliation`, and — when the feedback invalidates an Acceptance Criteria
  scenario — updates the feature AC through `spur feature update <id> --section
  "Acceptance Criteria" --from-file <file>` (never direct feature-file edits).
- **Exit gate:** every design exit into decomposition (auto-approved and interactive-approved)
  re-runs `spur feature check <id>`, so stale or invalidated AC cannot proceed.

**Task ordering (decompose / handoff-finalize).** Decomposition also emits the private
run-scoped order sidecar `.spur/run/<runId>-idea-task-order.json` — a JSON array of
`{ name, depends_on_names[] }` (one entry per batch item, names matching batch item `name`s
exactly; `[]` when no ordering exists). It is private workflow data, not part of
`task-batch.schema.json`. A post-decompose validation fails the run on any ambiguous or
missing title match. After batch creation, `handoff-finalize`:

1. Zips batch item names to the created WBS values from the captured
   `.spur/run/<runId>-idea-batch-create-result.json` (`task batch-create --json` output;
   `wbs[]` is in input order).
2. Applies every non-empty `depends_on_names` list through
   `spur task deps <wbs> set <dep-wbs...> --json` — a mapping or CLI error fails the run
   before handoff.
3. Refreshes the feature roster with `spur feature refresh --feature <id> --json`.
4. Checks each created task (`spur task check <wbs> --json`) and writes
   `.spur/run/<runId>-idea-handoff.md` with exactly **one** next command:
   `/sp:dev-refineall --feature <id> --auto --depth ready` when any task is unready
   (runall is then omitted), otherwise `/sp:dev-runall --feature <id> --auto`. The terminal
   handoff note points at this report.

**Ready preparation (ready-prepare, 0788).** The input is the `.wbs` array in
`.spur/run/<runId>-idea-batch-create-result.json`. For EACH wbs: resolve the task file with
`spur task path <wbs> --json` and apply the ready-refinement checklist — make requirements,
design, plan, acceptance criteria, decisions, dependencies and premises present and
non-placeholder so `spur task check <wbs> --json` exits 0. Write planning sections only, through
`spur task update <wbs> --section <Name> --from-file <file>` — never Solution, Testing, Review
or History. Record one checklist row per id, with concrete evidence of how you verified it.
The `premises` row passes only when each material premise was read against the current tree:
identify each material premise in the task Background, open the cited file at the cited line and
confirm the claim, then record the evidence as verified `path:line` citations — handoff-finalize
lints the row and degrades the task to ready refine when the evidence carries no citation or
cites a missing file or an out-of-range line.
Compute the planning digest with the project's own implementation when this is a monorepo
checkout — resolve the file with `spur task path <wbs> --json`, then run:

```bash
bun -e 'const m = await import("./packages/app/src/services/task-readiness"); console.log(m.computePlanningDigest(await Bun.file(process.argv[1]).text()))' <task-file>
```

When that is impossible in this checkout, set status `skipped` instead of guessing a digest.
Finally write `.spur/run/<runId>-idea-ready.json` with exactly this shape:

```json
{"runId":"<runId>","depth":"ready","tasks":[{"wbs":"<wbs>","status":"ready" | "failed" | "skipped","planningDigest":"<sha256 hex>","checks":[{"id":"requirements" | "design" | "plan" | "ac" | "decisions" | "dependencies" | "premises","pass":true,"evidence":"<how verified>"}]}]}
```

A task you cannot fully prepare gets status `failed` or `skipped` — never fabricate evidence;
the handoff degrades to refineall.

## Step 6: Refine before execute (the spec-completion gate)

`batch-create` accepts optional `design` / `plan` / `acceptance_criteria` fields (plus
`background` / `requirements`). **Default planning path:** the decomposition agent fills `design`
(and preferably Plan/AC) so tasks land **content-ready**. **`--skip-design`:** leave `design`
empty — headings only.

**Refine is the fallback** Design author, not the primary one. It is also the audit path for
tasks that already have content:

```text
/sp:dev-refine <wbs>          # single task — fills blank Design/AC/Plan if L3 gaps
/sp:dev-refine <wbs> --depth ready    # evaluate + correct an existing/filed task, then promote to todo
/sp:dev-refineall --feature X --auto
/sp:dev-refineall --feature X --auto --depth ready   # implement-ready freeze (no L3-only SKIP)
```

Under `--auto` + **`--depth standard`** (default), refine **SKIP**s when target sections
(Background, Requirements, AC, Design, Plan) already have no L3 findings. If Design is still
placeholder, synthesis runs (standard tier by default; escalates only on gate-fail). Under
**`--depth ready`**, do not SKIP on L3-clean alone — run the implement-ready checklist in
[dev-operations.md](dev-operations.md) § refine (frozen APIs, anti-patterns, file targets, handoffs)
so another agent can implement without inventing design. Ready depth also audits every existing
claim against the current tree, records corrections in Background, and promotes a passing task
`backlog → todo`.

**Check the variant before you write.** Which sections a task carries is decided by its `template:`
frontmatter against `.spur/tasks/section-matrix.yaml` — NOT a fixed list. Before authoring any
section, run `spur task check <wbs> --json` and read `requiredSections` / the L2 findings: they tell
you exactly what this variant allows at the current status. The default `standard` variant wants
`Acceptance Criteria` + `Design` + `Plan` at `todo`, but other variants differ — e.g. the `review`
variant puts findings under `### Background` (`#### Review Findings`) and the fix checklist in
`### Plan`, and does **not** use `### Requirements`/`### Acceptance Criteria`. Authoring a section
that isn't in the variant's allowed list produces an L2 "not allowed in this variant/status" warning
and an off-variant task. Write only what the matrix permits; route findings/checklists into the
sections the variant actually defines.

**Avoid creating off-variant sections in the first place.** There is no section-delete verb —
intentionally none (the CLI surface stays minimal). Writing an empty body via
`spur task update <wbs> --section <name> --from-file <empty-file>` currently leaves a **bare
heading**, not a removal. So once an off-variant section exists, it cannot be cleanly dropped from
the skill — the prevention (check the variant before writing, above) is the only reliable path. If
you must correct an off-variant section, overwrite its body with a single line pointing at the
correct section (e.g. *"See `### Plan` for the fix checklist."*) rather than leaving it empty.

**Do this just-in-time, per task, immediately before execution** — not in bulk at decomposition
time. Design written against a stale snapshot of the codebase rots; design written right before
`implement` reflects current reality. Refine `0042`, run `0042`; refine `0043`, run `0043`.

**Batch refine (optional pre-pass).** When an operator wants every planning-side task under a
feature filled before a runall, use `/sp:dev-refineall --feature <id> --auto` (batch counterpart
of `/sp:dev-refine`). It reuses the same per-task refine operation, freezes the set, topo-sorts by
`dependencies[]`, and emits a batch report — see [dev-operations.md](dev-operations.md) § refineall.
This does **not** replace just-in-time refine before each implement; it is a bulk pre-pass when the
feature's tasks are still `backlog`/`todo` placeholders. Prefer `--auto` for batch scale.
`/sp:dev-refineall` takes no `--next`; chain with `/sp:dev-runall --feature <id>` afterwards.

**Refine arguments** (defined on the `/sp:dev-refine` entry point, passed through verbatim; also
shared flags on `/sp:dev-refineall`):

| Argument | Effect |
|----------|--------|
| `--focus <mode>` | Narrows the gap analysis to a subset of domain hints. Values `all`, `requirements`, `background`, `constraints`, `acceptance`, `quick` — hint table in [dev-operations.md](dev-operations.md) § refine. Default `all`. Under `--depth ready` it only orders the work. |
| `--depth <standard\|ready>` | Spec depth bar. `standard` (default) = L3 structural completeness + L3 SKIP under `--auto`. `ready` = audit + implement-ready freeze + promote to `todo` (never L3-only SKIP). See [flag-glossary.md](flag-glossary.md#flag-depth). |
| `--auto` | Skip interactive Q&A — synthesize improvements from the task content alone. Use for well-scoped tasks where the agent can fill gaps without operator input. **Required for practical batch use** via `dev-refineall`. |

**Pre-synthesis skip gate (under `--auto` + `--depth standard`).** Before synthesizing, run `spur task check <wbs> --json`. When the **refine target sections** show no L3 findings, emit a structured SKIP instead of calling the synthesis agent. **Not applied when `--depth ready`.**

**Refine target sections (anti-drift lock):** `{Background, Requirements, Acceptance Criteria, Design, Plan}`.  
These must be solid enough that a cheaper implementer cannot invent another path. **Solution is not a refine target** — it is written by implement as the as-built change-map.

```
SKIP — sections already meet L3: sections-considered=[Background, Requirements, Acceptance Criteria, Design, Plan], reason="no L3 findings for target sections"
```

This is the expected outcome for a task that is already well-specified. Under `--auto`, a SKIP is not an error — it means no gap was found. The operator can verify by reading the check output or the task file directly. Synthesis is only invoked when a real gap exists in a target section (including empty/placeholder Design or AC).

> **Requirements formatting:** author R-items as a GitHub task-list checkbox — `- [ ] R1. <text>`
> — one per line, so progress is trackable in the file. Keep the `Rn.` (period) token inside the
> marker so the R-numbering rule recognizes it.
>
> The L3 check *tolerates* `- Rn.` and bare `Rn.` for backward compatibility with the existing
> corpus — that tolerance is **not** permission to emit them. A bare `Rn.` line carries no markdown
> list marker, so consecutive items collapse into one run-on paragraph in the Board's markdown
> preview while `spur task check` still passes. Emit the checkbox form on every write path
> (refine synthesis, `spur task update --section`, and `batch-create` bodies — see
> `sp:spec-decomposition` → `references/decomposition.md`, "Section bodies are markdown").
