vigiles 29.1.0 → 30.0.1
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/dist/adapter-conformance.d.ts +1 -1
- package/dist/adapter-conformance.js +106 -25
- package/dist/adapter-registry.d.ts +61 -14
- package/dist/adapter-registry.js +78 -10
- package/dist/adapter.d.ts +23 -2
- package/dist/adapter.js +13 -1
- package/dist/adapters/claude-code/adapter.d.ts +32 -2
- package/dist/adapters/claude-code/adapter.js +44 -23
- package/dist/adapters/claude-code/agent-runtime.js +3 -1
- package/dist/adapters/claude-code/dialect.js +87 -21
- package/dist/adapters/claude-code/effect-region.js +3 -1
- package/dist/adapters/claude-code/hook-protocol.js +16 -0
- package/dist/adapters/claude-code/instruction-chain.d.ts +25 -0
- package/dist/adapters/claude-code/instruction-chain.js +626 -0
- package/dist/adapters/claude-code/layout.d.ts +2 -2
- package/dist/adapters/claude-code/layout.js +42 -8
- package/dist/adapters/claude-code/model-access.d.ts +41 -0
- package/dist/adapters/claude-code/model-access.js +46 -0
- package/dist/adapters/claude-code/skill-reachability.d.ts +125 -0
- package/dist/adapters/claude-code/skill-reachability.js +111 -0
- package/dist/adapters/claude-code/skill-runtime.js +3 -1
- package/dist/adapters/codex/adapter.d.ts +39 -2
- package/dist/adapters/codex/adapter.js +29 -29
- package/dist/adapters/codex/dialect.js +11 -6
- package/dist/adapters/codex/eval.d.ts +10 -0
- package/dist/adapters/codex/eval.js +48 -1
- package/dist/adapters/codex/hook-protocol.d.ts +2 -1
- package/dist/adapters/codex/hook-protocol.js +10 -0
- package/dist/adapters/codex/instruction-chain.d.ts +40 -0
- package/dist/adapters/codex/instruction-chain.js +105 -0
- package/dist/adapters/codex/layout.d.ts +1 -1
- package/dist/adapters/codex/layout.js +41 -14
- package/dist/adapters/opencode/adapter.d.ts +33 -2
- package/dist/adapters/opencode/adapter.js +36 -36
- package/dist/adapters/opencode/dialect.js +2 -2
- package/dist/adapters/opencode/instruction-chain.d.ts +37 -0
- package/dist/adapters/opencode/instruction-chain.js +70 -0
- package/dist/adapters/opencode/layout.d.ts +19 -0
- package/dist/adapters/opencode/layout.js +34 -15
- package/dist/adoptability.d.ts +31 -1
- package/dist/adoptability.js +57 -0
- package/dist/cli-main.js +185 -102
- package/dist/core/adapter.d.ts +213 -61
- package/dist/core/compile.d.ts +2 -2
- package/dist/core/compile.js +57 -46
- package/dist/core/compose.d.ts +5 -3
- package/dist/core/compose.js +5 -3
- package/dist/core/config-schema.d.ts +14 -2
- package/dist/core/config-schema.js +20 -7
- package/dist/core/dialect.d.ts +54 -12
- package/dist/core/dialect.js +56 -0
- package/dist/core/eval-driver.d.ts +194 -0
- package/dist/core/eval-driver.js +3 -0
- package/dist/core/frontmatter-read.d.ts +10 -0
- package/dist/core/frontmatter-read.js +30 -3
- package/dist/core/guards.js +3 -1
- package/dist/core/hook-program.d.ts +27 -2
- package/dist/core/hook-program.js +29 -24
- package/dist/core/hook-protocol.d.ts +54 -0
- package/dist/core/install-reader.d.ts +18 -0
- package/dist/core/install-reader.js +88 -0
- package/dist/core/instruction-chain.d.ts +444 -0
- package/dist/core/instruction-chain.js +292 -0
- package/dist/core/instruction-weight.d.ts +96 -14
- package/dist/core/instruction-weight.js +65 -30
- package/dist/core/layout.d.ts +220 -33
- package/dist/core/layout.js +115 -1
- package/dist/core/lethal-trifecta.d.ts +12 -7
- package/dist/core/lethal-trifecta.js +13 -13
- package/dist/core/live-driver.d.ts +137 -0
- package/dist/core/live-driver.js +14 -0
- package/dist/core/markdown.d.ts +23 -0
- package/dist/core/markdown.js +77 -28
- package/dist/core/orphans.js +9 -7
- package/dist/core/settings-codec.d.ts +17 -0
- package/dist/core/settings-codec.js +56 -0
- package/dist/core/surface-discovery.d.ts +2 -2
- package/dist/core/surface-discovery.js +24 -12
- package/dist/core/surface-scopes.d.ts +26 -6
- package/dist/core/surface-scopes.js +52 -11
- package/dist/core/validate.js +16 -3
- package/dist/coverage-artifact.d.ts +3 -2
- package/dist/coverage-artifact.js +6 -5
- package/dist/eval-cache.d.ts +6 -1
- package/dist/eval-cache.js +11 -1
- package/dist/eval.d.ts +16 -108
- package/dist/eval.js +36 -2
- package/dist/harness-test.d.ts +3 -63
- package/dist/hook-install.d.ts +12 -1
- package/dist/hook-install.js +12 -1
- package/dist/hook-runtime.js +4 -2
- package/dist/hook-state-store.js +3 -1
- package/dist/local-files-tracked.d.ts +17 -0
- package/dist/local-files-tracked.js +70 -0
- package/dist/local-files.d.ts +62 -0
- package/dist/local-files.js +183 -0
- package/dist/observe.d.ts +3 -2
- package/dist/observe.js +7 -6
- package/dist/plugin-loader.d.ts +1 -1
- package/dist/plugin-loader.js +43 -36
- package/dist/scan-behavioral.d.ts +34 -25
- package/dist/scan-behavioral.js +122 -58
- package/dist/scan-core.js +37 -18
- package/dist/scan-files.d.ts +1 -1
- package/dist/scan-files.js +53 -33
- package/dist/scan-trigger-suggest.d.ts +0 -21
- package/dist/scan-trigger-suggest.js +0 -23
- package/dist/scan.d.ts +4 -4
- package/dist/scan.js +120 -84
- package/dist/skill-harness.d.ts +21 -5
- package/dist/skill-harness.js +29 -11
- package/dist/surface-discovery-fs.d.ts +2 -0
- package/dist/surface-discovery-fs.js +108 -6
- package/dist/test-coverage-files.js +24 -17
- package/dist/test-coverage.d.ts +9 -3
- package/dist/test-coverage.js +32 -22
- package/dist/verify-plugin-guards.js +1 -1
- package/package.json +1 -1
- package/dist/skill-reachability.d.ts +0 -68
- package/dist/skill-reachability.js +0 -205
- /package/dist/{dialect-drift.d.ts → adapters/claude-code/dialect-drift.d.ts} +0 -0
- /package/dist/{dialect-drift.js → adapters/claude-code/dialect-drift.js} +0 -0
package/dist/cli-main.js
CHANGED
|
@@ -36,9 +36,9 @@ const test_coverage_js_1 = require("./test-coverage.js");
|
|
|
36
36
|
const scan_js_1 = require("./scan.js");
|
|
37
37
|
const surface_discovery_js_1 = require("./core/surface-discovery.js");
|
|
38
38
|
const scan_trigger_suggest_js_1 = require("./scan-trigger-suggest.js");
|
|
39
|
-
const
|
|
40
|
-
const skill_reachability_js_1 = require("./skill-reachability.js");
|
|
39
|
+
const install_reader_js_1 = require("./core/install-reader.js");
|
|
41
40
|
const plugin_declaration_js_1 = require("./plugin-declaration.js");
|
|
41
|
+
const local_files_tracked_js_1 = require("./local-files-tracked.js");
|
|
42
42
|
const scan_behavioral_js_1 = require("./scan-behavioral.js");
|
|
43
43
|
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
44
44
|
const skill_harness_js_1 = require("./skill-harness.js");
|
|
@@ -1500,7 +1500,7 @@ async function runLint(restArgs, flags, excludes, config) {
|
|
|
1500
1500
|
// hand-edit of a compiled subagent slipped past `lint`. A hand-written agent
|
|
1501
1501
|
// (no hash header) stays a no-op, and require-instructions-spec is filename-
|
|
1502
1502
|
// scoped to CLAUDE/AGENTS so agents are never falsely flagged as spec-less.
|
|
1503
|
-
const files = findInstructionFiles(restArgs, excludes, adapter.layout.
|
|
1503
|
+
const files = findInstructionFiles(restArgs, excludes, adapter.layout.surfaces.agent ?? "");
|
|
1504
1504
|
// 1. Verify hashes and structure
|
|
1505
1505
|
if (!silent) {
|
|
1506
1506
|
if (files.length > 0) {
|
|
@@ -2822,7 +2822,26 @@ async function promptSetup() {
|
|
|
2822
2822
|
rl.close();
|
|
2823
2823
|
}
|
|
2824
2824
|
}
|
|
2825
|
-
|
|
2825
|
+
/**
|
|
2826
|
+
* The instruction filenames `init` probes for on disk — DERIVED from the
|
|
2827
|
+
* registered adapters, never listed.
|
|
2828
|
+
*
|
|
2829
|
+
* 🔴 THIS IS THE SAME DEFECT CLASS THE PORT REDESIGN REMOVES: a fact restated
|
|
2830
|
+
* by hand beside the registry that owns it. The literal read `["CLAUDE.md",
|
|
2831
|
+
* "AGENTS.md"]` and was correct only because the adapters happen to say that
|
|
2832
|
+
* today — it could not have noticed a third adapter, and it did not notice when
|
|
2833
|
+
* Claude Code's `instructionTargets` GAINED `AGENTS.md` (it was already there
|
|
2834
|
+
* by coincidence).
|
|
2835
|
+
*
|
|
2836
|
+
* `instructionTargets` rather than `layout.instructionFile`, because the
|
|
2837
|
+
* question here is "which files might exist and want a spec", which is every
|
|
2838
|
+
* name a registered harness READS — a repository with only an `AGENTS.md` is
|
|
2839
|
+
* one `init` must still see. Verified identical before and after over the
|
|
2840
|
+
* registry as it stands: union = `["CLAUDE.md", "AGENTS.md"]`.
|
|
2841
|
+
*/
|
|
2842
|
+
const KNOWN_INSTRUCTION_FILES = [
|
|
2843
|
+
...new Set(adapter_registry_js_1.ADAPTERS.flatMap((a) => a.dialect.instructionTargets)),
|
|
2844
|
+
];
|
|
2826
2845
|
const KNOWN_OTHER_CONFIGS = {
|
|
2827
2846
|
".cursorrules": "Cursor",
|
|
2828
2847
|
".github/copilot-instructions.md": "GitHub Copilot",
|
|
@@ -2903,13 +2922,20 @@ function detectProject() {
|
|
|
2903
2922
|
function determineTargets(detected, targetValue, harnesses) {
|
|
2904
2923
|
if (targetValue)
|
|
2905
2924
|
return [targetValue];
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2925
|
+
// 🔴 WHICH FILE A HARNESS COMPILES INTO IS THE LAYOUT'S ANSWER, not a pair of
|
|
2926
|
+
// name literals here. This read `harnesses.includes("claude") -> "CLAUDE.md"`
|
|
2927
|
+
// and `includes("codex") -> "AGENTS.md"` — the registry restated, in the one
|
|
2928
|
+
// place a third adapter would be forgotten. `getAdapter` is alias-aware, so
|
|
2929
|
+
// the short `"claude"` that `init` uses resolves exactly as it did.
|
|
2930
|
+
//
|
|
2931
|
+
// ITERATING THE REGISTRY, NOT `harnesses`, KEEPS THE ORDER FIXED: the old
|
|
2932
|
+
// code always produced CLAUDE.md before AGENTS.md whatever order the config
|
|
2933
|
+
// listed, and that order reaches the printed spec targets. Verified over all
|
|
2934
|
+
// five inputs (each harness alone, both orders, and none): identical.
|
|
2935
|
+
const selected = new Set(harnesses.map((h) => (0, adapter_registry_js_1.getAdapter)(h)?.name).filter((n) => n !== undefined));
|
|
2936
|
+
const targets = adapter_registry_js_1.ADAPTERS.filter((a) => selected.has(a.name)).map((a) => a.layout.instructionFile);
|
|
2911
2937
|
if (targets.length === 0)
|
|
2912
|
-
targets.push(
|
|
2938
|
+
targets.push(adapter_registry_js_1.defaultAdapter.layout.instructionFile);
|
|
2913
2939
|
// Any existing instruction file without a spec also gets one.
|
|
2914
2940
|
for (const f of detected.instructionFiles) {
|
|
2915
2941
|
if (!f.hasSpec && !targets.includes(f.path))
|
|
@@ -3257,6 +3283,7 @@ function ensureVigilesDevDep() {
|
|
|
3257
3283
|
* (`claude`). Everything else passes through trimmed + lowercased. */
|
|
3258
3284
|
function shortHarness(name) {
|
|
3259
3285
|
const n = name.trim().toLowerCase();
|
|
3286
|
+
// eslint-disable-next-line no-restricted-syntax -- COMPOSITION ROOT'S OWN UI, not a harness decision: `init` keys its install/plan path on a short form, and this is the one place the two spellings are related. Nothing about the harness is read from the answer.
|
|
3260
3287
|
return n === "claude-code" ? "claude" : n;
|
|
3261
3288
|
}
|
|
3262
3289
|
/** Which harnesses to set up, in precedence order: an explicit `--harness=`
|
|
@@ -3631,7 +3658,7 @@ function checkSubagentToolContracts(config, silent, adapter, scanRoot) {
|
|
|
3631
3658
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["subagent-tool-contract"]);
|
|
3632
3659
|
if (!sev)
|
|
3633
3660
|
return { issues: 0, errors: 0 };
|
|
3634
|
-
if (!adapter.
|
|
3661
|
+
if (!adapter.subagents) {
|
|
3635
3662
|
reportNotApplicable("Subagent tool-contract check", "subagents", adapter, silent);
|
|
3636
3663
|
return { issues: 0, errors: 0 };
|
|
3637
3664
|
}
|
|
@@ -3674,7 +3701,7 @@ function checkHookEvents(config, silent, adapter, scanRoot) {
|
|
|
3674
3701
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["hook-events"]);
|
|
3675
3702
|
if (!sev)
|
|
3676
3703
|
return { issues: 0, errors: 0 };
|
|
3677
|
-
if (!adapter.
|
|
3704
|
+
if (!adapter.shellHooks) {
|
|
3678
3705
|
reportNotApplicable("Hook-event check", "shell hooks", adapter, silent);
|
|
3679
3706
|
return { issues: 0, errors: 0 };
|
|
3680
3707
|
}
|
|
@@ -3705,7 +3732,7 @@ function checkFrontmatterSchema(config, silent, adapter, scanRoot) {
|
|
|
3705
3732
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["subagent-frontmatter"]);
|
|
3706
3733
|
if (!sev)
|
|
3707
3734
|
return { issues: 0, errors: 0 };
|
|
3708
|
-
if (!adapter.
|
|
3735
|
+
if (!adapter.subagents) {
|
|
3709
3736
|
reportNotApplicable("Subagent-frontmatter check", "subagents", adapter, silent);
|
|
3710
3737
|
return { issues: 0, errors: 0 };
|
|
3711
3738
|
}
|
|
@@ -3818,7 +3845,7 @@ function checkDisallowedTools(config, silent, adapter, scanRoot) {
|
|
|
3818
3845
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["disallowed-tools-contract"]);
|
|
3819
3846
|
if (!sev)
|
|
3820
3847
|
return { issues: 0, errors: 0 };
|
|
3821
|
-
if (!adapter.
|
|
3848
|
+
if (!adapter.subagents) {
|
|
3822
3849
|
reportNotApplicable("Disallowed-tools check", "subagents", adapter, silent);
|
|
3823
3850
|
return { issues: 0, errors: 0 };
|
|
3824
3851
|
}
|
|
@@ -4102,7 +4129,7 @@ function checkHookBlockIneffective(config, silent, adapter, scanRoot) {
|
|
|
4102
4129
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["hook-block-ineffective"]);
|
|
4103
4130
|
if (!sev)
|
|
4104
4131
|
return { issues: 0, errors: 0 };
|
|
4105
|
-
if (!adapter.
|
|
4132
|
+
if (!adapter.shellHooks) {
|
|
4106
4133
|
reportNotApplicable("Hook-block check", "shell hooks", adapter, silent);
|
|
4107
4134
|
return { issues: 0, errors: 0 };
|
|
4108
4135
|
}
|
|
@@ -4136,7 +4163,7 @@ function checkHookMatcher(config, silent, adapter, scanRoot) {
|
|
|
4136
4163
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["hook-matcher"]);
|
|
4137
4164
|
if (!sev)
|
|
4138
4165
|
return { issues: 0, errors: 0 };
|
|
4139
|
-
if (!adapter.
|
|
4166
|
+
if (!adapter.shellHooks) {
|
|
4140
4167
|
reportNotApplicable("Hook-matcher check", "shell hooks", adapter, silent);
|
|
4141
4168
|
return { issues: 0, errors: 0 };
|
|
4142
4169
|
}
|
|
@@ -4167,7 +4194,7 @@ function checkMcpHookTargets(config, silent, adapter, scanRoot) {
|
|
|
4167
4194
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["mcp-hook-target-resolves"]);
|
|
4168
4195
|
if (!sev)
|
|
4169
4196
|
return { issues: 0, errors: 0 };
|
|
4170
|
-
if (!adapter.
|
|
4197
|
+
if (!adapter.shellHooks) {
|
|
4171
4198
|
reportNotApplicable("MCP hook-target check", "shell hooks", adapter, silent);
|
|
4172
4199
|
return { issues: 0, errors: 0 };
|
|
4173
4200
|
}
|
|
@@ -4199,7 +4226,7 @@ function checkHookScriptExists(config, silent, adapter, scanRoot) {
|
|
|
4199
4226
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["hook-script-exists"]);
|
|
4200
4227
|
if (!sev)
|
|
4201
4228
|
return { issues: 0, errors: 0 };
|
|
4202
|
-
if (!adapter.
|
|
4229
|
+
if (!adapter.shellHooks) {
|
|
4203
4230
|
reportNotApplicable("Hook-script existence check", "shell hooks", adapter, silent);
|
|
4204
4231
|
return { issues: 0, errors: 0 };
|
|
4205
4232
|
}
|
|
@@ -4234,7 +4261,7 @@ function checkMcpToolResolves(config, silent, adapter, scanRoot) {
|
|
|
4234
4261
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["mcp-tool-resolves"]);
|
|
4235
4262
|
if (!sev)
|
|
4236
4263
|
return { issues: 0, errors: 0 };
|
|
4237
|
-
if (!adapter.
|
|
4264
|
+
if (!adapter.subagents) {
|
|
4238
4265
|
reportNotApplicable("MCP tool-resolution check", "subagents", adapter, silent);
|
|
4239
4266
|
return { issues: 0, errors: 0 };
|
|
4240
4267
|
}
|
|
@@ -4408,7 +4435,6 @@ async function handleMeasure(restArgs, args) {
|
|
|
4408
4435
|
const json = args.includes("--json");
|
|
4409
4436
|
const harnessFlag = harnessFlagFrom(args);
|
|
4410
4437
|
const adapter = resolveCommandHarness(dir, harnessFlag).adapter;
|
|
4411
|
-
const harness = adapter.name === "codex" ? "codex" : "claude-code";
|
|
4412
4438
|
const promptsPath = flagValue(args, "--prompts");
|
|
4413
4439
|
if (!promptsPath) {
|
|
4414
4440
|
console.error("measure needs --prompts=<file.json> (a map of skill name → { prompts, irrelevant }).");
|
|
@@ -4437,13 +4463,13 @@ async function handleMeasure(restArgs, args) {
|
|
|
4437
4463
|
concurrency,
|
|
4438
4464
|
minPrompts: num("--min-prompts"),
|
|
4439
4465
|
model,
|
|
4440
|
-
|
|
4466
|
+
adapter,
|
|
4441
4467
|
});
|
|
4442
4468
|
const collisions = await (0, scan_behavioral_js_1.measurePluginSelection)(dir, promptSet, {
|
|
4443
4469
|
concurrency,
|
|
4444
4470
|
trials: num("--trials"),
|
|
4445
4471
|
model,
|
|
4446
|
-
|
|
4472
|
+
adapter,
|
|
4447
4473
|
});
|
|
4448
4474
|
if (json) {
|
|
4449
4475
|
console.log(JSON.stringify({ trigger, collisions }, null, 2));
|
|
@@ -5009,6 +5035,10 @@ async function handleRunScripts(kind, args, restArgs, excludes) {
|
|
|
5009
5035
|
// a flag: the run already happened, and this is the runner recording what it
|
|
5010
5036
|
// saw — the same shape as the flight-recorder ledger it already appends to.
|
|
5011
5037
|
recordRunCoverage(cwd, results, kind, harnessFlagFrom(args));
|
|
5038
|
+
// The ignore file keeps NEW copies of `.vigiles/` local files out of git, but
|
|
5039
|
+
// cannot untrack one a repo already committed. Said here, on the CLI, because
|
|
5040
|
+
// it spawns `git` — never from a hook runtime.
|
|
5041
|
+
(0, local_files_tracked_js_1.warnTrackedLocalFiles)(cwd);
|
|
5012
5042
|
console.log("\n" + (0, run_scripts_js_1.formatScriptSummary)(results));
|
|
5013
5043
|
if ((0, run_scripts_js_1.anyFailed)(results))
|
|
5014
5044
|
process.exit(1);
|
|
@@ -5857,25 +5887,34 @@ async function installHookFile(file, adapter, registeredProviders = []) {
|
|
|
5857
5887
|
`${(0, hook_install_js_1.hookGateRef)(ref, adapter.layout.projectRootTokens)} || exit ${(0, hook_install_js_1.hookRuntimeMissingExit)((0, hook_program_js_1.dispatchKind)(program))}`,
|
|
5858
5888
|
dialect: adapter.dialect,
|
|
5859
5889
|
hookProtocol: adapter.hookProtocol,
|
|
5860
|
-
|
|
5890
|
+
settings: adapter.layout.settings,
|
|
5861
5891
|
registeredProviders,
|
|
5862
5892
|
});
|
|
5863
5893
|
// Tamper-evident stamp beside the source (one dir → basename is unique).
|
|
5864
5894
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)((0, hook_runtime_js_1.hookStampPath)(file)), { recursive: true });
|
|
5865
5895
|
(0, node_fs_1.writeFileSync)((0, hook_runtime_js_1.hookStampPath)(file), JSON.stringify({ file, stamp: compiled.stamp }, null, 2) + "\n");
|
|
5866
5896
|
// Merge into the harness's native config, idempotently.
|
|
5867
|
-
|
|
5897
|
+
//
|
|
5898
|
+
// 🔴 THREE FORMAT BRANCHES BECAME ZERO, and they were three because the ONE
|
|
5899
|
+
// enum they all read was standing in for two different questions. Reading
|
|
5900
|
+
// and writing the file is the ENCODING (`layout.settings`, a codec); folding
|
|
5901
|
+
// our registrations into what is already there is the entry SHAPE
|
|
5902
|
+
// (`hookProtocol.mergeRegistrations` — CC nests several commands under one
|
|
5903
|
+
// matcher, Codex carries one command per entry). A TOML harness with
|
|
5904
|
+
// CC-shaped entries was expressible under the enum and would have been
|
|
5905
|
+
// merged wrong; neither port can be wrong about its own half.
|
|
5868
5906
|
const settingsAbs = (0, node_path_1.resolve)(process.cwd(), adapter.layout.settingsPath);
|
|
5869
5907
|
const existing = (0, node_fs_1.existsSync)(settingsAbs)
|
|
5870
|
-
?
|
|
5871
|
-
? (0, toml_1.parse)((0, node_fs_1.readFileSync)(settingsAbs, "utf-8"))
|
|
5872
|
-
: JSON.parse((0, node_fs_1.readFileSync)(settingsAbs, "utf-8"))
|
|
5908
|
+
? adapter.layout.settings.parse((0, node_fs_1.readFileSync)(settingsAbs, "utf-8"))
|
|
5873
5909
|
: {};
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5910
|
+
// `shellHooks` narrows the adapter union: a harness whose hooks are code
|
|
5911
|
+
// modules has no settings block to install into, and the type says so.
|
|
5912
|
+
if (!adapter.shellHooks) {
|
|
5913
|
+
throw new Error(`Harness "${adapter.name}" has no shell-hook settings to install into (hooks are code modules).`);
|
|
5914
|
+
}
|
|
5915
|
+
const merged = adapter.hookProtocol.mergeRegistrations(existing, compiled.hooks, ref);
|
|
5877
5916
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(settingsAbs), { recursive: true });
|
|
5878
|
-
(0, node_fs_1.writeFileSync)(settingsAbs,
|
|
5917
|
+
(0, node_fs_1.writeFileSync)(settingsAbs, adapter.layout.settings.render(merged));
|
|
5879
5918
|
// No silent skips: warn loudly only where a hook's OUTPUT genuinely may not
|
|
5880
5919
|
// apply on this harness. INJECT's `additionalContext` shape is now CONFIRMED
|
|
5881
5920
|
// shared with Codex (per the official hooks docs), so an inject hook only
|
|
@@ -5900,6 +5939,7 @@ async function installHookFile(file, adapter, registeredProviders = []) {
|
|
|
5900
5939
|
`(stderr at exit 0 goes to the debug log, not the transcript, and the model ` +
|
|
5901
5940
|
`never sees it). Injectable here: ${injectable.join(", ") || "(none)"}. ` +
|
|
5902
5941
|
`Move the hook to one of those events, or use run() if you meant an action.`;
|
|
5942
|
+
// eslint-disable-next-line no-restricted-syntax -- KEPT ON PURPOSE (design §2 "the eighth site" / §6). This is a per-channel MEASUREMENT STATUS — react output is CONFIRMED only on Claude Code — and there is no port to read it from: `EventCapability` carries `carries`/`honours`/`matcher`/`denyShape` and no verified-status column. The fix is that column, owned by the capability table, not by this port. Until it exists, a name check that says so is honest and a fabricated capability field would not be.
|
|
5903
5943
|
}
|
|
5904
5944
|
else if (adapter.name !== "claude-code") {
|
|
5905
5945
|
if (role === "inject" && !injectable.includes(event)) {
|
|
@@ -6191,7 +6231,6 @@ async function runAuditServe(report, adoptable, cliErr) {
|
|
|
6191
6231
|
*/
|
|
6192
6232
|
async function runAutoTrigger(dir, report, adapter, args) {
|
|
6193
6233
|
const json = args.includes("--json");
|
|
6194
|
-
const harness = adapter.name === "codex" ? "codex" : "claude-code";
|
|
6195
6234
|
const skills = report.skills
|
|
6196
6235
|
.filter((s) => s.hasDescription && !s.userInvoked && s.description)
|
|
6197
6236
|
.map((s) => ({ name: s.name, description: s.description ?? "" }));
|
|
@@ -6210,7 +6249,7 @@ async function runAutoTrigger(dir, report, adapter, args) {
|
|
|
6210
6249
|
minPrompts: audit_prompts_js_1.AUTO_RECALL_COUNT,
|
|
6211
6250
|
minDistance: audit_prompts_js_1.AUTO_MIN_DISTANCE,
|
|
6212
6251
|
model,
|
|
6213
|
-
|
|
6252
|
+
adapter,
|
|
6214
6253
|
// Discover candidates with the resolved adapter's layout/dialect — a Codex
|
|
6215
6254
|
// repo's skills live under the Codex layout, not the default CC one.
|
|
6216
6255
|
layout: adapter.layout,
|
|
@@ -6222,14 +6261,14 @@ async function runAutoTrigger(dir, report, adapter, args) {
|
|
|
6222
6261
|
// this proves the wrong one actually fires). Only meaningful with ≥2 model-
|
|
6223
6262
|
// invocable skills (a lone skill can't collide); reuses the same auto prompts.
|
|
6224
6263
|
const collisions = skills.length >= 2
|
|
6225
|
-
? await (0, scan_behavioral_js_1.measurePluginSelection)(dir, promptSet, { model,
|
|
6264
|
+
? await (0, scan_behavioral_js_1.measurePluginSelection)(dir, promptSet, { model, adapter })
|
|
6226
6265
|
: null;
|
|
6227
6266
|
// Third behavioral eval (same consent): adversarial-gate — do enforcement-gate
|
|
6228
6267
|
// skills HOLD when the agent is told to violate them? Auto-derives its own
|
|
6229
6268
|
// attacks; a no-op (no model calls) when the plugin declares no gate skills.
|
|
6230
6269
|
const gates = await (0, scan_behavioral_js_1.measureGateAdversarial)(dir, {
|
|
6231
6270
|
model,
|
|
6232
|
-
|
|
6271
|
+
adapter,
|
|
6233
6272
|
layout: adapter.layout,
|
|
6234
6273
|
dialect: adapter.dialect,
|
|
6235
6274
|
});
|
|
@@ -6264,7 +6303,7 @@ async function runAutoTrigger(dir, report, adapter, args) {
|
|
|
6264
6303
|
*
|
|
6265
6304
|
* Returns `execute` (run the executing checks?) + a `note` to print at the end.
|
|
6266
6305
|
*/
|
|
6267
|
-
async function resolveExecution(s, decision, json,
|
|
6306
|
+
async function resolveExecution(s, decision, json, access) {
|
|
6268
6307
|
if (decision.kind === "run")
|
|
6269
6308
|
return { execute: true, note: null };
|
|
6270
6309
|
if (decision.kind === "skip")
|
|
@@ -6273,7 +6312,7 @@ async function resolveExecution(s, decision, json, harness) {
|
|
|
6273
6312
|
note: json ? null : (0, scan_trigger_suggest_js_1.formatExecuteSkip)(decision.reason),
|
|
6274
6313
|
};
|
|
6275
6314
|
// ask — prompt once, then remember the answer.
|
|
6276
|
-
const answer = await askOnce(buildExecuteDisclosure(s,
|
|
6315
|
+
const answer = await askOnce(buildExecuteDisclosure(s, access));
|
|
6277
6316
|
const yes = /^y(es)?$/i.test(answer); // default NO (executes your hooks / servers)
|
|
6278
6317
|
rememberAuditMeasure(yes);
|
|
6279
6318
|
return {
|
|
@@ -6284,10 +6323,11 @@ async function resolveExecution(s, decision, json, harness) {
|
|
|
6284
6323
|
};
|
|
6285
6324
|
}
|
|
6286
6325
|
/** The bundled consent prompt — discloses exactly what will execute (and what it
|
|
6287
|
-
* costs) so the yes is informed. Default NO. Harness-aware
|
|
6288
|
-
*
|
|
6289
|
-
*
|
|
6290
|
-
|
|
6326
|
+
* costs) so the yes is informed. Default NO. Harness-aware WITHOUT knowing which
|
|
6327
|
+
* harness: the cost wording comes from the driving adapter's own `access`, so a
|
|
6328
|
+
* repo on any harness is disclosed in that harness's terms — the old version had
|
|
6329
|
+
* to name Codex to stop the prompt falsely reading "no model access". */
|
|
6330
|
+
function buildExecuteDisclosure(s, access) {
|
|
6291
6331
|
const lines = ["\nRun the executing checks against your harness?"];
|
|
6292
6332
|
if (s.hasMcp)
|
|
6293
6333
|
lines.push(" · start your MCP servers — connects to their backends");
|
|
@@ -6297,29 +6337,51 @@ function buildExecuteDisclosure(s, harness) {
|
|
|
6297
6337
|
const what = s.triggerableSkills >= 2
|
|
6298
6338
|
? "measure whether skills fire and collide"
|
|
6299
6339
|
: "measure whether skills fire";
|
|
6300
|
-
lines.push(` · ${what} (${triggerCostWording(
|
|
6340
|
+
lines.push(` · ${what} (${triggerCostWording(access)})`);
|
|
6301
6341
|
}
|
|
6302
6342
|
if (s.gateSkills > 0) {
|
|
6303
6343
|
// The adversarial-gate eval runs the FULL (unstubbed) skill — the most
|
|
6304
6344
|
// expensive check — so disclose it separately when gate skills are present.
|
|
6305
|
-
lines.push(` · test whether ${String(s.gateSkills)} enforcement-gate skill${s.gateSkills === 1 ? "" : "s"} hold under pressure — runs the full skill (${triggerCostWording(
|
|
6345
|
+
lines.push(` · test whether ${String(s.gateSkills)} enforcement-gate skill${s.gateSkills === 1 ? "" : "s"} hold under pressure — runs the full skill (${triggerCostWording(access)})`);
|
|
6306
6346
|
}
|
|
6307
6347
|
if (s.adoptableRefs) {
|
|
6308
|
-
lines.push(` · draft + verify your instruction file's references (${triggerCostWording(
|
|
6348
|
+
lines.push(` · draft + verify your instruction file's references (${triggerCostWording(access)})`);
|
|
6309
6349
|
}
|
|
6310
6350
|
lines.push("Asked once — remembered in .vigilesrc.json. [y/N] ");
|
|
6311
6351
|
return lines.join("\n");
|
|
6312
6352
|
}
|
|
6313
|
-
/**
|
|
6314
|
-
*
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6353
|
+
/**
|
|
6354
|
+
* Cost/availability wording for the trigger tier, read off the DRIVING HARNESS's
|
|
6355
|
+
* own answer instead of branching on its name.
|
|
6356
|
+
*
|
|
6357
|
+
* 🔴 IT REPLACES THREE THINGS AT ONCE: `harness === "codex"`, and the pair of
|
|
6358
|
+
* Claude-env predicates that ran for every other harness. The old Codex arm was a
|
|
6359
|
+
* guess ("skips if `codex` isn't on PATH") because nothing had asked; `access`
|
|
6360
|
+
* asks, so the prompt now discloses what will actually happen.
|
|
6361
|
+
*/
|
|
6362
|
+
function triggerCostWording(access) {
|
|
6363
|
+
switch (access.kind) {
|
|
6364
|
+
case "none":
|
|
6365
|
+
return `needs model access — none detected, will skip (${access.fix})`;
|
|
6366
|
+
case "metered":
|
|
6367
|
+
return "⚠ spends API credits";
|
|
6368
|
+
case "subscription":
|
|
6369
|
+
return "your subscription, $0 metered";
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
/**
|
|
6373
|
+
* This adapter's answer to "is a real model reachable, and on whose bill".
|
|
6374
|
+
*
|
|
6375
|
+
* A reference-only harness has no executing tier at all, which is a `none` whose
|
|
6376
|
+
* `fix` says so rather than a Claude-shaped credential hint.
|
|
6377
|
+
*/
|
|
6378
|
+
async function modelAccessFor(adapter) {
|
|
6379
|
+
if (!adapter.harnessTesting)
|
|
6380
|
+
return {
|
|
6381
|
+
kind: "none",
|
|
6382
|
+
fix: `${adapter.name} has no executing-tier driver in this build`,
|
|
6383
|
+
};
|
|
6384
|
+
return (await adapter.liveDriver()).access(process.env);
|
|
6323
6385
|
}
|
|
6324
6386
|
/** Run the trigger tier: a curated `--prompts` file, else auto-generated probes. */
|
|
6325
6387
|
async function runTriggerTier(dir, report, adapter, args) {
|
|
@@ -6509,7 +6571,14 @@ async function main() {
|
|
|
6509
6571
|
const json = args.includes("--json");
|
|
6510
6572
|
// A single dir that's a marketplace (e.g. wshobson/agents' 80+ plugins
|
|
6511
6573
|
// under one marketplace.json) expands into its members and ranks them.
|
|
6512
|
-
|
|
6574
|
+
// The layout is passed rather than defaulted: `inspectMarketplace` reads
|
|
6575
|
+
// `dirname(layout.manifestPath)`, which is a different directory per
|
|
6576
|
+
// harness, and it used to fall back to the Claude Code layout inside the
|
|
6577
|
+
// harness-agnostic detector. `harnessLayoutFor` is the same resolution
|
|
6578
|
+
// the audit itself uses a few lines down.
|
|
6579
|
+
const market = dirs.length === 1
|
|
6580
|
+
? (0, scan_js_1.inspectMarketplace)((0, node_path_1.resolve)(dirs[0]), harnessLayoutFor((0, node_path_1.resolve)(dirs[0]), config, harnessFlagFrom(args)))
|
|
6581
|
+
: null;
|
|
6513
6582
|
// A marketplace whose members are all EXTERNAL expands to nothing, and the
|
|
6514
6583
|
// fallback below already says what to do about that: the target is the
|
|
6515
6584
|
// directory itself.
|
|
@@ -6659,21 +6728,13 @@ async function main() {
|
|
|
6659
6728
|
if (selection.kind === "notice") {
|
|
6660
6729
|
console.log(`⚠ ${selection.notice}`);
|
|
6661
6730
|
}
|
|
6662
|
-
//
|
|
6663
|
-
//
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
// skills it ships? `npm install` drops them in node_modules, which
|
|
6670
|
-
// Claude Code never scans — the plugin install is what wires them,
|
|
6671
|
-
// and until it runs the whole teaching surface is silently absent.
|
|
6672
|
-
// Advisory only (machine state, not repo state) — never scored.
|
|
6673
|
-
const reach = (0, skill_reachability_js_1.formatSkillReachability)((0, skill_reachability_js_1.checkSkillReachability)(root));
|
|
6674
|
-
if (reach)
|
|
6675
|
-
console.log(reach);
|
|
6676
|
-
}
|
|
6731
|
+
// Install diagnostics from the DRIVING harness — "is what vigiles
|
|
6732
|
+
// assumes about this harness's install true on this machine". Advisory
|
|
6733
|
+
// only (machine state, not repo state), never scored, printed as-is.
|
|
6734
|
+
// This used to be `if (adapter.name === "claude-code") { …two Claude
|
|
6735
|
+
// Code checks… }`; the checks were right and the gate was the defect.
|
|
6736
|
+
for (const line of adapter.advisories((0, install_reader_js_1.buildInstallReader)(adapter, root)))
|
|
6737
|
+
console.log(line);
|
|
6677
6738
|
console.log("");
|
|
6678
6739
|
}
|
|
6679
6740
|
// The versioned AuditReport is the product boundary — the same JSON the
|
|
@@ -6693,12 +6754,19 @@ async function main() {
|
|
|
6693
6754
|
// `Evaluated` ring needs to know whether this run will ever ask the
|
|
6694
6755
|
// skill-firing question — see `firingMeasured` below.
|
|
6695
6756
|
const isForeign = root !== process.cwd();
|
|
6757
|
+
// ONE read of "can we reach a model, and on whose bill", from the driving
|
|
6758
|
+
// adapter. It replaces a name check OR-ed with one harness's env vars at
|
|
6759
|
+
// four sites below, and the two hard-coded "authenticate the `claude` CLI"
|
|
6760
|
+
// strings that printed whatever harness was driving.
|
|
6761
|
+
const modelAccess = await modelAccessFor(adapter);
|
|
6696
6762
|
const surfaces = {
|
|
6697
6763
|
hasMcp: report.mcp && !isForeign,
|
|
6698
6764
|
triggerableSkills: report.skills.filter((s) => s.hasDescription && !s.userInvoked).length,
|
|
6699
6765
|
gateSkills: (0, scan_behavioral_js_1.detectGateSkills)(report.skills).length,
|
|
6700
|
-
|
|
6701
|
-
|
|
6766
|
+
// NOT gated on a harness name any more. The drafter is the adapter's
|
|
6767
|
+
// own eval driver and the verifier is deterministic and harness-free,
|
|
6768
|
+
// so an instruction file is an instruction file: prose, on any harness.
|
|
6769
|
+
adoptableRefs: (0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, adapter.layout.instructionFile)),
|
|
6702
6770
|
};
|
|
6703
6771
|
// `decideExecute` is PURE and prompt-free, so the read-vs-run outcome can
|
|
6704
6772
|
// be known before anything prints. Only a settled `run` (a remembered yes)
|
|
@@ -6718,7 +6786,7 @@ async function main() {
|
|
|
6718
6786
|
});
|
|
6719
6787
|
const firingMeasured = execDecision.kind === "run" &&
|
|
6720
6788
|
surfaces.triggerableSkills > 0 &&
|
|
6721
|
-
|
|
6789
|
+
modelAccess.kind !== "none";
|
|
6722
6790
|
// The report scaffold WITHOUT the rule map — the map's catalog
|
|
6723
6791
|
// enrichment (enabled-state / "documented but OFF") enumerates the repo's
|
|
6724
6792
|
// linter, which is gated on the SAME audit.measure consent as the
|
|
@@ -6814,7 +6882,7 @@ async function main() {
|
|
|
6814
6882
|
// that isn't shipped, so it lives in the vigiles testing API.)
|
|
6815
6883
|
// `surfaces` + `execDecision` were resolved above (the ring needed them);
|
|
6816
6884
|
// this only turns an `ask` into a prompt and remembers the answer.
|
|
6817
|
-
const { execute, note: execNote } = await resolveExecution(surfaces, execDecision, json,
|
|
6885
|
+
const { execute, note: execNote } = await resolveExecution(surfaces, execDecision, json, modelAccess);
|
|
6818
6886
|
// Consent is now settled (and remembered via .vigilesrc.json, which
|
|
6819
6887
|
// computeRuleRouting re-reads) — route the prose rules, enumerating the
|
|
6820
6888
|
// live catalog when consented + own-repo. Feeds the JSON report, the
|
|
@@ -6881,17 +6949,14 @@ async function main() {
|
|
|
6881
6949
|
// of the same consent (`execute`), and only when a model is reachable;
|
|
6882
6950
|
// otherwise it's a one-line note (never a hang).
|
|
6883
6951
|
if (execute && surfaces.triggerableSkills > 0) {
|
|
6884
|
-
//
|
|
6885
|
-
//
|
|
6886
|
-
//
|
|
6887
|
-
|
|
6888
|
-
// unavailable. (harness-parity: never block Codex behind a CC check.)
|
|
6889
|
-
const modelReachable = adapter.name === "codex" || (0, scan_trigger_suggest_js_1.hasModelAccess)(process.env);
|
|
6890
|
-
if (modelReachable) {
|
|
6952
|
+
// The adapter answered this once, above. `fix` is THIS harness's own
|
|
6953
|
+
// sentence: the string here used to say "authenticate the `claude` CLI
|
|
6954
|
+
// or set ANTHROPIC_API_KEY" whatever harness the repo was on.
|
|
6955
|
+
if (modelAccess.kind !== "none") {
|
|
6891
6956
|
await runTriggerTier(targets[0], report, adapter, args);
|
|
6892
6957
|
}
|
|
6893
6958
|
else if (!json) {
|
|
6894
|
-
console.log(
|
|
6959
|
+
console.log(`\nℹ skill firing not measured — no model access (${modelAccess.fix}).`);
|
|
6895
6960
|
}
|
|
6896
6961
|
}
|
|
6897
6962
|
// The adoption preview — "what would vigiles catch in YOUR repo?" The model
|
|
@@ -6899,34 +6964,52 @@ async function main() {
|
|
|
6899
6964
|
// VERIFIES each, so "M broken right now" is trustworthy though the extraction
|
|
6900
6965
|
// is probabilistic. Same consent as the trigger tier (`surfaces.adoptableRefs`
|
|
6901
6966
|
// makes a bare instruction-file repo consent-eligible — the prime adoption
|
|
6902
|
-
// target).
|
|
6903
|
-
//
|
|
6904
|
-
//
|
|
6967
|
+
// target).
|
|
6968
|
+
//
|
|
6969
|
+
// 🔴 NO LONGER CLAUDE CODE ONLY, and the feature gate that said so is
|
|
6970
|
+
// gone rather than widened. The drafter is the driving adapter's own eval
|
|
6971
|
+
// driver (`draftWith`); the verifier was always deterministic and
|
|
6972
|
+
// harness-free. What made this CC-only was that `defaultDraft` defaulted
|
|
6973
|
+
// to the Claude runner and parser and nobody had passed anything else —
|
|
6974
|
+
// a TODO, which the old `adapter.name === "claude-code"` froze into the
|
|
6975
|
+
// report as if it were a vendor fact.
|
|
6905
6976
|
let adoptabilityResult;
|
|
6906
6977
|
if (execute && surfaces.adoptableRefs) {
|
|
6907
|
-
if (
|
|
6978
|
+
if (modelAccess.kind !== "none" && adapter.harnessTesting) {
|
|
6908
6979
|
const instrPath = (0, node_path_1.resolve)(root, adapter.layout.instructionFile);
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6980
|
+
// 🔴 CAUGHT, because a FAILED RUN and an EMPTY RESULT are different
|
|
6981
|
+
// answers and this tier used to print the second for both. It
|
|
6982
|
+
// reaches the harness binary directly — no behavioral probe in
|
|
6983
|
+
// front of it to self-report unavailability — so on a machine
|
|
6984
|
+
// without that CLI the drafter would exit non-zero with no output
|
|
6985
|
+
// and the report would say "no machine-verifiable references
|
|
6986
|
+
// found". `DraftRunFailed` is thrown for exactly that; anything
|
|
6987
|
+
// else is a real bug and is left to propagate.
|
|
6988
|
+
try {
|
|
6989
|
+
adoptabilityResult = await (0, adoptability_js_1.runAdoptabilityTier)({
|
|
6990
|
+
instructionContent: (0, node_fs_1.readFileSync)(instrPath, "utf-8"),
|
|
6991
|
+
basePath: root,
|
|
6992
|
+
draft: (0, adoptability_js_1.draftWith)((await adapter.liveDriver()).evalDriver),
|
|
6993
|
+
});
|
|
6994
|
+
if (!json)
|
|
6995
|
+
console.log("\n" +
|
|
6996
|
+
(0, adoptability_js_1.formatAdoptability)(adoptabilityResult, adapter.layout.instructionFile));
|
|
6997
|
+
}
|
|
6998
|
+
catch (e) {
|
|
6999
|
+
if (!(e instanceof adoptability_js_1.DraftRunFailed))
|
|
7000
|
+
throw e;
|
|
7001
|
+
if (!json)
|
|
7002
|
+
console.log(`\nℹ adoptability not measured — ${e.message}.`);
|
|
7003
|
+
}
|
|
6916
7004
|
}
|
|
6917
7005
|
else if (!json && surfaces.triggerableSkills === 0) {
|
|
6918
7006
|
// Only when the trigger tier didn't already print the same note.
|
|
6919
|
-
console.log(
|
|
7007
|
+
console.log(`\nℹ adoptability not measured — no model access (${modelAccess.kind === "none" ? modelAccess.fix : `${adapter.name} has no executing-tier driver`}).`);
|
|
6920
7008
|
}
|
|
6921
7009
|
}
|
|
6922
|
-
//
|
|
6923
|
-
//
|
|
6924
|
-
//
|
|
6925
|
-
if (!json &&
|
|
6926
|
-
adapter.name === "codex" &&
|
|
6927
|
-
(0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, adapter.layout.instructionFile))) {
|
|
6928
|
-
console.log("\nℹ adoptability preview (what vigiles would catch in your repo) is Claude Code only for now — Codex support is a follow-up.");
|
|
6929
|
-
}
|
|
7010
|
+
// The "adoptability preview is Claude Code only for now — Codex support
|
|
7011
|
+
// is a follow-up" note used to print here. It is DELETED, not reworded:
|
|
7012
|
+
// Codex is not a follow-up, so there is nothing to defer.
|
|
6930
7013
|
// The loud read-vs-run nudge for a headless / remembered-no skip — printed
|
|
6931
7014
|
// AFTER the report so the deterministic read leads.
|
|
6932
7015
|
if (execNote)
|