@gobing-ai/spur 0.3.49 → 0.3.51

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 (66) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +12198 -1
  3. package/config/rules/quality/coverage-gate.yaml +3 -2
  4. package/config/workflows/pr-review.yaml +13 -11
  5. package/config/workflows/task-pipeline.yaml +37 -16
  6. package/package.json +9 -9
  7. package/plugins/sp/plugin.json +1 -1
  8. package/plugins/sp/scripts/pr-reviewing.ts +35 -1
  9. package/plugins/sp/skills/code-verification/SKILL.md +14 -0
  10. package/plugins/sp/skills/pr-reviewing/SKILL.md +6 -3
  11. package/plugins/sp/skills/spur-cli/references/tasks.md +1 -0
  12. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +18 -2
  13. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -6
  14. package/spur.js +4804 -5601
  15. package/web/_astro/BoardApp.pdtYu-yW.js +1 -0
  16. package/web/_astro/{BoardApp.BjQUNhuj.js → BoardApp.plVXYkKo.js} +11 -11
  17. package/web/_astro/{TaskDetail.CVBuD6dF.js → TaskDetail.5DIyVQ_N.js} +1 -1
  18. package/web/_astro/{arc.BMMjdODi.js → arc.BnUgL7ho.js} +1 -1
  19. package/web/_astro/{architectureDiagram-3BPJPVTR.BU5ShzXf.js → architectureDiagram-3BPJPVTR.B9YMJau-.js} +1 -1
  20. package/web/_astro/{blockDiagram-GPEHLZMM.Bj1iEqPD.js → blockDiagram-GPEHLZMM.Dw2qgvWJ.js} +1 -1
  21. package/web/_astro/{c4Diagram-AAUBKEIU.vX8wepCL.js → c4Diagram-AAUBKEIU.jTbU0-Nk.js} +1 -1
  22. package/web/_astro/channel.DEqvaz-I.js +1 -0
  23. package/web/_astro/{chunk-2J33WTMH.BKAipTym.js → chunk-2J33WTMH.Di46EtkU.js} +1 -1
  24. package/web/_astro/{chunk-4BX2VUAB.B68XkPG7.js → chunk-4BX2VUAB.DweHBwqQ.js} +1 -1
  25. package/web/_astro/{chunk-55IACEB6.BmeDLcrc.js → chunk-55IACEB6.rRjTKaa7.js} +1 -1
  26. package/web/_astro/{chunk-727SXJPM.PDuBA3Kw.js → chunk-727SXJPM.BkEQIkSV.js} +1 -1
  27. package/web/_astro/{chunk-AQP2D5EJ.C7A044za.js → chunk-AQP2D5EJ.Cfe9IvAd.js} +1 -1
  28. package/web/_astro/{chunk-FMBD7UC4.BtzKKFqR.js → chunk-FMBD7UC4.fI6tLbeN.js} +1 -1
  29. package/web/_astro/{chunk-ND2GUHAM.BJuDeeOy.js → chunk-ND2GUHAM.BIjgpq0O.js} +1 -1
  30. package/web/_astro/{chunk-QZHKN3VN.DSeMDgcQ.js → chunk-QZHKN3VN.ZwLnVxyz.js} +1 -1
  31. package/web/_astro/{classDiagram-4FO5ZUOK.D53Q4tCw.js → classDiagram-4FO5ZUOK.BJAGYidE.js} +1 -1
  32. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D53Q4tCw.js → classDiagram-v2-Q7XG4LA2.BJAGYidE.js} +1 -1
  33. package/web/_astro/{cose-bilkent-S5V4N54A.c712AFRH.js → cose-bilkent-S5V4N54A.B5y3nz26.js} +1 -1
  34. package/web/_astro/{dagre-BM42HDAG.D-idisph.js → dagre-BM42HDAG.DHmw0ZBC.js} +1 -1
  35. package/web/_astro/{diagram-2AECGRRQ.DLgnsJCU.js → diagram-2AECGRRQ.10gapJfY.js} +1 -1
  36. package/web/_astro/{diagram-5GNKFQAL.BiaxBVqx.js → diagram-5GNKFQAL.Gh33_drM.js} +1 -1
  37. package/web/_astro/{diagram-KO2AKTUF.C8HX1vd8.js → diagram-KO2AKTUF.8ROWlfcX.js} +1 -1
  38. package/web/_astro/{diagram-LMA3HP47.CfqDLLes.js → diagram-LMA3HP47.BsP3tKmg.js} +1 -1
  39. package/web/_astro/{diagram-OG6HWLK6.15SDiEed.js → diagram-OG6HWLK6.DM2WFDhU.js} +1 -1
  40. package/web/_astro/{erDiagram-TEJ5UH35.DksYtOYM.js → erDiagram-TEJ5UH35.royaC_lH.js} +1 -1
  41. package/web/_astro/{flowDiagram-I6XJVG4X.DR_Au-HV.js → flowDiagram-I6XJVG4X.C9GEvIie.js} +1 -1
  42. package/web/_astro/{ganttDiagram-6RSMTGT7.CHhHrffI.js → ganttDiagram-6RSMTGT7.CXO4T2U0.js} +1 -1
  43. package/web/_astro/{gitGraphDiagram-PVQCEYII.B2Xehvam.js → gitGraphDiagram-PVQCEYII.Q02KzYH7.js} +1 -1
  44. package/web/_astro/{infoDiagram-5YYISTIA.C9c3CNNN.js → infoDiagram-5YYISTIA.DNfSDKJt.js} +1 -1
  45. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BibUHkh8.js → ishikawaDiagram-YF4QCWOH.BFttvtgJ.js} +1 -1
  46. package/web/_astro/{journeyDiagram-JHISSGLW.BYoVHiyO.js → journeyDiagram-JHISSGLW.Cfm-2SbG.js} +1 -1
  47. package/web/_astro/{kanban-definition-UN3LZRKU.CM1K5wHE.js → kanban-definition-UN3LZRKU.BWGNW6fV.js} +1 -1
  48. package/web/_astro/{linear.SPpjJUb-.js → linear.BprdUk2Y.js} +1 -1
  49. package/web/_astro/{mermaid.core.BAgx3nnb.js → mermaid.core.B2zRNxtX.js} +4 -4
  50. package/web/_astro/{mindmap-definition-RKZ34NQL.D35oPG1R.js → mindmap-definition-RKZ34NQL.g9jij8sw.js} +1 -1
  51. package/web/_astro/{pieDiagram-4H26LBE5.DiWuRwk7.js → pieDiagram-4H26LBE5.CbzR4n5b.js} +1 -1
  52. package/web/_astro/{quadrantDiagram-W4KKPZXB.B9PBzTWn.js → quadrantDiagram-W4KKPZXB.DxV3viWt.js} +1 -1
  53. package/web/_astro/{requirementDiagram-4Y6WPE33.CYuuamFN.js → requirementDiagram-4Y6WPE33.DvAb90JS.js} +1 -1
  54. package/web/_astro/{sankeyDiagram-5OEKKPKP.W24UhhtD.js → sankeyDiagram-5OEKKPKP.CIFz5t4D.js} +1 -1
  55. package/web/_astro/{sequenceDiagram-3UESZ5HK.BpbNjA51.js → sequenceDiagram-3UESZ5HK.DzlEQynd.js} +1 -1
  56. package/web/_astro/{stateDiagram-AJRCARHV.DqVsHudf.js → stateDiagram-AJRCARHV.Dph2V-ts.js} +1 -1
  57. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CzwHYX81.js → stateDiagram-v2-BHNVJYJU.wJhfrLAy.js} +1 -1
  58. package/web/_astro/{timeline-definition-PNZ67QCA.Bc3B6djw.js → timeline-definition-PNZ67QCA.BrSiyubX.js} +1 -1
  59. package/web/_astro/{vennDiagram-CIIHVFJN.C-D5rh8O.js → vennDiagram-CIIHVFJN.COobPrUX.js} +1 -1
  60. package/web/_astro/{wardley-L42UT6IY.D7PdYCqn.js → wardley-L42UT6IY.CrjeTx8A.js} +1 -1
  61. package/web/_astro/{wardleyDiagram-YWT4CUSO.CwmJKXF3.js → wardleyDiagram-YWT4CUSO.B8_fTQ8E.js} +1 -1
  62. package/web/_astro/{xychartDiagram-2RQKCTM6.avDYnLsb.js → xychartDiagram-2RQKCTM6.BPpokgVv.js} +1 -1
  63. package/web/favicon.svg +1 -0
  64. package/web/index.html +1 -1
  65. package/web/_astro/BoardApp.8hiqShQn.js +0 -1
  66. package/web/_astro/channel.EwdSemIC.js +0 -1
