@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
@@ -17,7 +17,7 @@
17
17
  //
18
18
  // Source of truth = the COMMANDS table below; a drift-guard test (commands.test.mjs) pins its keys to
19
19
  // the `### Mode:` headers in SKILL.md, so the catalog cannot silently drift from the documented modes.
20
- // Pure, dependency-free, Node >= 18. No side effects on import (the isDirectRun idiom).
20
+ // Pure, dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
21
21
 
22
22
  import { pathToFileURL } from 'node:url';
23
23
 
@@ -110,7 +110,7 @@ const CATALOG = [
110
110
  invocation: invocationOf('gates'),
111
111
  group: 'Inspect',
112
112
  kind: PROJECT_EXEC,
113
- oneLine: 'Run the project’s own declared gate commands (docs/ai/gates.json) as one batch — a PASS/FAIL table, one summary line. Writes nothing by default; opt-in --record mints a gate-run record via the review-ledger writer (the segment’s green-baseline receipt). A separate consent-gated seeder can propose entries from your project’s own scripts (preview first, written only on your yes).',
113
+ oneLine: 'Run the project’s own declared gate commands (docs/ai/gates.json) as one batch — a PASS/FAIL table, one summary line. Writes nothing by default; --final runs the FULL matrix as the final verification run and mints the ONE receipt the commit guard consumes (every attempt recorded, green or red). A separate consent-gated init preview can propose entries from your project’s own scripts (written only on your yes).',
114
114
  },
115
115
  {
116
116
  key: 'setup',
@@ -204,25 +204,41 @@ const CATALOG = [
204
204
  oneLine: 'Assemble the verified-facts payload a grounded review runs against — the entry-point Hard Constraints, the effective autonomy policy, plus a plan’s decision sections; prints it, or writes ONE scratch file with --out.',
205
205
  },
206
206
  {
207
- key: 'review-ledger',
208
- invocation: invocationOf('review-ledger'),
207
+ key: 'core-evidence',
208
+ invocation: invocationOf('core-evidence'),
209
209
  group: 'Orchestrate',
210
210
  kind: WRITER,
211
- oneLine: 'Record each review round, its triage, and recorded overrides (oracle-change / red-proof / size-cap waivers) and read the computed crossover-stop for the plan-execution loop — per SEGMENT since v4 (base = HEAD; caps and teeth reset only at a gated commit; class refuted is the honest phantom lane); --check turns it into a gate exit code, --telemetry renders counts-only gate-efficacy data.',
211
+ oneLine: 'Declare loop evidence into the ONE git-dir store an observed-red proof for a bugfix test (red-proof, minted BEFORE the fix) or an explicit per-backend review degrade for the current tree; `summary` renders the whole loop state statelessly from the receipts + the store.',
212
212
  },
213
213
  {
214
- key: 'fold-completeness',
215
- invocation: invocationOf('fold-completeness'),
214
+ key: 'coverage-check',
215
+ invocation: invocationOf('coverage-check'),
216
216
  group: 'Orchestrate',
217
- kind: WRITER,
218
- oneLine: 'Verify the review loop’s folded fixes are pinned by HONEST tests — every changed executable line executed, and each bound test carries an observed-red receipt (--red, minted BEFORE the fix), N/N-green probes, and content custody (waivable per-testId only by a recorded red-proof override), over a test surface whose tampered files carry recorded oracle-change overrides; SEGMENT-scoped since v3 (a committed phase’s custody obligations close with its commit); --check turns the result into a gate exit code.',
217
+ kind: PROJECT_EXEC,
218
+ oneLine: 'The final-run checker: every CHANGED executable Node line must be covered by the lcov the declared unit-tests gate produced (uncovered lines listed file:line), and every current-base red-proof declaration must verify (bound test green N/N, content custody intact); --check turns it into a gate exit code.',
219
+ },
220
+ {
221
+ key: 'commit-guard',
222
+ invocation: invocationOf('commit-guard'),
223
+ group: 'Orchestrate',
224
+ kind: READ_ONLY,
225
+ oneLine: 'The read-only pre-commit guard: binds the LATEST completed run-gates --final receipt to the CURRENT tree — refusing on any fingerprint, declaration, evidence-hash, or lcov drift, a dangling later attempt, or unsatisfied review obligations; re-runs no gate or test.',
219
226
  },
220
227
  {
221
228
  key: 'doc-parity',
222
229
  invocation: invocationOf('doc-parity'),
223
230
  group: 'Orchestrate',
224
231
  kind: READ_ONLY,
225
- oneLine: 'Check that the documented contract values (review caps, schema versions, ledger vocabulary) still match the live code constants they describe — a read-only lint that fails closed on drift; --check turns it into a gate exit code.',
232
+ oneLine: 'Check that the documented contract tokens still match the live code constants they describe — a read-only lint that fails closed on drift; --check turns it into a gate exit code.',
233
+ },
234
+ {
235
+ // NEVER `guarded` — that kind promises dry-run-first, which these writers do not have; the
236
+ // honest strongest caution is `writer` with the destructive arm named in the line itself.
237
+ key: 'worktrees',
238
+ invocation: invocationOf('worktrees'),
239
+ group: 'Orchestrate',
240
+ kind: WRITER,
241
+ oneLine: 'Run features in parallel git worktrees: provision an isolated sibling copy, list them, stage a finished one back onto clean main (the commit still asks in dialogue), and remove a live-verified landed one. No preview step; list is read-only; cleanup --abandon destroys unlanded work.',
226
242
  },
227
243
  ];
