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
package/dist/core/validate.js
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_RULES = void 0;
|
|
4
4
|
exports.findInstructionFiles = findInstructionFiles;
|
|
5
|
-
exports.normalizeSeverity = normalizeSeverity;
|
|
6
|
-
exports.asStringArray = asStringArray;
|
|
7
5
|
exports.loadConfig = loadConfig;
|
|
8
6
|
exports.parseRules = parseRules;
|
|
9
7
|
exports.validate = validate;
|
|
@@ -14,6 +12,25 @@ const node_fs_1 = require("node:fs");
|
|
|
14
12
|
const glob_1 = require("glob");
|
|
15
13
|
const node_path_1 = require("node:path");
|
|
16
14
|
const cosmiconfig_1 = require("cosmiconfig");
|
|
15
|
+
// 🔴 A TOP-LEVEL IMPORT, and the lazy `require` it replaced is recorded because
|
|
16
|
+
// the instinct to reintroduce it is correct-sounding: Zod is a ~45 ms cold
|
|
17
|
+
// import and this module is on the CLI's barrel. Two measured facts settle it.
|
|
18
|
+
//
|
|
19
|
+
// (1) It does not reach the HOT rail at all. A compiled hook's decision
|
|
20
|
+
// (`vigiles hook-runtime run-program`) never loads the verb barrel — the
|
|
21
|
+
// dispatcher shim in `src/cli.ts` branches before it, and
|
|
22
|
+
// `src/hook-runtime-graph.test.ts` fails the day that stops being true — so
|
|
23
|
+
// it never loads this file, lazily or otherwise.
|
|
24
|
+
// (2) On the rails that DO load the barrel (the PostToolUse `refs` and
|
|
25
|
+
// `eval-lock-nudge` nudges) the floor is ~316 ms of Node startup plus ~85
|
|
26
|
+
// existing requires, against which 45 ms is ~13%, not a breach.
|
|
27
|
+
//
|
|
28
|
+
// And the attempt is on record as well: an in-body `require("./config-schema")`
|
|
29
|
+
// resolves in `dist` (CommonJS) and NOT under vitest, which imports the `.ts`
|
|
30
|
+
// sources — it failed every suite that touches `loadConfig`. A deferral that
|
|
31
|
+
// only works in one of the two worlds the code runs in is not one line of
|
|
32
|
+
// hygiene, it is a second module system.
|
|
33
|
+
const config_schema_js_1 = require("./config-schema.js");
|
|
17
34
|
// ---------------------------------------------------------------------------
|
|
18
35
|
// Constants & regex
|
|
19
36
|
// ---------------------------------------------------------------------------
|
|
@@ -21,7 +38,6 @@ const GUIDANCE_RE = /\*\*Guidance only\*\*/;
|
|
|
21
38
|
const DISABLE_RE = /<!--\s*vigiles-disable\s*-->/;
|
|
22
39
|
const RULE_HEADER_RE = /^###\s+(.+)$/;
|
|
23
40
|
const CHECKBOX_RE = /^- \[([ xX])\]\s+(.+)$/;
|
|
24
|
-
const VALID_MARKERS = ["headings", "checkboxes"];
|
|
25
41
|
// ---------------------------------------------------------------------------
|
|
26
42
|
// Default config
|
|
27
43
|
// ---------------------------------------------------------------------------
|
|
@@ -31,103 +47,25 @@ const VALID_MARKERS = ["headings", "checkboxes"];
|
|
|
31
47
|
const INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
32
48
|
// The default instruction file to validate when no config names one.
|
|
33
49
|
const DEFAULT_FILES = [INSTRUCTION_FILES[0]];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"require-instructions-spec": "warn",
|
|
54
|
-
// Default OFF — the consistent `require-<surface>-spec` parallel. Skills are
|
|
55
|
-
// legitimately hand-written, so requiring a .spec.ts per SKILL.md is the wrong
|
|
56
|
-
// default (it would nag about vendored/fixture/bench skills); the coverage that
|
|
57
|
-
// matters is the `untested-*` rules ("every skill/agent/hook ships with a test
|
|
58
|
-
// or eval"). Set `require-skill-spec` explicitly if your team wants every skill
|
|
59
|
-
// spec-managed.
|
|
60
|
-
"require-skill-spec": false,
|
|
61
|
-
integrity: "warn",
|
|
62
|
-
coverage: false,
|
|
63
|
-
// Per-kind surface-coverage: a skill/agent/hook must ship with a test or eval.
|
|
64
|
-
"untested-skill": "warn",
|
|
65
|
-
"untested-subagent": "warn",
|
|
66
|
-
"untested-hook": "warn",
|
|
67
|
-
"unmarked-refs": "warn",
|
|
68
|
-
// High-precision (never-available + close typos only), so on by default at warn.
|
|
69
|
-
"subagent-tool-contract": "warn",
|
|
70
|
-
// High-precision (close typos only), on by default at warn.
|
|
71
|
-
"hook-events": "warn",
|
|
72
|
-
// Missing required frontmatter (name/description) — on by default at warn.
|
|
73
|
-
"subagent-frontmatter": "warn",
|
|
74
|
-
// A declared MCP server with no command/url can't start — on by default at warn.
|
|
75
|
-
"mcp-config": "warn",
|
|
76
|
-
// Best-practice nudge (skills load without frontmatter) — warn, not error.
|
|
77
|
-
"skill-frontmatter": "warn",
|
|
78
|
-
// High-precision (gated on a declared MCP set; built-ins allowlisted) — warn.
|
|
79
|
-
"mcp-tool-resolves": "warn",
|
|
80
|
-
// A hook script referenced but missing never runs — on by default at warn.
|
|
81
|
-
"hook-script-exists": "warn",
|
|
82
|
-
// Discovery nudge toward compiled hooks (one finding) — default OFF: it's a
|
|
83
|
-
// recommendation, not a defect (the hand-written shell lane stays first-class),
|
|
84
|
-
// so it shouldn't fire unasked. Set "warn"/"error" to opt in.
|
|
85
|
-
"prefer-compiled-hooks": false,
|
|
86
|
-
// High-precision (close-typo only) deny-list mirror of subagent-tool-contract.
|
|
87
|
-
"disallowed-tools-contract": "warn",
|
|
88
|
-
// Deterministic NCD precision proxy (near-identical skill descriptions) — warn.
|
|
89
|
-
"description-overlap": "warn",
|
|
90
|
-
// A model-invocable skill's description so long the trigger signal is buried —
|
|
91
|
-
// WARN only (heuristic proxy, generous 500-char budget); never gates.
|
|
92
|
-
"skill-description-budget": "warn",
|
|
93
|
-
// Malformed-YAML frontmatter — WARN only (js-yaml is stricter than some loaders).
|
|
94
|
-
"frontmatter-valid": "warn",
|
|
95
|
-
// A mcp_tool hook incomplete / targeting an undeclared server — on by default at warn.
|
|
96
|
-
"mcp-hook-target-resolves": "warn",
|
|
97
|
-
// Lethal-trifecta capability set-intersection (read-private + ingest-untrusted +
|
|
98
|
-
// exfiltrate in one unit) — WARN by default (don't-cry-wolf rollout); raise to error.
|
|
99
|
-
"lethal-trifecta": "warn",
|
|
100
|
-
// A SKILL.md body referencing a missing bundled resource — WARN by default
|
|
101
|
-
// (don't-cry-wolf rollout, FP-safe); raise to error to gate CI.
|
|
102
|
-
"skill-resource-resolves": "warn",
|
|
103
|
-
// A SKILL.md missing its opening `---` fence (invisible skill) — WARN by
|
|
104
|
-
// default (FP-safe key whitelist); raise to error to gate CI.
|
|
105
|
-
"skill-missing-fence": "warn",
|
|
106
|
-
// Functional dirs nested inside `.claude-plugin/` (invisible surfaces) — WARN
|
|
107
|
-
// by default; raise to error to gate CI.
|
|
108
|
-
"plugin-dir-layout": "warn",
|
|
109
|
-
// A lethal trifecta emerging across a delegation edge (combined blast radius) —
|
|
110
|
-
// WARN by default (don't-cry-wolf rollout); raise to error to gate CI.
|
|
111
|
-
"delegation-trifecta": "warn",
|
|
112
|
-
// A hook that looks like it blocks but silently doesn't (#19009) — WARN by
|
|
113
|
-
// default (FP-safe literal patterns); raise to error to gate CI.
|
|
114
|
-
"hook-block-ineffective": "warn",
|
|
115
|
-
// A hook matcher that doesn't fire as written (tool typo, an MCP pattern
|
|
116
|
-
// that matches no tool name, or one too narrow for real server names) — WARN
|
|
117
|
-
// by default (high-precision); raise to error to gate CI.
|
|
118
|
-
"hook-matcher": "warn",
|
|
119
|
-
// Builder calls quoted inside ```ts fences in markdown — default OFF. Measured
|
|
120
|
-
// over 2 582 markdown files across two repos: 52 refs, 0 true positives, and
|
|
121
|
-
// every error raised was a false one (a design sketch's `cmd("npm test")`, a
|
|
122
|
-
// vendored third-party CLAUDE.md). A fence in prose is a DRAWING of config;
|
|
123
|
-
// reading it as config is the defect. Opt in where markdown IS the source.
|
|
124
|
-
"doc-refs": false,
|
|
125
|
-
};
|
|
126
|
-
const DEFAULT_CONFIG = {
|
|
127
|
-
ruleMarkers: ["headings", "checkboxes"],
|
|
128
|
-
rules: exports.DEFAULT_RULES,
|
|
129
|
-
files: DEFAULT_FILES,
|
|
130
|
-
};
|
|
50
|
+
/**
|
|
51
|
+
* The shipped default severity of every rule — DERIVED from the schema, never
|
|
52
|
+
* listed twice.
|
|
53
|
+
*
|
|
54
|
+
* It used to be the literal beside the type, which is exactly the pair that
|
|
55
|
+
* drifts: `rule-meta.test.ts` already cross-checks each rule's documented
|
|
56
|
+
* `defaultSeverity` against this object, and it could only ever catch a doc that
|
|
57
|
+
* disagreed with the literal, never a literal that disagreed with what parsing
|
|
58
|
+
* actually produced. Reading it out of the parser closes that gap: this IS what
|
|
59
|
+
* a `{}` config loads as.
|
|
60
|
+
*/
|
|
61
|
+
exports.DEFAULT_RULES = defaultConfig()
|
|
62
|
+
.rules;
|
|
63
|
+
/** The default rule markers — read from the schema, like every other default. */
|
|
64
|
+
const DEFAULT_MARKERS = defaultConfig().ruleMarkers;
|
|
65
|
+
/** A freshly parsed empty config — the defaults, straight from the schema. */
|
|
66
|
+
function defaultConfig() {
|
|
67
|
+
return config_schema_js_1.vigilesConfigSchema.parse({});
|
|
68
|
+
}
|
|
131
69
|
// ---------------------------------------------------------------------------
|
|
132
70
|
// Instruction file discovery
|
|
133
71
|
// ---------------------------------------------------------------------------
|
|
@@ -139,41 +77,7 @@ function findInstructionFiles(cwd = process.cwd(), configFiles) {
|
|
|
139
77
|
// Config loading
|
|
140
78
|
// ---------------------------------------------------------------------------
|
|
141
79
|
/**
|
|
142
|
-
*
|
|
143
|
-
* vigiles's `"warn" | "error" | false` so a rule the user meant to DISABLE
|
|
144
|
-
* (`"off"`) or GATE (`2`) actually does — instead of a truthy string / number
|
|
145
|
-
* silently rendering as a non-gating warn (issue #112 + numeric severities). The
|
|
146
|
-
* array form `[sev, opts]` recurses on the head. An unrecognized value is left
|
|
147
|
-
* as-is (it renders as a warn, the pre-existing behavior).
|
|
148
|
-
*/
|
|
149
|
-
function normalizeSeverity(v) {
|
|
150
|
-
if (Array.isArray(v))
|
|
151
|
-
return [normalizeSeverity(v[0]), v[1]];
|
|
152
|
-
if (v === "off" || v === 0 || v === false)
|
|
153
|
-
return false;
|
|
154
|
-
if (v === "error" || v === 2)
|
|
155
|
-
return "error";
|
|
156
|
-
if (v === "warn" || v === 1)
|
|
157
|
-
return "warn";
|
|
158
|
-
return v;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Coerce a config value that should be a `string[]`: a bare STRING becomes a
|
|
162
|
-
* one-element array (the natural first-value mistake), so `exclude` /
|
|
163
|
-
* `orphans.include` don't silently iterate a string's CHARACTERS as globs — a
|
|
164
|
-
* no-op at best, and garbage "orphan" matches (`.`, `/`, `README.md`) at worst.
|
|
165
|
-
* A non-string/array value falls back with a warning (the `ruleMarkers` pattern).
|
|
166
|
-
*/
|
|
167
|
-
function asStringArray(v, fallback, key) {
|
|
168
|
-
if (typeof v === "string")
|
|
169
|
-
return [v];
|
|
170
|
-
if (Array.isArray(v))
|
|
171
|
-
return v.filter((x) => typeof x === "string");
|
|
172
|
-
console.warn(`Invalid ${key} in config: expected a string or string[], got ${JSON.stringify(v)}. Ignoring.`);
|
|
173
|
-
return fallback;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Read `.vigilesrc.json`.
|
|
80
|
+
* Read and VALIDATE `.vigilesrc.json`.
|
|
177
81
|
*
|
|
178
82
|
* 🔴 `searchFrom` IS NOT A CONVENIENCE. cosmiconfig defaults to the process's
|
|
179
83
|
* working directory and walks up — right for a CLI verb, where the user is
|
|
@@ -184,61 +88,69 @@ function asStringArray(v, fallback, key) {
|
|
|
184
88
|
* "off" was never found. Nothing in the output distinguishes that from a project
|
|
185
89
|
* that never configured the rule.
|
|
186
90
|
*
|
|
187
|
-
*
|
|
91
|
+
* 🔴 `onInvalid` IS THE ONE PLACE THE CALL SITES DIFFER, AND IT IS NOT A SPLIT
|
|
92
|
+
* IN WHAT IS CHECKED. Every reader validates; they disagree only about what a
|
|
93
|
+
* bad config is allowed to do to them. A VERB is a human standing at a prompt
|
|
94
|
+
* having just edited the file — `"throw"`, so the line they typed is refused out
|
|
95
|
+
* loud. A HOOK RAIL is a fresh process inside somebody's editing session, and a
|
|
96
|
+
* hook that dies on a malformed config turns a typo in a JSON file into a failed
|
|
97
|
+
* edit, which is a worse outcome than the nudge not firing — `"warn"`, print the
|
|
98
|
+
* same lines to stderr and carry on with the defaults.
|
|
99
|
+
*
|
|
100
|
+
* The two hook rails are `refsHookCommand` and `evalLockNudgeHookCommand`
|
|
101
|
+
* (`vigiles hook-runtime refs` / `eval-lock-nudge`, both registered as
|
|
102
|
+
* PostToolUse `Edit|Write` in `.claude-plugin/plugin.json`). Every other reader
|
|
103
|
+
* is a verb.
|
|
104
|
+
*
|
|
105
|
+
* ⚠️ THE SCHEMA MODULE IS REQUIRED IN-BODY, and that is hygiene rather than an
|
|
106
|
+
* optimization worth a paragraph: Zod is a ~45 ms cold import, `tsc` emits
|
|
107
|
+
* CommonJS across 230 separate files, so a `require` in a function body genuinely
|
|
108
|
+
* does not execute until the function is called. A rail that never reads a
|
|
109
|
+
* config never pays for the validator.
|
|
188
110
|
*/
|
|
189
|
-
function loadConfig(searchFrom) {
|
|
111
|
+
function loadConfig(searchFrom, { onInvalid = "throw" } = {}) {
|
|
112
|
+
let raw;
|
|
190
113
|
try {
|
|
191
114
|
const explorer = (0, cosmiconfig_1.cosmiconfigSync)("vigiles", {
|
|
192
115
|
searchPlaces: [".vigilesrc.json"],
|
|
193
116
|
mergeSearchPlaces: false,
|
|
194
117
|
});
|
|
195
|
-
|
|
196
|
-
if (!result?.config)
|
|
197
|
-
return { ...DEFAULT_CONFIG };
|
|
198
|
-
const userConfig = result.config;
|
|
199
|
-
const config = {
|
|
200
|
-
...DEFAULT_CONFIG,
|
|
201
|
-
...userConfig,
|
|
202
|
-
// Normalize ESLint-idiom severities ("off"/0/1/2) across the merged rules,
|
|
203
|
-
// so a config value coerces to a real gating decision instead of a truthy
|
|
204
|
-
// string silently downgrading to warn.
|
|
205
|
-
rules: Object.fromEntries(Object.entries({ ...exports.DEFAULT_RULES, ...userConfig.rules }).map(([k, v]) => [k, normalizeSeverity(v)])),
|
|
206
|
-
files: Array.isArray(userConfig.files) ? userConfig.files : DEFAULT_FILES,
|
|
207
|
-
};
|
|
208
|
-
// Parse-don't-validate the array-shaped keys ONCE here: a bare string is
|
|
209
|
-
// accepted as [string]; anything else warns + falls back. Downstream code
|
|
210
|
-
// then always sees a real string[] (no char-by-char glob spread).
|
|
211
|
-
if (userConfig.exclude !== undefined)
|
|
212
|
-
config.exclude = asStringArray(userConfig.exclude, [], "exclude");
|
|
213
|
-
if (userConfig.sharedDirs !== undefined)
|
|
214
|
-
config.sharedDirs = asStringArray(userConfig.sharedDirs, [], "sharedDirs");
|
|
215
|
-
if (config.orphans) {
|
|
216
|
-
config.orphans = {
|
|
217
|
-
...config.orphans,
|
|
218
|
-
include: config.orphans.include === undefined
|
|
219
|
-
? undefined
|
|
220
|
-
: asStringArray(config.orphans.include, [], "orphans.include"),
|
|
221
|
-
exclude: config.orphans.exclude === undefined
|
|
222
|
-
? undefined
|
|
223
|
-
: asStringArray(config.orphans.exclude, [], "orphans.exclude"),
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
if (!Array.isArray(config.ruleMarkers) ||
|
|
227
|
-
!config.ruleMarkers.every((m) => VALID_MARKERS.includes(m))) {
|
|
228
|
-
console.warn(`Invalid ruleMarkers in config: ${JSON.stringify(config.ruleMarkers)}. Using default.`);
|
|
229
|
-
config.ruleMarkers = [...DEFAULT_CONFIG.ruleMarkers];
|
|
230
|
-
}
|
|
231
|
-
return config;
|
|
118
|
+
raw = explorer.search(searchFrom)?.config;
|
|
232
119
|
}
|
|
233
120
|
catch {
|
|
234
|
-
|
|
121
|
+
// No file, unreadable file, malformed JSON — the defaults, as always. A
|
|
122
|
+
// config we CAN read and refuse is a different thing and is handled below.
|
|
123
|
+
return defaultConfig();
|
|
124
|
+
}
|
|
125
|
+
if (raw === undefined || raw === null)
|
|
126
|
+
return defaultConfig();
|
|
127
|
+
// The replaced keys get their own message BEFORE the schema's, because the
|
|
128
|
+
// reader is someone whose config used to work: `.strict()` would tell them
|
|
129
|
+
// "unknown key harness", which is true and useless. See REPLACED_KEYS.
|
|
130
|
+
const problems = [];
|
|
131
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
132
|
+
const present = config_schema_js_1.REPLACED_KEYS.filter((k) => k.key in raw);
|
|
133
|
+
if (present.length > 0)
|
|
134
|
+
problems.push((0, config_schema_js_1.replacedKeyMessage)(present));
|
|
135
|
+
}
|
|
136
|
+
if (problems.length === 0) {
|
|
137
|
+
const parsed = config_schema_js_1.vigilesConfigSchema.safeParse(raw);
|
|
138
|
+
if (parsed.success)
|
|
139
|
+
return parsed.data;
|
|
140
|
+
problems.push(...(0, config_schema_js_1.formatConfigIssues)(parsed.error.issues));
|
|
235
141
|
}
|
|
142
|
+
if (onInvalid === "throw")
|
|
143
|
+
throw new config_schema_js_1.VigilesConfigError(problems.join("\n"));
|
|
144
|
+
for (const line of problems)
|
|
145
|
+
console.warn(`⚠ ${line}`);
|
|
146
|
+
console.warn("⚠ Using default configuration.");
|
|
147
|
+
return defaultConfig();
|
|
236
148
|
}
|
|
237
149
|
// ---------------------------------------------------------------------------
|
|
238
150
|
// Parsing
|
|
239
151
|
// ---------------------------------------------------------------------------
|
|
240
152
|
function parseRules(content, { ruleMarkers } = {}) {
|
|
241
|
-
const markers = ruleMarkers ??
|
|
153
|
+
const markers = ruleMarkers ?? DEFAULT_MARKERS;
|
|
242
154
|
const lines = content.split("\n");
|
|
243
155
|
const rules = [];
|
|
244
156
|
let currentRule = null;
|
package/dist/exclude.d.ts
CHANGED
|
@@ -22,4 +22,24 @@ export interface ExcludeSet {
|
|
|
22
22
|
}
|
|
23
23
|
/** Parse `.vigilesrc.json#exclude` once, against `root`. */
|
|
24
24
|
export declare function excludeSet(root: string, patterns: readonly string[] | undefined): ExcludeSet;
|
|
25
|
+
/**
|
|
26
|
+
* Is this ABSOLUTE path dropped by `.vigilesrc.json#exclude`?
|
|
27
|
+
*
|
|
28
|
+
* The PREDICATE face, for a hand-rolled recursive walk that has an absolute path
|
|
29
|
+
* in hand and no glob (`readTree` in `src/plugin-loader.ts`, the bounded root
|
|
30
|
+
* walk in `src/surface-discovery-fs.ts`). A predicate rather than an
|
|
31
|
+
* `ExcludeSet` so the walk never has to remember which root the patterns are
|
|
32
|
+
* relative to: {@link excludedBy} closes over `excludes.root`, which is the REPO
|
|
33
|
+
* root and NOT the audited dir — `vigiles audit some/dir` must still honour a
|
|
34
|
+
* root-relative `exclude`.
|
|
35
|
+
*
|
|
36
|
+
* Lives HERE, beside the other two faces, because this file is the one
|
|
37
|
+
* exclusion policy (#192) and this was its third face living in one caller's
|
|
38
|
+
* private scope — which is how a second walk ends up writing a fourth.
|
|
39
|
+
*/
|
|
40
|
+
export type Excluded = (absPath: string) => boolean;
|
|
41
|
+
/** The default: exclude nothing (every caller that passes no `ExcludeSet`). */
|
|
42
|
+
export declare const excludesNothing: Excluded;
|
|
43
|
+
/** The {@link Excluded} face of an `ExcludeSet`, or {@link excludesNothing}. */
|
|
44
|
+
export declare function excludedBy(excludes: ExcludeSet | undefined): Excluded;
|
|
25
45
|
//# sourceMappingURL=exclude.d.ts.map
|
package/dist/exclude.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EXCLUDE_FLOOR = void 0;
|
|
3
|
+
exports.excludesNothing = exports.EXCLUDE_FLOOR = void 0;
|
|
4
4
|
exports.excludeSet = excludeSet;
|
|
5
|
+
exports.excludedBy = excludedBy;
|
|
5
6
|
/**
|
|
6
7
|
* The ONE exclusion policy for every walk that polices the user's repository (#192) — the parsed `.vigilesrc.json#exclude` as an `ExcludeSet`, built ONCE where `loadConfig()` runs and taken as a REQUIRED parameter by every in-scope discovery (`findSpecs`, `findInstructionFiles`, `discoverNestedBundles`, `collectDocumentedRules`, `gatherInstructionFiles` in cli.ts; the string face handed to `findDocRefs`, `findOrphanDocs` (`repoExclude`), `findUntestedSurfaces`/`skillTestNudge`, `discoverScripts`, `computeScriptCoverage`).
|
|
7
8
|
* Two faces: `globIgnore` (an `IgnoreLike` keyed on the path's position relative to the REPO root, so a glob rooted below it — `vigiles lint some/dir` — still applies a root-relative exclude) and `ignore` (the normalized string list for pure core detectors that glob from the root).
|
|
@@ -65,4 +66,13 @@ function excludeSet(root, patterns) {
|
|
|
65
66
|
explain,
|
|
66
67
|
};
|
|
67
68
|
}
|
|
69
|
+
/** The default: exclude nothing (every caller that passes no `ExcludeSet`). */
|
|
70
|
+
const excludesNothing = () => false;
|
|
71
|
+
exports.excludesNothing = excludesNothing;
|
|
72
|
+
/** The {@link Excluded} face of an `ExcludeSet`, or {@link excludesNothing}. */
|
|
73
|
+
function excludedBy(excludes) {
|
|
74
|
+
if (!excludes)
|
|
75
|
+
return exports.excludesNothing;
|
|
76
|
+
return (abs) => excludes.matches((0, node_path_1.relative)(excludes.root, abs));
|
|
77
|
+
}
|
|
68
78
|
//# sourceMappingURL=exclude.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginLayout } from "./core/layout.js";
|
|
2
|
+
/** Every SHIPPED harness layout, in registry order. */
|
|
3
|
+
export declare const REGISTERED_LAYOUTS: readonly PluginLayout[];
|
|
4
|
+
/**
|
|
5
|
+
* "Is this repo-relative path read by some harness vigiles knows about?" — one
|
|
6
|
+
* predicate per registered layout, the input to `unclaimedSurfaces`.
|
|
7
|
+
*
|
|
8
|
+
* Note it is the layouts of every REGISTERED harness, not of the DETECTED one.
|
|
9
|
+
* A repo carrying both `.claude/skills` and `.agents/skills` has each half
|
|
10
|
+
* claimed by a different harness and neither is a finding; a repo whose skills
|
|
11
|
+
* sit under `.ai/` has them claimed by nobody, and that is the finding (#240).
|
|
12
|
+
*/
|
|
13
|
+
export declare const registeredClaims: ReadonlyArray<(path: string) => boolean>;
|
|
14
|
+
//# sourceMappingURL=layout-registry.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registeredClaims = exports.REGISTERED_LAYOUTS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The registered harnesses' LAYOUTS, as pure data — the claim side of surface
|
|
6
|
+
* discovery, available where the adapter bundle is not.
|
|
7
|
+
*
|
|
8
|
+
* 🔴 WHY THIS IS NOT JUST `ADAPTERS.map(a => a.layout)`. `src/adapter-registry.ts`
|
|
9
|
+
* imports the full `HarnessAdapter` bundles, and an adapter's `detect(root)` does
|
|
10
|
+
* real filesystem work — so that module pulls `node:fs` in. `src/scan-files.ts`
|
|
11
|
+
* is the BROWSER-SAFE twin of the scan ("NO filesystem, NO child_process, NO disk
|
|
12
|
+
* I/O at all") and needs exactly one thing from the registry: which paths a
|
|
13
|
+
* shipped harness reads. A `PluginLayout` is a plain object of strings, so this
|
|
14
|
+
* list is importable from both sides.
|
|
15
|
+
*
|
|
16
|
+
* The drift that split lists invite is CHECKED, not hoped for:
|
|
17
|
+
* `src/layout-registry.test.ts` asserts this list is exactly the layouts the
|
|
18
|
+
* `ADAPTERS` registry carries, and that every adapter's `claims` agrees with
|
|
19
|
+
* `layoutClaims` over its own layout — so adding a harness to one registry and
|
|
20
|
+
* not the other fails a test instead of quietly halving discovery.
|
|
21
|
+
*/
|
|
22
|
+
const layout_js_1 = require("./adapters/claude-code/layout.js");
|
|
23
|
+
const layout_js_2 = require("./adapters/codex/layout.js");
|
|
24
|
+
const surface_discovery_js_1 = require("./core/surface-discovery.js");
|
|
25
|
+
/** Every SHIPPED harness layout, in registry order. */
|
|
26
|
+
exports.REGISTERED_LAYOUTS = [
|
|
27
|
+
layout_js_1.claudeCodeLayout,
|
|
28
|
+
layout_js_2.codexLayout,
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* "Is this repo-relative path read by some harness vigiles knows about?" — one
|
|
32
|
+
* predicate per registered layout, the input to `unclaimedSurfaces`.
|
|
33
|
+
*
|
|
34
|
+
* Note it is the layouts of every REGISTERED harness, not of the DETECTED one.
|
|
35
|
+
* A repo carrying both `.claude/skills` and `.agents/skills` has each half
|
|
36
|
+
* claimed by a different harness and neither is a finding; a repo whose skills
|
|
37
|
+
* sit under `.ai/` has them claimed by nobody, and that is the finding (#240).
|
|
38
|
+
*/
|
|
39
|
+
exports.registeredClaims = exports.REGISTERED_LAYOUTS.map((l) => (path) => (0, surface_discovery_js_1.layoutClaims)(l, path));
|
|
40
|
+
//# sourceMappingURL=layout-registry.js.map
|
package/dist/plugin-loader.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PluginLayout } from "./core/layout.js";
|
|
2
|
+
import type { ExcludeSet } from "./exclude.js";
|
|
2
3
|
export interface LoadedPlugin {
|
|
3
4
|
/** A `.claude/settings.json`-shaped object with hooks resolved. */
|
|
4
5
|
readonly settings: {
|
|
@@ -29,8 +30,55 @@ export interface LoadedPlugin {
|
|
|
29
30
|
* the files (CLAUDE.md + skills + agents + commands) to write into the test
|
|
30
31
|
* sandbox, and `warnings` for surfaces the deterministic tier can't drive. Merge
|
|
31
32
|
* `settings` with any inline settings and spread `files` into the fixture.
|
|
33
|
+
*
|
|
34
|
+
* `surfaceRoots` is this function's parameter name for what the repo owner
|
|
35
|
+
* writes as `.vigilesrc.json#harnesses["<name>"].roots` — extra repo-relative
|
|
36
|
+
* bases to read `<base>/<surfaceDir>/…` from, for a repo that keeps its skills
|
|
37
|
+
* somewhere no harness reads by default. The config key is nested UNDER a
|
|
38
|
+
* harness name precisely so a root cannot be declared without saying whose
|
|
39
|
+
* layout reads it; this parameter receives one harness's slice of that, which
|
|
40
|
+
* is why it is still a bare list here. `excludes` still wins over it:
|
|
41
|
+
* both the per-tree check in {@link materializeSurfaces} and `readTree` drop an
|
|
42
|
+
* excluded path whatever declared it, so a root that is declared AND excluded is
|
|
43
|
+
* read exactly as if it had never been declared.
|
|
32
44
|
*/
|
|
33
|
-
export declare function loadPlugin(pluginPath: string, layout: PluginLayout): LoadedPlugin;
|
|
45
|
+
export declare function loadPlugin(pluginPath: string, layout: PluginLayout, excludes?: ExcludeSet, surfaceRoots?: readonly string[]): LoadedPlugin;
|
|
46
|
+
/**
|
|
47
|
+
* ONE declared harness for {@link loadPlugins}: the layout to read the repo
|
|
48
|
+
* under, and the extra roots declared for THAT harness.
|
|
49
|
+
*/
|
|
50
|
+
export interface HarnessLoad {
|
|
51
|
+
readonly layout: PluginLayout;
|
|
52
|
+
readonly roots?: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Load the repo once PER DECLARED HARNESS and merge the results into one
|
|
56
|
+
* `LoadedPlugin` (#240).
|
|
57
|
+
*
|
|
58
|
+
* 🔴 THE MERGE IS WHY THIS EXISTS, AND THE DEDUPLICATION IS ITS WHOLE CONTRACT.
|
|
59
|
+
* A repo that declares two harnesses is a repo whose grade must cover both — the
|
|
60
|
+
* flat `harness` array could not do that, because whichever name sat first
|
|
61
|
+
* decided the ONE layout everything was read under: measured on a repo with
|
|
62
|
+
* `AGENTS.md` + `.ai/skills/alpha/SKILL.md`, Claude-Code-first graded the skill
|
|
63
|
+
* and reported 0 chars of always-loaded instructions, Codex-first read
|
|
64
|
+
* `AGENTS.md` and reported no skill at all. Neither order produced both halves.
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ AND THE OBVIOUS FIX HAS AN OBVIOUS SECOND BUG: a repo honest enough to say
|
|
67
|
+
* one tree serves both tools would then have that tree read twice and every
|
|
68
|
+
* skill in it counted twice, so declaring the truth would lower the grade. So the
|
|
69
|
+
* merge keys on the REAL ON-DISK PATH (`sources`), not on the materialized key:
|
|
70
|
+
* the first harness to claim a file keeps it, later ones skip it, and the counts
|
|
71
|
+
* are of files rather than of claims. Keying on the materialized key would not
|
|
72
|
+
* do — two layouts can reach one file under two different keys (`.ai/.agents`
|
|
73
|
+
* + `skills` and `.ai` + `.agents/skills` are the same directory), and that is
|
|
74
|
+
* exactly the case an honest dual declaration produces.
|
|
75
|
+
*
|
|
76
|
+
* Settings come from the FIRST harness that yields any, and the file-map merge
|
|
77
|
+
* is first-wins for the same reason: the primary harness is the one whose
|
|
78
|
+
* dialect the report is rendered in, so its reading of a shared path is the one
|
|
79
|
+
* the rest of the report is consistent with.
|
|
80
|
+
*/
|
|
81
|
+
export declare function loadPlugins(pluginPath: string, harnesses: readonly HarnessLoad[], excludes?: ExcludeSet): LoadedPlugin;
|
|
34
82
|
export declare function danglingRefs(root: string, layout: PluginLayout): string[];
|
|
35
83
|
/**
|
|
36
84
|
* Resolve the effective harness for a test/eval (arm): load the plugin if given,
|