vigiles 29.0.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 -38
  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 +24 -3
  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 -8
  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 -8
  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 -73
  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 -17
  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
@@ -20,7 +20,7 @@ export declare function assertHarnessTestable(adapter: HarnessAdapter): {
20
20
  };
21
21
  /**
22
22
  * Behavioural conformance the pure checks can't reach: write a minimal settings
23
- * file in the adapter's declared `settingsFormat` (with a hook), load it through
23
+ * file using the adapter's OWN codec and registration shape, load it through
24
24
  * the adapter's `layout`, and assert the hooks actually came back. This is what
25
25
  * catches a layout that points at the right file but in the wrong format (the
26
26
  * JSON-vs-TOML trap) — the pure checker would pass it, the agent would silently
@@ -19,6 +19,8 @@ const spec_js_1 = require("./core/spec.js");
19
19
  const plugin_loader_js_1 = require("./plugin-loader.js");
20
20
  const vocabulary_consistency_js_1 = require("./core/vocabulary-consistency.js");
21
21
  const event_capability_js_1 = require("./core/event-capability.js");
22
+ const dialect_js_1 = require("./core/dialect.js");
23
+ const layout_js_1 = require("./core/layout.js");
22
24
  const tmp_root_js_1 = require("./core/tmp-root.js");
23
25
  /** Check an adapter against the port contracts; returns the (possibly empty) failure list. */
