@gobing-ai/spur 0.4.1 → 0.4.3

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 (159) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +4 -2
  3. package/config/decisions/maker-liveness.yaml +39 -0
  4. package/config/plugin-scripts.json +0 -4
  5. package/config/rules/README.md +1 -0
  6. package/config/rules/docs/sourced-api-claims.yaml +56 -12
  7. package/config/rules/strict/runtime-boundaries.yaml +7 -0
  8. package/config/script-placement-baseline.json +1 -19
  9. package/config/templates/AGENTS.md +1 -1
  10. package/config/workflows/feature-lifecycle.yaml +46 -1
  11. package/config/workflows/feature-verification.yaml +11 -0
  12. package/config/workflows/wrapup-pipeline.yaml +1 -1
  13. package/package.json +10 -10
  14. package/plugins/sp/README.md +23 -18
  15. package/plugins/sp/agents/super-planner.md +5 -6
  16. package/plugins/sp/agents/super-reviewer.md +43 -0
  17. package/plugins/sp/bunfig.toml +8 -5
  18. package/plugins/sp/commands/dev-review-session.md +2 -1
  19. package/plugins/sp/commands/dev-runall.md +18 -9
  20. package/plugins/sp/hooks/careful-guard.ts +1 -1
  21. package/plugins/sp/hooks/context-post-tool.ts +7 -404
  22. package/plugins/sp/hooks/context-session-start.ts +6 -192
  23. package/plugins/sp/hooks/context-session-stop.ts +6 -91
  24. package/plugins/sp/hooks/pi/guard-extension.ts +26 -12
  25. package/plugins/sp/hooks/task-write-guard.ts +1 -1
  26. package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
  27. package/plugins/sp/lib/context-owner.ts +56 -0
  28. package/plugins/sp/lib/context-post-tool.ts +432 -0
  29. package/plugins/sp/lib/context-session-start.ts +214 -0
  30. package/plugins/sp/lib/context-session-stop.ts +196 -0
  31. package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
  32. package/plugins/sp/lib/dogfood-report.ts +278 -0
  33. package/plugins/sp/lib/idea-handoff.generated.mjs +6 -4
  34. package/plugins/sp/lib/inline-run-app.ts +40 -0
  35. package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
  36. package/plugins/sp/lib/inline-run.generated.mjs +1361 -87
  37. package/plugins/sp/lib/plan-projection.generated.d.mts +169 -0
  38. package/plugins/sp/lib/plan-projection.generated.mjs +704 -1
  39. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
  40. package/plugins/sp/lib/quality-gate.generated.mjs +62 -231
  41. package/plugins/sp/lib/residual-scan.generated.d.mts +7 -1
  42. package/plugins/sp/lib/residual-scan.generated.mjs +71 -6
  43. package/plugins/sp/lib/run-summary-core.ts +34 -370
  44. package/plugins/sp/lib/transcript.ts +37 -200
  45. package/plugins/sp/plugin.json +1 -1
  46. package/plugins/sp/references/roles.md +1 -1
  47. package/plugins/sp/scripts/batch-plan.mjs +740 -1
  48. package/plugins/sp/scripts/batch-plan.ts +48 -3
  49. package/plugins/sp/scripts/batch-preflight.mjs +23 -1
  50. package/plugins/sp/scripts/batch-preflight.ts +35 -295
  51. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +108 -87
  52. package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
  53. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -1
  54. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
  55. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +3 -0
  56. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
  57. package/plugins/sp/scripts/feature-verification-steps.mjs +7 -123
  58. package/plugins/sp/scripts/feature-verification-steps.ts +29 -181
  59. package/plugins/sp/scripts/inline-run-dispatch.mjs +27 -26
  60. package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
  61. package/plugins/sp/scripts/inline-run-setup.mjs +158 -120
  62. package/plugins/sp/scripts/inline-run-setup.ts +1 -21
  63. package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
  64. package/plugins/sp/scripts/pr-reviewing.ts +34 -9
  65. package/plugins/sp/scripts/quality-gate.mjs +38 -208
  66. package/plugins/sp/scripts/residual-scan.mjs +73 -6
  67. package/plugins/sp/scripts/residual-scan.ts +8 -5
  68. package/plugins/sp/scripts/run-summary.mjs +160 -95
  69. package/plugins/sp/scripts/run-summary.ts +7 -5
  70. package/plugins/sp/scripts/session-timeline.mjs +57 -29
  71. package/plugins/sp/scripts/session-timeline.ts +4 -18
  72. package/plugins/sp/scripts/task-diffstat.mjs +43 -30
  73. package/plugins/sp/scripts/task-diffstat.ts +40 -71
  74. package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
  75. package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
  76. package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
  77. package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
  78. package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
  79. package/plugins/sp/skills/next-feature/SKILL.md +1 -1
  80. package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
  81. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +2 -2
  82. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
  83. package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
  84. package/plugins/sp/skills/session-review/SKILL.md +5 -3
  85. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  86. package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
  87. package/plugins/sp/skills/spur-cli/references/decision.md +40 -8
  88. package/plugins/sp/skills/spur-cli/references/features.md +46 -5
  89. package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
  90. package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
  91. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +27 -0
  92. package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -3
  93. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +6 -1
  94. package/plugins/sp/skills/spur-dev/references/execution-batch.md +56 -7
  95. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
  96. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
  97. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
  98. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
  99. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +37 -25
  100. package/spur.js +89792 -76992
  101. package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.5IIl2O-z.js} +1 -1
  102. package/web/_astro/BoardApp.Drnx-KeM.js +324 -0
  103. package/web/_astro/TaskDetail.Ci4LNn9S.js +1 -0
  104. package/web/_astro/{arc._BUadrdH.js → arc.j2EVkYVd.js} +1 -1
  105. package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DZWSFqEG.js} +1 -1
  106. package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.DE6GpXCN.js} +1 -1
  107. package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.DoVR1G6B.js} +1 -1
  108. package/web/_astro/channel.DmdDxGsq.js +1 -0
  109. package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.Do6-Gbyf.js} +1 -1
  110. package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.BYV1HGHx.js} +1 -1
  111. package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.CvI1zkrc.js} +1 -1
  112. package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DNGSCRlX.js} +1 -1
  113. package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.DXhhgT5z.js} +1 -1
  114. package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.dzkRKP2u.js} +1 -1
  115. package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.DujGLeEf.js} +1 -1
  116. package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.BWtIT3ZU.js} +1 -1
  117. package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.DyApg0Nn.js} +1 -1
  118. package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.DyApg0Nn.js} +1 -1
  119. package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.DJQB-u_l.js} +1 -1
  120. package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.bi3D_siV.js} +1 -1
  121. package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.C-f7TrRg.js} +1 -1
  122. package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DrPfjHQB.js} +1 -1
  123. package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.C9S4WUHO.js} +1 -1
  124. package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.Dzzye8QH.js} +1 -1
  125. package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.DX-LRiof.js} +1 -1
  126. package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.Z-kAKwIz.js} +1 -1
  127. package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.D6dyClSv.js} +1 -1
  128. package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.Cx3q2cxD.js} +1 -1
  129. package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.DU3gH6CT.js} +1 -1
  130. package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.ISmCnJ87.js} +1 -1
  131. package/web/_astro/index.Do2lLdgo.css +1 -0
  132. package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DR6_iNOK.js} +1 -1
  133. package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js} +1 -1
  134. package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.DkP9-WK8.js} +1 -1
  135. package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.wtgY98M2.js} +1 -1
  136. package/web/_astro/{linear.MzmRrNER.js → linear.CI6djNi7.js} +1 -1
  137. package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CuQPxS8b.js} +4 -4
  138. package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.5HNIEG7R.js} +1 -1
  139. package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.CchhDFjE.js} +1 -1
  140. package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.PYXXJPSJ.js} +1 -1
  141. package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.cUQuNAKU.js} +1 -1
  142. package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.BO7MnRIA.js} +1 -1
  143. package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.D9bElyD6.js} +1 -1
  144. package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.DhGdRz7I.js} +1 -1
  145. package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.5GvLeAoL.js} +1 -1
  146. package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.LtAT9haM.js} +1 -1
  147. package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.DRth1yLA.js} +1 -1
  148. package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h9MlZfcr.js} +1 -1
  149. package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.DHqy9DYA.js} +1 -1
  150. package/web/index.html +2 -2
  151. package/config/proportional-route-table.ts +0 -155
  152. package/plugins/sp/commands/dev-parallel.md +0 -37
  153. package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
  154. package/web/_astro/BoardApp.D6sjAJr_.js +0 -324
  155. package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
  156. package/web/_astro/channel.DZVfto-r.js +0 -1
  157. package/web/_astro/index.COh_kBxN.css +0 -1
  158. /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
  159. /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
