vigiles 29.1.0 → 30.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -46
- package/dist/core/compose.d.ts +5 -3
- package/dist/core/compose.js +5 -3
- package/dist/core/config-schema.d.ts +14 -2
- package/dist/core/config-schema.js +20 -7
- package/dist/core/dialect.d.ts +54 -12
- package/dist/core/dialect.js +56 -0
- package/dist/core/eval-driver.d.ts +194 -0
- package/dist/core/eval-driver.js +3 -0
- package/dist/core/frontmatter-read.d.ts +10 -0
- package/dist/core/frontmatter-read.js +30 -3
- package/dist/core/hook-program.d.ts +27 -2
- package/dist/core/hook-program.js +29 -24
- package/dist/core/hook-protocol.d.ts +54 -0
- package/dist/core/install-reader.d.ts +18 -0
- package/dist/core/install-reader.js +88 -0
- package/dist/core/instruction-chain.d.ts +444 -0
- package/dist/core/instruction-chain.js +292 -0
- package/dist/core/instruction-weight.d.ts +96 -14
- package/dist/core/instruction-weight.js +65 -30
- package/dist/core/layout.d.ts +220 -33
- package/dist/core/layout.js +115 -1
- package/dist/core/lethal-trifecta.d.ts +12 -7
- package/dist/core/lethal-trifecta.js +13 -13
- package/dist/core/live-driver.d.ts +137 -0
- package/dist/core/live-driver.js +14 -0
- package/dist/core/markdown.d.ts +23 -0
- package/dist/core/markdown.js +77 -28
- package/dist/core/orphans.js +9 -7
- package/dist/core/settings-codec.d.ts +17 -0
- package/dist/core/settings-codec.js +56 -0
- package/dist/core/surface-discovery.d.ts +2 -2
- package/dist/core/surface-discovery.js +24 -12
- package/dist/core/surface-scopes.d.ts +26 -6
- package/dist/core/surface-scopes.js +52 -11
- package/dist/core/validate.js +16 -3
- package/dist/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 -84
- package/dist/skill-harness.d.ts +21 -5
- package/dist/skill-harness.js +29 -11
- package/dist/surface-discovery-fs.d.ts +2 -0
- package/dist/surface-discovery-fs.js +108 -6
- package/dist/test-coverage-files.js +24 -17
- package/dist/test-coverage.d.ts +9 -3
- package/dist/test-coverage.js +32 -22
- package/dist/verify-plugin-guards.js +1 -1
- package/package.json +1 -1
- package/dist/skill-reachability.d.ts +0 -68
- package/dist/skill-reachability.js +0 -205
- /package/dist/{dialect-drift.d.ts → adapters/claude-code/dialect-drift.d.ts} +0 -0
- /package/dist/{dialect-drift.js → adapters/claude-code/dialect-drift.js} +0 -0
package/dist/core/adapter.d.ts
CHANGED
|
@@ -18,27 +18,29 @@ import type { HarnessRuntime } from "./runtime.js";
|
|
|
18
18
|
import type { HookProtocol } from "./hook-protocol.js";
|
|
19
19
|
import type { ModelMock } from "./model-mock.js";
|
|
20
20
|
import type { HarnessTestDriver } from "./harness-driver.js";
|
|
21
|
+
import type { HarnessLiveDriver } from "./live-driver.js";
|
|
21
22
|
/**
|
|
22
23
|
* Which vigiles pillars/tiers a harness can drive — the capability matrix made
|
|
23
|
-
* executable (see `docs/harnesses.md`).
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* executable (see `docs/harnesses.md`).
|
|
25
|
+
*
|
|
26
|
+
* 🔴 KEPT AS A TYPE, NOT AS A FIELD. The flags used to sit in a nested
|
|
27
|
+
* `capabilities` object on the adapter, and that nesting is precisely what made
|
|
28
|
+
* the illegal state below expressible: TypeScript narrows a union by a
|
|
29
|
+
* discriminant on the object ITSELF, never by `a.capabilities.x`, so no shape
|
|
30
|
+
* of this interface could have tied `harnessTesting: true` to the presence of a
|
|
31
|
+
* driver. The flags are now discriminants on `HarnessAdapter` directly; this
|
|
32
|
+
* interface remains as the documented projection of them.
|
|
33
|
+
*
|
|
34
|
+
* `referenceVerification` is gone. It was `true` on every adapter and its type
|
|
35
|
+
* was the literal `true` — a field that can hold one value carries no
|
|
36
|
+
* information, and the conformance check for it could not fail.
|
|
30
37
|
*/
|
|
31
38
|
export interface AdapterCapabilities {
|
|
32
|
-
/**
|
|
33
|
-
* Pillar 1 — reference verification (dialect + layout). Always `true`: every
|
|
34
|
-
* harness with an instruction-file format can have its references verified.
|
|
35
|
-
*/
|
|
36
|
-
readonly referenceVerification: true;
|
|
37
39
|
/**
|
|
38
40
|
* Pillar 2 — deterministic harness tests + evals: the binary can be spawned
|
|
39
|
-
* and pointed at a mock model. Requires `runtime` + `modelMock
|
|
40
|
-
* closed harnesses that route through a fixed
|
|
41
|
-
* Devin, Amp, Amazon Q —
|
|
41
|
+
* and pointed at a mock model. Requires `runtime` + `modelMock` +
|
|
42
|
+
* `harnessTestDriver`. `false` for closed harnesses that route through a fixed
|
|
43
|
+
* backend (no BYOM): Cursor, Devin, Amp, Amazon Q — pillar-1-only adapters.
|
|
42
44
|
*/
|
|
43
45
|
readonly harnessTesting: boolean;
|
|
44
46
|
/**
|
|
@@ -56,65 +58,63 @@ export interface AdapterCapabilities {
|
|
|
56
58
|
* is `false` those rules report **n/a** rather than running. `false` for Codex,
|
|
57
59
|
* whose `[agents]` TOML is a concurrency table, not a tool-contract file — a
|
|
58
60
|
* wholly different concept that deliberately shares the word.
|
|
61
|
+
*
|
|
62
|
+
* No port sits behind it, so it stays a plain flag on the base rather than
|
|
63
|
+
* becoming a discriminant of a union with nothing in its arms.
|
|
59
64
|
*/
|
|
60
65
|
readonly subagents: boolean;
|
|
61
66
|
}
|
|
62
|
-
|
|
67
|
+
/**
|
|
68
|
+
* How, and how strongly, a repo looks like this harness.
|
|
69
|
+
*
|
|
70
|
+
* `via` exists so the REGISTRY can break a tie without naming a harness. The
|
|
71
|
+
* one false "both" tie is at the weak instruction-file level, where two
|
|
72
|
+
* adapters match on mirrored root files; `via: "instruction-file"` is what
|
|
73
|
+
* makes that case recognisable from the outside, and it used to be recognised
|
|
74
|
+
* by comparing a winner's `name` against a literal in the registry.
|
|
75
|
+
*/
|
|
76
|
+
export interface DetectSignal {
|
|
77
|
+
/** 0 = not this harness; higher = a more specific match. The registry picks
|
|
78
|
+
* the highest scorer, so a strong signal (a plugin manifest) beats a weak
|
|
79
|
+
* one (a shared `AGENTS.md`) regardless of registration order. */
|
|
80
|
+
readonly specificity: number;
|
|
81
|
+
/** WHICH kind of marker produced the score. */
|
|
82
|
+
readonly via: "manifest" | "settings" | "instruction-file";
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The fields EVERY adapter has, whatever it can drive. The capability-gated
|
|
86
|
+
* ports are deliberately NOT here — see the unions below.
|
|
87
|
+
*/
|
|
88
|
+
interface AdapterBase {
|
|
63
89
|
/** Stable identifier, e.g. "claude-code". The CLI/registry key. */
|
|
64
90
|
readonly name: string;
|
|
65
|
-
/** What this harness can drive — gates which ports below are required. */
|
|
66
|
-
readonly capabilities: AdapterCapabilities;
|
|
67
91
|
/** Format axis: tool catalog, hook events, instruction targets, plugin-root token. */
|
|
68
92
|
readonly dialect: HarnessDialect;
|
|
69
93
|
/** Layout axis: where the instruction file / skills / agents / hooks live on disk. */
|
|
70
94
|
readonly layout: PluginLayout;
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
readonly runtime?: HarnessRuntime;
|
|
74
|
-
/** Transport axis: how a hook signals a block/deny. Present iff
|
|
75
|
-
* `capabilities.shellHooks`. */
|
|
76
|
-
readonly hookProtocol?: HookProtocol;
|
|
77
|
-
/** Transport axis: the mock model's wire format + endpoints. Present iff
|
|
78
|
-
* `capabilities.harnessTesting`. */
|
|
79
|
-
readonly modelMock?: ModelMock;
|
|
80
|
-
/**
|
|
81
|
-
* Pillar-2 deterministic-runner driver: how `runHarnessTest` builds this
|
|
82
|
-
* harness's argv, starts its scripted mock, and parses its stdout. Present iff
|
|
83
|
-
* `capabilities.harnessTesting` (it composes the runtime + modelMock into the
|
|
84
|
-
* one seam the runner dispatches through). Carried on the bundle so the runner
|
|
85
|
-
* never imports a sibling adapter to find it.
|
|
86
|
-
*/
|
|
95
|
+
/** See {@link AdapterCapabilities.subagents}. */
|
|
96
|
+
readonly subagents: boolean;
|
|
87
97
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* loads a NATIVE binary. Holding the driver eagerly meant every consumer of an
|
|
92
|
-
* adapter paid for all of it — including the hook runtime, which reads only
|
|
93
|
-
* `dialect` and `hookProtocol` and never runs a harness test at all.
|
|
98
|
+
* How strongly, and by WHAT, a repo looks like it targets this harness — the
|
|
99
|
+
* CLI uses it to auto-detect which adapter to use (the library selects by
|
|
100
|
+
* import).
|
|
94
101
|
*
|
|
95
|
-
*
|
|
102
|
+
* 🔴 IT TAKES A PREDICATE, NOT A ROOT, and that is the same rule `claims`
|
|
103
|
+
* follows one docblock down. `detect(root: string)` handed every adapter a
|
|
104
|
+
* directory and `node:fs`: an adapter could enumerate anything under it, so
|
|
105
|
+
* registering an adapter COULD change what vigiles reads in someone's
|
|
106
|
+
* repository — the exact inversion `claims` exists to prevent, sitting
|
|
107
|
+
* unnoticed beside it. With `exists` injected, an adapter can ask about a
|
|
108
|
+
* path and nothing else, and the domain decides what asking means. It also
|
|
109
|
+
* removes `node:fs` from the adapter bundles.
|
|
96
110
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* is
|
|
101
|
-
*
|
|
102
|
-
* ASYNC because a dynamic `import()` is the only form that defers in BOTH
|
|
103
|
-
* environments this code runs in: the CJS `dist/` build (where TypeScript
|
|
104
|
-
* lowers it to a deferred `require`) and vitest loading the TS sources
|
|
105
|
-
* directly, where a synchronous `require` of a sibling `.ts` does not resolve
|
|
106
|
-
* at all — measured, not assumed.
|
|
107
|
-
*/
|
|
108
|
-
readonly harnessTestDriver?: () => Promise<HarnessTestDriver>;
|
|
109
|
-
/**
|
|
110
|
-
* How strongly a repo at `root` looks like it targets this harness — the CLI
|
|
111
|
-
* uses it to auto-detect which adapter to use (the library selects by import).
|
|
112
|
-
* Returns a **specificity score**: 0 = not this harness; higher = a more
|
|
113
|
-
* specific match. The registry picks the highest scorer, so a strong signal
|
|
114
|
-
* (a `.claude-plugin/` manifest) beats a weak one (a bare `CLAUDE.md`, or an
|
|
115
|
-
* `AGENTS.md` that many harnesses share) regardless of registration order.
|
|
111
|
+
* The property a test asserts (`adapter-properties.test.ts`): `detect` may
|
|
112
|
+
* only ask about paths its own `claims` returns true for. An adapter that
|
|
113
|
+
* wanted to detect by a marker it does not read would be refused by that,
|
|
114
|
+
* which is the intended trade — a detector that reads what it does not claim
|
|
115
|
+
* is how a grade starts covering files nobody declared.
|
|
116
116
|
*/
|
|
117
|
-
detect(
|
|
117
|
+
detect(exists: (repoRelative: string) => boolean): DetectSignal;
|
|
118
118
|
/**
|
|
119
119
|
* Is this repo-relative path one THIS harness reads — "is it mine?"
|
|
120
120
|
*
|
|
@@ -138,5 +138,157 @@ export interface HarnessAdapter {
|
|
|
138
138
|
* Override it only for a location the `PluginLayout` fields cannot express.
|
|
139
139
|
*/
|
|
140
140
|
claims(path: string): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Diagnostics about THIS harness's INSTALL on this machine that bear on how
|
|
143
|
+
* far the report can be trusted — never scored, printed as-is, one string per
|
|
144
|
+
* line. `[]` is the honest answer for a harness with nothing to say.
|
|
145
|
+
*
|
|
146
|
+
* 🔴 REQUIRED, NOT A CAPABILITY FLAG. A `localAdvisories: boolean` would be a
|
|
147
|
+
* flag with nothing behind it — the argument {@link AdapterCapabilities.subagents}
|
|
148
|
+
* already makes from the other side — because an empty array says exactly what
|
|
149
|
+
* `false` would say, and cannot fall out of step with the method the way a
|
|
150
|
+
* flag beside a port can.
|
|
151
|
+
*
|
|
152
|
+
* WHAT IT REPLACES: `if (adapter.name === "claude-code") { …two Claude Code
|
|
153
|
+
* install checks… }` in the CLI. Those checks are real and they are genuinely
|
|
154
|
+
* this harness's (one reads the installed vendor package, the other the
|
|
155
|
+
* plugin install), and run against another harness's repo they would print a
|
|
156
|
+
* fix line for a CLI that repo does not use. So the gate was right and its
|
|
157
|
+
* SHAPE was wrong: the consumer now prints uniformly and the knowledge sits
|
|
158
|
+
* with the adapter that has it.
|
|
159
|
+
*
|
|
160
|
+
* 🔴 IT TAKES A READER, NOT A ROOT — the same bound as `detect(exists)` one
|
|
161
|
+
* docblock up, for the same reason: handed a root, an adapter could enumerate
|
|
162
|
+
* anything in the repository, and registering an adapter would change what
|
|
163
|
+
* vigiles reads. {@link InstallReader.repo} answers only for paths this
|
|
164
|
+
* adapter `claims`, which `adapter-properties.test.ts` asserts.
|
|
165
|
+
*/
|
|
166
|
+
advisories(read: InstallReader): readonly string[];
|
|
141
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* What {@link AdapterBase.advisories} may read.
|
|
170
|
+
*
|
|
171
|
+
* Built by the DOMAIN and handed in, so the adapter holds no filesystem. The two
|
|
172
|
+
* plain FACTS are here rather than as reads because the shipped checks need them
|
|
173
|
+
* from files NO adapter claims (`package.json`, and vigiles's own package inside
|
|
174
|
+
* `node_modules`) — an unclaimed read is exactly what the bound forbids, so the
|
|
175
|
+
* domain reads them once and passes the answer.
|
|
176
|
+
*/
|
|
177
|
+
export interface InstallReader {
|
|
178
|
+
/**
|
|
179
|
+
* A repo file's contents, or null when it is missing, unreadable, or NOT a
|
|
180
|
+
* path this adapter claims. A refusal and an absence are deliberately the same
|
|
181
|
+
* answer: an adapter must not be able to probe for the existence of files
|
|
182
|
+
* outside its own surface.
|
|
183
|
+
*/
|
|
184
|
+
readonly repo: (repoRelative: string) => string | null;
|
|
185
|
+
/**
|
|
186
|
+
* A file under the user's HOME, or null. This is a read of the MACHINE, never
|
|
187
|
+
* of the repository — the plugin-install record lives there — and everything
|
|
188
|
+
* it feeds is advisory-only.
|
|
189
|
+
*/
|
|
190
|
+
readonly home: (homeRelative: string) => string | null;
|
|
191
|
+
/** Does this repo take a dependency on vigiles? (From `package.json`, unclaimed.) */
|
|
192
|
+
readonly repoDependsOnVigiles: boolean;
|
|
193
|
+
/** Skill names vigiles ships inside its own installed package, if it is installed. */
|
|
194
|
+
readonly vendoredSkillNames: readonly string[];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Pillar 2's three ports, present IFF `harnessTesting` — as a union, so
|
|
198
|
+
* declaring the capability without the ports is a COMPILE error.
|
|
199
|
+
*
|
|
200
|
+
* 🔴 THE STATE THIS REMOVES WAS SHIPPING. `opencodeAdapter` declared
|
|
201
|
+
* `harnessTesting: true` and carried no `harnessTestDriver`; the runner threw
|
|
202
|
+
* at `harness-test.ts:782` — at RUN time, after a driver was asked for — and
|
|
203
|
+
* the conformance kit did not catch it, because it checked `runtime` and
|
|
204
|
+
* `modelMock` and not the thunk. Adding a third check would have been the
|
|
205
|
+
* third place to remember. The union needs no check at all: the shape is
|
|
206
|
+
* unwritable.
|
|
207
|
+
*
|
|
208
|
+
* The `?: never` arms matter as much as the `true` arm. Without them a
|
|
209
|
+
* `false` adapter could still carry a driver, which is the same defect
|
|
210
|
+
* pointing the other way — a port nothing will ever call, read by a reader as
|
|
211
|
+
* capability that is not there.
|
|
212
|
+
*/
|
|
213
|
+
type TestingPorts = {
|
|
214
|
+
readonly harnessTesting: true;
|
|
215
|
+
/** Transport axis: the agent binary to spawn + the mock-model env. */
|
|
216
|
+
readonly runtime: HarnessRuntime;
|
|
217
|
+
/** Transport axis: the mock model's wire format + endpoints. */
|
|
218
|
+
readonly modelMock: ModelMock;
|
|
219
|
+
/**
|
|
220
|
+
* Pillar-2 deterministic-runner driver: how `runHarnessTest` builds this
|
|
221
|
+
* harness's argv, starts its scripted mock, and parses its stdout.
|
|
222
|
+
*
|
|
223
|
+
* 🔴 A THUNK, NOT THE DRIVER, and the indirection is the whole point. A
|
|
224
|
+
* driver lives in `harness-test.ts`, which imports the conformance suite,
|
|
225
|
+
* which imports the compiler, which imports the cross-language symbol
|
|
226
|
+
* index, which loads a NATIVE binary. Holding the driver eagerly meant
|
|
227
|
+
* every consumer of an adapter paid for all of it — including the hook
|
|
228
|
+
* runtime, which reads only `dialect` and `hookProtocol` and never runs a
|
|
229
|
+
* harness test at all.
|
|
230
|
+
*
|
|
231
|
+
* Measured 2026-09-19, `require("./adapter-registry.js")`:
|
|
232
|
+
*
|
|
233
|
+
* eager: 107 modules, 7 ast-grep, 1 native .node
|
|
234
|
+
*
|
|
235
|
+
* ...on a path whose actual work takes about a millisecond. Calling the
|
|
236
|
+
* thunk is what loads the driver, so the test tier pays and the runtime
|
|
237
|
+
* does not.
|
|
238
|
+
*
|
|
239
|
+
* ASYNC because a dynamic `import()` is the only form that defers in BOTH
|
|
240
|
+
* environments this code runs in: the CJS `dist/` build (where TypeScript
|
|
241
|
+
* lowers it to a deferred `require`) and vitest loading the TS sources
|
|
242
|
+
* directly, where a synchronous `require` of a sibling `.ts` does not
|
|
243
|
+
* resolve at all — measured, not assumed.
|
|
244
|
+
*/
|
|
245
|
+
readonly harnessTestDriver: () => Promise<HarnessTestDriver>;
|
|
246
|
+
/**
|
|
247
|
+
* The EXECUTING tiers' driver: how vigiles drives this harness against a
|
|
248
|
+
* REAL model on the user's own credentials — which eval transport, how
|
|
249
|
+
* firing shows in the trace, whether a model is reachable and on whose
|
|
250
|
+
* bill, and whether the probe may stub the skill bodies. See
|
|
251
|
+
* {@link HarnessLiveDriver}.
|
|
252
|
+
*
|
|
253
|
+
* A THUNK, for the same measured load-cost reason as the line above: the
|
|
254
|
+
* eval transport reaches the whole real-model graph, and an adapter is
|
|
255
|
+
* read by the hook runtime, which never runs a model at all.
|
|
256
|
+
*
|
|
257
|
+
* 🔴 IN THIS ARM RATHER THAN BEHIND A FLAG OF ITS OWN. Every
|
|
258
|
+
* implementation that can go live can also be mocked, and this
|
|
259
|
+
* capability's own docblock already claims both tiers. A `liveEval:
|
|
260
|
+
* boolean` beside it would be `true` exactly when `harnessTesting` is —
|
|
261
|
+
* a second copy of one fact, which is the state this union exists to
|
|
262
|
+
* make unwritable. The `?: never` below is the other half: a `false`
|
|
263
|
+
* adapter cannot carry a live driver nothing will ever call.
|
|
264
|
+
*/
|
|
265
|
+
readonly liveDriver: () => Promise<HarnessLiveDriver>;
|
|
266
|
+
} | {
|
|
267
|
+
readonly harnessTesting: false;
|
|
268
|
+
readonly runtime?: never;
|
|
269
|
+
readonly modelMock?: never;
|
|
270
|
+
readonly harnessTestDriver?: never;
|
|
271
|
+
readonly liveDriver?: never;
|
|
272
|
+
};
|
|
273
|
+
/** The shell-hook port, present IFF `shellHooks`. Same construction, same reason. */
|
|
274
|
+
type ShellHookPorts = {
|
|
275
|
+
readonly shellHooks: true;
|
|
276
|
+
/** Transport axis: how a hook signals a block/deny. */
|
|
277
|
+
readonly hookProtocol: HookProtocol;
|
|
278
|
+
} | {
|
|
279
|
+
readonly shellHooks: false;
|
|
280
|
+
readonly hookProtocol?: never;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* A harness, as one addable unit: the ports it implements plus the flags that
|
|
284
|
+
* say which ones those are.
|
|
285
|
+
*
|
|
286
|
+
* ⚠️ THE COST, NAMED SO NOBODY IS SURPRISED BY IT: a wrong adapter literal
|
|
287
|
+
* produces a TypeScript error against an INTERSECTION OF UNIONS, which reads
|
|
288
|
+
* badly — it will list both arms of each union rather than say "you declared
|
|
289
|
+
* harnessTesting and gave me no driver". That is why the conformance kit keeps
|
|
290
|
+
* its per-flag messages: the TYPE is the gate, the KIT is the explanation.
|
|
291
|
+
*/
|
|
292
|
+
export type HarnessAdapter = AdapterBase & TestingPorts & ShellHookPorts;
|
|
293
|
+
export {};
|
|
142
294
|
//# sourceMappingURL=adapter.d.ts.map
|
package/dist/core/compile.d.ts
CHANGED
|
@@ -160,8 +160,8 @@ export interface CompileSkillResult {
|
|
|
160
160
|
export declare function compileSkill(spec: SkillSpec, options?: {
|
|
161
161
|
basePath?: string;
|
|
162
162
|
specFile?: string;
|
|
163
|
-
/** The harness dialect —
|
|
164
|
-
*
|
|
163
|
+
/** The harness dialect — supplies `skillFrontmatterKeys`. Omitting it emits
|
|
164
|
+
* every key the compiler can render, so existing callers are unchanged. */
|
|
165
165
|
dialect?: HarnessDialect;
|
|
166
166
|
}): CompileSkillResult;
|
|
167
167
|
export interface CompileAgentResult {
|
package/dist/core/compile.js
CHANGED
|
@@ -38,10 +38,16 @@ const skill_normalize_js_1 = require("./skill-normalize.js");
|
|
|
38
38
|
const linters_js_1 = require("./linters.js");
|
|
39
39
|
const tool_contract_js_1 = require("./tool-contract.js");
|
|
40
40
|
const effects_js_1 = require("./effects.js");
|
|
41
|
+
const dialect_js_1 = require("./dialect.js");
|
|
41
42
|
// vigiles's default compile target when a spec names none and no dialect is
|
|
42
43
|
// injected — a product convention (vigiles emits CLAUDE.md by default), not a
|
|
43
44
|
// harness dialect. When a dialect IS injected its instructionTargets win.
|
|
44
|
-
|
|
45
|
+
//
|
|
46
|
+
// DERIVED, not restated: this is the head of the one recognized-filenames list,
|
|
47
|
+
// and `instructionTargets` already contracts that `[0]` is the default target.
|
|
48
|
+
// Writing the literal here made "what vigiles recognizes" and "what vigiles
|
|
49
|
+
// emits" two facts that could disagree while both looking right.
|
|
50
|
+
const DEFAULT_TARGET = dialect_js_1.DEFAULT_INSTRUCTION_TARGETS[0];
|
|
45
51
|
// ---------------------------------------------------------------------------
|
|
46
52
|
// Hash utilities
|
|
47
53
|
// ---------------------------------------------------------------------------
|
|
@@ -781,46 +787,54 @@ function yamlScalar(value) {
|
|
|
781
787
|
// A JSON string IS a YAML double-quoted scalar, escaping included.
|
|
782
788
|
return JSON.stringify(value);
|
|
783
789
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
if (
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
790
|
+
/**
|
|
791
|
+
* Render a SKILL.md frontmatter block, emitting a key iff `keys` holds it.
|
|
792
|
+
*
|
|
793
|
+
* 🔴 ONE CODE PATH, NOT A PROFILE BRANCH. This used to take a
|
|
794
|
+
* `SkillFrontmatterProfile` (`"claude-code" | "minimal"`) and wrap five `push`
|
|
795
|
+
* calls in `if (profile === "claude-code")`. The branch was never about a
|
|
796
|
+
* harness: it was about which keys the reader on the other end parses, which is
|
|
797
|
+
* exactly what a key set says. A harness that reads four of the seven now gets
|
|
798
|
+
* four, instead of falling into whichever of two buckets someone picked for it.
|
|
799
|
+
*/
|
|
800
|
+
function renderSkillFrontmatter(spec, keys = dialect_js_1.RENDERABLE_SKILL_FRONTMATTER_KEYS) {
|
|
801
|
+
const emits = (key) => keys.includes(key);
|
|
802
|
+
const fm = ["---"];
|
|
803
|
+
if (emits("name"))
|
|
804
|
+
fm.push(`name: ${yamlScalar(spec.name)}`);
|
|
805
|
+
if (emits("description")) {
|
|
806
|
+
fm.push(`description: ${yamlScalar(spec.description)}`);
|
|
807
|
+
}
|
|
808
|
+
if (emits("disable-model-invocation") &&
|
|
809
|
+
spec.disableModelInvocation !== undefined) {
|
|
810
|
+
fm.push(`disable-model-invocation: ${String(spec.disableModelInvocation)}`);
|
|
811
|
+
}
|
|
812
|
+
if (emits("context") && spec.context !== undefined) {
|
|
813
|
+
fm.push(`context: ${spec.context}`);
|
|
814
|
+
}
|
|
815
|
+
const argHint = spec.inputs && spec.inputs.length > 0
|
|
816
|
+
? renderArgumentHint(spec.inputs)
|
|
817
|
+
: spec.argumentHint;
|
|
818
|
+
if (emits("argument-hint") && argHint) {
|
|
819
|
+
fm.push(`argument-hint: ${yamlScalar(argHint)}`);
|
|
820
|
+
}
|
|
821
|
+
if (emits("allowed-tools") && spec.tools && spec.tools.length > 0) {
|
|
822
|
+
// A Claude Code SKILL declares its tool contract under `allowed-tools`
|
|
823
|
+
// (NOT `tools:` — that's the SUBAGENT key), as a real YAML sequence. Flow
|
|
824
|
+
// style keeps it one line while parsing as a list, not a single comma
|
|
825
|
+
// scalar. Previously this emitted `tools: a, b` — the wrong key AND an
|
|
826
|
+
// ambiguous scalar, so the restriction was lost on the CC round-trip. (#107)
|
|
827
|
+
fm.push(`allowed-tools: [${spec.tools.join(", ")}]`);
|
|
828
|
+
}
|
|
829
|
+
if (emits("disallowed-tools") &&
|
|
830
|
+
spec.disallowedTools &&
|
|
831
|
+
spec.disallowedTools.length > 0) {
|
|
832
|
+
// 🔴 `disallowed-tools`, HYPHENATED — that is a skill's fence. A subagent's
|
|
833
|
+
// key is `disallowedTools:` (camelCase) and a different reader parses it, so
|
|
834
|
+
// writing the agent spelling here emits a key nothing looks at: inert, and
|
|
835
|
+
// inert in the direction that reads as protection. Same class as the #107
|
|
836
|
+
// defect two lines up, where `tools:` on a skill silently lost the contract.
|
|
837
|
+
fm.push(`disallowed-tools: [${spec.disallowedTools.join(", ")}]`);
|
|
824
838
|
}
|
|
825
839
|
fm.push("---");
|
|
826
840
|
return fm.join("\n");
|
|
@@ -899,10 +913,7 @@ function compileSkill(spec, options = {}) {
|
|
|
899
913
|
spec = (0, skill_normalize_js_1.foldLegacyPostcondition)(spec);
|
|
900
914
|
const basePath = options.basePath ?? process.cwd();
|
|
901
915
|
const specFile = options.specFile ?? "SKILL.md.spec.ts";
|
|
902
|
-
const
|
|
903
|
-
// Downstream of the SkillFrontmatterProfile alias (src/core/dialect.ts).
|
|
904
|
-
// eslint-disable-next-line local/no-harness-names -- goes away with that alias
|
|
905
|
-
options.dialect?.skillFrontmatter ?? "claude-code";
|
|
916
|
+
const frontmatterKeys = options.dialect?.skillFrontmatterKeys ?? dialect_js_1.RENDERABLE_SKILL_FRONTMATTER_KEYS;
|
|
906
917
|
const errors = [];
|
|
907
918
|
// Verify spec file naming
|
|
908
919
|
if (!specFile.endsWith(".spec.ts")) {
|
|
@@ -971,7 +982,7 @@ function compileSkill(spec, options = {}) {
|
|
|
971
982
|
// compilation (so adoption always compiles), just nudge toward file().
|
|
972
983
|
const warnings = checkInlineCode(sections, spec.maxInlineCodeLines ?? DEFAULT_MAX_INLINE_CODE_LINES);
|
|
973
984
|
const marker = purityMarker(spec.purity);
|
|
974
|
-
const content = renderSkillFrontmatter(spec,
|
|
985
|
+
const content = renderSkillFrontmatter(spec, frontmatterKeys) +
|
|
975
986
|
// ONE newline, not two: `placeIntegrityHeader` puts the stamp AFTER the
|
|
976
987
|
// frontmatter and supplies its own blank line on each side, so a second one
|
|
977
988
|
// here becomes two blank lines in the artifact — which `prettier --check`
|
package/dist/core/compose.d.ts
CHANGED
|
@@ -59,9 +59,11 @@ export declare function detectSyncTools(root: string): DetectedSyncTool[];
|
|
|
59
59
|
/**
|
|
60
60
|
* Detect whether `CLAUDE.md` and `AGENTS.md` at `root` are ONE artifact, not two
|
|
61
61
|
* — a symlink in either direction (`ln -s CLAUDE.md AGENTS.md`) or byte-identical
|
|
62
|
-
* content (a sync tool keeping them in lockstep). Claude Code
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* content (a sync tool keeping them in lockstep). The idiom predates Claude Code
|
|
63
|
+
* reading `AGENTS.md` natively ([anthropics/claude-code#34235], reversed in
|
|
64
|
+
* v2.1.277) and outlives it, because a repo with BOTH files loads only the
|
|
65
|
+
* `CLAUDE.md` — so the mirror is still how one text reaches both toolchains (see
|
|
66
|
+
* `research/sync-tool-compatibility.md` requirement 7). When mirrored,
|
|
65
67
|
* vigiles must treat them as the same file — hash + `require-instructions-spec` run once on the
|
|
66
68
|
* real one, and the mirror is never flagged as a second, spec-less instruction
|
|
67
69
|
* file. Returns null when one is absent, or both exist but genuinely differ.
|
package/dist/core/compose.js
CHANGED
|
@@ -72,9 +72,11 @@ function targetName(target) {
|
|
|
72
72
|
/**
|
|
73
73
|
* Detect whether `CLAUDE.md` and `AGENTS.md` at `root` are ONE artifact, not two
|
|
74
74
|
* — a symlink in either direction (`ln -s CLAUDE.md AGENTS.md`) or byte-identical
|
|
75
|
-
* content (a sync tool keeping them in lockstep). Claude Code
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
* content (a sync tool keeping them in lockstep). The idiom predates Claude Code
|
|
76
|
+
* reading `AGENTS.md` natively ([anthropics/claude-code#34235], reversed in
|
|
77
|
+
* v2.1.277) and outlives it, because a repo with BOTH files loads only the
|
|
78
|
+
* `CLAUDE.md` — so the mirror is still how one text reaches both toolchains (see
|
|
79
|
+
* `research/sync-tool-compatibility.md` requirement 7). When mirrored,
|
|
78
80
|
* vigiles must treat them as the same file — hash + `require-instructions-spec` run once on the
|
|
79
81
|
* real one, and the mirror is never flagged as a second, spec-less instruction
|
|
80
82
|
* file. Returns null when one is absent, or both exist but genuinely differ.
|
|
@@ -196,8 +196,20 @@ export declare const REPLACED_KEYS: ReadonlyArray<{
|
|
|
196
196
|
readonly was: string;
|
|
197
197
|
readonly now: string;
|
|
198
198
|
}>;
|
|
199
|
-
/**
|
|
200
|
-
|
|
199
|
+
/**
|
|
200
|
+
* The message a config written in the replaced shape gets.
|
|
201
|
+
*
|
|
202
|
+
* `names` are the harnesses the user's OWN config named under the removed
|
|
203
|
+
* `harness` key, so the worked example below shows THEIR migration.
|
|
204
|
+
*
|
|
205
|
+
* 🔴 IT USED TO BE A FIXED EXAMPLE — `{ "harnesses": { "claude-code": …,
|
|
206
|
+
* "codex": {} } }`, two names typed into the core. Its own comment called that
|
|
207
|
+
* debt and predicted the failure: "with a third adapter this sample goes
|
|
208
|
+
* stale". Reading the names from the config being migrated is better than
|
|
209
|
+
* reading them from the registry would have been, and it is available here:
|
|
210
|
+
* it shows the reader their own keys instead of somebody else's.
|
|
211
|
+
*/
|
|
212
|
+
export declare function replacedKeyMessage(present: ReadonlyArray<(typeof REPLACED_KEYS)[number]>, names: readonly string[]): string;
|
|
201
213
|
/**
|
|
202
214
|
* Turn a Zod failure into the lines a human acts on — ONE per real problem.
|
|
203
215
|
*
|
|
@@ -298,15 +298,28 @@ exports.REPLACED_KEYS = [
|
|
|
298
298
|
now: '"roots" INSIDE the harness that reads them',
|
|
299
299
|
},
|
|
300
300
|
];
|
|
301
|
-
/**
|
|
302
|
-
|
|
301
|
+
/**
|
|
302
|
+
* The message a config written in the replaced shape gets.
|
|
303
|
+
*
|
|
304
|
+
* `names` are the harnesses the user's OWN config named under the removed
|
|
305
|
+
* `harness` key, so the worked example below shows THEIR migration.
|
|
306
|
+
*
|
|
307
|
+
* 🔴 IT USED TO BE A FIXED EXAMPLE — `{ "harnesses": { "claude-code": …,
|
|
308
|
+
* "codex": {} } }`, two names typed into the core. Its own comment called that
|
|
309
|
+
* debt and predicted the failure: "with a third adapter this sample goes
|
|
310
|
+
* stale". Reading the names from the config being migrated is better than
|
|
311
|
+
* reading them from the registry would have been, and it is available here:
|
|
312
|
+
* it shows the reader their own keys instead of somebody else's.
|
|
313
|
+
*/
|
|
314
|
+
function replacedKeyMessage(present, names) {
|
|
315
|
+
// Empty when the config used only `surfaceRoots`, or named no harness — a
|
|
316
|
+
// placeholder the reader will obviously replace, never an invented name.
|
|
317
|
+
const example = (names.length > 0 ? names : ["<harness>"])
|
|
318
|
+
.map((n, i) => `"${n}": ${i === 0 ? '{ "roots": [".ai"] }' : "{}"}`)
|
|
319
|
+
.join(", ");
|
|
303
320
|
return (`.vigilesrc.json: ${present.map((k) => `"${k.key}"`).join(" and ")} ` +
|
|
304
321
|
`${present.length === 1 ? "was" : "were"} replaced by one nested key, "harnesses".\n` +
|
|
305
|
-
|
|
306
|
-
// adapter this sample goes stale, so it wants rendering from the adapter
|
|
307
|
-
// registry rather than two names typed out here.
|
|
308
|
-
// eslint-disable-next-line local/no-harness-names -- example config text
|
|
309
|
-
` Write: { "harnesses": { "claude-code": { "roots": [".ai"] }, "codex": {} } }\n` +
|
|
322
|
+
` Write: { "harnesses": { ${example} } }\n` +
|
|
310
323
|
present.map((k) => ` - ${k.was} → ${k.now}`).join("\n") +
|
|
311
324
|
`\n The old harness ARRAY's order silently decided what got read: one order graded the ` +
|
|
312
325
|
`skills and read no instruction file, the other read the instruction file and found no ` +
|