@gobing-ai/spur 0.4.4 → 0.4.6

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 (82) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +3 -4
  3. package/config/config.global.yaml +3 -4
  4. package/config/rules/docs/sourced-api-claims.yaml +3 -2
  5. package/config/rules/quality/coverage-gate.yaml +6 -4
  6. package/config/rules/recommended-pre-check.yaml +11 -9
  7. package/config/transition-shims.json +0 -14
  8. package/config/workflows/task-pipeline.yaml +31 -71
  9. package/package.json +1 -1
  10. package/plugins/sp/agents/super-reviewer.md +11 -27
  11. package/plugins/sp/commands/dev-review.md +1 -1
  12. package/plugins/sp/lib/inline-run.generated.mjs +3 -3
  13. package/plugins/sp/lib/residual-scan.generated.d.mts +0 -13
  14. package/plugins/sp/lib/residual-scan.generated.mjs +3 -47
  15. package/plugins/sp/plugin.json +1 -1
  16. package/plugins/sp/scripts/commit-guard.mjs +49 -39
  17. package/plugins/sp/scripts/commit-guard.ts +60 -61
  18. package/plugins/sp/scripts/persist-out-check.mjs +28 -24
  19. package/plugins/sp/scripts/persist-out-check.ts +42 -33
  20. package/plugins/sp/scripts/residual-scan.mjs +5 -50
  21. package/plugins/sp/scripts/residual-scan.ts +4 -6
  22. package/plugins/sp/skills/code-verification/SKILL.md +1 -2
  23. package/plugins/sp/skills/code-verification/references/verdict-schema.md +16 -35
  24. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +4 -2
  25. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +3 -1
  26. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -2
  27. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +7 -11
  28. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +7 -6
  29. package/spur.js +422 -431
  30. package/web/_astro/{BoardApp.hF0RAIrg.js → BoardApp.C5jnkkKP.js} +124 -124
  31. package/web/_astro/{BoardApp.Niubpe6z.js → BoardApp.CaNvQBD_.js} +1 -1
  32. package/web/_astro/{TaskDetail.DJpapt38.js → TaskDetail.DGKndhI8.js} +1 -1
  33. package/web/_astro/{arc.BgKcK0i8.js → arc.DHrwdg3G.js} +1 -1
  34. package/web/_astro/{architectureDiagram-3BPJPVTR.ChP-Ig0V.js → architectureDiagram-3BPJPVTR.pvKpK1s5.js} +1 -1
  35. package/web/_astro/{blockDiagram-GPEHLZMM.BVaCBoeN.js → blockDiagram-GPEHLZMM.BQmRcZsS.js} +1 -1
  36. package/web/_astro/{c4Diagram-AAUBKEIU.CHSDpEO5.js → c4Diagram-AAUBKEIU.CCLjHeH1.js} +1 -1
  37. package/web/_astro/channel.C7wG9RxA.js +1 -0
  38. package/web/_astro/{chunk-2J33WTMH.MYRcYVRa.js → chunk-2J33WTMH.DL6Yuoma.js} +1 -1
  39. package/web/_astro/{chunk-4BX2VUAB.S5j22Zqa.js → chunk-4BX2VUAB.CzlyEI7l.js} +1 -1
  40. package/web/_astro/{chunk-55IACEB6.gciRPvmZ.js → chunk-55IACEB6.B60EWxp1.js} +1 -1
  41. package/web/_astro/{chunk-727SXJPM.CdqrSQkX.js → chunk-727SXJPM.c7v_fRLo.js} +1 -1
  42. package/web/_astro/{chunk-AQP2D5EJ.CyokdHT3.js → chunk-AQP2D5EJ.8BxLWdDc.js} +1 -1
  43. package/web/_astro/{chunk-FMBD7UC4.Bg9KJhjW.js → chunk-FMBD7UC4.DmKuxGFT.js} +1 -1
  44. package/web/_astro/{chunk-ND2GUHAM.1gxxI-vD.js → chunk-ND2GUHAM.1Zwd4mqV.js} +1 -1
  45. package/web/_astro/{chunk-QZHKN3VN.DDEYtCKU.js → chunk-QZHKN3VN.BvYLu-A1.js} +1 -1
  46. package/web/_astro/{classDiagram-4FO5ZUOK.Ch3zi37T.js → classDiagram-4FO5ZUOK.CEqRt0WU.js} +1 -1
  47. package/web/_astro/{classDiagram-v2-Q7XG4LA2.Ch3zi37T.js → classDiagram-v2-Q7XG4LA2.CEqRt0WU.js} +1 -1
  48. package/web/_astro/{cose-bilkent-S5V4N54A.D2q5AINI.js → cose-bilkent-S5V4N54A.C7JiUdj2.js} +1 -1
  49. package/web/_astro/{cynefin-OW5HDTMX.BA9kC2YT.js → cynefin-OW5HDTMX.B6mZ-1be.js} +1 -1
  50. package/web/_astro/{dagre-BM42HDAG.CHO08nRp.js → dagre-BM42HDAG.BuryfDy6.js} +1 -1
  51. package/web/_astro/{diagram-2AECGRRQ.BfFQt0LW.js → diagram-2AECGRRQ.dao39YQM.js} +1 -1
  52. package/web/_astro/{diagram-5GNKFQAL.CHMwMTe4.js → diagram-5GNKFQAL.lnGZe4At.js} +1 -1
  53. package/web/_astro/{diagram-KO2AKTUF.BJ-7gK-w.js → diagram-KO2AKTUF.uWfhzEfT.js} +1 -1
  54. package/web/_astro/{diagram-LMA3HP47.C5xqcc0t.js → diagram-LMA3HP47.ppq8-198.js} +1 -1
  55. package/web/_astro/{diagram-OG6HWLK6.C2pQjfrC.js → diagram-OG6HWLK6.CKSCqDt5.js} +1 -1
  56. package/web/_astro/{erDiagram-TEJ5UH35._FoNyxgC.js → erDiagram-TEJ5UH35.sFSf2yBN.js} +1 -1
  57. package/web/_astro/{flowDiagram-I6XJVG4X.B_ENkT2I.js → flowDiagram-I6XJVG4X.BJQjKLbz.js} +1 -1
  58. package/web/_astro/{ganttDiagram-6RSMTGT7.B3q8h77C.js → ganttDiagram-6RSMTGT7.DAHKNj2N.js} +1 -1
  59. package/web/_astro/{gitGraphDiagram-PVQCEYII.QWZFDboZ.js → gitGraphDiagram-PVQCEYII.BlmzcW_O.js} +1 -1
  60. package/web/_astro/index.BCY1JJHd.css +1 -0
  61. package/web/_astro/{infoDiagram-5YYISTIA.Cs6eEc92.js → infoDiagram-5YYISTIA.DGhjaqsi.js} +1 -1
  62. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BU9DJMHA.js → ishikawaDiagram-YF4QCWOH.gKIpgt6F.js} +1 -1
  63. package/web/_astro/{journeyDiagram-JHISSGLW.D35k6Jpe.js → journeyDiagram-JHISSGLW.DG3tmP0R.js} +1 -1
  64. package/web/_astro/{kanban-definition-UN3LZRKU.DBqcAT4m.js → kanban-definition-UN3LZRKU.CgHDSxkR.js} +1 -1
  65. package/web/_astro/{linear.D-LSNK8W.js → linear.BuGEpcuM.js} +1 -1
  66. package/web/_astro/{mermaid.core.CSruWVOa.js → mermaid.core.DBVqkWBp.js} +4 -4
  67. package/web/_astro/{mindmap-definition-RKZ34NQL.BiFaUsfj.js → mindmap-definition-RKZ34NQL.D-kOcji9.js} +1 -1
  68. package/web/_astro/{pieDiagram-4H26LBE5.D1KTKqb9.js → pieDiagram-4H26LBE5.BvGPwtZt.js} +1 -1
  69. package/web/_astro/{quadrantDiagram-W4KKPZXB.DBHflsMG.js → quadrantDiagram-W4KKPZXB.BKb_9FV6.js} +1 -1
  70. package/web/_astro/{requirementDiagram-4Y6WPE33.hwX9CNdk.js → requirementDiagram-4Y6WPE33.Cx0fvD8v.js} +1 -1
  71. package/web/_astro/{sankeyDiagram-5OEKKPKP.DkI8mJiE.js → sankeyDiagram-5OEKKPKP.fatULi0W.js} +1 -1
  72. package/web/_astro/{sequenceDiagram-3UESZ5HK.DMz7jBDx.js → sequenceDiagram-3UESZ5HK.ChHyKqco.js} +1 -1
  73. package/web/_astro/{stateDiagram-AJRCARHV.B6Ef2xJ1.js → stateDiagram-AJRCARHV.BdUpKWyj.js} +1 -1
  74. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CgoaufB9.js → stateDiagram-v2-BHNVJYJU.fmJxqIm5.js} +1 -1
  75. package/web/_astro/{timeline-definition-PNZ67QCA.B3c8h1t-.js → timeline-definition-PNZ67QCA.BayDn5-V.js} +1 -1
  76. package/web/_astro/{vennDiagram-CIIHVFJN.BdHPAxAb.js → vennDiagram-CIIHVFJN.Bm2Aggwa.js} +1 -1
  77. package/web/_astro/{wardleyDiagram-YWT4CUSO.BrJXPPAj.js → wardleyDiagram-YWT4CUSO.DI75O-uG.js} +1 -1
  78. package/web/_astro/{xychartDiagram-2RQKCTM6.CMH5yFsV.js → xychartDiagram-2RQKCTM6.DL90E7Q7.js} +1 -1
  79. package/web/index.html +2 -2
  80. package/config/rules/docs/verdict-confidence.yaml +0 -68
  81. package/web/_astro/channel.DkpvN2tD.js +0 -1
  82. package/web/_astro/index.BFyW13JJ.css +0 -1
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
3
  * persist-out-check — WT-4 pre-removal assertion for E71 persist-out (task 1067 R1, 1148 R1).
