---
name: spur-cli-tasks
description: "spur-cli noun reference: operate `spur task` as the project's task-file CLI — create tasks (with template variants), edit sections, drive the status lifecycle, record pipeline verdicts, derive verdicts, query the four-layer readiness matrix via `check --json`, and re-scan the corpus. The committed-corpus side of the planning layer that the spine orchestrates against."
see_also:
  - spur-cli
---

# spur task — the task-file CLI

`spur task` is the CLI for the **task corpus** — the markdown task files under `docs/tasks/` (DD-08).
Each verb is deterministic and machine-gated: WBS allocation is race-safe, section edits are
file-wins, `record` writes pipeline verdicts mechanically, and `check` is the four-layer readiness
matrix the rest of the planning layer reads.

This is a **companion reference**, not an orchestrator. It documents *what each verb is and how to
use it well*. The end-to-end planning + execution loop (intake → feature → decomposition →
pipeline run) lives in **`sp:spur-dev`** — do not reimplement that loop here. When you need to
*drive* a task through its lifecycle, reach for `sp:spur-dev`; when you need to know *which verb
does what*, this skill.

## WBS lookup fast path

Start from the WBS, not the corpus layout:

```bash
spur task show <wbs> --json  # metadata + full content + filePath
spur task get  <wbs> --json  # alias of `show` — same output, no separate code path
spur task path <wbs> --json  # absolute path only
```

Do not search `docs/tasks*` or guess `--folder` to locate a known WBS. `show` is the default when an
agent needs to read a task; use `path` only when another filesystem command needs the absolute path.
Both commands resolve across configured task folders. Add `--folder` only when deliberately limiting
the lookup to one non-default corpus. Capture `show` once per run and reuse its response instead of
re-reading or re-tokenizing the task.

## Verb map

