@sabaiway/agent-workflow-kit 2.1.0 → 3.1.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 (107) hide show
  1. package/CHANGELOG.md +94 -1
  2. package/README.md +7 -5
  3. package/SKILL.md +16 -8
  4. package/bin/install.mjs +11 -2
  5. package/bridges/antigravity-cli-bridge/SKILL.md +8 -1
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +163 -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 +171 -22
  10. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +59 -41
  11. package/bridges/antigravity-cli-bridge/capability.json +3 -2
  12. package/bridges/codex-cli-bridge/SKILL.md +8 -1
  13. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +67 -22
  14. package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +156 -0
  15. package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
  16. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +158 -27
  17. package/bridges/codex-cli-bridge/capability.json +3 -2
  18. package/capability.json +1 -1
  19. package/migrations/3.0.0-hardened-core-loop.md +44 -0
  20. package/migrations/README.md +1 -1
  21. package/package.json +2 -2
  22. package/references/hooks/gate-approve.mjs +1 -1
  23. package/references/modes/bootstrap.md +3 -3
  24. package/references/modes/commit-guard.md +19 -0
  25. package/references/modes/core-evidence.md +15 -0
  26. package/references/modes/coverage-check.md +12 -0
  27. package/references/modes/doc-parity.md +7 -6
  28. package/references/modes/gates.md +7 -6
  29. package/references/modes/grounding.md +1 -2
  30. package/references/modes/hook.md +1 -1
  31. package/references/modes/recommendations.md +1 -0
  32. package/references/modes/review-state.md +3 -3
  33. package/references/modes/upgrade.md +6 -6
  34. package/references/modes/velocity.md +2 -2
  35. package/references/modes/worktrees.md +120 -0
  36. package/references/scripts/archive-decisions.mjs +3 -3
  37. package/references/scripts/archive-decisions.test.mjs +5 -5
  38. package/references/scripts/check-docs-size-cli.test.mjs +41 -0
  39. package/references/scripts/check-docs-size.mjs +46 -29
  40. package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
  41. package/references/scripts/install-git-hooks.mjs +90 -18
  42. package/references/scripts/install-git-hooks.test.mjs +102 -0
  43. package/references/scripts/migrate-gates-branches.test.mjs +157 -0
  44. package/references/scripts/migrate-gates.mjs +395 -0
  45. package/references/scripts/migrate-gates.test.mjs +284 -0
  46. package/references/templates/agent_rules.md +2 -2
  47. package/tools/ack-write.mjs +1 -1
  48. package/tools/atomic-write.mjs +2 -2
  49. package/tools/autonomy-config.mjs +1 -1
  50. package/tools/autonomy-doctor.mjs +2 -2
  51. package/tools/autonomy-write.mjs +1 -1
  52. package/tools/bridge-settings-read.mjs +1 -1
  53. package/tools/bridge-settings.mjs +1 -1
  54. package/tools/changed-surface.mjs +16 -77
  55. package/tools/cheap-agents.mjs +2 -2
  56. package/tools/commands.mjs +26 -10
  57. package/tools/commit-guard.mjs +167 -0
  58. package/tools/core-evidence.mjs +914 -0
  59. package/tools/coverage-check.mjs +260 -0
  60. package/tools/delegation.mjs +1 -1
  61. package/tools/detect-backends.mjs +3 -3
  62. package/tools/doc-parity.mjs +11 -27
  63. package/tools/engine-source.mjs +1 -1
  64. package/tools/family-members.mjs +1 -1
  65. package/tools/family-registry.mjs +1 -1
  66. package/tools/fs-safe.mjs +1 -1
  67. package/tools/gate-hook.mjs +1 -1
  68. package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
  69. package/tools/grounding.mjs +19 -96
  70. package/tools/hide-footprint.mjs +1 -1
  71. package/tools/inject-methodology.mjs +72 -41
  72. package/tools/known-footprint.mjs +1 -1
  73. package/tools/labels.mjs +1 -1
  74. package/tools/lcov.mjs +6 -10
  75. package/tools/lens-region.mjs +1 -1
  76. package/tools/manifest/validate.mjs +19 -0
  77. package/tools/migrate-adr-store.mjs +5 -5
  78. package/tools/orchestration-config.mjs +1 -1
  79. package/tools/orchestration-write.mjs +1 -1
  80. package/tools/presentation.mjs +1 -1
  81. package/tools/procedures.mjs +3 -3
  82. package/tools/recipes.mjs +62 -11
  83. package/tools/recommendations.mjs +97 -14
  84. package/tools/release-scan.mjs +10 -3
  85. package/tools/renderers.mjs +1 -1
  86. package/tools/review-state.mjs +291 -343
  87. package/tools/run-gates.mjs +216 -92
  88. package/tools/sandbox-masks.mjs +14 -14
  89. package/tools/semver-lite.mjs +1 -1
  90. package/tools/set-autonomy.mjs +1 -1
  91. package/tools/set-recipe.mjs +2 -2
  92. package/tools/setup-backends.mjs +1 -1
  93. package/tools/surface.mjs +1 -1
  94. package/tools/uninstall.mjs +1 -1
  95. package/tools/velocity-profile.mjs +9 -9
  96. package/tools/view-model.mjs +1 -1
  97. package/tools/worktrees.mjs +2292 -0
  98. package/references/modes/fold-completeness.md +0 -30
  99. package/references/modes/review-ledger.md +0 -34
  100. package/references/templates/verification-profile.json +0 -10
  101. package/tools/fold-completeness-run.mjs +0 -1120
  102. package/tools/fold-completeness.mjs +0 -672
  103. package/tools/review-ledger-core.mjs +0 -428
  104. package/tools/review-ledger-write.mjs +0 -647
  105. package/tools/review-ledger.mjs +0 -630
  106. package/tools/sarif.mjs +0 -52
  107. package/tools/verification-profile.mjs +0 -219
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ // coverage-check.mjs — the D3(c)+(d) final-run checker (strip-the-kit 2.3). ONE deterministic
3
+ // read-only check over two evidence sources:
4
+ // • COVERAGE (D3(d)): the suite leaves an lcov file at ONE FIXED kit-owned path —
5
+ // <git dir>/agent-workflow-lcov.info (inside the git dir it sits outside the fingerprint
6
+ // domain and is never committable by construction). Uncovered CHANGED executable Node lines
7
+ // are LISTED file:line and fail; a changed file ABSENT from the coverage map is a file-level
8
+ // red (never "non-executable by silence"); changed out-of-domain files are LISTED (the claim
9
+ // is narrowed to Node executable lines — stated, never silently widened or greened); a repo
10
+ // with NO lcov at the path is a LOUD `skipped-no-lcov`, never a silent green; the path is
11
+ // lstat'd no-follow — a symlink is a refusal.
12
+ // • RED PROOF (D3(c)): every authoritative red-proof record at the CURRENT base must hold NOW —
13
+ // the bound test file exists, its content hash is unchanged (custody), the testId resolves to
14
+ // >=1 test and runs GREEN N/N, and the record's PRE-FIX fingerprint differs from the current
15
+ // tree (an equal fingerprint means nothing changed since the red — reuse/forgery, refused).
16
+ // Deleted-test and zero-match guards ride the same bound set. A malformed evidence store
17
+ // fails CLOSED (the obligations are unknown).
18
+ // Every refusal names its locations (file:line / testId) — never a bare count.
19
+ //
20
+ // Read-only: never writes; spawns read-only git queries + the bound-test probes (node --test).
21
+ // Dependency-free, Node >= 22. No side effects on import.
22
+
23
+ import { lstatSync, readFileSync, realpathSync } from 'node:fs';
24
+ import { createHash } from 'node:crypto';
25
+ import { join } from 'node:path';
26
+ import { pathToFileURL } from 'node:url';
27
+ import { spawnSync } from 'node:child_process';
28
+ import { computeChangedSurface } from './changed-surface.mjs';
29
+ import { lcovCoveredMap, uncoveredChangedFromLcov } from './lcov.mjs';
30
+ import {
31
+ computeTreeFingerprint,
32
+ resolveBase,
33
+ resolveEvidencePath,
34
+ readEvidence,
35
+ authoritativeOfKind,
36
+ resolveTestFile,
37
+ hashFileBytes,
38
+ defaultBoundArgv,
39
+ parseProbeOutput,
40
+ childTestEnv,
41
+ probeKnobsFromEnv,
42
+ splitTestId,
43
+ } from './core-evidence.mjs';
44
+
45
+ export const COVERAGE_CHECK_STOP = 'COVERAGE_CHECK_STOP';
46
+ const usageFail = (message) => Object.assign(new Error(`[agent-workflow-kit] ${message}`), { exitCode: 2 });
47
+
48
+ export const LCOV_BASENAME = 'agent-workflow-lcov.info';
49
+
50
+ const GIT_MAX_BUFFER = 256 * 1024 * 1024;
51
+ const gitLine = (args, cwd) => {
52
+ const r = spawnSync('git', args, { cwd, maxBuffer: GIT_MAX_BUFFER, windowsHide: true });
53
+ if (r.error || r.status !== 0) return null;
54
+ return r.stdout.toString('utf8').replace(/\r?\n$/, '');
55
+ };
56
+
57
+ // AW_LCOV_FILE overrides (tests); else <git dir>/basename — resolved via the TRUE git dir (in a
58
+ // worktree `.git` is a FILE, a hardcoded path would ENOTDIR); null outside a git work tree.
59
+ export const resolveLcovPath = (cwd, env = process.env) => {
60
+ if (env.AW_LCOV_FILE) return env.AW_LCOV_FILE;
61
+ const gitDir = gitLine(['rev-parse', '--absolute-git-dir'], cwd);
62
+ return gitDir == null ? null : join(gitDir, LCOV_BASENAME);
63
+ };
64
+
65
+ // ── the coverage arm (D3(d)) ──────────────────────────────────────────────────────────────────────
66
+
67
+ // checkCoverage({ rootTop, lcovPath }) → { failures: ["file:line"...], notes: [...], skipped,
68
+ // lcovSha256 } — the sha is of the EXACT bytes this check consumed (read once), so the final-run
69
+ // receipt can bind what was CHECKED, not what happens to sit on disk later (M2, round 1).
70
+ const checkCoverage = ({ rootTop, lcovPath }) => {
71
+ const notes = [];
72
+ const failures = [];
73
+ let st = null;
74
+ try {
75
+ st = lstatSync(lcovPath);
76
+ } catch {
77
+ st = null;
78
+ }
79
+ if (st == null) {
80
+ return { failures, notes: [`skipped-no-lcov: no lcov file at ${lcovPath} — NO coverage check ran (produce it via the declared unit-tests gate cmd's lcov reporters)`], skipped: true, lcovSha256: null };
81
+ }
82
+ if (!st.isFile()) {
83
+ failures.push(`${lcovPath}: not a regular file (a symlink/device is never read — fail closed)`);
84
+ return { failures, notes, skipped: false, lcovSha256: null };
85
+ }
86
+ const surface = computeChangedSurface(rootTop);
87
+ const lcovBytes = readFileSync(lcovPath);
88
+ const lcovSha256 = createHash('sha256').update(lcovBytes).digest('hex');
89
+ const covered = lcovCoveredMap(lcovBytes.toString('utf8'), rootTop);
90
+ for (const [rel, lines] of surface.assessable) {
91
+ const key = keyFor(rootTop, rel);
92
+ const uncovered = uncoveredChangedFromLcov(covered, key, lines);
93
+ if (uncovered === null) {
94
+ failures.push(`${rel}: absent from coverage — the suite never executed this changed file`);
95
+ continue;
96
+ }
97
+ for (const n of uncovered) failures.push(`${rel}:${n}`);
98
+ }
99
+ if (surface.outOfDomain.length > 0) {
100
+ notes.push(`out-of-domain changed files (no Node-line coverage claim; covered by their own suites): ${surface.outOfDomain.join(', ')}`);
101
+ }
102
+ if (surface.unsupported.length > 0) {
103
+ notes.push(`unsupported-source changed files (outside the narrowed Node domain): ${surface.unsupported.join(', ')}`);
104
+ }
105
+ return { failures, notes, skipped: false, lcovSha256 };
106
+ };
107
+
108
+ // lcovCoveredMap canonicalizes SF paths with realpath — the lookup key must mirror it. On an
109
+ // unresolvable path (deleted between the surface pass and here) the lexical abs stands in, which
110
+ // then reads as absent-from-map → a file-level red, never a silent skip. Exported as a test seam.
111
+ export const keyFor = (rootTop, rel) => {
112
+ const abs = join(rootTop, rel);
113
+ try {
114
+ return realpathSync(abs);
115
+ } catch {
116
+ return abs;
117
+ }
118
+ };
119
+
120
+ // ── the red-proof verification arm (D3(c)) ────────────────────────────────────────────────────────
121
+
122
+ // verifyRedProofs({ rootTop, cwd, env }) → { failures: [...], verified: n } | { storeFailure }.
123
+ const verifyRedProofs = ({ rootTop, cwd, env }) => {
124
+ const storePath = resolveEvidencePath(cwd, env);
125
+ const read = storePath ? readEvidence(storePath) : { records: [], malformed: 0, malformedReasons: [] };
126
+ if ((read.malformed ?? 0) > 0 || read.readError) {
127
+ return { storeFailure: `evidence store unavailable (${read.malformed} malformed line(s)${read.readError ? `, read error: ${read.readError}` : ''}) — the red-proof obligations are unknown (fail closed); inspect ${storePath}` };
128
+ }
129
+ const base = resolveBase(cwd);
130
+ const fingerprint = computeTreeFingerprint(cwd);
131
+ const bound = authoritativeOfKind(read.records, 'red-proof').filter((r) => r.base === base);
132
+ const { reruns, timeoutS } = probeKnobsFromEnv(env);
133
+ const failures = [];
134
+ for (const record of bound) {
135
+ const id = record.testId;
136
+ if (record.fingerprint === fingerprint) {
137
+ failures.push(`${id}: the record's pre-fix fingerprint EQUALS the current tree — nothing changed since the observed red (reuse/forgery); re-observe on the pre-fix tree`);
138
+ continue;
139
+ }
140
+ const resolved = resolveTestFile(rootTop, record.file);
141
+ if (!resolved.ok) {
142
+ failures.push(`${id}: the bound test file ${resolved.reason.includes('does not exist') ? 'was deleted — ' : ''}${resolved.reason}`);
143
+ continue;
144
+ }
145
+ const currentHash = hashFileBytes(resolved.abs);
146
+ if (currentHash !== record.fileHash) {
147
+ failures.push(`${id}: the bound test file's content changed since the observed red (hash mismatch) — re-observe red at the new expectations`);
148
+ continue;
149
+ }
150
+ let greens = 0;
151
+ let executed = 0;
152
+ for (let i = 0; i < reruns; i += 1) {
153
+ const argv = defaultBoundArgv(resolved.abs, splitTestId(id).pattern);
154
+ const res = spawnSync(argv[0], argv.slice(1), { cwd: rootTop, env: childTestEnv(env), encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER, timeout: timeoutS * 1000 });
155
+ const p = parseProbeOutput({ stdout: res.stdout ?? '', code: res.error ? 1 : res.status ?? 1, fileArg: record.file });
156
+ executed = Math.max(executed, p.executed);
157
+ if (p.resolvable && p.baselineGreen) greens += 1;
158
+ }
159
+ if (executed === 0) {
160
+ failures.push(`${id}: the pattern selects no test (zero-match) — the declared red→green pin is gone`);
161
+ continue;
162
+ }
163
+ if (greens !== reruns) {
164
+ failures.push(`${id}: not green ${reruns}/${reruns} at the final run (${greens}/${reruns} green) — the declared fix is red`);
165
+ }
166
+ }
167
+ return { failures, verified: bound.length };
168
+ };
169
+
170
+ // ── the check ─────────────────────────────────────────────────────────────────────────────────────
171
+
172
+ export const runCheck = ({ cwd = process.cwd(), env = process.env } = {}) => {
173
+ const rootTop = gitLine(['rev-parse', '--show-toplevel'], cwd);
174
+ if (rootTop == null) return { code: 0, lines: ['coverage-check: not a git work tree — nothing to check'] };
175
+ const lcovPath = resolveLcovPath(cwd, env);
176
+ const lines = [];
177
+ let failed = false;
178
+ const cov = checkCoverage({ rootTop, lcovPath });
179
+ // The machine line the final-run receipt binds (M2): the sha of the exact bytes THIS check
180
+ // consumed — `none` states loudly that no lcov was read.
181
+ lines.push(`coverage-check: lcov-sha256=${cov.lcovSha256 ?? 'none'}`);
182
+ if (cov.skipped) {
183
+ lines.push(`coverage-check: ${cov.notes[0]}`);
184
+ } else {
185
+ for (const note of cov.notes) lines.push(`coverage-check: ${note}`);
186
+ if (cov.failures.length > 0) {
187
+ failed = true;
188
+ lines.push('coverage-check: FAIL — uncovered/unattributed changed Node lines:');
189
+ for (const f of cov.failures) lines.push(` ${f}`);
190
+ }
191
+ }
192
+ const red = verifyRedProofs({ rootTop, cwd, env });
193
+ if (red.storeFailure) {
194
+ failed = true;
195
+ lines.push(`coverage-check: FAIL — ${red.storeFailure}`);
196
+ } else {
197
+ if (red.failures.length > 0) {
198
+ failed = true;
199
+ lines.push('coverage-check: FAIL — red-proof obligations not satisfied:');
200
+ for (const f of red.failures) lines.push(` ${f}`);
201
+ } else if (red.verified > 0) {
202
+ lines.push(`coverage-check: ${red.verified} red-proof record(s) verified green N/N with custody intact`);
203
+ }
204
+ }
205
+ if (!failed && !cov.skipped && cov.failures.length === 0) {
206
+ lines.push('coverage-check: PASS — every changed Node line is covered');
207
+ }
208
+ return { code: failed ? 1 : 0, lines };
209
+ };
210
+
211
+ // ── CLI ───────────────────────────────────────────────────────────────────────────────────────────
212
+
213
+ const HELP = `coverage-check — the D3(c)+(d) final-run checker (agent-workflow family).
214
+
215
+ Usage:
216
+ node coverage-check.mjs --check [--cwd <dir>]
217
+
218
+ Reads the FIXED kit-owned lcov file (<git dir>/${LCOV_BASENAME}; AW_LCOV_FILE overrides —
219
+ produced by the declared unit-tests gate cmd's --experimental-test-coverage lcov reporters and
220
+ deleted fresh by run-gates --final), lists uncovered CHANGED executable Node lines file:line,
221
+ lists changed out-of-domain files (the coverage claim is narrowed to Node lines), and VERIFIES
222
+ every current-base red-proof record: bound test present, content hash unchanged, green N/N
223
+ (AW_CORE_EVIDENCE_RERUNS, default 3), pre-fix fingerprint differing from the current tree.
224
+ An absent lcov file is a LOUD skipped-no-lcov (exit 0 — NO coverage check ran, stated); a
225
+ symlinked lcov path, an uncovered line, a broken red-proof obligation, or a malformed evidence
226
+ store fails (exit 1).
227
+
228
+ Sandbox-safe: no network; writes nothing; spawns read-only git queries and the bound-test
229
+ probes (node --test, shell-free) — the D4 sandbox lane.
230
+ Read-only. Exit codes: 0 pass/skipped-loud; 1 fail; 2 usage.`;
231
+
232
+ export const main = (argv, ctx = {}) => {
233
+ const env = ctx.env ?? process.env;
234
+ try {
235
+ if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
236
+ let cwd = ctx.cwd ?? process.cwd();
237
+ const rest = [...argv];
238
+ const cwdAt = rest.indexOf('--cwd');
239
+ if (cwdAt !== -1) {
240
+ cwd = rest[cwdAt + 1];
241
+ if (cwd === undefined) throw usageFail('--cwd needs a directory');
242
+ rest.splice(cwdAt, 2);
243
+ }
244
+ const checkAt = rest.indexOf('--check');
245
+ if (checkAt !== -1) rest.splice(checkAt, 1);
246
+ if (rest.length > 0) throw usageFail(`unknown argument: ${rest[0]}`);
247
+ const { code, lines } = runCheck({ cwd, env });
248
+ return { code, stdout: lines.join('\n'), stderr: '' };
249
+ } catch (err) {
250
+ return { code: err.exitCode ?? 1, stdout: '', stderr: `coverage-check: ${err.message}` };
251
+ }
252
+ };
253
+
254
+ const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
255
+ if (isDirectRun) {
256
+ const r = main(process.argv.slice(2));
257
+ if (r.stdout) process.stdout.write(r.stdout.endsWith('\n') ? r.stdout : `${r.stdout}\n`);
258
+ if (r.stderr) process.stderr.write(r.stderr.endsWith('\n') ? r.stderr : `${r.stderr}\n`);
259
+ process.exitCode = r.code;
260
+ }
@@ -6,7 +6,7 @@
6
6
  // detectMemory(dir) → { delegate, reason, ... } runs the kit's OWN validator + asset check
