@sabaiway/agent-workflow-kit 2.0.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.
- package/CHANGELOG.md +74 -0
- package/README.md +6 -5
- package/SKILL.md +12 -8
- package/bin/install.mjs +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +16 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +150 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +131 -23
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +193 -15
- package/bridges/antigravity-cli-bridge/capability.json +12 -8
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +6 -2
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +3 -2
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +1 -1
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +143 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +93 -10
- package/bridges/codex-cli-bridge/capability.json +3 -2
- package/capability.json +1 -1
- package/migrations/3.0.0-hardened-core-loop.md +44 -0
- package/migrations/README.md +1 -1
- package/package.json +2 -2
- package/references/hooks/gate-approve.mjs +1 -1
- package/references/modes/bootstrap.md +3 -3
- package/references/modes/commit-guard.md +19 -0
- package/references/modes/core-evidence.md +15 -0
- package/references/modes/coverage-check.md +12 -0
- package/references/modes/doc-parity.md +7 -6
- package/references/modes/gates.md +7 -6
- package/references/modes/grounding.md +1 -2
- package/references/modes/hook.md +1 -1
- package/references/modes/review-state.md +3 -3
- package/references/modes/upgrade.md +6 -6
- package/references/modes/velocity.md +2 -2
- package/references/scripts/archive-decisions.mjs +1 -1
- package/references/scripts/install-git-hooks-repo-exec.test.mjs +82 -0
- package/references/scripts/install-git-hooks.mjs +90 -18
- package/references/scripts/install-git-hooks.test.mjs +102 -0
- package/references/scripts/migrate-gates-branches.test.mjs +157 -0
- package/references/scripts/migrate-gates.mjs +395 -0
- package/references/scripts/migrate-gates.test.mjs +284 -0
- package/references/templates/agent_rules.md +2 -2
- package/tools/ack-write.mjs +1 -1
- package/tools/atomic-write.mjs +2 -2
- package/tools/autonomy-config.mjs +1 -1
- package/tools/autonomy-doctor.mjs +1 -1
- package/tools/autonomy-write.mjs +1 -1
- package/tools/bridge-settings-read.mjs +1 -1
- package/tools/bridge-settings.mjs +1 -1
- package/tools/changed-surface.mjs +8 -69
- package/tools/cheap-agents.mjs +2 -2
- package/tools/commands.mjs +17 -10
- package/tools/commit-guard.mjs +167 -0
- package/tools/core-evidence.mjs +914 -0
- package/tools/coverage-check.mjs +260 -0
- package/tools/delegation.mjs +1 -1
- package/tools/detect-backends.mjs +7 -6
- package/tools/doc-parity.mjs +11 -27
- package/tools/engine-source.mjs +1 -1
- package/tools/family-members.mjs +1 -1
- package/tools/family-registry.mjs +1 -1
- package/tools/fs-safe.mjs +1 -1
- package/tools/gate-hook.mjs +1 -1
- package/tools/{seed-gates.mjs → gates-init.mjs} +58 -52
- package/tools/grounding.mjs +6 -83
- package/tools/hide-footprint.mjs +1 -1
- package/tools/inject-methodology.mjs +1 -1
- package/tools/known-footprint.mjs +1 -1
- package/tools/labels.mjs +1 -1
- package/tools/lcov.mjs +6 -10
- package/tools/lens-region.mjs +1 -1
- package/tools/manifest/validate.mjs +19 -0
- package/tools/migrate-adr-store.mjs +2 -2
- package/tools/orchestration-config.mjs +1 -1
- package/tools/orchestration-write.mjs +1 -1
- package/tools/presentation.mjs +1 -1
- package/tools/procedures.mjs +2 -2
- package/tools/recipes.mjs +60 -9
- package/tools/recommendations.mjs +63 -7
- package/tools/release-scan.mjs +1 -1
- package/tools/renderers.mjs +1 -1
- package/tools/review-state.mjs +288 -340
- package/tools/run-gates.mjs +216 -92
- package/tools/sandbox-masks.mjs +1 -1
- package/tools/semver-lite.mjs +1 -1
- package/tools/set-autonomy.mjs +1 -1
- package/tools/set-recipe.mjs +1 -1
- package/tools/setup-backends.mjs +1 -1
- package/tools/surface.mjs +1 -1
- package/tools/uninstall.mjs +1 -1
- package/tools/velocity-profile.mjs +2 -2
- package/tools/view-model.mjs +1 -1
- package/references/modes/fold-completeness.md +0 -30
- package/references/modes/review-ledger.md +0 -34
- package/references/templates/verification-profile.json +0 -10
- package/tools/fold-completeness-run.mjs +0 -1120
- package/tools/fold-completeness.mjs +0 -672
- package/tools/review-ledger-core.mjs +0 -428
- package/tools/review-ledger-write.mjs +0 -647
- package/tools/review-ledger.mjs +0 -630
- package/tools/sarif.mjs +0 -52
- package/tools/verification-profile.mjs +0 -219
|
@@ -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
|
+
}
|