@sabaiway/agent-workflow-kit 2.1.0 → 3.0.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +6 -5
  3. package/SKILL.md +12 -8
  4. package/bin/install.mjs +1 -1
  5. package/bridges/antigravity-cli-bridge/SKILL.md +8 -1
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +150 -0
  7. package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
  8. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +80 -15
  9. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +95 -4
  10. package/bridges/antigravity-cli-bridge/capability.json +3 -2
  11. package/bridges/codex-cli-bridge/SKILL.md +8 -1
  12. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +143 -0
  13. package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
  14. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +93 -10
  15. package/bridges/codex-cli-bridge/capability.json +3 -2
  16. package/capability.json +1 -1
  17. package/migrations/3.0.0-hardened-core-loop.md +44 -0
  18. package/migrations/README.md +1 -1
  19. package/package.json +2 -2
  20. package/references/hooks/gate-approve.mjs +1 -1
  21. package/references/modes/bootstrap.md +3 -3
  22. package/references/modes/commit-guard.md +19 -0
  23. package/references/modes/core-evidence.md +15 -0
  24. package/references/modes/coverage-check.md +12 -0
  25. package/references/modes/doc-parity.md +7 -6
  26. package/references/modes/gates.md +7 -6
  27. package/references/modes/grounding.md +1 -2
  28. package/references/modes/hook.md +1 -1
  29. package/references/modes/review-state.md +3 -3
  30. package/references/modes/upgrade.md +6 -6
  31. package/references/modes/velocity.md +2 -2
  32. package/references/scripts/archive-decisions.mjs +1 -1
  33. package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
  34. package/references/scripts/install-git-hooks.mjs +90 -18
  35. package/references/scripts/install-git-hooks.test.mjs +102 -0
  36. package/references/scripts/migrate-gates-branches.test.mjs +157 -0
  37. package/references/scripts/migrate-gates.mjs +395 -0
  38. package/references/scripts/migrate-gates.test.mjs +284 -0
  39. package/references/templates/agent_rules.md +2 -2
  40. package/tools/ack-write.mjs +1 -1
  41. package/tools/atomic-write.mjs +2 -2
  42. package/tools/autonomy-config.mjs +1 -1
  43. package/tools/autonomy-doctor.mjs +1 -1
  44. package/tools/autonomy-write.mjs +1 -1
  45. package/tools/bridge-settings-read.mjs +1 -1
  46. package/tools/bridge-settings.mjs +1 -1
  47. package/tools/changed-surface.mjs +8 -69
  48. package/tools/cheap-agents.mjs +2 -2
  49. package/tools/commands.mjs +17 -10
  50. package/tools/commit-guard.mjs +167 -0
  51. package/tools/core-evidence.mjs +914 -0
  52. package/tools/coverage-check.mjs +260 -0
  53. package/tools/delegation.mjs +1 -1
  54. package/tools/detect-backends.mjs +3 -3
  55. package/tools/doc-parity.mjs +11 -27
  56. package/tools/engine-source.mjs +1 -1
  57. package/tools/family-members.mjs +1 -1
  58. package/tools/family-registry.mjs +1 -1
  59. package/tools/fs-safe.mjs +1 -1
  60. package/tools/gate-hook.mjs +1 -1
  61. package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
  62. package/tools/grounding.mjs +6 -83
  63. package/tools/hide-footprint.mjs +1 -1
  64. package/tools/inject-methodology.mjs +1 -1
  65. package/tools/known-footprint.mjs +1 -1
  66. package/tools/labels.mjs +1 -1
  67. package/tools/lcov.mjs +6 -10
  68. package/tools/lens-region.mjs +1 -1
  69. package/tools/manifest/validate.mjs +19 -0
  70. package/tools/migrate-adr-store.mjs +2 -2
  71. package/tools/orchestration-config.mjs +1 -1
  72. package/tools/orchestration-write.mjs +1 -1
  73. package/tools/presentation.mjs +1 -1
  74. package/tools/procedures.mjs +2 -2
  75. package/tools/recipes.mjs +60 -9
  76. package/tools/recommendations.mjs +63 -7
  77. package/tools/release-scan.mjs +1 -1
  78. package/tools/renderers.mjs +1 -1
  79. package/tools/review-state.mjs +288 -340
  80. package/tools/run-gates.mjs +216 -92
  81. package/tools/sandbox-masks.mjs +1 -1
  82. package/tools/semver-lite.mjs +1 -1
  83. package/tools/set-autonomy.mjs +1 -1
  84. package/tools/set-recipe.mjs +1 -1
  85. package/tools/setup-backends.mjs +1 -1
  86. package/tools/surface.mjs +1 -1
  87. package/tools/uninstall.mjs +1 -1
  88. package/tools/velocity-profile.mjs +2 -2
  89. package/tools/view-model.mjs +1 -1
  90. package/references/modes/fold-completeness.md +0 -30
  91. package/references/modes/review-ledger.md +0 -34
  92. package/references/templates/verification-profile.json +0 -10
  93. package/tools/fold-completeness-run.mjs +0 -1120
  94. package/tools/fold-completeness.mjs +0 -672
  95. package/tools/review-ledger-core.mjs +0 -428
  96. package/tools/review-ledger-write.mjs +0 -647
  97. package/tools/review-ledger.mjs +0 -630
  98. package/tools/sarif.mjs +0 -52
  99. package/tools/verification-profile.mjs +0 -219
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
- // seed-gates.mjs — the consent-gated docs/ai/gates.json seeder (AD-042). Reached ONLY through
3
- // explicitly-consenting prose (the bootstrap accelerators block and the gates.md consent-seed
4
- // section) it is NOT a routable mode token, it sits OUTSIDE every velocity allowlist tier
5
- // (a consent-per-run writer is never pre-approved), and the shipped gates.json TEMPLATE stays
6
- // EMPTY (AD-021/AD-038): a populated declaration is per-entry maintainer consent recorded through
7
- // this preview, never auto-seeding.
2
+ // gates-init.mjs — the consented docs/ai/gates.json FILL preview (D9; the pure derivation carried
3
+ // from the retired standalone seeder, AD-042/AD-052 invariants intact). Reached ONLY through
4
+ // explicitly-consenting prose (the init/bootstrap flows and the gates.md consent-fill section)
5
+ // it is NOT a routable mode token, it sits OUTSIDE every velocity allowlist tier (a
6
+ // consent-per-run writer is never pre-approved), and the shipped gates.json TEMPLATE stays EMPTY
7
+ // (AD-021/AD-038): a populated declaration is per-entry maintainer consent recorded through this
8
+ // preview, never auto-seeding. ONE lifecycle contract: this preview runs at INIT; at UPGRADE the
9
+ // only gates.json writer is the consented legacy migration.
8
10
  //