4
- *
5
4
  * Before `git worktree remove`, verify the worktree's OWNED durable evidence would not be abandoned:
6
5
  * every `.spur/run/` and `.spur/memory/evidence/` file starting with a forwarded `<wbs>-`/`<runId>-`
7
6
  * prefix must exist in the invoking tree with identical bytes. Unowned evidence is counted
@@ -17,6 +16,9 @@ const EVIDENCE_DIR = join('.spur', 'memory', 'evidence');
17
16
  const RUN_DIR = join('.spur', 'run');
18
17
  const NAMED_CAP = 32;
19
18
  const PREFIX_CAP = 64;
19
+ /** The durable-verdict family persist-out itself transfers (1026/1043) — the one artifact class whose
20
+ * silent abandonment WT-4 exists to prevent, even when the driver forwarded no matching prefix. */
21
+ const DURABLE_FAMILY_RE = /^\d{4}-(?:verdict|feature-verification)\.json$/;
20
22
 
21
23
  /** Task files own their `<wbs>-*` evidence: leading digits before `_` in the basename. */
22
24
  export function wbsOfTaskFile(path: string): string | null {
@@ -27,6 +29,8 @@ export function wbsOfTaskFile(path: string): string | null {
27
29
  export interface ListObligationsResult {
28
30
  files: string[];
29
31
  unowned: number;
32
+ /** Every file seen in both planes (recursive) — lets callers audit outside the forwarded prefixes. */
33
+ planeFiles: string[];
30
34
  }
31
35
 
32
36
  /** Recursively list files under `dir`, relative to `root`. */
@@ -58,8 +62,9 @@ function collectOwned(rels: string[], prefixes: string[], counts: Map<string, nu
58
62
  }
59
63
 
60
64
  /**
61
- * Evidence the worktree owns: `.spur/memory/evidence/` (recursive) and `.spur/run/` (flat) files
62
- * whose basename starts with a forwarded `<wbs>-`/`<runId>-` prefix. Evidence matching no prefix is
65
+ * Evidence the worktree owns: `.spur/memory/evidence/` (recursive) and `.spur/run/` (recursive — the
66
+ * driver keeps run scratch under `.spur/run/<run-id>/`, which a flat scan silently missed) files whose
67
+ * basename starts with a forwarded `<wbs>-`/`<runId>-` prefix. Evidence matching no prefix is
63
68
  * counted as `unowned` (1148 R1). Null when no prefix is forwarded or a prefix cap is exceeded.
64
69
  */
65
70
  export function listObligations(wtRoot: string, wbsList: string[], runIds: string[]): ListObligationsResult | null {
@@ -69,19 +74,14 @@ export function listObligations(wtRoot: string, wbsList: string[], runIds: strin
69
74
  const evDir = join(wtRoot, EVIDENCE_DIR);
70
75
  const runDir = join(wtRoot, RUN_DIR);
71
76
  const evFiles = walkFiles(evDir, wtRoot);
72
- const runFiles = existsSync(runDir)
73
- ? readdirSync(runDir, { withFileTypes: true })
74
- .filter((e) => e.isFile())
75
- .map((e) => join(RUN_DIR, e.name))
76
- .sort()
77
- : [];
77
+ const runFiles = walkFiles(runDir, wtRoot);
78
78
 
79
79
  const files: string[] = [];
80
80
  const counts = new Map<string, number>();
81
81
  if (!collectOwned(evFiles, prefixes, counts, files)) return null;
82
82
  if (!collectOwned(runFiles, prefixes, counts, files)) return null;
83
83
  const unowned = evFiles.length - files.filter((f) => f.startsWith(EVIDENCE_DIR)).length;
84
- return { files: [...new Set(files)].sort(), unowned };
84
+ return { files: [...new Set(files)].sort(), unowned, planeFiles: [...evFiles, ...runFiles] };
85
85
  }
86
86
 
87
87
  /** Classify obligations against the invoking tree: missing, divergent (byte-differs), or ok. */
@@ -164,14 +164,12 @@ export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd(
164
164
  }
165
165
  if (wbsList.length === 0 && runIds.length === 0) {
166
166
  process.stderr.write(
167
- `${persistOutCheckUsage()}\npersist-out-check: at least one --task-file or --run-id prefix required\n`,
167
+ `${persistOutCheckUsage()}\npersist-out-check: at least one --task-file or --run-id required\n`,
168
168
  );
169
169
  return 2;
170
170
  }
171
171
  if (!wtRoot || !existsSync(wtRoot) || !statSync(wtRoot).isDirectory()) {
172
- process.stderr.write(
173
- `${persistOutCheckUsage()}\npersist-out-check: --from must be an existing worktree directory\n`,
174
- );
172
+ process.stderr.write(`${persistOutCheckUsage()}\npersist-out-check: --from must be an existing directory\n`);
175
173
  return 2;
176
174
  }
177
175
  const foreignDivergent = new Set<string>();
@@ -182,27 +180,26 @@ export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd(
182
180
  join(wtRoot, '.spur', 'run', 'persist-out.json'),
183
181
  ].filter(Boolean);
184
182
  for (const cand of candidates) {
185
- if (existsSync(cand)) {
186
- try {
187
- const parsed = JSON.parse(readFileSync(cand, 'utf8'));
188
- if (Array.isArray(parsed.evidenceSkipped)) {
189
- for (const item of parsed.evidenceSkipped) {
190
- if (item.reason === 'foreign-divergent' && typeof item.name === 'string') {
191
- foreignDivergent.add(item.name);
192
- foreignDivergent.add(join(EVIDENCE_DIR, item.name));
193
- }
183
+ if (!existsSync(cand)) continue;
184
+ try {
185
+ const parsed = JSON.parse(readFileSync(cand, 'utf8'));
186
+ if (Array.isArray(parsed.evidenceSkipped)) {
187
+ for (const item of parsed.evidenceSkipped) {
188
+ if (item.reason === 'foreign-divergent' && typeof item.name === 'string') {
189
+ foreignDivergent.add(item.name);
190
+ foreignDivergent.add(join(EVIDENCE_DIR, item.name));
194
191
  }
195
192
  }
196
- if (Array.isArray(parsed.skipped)) {
197
- for (const item of parsed.skipped) {
198
- if (typeof item.id === 'string' && typeof item.reason === 'string') {
199
- skips.push({ id: item.id, reason: item.reason });
200
- }
193
+ }
194
+ if (Array.isArray(parsed.skipped)) {
195
+ for (const item of parsed.skipped) {
196
+ if (typeof item.id === 'string' && typeof item.reason === 'string') {
197
+ skips.push({ id: item.id, reason: item.reason });
201
198
  }
202
199
  }
203
- break;
204
- } catch {}
205
- }
200
+ }
201
+ break;
202
+ } catch {}
206
203
  }
207
204
  const obligations = listObligations(wtRoot, wbsList, runIds);
208
205
  if (obligations === null) {
@@ -211,11 +208,21 @@ export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd(
211
208
  );
212
209
  return 1;
213
210
  }
214
- const { files, unowned } = obligations;
211
+ const { files, unowned, planeFiles } = obligations;
212
+ // Session-incident class (runall-1208-1209-bdcd): the driver forwarded only the batch run id, no
213
+ // prefix matched, and the check passed at 0 obligations while the worktree held its verdicts. Family
214
+ // files outside the prefixes (not foreign-divergent) block teardown; non-family scratch never does.
215
+ const unforwarded = planeFiles
216
+ .filter((rel) => DURABLE_FAMILY_RE.test(basename(rel)))
217
+ .filter((rel) => !files.includes(rel) && !foreignDivergent.has(rel) && !foreignDivergent.has(basename(rel)));
218
+ const unforwardedFindings = unforwarded.map(
219
+ (f) => `UNFORWARDED ${f} — durable verdict/receipt outside the forwarded prefixes`,
220
+ );
215
221
  const { missing, divergent, ok } = compareTrees(wtRoot, invokeRoot, files, foreignDivergent);
216
222
  const findings = [
217
223
  ...missing.map((f) => `MISSING ${f}`),
218
224
  ...divergent.map((f) => `DIVERGENT ${f} — reconcile by hand (persist-out never overwrites)`),
225
+ ...unforwardedFindings,
219
226
  ];
220
227
  for (const line of findings.slice(0, NAMED_CAP)) process.stdout.write(`${line}\n`);
221
228
  if (findings.length > NAMED_CAP) process.stdout.write(`… +${findings.length - NAMED_CAP} more\n`);
@@ -230,7 +237,9 @@ export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd(
230
237
  return 0;
231
238
  }
232
239
  process.stderr.write(
233
- `persist-out-check: BLOCKED ${missing.length} missing, ${divergent.length} divergent — run inline-run-setup --persist-out --from <worktree> before worktree removal\n`,
240
+ `persist-out-check: BLOCKED ${missing.length} missing, ${divergent.length} divergent` +
241
+ `${unforwarded.length > 0 ? `, ${unforwarded.length} unforwarded durable verdict/receipt` : ''}` +
242
+ ' — run inline-run-setup --persist-out --from <worktree> (--task-file <wbs>_<slug>.md) before worktree removal\n',
234
243
  );
235
244
  return 1;
236
245
  }
@@ -33,14 +33,6 @@ function hasClaimMarker(evidence) {
33
33
  function hasCitationForm(evidence) {
34
34
  return CITATION_FORMS.some((re) => re.test(evidence));
35
35
  }
36
- var CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
37
- function extractAnswerConfidence(text) {
38
- const matches = [...text.matchAll(/^\s*Confidence:\s*(\S+)\s*$/gim)];
39
- if (matches.length !== 1)
40
- return;
41
- const value = (matches[0]?.[1] ?? "").toUpperCase();
42
- return CONFIDENCE_LEVELS.includes(value) ? value : undefined;
43
- }
44
36
  var MARKER_PATTERN = /TODO|FIXME|XXX|HACK/;
45
37
  var PRIORITY_PATTERN = /^P[1-4]/;
46
38
  var RANGE_PRIORITY = /^P[1-4]\s*[\u2013\u2014-]\s*P?[1-4]/;
@@ -212,13 +204,6 @@ function blockingReviewFindings(taskContent, deferrals) {
212
204
  }
213
205
  function reviewContractViolations(taskContent) {
214
206
  const violations = [];
215
- const body = reviewBody(taskContent);
216
- if (body !== null && extractAnswerConfidence(body.replace(/\*\*/g, "")) === undefined) {
217
- violations.push({
218
- location: "task-file:Review",
219
- text: "Review report carries no single valid `**Confidence:** HIGH | MEDIUM | LOW` line — " + "state the level the report can defend (HIGH: every requirement row MET, every finding " + "dispositioned, evidence cited; MEDIUM/LOW: name the gap)."
220
- });
221
- }
222
207
  for (const row of parseReviewFindings(taskContent)) {
223
208
  if (hasClaimMarker(row.text) && !hasCitationForm(row.text)) {
224
209
  violations.push({
@@ -229,32 +214,8 @@ function reviewContractViolations(taskContent) {
229
214
  }
230
215
  return violations;
231
216
  }
232
- function reviewConfidenceViolation(reviewAnswer, ackLowConfidence) {
233
- if (ackLowConfidence === "true")
234
- return null;
235
- const level = extractAnswerConfidence(reviewAnswer.replace(/\*\*/g, ""));
236
- if (level === "HIGH" || level === "MEDIUM")
237
- return null;
238
- return {
239
- location: "review-answer:Confidence",
240
- text: level === undefined ? "The review answer carries no valid `**Confidence:** HIGH | MEDIUM | LOW` line — a " + "PASS must be graded, or the operator must set ackLowConfidence" : `The review answer graded its PASS ${level} — HIGH/MEDIUM cross unadorned, ` + "LOW/absent needs the operator’s ackLowConfidence"
241
- };
242
- }
243
- function reviewGradingInput(root, reviewAnswer, ackLowConfidence, fs) {
244
- if (reviewAnswer === undefined)
245
- return;
246
- const path = join(root, reviewAnswer);
247
- let isFile = false;
248
- try {
249
- isFile = fs.statSync(path).isFile();
250
- } catch {
251
- isFile = false;
252
- }
253
- return { answer: isFile ? fs.readFileSync(path, "utf8") : "", ackLowConfidence };
254
- }
255
- function buildReviewGateArtifact(wbs, taskContent, deferrals, review) {
256
- const confidence = review === undefined ? null : reviewConfidenceViolation(review.answer, review.ackLowConfidence);
257
- const contractViolations = [...reviewContractViolations(taskContent), ...confidence === null ? [] : [confidence]];
217
+ function buildReviewGateArtifact(wbs, taskContent, deferrals) {
218
+ const contractViolations = reviewContractViolations(taskContent);
258
219
  const items = [
259
220
  ...blockingReviewFindings(taskContent, deferrals),
260
221
  ...contractViolations.map((v) => ({
@@ -390,7 +351,6 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
390
351
  let spurBin;
391
352
  let root = cwd;
392
353
  let tmpDir = defaultTmpDir;
393
- let reviewAnswer;
394
354
  for (let i = 0;i < argv.length; i++) {
395
355
  const a = argv[i];
396
356
  if (a === undefined)
@@ -401,8 +361,6 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
401
361
  root = argv[++i] ?? root;
402
362
  else if (a === "--tmp-dir")
403
363
  tmpDir = argv[++i] ?? tmpDir;
404
- else if (a === "--review-answer")
405
- reviewAnswer = argv[++i];
406
364
  else if (a === "--help" || a === "-h")
407
365
  return null;
408
366
  else if (mode === "")
@@ -410,7 +368,7 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
410
368
  else if (wbs === "")
411
369
  wbs = a;
412
370
  }
413
- return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir, reviewAnswer };
371
+ return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir };
414
372
  }
415
373
 
416
374
  // plugins/sp/lib/spur-bin.ts
@@ -420,7 +378,7 @@ function spurCommand(spurBin) {
420
378
  }
421
379
 
422
380
  // plugins/sp/scripts/residual-scan.ts
423
- var RESIDUAL_SCAN_USAGE = "usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>] [--review-answer <path>]";
381
+ var RESIDUAL_SCAN_USAGE = "usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>]";
424
382
  function run(cmd, args, cwd) {
425
383
  const result = spawnSync(cmd, args, { cwd, encoding: "utf8" });
426
384
  if (result.error !== undefined)
@@ -515,8 +473,7 @@ function reviewGateMode(opts, env, io) {
515
473
  const runDir = join2(opts.root, ".spur", "run");
516
474
  fs.mkdirSync(runDir, { recursive: true });
517
475
  const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
518
- const grading = reviewGradingInput(opts.root, opts.reviewAnswer, env.ackLowConfidence, fs);
519
- const gate = buildReviewGateArtifact(opts.wbs, content, readDeferrals(runDir, opts.wbs), grading);
476
+ const gate = buildReviewGateArtifact(opts.wbs, content, readDeferrals(runDir, opts.wbs));
520
477
  fs.writeFileSync(join2(runDir, `${opts.wbs}-residuals.json`), `${JSON.stringify(gate.artifact, null, 2)}
521
478
  `);
522
479
  io.out(gate.note);
@@ -671,9 +628,7 @@ export {
671
628
  parseScanArgs,
672
629
  recordedVerdictPath,
673
630
  renderReport,
674
- reviewConfidenceViolation,
675
631
  reviewContractViolations,
676
- reviewGradingInput,
677
632
  scanResiduals2 as scanResiduals,
678
633
  verdictDisagreementNote
679
634
  };
@@ -10,7 +10,7 @@ import { spurCommand } from '../lib/spur-bin';
10
10
 
11
11
  export * from '../lib/residual-scan.generated.mjs';
12
12
  export const RESIDUAL_SCAN_USAGE =
13
- 'usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>] [--review-answer <path>]';
13
+ 'usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>]';
14
14
  export type ScanEnv = { spurBin?: string } & Record<string, string | undefined>;
15
15
  export type ScanIo = { out: (line: string) => void; err: (line: string) => void };
16
16
  export type ScanOptions = { cwd?: string; io?: ScanIo };
@@ -106,16 +106,14 @@ function scanMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
106
106
  /**
107
107
  * Task 1122 R2: the review-time gate — the record sweep's review-finding slice
108
108
  * (core.buildReviewGateArtifact) at the review PASS edges; writes the same `<wbs>-residuals.json`
109
- * the test-fix hop reads. Exit 1 on an open P1-P3 finding, or on a review answer graded LOW or left
110
- * ungraded without `env.ackLowConfidence` (task 1204, graded via `core.reviewGradingInput` — it
111
- * cannot sit inline in the edge because ADR-115 caps a guard at 5 logical commands).
109
+ * the test-fix hop reads. Exit 1 on an open P1-P3 finding. The answer-grade rule (1204) was
110
+ * retired with the asserted confidence machinery (F511); findings stay the gate.
112
111
  */
113
112
  function reviewGateMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
114
113
  const runDir = join(opts.root, '.spur', 'run');
115
114
  fs.mkdirSync(runDir, { recursive: true });
116
115
  const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
117
- const grading = core.reviewGradingInput(opts.root, opts.reviewAnswer, env.ackLowConfidence, fs);
118
- const gate = core.buildReviewGateArtifact(opts.wbs, content, readDeferrals(runDir, opts.wbs), grading);
116
+ const gate = core.buildReviewGateArtifact(opts.wbs, content, readDeferrals(runDir, opts.wbs));
119
117
  fs.writeFileSync(join(runDir, `${opts.wbs}-residuals.json`), `${JSON.stringify(gate.artifact, null, 2)}\n`);
120
118
  io.out(gate.note);
121
119
  return gate.artifact.counts.blocking > 0 ? 1 : 0;
@@ -341,7 +341,6 @@ Verdict: PASS (or PARTIAL / FAIL)
341
341
 
342
342
  ```markdown
343
343
  Verdict: PASS
344
- Confidence: HIGH
345
344
 
346
345
  ### Per-Requirement Traceability
347
346
  | Req | Status | Evidence |
@@ -360,7 +359,7 @@ Confidence: HIGH
360
359
  ```
361
360
 
362
361
  The per-requirement traceability table MUST use `| Req | Status | Evidence |` (exactly this header, no `R#`/`R`/`Requirement` variant — `Status` in column 2 — and no extra columns between Req and Status). The Acceptance Criteria table MUST use `| AC | Status | Evidence Type | Evidence |`.
363
- The answer MUST carry exactly one `Confidence: HIGH|MEDIUM|LOW` line (task 1068): the verifier's stated confidence in the verdict — HIGH = fresh executable evidence + re-read anchors this run; MEDIUM = partial reliance on prior evidence, external/delegated outcomes, or an unstable tree; LOW = key evidence not re-executed. A missing or out-of-vocabulary line fails lint (`confidence-missing` / `confidence-value`) and blocks derivation; the value lands in the verdict artifact's `confidence` field and the recorded `## Testing` section.
362
+ The answer carries no `Confidence:` line (F511, task 1207): model-asserted confidence is not verification — a verifier that can write a PASS can write `HIGH`. What the old level compressed now lives in the evidence cells themselves: cite fresh executable evidence and name residual gaps explicitly. Legacy answers that still carry a confidence line parse (the value is ignored); no lint code reads it and the artifact and recorded `## Testing` section carry no confidence value.
364
363
  The answer MUST cite a source for external API/library claims in Requirement/AC evidence cells: a cell asserting a scoped-package (`@scope/name`), `node_modules/`, semver-version (`x.y.z`), or backticked function-call (`` `name(args)` ``) claim is rejected with `evidence-citation` unless the SAME cell carries a citation — a repo `path:line(-end)` (backticked or bare), the external named-origin form (@origin `path` line N), or a URL. Cross-cell references ("receipt above") do not count; marker-free receipts ("bun test 398 pass / 0 fail") need no citation.
365
364
  **MUST NOT:** place a `Severity` column between `Req` and `Status`. The parser tolerates variants
366
365
  as defense-in-depth; this contract is canonical.
@@ -35,42 +35,21 @@ interface VerifyVerdict {
35
35
  status: 'pass' | 'fail' | 'warn';
36
36
  evidence: string;
37
37
  }>;
38
- /** Verifier confidence (task 1068 R3). REQUIRED for PASS — the done gate denies
39
- * a confidence-less PASS. Accepted case-insensitively, normalized to uppercase. */
40
- confidence?: 'HIGH' | 'MEDIUM' | 'LOW';
38
+ // F511 (task 1207): no asserted `confidence` field. Legacy artifacts that still carry one
39
+ // parse with the value ignored — unknown legacy keys are tolerated on read.
41
40
  }
42
41
  ```
43
42
 
44
- ## Confidence level (task 1068 R3)
43
+ ## No asserted confidence (F511, task 1207)
45
44
 
46
- A PASS verdict must state the verifier's confidence: `"confidence": "HIGH" | "MEDIUM" | "LOW"`.
47
- The value is case-insensitive on read (normalized to uppercase), but a present value outside the
48
- enum invalidates the artifact, and a PASS with the field absent is denied at the `* → done`
49
- transition with a remediation naming the field (`--force-done` remains the documented escape).
50
- The verify answer's `Confidence:` lint rule (producer side) already guarantees the field for
51
- pipeline-produced artifacts; this closes the hand-authored / pre-1068 artifact hole.
52
-
53
- ### Coherence: the level must be earned (task 1133)
54
-
55
- Presence and vocabulary are not enough — the level has to match the rows in the same answer, so the
56
- lint rejects a level that contradicts its own evidence. Two rejection classes, both addressed to the
57
- `Confidence:` line and both capped by the shared `ANSWER_LINT_MAX_FINDINGS` budget:
58
-
59
- - `confidence-unwarranted` — `HIGH` while any requirement row normalizes to `PARTIAL`/`UNMET`, any
60
- acceptance-criteria row does not normalize to `MET`, or any `MET` row's evidence is hedged.
61
- A `HIGH` claim says the evidence needs no re-check; a non-MET or hedged row says the opposite.
62
- - `confidence-understated` — `LOW` while at least one requirement row exists and every requirement
63
- and acceptance-criteria row normalizes to `MET`. The evidence already did the work, so `LOW`
64
- misreports the state as unproven.
65
-
66
- `MEDIUM` carries no coherence rule in either direction: it is the level a verifier uses for a caveat
67
- with no row to point at, and closing it would force either an overclaimed `HIGH` or an unusable
68
- `LOW`. The level is normalized to uppercase before the decision, so `high` is judged as `HIGH`.
69
-
70
- Set the level from the rows, not the mood: all rows `MET` with fresh evidence is `HIGH`; a
71
- `PARTIAL`/`UNMET` row or a hedge means `MEDIUM`, not `HIGH`. An unwarranted level does not merely
72
- warn — the lint rejects the answer and no verdict artifact is written, so the level is worth
73
- choosing deliberately before the verdict line is finalized.
45
+ Verdict artifacts carry no `confidence` field and verify answers no `Confidence:` line. The
46
+ 1068/1133 machinery that required, linted and coherence-checked a model-asserted
47
+ `HIGH | MEDIUM | LOW` was retired: a verifier that can write a PASS can write `HIGH`, so the grade
48
+ verifies nothing. What the level compressed belongs in the evidence cells — cite fresh executable
49
+ evidence and name residual gaps explicitly. The done gate no longer denies a confidence-less PASS:
50
+ it checks the proof digests, which are the part a hand-edited artifact cannot fake. The DERIVED
51
+ confidence family is a different thing and stays: feature-verification receipts (ADR-131, 1196),
52
+ the decision subsystem threshold, history-anatomy reading.
74
53
 
75
54
  ## Compatibility alias: `scenario` row key
76
55
 
@@ -124,9 +103,11 @@ is part of the contract so weak proof is visible to the pipeline and to reviewer
124
103
  - `n/a`: explicitly justified non-applicability.
125
104
 
126
105
  Every CORE behavior-bearing AC requires executable evidence: at least one `test` or `command` row.
127
- `spur task verdict` treats AC rows as core and behavior-bearing by default; add `[advisory]`,
128
- `[non-core]`, `[non-behavior]`, or `[docs-only]` in the AC id only when that weaker rule is
129
- intentional. A MET behavior AC with only `static-ref`, `manual-review`, or `llm-judge` evidence is
106
+ `spur task verdict` treats AC rows as core and behavior-bearing by default. Only the task author
107
+ can exempt an AC: the `[advisory]`, `[non-core]`, `[non-behavior]` or `[docs-only]` tag must be on
108
+ the AC item in the task file's `## Acceptance Criteria`. The verifier must not add these tags to
109
+ answer rows; a tag that appears only in the answer is ignored and flagged by an advisory
110
+ `ac-exemption-not-in-task` check. A MET behavior AC with only `static-ref`, `manual-review`, or `llm-judge` evidence is
130
111
  downgraded to `PARTIAL` and emits an `evidence-rule-failed` check. CLI-surface changes should also
131
112
  emit a `cli-golden-path-present` check backed by a golden-path `--json` command invocation.
132
113
 
@@ -377,8 +377,10 @@ rows), there is no checklist label: cite each row by its exact scenario title
377
377
  matches nothing and the verdict is rejected.
378
378
 
379
379
  **Evidence rule (behavior-bearing AC):** an AC row with `status: MET` on a behavior-bearing id
380
- (no `[advisory]`/`[non-core]`/`[non-behavior]`/`[docs-only]` marker) MUST carry `test` or
381
- `command` evidence; any other evidence type downgrades the row to `PARTIAL` and caps the verdict.
380
+ (no `[advisory]`/`[non-core]`/`[non-behavior]`/`[docs-only]` marker **on the AC item in the task
381
+ file**) MUST carry `test` or `command` evidence; any other evidence type downgrades the row to
382
+ `PARTIAL` and caps the verdict. Tags written only in the answer are ignored and reported by the
383
+ advisory `ac-exemption-not-in-task` check.
382
384
 
383
385
  **Worked example** — a minimal PASS-producing answer file:
384
386
 
@@ -219,7 +219,9 @@ clean at authoring time.
219
219
  ### Which tags exempt a row from executable evidence
220
220
 
221
221
  A `MET` row is silently demoted to `PARTIAL` unless it carries `test` or `command` evidence — the
222
- executable-evidence rule. Five tags opt a row out, because not every scenario is behavioral:
222
+ executable-evidence rule. Five tags opt a row out, because not every scenario is behavioral. The
223
+ tag counts only when the task author writes it on the AC item in the task file; `spur task verdict`
224
+ ignores (and flags) a tag that appears only in the verify answer:
223
225
 
224
226
  `[doc-only]` · `[docs-only]` · `[non-behavior]` · `[advisory]` · `[non-core]`
225
227
 
@@ -139,8 +139,9 @@ silently incomplete (H6 shipped at 23/48 that way, with one verdict carrying an
139
139
  parses zero AC rows while lint still reports PASS.
140
140
  3. A behavioral AC marked `MET` with a non-executable evidence type (`static-ref`,
141
141
  `manual-review`, `llm-judge`) is **downgraded to PARTIAL** by `task verdict`, making the whole
142
- verdict PARTIAL. Use `test`/`command` (grep-based verification counts as `command`), or tag the
143
- AC id `[non-behavior]`/`[advisory]` when executable evidence genuinely doesn't apply.
142
+ verdict PARTIAL. Use `test`/`command` (grep-based verification counts as `command`). Only the
143
+ task author can exempt an AC, by tagging the AC item in the task file `[non-behavior]`/`[advisory]`;
144
+ the same tag added in the verify answer alone is ignored.
144
145
 
145
146
  **Invariant:** a force-done task has a non-empty `done_reason` naming the timeout, a verdict
146
147
  artifact whose AC rows cover every declared scenario, and a green lint/test run recorded in
@@ -171,17 +171,13 @@ Two branches reach the bounded repair hop, not one (session finding after 1088):
171
171
  an on-graph, bounded path to remediation instead of dead-ending the run, and the fix re-enters
172
172
  quality → review → verify on a freshly captured proof digest.
173
173
 
174
- The completion gate also reads the verifier's confidence (session finding after 1088): the verify
175
- answer's `Confidence: HIGH|MEDIUM|LOW` line reaches the verdict artifact, and both `verify → record`
176
- and `record → done` refuse a `LOW` (or absent) level unless the run was launched with
177
- `--vars '{"ackLowConfidence":"true"}'`. A PASS the verifier would not stand behind is an operator
178
- risk decision, so it takes an operator acknowledgement — not a silent certification.
179
- The CLI-layer done gate (`done-transition-guard`, task 1068 R3) separately requires the artifact to *state* a
180
- level at all: a PASS artifact whose `confidence` is missing is denied there too, and a present but
181
- out-of-vocabulary value fails the read closed (`invalid confidence field`), so a hand-driven
182
- `task update <wbs> done` — which is how this pipeline itself reaches `done` (`--no-lifecycle`) —
183
- cannot certify a verdict that carries no confidence. `LOW` is accepted at that gate; the
184
- acknowledgement rule above stays the pipeline's.
174
+ The completion gate reads deterministic evidence only (F511, task 1207): both `verify → record` and
175
+ `record → done` check the proof digests in `.spur/run/<wbs>-verdict.json` — verdict PASS, matching
176
+ run/definition digests, review stage completed. No confidence is read, stated or acknowledged; the
177
+ asserted `Confidence:` line and the `ackLowConfidence` operator var were retired because a model
178
+ that can write a PASS can write `HIGH` — the grade verified nothing. What protects a hand-driven
179
+ `task update <wbs> done` is unchanged: the digests must match the run's captured proof, and the
180
+ review stage must actually be completed in the artifact.
185
181
 
186
182
  Agentic steps use the pure slash inputs declared by each YAML `agent.run` action (ADR-043). The
187
183
  workflow engine dispatches them; the interactive driver invokes their backing skills in-session.
@@ -844,9 +844,10 @@ leave no `.spur/run/<wbs>-check-receipt.json`. The deciding artifacts are then
844
844
  1. **Drive the pipeline** for anything that must be certified: only a digest-bound full receipt
845
845
  satisfies a review/verify/record boundary (`sp-spur-check`), and the `test` hop owns the capture.
846
846
  2. **When a host-driven pass is unavoidable, say so in the verdict** — name the status file and gate
847
- log as the evidence and grade confidence `MEDIUM`. A `HIGH` claim needs re-runnable evidence from a
848
- checked path (the `sp-code-verification` ladder); claiming a receipt that was never written is the
849
- failure mode this note exists to prevent (observed across four A91 tasks, 2026-10-12).
847
+ log as the evidence, and mark the reliance explicitly in the evidence cell (a host-driven gate is
848
+ not re-runnable proof from a checked path; the `sp-code-verification` ladder is). Claiming a
849
+ receipt that was never written is the failure mode this note exists to prevent (observed across
850
+ four A91 tasks, 2026-10-12).
850
851
 
851
852
  **A mismatch names the drifted paths (R5).** At gate entry the pipeline snapshots the non-corpus tree
852
853
  state (`git status --porcelain=v1 -uall` plus `git diff --name-only HEAD`, excluding
@@ -977,14 +978,14 @@ Each item below cost a driver cycle to discover. They are contracts, not tips.
977
978
  `.spur/run/<wbs>-diffstat.json` and never on command output. Never the bare unguarded
978
979
  source-repo path form — the script-contract check forbids it on a shipped surface because it
979
980
  shadows the installed twin in a consumer.
980
- - **The verify answer grammar is exact.** One `Verdict:` line, one `Confidence:` line, then
981
+ - **The verify answer grammar is exact.** One `Verdict:` line, then
981
982
  `### Per-Requirement Traceability` (`| Req | Status | Evidence |`) and
982
983
  `### Acceptance Criteria Verification` (`| AC | Status | Evidence Type | Evidence |`). Requirement
983
984
  statuses are `MET | PARTIAL | UNMET`; AC statuses add `N/A`; every AC row id must match a declared
984
985
  scenario title verbatim, including its `(req: ...)` suffix. A `MET` row whose evidence hedges
985
986
  (`likely`, `probably`, `seems`, `appears`, `possibly`, `presumably`) is rejected as
986
- `evidence-hedged`, and a level the rows do not earn is rejected as `confidence-unwarranted` or
987
- `confidence-understated`. `spur task verdict` lints before writing, so a rejected answer leaves no
987
+ `evidence-hedged`. No `Confidence:` line exists (F511, task 1207) — legacy answers that carry one
988
+ parse with the value ignored. `spur task verdict` lints before writing, so a rejected answer leaves no
988
989
  artifact and exits non-zero.
989
990
  - **Harness tooling refuses direct task-file writes.** Resolve a merge conflict in a task file by
990
991
  choosing a git side, then applying the corpus delta through `spur task update` (for example