vigiles 29.1.0 → 30.0.1
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/agent-runtime.js +3 -1
- package/dist/adapters/claude-code/dialect.js +87 -21
- package/dist/adapters/claude-code/effect-region.js +3 -1
- 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/claude-code/skill-runtime.js +3 -1
- 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 +185 -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/guards.js +3 -1
- 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/coverage-artifact.d.ts +3 -2
- package/dist/coverage-artifact.js +6 -5
- package/dist/eval-cache.d.ts +6 -1
- package/dist/eval-cache.js +11 -1
- package/dist/eval.d.ts +16 -108
- package/dist/eval.js +36 -2
- 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/hook-runtime.js +4 -2
- package/dist/hook-state-store.js +3 -1
- package/dist/local-files-tracked.d.ts +17 -0
- package/dist/local-files-tracked.js +70 -0
- package/dist/local-files.d.ts +62 -0
- package/dist/local-files.js +183 -0
- package/dist/observe.d.ts +3 -2
- package/dist/observe.js +7 -6
- 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
|
@@ -82,19 +82,25 @@ function matchSurface(files, prefixes, leafRe) {
|
|
|
82
82
|
}
|
|
83
83
|
function discoverSkills(files, layout, repoName) {
|
|
84
84
|
const out = [];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
// 🔴 THE GATE MOVES TO THE TOP so it covers the root-`SKILL.md` case below too
|
|
86
|
+
// — the twin of this function on disk had the same hole. A layout declaring no
|
|
87
|
+
// skill surface reported a root `SKILL.md` as an untested skill, with a token
|
|
88
|
+
// reading `undefined/<name>`, lowering Tested for a repo whose harness never
|
|
89
|
+
// loads skills at all. Shape matches `discoverAgents` right below.
|
|
90
|
+
const skillDir = layout.surfaces.skill;
|
|
91
|
+
if (skillDir === undefined)
|
|
92
|
+
return out;
|
|
93
|
+
const prefixes = surfacePrefixes(skillDir, (0, layout_js_1.materializePrefix)(layout));
|
|
94
|
+
for (const path of matchSurface(files, prefixes, "[^/]+/SKILL\\.md")) {
|
|
95
|
+
const name = (0, posix_path_js_1.basename)((0, posix_path_js_1.dirname)(path));
|
|
96
|
+
const content = files[path];
|
|
97
|
+
out.push({
|
|
98
|
+
kind: "skill",
|
|
99
|
+
path,
|
|
100
|
+
name,
|
|
101
|
+
tokens: [`${skillDir}/${name}`, `:${name}`],
|
|
102
|
+
ignored: content.includes(IGNORE_MARKER),
|
|
103
|
+
});
|
|
98
104
|
}
|
|
99
105
|
// Single-skill-directory target: a bare `SKILL.md` at the repo root.
|
|
100
106
|
//
|
|
@@ -113,7 +119,7 @@ function discoverSkills(files, layout, repoName) {
|
|
|
113
119
|
kind: "skill",
|
|
114
120
|
path: "SKILL.md",
|
|
115
121
|
name,
|
|
116
|
-
tokens: [`${
|
|
122
|
+
tokens: [`${skillDir}/${name}`, `:${name}`],
|
|
117
123
|
ignored: content.includes(IGNORE_MARKER),
|
|
118
124
|
});
|
|
119
125
|
}
|
|
@@ -121,9 +127,10 @@ function discoverSkills(files, layout, repoName) {
|
|
|
121
127
|
}
|
|
122
128
|
function discoverAgents(files, layout) {
|
|
123
129
|
const out = [];
|
|
124
|
-
|
|
130
|
+
const agentDir = layout.surfaces.agent;
|
|
131
|
+
if (agentDir === undefined)
|
|
125
132
|
return out;
|
|
126
|
-
const prefixes = surfacePrefixes(
|
|
133
|
+
const prefixes = surfacePrefixes(agentDir, (0, layout_js_1.materializePrefix)(layout));
|
|
127
134
|
// Same depth rule as the scan classifier — quoted from AGENT_FILE_LEAF_RE, not
|
|
128
135
|
// respelled. This discoverer feeds the `Tested` metric; when it disagreed with
|
|
129
136
|
// the classifier, `audit` printed a subagent count and an untested-surface
|
|
@@ -132,7 +139,7 @@ function discoverAgents(files, layout) {
|
|
|
132
139
|
if (path.endsWith(".spec.ts"))
|
|
133
140
|
continue;
|
|
134
141
|
const content = files[path];
|
|
135
|
-
const name = (0, layout_js_1.agentSurfaceName)(path,
|
|
142
|
+
const name = (0, layout_js_1.agentSurfaceName)(path, agentDir) ?? (0, posix_path_js_1.basename)(path, ".md");
|
|
136
143
|
const dir = (0, posix_path_js_1.dirname)(path);
|
|
137
144
|
out.push({
|
|
138
145
|
kind: "agent",
|
package/dist/test-coverage.d.ts
CHANGED
|
@@ -217,7 +217,13 @@ export interface TestCoverageOptions {
|
|
|
217
217
|
* manifest/settings paths. Defaults to Claude Code; a non-CC adapter passes its
|
|
218
218
|
* own so the surface globs and hook-token expansion aren't hard-coded.
|
|
219
219
|
*/
|
|
220
|
-
|
|
220
|
+
/**
|
|
221
|
+
* The layout to discover surfaces under. REQUIRED: it used to default to the
|
|
222
|
+
* Claude Code layout, which meant this harness-agnostic detector imported an
|
|
223
|
+
* adapter, and a caller that forgot to pass one silently graded a Codex repo
|
|
224
|
+
* with Claude Code's directories.
|
|
225
|
+
*/
|
|
226
|
+
readonly layout: PluginLayout;
|
|
221
227
|
}
|
|
222
228
|
/**
|
|
223
229
|
* Find harness surfaces (skills / agents / hooks) that no test or eval covers.
|
|
@@ -231,7 +237,7 @@ export interface TestCoverageOptions {
|
|
|
231
237
|
* deterministic coverage, no evals" from "has neither" — two positions the single
|
|
232
238
|
* count made indistinguishable.
|
|
233
239
|
*/
|
|
234
|
-
export declare function findUntestedSurfaces(options
|
|
240
|
+
export declare function findUntestedSurfaces(options: TestCoverageOptions): UntestedReport;
|
|
235
241
|
/** Suggested colocated test path for an untested surface (shown in the warning). */
|
|
236
242
|
export declare function suggestedTestPath(surface: Surface, ext?: string): string;
|
|
237
243
|
/** Tally the union tier's coverage decisions by how each was established. */
|
|
@@ -260,7 +266,7 @@ export declare function coverageEvidenceCounts(report: UntestedReport): Evidence
|
|
|
260
266
|
* Returns `null` when the edited file isn't a skill/agent surface, or when it is
|
|
261
267
|
* covered on both tiers. Never throws — a nudge must not disrupt an edit.
|
|
262
268
|
*/
|
|
263
|
-
export declare function skillTestNudge(filePath: string, options
|
|
269
|
+
export declare function skillTestNudge(filePath: string, options: TestCoverageOptions): string | null;
|
|
264
270
|
/**
|
|
265
271
|
* What the PAID eval tier measures for a surface of this kind — or `null` when
|
|
266
272
|
* it measures nothing for it.
|
package/dist/test-coverage.js
CHANGED
|
@@ -76,23 +76,18 @@ const assert_never_js_1 = require("./core/assert-never.js");
|
|
|
76
76
|
const ts_runner_caps_js_1 = require("./ts-runner-caps.js");
|
|
77
77
|
const glob_1 = require("glob");
|
|
78
78
|
const layout_js_1 = require("./core/layout.js");
|
|
79
|
-
// 🔴 Same finding as src/scan.ts: a module listed as a harness-agnostic detector
|
|
80
|
-
// importing the Claude Code adapter for a DEFAULT. Invisible to both existing
|
|
81
|
-
// fences — unclassified for `boundaries/dependencies`, and `\.claude` does not
|
|
82
|
-
// match `/claude-code/`. The default belongs to the caller, not the detector.
|
|
83
|
-
// eslint-disable-next-line local/no-harness-names -- see above
|
|
84
|
-
const layout_js_2 = require("./adapters/claude-code/layout.js");
|
|
85
79
|
const coverage_evidence_js_1 = require("./coverage-evidence.js");
|
|
86
80
|
const coverage_artifact_js_1 = require("./coverage-artifact.js");
|
|
87
81
|
/**
|
|
88
82
|
* The two on-disk locations a surface dir can occupy: the plugin-root form
|
|
89
83
|
* (`skills/…`) and the materialized form (`.claude/skills/…`) — derived from the
|
|
90
|
-
* layout so a non-Claude-Code harness (its own
|
|
91
|
-
* is discovered without hard-coding `.claude`. An
|
|
92
|
-
* the surface, e.g. Codex subagents) yields no globs
|
|
84
|
+
* layout so a non-Claude-Code harness (its own materialize prefix / surface dir)
|
|
85
|
+
* is discovered without hard-coding `.claude`. An ABSENT `dir` (a harness
|
|
86
|
+
* lacking the surface, e.g. Codex subagents) yields no globs — it used to be an
|
|
87
|
+
* empty string, which was a second spelling of the same fact.
|
|
93
88
|
*/
|
|
94
89
|
function surfaceGlobs(dir, leaf, materializeRoot) {
|
|
95
|
-
if (
|
|
90
|
+
if (dir === undefined)
|
|
96
91
|
return [];
|
|
97
92
|
const matForm = materializeRoot ? `${materializeRoot}/${dir}` : dir;
|
|
98
93
|
return [...new Set([`${dir}/${leaf}`, `${matForm}/${leaf}`])];
|
|
@@ -151,7 +146,17 @@ function read(path) {
|
|
|
151
146
|
}
|
|
152
147
|
function discoverSkills(basePath, ignore, layout) {
|
|
153
148
|
const out = [];
|
|
154
|
-
|
|
149
|
+
// 🔴 ONE GATE FOR THE WHOLE FUNCTION, and it is structural rather than
|
|
150
|
+
// repeated. A harness whose layout declares no skill surface has no skills to
|
|
151
|
+
// find — `surfaceGlobs` already returned `[]` for the nested case, but the
|
|
152
|
+
// root-`SKILL.md` case below did not ask, so a third-party layout with only
|
|
153
|
+
// agents or commands still reported an untested skill, and its token read
|
|
154
|
+
// `undefined/<name>`. Narrowing here makes that token unwritable instead of
|
|
155
|
+
// merely unwritten: `skillDir` is a `string` from this line on.
|
|
156
|
+
const skillDir = layout.surfaces.skill;
|
|
157
|
+
if (skillDir === undefined)
|
|
158
|
+
return out;
|
|
159
|
+
const found = (0, glob_1.globSync)(surfaceGlobs(skillDir, "*/SKILL.md", (0, layout_js_1.materializePrefix)(layout)), { cwd: basePath, ignore });
|
|
155
160
|
for (const path of found.sort()) {
|
|
156
161
|
const name = (0, node_path_1.basename)((0, node_path_1.dirname)(path));
|
|
157
162
|
const content = read((0, node_path_1.join)(basePath, path));
|
|
@@ -159,7 +164,7 @@ function discoverSkills(basePath, ignore, layout) {
|
|
|
159
164
|
kind: "skill",
|
|
160
165
|
path,
|
|
161
166
|
name,
|
|
162
|
-
tokens: [`${
|
|
167
|
+
tokens: [`${skillDir}/${name}`, `:${name}`],
|
|
163
168
|
ignored: content.includes(IGNORE_MARKER),
|
|
164
169
|
});
|
|
165
170
|
}
|
|
@@ -181,7 +186,7 @@ function discoverSkills(basePath, ignore, layout) {
|
|
|
181
186
|
kind: "skill",
|
|
182
187
|
path: "SKILL.md",
|
|
183
188
|
name,
|
|
184
|
-
tokens: [`${
|
|
189
|
+
tokens: [`${skillDir}/${name}`, `:${name}`],
|
|
185
190
|
ignored: content.includes(IGNORE_MARKER),
|
|
186
191
|
});
|
|
187
192
|
}
|
|
@@ -204,11 +209,15 @@ function discoverAgents(basePath, ignore, layout) {
|
|
|
204
209
|
// dialect is exactly how these two discoverers drifted from the scan
|
|
205
210
|
// classifier in the first place, so only one dialect is authoritative and the
|
|
206
211
|
// other is allowed to over-match.
|
|
207
|
-
const found = (0, glob_1.globSync)(surfaceGlobs(layout.
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
const found = (0, glob_1.globSync)(surfaceGlobs(layout.surfaces.agent, "**/*.md", (0, layout_js_1.materializePrefix)(layout)), { cwd: basePath, ignore });
|
|
213
|
+
const agentDir = layout.surfaces.agent;
|
|
214
|
+
const matPrefix = (0, layout_js_1.materializePrefix)(layout);
|
|
215
|
+
const prefixes = agentDir === undefined
|
|
216
|
+
? []
|
|
217
|
+
: matPrefix
|
|
218
|
+
? [agentDir, `${matPrefix}/${agentDir}`]
|
|
219
|
+
: [agentDir];
|
|
220
|
+
const isAgentFile = agentDir
|
|
212
221
|
? new RegExp(`^(?:${[...new Set(prefixes)]
|
|
213
222
|
.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
214
223
|
.join("|")})/${layout_js_1.AGENT_FILE_LEAF_RE}$`)
|
|
@@ -225,7 +234,8 @@ function discoverAgents(basePath, ignore, layout) {
|
|
|
225
234
|
if (isAgentFile && !isAgentFile.test(rel))
|
|
226
235
|
continue;
|
|
227
236
|
const content = read((0, node_path_1.join)(basePath, path));
|
|
228
|
-
const name = (0, layout_js_1.agentSurfaceName)(rel,
|
|
237
|
+
const name = (agentDir === undefined ? null : (0, layout_js_1.agentSurfaceName)(rel, agentDir)) ??
|
|
238
|
+
(0, node_path_1.basename)(rel, ".md");
|
|
229
239
|
const dir = (0, node_path_1.dirname)(path);
|
|
230
240
|
out.push({
|
|
231
241
|
kind: "agent",
|
|
@@ -415,9 +425,9 @@ function tierOf(considered, tests, index, tier, globs) {
|
|
|
415
425
|
* deterministic coverage, no evals" from "has neither" — two positions the single
|
|
416
426
|
* count made indistinguishable.
|
|
417
427
|
*/
|
|
418
|
-
function findUntestedSurfaces(options
|
|
428
|
+
function findUntestedSurfaces(options) {
|
|
419
429
|
const basePath = options.basePath ?? process.cwd();
|
|
420
|
-
const layout = options
|
|
430
|
+
const { layout } = options;
|
|
421
431
|
const ignore = [...DEFAULT_IGNORE, ...(options.exclude ?? [])];
|
|
422
432
|
const globs = options.include ?? DEFAULT_TEST_GLOBS;
|
|
423
433
|
const surfaces = [];
|
|
@@ -531,7 +541,7 @@ function coverageEvidenceCounts(report) {
|
|
|
531
541
|
* Returns `null` when the edited file isn't a skill/agent surface, or when it is
|
|
532
542
|
* covered on both tiers. Never throws — a nudge must not disrupt an edit.
|
|
533
543
|
*/
|
|
534
|
-
function skillTestNudge(filePath, options
|
|
544
|
+
function skillTestNudge(filePath, options) {
|
|
535
545
|
let report;
|
|
536
546
|
try {
|
|
537
547
|
report = findUntestedSurfaces({ ...options, hooks: false });
|
|
@@ -573,7 +573,7 @@ function experimental_verifyPluginGuards(dir, opts = {}) {
|
|
|
573
573
|
// A harness whose hooks are not shell processes has nothing this tier can
|
|
574
574
|
// drive. Saying so is the `no-silent-skips` half — an empty `hooks` list with
|
|
575
575
|
// no note is indistinguishable from "we looked and it was fine".
|
|
576
|
-
if (!adapter.
|
|
576
|
+
if (!adapter.shellHooks || !adapter.hookProtocol)
|
|
577
577
|
return {
|
|
578
578
|
...base,
|
|
579
579
|
hooks: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "30.0.1",
|
|
4
4
|
"description": "Audit, test and measure the harness your AI agent runs on — grade your CLAUDE.md / AGENTS.md, skills, subagents and hooks, run them against a scripted model, and measure whether they actually fire.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/** The plugin id `claude plugin install` records — `<plugin>@<marketplace>`. */
|
|
2
|
-
export declare const VIGILES_PLUGIN_ID = "vigiles@vigiles";
|
|
3
|
-
/**
|
|
4
|
-
* Where a reachable install was found. Deliberately does NOT include a project
|
|
5
|
-
* `enabledPlugins` entry: that declares the plugin, it does not install it (see
|
|
6
|
-
* the header). It is reported as {@link SkillReachability.declaredNotInstalled}.
|
|
7
|
-
*/
|
|
8
|
-
export type ReachabilitySource =
|
|
9
|
-
/** `~/.claude/plugins/installed_plugins.json` — what `claude plugin install` writes. */
|
|
10
|
-
"global-plugin"
|
|
11
|
-
/** The skills vendored into the repo's own `.claude/skills/` (standalone config, always loaded). */
|
|
12
|
-
| "repo-skills";
|
|
13
|
-
/** The advisory: can the agent see vigiles's skills from this repo? */
|
|
14
|
-
export interface SkillReachability {
|
|
15
|
-
/** True when at least one {@link ReachabilitySource} was found. */
|
|
16
|
-
readonly reachable: boolean;
|
|
17
|
-
/** Every source that resolved, in check order. Empty when un-wired. */
|
|
18
|
-
readonly sources: readonly ReachabilitySource[];
|
|
19
|
-
/**
|
|
20
|
-
* The repo's `.claude/settings.json` enables the plugin, but no install was
|
|
21
|
-
* found. Claude Code will prompt this collaborator to install it; until they
|
|
22
|
-
* do, it does not load. A distinct state from "nothing configured at all",
|
|
23
|
-
* because the fix belongs to the person, not the repo.
|
|
24
|
-
*/
|
|
25
|
-
readonly declaredNotInstalled: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Shipped skills found under `node_modules/vigiles/skills/` while UNREACHABLE
|
|
28
|
-
* — present on disk, invisible to the agent. Empty when reachable (there is
|
|
29
|
-
* nothing stranded if they are wired) or when the package isn't installed yet.
|
|
30
|
-
*/
|
|
31
|
-
readonly strandedSkills: readonly string[];
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Is `vigiles` a declared dependency of this package.json text? Pure. Any of the
|
|
35
|
-
* four dependency fields counts — the question is "did this repo take vigiles
|
|
36
|
-
* on", not how.
|
|
37
|
-
*/
|
|
38
|
-
export declare function declaresVigilesDependency(pkgJson: string): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Does the global registry record a live install of the vigiles plugin? Pure over
|
|
41
|
-
* the raw `installed_plugins.json` text. An entry with an EMPTY array is a
|
|
42
|
-
* leftover record, not an install, so it does not count.
|
|
43
|
-
*/
|
|
44
|
-
export declare function hasGlobalPluginInstall(installedPluginsJson: string): boolean;
|
|
45
|
-
/**
|
|
46
|
-
* Does the repo's `.claude/settings.json` explicitly enable the vigiles plugin?
|
|
47
|
-
* Pure. An explicit `false` is a deliberate disable and does NOT count.
|
|
48
|
-
*/
|
|
49
|
-
export declare function hasEnabledPlugin(settingsJson: string): boolean;
|
|
50
|
-
/**
|
|
51
|
-
* Best-effort, read-local reachability check for `vigiles audit`. Returns null
|
|
52
|
-
* when the question does not apply — the repo has no package.json, or does not
|
|
53
|
-
* depend on vigiles, or IS vigiles — so a non-consumer is never nagged. NEVER
|
|
54
|
-
* throws: every read degrades to "not found".
|
|
55
|
-
*
|
|
56
|
-
* `opts.home` overrides `$HOME` (tests, and any caller with a relocated config).
|
|
57
|
-
*/
|
|
58
|
-
export declare function checkSkillReachability(dir: string, opts?: {
|
|
59
|
-
readonly home?: string;
|
|
60
|
-
}): SkillReachability | null;
|
|
61
|
-
/**
|
|
62
|
-
* The warning for an un-wired repo, or null when there is nothing to say
|
|
63
|
-
* (reachable, or the check did not apply). Names the skill the user most likely
|
|
64
|
-
* went looking for, says where the copies are stranded when they exist, and ends
|
|
65
|
-
* on a command that fixes it.
|
|
66
|
-
*/
|
|
67
|
-
export declare function formatSkillReachability(r: SkillReachability | null): string | null;
|
|
68
|
-
//# sourceMappingURL=skill-reachability.d.ts.map
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VIGILES_PLUGIN_ID = void 0;
|
|
4
|
-
exports.declaresVigilesDependency = declaresVigilesDependency;
|
|
5
|
-
exports.hasGlobalPluginInstall = hasGlobalPluginInstall;
|
|
6
|
-
exports.hasEnabledPlugin = hasEnabledPlugin;
|
|
7
|
-
exports.checkSkillReachability = checkSkillReachability;
|
|
8
|
-
exports.formatSkillReachability = formatSkillReachability;
|
|
9
|
-
/**
|
|
10
|
-
* Are vigiles's SHIPPED SKILLS actually reachable by the agent in this repo?
|
|
11
|
-
*
|
|
12
|
-
* vigiles publishes six user-facing skills (`SHIPPED_SKILLS`) — the teaching
|
|
13
|
-
* surface. `test-harness` alone answers "which testing tier do I want?", the
|
|
14
|
-
* question this project's docs are otherwise organized around. They reach the
|
|
15
|
-
* agent through the GLOBAL plugin install (`vigiles init`, or
|
|
16
|
-
* `claude plugin marketplace add zernie/vigiles` + `claude plugin install
|
|
17
|
-
* vigiles@vigiles`), which lands in `~/.claude/plugins/` — deliberately never
|
|
18
|
-
* vendored into the repo (`docs/agent-setup.md`).
|
|
19
|
-
*
|
|
20
|
-
* The failure this module makes loud: `npm install vigiles` ALSO puts those
|
|
21
|
-
* skills on disk, at `node_modules/vigiles/skills/` (they are in package.json's
|
|
22
|
-
* `files`, because the npm tarball doubles as the plugin payload). Claude Code
|
|
23
|
-
* never scans `node_modules`. So a repo that took the dependency but never ran
|
|
24
|
-
* the plugin install has all six skills present, unreachable, and **silent** —
|
|
25
|
-
* observed in a real consumer repo, where a full day went into re-deriving what
|
|
26
|
-
* `test-harness` teaches while it sat three directories away.
|
|
27
|
-
*
|
|
28
|
-
* ⚠️ The part that is easy to get wrong, and did get wrong TWICE, in opposite
|
|
29
|
-
* directions:
|
|
30
|
-
*
|
|
31
|
-
* 1. The authoritative record of a `claude plugin install` is the GLOBAL
|
|
32
|
-
* `~/.claude/plugins/installed_plugins.json`. A repo's `.claude/settings.json`
|
|
33
|
-
* carries PROJECT-level `enabledPlugins`, which a correctly-installed
|
|
34
|
-
* user-scope plugin does not appear in. Judging "is it wired?" from
|
|
35
|
-
* `settings.json` alone reports a working install as broken — the misread
|
|
36
|
-
* that made this look like an npm packaging bug.
|
|
37
|
-
* 2. The converse is ALSO false: a project `enabledPlugins` entry does not make
|
|
38
|
-
* the plugin load. Per the Claude Code docs (Discover plugins → "Configure
|
|
39
|
-
* team marketplaces"), as of CC v2.1.195 — "A plugin that only the project's
|
|
40
|
-
* `.claude/settings.json` enables, and that comes from an external source
|
|
41
|
-
* such as a GitHub repository or npm package, doesn't load until the team
|
|
42
|
-
* member installs it." vigiles ships from a GitHub marketplace, so that is
|
|
43
|
-
* exactly our case. Committing `extraKnownMarketplaces` + `enabledPlugins`
|
|
44
|
-
* makes Claude Code PROMPT each collaborator to install; it does not install.
|
|
45
|
-
* Confirmed empirically: in a repo whose committed settings.json declares an
|
|
46
|
-
* external plugin project-level, the global registry had no marketplace
|
|
47
|
-
* entry, no cache dir, and no install record for it, while a plugin installed
|
|
48
|
-
* the normal way on the same machine had all three.
|
|
49
|
-
*
|
|
50
|
-
* So a project declaration is a THIRD state — declared, not installed — that
|
|
51
|
-
* still warrants a warning, with a different fix line: the collaborator runs the
|
|
52
|
-
* install, the repo cannot run it for them.
|
|
53
|
-
*
|
|
54
|
-
* Shape follows `src/dialect-drift.ts`: pure parsers + a best-effort local read
|
|
55
|
-
* that NEVER throws + a formatter that returns null when there is nothing to
|
|
56
|
-
* say, so `vigiles audit` can print it without a new verb, flag, or failure mode.
|
|
57
|
-
* It is ADVISORY — it never touches the audit score, because reachability is a
|
|
58
|
-
* property of the machine (is the plugin installed?), not of the repo, and a
|
|
59
|
-
* score that moved between a laptop and CI for identical source would be a lie.
|
|
60
|
-
*/
|
|
61
|
-
const node_fs_1 = require("node:fs");
|
|
62
|
-
const node_os_1 = require("node:os");
|
|
63
|
-
const node_path_1 = require("node:path");
|
|
64
|
-
const setup_plan_js_1 = require("./setup-plan.js");
|
|
65
|
-
/** The plugin id `claude plugin install` records — `<plugin>@<marketplace>`. */
|
|
66
|
-
exports.VIGILES_PLUGIN_ID = "vigiles@vigiles";
|
|
67
|
-
/**
|
|
68
|
-
* Is `vigiles` a declared dependency of this package.json text? Pure. Any of the
|
|
69
|
-
* four dependency fields counts — the question is "did this repo take vigiles
|
|
70
|
-
* on", not how.
|
|
71
|
-
*/
|
|
72
|
-
function declaresVigilesDependency(pkgJson) {
|
|
73
|
-
let pkg;
|
|
74
|
-
try {
|
|
75
|
-
pkg = JSON.parse(pkgJson);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
// The vigiles repo itself is not a consumer — it IS the plugin.
|
|
81
|
-
if (pkg.name === "vigiles")
|
|
82
|
-
return false;
|
|
83
|
-
const fields = [
|
|
84
|
-
"dependencies",
|
|
85
|
-
"devDependencies",
|
|
86
|
-
"peerDependencies",
|
|
87
|
-
"optionalDependencies",
|
|
88
|
-
];
|
|
89
|
-
return fields.some((f) => {
|
|
90
|
-
const deps = pkg[f];
|
|
91
|
-
return typeof deps === "object" && deps !== null && "vigiles" in deps;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Does the global registry record a live install of the vigiles plugin? Pure over
|
|
96
|
-
* the raw `installed_plugins.json` text. An entry with an EMPTY array is a
|
|
97
|
-
* leftover record, not an install, so it does not count.
|
|
98
|
-
*/
|
|
99
|
-
function hasGlobalPluginInstall(installedPluginsJson) {
|
|
100
|
-
try {
|
|
101
|
-
const parsed = JSON.parse(installedPluginsJson);
|
|
102
|
-
const entry = parsed.plugins?.[exports.VIGILES_PLUGIN_ID];
|
|
103
|
-
return Array.isArray(entry) && entry.length > 0;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Does the repo's `.claude/settings.json` explicitly enable the vigiles plugin?
|
|
111
|
-
* Pure. An explicit `false` is a deliberate disable and does NOT count.
|
|
112
|
-
*/
|
|
113
|
-
function hasEnabledPlugin(settingsJson) {
|
|
114
|
-
try {
|
|
115
|
-
const parsed = JSON.parse(settingsJson);
|
|
116
|
-
return parsed.enabledPlugins?.[exports.VIGILES_PLUGIN_ID] === true;
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/** Directory names directly under `dir`, or [] when it isn't readable. */
|
|
123
|
-
function dirNames(dir) {
|
|
124
|
-
try {
|
|
125
|
-
return (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })
|
|
126
|
-
.filter((e) => e.isDirectory())
|
|
127
|
-
.map((e) => e.name);
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/** Read a file, or null when it isn't there / isn't readable. Never throws. */
|
|
134
|
-
function readOrNull(path) {
|
|
135
|
-
try {
|
|
136
|
-
return (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.readFileSync)(path, "utf-8") : null;
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Best-effort, read-local reachability check for `vigiles audit`. Returns null
|
|
144
|
-
* when the question does not apply — the repo has no package.json, or does not
|
|
145
|
-
* depend on vigiles, or IS vigiles — so a non-consumer is never nagged. NEVER
|
|
146
|
-
* throws: every read degrades to "not found".
|
|
147
|
-
*
|
|
148
|
-
* `opts.home` overrides `$HOME` (tests, and any caller with a relocated config).
|
|
149
|
-
*/
|
|
150
|
-
function checkSkillReachability(dir, opts = {}) {
|
|
151
|
-
const pkgJson = readOrNull((0, node_path_1.join)(dir, "package.json"));
|
|
152
|
-
if (pkgJson === null || !declaresVigilesDependency(pkgJson))
|
|
153
|
-
return null;
|
|
154
|
-
const home = opts.home ?? (0, node_os_1.homedir)();
|
|
155
|
-
const sources = [];
|
|
156
|
-
const installed = readOrNull((0, node_path_1.join)(home, ".claude", "plugins", "installed_plugins.json"));
|
|
157
|
-
if (installed !== null && hasGlobalPluginInstall(installed))
|
|
158
|
-
sources.push("global-plugin");
|
|
159
|
-
// Vendored copies: only vigiles's OWN skill names count. A repo with 38
|
|
160
|
-
// unrelated skills in `.claude/skills/` is still un-wired.
|
|
161
|
-
const repoSkills = new Set(dirNames((0, node_path_1.join)(dir, ".claude", "skills")));
|
|
162
|
-
if (setup_plan_js_1.SHIPPED_SKILLS.some((s) => repoSkills.has(s)))
|
|
163
|
-
sources.push("repo-skills");
|
|
164
|
-
const reachable = sources.length > 0;
|
|
165
|
-
// A project declaration is NOT a source — it makes Claude Code prompt for an
|
|
166
|
-
// install, it does not perform one. Only meaningful while unreachable.
|
|
167
|
-
const settings = readOrNull((0, node_path_1.join)(dir, ".claude", "settings.json"));
|
|
168
|
-
const declaredNotInstalled = !reachable && settings !== null && hasEnabledPlugin(settings);
|
|
169
|
-
const vendored = new Set(dirNames((0, node_path_1.join)(dir, "node_modules", "vigiles", "skills")));
|
|
170
|
-
return {
|
|
171
|
-
reachable,
|
|
172
|
-
sources,
|
|
173
|
-
declaredNotInstalled,
|
|
174
|
-
strandedSkills: reachable
|
|
175
|
-
? []
|
|
176
|
-
: setup_plan_js_1.SHIPPED_SKILLS.filter((s) => vendored.has(s)),
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* The warning for an un-wired repo, or null when there is nothing to say
|
|
181
|
-
* (reachable, or the check did not apply). Names the skill the user most likely
|
|
182
|
-
* went looking for, says where the copies are stranded when they exist, and ends
|
|
183
|
-
* on a command that fixes it.
|
|
184
|
-
*/
|
|
185
|
-
function formatSkillReachability(r) {
|
|
186
|
-
if (!r || r.reachable)
|
|
187
|
-
return null;
|
|
188
|
-
const stranded = r.strandedSkills.length > 0
|
|
189
|
-
? ` ${String(r.strandedSkills.length)} of them are sitting in ` +
|
|
190
|
-
`node_modules/vigiles/skills/, which the agent never scans.`
|
|
191
|
-
: "";
|
|
192
|
-
const why = r.declaredNotInstalled
|
|
193
|
-
? `This repo DECLARES the vigiles plugin in .claude/settings.json, but a ` +
|
|
194
|
-
`project declaration doesn't install it — Claude Code loads an ` +
|
|
195
|
-
`external-source plugin only once each collaborator installs it on their ` +
|
|
196
|
-
`own machine.`
|
|
197
|
-
: `This repo depends on vigiles, but its plugin isn't installed.`;
|
|
198
|
-
return (`⚠ vigiles's skills are NOT reachable by your agent here. ${why} So the ` +
|
|
199
|
-
`shipped skills (${setup_plan_js_1.SHIPPED_SKILLS.join(", ")}) can't be selected — ` +
|
|
200
|
-
`including test-harness, which picks the testing tier for you.${stranded}\n` +
|
|
201
|
-
` Fix (per machine): claude plugin marketplace add zernie/vigiles && ` +
|
|
202
|
-
`claude plugin install ${exports.VIGILES_PLUGIN_ID}\n` +
|
|
203
|
-
` (or run \`vigiles init\`, which does both)`);
|
|
204
|
-
}
|
|
205
|
-
//# sourceMappingURL=skill-reachability.js.map
|
|
File without changes
|
|
File without changes
|