228
244
 
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ // commit-guard.mjs — the read-only pre-commit guard (strip-the-kit 2.5, D10). It re-runs NO
3
+ // gate/test subprocess: the heavy D3(b)/(c)/(d) verification lives in `run-gates --final`, whose
4
+ // receipt this guard binds. `--check`:
5
+ // 1. recomputes the CURRENT tree fingerprint (the review-state export — read-only git plumbing);
6
+ // 2. reads the LATEST completed final-run record from the core-evidence store (only the latest
7
+ // attempt at a fingerprint is authoritative — a green receipt is DEAD once a later attempt at
8
+ // the same fingerprint went red) and refuses on: no record for the current fingerprint · a
9
+ // red record · fingerprintBefore ≠ fingerprintAfter · a LATER final-start with no completion
10
+ // (an attempt of unknown outcome) · a DECLARATION whose current {id, cmd} content differs
11
+ // from the recorded one · evidence hashes that no longer match the store's canonical
12
+ // red-proof/degrade serializations · an lcov file whose sha moved. The guard's own reads
13
+ // resolve FIXED git-dir paths (env overrides are producer test seams, never guard inputs);
14
+ // 3. re-computes the review-state decision (the ship-receipt arm) — a missing/vetoed ship
15
+ // receipt refuses.
16
+ // `git commit --no-verify` stays the stated residual (a self-discipline mechanism, not a security
17
+ // boundary). Read-only; dependency-free; Node >= 22. No side effects on import.
18
+
19
+ import { readFileSync, lstatSync } from 'node:fs';
20
+ import { resolve } from 'node:path';
21
+ import { pathToFileURL } from 'node:url';
22
+ import { spawnSync } from 'node:child_process';
23
+ import { createHash } from 'node:crypto';
24
+ import { computeTreeFingerprint, buildState, decideCheck } from './review-state.mjs';
25
+ import { resolveEvidencePath, readEvidence, authoritativeOfKind, canonicalKindSerialization } from './core-evidence.mjs';
26
+ import { resolveLcovPath } from './coverage-check.mjs';
27
+ import { GATES_REL, loadDeclaration } from './run-gates.mjs';
28
+
29
+ const usageFail = (message) => Object.assign(new Error(`[agent-workflow-kit] ${message}`), { exitCode: 2 });
30
+ const sha = (text) => createHash('sha256').update(text).digest('hex');
31
+
32
+ const GIT_MAX_BUFFER = 256 * 1024 * 1024;
33
+ const gitLine = (args, cwd) => {
34
+ const r = spawnSync('git', args, { cwd, maxBuffer: GIT_MAX_BUFFER, windowsHide: true });
35
+ if (r.error || r.status !== 0) return null;
36
+ return r.stdout.toString('utf8').replace(/\r?\n$/, '');
37
+ };
38
+
39
+ // resolveGitHooksPath(projectDir) → the ABSOLUTE hooks dir git itself reports (a linked worktree
40
+ // answers with ITS OWN hooks path — never a hardcoded `.git/hooks`), or null outside a git tree.
41
+ // The one home consumers (the recommendations guard-install probe) read instead of re-deriving it.
42
+ export const resolveGitHooksPath = (projectDir) => {
43
+ const line = gitLine(['rev-parse', '--git-path', 'hooks'], projectDir);
44
+ return line == null ? null : resolve(projectDir, line);
45
+ };
46
+
47
+ // runGuard({ cwd, env }) → { code, lines }. Every refusal names its recovery.
48
+ export const runGuard = ({ cwd = process.cwd(), env = process.env } = {}) => {
49
+ const rootTop = gitLine(['rev-parse', '--show-toplevel'], cwd);
50
+ if (rootTop == null) return { code: 1, lines: ['commit-guard: not a git work tree — nothing to guard'] };
51
+ const fingerprint = computeTreeFingerprint(cwd);
52
+ // The guard's OWN reads resolve FIXED git-dir paths — a stray AW_CORE_EVIDENCE / AW_LCOV_FILE
53
+ // in the committing shell must never redirect the LAST line of defense to a forged artifact
54
+ // (the env stays a test seam for the producers, never for this consumer).
55
+ const storePath = resolveEvidencePath(cwd, {});
56
+ const read = storePath ? readEvidence(storePath) : { records: [], malformed: 0 };
57
+ if ((read.malformed ?? 0) > 0 || read.readError) {
58
+ return { code: 1, lines: [`commit-guard: REFUSED — evidence store unavailable (${read.malformed} malformed line(s)${read.readError ? `, read error: ${read.readError}` : ''}); inspect ${storePath}`] };
59
+ }
60
+ const finals = authoritativeOfKind(read.records, 'final');
61
+ const current = finals.find((r) => r.fingerprintBefore === fingerprint) ?? null;
62
+ if (!current) {
63
+ return { code: 1, lines: [`commit-guard: REFUSED — no completed final-run record for the current tree fingerprint (${fingerprint.slice(0, 12)}…). Stage everything, run the required reviews, then: node agent-workflow-kit/tools/run-gates.mjs --final — and commit immediately (any edit after the final run re-stales it)`] };
64
+ }
65
+ if (current.status !== 'green') {
66
+ return { code: 1, lines: ['commit-guard: REFUSED — the LATEST completed final attempt at this fingerprint is RED (a dead green never revives); fix the failing gates and re-run run-gates.mjs --final'] };
67
+ }
68
+ if (current.fingerprintAfter !== current.fingerprintBefore) {
69
+ return { code: 1, lines: ['commit-guard: REFUSED — the tree moved UNDER the final run (fingerprint before ≠ after); re-run run-gates.mjs --final on a quiescent tree'] };
70
+ }
71
+ // A dangling LATER attempt: a final-start at this fingerprint appended AFTER the latest
72
+ // completion, whose own completion never landed (interrupted run / failed receipt append) —
73
+ // the green above cannot stand for an attempt whose outcome is unknown. Scoped to
74
+ // after-the-latest-completion so an old dead start never bricks recovery: the recovery IS
75
+ // re-running --final, whose completion closes its own start and becomes the new latest.
76
+ let lastFinalIdx = -1;
77
+ read.records.forEach((r, i) => {
78
+ if (r.kind === 'final' && r.fingerprintBefore === fingerprint) lastFinalIdx = i;
79
+ });
80
+ const completedAttempts = new Set(read.records.filter((r) => r.kind === 'final').map((r) => r.attempt));
81
+ const dangling = read.records.some(
82
+ (r, i) => r.kind === 'final-start' && r.fingerprint === fingerprint && i > lastFinalIdx && !completedAttempts.has(r.attempt),
83
+ );
84
+ if (dangling) {
85
+ return { code: 1, lines: ['commit-guard: REFUSED — a later final attempt started and never completed (interrupted, or its receipt failed to write); re-run run-gates.mjs --final'] };
86
+ }
87
+ const declaration = loadDeclaration(rootTop);
88
+ if (declaration.outcome !== 'loaded') {
89
+ return { code: 1, lines: [`commit-guard: REFUSED — no readable gate declaration at ${GATES_REL}`] };
90
+ }
91
+ const currentDeclared = declaration.gates.map(({ id, cmd }) => ({ id, cmd }));
92
+ if (JSON.stringify(currentDeclared) !== JSON.stringify(current.declared)) {
93
+ return { code: 1, lines: [`commit-guard: REFUSED — the gate declaration changed after the final run (${GATES_REL} no longer matches the receipt's recorded {id, cmd} content); re-run run-gates.mjs --final`] };
94
+ }
95
+ const wantHashes = {
96
+ redProof: sha(canonicalKindSerialization(read.records, 'red-proof')),
97
+ degrade: sha(canonicalKindSerialization(read.records, 'degrade')),
98
+ };
99
+ if (wantHashes.redProof !== current.evidenceHashes.redProof || wantHashes.degrade !== current.evidenceHashes.degrade) {
100
+ return { code: 1, lines: ['commit-guard: REFUSED — the evidence store moved under the receipt (canonical red-proof/degrade hashes no longer match); re-run run-gates.mjs --final'] };
101
+ }
102
+ if (current.lcovSha256 !== null) {
103
+ const lcovPath = resolveLcovPath(cwd, {}); // the SAME fixed resolution the checker defaults to — env ignored here
104
+ let lcovNow = null;
105
+ try {
106
+ if (lstatSync(lcovPath).isFile()) lcovNow = sha(readFileSync(lcovPath));
107
+ } catch { /* absent → mismatch below */ }
108
+ if (lcovNow !== current.lcovSha256) {
109
+ return { code: 1, lines: ['commit-guard: REFUSED — the lcov file the receipt consumed moved or vanished; re-run run-gates.mjs --final'] };
110
+ }
111
+ }
112
+ // The ship-receipt arm: the SAME normative decision review-state --check computes (configured
113
+ // obligations, ship-class-only, veto, degrade escape) — a file-read recompute, no subprocess —
114
+ // over a SANITIZED env: the store overrides are producer test seams, and honoring them HERE
115
+ // would let a forged receipts/degrade store bypass the fixed-path reads above.
116
+ const reviewEnv = { ...env };
117
+ delete reviewEnv.AW_REVIEW_RECEIPTS;
118
+ delete reviewEnv.AW_CORE_EVIDENCE;
119
+ const review = decideCheck(buildState({ cwd, env: reviewEnv }));
120
+ if (review.code !== 0) {
121
+ return { code: 1, lines: [`commit-guard: REFUSED — the review obligations are not satisfied: ${review.reason}`] };
122
+ }
123
+ return { code: 0, lines: [`commit-guard: PASS — a green final receipt binds this exact tree (${fingerprint.slice(0, 12)}…), the declaration and evidence hashes match, and the review obligations are satisfied`] };
124
+ };
125
+
126
+ const HELP = `commit-guard — the read-only pre-commit guard (agent-workflow family, D10).
127
+
128
+ Usage:
129
+ node commit-guard.mjs --check [--cwd <dir>]
130
+
131
+ Re-runs NOTHING: recomputes the current tree fingerprint and binds the LATEST completed
132
+ run-gates --final receipt — refusing on { no receipt for this tree · a red latest attempt ·
133
+ before≠after · declaration content drift · evidence-hash drift · lcov drift · unsatisfied review
134
+ obligations (the review-state decision) }. Wire it into pre-commit; \`git commit --no-verify\`
135
+ stays the stated residual (self-discipline, not a security boundary).
136
+
137
+ Exit codes: 0 pass; 1 refused (reason named); 2 usage.`;
138
+
139
+ export const main = (argv, ctx = {}) => {
140
+ const env = ctx.env ?? process.env;
141
+ try {
142
+ if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
143
+ let cwd = ctx.cwd ?? process.cwd();
144
+ const rest = [...argv];
145
+ const cwdAt = rest.indexOf('--cwd');
146
+ if (cwdAt !== -1) {
147
+ cwd = rest[cwdAt + 1];
148
+ if (cwd === undefined) throw usageFail('--cwd needs a directory');
149
+ rest.splice(cwdAt, 2);
150
+ }
151
+ const checkAt = rest.indexOf('--check');
152
+ if (checkAt !== -1) rest.splice(checkAt, 1);
153
+ if (rest.length > 0) throw usageFail(`unknown argument: ${rest[0]}`);
154
+ const { code, lines } = runGuard({ cwd, env });
155
+ return { code, stdout: lines.join('\n'), stderr: '' };
156
+ } catch (err) {
157
+ return { code: err.exitCode ?? 1, stdout: '', stderr: `commit-guard: ${err.message}` };
158
+ }
159
+ };
160
+
161
+ const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
162
+ if (isDirectRun) {
163
+ const r = main(process.argv.slice(2));
164
+ if (r.stdout) process.stdout.write(r.stdout.endsWith('\n') ? r.stdout : `${r.stdout}\n`);
165
+ if (r.stderr) process.stderr.write(r.stderr.endsWith('\n') ? r.stderr : `${r.stderr}\n`);
166
+ process.exitCode = r.code;
167
+ }