@sabaiway/agent-workflow-kit 1.45.1 → 1.47.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 +82 -0
- package/README.md +2 -2
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +6 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +6 -0
- package/bridges/antigravity-cli-bridge/capability.json +6 -2
- package/bridges/codex-cli-bridge/SKILL.md +1 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +20 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +50 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +1 -1
- package/bridges/codex-cli-bridge/capability.json +6 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/recommendations.md +25 -5
- package/references/modes/review-ledger.md +2 -1
- package/references/modes/upgrade.md +3 -2
- package/references/modes/velocity.md +1 -1
- package/references/shared/report-footer.md +14 -0
- package/references/templates/agent_rules.md +1 -1
- package/tools/detect-backends.mjs +6 -0
- package/tools/doc-parity.mjs +16 -4
- package/tools/manifest/schema.md +29 -2
- package/tools/manifest/validate.mjs +38 -0
- package/tools/procedures.mjs +8 -1
- package/tools/recommendations.mjs +279 -79
- package/tools/review-ledger-write.mjs +112 -6
- package/tools/review-ledger.mjs +1 -1
- package/tools/velocity-profile.mjs +12 -10
|
@@ -406,7 +406,91 @@ export const recordTriage = (params, deps = {}) => {
|
|
|
406
406
|
return appendRecord(ledgerPath, record, deps);
|
|
407
407
|
};
|
|
408
408
|
|
|
409
|
-
// ──
|
|
409
|
+
// ── batch (D4/D5 — one invocation applies an ordered list of record/classify/override ops) ────────
|
|
410
|
+
// The prompt-economy lane (WRITER-BURST-BATCH): the ledger triad a records stage would otherwise
|
|
411
|
+
// fire one writer call at a time — records, classifications, overrides — rides ONE `batch`
|
|
412
|
+
// invocation. Every op runs the SAME single-verb code path (no forked validator, D4), so a batch of
|
|
413
|
+
// N ops is record-equivalent to the same N single-verb invocations. TWO passes (D5): pass 1
|
|
414
|
+
// validates the WHOLE envelope structurally with ZERO writes (a bad envelope stops before any op
|
|
415
|
+
// runs); pass 2 applies the ops sequentially — a DOMAIN failure (a tooth, a missing receipt, a
|
|
416
|
+
// malformed record) stops the batch with an honest report, and the ops already applied stay recorded
|
|
417
|
+
// (the ledger is append-only — no rollback pretense).
|
|
418
|
+
|
|
419
|
+
// The verbs a batch operation may carry — the SAME functions the single verbs dispatch.
|
|
420
|
+
const BATCH_VERBS = new Set(['record', 'classify', 'override']);
|
|
421
|
+
|
|
422
|
+
// Pass 1 — structural envelope validation, ZERO writes (a usage failure, exit 2). Deep per-op payload
|
|
423
|
+
// validity is NOT checked here (that is pass 2's single-verb code path — checking it here would fork
|
|
424
|
+
// the validator); pass 1 proves only the envelope shape + a known verb per op, so no raw TypeError
|
|
425
|
+
// and no silent success reaches pass 2.
|
|
426
|
+
export const validateBatchEnvelope = (payload) => {
|
|
427
|
+
if (payload == null || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
428
|
+
throw usageFail('batch payload must be an object of the form { "operations": [ … ] }');
|
|
429
|
+
}
|
|
430
|
+
const operations = payload.operations;
|
|
431
|
+
if (!Array.isArray(operations)) throw usageFail('batch payload.operations must be an array of operations');
|
|
432
|
+
if (operations.length === 0) throw usageFail('batch payload.operations is empty — nothing to record');
|
|
433
|
+
operations.forEach((op, i) => {
|
|
434
|
+
if (op == null || typeof op !== 'object' || Array.isArray(op)) {
|
|
435
|
+
throw usageFail(`batch operation [${i}] must be an object of the form { "verb": …, … }`);
|
|
436
|
+
}
|
|
437
|
+
if (!BATCH_VERBS.has(op.verb)) {
|
|
438
|
+
throw usageFail(`batch operation [${i}] names an unknown verb "${op.verb}" (expected: record | classify | override)`);
|
|
439
|
+
}
|
|
440
|
+
// The batch resolves cwd/env from the INVOCATION, never per operation — a per-op override could
|
|
441
|
+
// read one project's state and write another's ledger. Reject it loudly.
|
|
442
|
+
for (const forbidden of ['cwd', 'env']) {
|
|
443
|
+
if (forbidden in op) {
|
|
444
|
+
throw usageFail(`batch operation [${i}] carries a "${forbidden}" field — the batch resolves cwd/env from the invocation, never per operation`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// fromReceipts must be a real boolean: a string "false" is truthy and would silently enable the
|
|
448
|
+
// draft. Reject any non-boolean when the field is present.
|
|
449
|
+
if ('fromReceipts' in op && typeof op.fromReceipts !== 'boolean') {
|
|
450
|
+
throw usageFail(`batch operation [${i}] has a non-boolean fromReceipts (${JSON.stringify(op.fromReceipts)}) — it must be the literal true or false`);
|
|
451
|
+
}
|
|
452
|
+
if ('fromReceipts' in op && op.verb !== 'record') {
|
|
453
|
+
throw usageFail(`batch operation [${i}] sets fromReceipts on "${op.verb}" — it applies only to record (even the literal false does not belong here)`);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return operations;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// Apply ONE operation through the SAME single-verb code path main() dispatches (including the
|
|
460
|
+
// --from-receipts backends[] draft for a record op). deps is threaded so a batch is as hermetic as
|
|
461
|
+
// the single verbs.
|
|
462
|
+
const applyOperation = (op, { cwd, env }, deps = {}) => {
|
|
463
|
+
const { verb, fromReceipts, ...payload } = op;
|
|
464
|
+
// The invocation's cwd/env WIN (spread last) — validateBatchEnvelope already forbids them in the
|
|
465
|
+
// payload, so this is defense-in-depth against a direct runBatch call bypassing the preflight.
|
|
466
|
+
if (verb === 'classify') return recordTriage({ ...payload, cwd, env }, deps);
|
|
467
|
+
if (verb === 'override') return recordOverride({ ...payload, cwd, env }, deps);
|
|
468
|
+
const params = { ...payload };
|
|
469
|
+
if (fromReceipts) {
|
|
470
|
+
const state = deps.buildState ? deps.buildState({ cwd, env }) : buildState({ cwd, env });
|
|
471
|
+
params.backends = draftBackendsFromReceipts({ state, findings: params.findings, explicitBackends: params.backends ?? [] }, deps);
|
|
472
|
+
}
|
|
473
|
+
return recordRound({ ...params, cwd, env }, deps);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// runBatch({ cwd, env, operations }, deps) → { applied: [ per-op results ], count }. Pass 2: apply
|
|
477
|
+
// sequentially, fail-fast on the first typed STOP; the ops already appended stay recorded.
|
|
478
|
+
export const runBatch = ({ cwd = process.cwd(), env = process.env, operations }, deps = {}) => {
|
|
479
|
+
const applied = [];
|
|
480
|
+
for (const [i, operation] of operations.entries()) {
|
|
481
|
+
try {
|
|
482
|
+
applied.push(applyOperation(operation, { cwd, env }, deps));
|
|
483
|
+
} catch (err) {
|
|
484
|
+
// Honest partial-success (D5): the STOP itself names the failing op index + the applied count.
|
|
485
|
+
// The ledger is append-only, so those ops are durable — resume by re-running the REMAINING ops.
|
|
486
|
+
err.message = `${err.message} [batch stopped at operation [${i}]: ${applied.length} of ${operations.length} operation(s) recorded before the stop and durable (append-only ledger); re-run the remaining operations]`;
|
|
487
|
+
throw err;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { applied, count: applied.length };
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// ── CLI (record / classify / override / batch) ─────────────────────────────────────────────────────
|
|
410
494
|
|
|
411
495
|
const HELP = `review-ledger-write — the review-round ledger WRITER (agent-workflow family, AD-045 + AD-047 + AD-048).
|
|
412
496
|
|
|
@@ -414,6 +498,7 @@ Usage:
|
|
|
414
498
|
node review-ledger-write.mjs record --json '<round-payload>' [--from-receipts] [--cwd <dir>]
|
|
415
499
|
node review-ledger-write.mjs classify --json '<triage-payload>' [--cwd <dir>]
|
|
416
500
|
node review-ledger-write.mjs override --json '<override-payload>' [--cwd <dir>]
|
|
501
|
+
node review-ledger-write.mjs batch --json '<{ "operations": [ … ] }>' [--cwd <dir>]
|
|
417
502
|
(every verb also accepts --json @<file> — the payload read from a file, keeping the command
|
|
418
503
|
line PLAIN: an inline JSON argv falls outside plain-invocation allow heuristics and prompts)
|
|
419
504
|
|
|
@@ -448,10 +533,24 @@ override appends one override record — the LOUD, durable waiver the gates cons
|
|
|
448
533
|
sanctions, SEGMENT-scoped — it dies at the next commit, D4).
|
|
449
534
|
REFUSES for a loop that is not the in-flight plan. QUARANTINE (a flaky/timed-out probe)
|
|
450
535
|
has NO override lane.
|
|
536
|
+
batch applies an ordered list of record / classify / override operations in ONE invocation — the
|
|
537
|
+
prompt-economy lane for a records stage (one writer call, not one per op). The payload is
|
|
538
|
+
{ "operations": [ { "verb": "record"|"classify"|"override", …that verb's payload } ] }; a
|
|
539
|
+
record op may carry "fromReceipts": true (the SAME draft as --from-receipts). Each op runs
|
|
540
|
+
the SAME per-verb code path (no forked validator), so a batch of N ops is record-equivalent
|
|
541
|
+
to the same N single invocations. TWO passes: the WHOLE envelope is validated structurally
|
|
542
|
+
first with ZERO writes (a bad envelope stops before any op runs); then ops apply sequentially
|
|
543
|
+
and fail-fast on the first typed STOP — the ops already applied stay recorded (the ledger is
|
|
544
|
+
append-only, no rollback), and the STOP names the failing op index + the applied count.
|
|
545
|
+
Resume by re-running the REMAINING operations.
|
|
451
546
|
|
|
452
547
|
The read-only checker is a SEPARATE tool: node review-ledger.mjs --check / --status / --json / --telemetry.
|
|
453
548
|
Exit codes: 0 written; 1 a typed STOP (teeth / malformed / missing receipt / fs error); 2 usage.`;
|
|
454
549
|
|
|
550
|
+
// The CLI subcommand set — the SINGLE source the dispatch and the doc-contract pin both read, so a
|
|
551
|
+
// documented verb list can never lag the dispatch (Phase 2.3 — the contract-test class).
|
|
552
|
+
export const SUBCOMMANDS = ['record', 'classify', 'override', 'batch'];
|
|
553
|
+
|
|
455
554
|
const parseArgs = (argv) => {
|
|
456
555
|
const opts = { cwd: undefined, json: undefined, fromReceipts: false };
|
|
457
556
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -500,11 +599,18 @@ export const main = (argv, ctx = {}) => {
|
|
|
500
599
|
try {
|
|
501
600
|
if (argv.includes('--help') || argv.includes('-h') || argv.length === 0) return { code: argv.length === 0 ? 2 : 0, stdout: HELP, stderr: '' };
|
|
502
601
|
const sub = argv[0];
|
|
503
|
-
if (sub
|
|
602
|
+
if (!SUBCOMMANDS.includes(sub)) throw usageFail(`unknown subcommand "${sub}" (expected: ${SUBCOMMANDS.join(' | ')})`);
|
|
504
603
|
const opts = parseArgs(argv.slice(1));
|
|
505
|
-
if (opts.fromReceipts && sub !== 'record') throw usageFail('--from-receipts applies only to `record`');
|
|
604
|
+
if (opts.fromReceipts && sub !== 'record') throw usageFail('--from-receipts applies only to `record` (a batch carries fromReceipts per operation)');
|
|
506
605
|
const payload = parsePayload(opts.json);
|
|
507
606
|
const cwd = opts.cwd ?? cwd0;
|
|
607
|
+
if (sub === 'batch') {
|
|
608
|
+
// Pass 1 (structural, ZERO writes) → pass 2 (sequential apply, fail-fast — prior ops durable).
|
|
609
|
+
const operations = validateBatchEnvelope(payload);
|
|
610
|
+
const { applied, count } = runBatch({ cwd, env, operations });
|
|
611
|
+
const writtenPath = applied.length ? applied[applied.length - 1].writtenPath : '(none)';
|
|
612
|
+
return { code: 0, stdout: `review-ledger-write: recorded ${count} operation(s) in one batch → ${writtenPath}`, stderr: '' };
|
|
613
|
+
}
|
|
508
614
|
if (opts.fromReceipts) {
|
|
509
615
|
// Draft backends[] from the current-fingerprint receipts + the supplied findings; origins /
|
|
510
616
|
// findings stay explicit. The drafted array then rides the normal recordRound teeth.
|
|
@@ -513,10 +619,10 @@ export const main = (argv, ctx = {}) => {
|
|
|
513
619
|
}
|
|
514
620
|
const result =
|
|
515
621
|
sub === 'record'
|
|
516
|
-
? recordRound({ cwd, env
|
|
622
|
+
? recordRound({ ...payload, cwd, env })
|
|
517
623
|
: sub === 'classify'
|
|
518
|
-
? recordTriage({ cwd, env
|
|
519
|
-
: recordOverride({ cwd, env
|
|
624
|
+
? recordTriage({ ...payload, cwd, env })
|
|
625
|
+
: recordOverride({ ...payload, cwd, env });
|
|
520
626
|
return { code: 0, stdout: `review-ledger-write: recorded a ${result.record.kind} for loop "${result.record.loop}" round ${result.record.round} → ${result.writtenPath}`, stderr: '' };
|
|
521
627
|
} catch (err) {
|
|
522
628
|
return { code: err.exitCode ?? 1, stdout: '', stderr: `review-ledger-write: ${err.message}` };
|
package/tools/review-ledger.mjs
CHANGED
|
@@ -568,7 +568,7 @@ gated commit, so the round-counter reset is earned, never declared.
|
|
|
568
568
|
rounds, override usage by scope, gate-run counts (quality-green / red-by-gate), fold runs,
|
|
569
569
|
observed-red receipts, quarantined probes. Counts only — interpretation stays with you.
|
|
570
570
|
|
|
571
|
-
The writer is a SEPARATE tool (review-ledger-write.mjs record/classify/override) — this read-only
|
|
571
|
+
The writer is a SEPARATE tool (review-ledger-write.mjs record/classify/override/batch) — this read-only
|
|
572
572
|
checker never imports it. Sandbox-safe: runs fully inside an OS sandbox (fs + git reads, no
|
|
573
573
|
network) — the D4 sandbox lane. Human residual: git commit --no-verify, ledger-file editing, and
|
|
574
574
|
forged counts remain possible — a self-discipline mechanism, not a security boundary.
|
|
@@ -163,9 +163,12 @@ const RUN_GATES_CWD_FLAG = '--cwd';
|
|
|
163
163
|
// ── the opt-in --bridge-tier (AD-044 Plan 4, Decision 2) ────────────────────────────────
|
|
164
164
|
// The FROZEN membership source: review-role wrapper NAMES only, spelled bare — that is how the
|
|
165
165
|
// wrappers are invoked (setup-backends places them as symlinks in ~/.local/bin). Roles come from
|
|
166
|
-
// THIS constant, never from the detector's wrapperCmds (which carries no role labels):
|
|
167
|
-
//
|
|
168
|
-
//
|
|
166
|
+
// THIS constant, never from the detector's wrapperCmds (which carries no role labels): codex-exec
|
|
167
|
+
// (execution role) and agy-run (probe role) are deliberately ABSENT. The velocity pain this tier
|
|
168
|
+
// closes is council REVIEW runs; codex-exec's nested-sandbox recovery is handled the canon way —
|
|
169
|
+
// route it outside ON the OBSERVED failure (orchestration.md §5), NOT a preemptive tier seed (AD-054
|
|
170
|
+
// final council: a blanket exclusion at opt-in contradicts «never a preemptive blanket»), guided by
|
|
171
|
+
// codex-exec.sh's own nested-sandbox detection hint.
|
|
169
172
|
export const BRIDGE_REVIEW_WRAPPERS = Object.freeze(['codex-review', 'agy-review']);
|
|
170
173
|
// Only the `code` review mode is auto-allowed (codex R2, Segment A): a bare `Bash(<wrapper>:*)`
|
|
171
174
|
// prefix would also cover the plan/diff file-argument modes, whose targets can point OUTSIDE the
|
|
@@ -190,13 +193,12 @@ const isQuotedGroundingToken = (token) => {
|
|
|
190
193
|
};
|
|
191
194
|
|
|
192
195
|
/**
|
|
193
|
-
* Derive the opt-in bridge-wrappers tier: one
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* skip). An absent bridge is a stated skip — its entry never derives.
|
|
196
|
+
* Derive the opt-in bridge-wrappers tier: one code-mode allow rule per PLACED review wrapper (the
|
|
197
|
+
* frozen constant is the membership source; placement is a probe, never a role source), the SAME
|
|
198
|
+
* wrapper names for sandbox.excludedCommands (the harness runs an excluded command OUTSIDE the
|
|
199
|
+
* sandbox, so a plain allowlisted invocation needs no sandbox-bypass approval — the zero-prompt
|
|
200
|
+
* wiring), and the grounding pre-step rule in its rendered quoted byte-form (derived only when
|
|
201
|
+
* agy-review is placed; an unseedable kit path is a stated skip). An absent bridge is a stated skip.
|
|
200
202
|
*/
|
|
201
203
|
export const deriveBridgeTierAllowlist = ({ findWrapper, groundingAbsPath } = {}) => {
|
|
202
204
|
const probe = findWrapper ?? ((cmd) => findOnPath(cmd).state === WRAPPER_PLACED);
|