24
26
  function checkAdapterConformance(adapter) {
@@ -27,12 +29,19 @@ function checkAdapterConformance(adapter) {
27
29
  if (!cond)
28
30
  failures.push(msg);
29
31
  };
30
- const caps = adapter.capabilities;
31
- // Widen to boolean so a malformed (non-TS) adapter that set this false is still
32
- // caught at runtime — the literal `true` type would make a direct check redundant.
33
- const refVerification = caps.referenceVerification;
32
+ // 🔴 THE KIT IS THE EXPLANATION, THE TYPE IS THE GATE. Every check below is
33
+ // now unreachable for an adapter authored in TypeScript: `HarnessAdapter` is
34
+ // an intersection of discriminated unions, so "declares the capability, ships
35
+ // no port" and "denies the capability, ships the port" are both compile
36
+ // errors. The checks stay for the two cases the type does not reach — a
37
+ // third-party adapter authored in JavaScript, and an object crossing a
38
+ // package boundary through a cast — and because a TypeScript error against a
39
+ // 4-member intersection reads badly next to a sentence naming the field.
40
+ //
41
+ // `capabilities.referenceVerification` was checked here and is gone. It was
42
+ // typed as the literal `true` on every adapter, so the check could not fail;
43
+ // a field that can hold one value carries no information.
34
44
  need(adapter.name.length > 0, "name is empty");
35
- need(refVerification, "capabilities.referenceVerification must be true (every adapter does pillar 1)");
36
45
  // --- Pillar 1 (always required): dialect + layout ---
37
46
  need(adapter.dialect.builtinAgentTools.length > 0, "dialect has no builtinAgentTools");
38
47
  // The dialect's several name lists describe ONE vocabulary from different
@@ -52,9 +61,40 @@ function checkAdapterConformance(adapter) {
52
61
  for (const problem of (0, vocabulary_consistency_js_1.vocabularyProjectionProblems)(adapter.dialect.hookEventVocabulary, adapter.dialect.hookEvents, []))
53
62
  need(false, `hookEventVocabulary: ${problem}`);
54
63
  need(adapter.dialect.instructionTargets.length > 0, "dialect has no instructionTargets");
64
+ // The key set replaced a two-valued profile enum, which means a dialect can
65
+ // now declare a set that no code path honours. Both halves are checkable:
66
+ // a key the compiler cannot render is a declaration nothing acts on, and a
67
+ // dialect that drops `name` or `description` emits a SKILL.md with no identity.
68
+ for (const key of adapter.dialect.skillFrontmatterKeys)
69
+ need(dialect_js_1.RENDERABLE_SKILL_FRONTMATTER_KEYS.includes(key), `dialect.skillFrontmatterKeys names "${key}", which the compiler cannot render`);
70
+ for (const required of ["name", "description"])
71
+ need(adapter.dialect.skillFrontmatterKeys.includes(required), `dialect.skillFrontmatterKeys omits "${required}" — a SKILL.md without it has no identity`);
55
72
  need(adapter.layout.instructionFile.length > 0, "layout.instructionFile is empty");
56
73
  need(adapter.layout.manifestPath.length > 0, "layout.manifestPath is empty");
57
- need(adapter.layout.surfaceDirs.length > 0, "layout has no surfaceDirs");
74
+ need((0, layout_js_1.surfaceDirs)(adapter.layout).length > 0, "layout.surfaces names no surface — at least one kind is required");
75
+ // A2: TypeScript has no non-empty-string type worth the ceremony (the
76
+ // template-literal `${string}${string}` trick matches `""` too), so the "no
77
+ // second spelling of absent" rule is a test for every optional path. An
78
+ // absent key already means "this harness has no such thing"; `""` would be a
79
+ // second one, and every reader would have to remember to test for both.
80
+ const optionalPaths = [
81
+ ["rulesDir", adapter.layout.rulesDir],
82
+ ["hookScriptsDir", adapter.layout.hookScriptsDir],
83
+ ["hooksConventionPath", adapter.layout.hooksConventionPath],
84
+ ["userSurfaceRoot", adapter.layout.userSurfaceRoot],
85
+ ["surfaces.skill", adapter.layout.surfaces.skill],
86
+ ["surfaces.agent", adapter.layout.surfaces.agent],
87
+ ["surfaces.command", adapter.layout.surfaces.command],
88
+ ];
89
+ for (const [field, value] of optionalPaths)
90
+ need(value === undefined || value.length > 0, `layout.${field} is "" — absence is spelled by omitting the key, never by an empty string`);
91
+ // A7: `hooksConventionPath` names a FILE, and every reader treats it as one
92
+ // (dirname it, parse it, round-trip it). `opencodeLayout` used to name the
93
+ // DIRECTORY `.opencode/plugin` there, so each of those readers was wrong
94
+ // about it in its own way. A basename with an extension is the cheapest
95
+ // statement of "this is a file" that does not touch the disk.
96
+ if (adapter.layout.hooksConventionPath !== undefined)
97
+ need(/\.[^./]+$/.test((0, node_path_1.basename)(adapter.layout.hooksConventionPath)), `layout.hooksConventionPath "${adapter.layout.hooksConventionPath}" has no file extension — it names a standalone hooks FILE, not a directory (omit it when the harness has none)`);
58
98
  need(typeof adapter.detect === "function", "detect is not a function");
59
99
  // --- Pillar 2 transport ports: required ONLY for the capabilities the
60
100
  // adapter declares. A pillar-1-only adapter (harnessTesting:false) may omit
@@ -65,9 +105,9 @@ function checkAdapterConformance(adapter) {
65
105
  ["dialect", adapter.dialect.name],
66
106
  ["layout", adapter.layout.name],
67
107
  ];
68
- if (caps.harnessTesting) {
69
- need(adapter.runtime !== undefined, "capabilities.harnessTesting is true but runtime is missing");
70
- need(adapter.modelMock !== undefined, "capabilities.harnessTesting is true but modelMock is missing");
108
+ if (adapter.harnessTesting) {
109
+ need(adapter.runtime !== undefined, "harnessTesting is true but runtime is missing");
110
+ need(adapter.modelMock !== undefined, "harnessTesting is true but modelMock is missing");
71
111
  if (adapter.runtime) {
72
112
  need(adapter.runtime.agentBinary.length > 0, "runtime.agentBinary is empty");
73
113
  need(adapter.runtime.modelBaseUrlEnv.length > 0, "runtime.modelBaseUrlEnv is empty");
@@ -77,12 +117,30 @@ function checkAdapterConformance(adapter) {
77
117
  need(adapter.modelMock.modelEndpoint.length > 0, "modelMock.modelEndpoint is empty");
78
118
  portNames.push(["modelMock", adapter.modelMock.name]);
79
119
  }
120
+ // 🔴 THE CHECK THAT WAS MISSING, AND THE STATE IT MISSED WAS SHIPPING.
121
+ // This block checked `runtime` and `modelMock` and not the DRIVER, so
122
+ // `opencodeAdapter` — `harnessTesting: true`, both ports present, no thunk
123
+ // — passed conformance and threw at run time inside `runHarnessTest`. The
124
+ // type now makes that shape unwritable in TypeScript; this is the same
125
+ // statement for an adapter the type never saw.
126
+ need(typeof adapter.harnessTestDriver === "function", "harnessTesting is true but harnessTestDriver is missing — the runner has nothing to dispatch through");
127
+ // ⚠️ AND THE SAME STATEMENT FOR THE PORT ADDED BY #263, because the comment
128
+ // above describes a gap that has now recurred once. `liveDriver` is
129
+ // REQUIRED in this arm of the type, so TypeScript covers every adapter it
130
+ // compiles — and this function exists for the adapters it does not: a
131
+ // third-party JavaScript one, or a cast object. Without this line such an
132
+ // adapter passed conformance and threw later inside `modelAccessFor` or a
133
+ // behavioral probe, which is exactly how `opencodeAdapter` shipped.
134
+ need(typeof adapter.liveDriver === "function", "harnessTesting is true but liveDriver is missing — the executing tiers have no model access or probe to read");
80
135
  }
81
136
  else {
82
- need(adapter.runtime === undefined && adapter.modelMock === undefined, "capabilities.harnessTesting is false — omit runtime/modelMock (a pillar-1-only adapter must not ship a half-wired transport)");
137
+ need(adapter.runtime === undefined &&
138
+ adapter.modelMock === undefined &&
139
+ adapter.harnessTestDriver === undefined &&
140
+ adapter.liveDriver === undefined, "harnessTesting is false — omit runtime/modelMock/harnessTestDriver/liveDriver (a pillar-1-only adapter must not ship a half-wired transport)");
83
141
  }
84
- if (caps.shellHooks) {
85
- need(adapter.hookProtocol !== undefined, "capabilities.shellHooks is true but hookProtocol is missing");
142
+ if (adapter.shellHooks) {
143
+ need(adapter.hookProtocol !== undefined, "shellHooks is true but hookProtocol is missing");
86
144
  if (adapter.hookProtocol) {
87
145
  need(Number.isInteger(adapter.hookProtocol.blockExitCode), "hookProtocol.blockExitCode is not an integer");
88
146
  // A shell-hook harness must declare WHICH events can inject developer
@@ -106,7 +164,7 @@ function checkAdapterConformance(adapter) {
106
164
  }
107
165
  }
108
166
  else {
109
- need(adapter.hookProtocol === undefined, "capabilities.shellHooks is false — omit hookProtocol (hooks are code modules, not shell processes)");
167
+ need(adapter.hookProtocol === undefined, "shellHooks is false — omit hookProtocol (hooks are code modules, not shell processes)");
110
168
  }
111
169
  // Cross-port invariants — the kind of mismatch a copy-paste authoring slip
112
170
  // produces, that no single-port check would catch. Only the present ports.
@@ -115,8 +173,23 @@ function checkAdapterConformance(adapter) {
115
173
  }
116
174
  need(adapter.layout.pluginRootToken === adapter.dialect.pluginRootToken, "layout.pluginRootToken and dialect.pluginRootToken disagree");
117
175
  need(adapter.dialect.instructionTargets.includes(adapter.layout.instructionFile), `layout.instructionFile "${adapter.layout.instructionFile}" is not one of dialect.instructionTargets`);
118
- need(adapter.layout.settingsFormat === "json" ||
119
- adapter.layout.settingsFormat === "toml", `layout.settingsFormat "${adapter.layout.settingsFormat}" is not "json" | "toml"`);
176
+ // 🔴 THE ENUM CHECK THAT USED TO BE HERE IS DELETED, and its deletion is the
177
+ // ratchet rather than a loosening. It asserted
178
+ // `layout.settingsFormat === "json" || === "toml"` — the core re-checking a
179
+ // closed set it had itself declared, which is the tell that the "data" field
180
+ // was a hidden switch. With a CODEC there is no set to be outside of, so a
181
+ // third-party adapter whose settings are YAML is legal and every reader
182
+ // already handles it. What IS checked is the round trip, below: a codec has
183
+ // to be able to read back what it wrote.
184
+ need(adapter.layout.settings.label.length > 0, "layout.settings has no label");
185
+ try {
186
+ const probe = { vigilesConformance: { n: 1 } };
187
+ const back = adapter.layout.settings.parse(adapter.layout.settings.render(probe));
188
+ need(JSON.stringify(back) === JSON.stringify(probe), `layout.settings ("${adapter.layout.settings.label}") does not round-trip: rendered then parsed gave ${JSON.stringify(back)}`);
189
+ }
190
+ catch (e) {
191
+ need(false, `layout.settings ("${adapter.layout.settings.label}") threw on its own output: ${String(e)}`);
192
+ }
120
193
  // Behavioural: the dialect drives the compiler — its own built-in tool must
121
194
  // pass the subagent tool-contract check under this dialect.
122
195
  const tool = adapter.dialect.builtinAgentTools[0];
@@ -150,35 +223,43 @@ function assertAdapterConformance(adapter) {
150
223
  * runtime+modelMock so the caller can use them without re-checking for undefined.
151
224
  */
152
225
  function assertHarnessTestable(adapter) {
153
- if (!adapter.capabilities.harnessTesting ||
154
- !adapter.runtime ||
155
- !adapter.modelMock) {
226
+ if (!adapter.harnessTesting || !adapter.runtime || !adapter.modelMock) {
156
227
  throw new Error(`Adapter "${adapter.name}" does not support harness testing (pillar 2): it is reference-verification-only (no mockable runtime). Use it for compile/scan/lint, not runHarnessTest/runEval.`);
157
228
  }
158
229
  return { runtime: adapter.runtime, modelMock: adapter.modelMock };
159
230
  }
160
231
  /**
161
232
  * Behavioural conformance the pure checks can't reach: write a minimal settings
162
- * file in the adapter's declared `settingsFormat` (with a hook), load it through
233
+ * file using the adapter's OWN codec and registration shape, load it through
163
234
  * the adapter's `layout`, and assert the hooks actually came back. This is what
164
235
  * catches a layout that points at the right file but in the wrong format (the
165
236
  * JSON-vs-TOML trap) — the pure checker would pass it, the agent would silently
166
237
  * run with zero hooks. Does filesystem IO, so it's a separate opt-in assert.
167
238
  */
168
239
  function assertAdapterLoadsHooks(adapter) {
240
+ // Only a shell-hook harness has a hooks file to round-trip; the caller
241
+ // (`adapter-contract.test.ts`) already skips the others loudly, and the type
242
+ // is what says so — `hookProtocol` is `?: never` on the `shellHooks: false`
243
+ // arm, so this narrowing is the union doing its job rather than a guard.
244
+ if (!adapter.shellHooks) {
245
+ throw new Error(`Adapter "${adapter.name}" declares shellHooks:false — there is no shell-hook settings round-trip to assert.`);
246
+ }
169
247
  const dir = (0, tmp_root_js_1.makeTmpDir)("conformance");
170
248
  try {
171
249
  const settingsAbs = (0, node_path_1.join)(dir, adapter.layout.settingsPath);
172
250
  (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(settingsAbs), { recursive: true });
173
- const content = adapter.layout.settingsFormat === "toml"
174
- ? '[[hooks.PreToolUse]]\ncommand = "echo conformance"\n'
175
- : JSON.stringify({
176
- hooks: { PreToolUse: [{ command: "echo conformance" }] },
177
- });
251
+ // 🔴 THE FIXTURE IS NOW BUILT FROM THE PORTS, not from a format branch.
252
+ // It used to be `settingsFormat === "toml" ? <TOML text> : <JSON text>`,
253
+ // which tested the two encodings the CHECK knew about rather than the ones
254
+ // the ADAPTER declares — a third encoding would have been handed JSON and
255
+ // failed for the wrong reason. `registration` supplies the harness's entry
256
+ // SHAPE and `settings.render` its ENCODING, which is exactly the pair this
257
+ // assertion exists to prove is wired to the same file.
258
+ const content = adapter.layout.settings.render(adapter.hookProtocol.registration("PreToolUse", undefined, "echo conformance"));
178
259
  (0, node_fs_1.writeFileSync)(settingsAbs, content);
179
260
  const loaded = (0, plugin_loader_js_1.loadPlugin)(dir, adapter.layout);
180
261
  if (!loaded.settings.hooks) {
181
- throw new Error(`Adapter "${adapter.name}": loadPlugin read no hooks from a ${adapter.layout.settingsFormat} settings file at ${adapter.layout.settingsPath} — the settings-format wiring is broken.`);
262
+ throw new Error(`Adapter "${adapter.name}": loadPlugin read no hooks from a ${adapter.layout.settings.label} settings file at ${adapter.layout.settingsPath} — the settings codec / registration wiring is broken.`);
182
263
  }
183
264
  }
184
265
  finally {
@@ -1,21 +1,68 @@
1
- /**
2
- * Adapter registry — the composition-root list the CLI uses to auto-detect which
3
- * harness a repo targets. The library API selects an adapter by import
4
- * (`vigiles/claude-code`); the CLI can't, so it walks this registry. Claude Code
5
- * is the default, so detection is backwards-compatible: an undetected repo (or a
6
- * repo with no adapter markers) resolves to Claude Code exactly as before.
7
- *
8
- * Adding a harness = add its `HarnessAdapter` to `ADAPTERS` (and a
9
- * `vigiles/<harness>` export). Order matters only if two adapters could both
10
- * match a repo; `detect()` returns a specificity score so the strongest signal
11
- * wins regardless of order.
12
- */
13
1
  import type { HarnessAdapter } from "./core/adapter.js";
14
2
  import type { HarnessDeclaration } from "./core/types.js";
15
3
  /** The default adapter when detection finds no harness markers. */
16
4
  export declare const defaultAdapter: HarnessAdapter;
17
- /** All registered adapters. detect() specificity (not order) breaks ties. */
18
- export declare const ADAPTERS: readonly HarnessAdapter[];
5
+ /**
6
+ * All registered adapters. detect() specificity (not order) breaks ties.
7
+ *
8
+ * 🔴 `as const satisfies`, NOT a `readonly HarnessAdapter[]` ANNOTATION, and the
9
+ * difference is the whole of {@link HarnessName}. An annotation widens
10
+ * `name: "claude-code"` to `string`, so the registry cannot say which names
11
+ * exist and every consumer that needed to know had to write the list out again
12
+ * — which is how `ProbeHarness = "claude-code" | "codex"` and the lint rule's
13
+ * `DEFAULT_NAMES` came to be hand-maintained copies of this array. `satisfies`
14
+ * keeps the literal types while still checking each element against the port.
15
+ */
16
+ export declare const ADAPTERS: readonly [{
17
+ readonly name: "claude-code";
18
+ readonly harnessTesting: true;
19
+ readonly shellHooks: true;
20
+ readonly subagents: true;
21
+ readonly dialect: import("./adapter.js").HarnessDialect;
22
+ readonly layout: import("./adapter.js").PluginLayout;
23
+ readonly runtime: import("./adapter.js").HarnessRuntime;
24
+ readonly hookProtocol: import("./adapter.js").HookProtocol;
25
+ readonly modelMock: import("./adapter.js").ModelMock;
26
+ readonly harnessTestDriver: () => Promise<import("./harness-test.js").HarnessTestDriver>;
27
+ readonly liveDriver: () => Promise<import("./core/live-driver.js").HarnessLiveDriver>;
28
+ readonly claims: (path: string) => boolean;
29
+ readonly detect: (exists: (repoRelative: string) => boolean) => import("./core/adapter.js").DetectSignal;
30
+ readonly advisories: (read: import("./core/adapter.js").InstallReader) => readonly string[];
31
+ }, {
32
+ readonly name: "codex";
33
+ readonly harnessTesting: true;
34
+ readonly shellHooks: true;
35
+ readonly subagents: false;
36
+ readonly dialect: import("./adapter.js").HarnessDialect;
37
+ readonly layout: import("./adapter.js").PluginLayout;
38
+ readonly runtime: import("./adapter.js").HarnessRuntime;
39
+ readonly hookProtocol: import("./adapter.js").HookProtocol;
40
+ readonly modelMock: import("./adapter.js").ModelMock;
41
+ readonly harnessTestDriver: () => Promise<import("./harness-test.js").HarnessTestDriver>;
42
+ readonly liveDriver: () => Promise<import("./core/live-driver.js").HarnessLiveDriver>;
43
+ readonly claims: (path: string) => boolean;
44
+ readonly detect: (exists: (repoRelative: string) => boolean) => import("./core/adapter.js").DetectSignal;
45
+ readonly advisories: () => readonly string[];
46
+ }];
47
+ /**
48
+ * Every registered harness name, derived from {@link ADAPTERS} — `"claude-code"
49
+ * | "codex"` today, and whatever the array holds tomorrow.
50
+ *
51
+ * ⚠️ WHAT THIS DOES NOT CATCH, stated because the type looks stronger than it
52
+ * is: two adapters declaring the SAME name collapse the union silently (a union
53
+ * of duplicates is that one member), so this type would still read
54
+ * `"claude-code" | "codex"` with three adapters registered and two of them
55
+ * called `codex`. `adapter-contract.test.ts` asserts the set size instead —
56
+ * a test, because there is no type-level cardinality to assert against.
57
+ *
58
+ * ⚠️ AND IT IS NOT A CEILING ON COMPARING NAMES. A branded or literal string is
59
+ * still comparable to a string literal in TypeScript (`===` uses comparability,
60
+ * not assignability — measured in the redesign's section 7), so this type cannot
61
+ * make `adapter.name === "codex"` an error. What stops that is the
62
+ * `local/no-harness-names` lint rule; what this type does is remove the REASON
63
+ * to write such a comparison, by letting the one list be the only list.
64
+ */
65
+ export type HarnessName = (typeof ADAPTERS)[number]["name"];
19
66
  /** The result of auto-detecting a harness from a repo's layout. */
20
67
  export interface DetectResult {
21
68
  readonly adapter: HarnessAdapter;
@@ -12,13 +12,37 @@ exports.resolveHarnessSelection = resolveHarnessSelection;
12
12
  exports.resolveHarnessAdapters = resolveHarnessAdapters;
13
13
  exports.declaredHarnessNames = declaredHarnessNames;
14
14
  exports.resolveDeclaredHarnesses = resolveDeclaredHarnesses;
15
+ /**
16
+ * Adapter registry — the composition-root list the CLI uses to auto-detect which
17
+ * harness a repo targets. The library API selects an adapter by import
18
+ * (`vigiles/claude-code`); the CLI can't, so it walks this registry. Claude Code
19
+ * is the default, so detection is backwards-compatible: an undetected repo (or a
20
+ * repo with no adapter markers) resolves to Claude Code exactly as before.
21
+ *
22
+ * Adding a harness = add its `HarnessAdapter` to `ADAPTERS` (and a
23
+ * `vigiles/<harness>` export). Order matters only if two adapters could both
24
+ * match a repo; `detect()` returns a specificity score so the strongest signal
25
+ * wins regardless of order.
26
+ */
27
+ const node_fs_1 = require("node:fs");
28
+ const node_path_1 = require("node:path");
15
29
  const surface_scopes_js_1 = require("./core/surface-scopes.js");
16
30
  const compose_js_1 = require("./core/compose.js");
17
31
  const adapter_js_1 = require("./adapters/claude-code/adapter.js");
18
32
  const adapter_js_2 = require("./adapters/codex/adapter.js");
19
33
  /** The default adapter when detection finds no harness markers. */
20
34
  exports.defaultAdapter = adapter_js_1.claudeCodeAdapter;
21
- /** All registered adapters. detect() specificity (not order) breaks ties. */
35
+ /**
36
+ * All registered adapters. detect() specificity (not order) breaks ties.
37
+ *
38
+ * 🔴 `as const satisfies`, NOT a `readonly HarnessAdapter[]` ANNOTATION, and the
39
+ * difference is the whole of {@link HarnessName}. An annotation widens
40
+ * `name: "claude-code"` to `string`, so the registry cannot say which names
41
+ * exist and every consumer that needed to know had to write the list out again
42
+ * — which is how `ProbeHarness = "claude-code" | "codex"` and the lint rule's
43
+ * `DEFAULT_NAMES` came to be hand-maintained copies of this array. `satisfies`
44
+ * keeps the literal types while still checking each element against the port.
45
+ */
22
46
  exports.ADAPTERS = [
23
47
  adapter_js_1.claudeCodeAdapter,
24
48
  adapter_js_2.codexAdapter,
@@ -35,12 +59,20 @@ exports.ADAPTERS = [
35
59
  * `research/audit-harness-dx.md`.
36
60
  */
37
61
  function detectAdapterResult(root) {
38
- const scored = exports.ADAPTERS.map((a) => ({ a, score: a.detect(root) })).filter((s) => s.score > 0);
62
+ // 🔴 THE DOMAIN OWNS THE FILESYSTEM, THE ADAPTER OWNS THE QUESTION. `detect`
63
+ // used to be handed `root` and reach for `node:fs` itself, so an adapter
64
+ // could enumerate anything under it — the inversion `claims` exists to
65
+ // prevent, sitting right next to it. This predicate is the whole of what an
66
+ // adapter can do, and `adapter-properties.test.ts` asserts each one asks only
67
+ // about paths its own `claims` covers.
68
+ const exists = (repoRelative) => (0, node_fs_1.existsSync)((0, node_path_1.join)(root, repoRelative));
69
+ const scored = exports.ADAPTERS.map((a) => ({ a, signal: a.detect(exists) })).filter((s) => s.signal.specificity > 0);
39
70
  if (scored.length === 0) {
40
71
  return { adapter: exports.defaultAdapter, fallback: true, ambiguousWith: [] };
41
72
  }
42
- const top = Math.max(...scored.map((s) => s.score));
43
- let winners = scored.filter((s) => s.score === top).map((s) => s.a);
73
+ const top = Math.max(...scored.map((s) => s.signal.specificity));
74
+ const atTop = scored.filter((s) => s.signal.specificity === top);
75
+ let winners = atTop.map((s) => s.a);
44
76
  // Mirror-collapse: the only false-"both" tie is at the weak instruction-file level
45
77
  // (top === 1 — Claude Code via CLAUDE.md, Codex via AGENTS.md). When those two files
46
78
  // are a MIRROR (symlink or byte-identical, per detectInstructionMirror), that's ONE
@@ -49,13 +81,49 @@ function detectAdapterResult(root) {
49
81
  // so it resolves to Claude Code without a spurious "matches claude-code, codex" notice.
50
82
  // A genuine dual instruction file (different content, or a real .codex/config.toml at
51
83
  // score 3) is NOT a mirror at top 1 → stays ambiguous, exactly as it should.
84
+ //
85
+ // 🔴 IT USED TO NAME A HARNESS TO DO THIS — `winners.some(w => w.name ===
86
+ // "codex")` and then `filter(w => w.name !== "codex")`, in the composition
87
+ // root, with the tie-break decided by which literal was typed here. What the
88
+ // rule is actually about is the KIND of marker that matched, which is what
89
+ // `DetectSignal.via` now carries: every winner matched only by its
90
+ // instruction file, and those files are a mirror, so it is one config. A
91
+ // third harness reading `AGENTS.md` now collapses correctly with no edit
92
+ // here. Which single adapter survives is settled just below.
52
93
  if (winners.length > 1 &&
53
- top === 1 &&
54
- winners.some((w) => w.name === "codex") &&
55
- (0, compose_js_1.detectInstructionMirror)(root) !== null) {
56
- // At top === 1 with a mirror, both files exist so Claude Code (CLAUDE.md) is
57
- // always a co-winner — dropping codex leaves a non-empty set.
58
- winners = winners.filter((w) => w.name !== "codex");
94
+ atTop.every((s) => s.signal.via === "instruction-file")) {
95
+ if ((0, compose_js_1.detectInstructionMirror)(root) !== null) {
96
+ // 🔴 THE MIRROR SAYS THESE ARE ONE CONFIG. IT DOES NOT SAY WHOSE — and a
97
+ // first draft of this collapse read the symlink's DIRECTION as if it did,
98
+ // keeping whichever adapter reads the link's real target. That silently
99
+ // reversed a repo with `CLAUDE.md -> AGENTS.md`: the audit selected the
100
+ // AGENTS-reading harness and then looked for its surfaces, missing
101
+ // `.claude/skills` entirely. The direction is a BRIDGING artifact — an
102
+ // AGENTS-first repository adds the `CLAUDE.md` link precisely so that
103
+ // Claude Code works — so if anything it is evidence FOR the linking
104
+ // side, and certainly not against it. Byte-identical mirrors (what
105
+ // rulesync and Ruler produce) carry no direction at all, which is the
106
+ // same problem with the ambiguity visible instead of hidden.
107
+ //
108
+ // So BOTH shapes are treated the same way: the collapse still has to
109
+ // produce ONE answer, because that is its whole job — the files are one
110
+ // config, and a "matches claude-code, codex" notice would be wrong — and
111
+ // the tie is broken by REGISTRY ORDER, deterministically, with the reason
112
+ // written here rather than left to whoever reads `winners[0]` later.
113
+ // That is the answer this repository shipped before the collapse was
114
+ // rewritten to stop naming a harness, reached without comparing a name
115
+ // against a literal in this file.
116
+ //
117
+ // ⏳ WHAT IS GENUINELY OPEN, stated rather than buried: a repository that
118
+ // means an AGENTS-reading harness AND keeps a `CLAUDE.md` bridge has no
119
+ // way to say so at this level, and registry order will answer Claude
120
+ // Code. The signal that would settle it is a STRONGER marker (a real
121
+ // `.codex/config.toml`, an explicit `harness` in config, `--harness=`),
122
+ // all of which already outrank this branch — `atTop.every(via ===
123
+ // "instruction-file")` is exactly the guard that keeps them from
124
+ // reaching here. Guessing from the link direction is not a substitute.
125
+ winners = [winners[0]];
126
+ }
59
127
  }
60
128
  return {
61
129
  adapter: winners[0],
package/dist/adapter.d.ts CHANGED
@@ -17,7 +17,28 @@
17
17
  */
18
18
  export type { HarnessAdapter, AdapterCapabilities } from "./core/adapter.js";
19
19
  export type { HarnessDialect } from "./core/dialect.js";
20
- export type { PluginLayout } from "./core/layout.js";
20
+ export type { PluginLayout, SurfaceDirs, SurfaceKind } from "./core/layout.js";
21
+ export { executableSourceDirs, materializePrefix, surfaceDirs, } from "./core/layout.js";
22
+ /**
23
+ * The instruction-chain vocabulary `PluginLayout.instructionChain` speaks.
24
+ *
25
+ * Exported because the method is on the port: without these an adapter author
26
+ * cannot write a return type, which makes the interface unimplementable from
27
+ * outside this package. `EMPTY_CHAIN` is the honest answer for a harness with no
28
+ * instruction surface — spelling out five empty arrays at each such site invites
29
+ * one of them to be forgotten.
30
+ */
31
+ export type { InstructionChain, InstructionRole, InstructionScope, LoadedInstruction, NamedImport, NotLoadedReason, PatternFrom, UnloadedInstruction, } from "./core/instruction-chain.js";
32
+ export { EMPTY_CHAIN } from "./core/instruction-chain.js";
33
+ /**
34
+ * ⚠️ `SettingsCodec` was in the same position and is exported here for the same
35
+ * reason: `PluginLayout.settings` has been typed as one since the codec landed,
36
+ * and the authoring barrel never re-exported it, so `docs/adapter-api.md`
37
+ * documents a field whose type a third party could not name. One line, and the
38
+ * alternative is a port that cannot be implemented from outside.
39
+ */
40
+ export type { SettingsCodec } from "./core/settings-codec.js";
41
+ export { jsonSettingsCodec, tomlSettingsCodec } from "./core/settings-codec.js";
21
42
  export type { HarnessRuntime } from "./core/runtime.js";
22
43
  export type { HookProtocol } from "./core/hook-protocol.js";
23
44
  export type { ModelMock } from "./core/model-mock.js";
@@ -30,5 +51,5 @@ export type { ModelMock } from "./core/model-mock.js";
30
51
  */
31
52
  export { layoutClaims } from "./core/surface-discovery.js";
32
53
  export { checkAdapterConformance, assertAdapterConformance, assertAdapterLoadsHooks, assertHarnessTestable, type ConformanceResult, } from "./adapter-conformance.js";
33
- export { ADAPTERS, defaultAdapter, detectAdapter, detectAdapterResult, resolveAdapter, getAdapter, type DetectResult, } from "./adapter-registry.js";
54
+ export { ADAPTERS, defaultAdapter, detectAdapter, detectAdapterResult, resolveAdapter, getAdapter, type DetectResult, type HarnessName, } from "./adapter-registry.js";
34
55
  //# sourceMappingURL=adapter.d.ts.map
package/dist/adapter.js CHANGED
@@ -1,6 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAdapter = exports.resolveAdapter = exports.detectAdapterResult = exports.detectAdapter = exports.defaultAdapter = exports.ADAPTERS = exports.assertHarnessTestable = exports.assertAdapterLoadsHooks = exports.assertAdapterConformance = exports.checkAdapterConformance = exports.layoutClaims = void 0;
3
+ exports.getAdapter = exports.resolveAdapter = exports.detectAdapterResult = exports.detectAdapter = exports.defaultAdapter = exports.ADAPTERS = exports.assertHarnessTestable = exports.assertAdapterLoadsHooks = exports.assertAdapterConformance = exports.checkAdapterConformance = exports.layoutClaims = exports.tomlSettingsCodec = exports.jsonSettingsCodec = exports.EMPTY_CHAIN = exports.surfaceDirs = exports.materializePrefix = exports.executableSourceDirs = void 0;
4
+ // The three derived readers. They are part of the port's surface, not helpers:
5
+ // an adapter author reads a layout through them, and they are what replaced the
6
+ // `surfaceDirs` / `intraRefDirs` / `materializeRoot` FIELDS.
7
+ var layout_js_1 = require("./core/layout.js");
8
+ Object.defineProperty(exports, "executableSourceDirs", { enumerable: true, get: function () { return layout_js_1.executableSourceDirs; } });
9
+ Object.defineProperty(exports, "materializePrefix", { enumerable: true, get: function () { return layout_js_1.materializePrefix; } });
10
+ Object.defineProperty(exports, "surfaceDirs", { enumerable: true, get: function () { return layout_js_1.surfaceDirs; } });
11
+ var instruction_chain_js_1 = require("./core/instruction-chain.js");
12
+ Object.defineProperty(exports, "EMPTY_CHAIN", { enumerable: true, get: function () { return instruction_chain_js_1.EMPTY_CHAIN; } });
13
+ var settings_codec_js_1 = require("./core/settings-codec.js");
14
+ Object.defineProperty(exports, "jsonSettingsCodec", { enumerable: true, get: function () { return settings_codec_js_1.jsonSettingsCodec; } });
15
+ Object.defineProperty(exports, "tomlSettingsCodec", { enumerable: true, get: function () { return settings_codec_js_1.tomlSettingsCodec; } });
4
16
  /**
5
17
  * The `claims(path)` helper every shipped adapter uses: derive "is this path
6
18
  * mine?" from the adapter's own `PluginLayout`, so a layout that moves takes its
@@ -1,3 +1,33 @@
1
- import type { HarnessAdapter } from "../../core/adapter.js";
2
- export declare const claudeCodeAdapter: HarnessAdapter;
1
+ /**
2
+ * claudeCodeAdapter — the Claude Code `HarnessAdapter`: the five port
3
+ * implementations bundled, plus a `detect` that recognizes a Claude Code repo
4
+ * (a `.claude-plugin/` manifest, a `.claude/settings.json`, or a `CLAUDE.md`).
5
+ * This is the reference adapter a second harness (Codex, Gemini, …) mirrors.
6
+ */
7
+ import type { DetectSignal, InstallReader } from "../../core/adapter.js";
8
+ export declare const claudeCodeAdapter: {
9
+ readonly name: "claude-code";
10
+ readonly harnessTesting: true;
11
+ readonly shellHooks: true;
12
+ readonly subagents: true;
13
+ readonly dialect: import("./dialect.js").HarnessDialect;
14
+ readonly layout: import("../../adapter.js").PluginLayout;
15
+ readonly runtime: import("../../adapter.js").HarnessRuntime;
16
+ readonly hookProtocol: import("../../adapter.js").HookProtocol;
17
+ readonly modelMock: import("../../adapter.js").ModelMock;
18
+ readonly harnessTestDriver: () => Promise<import("../../harness-test.js").HarnessTestDriver>;
19
+ readonly liveDriver: () => Promise<import("../../core/live-driver.js").HarnessLiveDriver>;
20
+ readonly claims: (path: string) => boolean;
21
+ readonly detect: (exists: (repoRelative: string) => boolean) => DetectSignal;
22
+ /**
23
+ * The two install diagnostics that used to sit behind
24
+ * `if (adapter.name === "claude-code")` in the CLI, now reached through the
25
+ * port. Both are genuinely THIS harness's and both would MISLEAD on another
26
+ * one — the reachability fix line is `claude plugin install`, which a Codex
27
+ * user cannot run — which is why the gate was right and only its shape wrong.
28
+ *
29
+ * Never throws and never scores: each check degrades to "nothing to say".
30
+ */
31
+ readonly advisories: (read: InstallReader) => readonly string[];
32
+ };
3
33
  //# sourceMappingURL=adapter.d.ts.map
@@ -1,14 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.claudeCodeAdapter = void 0;
4
- /**
5
- * claudeCodeAdapter — the Claude Code `HarnessAdapter`: the five port
6
- * implementations bundled, plus a `detect` that recognizes a Claude Code repo
7
- * (a `.claude-plugin/` manifest, a `.claude/settings.json`, or a `CLAUDE.md`).
8
- * This is the reference adapter a second harness (Codex, Gemini, …) mirrors.
9
- */
10
- const node_fs_1 = require("node:fs");
11
- const node_path_1 = require("node:path");
4
+ const dialect_drift_js_1 = require("./dialect-drift.js");
5
+ const skill_reachability_js_1 = require("./skill-reachability.js");
12
6
  const dialect_js_1 = require("./dialect.js");
13
7
  const layout_js_1 = require("./layout.js");
14
8
  const surface_discovery_js_1 = require("../../core/surface-discovery.js");
@@ -19,12 +13,14 @@ exports.claudeCodeAdapter = {
19
13
  name: "claude-code",
20
14
  // The reference harness: every tier. Mockable transport (Anthropic SSE) and
21
15
  // shell hooks (exit 2 / decision JSON) — both pillars, all tiers.
22
- capabilities: {
23
- referenceVerification: true,
24
- harnessTesting: true,
25
- shellHooks: true,
26
- subagents: true,
27
- },
16
+ //
17
+ // The flags are FLAT, not nested under `capabilities`, because TypeScript
18
+ // narrows a union by a discriminant on the object itself and never by
19
+ // `a.capabilities.x` — nesting them is what made "declares the capability,
20
+ // ships no port" expressible at all.
21
+ harnessTesting: true,
22
+ shellHooks: true,
23
+ subagents: true,
28
24
  dialect: dialect_js_1.claudeCodeDialect,
29
25
  layout: layout_js_1.claudeCodeLayout,
30
26
  runtime: runtime_js_1.claudeCodeRuntime,
@@ -33,22 +29,47 @@ exports.claudeCodeAdapter = {
33
29
  // Imported inside the thunk, not at the top: a top-level import runs at module
34
30
  // init and would pull the whole test/compiler graph back in.
35
31
  harnessTestDriver: async () => (await import("../../harness-test.js")).claudeCodeDriver,
32
+ // The executing tiers' driver, thunked for the same reason: it reaches the
33
+ // real-model graph, which an adapter READ (the hook runtime) must not pay for.
34
+ liveDriver: async () => (await import("../../eval.js")).claudeCodeLiveDriver,
36
35
  // Derived from the layout, never listed again here — see `claims` on
37
36
  // `HarnessAdapter` for why this method takes a PATH and not a root.
38
37
  claims(path) {
39
38
  return (0, surface_discovery_js_1.layoutClaims)(layout_js_1.claudeCodeLayout, path);
40
39
  },
41
- detect(root) {
40
+ // Takes the domain's `exists`, never a root: this adapter cannot enumerate
41
+ // anything, and it asks only about the three paths its own `claims` covers.
42
+ detect(exists) {
42
43
  // Most specific signal wins: a plugin manifest (3) > repo settings (2) >
43
44
  // a bare CLAUDE.md (1, weak — many tools also read it / AGENTS.md).
44
- const has = (rel) => (0, node_fs_1.existsSync)((0, node_path_1.join)(root, rel));
45
- if (has(layout_js_1.claudeCodeLayout.manifestPath))
46
- return 3;
47
- if (has(layout_js_1.claudeCodeLayout.settingsPath))
48
- return 2;
49
- if (has(layout_js_1.claudeCodeLayout.instructionFile))
50
- return 1;
51
- return 0;
45
+ if (exists(layout_js_1.claudeCodeLayout.manifestPath))
46
+ return { specificity: 3, via: "manifest" };
47
+ if (exists(layout_js_1.claudeCodeLayout.settingsPath))
48
+ return { specificity: 2, via: "settings" };
49
+ if (exists(layout_js_1.claudeCodeLayout.instructionFile))
50
+ return { specificity: 1, via: "instruction-file" };
51
+ return { specificity: 0, via: "instruction-file" };
52
+ },
53
+ /**
54
+ * The two install diagnostics that used to sit behind
55
+ * `if (adapter.name === "claude-code")` in the CLI, now reached through the
56
+ * port. Both are genuinely THIS harness's and both would MISLEAD on another
57
+ * one — the reachability fix line is `claude plugin install`, which a Codex
58
+ * user cannot run — which is why the gate was right and only its shape wrong.
59
+ *
60
+ * Never throws and never scores: each check degrades to "nothing to say".
61
+ */
62
+ advisories(read) {
63
+ return [
64
+ // Our hand-maintained tool/hook catalog vs the `@anthropic-ai/claude-code`
65
+ // actually installed on this machine. A machine read, not a repo read.
66
+ (0, dialect_drift_js_1.formatDialectDrift)((0, dialect_drift_js_1.checkDialectDrift)()),
67
+ // Can the agent SEE the skills vigiles ships here? `npm install` drops
68
+ // them in node_modules, which Claude Code never scans; the plugin install
69
+ // is what wires them, and until it runs the teaching surface is silently
70
+ // absent.
71
+ (0, skill_reachability_js_1.formatSkillReachability)((0, skill_reachability_js_1.checkSkillReachability)(read)),
72
+ ].filter((line) => line !== null);
52
73
  },
53
74
  };
54
75
  //# sourceMappingURL=adapter.js.map