@sabaiway/agent-workflow-kit 5.3.0 → 5.5.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 +138 -0
- package/README.md +2 -1
- package/SKILL.md +5 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +1 -1
- package/bridges/antigravity-cli-bridge/capability.json +1 -1
- package/bridges/codex-cli-bridge/SKILL.md +53 -5
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +622 -30
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +731 -3
- package/bridges/codex-cli-bridge/bin/codex-review.sh +1 -1
- package/bridges/codex-cli-bridge/capability.json +15 -10
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/core-evidence.md +1 -1
- package/references/modes/coverage-check.md +1 -1
- package/references/modes/dispatch.md +29 -0
- package/references/modes/gates.md +7 -2
- package/references/modes/receipt-deadline.md +3 -3
- package/references/modes/recommendations.md +3 -1
- package/references/modes/upgrade.md +1 -1
- package/references/modes/velocity.md +5 -1
- package/references/scripts/migrate-gates.mjs +102 -10
- package/references/scripts/migrate-gates.test.mjs +37 -0
- package/tools/commands.mjs +7 -0
- package/tools/core-evidence.mjs +79 -5
- package/tools/coverage-check.mjs +23 -7
- package/tools/coverage-producer.mjs +68 -0
- package/tools/coverage-state.mjs +24 -0
- package/tools/declared-paths.mjs +32 -0
- package/tools/detect-backends.mjs +5 -4
- package/tools/dispatch-record.mjs +10 -3
- package/tools/dispatch-store.mjs +392 -0
- package/tools/dispatch.mjs +1779 -0
- package/tools/doc-parity.mjs +27 -4
- package/tools/exec-producer.mjs +483 -0
- package/tools/exec-receipt.mjs +263 -0
- package/tools/flow-store.mjs +111 -462
- package/tools/gates-declaration.mjs +49 -0
- package/tools/gates-init.mjs +83 -6
- package/tools/receipt-deadline.mjs +25 -3
- package/tools/recommendations.mjs +63 -19
- package/tools/release-scan.mjs +33 -0
- package/tools/run-gates.mjs +111 -32
- package/tools/store-append.mjs +444 -0
- package/tools/velocity-profile.mjs +102 -23
package/tools/run-gates.mjs
CHANGED
|
@@ -68,7 +68,13 @@ import {
|
|
|
68
68
|
isReviewDependentGate, unknownPregateExcludeIds, derivePregateSubsetGates,
|
|
69
69
|
} from './gates-declaration.mjs';
|
|
70
70
|
|
|
71
|
+
// The coverage vocabulary lives in a LEAF below this runner AND core-evidence (which validates the
|
|
72
|
+
// token this runner records) — run-gates already imports core-evidence, so a shared home is the
|
|
73
|
+
// only direction that has no cycle. Re-exported here: this module is the vocabulary's public face.
|
|
74
|
+
import { COVERAGE } from './coverage-state.mjs';
|
|
75
|
+
|
|
71
76
|
export { GATES_REL, loadDeclaration, validateDeclaration, canonicalCheckerGates, isFinalCapableDeclaration, isReviewDependentGate };
|
|
77
|
+
export { COVERAGE };
|
|
72
78
|
|
|
73
79
|
// The full exit-code table — one distinct code per honest outcome (never a silent green).
|
|
74
80
|
// 7 is RETIRED (the deleted --record arm's outcome) — never reused for a new meaning.
|
|
@@ -117,12 +123,18 @@ const USAGE = [
|
|
|
117
123
|
'deletes the stale git-dir lcov first, exports AW_GIT_DIR to every gate cmd, records EVERY',
|
|
118
124
|
'attempt (start + completed green/red) in the core-evidence store, and binds the receipt to',
|
|
119
125
|
'{ fingerprint before/after, the full declaration, per-gate results, the canonical red-proof +',
|
|
120
|
-
'degrade evidence hashes, the lcov sha, and — when a flow store
|
|
121
|
-
'the owner-scoped flow projection hash (D10; projection movement
|
|
122
|
-
'integrityFailure) }. --final refuses --only (a subset never attests).',
|
|
126
|
+
'degrade evidence hashes, the lcov sha, the run\'s own coverage token, and — when a flow store',
|
|
127
|
+
'exists — evidenceHashes.flow, the owner-scoped flow projection hash (D10; projection movement',
|
|
128
|
+
'under the run is a red integrityFailure) }. --final refuses --only (a subset never attests).',
|
|
123
129
|
'',
|
|
124
130
|
`Runs the gates declared in <cwd>/${GATES_REL} (one bash command line each, project root as cwd).`,
|
|
125
131
|
'Prints a per-gate PASS/FAIL table + one machine-readable summary line; exit 0 iff all green.',
|
|
132
|
+
`The summary line carries coverage=<${COVERAGE.certified}|${COVERAGE.notRun}|${COVERAGE.none}|${COVERAGE.unknown}> — whether a coverage`,
|
|
133
|
+
'VERDICT rode this run: certified (one was issued, pass OR fail), not-run (the checker issued',
|
|
134
|
+
'none — no lcov, or this run does not own it), none (no canonical checker ran here), unknown (the',
|
|
135
|
+
'run ended before the gates produced a signal, or the signal is unreadable). The checker\'s own',
|
|
136
|
+
'table row names a withheld verdict in the same words. DETAIL only: the exit code and the status=',
|
|
137
|
+
'token are untouched by it.',
|
|
126
138
|
'',
|
|
127
139
|
'Producer env: AW_GIT_DIR (inside a git tree) and AW_LCOV_FILE (--final only) are computed and',
|
|
128
140
|
'exported to every gate child, and STRIPPED from the inherited environment first — a host-set',
|
|
@@ -238,23 +250,82 @@ export const runGates = (gates, { cwd, spawn = spawnGateViaBash, now = Date.now,
|
|
|
238
250
|
};
|
|
239
251
|
|
|
240
252
|
// The per-gate PASS/FAIL table (printed after every gate ran — failures never stop the matrix).
|
|
241
|
-
|
|
253
|
+
// `notes` annotates rows by gate id: the coverage signal names a WITHHELD verdict on the checker's
|
|
254
|
+
// own row, so a PASS there can never read as a claim the checker did not make. A Map, not an
|
|
255
|
+
// object — a gate id is any kebab word, `constructor` included, and a plain object would answer
|
|
256
|
+
// that lookup from its prototype.
|
|
257
|
+
export const formatTable = (results, notes = new Map()) => {
|
|
242
258
|
const idWidth = Math.max(...results.map((result) => result.id.length), 'gate'.length);
|
|
243
259
|
const pad = (text) => text + ' '.repeat(idWidth - text.length);
|
|
244
260
|
const lines = ['', `${pad('gate')} result`];
|
|
245
261
|
for (const result of results) {
|
|
246
|
-
|
|
262
|
+
const note = notes.get(result.id);
|
|
263
|
+
lines.push(`${pad(result.id)} ${result.ok ? 'PASS' : `FAIL (exit ${result.code})`}${note ? ` ${note}` : ''}`);
|
|
247
264
|
}
|
|
248
265
|
return lines;
|
|
249
266
|
};
|
|
250
267
|
|
|
268
|
+
// ── the coverage signal this run carries (Decision 8) ─────────────────────────────────
|
|
269
|
+
// The checker's two fully anchored machine lines. Exactly ONE of each rides a run: the --final
|
|
270
|
+
// receipt binds them (an injected or duplicated line must never shadow the real one), and the
|
|
271
|
+
// summary field below is derived from the SAME bytes, so the two can never disagree.
|
|
272
|
+
const LCOV_SHA_LINE_RE = /^coverage-check: lcov-sha256=([0-9a-f]{64}|none)$/;
|
|
273
|
+
const ATTESTED_LINE_RE = /^coverage-check: attested=(yes|no)$/;
|
|
274
|
+
const anchoredMachineLines = (stdout, re) => String(stdout ?? '').split(/\r?\n/).filter((line) => re.test(line));
|
|
275
|
+
const exactlyOneMachineValue = (stdout, re) => {
|
|
276
|
+
const lines = anchoredMachineLines(stdout, re);
|
|
277
|
+
return lines.length === 1 ? re.exec(lines[0])[1] : null;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Which SELECTED gate is the canonical checker — resolved BEFORE anything spawns, because the
|
|
281
|
+
// answer is a property of the DECLARATION, not of the post-run filesystem. The predicate reads the
|
|
282
|
+
// tool path through realpath, so a gate that deletes or redirects it mid-run would otherwise turn
|
|
283
|
+
// the checker this run selected into "no checker at all" (`none`) — and a --final receipt refuses
|
|
284
|
+
// that state, which would lose the whole attempt's evidence rather than record it honestly.
|
|
285
|
+
export const resolveCheckerIndex = (selected, projectDir) =>
|
|
286
|
+
selected.findIndex((gate) => matchesCanonicalCheck(FINAL_CORE_CHECKS[1], gate.cmd, projectDir));
|
|
287
|
+
|
|
288
|
+
// coverageSignal(checkerAt, results) → { state, note, checkerRow } — what THIS run can honestly say
|
|
289
|
+
// about coverage, decided by the checker's own OUTPUT rather than by its exit status (it exits 0
|
|
290
|
+
// both when it certifies and when it withholds). BOTH anchored lines are read and CROSS-CHECKED:
|
|
291
|
+
// neither alone is trustworthy — a missing or duplicated line is unreadable, and an attestation
|
|
292
|
+
// over bytes that were never consumed is a contradiction. `note` is the checker row's table
|
|
293
|
+
// annotation, null when there is nothing to name.
|
|
294
|
+
export const coverageSignal = (checkerAt, results) => {
|
|
295
|
+
if (checkerAt === -1) return { state: COVERAGE.none, note: null, checkerRow: null };
|
|
296
|
+
const checkerRow = results[checkerAt] ?? null;
|
|
297
|
+
if (checkerRow === null || checkerRow.code === SPAWN_FAILED_CODE) {
|
|
298
|
+
return { state: COVERAGE.unknown, note: null, checkerRow };
|
|
299
|
+
}
|
|
300
|
+
const unreadable = (why) => ({ state: COVERAGE.unknown, note: `coverage=${COVERAGE.unknown} (${why})`, checkerRow });
|
|
301
|
+
const attested = exactlyOneMachineValue(checkerRow.stdout, ATTESTED_LINE_RE);
|
|
302
|
+
const consumed = exactlyOneMachineValue(checkerRow.stdout, LCOV_SHA_LINE_RE);
|
|
303
|
+
if (attested === null || consumed === null) {
|
|
304
|
+
return unreadable('the checker printed no single anchored attested= / lcov-sha256 pair — whether coverage was certified is unknowable');
|
|
305
|
+
}
|
|
306
|
+
if (attested === 'yes') {
|
|
307
|
+
return consumed === 'none'
|
|
308
|
+
? unreadable('the checker attested over an lcov it never read — a contradictory pair, fail closed')
|
|
309
|
+
: { state: COVERAGE.certified, note: null, checkerRow };
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
state: COVERAGE.notRun,
|
|
313
|
+
note: consumed === 'none'
|
|
314
|
+
? `coverage=${COVERAGE.notRun} (no lcov bytes were read; no coverage verdict was issued)`
|
|
315
|
+
: `coverage=${COVERAGE.notRun} (an lcov was read but no verdict was issued)`,
|
|
316
|
+
checkerRow,
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
|
|
251
320
|
// The ONE machine-readable summary line — always the LAST line printed for every non-usage
|
|
252
|
-
// outcome. Schema (pinned by tests): status ∈ ok|fail|missing|empty|malformed|no-bash
|
|
253
|
-
|
|
321
|
+
// outcome. Schema (pinned by tests): status ∈ ok|fail|missing|empty|malformed|no-bash, plus
|
|
322
|
+
// coverage ∈ the COVERAGE vocabulary. The coverage DEFAULT is `unknown`: a lane that ended before
|
|
323
|
+
// the gates could produce a signal says exactly that, never a claim it cannot support.
|
|
324
|
+
export const composeSummaryLine = ({ status, results = [], coverage = COVERAGE.unknown }) => {
|
|
254
325
|
const passed = results.filter((result) => result.ok).length;
|
|
255
326
|
const failed = results.filter((result) => !result.ok);
|
|
256
327
|
const failedIds = failed.length > 0 ? failed.map((result) => result.id).join(',') : NO_FAILED_IDS;
|
|
257
|
-
return `[run-gates] status=${status} gates=${results.length} passed=${passed} failed=${failed.length} failed_ids=${failedIds}`;
|
|
328
|
+
return `[run-gates] status=${status} gates=${results.length} passed=${passed} failed=${failed.length} failed_ids=${failedIds} coverage=${coverage}`;
|
|
258
329
|
};
|
|
259
330
|
|
|
260
331
|
// ── CLI ───────────────────────────────────────────────────────────────────────────────
|
|
@@ -655,8 +726,15 @@ export const runCli = (argv, deps = {}) => {
|
|
|
655
726
|
return EXIT.finalFailed;
|
|
656
727
|
}
|
|
657
728
|
}
|
|
729
|
+
// The checker's position is pinned BEFORE the matrix spawns; the tree it leaves behind never
|
|
730
|
+
// gets to re-decide which gate this run selected.
|
|
731
|
+
const checkerAt = resolveCheckerIndex(selected, projectDir);
|
|
658
732
|
const results = runGates(selected, { cwd: projectDir, spawn: gateSpawn, log, now });
|
|
659
|
-
|
|
733
|
+
// Decided once, from the checker's own machine lines, and carried to BOTH surfaces of this run:
|
|
734
|
+
// the checker's table row and the machine summary field (Decision 7/8).
|
|
735
|
+
const coverage = coverageSignal(checkerAt, results);
|
|
736
|
+
const coverageNotes = coverage.note && coverage.checkerRow ? new Map([[coverage.checkerRow.id, coverage.note]]) : new Map();
|
|
737
|
+
for (const line of formatTable(results, coverageNotes)) log(line);
|
|
660
738
|
const allGreen = results.every((result) => result.ok);
|
|
661
739
|
if (opts.preReview) {
|
|
662
740
|
// The named diagnosis (#66): review-dependence is derived, so an abstracted checker can only
|
|
@@ -675,7 +753,7 @@ export const runCli = (argv, deps = {}) => {
|
|
|
675
753
|
&& endRead.records.some((r) => r.kind === CHAIN_KIND && r.purpose === 'adoption');
|
|
676
754
|
if (armedNow) {
|
|
677
755
|
logError('[run-gates] --pre-review: the flow was ARMED while this run executed — the run started unarmed, so its result is NOT recorded (round-11 fold); re-run under the armed flow');
|
|
678
|
-
log(composeSummaryLine({ status: 'fail', results }));
|
|
756
|
+
log(composeSummaryLine({ status: 'fail', results, coverage: coverage.state }));
|
|
679
757
|
return EXIT.fail;
|
|
680
758
|
}
|
|
681
759
|
}
|
|
@@ -686,7 +764,7 @@ export const runCli = (argv, deps = {}) => {
|
|
|
686
764
|
if (results.some((result) => result.code === SPAWN_FAILED_CODE)) {
|
|
687
765
|
logError('[run-gates] --pre-review: a gate could not SPAWN — an infrastructure failure is not a gate red, so NO subset-attempt was recorded; fix the spawn failure and re-run');
|
|
688
766
|
releaseSubsetRunLock();
|
|
689
|
-
log(composeSummaryLine({ status: 'fail', results }));
|
|
767
|
+
log(composeSummaryLine({ status: 'fail', results, coverage: coverage.state }));
|
|
690
768
|
return EXIT.fail;
|
|
691
769
|
}
|
|
692
770
|
const attemptStatus = allGreen ? 'green' : 'red';
|
|
@@ -711,23 +789,18 @@ export const runCli = (argv, deps = {}) => {
|
|
|
711
789
|
logError(`[run-gates] --pre-review: the subset attempt could not be recorded — ${err.message}`);
|
|
712
790
|
logError("[run-gates] an armed flow's subset run IS a recorded attempt; an unrecordable run refuses (fail closed)");
|
|
713
791
|
releaseSubsetRunLock();
|
|
714
|
-
log(composeSummaryLine({ status: 'fail', results }));
|
|
792
|
+
log(composeSummaryLine({ status: 'fail', results, coverage: coverage.state }));
|
|
715
793
|
return EXIT.fail;
|
|
716
794
|
}
|
|
717
795
|
}
|
|
718
796
|
}
|
|
719
|
-
// A green gate's stdout is deliberately not echoed — the table IS the report.
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
if (checkerRow?.ok && /^coverage-check: attested=no$/m.test(String(checkerRow.stdout ?? ''))) {
|
|
727
|
-
log(`── ${checkerRow.id} — NO COVERAGE VERDICT (the gate passed; it did not certify)`);
|
|
728
|
-
for (const line of String(checkerRow.stdout).split(/\r?\n/).filter((l) => /^coverage-check: (NO VERDICT|skipped-no-lcov)/.test(l))) log(line);
|
|
729
|
-
log(' A coverage verdict is issued only by run-gates.mjs --final, which owns the lcov for the whole run.');
|
|
730
|
-
}
|
|
797
|
+
// A green gate's stdout is deliberately not echoed — the table IS the report. The row above
|
|
798
|
+
// now names the withheld verdict; this block adds the checker's OWN words and the remedy, so a
|
|
799
|
+
// plain run never leaves a PASS standing for a coverage claim that was never made. Only that.
|
|
800
|
+
if (!opts.final && coverage.state === COVERAGE.notRun && coverage.checkerRow?.ok) {
|
|
801
|
+
log(`── ${coverage.checkerRow.id} — NO COVERAGE VERDICT (the gate passed; it did not certify)`);
|
|
802
|
+
for (const line of String(coverage.checkerRow.stdout).split(/\r?\n/).filter((l) => /^coverage-check: (NO VERDICT|skipped-no-lcov)/.test(l))) log(line);
|
|
803
|
+
log(' A coverage verdict is issued only by run-gates.mjs --final, which owns the lcov for the whole run.');
|
|
731
804
|
}
|
|
732
805
|
if (opts.final) {
|
|
733
806
|
// The checker's verbatim diagnostics surface even on green — skipped-no-lcov and the
|
|
@@ -770,17 +843,15 @@ export const runCli = (argv, deps = {}) => {
|
|
|
770
843
|
}
|
|
771
844
|
// Exactly ONE full machine line binds the receipt — an unanchored first-match would let
|
|
772
845
|
// an injected/duplicated line shadow the real one and skip the end re-hash.
|
|
773
|
-
const
|
|
774
|
-
const
|
|
775
|
-
const shaValue = shaLines.length === 1 ? shaLineRe.exec(shaLines[0])[1] : null;
|
|
846
|
+
const shaLines = anchoredMachineLines(checkerRow?.stdout, LCOV_SHA_LINE_RE);
|
|
847
|
+
const shaValue = shaLines.length === 1 ? LCOV_SHA_LINE_RE.exec(shaLines[0])[1] : null;
|
|
776
848
|
const lcovSha256 = shaValue !== null && shaValue !== 'none' ? shaValue : null;
|
|
777
849
|
// The attestation line, on the SAME exactly-one-anchored-line contract as the sha: a green
|
|
778
850
|
// exit status alone never proves the checker certified anything — it exits 0 both when it
|
|
779
851
|
// attests and when it withholds a verdict. Without this arm a gate that removed the start
|
|
780
852
|
// record mid-run would yield a green receipt carrying no coverage claim at all.
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const attested = attestLines.length === 1 ? attestLineRe.exec(attestLines[0])[1] : null;
|
|
853
|
+
const attestLines = anchoredMachineLines(checkerRow?.stdout, ATTESTED_LINE_RE);
|
|
854
|
+
const attested = attestLines.length === 1 ? ATTESTED_LINE_RE.exec(attestLines[0])[1] : null;
|
|
784
855
|
if (allGreen && integrityFailure === null && lcovSha256 !== null) {
|
|
785
856
|
if (attestLines.length !== 1) {
|
|
786
857
|
integrityFailure = attestLines.length === 0
|
|
@@ -826,6 +897,11 @@ export const runCli = (argv, deps = {}) => {
|
|
|
826
897
|
...(finalFlow?.present ? { flow: finalFlow.hash } : {}),
|
|
827
898
|
},
|
|
828
899
|
lcovSha256,
|
|
900
|
+
// The run's OWN coverage token, recorded rather than re-derived: `lcovSha256` says what
|
|
901
|
+
// the receipt binds, never whether a verdict was issued (a red run can bind a digest
|
|
902
|
+
// over an uncertified read, and a null digest is also what an unreadable sha line
|
|
903
|
+
// leaves). The stateless render reads this field instead of guessing from the digest.
|
|
904
|
+
coverage: coverage.state,
|
|
829
905
|
integrityFailure,
|
|
830
906
|
timestamp: new Date().toISOString(),
|
|
831
907
|
},
|
|
@@ -848,14 +924,17 @@ export const runCli = (argv, deps = {}) => {
|
|
|
848
924
|
// would be a silent green in the one place a reader parses instead of reads. The run lock
|
|
849
925
|
// releases BEFORE the line composes so a custody violation can never hide behind status=ok.
|
|
850
926
|
const runLockIssue = releaseSubsetRunLock();
|
|
851
|
-
log(composeSummaryLine({ status: allGreen && finalError === null && runLockIssue == null ? 'ok' : 'fail', results }));
|
|
927
|
+
log(composeSummaryLine({ status: allGreen && finalError === null && runLockIssue == null ? 'ok' : 'fail', results, coverage: coverage.state }));
|
|
852
928
|
if (finalError) return EXIT.finalFailed;
|
|
853
929
|
if (runLockIssue != null) return EXIT.fail;
|
|
854
930
|
return allGreen ? EXIT.ok : EXIT.fail;
|
|
855
931
|
} catch (err) {
|
|
856
932
|
releaseSubsetRunLock();
|
|
857
933
|
logError(`[run-gates] ${err.message}`);
|
|
858
|
-
|
|
934
|
+
// The machine line is the LAST line for every NON-USAGE outcome — a thrown refusal is one, and
|
|
935
|
+
// the gates never produced a signal there, so it carries coverage=unknown. Usage is the single
|
|
936
|
+
// documented exception: it prints the usage text and no summary at all.
|
|
937
|
+
if (err.exitCode !== EXIT.usage) log(composeSummaryLine({ status: err.exitCode === EXIT.malformed ? 'malformed' : 'fail' }));
|
|
859
938
|
return err.exitCode ?? EXIT.fail;
|
|
860
939
|
}
|
|
861
940
|
};
|