@gobing-ai/spur 0.3.50 → 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.
Files changed (30) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +1995 -1939
  3. package/config/rules/quality/coverage-gate.yaml +3 -2
  4. package/config/tasks/section-matrix.yaml +7 -2
  5. package/config/workflows/pr-review.yaml +13 -11
  6. package/config/workflows/task-lifecycle.yaml +7 -6
  7. package/config/workflows/task-pipeline.yaml +37 -16
  8. package/package.json +1 -1
  9. package/plugins/sp/agents/super-planner.md +3 -2
  10. package/plugins/sp/agents/super-reviewer.md +5 -0
  11. package/plugins/sp/commands/dev-review.md +1 -1
  12. package/plugins/sp/plugin.json +1 -1
  13. package/plugins/sp/scripts/pr-reviewing.ts +57 -5
  14. package/plugins/sp/scripts/stage-registry-adapter.ts +40 -25
  15. package/plugins/sp/skills/code-improvement/SKILL.md +5 -3
  16. package/plugins/sp/skills/code-verification/SKILL.md +28 -31
  17. package/plugins/sp/skills/code-verification/references/verdict-schema.md +47 -0
  18. package/plugins/sp/skills/functional-review/SKILL.md +16 -15
  19. package/plugins/sp/skills/pr-reviewing/SKILL.md +6 -3
  20. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +13 -14
  21. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +7 -3
  22. package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +22 -13
  23. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -11
  24. package/plugins/sp/skills/spur-cli/references/tasks.md +16 -10
  25. package/plugins/sp/skills/spur-dev/SKILL.md +10 -5
  26. package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -5
  27. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +11 -4
  28. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
  29. package/plugins/sp/skills/spur-dev/references/section-batching.md +29 -13
  30. package/spur.js +382 -231
@@ -2,8 +2,9 @@ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
2
2
  # Coverage gate — per-file line coverage meets threshold from Bun's lcov output.
3
3
  # Absorbed from ts-libs/.spur/rules/quality/coverage-gate.yaml, re-scoped to
4
4
  # Spur's monorepo layout:
5
- # - lcovPath kept at .coverage/lcov.info (Spur's `bun run test` writes
6
- # coverage there via --coverage-dir=.coverage)
5
+ # - lcovPath kept at .coverage/lcov.info (Spur's `bun run test:coverage`
6
+ # what `bun run check` and the `*:full` chains run — writes coverage there
7
+ # via --coverage-dir=.coverage; plain `bun run test` skips coverage)
7
8
  # - include expanded to apps/** + packages/** (Spur tests cover both;
8
9
  # ts-libs only covers packages/**)
9
10
  # - threshold kept at 90 matching bunfig.toml coverageThreshold
@@ -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:
@@ -43,19 +43,21 @@ terminalStates:
43
43
  failureStates:
44
44
  - failed
45
45
  vars:
46
- mode: 'full'
47
- baseBranch: ''
48
- focus: ''
49
- noWait: 'false'
50
- waitTimeoutSec: '600'
51
- waitIntervalSec: '30'
52
- preReviewCmd: ''
53
- __runId: ''
46
+ mode: "full"
47
+ baseBranch: ""
48
+ focus: ""
49
+ noWait: "false"
50
+ waitTimeoutSec: "600"
51
+ waitIntervalSec: "30"
52
+ preReviewCmd: ""
53
+ __runId: ""
54
54
 
55
55
  states:
56
56
  - id: preflight
57
57
  description: >
58
- Soft probe: git/gh/repo checks (detached HEAD, dirty tree, gh auth, GitHub remote).
58
+ Soft probe: git/gh/repo checks (detached HEAD, dirty tree, gh auth, GitHub remote,
59
+ base-branch refusal — preflight refuses when the current branch IS the resolved base,
60
+ before any push can publish it).
59
61
  Writes PASS|FAIL to .spur/run/${vars.__runId}-pr-preflight.status; always exit 0.
60
62
  onEnter:
61
63
  - kind: shell
@@ -64,7 +66,7 @@ states:
64
66
  mkdir -p .spur/run &&
65
67
  STATUS_FILE=".spur/run/$__runId-pr-preflight.status" &&
66
68
  set +e &&
67
- bun "$(superskill script path sp pr-reviewing.ts)" preflight --json > ".spur/run/$__runId-pr-context.json";
69
+ bun "$(superskill script path sp pr-reviewing.ts)" preflight --base "$baseBranch" --json > ".spur/run/$__runId-pr-context.json";
68
70
  rc=$?; set -e &&