7
7
  // handoffPlan(delegate) → who writes what, which stamps end up present, who owns the commit gate
8
8
  //
9
- // Pure (dependency-injectable validator + fs), dependency-free, Node >= 18.
9
+ // Pure (dependency-injectable validator + fs), dependency-free, Node >= 22.
10
10
 
11
11
  import { statSync } from 'node:fs';
12
12
  import { join, resolve } from 'node:path';
@@ -16,7 +16,7 @@
16
16
  // credential marker, stable setup URL) must live here. A drift-guard test keeps the registry in
17
17
  // lockstep with the in-repo manifests.
18
18
  //
19
- // Pure, dependency-injectable (fs/env/validator are deps), dependency-free, Node >= 18. Every fs
19
+ // Pure, dependency-injectable (fs/env/validator are deps), dependency-free, Node >= 22. Every fs
20
20
  // probe is wrapped → an explicit `unknown` + reason, never a throw and never a nameless failure.
21
21
 
22
22
  import { existsSync, statSync, accessSync, realpathSync, constants } from 'node:fs';
@@ -91,7 +91,7 @@ const RAW_BACKENDS = [
91
91
  ],
92
92
  grounding: 'automatic — the wrapper precomputes the full working-tree change set (repo map, status, diffs, untracked contents) and codex auto-merges the root AGENTS.md; no grounding flags',
