pi-gauntlet 5.3.1 → 5.3.2
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v5.3.2 - 2026-09-06
|
|
4
|
+
|
|
5
|
+
- Plan coverage: `## Spec coverage` rows accept the exact owner `Verification` for requirements the plan header's `**Verification:**` command proves. `table-closure` closes such rows (mechanical row, unparseable anchor, or literal-free anchor -> finding; never counted as task coverage); `quote-integrity` resolves their literals by verbatim containment in the backtick-stripped header. Orphan-owner reason now names `Verification`; anchor-parseability is checked once per row. `header-entrypoint` unchanged. `writing-plans` documents the owner form with a scoped-tests / final-suite example. Spec: `doc/specs/2026-09-06-gh-24-final-verification-ownership.md` (closes #24).
|
|
6
|
+
|
|
3
7
|
## v5.3.1 - 2026-09-06
|
|
4
8
|
|
|
5
9
|
- Task tracking: planning initializes the list once; execution starts and completes existing indices explicitly. Execution retries and fix loops reuse their task; conformance adds only new remediation gaps and reuses their `Gn` indices across rounds.
|
|
@@ -16,6 +16,9 @@ const SPEC_TEXT = [
|
|
|
16
16
|
"", // 11
|
|
17
17
|
"## Other", // 12
|
|
18
18
|
"Stuff.", // 13
|
|
19
|
+
"", // 14
|
|
20
|
+
"## Acceptance", // 15
|
|
21
|
+
"Full suite passes: `npm run fixture-verify`.", // 16
|
|
19
22
|
].join("\n");
|
|
20
23
|
|
|
21
24
|
const VALID_PLAN = `# Fixture Plan
|
|
@@ -70,6 +73,7 @@ The literal TODO is intentionally documented here per spec quote-integrity requi
|
|
|
70
73
|
| § "Testing" L9-L9 | banned token literal handling | Task 3 |
|
|
71
74
|
| - | mechanical: wire test into CI | Task 2 |
|
|
72
75
|
| § "Other" L12-L13 | out of scope thing | waived: out of scope per spec |
|
|
76
|
+
| § "Acceptance" L16 | full suite passes | Verification |
|
|
73
77
|
`;
|
|
74
78
|
|
|
75
79
|
function alwaysTruePort(): FsPort {
|
|
@@ -157,7 +161,7 @@ test("check 1 owner-cell grammar: trailing junk after a Task <n> list is malform
|
|
|
157
161
|
assert.ok(
|
|
158
162
|
tc.some(
|
|
159
163
|
(f) =>
|
|
160
|
-
f.reason.includes("owner cell is not a 'Task <n>' list or 'waived: <reason>'") &&
|
|
164
|
+
f.reason.includes("owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'") &&
|
|
161
165
|
f.text.includes("Task 1 (see note)"),
|
|
162
166
|
),
|
|
163
167
|
`expected an owner-cell malformed finding, got: ${JSON.stringify(tc)}`,
|
|
@@ -165,7 +169,7 @@ test("check 1 owner-cell grammar: trailing junk after a Task <n> list is malform
|
|
|
165
169
|
assert.ok(
|
|
166
170
|
tc.some(
|
|
167
171
|
(f) =>
|
|
168
|
-
f.reason.includes("owner cell is not a 'Task <n>' list or 'waived: <reason>'") &&
|
|
172
|
+
f.reason.includes("owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'") &&
|
|
169
173
|
f.line === lineOf(mutated, '| § "Design" L4-L6 | parser grammar basics | Task 1 (see note) |'),
|
|
170
174
|
),
|
|
171
175
|
`expected the owner-cell malformed finding's line to point at the offending row, got: ${JSON.stringify(tc)}`,
|
|
@@ -180,19 +184,173 @@ test("check 1 owner-cell grammar: empty waiver reason is malformed, not accepted
|
|
|
180
184
|
const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
|
|
181
185
|
const tc = findingsFor(findings, "table-closure");
|
|
182
186
|
assert.ok(
|
|
183
|
-
tc.some((f) => f.reason.includes("owner cell is not a 'Task <n>' list or 'waived: <reason>'")),
|
|
187
|
+
tc.some((f) => f.reason.includes("owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'")),
|
|
184
188
|
`expected an owner-cell malformed finding for empty waiver reason, got: ${JSON.stringify(tc)}`,
|
|
185
189
|
);
|
|
186
190
|
assert.ok(
|
|
187
191
|
tc.some(
|
|
188
192
|
(f) =>
|
|
189
|
-
f.reason.includes("owner cell is not a 'Task <n>' list or 'waived: <reason>'") &&
|
|
193
|
+
f.reason.includes("owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'") &&
|
|
190
194
|
f.line === lineOf(mutated, '| § "Other" L12-L13 | out of scope thing | waived: |'),
|
|
191
195
|
),
|
|
192
196
|
`expected the owner-cell malformed finding's line to point at the offending row, got: ${JSON.stringify(tc)}`,
|
|
193
197
|
);
|
|
194
198
|
});
|
|
195
199
|
|
|
200
|
+
const VERIFICATION_ROW = '| § "Acceptance" L16 | full suite passes | Verification |';
|
|
201
|
+
|
|
202
|
+
function withRow(plan: string, row: string): string {
|
|
203
|
+
return plan.replace(
|
|
204
|
+
'| § "Other" L12-L13 | out of scope thing | waived: out of scope per spec |',
|
|
205
|
+
`| § "Other" L12-L13 | out of scope thing | waived: out of scope per spec |\n${row}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const owner of ["verification", "Verify", "VERIFICATION", "Task 1, Verification"]) {
|
|
210
|
+
test(`Verification owner: '${owner}' is a malformed owner -> table-closure`, () => {
|
|
211
|
+
const row = `| § "Acceptance" L16 | full suite passes | ${owner} |`;
|
|
212
|
+
const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
|
|
213
|
+
const tc = findingsFor(findings, "table-closure");
|
|
214
|
+
assert.ok(
|
|
215
|
+
tc.some(
|
|
216
|
+
(f) =>
|
|
217
|
+
f.reason === "owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'" &&
|
|
218
|
+
f.text === row,
|
|
219
|
+
),
|
|
220
|
+
`expected orphan-owner finding for ${owner}, got: ${JSON.stringify(tc)}`,
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
test("Verification owner: unparseable anchor ('-') -> table-closure parseability reason", () => {
|
|
226
|
+
const row = "| - | full suite passes | Verification |";
|
|
227
|
+
const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
|
|
228
|
+
const tc = findingsFor(findings, "table-closure");
|
|
229
|
+
assert.ok(
|
|
230
|
+
tc.some(
|
|
231
|
+
(f) =>
|
|
232
|
+
f.reason === 'requirement row anchor is not a parseable § "heading" L<n>-L<n> anchor' && f.text === row,
|
|
233
|
+
),
|
|
234
|
+
`expected parseability finding, got: ${JSON.stringify(tc)}`,
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("Verification owner: anchored lines without a backtick literal -> table-closure", () => {
|
|
239
|
+
const row = '| § "Other" L13 | stuff | Verification |';
|
|
240
|
+
const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
|
|
241
|
+
const tc = findingsFor(findings, "table-closure");
|
|
242
|
+
assert.ok(
|
|
243
|
+
tc.some(
|
|
244
|
+
(f) => f.reason === "Verification row has no backtick literal to check against the header" && f.text === row,
|
|
245
|
+
),
|
|
246
|
+
`expected no-literal finding, got: ${JSON.stringify(tc)}`,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("Verification owner: mechanical row -> table-closure", () => {
|
|
251
|
+
const row = "| - | mechanical: run the suite | Verification |";
|
|
252
|
+
const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
|
|
253
|
+
const tc = findingsFor(findings, "table-closure");
|
|
254
|
+
assert.ok(
|
|
255
|
+
tc.some((f) => f.reason === "mechanical row owner must be a Task <n>" && f.text === row),
|
|
256
|
+
`expected mechanical-owner finding, got: ${JSON.stringify(tc)}`,
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("table-closure: multi-owner row with unparseable anchor yields one parseability finding", () => {
|
|
261
|
+
const row = "| L999 | naming details | Task 1, Task 2 |";
|
|
262
|
+
const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
|
|
263
|
+
const parseability = findingsFor(findings, "table-closure").filter(
|
|
264
|
+
(f) => f.text === row && f.reason.includes("not a parseable"),
|
|
265
|
+
);
|
|
266
|
+
assert.equal(parseability.length, 1);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("Verification owner: does not satisfy a task's coverage requirement", () => {
|
|
270
|
+
const mutated = withRow(VALID_PLAN, VERIFICATION_ROW).replace(
|
|
271
|
+
'| § "Testing" L9-L9 | banned token literal handling | Task 3 |\n',
|
|
272
|
+
"",
|
|
273
|
+
);
|
|
274
|
+
const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
|
|
275
|
+
assert.ok(
|
|
276
|
+
findingsFor(findings, "table-closure").some((f) =>
|
|
277
|
+
f.reason.includes("Task 3 does not appear as an owner"),
|
|
278
|
+
),
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("Verification quote-integrity: literal missing from header -> quote-integrity on the row", () => {
|
|
283
|
+
const mutated = VALID_PLAN.replace("**Verification:** npm run fixture-verify", "**Verification:** npm run other");
|
|
284
|
+
const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
|
|
285
|
+
const qi = findingsFor(findings, "quote-integrity");
|
|
286
|
+
const row = '| § "Acceptance" L16 | full suite passes | Verification |';
|
|
287
|
+
assert.ok(
|
|
288
|
+
qi.some(
|
|
289
|
+
(f) =>
|
|
290
|
+
f.reason === "verification header does not contain the required verbatim literal `npm run fixture-verify`" &&
|
|
291
|
+
f.text === row &&
|
|
292
|
+
f.line === lineOf(mutated, row),
|
|
293
|
+
),
|
|
294
|
+
`expected header-containment finding, got: ${JSON.stringify(qi)}`,
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("Verification quote-integrity: multi-command header contains the literal", () => {
|
|
299
|
+
const mutated = VALID_PLAN.replace(
|
|
300
|
+
"**Verification:** npm run fixture-verify",
|
|
301
|
+
"**Verification:** `npm run fixture-verify && npm run lint`",
|
|
302
|
+
);
|
|
303
|
+
assert.deepEqual(findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), "quote-integrity"), []);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("Verification quote-integrity: two-span header contains both literals", () => {
|
|
307
|
+
const spec = SPEC_TEXT.replace(
|
|
308
|
+
"Full suite passes: `npm run fixture-verify`.",
|
|
309
|
+
"Full suite passes: `npm run fixture-verify` and `npm run lint`.",
|
|
310
|
+
);
|
|
311
|
+
const mutated = VALID_PLAN.replace(
|
|
312
|
+
"**Verification:** npm run fixture-verify",
|
|
313
|
+
"**Verification:** `npm run fixture-verify`, `npm run lint`",
|
|
314
|
+
);
|
|
315
|
+
assert.deepEqual(findingsFor(checkPlan(mutated, spec, alwaysTruePort()), "quote-integrity"), []);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("Verification quote-integrity: missing header -> quote-integrity per literal plus header-entrypoint", () => {
|
|
319
|
+
const mutated = VALID_PLAN.replace("**Verification:** npm run fixture-verify\n", "");
|
|
320
|
+
const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
|
|
321
|
+
assert.equal(
|
|
322
|
+
findingsFor(findings, "quote-integrity").filter((f) => f.reason.includes("`npm run fixture-verify`")).length,
|
|
323
|
+
1,
|
|
324
|
+
);
|
|
325
|
+
assert.ok(findingsFor(findings, "header-entrypoint").some((f) => f.reason.includes("missing header")));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("Verification quote-integrity: task body containing the full header string still fails header-entrypoint", () => {
|
|
329
|
+
const mutated = VALID_PLAN.replace(
|
|
330
|
+
"This task handles naming details.",
|
|
331
|
+
"This task handles naming details. Run npm run fixture-verify here.",
|
|
332
|
+
);
|
|
333
|
+
const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
|
|
334
|
+
assert.ok(findingsFor(findings, "header-entrypoint").some((f) => f.text.includes("Run npm run fixture-verify here")));
|
|
335
|
+
assert.deepEqual(findingsFor(findings, "quote-integrity"), []);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("Verification quote-integrity: task body containing only a sub-command of a multi-command header is not caught by header-entrypoint", () => {
|
|
339
|
+
const mutated = VALID_PLAN.replace(
|
|
340
|
+
"**Verification:** npm run fixture-verify",
|
|
341
|
+
"**Verification:** `npm run fixture-verify && npm run lint`",
|
|
342
|
+
).replace("This task handles naming details.", "This task handles naming details. Run npm run fixture-verify here.");
|
|
343
|
+
const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
|
|
344
|
+
assert.deepEqual(findingsFor(findings, "header-entrypoint"), []);
|
|
345
|
+
assert.deepEqual(findingsFor(findings, "quote-integrity"), []);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("Verification quote-integrity: task-owned literal check unchanged", () => {
|
|
349
|
+
const mutated = VALID_PLAN.replace("This task implements helperFn() for parsing.", "This task implements the helper.");
|
|
350
|
+
const qi = findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), "quote-integrity");
|
|
351
|
+
assert.ok(qi.some((f) => f.reason.includes("Task 1 body does not contain the required verbatim literal `helperFn()`")));
|
|
352
|
+
});
|
|
353
|
+
|
|
196
354
|
test("check 3 anchor-resolution: ambiguous heading match (duplicate spec heading)", () => {
|
|
197
355
|
const dupSpec = SPEC_TEXT.replace('## Testing', '## Design\n\nduplicate section body.\n\n## Testing');
|
|
198
356
|
const findings = checkPlan(VALID_PLAN, dupSpec, alwaysTruePort());
|
|
@@ -69,6 +69,7 @@ interface CoverageRow {
|
|
|
69
69
|
isWaived: boolean;
|
|
70
70
|
ownerTasks: number[];
|
|
71
71
|
ownerMalformed: boolean;
|
|
72
|
+
isVerification: boolean;
|
|
72
73
|
anchor: Anchor | undefined;
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -270,6 +271,7 @@ function parsePlan(planText: string): ParsedPlan {
|
|
|
270
271
|
let isWaived = false;
|
|
271
272
|
let ownerTasks: number[] = [];
|
|
272
273
|
let ownerMalformed = false;
|
|
274
|
+
const isVerification = ownerCell === "Verification";
|
|
273
275
|
if (waivedMatch) {
|
|
274
276
|
if (waivedMatch[1].trim().length === 0) {
|
|
275
277
|
ownerMalformed = true;
|
|
@@ -278,7 +280,7 @@ function parsePlan(planText: string): ParsedPlan {
|
|
|
278
280
|
}
|
|
279
281
|
} else if (isTaskList) {
|
|
280
282
|
ownerTasks = [...ownerCell.matchAll(/Task (\d+)/g)].map((mm) => Number(mm[1]));
|
|
281
|
-
} else {
|
|
283
|
+
} else if (!isVerification) {
|
|
282
284
|
ownerMalformed = true;
|
|
283
285
|
}
|
|
284
286
|
let anchor: Anchor | undefined;
|
|
@@ -296,6 +298,7 @@ function parsePlan(planText: string): ParsedPlan {
|
|
|
296
298
|
isWaived,
|
|
297
299
|
ownerTasks,
|
|
298
300
|
ownerMalformed,
|
|
301
|
+
isVerification,
|
|
299
302
|
anchor,
|
|
300
303
|
});
|
|
301
304
|
p++;
|
|
@@ -380,7 +383,7 @@ function computeRequiredLiteralsPerTask(parsed: ParsedPlan, specLines: string[])
|
|
|
380
383
|
return map;
|
|
381
384
|
}
|
|
382
385
|
|
|
383
|
-
function checkTableClosure(parsed: ParsedPlan): PlanCheckFinding[] {
|
|
386
|
+
function checkTableClosure(parsed: ParsedPlan, specLines: string[]): PlanCheckFinding[] {
|
|
384
387
|
const findings: PlanCheckFinding[] = [];
|
|
385
388
|
if (!parsed.coverageTableFound) {
|
|
386
389
|
findings.push({ check: "table-closure", line: 0, text: "", reason: "no '## Spec coverage' table found" });
|
|
@@ -396,34 +399,54 @@ function checkTableClosure(parsed: ParsedPlan): PlanCheckFinding[] {
|
|
|
396
399
|
check: "table-closure",
|
|
397
400
|
line: row.line,
|
|
398
401
|
text: row.text,
|
|
399
|
-
reason: "owner cell is not a 'Task <n>' list or 'waived: <reason>'",
|
|
402
|
+
reason: "owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'",
|
|
403
|
+
});
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (row.isVerification && row.isMechanical) {
|
|
407
|
+
findings.push({
|
|
408
|
+
check: "table-closure",
|
|
409
|
+
line: row.line,
|
|
410
|
+
text: row.text,
|
|
411
|
+
reason: "mechanical row owner must be a Task <n>",
|
|
400
412
|
});
|
|
401
413
|
continue;
|
|
402
414
|
}
|
|
403
415
|
if (row.isWaived) continue;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
416
|
+
const anchorUnparseable = !row.isMechanical && !row.anchor;
|
|
417
|
+
if (anchorUnparseable) {
|
|
418
|
+
findings.push({
|
|
419
|
+
check: "table-closure",
|
|
420
|
+
line: row.line,
|
|
421
|
+
text: row.text,
|
|
422
|
+
reason: 'requirement row anchor is not a parseable § "heading" L<n>-L<n> anchor',
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (row.isVerification) {
|
|
426
|
+
if (!anchorUnparseable && requiredLiteralsForRow(row, specLines).length === 0) {
|
|
409
427
|
findings.push({
|
|
410
428
|
check: "table-closure",
|
|
411
429
|
line: row.line,
|
|
412
430
|
text: row.text,
|
|
413
|
-
reason:
|
|
431
|
+
reason: "Verification row has no backtick literal to check against the header",
|
|
414
432
|
});
|
|
415
|
-
continue;
|
|
416
433
|
}
|
|
417
|
-
|
|
418
|
-
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
for (const n of row.ownerTasks) {
|
|
437
|
+
coveredTasks.add(n);
|
|
438
|
+
if (row.isMechanical) mechanicalCoveredTasks.add(n);
|
|
439
|
+
const task = taskByNumber.get(n);
|
|
440
|
+
if (!task) {
|
|
419
441
|
findings.push({
|
|
420
442
|
check: "table-closure",
|
|
421
443
|
line: row.line,
|
|
422
444
|
text: row.text,
|
|
423
|
-
reason:
|
|
445
|
+
reason: `row references Task ${n} but no such task exists`,
|
|
424
446
|
});
|
|
425
447
|
continue;
|
|
426
448
|
}
|
|
449
|
+
if (row.isMechanical || anchorUnparseable) continue;
|
|
427
450
|
const contained = task.anchors.some(
|
|
428
451
|
(a) => a.heading === row.anchor!.heading && a.start === row.anchor!.start && a.end === row.anchor!.end,
|
|
429
452
|
);
|
|
@@ -465,10 +488,24 @@ function checkQuoteIntegrity(parsed: ParsedPlan, specLines: string[]): PlanCheck
|
|
|
465
488
|
const findings: PlanCheckFinding[] = [];
|
|
466
489
|
if (!parsed.coverageTableFound) return findings;
|
|
467
490
|
const taskByNumber = new Map(parsed.tasks.map((t) => [t.number, t]));
|
|
491
|
+
const headerText = (parsed.header.verificationText ?? "").replaceAll("`", "");
|
|
468
492
|
for (const row of parsed.coverageRows) {
|
|
469
493
|
if (row.ownerMalformed || row.isWaived || row.isMechanical) continue;
|
|
470
494
|
const literals = requiredLiteralsForRow(row, specLines);
|
|
471
495
|
if (literals.length === 0) continue;
|
|
496
|
+
if (row.isVerification) {
|
|
497
|
+
for (const lit of literals) {
|
|
498
|
+
if (!headerText.includes(lit)) {
|
|
499
|
+
findings.push({
|
|
500
|
+
check: "quote-integrity",
|
|
501
|
+
line: row.line,
|
|
502
|
+
text: row.text,
|
|
503
|
+
reason: `verification header does not contain the required verbatim literal \`${lit}\``,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
472
509
|
for (const n of row.ownerTasks) {
|
|
473
510
|
const task = taskByNumber.get(n);
|
|
474
511
|
if (!task) continue;
|
|
@@ -805,7 +842,7 @@ export function checkPlan(planText: string, specText: string, fs: FsPort): PlanC
|
|
|
805
842
|
}
|
|
806
843
|
|
|
807
844
|
const specLines = specText.split("\n");
|
|
808
|
-
findings.push(...checkTableClosure(parsed));
|
|
845
|
+
findings.push(...checkTableClosure(parsed, specLines));
|
|
809
846
|
findings.push(...checkQuoteIntegrity(parsed, specLines));
|
|
810
847
|
findings.push(...checkAnchorResolution(parsed, specLines));
|
|
811
848
|
findings.push(...checkPathsExist(parsed, fs));
|
package/package.json
CHANGED
|
@@ -264,11 +264,14 @@ Every plan ends with a `## Spec coverage` section — authored last, placed afte
|
|
|
264
264
|
|---|---|---|
|
|
265
265
|
| § "Design" L34-L37 | anchor line in task template | Task 2 |
|
|
266
266
|
| § "Edge cases" L120 | stale anchor = blocking SR finding | Task 4, Task 5 |
|
|
267
|
+
| § "Testing" L84 | checker fixtures: `node --test extensions/lib/plan-check.test.ts` | Task 3 |
|
|
268
|
+
| § "Acceptance" L88 | full suite passes: `npm test` | Verification |
|
|
267
269
|
| § "Out of scope" L131 | fix-round anchoring | waived: out of scope per spec |
|
|
268
270
|
| - | mechanical: release commit | Task 7 |
|
|
269
271
|
```
|
|
270
272
|
|
|
271
|
-
- **Requirement rows:** anchor + short requirement + owner = task-ID list, or `waived: <reason>` **only when the spec itself marks the item out of scope**. A waiver on an in-scope normative requirement is a Self-Review failure — there is no human plan-review gate to catch it downstream.
|
|
273
|
+
- **Requirement rows:** anchor + short requirement + owner = task-ID list, or `Verification`, or `waived: <reason>` **only when the spec itself marks the item out of scope**. A waiver on an in-scope normative requirement is a Self-Review failure — there is no human plan-review gate to catch it downstream.
|
|
274
|
+
- **`Verification` owner:** use for a requirement the header `**Verification:**` command proves. Write the exact string `Verification`, alone. Quote only literals contained in that header. Anchor the single requirement line. Keep scoped commands task-owned.
|
|
272
275
|
- **Mechanical-task rows:** anchor `-`, requirement `mechanical: <short>`, owner = the task ID. One such row per anchor-less task.
|
|
273
276
|
- The table is plan-authoring-time only — never passed to implementer or reviewer dispatches.
|
|
274
277
|
|
|
@@ -293,12 +296,13 @@ If a decision is genuinely open, put it in an explicit **Open Questions** sectio
|
|
|
293
296
|
After drafting the plan and before announcing it complete, run the deterministic checker, then the judgment checks yourself — not a subagent dispatch.
|
|
294
297
|
|
|
295
298
|
- **Deterministic checker.** Run `plan_check({ planPath })` on the saved plan. Assess and fix every finding yourself (no human involvement), then re-run until it passes — a pass writes the execution stamp that implement-start verifies mechanically. If the same finding survives 3 fix rounds, convert it to an explicit Open Question and stop (the pre-existing Open-Questions halt, resolved by the human in-session — not a new gate). The checker covers table closure, quote integrity, anchor resolution, path existence, placeholder scan, wave file-disjointness, solo-line presence, and header-only entrypoint.
|
|
296
|
-
- **Code-vs-anchor sanity.** For each
|
|
299
|
+
- **Code-vs-anchor sanity.** For each task-owned requirement row, re-read the anchored spec lines and confirm the owner tasks' bodies do what they say - mechanism present, not just the quoted literal. For each `Verification` row, confirm the header command exercises the anchored requirement. Fix the task, don't annotate.
|
|
297
300
|
- **Type / API consistency.** Function signatures and field names that appear in multiple tasks must match exactly. The plan is its own contract — internal contradictions surface as bugs during execution.
|
|
298
301
|
- **Scoped-test coverage.** Every code-touching wave declares at least one scoped test command; only doc-only waves may have none.
|
|
299
302
|
- **Runtime-resource disjointness.** For every multi-task wave, confirm no two tasks contend on a shared mutable runtime resource (DB/schema, port, fixture, external service, shared temp path) — `Files:` overlap is checked mechanically, resource contention is not. Contention = mis-grouped wave; split or re-order before handoff.
|
|
300
303
|
- **Solo-reason validity.** Every single-task wave's `Solo:` line (presence is checked mechanically) must name its specific blocker — the blocking task/wave, the contended resource, or `lone remaining task`. Category-only justifications are under-justified; merge or justify before handoff.
|
|
301
304
|
- **Waiver authorization.** Every `waived: <reason>` owner in `## Spec coverage` is authorized by the spec itself marking the item out of scope. A waiver on an in-scope normative requirement is a Self-Review failure — there is no human plan-review gate to catch it downstream.
|
|
305
|
+
- **Verification-ownership authorization.** `Verification` on a requirement no header command exercises is a Self-Review failure.
|
|
302
306
|
- **Documentation-impact mapping.** Each Documentation impact entry maps to a plan task (or explicit "none").
|
|
303
307
|
|
|
304
308
|
Fix what this review finds before handoff.
|