| Verb | Purpose | Key flags |
| ---- | ------- | --------- |
| `create <title>` | Allocate a new task (race-safe WBS) | `--feature <id>` `--parent <wbs>` `--template <variant>` `--dedupe-within <s>` `--allow-duplicate-name` `--folder` `--json` |
| `show <wbs>` | Print one task's frontmatter + body | `--folder` `--json` |
| `update <wbs> [status]` | Lifecycle transition, section replace, **or** frontmatter set | `--section <name> --from-file <path>` `--assignee <spec-id>` (exclusive with `--section`) `--feature <id>` `--priority <p>` `--no-lifecycle` `--force-done` `--reason <text>` `--verdict-dir <path>` `--folder` `--json` |
| `deps <wbs> <op> [values...]` | Mutate `dependencies[]` frontmatter array (ops: `set`, `add`, `remove`, `clear`) | `--folder` `--json` |
| `sections <wbs> <op> [name]` | Initialize, add, or list canonical task sections (ops: `init`, `add`, `list`) | `--folder` `--json` |
| `list` | List tasks, filtered | `--status <s>` `--phase <p>` `--parent <wbs>` `--feature <id>` `--folder` `--json` |
| `refresh` | Re-scan the corpus and report counts (**`kanban.md` retired** — web Task Kanban is SSOT) | `--folder` `--json` |
| `migrate` | One-time A17 corpus normalization pass | `--dry-run` `--folder` `--json` |
| `migrate-anchors` | Qualify in-repo evidence anchors to repo-relative paths (0583 R1–R3) | `--dry-run` `--json` |
| `refresh-roster <wbs>` | Regenerate a parent task's sub-task roster block in `## Plan` | `--folder` `--json` |
| `batch-create` | Create many tasks from a validated JSON array | `--file <path>` `--folder` `--json` |
| `record <wbs>` | Write `Testing` from a verify verdict (deterministic); bare-`## Review` fallback only; optional Solution + transition | `--verdict-file <path>` `--solution-from-diff` `--transition <status>` `--folder` `--json` |
| `verdict <wbs>` | Derive PASS/PARTIAL/FAIL/UNKNOWN from verify answer text → verdict JSON; see [answer-file shape](tasks/verbs.md#answer-file-shape-what---from-answer-parses) | `--from-answer <path>` `--folder` `--json` |
| `check [wbs]` | Four-layer validation; `--fix` repairs structure; `--corpus` is the explicit unsuppressed audit | `--strict` `--as <status>` `--strict-core` `--fix` `--folder` `--corpus` `--since <ref>` `--json` |
| `resolve <file-path>` | Map a file path to its owning task WBS | `--strict` `--folder` `--json` |
| `path <wbs>` | Map a WBS to its absolute task file path (inverse of `resolve`) | `--folder` `--json` |
| `run-link <wbs>` | Record pipeline run provenance link for task | `--source <src>` `--run-id <id>` `--json` |
| `verifyall-aggregate` | Aggregate per-task verify outcomes into a batch verdict (NOT-STARTED excluded) | `--from-file <path>` `--json` |
| `scaffold-tests <wbs>` | Generate BDD test stubs from task Acceptance Criteria | `--file <path>` `--folder` `--json` |

All verbs accept `--json` for machine consumption and `--folder <path>` to target a non-default
tasks folder. **Exit codes:** `0` success, `1` error, `2` invalid usage.

## Creating tasks

```bash
spur task create "Add email validation" --feature H2 --parent 0040
```

- **`--feature <id>`** wires traceability: it derives the task's `Background` from the feature's
  `Goal` and records `feature_id` in frontmatter (L4 traceability reads this edge).
- **`--parent <wbs>`** groups the new task under a parent WBS for sub-task decomposition.
- **`--template <variant>`** selects the section-matrix variant that shapes the new file's sections:
  `standard·feature-impl·issue·review·meta·brainstorm`. The default is **`feature-impl` when
  `--feature` is given, else `standard`**. An unknown variant is exit `2`.

The same `--template` axis drives both *which sections the new file carries* (per the
Section-Status-Matrix) and *its creation status*: a spec'd task (a `--feature` link, or a batch item
with `background`/`requirements`) is created at **`todo`**; a bare capture is created at **`backlog`**.
See [tasks/verbs.md](tasks/verbs.md) for the variant detail.
- **Dedup guard (default-on)** — when `--feature <id>` is set, the CLI refuses creation if an
  existing task under the same feature has an identical (case-insensitive) title created within the
  last **300 seconds** (5 min). This prevents the orphan-skeleton + re-create pattern: an agent
  creates a task, loses context, then creates it again. The guard emits `duplicate-follow-up` with
  exit code `3` and names the existing WBS — reuse it.
  - **`--dedupe-within <seconds>`** overrides the default window (e.g. `--dedupe-within 60`).
  - **`--allow-duplicate-name`** disables the guard entirely (explicit override).
  - The guard applies to every create: feature-scoped tasks collide within the same
  feature; unscoped tasks (no `--feature`) collide with other unscoped tasks of the
  same title (their own collision scope).
  - With `--json`, a duplicate emits `{ ok: false, error: { code, message, existingWbs,
    existingName, attemptedName } }` for deterministic reuse.

```bash
# Default guard is on — second call within 5 min exits 3:
spur task create "Add email validation" --feature H2
spur task create "Add email validation" --feature H2  # → duplicate-follow-up, exit 3
# Narrow the window:
spur task create "Add email validation" --feature H2 --dedupe-within 60
# Override (creates anyway):
spur task create "Add email validation" --feature H2 --allow-duplicate-name
```

Many tasks at once (the decomposition output) go through `batch-create` with a JSON **array** file.
After child creation succeeds, the CLI refreshes each referenced parent roster and advances a parent
still at `todo` to `wip`; the JSON result includes `parentsWired[]` for these best-effort side
effects. Shape and gating live in `sp:spur-dev`'s decomposition reference.

## Editing a task: status vs. section vs. frontmatter

`update` is multi-mode. **The first positional after the WBS is a status; `--section` switches to
section-edit mode; `--feature`/`--priority` set a frontmatter scalar.** Status and `--section` are
mutually exclusive.

**Lifecycle transition** (positional status):

```bash
spur task update 0040 wip
```