93
93
  continue: [],
94
- receipt: 'side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit\'s review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); a write failure warns, never fails the review',
94
+ receipt: 'side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit\'s review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized \'Verdict: <ship|revise|rethink>\' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); a write failure warns, never fails the review',
95
95
  },
96
96
  },
97
97
  bin: 'codex',
@@ -125,7 +125,7 @@ const RAW_BACKENDS = [
125
125
  'agy-review --continue [--decided @f] [--focus "…"]',
126
126
  'agy-review --conversation <id> [--decided @f] [--focus "…"]',
127
127
  ],
128
- receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); a write failure warns, never fails the review",
128
+ receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); a write failure warns, never fails the review",
129
129
  notes: [
130
130
  'pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt',
131
131
  ],
@@ -7,25 +7,21 @@
7
7
  // asserts the current value renders into every bound `references/modes/*.md` file.
8
8
  //
9
9
  // Why the modes/*.md docs and not the tool HELP strings: every tool's HELP INTERPOLATES the same
10
- // constant (`the ${DEFAULT_DIFF_CAP}-line diff cap`), so it can never drift from the code — there is
11
- // nothing to check there. The hand-authored contract prose in `references/modes/*.md` is the surface
12
- // that DOES drift, so that is exactly what this lint pins. The numeric/version tokens are IMPORTED
13
- // live from the tools (never re-typed here), and the ledger vocabulary is sourced from the schema's
14
- // own exported `V4_CLASSES` / `V4_OVERRIDE_SCOPES` Sets — so the registry cannot itself go stale.
10
+ // constant, so it can never drift from the code — there is nothing to check there. The
11
+ // hand-authored contract prose in `references/modes/*.md` is the surface that DOES drift, so that
12
+ // is exactly what this lint pins. The tokens are IMPORTED live from the tools (never re-typed
13
+ // here) so the registry cannot itself go stale.
15
14
  //
