vigiles 28.0.0 โ 29.1.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/README.md +1 -1
- package/dist/adapter-registry.d.ts +39 -0
- package/dist/adapter-registry.js +45 -0
- package/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +10 -1
- package/dist/adapters/claude-code/adapter.js +6 -0
- package/dist/adapters/claude-code/layout.d.ts +5 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +10 -1
- package/dist/adapters/claude-code/plugin-loader.js +10 -1
- package/dist/adapters/codex/adapter.js +6 -0
- package/dist/adapters/codex/layout.d.ts +51 -5
- package/dist/adapters/codex/layout.js +13 -4
- package/dist/adapters/opencode/adapter.js +6 -0
- package/dist/audit-report.template.html +1 -1
- package/dist/audit-score.d.ts +7 -0
- package/dist/audit-score.js +49 -2
- package/dist/cli-main.js +82 -20
- package/dist/core/adapter.d.ts +23 -0
- package/dist/core/compile.js +21 -3
- package/dist/core/config-schema.d.ts +244 -0
- package/dist/core/config-schema.js +460 -0
- package/dist/core/lethal-trifecta.js +5 -0
- package/dist/core/refs.js +10 -1
- package/dist/core/surface-discovery.d.ts +270 -0
- package/dist/core/surface-discovery.js +429 -0
- package/dist/core/surface-scopes.d.ts +38 -1
- package/dist/core/surface-scopes.js +73 -1
- package/dist/core/symbols.d.ts +24 -2
- package/dist/core/symbols.js +66 -18
- package/dist/core/types.d.ts +36 -107
- package/dist/core/validate.d.ts +36 -22
- package/dist/core/validate.js +88 -176
- package/dist/exclude.d.ts +20 -0
- package/dist/exclude.js +11 -1
- package/dist/layout-registry.d.ts +14 -0
- package/dist/layout-registry.js +40 -0
- package/dist/plugin-loader.d.ts +49 -1
- package/dist/plugin-loader.js +120 -14
- package/dist/scan-core.d.ts +19 -0
- package/dist/scan-core.js +30 -0
- package/dist/scan-files.js +15 -5
- package/dist/scan.d.ts +63 -0
- package/dist/scan.js +79 -12
- package/dist/score-core.js +8 -0
- package/dist/setup-plan.d.ts +2 -1
- package/dist/setup-plan.js +7 -2
- package/dist/surface-discovery-fs.d.ts +12 -0
- package/dist/surface-discovery-fs.js +108 -0
- package/dist/test-coverage.js +5 -0
- package/dist/vigilesrc.schema.json +1689 -0
- package/package.json +10 -6
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface DISCOVERY โ WHERE a repo's loadable surfaces are, answered by SHAPE
|
|
3
|
+
* over a BOUNDED set of roots, BEFORE anything asks which harness this repo is.
|
|
4
|
+
*
|
|
5
|
+
* ๐ด WHY THIS MODULE EXISTS โ THE ORDER OF THE TWO QUESTIONS WAS BACKWARDS.
|
|
6
|
+
* The tool asked "which ONE harness is this?" first and then trusted the winning
|
|
7
|
+
* {@link PluginLayout} to name the directories. `PluginLayout` does two jobs at
|
|
8
|
+
* once โ it is the DIALECT (what the harness understands) and the SEARCH PATH
|
|
9
|
+
* (where to look) โ so a repo whose skills sit somewhere no shipped layout names
|
|
10
|
+
* was not merely unread, it was graded anyway. Measured by an outside adopter
|
|
11
|
+
* (#240, vigiles 27.2.0): 37 skills, 24 over-budget descriptions and 14 missing
|
|
12
|
+
* bundled resources under `.ai/` produced
|
|
13
|
+
*
|
|
14
|
+
* Detected harness: codex
|
|
15
|
+
* Harness health: A (100/100)
|
|
16
|
+
* โ no structural issues found
|
|
17
|
+
*
|
|
18
|
+
* while the same binary pointed straight at `<repo>/.ai` graded it F (0/100).
|
|
19
|
+
* Same repo, same commit, same version โ the grade decided by one positional
|
|
20
|
+
* argument, and the one nobody would question was the wrong one.
|
|
21
|
+
*
|
|
22
|
+
* โโ THE DEPENDENCY DIRECTION THIS BUYS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
23
|
+
* Discovery is the DOMAIN's job and an adapter cannot widen it. A harness
|
|
24
|
+
* adapter answers only the pure question `claims(path)` โ "is this path mine?" โ
|
|
25
|
+
* so it can LABEL what was already found and nothing else. The property:
|
|
26
|
+
* **registering a new adapter cannot make vigiles read more in anyone's
|
|
27
|
+
* repository.** The rejected alternative (each adapter declares roots, the audit
|
|
28
|
+
* walks the union) inverts that: adding a Cursor adapter would start reading
|
|
29
|
+
* `.cursor/rules` in every user's repo, and a surface no adapter declared would
|
|
30
|
+
* stay invisible โ the blindness preserved. See `research/audit-harness-dx.md`
|
|
31
|
+
* ยง9 for the four options and why this one.
|
|
32
|
+
*
|
|
33
|
+
* And the corollary that makes it a product statement rather than a refactor: a
|
|
34
|
+
* surface NOBODY claims is a FINDING, not silence. That is this tool's own thesis
|
|
35
|
+
* applied to itself โ a passing signal standing in for work nobody did.
|
|
36
|
+
*
|
|
37
|
+
* โโ THE BOUND, AND WHY IT IS NOT A PORT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
38
|
+
* Walking the whole repo is not an option and the reporter of #240 said why
|
|
39
|
+
* before we did: an unbounded `**\/SKILL.md` in that repo finds 53 VENDORED
|
|
40
|
+
* third-party plugin skills beside the 37 real ones and grades them as one
|
|
41
|
+
* machine. So discovery looks in the repo root, plus each of its DEPTH-1
|
|
42
|
+
* DOT-DIRECTORIES ({@link isDiscoveryRoot}). The reason that set is the right shape โ
|
|
43
|
+
* every harness convention puts its tree in a top-level dot-directory
|
|
44
|
+
* (`.claude`, `.codex`, `.agents`, `.cursor`, `.gemini`, `.opencode`,
|
|
45
|
+
* `.windsurf`, and #240's `.ai`), and an ordinary source tree does not. Cost is
|
|
46
|
+
* one `readdir` of the repo root plus one per dot-directory, then descent ONLY
|
|
47
|
+
* into a directory named by {@link SURFACE_SHAPES} โ never into `src/`,
|
|
48
|
+
* `packages/` or anything else.
|
|
49
|
+
*
|
|
50
|
+
* It is a DOMAIN CONSTANT, not a port method, and that is load-bearing: if an
|
|
51
|
+
* adapter could add a root we would be back to the inverted dependency above.
|
|
52
|
+
*
|
|
53
|
+
* โ ๏ธ KNOWN LIMITATION, NAMED RATHER THAN FAKED โ the Codex WALK-UP is still not
|
|
54
|
+
* expressed. The vendor scans `.agents/skills` in EVERY directory from the cwd up
|
|
55
|
+
* to the repository root; this bound covers `<root>/.agents/skills` only, so a
|
|
56
|
+
* subdirectory's own `.agents/skills` stays invisible. Reaching it means walking
|
|
57
|
+
* arbitrary directories, which is exactly what the paragraph above refuses. For
|
|
58
|
+
* the normal case โ `vigiles audit` at the repo root โ the root IS the whole
|
|
59
|
+
* walk-up chain, so the gap is a monorepo subpackage, not the common shape.
|
|
60
|
+
*
|
|
61
|
+
* Pure, IO-free and Node-free ON PURPOSE, exactly as `surface-scopes.ts` is: the
|
|
62
|
+
* disk loader (`src/scan.ts` via `src/surface-discovery-fs.ts`) and the browser
|
|
63
|
+
* file-map twin (`src/scan-files.ts`) each enumerate paths from their own storage
|
|
64
|
+
* and call THIS for the decision, so the pair this repo has repeatedly been
|
|
65
|
+
* bitten by fixing on one side only cannot disagree about what a surface is.
|
|
66
|
+
*/
|
|
67
|
+
import { type PluginLayout } from "./layout.js";
|
|
68
|
+
/** A surface kind discovery can recognize from a path alone. */
|
|
69
|
+
export type DiscoveredKind = "skill" | "agent" | "command";
|
|
70
|
+
/**
|
|
71
|
+
* The SHAPE of each surface kind: the directory name that holds it, and the
|
|
72
|
+
* path tail that makes a file inside it loadable.
|
|
73
|
+
*
|
|
74
|
+
* These are the CROSS-VENDOR names, not one harness's: Codex's skills live at
|
|
75
|
+
* `.agents/skills`, Claude Code's at `skills` or `.claude/skills` โ the same
|
|
76
|
+
* `skills` segment under a different root, which is why the root is the variable
|
|
77
|
+
* and the shape name is not. The tails quote the classifier the scan already
|
|
78
|
+
* uses (`makeClassifier`, scan-core.ts): a skill is `<name>/SKILL.md`, an agent
|
|
79
|
+
* is read RECURSIVELY per {@link AGENT_FILE_LEAF_RE} (do not hard-code a depth
|
|
80
|
+
* here โ that header says why), a command is any `.md` below the dir.
|
|
81
|
+
*
|
|
82
|
+
* Codex's `prompts` is deliberately absent: it is claimed by `codexLayout` and
|
|
83
|
+
* read by the loader, and giving a non-dot word like `prompts` shape status at
|
|
84
|
+
* the repo root would match ordinary prompt-engineering directories that are not
|
|
85
|
+
* a harness surface at all.
|
|
86
|
+
*/
|
|
87
|
+
export declare const SURFACE_SHAPES: ReadonlyArray<{
|
|
88
|
+
readonly dir: string;
|
|
89
|
+
readonly kind: DiscoveredKind;
|
|
90
|
+
readonly tail: string;
|
|
91
|
+
}>;
|
|
92
|
+
/** One surface found by shape, and the bounded root it was found under. */
|
|
93
|
+
export interface DiscoveredSurface {
|
|
94
|
+
readonly kind: DiscoveredKind;
|
|
95
|
+
/** Repo-relative POSIX path of the loadable file. */
|
|
96
|
+
readonly path: string;
|
|
97
|
+
/** The discovery root it sits under โ `""` for the repo root. */
|
|
98
|
+
readonly root: string;
|
|
99
|
+
/** The surface directory, repo-relative (`.ai/skills`, `skills`, โฆ). */
|
|
100
|
+
readonly dir: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Is this repo-root-relative DIRECTORY NAME one of the bounded discovery roots?
|
|
104
|
+
*
|
|
105
|
+
* The repo root itself is always one (`""`). Everything else must be a
|
|
106
|
+
* DOT-directory โ see the module header for why that is the whole bound. The
|
|
107
|
+
* caller supplies the names (a `readdir` on disk, the key set in the browser),
|
|
108
|
+
* so this function stays IO-free.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isDiscoveryRoot(name: string): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Every surface a set of repo-relative paths holds, by SHAPE โ harness-blind.
|
|
113
|
+
*
|
|
114
|
+
* `paths` must already be bounded by the caller's walk; this re-checks the root
|
|
115
|
+
* rule anyway so the browser twin (which hands over the whole fetched key set)
|
|
116
|
+
* gets the same answer as the disk walk.
|
|
117
|
+
*/
|
|
118
|
+
export declare function discoverSurfaces(paths: readonly string[]): readonly DiscoveredSurface[];
|
|
119
|
+
/**
|
|
120
|
+
* Every repo-relative location a layout READS โ the raw material for
|
|
121
|
+
* {@link layoutClaims}.
|
|
122
|
+
*
|
|
123
|
+
* Derived from the layout rather than listed per adapter, so a layout that moves
|
|
124
|
+
* (as `codexLayout` just did, `.codex/skills` โ `.agents/skills`) moves its claim
|
|
125
|
+
* with it and the two cannot drift.
|
|
126
|
+
*
|
|
127
|
+
* โ ๏ธ `materializeRoot` goes through {@link located} because `codexLayout` sets it
|
|
128
|
+
* to `""` and a directory prefix of `""` is meaningless. It is DEFENCE IN DEPTH,
|
|
129
|
+
* not the load-bearing guard, and the difference was MEASURED rather than
|
|
130
|
+
* assumed: removing this filter alone leaves `layoutClaims(codexLayout,
|
|
131
|
+
* "src/index.ts")` at `false`, because the boundary form `path.startsWith(`${d}/`)`
|
|
132
|
+
* in {@link layoutClaims} already refuses a `""` prefix (no repo-relative path
|
|
133
|
+
* starts with `/`). Both have to go before Codex claims the whole repository โ
|
|
134
|
+
* which is the combined mutation `surface-discovery.test.ts` pins, precisely
|
|
135
|
+
* because removing either one on its own is survivable and would otherwise read
|
|
136
|
+
* as "this line is protecting us".
|
|
137
|
+
*/
|
|
138
|
+
export declare function layoutLocations(layout: PluginLayout): {
|
|
139
|
+
readonly dirs: readonly string[];
|
|
140
|
+
readonly files: readonly string[];
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Does this layout's harness READ this repo-relative path โ "is it mine?"
|
|
144
|
+
*
|
|
145
|
+
* The ONE answer behind every adapter's `claims`, so an adapter cannot express a
|
|
146
|
+
* claim its own layout contradicts. Pure: a string question about a string, with
|
|
147
|
+
* no filesystem and no knowledge of what else exists.
|
|
148
|
+
*/
|
|
149
|
+
export declare function layoutClaims(layout: PluginLayout, path: string): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Does a repo owner's DECLARED root cause this path to be read, under the
|
|
152
|
+
* layout of the harness it was declared UNDER?
|
|
153
|
+
*
|
|
154
|
+
* ๐ด IT CLAIMS EXACTLY WHAT IT MAKES READABLE, AND NOT ONE PATH MORE. The set is
|
|
155
|
+
* `<root>/<surfaceDir>/โฆ` for that harness's own `surfaceDirs` โ which is
|
|
156
|
+
* verbatim the set `materializeSurfaces` reads for a declared scope. Derived
|
|
157
|
+
* from the same field rather than listed twice, so the two cannot drift.
|
|
158
|
+
*
|
|
159
|
+
* The consequence is the point: a declaration silences the finding only where it
|
|
160
|
+
* actually reaches. Declaring `.ai` under `"codex"` does not silence a
|
|
161
|
+
* `.ai/skills/` tree, because Codex's skill dir is `.agents/skills` and
|
|
162
|
+
* `.ai/skills` is still read by nobody. Under the flat `surfaceRoots` key that
|
|
163
|
+
* was where the story ended โ the tool stayed quiet about a declaration that
|
|
164
|
+
* changed nothing. Now the harness is part of the declaration, so the same fact
|
|
165
|
+
* is an ERROR the owner can act on ({@link unresolvedDeclaredRoots}).
|
|
166
|
+
*
|
|
167
|
+
* โ ๏ธ A declaration is the REPO OWNER's, never an adapter's. Nothing here lets a
|
|
168
|
+
* harness widen what vigiles reads in someone else's repository โ the property
|
|
169
|
+
* the module header exists to protect. See `research/audit-harness-dx.md` ยง9.
|
|
170
|
+
*/
|
|
171
|
+
export declare function declaredRootClaims(layout: PluginLayout, roots: readonly string[], path: string): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* The repo-relative dirs a declaration REACHES: `<root>/<surfaceDir>` for every
|
|
174
|
+
* declared root crossed with this layout's own surface dirs.
|
|
175
|
+
*
|
|
176
|
+
* ONE derivation, read two ways โ {@link declaredRootClaims} asks whether a
|
|
177
|
+
* found path is in the set, {@link unresolvedDeclaredRoots} asks whether any
|
|
178
|
+
* member of the set exists on disk. Two spellings of "where does this
|
|
179
|
+
* declaration reach" is how a declaration could be claimed by one and refused by
|
|
180
|
+
* the other.
|
|
181
|
+
*/
|
|
182
|
+
export declare function declaredRootDirs(layout: PluginLayout, roots: readonly string[]): readonly string[];
|
|
183
|
+
/** One declared harness as this module needs it: a name and its layout + roots. */
|
|
184
|
+
export interface DeclaredRootScope {
|
|
185
|
+
/** The harness KEY the root was declared under. */
|
|
186
|
+
readonly harness: string;
|
|
187
|
+
readonly layout: PluginLayout;
|
|
188
|
+
readonly roots: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Declared roots that reach NO directory on disk โ the one silent state the
|
|
192
|
+
* nested shape would otherwise keep (#240).
|
|
193
|
+
*
|
|
194
|
+
* ๐ด WHY THIS IS AN ERROR AND NOT A WARNING. A root under a harness whose layout
|
|
195
|
+
* keeps that surface somewhere else is not a near-miss, it is a statement that
|
|
196
|
+
* cannot be true: `{"codex": {"roots": [".ai"]}}` over a `.ai/skills/` tree makes
|
|
197
|
+
* vigiles look at `.ai/.agents/skills/` and `.ai/prompts/`, finds neither, reads
|
|
198
|
+
* nothing, and changes not one line of the report. The owner wrote a line
|
|
199
|
+
* believing their skills were now graded. Silence there is the tool agreeing.
|
|
200
|
+
*
|
|
201
|
+
* โ ๏ธ IT CHECKS THE DIRECTORY, NOT ITS CONTENTS, and the difference is
|
|
202
|
+
* deliberate: an EMPTY `.ai/skills/` is a real, correctly-declared home that
|
|
203
|
+
* happens to hold nothing today, and failing a build over an empty directory
|
|
204
|
+
* would be a gate on repo state rather than on the declaration. What is refused
|
|
205
|
+
* is a declaration that names no directory at all.
|
|
206
|
+
*
|
|
207
|
+
* `dirExists` is injected (repo-relative path in, boolean out) so the rule is
|
|
208
|
+
* pure, node-free and testable without a filesystem โ the same contract every
|
|
209
|
+
* other decision in this module keeps.
|
|
210
|
+
*/
|
|
211
|
+
export declare function unresolvedDeclaredRoots(scopes: readonly DeclaredRootScope[], dirExists: (repoRelativeDir: string) => boolean): readonly string[];
|
|
212
|
+
/**
|
|
213
|
+
* The discovered surfaces NO registered harness claims โ the finding.
|
|
214
|
+
*
|
|
215
|
+
* `claimers` is every registered adapter's `claims`, so "unclaimed" means "no
|
|
216
|
+
* harness vigiles knows about reads this", not "the detected harness does not".
|
|
217
|
+
* That distinction is the whole point of discovering before detecting: a repo
|
|
218
|
+
* carrying both `.claude/skills` and `.agents/skills` has each half claimed by a
|
|
219
|
+
* different adapter and neither is a finding, while #240's `.ai/skills` is
|
|
220
|
+
* claimed by nobody and becomes one.
|
|
221
|
+
*/
|
|
222
|
+
export declare function unclaimedSurfaces(surfaces: readonly DiscoveredSurface[], claimers: ReadonlyArray<(path: string) => boolean>): readonly DiscoveredSurface[];
|
|
223
|
+
/**
|
|
224
|
+
* The unclaimed surfaces, grouped into ONE finding per directory.
|
|
225
|
+
*
|
|
226
|
+
* A finding per FILE would put 37 lines in #240's report for one mistake; the
|
|
227
|
+
* actionable unit is the directory nobody reads, so that is the unit counted and
|
|
228
|
+
* the unit graded.
|
|
229
|
+
*/
|
|
230
|
+
export declare function unclaimedDirs(unclaimed: readonly DiscoveredSurface[]): ReadonlyArray<{
|
|
231
|
+
readonly dir: string;
|
|
232
|
+
readonly root: string;
|
|
233
|
+
readonly kind: DiscoveredKind;
|
|
234
|
+
readonly count: number;
|
|
235
|
+
}>;
|
|
236
|
+
/** One directory of surfaces that no registered harness reads. */
|
|
237
|
+
export interface UnclaimedSurfaceFinding {
|
|
238
|
+
/** The unread surface directory, repo-relative (`.ai/skills`). */
|
|
239
|
+
readonly dir: string;
|
|
240
|
+
readonly kind: DiscoveredKind;
|
|
241
|
+
/** How many loadable files it holds. */
|
|
242
|
+
readonly count: number;
|
|
243
|
+
/** The report line, worded ONCE so the disk scan and the browser twin agree. */
|
|
244
|
+
readonly message: string;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The whole discovery verdict in one call: paths in, findings out.
|
|
248
|
+
*
|
|
249
|
+
* The ENTRY POINT both producers use (`src/scan.ts` over a bounded disk walk,
|
|
250
|
+
* `src/scan-files.ts` over the fetched key set), so the wording, the grouping
|
|
251
|
+
* and the claim rule cannot differ between the CLI and the in-browser audit.
|
|
252
|
+
*
|
|
253
|
+
* It takes LAYOUTS rather than bare predicates because the answer needs both
|
|
254
|
+
* halves of the same fact: who claims a path, and where those claimants
|
|
255
|
+
* actually keep that kind of surface. Taking them apart is how the message and
|
|
256
|
+
* the claim rule would come to disagree.
|
|
257
|
+
*
|
|
258
|
+
* `declared` is the repo owner's opt-in: one entry per harness declared in
|
|
259
|
+
* `.vigilesrc.json#harnesses`, each carrying the roots declared under it and the
|
|
260
|
+
* LAYOUT those roots are read with. It joins the claimers rather than filtering
|
|
261
|
+
* the findings afterwards, so "no harness reads this" and "this is read" stay
|
|
262
|
+
* ONE question with one answer. Omitted by the browser twin, which has no
|
|
263
|
+
* config โ see {@link declaredRootClaims} for what it does and does not silence.
|
|
264
|
+
*
|
|
265
|
+
* โ ๏ธ `exclude` needs no mention here and that is structural, not an oversight:
|
|
266
|
+
* an excluded path never reaches `paths` (the walk drops it), so it can be
|
|
267
|
+
* neither a finding nor a graded surface however it was declared.
|
|
268
|
+
*/
|
|
269
|
+
export declare function unclaimedSurfaceFindings(paths: readonly string[], layouts: readonly PluginLayout[], declared?: readonly DeclaredRootScope[]): readonly UnclaimedSurfaceFinding[];
|
|
270
|
+
//# sourceMappingURL=surface-discovery.d.ts.map
|