pi-gauntlet 7.41.3 → 7.43.0

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.
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { checkPlan, sha256, type FsPort, type PlanCheckFinding } from "./plan-check.ts";
3
+ import { checkPlan, renderManifest, sha256, type FsPort, type PlanCheckFinding } from "./plan-check.ts";
4
4
 
5
5
  const ledgerSpec = `# Spec
6
6
  ## Acceptance criteria
@@ -17,7 +17,7 @@ const ledger = (rows = ["1 | open | 0 |", "2 | cut | 0 | deferred: #70", "3 | do
17
17
 
18
18
  test("ledger admission bypasses task checks and permits empty open evidence", () => {
19
19
  for (const input of [ledger(), ledger().replace("iterations: 0\n", "iterations: 0\ncap: 97\n")]) {
20
- assert.deepEqual(checkPlan(input, ledgerSpec, { exists: () => false, glob: () => [] }), []);
20
+ assert.deepEqual(checkPlan(input, ledgerSpec, { exists: () => false, glob: () => [] }).findings, []);
21
21
  }
22
22
  });
23
23
  for (const [name, rows] of Object.entries({
@@ -32,38 +32,38 @@ for (const [name, rows] of Object.entries({
32
32
  attempts: ["1 | open | -1 |", "2 | cut | 0 | deferred: #70", "3 | open | 0 |"],
33
33
  fractional: ["1 | open | 1.5 |", "2 | cut | 0 | deferred: #70", "3 | open | 0 |"],
34
34
  })) test(`ledger rejects ${name}`, () => {
35
- assert.ok(checkPlan(ledger(rows), ledgerSpec, { exists: () => false, glob: () => [] }).length);
35
+ assert.ok(checkPlan(ledger(rows), ledgerSpec, { exists: () => false, glob: () => [] }).findings.length);
36
36
  });
37
37
  test("ledger discriminant is the exact first line", () => {
38
- for (const prefix of ["\n", " "]) assert.ok(checkPlan(prefix + ledger(), ledgerSpec, { exists: () => false, glob: () => [] }).some(f => f.check === "input"));
38
+ for (const prefix of ["\n", " "]) assert.ok(checkPlan(prefix + ledger(), ledgerSpec, { exists: () => false, glob: () => [] }).findings.some(f => f.check === "input"));
39
39
  });
40
40
  for (const disposition of ["in-scope", "venue: browser", "deviates: alternate", "deferred: #70", "elsewhere: #71"]) {
41
41
  test(`ledger disposition ${disposition}`, () => {
42
42
  const cut = /^(deviates|deferred|elsewhere):/.test(disposition);
43
43
  const spec = `## Acceptance criteria\n- [ ] Works\n ${disposition}\n`;
44
- assert.deepEqual(checkPlan(ledger([`1 | ${cut ? "cut" : "open"} | 0 | ${cut ? disposition : ""}`]), spec, { exists: () => false, glob: () => [] }), []);
45
- assert.ok(checkPlan(ledger([`1 | ${cut ? "done" : "cut"} | 0 | check passed`]), spec, { exists: () => false, glob: () => [] }).length);
44
+ assert.deepEqual(checkPlan(ledger([`1 | ${cut ? "cut" : "open"} | 0 | ${cut ? disposition : ""}`]), spec, { exists: () => false, glob: () => [] }).findings, []);
45
+ assert.ok(checkPlan(ledger([`1 | ${cut ? "done" : "cut"} | 0 | check passed`]), spec, { exists: () => false, glob: () => [] }).findings.length);
46
46
  });
47
47
  }
48
48
  for (const status of ["done", "failed", "cut"]) test(`ledger empty evidence rejects ${status}`, () => {
49
49
  const spec = `## Acceptance criteria\n- [ ] Works\n${status === "cut" ? " deferred: #70\n" : ""}`;
50
- assert.ok(checkPlan(ledger([`1 | ${status} | 0 |`]), spec, { exists: () => false, glob: () => [] }).some(f => /evidence is required/.test(f.reason)));
50
+ assert.ok(checkPlan(ledger([`1 | ${status} | 0 |`]), spec, { exists: () => false, glob: () => [] }).findings.some(f => /evidence is required/.test(f.reason)));
51
51
  });
52
52
  for (const evidence of ["<check>", "TODO", "TBD", "etc.", "probably"]) {
53
53
  test(`ledger rejects non-open evidence ${JSON.stringify(evidence)}`, () => {
54
- assert.ok(checkPlan(ledger([`1 | failed | 1 | ${evidence}`, "2 | cut | 0 | deferred: #70", "3 | open | 0 |"]), ledgerSpec, { exists: () => false, glob: () => [] }).length);
54
+ assert.ok(checkPlan(ledger([`1 | failed | 1 | ${evidence}`, "2 | cut | 0 | deferred: #70", "3 | open | 0 |"]), ledgerSpec, { exists: () => false, glob: () => [] }).findings.length);
55
55
  });
56
56
  }
57
57
 
58
58
  test("ledger keeps nested checkboxes and the parent disposition in one AC row", () => {
59
59
  const spec = "## Acceptance criteria\n- [ ] Parent\n - [ ] Nested detail\n deferred: #70\n";
60
- assert.deepEqual(checkPlan(ledger(["1 | cut | 0 | deferred: #70"]), spec, { exists: () => false, glob: () => [] }), []);
61
- assert.ok(checkPlan(ledger(["1 | open | 0 |"]), spec, { exists: () => false, glob: () => [] }).some(f => /status does not match/.test(f.reason)));
60
+ assert.deepEqual(checkPlan(ledger(["1 | cut | 0 | deferred: #70"]), spec, { exists: () => false, glob: () => [] }).findings, []);
61
+ assert.ok(checkPlan(ledger(["1 | open | 0 |"]), spec, { exists: () => false, glob: () => [] }).findings.some(f => /status does not match/.test(f.reason)));
62
62
  });
63
63
 
64
64
  for (const evidence of ["xxx", "[fill in]", "something like", "check; expected a\\|b; passed"]) {
65
65
  test(`ledger permits evidence outside the placeholder vocabulary ${JSON.stringify(evidence)}`, () => {
66
- assert.deepEqual(checkPlan(ledger([`1 | done | 1 | ${evidence}`, "2 | cut | 0 | deferred: #70", "3 | open | 0 |"]), ledgerSpec, { exists: () => false, glob: () => [] }), []);
66
+ assert.deepEqual(checkPlan(ledger([`1 | done | 1 | ${evidence}`, "2 | cut | 0 | deferred: #70", "3 | open | 0 |"]), ledgerSpec, { exists: () => false, glob: () => [] }).findings, []);
67
67
  });
68
68
  }
69
69
 
@@ -173,13 +173,13 @@ function findingsFor(findings: PlanCheckFinding[], check: string): PlanCheckFind
173
173
  }
174
174
 