16
15
  // Edit-safe by construction (the U2-DEBT closed-world lesson): adding a binding ADDS a checked entry;
17
16
  // it never widens a blocklist. A token that stops appearing, a file that cannot be read, or an
18
17
  // unknown binding all FAIL CLOSED — never a silent pass.
19
18
  //
20
19
  // Read-only: never writes, never commits, never runs a subscription CLI, spawns nothing. Dependency-
21
- // free, Node >= 18. No side effects on import (the isDirectRun idiom).
20
+ // free, Node >= 22. No side effects on import (the isDirectRun idiom).
22
21
 
23
22
  import { readFileSync } from 'node:fs';
24
23
  import { dirname, resolve } from 'node:path';
25
24
  import { fileURLToPath, pathToFileURL } from 'node:url';
26
- import { SCHEMA_VERSION, REVIEW_CAP, V4_CLASSES, V4_OVERRIDE_SCOPES } from './review-ledger.mjs';
27
- import { HARD_MAX, DEFAULT_DIFF_CAP } from './review-ledger-write.mjs';
28
- import { RESULT_SCHEMA_VERSION } from './fold-completeness.mjs';
29
25
  import { EXIT as DOCTOR_EXIT, STATUS as DOCTOR_STATUS, TRUSTED_DIRS as DOCTOR_TRUSTED_DIRS } from './autonomy-doctor.mjs';