69
71
  if [ "$rc" -eq 0 ]; then printf 'PASS\n' > "$STATUS_FILE"; else printf 'FAIL\n' > "$STATUS_FILE"; fi &&
70
72
  exit 0
@@ -212,7 +214,7 @@ transitions:
212
214
  command: 'test "$(cat .spur/run/$__runId-pr-preflight.status 2>/dev/null)" = PASS'
213
215
  - from: preflight
214
216
  to: failed
215
- description: Preflight red (detached HEAD, dirty tree, gh auth, no GitHub remote) — stop before any publishing
217
+ description: Preflight red (detached HEAD, dirty tree, gh auth, no GitHub remote, or the current branch being the base branch) — stop before any publishing
216
218
  guard:
217
219
  kind: always
218
220
 
@@ -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
@@ -85,6 +85,12 @@ vars:
85
85
  # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
86
86
  # interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
87
87
  qualityGateCmd: "bun run format && bun run spur-check"
88
+ # Cheap red-detector run before the full gate on **recheck only**; empty ⇒ no probe
89
+ # (full gate every recheck — the pre-0587 behavior). A project overriding qualityGateCmd
90
+ # should override this too. TRUSTED CONFIG ONLY — executed via `sh -c` (same surface as
91
+ # qualityGateCmd). Invariant: `review` is only ever entered through a full green
92
+ # qualityGateCmd — only the full gate writes PASS to <wbs>-test-gate.status.
93
+ gateProbeCmd: "bun run lint"
88
94
  # Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
89
95
  # Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
90
96
  qualityGateMaxFixAttempts: "2"
@@ -300,7 +306,7 @@ states:
300
306
  # `${vars.qualityGateCmd}` at `test` is the gate that actually decides.
301
307
  - kind: shell
302
308
  options:
303
- command: '$formatCmd ; exit 0'
309
+ command: "$formatCmd ; exit 0"
304
310
 
305
311
  # ── test hop (quality gate + bounded auto-fix) ─────────────────────────────
306
312
  # NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
@@ -389,28 +395,43 @@ states:
389
395
  or the pipeline `failed` state (FAIL and attempts exhausted) — never a
390
396
  raw lifecycle abort that skips the terminal `failed` state.
391
397
  onEnter:
398
+ # 0587 R3: probe-then-full recheck. A red gateProbeCmd records FAIL and skips the full
399
+ # gate (the measured waste is re-running a 110–140s gate to learn the tree is still red);
400
+ # a green probe (or empty gateProbeCmd) falls through to the full-gate loop unchanged.
401
+ # Only the full gate writes PASS, so the `test-recheck → review` guard (reads PASS)
402
+ # still means a full green qualityGateCmd ran — invariant preserved by construction.
392
403
  - kind: shell
393
404
  options:
394
405
  command: >-
395
406
  mkdir -p .spur/run &&
396
407
  STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
397
408
  LOG_FILE=".spur/run/$wbs-test-gate.log" &&
409
+ FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
398
410
  : > "$LOG_FILE" &&
399
- gate_attempt=1;
400
- while [ "$gate_attempt" -le 5 ]; do
401
- ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
402
- sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
403
- gate_locked=0;
404
- grep -q 'SQLiteError: database is locked' "$ATTEMPT_LOG" && gate_locked=1;
405
- cat "$ATTEMPT_LOG" >> "$LOG_FILE";
406
- rm -f "$ATTEMPT_LOG";
407
- if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
408
- printf 'quality gate: database is locked; retrying (%s/5) in 10s\n' "$gate_attempt" | tee -a "$LOG_FILE";
409
- sleep 10;
410
- gate_attempt=$((gate_attempt + 1));
411
- done &&
411
+ probe_rc=0;
412
+ if [ -n "$gateProbeCmd" ]; then
413
+ sh -c "$gateProbeCmd" > "$LOG_FILE.probe" 2>&1; probe_rc=$?;
414
+ cat "$LOG_FILE.probe" >> "$LOG_FILE";
415
+ rm -f "$LOG_FILE.probe";
416
+ fi;
417
+ if [ "$probe_rc" -ne 0 ]; then
418
+ gate_rc=$probe_rc;
419
+ else
420
+ gate_attempt=1;
421
+ while [ "$gate_attempt" -le 5 ]; do
422
+ ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
423
+ sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
424
+ gate_locked=0;
425
+ grep -q 'SQLiteError: database is locked' "$ATTEMPT_LOG" && gate_locked=1;
426
+ cat "$ATTEMPT_LOG" >> "$LOG_FILE";
427
+ rm -f "$ATTEMPT_LOG";
428
+ if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
429
+ printf 'quality gate: database is locked; retrying (%s/5) in 10s\n' "$gate_attempt" | tee -a "$LOG_FILE";
430
+ sleep 10;
431
+ gate_attempt=$((gate_attempt + 1));
432
+ done;
433
+ fi &&
412
434
  cat "$LOG_FILE" &&