9
11
  // What it offers (the derivation invariants, test-pinned) — CLOSED-WORLD since AD-052: an entry
10
12
  // is offered only when every axis is proven safe by MEMBERSHIP in a finite, test-guarded set,
@@ -29,7 +31,7 @@
29
31
  // executes project-controlled tooling (a node_modules/.bin PATH shim intercepts under every
30
32
  // form) — the documented residual, disclosed in the preview, bounded by the two consents;
31
33
  // • ids derive kebab-case from script names (build:prod → build-prod) and every offered entry
32
- // passes the runner's validateDeclaration (the seeder imports the validator — NEVER the
34
+ // passes the runner's validateDeclaration (this module imports the validator — NEVER the
33
35
  // reverse: run-gates.mjs stays a runner that writes nothing);
34
36
  // • the review-state candidate appears ONLY when docs/ai/orchestration.json DECLARES
35
37
  // reviewed/council on plan-execution.review — the slot the checker enforces — with the
@@ -44,7 +46,7 @@
44
46
  // .workflow-version == lineage-head stamp gate on --apply only (the velocity/gate-hook precedent).
45
47
  //
46
48
  // Exit codes: 0 done / dry-run; 1 precondition STOP (no deployment, stamp, symlink, malformed
47
- // declaration, id collision); 2 usage. Dependency-free, Node >= 18. No side effects on import.
49
+ // declaration, id collision); 2 usage. Dependency-free, Node >= 22. No side effects on import.
48
50
 
49
51
  import { existsSync, lstatSync, readFileSync } from 'node:fs';
50
52
  import { join, resolve, dirname } from 'node:path';
@@ -58,39 +60,40 @@ const HERE = dirname(fileURLToPath(import.meta.url));
58
60
  const KIT_ROOT = resolve(HERE, '..');
59
61
  const TEMPLATE_PATH = join(KIT_ROOT, 'references', 'templates', 'gates.json');
60
62
  const REVIEW_STATE_TOOL = join(KIT_ROOT, 'tools', 'review-state.mjs');
61
- const REVIEW_LEDGER_TOOL = join(KIT_ROOT, 'tools', 'review-ledger.mjs');
63
+ const COVERAGE_CHECK_TOOL = join(KIT_ROOT, 'tools', 'coverage-check.mjs');
62
64
  const STAMP_REL = join('docs', 'ai', '.workflow-version');
63
65
 
64
66
  const EXIT_OK = 0;
65
67
  const EXIT_PRECONDITION = 1;
66
68
  const EXIT_USAGE = 2;
67
69
 
68
- export const SEED_GATES_STOP = 'SEED_GATES_STOP';
70
+ export const GATES_INIT_STOP = 'GATES_INIT_STOP';
69
71
  const stop = (message) =>
70
- Object.assign(new Error(`[agent-workflow-kit] ${message}`), { name: 'SeedGatesStop', code: SEED_GATES_STOP, exitCode: EXIT_PRECONDITION });
72
+ Object.assign(new Error(`[agent-workflow-kit] ${message}`), { name: 'GatesInitStop', code: GATES_INIT_STOP, exitCode: EXIT_PRECONDITION });
71
73
  const usageFail = (message) =>
72
74
  Object.assign(new Error(`[agent-workflow-kit] ${message}`), { exitCode: EXIT_USAGE });
73
75
 
74
76
  // The trust-chain disclosure (AD-042) — printed with EVERY preview, token-pinned by the tests: a
75
77
  // consent-seeded command becomes auto-approvable only after TWO explicit consents.
76
78
  export const TRUST_CHAIN_DISCLOSURE =