30
26
  import {
31
27
  RECOMMENDATIONS_SECTION_HEADER,
@@ -40,8 +36,6 @@ import { SKIPPED_READONLY } from './setup-backends.mjs';
40
36
 
41
37
  const KIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
42
38
 
43
- const REVIEW_LEDGER_DOC = 'references/modes/review-ledger.md';
44
- const FOLD_DOC = 'references/modes/fold-completeness.md';
45
39
  const AUTONOMY_DOCTOR_DOC = 'references/modes/autonomy-doctor.md';
46
40
  const RECOMMENDATIONS_DOC = 'references/modes/recommendations.md';
47
41
  const UPGRADE_DOC = 'references/modes/upgrade.md';
@@ -57,10 +51,6 @@ const usageFail = (message) => Object.assign(new Error(message), { exitCode: 2 }
57
51
  // prose in the named files; a value drift makes the current-value token absent → a loud failure.
58
52
  const valueBinding = (constant, value, phrase, files) => ({ constant, value, token: phrase, files });
59
53
 
60
- // The ledger vocabulary — sourced from the schema's own exported Sets plus the v4 `gate-run` kind,
61
- // so the closed set can never disagree with the code. Every word must appear in the ledger contract.
62
- const LEDGER_VOCAB = [...V4_CLASSES, ...V4_OVERRIDE_SCOPES, 'gate-run'];
63
-
64
54
  // The autonomy-doctor D7 contract (AD-044 Plan 2): the live EXIT table + every status token must
65
55
  // render into the mode's contract doc. `usage` is skipped as a bare-word token (trivially present
66
56
  // everywhere) — its exit-code phrase below pins that outcome instead.
@@ -75,12 +65,6 @@ const DOCTOR_EXIT_PHRASES = [
75
65
  ];
76
66
 
77
67
  export const BINDINGS = Object.freeze([
78
- valueBinding('SCHEMA_VERSION', SCHEMA_VERSION, `schema v${SCHEMA_VERSION}`, [REVIEW_LEDGER_DOC]),
79
- valueBinding('HARD_MAX', HARD_MAX, `hard-max ceiling of ${HARD_MAX}`, [REVIEW_LEDGER_DOC]),
80
- valueBinding('DEFAULT_DIFF_CAP', DEFAULT_DIFF_CAP, `default ${DEFAULT_DIFF_CAP}`, [REVIEW_LEDGER_DOC]),
81
- valueBinding('REVIEW_CAP', REVIEW_CAP, `cap ≤${REVIEW_CAP}`, [REVIEW_LEDGER_DOC]),
82
- valueBinding('RESULT_SCHEMA_VERSION', RESULT_SCHEMA_VERSION, `schema v${RESULT_SCHEMA_VERSION}`, [FOLD_DOC]),
83
- ...LEDGER_VOCAB.map((word) => valueBinding(`vocab:${word}`, word, word, [REVIEW_LEDGER_DOC])),
84
68
  ...DOCTOR_EXIT_PHRASES.map(([key, phrase]) => valueBinding(`doctor-exit:${key}`, DOCTOR_EXIT[key], phrase, [AUTONOMY_DOCTOR_DOC])),
85
69
  ...Object.values(DOCTOR_STATUS)
86
70
  .filter((token) => token !== DOCTOR_STATUS.usage)
@@ -149,12 +133,12 @@ const HELP = `doc-parity — deterministic doc-drift lint for the agent-workflow
149
133
  Usage:
150
134
  node doc-parity.mjs [--check | --json]
151
135
 
152
- A CLOSED, exported registry binds each live code constant (review-ledger SCHEMA_VERSION / REVIEW_CAP,
153
- review-ledger-write HARD_MAX / DEFAULT_DIFF_CAP, fold-completeness RESULT_SCHEMA_VERSION), the
154
- ledger vocabulary (V4_CLASSES / V4_OVERRIDE_SCOPES + gate-run), and the autonomy-doctor contract
155
- (the EXIT table, the status tokens, the trusted-dir allowlist) to the exact token its
156
- references/modes/*.md contract must carry, and asserts the CURRENT value renders into every bound
157
- file. A drifted doc, an unreadable bound file, or an absent token FAILS CLOSED.
136
+ A CLOSED, exported registry binds each live code constant — the autonomy-doctor contract (the EXIT
137
+ table, the status tokens, the trusted-dir allowlist), the recommendations/upgrade presentation
138
+ contract (section header, empty line, verdict templates), the acks-store path, and the setup
139
+ refresh degrade token to the exact token its references/modes/*.md contract must carry, and
140
+ asserts the CURRENT value renders into every bound file. A drifted doc, an unreadable bound file,
141
+ or an absent token FAILS CLOSED.
158
142
 
159
143
  --check exits 0/1 as a gate (declare it in docs/ai/gates.json by hand). --json prints the structured
160
144
  result. Default prints the per-binding report.
@@ -11,7 +11,7 @@
11
11
  // Fail-closed: readEngineFragment never falls back to a bundled copy (there is none after the
12
12
  // mirror retirement) — when the engine is needed but absent/invalid it throws with the exact
13
13
  // remediation, so the reconcile STOPs loudly rather than silently dropping the slot (AGENTS.md:
14
- // no silent failures). Pure-where-possible (fs + validator injectable for tests), Node >= 18.
14
+ // no silent failures). Pure-where-possible (fs + validator injectable for tests), Node >= 22.
15
15
 
16
16
  import { statSync, readFileSync } from 'node:fs';
17
17
  import { join } from 'node:path';
@@ -8,7 +8,7 @@
8
8
  // + the drift-guard (family-registry.test.mjs pins FAMILY_MEMBERS to the 5 in-repo capability.json)
9
9
  // are preserved — the table just lives in a dependency-free leaf now.
10
10
  //
11
- // Pure data, no imports, no side effects, Node >= 18.
11
+ // Pure data, no imports, no side effects, Node >= 22.
12
12
 
13
13
  // ── the unified registry ───────────────────────────────────────────────────────
14
14
  // One entry per family member. `installed` is the detect.installed spec (env + home-relative default
@@ -13,7 +13,7 @@
13
13
  // must live here. A drift-guard test (family-registry.test.mjs) pins FAMILY_MEMBERS to the 5 in-repo
14
14
  // capability.json files, so the table cannot silently drift from the manifests it mirrors.
15
15
  //
16
- // Pure, dependency-injectable (fs/env/home/validator are deps), dependency-free, Node >= 18. No
16
+ // Pure, dependency-injectable (fs/env/home/validator are deps), dependency-free, Node >= 22. No
17
17
  // side effects on import (the isDirectRun idiom) — tests import the helpers with nothing run.
18
18
 
19
19
  import { existsSync, statSync, readFileSync, lstatSync } from 'node:fs';
package/tools/fs-safe.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  // (bin/install.mjs) and the backend linker (tools/setup-backends.mjs). Importing this module has NO
3
3
  // side effects: it runs nothing. Every fs primitive is injectable via `deps.*` so the guards are
4
4
  // unit-testable without touching the real filesystem; the defaults are Node's SYNC fs (matching the
5
- // tools/ detector style). Dependency-free, Node >= 18.
5
+ // tools/ detector style). Dependency-free, Node >= 22.
6
6
  //
7
7
  // Five primitives:
8
8
  // assertContainedRealPath — refuse to write through/into a symlink, or to a dest outside a root.
@@ -36,7 +36,7 @@
36
36
  // gates.json — see the read-lane writer section below.
37
37
  //
38
38
  // Exit codes: 0 done / dry-run (incl. the report-only diverged-but-wired state); 1 precondition
39
- // STOP; 2 usage. Dependency-free beyond the kit's own internal exports, Node >= 18. No side
39
+ // STOP; 2 usage. Dependency-free beyond the kit's own internal exports, Node >= 22. No side
40
40
  // effects on import.
41
41
 
42
42
  import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';