Valid statuses: `backlog · todo · wip · testing · blocked · done · cancelled` (the lifecycle engine
enforces legal transitions). Two transitions are **guarded by `check`**: `wip→testing` runs
`spur task check <wbs> --as testing`, and `testing→done` runs `spur task check <wbs> --as done`
(F92 R3 — target-aware guards) — a failing
gate blocks the transition (§7.5).

**`--no-lifecycle`** suppresses lifecycle workflow *run record* creation (use during pipeline-driven
transitions so nested lifecycle runs are not orphaned). **It is not a guard bypass** — the
`wip→testing` / `testing→done` `check` gates still run, evaluated inline when the FSM guard does not.
**Forced-done override** (`--force-done`, paired with `--reason`):

```bash
spur task update 0040 done --force-done --reason "verify PASS deferred; manual review confirmed"
spur task update 0040 done --force-done --reason "verify PASS deferred" --verdict-dir .spur/run/ci
```

- **`--force-done`** allows a `testing->done` transition even when the verify verdict is not PASS
  (0292); it records an override rather than silently bypassing the gate.
- **`--reason <text>`** is the rationale for the override, persisted as `done_reason` in frontmatter.
- **`--verdict-dir <path>`** sets the directory the lifecycle guard reads `<wbs>-verdict.json`
  artifacts from (default `.spur/run`) - useful when a pipeline writes verdicts to a non-default
  location.

**Section replace** (file-wins, crash-safe):

```bash
spur task update 0040 --section Review --from-file /tmp/review.md
```

- `--section` **requires** `--from-file` — there is no inline-body flag; the new body is always
  read from a file (this is what makes edits crash-safe and reviewable). Exit `2` otherwise.
- Section names match the DD-08 task headings: `Background`, `Acceptance Criteria`, `Plan`,
  `Solution`, `Testing`, `Review`, `References`, `History`.
- Valid section names without a failed write: `spur task sections <wbs> list` (the same
  pointer `task update --help` carries).
- The write replaces the **whole** named section body; assemble the full section in the temp file
  first, then point `--from-file` at it.

**Frontmatter set** (the only post-create path to scalar fields, allow-listed to
`feature_id`/`parent_wbs`/`priority`, plus the two AC controls below):

```bash
spur task update 0040 --feature H2
spur task update 0040 --priority P1
```

### AC altitude — `--ac-altitude` (task 0818 R5)

`--ac-altitude` and `--ac-numbering` are **independent** controls that are easy to confuse:

| Flag | Controls | Default | `task-local` means |
| --- | --- | --- | --- |
| `--ac-altitude <graduating\|task-local>` | DD-09 **feature-AC subset** rule (task scenarios ⊆ linked feature AC) | `graduating` | the task's scenarios are deliberately **not** feature ship criteria — skip the subset rule |
| `--ac-numbering <task-local>` | **Requirements↔AC coverage** inside the task | off | opt the task into the R-to-AC coverage check |

Setting one says nothing about the other: a `task-local`-altitude task can still be under full
R-to-AC coverage, and usually should be.

**The standing pattern for an issue or fix-batch task.** Link it to the feature it substantively
belongs to — do not leave it orphaned and do not relink unrelated corpus to silence a diagnostic.
Then, *only* when its regression scenarios intentionally do not represent that feature's ship
criteria, declare `--ac-altitude task-local` and record the rationale in the task body (Background
or Design), so the choice is auditable rather than inferred:

```bash
# source-local CLI (before `bun link`, or when pinning to this checkout).
# One frontmatter flag per call: `update` applies a single field, so a second
# frontmatter flag in the same invocation is silently ignored.
bun run apps/cli/src/index.ts task update 0818 --feature D6 --json
bun run apps/cli/src/index.ts task update 0818 --ac-altitude task-local --json
```

`graduating` stays the default, and DD-09 stays enforced for every graduating task — this flag
expresses a real altitude distinction, not a gate escape hatch. Ordinary orphan warnings are
unchanged, and no checker policy changes.

