@sabaiway/agent-workflow-kit 1.37.1 → 1.39.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.
@@ -18,16 +18,25 @@
18
18
  // Honest outcomes — each distinct, never a silent green (the exit-code table + the summary-line
19
19
  // schema are pinned by run-gates.test.mjs):
20
20
  // 0 ok · 1 gate failure · 2 usage · 3 missing declaration · 4 empty gates list ·
21
- // 5 malformed/invalid declaration · 6 bash unavailable. Gate `cmd` lines are BASH command
22
- // lines (brace/glob expansion); a host without bash gets the loud exit-6 preflight error,
23
- // never a silent reinterpretation under another shell.
21
+ // 5 malformed/invalid declaration · 6 bash unavailable · 7 --record asked but the gate-run
22
+ // record could not be written. Gate `cmd` lines are BASH command lines (brace/glob expansion);
23
+ // a host without bash gets the loud exit-6 preflight error, never a silent reinterpretation
24
+ // under another shell.
24
25
  //
25
- // The runner itself WRITES NOTHING. Dependency-free, Node >= 18. No side effects on import.
26
+ // The runner itself WRITES NOTHING by default. `--record` (BUGFREE-2 / AD-048, D5) mints ONE
27
+ // v4 `gate-run` record — the green-baseline receipt the review-ledger writer's D5 tooth consumes —
28
+ // by DELEGATING to the ledger's sole writer (recordGateRun in review-ledger-write.mjs): this
29
+ // runner never opens the ledger file itself (an import/structure pin holds the boundary). The
30
+ // record carries the FULL declaration + exactly what ran (a --only subset records honestly as a
31
+ // subset — it never satisfies quality-green) + the tree fingerprint BEFORE and AFTER the run (a
32
+ // mutating gate attests no particular tree). Dependency-free, Node >= 18. No side effects on import.
26
33
 
27
34
  import { readFileSync, lstatSync } from 'node:fs';
28
35
  import { join } from 'node:path';
29
36
  import { spawnSync } from 'node:child_process';
30
37
  import { pathToFileURL } from 'node:url';
38
+ import { computeTreeFingerprint } from './review-state.mjs';
39
+ import { recordGateRun } from './review-ledger-write.mjs';
31
40
 
32
41
  // The per-project declaration (strict JSON, hand-editable). cwd-relative — errors show a path the
33
42
  // user can open (the orchestration-config CONFIG_REL idiom).
@@ -42,6 +51,10 @@ export const EXIT = Object.freeze({
42
51
  empty: 4,
43
52
  malformed: 5,
44
53
  noBash: 6,
54
+ // --record was asked for but the gate-run record could not be written (no in-flight loop, a
55
+ // malformed ledger, an fs refusal): the invocation's contract included a ledger receipt — not
56
+ // delivering it is its own loud outcome, never folded into ok/fail.
57
+ recordFailed: 7,
45
58
  });
46
59
 
47
60
  // A tagged failure carrying its process exit code (the shared orchestration-config idiom).
@@ -54,12 +67,15 @@ const SPAWN_FAILED_CODE = -1;
54
67
  const MAX_GATE_OUTPUT_BYTES = 64 * 1024 * 1024;
55
68
 
56
69
  const USAGE = [
57
- 'usage: run-gates.mjs [--cwd <dir>] [--only <id>]... [--help]',
70
+ 'usage: run-gates.mjs [--cwd <dir>] [--only <id>]... [--record] [--help]',
58
71
  '',
59
72
  `Runs the gates declared in <cwd>/${GATES_REL} (one bash command line each, project root as cwd).`,
60
73
  'Prints a per-gate PASS/FAIL table + one machine-readable summary line; exit 0 iff all green.',
74
+ '--record additionally mints ONE v4 gate-run record into the review ledger via its sole writer',
75
+ '(the D5 green-baseline receipt; needs a single in-flight plan): the full declaration + what ran',
76
+ '+ the pre/post tree fingerprints. A red run records honestly; a --only subset records as a subset.',
61
77
  `Exit codes: 0 ok · 1 gate failure · 2 usage · 3 missing declaration · 4 empty gates list ·`,
62
- '5 malformed/invalid declaration · 6 bash unavailable.',
78
+ '5 malformed/invalid declaration · 6 bash unavailable · 7 --record asked but the record failed.',
63
79
  ].join('\n');
64
80
 
65
81
  // ── declaration validation (malformed → exit 5, loud `path: reason`) ─────────────────
