@gobing-ai/spur 0.3.51 → 0.3.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "plugins": [
8
8
  {
9
- "version": "0.3.51",
9
+ "version": "0.3.52",
10
10
  "name": "sp",
11
11
  "source": "./plugins/sp",
12
12
  "description": "Spur - my harness toolkits"
@@ -1,12 +1,17 @@
1
1
  # Section-Status-Matrix: which sections are required/optional/forbidden per task status.
2
- # Design §3.2 — evaluated CLI-side only. Ships permissive; tightened by compliance data.
2
+ # Design §3.2 — the SOLE semantic authority for task section layout (F92 R1):
3
+ # * VALIDATION (`spur task check`, incl. target-aware `--as <status>` lifecycle guard)
4
+ # * CREATION (`spur task create` / `spur task batch-create` -> `buildTaskSkeleton`)
5
+ # There is NO hand-maintained creation or matrix fallback — packaged/compiled
6
+ # execution loads data copied/generated from THIS canonical file and fails loudly
7
+ # if no asset is reachable. Template files supply section BODY content + guidance
8
+ # only; they never own the heading list (which comes from this matrix).
3
9
  #
4
10
  # Each VARIANT (the unified template axis — TASK_VARIANTS) maps status →
5
11
  # { required, optional, forbidden } section lists. Sections reference the canonical
6
12
  # section vocabulary (§2.1). `gate: true` on a status makes L2 violations hard errors.
7
13
  #
8
14
  # The variant is selected by a task's `template:` frontmatter (default: `standard`).
9
- # It drives BOTH validation (`spur task check`) AND creation (`buildTaskSkeleton`).
10
15
  $schema: "@gobing-ai/spur/schemas/section-matrix.schema.json"
11
16
 
12
17
  variants:
@@ -12,15 +12,16 @@ name: task-lifecycle
12
12
  description: >
13
13
  Task lifecycle FSM (design §2.3, §5.1). States are the canonical TaskStatus
14
14
  vocabulary (DD-01); transitions encode the §2.3 graph; guards invoke
15
- `${vars.spurBin} task check` at the wip→testing and testing→done placements.
15
+ `${vars.spurBin} task check --as <target>` at the wip→testing and testing→done
16
+ placements (F92 R3): each guard evaluates the task AS the transition target so
17
+ testing→done checks the `done` row instead of the current `testing` row.
16
18
  `done` is re-enterable (reopen with a warning); `cancelled` is terminal.
17
- Guard commands reference the check verb (0051) structural validation passes
18
- today, behavioral wiring activates as the verb ships. Unconditional transitions
19
+ Guard commands reference the check verb (0051). Unconditional transitions
19
20
  use the `always` guard (externally-driven via `requestTransition`, not
20
21
  auto-advance).
21
22
 
22
23
  Two-layer done gate (task 0292): this workflow's testing→done guard
23
- (`spur task check --strict-core`) is the FIRST gate. The CLI verdict guard
24
+ (`spur task check --as done`, F92 R3 — target-aware) is the FIRST gate. The CLI verdict guard
24
25
  (`packages/app/src/services/done-transition-guard.ts`) is the SECOND gate,
25
26
  invoked by `apps/cli/src/commands/task.ts` for every `spur task update <wbs>
26
27
  done` call — it reads `.spur/run/<wbs>-verdict.json` and blocks non-PASS
@@ -69,14 +70,14 @@ transitions:
69
70
  guard:
70
71
  kind: shell
71
72
  options:
72
- command: '$spurBin task check $wbs'
73
+ command: '$spurBin task check $wbs --as testing'
73
74
  - from: testing
74
75
  to: done
75
76
  description: Testing passed — task complete
76
77
  guard:
77
78
  kind: shell
78
79
  options:
79
- command: '$spurBin task check $wbs --strict-core'
80
+ command: '$spurBin task check $wbs --as done'
80
81
 
81
82
  # Reopen: done → wip (warned, mandatory History entry)
82
83
  - from: done
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.51",
3
+ "version": "0.3.52",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -183,8 +183,9 @@ When you fan out or dispatch a subagent, apply the four disciplines the SSOT
183
183
  - [ ] Never replace yourself as orchestrator when `--agent` is set - it pins the step executor, not you.
184
184
  - [ ] Never auto-approve a HITL gate inside a task unless `--auto` was passed (it sets `profile=auto`).
185
185
  - [ ] Never silent-pick multi-candidate router stops; surface HITL (batch `--auto` does not break ties).
186
- - [ ] Never mutate the corpus - the pipeline's `record` step writes per-task `## Testing` / `## Review`
187
- sections; your sole output is the batch report (+ optional recovery dispatch of an existing
186
+ - [ ] Never mutate the corpus - the review coordinator writes `## Review`, the pipeline's `record`
187
+ step writes `## Testing` deterministically (bare-`## Review` fallback only never an
188
+ overwrite of authored Review, F92 0593 R1); your sole output is the batch report (+ optional recovery dispatch of an existing
188
189
  `/sp:dev-*` command).
189
190
  - [ ] Never run tasks in parallel unless the operator requested parallel mode and the
190
191
  `sp:parallel-execution` checks pass. If checks fail, serialize and report why.
@@ -38,6 +38,11 @@ You are a **thin delegator**. You do not own the review logic; the three skills
38
38
  | SECUA quality | `sp:code-verification` (review mode) | Is the code correct/secure/efficient/usable? |
39
39
  | Architectural depth | `sp:code-improvement` | Is the architecture deep / testable? |
40
40
 
41
+ **Section ownership (F92 0593 R1).** Component skills **return review fragments only** — never
42
+ write `## Review`. This coordinator is the single `## Review` writer in coordinated/pipeline mode
43
+ (combined fragment merge). `spur task record`'s bare-Review backfill is a standalone compatibility
44
+ fallback only and never overwrites authored Review.
45
+
41
46
  Your job: establish scope, dispatch each requested dimension to its skill, collect findings, merge
42
47
  them into a ranked report, and write the report to the task's `## Review` section (pipeline mode) or
43
48
  emit it as advisory output (standalone mode).
@@ -27,6 +27,6 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
27
27
  ## Implementation
28
28
 
29
29
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
30
- - WBS mode (`<wbs>`): `Skill(skill="sp:functional-review", args="<wbs> $ARGUMENTS")` + `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<wbs> $ARGUMENTS")` (functional traceability + SECUA framework + architectural depth; may write `Review` section to task)
30
+ - WBS mode (`<wbs>`): `Skill(skill="sp:functional-review", args="<wbs> $ARGUMENTS")` + `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<wbs> $ARGUMENTS")` (functional traceability + SECUA framework + architectural depth; the three skills return review fragments and the coordinator (`sp:super-reviewer`) writes the combined `## Review` F92 0593 R1)
31
31
  - Path mode (`<path>`): `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<path> $ARGUMENTS")` (advisory SECUA quality + architectural depth; performs no task mutation)
32
32
  - `--fix`: Deprecated (no-op + warning message; route remediation to `/sp:dev-verify --fix`). **`--next` removed** (feature H8, 2026-07-31) — it was a deprecated no-op; route progression to `/sp:dev-next`. **was: `--next` deprecated no-op.**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.51",
3
+ "version": "0.3.52",
4
4
  "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
5
5
  "extensions": {
6
6
  "pi": ["./hooks/pi/guard-extension.ts"]
@@ -25,7 +25,7 @@ import { join } from 'node:path';
25
25
 
26
26
  // ─── Types ──────────────────────────────────────────────────────────────────
27
27
 
28
- interface CmdResult {
28
+ export interface CmdResult {
29
29
  code: number;
30
30
  stdout: string;
31
31
  stderr: string;
@@ -110,10 +110,14 @@ interface ParsedArgs {
110
110
  booleans: Set<string>;
111
111
  }
112
112
 
113
- // ─── Process runner (git/gh resolved from PATH so tests can stub them) ──────
113
+ // ─── Process runner (single seam; every git/gh call routes through run) ─────
114
114
 
115
- function run(cmd: readonly string[]): CmdResult {
116
- // Pass env explicitly so both Node and Bun resolve git/gh against the test fixture PATH.
115
+ /** How an external command is executed. The only seam between this script and git/gh. */
116
+ export type CommandRunner = (cmd: readonly string[]) => CmdResult;
117
+
118
+ /** Real execution: resolve the binary from PATH and capture its output. The default runner. */
119
+ export const spawnRunner: CommandRunner = (cmd) => {
120
+ // Pass env explicitly so both Node and Bun resolve git/gh against the caller's PATH.
117
121
  const proc = spawnSync(cmd[0] ?? '', [...cmd.slice(1)], { encoding: 'utf8', env: process.env });
118
122
  return {
119
123
  code: proc.status ?? 1,
@@ -121,6 +125,20 @@ function run(cmd: readonly string[]): CmdResult {
121
125
  stderr: proc.stderr ?? '',
122
126
  error: proc.error?.message,
123
127
  };
128
+ };
129
+
130
+ let runner: CommandRunner = spawnRunner;
131
+
132
+ /**
133
+ * Swap the git/gh runner. Tests inject an in-process stub so a suite of CLI cases costs no
134
+ * subprocess spawns; call with no argument to restore real execution.
135
+ */
136
+ export function setCommandRunner(next?: CommandRunner): void {
137
+ runner = next ?? spawnRunner;
138
+ }
139
+
140
+ function run(cmd: readonly string[]): CmdResult {
141
+ return runner(cmd);
124
142
  }
125
143
 
126
144
  function runOk(cmd: readonly string[], what: string): string {
@@ -30,7 +30,7 @@ import { join } from 'node:path';
30
30
  // ─── Inline type definitions (mirrors packages/domain/src/stage-registry/) ─
31
31
 
32
32
  export type SchemaVersion = { major: number; minor: number };
33
- export const CURRENT_SCHEMA_VERSION: SchemaVersion = { major: 1, minor: 0 };
33
+ export const CURRENT_SCHEMA_VERSION: SchemaVersion = { major: 1, minor: 3 };
34
34
  export const AUTHORITY_LANES = ['registry', 'workflow', 'skill', 'cli', 'adapter'] as const;
35
35
  export type AuthorityLane = (typeof AUTHORITY_LANES)[number];
36
36
  // Mirrors packages/domain/src/stage-registry/schema.ts MUTATION_CLASSES. Pinned by
@@ -65,6 +65,8 @@ export type ContextLayerName = (typeof CONTEXT_LAYER_NAMES)[number];
65
65
  export interface StageArtifact {
66
66
  kind: string;
67
67
  direction: ArtifactDirection;
68
+ /** Exact artifact identity (F92 0593 R2): task-section name or artifact file basename. */
69
+ identity?: string;
68
70
  description?: string;
69
71
  required?: boolean;
70
72
  }
@@ -308,10 +310,7 @@ export const REGISTERED_STAGES: StageRecord[] = [
308
310
  ],
309
311
  reasoning_skill: 'sp:spur-dev',
310
312
  required_references: ['references/dev-operations.md', 'spur-dev/references/decision-brief.md'],
311
- gates: [
312
- { name: 'refine-skip-gate', timing: 'pre', description: 'Skip sections that already meet L3' },
313
- { name: 'l4-advisory', timing: 'post', min_verdict: 'pass', description: 'L4 advisory surface' },
314
- ],
313
+ gates: [],
315
314
  mutation_class: 'corpus',
316
315
  retry: defaultRetry,
317
316
  model_policy: policy('refine', ['capable-2']),
@@ -346,7 +345,7 @@ export const REGISTERED_STAGES: StageRecord[] = [
346
345
  description: 'dev-run --mode implement: code edits in worktree',
347
346
  artifacts: [
348
347
  { kind: 'worktree-diff', direction: 'output', required: true },
349
- { kind: 'task-section', direction: 'input', description: 'Solution section constraints' },
348
+ { kind: 'task-section', direction: 'output', required: true, identity: 'Solution' },
350
349
  ],
351
350
  reasoning_skill: 'sp:code-implementation',
352
351
  gates: [],
@@ -370,7 +369,7 @@ export const REGISTERED_STAGES: StageRecord[] = [
370
369
  { kind: 'coverage-report', direction: 'output', required: false },
371
370
  ],
372
371
  reasoning_skill: 'sp:code-testing',
373
- gates: [{ name: 'coverage-floor', timing: 'post', min_verdict: 'pass', description: '≥90% function coverage' }],
372
+ gates: [],
374
373
  mutation_class: 'tests',
375
374
  retry: defaultRetry,
376
375
  model_policy: policy('test', ['capable-1']),
@@ -403,13 +402,13 @@ export const REGISTERED_STAGES: StageRecord[] = [
403
402
  id: 'verify',
404
403
  description: 'dev-verify: SECUA review + requirements traceability',
405
404
  artifacts: [
406
- { kind: 'verdict-artifact', direction: 'output', required: true },
407
- { kind: 'task-section', direction: 'output', description: 'Testing/Review sections' },
405
+ { kind: 'worktree-diff', direction: 'input', required: true },
406
+ { kind: 'verdict-artifact', direction: 'output', required: true, identity: '<wbs>-verdict.json' },
408
407
  ],
409
408
  reasoning_skill: 'sp:code-verification',
410
409
  gates: [
411
410
  { name: 'verdict-artifact', timing: 'post', min_verdict: 'pass' },
412
- { name: 'strict-core', timing: 'post', description: 'L3 core findings must pass' },
411
+ { name: 'strict-core', timing: 'post', min_verdict: 'pass', description: 'L3 core findings must pass' },
413
412
  ],
414
413
  mutation_class: 'verdict',
415
414
  retry: { max_attempts: 2, terminal_stop: 'escalate', timeout_seconds: 600 },
@@ -423,10 +422,7 @@ export const REGISTERED_STAGES: StageRecord[] = [
423
422
  id: 'wrap',
424
423
  aliases: ['dev-wrap'],
425
424
  description: 'dev-wrap: learnings/doc-sync/feature transition',
426
- artifacts: [
427
- { kind: 'learning-entry', direction: 'output' },
428
- { kind: 'task-section', direction: 'output', description: 'Testing/Review updated' },
429
- ],
425
+ artifacts: [{ kind: 'learning-entry', direction: 'output' }],
430
426
  reasoning_skill: 'sp:spur-dev',
431
427
  gates: [
432
428
  {
@@ -447,9 +443,9 @@ export const REGISTERED_STAGES: StageRecord[] = [
447
443
  schema_version: CURRENT_SCHEMA_VERSION,
448
444
  id: 'review',
449
445
  description: 'dev-review: multi-dimensional code review (functional/SECUA/architecture)',
450
- artifacts: [{ kind: 'review-findings', direction: 'output', required: true }],
446
+ artifacts: [{ kind: 'review-findings', direction: 'output', required: true, identity: 'Review' }],
451
447
  reasoning_skill: 'sp:code-verification',
452
- gates: [{ name: 'review-guard', timing: 'post', min_verdict: 'pass', description: 'No P1 findings blocking' }],
448
+ gates: [],
453
449
  mutation_class: 'verdict',
454
450
  retry: { max_attempts: 2, terminal_stop: 'block', timeout_seconds: 300 },
455
451
  model_policy: policy('review', ['capable-1']),
@@ -457,6 +453,32 @@ export const REGISTERED_STAGES: StageRecord[] = [
457
453
  observability: [event('stage-started'), event('findings-produced')],
458
454
  execution: inlineInline(),
459
455
  },
456
+ {
457
+ schema_version: CURRENT_SCHEMA_VERSION,
458
+ id: 'record',
459
+ aliases: ['dev-record'],
460
+ description: 'record: deterministic Testing write-back from the verdict artifact; bare-Review fallback only',
461
+ artifacts: [
462
+ { kind: 'verdict-artifact', direction: 'input', required: true, identity: '<wbs>-verdict.json' },
463
+ { kind: 'task-section', direction: 'output', required: true, identity: 'Testing' },
464
+ {
465
+ kind: 'task-section',
466
+ direction: 'output',
467
+ required: false,
468
+ identity: 'Review',
469
+ description:
470
+ 'fallback-only: backfills Review only when the section is bare; never overwrites authored Review',
471
+ },
472
+ ],
473
+ reasoning_skill: 'inline',
474
+ gates: [],
475
+ mutation_class: 'corpus',
476
+ retry: { max_attempts: 1, terminal_stop: 'block', timeout_seconds: 120 },
477
+ model_policy: policy('record'),
478
+ context_layers: [layer('task-state')],
479
+ observability: [event('stage-started')],
480
+ execution: inlineDeterministic('cli'),
481
+ },
460
482
  {
461
483
  schema_version: CURRENT_SCHEMA_VERSION,
462
484
  id: 'dogfood',
@@ -467,15 +489,7 @@ export const REGISTERED_STAGES: StageRecord[] = [
467
489
  ],
468
490
  reasoning_skill: 'sp:dogfood-testing',
469
491
  required_references: ['references/monitor-ledger.md', 'references/report-template.md'],
470
- gates: [
471
- { name: 'detect-pipeline-driving', timing: 'pre', description: 'Refuse dogfood when driving a pipeline' },
472
- {
473
- name: 'report-validate',
474
- timing: 'post',
475
- min_verdict: 'pass',
476
- description: 'Report must pass schema validation',
477
- },
478
- ],
492
+ gates: [],
479
493
  mutation_class: 'driver',
480
494
  retry: { max_attempts: 3, terminal_stop: 'block', timeout_seconds: 600 },
481
495
  model_policy: policy('dogfood'),
@@ -568,6 +582,7 @@ export function listStages(): StageLookupEntry[] {
568
582
  'quality-gate': '/sp:dev-fixall',
569
583
  verify: '/sp:dev-verify',
570
584
  wrap: '/sp:dev-wrap',
585
+ record: 'spur task record',
571
586
  review: '/sp:dev-review',
572
587
  dogfood: '/sp:dev-dogfood',
573
588
  handover: 'inline (dev-handover)',
@@ -187,9 +187,11 @@ When invoked as the `--focus architecture` dimension of `/sp:dev-review`:
187
187
  - The scope is the task's diff (pipeline) or the `path` arg (standalone).
188
188
  - `blocker`/`major` candidates block the `approve(HITL)` gate alongside any SECUA blockers from
189
189
  `sp:code-verification`.
190
- - The candidate list is recorded in the task's `## Review` section (written by the pipeline's
191
- `record` step via `spur task update --section`).
192
- - This skill does **not** write to the task file directly the pipeline (or the operator) does.
190
+ - The candidate list is returned as a **review fragment**; the review coordinator
191
+ (`sp:super-reviewer` under `/sp:dev-review`) merges it into the combined `## Review` section
192
+ it is never written by `record`, which backfills `## Review` only when the section is bare
193
+ (fallback-only, F92 0593 R1).
194
+ - This skill does **not** write to the task file directly — the coordinator (or the operator) does.
193
195
 
194
196
  Standalone, the skill emits the candidate list as advisory output; the operator acts on it.
195
197
 
@@ -27,15 +27,16 @@ metadata:
27
27
 
28
28
  The **verifier** in the Spur execution loop. A coding agent reports "done" with overconfidence;
29
29
  this skill is the deterministic counterweight that proves — or disproves — the claim against the
30
- task's own requirements and Acceptance Criteria, then writes the evidence back to the corpus
31
- through CLI verbs.
30
+ task's own requirements and Acceptance Criteria, then emits the canonical verdict artifact
31
+ through CLI verbs (section writes are owned by the review coordinator and the deterministic
32
+ `record` step — F92 0593 R1).
32
33
 
33
34
  It backs two commands:
34
35
 
35
36
  | Command | Mode | Input | Output |
36
37
  |---------|------|-------|--------|
37
- | `/sp:dev-verify <wbs>` | **verify** | a task WBS | per-requirement verdict → `## Testing`; `.spur/run/<wbs>-verdict.json` |
38
- | `/sp:dev-review <wbs>` | **review** | a task WBS (diff scope) | three-dimensional findings → `## Review` (functional + SECUA + architecture) |
38
+ | `/sp:dev-verify <wbs>` | **verify** | a task WBS | `.spur/run/<wbs>-verdict.json`; `record` transcribes `## Testing` |
39
+ | `/sp:dev-review <wbs>` | **review** (coordinator) | a task WBS (diff scope) | merged three-dimensional findings → `## Review` |
39
40
 
40
41
  The verify mode is the **completion gate's evidence source**: it emits a machine verdict the
41
42
  `task-pipeline.yaml` workflow reads before allowing `record → done`. A `PASS` clears the gate; a
@@ -243,34 +244,32 @@ blocker finding → FAIL; core PARTIAL or an unresolved major finding (no FAIL)
243
244
  MET or justified N/A → PASS. Minor/advisory findings do not block. Only `PASS` clears the pipeline
244
245
  completion gate (`PARTIAL`/`FAIL` route the pipeline to `failed`).
245
246
 
246
- ### Step 10 — Write findings to the task
247
+ ### Step 10 — Emit the verdict artifact (the only verify output)
247
248
 
248
- Assemble the evidence and write via CLI verbs (temp-file `--section`):
249
+ Assemble the evidence and **emit the canonical verdict artifact** verification writes no task
250
+ section (F92 0593 R1). Under the pipeline, the output is captured as
251
+ `.spur/run/<wbs>-verify-answer.txt`; a deterministic shell step derives
252
+ `.spur/run/<wbs>-verdict.json`, and the `record` step transcribes `## Testing` from it.
253
+
254
+ **Standalone** (`/sp:dev-verify` outside the pipeline), write the artifact yourself, then invoke
255
+ the deterministic Testing writer `spur task record` (section authorship never happens here):
249
256
 
250
257
  ```bash
251
- # Testing section: per-requirement and per-AC verdict tables + evidence
252
- printf '...' > /tmp/<wbs>-testing.md
253
- spur task update <wbs> --section Testing --from-file /tmp/<wbs>-testing.md
258
+ # write .spur/run/<wbs>-verdict.json (shape in references/verdict-schema.md), then:
259
+ spur task record <wbs> --verdict-file .spur/run/<wbs>-verdict.json # renders ## Testing
254
260
  ```
255
261
 
256
262
  > **Corrections: the answer file is the source of truth.** `spur task record` re-transcribes
257
- > `## Testing` from the verdict artifact, overwriting `--section Testing` writes direct section
258
- > fixes are futile. Fix `.spur/run/<wbs>-verify-answer.txt` → `spur task verdict <wbs>
259
- > --from-answer <file>` → re-record. `--section` is initial authorship only.
260
-
261
- > **Do not write `## Review` directly in verify mode.** The `## Review` section is owned by the
262
- > `review` step (`/sp:dev-review`), which dispatches `functional-review` + `code-verification`
263
- > review mode + `code-improvement`. The `record` step backfills `## Review` from the verdict
264
- > artifact only if the section is bare (`sectionIsBare` guard, `task-service.ts:485`). Writing
265
- > `## Review` here bypasses that guard and destroys the review step's three-dimensional findings.
266
-
267
- Section bodies passed to `spur task update --section` must be **body-only**. Do not put a same-level
268
- heading inside any section body; the task writer strips same-level headings to prevent phantom
269
- sections. Concretely:
263
+ > `## Testing` from the verdict artifact direct `--section Testing` writes are futile. Fix
264
+ > `.spur/run/<wbs>-verify-answer.txt` → `spur task verdict <wbs> --from-answer <file>` → re-record.
270
265
 
271
- - **Testing section:** do not put `### Acceptance Criteria Verification`, `### Per-Requirement
272
- Traceability`, or any `###` heading inside the Testing body. Use bold labels
273
- (`**Acceptance Criteria Verification**`) or tables instead.
266
+ > **Do not write `## Review` directly, ever.** The `## Review` section is owned by the
267
+ > `review` coordinator (`/sp:dev-review` `sp:super-reviewer`), which merges
268
+ > `functional-review` + `code-verification` review mode + `code-improvement` fragments. The
269
+ > `record` step backfills `## Review` from the verdict artifact **only** if the section is bare
270
+ > (`sectionIsBare` guard, `task-service.ts`) — a standalone compatibility fallback, never an
271
+ > overwrite of authored Review. Writing `## Review` here destroys the review step's
272
+ > three-dimensional findings.
274
273
 
275
274
  ### Step 11 — State the verdict and hand off (the gate contract)
276
275
 
@@ -311,10 +310,8 @@ canonical.
311
310
  `.spur/run/<wbs>-verify-answer.txt`. A deterministic shell step then derives
312
311
  `.spur/run/<wbs>-verdict.json` from it plus an independent `spur task check` (R9; the agent
313
312
  reporting PASS in prose is necessary but not sufficient — the artifact is never left to the agent's
314
- discretion). The **record** step transcribes only `## Testing` from the verdict verdict + per-
315
- requirement/AC tables + evidence. `## Review` is owned by the review step (`/sp:dev-review`) and
316
- the record step's `sectionIsBare` guard (`task-service.ts:485`) preserves any non-bare Review
317
- content. Verify mode never writes `## Review`.
313
+ discretion). Section transcription follows the Step 10 contract (record `## Testing`; bare-only
314
+ Review fallback; verify never writes sections).
318
315
 
319
316
  **Standalone** (`/sp:dev-verify` outside the pipeline — no answer-file capture exists), write the
320
317
  artifact yourself; shape and field-by-field contract in
@@ -455,8 +452,8 @@ re-audit is never misread as a successful `testing -> done` (dev-verify.md `--ne
455
452
  ## Mode: review (`/sp:dev-review`)
456
453
 
457
454
  The source-oriented path: SECUA review of a task's diff without the full traceability verdict. Runs
458
- Steps 3 + 7 + 10 (Review section only) — no verdict artifact, no `done` gate. Use for a focused
459
- quality/security audit of changes when the full verify isn't wanted.
455
+ Steps 3 + 7 and returns a **review fragment** — no verdict artifact, no section write, no `done`
456
+ gate (F92 0593 R1); the coordinator (`sp:super-reviewer`) merges fragments into `## Review`.
460
457
 
461
458
  Flags: `--agent <inline|auto|name>` (execution surface — inline default, with named escalation triggers taking precedence), `--auto` (no confirmations), `--fix <none|blockers-first|all>` (post-review repair), and `--focus <all|security|efficiency|correctness|usability|architecture>` (SECUA dimensions). Apply the [central contract](../spur-dev/references/cross-cutting.md#inline-default-execution-surface) before starting the review.
462
459
 
@@ -139,3 +139,50 @@ completion without an explicit PASS artifact.
139
139
  - **Read:** by the workflow gate on the `verify → record` transition.
140
140
  - **Location:** `.spur/run/` (the run-scratch dir the pipeline's `record` step also uses).
141
141
  - **Lifetime:** per execution attempt; overwritten on the next verify of the same WBS.
142
+
143
+ ## Optional check severity (task 0592, feature F92)
144
+
145
+ `checks[]` rows may carry an optional `severity` field so the aggregation policy can distinguish a
146
+ blocking review finding from a non-blocking one:
147
+
148
+ ```typescript
149
+ checks: Array<{
150
+ name: string; // aliases accepted on read: `check`, `id`
151
+ status: 'pass' | 'fail' | 'warn';
152
+ evidence: string;
153
+ severity?: 'blocker' | 'major' | 'minor' | 'advisory';
154
+ }>;
155
+ ```
156
+
157
+ ### Compatibility aliases: check label key
158
+
159
+ Producers SHOULD write the check label as `name`. Consumers ALSO accept `check` and `id` as
160
+ aliases, normalized to `name` in the same single place the `scenario`→`id` coverage alias is
161
+ normalized (`checkSchema` in `verify-verdict.ts`). A row carrying none of the three is
162
+ **structurally invalid** — an unnamed check cannot be matched by the aggregation policy's
163
+ `task-check` detection, so it would silently exempt a failed task-check from the completion rule.
164
+
165
+ The alias resolves for raw rows as well as parsed ones (`checkRowName`), because
166
+ `aggregateVerifyVerdict` and the done guard's task-check lookup both run over unparsed artifacts.
167
+ Precedence is `name` → `check` → `id`, first non-empty string wins.
168
+
169
+ ## Canonical runtime contract + one aggregation policy (task 0592)
170
+
171
+ The prose shape above is executed by a single runtime-validated contract:
172
+ `packages/app/src/services/verify-verdict.ts`. It owns:
173
+
174
+ - **`verifyVerdictSchema`** (Zod) — validates the persisted artifact and distinguishes
175
+ **missing** (file absent / empty), **malformed** (bad JSON), **structurally invalid**
176
+ (`invalid` outcome), and **valid** (with case-normalized `verdict`). The `scenario`→`id` coverage
177
+ alias and the `check`/`id`→`name` check-label alias are both normalized here, in exactly one place.
178
+ - **`aggregateVerifyVerdict`** — the ONE aggregation policy every verdict consumer uses (answer
179
+ derivation, persisted-artifact consistency checks, task/feature validation, record rendering,
180
+ and the done-transition gate). Requirements/AC use `MET`/`PARTIAL`/`UNMET`/`N/A`. Checks:
181
+ non-pass **blocker** → FAIL, non-pass **major** → PARTIAL, **minor**/**advisory** do not block;
182
+ legacy rows without a severity map `fail` → FAIL and `warn` → PARTIAL. An independent task-check
183
+ failure can never yield PASS.
184
+ - A row-less artifact aggregates to UNKNOWN — a stored PASS that does not recompute to PASS
185
+ (including a row-less PASS) is treated as non-PASS at the done gate. The done-transition choke
186
+ point (`done-transition-guard.ts` `evaluateDoneTransition`) is the final authority; workflow JSON
187
+ routing may select `verify → record/failed` but cannot weaken the final transition.
188
+ - `--force-done --reason` on `spur task update <wbs> done` remains the sole auditable override.
@@ -241,16 +241,16 @@ all core requirements MET → PASS
241
241
  `PARTIAL` blocks the gate exactly like `FAIL` — the distinction only tells the operator *how far
242
242
  off* delivery is.
243
243
 
244
- ### Step 7 — Write findings to the task
244
+ ### Step 7 — Return the review fragment (no section write)
245
245
 
246
- Write the review body to the task's `## Review` section via CLI verbs. The body MUST lead with a
247
- `| Priority | Dimension | Location | Finding |` table (the L3 `hasPopulatedPriorityTable` gate at
246
+ Assemble the findings into a **review fragment** the priority-table lead + the per-requirement
247
+ traceability table. The fragment MUST lead with a `| Priority | Dimension | Location | Finding |`
248
+ table (the L3 `hasPopulatedPriorityTable` gate at
248
249
  `task-check.ts:96-106` requires at least one `P[1-4]` row with non-placeholder siblings — any other
249
250
  shape, e.g. `| Req | Status | Evidence |` alone, is structurally rejected and denies the
250
251
  `wip→testing` transition). Use the same canonical shape as `sp:code-verification`:
251
252
 
252
- ```bash
253
- cat > /tmp/<wbs>-functional.md <<'BODY'
253
+ ```markdown
254
254
  | Priority | Dimension | Location | Finding |
255
255
  | --- | --- | --- | --- |
256
256
  | P4 | — | — | No P1–P3 findings; functional verdict PASS |
@@ -260,16 +260,17 @@ cat > /tmp/<wbs>-functional.md <<'BODY'
260
260
  | R1 | MET | `src/api/users.ts:42` — `createUser()` |
261
261
  | R2 | PARTIAL | `src/api/users.ts:42` — basic only; MISSING duplicate-email handling |
262
262
  | R3 | UNMET | no implementation found; searched src/api/, src/services/ |
263
- BODY
264
- spur task update <wbs> --section Review --from-file /tmp/<wbs>-functional.md
265
- rm /tmp/<wbs>-functional.md
266
263
  ```
267
264
 
268
265
  For a PARTIAL/FAIL verdict, replace the P4 row with the actual P1–P3 findings ranked by severity.
269
266
  The priority table leads; the traceability table follows for per-requirement detail.
270
267
 
271
- Section bodies passed to `spur task update --section` must be **body-only** — no same-level (`##`)
272
- headings inside the body. Tables and bold labels are fine.
268
+ **Fragment-only discipline (F92 0593 R1).** In coordinated mode (dispatched by `/sp:dev-review` /
269
+ `sp:super-reviewer`), do **not** write `## Review` return the fragment to the coordinator, which
270
+ merges the functional + SECUA + architecture fragments into the combined `## Review` section. Only
271
+ `sp:super-reviewer` (the review coordinator) writes `## Review`; direct component-skill use is
272
+ advisory output. `spur task record` backfills a **bare** `## Review` from the verdict artifact as a
273
+ standalone compatibility fallback only and never overwrites authored Review (F92 0593 R1).
273
274
 
274
275
  ### Step 8 — Report
275
276
 
@@ -289,11 +290,11 @@ Include the per-requirement traceability table in the report:
289
290
  | R3 | UNMET | no implementation found; searched src/api/, src/services/ |
290
291
  ```
291
292
 
292
- **Under the pipeline**, `sp:functional-review` is the review step dispatched by `/sp:dev-review`,
293
- so it owns `## Review` — its `--section Review` write is the authoritative source. The pipeline's
294
- `record` step transcribes only `## Testing` from the verify verdict (`code-verification/SKILL.md`,
295
- `task-record.ts:226-247`); it does not overwrite a non-bare `## Review` thanks to the
296
- `sectionIsBare` guard (`task-service.ts:485`). Keep the priority-table lead stable so the L3 gate
293
+ **Under the pipeline**, `sp:functional-review` is a component of `/sp:dev-review`: it returns its
294
+ fragment to the coordinator (`sp:super-reviewer`), which writes the combined `## Review`. The
295
+ `record` step transcribes only `## Testing` from the verdict artifact and backfills `## Review`
296
+ only when the section is bare (`sectionIsBare` guard, `task-service.ts`); it never overwrites the
297
+ coordinator's authored Review. Keep the priority-table lead stable in the fragment so the L3 gate
297
298
  stays satisfied through `record` → `done`.
298
299
 
299
300
  ---
@@ -147,8 +147,9 @@ blank Design before implement (`/sp:dev-refine` / `dev-refineall`).
147
147
  The `template` field is the **single variant axis** (TASK_VARIANTS): it selects the section
148
148
  layout (the `section-matrix.yaml` variant), the scaffold body file
149
149
  (`.spur/templates/task/<variant>.md`), and is written to the task's `template:` frontmatter.
150
- Which sections actually appear is **stage-driven** by the matrix (e.g. `Solution` only from `wip`),
151
- not a fixed list — pick the variant by *purpose*, not by a section checklist.
150
+ Which sections actually appear is **status-driven** by the runtime matrix query
151
+ `spur task sections <wbs> list --json` rather than assuming a fixed list — pick the variant by
152
+ *purpose*, not by a section checklist.
152
153
 
153
154
  | Variant | When to use |
154
155
  |---------|-------------|
@@ -464,18 +465,16 @@ with research/design/implement/integrate/test as `## Plan` steps.
464
465
 
465
466
  A task created with a spec (a `--feature` link, or a batch item carrying `background`/
466
467
  `requirements`) lands at **`todo`** — "ready to execute" (§2.3). A bare capture lands at
467
- **`backlog`** — "still preparing". The Section-Status-Matrix
468
- (`.spur/tasks/section-matrix.yaml`) decides which sections a task carries at each stage; the
469
- producer renders them with invisible HTML guidance comments. You do **not** hand-build the section
470
- list — `spur task create` / `batch-create` does it from the matrix.
471
-
472
- | Stage | Means | Sections present |
473
- |-------|-------|------------------|
474
- | `backlog` | still preparing | Background |
475
- | `todo` | ready to execute the **HITL review gate** | Background, Acceptance Criteria, Design, Plan (+ Q&A/Requirements optional) |
476
- | `wip` | implementing | + Solution (the change-map starts here) |
477
- | `testing` | verifying | Solution, Testing |
478
- | `done` | shipped | Solution, Testing, Review (gated) |
468
+ **`backlog`** — "still preparing". The runtime section matrix decides which sections a task
469
+ carries at each status; the producer renders them with invisible HTML guidance comments. You do
470
+ **not** hand-build the section list `spur task create` / `batch-create` does it from the matrix.
471
+
472
+ **Query the runtime contract — never restate a status-to-section table (F92 0593 R2).**
473
+ `spur task sections <wbs> list --json` returns the matrix required/optional/forbidden sections
474
+ per status; `spur task check <wbs> --json` returns what the gate requires at the current status.
475
+ Both replace any static "sections present at stage X" projection. The section *content* guidance
476
+ below (Design = the decision record, Solution = the change-map) is prose ownership, not a
477
+ section-layout authority.
479
478
 
480
479
  **Design (written at `todo`, for HITL review) = the decision record — WHAT/WHY:**
481
480
  the chosen approach + a one-line reason, rejected alternatives, key interface/type **signatures**
@@ -8,7 +8,10 @@ see_also:
8
8
 
9
9
  # Task L3 guard cheat sheet
10
10
 
11
- Read this before writing `Solution`, `Testing`, or `Review`. The owning implementations are
11
+ Read this before writing `Solution`, `Testing`, or `Review`. Section **writers** are one per
12
+ evidence section (F92 0593 R1): implement owns `Solution`, the review coordinator
13
+ (`/sp:dev-review`) owns `Review`, and `spur task record` owns `Testing` (bare-`## Review`
14
+ backfill is fallback-only). The owning implementations are
12
15
  `.spur/workflows/task-lifecycle.yaml`, `hasPopulatedPriorityTable()` in
13
16
  `packages/app/src/services/task-check.ts`, the verdict normalizers in
14
17
  `packages/app/src/services/task-record.ts`, and `TASK_CANONICAL_SECTIONS` in
@@ -25,8 +28,9 @@ done → wip
25
28
  backlog|todo|wip|testing|blocked → cancelled
26
29
  ```
27
30
 
28
- - `wip → testing` runs `spur task check <wbs>`.
29
- - `testing → done` runs `spur task check <wbs> --strict-core`, followed by the PASS-verdict gate.
31
+ - `wip → testing` runs `spur task check <wbs> --as testing` (F92 R3 — target-aware).
32
+ - `testing → done` runs `spur task check <wbs> --as done` (evaluates the `done` row incl. `gate:true`),
33
+ followed by the PASS-verdict gate. `--strict-core` remains as a compatibility alias only.
30
34
  - Invalid: `todo → testing`, `todo → done`, and `wip → done`.
31
35
  - Normal path: `backlog → todo → wip → testing → done`.
32
36