@@ -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
@@ -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
 
@@ -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.49",
3
+ "version": "0.3.51",
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",
@@ -53,14 +53,14 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@commander-js/extra-typings": "^14.0.0",
56
- "@gobing-ai/ts-db": "^0.4.35",
57
- "@gobing-ai/ts-ai-runner": "^0.4.35",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.4.35",
59
- "@gobing-ai/ts-infra": "^0.4.35",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.4.31",
61
- "@gobing-ai/ts-rule-engine": "^0.4.35",
62
- "@gobing-ai/ts-runtime": "^0.4.35",
63
- "@gobing-ai/ts-utils": "^0.4.35",
56
+ "@gobing-ai/ts-db": "^0.4.38",
57
+ "@gobing-ai/ts-ai-runner": "^0.4.38",
58
+ "@gobing-ai/ts-dual-workflow-engine": "^0.4.38",
59
+ "@gobing-ai/ts-infra": "^0.4.38",
60
+ "@gobing-ai/ts-llm-jsonl-importer": "^0.4.38",
61
+ "@gobing-ai/ts-rule-engine": "^0.4.38",
62
+ "@gobing-ai/ts-runtime": "^0.4.38",
63
+ "@gobing-ai/ts-utils": "^0.4.38",
64
64
  "@types/bun": "1.3.14",
