@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.
- package/CHANGELOG.md +94 -1
- package/README.md +7 -5
- package/SKILL.md +16 -8
- package/bin/install.mjs +11 -2
- package/bridges/antigravity-cli-bridge/SKILL.md +8 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +163 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +76 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +80 -15
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +171 -22
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +59 -41
- package/bridges/antigravity-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +67 -22
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +156 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +158 -27
- 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/recommendations.md +1 -0
- package/references/modes/review-state.md +3 -3
- package/references/modes/upgrade.md +6 -6
- package/references/modes/velocity.md +2 -2
- package/references/modes/worktrees.md +120 -0
- package/references/scripts/archive-decisions.mjs +3 -3
- package/references/scripts/archive-decisions.test.mjs +5 -5
- package/references/scripts/check-docs-size-cli.test.mjs +41 -0
- package/references/scripts/check-docs-size.mjs +46 -29
- 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 +2 -2
- 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 +16 -77
- package/tools/cheap-agents.mjs +2 -2
- package/tools/commands.mjs +26 -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 +3 -3
- 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 +19 -96
- package/tools/hide-footprint.mjs +1 -1
- package/tools/inject-methodology.mjs +72 -41
- 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 +5 -5
- package/tools/orchestration-config.mjs +1 -1
- package/tools/orchestration-write.mjs +1 -1
- package/tools/presentation.mjs +1 -1
- package/tools/procedures.mjs +3 -3
- package/tools/recipes.mjs +62 -11
- package/tools/recommendations.mjs +97 -14
- package/tools/release-scan.mjs +10 -3
- package/tools/renderers.mjs +1 -1
- package/tools/review-state.mjs +291 -343
- package/tools/run-gates.mjs +216 -92
- package/tools/sandbox-masks.mjs +14 -14
- package/tools/semver-lite.mjs +1 -1
- package/tools/set-autonomy.mjs +1 -1
- package/tools/set-recipe.mjs +2 -2
- package/tools/setup-backends.mjs +1 -1
- package/tools/surface.mjs +1 -1
- package/tools/uninstall.mjs +1 -1
- package/tools/velocity-profile.mjs +9 -9
- package/tools/view-model.mjs +1 -1
- package/tools/worktrees.mjs +2292 -0
- 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
package/tools/commands.mjs
CHANGED
|
@@ -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 >=
|
|
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;
|
|
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: '
|
|
208
|
-
invocation: invocationOf('
|
|
207
|
+
key: 'core-evidence',
|
|
208
|
+
invocation: invocationOf('core-evidence'),
|
|
209
209
|
group: 'Orchestrate',
|
|
210
210
|
kind: WRITER,
|
|
211
|
-
oneLine: '
|
|
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: '
|
|
215
|
-
invocation: invocationOf('
|
|
214
|
+
key: 'coverage-check',
|
|
215
|
+
invocation: invocationOf('coverage-check'),
|
|
216
216
|
group: 'Orchestrate',
|
|
217
|
-
kind:
|
|
218
|
-
oneLine: '
|
|
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
|
|
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
|
+
}
|