77
- 'Disclosure: once the optional approval hook is wired (/agent-workflow-kit hook), it ' +
78
- 'auto-approves byte-exact declared gate commands from the project root — seeding (this consent) ' +
79
- 'and hook wiring (its own consent) are two separate yeses. A script gate runs the project\'s ' +
80
- 'own tooling, which executes project-controlled code (the seeder does not sandbox it; the two ' +
81
- 'consents bound it).';
82
-
83
- const USAGE = `usage: seed-gates [--dry-run | --apply] [--only <id>]... [--cwd <dir>] [--help]
84
-
85
- Consent-gated seeder for the project's own docs/ai/gates.json. Default is --dry-run: prints the
86
- derived { id, title, cmd } entries and writes NOTHING. --apply APPENDS exactly the consented
87
- entries (--only <id> selects a subset; append-only — existing entries are never modified or
88
- removed, an id collision is refused). The offer is CLOSED-WORLD: only a terminating-class script
89
- name (test / lint / type-check / build — never release/publish/deploy, never watch/serve, never a
90
- write-mode variant) whose BODY is a member of the literal runner allowlist is offered, as the
91
- hook-free \`COREPACK_ENABLE_NETWORK=0 <pm> exec -- <body>\` form for the detected package manager. A gate-class script whose
92
- body is NOT in the allowlist is screened out with a note naming it (a command you trust can still
93
- be declared by hand); non-gate-class names are excluded silently, as always.
79
+ 'Disclosure: gates.json is a PRIVILEGED file — once the optional approval hook is wired ' +
80
+ '(/agent-workflow-kit hook), it auto-approves byte-exact declared gate commands from the ' +
81
+ 'project root — filling (this consent) and hook wiring (its own consent) are two separate ' +
82
+ 'yeses. A script gate runs the project\'s own tooling, which executes project-controlled code ' +
83
+ '(this preview does not sandbox it; the two consents bound it).';
84
+
85
+ const USAGE = `usage: gates-init [--dry-run | --apply] [--only <id>]... [--cwd <dir>] [--help]
86
+
87
+ The consented FILL preview for the project's own docs/ai/gates.json (D9). Default is --dry-run:
88
+ prints the derived { id, title, cmd } entries and writes NOTHING. --apply APPENDS exactly the
89
+ consented entries (--only <id> selects a subset; append-only — existing entries are never
90
+ modified or removed, an id collision is refused). The offer is CLOSED-WORLD: only a
91
+ terminating-class script name (test / lint / type-check / build — never release/publish/deploy,
92
+ never watch/serve, never a write-mode variant) whose BODY is a member of the literal runner
93
+ allowlist is offered, as the hook-free \`COREPACK_ENABLE_NETWORK=0 <pm> exec -- <body>\` form for
94
+ the detected package manager. A gate-class script whose body is NOT in the allowlist is screened
95
+ out with a note naming it (a command you trust can still be declared by hand); non-gate-class
96
+ names are excluded silently, as always.
94
97
  ${TRUST_CHAIN_DISCLOSURE}`;
95
98
 
96
99
  // ── candidate classification: the NAME screens (the LOCKED derivation invariants) ──────
@@ -310,12 +313,14 @@ export const reviewStateCandidate = (cwd, deps = {}) => {
310
313
  }
311
314
  };
312
315
 
