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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter-registry.d.ts +39 -0
  3. package/dist/adapter-registry.js +45 -0
  4. package/dist/adapter.d.ts +8 -0
  5. package/dist/adapter.js +10 -1
  6. package/dist/adapters/claude-code/adapter.js +6 -0
  7. package/dist/adapters/claude-code/layout.d.ts +5 -0
  8. package/dist/adapters/claude-code/plugin-loader.d.ts +10 -1
  9. package/dist/adapters/claude-code/plugin-loader.js +10 -1
  10. package/dist/adapters/codex/adapter.js +6 -0
  11. package/dist/adapters/codex/layout.d.ts +51 -5
  12. package/dist/adapters/codex/layout.js +13 -4
  13. package/dist/adapters/opencode/adapter.js +6 -0
  14. package/dist/audit-report.template.html +1 -1
  15. package/dist/audit-score.d.ts +7 -0
  16. package/dist/audit-score.js +49 -2
  17. package/dist/cli-main.js +82 -20
  18. package/dist/core/adapter.d.ts +23 -0
  19. package/dist/core/compile.js +21 -3
  20. package/dist/core/config-schema.d.ts +244 -0
  21. package/dist/core/config-schema.js +460 -0
  22. package/dist/core/lethal-trifecta.js +5 -0
  23. package/dist/core/refs.js +10 -1
  24. package/dist/core/surface-discovery.d.ts +270 -0
  25. package/dist/core/surface-discovery.js +429 -0
  26. package/dist/core/surface-scopes.d.ts +38 -1
  27. package/dist/core/surface-scopes.js +73 -1
  28. package/dist/core/symbols.d.ts +24 -2
  29. package/dist/core/symbols.js +66 -18
  30. package/dist/core/types.d.ts +36 -107
  31. package/dist/core/validate.d.ts +36 -22
  32. package/dist/core/validate.js +88 -176
  33. package/dist/exclude.d.ts +20 -0
  34. package/dist/exclude.js +11 -1
  35. package/dist/layout-registry.d.ts +14 -0
  36. package/dist/layout-registry.js +40 -0
  37. package/dist/plugin-loader.d.ts +49 -1
  38. package/dist/plugin-loader.js +120 -14
  39. package/dist/scan-core.d.ts +19 -0
  40. package/dist/scan-core.js +30 -0
  41. package/dist/scan-files.js +15 -5
  42. package/dist/scan.d.ts +63 -0
  43. package/dist/scan.js +79 -12
  44. package/dist/score-core.js +8 -0
  45. package/dist/setup-plan.d.ts +2 -1
  46. package/dist/setup-plan.js +7 -2
  47. package/dist/surface-discovery-fs.d.ts +12 -0
  48. package/dist/surface-discovery-fs.js +108 -0
  49. package/dist/test-coverage.js +5 -0
  50. package/dist/vigilesrc.schema.json +1689 -0
  51. 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