The section-write-then-replace pattern is the workflow agents use to fill in `Plan` / `Solution` /
`Testing` / `Review` during a run. See
[tasks/section-editing.md](tasks/section-editing.md) for the full recipe. For pipeline
output specifically, prefer **`record`** for `Testing` (below) over hand-assembling section files; `Review` is authored by the review coordinator (`/sp:dev-review`), not hand-filled.

## Recording pipeline results — `record`

`spur task record <wbs>` writes the `Testing` section **from a verify verdict artifact** (the
deterministic Testing writer — F92 0593 R1) and backfills `Review` **only when the section is
bare** (standalone compatibility fallback; never overwrites authored Review), so the pipeline's
record step is one CLI call instead of awk/sed/jq plumbing:

```bash
spur task record 0040 --transition testing
spur task record 0040 --verdict-file .spur/run/0040-verdict.json --solution-from-diff --transition testing
```

- Reads the verdict JSON (default `.spur/run/<wbs>-verdict.json`); renders `Testing` as a
  per-requirement table and, when the `Review` section is bare, backfills a P1–P4 findings table.
  A missing/malformed verdict degrades to an `UNKNOWN` verdict — it never throws.
- **`--solution-from-diff`** backfills `Solution` from `git diff -U0` hunk headers **only when the
  Solution section is still bare** — a safety net, not an overwrite.
- **`--transition <status>`** optionally advances the lifecycle after writing (e.g. `testing`).
  `--transition done` is a **guarded** completion path: it requires a PASS verdict, auto-walks
  `wip → testing → done`, and auto-creates the pipeline run-link (0436 R4); a non-PASS verdict to
  `done` errors instead of transitioning, and the normal done gates still apply (0108).

The verdict shape (`wbs`, `verdict`, `requirements[]`, `checks[]`) and the rendered tables are
documented in [tasks/verbs.md](tasks/verbs.md).

## Batch verdict aggregation - `verifyall-aggregate`

`spur task verifyall-aggregate` replaces agent-discretion rollup prose with deterministic code: it
reads a JSON array of per-task outcomes and emits a single batch verdict, excluding NOT-STARTED
tasks from the rollup.

```bash
spur task verifyall-aggregate --from-file .spur/run/verifyall-batch-input.json --json
```

- **`--from-file <path>`** points at a JSON array of `{ wbs, outcome, reason? }` rows (default
  `.spur/run/verifyall-batch-input.json`). `outcome` is one of
  `PASS · PARTIAL · FAIL · NOT-STARTED · UNKNOWN`.
- The aggregation rolls up every task **except** NOT-STARTED (those are reported separately in
  `notStarted[]`). The batch verdict is `FAIL` if any rolled-up task is FAIL; `PARTIAL` if any is
  PARTIAL; else `PASS`.
- **`--json`** emits `{ verdict, rolledUp[], notStarted[], summary }` where each entry is
  `{ wbs, outcome, verdict?, reason? }`.
- **Exit codes:** `0` on PASS/PARTIAL, `1` on FAIL (and on missing/invalid input), `2` invalid usage.

## Test scaffolding - `scaffold-tests`

`spur task scaffold-tests <wbs>` generates BDD test stubs from a task's `## Acceptance Criteria` -
one stub per scenario - so a spec'd task lands with a runnable test file from the start.

```bash
spur task scaffold-tests 0040 --json
spur task scaffold-tests 0040 --file tests/0040.test.ts
```

- **`--file <path>`** overrides the default target test file path.
- **`--json`** emits `{ wbs, targetFile, created, skipped, drifted, driftedScenarios[], warnings[] }`.
  `drifted`/`driftedScenarios[]` flag scenarios whose AC titles no longer match the existing test
  names; `warnings[]` carries non-fatal notices.
- **Exit codes:** `0` success, `1` error (e.g. task WBS not found), `2` invalid usage.

## The readiness matrix — `check --json`

`spur task check` is the **deterministic four-layer gate** over the corpus (design §3): L1
frontmatter, L2 section-matrix, L3 structure/format, L4 traceability. Run it bare for the whole
corpus, or with a WBS for one task:

