@sabaiway/agent-workflow-kit 2.1.0 → 3.0.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 +45 -0
- package/README.md +6 -5
- package/SKILL.md +12 -8
- package/bin/install.mjs +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +8 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +150 -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 +95 -4
- 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-review-honesty.test.mjs +143 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +71 -10
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +93 -10
- 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/review-state.md +3 -3
- package/references/modes/upgrade.md +6 -6
- package/references/modes/velocity.md +2 -2
- package/references/scripts/archive-decisions.mjs +1 -1
- 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 +1 -1
- 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 +8 -69
- package/tools/cheap-agents.mjs +2 -2
- package/tools/commands.mjs +17 -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 +6 -83
- package/tools/hide-footprint.mjs +1 -1
- package/tools/inject-methodology.mjs +1 -1
- 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 +2 -2
- package/tools/orchestration-config.mjs +1 -1
- package/tools/orchestration-write.mjs +1 -1
- package/tools/presentation.mjs +1 -1
- package/tools/procedures.mjs +2 -2
- package/tools/recipes.mjs +60 -9
- package/tools/recommendations.mjs +63 -7
- package/tools/release-scan.mjs +1 -1
- package/tools/renderers.mjs +1 -1
- package/tools/review-state.mjs +288 -340
- package/tools/run-gates.mjs +216 -92
- package/tools/sandbox-masks.mjs +1 -1
- package/tools/semver-lite.mjs +1 -1
- package/tools/set-autonomy.mjs +1 -1
- package/tools/set-recipe.mjs +1 -1
- package/tools/setup-backends.mjs +1 -1
- package/tools/surface.mjs +1 -1
- package/tools/uninstall.mjs +1 -1
- package/tools/velocity-profile.mjs +2 -2
- package/tools/view-model.mjs +1 -1
- 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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
2
|
+
// gates-init.mjs — the consented docs/ai/gates.json FILL preview (D9; the pure derivation carried
|
|
3
|
+
// from the retired standalone seeder, AD-042/AD-052 invariants intact). Reached ONLY through
|
|
4
|
+
// explicitly-consenting prose (the init/bootstrap flows and the gates.md consent-fill section) —
|
|
5
|
+
// it is NOT a routable mode token, it sits OUTSIDE every velocity allowlist tier (a
|
|
6
|
+
// consent-per-run writer is never pre-approved), and the shipped gates.json TEMPLATE stays EMPTY
|
|
7
|
+
// (AD-021/AD-038): a populated declaration is per-entry maintainer consent recorded through this
|
|
8
|
+
// preview, never auto-seeding. ONE lifecycle contract: this preview runs at INIT; at UPGRADE the
|
|
9
|
+
// only gates.json writer is the consented legacy migration.
|
|
8
10
|
//
|
|
9
11
|
// What it offers (the derivation invariants, test-pinned) — CLOSED-WORLD since AD-052: an entry
|
|
10
12
|
// is offered only when every axis is proven safe by MEMBERSHIP in a finite, test-guarded set,
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
// executes project-controlled tooling (a node_modules/.bin PATH shim intercepts under every
|
|
30
32
|
// form) — the documented residual, disclosed in the preview, bounded by the two consents;
|
|
31
33
|
// • ids derive kebab-case from script names (build:prod → build-prod) and every offered entry
|
|
32
|
-
// passes the runner's validateDeclaration (
|
|
34
|
+
// passes the runner's validateDeclaration (this module imports the validator — NEVER the
|
|
33
35
|
// reverse: run-gates.mjs stays a runner that writes nothing);
|
|
34
36
|
// • the review-state candidate appears ONLY when docs/ai/orchestration.json DECLARES
|
|
35
37
|
// reviewed/council on plan-execution.review — the slot the checker enforces — with the
|
|
@@ -44,7 +46,7 @@
|
|
|
44
46
|
// .workflow-version == lineage-head stamp gate on --apply only (the velocity/gate-hook precedent).
|
|
45
47
|
//
|
|
46
48
|
// Exit codes: 0 done / dry-run; 1 precondition STOP (no deployment, stamp, symlink, malformed
|
|
47
|
-
// declaration, id collision); 2 usage. Dependency-free, Node >=
|
|
49
|
+
// declaration, id collision); 2 usage. Dependency-free, Node >= 22. No side effects on import.
|
|
48
50
|
|
|
49
51
|
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
50
52
|
import { join, resolve, dirname } from 'node:path';
|
|
@@ -58,39 +60,40 @@ const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
58
60
|
const KIT_ROOT = resolve(HERE, '..');
|
|
59
61
|
const TEMPLATE_PATH = join(KIT_ROOT, 'references', 'templates', 'gates.json');
|
|
60
62
|
const REVIEW_STATE_TOOL = join(KIT_ROOT, 'tools', 'review-state.mjs');
|
|
61
|
-
const
|
|
63
|
+
const COVERAGE_CHECK_TOOL = join(KIT_ROOT, 'tools', 'coverage-check.mjs');
|
|
62
64
|
const STAMP_REL = join('docs', 'ai', '.workflow-version');
|
|
63
65
|
|
|
64
66
|
const EXIT_OK = 0;
|
|
65
67
|
const EXIT_PRECONDITION = 1;
|
|
66
68
|
const EXIT_USAGE = 2;
|
|
67
69
|
|
|
68
|
-
export const
|
|
70
|
+
export const GATES_INIT_STOP = 'GATES_INIT_STOP';
|
|
69
71
|
const stop = (message) =>
|
|
70
|
-
Object.assign(new Error(`[agent-workflow-kit] ${message}`), { name: '
|
|
72
|
+
Object.assign(new Error(`[agent-workflow-kit] ${message}`), { name: 'GatesInitStop', code: GATES_INIT_STOP, exitCode: EXIT_PRECONDITION });
|
|
71
73
|
const usageFail = (message) =>
|
|
72
74
|
Object.assign(new Error(`[agent-workflow-kit] ${message}`), { exitCode: EXIT_USAGE });
|
|
73
75
|
|
|
74
76
|
// The trust-chain disclosure (AD-042) — printed with EVERY preview, token-pinned by the tests: a
|
|
75
77
|
// consent-seeded command becomes auto-approvable only after TWO explicit consents.
|
|
76
78
|
export const TRUST_CHAIN_DISCLOSURE =
|
|
77
|
-
'Disclosure: once the optional approval hook is wired
|
|
78
|
-
'auto-approves byte-exact declared gate commands from the
|
|
79
|
-
'and hook wiring (its own consent) are two separate
|
|
80
|
-
'own tooling, which executes project-controlled code
|
|
81
|
-
'consents bound it).';
|
|
82
|
-
|
|
83
|
-
const USAGE = `usage:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
derived { id, title, cmd } entries and writes NOTHING. --apply APPENDS exactly the
|
|
87
|
-
entries (--only <id> selects a subset; append-only — existing entries are never
|
|
88
|
-
removed, an id collision is refused). The offer is CLOSED-WORLD: only a
|
|
89
|
-
name (test / lint / type-check / build — never release/publish/deploy,
|
|
90
|
-
write-mode variant) whose BODY is a member of the literal runner
|
|
91
|
-
hook-free \`COREPACK_ENABLE_NETWORK=0 <pm> exec -- <body>\` form for
|
|
92
|
-
|
|
93
|
-
be declared by hand); non-gate-class
|
|
79
|
+
'Disclosure: gates.json is a PRIVILEGED file — once the optional approval hook is wired ' +
|
|
80
|
+
'(/agent-workflow-kit hook), it auto-approves byte-exact declared gate commands from the ' +
|
|
81
|
+
'project root — filling (this consent) and hook wiring (its own consent) are two separate ' +
|
|
82
|
+
'yeses. A script gate runs the project\'s own tooling, which executes project-controlled code ' +
|
|
83
|
+
'(this preview does not sandbox it; the two consents bound it).';
|
|
84
|
+
|
|
85
|
+
const USAGE = `usage: gates-init [--dry-run | --apply] [--only <id>]... [--cwd <dir>] [--help]
|
|
86
|
+
|
|
87
|
+
The consented FILL preview for the project's own docs/ai/gates.json (D9). Default is --dry-run:
|
|
88
|
+
prints the derived { id, title, cmd } entries and writes NOTHING. --apply APPENDS exactly the
|
|
89
|
+
consented entries (--only <id> selects a subset; append-only — existing entries are never
|
|
90
|
+
modified or removed, an id collision is refused). The offer is CLOSED-WORLD: only a
|
|
91
|
+
terminating-class script name (test / lint / type-check / build — never release/publish/deploy,
|
|
92
|
+
never watch/serve, never a write-mode variant) whose BODY is a member of the literal runner
|
|
93
|
+
allowlist is offered, as the hook-free \`COREPACK_ENABLE_NETWORK=0 <pm> exec -- <body>\` form for
|
|
94
|
+
the detected package manager. A gate-class script whose body is NOT in the allowlist is screened
|
|
95
|
+
out with a note naming it (a command you trust can still be declared by hand); non-gate-class
|
|
96
|
+
names are excluded silently, as always.
|
|
94
97
|
${TRUST_CHAIN_DISCLOSURE}`;
|
|
95
98
|
|
|
96
99
|
// ── candidate classification: the NAME screens (the LOCKED derivation invariants) ──────
|
|
@@ -310,12 +313,14 @@ export const reviewStateCandidate = (cwd, deps = {}) => {
|
|
|
310
313
|
}
|
|
311
314
|
};
|
|
312
315
|
|
|
313
|
-
// The conditional
|
|
314
|
-
// review-state candidate (offered ONLY when plan-execution.review is reviewed/council), keyed on
|
|
315
|
-
// same slot, path resolved + QUOTED.
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
// The conditional COVERAGE-CHECK candidate (D3(a)) — the SAME consent + conditional rule as the
|
|
317
|
+
// review-state candidate (offered ONLY when plan-execution.review is reviewed/council), keyed on
|
|
318
|
+
// the same slot, path resolved + QUOTED. Together they are the canonical core pair `run-gates
|
|
319
|
+
// --final` requires (the checker declared LAST — buildOffer appends it last so a whole-offer
|
|
320
|
+
// apply lands final-ready); review-state gates receipt satisfaction, coverage-check verifies the
|
|
321
|
+
// consumed lcov + the red-proof declarations.
|
|
322
|
+
export const coverageCheckCandidate = (cwd, deps = {}) => {
|
|
323
|
+
const toolPath = deps.coverageCheckTool ?? COVERAGE_CHECK_TOOL;
|
|
319
324
|
try {
|
|
320
325
|
const { config } = loadConfig(resolve(cwd), deps.readFile ?? readFileSync, deps.lstat ?? lstatSync);
|
|
321
326
|
const declared = config?.['plan-execution']?.review;
|
|
@@ -324,15 +329,15 @@ export const reviewLedgerCandidate = (cwd, deps = {}) => {
|
|
|
324
329
|
return {
|
|
325
330
|
candidate: null,
|
|
326
331
|
note:
|
|
327
|
-
`the
|
|
332
|
+
`the coverage-check candidate was withheld: the resolved kit path contains shell ` +
|
|
328
333
|
`metacharacters that do not survive double-quoting (${toolPath}) — declare the gate ` +
|
|
329
|
-
`by hand per references/modes/
|
|
334
|
+
`by hand per references/modes/coverage-check.md`,
|
|
330
335
|
};
|
|
331
336
|
}
|
|
332
337
|
return {
|
|
333
338
|
candidate: {
|
|
334
|
-
id: '
|
|
335
|
-
title: '
|
|
339
|
+
id: 'coverage-check',
|
|
340
|
+
title: 'Changed-line coverage + red-proof verification (the final-run checker)',
|
|
336
341
|
cmd: `node "${toolPath}" --check`,
|
|
337
342
|
},
|
|
338
343
|
note: null,
|
|
@@ -340,7 +345,7 @@ export const reviewLedgerCandidate = (cwd, deps = {}) => {
|
|
|
340
345
|
} catch (err) {
|
|
341
346
|
return {
|
|
342
347
|
candidate: null,
|
|
343
|
-
note: `orchestration config unreadable (${err.message}) — the
|
|
348
|
+
note: `orchestration config unreadable (${err.message}) — the coverage-check candidate was not evaluated`,
|
|
344
349
|
};
|
|
345
350
|
}
|
|
346
351
|
};
|
|
@@ -356,22 +361,23 @@ const assertOnlyIdsOffered = (offer, onlyIds = []) => {
|
|
|
356
361
|
}
|
|
357
362
|
};
|
|
358
363
|
|
|
359
|
-
// The full offer: script entries + the conditional review-state +
|
|
360
|
-
//
|
|
361
|
-
//
|
|
364
|
+
// The full offer: script entries + the conditional review-state + coverage-check candidates
|
|
365
|
+
// (coverage-check LAST — the `run-gates --final` declaration-shape rule requires the checker as
|
|
366
|
+
// the last declared gate, so a whole-offer apply is final-ready by construction). Both key on the
|
|
367
|
+
// same slot (plan-execution.review reviewed/council) but gate distinct axes.
|
|
362
368
|
export const buildOffer = (cwd, deps = {}) => {
|
|
363
369
|
const scripts = deriveScripts(cwd, deps);
|
|
364
370
|
const rs = reviewStateCandidate(cwd, deps);
|
|
365
|
-
const
|
|
366
|
-
const candidates = [rs.candidate,
|
|
371
|
+
const cc = coverageCheckCandidate(cwd, deps);
|
|
372
|
+
const candidates = [rs.candidate, cc.candidate].filter(Boolean);
|
|
367
373
|
return {
|
|
368
374
|
entries: [...scripts.entries, ...candidates],
|
|
369
|
-
notes: [...scripts.notes, rs.note,
|
|
375
|
+
notes: [...scripts.notes, rs.note, cc.note].filter(Boolean),
|
|
370
376
|
};
|
|
371
377
|
};
|
|
372
378
|
|
|
373
379
|
// The RUNNABLE apply invocation for a given project — this tool has no bin and no mode token, so
|
|
374
|
-
// the consent step must print the real command, never a bare `
|
|
380
|
+
// the consent step must print the real command, never a bare `gates-init`. Consent integrity: a
|
|
375
381
|
// previewed --only subset is carried into the hint VERBATIM (ids are shell-safe by construction —
|
|
376
382
|
// kebabIdOf output / the fixed `review-state`), so following the hint can never widen the consent.
|
|
377
383
|
// Paths that do not survive double-quoting (the reviewStateCandidate screen, same pattern) make
|
|
@@ -386,7 +392,7 @@ export const applyInvocationFor = (cwd, onlyIds = []) => {
|
|
|
386
392
|
const GENERIC_APPLY_HINT = 're-run this same command with --apply [--only <id>]...';
|
|
387
393
|
|
|
388
394
|
export const formatPreview = (offer, applyInvocation = null, { explicitOnly = false } = {}) => {
|
|
389
|
-
const lines = ['[agent-workflow-kit] gates
|
|
395
|
+
const lines = ['[agent-workflow-kit] gates fill preview (dry-run — nothing was written):'];
|
|
390
396
|
if (!offer.entries.length) {
|
|
391
397
|
lines.push(' nothing to offer — no seedable terminating verification scripts were found.');
|
|
392
398
|
}
|
|
@@ -418,14 +424,14 @@ const loadExistingDeclaration = (cwd, deps = {}) => {
|
|
|
418
424
|
try {
|
|
419
425
|
return JSON.parse(String(read(full, 'utf8')));
|
|
420
426
|
} catch (err) {
|
|
421
|
-
throw stop(`${GATES_REL}: malformed JSON (${err.message}) — fix it by hand;
|
|
427
|
+
throw stop(`${GATES_REL}: malformed JSON (${err.message}) — fix it by hand; this preview never writes over a declaration it cannot parse`);
|
|
422
428
|
}
|
|
423
429
|
})();
|
|
424
430
|
const gates = (() => {
|
|
425
431
|
try {
|
|
426
432
|
return validateDeclaration(parsed);
|
|
427
433
|
} catch (err) {
|
|
428
|
-
throw stop(`${err.message} — fix it by hand;
|
|
434
|
+
throw stop(`${err.message} — fix it by hand; this preview never writes over an invalid declaration`);
|
|
429
435
|
}
|
|
430
436
|
})();
|
|
431
437
|
return { outcome: 'loaded', readme: typeof parsed._README === 'string' ? parsed._README : undefined, gates };
|
|
@@ -453,7 +459,7 @@ const readStampValue = (cwd, deps = {}) => {
|
|
|
453
459
|
};
|
|
454
460
|
|
|
455
461
|
// ── apply (append exactly the consented entries) ───────────────────────────────────────
|
|
456
|
-
export const
|
|
462
|
+
export const applyFill = ({ cwd, onlyIds = [] }, deps = {}) => {
|
|
457
463
|
assertDocsAiDeployment(cwd, deps, { stop, noun: 'a gate declaration', rel: GATES_REL });
|
|
458
464
|
const stampValue = readStampValue(cwd, deps);
|
|
459
465
|
if (stampValue !== EXPECTED_WORKFLOW_VERSION) {
|
|
@@ -474,7 +480,7 @@ export const applySeed = ({ cwd, onlyIds = [] }, deps = {}) => {
|
|
|
474
480
|
if (collisions.length) {
|
|
475
481
|
throw stop(
|
|
476
482
|
`id collision — already declared in ${GATES_REL}: ${collisions.join(', ')} (append-only: the ` +
|
|
477
|
-
`
|
|
483
|
+
`fill never modifies or removes an existing entry; pick the others with --only, or edit by hand)`,
|
|
478
484
|
);
|
|
479
485
|
}
|
|
480
486
|
|
|
@@ -539,7 +545,7 @@ export const main = (argv = process.argv.slice(2), deps = {}) => {
|
|
|
539
545
|
log(formatPreview(filtered, applyInvocationFor(cwd, args.only), { explicitOnly: args.only.length > 0 }));
|
|
540
546
|
return EXIT_OK;
|
|
541
547
|
}
|
|
542
|
-
const result =
|
|
548
|
+
const result = applyFill({ cwd, onlyIds: args.only }, deps);
|
|
543
549
|
if (result.outcome === 'nothing') {
|
|
544
550
|
log('[agent-workflow-kit] nothing to offer — no seedable terminating verification scripts were found; wrote nothing.');
|
|
545
551
|
for (const note of result.notes) log(` note: ${note}`); // the user learns WHY (same note as the preview)
|
package/tools/grounding.mjs
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// not-ignored path (a new untracked file would itself move the review fingerprint the facts are
|
|
24
24
|
// about to ground). stdout is the default. It never commits and never runs a subscription CLI.
|
|
25
25
|
//
|
|
26
|
-
// Dependency-free, Node >=
|
|
26
|
+
// Dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
|
|
27
27
|
|
|
28
28
|
import { readFileSync, writeFileSync, lstatSync, realpathSync } from 'node:fs';
|
|
29
29
|
import { resolve, join, relative, isAbsolute, dirname, basename, sep } from 'node:path';
|
|
@@ -31,11 +31,6 @@ import { tmpdir } from 'node:os';
|
|
|
31
31
|
import { pathToFileURL } from 'node:url';
|
|
32
32
|
import { spawnSync } from 'node:child_process';
|
|
33
33
|
import { fail } from './orchestration-config.mjs';
|
|
34
|
-
// (e) --ledger-summary (BUGFREE-3 / AD-049): a computed, loop/base-SCOPED review-ledger digest for
|
|
35
|
-
// the facts payload. The review ledger is read-only here — grounding never writes it. ORIGINS +
|
|
36
|
-
// computeTelemetry give the counts; the segment filter gives the scope.
|
|
37
|
-
import { ORIGINS, computeTelemetry, filterSegmentRecords, readLedger, resolveLedgerPath, resolveBase } from './review-ledger.mjs';
|
|
38
|
-
import { plansInFlight } from './review-state.mjs';
|
|
39
34
|
// (f) --autonomy (AD-044 Plan 3): the effective per-project autonomy policy for the facts payload.
|
|
40
35
|
// READ core only — never autonomy-write.mjs (the import-split invariant).
|
|
41
36
|
import { AUTONOMY_REL, loadAutonomy, resolveAutonomy, isSparseSeedConfig } from './autonomy-config.mjs';
|
|
@@ -119,72 +114,10 @@ export const trimToBudget = (payload, budget) => {
|
|
|
119
114
|
return { text, trimmedBytes: bytes - keep };
|
|
120
115
|
};
|
|
121
116
|
|
|
122
|
-
// ── (e) --ledger-summary: a loop/base-SCOPED review-ledger digest (read-only, computed) ─────────
|
|
123
|
-
// `computeTelemetry` aggregates ALL loops and is not per-round-facts-shaped, so this renders the
|
|
124
|
-
// in-flight SEGMENT only (plan-execution, loop, base) — the telemetry COUNTS filtered to that
|
|
125
|
-
// segment, plus a terse per-round/triage/override render. Deliberately compact + distinct from the
|
|
126
|
-
// review-ledger `--status` human view (that one is indented + segment-grouped): this is a byte-
|
|
127
|
-
// budgeted FACTS block that agy/codex review against, so unrelated loops are excluded by construction.
|
|
128
|
-
|
|
129
|
-
const baseTag = (base) => (base === null ? '(unborn branch)' : String(base).slice(0, 12));
|
|
130
|
-
const countsOf = (obj) => {
|
|
131
|
-
const keys = Object.keys(obj).sort();
|
|
132
|
-
return keys.length === 0 ? '(none)' : keys.map((k) => `${k}:${obj[k]}`).join(' ');
|
|
133
|
-
};
|
|
134
|
-
const summaryRoundLine = (r) =>
|
|
135
|
-
`round ${r.round} — ${r.backends.map((b) => `${b.backend} ${b.degraded ? `degraded(${b.reason})` : `${b.blockers}/${b.majors}/${b.minors} ${b.verdict}`}`).join(', ')}` +
|
|
136
|
-
(r.findings.length ? ` [${r.findings.map((f) => `${f.findingKey}(${f.severity})`).join(', ')}]` : '');
|
|
137
|
-
const summaryTriageLine = (t) => `triage @round ${t.round} — ${t.classifications.map((c) => `${c.findingKey}=${c.class}`).join(', ')}`;
|
|
138
|
-
const summaryOverrideLine = (o) =>
|
|
139
|
-
`override @round ${o.round} [${o.scope}] — ${o.scope === 'oracle-change' ? o.files.join(', ') : o.scope === 'size-cap' ? `sanctioned ${o.sanctionedLines} lines` : o.testId}: ${o.reason}`;
|
|
140
|
-
const summaryGateRunLine = (g) => `gate-run — status=${g.summary.status} ${g.summary.passed}/${g.summary.gates} green`;
|
|
141
|
-
const summaryRecordLine = (r) =>
|
|
142
|
-
r.kind === 'round' ? summaryRoundLine(r) : r.kind === 'triage' ? summaryTriageLine(r) : r.kind === 'override' ? summaryOverrideLine(r) : summaryGateRunLine(r);
|
|
143
|
-
|
|
144
|
-
// renderLedgerSummary(records, { loop, base }) → the scoped facts section, or '' when the segment
|
|
145
|
-
// holds no records (empty/absent → nothing to ground). Pure: the caller does the I/O.
|
|
146
|
-
export const renderLedgerSummary = (records, { loop, base }) => {
|
|
147
|
-
const segment = filterSegmentRecords(records, { activity: PLAN_EXECUTION, loop, base });
|
|
148
|
-
if (segment.length === 0) return '';
|
|
149
|
-
const t = computeTelemetry(segment, []).loops[0];
|
|
150
|
-
const lines = [
|
|
151
|
-
`## Review-ledger summary — loop ${loop} @ base ${baseTag(base)}`,
|
|
152
|
-
'',
|
|
153
|
-
`rounds ${t.rounds} · divergence-rounds ${t.divergenceRounds}`,
|
|
154
|
-
`finding origins — ${ORIGINS.map((k) => `${k}:${t.origins[k]}`).join(' ')}`,
|
|
155
|
-
`classifications — ${countsOf(t.classifications)}`,
|
|
156
|
-
`backend verdicts — ${Object.keys(t.backendVerdicts).sort().map((b) => `${b}{${countsOf(t.backendVerdicts[b])}}`).join(' · ') || '(none)'}`,
|
|
157
|
-
`overrides — ${countsOf(t.overrides)}`,
|
|
158
|
-
'',
|
|
159
|
-
...segment.map(summaryRecordLine),
|
|
160
|
-
];
|
|
161
|
-
return `${lines.join('\n')}\n`;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// resolveLedgerSummary({ cwd, env }) → the scoped section text for the SINGLE in-flight plan-execution
|
|
165
|
-
// segment. A loud STOP unless exactly one plan is in flight (the family's single-plan discipline —
|
|
166
|
-
// never guess which loop to ground). Reads the ledger read-only.
|
|
167
117
|
const gitTop = (cwd) => {
|
|
168
118
|
const r = gitLine(['rev-parse', '--show-toplevel'], cwd);
|
|
169
119
|
return r && r.status === 0 ? r.stdout.replace(/\r?\n$/, '') : cwd;
|
|
170
120
|
};
|
|
171
|
-
export const resolveLedgerSummary = ({ cwd, env }) => {
|
|
172
|
-
const root = gitTop(cwd);
|
|
173
|
-
const plans = plansInFlight(root);
|
|
174
|
-
if (plans.length !== 1) {
|
|
175
|
-
throw fail(1, `--ledger-summary needs exactly one in-flight plan (in flight: ${plans.length ? plans.join(', ') : 'none'}) — resolve to one active plan`);
|
|
176
|
-
}
|
|
177
|
-
const loop = plans[0].replace(/\.md$/, '');
|
|
178
|
-
const base = resolveBase(cwd);
|
|
179
|
-
const ledgerPath = resolveLedgerPath(cwd, env);
|
|
180
|
-
const { records, readError, malformed, malformedReasons } = ledgerPath ? readLedger(ledgerPath) : { records: [], malformed: 0 };
|
|
181
|
-
// Fail CLOSED like every sibling ledger reader (No-silent-failures): an unreadable OR malformed
|
|
182
|
-
// ledger must never silently render an empty/partial digest the reviewer then grounds against — an
|
|
183
|
-
// empty section would be indistinguishable from a legitimately-empty segment.
|
|
184
|
-
if (readError) throw fail(1, `--ledger-summary cannot read the ledger (${readError}) — failing closed; inspect ${ledgerPath}`);
|
|
185
|
-
if (malformed > 0) throw fail(1, `--ledger-summary: the ledger has ${malformed} malformed line(s) — failing closed (a dropped line could hide a round/finding): ${malformedReasons.join('; ')}`);
|
|
186
|
-
return renderLedgerSummary(records, { loop, base });
|
|
187
|
-
};
|
|
188
121
|
|
|
189
122
|
// ── (f) --autonomy: the effective autonomy policy as a computed facts block (AD-044 Plan 3) ─────
|
|
190
123
|
// The ONE shared resolver (resolveAutonomy) renders the FULL effective policy — every red-line and
|
|
@@ -310,7 +243,7 @@ export const assertScratchDestination = (outPath, cwd) => {
|
|
|
310
243
|
const HELP = `grounding — grounded-review facts assembler for the agent-workflow family (AD-038).
|
|
311
244
|
|
|
312
245
|
Usage:
|
|
313
|
-
node grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--
|
|
246
|
+
node grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--reserve-bytes <n>] [--out <path>]
|
|
314
247
|
|
|
315
248
|
--constraints slice the root AGENTS.md "Hard Constraints" section verbatim
|
|
316
249
|
(exactly one matching heading, else a loud STOP)
|
|
@@ -322,10 +255,6 @@ Usage:
|
|
|
322
255
|
--plan <path> extract the plan's decision-bearing sections verbatim + whole:
|
|
323
256
|
"## Approach" + "## Verification" (REQUIRED — STOP if missing),
|
|
324
257
|
"## Decisions (locked)" when present; a duplicate heading is a STOP
|
|
325
|
-
--ledger-summary append a COMPUTED review-ledger digest for the SINGLE in-flight
|
|
326
|
-
plan-execution segment (rounds · origins · classifications · verdicts ·
|
|
327
|
-
overrides + a per-round render) — "computed, not remembered" facts; a
|
|
328
|
-
loud STOP unless exactly one plan is in flight
|
|
329
258
|
--reserve-bytes <n> the artifact share agy-review will add around these facts — the output
|
|
330
259
|
budget becomes AGY_MAX_PROMPT_BYTES − n (loud tail-trim on overflow)
|
|
331
260
|
--out <path> write instead of stdout — system-temp scratch (rewritable), or a FRESH
|
|
@@ -345,12 +274,10 @@ const parseArgs = (argv) => {
|
|
|
345
274
|
let plan = null;
|
|
346
275
|
let out = null;
|
|
347
276
|
let reserve = 0;
|
|
348
|
-
let ledgerSummary = false;
|
|
349
277
|
for (let i = 0; i < argv.length; i += 1) {
|
|
350
278
|
const a = argv[i];
|
|
351
279
|
if (a === '--constraints') constraints = true;
|
|
352
280
|
else if (a === '--autonomy') autonomy = true;
|
|
353
|
-
else if (a === '--ledger-summary') ledgerSummary = true;
|
|
354
281
|
else if (a === '--plan') {
|
|
355
282
|
plan = argv[i + 1];
|
|
356
283
|
if (!plan || plan.startsWith('--')) throw fail(2, '--plan requires a <path>');
|
|
@@ -366,10 +293,10 @@ const parseArgs = (argv) => {
|
|
|
366
293
|
i += 1;
|
|
367
294
|
} else throw fail(2, `unknown argument: ${a}`);
|
|
368
295
|
}
|
|
369
|
-
if (!constraints && !autonomy && plan == null
|
|
370
|
-
throw fail(2, 'nothing to assemble — pass --constraints, --autonomy, --plan <path
|
|
296
|
+
if (!constraints && !autonomy && plan == null) {
|
|
297
|
+
throw fail(2, 'nothing to assemble — pass --constraints, --autonomy, and/or --plan <path>');
|
|
371
298
|
}
|
|
372
|
-
return { constraints, autonomy, plan, out, reserve
|
|
299
|
+
return { constraints, autonomy, plan, out, reserve };
|
|
373
300
|
};
|
|
374
301
|
|
|
375
302
|
const resolveBudget = (env, reserve) => {
|
|
@@ -389,7 +316,7 @@ export const main = (argv, ctx = {}) => {
|
|
|
389
316
|
const env = ctx.env ?? process.env;
|
|
390
317
|
try {
|
|
391
318
|
if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
|
|
392
|
-
const { constraints, autonomy, plan, out, reserve
|
|
319
|
+
const { constraints, autonomy, plan, out, reserve } = parseArgs(argv);
|
|
393
320
|
const budget = resolveBudget(env, reserve);
|
|
394
321
|
|
|
395
322
|
const readOrStop = (path, label) => {
|
|
@@ -424,10 +351,6 @@ export const main = (argv, ctx = {}) => {
|
|
|
424
351
|
const parts = [];
|
|
425
352
|
const assembled = assembleGrounding({ constraintsText, autonomyText, planText, planLabel: plan ?? 'plan' });
|
|
426
353
|
if (assembled) parts.push(assembled);
|
|
427
|
-
if (ledgerSummary) {
|
|
428
|
-
const summary = resolveLedgerSummary({ cwd, env });
|
|
429
|
-
if (summary) parts.push(summary);
|
|
430
|
-
}
|
|
431
354
|
const payload = parts.join('\n');
|
|
432
355
|
const { text, trimmedBytes } = trimToBudget(payload, budget);
|
|
433
356
|
const stderr = trimmedBytes > 0
|
package/tools/hide-footprint.mjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// path). Add-local-first, then DETECT + REPORT the residual legacy global block — removal is the
|
|
18
18
|
// explicit, ASK-gated `--remove-global` (prints a restorable backup), never a default (an arbitrary
|
|
19
19
|
// host's OTHER hidden repo may rely on the same root-anchored global lines). Windows is supported (text
|
|
20
|
-
// edit; forward-slash patterns; CRLF preserved via EOL detection). Dependency-free, Node >=
|
|
20
|
+
// edit; forward-slash patterns; CRLF preserved via EOL detection). Dependency-free, Node >= 22.
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
readFileSync, writeFileSync, statSync, readdirSync,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// The live read is lazy + fail-loud: resolve+read the engine ONLY when a fill is actually needed, and
|
|
19
19
|
// STOP loudly (never a silent fallback) when the engine is needed but absent/invalid.
|
|
20
20
|
//
|
|
21
|
-
// Pure string functions (testable with byte-preservation fixtures); dependency-free, Node >=
|
|
21
|
+
// Pure string functions (testable with byte-preservation fixtures); dependency-free, Node >= 22.
|
|
22
22
|
//
|
|
23
23
|
// Canonical-refresh (AD-025): a slot filled BEFORE a clause existed is normally preserved verbatim
|
|
24
24
|
// (reconcile only fills an EMPTY slot). To push a NEW canonical clause to the EXISTING filled base
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// count sentinel + intrinsic invariants (anchoring, uniqueness, disjointness, no subsumption) — and
|
|
15
15
|
// references/contracts.md carries the human-readable mirror table, kept in sync by review.
|
|
16
16
|
//
|
|
17
|
-
// Pure, dependency-free, Node >=
|
|
17
|
+
// Pure, dependency-free, Node >= 22. The only fs touch is expandGlob (readdir/stat of a glob parent),
|
|
18
18
|
// injected so the registry stays unit-testable without the real filesystem.
|
|
19
19
|
|
|
20
20
|
import { readdirSync, statSync } from 'node:fs';
|
package/tools/labels.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// imports family-registry for vocabulary, and family-registry imports + re-exports the public subset
|
|
8
8
|
// it exported before (the 7 state constants + DISPLAY_NAMES), so every existing importer stays green.
|
|
9
9
|
//
|
|
10
|
-
// Pure data, no side effects, Node >=
|
|
10
|
+
// Pure data, no side effects, Node >= 22. Every export is frozen — this is a contract, not a mutable.
|
|
11
11
|
|
|
12
12
|
// ── manifestState values (internal; the detect-backends precedence, generalized to any member) ──────
|
|
13
13
|
// These are INTERNAL — they must never reach a user surface verbatim (mapped through STATE_PUBLIC
|
package/tools/lcov.mjs
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// lcov.mjs — a dependency-free LCOV parser for the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// (the diff-cover / c8 --reporter=lcov pattern) and this module reads it into a per-file
|
|
6
|
-
// line→hits map, keyed by the SAME canonical absolute path the V8 coverage map uses — so the
|
|
7
|
-
// runner's ONE uncovered-changed loop consumes either source unchanged (computeUncoveredLines /
|
|
8
|
-
// effectiveCount stay the V8-only path, D10).
|
|
2
|
+
// lcov.mjs — a dependency-free LCOV parser for the coverage-check final-run checker (D3(d)).
|
|
3
|
+
// The declared unit-tests gate leaves an LCOV file at the fixed git-dir path and this module reads
|
|
4
|
+
// it into a per-file line→hits map, keyed by a canonical absolute path.
|
|
9
5
|
//
|
|
10
6
|
// FAIL-CLOSED posture (the whole reason the fold gate exists): a malformed record can never mark a
|
|
11
7
|
// line COVERED — a DA with a valid line but a non-integer HIT count reads the line UNCOVERED (hits 0,
|
|
@@ -16,7 +12,7 @@
|
|
|
16
12
|
// changed executable line with NO DA entry reads non-executable — LCOV carries no separate
|
|
17
13
|
// executability signal; the V8 path, which reads an absent line as uncovered, differs by format.)
|
|
18
14
|
//
|
|
19
|
-
// Dependency-free, Node >=
|
|
15
|
+
// Dependency-free, Node >= 22. No side effects on import.
|
|
20
16
|
|
|
21
17
|
import { join, resolve, isAbsolute, sep } from 'node:path';
|
|
22
18
|
import { realpathSync } from 'node:fs';
|
|
@@ -80,8 +76,8 @@ const defaultCanon = (p) => {
|
|
|
80
76
|
};
|
|
81
77
|
|
|
82
78
|
// Segment-safe containment ('/a' never contains '/ab'); correct at the filesystem root (mirrors
|
|
83
|
-
//
|
|
84
|
-
//
|
|
79
|
+
// core-evidence.mjs containsPath). Defined locally so lcov.mjs imports no family module (its
|
|
80
|
+
// consumers import THIS module).
|
|
85
81
|
const containsPath = (realRoot, realAbs) =>
|
|
86
82
|
realAbs === realRoot || realAbs.startsWith(realRoot.endsWith(sep) ? realRoot : realRoot + sep);
|
|
87
83
|
|
package/tools/lens-region.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// never a silent truncate; a file without frontmatter/`maxLines` skips the guard with a stated
|
|
23
23
|
// note (such a file is outside the docs cap gate anyway).
|
|
24
24
|
//
|
|
25
|
-
// Pure string functions (fs only in the CLI); dependency-free, Node >=
|
|
25
|
+
// Pure string functions (fs only in the CLI); dependency-free, Node >= 22.
|
|
26
26
|
|
|
27
27
|
import { normalizeCanonical } from './orchestration-config.mjs';
|
|
28
28
|
|
|
@@ -704,6 +704,25 @@ export const validateManifest = (skillDir) => {
|
|
|
704
704
|
else validateModeCatalog(modeCatalog, roles, errors);
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
+
// `posture` (strip-the-kit D5): the DEFAULT dispatch posture pins the wrapper ships — the
|
|
708
|
+
// checkable source the kit renders the configured posture from (before D5 these lived only in
|
|
709
|
+
// shell defaults + prose). ADDITIVE-OPTIONAL like modeCatalog (absence = a bridge predating
|
|
710
|
+
// posture labeling); a PRESENT block is typed-validated with the SAME shape rule the receipt
|
|
711
|
+
// predicate applies (isValidReceiptPosture's manifest twin): `model` a non-empty string;
|
|
712
|
+
// `effort` (when present) a non-empty string; `tier` (when present) a non-empty string or null.
|
|
713
|
+
if (Object.hasOwn(manifest, 'posture')) {
|
|
714
|
+
const posture = manifest.posture;
|
|
715
|
+
if (posture === null || typeof posture !== 'object' || Array.isArray(posture)) {
|
|
716
|
+
errors.push('`posture` must be an object of dispatch-posture pins');
|
|
717
|
+
} else {
|
|
718
|
+
if (typeof posture.model !== 'string' || posture.model.length === 0) errors.push('`posture.model` must be a non-empty string');
|
|
719
|
+
if (Object.hasOwn(posture, 'effort') && (typeof posture.effort !== 'string' || posture.effort.length === 0)) errors.push('`posture.effort` (when declared) must be a non-empty string');
|
|
720
|
+
if (Object.hasOwn(posture, 'tier') && posture.tier !== null && (typeof posture.tier !== 'string' || posture.tier.length === 0)) errors.push('`posture.tier` (when declared) must be a non-empty string or null');
|
|
721
|
+
const unknown = Object.keys(posture).filter((k) => !['model', 'effort', 'tier'].includes(k));
|
|
722
|
+
if (unknown.length > 0) errors.push(`\`posture\` carries unknown key(s): ${unknown.join(', ')} (closed vocabulary: model, effort, tier)`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
707
726
|
if (!isStub) {
|
|
708
727
|
const auth = readAuthoritativeVersion(skillDir);
|
|
709
728
|
if (auth.version == null) errors.push(`could not resolve an authoritative version (${auth.from})`);
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
//
|
|
24
24
|
// Write discipline: preview (--dry-run) is the DEFAULT and writes NOTHING; --apply performs the
|
|
25
25
|
// migration. It NEVER commits. Exit codes: 0 done / dry-run / no-op; 1 precondition STOP (no
|
|
26
|
-
// deployment, no writable snapshot base, a failed migration); 2 usage. Dependency-free, Node >=
|
|
26
|
+
// deployment, no writable snapshot base, a failed migration); 2 usage. Dependency-free, Node >= 22.
|
|
27
27
|
// No side effects on import.
|
|
28
28
|
|
|
29
29
|
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs';
|
|
@@ -65,7 +65,7 @@ conservation proof) and writes NOTHING. --apply snapshots, force-refreshes the e
|
|
|
65
65
|
then runs the conservation-checked migration. It NEVER commits — review the tree and commit yourself.`;
|
|
66
66
|
|
|
67
67
|
// The mutually-exclusive dry-run/apply parse (a consent-gated writer never lets a later flag silently
|
|
68
|
-
// decide whether it mutates) + --cwd
|
|
68
|
+
// decide whether it mutates) + --cwd — the family's shared consented-writer contract.
|
|
69
69
|
export const parseArgs = (argv) => {
|
|
70
70
|
const parsed = argv.reduce(
|
|
71
71
|
(acc, a, i) => {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
//
|
|
17
17
|
// This module performs NO filesystem WRITES — only reads (loadConfig). The single fs-writer lives in
|
|
18
18
|
// orchestration-write.mjs, which procedures.mjs never imports, so "procedures never reaches a writer"
|
|
19
|
-
// is structurally true. Pure-where-possible (fs injectable), dependency-free, Node >=
|
|
19
|
+
// is structurally true. Pure-where-possible (fs injectable), dependency-free, Node >= 22. No side
|
|
20
20
|
// effects on import.
|
|
21
21
|
|
|
22
22
|
import { readFileSync, lstatSync } from 'node:fs';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// consumed here with this module's own STOP identity, so the public API + error contract are
|
|
11
11
|
// unchanged (this file's tests pin them, characterize-first).
|
|
12
12
|
//
|
|
13
|
-
// Dependency-free, Node >=
|
|
13
|
+
// Dependency-free, Node >= 22. Every fs primitive is injectable (deps.*) so the guards are unit-testable.
|
|
14
14
|
|
|
15
15
|
import { CONFIG_REL, serializeConfig } from './orchestration-config.mjs';
|
|
16
16
|
import { writeDocsAiFileAtomic } from './atomic-write.mjs';
|
package/tools/presentation.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// in the user's language per SKILL.md §4.4 — it never reads this file. Keeping the vocabulary here (a
|
|
7
7
|
// frozen leaf) means the renderers carry no inline strings and the phrasing is pinned by tests.
|
|
8
8
|
//
|
|
9
|
-
// Pure data, no side effects, Node >=
|
|
9
|
+
// Pure data, no side effects, Node >= 22. Every export frozen.
|
|
10
10
|
|
|
11
11
|
// public state token (STATE_PUBLIC value) → the direct-CLI English phrase. `installed` is null because
|
|
12
12
|
// an installed member shows its VERSION, not a phrase (the renderer special-cases it). Mirrors the
|
package/tools/procedures.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Invariants (mirror recipes.mjs): pure-where-possible, READ-ONLY (never writes, never commits, never
|
|
11
11
|
// runs a subscription CLI). The deterministic resolution lives in the kit (resolveActivityRecipe), not
|
|
12
|
-
// in the agent. Dependency-free, Node >=
|
|
12
|
+
// in the agent. Dependency-free, Node >= 22.
|
|
13
13
|
//
|
|
14
14
|
// Exit codes: 0 success (an unsatisfiable explicit override degrades LOUDLY but still exits 0 — it is a
|
|
15
15
|
// valid request that gracefully degraded); 2 usage (unknown <activity> / bad --override syntax);
|
|
@@ -213,7 +213,7 @@ const reviewLoopAdvice = (slots, activity) =>
|
|
|
213
213
|
' • At the cap, classify every surviving blocking finding: fixable-bug (fold ONCE as a red→green test, re-review) / inherent-layer-residual (document + raise to an acceptance criterion) / escalate (the maintainer decides); a minor never forces triage.',
|
|
214
214
|
...(activity === 'plan-execution'
|
|
215
215
|
? [
|
|
216
|
-
' • The computed instrument for THIS loop:
|
|
216
|
+
' • The computed instrument for THIS loop: declare each bugfix red BEFORE the fix (core-evidence red-proof — observed N/N red, custody-hashed); an unavailable backend gets an explicit core-evidence degrade record, never a silent skip; then stage everything, run the reviews on the STAGED tree, and mint the ONE receipt with run-gates --final (coverage + red-proof verification ride the final run); the commit is gated by commit-guard --check (the D13 ordering: any edit after the final run re-stales it); read the loop state statelessly with core-evidence summary.',
|
|
217
217
|
]
|
|
218
218
|
: []),
|
|
219
219
|
]
|