@@ -11,12 +11,20 @@ function getEnvVars() {
11
11
  return process.env;
12
12
  }
13
13
 
14
- // plugins/sp/scripts/task-diffstat.ts
14
+ // plugins/sp/lib/plan-projection.generated.mjs
15
+ var NON_ABORTING_PREFLIGHT_CODES = new Set([
16
+ "L4.scenario-unverified",
17
+ "L4.verifying-incomplete-tasks"
18
+ ]);
19
+ var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
20
+ var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
21
+ var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
22
+ var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
15
23
  var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
16
24
  var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
17
25
  function globToRegex(glob) {
18
26
  const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
19
- return new RegExp(`^${escaped}$`);
27
+ return new RegExp(`^${escaped}$`, "s");
20
28
  }
21
29
  var SENSITIVE_MATCHERS = [
22
30
  ...SENSITIVE_PREFIXES.map((prefix) => ({
@@ -32,6 +40,8 @@ function sensitiveReasonForPath(path) {
32
40
  const match = SENSITIVE_MATCHERS.find((entry) => entry.test(path));
33
41
  return match ? `matches sensitive pattern ${match.label}` : null;
34
42
  }
43
+
44
+ // plugins/sp/scripts/task-diffstat.ts
35
45
  function defaultGitRunner(cwd) {
36
46
  return (args) => {
37
47
  const run = spawnSync("git", args, { cwd, encoding: "utf8" });
@@ -52,22 +62,12 @@ function countNewlines(path, cwd) {
52
62
  lines += 1;
53
63
  return lines;
54
64
  }
55
- function expandDiffPath(raw) {
56
- const arrow = raw.indexOf(" => ");
57
- if (arrow === -1)
58
- return [raw];
59
- const open = raw.indexOf("{");
60
- const close = raw.lastIndexOf("}");
61
- if (open !== -1 && close > open && open < arrow && arrow < close) {
62
- const inner = raw.slice(open + 1, close);
63
- const innerArrow = inner.indexOf(" => ");
64
- if (innerArrow !== -1) {
65
- const head = raw.slice(0, open);
66
- const tail = raw.slice(close + 1);
67
- return [head + inner.slice(0, innerArrow) + tail, head + inner.slice(innerArrow + 4) + tail];
68
- }
69
- }
70
- return [raw.slice(0, arrow), raw.slice(arrow + 4)];
65
+ function addCounts(add, del) {
66
+ for (const count of [add, del])
67
+ if (count !== "-" && !/^\d+$/.test(count))
68
+ return null;
69
+ const [a, d] = [add === "-" ? 0 : Number(add), del === "-" ? 0 : Number(del)];
70
+ return Number.isSafeInteger(a) && Number.isSafeInteger(d) ? [a, d] : null;
71
71
  }
72
72
  function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
73
73
  const cwd = options.cwd;
@@ -99,23 +99,37 @@ function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
99
99
  } catch {}
100
100
  if (!/^[0-9a-f]{7,40}$/i.test(base))
101
101
  return failSafe("run base .spur/run/<wbs>-base.sha missing or malformed");
102
- const numstat = git(["diff", "--numstat", base]);
102
+ const numstat = git(["diff", "--numstat", "-z", base]);
103
103
  if (numstat.status !== 0)
104
104
  return failSafe(`git diff --numstat failed (status=${numstat.status})`);
105
+ if (numstat.stdout.length > 0 && !numstat.stdout.endsWith("\x00"))
106
+ return failSafe("git diff --numstat -z output is not NUL-terminated (malformed framing)");
105
107
  const paths = new Set;
106
108
  let insertions = 0;
107
109
  let deletions = 0;
108
- for (const line of numstat.stdout.split(`
109
- `)) {
110
- const row2 = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
111
- if (!row2)
110
+ const records = numstat.stdout.split("\x00").slice(0, -1);
111
+ for (let i = 0;i < records.length; i++) {
112
+ const header = records[i] ?? "";
113
+ const first = header.indexOf("\t");
114
+ const second = header.indexOf("\t", first + 1);
115
+ if (first < 1 || second < first + 2)
116
+ return failSafe("git diff --numstat -z record is missing counts or path");
117
+ const counts = addCounts(header.slice(0, first), header.slice(first + 1, second));
118
+ if (counts === null)
119
+ return failSafe("git diff --numstat -z record has a malformed count");
120
+ insertions += counts[0];
121
+ deletions += counts[1];
122
+ const path = header.slice(second + 1);
123
+ if (path !== "") {
124
+ paths.add(path);
112
125
  continue;
113
- for (const p of expandDiffPath(row2[3] ?? ""))
114
- paths.add(p);
115
- if (row2[1] !== "-")
116
- insertions += Number(row2[1]);
117
- if (row2[2] !== "-")
118
- deletions += Number(row2[2]);
126
+ }
127
+ const oldPath = records[++i];
128
+ const newPath = records[++i];
129
+ if (!oldPath || !newPath)
130
+ return failSafe("git diff --numstat -z rename record has incomplete paths");
131
+ paths.add(oldPath);
132
+ paths.add(newPath);
119
133
  }
120
134
  const others = git(["ls-files", "--others", "--exclude-standard", "-z"]);
121
135
  if (others.status !== 0)
@@ -161,7 +175,6 @@ export {
161
175
  sensitiveReasonForPath,
162
176
  runDiffstat,
163
177
  main,
164
- expandDiffPath,
165
178
  TASK_DIFFSTAT_USAGE,
166
179
  SENSITIVE_PREFIXES,
167
180
  SENSITIVE_GLOBS
@@ -7,11 +7,11 @@
7
7
  * `precheck`, F96 R1) and the current working tree, then writes:
8
8
  * `.spur/run/<wbs>-diffstat.json` `{ files, insertions, deletions, paths[], sensitive }`
9
9
  *
10
- * Counts come from `git diff --numstat <base>` (tracked changes; binary rows count the
11
- * path but no lines) plus untracked files (`git ls-files --others --exclude-standard`,
12
- * newline-counted — # ponytail: untracked line counts are a newline scan, numstat-grade
13
- * binary detection is unnecessary for a lane heuristic; switch to `--numstat --no-index`
14
- * if a miscount ever flips a lane).
10
+ * Counts come from `git diff --numstat -z <base>` (tracked changes; binary rows count the path
11
+ * but no lines; NUL records carry RAW paths, so a non-ASCII or newline-bearing path reaches the
12
+ * sensitive matchers intact) plus untracked files (`git ls-files --others --exclude-standard -z`,
13
+ * newline-counted — # ponytail: untracked line counts are a newline scan, numstat-grade binary
14
+ * detection is unnecessary here; switch to `--numstat --no-index` if a miscount flips a lane).
15
15
  *
16
16
  * `sensitive` is true when ANY changed path matches the exported SENSITIVE list (0943 R2c);
17
17
  * the workflow's pre-decide guard maps sensitive (or >400 changed lines) to the safety lane
@@ -65,45 +65,14 @@ export interface DiffstatResult extends Diffstat {
65
65
  exitCode: number;
66
66
  }
67
67
 
68
- /**
69
- * Sensitive paths (0943 R2c): any changed path under one of these forces the safety lane
70
- * before the `task-triage` decide runs — sensitive work is never lane-negotiated with a
71
- * model. Frozen list: `drizzle/`, `packages/config/`, auth-prefixed basenames anywhere
72
- * under the server src tree, `*secret*` basenames, `.github/`, `plugins/sp/hooks/`, and
73
- * any `*.sql` — spelled out in SENSITIVE_PREFIXES / SENSITIVE_GLOBS below (kept as data,
74
- * not prose, so the `*` / `/` sequences survive the doc comment).
75
- */
76
- export const SENSITIVE_PREFIXES = ['drizzle/', 'packages/config/', '.github/', 'plugins/sp/hooks/'] as const;
77
- export const SENSITIVE_GLOBS = ['apps/server/src/**/auth*', '**/*secret*', '**/*.sql'] as const;
78
-
79
- // Glob → RegExp: `**` + `/` matches any-or-no directories, bare `**` any characters, `*` within one segment.
80
- function globToRegex(glob: string): RegExp {
81
- const escaped = glob
82
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
83
- .replaceAll('**/', '\u0000')
84
- .replaceAll('**', '\u0001')
85
- .replaceAll('*', '[^/]*')
86
- .replaceAll('\u0000', '(?:.*/)?')
87
- .replaceAll('\u0001', '.*');
88
- return new RegExp(`^${escaped}$`);
89
- }
68
+ // Task 1176: the sensitive-path policy (0943 R2c) moved to the workflow/task owner —
69
+ // `packages/app/src/workflow/task-triage.ts`, reached through the generated pure bundle. That policy
70
+ // still forces the safety lane for any changed path under the frozen sensitive list before the
71
+ // `task-triage` decide runs, so sensitive work is never lane-negotiated with a model; this entry
72
+ // keeps git capture and the output artifact, and re-exports the policy for existing callers.
73
+ import { SENSITIVE_GLOBS, SENSITIVE_PREFIXES, sensitiveReasonForPath } from '../lib/plan-projection.generated.mjs';
90
74
 
91
- const SENSITIVE_MATCHERS: Array<{ label: string; test: (path: string) => boolean }> = [
92
- ...SENSITIVE_PREFIXES.map((prefix) => ({
93
- label: prefix,
94
- test: (path: string): boolean => path.startsWith(prefix),
95
- })),
96
- ...SENSITIVE_GLOBS.map((glob) => {
97
- const regex = globToRegex(glob);
98
- return { label: glob, test: (path: string): boolean => regex.test(path) };
99
- }),
100
- ];
101
-
102
- /** Sensitive-pattern match for one path, or null — mirrors wrapup-drift-probe's driftReasonForPath. */
103
- export function sensitiveReasonForPath(path: string): string | null {
104
- const match = SENSITIVE_MATCHERS.find((entry) => entry.test(path));
105
- return match ? `matches sensitive pattern ${match.label}` : null;
106
- }
75
+ export { SENSITIVE_GLOBS, SENSITIVE_PREFIXES, sensitiveReasonForPath };
107
76
 
108
77
  function defaultGitRunner(cwd?: string): GitRunner {
109
78
  return (args: string[]): GitRunResult => {
@@ -125,26 +94,10 @@ function countNewlines(path: string, cwd?: string): number {
125
94
  return lines;
126
95
  }
127
96
 
128
- /**
129
- * git renders renames in --numstat paths as `old => new` (full) or `head{old => new}tail`
130
- * (partial). The sensitive matchers anchor on single paths, so both rename halves must be
131
- * surfaced — otherwise a rename INTO a sensitive path (review 0943 P3#1) classified clean.
132
- */
133
- export function expandDiffPath(raw: string): string[] {
134
- const arrow = raw.indexOf(' => ');
135
- if (arrow === -1) return [raw];
136
- const open = raw.indexOf('{');
137
- const close = raw.lastIndexOf('}');
138
- if (open !== -1 && close > open && open < arrow && arrow < close) {
139
- const inner = raw.slice(open + 1, close);
140
- const innerArrow = inner.indexOf(' => ');
141
- if (innerArrow !== -1) {
142
- const head = raw.slice(0, open);
143
- const tail = raw.slice(close + 1);
144
- return [head + inner.slice(0, innerArrow) + tail, head + inner.slice(innerArrow + 4) + tail];
145
- }
146
- }
147
- return [raw.slice(0, arrow), raw.slice(arrow + 4)];
97
+ function addCounts(add: string, del: string): [number, number] | null {
98
+ for (const count of [add, del]) if (count !== '-' && !/^\d+$/.test(count)) return null;
99
+ const [a, d] = [add === '-' ? 0 : Number(add), del === '-' ? 0 : Number(del)];
100
+ return Number.isSafeInteger(a) && Number.isSafeInteger(d) ? [a, d] : null;
148
101
  }
149
102
 
150
103
  /**
@@ -190,18 +143,34 @@ export function runDiffstat(
190
143
  }
191
144
  if (!/^[0-9a-f]{7,40}$/i.test(base)) return failSafe('run base .spur/run/<wbs>-base.sha missing or malformed');
192
145
 
193
- const numstat = git(['diff', '--numstat', base]);
146
+ const numstat = git(['diff', '--numstat', '-z', base]);
194
147
  if (numstat.status !== 0) return failSafe(`git diff --numstat failed (status=${numstat.status})`);
148
+ if (numstat.stdout.length > 0 && !numstat.stdout.endsWith('\u0000'))
149
+ return failSafe('git diff --numstat -z output is not NUL-terminated (malformed framing)');
195
150
  const paths = new Set<string>();
196
151
  let insertions = 0;
197
152
  let deletions = 0;
198
- for (const line of numstat.stdout.split('\n')) {
199
- // Binary rows report `-` for both counts; the path still counts as changed.
200
- const row = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
201
- if (!row) continue;
202
- for (const p of expandDiffPath(row[3] ?? '')) paths.add(p);
203
- if (row[1] !== '-') insertions += Number(row[1]);
204
- if (row[2] !== '-') deletions += Number(row[2]);
153
+ const records = numstat.stdout.split('\u0000').slice(0, -1); // drop the terminating NUL tail
154
+ for (let i = 0; i < records.length; i++) {
155
+ const header = records[i] ?? '';
156
+ const first = header.indexOf('\t'); // first two tabs only
157
+ const second = header.indexOf('\t', first + 1); // a raw path may itself hold more tabs
158
+ if (first < 1 || second < first + 2) return failSafe('git diff --numstat -z record is missing counts or path');
159
+ const counts = addCounts(header.slice(0, first), header.slice(first + 1, second));
160
+ if (counts === null) return failSafe('git diff --numstat -z record has a malformed count');
161
+ insertions += counts[0];
162
+ deletions += counts[1]; // once per count header, never once per rename half
163
+ const path = header.slice(second + 1);
164
+ if (path !== '') {
165
+ paths.add(path);
166
+ continue;
167
+ }
168
+ // An empty path field is Git's rename/copy form: the next two records are old and new.
169
+ const oldPath = records[++i];
170
+ const newPath = records[++i];
171
+ if (!oldPath || !newPath) return failSafe('git diff --numstat -z rename record has incomplete paths');
172
+ paths.add(oldPath);
173
+ paths.add(newPath);
205
174
  }
206
175
  const others = git(['ls-files', '--others', '--exclude-standard', '-z']);
207
176
  if (others.status !== 0) return failSafe(`git ls-files failed (status=${others.status})`);
@@ -82,6 +82,12 @@ not have and work you were not asked to do.
82
82
  - **Never implement a requirement belonging to another WBS**, even when it looks like a
83
83
  prerequisite. If the target genuinely cannot proceed without it, stop and say so — a blocked task
84
84
  is a cheap finding; a two-task diff costs the reverts.
85
+ - **Flip the target's `## Plan` boxes as each step completes.** They are this pass's progress
86
+ ledger, and the record-time residual sweep treats an *unchecked* Plan box as blocking: it
87
+ downgrades the verdict to PARTIAL and closes the `done` gate
88
+ (`docs/design/task-residual-sweep.md`), which costs a remediation cycle and a manual re-record.
89
+ Leave every finished step ticked through `spur task update <wbs> --section Plan --from-file`, and
90
+ if you deliberately skipped one, say so in the Solution change map.
85
91
 
86
92
  Why this is a rule and not a nicety: driving task 0486 lost several hours to exactly this. Task 0485
87
93
  was committed but still `todo` in the tree, and two different executors (omp run `ca130182`, claude
@@ -165,7 +171,7 @@ that single full gate.
165
171
  1. The **changed-path matrix** below (narrow behavior tests + affected consumers);
166
172
  2. the resolved `quality-gate` script in `light` mode (source-repo probe, else the installed twin) (biome on changed files, per-workspace typecheck, rule presets `rules:pre`/`rules:post`, related tests).
167
173
 
168
- Neither step takes the host-wide full-gate lock. The full gate is named only for the pipeline's `test` and `test-recheck` hops.
174
+ Neither step takes the full project gate. The full gate is named only for the pipeline's `test` and `test-recheck` hops.
169
175
 
170
176
  | Changed surface | Required targeted tests | Required typechecks |
171
177
  | --- | --- | --- |
@@ -92,7 +92,8 @@ Before declaring a task `done`, run this lightweight checklist. It catches the m
92
92
  - [ ] Solution section contains `file:line` citations for every changed file.
93
93
  - [ ] API/library claims in changed docs carry a source citation (URL or `path:line`) and an honest
94
94
  confidence level (HIGH/MEDIUM/LOW) per source-driven-development; the deterministic advisory backstop is
95
- the `cited-version-strings` / `cited-api-claims` rules (`.spur/rules/docs/sourced-api-claims.yaml`).
95
+ the `cited-version-strings` / `cited-api-claims` / `cited-api-claims-confidence` rules
96
+ (`.spur/rules/docs/sourced-api-claims.yaml`, run ad hoc — not wired into a preset).
96
97
  - [ ] Review section has P1–P4 findings table (even if all rows are empty — the table itself proves review happened).
97
98
 
98
99
  This checklist runs as part of the verify step when the task reaches `testing` status. It does not replace the formal gates — it augments them with between-pipeline hygiene checks.
@@ -202,6 +202,10 @@ citation in the SAME cell, or lint rejects the row with `evidence-citation` (blo
202
202
  - The rule is cell-local: a citation in another row or a "receipt above" cross-reference does not
203
203
  satisfy it. Marker-free receipts (test counts, reviewed-log notes) need no citation. The check is
204
204
  presence-only — which citation proves which claim is not deterministically decidable.
205
+ - The same two predicates (`hasClaimMarker` / `hasCitationForm` in
206
+ `packages/app/src/services/verify-answer-lint.ts`) gate the review report's findings table through
207
+ `residual-scan review-gate` as the blocking `review-contract` category — one definition of
208
+ "external claim" and one set of accepted citation forms for both paths.
205
209
 
206
210
  ## Hedged evidence is rejected on MET rows
207
211
 
@@ -82,11 +82,11 @@ The command forwards these via `$ARGUMENTS`:
82
82
  > <!-- pipeline-tokens:end -->
83
83
  > matched as a **distinct hyphen-word**
84
84
  > (machine-checked by
85
- > [`detectPipelineDriving`](../../scripts/dogfood-testing/detect-pipeline-driving.ts);
85
+ > [`detectPipelineDriving`](../../lib/dogfood-pipeline.ts);
86
86
  > see [§Pipeline-driving word-boundary contract](#pipeline-driving-word-boundary-contract))
87
87
  > → `⚠ pipeline-driving testee detected; pass --max-retry 0 (observe-only) or --max-retry N (fix mode, tree mutation acknowledged)`.
88
88
  > - **Mutating `--fix` modes** — `--fix all` / `--fix blockers-first`, boundary-guarded via
89
- > [`hasMutatingFixMode`](../../scripts/dogfood-testing/detect-pipeline-driving.ts) (never matches
89
+ > [`hasMutatingFixMode`](../../lib/dogfood-pipeline.ts) (never matches
90
90
  > `--fix none` / `--focus all` / `--prefix all`). No pipeline token required: a verify/review leg
91
91
  > with `--fix all` mutates the tree on its own (0280 dogfood P2, task 0293). Honesty note:
92
92
  > `--max-retry 0` here bounds **the driver only** — the testee still mutates the tree — so the
@@ -144,14 +144,21 @@ an explicit allowlist entry.
144
144
  env dumps. Token estimates for Bash/Grep/Glob use response size **after a 4 KiB cap** and secret
145
145
  scrub; omit `tokens` when unknown (never `0`).
146
146
 
147
+ **Session lifecycle:** `.spur/context/.session.json` is a private pointer to the in-flight session.
148
+ It records the creating host's owner identity (`{host, sessionId}` — Claude's hook `session_id`, Pi's
149
+ session-manager id) and only that owner's `SessionEnd` / `session_shutdown` may write the rollup and
150
+ remove the pointer. A nested run reuses the parent's ledger for tool recording but cannot finalize
151
+ or delete it; an ownerless (legacy) pointer is never finalized and expires under the existing
152
+ idle-start window instead.
153
+
147
154
  ```jsonl
148
- {"ts":"2026-07-09T14:23:01Z","session":"session-2026-07-09-1423","type":"session_start"}
149
- {"ts":"2026-07-09T14:23:15Z","session":"session-2026-07-09-1423","type":"read","file":"src/dao.ts","tokens":648}
150
- {"ts":"2026-07-09T14:24:00Z","session":"session-2026-07-09-1423","type":"write","file":"src/dao.ts","tokens":222,"action":"edit"}
151
- {"ts":"2026-07-12T15:00:00Z","session":"session-2026-07-12-1500","type":"bash","summary":"ls -la","tokens":12}
152
- {"ts":"2026-07-12T15:00:01Z","session":"session-2026-07-12-1500","type":"grep","summary":"/TODO/ src"}
153
- {"ts":"2026-07-12T15:00:02Z","session":"session-2026-07-12-1500","type":"glob","summary":"**/*.test.ts"}
154
- {"ts":"2026-07-09T14:30:00Z","session":"session-2026-07-09-1423","type":"session_end","totals":{"reads":5,"writes":19,"tokens":2783}}
155
+ {"ts":"2026-07-09T14:23:01Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"session_start"}
156
+ {"ts":"2026-07-09T14:23:15Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"read","file":"src/dao.ts","tokens":648}
157
+ {"ts":"2026-07-09T14:24:00Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"write","file":"src/dao.ts","tokens":222,"action":"edit"}
158
+ {"ts":"2026-07-12T15:00:00Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"bash","summary":"ls -la","tokens":12}
159
+ {"ts":"2026-07-12T15:00:01Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"grep","summary":"/TODO/ src"}
160
+ {"ts":"2026-07-12T15:00:02Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"glob","summary":"**/*.test.ts"}
161
+ {"ts":"2026-07-09T14:30:00Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"session_end","totals":{"reads":5,"writes":19,"tokens":2783}}
155
162
  ```
156
163
 
157
164
  **Never edit `token-ledger.jsonl` by hand.** It feeds the Board **Observability → Tool Using** tab.
@@ -64,7 +64,7 @@ Run the steps in order. Each step's depth lives in its reference; this file is t
64
64
  change a frontier feature's status, the report **leads with the drift summary** ("sync first")
65
65
  and the ranking is computed over the *post-sync* status view. Details:
66
66
  [references/signal-derivation.md](references/signal-derivation.md) §0.
67
- 1. **Assemble the candidate set.** `spur feature list --json`; drop `done`/`cancelled`, drop
67
+ 1. **Assemble the candidate set.** `spur feature list --json`; drop `done`/`cancelled`/`deprecated`, drop
68
68
  structural containers (has children, no own open tasks — defect D1), drop the map/feature that
69
69
  owns this investigation if present. The `group` tag is **not** a reliable container marker (D3).
70
70
  2. **Gate on actionability.** Read the frontier predicate **at runtime** from
@@ -104,7 +104,7 @@ was meant to avoid. Let the reviewer reach its own conclusion.
104
104
 
105
105
  - **`sp:spur-dev`** owns task selection and lifecycle. When a batch contains independent tasks, the spine consults this skill for the fan-out pattern.
106
106
  - **`sp:super-planner`** is the orchestrator that executes the fan-out. Its parallel mode (documented in its agent definition) applies the patterns from this skill.
107
- - **`/sp:dev-parallel`** is the thin slash-command entry point that delegates to this skill.
107
+ - **`/sp:dev-runall --mode parallel|auto`** is the task-batch entry point; its `batch-plan.mjs order` schedule applies this skill's dependency and file-overlap rows deterministically.
108
108
 
109
109
  ## When to use
110
110
 
@@ -135,7 +135,7 @@ Do **not** use this skill for:
135
135
 
136
136
  - **`sp:spur-dev`** — the orchestration spine that consults this skill for fan-out decisions.
137
137
  - **`sp:super-planner`** — the batch orchestrator that executes fan-out patterns.
138
- - **`/sp:dev-parallel`** — the slash command entry point for parallel execution.
138
+ - **`/sp:dev-runall --mode parallel|auto`** — the slash command entry point for parallel task batches.
139
139
 
140
140
  ## Platform Notes
141
141
 
@@ -70,7 +70,7 @@ own axis.
70
70
  Surface choice and model choice stay separate axes, but the invocation surface is where the
71
71
  cheap-model hint is applied. On hosts whose native subagent invocation accepts a per-call model
72
72
  override (Claude Code: the Agent tool's `model` parameter), dispatch read-only fan-out workers —
73
- `dev-parallel --mode investigation` and competency-lens review's read-only lenses — with the host's
73
+ N-way investigation and competency-lens review's read-only lenses — with the host's
74
74
  cheapest capable model. The rule is owned by
75
75
  [fan-out-patterns.md § Cheap-model hint for read-only fan-out](fan-out-patterns.md#cheap-model-hint-for-read-only-fan-out)
76
76
  and carries two prohibitions and one exemption: invocation-side only, **never** a subagent
@@ -167,7 +167,7 @@ rule at the source, so no per-path shim is needed:
167
167
  | `spur agent loop` | `AgentService.run` per drained iteration | Same resolution path as `spur agent run`; inherits its own `SPUR_ROLE` |
168
168
  | `spur serve` team supervisor → member | spawns `spur agent loop` | Member inherits the supervisor's `SPUR_ROLE` (recursive by construction) |
169
169
  | Native subagent fan-out (this skill's default) | in-session `Task()`/`Skill()` | In-session subagents share the host session; when they themselves dispatch, the host's role is already in the session env — the rule holds at the next `spur agent run` boundary |
170
- | `plugins/sp/evals/run-eval.ts` | `spawnSync('spur agent run', …)` per scenario | Out of scope: a top-level eval harness, not a fan-out — no dispatcher role exists to inherit; each scenario is an independent top-level run (documented, no shim) |
170
+ | `scripts/commands/skill-eval.ts` | `spawnSync('spur agent run', …)` per scenario | Out of scope: a top-level eval harness, not a fan-out — no dispatcher role exists to inherit; each scenario is an independent top-level run (documented, no shim) |
171
171
 
172
172
  The inventory is recorded in `docs/04_DESIGN.md` § `spur agent run` envelope (`roleOrigin`).
173
173
  Paths that shell out to `spur agent run` without the rule would silently drop or double-attribute
@@ -134,13 +134,13 @@ blocker** — never a permission-probing subsystem:
134
134
  [Keep a durable progress ledger](../SKILL.md#keep-a-durable-progress-ledger)).
135
135
 
136
136
  Read-only investigation fan-out avoids the write-permission class by construction:
137
- `dev-parallel --mode investigation` and competency-lens review's read-only lenses dispatch
137
+ N-way investigation and competency-lens review's read-only lenses dispatch
138
138
  **read-only worker shapes** — search/read tools only, with no write surface declared.
139
139
 
140
140
  ## Cheap-model hint for read-only fan-out
141
141
 
142
142
  On hosts whose native subagent invocation accepts a per-call model override (Claude Code: the Agent
143
- tool's `model` parameter), dispatch read-only fan-out workers — `dev-parallel --mode investigation`
143
+ tool's `model` parameter), dispatch read-only fan-out workers — N-way investigation
144
144
  and competency-lens review's read-only lenses — with the host's cheapest capable model. The rule is
145
145
  invocation-side, with two prohibitions and one exemption:
146
146
 
@@ -112,9 +112,13 @@ unrelated uncommitted changes make safe editing or committing ambiguous.
112
112
  **Current-HEAD invariant.** Treat the PR's pushed `headRefOid` as the review identity. After every
113
113
  preflight, push, commit, and request, record the returned HEAD and require `wait`, `collect`, and
114
114
  the report to match that exact HEAD. A result for an older commit is stale; a result absent for the
115
- current HEAD is pending, not clean. Reject uncorrelated issue/conversation comments rather than
116
- using them to manufacture a current-HEAD result. In `fix` mode, discard the old result after the
117
- fix commit and judge only the forced re-review for the new pushed HEAD.
115
+ current HEAD is pending, not clean. `wait` re-asserts this at two points, not one: at the start of
116
+ every poll, and again immediately before it emits FOUND/CLEAN/TIMEOUT — a head that moved while the
117
+ review was being fetched, or between polls, fails the wait (exit 2) instead of accepting an old-head
118
+ result. The pinned identity is the PR number AND the expected head, so a different PR appearing on
119
+ the branch is also a failure, never a silent retarget. Reject uncorrelated issue/conversation
120
+ comments rather than using them to manufacture a current-HEAD result. In `fix` mode, discard the old
121
+ result after the fix commit and judge only the forced re-review for the new pushed HEAD.
118
122
 
119
123
  **Reviewer authority.** Codex is an independent reviewer, not an authority. For every finding that
120
124
  may lead to a code change: inspect the referenced code, verify reachability and intended behavior,
@@ -101,9 +101,11 @@ issues with its evidence. Notes stay report-only.
101
101
  refresh it with `superskill install sp` before reporting numbers; report `n/a` until then.
102
102
  The transcript resolves from `--transcript`, else `CLAUDE_CODE_SESSION_ID`, else `PI_SESSION_FILE`
103
103
  (the pi host sets it and no Claude session id); with none of them the measurement reports
104
- `available:false` with a reason and the table renders `n/a` instead of guessing. A
105
- skill-injected body (`<skill name="…">`) is counted as activity in `injectedPrompts`, never as a
106
- segment — only the operator's own prompts segment the session.
104
+ `available:false` with a reason and the table renders `n/a` instead of guessing. A pi
105
+ `<skill name="…">` wrapper row is the OPERATOR'S own `/skill` invocation, not an injected body:
106
+ it opens a segment rendered `/<name> <args>` (or `/<name>` when no arguments follow the closing
107
+ tag), and the wrapper body never appears in the prompt excerpt. Only the operator's prompts
108
+ segment the session.
107
109
  2. **Inventory outcomes.** List requested outcomes and classify each as completed, partial, blocked,
108
110
  or not attempted. Collapse repeated attempts into one outcome.
109
111
  3. **Classify issues.** For every material issue, distinguish resolved, open, or attempted. Record
@@ -101,10 +101,13 @@ line: `Verification: HIGH|MEDIUM|LOW — <first failing check and deviation, or
101
101
  - **Honest confidence** — the stated HIGH/MEDIUM/LOW matches the evidence: HIGH requires a check against the pinned source this session; MEDIUM/LOW items are labeled, and LOW is never presented as fact.
102
102
  - **HIGH** — all three checks pass. **MEDIUM** — coverage intact but some citations are off-version (latest docs for a pinned dependency) or secondary-source only. **LOW** — an API/library claim lacks any citation, or a LOW-confidence claim was asserted as fact.
103
103
 
104
- Deterministic backstop: the `cited-version-strings` / `cited-api-claims` rules
105
- (`.spur/rules/docs/sourced-api-claims.yaml`, run in `recommended-pre-check`) flag markdown lines that
106
- assert version strings or time-anchored API claims with no citation anchor on the same line. They
107
- are an info-severity advisory backstop for this contract (they do not gate; the checks above and the SECUA checklist are the enforcement points).
104
+ Deterministic backstop: the `cited-version-strings`, `cited-api-claims` and
105
+ `cited-api-claims-confidence` rules (`.spur/rules/docs/sourced-api-claims.yaml`) flag markdown lines
106
+ that assert a version string or a time-anchored API claim with no citation anchor on the same line,
107
+ and claim lines that carry no HIGH/MEDIUM/LOW level. They are an info-severity advisory backstop for
108
+ this contract and are deliberately NOT wired into `recommended-pre-check` — run them ad hoc with
109
+ `spur rule run --file .spur/rules/docs/sourced-api-claims.yaml`. The checks above and the SECUA
110
+ checklist remain the enforcement points.
108
111
 
109
112
  ## Common Rationalizations
110
113
 
@@ -8,8 +8,9 @@ see_also:
8
8
  # spur builder - release plumbing
9
9
 
10
10
  `spur builder` is the CLI for **version bumps and release tags**. It wraps the internal
11
- `spur-dev release` flow behind a public two-verb surface, promoted verbatim from
12
- `scripts/commands/release.ts` (now a thin forwarder to the same implementation). Package ids are
11
+ `release-ops` implementation behind a public two-verb surface. The repo-local forwarder
12
+ (`scripts/commands/release.ts`) and its `spur-dev` dispatcher cases were retired in task 1173; the
13
+ root `bun run bump-ver` / `bun run drop-tags` names route to `spur builder` directly. Package ids are
13
14
  the unscoped short names (`@gobing-ai/spur` → `spur`); the released set and the aggregate tag are
14
15
  discovered from the repo's own workspace manifests, so the same code serves any git+semver
15
16
  monorepo.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spur-cli-decision
3
- description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers. Backed by @gobing-ai/ts-ai-decision 0.5.16 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093."
3
+ description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers with per-maker readiness marks (registered, configured, recorded, probed). Backed by @gobing-ai/ts-ai-decision 0.5.22 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093; readiness marks and `--probe` by feature P11."
4
4
  see_also:
5
5
  - spur-cli
6
6
  ---
@@ -25,9 +25,10 @@ Decisions resolve first-wins across three layers:
25
25
  | `registered` | folders listed in config `decisions.paths` | Operator-added catalogs |
26
26
  | `shared` | `decisions/` under the bundled config root | Shipped catalogs (`config/decisions/` in-repo) |
27
27
 
28
- The bundled shared catalog ships `task-triage`, `failure-class`, and `review-failure-class` (all
29
- `choice` type, param `wbs`). Duplicate ids across layers keep the higher layer; duplicates inside
30
- one file are a load error.
28
+ The bundled shared catalog ships `task-triage`, `failure-class`, `review-failure-class`,
29
+ `gate-evidence`, `idea-recommendation`, `needs-design`, `anatomy-validation-verdict` and
30
+ `maker-liveness` (the last served only by `spur decision status --probe`; no workflow references
31
+ it). Duplicate ids across layers keep the higher layer; duplicates inside one file are a load error.
31
32
 
32
33
  **Config keys** (under `decisions:` in the global `~/.config/spur/config.yaml` or project `.spur/config.yaml`):
33
34
  `paths` (extra catalog folders forming the `registered` layer), `maker` (default DecisionMaker
@@ -41,7 +42,7 @@ config error, never a silent fallback (design §3.6).
41
42
  | `list` | Every visible decision: id, type, layer, catalog file, description | `--layer <layer>`, `--json` |
42
43
  | `show <id>` | One served contract: type, parameters, criteria, fallback, minConfidence, effective maker + source | `--json` |
43
44
  | `run <id>` | Serve one outcome (closed vocabulary; **never writes a resultFile**) | `--param <pairs...>`, `--evidence <files...>`, `--maker <name>`, `--json` |
44
- | `status` | Layer counts, registered makers, per-decision maker resolution, load errors | `--json` |
45
+ | `status` | Sectioned readiness report: one status line, errors, aligned `Makers` and `Decisions` tables (registration, configuration source, recorded evidence), `Catalog` block | `--probe`, `--reliability [--since <iso>]`, `--json` |
45
46
 
46
47
  All verbs support `--json` (bare payload) and `--json-envelope` (`{ok, data|error}` envelope,
47
48
  ADR-091). Failure output for `--json` commands uses the canonical error envelope only under
@@ -51,6 +52,25 @@ ADR-091). Failure output for `--json` commands uses the canonical error envelope
51
52
  (design §3.4 — load errors surface via `status`) except for an invalid `--layer` value (exit 1).
52
53
  Error envelopes use `NOT_FOUND` for an unknown id and `VALIDATION_FAILED` for every other caller mistake.
53
54
 
55
+ ## Readiness marks (`status`, feature P11)
56
+
57
+ The default `status` output separates four marks per maker and per decision
58
+ (`docs/design/decision-observability-and-adoption.md` §3.4.1):
59
+
60
+ | Mark | Column | Source |
61
+ | ---- | ------ | ------ |
62
+ | registered | `ST` glyph on the maker row | the maker registry holds the name |
63
+ | configured | `CONFIGURED BY` | `decisions.maker` / `decisions.makers.<id>` |
64
+ | recorded | `RECORDED` / `ACCEPTED` / `LAST SEEN` | the recorded `decision.end` ledger, matched on `(decisionId, maker)` |
65
+ | proven | `PROBE` (only with `--probe`) | one live liveness decision per maker |
66
+
67
+ `RECORDED` with `ACCEPTED 0` is the state that reads as healthy everywhere else: the maker is
68
+ registered and configured but has never once succeeded. `--probe` is the only path that calls a
69
+ maker, is bounded per driver, writes no decision log row and emits no event (`--probe` with
70
+ `--reliability` is refused). Parse the marks with `--json`: `makers[]`
71
+ (`registered`, `configuredBy`, `serves`, `evidence`), `perDecision[].evidence`, and `probes[]`
72
+ under `--probe`.
73
+
54
74
  **Exit contract for `run` (design §3.4):** any backend-observed outcome — low confidence, model
55
75
  error surfaced as fallback, declined — is still a **successful serve** (exit 0, envelope carries
56
76
  `source`/`confidence` so callers can see why). Exit 1 is reserved for caller mistakes caught
@@ -79,6 +99,13 @@ exist there). The final `catalog-default` maker is the hub built-in `typesafe`
79
99
  without a configured backend still produce deterministic closed-vocabulary outcomes, which is why
80
100
  `run` is usable in tests and scripts with no model access.
81
101
 
102
+ **Available makers:** the three upstream built-ins (`typesafe`, `fm-local`, `laya-local`) plus
103
+ Spur's hosted `clef` (Cloudflare Workers AI via `@gobing-ai/ts-decision-clef`). `clef` registers
104
+ only when both `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_AUTH_TOKEN` are set in the environment;
105
+ without them it is absent from `registeredMakers`, so naming it in config or `--maker` is a config
106
+ error, never a silent fallback. Its model defaults to `clef-flash` (a catalog `model` selects
107
+ `clef`).
108
+
82
109
  ## Examples
83
110
 
84
111
  ```bash
@@ -88,11 +115,16 @@ spur decision run task-triage --param wbs=1093 --json
88
115
  spur decision run failure-class --param wbs=1093 \
89
116
  --evidence .spur/run/<runId>-test-output.txt # redacted evidence -> instructions
90
117
  spur decision status --json | jq '.loadErrors' # audit catalog health
118
+ spur decision status # readiness marks: registered / configured / recorded
119
+ spur decision status --probe # prove each effective maker answers now
120
+ spur decision status --json | jq '.makers[] | {name, registered, recorded: .evidence.recorded}'
91
121
  ```
92
122
 
93
123
  ## References
94
124
 
95
- - Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface)
96
- - Hub: `@gobing-ai/ts-ai-decision` 0.5.16 (`DecisionService`, catalog schema, maker registry)
97
- - App service: `packages/app/src/decision/decision-service.ts`
125
+ - Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface),
126
+ `docs/design/decision-observability-and-adoption.md` (§3.4 reliability report, §3.4.1 readiness view)
127
+ - Hub: `@gobing-ai/ts-ai-decision` 0.5.22 (`DecisionService`, catalog schema, maker registry)
128
+ - App service: `packages/app/src/decision/decision-service.ts` (makers, `probeMaker`),
129
+ `packages/app/src/decision/decision-status-readiness.ts` (readiness marks, probe loop)
98
130
  - Shipped catalogs: `config/decisions/*.yaml`