vigiles 27.3.0 → 29.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +9 -2
- 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 +7 -2
- 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 +162 -39
- package/dist/core/adapter.d.ts +45 -1
- package/dist/core/compile.js +11 -1
- package/dist/core/config-schema.d.ts +244 -0
- package/dist/core/config-schema.js +452 -0
- package/dist/core/hook-program.d.ts +43 -0
- package/dist/core/hook-program.js +32 -0
- package/dist/core/refs.js +10 -1
- package/dist/core/surface-discovery.d.ts +270 -0
- package/dist/core/surface-discovery.js +425 -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 +46 -18
- package/dist/core/validate.js +98 -172
- package/dist/exclude.d.ts +20 -0
- package/dist/exclude.js +11 -1
- package/dist/harness-test.js +3 -3
- package/dist/hook-install.d.ts +53 -0
- package/dist/hook-install.js +60 -0
- package/dist/hook-runtime.d.ts +2 -2
- package/dist/hook-runtime.js +82 -63
- package/dist/layout-registry.d.ts +14 -0
- package/dist/layout-registry.js +40 -0
- package/dist/load-hook.d.ts +1 -1
- package/dist/load-hook.js +2 -2
- package/dist/plugin-loader.d.ts +49 -1
- package/dist/plugin-loader.js +120 -14
- package/dist/run-hook.js +17 -1
- 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 +68 -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/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,92 +77,80 @@ function findInstructionFiles(cwd = process.cwd(), configFiles) {
|
|
|
139
77
|
// Config loading
|
|
140
78
|
// ---------------------------------------------------------------------------
|
|
141
79
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
80
|
+
* Read and VALIDATE `.vigilesrc.json`.
|
|
81
|
+
*
|
|
82
|
+
* 🔴 `searchFrom` IS NOT A CONVENIENCE. cosmiconfig defaults to the process's
|
|
83
|
+
* working directory and walks up — right for a CLI verb, where the user is
|
|
84
|
+
* standing in the project they mean, and wrong for a hook, whose process has no
|
|
85
|
+
* stable cwd. A hook rail that omits it reads a DIFFERENT project's config, or
|
|
86
|
+
* none, and the failure runs the wrong way: a missing file means defaults, so a
|
|
87
|
+
* rule the author switched OFF comes back on, silently, because the file saying
|
|
88
|
+
* "off" was never found. Nothing in the output distinguishes that from a project
|
|
89
|
+
* that never configured the rule.
|
|
90
|
+
*
|
|
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.
|
|
148
110
|
*/
|
|
149
|
-
function
|
|
150
|
-
|
|
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
|
-
function loadConfig() {
|
|
111
|
+
function loadConfig(searchFrom, { onInvalid = "throw" } = {}) {
|
|
112
|
+
let raw;
|
|
176
113
|
try {
|
|
177
114
|
const explorer = (0, cosmiconfig_1.cosmiconfigSync)("vigiles", {
|
|
178
115
|
searchPlaces: [".vigilesrc.json"],
|
|
179
116
|
mergeSearchPlaces: false,
|
|
180
117
|
});
|
|
181
|
-
|
|
182
|
-
if (!result?.config)
|
|
183
|
-
return { ...DEFAULT_CONFIG };
|
|
184
|
-
const userConfig = result.config;
|
|
185
|
-
const config = {
|
|
186
|
-
...DEFAULT_CONFIG,
|
|
187
|
-
...userConfig,
|
|
188
|
-
// Normalize ESLint-idiom severities ("off"/0/1/2) across the merged rules,
|
|
189
|
-
// so a config value coerces to a real gating decision instead of a truthy
|
|
190
|
-
// string silently downgrading to warn.
|
|
191
|
-
rules: Object.fromEntries(Object.entries({ ...exports.DEFAULT_RULES, ...userConfig.rules }).map(([k, v]) => [k, normalizeSeverity(v)])),
|
|
192
|
-
files: Array.isArray(userConfig.files) ? userConfig.files : DEFAULT_FILES,
|
|
193
|
-
};
|
|
194
|
-
// Parse-don't-validate the array-shaped keys ONCE here: a bare string is
|
|
195
|
-
// accepted as [string]; anything else warns + falls back. Downstream code
|
|
196
|
-
// then always sees a real string[] (no char-by-char glob spread).
|
|
197
|
-
if (userConfig.exclude !== undefined)
|
|
198
|
-
config.exclude = asStringArray(userConfig.exclude, [], "exclude");
|
|
199
|
-
if (userConfig.sharedDirs !== undefined)
|
|
200
|
-
config.sharedDirs = asStringArray(userConfig.sharedDirs, [], "sharedDirs");
|
|
201
|
-
if (config.orphans) {
|
|
202
|
-
config.orphans = {
|
|
203
|
-
...config.orphans,
|
|
204
|
-
include: config.orphans.include === undefined
|
|
205
|
-
? undefined
|
|
206
|
-
: asStringArray(config.orphans.include, [], "orphans.include"),
|
|
207
|
-
exclude: config.orphans.exclude === undefined
|
|
208
|
-
? undefined
|
|
209
|
-
: asStringArray(config.orphans.exclude, [], "orphans.exclude"),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (!Array.isArray(config.ruleMarkers) ||
|
|
213
|
-
!config.ruleMarkers.every((m) => VALID_MARKERS.includes(m))) {
|
|
214
|
-
console.warn(`Invalid ruleMarkers in config: ${JSON.stringify(config.ruleMarkers)}. Using default.`);
|
|
215
|
-
config.ruleMarkers = [...DEFAULT_CONFIG.ruleMarkers];
|
|
216
|
-
}
|
|
217
|
-
return config;
|
|
118
|
+
raw = explorer.search(searchFrom)?.config;
|
|
218
119
|
}
|
|
219
120
|
catch {
|
|
220
|
-
|
|
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));
|
|
221
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();
|
|
222
148
|
}
|
|
223
149
|
// ---------------------------------------------------------------------------
|
|
224
150
|
// Parsing
|
|
225
151
|
// ---------------------------------------------------------------------------
|
|
226
152
|
function parseRules(content, { ruleMarkers } = {}) {
|
|
227
|
-
const markers = ruleMarkers ??
|
|
153
|
+
const markers = ruleMarkers ?? DEFAULT_MARKERS;
|
|
228
154
|
const lines = content.split("\n");
|
|
229
155
|
const rules = [];
|
|
230
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
|
package/dist/harness-test.js
CHANGED
|
@@ -430,7 +430,7 @@ async function runHarnessTest(spec, opts = {}) {
|
|
|
430
430
|
// Default (no adapter): the unchanged Claude Code driver — keeps the
|
|
431
431
|
// sandbox/confined path and behaviour byte-for-byte identical.
|
|
432
432
|
const driver = adapter
|
|
433
|
-
? requireDriver(adapter)
|
|
433
|
+
? await requireDriver(adapter)
|
|
434
434
|
: exports.claudeCodeDriver;
|
|
435
435
|
const isClaudeCode = driver.runtime.name === runtime_js_1.claudeCodeRuntime.name;
|
|
436
436
|
const decision = (0, sandbox_js_1.decideSandbox)({
|
|
@@ -528,12 +528,12 @@ async function runHarness(spec, opts = {}) {
|
|
|
528
528
|
return runHarnessTest(spec, opts);
|
|
529
529
|
}
|
|
530
530
|
/** Pull the pillar-2 driver off an adapter, asserting it supports testing. */
|
|
531
|
-
function requireDriver(adapter) {
|
|
531
|
+
async function requireDriver(adapter) {
|
|
532
532
|
(0, adapter_conformance_js_1.assertHarnessTestable)(adapter);
|
|
533
533
|
if (!adapter.harnessTestDriver) {
|
|
534
534
|
throw new Error(`Adapter "${adapter.name}" declares harnessTesting but carries no harnessTestDriver — it cannot drive runHarnessTest.`);
|
|
535
535
|
}
|
|
536
|
-
return adapter.harnessTestDriver;
|
|
536
|
+
return await adapter.harnessTestDriver();
|
|
537
537
|
}
|
|
538
538
|
/* v8 ignore stop */
|
|
539
539
|
//# sourceMappingURL=harness-test.js.map
|
package/dist/hook-install.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DispatchKind } from "./core/hook-program.js";
|
|
1
2
|
/** The agnostic, committed home for hook SOURCE — one dir, cross-adapter. */
|
|
2
3
|
export declare const HOOKS_DIR = ".vigiles/hooks";
|
|
3
4
|
/** The committed home for registered context-provider SOURCE (v2). */
|
|
@@ -55,6 +56,58 @@ export declare function normalizeHookRef(hookPath: string, cwd?: string): string
|
|
|
55
56
|
* `bareToken(hookGateRef(ref, tokens)) === ref` is what keeps a recompile idempotent, and
|
|
56
57
|
* it is asserted directly rather than left to inspection.
|
|
57
58
|
*/
|
|
59
|
+
/**
|
|
60
|
+
* Where the hook runtime lives, spelled so the shell can find it WITHOUT `npx`.
|
|
61
|
+
*
|
|
62
|
+
* 🔴 MEASURED 2026-09-19, warm cache, five runs each:
|
|
63
|
+
*
|
|
64
|
+
* node <local>/dist/cli.js hook-runtime run-program … 193 ms
|
|
65
|
+
* npx vigiles hook-runtime run-program … 2545 ms
|
|
66
|
+
*
|
|
67
|
+
* Thirteen times, on every tool call, because `npx` re-resolves the package on
|
|
68
|
+
* each invocation — local, then global, then the registry. That search is the
|
|
69
|
+
* single largest cost in a hook's life; everything the runtime does inside adds
|
|
70
|
+
* up to less than a fifth of it.
|
|
71
|
+
*
|
|
72
|
+
* A harness with no project-root token gets the relative spelling, which is all
|
|
73
|
+
* it can be given — see {@link hookGateRef} for the same fallback.
|
|
74
|
+
*/
|
|
75
|
+
export declare function hookRuntimeRef(projectRootTokens: readonly string[] | undefined): string;
|
|
76
|
+
/**
|
|
77
|
+
* What the shell must do when the runtime above CANNOT START — a missing
|
|
78
|
+
* `node_modules/vigiles`, an unreadable file, an interpreter that dies before a
|
|
79
|
+
* single line of ours runs. No code of ours executes in that case, so the policy
|
|
80
|
+
* has to be expressed in the emitted command or not at all.
|
|
81
|
+
*
|
|
82
|
+
* 🔴 THIS IS NOT A NEW POLICY. `runHookProgramCommand`'s load-failure branch has
|
|
83
|
+
* decided it since 2026-08: *"an inject's purpose is to ADD context, not to
|
|
84
|
+
* ENFORCE a decision … Gates (file, bash, prompt, stop) remain conservative and
|
|
85
|
+
* fail closed."* That branch only reaches failures that happen AFTER the runtime
|
|
86
|
+
* starts. This carries the same rule one layer out, to the failures that happen
|
|
87
|
+
* before it.
|
|
88
|
+
*
|
|
89
|
+
* WHY THE SPLIT, RATHER THAN ONE ANSWER FOR EVERYTHING — the two failures are
|
|
90
|
+
* not comparable:
|
|
91
|
+
*
|
|
92
|
+
* A GATE THAT SILENTLY PASSES IS WORSE THAN NO GATE. Its whole value is the
|
|
93
|
+
* refusal, and a harness that reports protection it is not providing is the
|
|
94
|
+
* one state worse than admitting it has none. So a gate whose runtime is
|
|
95
|
+
* missing exits 2: loud, blocking, and the cause is on stderr.
|
|
96
|
+
*
|
|
97
|
+
* A NUDGE THAT BLOCKS COSTS THE WHOLE REPOSITORY. Measured here 2026-08-10:
|
|
98
|
+
* merge-conflict markers in `package.json` stopped every hook loading, the
|
|
99
|
+
* Bash gate then refused `git merge --abort` — the one command that undoes the
|
|
100
|
+
* cause — and the session could not be repaired from inside. A reminder is
|
|
101
|
+
* never worth that, so a nudge exits 0 and says nothing it cannot say.
|
|
102
|
+
*
|
|
103
|
+
* The role is not a flag someone can flip: `Reaction` has no `deny` and an
|
|
104
|
+
* inject returns context, so "nudge" is a fact about the TYPE the author chose.
|
|
105
|
+
*
|
|
106
|
+
* (Industry does not agree on one answer either — husky and lefthook skip,
|
|
107
|
+
* pre-commit fails. Which is itself the argument for deciding by role instead
|
|
108
|
+
* of picking one and imposing it on both.)
|
|
109
|
+
*/
|
|
110
|
+
export declare function hookRuntimeMissingExit(kind: DispatchKind): 0 | 2;
|
|
58
111
|
export declare function hookGateRef(ref: string, projectRootTokens: readonly string[] | undefined): string;
|
|
59
112
|
/**
|
|
60
113
|
* Idempotently merge a compiled hook's block into an existing `settings.json`
|
package/dist/hook-install.js
CHANGED
|
@@ -4,6 +4,8 @@ exports.PROVIDERS_DIR = exports.HOOKS_DIR = void 0;
|
|
|
4
4
|
exports.discoverHookFiles = discoverHookFiles;
|
|
5
5
|
exports.discoverProviderFiles = discoverProviderFiles;
|
|
6
6
|
exports.normalizeHookRef = normalizeHookRef;
|
|
7
|
+
exports.hookRuntimeRef = hookRuntimeRef;
|
|
8
|
+
exports.hookRuntimeMissingExit = hookRuntimeMissingExit;
|
|
7
9
|
exports.hookGateRef = hookGateRef;
|
|
8
10
|
exports.mergeHooksJson = mergeHooksJson;
|
|
9
11
|
exports.mergeHooksToml = mergeHooksToml;
|
|
@@ -99,6 +101,64 @@ function normalizeHookRef(hookPath, cwd = process.cwd()) {
|
|
|
99
101
|
* `bareToken(hookGateRef(ref, tokens)) === ref` is what keeps a recompile idempotent, and
|
|
100
102
|
* it is asserted directly rather than left to inspection.
|
|
101
103
|
*/
|
|
104
|
+
/**
|
|
105
|
+
* Where the hook runtime lives, spelled so the shell can find it WITHOUT `npx`.
|
|
106
|
+
*
|
|
107
|
+
* 🔴 MEASURED 2026-09-19, warm cache, five runs each:
|
|
108
|
+
*
|
|
109
|
+
* node <local>/dist/cli.js hook-runtime run-program … 193 ms
|
|
110
|
+
* npx vigiles hook-runtime run-program … 2545 ms
|
|
111
|
+
*
|
|
112
|
+
* Thirteen times, on every tool call, because `npx` re-resolves the package on
|
|
113
|
+
* each invocation — local, then global, then the registry. That search is the
|
|
114
|
+
* single largest cost in a hook's life; everything the runtime does inside adds
|
|
115
|
+
* up to less than a fifth of it.
|
|
116
|
+
*
|
|
117
|
+
* A harness with no project-root token gets the relative spelling, which is all
|
|
118
|
+
* it can be given — see {@link hookGateRef} for the same fallback.
|
|
119
|
+
*/
|
|
120
|
+
function hookRuntimeRef(projectRootTokens) {
|
|
121
|
+
const rel = "node_modules/vigiles/dist/cli.js";
|
|
122
|
+
const token = projectRootTokens?.[0];
|
|
123
|
+
return token === undefined ? `node ${rel}` : `node "${token}/${rel}"`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* What the shell must do when the runtime above CANNOT START — a missing
|
|
127
|
+
* `node_modules/vigiles`, an unreadable file, an interpreter that dies before a
|
|
128
|
+
* single line of ours runs. No code of ours executes in that case, so the policy
|
|
129
|
+
* has to be expressed in the emitted command or not at all.
|
|
130
|
+
*
|
|
131
|
+
* 🔴 THIS IS NOT A NEW POLICY. `runHookProgramCommand`'s load-failure branch has
|
|
132
|
+
* decided it since 2026-08: *"an inject's purpose is to ADD context, not to
|
|
133
|
+
* ENFORCE a decision … Gates (file, bash, prompt, stop) remain conservative and
|
|
134
|
+
* fail closed."* That branch only reaches failures that happen AFTER the runtime
|
|
135
|
+
* starts. This carries the same rule one layer out, to the failures that happen
|
|
136
|
+
* before it.
|
|
137
|
+
*
|
|
138
|
+
* WHY THE SPLIT, RATHER THAN ONE ANSWER FOR EVERYTHING — the two failures are
|
|
139
|
+
* not comparable:
|
|
140
|
+
*
|
|
141
|
+
* A GATE THAT SILENTLY PASSES IS WORSE THAN NO GATE. Its whole value is the
|
|
142
|
+
* refusal, and a harness that reports protection it is not providing is the
|
|
143
|
+
* one state worse than admitting it has none. So a gate whose runtime is
|
|
144
|
+
* missing exits 2: loud, blocking, and the cause is on stderr.
|
|
145
|
+
*
|
|
146
|
+
* A NUDGE THAT BLOCKS COSTS THE WHOLE REPOSITORY. Measured here 2026-08-10:
|
|
147
|
+
* merge-conflict markers in `package.json` stopped every hook loading, the
|
|
148
|
+
* Bash gate then refused `git merge --abort` — the one command that undoes the
|
|
149
|
+
* cause — and the session could not be repaired from inside. A reminder is
|
|
150
|
+
* never worth that, so a nudge exits 0 and says nothing it cannot say.
|
|
151
|
+
*
|
|
152
|
+
* The role is not a flag someone can flip: `Reaction` has no `deny` and an
|
|
153
|
+
* inject returns context, so "nudge" is a fact about the TYPE the author chose.
|
|
154
|
+
*
|
|
155
|
+
* (Industry does not agree on one answer either — husky and lefthook skip,
|
|
156
|
+
* pre-commit fails. Which is itself the argument for deciding by role instead
|
|
157
|
+
* of picking one and imposing it on both.)
|
|
158
|
+
*/
|
|
159
|
+
function hookRuntimeMissingExit(kind) {
|
|
160
|
+
return kind === "inject" || kind === "react" ? 0 : 2;
|
|
161
|
+
}
|
|
102
162
|
function hookGateRef(ref, projectRootTokens) {
|
|
103
163
|
const token = projectRootTokens?.[0];
|
|
104
164
|
return token === undefined ? ref : `"${token}/${ref}"`;
|
package/dist/hook-runtime.d.ts
CHANGED
|
@@ -45,9 +45,9 @@ import { loadHook } from "./load-hook.js";
|
|
|
45
45
|
*/
|
|
46
46
|
export declare const loadHookProgram: typeof loadHook;
|
|
47
47
|
/** Load a registered provider (`.vigiles/providers/<name>`) → its definition. */
|
|
48
|
-
export declare function loadProvider(file: string): Promise<RegisteredProvider>;
|
|
48
|
+
export declare function loadProvider(file: string, root?: string): Promise<RegisteredProvider>;
|
|
49
49
|
/** Path of the tamper-evident stamp sidecar for a hook file. */
|
|
50
|
-
export declare function hookStampPath(file: string): string;
|
|
50
|
+
export declare function hookStampPath(file: string, root?: string): string;
|
|
51
51
|
/**
|
|
52
52
|
* `vigiles hook-runtime run-program <file>` — the runtime the compiled hooks block
|
|
53
53
|
* points at. Reads the live event on stdin, loads the typed program, verifies
|