@gobing-ai/spur 0.4.4 → 0.4.5
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/rules/docs/sourced-api-claims.yaml +3 -2
- package/config/rules/recommended-pre-check.yaml +11 -9
- package/config/workflows/task-pipeline.yaml +31 -71
- package/package.json +1 -1
- package/plugins/sp/agents/super-reviewer.md +11 -27
- package/plugins/sp/commands/dev-review.md +1 -1
- package/plugins/sp/lib/inline-run.generated.mjs +2 -2
- package/plugins/sp/lib/residual-scan.generated.d.mts +0 -13
- package/plugins/sp/lib/residual-scan.generated.mjs +3 -47
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/residual-scan.mjs +5 -50
- package/plugins/sp/scripts/residual-scan.ts +4 -6
- package/plugins/sp/skills/code-verification/SKILL.md +1 -2
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +11 -32
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +7 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +7 -6
- package/spur.js +289 -405
- package/config/rules/docs/verdict-confidence.yaml +0 -68
|
@@ -47,8 +47,6 @@ export declare interface ParsedScanArgs {
|
|
|
47
47
|
spurBin?: string;
|
|
48
48
|
root: string;
|
|
49
49
|
tmpDir: string;
|
|
50
|
-
/** The --review-answer <path> flag: the review answer the review-gate mode grades (task 1204). */
|
|
51
|
-
reviewAnswer?: string;
|
|
52
50
|
}
|
|
53
51
|
export declare const ALLOW_PRAGMA: 'residual-scan:allow';
|
|
54
52
|
export declare function recordedVerdictPath(runDir: string, wbs: string, fs: Pick<typeof import('node:fs'), 'lstatSync' | 'existsSync'>): string;
|
|
@@ -70,21 +68,10 @@ export declare function scanResiduals(inputs: ResidualScanInputs): ResidualArtif
|
|
|
70
68
|
export declare function parseDeferralEntries(raw: unknown): Deferral[];
|
|
71
69
|
export declare function blockingReviewFindings(taskContent: string, deferrals: Deferral[]): ResidualItem[];
|
|
72
70
|
export declare function reviewContractViolations(taskContent: string): Array<{ location: string; text: string }>;
|
|
73
|
-
export declare function reviewConfidenceViolation(
|
|
74
|
-
reviewAnswer: string,
|
|
75
|
-
ackLowConfidence: string | undefined,
|
|
76
|
-
): { location: string; text: string } | null;
|
|
77
|
-
export declare function reviewGradingInput(
|
|
78
|
-
root: string,
|
|
79
|
-
reviewAnswer: string | undefined,
|
|
80
|
-
ackLowConfidence: string | undefined,
|
|
81
|
-
fs: Pick<typeof import('node:fs'), 'readFileSync' | 'statSync'>,
|
|
82
|
-
): { answer: string; ackLowConfidence: string | undefined } | undefined;
|
|
83
71
|
export declare function buildReviewGateArtifact(
|
|
84
72
|
wbs: string,
|
|
85
73
|
taskContent: string,
|
|
86
74
|
deferrals: Deferral[],
|
|
87
|
-
review?: { answer: string; ackLowConfidence: string | undefined },
|
|
88
75
|
): { artifact: ResidualArtifact; note: string };
|
|
89
76
|
export declare function parseScanArgs(
|
|
90
77
|
argv: readonly (string | undefined)[],
|
|
@@ -21,14 +21,6 @@ function hasClaimMarker(evidence) {
|
|
|
21
21
|
function hasCitationForm(evidence) {
|
|
22
22
|
return CITATION_FORMS.some((re) => re.test(evidence));
|
|
23
23
|
}
|
|
24
|
-
var CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
|
|
25
|
-
function extractAnswerConfidence(text) {
|
|
26
|
-
const matches = [...text.matchAll(/^\s*Confidence:\s*(\S+)\s*$/gim)];
|
|
27
|
-
if (matches.length !== 1)
|
|
28
|
-
return;
|
|
29
|
-
const value = (matches[0]?.[1] ?? "").toUpperCase();
|
|
30
|
-
return CONFIDENCE_LEVELS.includes(value) ? value : undefined;
|
|
31
|
-
}
|
|
32
24
|
|
|
33
25
|
// packages/app/src/services/residual-scan.ts
|
|
34
26
|
var MARKER_PATTERN = /TODO|FIXME|XXX|HACK/;
|
|
@@ -202,13 +194,6 @@ function blockingReviewFindings(taskContent, deferrals) {
|
|
|
202
194
|
}
|
|
203
195
|
function reviewContractViolations(taskContent) {
|
|
204
196
|
const violations = [];
|
|
205
|
-
const body = reviewBody(taskContent);
|
|
206
|
-
if (body !== null && extractAnswerConfidence(body.replace(/\*\*/g, "")) === undefined) {
|
|
207
|
-
violations.push({
|
|
208
|
-
location: "task-file:Review",
|
|
209
|
-
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)."
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
197
|
for (const row of parseReviewFindings(taskContent)) {
|
|
213
198
|
if (hasClaimMarker(row.text) && !hasCitationForm(row.text)) {
|
|
214
199
|
violations.push({
|
|
@@ -219,32 +204,8 @@ function reviewContractViolations(taskContent) {
|
|
|
219
204
|
}
|
|
220
205
|
return violations;
|
|
221
206
|
}
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
return null;
|
|
225
|
-
const level = extractAnswerConfidence(reviewAnswer.replace(/\*\*/g, ""));
|
|
226
|
-
if (level === "HIGH" || level === "MEDIUM")
|
|
227
|
-
return null;
|
|
228
|
-
return {
|
|
229
|
-
location: "review-answer:Confidence",
|
|
230
|
-
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"
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
function reviewGradingInput(root, reviewAnswer, ackLowConfidence, fs) {
|
|
234
|
-
if (reviewAnswer === undefined)
|
|
235
|
-
return;
|
|
236
|
-
const path = join(root, reviewAnswer);
|
|
237
|
-
let isFile = false;
|
|
238
|
-
try {
|
|
239
|
-
isFile = fs.statSync(path).isFile();
|
|
240
|
-
} catch {
|
|
241
|
-
isFile = false;
|
|
242
|
-
}
|
|
243
|
-
return { answer: isFile ? fs.readFileSync(path, "utf8") : "", ackLowConfidence };
|
|
244
|
-
}
|
|
245
|
-
function buildReviewGateArtifact(wbs, taskContent, deferrals, review) {
|
|
246
|
-
const confidence = review === undefined ? null : reviewConfidenceViolation(review.answer, review.ackLowConfidence);
|
|
247
|
-
const contractViolations = [...reviewContractViolations(taskContent), ...confidence === null ? [] : [confidence]];
|
|
207
|
+
function buildReviewGateArtifact(wbs, taskContent, deferrals) {
|
|
208
|
+
const contractViolations = reviewContractViolations(taskContent);
|
|
248
209
|
const items = [
|
|
249
210
|
...blockingReviewFindings(taskContent, deferrals),
|
|
250
211
|
...contractViolations.map((v) => ({
|
|
@@ -380,7 +341,6 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
|
|
|
380
341
|
let spurBin;
|
|
381
342
|
let root = cwd;
|
|
382
343
|
let tmpDir = defaultTmpDir;
|
|
383
|
-
let reviewAnswer;
|
|
384
344
|
for (let i = 0;i < argv.length; i++) {
|
|
385
345
|
const a = argv[i];
|
|
386
346
|
if (a === undefined)
|
|
@@ -391,8 +351,6 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
|
|
|
391
351
|
root = argv[++i] ?? root;
|
|
392
352
|
else if (a === "--tmp-dir")
|
|
393
353
|
tmpDir = argv[++i] ?? tmpDir;
|
|
394
|
-
else if (a === "--review-answer")
|
|
395
|
-
reviewAnswer = argv[++i];
|
|
396
354
|
else if (a === "--help" || a === "-h")
|
|
397
355
|
return null;
|
|
398
356
|
else if (mode === "")
|
|
@@ -400,7 +358,7 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
|
|
|
400
358
|
else if (wbs === "")
|
|
401
359
|
wbs = a;
|
|
402
360
|
}
|
|
403
|
-
return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir
|
|
361
|
+
return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir };
|
|
404
362
|
}
|
|
405
363
|
export {
|
|
406
364
|
ALLOW_PRAGMA,
|
|
@@ -419,9 +377,7 @@ export {
|
|
|
419
377
|
parseScanArgs,
|
|
420
378
|
recordedVerdictPath,
|
|
421
379
|
renderReport,
|
|
422
|
-
reviewConfidenceViolation,
|
|
423
380
|
reviewContractViolations,
|
|
424
|
-
reviewGradingInput,
|
|
425
381
|
scanResiduals,
|
|
426
382
|
verdictDisagreementNote
|
|
427
383
|
};
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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"]
|
|
@@ -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
|
|
233
|
-
|
|
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
|
|
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>]
|
|
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
|
|
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>]
|
|
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
|
|
110
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
##
|
|
43
|
+
## No asserted confidence (F511, task 1207)
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
|
@@ -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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
848
|
-
checked path
|
|
849
|
-
failure mode this note exists to prevent (observed across
|
|
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,
|
|
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
|
|
987
|
-
|
|
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
|