```bash
spur task check --json              # whole corpus
spur task check 0040 --json         # one task
spur task check --strict --json     # elevate ALL warnings to failures
spur task check 0040 --as done        # evaluate as the done row (lifecycle target, F92 R2)
spur task check 0040 --strict-core    # temporary compatibility alias
```

**Folder resolution (task 0522):** a WBS-targeted check (`<wbs>` present, no `--folder`) resolves
the task across **all configured task folders** — the same resolution as `task show` / `task path` /
`task update` — so a task in an inactive configured folder is checked, not reported missing. An
explicit `--folder <path>` is normalized to an absolute path and restricts lookup to that single
directory (relative and absolute spellings are equivalent). Unscoped checks (no WBS) and
`task list` remain active-folder-only.

`--json` emits the structured matrix — per-task findings (missing sections, broken feature edges,
AC-coverage orphans via L4 traceability) keyed by WBS, plus a per-task `pass` verdict. **Query this,
do not re-derive it**: parse the JSON to answer "which tasks are ready?", "what's blocking 0040?",
or "are there orphaned scenarios?" rather than reading task files and re-implementing the checks.

The two flags are distinct gate profiles:

- **`--strict`** elevates *all* warnings to failures (the strictest reading).
- **`--as <status>`** evaluates the task as if it were already in `<status>` (F92 R2); the lifecycle
  guards pass the transition target. Validated against canonical task statuses; excluded with `--corpus`.
- **`--strict-core`** is a **temporary compatibility alias** (F92 R2). Fails only on hard-core errors — Solution `file:line`, Review P1–P4, and
  `gate:true` required-section misses — *without* the blanket elevation. This is the variant wired
  as the `testing→done` lifecycle guard.

See [tasks/verbs.md](tasks/verbs.md) for the JSON shape per finding.

## Corpus scan — `refresh` (kanban.md retired)

```bash
spur task refresh
```

Re-scans the task corpus and reports counts (`Corpus scanned — N tasks across M folder(s)`). With
`--json`, emits `{ folders, tasks }`. **`kanban.md` generation is retired** (A17 cutover) — the web
Task Kanban board is the daily driver. `refresh` does not write task files.

## Path resolution — `resolve` / `path`

```bash
spur task resolve docs/tasks/0040_add-email-validation.md
spur task resolve src/lib/validation.ts --json
spur task path 0040 --json
```

- `resolve <file-path>` maps a file path to its **owning task** (returns WBS + file). Strategies, in
  order: direct task-file match, filename WBS parse, then walk-up the tree (A10). `--strict` disables
  basename-WBS fallback. Exit `1` when no task owns the path.
- `path <wbs>` is the inverse — absolute task file path for a WBS.

## What this skill is NOT

- **Not the pipeline.** Driving a task through `task-pipeline.yaml`, HITL surfacing, and
  `workflow continue` is `sp:spur-dev`'s execution half — not here (R3).
- **Not validation logic.** This skill tells you *to run* `check`; the rules it enforces live in
  the CLI (`task check` / `feature check`), never restated as prose checks here.
- **Not features.** Feature authoring, hierarchical IDs, and AC conventions live in the companion
  **`spur feature` (see [features.md](features.md))**.

## References

| Reference | Covers |
| --------- | ------ |
| [tasks/verbs.md](tasks/verbs.md) | Per-verb flag detail, template variants, verdict + check JSON shapes, exit codes |
| [tasks/section-editing.md](tasks/section-editing.md) | The temp-file → `--section`/`--from-file` recipe; which sections to fill when; `record` vs. hand-editing |

## See also

- **`sp:spur-dev`** — the umbrella skill that orchestrates these verbs into the planning +
  execution loop. Use it to *drive* work; use this skill to *look up a verb*.
- **`spur feature` (see [features.md](features.md))** — the companion for `spur feature` (hierarchical IDs, AC conventions,
  traceability).

> **Shared option declarations (0618):** options shared across command modules resolve from
> `apps/cli/src/commands/shared-options.ts` (`SHARED_OPTIONS`). Never re-declare a shared flag
> inline in a command module — see SKILL.md "Shared option registry" and
> `docs/04_DESIGN.md` §1.0.1.
