@sabaiway/agent-workflow-kit 1.43.0 → 1.45.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 +99 -0
- package/README.md +8 -4
- package/SKILL.md +20 -8
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +87 -16
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +3 -0
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +4 -2
- package/bridges/antigravity-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/SKILL.md +1 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +3 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +90 -19
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +4 -0
- package/bridges/codex-cli-bridge/capability.json +3 -2
- package/bridges/codex-cli-bridge/references/driving-codex.md +3 -2
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +3 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/autonomy-doctor.md +21 -0
- package/references/modes/bootstrap.md +1 -1
- package/references/modes/doc-parity.md +2 -1
- package/references/modes/grounding.md +8 -7
- package/references/modes/recommendations.md +14 -0
- package/references/modes/review-state.md +1 -1
- package/references/modes/sandbox-masks.md +15 -0
- package/references/modes/upgrade.md +10 -7
- package/references/modes/velocity.md +13 -2
- package/references/shared/composition-handoff.md +21 -16
- package/references/shared/report-footer.md +5 -1
- package/references/templates/AGENTS.md +2 -1
- package/references/templates/autonomy.json +3 -0
- package/tools/autonomy-config.mjs +13 -3
- package/tools/autonomy-doctor.mjs +488 -0
- package/tools/commands.mjs +24 -3
- package/tools/delegation.mjs +9 -5
- package/tools/detect-backends.mjs +2 -2
- package/tools/doc-parity.mjs +31 -2
- package/tools/engine-source.mjs +6 -0
- package/tools/family-registry.mjs +21 -7
- package/tools/fold-completeness-run.mjs +5 -0
- package/tools/grounding.mjs +139 -22
- package/tools/inject-methodology.mjs +117 -28
- package/tools/manifest/schema.md +20 -0
- package/tools/manifest/validate.mjs +26 -0
- package/tools/procedures.mjs +61 -8
- package/tools/recipes.mjs +94 -15
- package/tools/recommendations.mjs +469 -0
- package/tools/review-ledger-write.mjs +14 -0
- package/tools/review-ledger.mjs +3 -2
- package/tools/review-state.mjs +101 -24
- package/tools/run-gates.mjs +3 -0
- package/tools/sandbox-masks.mjs +370 -0
- package/tools/set-recipe.mjs +13 -1
- package/tools/velocity-profile.mjs +231 -24
|
@@ -414,6 +414,8 @@ Usage:
|
|
|
414
414
|
node review-ledger-write.mjs record --json '<round-payload>' [--from-receipts] [--cwd <dir>]
|
|
415
415
|
node review-ledger-write.mjs classify --json '<triage-payload>' [--cwd <dir>]
|
|
416
416
|
node review-ledger-write.mjs override --json '<override-payload>' [--cwd <dir>]
|
|
417
|
+
(every verb also accepts --json @<file> — the payload read from a file, keeping the command
|
|
418
|
+
line PLAIN: an inline JSON argv falls outside plain-invocation allow heuristics and prompts)
|
|
417
419
|
|
|
418
420
|
Every verb operates on the current SEGMENT — (loop, base = git rev-parse HEAD): round numbers,
|
|
419
421
|
caps, and teeth reset only when a gated commit moves base (schema 4; records carry base).
|
|
@@ -461,6 +463,18 @@ const parseArgs = (argv) => {
|
|
|
461
463
|
} else if (a === '--json') {
|
|
462
464
|
opts.json = argv[i + 1];
|
|
463
465
|
if (opts.json === undefined) throw usageFail('--json needs a JSON payload');
|
|
466
|
+
// `--json @<file>` reads the payload from a file: a large inline JSON argv (quotes, braces)
|
|
467
|
+
// falls outside every plain-invocation allow heuristic and prompts, and hand-composing it on
|
|
468
|
+
// a command line is exactly the error-prone class --from-receipts exists to shrink — the
|
|
469
|
+
// file form keeps the COMMAND plain while the payload stays explicit (AD-044 Plan 4).
|
|
470
|
+
if (opts.json.startsWith('@')) {
|
|
471
|
+
const payloadPath = opts.json.slice(1);
|
|
472
|
+
try {
|
|
473
|
+
opts.json = readFileSync(payloadPath, 'utf8');
|
|
474
|
+
} catch (err) {
|
|
475
|
+
throw usageFail(`--json @${payloadPath}: unreadable payload file (${err.code ?? err.message})`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
464
478
|
i += 1;
|
|
465
479
|
} else if (a === '--from-receipts') {
|
|
466
480
|
opts.fromReceipts = true;
|
package/tools/review-ledger.mjs
CHANGED
|
@@ -569,8 +569,9 @@ gated commit, so the round-counter reset is earned, never declared.
|
|
|
569
569
|
observed-red receipts, quarantined probes. Counts only — interpretation stays with you.
|
|
570
570
|
|
|
571
571
|
The writer is a SEPARATE tool (review-ledger-write.mjs record/classify/override) — this read-only
|
|
572
|
-
checker never imports it.
|
|
573
|
-
|
|
572
|
+
checker never imports it. Sandbox-safe: runs fully inside an OS sandbox (fs + git reads, no
|
|
573
|
+
network) — the D4 sandbox lane. Human residual: git commit --no-verify, ledger-file editing, and
|
|
574
|
+
forged counts remain possible — a self-discipline mechanism, not a security boundary.
|
|
574
575
|
|
|
575
576
|
Exit codes: 0 pass (or plain report); 1 check failed or config error (loud); 2 usage.`;
|
|
576
577
|
|
package/tools/review-state.mjs
CHANGED
|
@@ -32,10 +32,16 @@
|
|
|
32
32
|
// a folded tree; only a fresh grounded re-run mints a gate-satisfying receipt).
|
|
33
33
|
//
|
|
34
34
|
// The fingerprint is the ONE canonical uncommitted-state identity — sha256 over: staged diff +
|
|
35
|
-
// unstaged diff + untracked-not-ignored file contents (binary
|
|
36
|
-
// ride as name-only notes).
|
|
37
|
-
//
|
|
38
|
-
//
|
|
35
|
+
// unstaged diff + untracked-not-ignored file contents (binary untracked files, symlinks, and
|
|
36
|
+
// directories/gitlinks ride as name-only notes). NEVER-COMMITTABLE untracked stat classes —
|
|
37
|
+
// character/block devices, FIFOs, sockets — are EXCLUDED from the domain entirely (no note): a
|
|
38
|
+
// sandbox that injects device masks into the work tree can no longer move the fingerprint or dirty
|
|
39
|
+
// the clean check (AD-044 Plan 4; the class is lstat-keyed because a lying dirent is exactly how
|
|
40
|
+
// the masks surface). Untracked SYMLINKS and DIRECTORIES stay in the domain — both are committable
|
|
41
|
+
// (a directory listed by `git ls-files --others` as `dir/` is an embedded repo, i.e. a gitlink).
|
|
42
|
+
// Domain == the review-payload domain the wrappers assemble; the prose definition lives in each
|
|
43
|
+
// bridge's capability.json roles.review.contract.receipt, and the bash twin lives in both
|
|
44
|
+
// wrappers — cross-checked by test/review-fingerprint-parity.test.mjs.
|
|
39
45
|
//
|
|
40
46
|
// HUMAN residual (accepted, documented): `git commit --no-verify` skips any pre-commit gate, and
|
|
41
47
|
// deleting/editing the receipt file forges state — receipts live in the git dir (never committable)
|
|
@@ -46,8 +52,8 @@
|
|
|
46
52
|
// Node >= 18. No side effects on import (the isDirectRun idiom).
|
|
47
53
|
|
|
48
54
|
import { readFileSync, readdirSync, lstatSync, readlinkSync, openSync, readSync, closeSync } from 'node:fs';
|
|
49
|
-
import { join } from 'node:path';
|
|
50
|
-
import { pathToFileURL } from 'node:url';
|
|
55
|
+
import { join, dirname } from 'node:path';
|
|
56
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
51
57
|
import { spawnSync } from 'node:child_process';
|
|
52
58
|
import { createHash } from 'node:crypto';
|
|
53
59
|
import { detectBackends } from './detect-backends.mjs';
|
|
@@ -102,10 +108,23 @@ const isBinaryFile = (path) => {
|
|
|
102
108
|
}
|
|
103
109
|
};
|
|
104
110
|
|
|
111
|
+
// The never-committable untracked stat classes (Decision 1, AD-044 Plan 4): character/block
|
|
112
|
+
// devices, FIFOs, sockets — git content can never carry them, so they are excluded from the ENTIRE
|
|
113
|
+
// review domain (fingerprint payload, clean check; the wrappers' bash twin filters the assembled
|
|
114
|
+
// payload identically). lstat-keyed by design: the sandbox mask class surfaces exactly where the
|
|
115
|
+
// dirent LIES (readdir says file, lstat says char device). A null stat (vanished path) is NOT in
|
|
116
|
+
// the class — it keeps its name-only note, like directories (gitlinks) and symlinks.
|
|
117
|
+
export const isNeverCommittableStat = (stat) =>
|
|
118
|
+
stat != null &&
|
|
119
|
+
(stat.isCharacterDevice() || stat.isBlockDevice() || stat.isFIFO() || stat.isSocket());
|
|
120
|
+
|
|
105
121
|
// The canonical payload bytes: staged diff + unstaged diff + the untracked-not-ignored section —
|
|
106
122
|
// byte-identical to the wrappers' emit_fingerprint_payload (same git invocations, same headers,
|
|
107
123
|
// same ls-files ordering), emitted from the work-tree ROOT. Returns null outside a git work tree.
|
|
108
|
-
|
|
124
|
+
// The lstat is injectable ONLY so tests can prove the never-committable filter non-vacuously (a
|
|
125
|
+
// lying lstat over a git-visible fixture path — the sandbox mechanism itself); production callers
|
|
126
|
+
// never pass it.
|
|
127
|
+
export const computeFingerprintPayload = (cwd, { lstat = lstatSync } = {}) => {
|
|
109
128
|
const top = gitLine(['rev-parse', '--show-toplevel'], cwd);
|
|
110
129
|
if (top == null) return null;
|
|
111
130
|
const staged = gitBuf(['diff', '--cached', '--no-ext-diff'], top);
|
|
@@ -117,10 +136,11 @@ export const computeFingerprintPayload = (cwd) => {
|
|
|
117
136
|
const full = join(top, rel);
|
|
118
137
|
let stat = null;
|
|
119
138
|
try {
|
|
120
|
-
stat =
|
|
139
|
+
stat = lstat(full);
|
|
121
140
|
} catch {
|
|
122
141
|
stat = null;
|
|
123
142
|
}
|
|
143
|
+
if (isNeverCommittableStat(stat)) continue;
|
|
124
144
|
if (stat?.isSymbolicLink()) {
|
|
125
145
|
let target = '?';
|
|
126
146
|
try {
|
|
@@ -142,26 +162,75 @@ export const computeFingerprintPayload = (cwd) => {
|
|
|
142
162
|
};
|
|
143
163
|
|
|
144
164
|
// sha256 hex of the canonical payload, or null outside a git work tree.
|
|
145
|
-
export const computeTreeFingerprint = (cwd) => {
|
|
146
|
-
const payload = computeFingerprintPayload(cwd);
|
|
165
|
+
export const computeTreeFingerprint = (cwd, fsx) => {
|
|
166
|
+
const payload = computeFingerprintPayload(cwd, fsx);
|
|
147
167
|
return payload == null ? null : createHash('sha256').update(payload).digest('hex');
|
|
148
168
|
};
|
|
149
169
|
|
|
150
|
-
// Clean = nothing staged, nothing unstaged, no untracked-not-ignored paths (the
|
|
151
|
-
// preflight).
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
170
|
+
// Clean = nothing staged, nothing unstaged, no REVIEWABLE untracked-not-ignored paths (the
|
|
171
|
+
// wrappers' no-diff preflight). Never-committable untracked paths (device/FIFO/socket) do not
|
|
172
|
+
// count as dirty — same filter as the fingerprint, so the two can never disagree about a
|
|
173
|
+
// masks-only tree. An lstat failure keeps the path in the domain (dirty), mirroring the
|
|
174
|
+
// fingerprint's null-stat note. null when not decidable (not a git work tree). Anchored at the
|
|
175
|
+
// work-tree ROOT like the fingerprint: `git ls-files --others` is cwd-SCOPED, so a subdirectory
|
|
176
|
+
// invocation would otherwise miss root/sibling untracked paths and report a dirty tree as clean
|
|
177
|
+
// (codex R1 finding).
|
|
178
|
+
export const isTreeClean = (cwd, { lstat = lstatSync } = {}) => {
|
|
155
179
|
const top = gitLine(['rev-parse', '--show-toplevel'], cwd);
|
|
156
180
|
if (top == null) return null;
|
|
157
181
|
const staged = gitRaw(['diff', '--cached', '--quiet'], top);
|
|
158
182
|
const unstaged = gitRaw(['diff', '--quiet'], top);
|
|
159
183
|
if (staged.error || unstaged.error || staged.status > 1 || unstaged.status > 1) return null;
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
162
|
-
|
|
184
|
+
const untrackedZ = gitBuf(['ls-files', '--others', '--exclude-standard', '-z'], top);
|
|
185
|
+
if (untrackedZ == null) return null;
|
|
186
|
+
const reviewable = untrackedZ
|
|
187
|
+
.toString('utf8')
|
|
188
|
+
.split('\0')
|
|
189
|
+
.filter(Boolean)
|
|
190
|
+
.filter((rel) => {
|
|
191
|
+
try {
|
|
192
|
+
return !isNeverCommittableStat(lstat(join(top, rel)));
|
|
193
|
+
} catch {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
return staged.status === 0 && unstaged.status === 0 && reviewable.length === 0;
|
|
163
198
|
};
|
|
164
199
|
|
|
200
|
+
// ── the sandbox-masks advisory (D lane, AD-044 Plan 4 Phase 1.5) ────────────────────
|
|
201
|
+
|
|
202
|
+
// Count the never-committable untracked paths the STANDARD walk still shows. The review domain
|
|
203
|
+
// ignores them by construction; this count only feeds ONE non-failing advisory line naming the
|
|
204
|
+
// cosmetic sandbox-masks apply — an applied managed block hides the paths from --exclude-standard,
|
|
205
|
+
// so the advisory disappears exactly when the status noise does (no standing detector).
|
|
206
|
+
export const countNeverCommittableUntracked = (cwd, { lstat = lstatSync } = {}) => {
|
|
207
|
+
const top = gitLine(['rev-parse', '--show-toplevel'], cwd);
|
|
208
|
+
if (top == null) return 0;
|
|
209
|
+
const untrackedZ = gitBuf(['ls-files', '--others', '--exclude-standard', '-z'], top);
|
|
210
|
+
if (untrackedZ == null) return 0;
|
|
211
|
+
return untrackedZ
|
|
212
|
+
.toString('utf8')
|
|
213
|
+
.split('\0')
|
|
214
|
+
.filter(Boolean)
|
|
215
|
+
.filter((rel) => {
|
|
216
|
+
try {
|
|
217
|
+
return isNeverCommittableStat(lstat(join(top, rel)));
|
|
218
|
+
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}).length;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Shell-quote one argument for a COPY-PASTE advisory command: plain safe tokens stay bare; anything
|
|
225
|
+
// else rides single quotes (a space/metacharacter path must never render a dead or unsafe paste —
|
|
226
|
+
// codex R1). Exported for the sandbox-masks probe, which renders the same apply one-liner.
|
|
227
|
+
export const shellQuoteArg = (s) => (/^[A-Za-z0-9_/.\-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`);
|
|
228
|
+
|
|
229
|
+
const maskAdvisoryLine = (state) =>
|
|
230
|
+
state.maskedUntracked > 0
|
|
231
|
+
? `notice: ${state.maskedUntracked} never-committable untracked path(s) (device/FIFO/socket) are ignored by the review domain — hide them from git status: node ${shellQuoteArg(join(dirname(fileURLToPath(import.meta.url)), 'sandbox-masks.mjs'))} --cwd ${shellQuoteArg(state.root)} --apply`
|
|
232
|
+
: '';
|
|
233
|
+
|
|
165
234
|
// ── plan-in-flight detector (the AD-038 naming convention; documented in queue.md) ─────
|
|
166
235
|
|
|
167
236
|
// Scratch by the naming convention: EXECUTE-/FEEDBACK- prefixes, or a name carrying PROMPT/prompt/
|
|
@@ -292,7 +361,7 @@ export const degradedExemptSet = ({ records, readError, malformed, base, plans,
|
|
|
292
361
|
// work-tree ROOT when one exists — the fingerprint is root-anchored, so a subdirectory invocation
|
|
293
362
|
// must read the same config/plans or a dirty unreceipted tree could false-PASS as "no plan in
|
|
294
363
|
// flight" (codex R1 finding). Outside a git tree the cwd is the only anchor (and --check exits 0).
|
|
295
|
-
export const buildState = ({ cwd, env = process.env, detect = detectBackends } = {}) => {
|
|
364
|
+
export const buildState = ({ cwd, env = process.env, detect = detectBackends, lstat = lstatSync } = {}) => {
|
|
296
365
|
const root = gitLine(['rev-parse', '--show-toplevel'], cwd) ?? cwd;
|
|
297
366
|
const { config, source: configSource } = loadConfig(root);
|
|
298
367
|
let detection = [];
|
|
@@ -306,8 +375,10 @@ export const buildState = ({ cwd, env = process.env, detect = detectBackends } =
|
|
|
306
375
|
const { dispatch } = planRecipe(resolved.recipe, detection);
|
|
307
376
|
const requiredBackends = dispatch.map((d) => DISPLAY_ALIASES[d.backend] ?? d.backend);
|
|
308
377
|
const plans = plansInFlight(root);
|
|
309
|
-
|
|
310
|
-
|
|
378
|
+
// The injected lstat threads through EVERY stat-dependent computation (fingerprint, clean, the
|
|
379
|
+
// mask count) — a partial injection would let a test observe an inconsistent state (codex R3).
|
|
380
|
+
const fingerprint = computeTreeFingerprint(cwd, { lstat });
|
|
381
|
+
const clean = fingerprint == null ? null : isTreeClean(cwd, { lstat });
|
|
311
382
|
const receiptsPath = resolveReceiptsPath(cwd, env);
|
|
312
383
|
const { receipts, malformed } = receiptsPath ? readReceipts(receiptsPath) : { receipts: [], malformed: 0 };
|
|
313
384
|
const backends = requiredBackends.map((b) => ({ backend: b, ...backendReceiptStatus(receipts, b, fingerprint) }));
|
|
@@ -324,6 +395,7 @@ export const buildState = ({ cwd, env = process.env, detect = detectBackends } =
|
|
|
324
395
|
requiredBackends,
|
|
325
396
|
backends,
|
|
326
397
|
plans,
|
|
398
|
+
root,
|
|
327
399
|
fingerprint,
|
|
328
400
|
clean,
|
|
329
401
|
receiptsPath,
|
|
@@ -334,6 +406,7 @@ export const buildState = ({ cwd, env = process.env, detect = detectBackends } =
|
|
|
334
406
|
ledgerMalformed: ledgerMalformed ?? 0,
|
|
335
407
|
ledgerReadError: ledgerReadError ?? null,
|
|
336
408
|
degradedExempt,
|
|
409
|
+
maskedUntracked: countNeverCommittableUntracked(cwd, { lstat }),
|
|
337
410
|
detectionWarning,
|
|
338
411
|
};
|
|
339
412
|
};
|
|
@@ -444,6 +517,7 @@ AND is not degraded-exempt. Declare it as a project gate by hand (docs/ai/gates.
|
|
|
444
517
|
explicit-consent seeder (tools/seed-gates.mjs) — never without consent.
|
|
445
518
|
|
|
446
519
|
Read-only: never writes, never commits, never runs a subscription CLI; spawns read-only git queries.
|
|
520
|
+
Sandbox-safe: runs fully inside an OS sandbox (fs + git reads, no network) — the D4 sandbox lane.
|
|
447
521
|
Human residual: git commit --no-verify and receipt-file deletion remain possible — this is a
|
|
448
522
|
self-discipline mechanism, not a security boundary.
|
|
449
523
|
|
|
@@ -459,15 +533,18 @@ export const main = (argv, ctx = {}) => {
|
|
|
459
533
|
if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
|
|
460
534
|
const unknown = argv.find((a) => !KNOWN_ARGS.has(a));
|
|
461
535
|
if (unknown !== undefined) throw fail(2, `unknown argument: ${unknown}`);
|
|
462
|
-
const state = buildState({ cwd, env, detect });
|
|
536
|
+
const state = buildState({ cwd, env, detect, lstat: ctx.lstat });
|
|
463
537
|
const check = decideCheck(state);
|
|
538
|
+
// The mask advisory is NON-FAILING by contract: one notice line, never an exit-code arm.
|
|
539
|
+
const advisory = maskAdvisoryLine(state);
|
|
464
540
|
if (argv.includes('--json')) {
|
|
465
541
|
return { code: argv.includes('--check') ? check.code : 0, stdout: JSON.stringify({ ...state, check }, null, 2), stderr: '' };
|
|
466
542
|
}
|
|
467
543
|
if (argv.includes('--check')) {
|
|
468
|
-
|
|
544
|
+
const line = `review-state check: ${check.code === 0 ? 'PASS' : 'FAIL'} — ${check.reason}`;
|
|
545
|
+
return { code: check.code, stdout: advisory ? `${line}\n${advisory}` : line, stderr: '' };
|
|
469
546
|
}
|
|
470
|
-
return { code: 0, stdout: formatHuman(state, check), stderr: '' };
|
|
547
|
+
return { code: 0, stdout: advisory ? `${formatHuman(state, check)}\n${advisory}` : formatHuman(state, check), stderr: '' };
|
|
471
548
|
} catch (err) {
|
|
472
549
|
return { code: err.exitCode ?? 1, stdout: '', stderr: `review-state: ${err.message}` };
|
|
473
550
|
}
|
package/tools/run-gates.mjs
CHANGED
|
@@ -84,6 +84,9 @@ const USAGE = [
|
|
|
84
84
|
'In --record mode the unit-tests gate is CREDITED (not re-spawned) when it is the FIRST declared gate',
|
|
85
85
|
'AND the fold-completeness runner already ran that EXACT command green at the current tree',
|
|
86
86
|
'(fingerprint-bound + exit-0 + cmd-identity); positioned after another gate, it always re-spawns.',
|
|
87
|
+
'Sandbox-safe: the runner itself needs no network and writes only repo-local state — the D4 sandbox',
|
|
88
|
+
'lane; each DECLARED gate command is the project\'s own, so ITS sandbox-safety is command-shape',
|
|
89
|
+
'dependent (first try the sandbox-safe shape — cache under $TMPDIR, offline/notifier off).',
|
|
87
90
|
`Exit codes: 0 ok · 1 gate failure · 2 usage · 3 missing declaration · 4 empty gates list ·`,
|
|
88
91
|
'5 malformed/invalid declaration · 6 bash unavailable · 7 --record asked but the record failed.',
|
|
89
92
|
].join('\n');
|