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.
- 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/dialect.js +87 -21
- 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/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 +180 -102
- package/dist/core/adapter.d.ts +213 -61
- package/dist/core/compile.d.ts +2 -2
- package/dist/core/compile.js +57 -38
- 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 +24 -3
- 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/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 -8
- 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 -8
- 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/eval.d.ts +16 -108
- package/dist/eval.js +34 -1
- 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/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 -73
- 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 -17
- 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
|
@@ -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
|
|
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
|
-
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
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.
|
|
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 (
|
|
69
|
-
need(adapter.runtime !== undefined, "
|
|
70
|
-
need(adapter.modelMock !== undefined, "
|
|
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 &&
|
|
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 (
|
|
85
|
-
need(adapter.hookProtocol !== undefined, "
|
|
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, "
|
|
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
|
-
|
|
119
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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.
|
|
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
|
-
/**
|
|
18
|
-
|
|
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;
|
package/dist/adapter-registry.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|