@@ -218,7 +234,7 @@ export const composeSummaryLine = ({ status, results = [] }) => {
218
234
  // ── CLI ───────────────────────────────────────────────────────────────────────────────
219
235
 
220
236
  const parseArgs = (argv) => {
221
- const opts = { cwd: null, only: [], help: false };
237
+ const opts = { cwd: null, only: [], record: false, help: false };
222
238
  for (let i = 0; i < argv.length; i += 1) {
223
239
  const arg = argv[i];
224
240
  if (arg === '--help' || arg === '-h') {
@@ -231,6 +247,8 @@ const parseArgs = (argv) => {
231
247
  i += 1;
232
248
  if (argv[i] === undefined) throw fail(EXIT.usage, '--only requires a gate id argument');
233
249
  opts.only.push(argv[i]);
250
+ } else if (arg === '--record') {
251
+ opts.record = true;
234
252
  } else {
235
253
  throw fail(EXIT.usage, `unknown argument "${arg}"\n${USAGE}`);
236
254
  }
@@ -244,12 +262,15 @@ const parseArgs = (argv) => {
244
262
  export const runCli = (argv, deps = {}) => {
245
263
  const {
246
264
  cwd = process.cwd(),
265
+ env = process.env,
247
266
  log = console.log,
248
267
  logError = console.error,
249
268
  spawn = spawnGateViaBash,
250
269
  readFile,
251
270
  lstat,
252
271
  now,
272
+ record = recordGateRun,
273
+ fingerprint = computeTreeFingerprint,
253
274
  } = deps;
254
275
  try {
255
276
  const opts = parseArgs(argv);
@@ -283,10 +304,40 @@ export const runCli = (argv, deps = {}) => {
283
304
  log(composeSummaryLine({ status: 'no-bash' }));
284
305
  return EXIT.noBash;
285
306
  }
307
+ const fingerprintBefore = opts.record ? fingerprint(projectDir) : null;
286
308
  const results = runGates(selected, { cwd: projectDir, spawn, log, now });
287
309
  for (const line of formatTable(results)) log(line);
288
310
  const allGreen = results.every((result) => result.ok);
311
+ // The gate-run record (D5): minted for green AND red runs alike (an honest red is telemetry
312
+ // fuel), via the ledger's sole writer. Emitted BEFORE the summary line — the machine summary
313
+ // stays the LAST line of every non-usage outcome (pinned).
314
+ let recordError = null;
315
+ if (opts.record) {
316
+ const failing = results.filter((result) => !result.ok);
317
+ try {
318
+ const { writtenPath } = record({
319
+ cwd: projectDir,
320
+ env,
321
+ declared: declaration.gates.map(({ id, cmd }) => ({ id, cmd })),
322
+ results: results.map(({ id, ok, code }) => ({ id, ok, code })),
323
+ summary: {
324
+ status: allGreen ? 'ok' : 'fail',
325
+ gates: results.length,
326
+ passed: results.length - failing.length,
327
+ failed: failing.length,
328
+ failedIds: failing.map((result) => result.id),
329
+ },
330
+ fingerprintBefore,
331
+ fingerprintAfter: fingerprint(projectDir),
332
+ });
333
+ log(`[run-gates] gate-run recorded → ${writtenPath}`);
334
+ } catch (err) {
335
+ recordError = err;
336
+ logError(`[run-gates] --record failed: ${err.message}`);
337
+ }
338
+ }
289
339
  log(composeSummaryLine({ status: allGreen ? 'ok' : 'fail', results }));
340
+ if (recordError) return EXIT.recordFailed;
290
341
  return allGreen ? EXIT.ok : EXIT.fail;
291
342
  } catch (err) {
292
343
  logError(`[run-gates] ${err.message}`);
@@ -208,7 +208,7 @@ export const reviewStateCandidate = (cwd, deps = {}) => {
208
208
 
209
209
  // The conditional review-LEDGER candidate (AD-045) — the SAME consent + conditional rule as the
210
210
  // review-state candidate (offered ONLY when plan-execution.review is reviewed/council), keyed on the
211
- // same slot, path resolved + QUOTED. It gates the review-ROUND ledger (converged / accepted-residual);
211
+ // same slot, path resolved + QUOTED. It gates the review-ROUND ledger (converged / resolved-residual);
212
212
  // review-state gates receipt PRESENCE. Both may be offered together — distinct axes.
213
213
  export const reviewLedgerCandidate = (cwd, deps = {}) => {
214
214
  const toolPath = deps.reviewLedgerTool ?? REVIEW_LEDGER_TOOL;
@@ -228,7 +228,7 @@ export const reviewLedgerCandidate = (cwd, deps = {}) => {
228
228
  return {
229
229
  candidate: {
230
230
  id: 'review-ledger',
231
- title: 'Review-round ledger: the in-flight loop is converged or accepted-residual',
231
+ title: 'Review-round ledger: the in-flight loop is converged or resolved-residual',
232
232
  cmd: `node "${toolPath}" --check`,
233
233
  },
234
234
  note: null,