313
- // The conditional review-LEDGER candidate (AD-045) — the SAME consent + conditional rule as the
314
- // review-state candidate (offered ONLY when plan-execution.review is reviewed/council), keyed on the
315
- // same slot, path resolved + QUOTED. It gates the review-ROUND ledger (converged / resolved-residual);
316
- // review-state gates receipt PRESENCE. Both may be offered together distinct axes.
317
- export const reviewLedgerCandidate = (cwd, deps = {}) => {
318
- const toolPath = deps.reviewLedgerTool ?? REVIEW_LEDGER_TOOL;
316
+ // The conditional COVERAGE-CHECK candidate (D3(a)) — the SAME consent + conditional rule as the
317
+ // review-state candidate (offered ONLY when plan-execution.review is reviewed/council), keyed on
318
+ // the same slot, path resolved + QUOTED. Together they are the canonical core pair `run-gates
319
+ // --final` requires (the checker declared LAST buildOffer appends it last so a whole-offer
320
+ // apply lands final-ready); review-state gates receipt satisfaction, coverage-check verifies the
321
+ // consumed lcov + the red-proof declarations.
322
+ export const coverageCheckCandidate = (cwd, deps = {}) => {
323
+ const toolPath = deps.coverageCheckTool ?? COVERAGE_CHECK_TOOL;
319
324
  try {
320
325
  const { config } = loadConfig(resolve(cwd), deps.readFile ?? readFileSync, deps.lstat ?? lstatSync);
321
326
  const declared = config?.['plan-execution']?.review;
@@ -324,15 +329,15 @@ export const reviewLedgerCandidate = (cwd, deps = {}) => {
324
329
  return {
325
330
  candidate: null,
326
331
  note:
327
- `the review-ledger candidate was withheld: the resolved kit path contains shell ` +
332
+ `the coverage-check candidate was withheld: the resolved kit path contains shell ` +
328
333
  `metacharacters that do not survive double-quoting (${toolPath}) — declare the gate ` +
329
- `by hand per references/modes/review-ledger.md`,
334
+ `by hand per references/modes/coverage-check.md`,
330
335
  };
331
336
  }
332
337
  return {
333
338
  candidate: {
334
- id: 'review-ledger',
335
- title: 'Review-round ledger: the in-flight loop is converged or resolved-residual',
339
+ id: 'coverage-check',
340
+ title: 'Changed-line coverage + red-proof verification (the final-run checker)',
336
341
  cmd: `node "${toolPath}" --check`,
337
342
  },
338
343
  note: null,
@@ -340,7 +345,7 @@ export const reviewLedgerCandidate = (cwd, deps = {}) => {
340
345
  } catch (err) {
341
346
  return {
342
347
  candidate: null,
343
- note: `orchestration config unreadable (${err.message}) — the review-ledger candidate was not evaluated`,
348
+ note: `orchestration config unreadable (${err.message}) — the coverage-check candidate was not evaluated`,
344
349
  };
345
350
  }
346
351
  };
@@ -356,22 +361,23 @@ const assertOnlyIdsOffered = (offer, onlyIds = []) => {
356
361
  }
357
362
  };
358
363
 
359
- // The full offer: script entries + the conditional review-state + review-ledger candidates (last),
360
- // plus loud notes. Both review candidates key on the same slot (plan-execution.review reviewed/council)
361
- // but gate distinct axes (receipt presence vs review-round convergence) offered together.
364
+ // The full offer: script entries + the conditional review-state + coverage-check candidates
365
+ // (coverage-check LAST the `run-gates --final` declaration-shape rule requires the checker as
366
+ // the last declared gate, so a whole-offer apply is final-ready by construction). Both key on the
367
+ // same slot (plan-execution.review reviewed/council) but gate distinct axes.
362
368
  export const buildOffer = (cwd, deps = {}) => {
363
369
  const scripts = deriveScripts(cwd, deps);
364
370
  const rs = reviewStateCandidate(cwd, deps);
365
- const rl = reviewLedgerCandidate(cwd, deps);
366
- const candidates = [rs.candidate, rl.candidate].filter(Boolean);
371
+ const cc = coverageCheckCandidate(cwd, deps);
372
+ const candidates = [rs.candidate, cc.candidate].filter(Boolean);
367
373
  return {
368
374
  entries: [...scripts.entries, ...candidates],
369
- notes: [...scripts.notes, rs.note, rl.note].filter(Boolean),
375
+ notes: [...scripts.notes, rs.note, cc.note].filter(Boolean),
370
376
  };
371
377
  };
372
378
 
373
379
  // The RUNNABLE apply invocation for a given project — this tool has no bin and no mode token, so
374
- // the consent step must print the real command, never a bare `seed-gates`. Consent integrity: a
380
+ // the consent step must print the real command, never a bare `gates-init`. Consent integrity: a
375
381
  // previewed --only subset is carried into the hint VERBATIM (ids are shell-safe by construction —
376
382
  // kebabIdOf output / the fixed `review-state`), so following the hint can never widen the consent.
377
383
  // Paths that do not survive double-quoting (the reviewStateCandidate screen, same pattern) make
@@ -386,7 +392,7 @@ export const applyInvocationFor = (cwd, onlyIds = []) => {
386
392
  const GENERIC_APPLY_HINT = 're-run this same command with --apply [--only <id>]...';
387
393
 
388
394
  export const formatPreview = (offer, applyInvocation = null, { explicitOnly = false } = {}) => {
389
- const lines = ['[agent-workflow-kit] gates seeding preview (dry-run — nothing was written):'];
395
+ const lines = ['[agent-workflow-kit] gates fill preview (dry-run — nothing was written):'];
390
396
  if (!offer.entries.length) {
391
397
  lines.push(' nothing to offer — no seedable terminating verification scripts were found.');
392
398
  }
@@ -418,14 +424,14 @@ const loadExistingDeclaration = (cwd, deps = {}) => {
418
424
  try {
419
425
  return JSON.parse(String(read(full, 'utf8')));
420
426
  } catch (err) {
421
- throw stop(`${GATES_REL}: malformed JSON (${err.message}) — fix it by hand; the seeder never writes over a declaration it cannot parse`);
427
+ throw stop(`${GATES_REL}: malformed JSON (${err.message}) — fix it by hand; this preview never writes over a declaration it cannot parse`);
422
428
  }
423
429
  })();
424
430
  const gates = (() => {
425
431
  try {
426
432
  return validateDeclaration(parsed);
427
433
  } catch (err) {
428
- throw stop(`${err.message} — fix it by hand; the seeder never writes over an invalid declaration`);
434
+ throw stop(`${err.message} — fix it by hand; this preview never writes over an invalid declaration`);
429
435
  }
430
436
  })();
431
437
  return { outcome: 'loaded', readme: typeof parsed._README === 'string' ? parsed._README : undefined, gates };
@@ -453,7 +459,7 @@ const readStampValue = (cwd, deps = {}) => {
453
459
  };
454
460
 
455
461
  // ── apply (append exactly the consented entries) ───────────────────────────────────────
456
- export const applySeed = ({ cwd, onlyIds = [] }, deps = {}) => {
462
+ export const applyFill = ({ cwd, onlyIds = [] }, deps = {}) => {
457
463
  assertDocsAiDeployment(cwd, deps, { stop, noun: 'a gate declaration', rel: GATES_REL });
458
464
  const stampValue = readStampValue(cwd, deps);
459
465
  if (stampValue !== EXPECTED_WORKFLOW_VERSION) {
@@ -474,7 +480,7 @@ export const applySeed = ({ cwd, onlyIds = [] }, deps = {}) => {
474
480
  if (collisions.length) {
475
481
  throw stop(
476
482
  `id collision — already declared in ${GATES_REL}: ${collisions.join(', ')} (append-only: the ` +
477
- `seeder never modifies or removes an existing entry; pick the others with --only, or edit by hand)`,
483
+ `fill never modifies or removes an existing entry; pick the others with --only, or edit by hand)`,
478
484
  );
479
485
  }
480
486
 
@@ -539,7 +545,7 @@ export const main = (argv = process.argv.slice(2), deps = {}) => {
539
545
  log(formatPreview(filtered, applyInvocationFor(cwd, args.only), { explicitOnly: args.only.length > 0 }));
540
546
  return EXIT_OK;
541
547
  }
542
- const result = applySeed({ cwd, onlyIds: args.only }, deps);
548
+ const result = applyFill({ cwd, onlyIds: args.only }, deps);
543
549
  if (result.outcome === 'nothing') {
544
550
  log('[agent-workflow-kit] nothing to offer — no seedable terminating verification scripts were found; wrote nothing.');
545
551
  for (const note of result.notes) log(` note: ${note}`); // the user learns WHY (same note as the preview)
@@ -23,7 +23,7 @@
23
23
  // not-ignored path (a new untracked file would itself move the review fingerprint the facts are
24
24
  // about to ground). stdout is the default. It never commits and never runs a subscription CLI.
25
25
  //
26
- // Dependency-free, Node >= 18. No side effects on import (the isDirectRun idiom).
26
+ // Dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
27
27
 
28
28
  import { readFileSync, writeFileSync, lstatSync, realpathSync } from 'node:fs';
29
29
  import { resolve, join, relative, isAbsolute, dirname, basename, sep } from 'node:path';
@@ -31,11 +31,6 @@ import { tmpdir } from 'node:os';
31
31
  import { pathToFileURL } from 'node:url';
32
32
  import { spawnSync } from 'node:child_process';
33
33
  import { fail } from './orchestration-config.mjs';
34
- // (e) --ledger-summary (BUGFREE-3 / AD-049): a computed, loop/base-SCOPED review-ledger digest for
35
- // the facts payload. The review ledger is read-only here — grounding never writes it. ORIGINS +
36
- // computeTelemetry give the counts; the segment filter gives the scope.
37
- import { ORIGINS, computeTelemetry, filterSegmentRecords, readLedger, resolveLedgerPath, resolveBase } from './review-ledger.mjs';
38
- import { plansInFlight } from './review-state.mjs';
39
34
  // (f) --autonomy (AD-044 Plan 3): the effective per-project autonomy policy for the facts payload.
40
35
  // READ core only — never autonomy-write.mjs (the import-split invariant).
41
36
  import { AUTONOMY_REL, loadAutonomy, resolveAutonomy, isSparseSeedConfig } from './autonomy-config.mjs';
@@ -119,72 +114,10 @@ export const trimToBudget = (payload, budget) => {
119
114
  return { text, trimmedBytes: bytes - keep };
120
115
  };
121
116
 
122
- // ── (e) --ledger-summary: a loop/base-SCOPED review-ledger digest (read-only, computed) ─────────
123
- // `computeTelemetry` aggregates ALL loops and is not per-round-facts-shaped, so this renders the
124
- // in-flight SEGMENT only (plan-execution, loop, base) — the telemetry COUNTS filtered to that
125
- // segment, plus a terse per-round/triage/override render. Deliberately compact + distinct from the
126
- // review-ledger `--status` human view (that one is indented + segment-grouped): this is a byte-
127
- // budgeted FACTS block that agy/codex review against, so unrelated loops are excluded by construction.
128
-
129
- const baseTag = (base) => (base === null ? '(unborn branch)' : String(base).slice(0, 12));
130
- const countsOf = (obj) => {
131
- const keys = Object.keys(obj).sort();
132
- return keys.length === 0 ? '(none)' : keys.map((k) => `${k}:${obj[k]}`).join(' ');
133
- };
134
- const summaryRoundLine = (r) =>
135
- `round ${r.round} — ${r.backends.map((b) => `${b.backend} ${b.degraded ? `degraded(${b.reason})` : `${b.blockers}/${b.majors}/${b.minors} ${b.verdict}`}`).join(', ')}` +
136
- (r.findings.length ? ` [${r.findings.map((f) => `${f.findingKey}(${f.severity})`).join(', ')}]` : '');
137
- const summaryTriageLine = (t) => `triage @round ${t.round} — ${t.classifications.map((c) => `${c.findingKey}=${c.class}`).join(', ')}`;
138
- const summaryOverrideLine = (o) =>
139
- `override @round ${o.round} [${o.scope}] — ${o.scope === 'oracle-change' ? o.files.join(', ') : o.scope === 'size-cap' ? `sanctioned ${o.sanctionedLines} lines` : o.testId}: ${o.reason}`;
140
- const summaryGateRunLine = (g) => `gate-run — status=${g.summary.status} ${g.summary.passed}/${g.summary.gates} green`;
141
- const summaryRecordLine = (r) =>
142
- r.kind === 'round' ? summaryRoundLine(r) : r.kind === 'triage' ? summaryTriageLine(r) : r.kind === 'override' ? summaryOverrideLine(r) : summaryGateRunLine(r);
143
-
144
- // renderLedgerSummary(records, { loop, base }) → the scoped facts section, or '' when the segment
145
- // holds no records (empty/absent → nothing to ground). Pure: the caller does the I/O.
146
- export const renderLedgerSummary = (records, { loop, base }) => {
147
- const segment = filterSegmentRecords(records, { activity: PLAN_EXECUTION, loop, base });
148
- if (segment.length === 0) return '';
149
- const t = computeTelemetry(segment, []).loops[0];
150
- const lines = [
151
- `## Review-ledger summary — loop ${loop} @ base ${baseTag(base)}`,
152
- '',
153
- `rounds ${t.rounds} · divergence-rounds ${t.divergenceRounds}`,
154
- `finding origins — ${ORIGINS.map((k) => `${k}:${t.origins[k]}`).join(' ')}`,
155
- `classifications — ${countsOf(t.classifications)}`,
156
- `backend verdicts — ${Object.keys(t.backendVerdicts).sort().map((b) => `${b}{${countsOf(t.backendVerdicts[b])}}`).join(' · ') || '(none)'}`,
157
- `overrides — ${countsOf(t.overrides)}`,
158
- '',
159
- ...segment.map(summaryRecordLine),
160
- ];
161
- return `${lines.join('\n')}\n`;
162
- };
163
-
164
- // resolveLedgerSummary({ cwd, env }) → the scoped section text for the SINGLE in-flight plan-execution
165
- // segment. A loud STOP unless exactly one plan is in flight (the family's single-plan discipline —
166
- // never guess which loop to ground). Reads the ledger read-only.
167
117
  const gitTop = (cwd) => {
168
118
  const r = gitLine(['rev-parse', '--show-toplevel'], cwd);
169
119
  return r && r.status === 0 ? r.stdout.replace(/\r?\n$/, '') : cwd;
170
120
  };
171
- export const resolveLedgerSummary = ({ cwd, env }) => {
172
- const root = gitTop(cwd);
173
- const plans = plansInFlight(root);
174
- if (plans.length !== 1) {
175
- throw fail(1, `--ledger-summary needs exactly one in-flight plan (in flight: ${plans.length ? plans.join(', ') : 'none'}) — resolve to one active plan`);
176
- }
177
- const loop = plans[0].replace(/\.md$/, '');
178
- const base = resolveBase(cwd);
179
- const ledgerPath = resolveLedgerPath(cwd, env);
180
- const { records, readError, malformed, malformedReasons } = ledgerPath ? readLedger(ledgerPath) : { records: [], malformed: 0 };
181
- // Fail CLOSED like every sibling ledger reader (No-silent-failures): an unreadable OR malformed
182
- // ledger must never silently render an empty/partial digest the reviewer then grounds against — an
183
- // empty section would be indistinguishable from a legitimately-empty segment.
184
- if (readError) throw fail(1, `--ledger-summary cannot read the ledger (${readError}) — failing closed; inspect ${ledgerPath}`);
185
- if (malformed > 0) throw fail(1, `--ledger-summary: the ledger has ${malformed} malformed line(s) — failing closed (a dropped line could hide a round/finding): ${malformedReasons.join('; ')}`);
186
- return renderLedgerSummary(records, { loop, base });
187
- };
188
121
 
189
122
  // ── (f) --autonomy: the effective autonomy policy as a computed facts block (AD-044 Plan 3) ─────
190
123
  // The ONE shared resolver (resolveAutonomy) renders the FULL effective policy — every red-line and
@@ -310,7 +243,7 @@ export const assertScratchDestination = (outPath, cwd) => {
310
243
  const HELP = `grounding — grounded-review facts assembler for the agent-workflow family (AD-038).
311
244
 
312
245
  Usage:
313
- node grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--ledger-summary] [--reserve-bytes <n>] [--out <path>]
246
+ node grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--reserve-bytes <n>] [--out <path>]
314
247
 
315
248
  --constraints slice the root AGENTS.md "Hard Constraints" section verbatim
316
249
  (exactly one matching heading, else a loud STOP)
@@ -322,10 +255,6 @@ Usage:
322
255
  --plan <path> extract the plan's decision-bearing sections verbatim + whole:
323
256
  "## Approach" + "## Verification" (REQUIRED — STOP if missing),
324
257
  "## Decisions (locked)" when present; a duplicate heading is a STOP
325
- --ledger-summary append a COMPUTED review-ledger digest for the SINGLE in-flight
326
- plan-execution segment (rounds · origins · classifications · verdicts ·
327
- overrides + a per-round render) — "computed, not remembered" facts; a
328
- loud STOP unless exactly one plan is in flight
329
258
  --reserve-bytes <n> the artifact share agy-review will add around these facts — the output
330
259
  budget becomes AGY_MAX_PROMPT_BYTES − n (loud tail-trim on overflow)
331
260
  --out <path> write instead of stdout — system-temp scratch (rewritable), or a FRESH
@@ -345,12 +274,10 @@ const parseArgs = (argv) => {
345
274
  let plan = null;
346
275
  let out = null;
347
276
  let reserve = 0;
348
- let ledgerSummary = false;
349
277
  for (let i = 0; i < argv.length; i += 1) {
350
278
  const a = argv[i];
351
279
  if (a === '--constraints') constraints = true;
352
280
  else if (a === '--autonomy') autonomy = true;
353
- else if (a === '--ledger-summary') ledgerSummary = true;
354
281
  else if (a === '--plan') {
355
282
  plan = argv[i + 1];
356
283
  if (!plan || plan.startsWith('--')) throw fail(2, '--plan requires a <path>');
@@ -366,10 +293,10 @@ const parseArgs = (argv) => {
366
293
  i += 1;
367
294
  } else throw fail(2, `unknown argument: ${a}`);
368
295
  }
369
- if (!constraints && !autonomy && plan == null && !ledgerSummary) {
370
- throw fail(2, 'nothing to assemble — pass --constraints, --autonomy, --plan <path>, and/or --ledger-summary');
296
+ if (!constraints && !autonomy && plan == null) {
297
+ throw fail(2, 'nothing to assemble — pass --constraints, --autonomy, and/or --plan <path>');
371
298
  }
372
- return { constraints, autonomy, plan, out, reserve, ledgerSummary };
299
+ return { constraints, autonomy, plan, out, reserve };
373
300
  };
374
301
 
375
302
  const resolveBudget = (env, reserve) => {
@@ -389,7 +316,7 @@ export const main = (argv, ctx = {}) => {
389
316
  const env = ctx.env ?? process.env;
390
317
  try {
391
318
  if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
392
- const { constraints, autonomy, plan, out, reserve, ledgerSummary } = parseArgs(argv);
319
+ const { constraints, autonomy, plan, out, reserve } = parseArgs(argv);
393
320
  const budget = resolveBudget(env, reserve);
394
321
 
395
322
  const readOrStop = (path, label) => {
@@ -424,10 +351,6 @@ export const main = (argv, ctx = {}) => {
424
351
  const parts = [];
425
352
  const assembled = assembleGrounding({ constraintsText, autonomyText, planText, planLabel: plan ?? 'plan' });
426
353
  if (assembled) parts.push(assembled);
427
- if (ledgerSummary) {
428
- const summary = resolveLedgerSummary({ cwd, env });
429
- if (summary) parts.push(summary);
430
- }
431
354
  const payload = parts.join('\n');
432
355
  const { text, trimmedBytes } = trimToBudget(payload, budget);
433
356
  const stderr = trimmedBytes > 0
@@ -17,7 +17,7 @@
17
17
  // path). Add-local-first, then DETECT + REPORT the residual legacy global block — removal is the
18
18
  // explicit, ASK-gated `--remove-global` (prints a restorable backup), never a default (an arbitrary
19
19
  // host's OTHER hidden repo may rely on the same root-anchored global lines). Windows is supported (text
20
- // edit; forward-slash patterns; CRLF preserved via EOL detection). Dependency-free, Node >= 18.
20
+ // edit; forward-slash patterns; CRLF preserved via EOL detection). Dependency-free, Node >= 22.
21
21
 
22
22
  import {
23
23
  readFileSync, writeFileSync, statSync, readdirSync,
@@ -18,7 +18,7 @@
18
18
  // The live read is lazy + fail-loud: resolve+read the engine ONLY when a fill is actually needed, and
19
19
  // STOP loudly (never a silent fallback) when the engine is needed but absent/invalid.
20
20
  //
21
- // Pure string functions (testable with byte-preservation fixtures); dependency-free, Node >= 18.
21
+ // Pure string functions (testable with byte-preservation fixtures); dependency-free, Node >= 22.
22
22
  //
23
23
  // Canonical-refresh (AD-025): a slot filled BEFORE a clause existed is normally preserved verbatim
24
24
  // (reconcile only fills an EMPTY slot). To push a NEW canonical clause to the EXISTING filled base
@@ -14,7 +14,7 @@
14
14
  // count sentinel + intrinsic invariants (anchoring, uniqueness, disjointness, no subsumption) — and
15
15
  // references/contracts.md carries the human-readable mirror table, kept in sync by review.
16
16
  //
17
- // Pure, dependency-free, Node >= 18. The only fs touch is expandGlob (readdir/stat of a glob parent),
17
+ // Pure, dependency-free, Node >= 22. The only fs touch is expandGlob (readdir/stat of a glob parent),
18
18
  // injected so the registry stays unit-testable without the real filesystem.
19
19
 
20
20
  import { readdirSync, statSync } from 'node:fs';
package/tools/labels.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  // imports family-registry for vocabulary, and family-registry imports + re-exports the public subset
8
8
  // it exported before (the 7 state constants + DISPLAY_NAMES), so every existing importer stays green.
9
9
  //
10
- // Pure data, no side effects, Node >= 18. Every export is frozen — this is a contract, not a mutable.
10
+ // Pure data, no side effects, Node >= 22. Every export is frozen — this is a contract, not a mutable.
11
11
 
12
12
  // ── manifestState values (internal; the detect-backends precedence, generalized to any member) ──────
13
13
  // These are INTERNAL — they must never reach a user surface verbatim (mapped through STATE_PUBLIC
package/tools/lcov.mjs CHANGED
@@ -1,11 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // lcov.mjs — a dependency-free LCOV parser for the fold-completeness runner's coverage.kind:"lcov"
3
- // branch (BUGFREE-3, AD-049 the language-independence contract). When a verification profile
4
- // declares coverage.kind:"lcov", the consumer's OWN suite leaves an LCOV file at the declared path
5
- // (the diff-cover / c8 --reporter=lcov pattern) and this module reads it into a per-file
6
- // line→hits map, keyed by the SAME canonical absolute path the V8 coverage map uses — so the
7
- // runner's ONE uncovered-changed loop consumes either source unchanged (computeUncoveredLines /
8
- // effectiveCount stay the V8-only path, D10).
2
+ // lcov.mjs — a dependency-free LCOV parser for the coverage-check final-run checker (D3(d)).
3
+ // The declared unit-tests gate leaves an LCOV file at the fixed git-dir path and this module reads
4
+ // it into a per-file line→hits map, keyed by a canonical absolute path.
9
5
  //
10
6
  // FAIL-CLOSED posture (the whole reason the fold gate exists): a malformed record can never mark a
11
7
  // line COVERED — a DA with a valid line but a non-integer HIT count reads the line UNCOVERED (hits 0,
@@ -16,7 +12,7 @@
16
12
  // changed executable line with NO DA entry reads non-executable — LCOV carries no separate
17
13
  // executability signal; the V8 path, which reads an absent line as uncovered, differs by format.)
18
14
  //
19
- // Dependency-free, Node >= 18. No side effects on import.
15
+ // Dependency-free, Node >= 22. No side effects on import.
20
16
 
21
17
  import { join, resolve, isAbsolute, sep } from 'node:path';
22
18
  import { realpathSync } from 'node:fs';
@@ -80,8 +76,8 @@ const defaultCanon = (p) => {
80
76
  };
81
77
 
82
78
  // Segment-safe containment ('/a' never contains '/ab'); correct at the filesystem root (mirrors
83
- // fold-completeness-run.mjs containsPath). Defined locally so lcov.mjs never imports the runner (the
84
- // runner imports THIS module).
79
+ // core-evidence.mjs containsPath). Defined locally so lcov.mjs imports no family module (its
80
+ // consumers import THIS module).
85
81
  const containsPath = (realRoot, realAbs) =>
86
82
  realAbs === realRoot || realAbs.startsWith(realRoot.endsWith(sep) ? realRoot : realRoot + sep);
87
83
 
@@ -22,7 +22,7 @@
22
22
  // never a silent truncate; a file without frontmatter/`maxLines` skips the guard with a stated
23
23
  // note (such a file is outside the docs cap gate anyway).
24
24
  //
25
- // Pure string functions (fs only in the CLI); dependency-free, Node >= 18.
25
+ // Pure string functions (fs only in the CLI); dependency-free, Node >= 22.
26
26
 
27
27
  import { normalizeCanonical } from './orchestration-config.mjs';
28
28
 
@@ -704,6 +704,25 @@ export const validateManifest = (skillDir) => {
704
704
  else validateModeCatalog(modeCatalog, roles, errors);
705
705
  }
706
706
 
707
+ // `posture` (strip-the-kit D5): the DEFAULT dispatch posture pins the wrapper ships — the
708
+ // checkable source the kit renders the configured posture from (before D5 these lived only in
709
+ // shell defaults + prose). ADDITIVE-OPTIONAL like modeCatalog (absence = a bridge predating
710
+ // posture labeling); a PRESENT block is typed-validated with the SAME shape rule the receipt
711
+ // predicate applies (isValidReceiptPosture's manifest twin): `model` a non-empty string;
712
+ // `effort` (when present) a non-empty string; `tier` (when present) a non-empty string or null.
713
+ if (Object.hasOwn(manifest, 'posture')) {
714
+ const posture = manifest.posture;
715
+ if (posture === null || typeof posture !== 'object' || Array.isArray(posture)) {
716
+ errors.push('`posture` must be an object of dispatch-posture pins');
717
+ } else {
718
+ if (typeof posture.model !== 'string' || posture.model.length === 0) errors.push('`posture.model` must be a non-empty string');
719
+ if (Object.hasOwn(posture, 'effort') && (typeof posture.effort !== 'string' || posture.effort.length === 0)) errors.push('`posture.effort` (when declared) must be a non-empty string');
720
+ if (Object.hasOwn(posture, 'tier') && posture.tier !== null && (typeof posture.tier !== 'string' || posture.tier.length === 0)) errors.push('`posture.tier` (when declared) must be a non-empty string or null');
721
+ const unknown = Object.keys(posture).filter((k) => !['model', 'effort', 'tier'].includes(k));
722
+ if (unknown.length > 0) errors.push(`\`posture\` carries unknown key(s): ${unknown.join(', ')} (closed vocabulary: model, effort, tier)`);
723
+ }
724
+ }
725
+
707
726
  if (!isStub) {
708
727
  const auth = readAuthoritativeVersion(skillDir);
709
728
  if (auth.version == null) errors.push(`could not resolve an authoritative version (${auth.from})`);
@@ -23,7 +23,7 @@
23
23
  //
24
24
  // Write discipline: preview (--dry-run) is the DEFAULT and writes NOTHING; --apply performs the
25
25
  // migration. It NEVER commits. Exit codes: 0 done / dry-run / no-op; 1 precondition STOP (no
26
- // deployment, no writable snapshot base, a failed migration); 2 usage. Dependency-free, Node >= 18.
26
+ // deployment, no writable snapshot base, a failed migration); 2 usage. Dependency-free, Node >= 22.
27
27
  // No side effects on import.
28
28
 
29
29
  import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs';
@@ -65,7 +65,7 @@ conservation proof) and writes NOTHING. --apply snapshots, force-refreshes the e
65
65
  then runs the conservation-checked migration. It NEVER commits — review the tree and commit yourself.`;
66
66
 
67
67
  // The mutually-exclusive dry-run/apply parse (a consent-gated writer never lets a later flag silently
68
- // decide whether it mutates) + --cwd, cloned from the seed-gates writer contract.
68
+ // decide whether it mutates) + --cwd the family's shared consented-writer contract.
69
69
  export const parseArgs = (argv) => {
70
70
  const parsed = argv.reduce(
71
71
  (acc, a, i) => {
@@ -16,7 +16,7 @@
16
16
  //
17
17
  // This module performs NO filesystem WRITES — only reads (loadConfig). The single fs-writer lives in
18
18
  // orchestration-write.mjs, which procedures.mjs never imports, so "procedures never reaches a writer"
19
- // is structurally true. Pure-where-possible (fs injectable), dependency-free, Node >= 18. No side
19
+ // is structurally true. Pure-where-possible (fs injectable), dependency-free, Node >= 22. No side
20
20
  // effects on import.
21
21
 
22
22
  import { readFileSync, lstatSync } from 'node:fs';
@@ -10,7 +10,7 @@
10
10
  // consumed here with this module's own STOP identity, so the public API + error contract are
11
11
  // unchanged (this file's tests pin them, characterize-first).
12
12
  //
13
- // Dependency-free, Node >= 18. Every fs primitive is injectable (deps.*) so the guards are unit-testable.
13
+ // Dependency-free, Node >= 22. Every fs primitive is injectable (deps.*) so the guards are unit-testable.
14
14
 
15
15
  import { CONFIG_REL, serializeConfig } from './orchestration-config.mjs';
16
16
  import { writeDocsAiFileAtomic } from './atomic-write.mjs';
@@ -6,7 +6,7 @@
6
6
  // in the user's language per SKILL.md §4.4 — it never reads this file. Keeping the vocabulary here (a
7
7
  // frozen leaf) means the renderers carry no inline strings and the phrasing is pinned by tests.
8
8
  //
9
- // Pure data, no side effects, Node >= 18. Every export frozen.
9
+ // Pure data, no side effects, Node >= 22. Every export frozen.
10
10
 
11
11
  // public state token (STATE_PUBLIC value) → the direct-CLI English phrase. `installed` is null because
12
12
  // an installed member shows its VERSION, not a phrase (the renderer special-cases it). Mirrors the
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // Invariants (mirror recipes.mjs): pure-where-possible, READ-ONLY (never writes, never commits, never
11
11
  // runs a subscription CLI). The deterministic resolution lives in the kit (resolveActivityRecipe), not
12
- // in the agent. Dependency-free, Node >= 18.
12
+ // in the agent. Dependency-free, Node >= 22.
13
13
  //
14
14
  // Exit codes: 0 success (an unsatisfiable explicit override degrades LOUDLY but still exits 0 — it is a
15
15
  // valid request that gracefully degraded); 2 usage (unknown <activity> / bad --override syntax);
@@ -213,7 +213,7 @@ const reviewLoopAdvice = (slots, activity) =>
213
213
  ' • At the cap, classify every surviving blocking finding: fixable-bug (fold ONCE as a red→green test, re-review) / inherent-layer-residual (document + raise to an acceptance criterion) / escalate (the maintainer decides); a minor never forces triage.',
214
214
  ...(activity === 'plan-execution'
215
215
  ? [
216
- ' • The computed instrument for THIS loop: run the FULL gate matrix with run-gates --record BEFORE recording a round (the green-baseline receipt the writer demands), then record each round + triage via review-ledger (record / classify); rounds, caps, and teeth are per SEGMENT (base = HEAD the counter resets only at a gated commit); read the stop with review-ledger --status (its render replaces the hand-composed tally); gate the commit with review-ledger --check.',
216
+ ' • The computed instrument for THIS loop: declare each bugfix red BEFORE the fix (core-evidence red-proof observed N/N red, custody-hashed); an unavailable backend gets an explicit core-evidence degrade record, never a silent skip; then stage everything, run the reviews on the STAGED tree, and mint the ONE receipt with run-gates --final (coverage + red-proof verification ride the final run); the commit is gated by commit-guard --check (the D13 ordering: any edit after the final run re-stales it); read the loop state statelessly with core-evidence summary.',
217
217
  ]
218
218
  : []),
219
219
  ]