413
- FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
414
435
  set +e; grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+' "$LOG_FILE" | sort -u | head -20 | tr '\n' ' ' > "$FINDINGS_FILE"; set -e &&
415
436
  if [ "$gate_rc" -eq 0 ]; then
416
437
  printf 'PASS\n' > "$STATUS_FILE";
@@ -709,4 +730,4 @@ transitions:
709
730
  guard:
710
731
  kind: shell
711
732
  options:
712
- command: '! $spurBin task check $wbs'
733
+ command: "! $spurBin task check $wbs"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.50",
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.50",
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;
@@ -88,6 +88,12 @@ interface PreflightContext {
88
88
  defaultBranch: string;
89
89
  }
90
90
 
91
+ interface Upstream {
92
+ ref: string;
93
+ ahead: number;
94
+ behind: number;
95
+ }
96
+
91
97
  interface Finding {
92
98
  kind: 'review' | 'inline' | 'comment';
93
99
  severity: string;
@@ -104,10 +110,14 @@ interface ParsedArgs {
104
110
  booleans: Set<string>;
105
111
  }
106
112
 
107
- // ─── Process runner (git/gh resolved from PATH so tests can stub them) ──────
113
+ // ─── Process runner (single seam; every git/gh call routes through run) ─────
108
114
 
109
- function run(cmd: readonly string[]): CmdResult {
110
- // 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.
111
121
  const proc = spawnSync(cmd[0] ?? '', [...cmd.slice(1)], { encoding: 'utf8', env: process.env });
112
122
  return {
113
123
  code: proc.status ?? 1,
@@ -115,6 +125,20 @@ function run(cmd: readonly string[]): CmdResult {
115
125
  stderr: proc.stderr ?? '',
116
126
  error: proc.error?.message,
117
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);
118
142
  }
119
143
 
120
144
  function runOk(cmd: readonly string[], what: string): string {
@@ -433,6 +457,22 @@ function preflightContext(): PreflightContext {
433
457
  };
434
458
  }
435
459
 
460
+ /** Upstream divergence, non-fatal: a missing upstream is a normal state, not an error. */
461
+ function resolveUpstream(): Upstream | null {
462
+ const refRes = run(['git', 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);
463
+ if (refRes.code !== 0) return null;
464
+ const ref = refRes.stdout.trim();
465
+ const count = (res: CmdResult): number => {
466
+ const n = Number(res.stdout.trim());
467
+ return res.code === 0 && Number.isFinite(n) ? n : 0;
468
+ };
469
+ return {
470
+ ref,
471
+ ahead: count(run(['git', 'rev-list', '--count', '@{u}..HEAD'])),
472
+ behind: count(run(['git', 'rev-list', '--count', 'HEAD..@{u}'])),
473
+ };
474
+ }
475
+
436
476
  function viewPr(): GhPr | null {
437
477
  const res = run([
438
478
  'gh',
@@ -507,15 +547,27 @@ function cmdPreflight(args: ParsedArgs): void {
507
547
  2,
508
548
  );
509
549
  }
550
+ const base = (args.flags.get('--base') ?? '').trim() || ctx.defaultBranch;
551
+ const upstream = resolveUpstream();
552
+ if (ctx.branch === base) {
553
+ writeStatus(args, 'FAIL');
554
+ fail(
555
+ args,
556
+ `current branch is the base branch (${base}) — a PR reviews a feature branch against it; ` +
557
+ 'check out a feature branch (nothing on the base branch is reviewable)',
558
+ 2,
559
+ );
560
+ }
510
561
  writeStatus(args, 'PASS');
511
562
  emit(
512
563
  args,
513
- { ok: true, ...ctx },
564
+ { ok: true, ...ctx, upstream },
514
565
  [
515
566
  `Repository: ${ctx.nameWithOwner}`,
516
567
  `Branch: ${ctx.branch}`,
517
568
  `HEAD: ${ctx.shortHead}`,
518
569
  `Default: ${ctx.defaultBranch}`,
570
+ `Upstream: ${upstream ? `${upstream.ref} (ahead ${upstream.ahead}, behind ${upstream.behind})` : `none (publishing would create origin/${ctx.branch})`}`,
519
571
  'Local: clean',
520
572
  ].join('\n'),
521
573
  );
@@ -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