65
65
  "@types/figlet": "^1.7.0",
66
66
  "@types/node-notifier": "8.0.5",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.49",
3
+ "version": "0.3.51",
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"]
@@ -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;
@@ -433,6 +439,22 @@ function preflightContext(): PreflightContext {
433
439
  };
434
440
  }
435
441
 
442
+ /** Upstream divergence, non-fatal: a missing upstream is a normal state, not an error. */
443
+ function resolveUpstream(): Upstream | null {
444
+ const refRes = run(['git', 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);
445
+ if (refRes.code !== 0) return null;
446
+ const ref = refRes.stdout.trim();
447
+ const count = (res: CmdResult): number => {
448
+ const n = Number(res.stdout.trim());
449
+ return res.code === 0 && Number.isFinite(n) ? n : 0;
450
+ };
451
+ return {
452
+ ref,
453
+ ahead: count(run(['git', 'rev-list', '--count', '@{u}..HEAD'])),
454
+ behind: count(run(['git', 'rev-list', '--count', 'HEAD..@{u}'])),
455
+ };
456
+ }
457
+
436
458
  function viewPr(): GhPr | null {
437
459
  const res = run([
438
460
  'gh',
@@ -507,15 +529,27 @@ function cmdPreflight(args: ParsedArgs): void {
507
529
  2,
508
530
  );
509
531
  }
532
+ const base = (args.flags.get('--base') ?? '').trim() || ctx.defaultBranch;
533
+ const upstream = resolveUpstream();
534
+ if (ctx.branch === base) {
535
+ writeStatus(args, 'FAIL');
536
+ fail(
537
+ args,
538
+ `current branch is the base branch (${base}) — a PR reviews a feature branch against it; ` +
539
+ 'check out a feature branch (nothing on the base branch is reviewable)',
540
+ 2,
541
+ );
542
+ }
510
543
  writeStatus(args, 'PASS');
511
544
  emit(
512
545
  args,
513
- { ok: true, ...ctx },
546
+ { ok: true, ...ctx, upstream },
514
547
  [
515
548
  `Repository: ${ctx.nameWithOwner}`,
516
549
  `Branch: ${ctx.branch}`,
517
550
  `HEAD: ${ctx.shortHead}`,
518
551
  `Default: ${ctx.defaultBranch}`,
552
+ `Upstream: ${upstream ? `${upstream.ref} (ahead ${upstream.ahead}, behind ${upstream.behind})` : `none (publishing would create origin/${ctx.branch})`}`,
519
553
  'Local: clean',
520
554
  ].join('\n'),
521
555
  );
@@ -127,6 +127,20 @@ heading/comment unrelated to the requirement fails the row to UNMET and surfaces
127
127
  a finding (severity >= P2). This closes the gap where a verify run certified a task `done` citing
128
128
  `evidence:134` that was actually a sibling ticket's telemetry text (0299 R1, from the 0282 re-audit).
129
129
 
130
+ **External evidence (task 0584 / ADR-062) — legal citation form.** Evidence that lives OUTSIDE this
131
+ repository has a frozen non-anchor form: a named origin plus a backticked path with the line number
132
+ **outside** the backticks.
133
+
134
+ ```
135
+ Evidence: @gobing-ai/ts-llm-jsonl-importer `src/mappers.ts` line 481 — omp call_id write
136
+ ```
137
+
138
+ Use this form ONLY when the evidence genuinely lives outside `spur`'s working tree (a `@gobing-ai/ts-*`
139
+ source under `~/xprojects/ts-libs`, or a gitignored `.spur/run/**` artifact). `checkLineAnchors`
140
+ classifies it as external and never raises `L4.stale-line-anchor` for it (R1). Do NOT use it for a file
141
+ that lives in this repo — in-repo evidence MUST use the repo-relative backtick form
142
+ `` `path:line` `` / `` `path:start-end` ``, and citing it in the external form still reports (R2).
143
+
130
144
  ### Step 5 — Acceptance Criteria guard
131
145
 
132
146
  If the task has a non-empty Acceptance Criteria section, evaluate every checklist item and every
@@ -135,9 +135,12 @@ bun "$(superskill script path sp pr-reviewing.ts)" <subcommand> [flags]
135
135
  Installed targets resolve the staged TypeScript source and execute it with Bun, matching the rest
136
136
  of `plugins/sp/scripts`.
137
137
 
138
- 1. **Preflight** — `<script> preflight --json`. Hard-fails on a
139
- detached HEAD, missing `gh` auth, no GitHub remote, or a dirty tree. On a dirty tree, triage
140
- with the user (commit/stash/exclude) before continuing the workflow refuses to guess.
138
+ 1. **Preflight** — `<script> preflight --base "$base" --json`. Hard-fails on a
139
+ detached HEAD, missing `gh` auth, no GitHub remote, a dirty tree, or the current branch
140
+ being the base branch (a PR reviews a feature branch against the base; nothing on the
141
+ base branch is reviewable, and the guard runs before any push can publish it). On a
142
+ dirty tree, triage with the user (commit/stash/exclude) before continuing — the
143
+ workflow refuses to guess.
141
144
  2. **Hygiene** — `<script> hygiene --base "$base" --json`. `BLOCK` (secrets, `.env`, conflict markers,
142
145
  private keys) stops the run — never submit a tainted diff. `WARN` (debug residue) rides along
143
146
  into the report. This is a submission sanity check, not a second local review.
@@ -46,6 +46,7 @@ re-reading or re-tokenizing the task.
46
46
  | `list` | List tasks, filtered | `--status <s>` `--phase <p>` `--parent <wbs>` `--feature <id>` `--folder` `--json` |
47
47
  | `refresh` | Re-scan the corpus and report counts (**`kanban.md` retired** — web Task Kanban is SSOT) | `--folder` `--json` |
48
48
  | `migrate` | One-time A17 corpus normalization pass | `--dry-run` `--folder` `--json` |
49
+ | `migrate-anchors` | Qualify in-repo evidence anchors to repo-relative paths (0583 R1–R3) | `--dry-run` `--json` |
49
50
  | `refresh-roster <wbs>` | Regenerate a parent task's sub-task roster block in `## Plan` | `--folder` `--json` |
50
51
  | `batch-create` | Create many tasks from a validated JSON array | `--file <path>` `--folder` `--json` |
51
52
  | `record <wbs>` | Write Testing/Review from a verify verdict; optional Solution + transition | `--verdict-file <path>` `--solution-from-diff` `--transition <status>` `--folder` `--json` |
@@ -103,7 +103,7 @@ omission is now reported as an `ac-row-dropped` check naming the row and the unr
103
103
  `rowMatchesScenario` accepts any of these as naming the feature scenario `R3 — Foo`:
104
104
 
105
105
  | Form | Example |
106
- |------|---------|
106
+ | ------ | --------- |
107
107
  | Exact title | `R3 — Foo` |
108
108
  | Bare title (R-prefix dropped) | `Foo` |
109
109
  | `Scenario:` prefix | `Scenario: R3 — Foo` |
@@ -201,7 +201,7 @@ regenerates the `## Tasks` block.
201
201
  Each resolved decision from a grilling interview (Phase 1) becomes one or more Gherkin scenarios:
202
202
 
203
203
  | Decision-tree element | Becomes |
204
- |-----------------------|---------|
204
+ | ----------------------- | --------- |
205
205
  | A **locked decision** (a capability the feature commits to) | A `@core` scenario — the must-ship behavior it enables |
206
206
  | The decision's **observable outcome** (why it was chosen) | The scenario's `Then` — assert the observable, not the mechanism |
207
207
  | A decision's **preconditions / constraints** | The scenario's `Given` |
@@ -211,3 +211,19 @@ Each resolved decision from a grilling interview (Phase 1) becomes one or more G
211
211
 
212
212
  Number scenarios `R1, R2, …` sequentially, stable forever (the title is the traceability identity
213
213
  key). Use the Gherkin template at `.spur/templates/bdd/gherkin.md`.
214
+
215
+ ## AC altitude (task 0584 / ADR-062)
216
+
217
+ A task must declare, via the `ac_altitude` frontmatter field, whether its AC scenarios are the
218
+ feature's ship contract or a finer-grained local contract. The field is the **only** input — it is
219
+ never inferred from `template`, `status`, or whether the AC uses Gherkin (R4).
220
+
221
+ | `ac_altitude` | Meaning | DD-09 subset rule |
222
+ | --- | --- | --- |
223
+ | `graduating` (or absent) | Task scenarios are ship-contract criteria that graduate the feature's AC | Enforced — every task scenario must match a linked-feature scenario by normalized title. Drifted titles still report (R5). |
224
+ | `task-local` | Task criteria sit at a finer altitude than the feature's ship contract (fix-task regression criteria, per-defect cases) | Skipped — no uncovered-scenario findings regardless of title drift (R3) |
225
+
226
+ Use `task-local` for a fix/refactor task whose regression criteria are not the feature's ship
227
+ contract. Keep a `graduating` task's scenario titles identical to the feature's so DD-09 stays
228
+ satisfied. Absent-altitude is `graduating` — set `task-local` explicitly only where the subset rule
229
+ truly does not apply (do not silently default new tasks to it).
@@ -24,7 +24,6 @@ not redefine it.
24
24
  > does not restate the contract. The value table below is authoritative; parity with it is
25
25
  > enforced by `validate-flag-contracts.ts` (C3a/C3b).
26
26
 
27
-
28
27
  ### The one rule
29
28
 
30
29
  > **`--agent <value>` names *who* does the model-bearing work. The execution surface is derived from
@@ -49,7 +48,7 @@ selects the zero-dispatch carve-out below.
49
48
  > fallback.**
50
49
 
51
50
  | Value | Who does the work | Derived surface |
52
- |---|---|---|
51
+ | --- | --- | --- |
53
52
  | `(omitted)` | The agent running this session | Host session — host-controlled; eligible model stages may use a native subagent (0508) |
54
53
  | `inline` | The agent running this session | Host session — hard guarantee: zero dispatch, never a subprocess, never a workflow hop; headless surfaces reject `inline` (exit 2, stable special error) |
55
54
  | `auto` | The role the caller declared — this command's `role:` frontmatter or the workflow step's `role:` (Layer 1, `plugins/sp/references/roles.md`); with nothing declared, `agent.default`'s role (0542) | Subprocess — a tier-resolved executor pins a specific agent/model, which the host session cannot supply |
@@ -219,7 +218,7 @@ stage is required, select the subprocess path (`--agent auto` or `--agent <name>
219
218
  Never edit a task or feature file directly. Every mutation goes through:
220
219
 
221
220
  | Intent | CLI verb |
222
- |--------|----------|
221
+ | -------- | ---------- |
223
222
  | Create a task | `spur task create` |
224
223
  | Change status | `spur task update <wbs> <status>` |
225
224
  | Edit a section | `spur task update <wbs> --section <name> --from-file <path>` |
@@ -329,6 +328,15 @@ loop?**
329
328
  operator, and the *answer* is recorded where the decision belongs — the feature body, an ADR
330
329
  (`docs/00_ADR.md`), or the design doc. A task may then be created for the work the answer implies.
331
330
 
331
+ **Authoring-contract decisions (task 0584 / ADR-062).** Two fields a task author must get right:
332
+
333
+ - **External evidence** — evidence that lives outside `spur`'s working tree uses the frozen non-anchor
334
+ form `origin`path`line N` (line OUTSIDE the backticks), e.g.
335
+ `@gobing-ai/ts-llm-jsonl-importer`src/mappers.ts`line 481`. It is classified external, never
336
+ `L4.stale-line-anchor`. In-repo evidence MUST use the repo-relative backtick form `path:line` —
337
+ citing an in-repo file in the external form still reports (R1/R2). See `ac-style-guide.md` and the
338
+ `code-verification` skill for when each applies.
339
+
332
340
  **Why this is a rule and not a preference.** A decision filed as a task sits in `spur task list` and
333
341
  in a feature's Tasks table looking like queued work. It gets handed to an implementing agent, which
334
342
  either stalls or invents the decision and calls it done. It also inflates task counts, which is how
@@ -432,7 +440,7 @@ line is a claim to re-verify, not evidence to forward. Re-run and paste.
432
440
  **Red Flags — an unverified claim is usually hiding behind one of these:**
433
441
 
434
442
  | Red flag | What it usually means |
435
- |---|---|
443
+ | --- | --- |
436
444
  | "This should work" / "this will pass" / "probably fine" | You are predicting, not reporting. Run it and paste the result. |
437
445
  | Expressing satisfaction ("great, that's done!") before any check ran | Relief is not evidence — the check has not been run this turn. |
438
446
  | Forwarding a subagent's "success" without re-running its gate | You are trusting a claim, not verifying it. Re-run the check yourself. |
@@ -529,7 +537,7 @@ invariants that keep the pipeline set coherent as new ones are added.
529
537
  ### Pipeline phase table
530
538
 
531
539
  | Pipeline | Lifecycle phase | Entry point | Terminal states |
532
- |---|---|---|---|
540
+ | --- | --- | --- | --- |
533
541
  | `idea-pipeline.yaml` | Ideation (vague idea → feature + AC + task batch) | `/sp:dev-idea` | `handoff`, `cancelled` |
534
542
  | `planning-pipeline.yaml` | Design (known slug/task → design handoff) | `/sp:dev-plan` | `handoff`, `cancelled` |
535
543
  | `task-pipeline.yaml` | Execution (one task → done) | `/sp:dev-run` | `done`, `failed` |
@@ -691,7 +699,7 @@ The signal is emitted by the `discovery` state's brainstorm dispatch and written
691
699
  determine routing:
692
700
 
693
701
  | `design` var | `needs_design` signal | Route |
694
- |---|---|---|
702
+ | --- | --- | --- |
695
703
  | `skip` | (ignored) | `decompose` (skip system-design; brainstorm summary still recorded) |
696
704
  | `auto` | `true` | `system-design` -> `design-approval` -> `decompose` |
697
705
  | `auto` | `false` | `decompose` (skip system-design) |