@sabaiway/agent-workflow-kit 11.0.0 → 11.2.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 +114 -0
- package/README.md +9 -8
- package/SKILL.md +1 -1
- package/capability.json +1 -1
- package/launchers/windsurf-workflow.md +2 -2
- package/package.json +1 -1
- package/references/contracts.md +1 -1
- package/references/modes/bootstrap.md +5 -5
- package/references/modes/procedures.md +4 -1
- package/references/modes/recipes.md +1 -1
- package/references/modes/recommendations.md +2 -1
- package/references/modes/set-recipe.md +2 -1
- package/references/modes/status.md +1 -1
- package/references/scripts/archiver-structure.test.mjs +48 -3
- package/references/scripts/install-git-hooks.test.mjs +31 -1
- package/references/shared/deploy-tail.md +1 -1
- package/references/templates/agent_rules.md +1 -1
- package/references/templates/changelog.md +1 -1
- package/references/templates/decisions.md +2 -2
- package/references/templates/orchestration.json +1 -1
- package/tools/carriers.mjs +1 -0
- package/tools/feedback-record-cli.mjs +300 -0
- package/tools/feedback-record.mjs +243 -0
- package/tools/inject-methodology.mjs +2 -0
- package/tools/orchestration-readme.mjs +12 -2
- package/tools/procedures.mjs +43 -8
- package/tools/recommendations.mjs +53 -3
package/tools/procedures.mjs
CHANGED
|
@@ -22,6 +22,8 @@ import { homedir } from 'node:os';
|
|
|
22
22
|
import { join, dirname } from 'node:path';
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { detectBackends, wrapperCmdFor, wrapperContractFor } from './detect-backends.mjs';
|
|
25
|
+
import { surveyVehicle as surveyReviewVehicle } from './cheap-agents-read.mjs';
|
|
26
|
+
import { isReadyMember, skippedLine } from './review-roster-resolve.mjs';
|
|
25
27
|
// The host-level bridge-settings registry (manifest-as-source) + its allowed-value labels. READ-ONLY
|
|
26
28
|
// core only — never the writer — so this read-only advisor never imports the atomic-write core.
|
|
27
29
|
import { loadRegistry, allowedLabel } from './bridge-settings-read.mjs';
|
|
@@ -150,7 +152,7 @@ export const extractSection = (text, activity) => {
|
|
|
150
152
|
|
|
151
153
|
// ── resolution + rendering ─────────────────────────────────────────────────────────
|
|
152
154
|
|
|
153
|
-
const resolveAllSlots = ({ activity, config, detection, overrides }) => {
|
|
155
|
+
const resolveAllSlots = ({ activity, config, detection, overrides, surveyLens }) => {
|
|
154
156
|
// The host-level settings knobs (manifest-as-source), best-effort: a corrupt bundle degrades to none
|
|
155
157
|
// and the advisor never crashes. Attached per wrapper cmd via each knob's `appliesTo`. Fact-only —
|
|
156
158
|
// model/effort are not knobs, so no model claim can ride here.
|
|
@@ -163,7 +165,7 @@ const resolveAllSlots = ({ activity, config, detection, overrides }) => {
|
|
|
163
165
|
})();
|
|
164
166
|
const knobsFor = (cmd) => [...registry.values()].filter((k) => (k.appliesTo ?? []).includes(cmd));
|
|
165
167
|
return Object.entries(ACTIVITIES[activity].slots).map(([slot, slotType]) => {
|
|
166
|
-
const resolved = resolveActivityRecipe({ config: config ?? {}, readiness: detection, activity, slot, override: overrides[slot] });
|
|
168
|
+
const resolved = resolveActivityRecipe({ config: config ?? {}, readiness: detection, activity, slot, override: overrides[slot], surveyLens });
|
|
167
169
|
if (isSwitchSlot(slotType)) return { slot, slotType, ...resolved, backends: [], contracts: [], vehicles: [] };
|
|
168
170
|
// The concrete wrapper set this slot's EFFECTIVE recipe dispatches (empty for solo). Reuse
|
|
169
171
|
// planRecipe's drift-guarded dispatch for WHICH backends, then resolve each (backend, role) to its
|
|
@@ -177,7 +179,7 @@ const resolveAllSlots = ({ activity, config, detection, overrides }) => {
|
|
|
177
179
|
// pairs, BEFORE they are flattened to wrapper names (the name array cannot reconstruct the role).
|
|
178
180
|
// Every slot with a non-empty dispatch gets contracts — including execute=delegated; the contract
|
|
179
181
|
// is NEVER gated by REVIEW_RECIPES (that set gates only the review-loop economics block).
|
|
180
|
-
const contracts = bridged
|
|
182
|
+
const contracts = activity === 'feedback-triage' ? [] : bridged
|
|
181
183
|
.map((d) => ({ backend: d.backend, role: d.role, cmd: wrapperCmdFor(d.backend, d.role), contract: wrapperContractFor(d.backend, d.role) }))
|
|
182
184
|
.filter((c) => c.cmd && c.contract)
|
|
183
185
|
// `retired` rides along: without it this surface advertised a RETIRED key as an ordinary
|
|
@@ -271,8 +273,40 @@ const reviewLoopAdvice = (slots, activity, flowArmed = false, plans = []) =>
|
|
|
271
273
|
export const GROUNDING_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'grounding.mjs');
|
|
272
274
|
export const REVIEW_ROUNDS_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'review-rounds-cli.mjs');
|
|
273
275
|
export const REPO_SEARCH_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'repo-search.mjs');
|
|
276
|
+
export const FEEDBACK_RECORD_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'feedback-record-cli.mjs');
|
|
277
|
+
const FEEDBACK_TRIAGE = 'feedback-triage';
|
|
274
278
|
const GROUNDING_FACTS_OUT = '/tmp/review-facts.md';
|
|
275
279
|
const MINTS_RECEIPT = Object.freeze({ mintsReceipt: true });
|
|
280
|
+
const FEEDBACK_BRIDGE_LINES = Object.freeze({
|
|
281
|
+
'codex-review': 'codex-review plan <record>',
|
|
282
|
+
'agy-review': 'agy-review plan <record> --facts @<facts>',
|
|
283
|
+
});
|
|
284
|
+
const FEEDBACK_LENS_LINE = 'the Agent tool, read-only, in the background — the brief carries <record> plus <facts>';
|
|
285
|
+
const mintsReceipts = (review) => review.roster == null
|
|
286
|
+
? (review.backends ?? []).length > 0 || REVIEW_RECIPES.has(review.degradedFrom)
|
|
287
|
+
: review.roster.some((row) => row.kind === 'bridge');
|
|
288
|
+
|
|
289
|
+
const renderFeedbackTriageAdvice = (activity, slots) => {
|
|
290
|
+
if (activity !== FEEDBACK_TRIAGE) return [];
|
|
291
|
+
const review = slots.find((slot) => slot.slot === 'review');
|
|
292
|
+
const members = review.roster == null
|
|
293
|
+
? review.backends.map((name) => ` then: ${FEEDBACK_BRIDGE_LINES[name]}`)
|
|
294
|
+
: review.roster.map((row) => !isReadyMember(row)
|
|
295
|
+
? ` then: ${row.member} — ${skippedLine(row)}`
|
|
296
|
+
: row.kind === 'bridge'
|
|
297
|
+
? ` then: ${FEEDBACK_BRIDGE_LINES[row.member]}`
|
|
298
|
+
: ` then: ${row.member} — ${FEEDBACK_LENS_LINE}`);
|
|
299
|
+
return [
|
|
300
|
+
` run: node ${renderToolPath(FEEDBACK_RECORD_TOOL)} --check <record> --excerpts <facts>`,
|
|
301
|
+
...members,
|
|
302
|
+
...(mintsReceipts(review)
|
|
303
|
+
? [
|
|
304
|
+
` then: node ${renderToolPath(REVIEW_ROUNDS_TOOL)} --artifact <record> --activity feedback-triage`,
|
|
305
|
+
ROUND_RENDER_FACT_LINE,
|
|
306
|
+
]
|
|
307
|
+
: [ROUND_RENDER_NO_BRIDGE_LINE]),
|
|
308
|
+
];
|
|
309
|
+
};
|
|
276
310
|
|
|
277
311
|
// The round table judges what review-rounds-cli resolves from the config (S27), never this render's
|
|
278
312
|
// override or degradation, so the advisor states that fact beneath the command instead of predicting
|
|
@@ -285,10 +319,7 @@ const roundRenderAdvice = (slots, plans) => {
|
|
|
285
319
|
const review = slots.find((s) => s.slot === 'review');
|
|
286
320
|
if (review === undefined) return [];
|
|
287
321
|
const roster = review.roster ?? null;
|
|
288
|
-
|
|
289
|
-
? (review.backends ?? []).length > 0 || REVIEW_RECIPES.has(review.degradedFrom)
|
|
290
|
-
: roster.some((row) => row.kind === 'bridge');
|
|
291
|
-
if (!shouldRenderCommand) return [ROUND_RENDER_NO_BRIDGE_LINE];
|
|
322
|
+
if (!mintsReceipts(review)) return [ROUND_RENDER_NO_BRIDGE_LINE];
|
|
292
323
|
const lensMembers = (roster ?? []).filter((row) => row.kind === 'lens').map((row) => row.member);
|
|
293
324
|
const operand = populatedPlan(plans, MINTS_RECEIPT);
|
|
294
325
|
return [
|
|
@@ -345,6 +376,7 @@ const slotSkewWarning = (section, activity) => {
|
|
|
345
376
|
return [`the installed engine's canon lists slots (${listed.join(', ')}) for ${activity} while this kit's registry names (${registry.join(', ')}) — the resolved lines follow the registry; the engine and this kit are out of step — /agent-workflow-kit status names which member is behind: upgrade that one. Tell the user.`];
|
|
346
377
|
};
|
|
347
378
|
const groundingPreStepAdvice = (activity, slots, plans) => {
|
|
379
|
+
if (activity === FEEDBACK_TRIAGE) return [];
|
|
348
380
|
if (!slots.some((s) => (s.backends ?? []).includes('agy-review'))) return [];
|
|
349
381
|
const operand = populatedPlan(plans);
|
|
350
382
|
const planArg = operand === null ? '--plan <path>' : `--plan ${operand}`;
|
|
@@ -621,6 +653,7 @@ const formatHuman = ({ activity, section, slots, warnings, plans, autonomy, flow
|
|
|
621
653
|
? ` ${parallelLine({ value: s.recipe, carrier })} — ${SOURCE_LABEL[s.source]}`
|
|
622
654
|
: ` ${s.slot}: ${s.recipe} — ${SOURCE_LABEL[s.source]}${arrow}${backendSetLabel(s.backends)}`);
|
|
623
655
|
if (s.reason) lines.push(` ↳ ${s.reason}`);
|
|
656
|
+
if (s.slot === 'review') lines.push(...renderFeedbackTriageAdvice(activity, slots));
|
|
624
657
|
// The form replaces the one-line vehicle mention: a carrier never told how to carry is a name,
|
|
625
658
|
// not an instruction. Indented like the driving contracts beside it.
|
|
626
659
|
for (const v of s.vehicles ?? []) {
|
|
@@ -664,6 +697,7 @@ const buildJson = ({ activity, section, slots, configSource, warnings, plans, au
|
|
|
664
697
|
readersSweep,
|
|
665
698
|
// ADDITIVE (AD-038): the populated grounding pre-step, structured (empty when agy is not dispatched).
|
|
666
699
|
groundingPreStep: groundingPreStepAdvice(activity, slots, plans),
|
|
700
|
+
feedbackTriage: renderFeedbackTriageAdvice(activity, slots),
|
|
667
701
|
// ADDITIVE (cost-tiered execution): the unconditional cost-lane advisory, structured.
|
|
668
702
|
costLanes: costLanesAdvice(),
|
|
669
703
|
// ADDITIVE (the fold channel): the finding-scope block, structured (empty outside plan-execution).
|
|
@@ -737,7 +771,8 @@ export const main = (argv, ctx = {}) => {
|
|
|
737
771
|
`backend detection failed (${(err && err.message) || err}) — treating every bridge as not ready; recipes needing a bridge degrade to solo (the executor vehicle is unaffected).`,
|
|
738
772
|
),
|
|
739
773
|
});
|
|
740
|
-
const
|
|
774
|
+
const surveyLens = ctx.surveyLens ?? ((spec) => surveyReviewVehicle(cwd, spec, ctx));
|
|
775
|
+
const slots = resolveAllSlots({ activity, config, detection, overrides, surveyLens });
|
|
741
776
|
const warnings = [...detectWarnings, ...collectWarnings(slots), ...slotSkewWarning(section, activity)];
|
|
742
777
|
const plans = plansInFlight(cwd);
|
|
743
778
|
// The autonomy facts (AD-044 Plan 4): resolved levels + red-lines from the policy file. A
|
|
@@ -83,6 +83,7 @@ import { DEFAULT_BUNDLE_ROOT } from './bridge-settings-read.mjs';
|
|
|
83
83
|
import { assertContainedRealPath } from './fs-safe.mjs';
|
|
84
84
|
import { loadWorktreesConfig, resolveProbeDir } from './worktrees.mjs';
|
|
85
85
|
import { preflightCheapAgents, EXECUTOR_VEHICLE } from './cheap-agents.mjs';
|
|
86
|
+
import { NODE_EVIDENCE, NODE_EVIDENCE_SCRIPTS, probeNodeEvidence } from './node-evidence.mjs';
|
|
86
87
|
// The vehicle READINESS comes from the read-only half — the same survey `recipes` and `status` read.
|
|
87
88
|
import { surveyExecutorVehicle, readStamp, readFsDeps, WORKFLOW_STAMP, EXPECTED_WORKFLOW_VERSION } from './cheap-agents-read.mjs';
|
|
88
89
|
// The ack store's path, keys, lane registry, fingerprint and guarded reader live in their own leaf
|
|
@@ -108,7 +109,12 @@ export { ACKS_FILE, ACKS_LANE_KEY, ACKS_WORKTREES_DIR_KEY, ACKS_COVERAGE_DOMAIN_
|
|
|
108
109
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
109
110
|
const toolPath = (rel) => join(HERE, rel);
|
|
110
111
|
const q = shellQuoteArg;
|
|
111
|
-
|
|
112
|
+
const ENFORCEMENT_ABSENT_CODE = 'ENOENT';
|
|
113
|
+
const ENFORCEMENT_KIT_ROOT = resolve(HERE, '..');
|
|
114
|
+
const ENFORCEMENT_BUNDLE_ROOT = join(ENFORCEMENT_KIT_ROOT, 'references', 'scripts');
|
|
115
|
+
const ENFORCEMENT_WHAT = 'the enforcement scripts are not deployed beside docs/ai (no Node evidence) \u2014 the offer copies them and installs the pre-commit hook';
|
|
116
|
+
const ENFORCEMENT_APPLY_TEMPLATE = 'HAND-APPLY: mkdir -p {root}/scripts && cp {kit}/references/scripts/*.mjs {root}/scripts/ && node {root}/scripts/install-git-hooks.mjs';
|
|
117
|
+
const COMMIT_GUARD_INSTALLER_REMEDY = 'the hook installer (scripts/install-git-hooks.mjs) is not deployed \u2014 deploy the enforcement scripts (bootstrap steps 8\u20139) and install the hook, then re-run';
|
|
112
118
|
// ── the section contract tokens (doc-parity-bound in upgrade.md + the mode doc) ────────────────
|
|
113
119
|
export const RECOMMENDATIONS_SECTION_HEADER = '## Recommendations (agent-workflow)';
|
|
114
120
|
export const RECOMMENDATIONS_EMPTY_LINE = 'no recommendations — flow optimal.';
|
|
@@ -166,6 +172,7 @@ export const SEVERITIES = Object.freeze({
|
|
|
166
172
|
// offer to enable something.
|
|
167
173
|
'gate-hook.marker-stale': SEVERITY_ATTENTION,
|
|
168
174
|
'commit-guard': SEVERITY_OPTIONAL,
|
|
175
|
+
enforcement: SEVERITY_OPTIONAL,
|
|
169
176
|
'read-lane': SEVERITY_OPTIONAL,
|
|
170
177
|
'read-lane.stale': SEVERITY_ATTENTION,
|
|
171
178
|
'read-lane.missing': SEVERITY_ATTENTION,
|
|
@@ -249,6 +256,7 @@ export const WHATS = Object.freeze({
|
|
|
249
256
|
'gate-hook': '{n} declared gate(s) prompt per run — the gate-approval hook is not wired',
|
|
250
257
|
'gate-hook.marker-stale': 'the declaration carries the lcovProducer key but the placed hook predates it — the hook goes dark and every gate prompts',
|
|
251
258
|
'commit-guard': 'the gate matrix is final-run-capable but no commit-guard arms the pre-commit hook — a commit needs no green receipt yet',
|
|
259
|
+
enforcement: ENFORCEMENT_WHAT,
|
|
252
260
|
'read-lane': 'the gate hook is wired but the read-only compound lane is off — pipes/chains of seeded reads still prompt one by one',
|
|
253
261
|
'read-lane.stale': 'the read-lane is ON but the placed gate hook is stale — an old hook never reads lanes.json, so the lane is silently dark; reseed it',
|
|
254
262
|
'read-lane.missing': 'the gate hook is wired but its placed file is missing — every Bash call errors and the read-lane is dark; re-place it',
|
|
@@ -313,6 +321,7 @@ export const BENEFITS = Object.freeze({
|
|
|
313
321
|
'source-size': 'maintainability — a module you can hold whole stays reviewable, and size drift becomes recorded, reasoned debt',
|
|
314
322
|
'gate-hook': 'velocity — your own declared gate commands auto-approve byte-exactly (opt-in PreToolUse hook)',
|
|
315
323
|
'commit-guard': 'integrity — commits require the ONE green --final receipt at the exact staged fingerprint (consented pre-commit arm)',
|
|
324
|
+
enforcement: 'enforcement — bundled checks and the pre-commit hook keep caps, archives, and the generated index current',
|
|
316
325
|
'read-lane': 'velocity — pipes/chains of your seeded read-only commands auto-approve instead of prompting (opt-in, conservatively classified)',
|
|
317
326
|
'state-block': 'no silent stalls — a turn ending on «you are not needed», or on work it never started, warns at once instead of waiting to be spotted',
|
|
318
327
|
'mcp-channel': 'velocity — path facts and literal searches arrive as typed tool calls whose arguments are JSON fields, never a shell string',
|
|
@@ -361,6 +370,7 @@ export const OPT_IN_CAPABILITIES = Object.freeze([
|
|
|
361
370
|
{ id: 'gate-hook', mode: 'hook', advisorKey: 'gate-hook' },
|
|
362
371
|
{ id: 'read-lane', mode: 'hook', advisorKey: 'read-lane' },
|
|
363
372
|
{ id: 'commit-guard', mode: 'commit-guard', advisorKey: 'commit-guard' },
|
|
373
|
+
{ id: 'enforcement', mode: 'bootstrap', advisorKey: 'enforcement' },
|
|
364
374
|
{ id: 'state-block', mode: 'state-block-guard', advisorKey: 'state-block' },
|
|
365
375
|
{ id: 'sandbox-masks', mode: 'sandbox-masks', advisorKey: 'sandbox-masks' },
|
|
366
376
|
{ id: 'mcp-channel', mode: 'mcp', advisorKey: 'mcp-channel' },
|
|
@@ -843,7 +853,7 @@ const probeCommitGuard = ({ root, deps, add, skip }) => {
|
|
|
843
853
|
if (!isFinalCapableDeclaration(gates, root)) return; // no receipt to guard yet — no offer
|
|
844
854
|
const installer = join(root, 'scripts', 'install-git-hooks.mjs');
|
|
845
855
|
if (!(deps.exists ?? existsSync)(installer)) {
|
|
846
|
-
skip('commit-guard', new Error(
|
|
856
|
+
skip('commit-guard', new Error(COMMIT_GUARD_INSTALLER_REMEDY));
|
|
847
857
|
return;
|
|
848
858
|
}
|
|
849
859
|
const hooksPath = (deps.gitHooksPath ?? resolveGitHooksPath)(root);
|
|
@@ -866,6 +876,45 @@ const probeCommitGuard = ({ root, deps, add, skip }) => {
|
|
|
866
876
|
}
|
|
867
877
|
};
|
|
868
878
|
|
|
879
|
+
const readEnforcementNode = (path, lstat) => {
|
|
880
|
+
try {
|
|
881
|
+
return lstat(path);
|
|
882
|
+
} catch (err) {
|
|
883
|
+
if (err?.code === ENFORCEMENT_ABSENT_CODE) return null;
|
|
884
|
+
throw new Error(`${path}: ${err?.code ?? err?.message ?? err}`);
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
export const probeEnforcement = ({ root, deps, add, skip }) => {
|
|
889
|
+
try {
|
|
890
|
+
const lstat = deps.lstat ?? lstatSync;
|
|
891
|
+
const docsAi = join(root, 'docs', 'ai');
|
|
892
|
+
const docsAiNode = readEnforcementNode(docsAi, lstat);
|
|
893
|
+
if (docsAiNode === null) return;
|
|
894
|
+
if (!docsAiNode.isDirectory()) throw new Error(`${docsAi} is not a plain directory`);
|
|
895
|
+
const evidence = probeNodeEvidence(root, lstat);
|
|
896
|
+
if (evidence.state === NODE_EVIDENCE.PACKAGE_JSON || evidence.state === NODE_EVIDENCE.DEPLOYED_SCRIPTS) return;
|
|
897
|
+
if (evidence.state === NODE_EVIDENCE.UNREADABLE) throw new Error(`${root}: ${evidence.error}`);
|
|
898
|
+
if (evidence.state !== NODE_EVIDENCE.NONE) throw new Error(`unknown Node-evidence state: ${evidence.state}`);
|
|
899
|
+
if (evidence.wrongKind.length > 0) throw new Error(join(root, evidence.wrongKind[0]));
|
|
900
|
+
const readDir = deps.readdir ?? readdirSync;
|
|
901
|
+
const bundleRoot = deps.scriptsBundleRoot ?? ENFORCEMENT_BUNDLE_ROOT;
|
|
902
|
+
const bundleBasenames = readDir(bundleRoot).filter((name) => name.endsWith('.mjs')).sort();
|
|
903
|
+
if (NODE_EVIDENCE_SCRIPTS.some((name) => !bundleBasenames.includes(name))) throw new Error(`${bundleRoot}: a runnable enforcement script is missing`);
|
|
904
|
+
const scripts = join(root, 'scripts');
|
|
905
|
+
const scriptsNode = readEnforcementNode(scripts, lstat);
|
|
906
|
+
if (scriptsNode !== null && !scriptsNode.isDirectory()) throw new Error(`${scripts} is not a plain directory`);
|
|
907
|
+
for (const basename of scriptsNode === null ? [] : bundleBasenames) {
|
|
908
|
+
const destination = join(scripts, basename);
|
|
909
|
+
if (readEnforcementNode(destination, lstat) === null) continue;
|
|
910
|
+
throw new Error(`${destination} already exists; the enforcement copy would clobber it`);
|
|
911
|
+
}
|
|
912
|
+
add('enforcement', ENFORCEMENT_WHAT, fillTemplate(ENFORCEMENT_APPLY_TEMPLATE, { root: q(root), kit: q(ENFORCEMENT_KIT_ROOT) }));
|
|
913
|
+
} catch (err) {
|
|
914
|
+
skip('enforcement', err);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
869
918
|
// The read-lane offer (AD-055 Part II, Help-through-Recommendations): once the gate hook is PLACED
|
|
870
919
|
// and WIRED, offer to enable the opt-in read-only compound lane. Mutually exclusive with the
|
|
871
920
|
// gate-hook item BY CONSTRUCTION — that item keys on `!wired`, this one on `wired` — so an unplaced
|
|
@@ -1242,7 +1291,7 @@ const readReadLaneToggle = (root, deps) => {
|
|
|
1242
1291
|
// D3: the risk-marked keys — every key here has a per-item posture note in the mode doc, surfaced
|
|
1243
1292
|
// at the consent moment; the static contract test asserts EXACT bidirectional coverage
|
|
1244
1293
|
// (risk-marked keys == mode-doc note keys — a dropped note goes red, not silent).
|
|
1245
|
-
export const RISK_NOTED_KEYS = Object.freeze(['sandbox-lane', 'read-lane', 'worktrees-dir', 'adr-store-migration', 'gates-inert', 'source-size', 'gate-hook', 'mcp-channel', 'spec-adoption']);
|
|
1294
|
+
export const RISK_NOTED_KEYS = Object.freeze(['sandbox-lane', 'read-lane', 'worktrees-dir', 'adr-store-migration', 'gates-inert', 'source-size', 'gate-hook', 'mcp-channel', 'spec-adoption', 'enforcement']);
|
|
1246
1295
|
|
|
1247
1296
|
// The feature-spec layer's adoption state (contract: kit/spec-adoption). The canon lets a plan cite
|
|
1248
1297
|
// zero governing specs while a project adopts the layer, and nothing ever said whether adoption had
|
|
@@ -1547,6 +1596,7 @@ const PROBES = Object.freeze([
|
|
|
1547
1596
|
probeGatesInert,
|
|
1548
1597
|
probeSourceSize,
|
|
1549
1598
|
probeCommitGuard,
|
|
1599
|
+
probeEnforcement,
|
|
1550
1600
|
probeReadLane,
|
|
1551
1601
|
probeStateBlockHook,
|
|
1552
1602
|
probeCheapAgents,
|