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