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
package/dist/core/layout.d.ts
CHANGED
|
@@ -10,50 +10,111 @@
|
|
|
10
10
|
* Paths are repo-relative (POSIX-style, `join`-friendly). The Claude Code
|
|
11
11
|
* implementation is `claudeCodeLayout` in `src/adapters/claude-code/layout.ts`.
|
|
12
12
|
*/
|
|
13
|
+
import type { InstructionChain } from "./instruction-chain.js";
|
|
14
|
+
import type { SettingsCodec } from "./settings-codec.js";
|
|
15
|
+
/**
|
|
16
|
+
* The three kinds of MODEL SURFACE — a thing a session can invoke by name.
|
|
17
|
+
*
|
|
18
|
+
* Instructions are deliberately not here: an instruction is READ, a surface is
|
|
19
|
+
* CALLED. That is why {@link PluginLayout.rulesDir} is not a key — counted, not
|
|
20
|
+
* felt: five of the seven consumers that range over the surfaces would have had
|
|
21
|
+
* to grow a `kind !== "rules"` branch (the empty-machine decision, the per-kind
|
|
22
|
+
* counts, the shape table, the known-homes map, `executableSourceDirs`), which
|
|
23
|
+
* is the per-value branch this record exists to remove.
|
|
24
|
+
*/
|
|
25
|
+
export type SurfaceKind = "skill" | "agent" | "command";
|
|
26
|
+
/**
|
|
27
|
+
* Where each model surface lives, keyed by kind — repo-relative and COMPLETE
|
|
28
|
+
* (`.agents/skills`, never `skills` under a root the reader has to remember).
|
|
29
|
+
*
|
|
30
|
+
* A kind with no entry is a surface this harness does not have, and that is the
|
|
31
|
+
* ONLY spelling of "none": there is no `""`.
|
|
32
|
+
*
|
|
33
|
+
* 🔴 A RECORD, NOT A LIST, AND NOT A SECOND FIELD BESIDE A LIST. What this
|
|
34
|
+
* replaced was `surfaceDirs: readonly string[]` standing beside `skillDir`,
|
|
35
|
+
* `agentDir` and `commandDir` — four places naming the same three directories,
|
|
36
|
+
* with nothing relating them. `opencodeLayout` disagreed with itself in exactly
|
|
37
|
+
* the way that invites: `skillDir: ".opencode/skill"` while `surfaceDirs` held
|
|
38
|
+
* only the agent and command dirs, so OpenCode's skills were named by the port
|
|
39
|
+
* and never read by anything. With one record there is no second place to
|
|
40
|
+
* disagree with, `surfaceDirs()` is derived, and a duplicate kind is a
|
|
41
|
+
* duplicate object key (TS1117) rather than a conformance finding.
|
|
42
|
+
*/
|
|
43
|
+
export type SurfaceDirs = Readonly<Partial<Record<SurfaceKind, string>>>;
|
|
44
|
+
/**
|
|
45
|
+
* Where one harness keeps the files vigiles reads: its instruction file, its
|
|
46
|
+
* model surfaces, its hook registrations and settings, plus the env token
|
|
47
|
+
* expanded to the plugin root. The loader reads all of it from this descriptor
|
|
48
|
+
* rather than hard-coding one harness's conventions, which is what lets a
|
|
49
|
+
* second harness be a VALUE passed to the same loader instead of a fork of it.
|
|
50
|
+
*
|
|
51
|
+
* 🔴 EVERY FIELD HOLDS ITS FACT ONCE. Four of the fields this interface used to
|
|
52
|
+
* have were second copies of a fact another field already held — `surfaceDirs`
|
|
53
|
+
* beside `skillDir`/`agentDir`/`commandDir`, `intraRefDirs` beside both, and
|
|
54
|
+
* `materializeRoot` beside `userSurfaceRoot` — and nothing related the copies,
|
|
55
|
+
* so a layout could disagree with itself and no check would see it. One did:
|
|
56
|
+
* `opencodeLayout` named a skill dir that `surfaceDirs` omitted, and OpenCode's
|
|
57
|
+
* skills were read by nothing. The copies are now `surfaceDirs()`,
|
|
58
|
+
* `executableSourceDirs()` and `materializePrefix()` — functions of what is
|
|
59
|
+
* left, so there is no second place to disagree with.
|
|
60
|
+
*/
|
|
13
61
|
export interface PluginLayout {
|
|
14
62
|
/** Stable identifier, e.g. "claude-code". */
|
|
15
63
|
readonly name: string;
|
|
16
64
|
/** Plugin manifest, e.g. `.claude-plugin/plugin.json`. */
|
|
17
65
|
readonly manifestPath: string;
|
|
18
|
-
/**
|
|
19
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The conventional standalone hooks FILE a plugin may ship instead of inline
|
|
68
|
+
* registrations, e.g. `hooks/hooks.json`. A file, never a directory.
|
|
69
|
+
*
|
|
70
|
+
* Optional because OpenCode has no such file — its `.opencode/plugin` is a
|
|
71
|
+
* DIRECTORY of JS modules, and naming it here made every reader that treats
|
|
72
|
+
* this as a file (dirname, parse, round-trip) wrong about it.
|
|
73
|
+
*/
|
|
74
|
+
readonly hooksConventionPath?: string;
|
|
20
75
|
/** Repo settings carrying hooks, e.g. `.claude/settings.json` or `.codex/config.toml`. */
|
|
21
76
|
readonly settingsPath: string;
|
|
22
77
|
/**
|
|
23
|
-
* How
|
|
24
|
-
*
|
|
25
|
-
*
|
|
78
|
+
* How {@link manifestPath} and {@link settingsPath} are ENCODED — a codec,
|
|
79
|
+
* not a format name. It knows bytes-to-value and nothing about what the value
|
|
80
|
+
* means; the hooks-entry SHAPE lives on `HookProtocol.registration`.
|
|
81
|
+
*
|
|
82
|
+
* 🔴 IT USED TO BE `settingsFormat: "json" | "toml"`, and nine call sites
|
|
83
|
+
* branched on it. The type had exactly two inhabitants and both were ours, so
|
|
84
|
+
* a third-party adapter whose settings are YAML could declare nothing the
|
|
85
|
+
* core would honour — and the conformance kit re-checked the enum, which is
|
|
86
|
+
* the tell: a data field whose legal values the core already knows is not
|
|
87
|
+
* data, it is a hidden `switch`. Six of the nine branches were the encoding
|
|
88
|
+
* and are now `settings.parse` / `settings.render`; three were the entry
|
|
89
|
+
* shape and moved to the port that owns shapes.
|
|
26
90
|
*/
|
|
27
|
-
readonly
|
|
91
|
+
readonly settings: SettingsCodec;
|
|
28
92
|
/** Top-level instruction file, e.g. `CLAUDE.md`. */
|
|
29
93
|
readonly instructionFile: string;
|
|
30
|
-
/** Surface dirs materialized into the sandbox, e.g. skills/agents/commands. */
|
|
31
|
-
readonly surfaceDirs: readonly string[];
|
|
32
94
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* skills-library shape) AND `<root>/<userSurfaceRoot>/<surface>` (the shape a
|
|
37
|
-
* plain Claude Code user has), normalizing to the same materialized key. Most
|
|
38
|
-
* Claude Code users are NOT publishing a plugin — their skills live here, so
|
|
39
|
-
* without this the loader would see an empty machine for a normal repo.
|
|
40
|
-
* Undefined ⇒ only the primary location is read (backwards-compatible).
|
|
95
|
+
* Where each model surface lives — see {@link SurfaceDirs}. At least one kind
|
|
96
|
+
* is required (conformance); a kind this harness does not have is an absent
|
|
97
|
+
* key, never `""`.
|
|
41
98
|
*/
|
|
42
|
-
readonly
|
|
43
|
-
/** Skills dir, holding the nested `<dir>/<name>/SKILL.md`, e.g. `skills`. */
|
|
44
|
-
readonly skillDir: string;
|
|
99
|
+
readonly surfaces: SurfaceDirs;
|
|
45
100
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
101
|
+
* The dot-directory a plain END USER keeps the same surfaces under, when the
|
|
102
|
+
* harness has such a second home (`.claude` → `.claude/skills`). When set,
|
|
103
|
+
* every surface is read from BOTH `<surface>` and `<userSurfaceRoot>/<surface>`,
|
|
104
|
+
* and this is ALSO the prefix a relocated scope is keyed under.
|
|
105
|
+
*
|
|
106
|
+
* 🔴 IT USED TO BE TWO FIELDS. `materializeRoot` sat beside this one and was
|
|
107
|
+
* EQUAL to it in all three shipped layouts (`.claude`/`.claude`, `""`/absent,
|
|
108
|
+
* `""`/absent) while having no defined meaning when they differed — the
|
|
109
|
+
* scope-key guard existed precisely to catch a layout that named `.claude` as
|
|
110
|
+
* both its materialize root and a second scope's base, and with one field that
|
|
111
|
+
* state cannot be written. Absent means the surfaces have exactly one home and
|
|
112
|
+
* file-map keys equal on-disk paths; see {@link materializePrefix}.
|
|
50
113
|
*/
|
|
51
|
-
readonly
|
|
52
|
-
/** Slash-commands dir, holding flat `<dir>/<name>.md`, e.g. `commands`. */
|
|
53
|
-
readonly commandDir: string;
|
|
114
|
+
readonly userSurfaceRoot?: string;
|
|
54
115
|
/**
|
|
55
116
|
* Path-scoped RULES dir, holding flat `<dir>/<name>.md`, e.g. `rules`
|
|
56
|
-
* (
|
|
117
|
+
* (absent = this harness has no such layer; `""` is refused by conformance).
|
|
57
118
|
*
|
|
58
119
|
* Claude Code loads `.claude/rules/*.md` as project instructions, scoped by a
|
|
59
120
|
* `paths:` frontmatter key. It is an INSTRUCTION surface — often where a
|
|
@@ -62,12 +123,67 @@ export interface PluginLayout {
|
|
|
62
123
|
* adopter reported five such files arriving in a session labelled "project
|
|
63
124
|
* instructions" while `lint` did not mention them at all (#175.3).
|
|
64
125
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
126
|
+
* Not a {@link SurfaceKind}: an instruction is read, not invoked — see the
|
|
127
|
+
* docblock there for the count behind that.
|
|
67
128
|
*/
|
|
68
129
|
readonly rulesDir?: string;
|
|
69
|
-
/**
|
|
70
|
-
|
|
130
|
+
/**
|
|
131
|
+
* The WORD a per-machine settings sibling inserts before the extension, e.g.
|
|
132
|
+
* `local` → `.claude/settings.local.json` (absent = this harness has no
|
|
133
|
+
* per-machine settings layer that we have OBSERVED).
|
|
134
|
+
*
|
|
135
|
+
* 🔴 THE CORE USED TO SYNTHESIZE THIS FOR EVERYBODY, and the function it used
|
|
136
|
+
* says on its own docblock why that cannot be right: "Both vendors name a
|
|
137
|
+
* per-machine file by inserting a word before the extension, and they choose
|
|
138
|
+
* DIFFERENT words — Claude Code `local`, Codex `override` — so the word is the
|
|
139
|
+
* argument and the spelling is not." `settingsSourcePaths` passed `"local"`
|
|
140
|
+
* unconditionally three lines below it, so a Codex repository holding
|
|
141
|
+
* `.codex/config.local.toml` had that file read as a real settings layer, and
|
|
142
|
+
* a `project_doc_fallback_filenames` in it could name an instruction file we
|
|
143
|
+
* then scored — out of a file the harness never opens. `opencode.local.json`
|
|
144
|
+
* the same.
|
|
145
|
+
*
|
|
146
|
+
* Absence is the honest default: only the adapter knows whether its vendor
|
|
147
|
+
* has such a layer, and we model what has been observed rather than what is
|
|
148
|
+
* plausible by analogy. Every other {@link siblingNamed} caller already lives
|
|
149
|
+
* in an adapter for exactly this reason.
|
|
150
|
+
*/
|
|
151
|
+
readonly settingsLocalInfix?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Which of the instruction-shaped files the DOMAIN enumerated this harness
|
|
154
|
+
* loads at a repo-root session, in load order — and for every one it does not
|
|
155
|
+
* load, WHY. Pure: a function of `files` alone, with no root, no `node:`
|
|
156
|
+
* module and no way to name a path the domain did not open.
|
|
157
|
+
*
|
|
158
|
+
* 🔴 IT REPLACED A GLOB MINI-LANGUAGE AND ITS PRIVATE INTERPRETER.
|
|
159
|
+
* `HarnessDialect.instructionBudget.alwaysLoaded` was an array of globs an
|
|
160
|
+
* ADAPTER wrote and the CORE walked the repository to expand, so shipping
|
|
161
|
+
* `"**\/AGENTS.md"` made vigiles read every directory of somebody else's
|
|
162
|
+
* project. See `./instruction-chain.ts` for the three defects that had, and
|
|
163
|
+
* for why none of them is expressible as a better glob.
|
|
164
|
+
*
|
|
165
|
+
* {@link instructionFile} still names the PRIMARY root file — the one
|
|
166
|
+
* `compile` writes, `init` scaffolds and `detect` scores. Whether it is in the
|
|
167
|
+
* loaded chain, and what else is, is this method's answer and never that
|
|
168
|
+
* field's.
|
|
169
|
+
*/
|
|
170
|
+
instructionChain(files: Readonly<Record<string, string>>): InstructionChain;
|
|
171
|
+
/**
|
|
172
|
+
* The directory a plugin keeps its EXECUTABLE HOOK SCRIPTS in (`hooks`) —
|
|
173
|
+
* distinct from where the hooks are REGISTERED ({@link hooksConventionPath},
|
|
174
|
+
* {@link settingsPath}). Absent means the harness has no scripts directory to
|
|
175
|
+
* scan (OpenCode's hooks are in-process code modules).
|
|
176
|
+
*
|
|
177
|
+
* 🔴 IT REPLACES TWO AD-HOC DERIVATIONS AND ONE HAND-WRITTEN LIST, which is
|
|
178
|
+
* why it is a field rather than something computed at each site. The list was
|
|
179
|
+
* `intraRefDirs`, written out per layout and therefore free to disagree with
|
|
180
|
+
* the surfaces beside it (on `opencode` it did, dropping the skill dir). The
|
|
181
|
+
* derivations were `hooksConventionPath.split("/")[0]`, copied into `scan.ts`
|
|
182
|
+
* and `scan-files.ts` — which reads `hooks` from `hooks/hooks.json` but
|
|
183
|
+
* `.codex` from `.codex/hooks.json`, i.e. it did not name a scripts directory
|
|
184
|
+
* at all for Codex. Both are now {@link executableSourceDirs}.
|
|
185
|
+
*/
|
|
186
|
+
readonly hookScriptsDir?: string;
|
|
71
187
|
/** Env token expanded to the plugin's absolute root in hook commands. */
|
|
72
188
|
readonly pluginRootToken: string;
|
|
73
189
|
/**
|
|
@@ -87,9 +203,37 @@ export interface PluginLayout {
|
|
|
87
203
|
readonly mcpConfigFile: string;
|
|
88
204
|
/** Manifest key declaring MCP servers, e.g. `mcpServers`. */
|
|
89
205
|
readonly mcpManifestKey: string;
|
|
90
|
-
/** Dirs scanned for dangling intra-plugin file references. */
|
|
91
|
-
readonly intraRefDirs: readonly string[];
|
|
92
206
|
}
|
|
207
|
+
/** The kinds, in the order every derived list emits them. Iterating a record's
|
|
208
|
+
* own keys would make the output depend on literal order in each layout; this
|
|
209
|
+
* makes it depend on nothing. */
|
|
210
|
+
export declare const SURFACE_KINDS: readonly ["skill", "agent", "command"];
|
|
211
|
+
/**
|
|
212
|
+
* Every surface dir a layout declares — what the `surfaceDirs` FIELD used to
|
|
213
|
+
* be, minus the possibility of disagreeing with the per-kind fields, because
|
|
214
|
+
* there are no per-kind fields left to disagree with.
|
|
215
|
+
*/
|
|
216
|
+
export declare function surfaceDirs(layout: PluginLayout): readonly string[];
|
|
217
|
+
/**
|
|
218
|
+
* Dirs whose non-prose files are scanned for intra-plugin references, and
|
|
219
|
+
* checked for misplacement inside the manifest dir: the surfaces plus
|
|
220
|
+
* {@link PluginLayout.hookScriptsDir}.
|
|
221
|
+
*
|
|
222
|
+
* Equal to the old hand-written `intraRefDirs` as a SET on Claude Code and
|
|
223
|
+
* Codex; on `opencode` it gains `.opencode/skill`, which the hand list had left
|
|
224
|
+
* out along with the rest of that layout's skill surface.
|
|
225
|
+
*/
|
|
226
|
+
export declare function executableSourceDirs(layout: PluginLayout): readonly string[];
|
|
227
|
+
/**
|
|
228
|
+
* The prefix a relocated surface is keyed under — `userSurfaceRoot` when the
|
|
229
|
+
* harness has a second home for its surfaces, `""` when the surfaces carry
|
|
230
|
+
* their own prefix and a file-map key equals the on-disk path.
|
|
231
|
+
*
|
|
232
|
+
* One line, named, because it used to be a FIELD (`materializeRoot`) and the
|
|
233
|
+
* only thing that kept it equal to `userSurfaceRoot` was that nobody had
|
|
234
|
+
* written a layout where they differed.
|
|
235
|
+
*/
|
|
236
|
+
export declare function materializePrefix(layout: PluginLayout): string;
|
|
93
237
|
/**
|
|
94
238
|
* How DEEP a harness reads its {@link PluginLayout.agentDir} — the one statement
|
|
95
239
|
* of that rule, as a RegExp source fragment matching the part of a path AFTER
|
|
@@ -120,6 +264,49 @@ export interface PluginLayout {
|
|
|
120
264
|
* this. A fourth reader that hard-codes a depth is the defect coming back.
|
|
121
265
|
*/
|
|
122
266
|
export declare const AGENT_FILE_LEAF_RE = "(?:.+/)?[^/]+\\.md";
|
|
267
|
+
/**
|
|
268
|
+
* How DEEP a harness reads its {@link PluginLayout.rulesDir} — the one statement
|
|
269
|
+
* of that rule, as a RegExp source fragment matching the part of a path AFTER
|
|
270
|
+
* `<rulesDir>/`. Same shape and same reason as {@link AGENT_FILE_LEAF_RE}.
|
|
271
|
+
*
|
|
272
|
+
* 🔴 IT USED TO SAY `[^/]+`, in the scan classifier, while the loader walked the
|
|
273
|
+
* directory recursively — so a rule in a subdirectory was READ and never
|
|
274
|
+
* CLASSIFIED, which means it was never frontmatter-checked, never counted and
|
|
275
|
+
* never weighed. Verbatim from the vendor page (`code.claude.com/docs/en/memory`,
|
|
276
|
+
* quoted in zernie/vigiles#262): rules directories are read recursively, "all
|
|
277
|
+
* `.md` files are discovered recursively".
|
|
278
|
+
*
|
|
279
|
+
* The two readers are the scan classifier (`makeClassifier`, scan-core.ts) and
|
|
280
|
+
* the instruction chain (`core/instruction-chain.ts` implementations). They
|
|
281
|
+
* quote this instead of each spelling the rule, for the reason the agent
|
|
282
|
+
* constant above records: two readers that each spell it disagree silently.
|
|
283
|
+
*/
|
|
284
|
+
export declare const RULE_FILE_LEAF_RE = "(?:.+/)?[^/]+\\.md";
|
|
285
|
+
/**
|
|
286
|
+
* The WHOLE rule-file matcher for a layout — the prefix as well as the leaf.
|
|
287
|
+
*
|
|
288
|
+
* 🔴 THE LEAF WAS SHARED AND THE PREFIX WAS NOT, so the two readers the constant
|
|
289
|
+
* above names went on disagreeing about the half it did not cover. Both spelled
|
|
290
|
+
* it `(?:^|/)<rulesDir>/`, which is deliberately loose for SURFACES — Claude
|
|
291
|
+
* Code reads `skills/` at a published plugin's root AND at `.claude/skills`, two
|
|
292
|
+
* legitimate homes — and wrong for RULES, which have exactly one home. Measured
|
|
293
|
+
* on the instruction chain at 2026-09-22:
|
|
294
|
+
*
|
|
295
|
+
* loaded: CLAUDE.md · .claude/rules/mine.md · .github/rules/policy.md
|
|
296
|
+
* · .agents/rules/policy.md
|
|
297
|
+
*
|
|
298
|
+
* The bound walks every depth-1 dot-directory's `rules` tree, so ANY of them
|
|
299
|
+
* matched — 8,000 characters of somebody else's policy charged to the
|
|
300
|
+
* always-loaded budget and able to push the report over it. Not one foreign
|
|
301
|
+
* directory, as the review that found it supposed: all of them.
|
|
302
|
+
*
|
|
303
|
+
* Anchoring is what {@link PluginLayout.rulesDir} already says in prose —
|
|
304
|
+
* "Claude Code loads `.claude/rules/*.md`" — and the prefix is built from
|
|
305
|
+
* `userSurfaceRoot` so that moving either field moves both readers at once.
|
|
306
|
+
* `null` when the layout declares no rules layer.
|
|
307
|
+
*/
|
|
308
|
+
export declare function rulesHome(layout: Pick<PluginLayout, "rulesDir" | "userSurfaceRoot">): string | null;
|
|
309
|
+
export declare function ruleFileRe(layout: Pick<PluginLayout, "rulesDir" | "userSurfaceRoot">): RegExp | null;
|
|
123
310
|
/**
|
|
124
311
|
* A subagent's identity, per the same docs paragraph: the path under
|
|
125
312
|
* `<agentDir>/` with `/` → `:` and the `.md` dropped, so plugin
|
package/dist/core/layout.js
CHANGED
|
@@ -1,7 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AGENT_FILE_LEAF_RE = void 0;
|
|
3
|
+
exports.RULE_FILE_LEAF_RE = exports.AGENT_FILE_LEAF_RE = exports.SURFACE_KINDS = void 0;
|
|
4
|
+
exports.surfaceDirs = surfaceDirs;
|
|
5
|
+
exports.executableSourceDirs = executableSourceDirs;
|
|
6
|
+
exports.materializePrefix = materializePrefix;
|
|
7
|
+
exports.rulesHome = rulesHome;
|
|
8
|
+
exports.ruleFileRe = ruleFileRe;
|
|
4
9
|
exports.agentSurfaceName = agentSurfaceName;
|
|
10
|
+
/** The kinds, in the order every derived list emits them. Iterating a record's
|
|
11
|
+
* own keys would make the output depend on literal order in each layout; this
|
|
12
|
+
* makes it depend on nothing. */
|
|
13
|
+
exports.SURFACE_KINDS = ["skill", "agent", "command"];
|
|
14
|
+
/**
|
|
15
|
+
* Every surface dir a layout declares — what the `surfaceDirs` FIELD used to
|
|
16
|
+
* be, minus the possibility of disagreeing with the per-kind fields, because
|
|
17
|
+
* there are no per-kind fields left to disagree with.
|
|
18
|
+
*/
|
|
19
|
+
function surfaceDirs(layout) {
|
|
20
|
+
return exports.SURFACE_KINDS.map((k) => layout.surfaces[k]).filter((d) => d !== undefined);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Dirs whose non-prose files are scanned for intra-plugin references, and
|
|
24
|
+
* checked for misplacement inside the manifest dir: the surfaces plus
|
|
25
|
+
* {@link PluginLayout.hookScriptsDir}.
|
|
26
|
+
*
|
|
27
|
+
* Equal to the old hand-written `intraRefDirs` as a SET on Claude Code and
|
|
28
|
+
* Codex; on `opencode` it gains `.opencode/skill`, which the hand list had left
|
|
29
|
+
* out along with the rest of that layout's skill surface.
|
|
30
|
+
*/
|
|
31
|
+
function executableSourceDirs(layout) {
|
|
32
|
+
const dirs = surfaceDirs(layout);
|
|
33
|
+
return layout.hookScriptsDir === undefined
|
|
34
|
+
? dirs
|
|
35
|
+
: [...dirs, layout.hookScriptsDir];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The prefix a relocated surface is keyed under — `userSurfaceRoot` when the
|
|
39
|
+
* harness has a second home for its surfaces, `""` when the surfaces carry
|
|
40
|
+
* their own prefix and a file-map key equals the on-disk path.
|
|
41
|
+
*
|
|
42
|
+
* One line, named, because it used to be a FIELD (`materializeRoot`) and the
|
|
43
|
+
* only thing that kept it equal to `userSurfaceRoot` was that nobody had
|
|
44
|
+
* written a layout where they differed.
|
|
45
|
+
*/
|
|
46
|
+
function materializePrefix(layout) {
|
|
47
|
+
return layout.userSurfaceRoot ?? "";
|
|
48
|
+
}
|
|
5
49
|
/**
|
|
6
50
|
* How DEEP a harness reads its {@link PluginLayout.agentDir} — the one statement
|
|
7
51
|
* of that rule, as a RegExp source fragment matching the part of a path AFTER
|
|
@@ -32,6 +76,76 @@ exports.agentSurfaceName = agentSurfaceName;
|
|
|
32
76
|
* this. A fourth reader that hard-codes a depth is the defect coming back.
|
|
33
77
|
*/
|
|
34
78
|
exports.AGENT_FILE_LEAF_RE = "(?:.+/)?[^/]+\\.md";
|
|
79
|
+
/**
|
|
80
|
+
* How DEEP a harness reads its {@link PluginLayout.rulesDir} — the one statement
|
|
81
|
+
* of that rule, as a RegExp source fragment matching the part of a path AFTER
|
|
82
|
+
* `<rulesDir>/`. Same shape and same reason as {@link AGENT_FILE_LEAF_RE}.
|
|
83
|
+
*
|
|
84
|
+
* 🔴 IT USED TO SAY `[^/]+`, in the scan classifier, while the loader walked the
|
|
85
|
+
* directory recursively — so a rule in a subdirectory was READ and never
|
|
86
|
+
* CLASSIFIED, which means it was never frontmatter-checked, never counted and
|
|
87
|
+
* never weighed. Verbatim from the vendor page (`code.claude.com/docs/en/memory`,
|
|
88
|
+
* quoted in zernie/vigiles#262): rules directories are read recursively, "all
|
|
89
|
+
* `.md` files are discovered recursively".
|
|
90
|
+
*
|
|
91
|
+
* The two readers are the scan classifier (`makeClassifier`, scan-core.ts) and
|
|
92
|
+
* the instruction chain (`core/instruction-chain.ts` implementations). They
|
|
93
|
+
* quote this instead of each spelling the rule, for the reason the agent
|
|
94
|
+
* constant above records: two readers that each spell it disagree silently.
|
|
95
|
+
*/
|
|
96
|
+
exports.RULE_FILE_LEAF_RE = "(?:.+/)?[^/]+\\.md";
|
|
97
|
+
/**
|
|
98
|
+
* The WHOLE rule-file matcher for a layout — the prefix as well as the leaf.
|
|
99
|
+
*
|
|
100
|
+
* 🔴 THE LEAF WAS SHARED AND THE PREFIX WAS NOT, so the two readers the constant
|
|
101
|
+
* above names went on disagreeing about the half it did not cover. Both spelled
|
|
102
|
+
* it `(?:^|/)<rulesDir>/`, which is deliberately loose for SURFACES — Claude
|
|
103
|
+
* Code reads `skills/` at a published plugin's root AND at `.claude/skills`, two
|
|
104
|
+
* legitimate homes — and wrong for RULES, which have exactly one home. Measured
|
|
105
|
+
* on the instruction chain at 2026-09-22:
|
|
106
|
+
*
|
|
107
|
+
* loaded: CLAUDE.md · .claude/rules/mine.md · .github/rules/policy.md
|
|
108
|
+
* · .agents/rules/policy.md
|
|
109
|
+
*
|
|
110
|
+
* The bound walks every depth-1 dot-directory's `rules` tree, so ANY of them
|
|
111
|
+
* matched — 8,000 characters of somebody else's policy charged to the
|
|
112
|
+
* always-loaded budget and able to push the report over it. Not one foreign
|
|
113
|
+
* directory, as the review that found it supposed: all of them.
|
|
114
|
+
*
|
|
115
|
+
* Anchoring is what {@link PluginLayout.rulesDir} already says in prose —
|
|
116
|
+
* "Claude Code loads `.claude/rules/*.md`" — and the prefix is built from
|
|
117
|
+
* `userSurfaceRoot` so that moving either field moves both readers at once.
|
|
118
|
+
* `null` when the layout declares no rules layer.
|
|
119
|
+
*/
|
|
120
|
+
function rulesHome(layout) {
|
|
121
|
+
const dir = layout.rulesDir;
|
|
122
|
+
if (dir === undefined || dir === "")
|
|
123
|
+
return null;
|
|
124
|
+
const root = layout.userSurfaceRoot;
|
|
125
|
+
return root === undefined || root === "" ? dir : `${root}/${dir}`;
|
|
126
|
+
}
|
|
127
|
+
function ruleFileRe(layout) {
|
|
128
|
+
// 🔴 THREE READERS, ONE SPELLING. The regexp here, the disk walk in
|
|
129
|
+
// `surface-discovery-fs.ts` and the bound in `core/instruction-chain.ts` all
|
|
130
|
+
// need "where does this layout keep its rules"; each one spelling it
|
|
131
|
+
// separately is how #271 happened — the walk joined `<base>/<rulesDir>` for
|
|
132
|
+
// EVERY dot-directory while this anchored to one, so the pair disagreed about
|
|
133
|
+
// a repository they were both handed. `rulesHome` is that answer, once.
|
|
134
|
+
const under = rulesHome(layout);
|
|
135
|
+
return under === null
|
|
136
|
+
? null
|
|
137
|
+
: new RegExp(`^${escapeRe(under)}/${exports.RULE_FILE_LEAF_RE}$`);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* ⚠️ The fourth copy of this three-liner in `src/` (`core/linters.ts`,
|
|
141
|
+
* `rule-inventory.ts`, `tool-intercept.ts`, `test-coverage-files.ts` hold the
|
|
142
|
+
* others). Consolidating them is its own change with its own callers to walk;
|
|
143
|
+
* duplicating it here is the smaller of two wrongs while the subject is a
|
|
144
|
+
* measured over-report.
|
|
145
|
+
*/
|
|
146
|
+
function escapeRe(s) {
|
|
147
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
148
|
+
}
|
|
35
149
|
/**
|
|
36
150
|
* A subagent's identity, per the same docs paragraph: the path under
|
|
37
151
|
* `<agentDir>/` with `/` → `:` and the `.md` dropped, so plugin
|
|
@@ -181,12 +181,17 @@ export declare function skillFenceLegs(disallowed: readonly string[], dialect: H
|
|
|
181
181
|
/**
|
|
182
182
|
* Whether THIS harness understands a skill-level `disallowed-tools:` at all.
|
|
183
183
|
*
|
|
184
|
-
* Read off the dialect record that already exists — `
|
|
185
|
-
* "which SKILL.md keys this harness
|
|
186
|
-
* the
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
184
|
+
* Read off the dialect record that already exists — `skillFrontmatterKeys` is
|
|
185
|
+
* exactly "which SKILL.md keys this harness reads", so the question "is there a
|
|
186
|
+
* fence here?" is the membership of the one key that IS the fence. No new
|
|
187
|
+
* capability flag: a second field saying the same thing is a second thing to
|
|
188
|
+
* keep true, and the compiler already emits from this one.
|
|
189
|
+
*
|
|
190
|
+
* 🔴 IT USED TO ASK THE WRONG QUESTION. The body was
|
|
191
|
+
* `dialect.skillFrontmatter === "claude-code"` — a harness name standing in for
|
|
192
|
+
* a key, which made a harness that read `disallowed-tools:` under any other name
|
|
193
|
+
* unfenceable by construction. The function already HAD its capability name;
|
|
194
|
+
* only the fact it read was wrong.
|
|
190
195
|
*/
|
|
191
196
|
export declare function dialectSupportsSkillFence(dialect: HarnessDialect): boolean;
|
|
192
197
|
/**
|
|
@@ -197,7 +202,7 @@ export declare function dialectSupportsSkillFence(dialect: HarnessDialect): bool
|
|
|
197
202
|
* the block comment above for the measurement).
|
|
198
203
|
*
|
|
199
204
|
* 🔴 AND IT IS A CLAUDE-CODE MECHANISM, which this applied to every harness. On a
|
|
200
|
-
* Codex repo (`
|
|
205
|
+
* Codex repo (`skillFrontmatterKeys: ["name", "description"]` — and our own
|
|
201
206
|
* compiler drops the tool keys there) every skill was reported as holding all
|
|
202
207
|
* three legs, scored against Safety, and handed the remedy "add a
|
|
203
208
|
* `disallowed-tools:` line". That line is INERT in Codex: the author does the
|
|
@@ -537,15 +537,20 @@ const PREAPPROVAL_NOTE = "`allowed-tools:` does NOT fence a skill — it PRE-APP
|
|
|
537
537
|
/**
|
|
538
538
|
* Whether THIS harness understands a skill-level `disallowed-tools:` at all.
|
|
539
539
|
*
|
|
540
|
-
* Read off the dialect record that already exists — `
|
|
541
|
-
* "which SKILL.md keys this harness
|
|
542
|
-
* the
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
540
|
+
* Read off the dialect record that already exists — `skillFrontmatterKeys` is
|
|
541
|
+
* exactly "which SKILL.md keys this harness reads", so the question "is there a
|
|
542
|
+
* fence here?" is the membership of the one key that IS the fence. No new
|
|
543
|
+
* capability flag: a second field saying the same thing is a second thing to
|
|
544
|
+
* keep true, and the compiler already emits from this one.
|
|
545
|
+
*
|
|
546
|
+
* 🔴 IT USED TO ASK THE WRONG QUESTION. The body was
|
|
547
|
+
* `dialect.skillFrontmatter === "claude-code"` — a harness name standing in for
|
|
548
|
+
* a key, which made a harness that read `disallowed-tools:` under any other name
|
|
549
|
+
* unfenceable by construction. The function already HAD its capability name;
|
|
550
|
+
* only the fact it read was wrong.
|
|
546
551
|
*/
|
|
547
552
|
function dialectSupportsSkillFence(dialect) {
|
|
548
|
-
return dialect.
|
|
553
|
+
return dialect.skillFrontmatterKeys.includes("disallowed-tools");
|
|
549
554
|
}
|
|
550
555
|
/**
|
|
551
556
|
* The lethal-trifecta finding for a MODEL-INVOCABLE SKILL, computed from its
|
|
@@ -555,7 +560,7 @@ function dialectSupportsSkillFence(dialect) {
|
|
|
555
560
|
* the block comment above for the measurement).
|
|
556
561
|
*
|
|
557
562
|
* 🔴 AND IT IS A CLAUDE-CODE MECHANISM, which this applied to every harness. On a
|
|
558
|
-
* Codex repo (`
|
|
563
|
+
* Codex repo (`skillFrontmatterKeys: ["name", "description"]` — and our own
|
|
559
564
|
* compiler drops the tool keys there) every skill was reported as holding all
|
|
560
565
|
* three legs, scored against Safety, and handed the remedy "add a
|
|
561
566
|
* `disallowed-tools:` line". That line is INERT in Codex: the author does the
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarnessLiveDriver — how vigiles drives THIS harness against a REAL model on
|
|
3
|
+
* the user's own credentials, as `HarnessTestDriver` (`./harness-driver.js`) is
|
|
4
|
+
* the same seam for the MOCK tiers.
|
|
5
|
+
*
|
|
6
|
+
* 🔴 IT IS WHAT `scan-behavioral.ts:buildProbe` BUILT BY SWITCHING ON A NAME.
|
|
7
|
+
* That function answered four questions — which eval driver, how firing shows
|
|
8
|
+
* in the trace, may the probe stub the bodies, is the runner reachable — and
|
|
9
|
+
* answered all four from `harness === "codex"`. The switch is gone because each
|
|
10
|
+
* adapter now brings the object; `ProbeHarness`, the hand-written
|
|
11
|
+
* `"claude-code" | "codex"` union it switched on, is gone with it.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ AND IT IS DELIBERATELY NOT FOUR BOOLEANS ON THE ADAPTER. A flag is a
|
|
14
|
+
* legitimate capability only when it cannot disagree with the ports the adapter
|
|
15
|
+
* already carries; a `skillFiring: boolean` beside an eval driver can, and a
|
|
16
|
+
* `tsc` probe shows the type cannot relate them (an adapter carrying the Codex
|
|
17
|
+
* eval driver and `skillFiring: true` compiles clean). That is the `subagents`
|
|
18
|
+
* argument at `./adapter.ts` pointing the other way: there is a port here, so
|
|
19
|
+
* the port is the thing, and the boolean would be a second copy of a fact the
|
|
20
|
+
* port already holds. See `docs/design/port-redesign-names-half-2026-09-22.md`.
|
|
21
|
+
*/
|
|
22
|
+
import type { EvalDriver, Trace } from "./eval-driver.js";
|
|
23
|
+
/**
|
|
24
|
+
* Whether a REAL model is reachable for the executing tiers, and on whose bill.
|
|
25
|
+
*
|
|
26
|
+
* A tagged union rather than a boolean because the CLI acts on all three arms
|
|
27
|
+
* differently: it SKIPS on `none` (printing `fix`), RUNS on the other two, and
|
|
28
|
+
* words the consent prompt "spends API credits" only on `metered`. A boolean
|
|
29
|
+
* would have forced the wording question back onto a second predicate, which is
|
|
30
|
+
* exactly the pair it replaces.
|
|
31
|
+
*
|
|
32
|
+
* WHAT THIS REPLACES: a per-harness name check OR-ed with one harness's env-var
|
|
33
|
+
* knowledge, plus the two literal "authenticate … or set …" strings that printed
|
|
34
|
+
* for every harness regardless of which one was driving.
|
|
35
|
+
*/
|
|
36
|
+
export type ModelAccess = {
|
|
37
|
+
readonly kind: "none";
|
|
38
|
+
/** One line: what is missing, in THIS harness's words — the sentence the
|
|
39
|
+
* CLI prints instead of running. It names this harness's binary and
|
|
40
|
+
* credential, so a repo on one harness is never told to authenticate
|
|
41
|
+
* another one's CLI. */
|
|
42
|
+
readonly fix: string;
|
|
43
|
+
}
|
|
44
|
+
/** Reachable on a plan the user already pays for: $0 metered for this run. */
|
|
45
|
+
| {
|
|
46
|
+
readonly kind: "subscription";
|
|
47
|
+
}
|
|
48
|
+
/** Reachable on a per-token key: this run bills. The consent prompt says so. */
|
|
49
|
+
| {
|
|
50
|
+
readonly kind: "metered";
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* How a skill's FIRING shows up in this harness's eval trace.
|
|
54
|
+
*
|
|
55
|
+
* `event` — a discrete skill-selection record in the trace, which the
|
|
56
|
+
* selection-collision matrix and the adversarial gate REQUIRE: they ask "WHICH
|
|
57
|
+
* skill fired", and an inference cannot answer that.
|
|
58
|
+
*
|
|
59
|
+
* `inferred` — no such record, so firing is deduced from something else the
|
|
60
|
+
* model did (reading the skill's instruction file, say), which can be wrong in
|
|
61
|
+
* BOTH directions. `caveat` is the sentence a report prints above an inferred
|
|
62
|
+
* number, so a possibly-wrong figure never reads as a measurement.
|
|
63
|
+
*
|
|
64
|
+
* The same fact as `EvalDriver.experimental` (a public field, kept for
|
|
65
|
+
* compatibility); `adapter-contract.test.ts` asserts the two agree.
|
|
66
|
+
*/
|
|
67
|
+
export type SkillFiringSignal = {
|
|
68
|
+
readonly kind: "event";
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: "inferred";
|
|
71
|
+
readonly caveat: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* The executing tiers' driver for one harness.
|
|
75
|
+
*
|
|
76
|
+
* 🔴 IT LIVES IN THE `harnessTesting: true` ARM, NOT BEHIND A FLAG OF ITS OWN.
|
|
77
|
+
* On every implementation in this repo live-eval ⇔ mock-testable, and the
|
|
78
|
+
* capability's own docblock already claims both ("deterministic harness tests +
|
|
79
|
+
* evals"). A second flag equal to the first everywhere is the defect this port
|
|
80
|
+
* redesign keeps removing — a fact stored twice with nothing relating the
|
|
81
|
+
* copies. If a harness ever goes live WITHOUT going mockable (a closed CLI with
|
|
82
|
+
* its own fixed model), split the arm then; today that shape has zero
|
|
83
|
+
* implementations and splitting it now would freeze a guess into the type.
|
|
84
|
+
*/
|
|
85
|
+
export interface HarnessLiveDriver {
|
|
86
|
+
/** The runner + parser that spawn the real binary and read its stream. */
|
|
87
|
+
readonly evalDriver: EvalDriver;
|
|
88
|
+
/**
|
|
89
|
+
* See {@link ModelAccess}. Read from `env` where the harness allows it
|
|
90
|
+
* (never a spent token); a binary probe where it does not. Takes the
|
|
91
|
+
* environment rather than reading `process.env` so the decision is testable
|
|
92
|
+
* and the adapter holds no ambient state.
|
|
93
|
+
*/
|
|
94
|
+
access(env: Readonly<Record<string, string | undefined>>): ModelAccess;
|
|
95
|
+
/** See {@link SkillFiringSignal}. */
|
|
96
|
+
readonly firing: SkillFiringSignal;
|
|
97
|
+
/**
|
|
98
|
+
* The predicate "did `skill` fire" over one trace.
|
|
99
|
+
*
|
|
100
|
+
* `plugin.name` is the manifest name the DOMAIN read (some harnesses namespace
|
|
101
|
+
* a plugin's skill as `<plugin>:<skill>`, others use the bare name); it is
|
|
102
|
+
* HANDED IN, so this method reads no disk — the same bound `claims` and
|
|
103
|
+
* `detect` keep, for the same reason.
|
|
104
|
+
*/
|
|
105
|
+
firedFor(skill: string, plugin: {
|
|
106
|
+
readonly name: string | null;
|
|
107
|
+
}): (t: Trace) => boolean;
|
|
108
|
+
/**
|
|
109
|
+
* May the probe rebuild the plugin to skills-only STUBS before measuring?
|
|
110
|
+
*
|
|
111
|
+
* `false` means the real bodies are installed. That is a MEASURED LIMITATION,
|
|
112
|
+
* not a capability: stubbing a plugin whose shape the harness did not define
|
|
113
|
+
* is unvalidated, so the honest answer is to install the real skills and
|
|
114
|
+
* detect firing regardless of body. Named here rather than keyed by harness
|
|
115
|
+
* name in the probe, which is where it used to live.
|
|
116
|
+
*/
|
|
117
|
+
readonly installsStubs: boolean;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A live driver whose firing signal is a discrete EVENT — the only accepted
|
|
121
|
+
* argument for the selection-collision matrix and the adversarial gate, which
|
|
122
|
+
* cannot be computed from an inference.
|
|
123
|
+
*/
|
|
124
|
+
export type EventFiringDriver = HarnessLiveDriver & {
|
|
125
|
+
readonly firing: {
|
|
126
|
+
readonly kind: "event";
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Narrows the DRIVER, which an inline `d.firing.kind === "event"` does not:
|
|
131
|
+
* TypeScript narrows the discriminated PROPERTY, so the driver itself stays
|
|
132
|
+
* `HarnessLiveDriver` and passing it where an {@link EventFiringDriver} is
|
|
133
|
+
* wanted is still an error (measured — probe P2 in the design doc). The
|
|
134
|
+
* negative arm keeps its `caveat`, which is what the n/a note prints.
|
|
135
|
+
*/
|
|
136
|
+
export declare function isEventFiring(d: HarnessLiveDriver): d is EventFiringDriver;
|
|
137
|
+
//# sourceMappingURL=live-driver.d.ts.map
|