175
175
  test("valid fixture: checkPlan returns no findings (also covers the placeholder exemption case)", () => {
176
- const findings = checkPlan(VALID_PLAN, SPEC_TEXT, alwaysTruePort());
176
+ const findings = checkPlan(VALID_PLAN, SPEC_TEXT, alwaysTruePort()).findings;
177
177
  assert.deepEqual(findings, []);
178
178
  });
179
179
 
180
180
  test("no gauntlet grammar at all: emits both an absent-wave-headers and an absent-task-headers finding", () => {
181
181
  const plan = "# Just a plan\n\nSome prose with no wave or task headers at all.\n";
182
- const findings = checkPlan(plan, SPEC_TEXT, alwaysTruePort());
182
+ const findings = checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings;
183
183
  const waveFindings = findings.filter((f) => f.reason.includes("wave headers"));
184
184
  const taskFindings = findings.filter((f) => f.reason.includes("task headers"));
185
185
  assert.equal(waveFindings.length, 1, "expected exactly one absent-wave-headers finding");
@@ -188,7 +188,7 @@ test("no gauntlet grammar at all: emits both an absent-wave-headers and an absen
188
188
 
189
189
  test("check 1 table-closure: task not covered by any Spec coverage row", () => {
190
190
  const mutated = VALID_PLAN.replace('| § "Testing" L9-L9 | banned token literal handling | Task 3 |\n', "");
191
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
191
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
192
192
  const tc = findingsFor(findings, "table-closure");
193
193
  assert.ok(
194
194
  tc.some((f) => f.reason.includes("Task 3") && f.reason.includes("does not appear as an owner")),
@@ -206,7 +206,7 @@ test("check 1 table-closure: task not covered by any Spec coverage row", () => {
206
206
 
207
207
  test("check 1 fail-closed: missing '## Spec coverage' table entirely", () => {
208
208
  const mutated = VALID_PLAN.slice(0, VALID_PLAN.indexOf("## Spec coverage"));
209
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
209
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
210
210
  const tc = findingsFor(findings, "table-closure");
211
211
  assert.equal(tc.length, 1);
212
212
  assert.match(tc[0].reason, /no '## Spec coverage' table found/);
@@ -218,7 +218,7 @@ test("check 2 quote-integrity: required verbatim literal missing from owner task
218
218
  "- via: `helperFn()`\n\nThis task implements helperFn() for parsing.",
219
219
  "- via: `parserSeam()`\n\nThis task implements the helper for parsing.",
220
220
  );
221
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
221
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
222
222
  const qi = findingsFor(findings, "quote-integrity");
223
223
  assert.ok(
224
224
  qi.some((f) => f.reason.includes("helperFn()") && f.reason.includes("Task 1")),
@@ -235,7 +235,7 @@ test("check 1 owner-cell grammar: trailing junk after a Task <n> list is malform
235
235
  '| § "Design" L4-L6 | parser grammar basics | Task 1 |',
236
236
  '| § "Design" L4-L6 | parser grammar basics | Task 1 (see note) |',
237
237
  );
238
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
238
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
239
239
  const tc = findingsFor(findings, "table-closure");
240
240
  assert.ok(
241
241
  tc.some(
@@ -260,7 +260,7 @@ test("check 1 owner-cell grammar: empty waiver reason is malformed, not accepted
260
260
  '| § "Other" L12-L13 | out of scope thing | waived: out of scope per spec |',
261
261
  '| § "Other" L12-L13 | out of scope thing | waived: |',
262
262
  );
263
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
263
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
264
264
  const tc = findingsFor(findings, "table-closure");
265
265
  assert.ok(
266
266
  tc.some((f) => f.reason.includes("owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'")),
@@ -281,7 +281,7 @@ const WAIVED_ROW = '| § "Other" L12-L13 | out of scope thing | waived: out of s
281
281
  test("check 9 waiver-literal: waived row whose requirement names a code literal fails", () => {
282
282
  const row = '| § "Other" L12-L13 | `protectedPaths: []` stays empty | waived: out of scope |';
283
283
  const mutated = VALID_PLAN.replace(WAIVED_ROW, row);
284
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
284
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
285
285
  const wl = findingsFor(findings, "waiver-literal");
286
286
  assert.equal(wl.length, 1, `expected exactly one waiver-literal finding, got: ${JSON.stringify(wl)}`);
287
287
  assert.equal(wl[0].line, lineOf(mutated, row));
@@ -294,7 +294,7 @@ test("check 9 waiver-literal: same requirement owned by a task passes the whole
294
294
  '**Spec:** doc/specs/fixture-spec.md § "Design" L4-L6',
295
295
  '**Spec:** doc/specs/fixture-spec.md § "Design" L4-L6, § "Other" L12-L13',
296
296
  );
297
- assert.deepEqual(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), []);
297
+ assert.deepEqual(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings, []);
298
298
  });
299
299
 
300
300
  test("check 9 waiver-literal: waived prose requirement without backticks passes the whole plan", () => {
@@ -302,7 +302,7 @@ test("check 9 waiver-literal: waived prose requirement without backticks passes
302
302
  WAIVED_ROW,
303
303
  '| § "Other" L12-L13 | do not add support for protectedPaths | waived: out of scope per spec |',
304
304
  );
305
- assert.deepEqual(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), []);
305
+ assert.deepEqual(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings, []);
306
306
  });
307
307
 
308
308
  const VERIFICATION_ROW = '| § "Acceptance" L16 | full suite passes | Verification |';
@@ -317,7 +317,7 @@ function withRow(plan: string, row: string): string {
317
317
  for (const owner of ["verification", "Verify", "VERIFICATION", "Task 1, Verification"]) {
318
318
  test(`Verification owner: '${owner}' is a malformed owner -> table-closure`, () => {
319
319
  const row = `| § "Acceptance" L16 | full suite passes | ${owner} |`;
320
- const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
320
+ const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort()).findings;
321
321
  const tc = findingsFor(findings, "table-closure");
322
322
  assert.ok(
323
323
  tc.some(
@@ -332,7 +332,7 @@ for (const owner of ["verification", "Verify", "VERIFICATION", "Task 1, Verifica
332
332
 
333
333
  test("Verification owner: unparseable anchor ('-') -> table-closure parseability reason", () => {
334
334
  const row = "| - | full suite passes | Verification |";
335
- const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
335
+ const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort()).findings;
336
336
  const tc = findingsFor(findings, "table-closure");
337
337
  assert.ok(
338
338
  tc.some(
@@ -345,7 +345,7 @@ test("Verification owner: unparseable anchor ('-') -> table-closure parseability
345
345
 
346
346
  test("Verification owner: anchored lines without a backtick literal -> table-closure", () => {
347
347
  const row = '| § "Other" L13 | stuff | Verification |';
348
- const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
348
+ const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort()).findings;
349
349
  const tc = findingsFor(findings, "table-closure");
350
350
  assert.ok(
351
351
  tc.some(
@@ -357,7 +357,7 @@ test("Verification owner: anchored lines without a backtick literal -> table-clo
357
357
 
358
358
  test("Verification owner: mechanical row -> table-closure", () => {
359
359
  const row = "| - | mechanical: run the suite | Verification |";
360
- const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
360
+ const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort()).findings;
361
361
  const tc = findingsFor(findings, "table-closure");
362
362
  assert.ok(
363
363
  tc.some((f) => f.reason === "mechanical row owner must be a Task <n>" && f.text === row),
@@ -367,7 +367,7 @@ test("Verification owner: mechanical row -> table-closure", () => {
367
367
 
368
368
  test("table-closure: multi-owner row with unparseable anchor yields one parseability finding", () => {
369
369
  const row = "| L999 | naming details | Task 1, Task 2 |";
370
- const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort());
370
+ const findings = checkPlan(withRow(VALID_PLAN, row), SPEC_TEXT, alwaysTruePort()).findings;
371
371
  const parseability = findingsFor(findings, "table-closure").filter(
372
372
  (f) => f.text === row && f.reason.includes("not a parseable"),
373
373
  );
@@ -379,7 +379,7 @@ test("Verification owner: does not satisfy a task's coverage requirement", () =>
379
379
  '| § "Testing" L9-L9 | banned token literal handling | Task 3 |\n',
380
380
  "",
381
381
  );
382
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
382
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
383
383
  assert.ok(
384
384
  findingsFor(findings, "table-closure").some((f) =>
385
385
  f.reason.includes("Task 3 does not appear as an owner"),
@@ -389,7 +389,7 @@ test("Verification owner: does not satisfy a task's coverage requirement", () =>
389
389
 
390
390
  test("Verification quote-integrity: literal missing from header -> quote-integrity on the row", () => {
391
391
  const mutated = VALID_PLAN.replace("**Verification:** npm run fixture-verify", "**Verification:** npm run other");
392
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
392
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
393
393
  const qi = findingsFor(findings, "quote-integrity");
394
394
  const row = '| § "Acceptance" L16 | full suite passes | Verification |';
395
395
  assert.ok(
@@ -408,7 +408,7 @@ test("Verification quote-integrity: multi-command header contains the literal",
408
408
  "**Verification:** npm run fixture-verify",
409
409
  "**Verification:** `npm run fixture-verify && npm run lint`",
410
410
  );
411
- assert.deepEqual(findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), "quote-integrity"), []);
411
+ assert.deepEqual(findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings, "quote-integrity"), []);
412
412
  });
413
413
 
414
414
  test("Verification quote-integrity: two-span header contains both literals", () => {
@@ -420,12 +420,12 @@ test("Verification quote-integrity: two-span header contains both literals", ()
420
420
  "**Verification:** npm run fixture-verify",
421
421
  "**Verification:** `npm run fixture-verify`, `npm run lint`",
422
422
  );
423
- assert.deepEqual(findingsFor(checkPlan(mutated, spec, alwaysTruePort()), "quote-integrity"), []);
423
+ assert.deepEqual(findingsFor(checkPlan(mutated, spec, alwaysTruePort()).findings, "quote-integrity"), []);
424
424
  });
425
425
 
426
426
  test("Verification quote-integrity: missing header -> quote-integrity per literal plus header-entrypoint", () => {
427
427
  const mutated = VALID_PLAN.replace("**Verification:** npm run fixture-verify\n", "");
428
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
428
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
429
429
  assert.equal(
430
430
  findingsFor(findings, "quote-integrity").filter((f) => f.reason.includes("`npm run fixture-verify`")).length,
431
431
  1,
@@ -438,7 +438,7 @@ test("Verification quote-integrity: task body containing the full header string
438
438
  "This task handles naming details.",
439
439
  "This task handles naming details. Run npm run fixture-verify here.",
440
440
  );
441
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
441
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
442
442
  assert.ok(findingsFor(findings, "header-entrypoint").some((f) => f.text.includes("Run npm run fixture-verify here")));
443
443
  assert.deepEqual(findingsFor(findings, "quote-integrity"), []);
444
444
  });
@@ -448,13 +448,13 @@ test("Verification quote-integrity: task body containing only a sub-command of a
448
448
  "**Verification:** npm run fixture-verify",
449
449
  "**Verification:** `npm run fixture-verify && npm run lint`",
450
450
  ).replace("This task handles naming details.", "This task handles naming details. Run npm run fixture-verify here.");
451
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
451
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
452
452
  assert.deepEqual(findingsFor(findings, "header-entrypoint"), []);
453
453
  assert.deepEqual(findingsFor(findings, "quote-integrity"), []);
454
454
  });
455
455
 
456
- const HE = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), "header-entrypoint");
457
- const WD = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), "wave-file-disjointness");
456
+ const HE = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, "header-entrypoint");
457
+ const WD = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, "wave-file-disjointness");
458
458
 
459
459
  test("header-entrypoint: Run: with backticked payload npm test under header npm test && npm run lint fails (regression for the hole)", () => {
460
460
  const mutated = VALID_PLAN.replace("**Verification:** npm run fixture-verify", "**Verification:** npm test && npm run lint")
@@ -487,21 +487,21 @@ test("header-entrypoint: Tests: bullets are judged by tests-block, not here", ()
487
487
  "**Tests:**\n- `npm run fixture-verify`",
488
488
  );
489
489
  assert.deepEqual(HE(mutated), []);
490
- assert.ok(findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), "tests-block").some((f) => f.reason.includes("full-suite command")));
490
+ assert.ok(findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings, "tests-block").some((f) => f.reason.includes("full-suite command")));
491
491
  });
492
492
 
493
493
  const withHappyPath = (plan: string, line: string) =>
494
494
  plan.replace("**Verification:** npm run fixture-verify", `**Verification:** npm run fixture-verify\n\n${line}`);
495
- const HP = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), "header-happy-path");
495
+ const HP = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, "header-happy-path");
496
496
 
497
497
  test("header-happy-path: line with label, backticked command, and (~5m) duration is accepted", () => {
498
498
  const plan = withHappyPath(VALID_PLAN, "**Happy path:** stack - `script/e2e-stack` (~5m)");
499
- assert.deepEqual(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), []);
499
+ assert.deepEqual(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, []);
500
500
  });
501
501
 
502
502
  test("header-happy-path: line without a duration is accepted", () => {
503
503
  const plan = withHappyPath(VALID_PLAN, "**Happy path:** stack - `script/e2e-stack`");
504
- assert.deepEqual(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), []);
504
+ assert.deepEqual(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, []);
505
505
  });
506
506
 
507
507
  test("header-happy-path: line without label, backticks, or a valid duration is malformed", () => {
@@ -528,7 +528,7 @@ test("tests-block: Tests: bullet equal to the happy-path command is a full-suite
528
528
  "**Tests:**\n- `node --test extensions/lib/fixture-task2.test.ts`",
529
529
  "**Tests:**\n- `script/e2e-stack`",
530
530
  );
531
- const tb = findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), "tests-block");
531
+ const tb = findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, "tests-block");
532
532
  assert.ok(tb.some((f) => f.reason.includes("full-suite command")), JSON.stringify(tb));
533
533
  });
534
534
 
@@ -543,7 +543,7 @@ test("header-entrypoint: Run: payload and free text repeating the happy-path com
543
543
  test("table-closure: owner cell 'Happy path' is malformed under the existing reason", () => {
544
544
  const row = '| § "Acceptance" L16 | full suite passes | Happy path |';
545
545
  const plan = withHappyPath(withRow(VALID_PLAN, row), "**Happy path:** stack - `script/e2e-stack` (~5m)");
546
- const tc = findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), "table-closure");
546
+ const tc = findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, "table-closure");
547
547
  assert.ok(tc.some((f) => f.reason === "owner cell is not a 'Task <n>' list, 'Verification', or 'waived: <reason>'" && f.text === row));
548
548
  });
549
549
 
@@ -565,7 +565,7 @@ test("Verification quote-integrity: task-owned literal check unchanged", () => {
565
565
  "- via: `helperFn()`\n\nThis task implements helperFn() for parsing.",
566
566
  "- via: `parserSeam()`\n\nThis task implements the helper.",
567
567
  );
568
- const qi = findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()), "quote-integrity");
568
+ const qi = findingsFor(checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings, "quote-integrity");
569
569
  assert.ok(qi.some((f) => f.reason.includes("Task 1 body does not contain the required verbatim literal `helperFn()`")));
570
570
  });
571
571
 
@@ -608,7 +608,7 @@ Run node --test extensions/lib/plan-check.test.ts and confirm green.
608
608
  `;
609
609
 
610
610
  test("quote-integrity: header entrypoint literal on an anchored line is satisfied by the header, not the task body", () => {
611
- const findings = checkPlan(ENTRYPOINT_PLAN, ENTRYPOINT_SPEC, alwaysTruePort());
611
+ const findings = checkPlan(ENTRYPOINT_PLAN, ENTRYPOINT_SPEC, alwaysTruePort()).findings;
612
612
  assert.deepEqual(findingsFor(findings, "quote-integrity"), []);
613
613
  assert.deepEqual(findingsFor(findings, "header-entrypoint"), []);
614
614
  });
@@ -618,20 +618,20 @@ test("quote-integrity: scoped command on the same anchored line is still require
618
618
  "- Test: extensions/lib/plan-check.test.ts\n\n**Tests:**\n- `node --test extensions/lib/plan-check.test.ts`\n- baseline: abc1234 pass\n\nRun node --test extensions/lib/plan-check.test.ts and confirm green.",
619
619
  "- Test: extensions/lib/other.test.ts\n\n**Tests:**\n- `node --test extensions/lib/other.test.ts`\n\nRun the scoped test and confirm green.",
620
620
  );
621
- const qi = findingsFor(checkPlan(mutated, ENTRYPOINT_SPEC, alwaysTruePort()), "quote-integrity");
621
+ const qi = findingsFor(checkPlan(mutated, ENTRYPOINT_SPEC, alwaysTruePort()).findings, "quote-integrity");
622
622
  assert.equal(qi.length, 1);
623
623
  assert.ok(qi[0].reason.includes("Task 1 body does not contain the required verbatim literal `node --test extensions/lib/plan-check.test.ts`"));
624
624
  });
625
625
 
626
626
  test("placeholder-scan: header entrypoint is not a required literal for the task (parity with quote-integrity)", () => {
627
- const findings = checkPlan(ENTRYPOINT_PLAN, ENTRYPOINT_SPEC, alwaysTruePort());
627
+ const findings = checkPlan(ENTRYPOINT_PLAN, ENTRYPOINT_SPEC, alwaysTruePort()).findings;
628
628
  assert.deepEqual(findingsFor(findings, "placeholder-scan"), []);
629
629
  assert.deepEqual(findings, []);
630
630
  });
631
631
 
632
632
  test("check 3 anchor-resolution: ambiguous heading match (duplicate spec heading)", () => {
633
633
  const dupSpec = SPEC_TEXT.replace('## Testing', '## Design\n\nduplicate section body.\n\n## Testing');
634
- const findings = checkPlan(VALID_PLAN, dupSpec, alwaysTruePort());
634
+ const findings = checkPlan(VALID_PLAN, dupSpec, alwaysTruePort()).findings;
635
635
  const ar = findingsFor(findings, "anchor-resolution");
636
636
  assert.ok(
637
637
  ar.some((f) => f.reason.includes("ambiguous") && f.reason.includes('"Design"')),
@@ -652,7 +652,7 @@ test("check 3 fail-closed: unparseable **Spec:** anchor line", () => {
652
652
  '**Spec:** doc/specs/fixture-spec.md § "Design" L4-L6',
653
653
  "**Spec:** doc/specs/fixture-spec.md § Design without quotes or line numbers",
654
654
  );
655
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
655
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
656
656
  const ar = findingsFor(findings, "anchor-resolution");
657
657
  assert.equal(ar.length, 1);
658
658
  assert.match(ar[0].reason, /unparseable/);
@@ -664,7 +664,7 @@ test("check 4 paths-exist: Modify: literal path does not exist", () => {
664
664
  exists: (p) => p !== "extensions/lib/fixture-shared.ts",
665
665
  glob: () => [],
666
666
  };
667
- const findings = checkPlan(VALID_PLAN, SPEC_TEXT, port);
667
+ const findings = checkPlan(VALID_PLAN, SPEC_TEXT, port).findings;
668
668
  const pe = findingsFor(findings, "paths-exist");
669
669
  assert.ok(
670
670
  pe.some((f) => f.reason.includes("fixture-shared.ts") && f.reason.includes("does not exist")),
@@ -681,7 +681,7 @@ test("check 4 paths-exist: backtick-wrapped Modify: path is stripped before the
681
681
  "- Modify: extensions/lib/fixture-shared.ts",
682
682
  "- Modify: `extensions/lib/fixture-shared.ts`",
683
683
  );
684
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
684
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
685
685
  assert.deepEqual(findingsFor(findings, "paths-exist"), []);
686
686
  });
687
687
 
@@ -696,7 +696,7 @@ test("check 4 fail-closed: invalid glob (port throws)", () => {
696
696
  throw new Error("bad pattern");
697
697
  },
698
698
  };
699
- const findings = checkPlan(mutated, SPEC_TEXT, port);
699
+ const findings = checkPlan(mutated, SPEC_TEXT, port).findings;
700
700
  const pe = findingsFor(findings, "paths-exist");
701
701
  assert.ok(
702
702
  pe.some((f) => f.reason.includes("invalid") && f.reason.includes("bad pattern")),
@@ -717,7 +717,7 @@ test("check 4 fail-closed: task missing **Files:** block", () => {
717
717
  "**Files:**\n- Modify: extensions/lib/fixture-task3.ts\n- Test: extensions/lib/fixture-task3.test.ts\n\n**Tests:**\n- `node --test extensions/lib/fixture-task3.test.ts`\n- baseline: abc1234 pass\n\n",
718
718
  "",
719
719
  );
720
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
720
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
721
721
  const pe = findingsFor(findings, "paths-exist");
722
722
  assert.ok(
723
723
  pe.some((f) => f.reason.includes("missing a **Files:** block") && f.text.includes("Task 3")),
@@ -738,7 +738,7 @@ test("check 5 placeholder-scan: banned token flagged outside a required-literal
738
738
  "This task handles naming details.",
739
739
  "This task handles naming details. TODO clean this up.",
740
740
  );
741
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
741
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
742
742
  const ps = findingsFor(findings, "placeholder-scan");
743
743
  assert.ok(
744
744
  ps.some((f) => f.reason.includes('"TODO"') && f.line === lineOf(mutated, "TODO clean this up")),
@@ -751,7 +751,7 @@ test("check 6 wave-file-disjointness: two tasks in the same wave declare the sam
751
751
  "- Modify: extensions/lib/fixture-other.ts",
752
752
  "- Modify: extensions/lib/fixture-shared.ts",
753
753
  );
754
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
754
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
755
755
  const wfd = findingsFor(findings, "wave-file-disjointness");
756
756
  assert.ok(
757
757
  wfd.some((f) => f.reason.includes("Task 1") && f.reason.includes("Task 2") && f.reason.includes("fixture-shared.ts")),
@@ -768,9 +768,9 @@ test("every-wave-singleton plan without Solo: lines passes; legacy Solo: lines a
768
768
  "### Task 2: Implement naming",
769
769
  "## Wave 2 - Naming\n\n### Task 2: Implement naming",
770
770
  ).replace("## Wave 2 — Solo task", "## Wave 3 - Documentation");
771
- assert.deepEqual(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), []);
771
+ assert.deepEqual(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, []);
772
772
  const legacy = plan.replace("## Wave 2 - Naming", "## Wave 2 - Naming\n\nSolo: Task 2 depends on Wave 1");
773
- assert.deepEqual(checkPlan(legacy, SPEC_TEXT, alwaysTruePort()), []);
773
+ assert.deepEqual(checkPlan(legacy, SPEC_TEXT, alwaysTruePort()).findings, []);
774
774
  });
775
775
 
776
776
  test("check 8 header-entrypoint: verification entrypoint string reused outside the header", () => {
@@ -778,7 +778,7 @@ test("check 8 header-entrypoint: verification entrypoint string reused outside t
778
778
  "This task handles naming details.",
779
779
  "This task handles naming details. Run `npm run fixture-verify` here too.",
780
780
  );
781
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
781
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
782
782
  const he = findingsFor(findings, "header-entrypoint");
783
783
  assert.ok(
784
784
  he.some((f) => f.line === lineOf(mutated, "Run `npm run fixture-verify` here too")),
@@ -791,7 +791,7 @@ test("check 8 header-entrypoint: entrypoint text inside the '## Spec coverage' t
791
791
  "parser grammar basics",
792
792
  "parser grammar basics npm run fixture-verify",
793
793
  );
794
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
794
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
795
795
  assert.deepEqual(findingsFor(findings, "header-entrypoint"), []);
796
796
  });
797
797
 
@@ -834,7 +834,7 @@ The literal TODO is intentionally documented here per spec quote-integrity requi
834
834
  `;
835
835
 
836
836
  test("check 2/5 exemption: task body carrying the required verbatim TODO literal satisfies quote-integrity and exempts placeholder-scan", () => {
837
- const findings = checkPlan(EXEMPTION_PLAN, EXEMPTION_SPEC_TEXT, alwaysTruePort());
837
+ const findings = checkPlan(EXEMPTION_PLAN, EXEMPTION_SPEC_TEXT, alwaysTruePort()).findings;
838
838
  assert.deepEqual(findings, []);
839
839
  });
840
840
 
@@ -843,7 +843,7 @@ test("check 2/5 exemption negative: without the task body carrying the literal,
843
843
  "The literal TODO is intentionally documented here per spec quote-integrity requirement.",
844
844
  "This task implements the handling here.",
845
845
  );
846
- const findings = checkPlan(mutated, EXEMPTION_SPEC_TEXT, alwaysTruePort());
846
+ const findings = checkPlan(mutated, EXEMPTION_SPEC_TEXT, alwaysTruePort()).findings;
847
847
  const qi = findingsFor(findings, "quote-integrity");
848
848
  const ps = findingsFor(findings, "placeholder-scan");
849
849
  assert.ok(
@@ -874,14 +874,14 @@ test("aggregate: independent mutations across two checks are all reported togeth
874
874
  "- Modify: extensions/lib/fixture-other.ts",
875
875
  "- Modify: extensions/lib/fixture-shared.ts",
876
876
  );
877
- const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort());
877
+ const findings = checkPlan(mutated, SPEC_TEXT, alwaysTruePort()).findings;
878
878
  assert.ok(findingsFor(findings, "quote-integrity").length > 0, "expected a quote-integrity finding");
879
879
  assert.ok(findingsFor(findings, "wave-file-disjointness").length > 0, "expected a wave-file-disjointness finding");
880
880
  });
881
881
 
882
882
  test("grammarless plan: absent-structure findings do not short-circuit the catalog (placeholder-scan still runs)", () => {
883
883
  const grammarless = "# Not a plan\n\nJust some prose with a TODO in it.\n";
884
- const findings = checkPlan(grammarless, SPEC_TEXT, alwaysTruePort());
884
+ const findings = checkPlan(grammarless, SPEC_TEXT, alwaysTruePort()).findings;
885
885
  const input = findingsFor(findings, "input");
886
886
  assert.ok(
887
887
  input.some((f) => f.reason.includes("no wave headers found")),
@@ -899,12 +899,12 @@ test("grammarless plan: absent-structure findings do not short-circuit the catal
899
899
  });
900
900
 
901
901
  test("fail-closed: checkPlan never throws on an empty string", () => {
902
- assert.doesNotThrow(() => checkPlan("", SPEC_TEXT, alwaysTruePort()));
902
+ assert.doesNotThrow(() => checkPlan("", SPEC_TEXT, alwaysTruePort()).findings);
903
903
  });
904
904
 
905
905
  test("fail-closed: checkPlan never throws on a non-markdown garbage blob", () => {
906
906
  const garbage = "\u0000\u0001binary\ngarbage\tstuff \u{1F4A9} \"\"\"\n```unterminated fence";
907
- assert.doesNotThrow(() => checkPlan(garbage, SPEC_TEXT, alwaysTruePort()));
907
+ assert.doesNotThrow(() => checkPlan(garbage, SPEC_TEXT, alwaysTruePort()).findings);
908
908
  });
909
909
 
910
910
  test("sha256 is deterministic and content-sensitive", () => {
@@ -926,7 +926,7 @@ const T2_TESTS = "**Tests:**\n- `node --test extensions/lib/fixture-task2.test.t
926
926
  const T2_FILES_TEST = "- Test: extensions/lib/fixture-task2.test.ts\n";
927
927
 
928
928
  function tb(plan: string, spec = SPEC_TEXT, fs: FsPort = alwaysTruePort()): PlanCheckFinding[] {
929
- return findingsFor(checkPlan(plan, spec, fs), "tests-block");
929
+ return findingsFor(checkPlan(plan, spec, fs).findings, "tests-block");
930
930
  }
931
931
 
932
932
  test("tests-block: block missing when a task has no **Tests:**", () => {
@@ -1116,7 +1116,7 @@ test("tests-block: no path normalization - ./x and x differ, as in Files:", () =
1116
1116
  const BASELINE = "- baseline: abc1234 pass";
1117
1117
  const T1_COMMAND = "node --test extensions/lib/fixture-task1.test.ts";
1118
1118
  const T1_PATH = "extensions/lib/fixture-task1.test.ts";
1119
- const baselineFindings = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()), "baseline-line");
1119
+ const baselineFindings = (plan: string) => findingsFor(checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings, "baseline-line");
1120
1120
 
1121
1121
  for (const [name, evidence] of [
1122
1122
  ["pass", BASELINE],
@@ -1196,7 +1196,7 @@ for (const [name, body, count, malformed] of [
1196
1196
  ] as const) {
1197
1197
  test(`open-questions ${name}`, () => {
1198
1198
  const plan = VALID_PLAN + (body ? "\n## Open Questions\n\n" + body + "\n" : "");
1199
- const findings = checkPlan(plan, SPEC_TEXT, alwaysTruePort());
1199
+ const findings = checkPlan(plan, SPEC_TEXT, alwaysTruePort()).findings;
1200
1200
  const questions = findingsFor(findings, "open-questions");
1201
1201
  assert.equal(questions.length, count);
1202
1202
  if (malformed) assert.equal(questions[0].reason, "## Open Questions malformed: expected `- ` rows or `- none`");
@@ -1207,7 +1207,7 @@ for (const [name, body, count, malformed] of [
1207
1207
 
1208
1208
  test("placeholder-scan masks only valid question output, not authored fences or continuations", () => {
1209
1209
  for (const text of ["```\nTODO\n```", "## Open Questions\n ```\n TODO\n ```", "## Open Questions\n- TODO unresolved", "## Open Questions\n" + FAILURE_ROW + "\n ```\n TODO", "## Open Questions\n" + FAILURE_ROW + "\n ```\nTODO\n ```", "## Open Questions\n" + FAILURE_ROW + "\n ran: `TODO`", "## Open Questions\n" + FAILURE_ROW + "\n blocked: TODO"]) {
1210
- assert.ok(findingsFor(checkPlan(VALID_PLAN + "\n" + text, SPEC_TEXT, alwaysTruePort()), "placeholder-scan").length);
1210
+ assert.ok(findingsFor(checkPlan(VALID_PLAN + "\n" + text, SPEC_TEXT, alwaysTruePort()).findings, "placeholder-scan").length);
1211
1211
  }
1212
1212
  });
1213
1213
 
@@ -1232,11 +1232,11 @@ const coordinatedLedger = () => ledger(["1 | open | 0 |", "2 | open | 0 |", "3 |
1232
1232
  + "\n## Leg mapping\n| row | ac | leg |\n|---|---|---|\n| 1 | 1 | repo-a |\n| 2 | 1 | repo-b |\n| 3 | 2 | repo-a |\n| 4 | 3 | repo-a |\n";
1233
1233
  const emptyPort: FsPort = { exists: () => false, glob: () => [] };
1234
1234
  test("coordinated ledger expands shared ownership and defaults unlisted ACs home", () => {
1235
- assert.deepEqual(checkPlan(coordinatedLedger(), coordinatedSpec, emptyPort), []);
1235
+ assert.deepEqual(checkPlan(coordinatedLedger(), coordinatedSpec, emptyPort).findings, []);
1236
1236
  });
1237
1237
  for (const scope of ["", "scope: AC 1\nscope: AC 2", "scope: AC 1, AC 1", "scope: AC 0", "scope: AC -1", "scope: AC 1.5", "scope: AC 4", "scope: 1", "scope: AC 1-2", "scope: AC 1,", "scope: none, AC 1"]) {
1238
1238
  test(`coordinated scope rejects ${JSON.stringify(scope)}`, () => {
1239
- assert.ok(checkPlan(coordinatedLedger(), coordinatedSpec.replace("scope: AC 1, AC 2", scope), emptyPort).some(f => f.check === "leg-scope"));
1239
+ assert.ok(checkPlan(coordinatedLedger(), coordinatedSpec.replace("scope: AC 1, AC 2", scope), emptyPort).findings.some(f => f.check === "leg-scope"));
1240
1240
  });
1241
1241
  }
1242
1242
  for (const [name, change] of Object.entries({
@@ -1250,10 +1250,10 @@ for (const [name, change] of Object.entries({
1250
1250
  foreignTarget: (s: string) => s + "pending: 3 | repo-b | abc1234\n",
1251
1251
  duplicatePending: (s: string) => s + "pending: seed | repo-b | abc1234\npending: seed | repo-b | abc1234\n",
1252
1252
  })) test(`coordinated ledger rejects ${name}`, () => {
1253
- assert.ok(checkPlan(change(coordinatedLedger()), coordinatedSpec, emptyPort).length);
1253
+ assert.ok(checkPlan(change(coordinatedLedger()), coordinatedSpec, emptyPort).findings.length);
1254
1254
  });
1255
1255
  test("coordinated ledger admits partial seeds, recovery tuples and later cap grants", () => {
1256
- for (const target of ["2", "seed"]) assert.deepEqual(checkPlan(coordinatedLedger().replace("seeded repo-a: no", "seeded repo-a: yes").replace("cap: 20", "cap: 99") + `pending: ${target} | REPO-B | abc1234\n`, coordinatedSpec, emptyPort), []);
1256
+ for (const target of ["2", "seed"]) assert.deepEqual(checkPlan(coordinatedLedger().replace("seeded repo-a: no", "seeded repo-a: yes").replace("cap: 20", "cap: 99") + `pending: ${target} | REPO-B | abc1234\n`, coordinatedSpec, emptyPort).findings, []);
1257
1257
  });
1258
1258
 
1259
1259
  const legPlanSpec = SPEC_TEXT + "\n## Legs\n### Leg: repo-a\nscope: none\n### Leg: repo-b\nscope: none\n";
@@ -1262,28 +1262,28 @@ const legPlan = () => VALID_PLAN.replace("---", "Baseline repo-a: abc1234\nBasel
1262
1262
  .replace("- baseline: abc1234 pass\n\nThis task handles", "- baseline: def5678 pass\n\nThis task handles");
1263
1263
  test("plan accepts distinct leg baselines, case-insensitive membership and absent home Leg", () => {
1264
1264
  const probes: (string | undefined)[] = [];
1265
- assert.deepEqual(checkPlan(legPlan(), legPlanSpec, { exists: (_p, leg) => { probes.push(leg); return true; }, glob: () => [] }), []);
1265
+ assert.deepEqual(checkPlan(legPlan(), legPlanSpec, { exists: (_p, leg) => { probes.push(leg); return true; }, glob: () => [] }).findings, []);
1266
1266
  assert.ok(probes.includes("repo-b"));
1267
1267
  assert.ok(probes.includes("repo-a"));
1268
1268
  });
1269
1269
  test("wrong-leg baseline and mixed-leg waves fail", () => {
1270
- assert.ok(checkPlan(legPlan().replace("def5678 pass", "abc1234 pass"), legPlanSpec, alwaysTruePort()).some(f => f.check === "baseline-line"));
1271
- assert.ok(checkPlan(legPlan().replace("## Wave 3 - Companion\n\n", ""), legPlanSpec, alwaysTruePort()).some(f => f.check === "wave-leg"));
1270
+ assert.ok(checkPlan(legPlan().replace("def5678 pass", "abc1234 pass"), legPlanSpec, alwaysTruePort()).findings.some(f => f.check === "baseline-line"));
1271
+ assert.ok(checkPlan(legPlan().replace("## Wave 3 - Companion\n\n", ""), legPlanSpec, alwaysTruePort()).findings.some(f => f.check === "wave-leg"));
1272
1272
  });
1273
1273
  test("unknown legs fail before filesystem probing", () => {
1274
1274
  const probes: string[] = [];
1275
- assert.ok(checkPlan(legPlan().replace("Leg: REPO-B", "Leg: stranger"), legPlanSpec, { exists: (p) => { probes.push(p); return true; }, glob: () => [] }).some(f => f.check === "leg-unknown"));
1275
+ assert.ok(checkPlan(legPlan().replace("Leg: REPO-B", "Leg: stranger"), legPlanSpec, { exists: (p) => { probes.push(p); return true; }, glob: () => [] }).findings.some(f => f.check === "leg-unknown"));
1276
1276
  assert.deepEqual(probes, []);
1277
1277
  });
1278
1278
  test("cross-leg creates do not supply Test paths or skipped baseline evidence", () => {
1279
1279
  const plan = legPlan().replaceAll("extensions/lib/fixture-task2.test.ts", "extensions/lib/fixture-task1.ts").replace("def5678 pass", "def5678 skipped - extensions/lib/fixture-task1.ts created by this task");
1280
- const findings = checkPlan(plan, legPlanSpec, { exists: p => p !== "extensions/lib/fixture-task1.ts", glob: () => [] });
1280
+ const findings = checkPlan(plan, legPlanSpec, { exists: p => p !== "extensions/lib/fixture-task1.ts", glob: () => [] }).findings;
1281
1281
  assert.ok(findings.some(f => f.check === "tests-block" && /unknown/.test(f.reason)));
1282
1282
  assert.ok(findings.some(f => f.check === "baseline-line" && /not a Create/.test(f.reason)));
1283
1283
  });
1284
1284
  test("identical paths across legs do not overlap even in rejected mixed waves", () => {
1285
1285
  const plan = legPlan().replace("## Wave 3 - Companion\n\n", "").replaceAll("fixture-other.ts", "fixture-shared.ts");
1286
- assert.deepEqual(checkPlan(plan, legPlanSpec, alwaysTruePort()).filter(f => f.check === "wave-file-disjointness"), []);
1286
+ assert.deepEqual(checkPlan(plan, legPlanSpec, alwaysTruePort()).findings.filter(f => f.check === "wave-file-disjointness"), []);
1287
1287
  });
1288
1288
 
1289
1289
  for (const [name, change] of Object.entries({
@@ -1304,33 +1304,80 @@ for (const [name, change] of Object.entries({
1304
1304
  unknownMapping: (s: string) => s.replace("| 2 | 1 | repo-b |", "| 2 | 1 | stranger |"),
1305
1305
  disposition: (s: string) => s.replace("| 3 | cut | 0 | deferred: #70 |", "| 3 | open | 0 | |"),
1306
1306
  })) test(`coordinated metadata rejects ${name}`, () => {
1307
- assert.ok(checkPlan(change(coordinatedLedger()), coordinatedSpec, emptyPort).length);
1307
+ assert.ok(checkPlan(change(coordinatedLedger()), coordinatedSpec, emptyPort).findings.length);
1308
1308
  });
1309
1309
  test("scope textual order does not change original AC or member order, and design claims no ownership", () => {
1310
1310
  const spec = coordinatedSpec.replace("scope: AC 1, AC 2", "scope: AC 2, AC 1\ndesign: AC 3 touches lib/other.ts");
1311
- assert.deepEqual(checkPlan(coordinatedLedger().replace("| 4 | open | 0 | |", "| 4 | done | 1 | checked a\\|b |"), spec, emptyPort), []);
1311
+ assert.deepEqual(checkPlan(coordinatedLedger().replace("| 4 | open | 0 | |", "| 4 | done | 1 | checked a\\|b |"), spec, emptyPort).findings, []);
1312
1312
  });
1313
1313
  test("scope none defaults all original ACs home, preserving cut numbering and ignoring nested checks", () => {
1314
1314
  const spec = coordinatedSpec.replace("- [ ] First", "- [ ] First\n - [ ] Nested").replace("scope: AC 1, AC 2", "scope: none").replace("scope: AC 1\n", "scope: none\n");
1315
1315
  const input = coordinatedLedger().replace("| 2 | open | 0 | |\n", "").replace("| 3 | cut", "| 2 | cut").replace("| 4 | open", "| 3 | open")
1316
1316
  .replace("| 2 | 1 | repo-b |\n", "").replace("| 3 | 2 | repo-a |", "| 2 | 2 | repo-a |").replace("| 4 | 3 | repo-a |", "| 3 | 3 | repo-a |");
1317
- assert.deepEqual(checkPlan(input, spec, emptyPort), []);
1317
+ assert.deepEqual(checkPlan(input, spec, emptyPort).findings, []);
1318
1318
  });
1319
1319
  test("single-member ledger preserves legacy headers and four-column bytes", () => {
1320
- assert.deepEqual(checkPlan(ledger(), ledgerSpec + "\n## Legs\n### Leg: repo-a\n", emptyPort), []);
1320
+ assert.deepEqual(checkPlan(ledger(), ledgerSpec + "\n## Legs\n### Leg: repo-a\n", emptyPort).findings, []);
1321
1321
  });
1322
1322
  test("leg-local globs and plain Files: resolve at each normalized leg", () => {
1323
1323
  const calls: string[] = [];
1324
1324
  const plan = legPlan().replaceAll("**Files:**", "Files:").replace("Modify: extensions/lib/fixture-other.ts", "Modify: extensions/lib/companion-*.ts");
1325
- assert.deepEqual(checkPlan(plan, legPlanSpec, { exists: () => true, glob: (pattern, leg) => { calls.push(`${leg}:${pattern}`); return ["extensions/lib/companion-one.ts"]; } }), []);
1325
+ assert.deepEqual(checkPlan(plan, legPlanSpec, { exists: () => true, glob: (pattern, leg) => { calls.push(`${leg}:${pattern}`); return ["extensions/lib/companion-one.ts"]; } }).findings, []);
1326
1326
  assert.deepEqual(calls, ["repo-b:extensions/lib/companion-*.ts"]);
1327
1327
  });
1328
1328
  test("coordinated scope counts only original unfenced top-level AC rows", () => {
1329
1329
  const spec = coordinatedSpec.replace("- [ ] First", "```text\n- [ ] Example, not an AC\n```\n- [ ] First");
1330
- assert.deepEqual(checkPlan(coordinatedLedger(), spec, emptyPort), []);
1331
- assert.ok(checkPlan(coordinatedLedger(), spec.replace("scope: AC 1\n", "scope: AC 4\n"), emptyPort).some(f => f.check === "leg-scope"));
1330
+ assert.deepEqual(checkPlan(coordinatedLedger(), spec, emptyPort).findings, []);
1331
+ assert.ok(checkPlan(coordinatedLedger(), spec.replace("scope: AC 1\n", "scope: AC 4\n"), emptyPort).findings.some(f => f.check === "leg-scope"));
1332
1332
  });
1333
1333
  test("coordinated ledger rejects duplicate mapping sections", () => {
1334
1334
  const input = coordinatedLedger();
1335
- assert.ok(checkPlan(input + input.slice(input.indexOf("## Leg mapping")), coordinatedSpec, emptyPort).length);
1335
+ assert.ok(checkPlan(input + input.slice(input.indexOf("## Leg mapping")), coordinatedSpec, emptyPort).findings.length);
1336
+ });
1337
+
1338
+
1339
+ test("manifest preserves ordered task data and optional coordination fields", () => {
1340
+ const plan = VALID_PLAN.replaceAll("Task 1", "Task 4").replaceAll("Task 2", "Task 9")
1341
+ .replace("Implement helper", 'Implement | "helper"')
1342
+ .replace("extensions/lib/fixture-shared.ts", 'extensions/lib/"shared".ts:12-14')
1343
+ .replace("This task implements", 'Orchestrator: persist the measured value into Task 9\nOrchestrator: ignored second line\nThis task implements')
1344
+ .replace("## Wave 1 — Two parallel tasks", "## Wave 1 - Two parallel tasks (in-place)")
1345
+ .replace("- via: `helperFn()`", "- via: `helperFn()`\n- `node extensions/lib/fixture-task1.test.ts; node extensions/lib/fixture-task1.test.ts | node extensions/lib/fixture-task1.test.ts`\n- baseline: abc1234 pass");
1346
+ const result = checkPlan(plan, SPEC_TEXT, alwaysTruePort());
1347
+ assert.deepEqual(result.findings, []);
1348
+ assert.equal(result.ledger, false);
1349
+ assert.ok(result.parsed);
1350
+ const [header, ...lines] = renderManifest(result.parsed, '/repo/plan.md', '/repo/spec.md').split("\n");
1351
+ assert.equal(header, 'manifest: /repo/plan.md | spec: /repo/spec.md | tasks: 3 | waves: 2');
1352
+ const tasks = lines.map(line => JSON.parse(line));
1353
+ assert.deepEqual(Object.keys(tasks[0]), ['task', 'wave', 'mode', 'heading', 'files', 'tests', 'orchestrator']);
1354
+ assert.equal(tasks[0].task, 4);
1355
+ assert.equal(tasks[0].wave, 1);
1356
+ assert.equal(tasks[0].mode, 'in-place');
1357
+ assert.equal(tasks[0].heading, 'Implement | "helper"');
1358
+ assert.deepEqual(tasks[0].files, [
1359
+ { kind: 'Create', path: 'extensions/lib/fixture-task1.ts' },
1360
+ { kind: 'Modify', path: 'extensions/lib/"shared".ts:12-14' },
1361
+ { kind: 'Test', path: 'extensions/lib/fixture-task1.test.ts' },
1362
+ ]);
1363
+ assert.deepEqual(tasks[0].tests, ['node --test extensions/lib/fixture-task1.test.ts', 'node extensions/lib/fixture-task1.test.ts; node extensions/lib/fixture-task1.test.ts | node extensions/lib/fixture-task1.test.ts']);
1364
+ assert.equal(tasks[0].orchestrator, 'persist the measured value into Task 9');
1365
+ assert.equal(tasks[1].task, 9);
1366
+ assert.ok(!('orchestrator' in tasks[1]));
1367
+ assert.ok(!('leg' in tasks[0]));
1368
+ assert.ok(!('mode' in tasks[2]));
1369
+ });
1370
+
1371
+ test("manifest uses normalized legs and omits unmarked prose and no-tests commands", () => {
1372
+ const result = checkPlan(legPlan(), legPlanSpec, alwaysTruePort());
1373
+ assert.deepEqual(result.findings, []);
1374
+ assert.ok(result.parsed);
1375
+ const tasks = renderManifest(result.parsed, '/repo/plan.md', '/repo/spec.md').split('\n').slice(1).map(line => JSON.parse(line));
1376
+ assert.deepEqual(tasks.map(t => t.leg), ['repo-a', 'repo-b', 'repo-a']);
1377
+ const plan = '# Plan\n**Verification:** all-checks\n---\n## Wave 1 - Docs\n### Task 9: Docs\n**Files:**\n- Modify: README.md\n**Tests:**\n- none: docs\nPrimary: persist the value\n## Spec coverage\n| anchor | requirement | owner |\n|---|---|---|\n| - | mechanical: docs | Task 9 |';
1378
+ const noTests = checkPlan(plan, '', alwaysTruePort());
1379
+ assert.deepEqual(noTests.findings, []);
1380
+ assert.ok(noTests.parsed);
1381
+ assert.deepEqual(JSON.parse(renderManifest(noTests.parsed, '/p', '/s').split('\n')[1]), { task: 9, wave: 1, heading: 'Docs', files: [{kind: 'Modify', path: 'README.md'}], tests: [] });
1382
+ assert.equal(checkPlan(ledger(), ledgerSpec, emptyPort).ledger, true);
1336
1383
  });