vigiles 32.1.0 → 33.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/dist/adapters/claude-code/run-scripts.d.ts +19 -3
- package/dist/adapters/claude-code/run-scripts.js +12 -4
- package/dist/cli-main.js +59 -14
- package/dist/core/hook-program.d.ts +67 -1
- package/dist/core/hook-program.js +74 -2
- package/dist/core/hook-providers.d.ts +1 -1
- package/dist/coverage-evidence.d.ts +1 -3
- package/dist/coverage-evidence.js +7 -35
- package/dist/coverage-probe.d.ts +15 -0
- package/dist/coverage-probe.js +29 -0
- package/dist/eval-define.js +3 -3
- package/dist/eval-lock.js +2 -1
- package/dist/hook-check-install.d.ts +3 -0
- package/dist/hook-check-install.js +57 -0
- package/dist/hook-check.d.ts +36 -0
- package/dist/hook-check.js +125 -0
- package/dist/hook-install.d.ts +93 -40
- package/dist/hook-install.js +235 -54
- package/dist/hook-runtime.d.ts +1 -1
- package/dist/hook-runtime.js +25 -26
- package/dist/hook-state-store.d.ts +1 -1
- package/dist/hook.js +1 -1
- package/dist/load-hook.d.ts +1 -1
- package/dist/load-hook.js +1 -1
- package/dist/local-files.js +1 -0
- package/dist/source-kinds.d.ts +128 -0
- package/dist/source-kinds.js +162 -0
- package/dist/test-coverage-files.js +2 -17
- package/dist/test-coverage.js +11 -8
- package/package.json +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What a file in a vigiles-owned source directory IS — decided in ONE place.
|
|
4
|
+
*
|
|
5
|
+
* `.vigiles/hooks/` and `.vigiles/providers/` hold several kinds of file side by
|
|
6
|
+
* side: the programs `vigiles compile` turns into wiring, the tests that exercise
|
|
7
|
+
* them, type declarations, and the `.json` stamp `compile` writes next to each
|
|
8
|
+
* hook. Discovery used to ask "does it end in a runnable extension?" and treat
|
|
9
|
+
* every yes as a hook (#278): a `task-list-nudge.harness.mjs` beside its
|
|
10
|
+
* `task-list-nudge.hook.ts` was compiled as a hook and failed the build. The
|
|
11
|
+
* obvious patch — subtract the names that bit (`.harness.`, `.test.`) — is a list
|
|
12
|
+
* of what a hook is NOT, and a list of that kind is open: the next helper file
|
|
13
|
+
* somebody colocates is the next report.
|
|
14
|
+
*
|
|
15
|
+
* So a hook is defined by what it IS. A file's kind comes from ONE marker, the
|
|
16
|
+
* LAST dot-separated segment before the extension (`guard.hook.mjs` → `hook`),
|
|
17
|
+
* looked up in ONE table ({@link MARKERS}). Because exactly one segment decides,
|
|
18
|
+
* a name has exactly one kind; because the table is closed, every consumer can
|
|
19
|
+
* `switch` over {@link SourceKind} exhaustively and the compiler names the next
|
|
20
|
+
* one that forgets a case. A runnable file whose marker is not in the table is
|
|
21
|
+
* `unclaimed` — a kind of its own, not an absence — so a renamed-away hook is a
|
|
22
|
+
* reported fact rather than a silent hole.
|
|
23
|
+
*
|
|
24
|
+
* The patterns test discovery uses (`vigiles test`, `vigiles eval`, the coverage
|
|
25
|
+
* defaults, the browser twin) are DERIVED from the same table. They used to be
|
|
26
|
+
* five hand-kept copies of one fact: `*.harness.*` and `*.eval.*` are tests.
|
|
27
|
+
*
|
|
28
|
+
* Pure and import-free on purpose. `hook-install.ts` imports this and is reached
|
|
29
|
+
* from the hook runtime, where every module is a per-call cost, and the browser
|
|
30
|
+
* twin of coverage detection needs it too — nothing here may touch `node:*`.
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.dirName = exports.baseName = exports.DEFAULT_TEST_GLOBS = exports.RUNNABLE_EXTS = void 0;
|
|
34
|
+
exports.classifySource = classifySource;
|
|
35
|
+
exports.isEvalScript = isEvalScript;
|
|
36
|
+
exports.isVigilesTest = isVigilesTest;
|
|
37
|
+
exports.testGlob = testGlob;
|
|
38
|
+
exports.markedName = markedName;
|
|
39
|
+
exports.preMarkerName = preMarkerName;
|
|
40
|
+
/**
|
|
41
|
+
* Every extension Node executes directly. `.mts`/`.cts` are real (TS 4.7+) and
|
|
42
|
+
* Node 22 strips their types with no toolchain — measured, not assumed.
|
|
43
|
+
*/
|
|
44
|
+
exports.RUNNABLE_EXTS = ["mjs", "cjs", "js", "mts", "cts", "ts"];
|
|
45
|
+
/**
|
|
46
|
+
* The marker table: the LAST dot-segment before the extension → its kind.
|
|
47
|
+
* Adding a kind of file means adding a row here and handling the new
|
|
48
|
+
* {@link SourceKind} member in each consumer's `switch`.
|
|
49
|
+
*/
|
|
50
|
+
const MARKERS = [
|
|
51
|
+
{ infix: "hook", kind: { kind: "hook" } },
|
|
52
|
+
{ infix: "provider", kind: { kind: "provider" } },
|
|
53
|
+
{ infix: "harness", kind: { kind: "vigiles-test", test: "harness" } },
|
|
54
|
+
{ infix: "eval", kind: { kind: "vigiles-test", test: "eval" } },
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* The tiers a vigiles test can belong to, read off {@link MARKERS}. A tier is
|
|
58
|
+
* also its own infix (`guard.eval.mjs` is tier `eval`): `source-kinds.test.ts`
|
|
59
|
+
* asserts that for every tier, so a row that broke it fails there.
|
|
60
|
+
*/
|
|
61
|
+
const TEST_TIERS = MARKERS.flatMap((m) => m.kind.kind === "vigiles-test" ? [m.kind.test] : []);
|
|
62
|
+
const EXT_ALT = exports.RUNNABLE_EXTS.join("|");
|
|
63
|
+
const DECLARATION = /\.d\.(?:ts|mts|cts)$/;
|
|
64
|
+
const STAMP = new RegExp(`\\.(?:${EXT_ALT})\\.json$`);
|
|
65
|
+
/**
|
|
66
|
+
* `<stem>.<infix>.<ext>`, the infix being the LAST segment before the extension.
|
|
67
|
+
* The stem may be empty (`.harness.mjs`): the runner's glob matches that, so the
|
|
68
|
+
* classifier must too; {@link classifySource} decides which kinds may go without
|
|
69
|
+
* one. `[\\s\\S]`, not `.`: `.` stops at a newline, and a name with one in it
|
|
70
|
+
* used to fall out of every kind and be dropped without a word.
|
|
71
|
+
*/
|
|
72
|
+
const MARKED = new RegExp(`^([\\s\\S]*)\\.([^.]+)\\.(?:${EXT_ALT})$`);
|
|
73
|
+
const RUNNABLE = new RegExp(`^[\\s\\S]+\\.(?:${EXT_ALT})$`);
|
|
74
|
+
const UNCLAIMED = { kind: "unclaimed" };
|
|
75
|
+
/**
|
|
76
|
+
* The kind of a file, from its BASENAME. Total: every string has an answer, and
|
|
77
|
+
* the answer is one member of {@link SourceKind}.
|
|
78
|
+
*
|
|
79
|
+
* Order matters and is the whole algorithm: a declaration is a declaration even
|
|
80
|
+
* when its infix says `hook` (`x.hook.d.ts`); a `.json` beside a source is its
|
|
81
|
+
* stamp; then the last infix is looked up. Case-sensitive, like the file systems
|
|
82
|
+
* these names mostly live on.
|
|
83
|
+
*/
|
|
84
|
+
function classifySource(filename) {
|
|
85
|
+
if (DECLARATION.test(filename))
|
|
86
|
+
return { kind: "declaration" };
|
|
87
|
+
if (STAMP.test(filename))
|
|
88
|
+
return { kind: "stamp" };
|
|
89
|
+
if (!RUNNABLE.test(filename))
|
|
90
|
+
return { kind: "non-source" };
|
|
91
|
+
const parts = MARKED.exec(filename);
|
|
92
|
+
const row = MARKERS.find((m) => m.infix === parts?.[2]);
|
|
93
|
+
if (row === undefined)
|
|
94
|
+
return UNCLAIMED;
|
|
95
|
+
// A test needs no name to be run (`vigiles test` finds `.harness.mjs`); a hook
|
|
96
|
+
// or a provider must be nameable, so a stemless one is nobody's.
|
|
97
|
+
return parts?.[1] === "" && row.kind.kind !== "vigiles-test"
|
|
98
|
+
? UNCLAIMED
|
|
99
|
+
: row.kind;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Is this basename an eval script — the PAID tier, the one that spends real
|
|
103
|
+
* model calls? It decides which of two runners owns a test file, and has been
|
|
104
|
+
* wrong in both directions:
|
|
105
|
+
*
|
|
106
|
+
* - As the full suffix `.eval.mjs`: `foo.eval.ts` fell into the free branch and
|
|
107
|
+
* would have spent real model calls on every push.
|
|
108
|
+
* - As the bare INFIX `.eval.`: `parser.eval.test.ts`, an ordinary deterministic
|
|
109
|
+
* test, was credited to the paid tier and dropped from the free one — though
|
|
110
|
+
* `vigiles eval` cannot discover that name at all.
|
|
111
|
+
*
|
|
112
|
+
* The answer that is wrong in neither direction is the runner's own: `.eval.`
|
|
113
|
+
* followed by a runnable extension AT THE END of the name, which is exactly what
|
|
114
|
+
* the last-infix rule of {@link classifySource} says.
|
|
115
|
+
*
|
|
116
|
+
* @param filename a BASENAME — callers strip the directory with their own path
|
|
117
|
+
* helper (`node:path` on disk, `posix-path` in the browser twin).
|
|
118
|
+
*/
|
|
119
|
+
function isEvalScript(filename) {
|
|
120
|
+
const k = classifySource(filename);
|
|
121
|
+
return k.kind === "vigiles-test" && k.test === "eval";
|
|
122
|
+
}
|
|
123
|
+
/** Is this basename a vigiles test of either tier? */
|
|
124
|
+
function isVigilesTest(filename) {
|
|
125
|
+
return classifySource(filename).kind === "vigiles-test";
|
|
126
|
+
}
|
|
127
|
+
/** The glob `vigiles test` / `vigiles eval` discover a tier's scripts with. */
|
|
128
|
+
function testGlob(tier) {
|
|
129
|
+
return `**/*.${tier}.{${exports.RUNNABLE_EXTS.join(",")}}`;
|
|
130
|
+
}
|
|
131
|
+
/** The patterns coverage credits tests with by default — one per tier, from the table. */
|
|
132
|
+
exports.DEFAULT_TEST_GLOBS = TEST_TIERS.map(testGlob);
|
|
133
|
+
/** Split at the last directory separator — either one, so a Windows path works too. */
|
|
134
|
+
const splitDir = (path) => {
|
|
135
|
+
const cut = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1;
|
|
136
|
+
return [path.slice(0, cut), path.slice(cut)];
|
|
137
|
+
};
|
|
138
|
+
/** The last path segment, whichever separator precedes it. */
|
|
139
|
+
const baseName = (path) => splitDir(path)[1];
|
|
140
|
+
exports.baseName = baseName;
|
|
141
|
+
/** Everything up to and including the last separator, or "" when there is none. */
|
|
142
|
+
const dirName = (path) => splitDir(path)[0];
|
|
143
|
+
exports.dirName = dirName;
|
|
144
|
+
/**
|
|
145
|
+
* `guard.mjs` → `guard.hook.mjs`: the name a file must carry to be claimed as
|
|
146
|
+
* `marker`. Keeps the directory. The inverse of {@link preMarkerName}.
|
|
147
|
+
*/
|
|
148
|
+
function markedName(path, marker) {
|
|
149
|
+
const [dir, base] = splitDir(path);
|
|
150
|
+
return dir + base.replace(/\.([^.]+)$/, `.${marker}.$1`);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* `guard.hook.mjs` → `guard.mjs`: the name a file carried before the marker
|
|
154
|
+
* existed, or `undefined` when the name has no such marker. Used to recognise
|
|
155
|
+
* the wiring an older vigiles wrote for the same file.
|
|
156
|
+
*/
|
|
157
|
+
function preMarkerName(path, marker) {
|
|
158
|
+
const [dir, base] = splitDir(path);
|
|
159
|
+
const m = new RegExp(`^([\\s\\S]+)\\.${marker}\\.(${EXT_ALT})$`).exec(base);
|
|
160
|
+
return m === null ? undefined : `${dir}${m[1]}.${m[2]}`;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=source-kinds.js.map
|
|
@@ -34,22 +34,7 @@ exports.findUntestedSurfacesInFiles = findUntestedSurfacesInFiles;
|
|
|
34
34
|
const posix_path_js_1 = require("./posix-path.js");
|
|
35
35
|
const layout_js_1 = require("./core/layout.js");
|
|
36
36
|
const coverage_evidence_js_1 = require("./coverage-evidence.js");
|
|
37
|
-
|
|
38
|
-
// — test-coverage.ts pulls in node:fs/glob, and this twin must stay browser-safe.
|
|
39
|
-
const DEFAULT_TEST_SUFFIXES = [
|
|
40
|
-
".harness.ts",
|
|
41
|
-
".harness.mts",
|
|
42
|
-
".harness.cts",
|
|
43
|
-
".harness.js",
|
|
44
|
-
".harness.mjs",
|
|
45
|
-
".harness.cjs",
|
|
46
|
-
".eval.ts",
|
|
47
|
-
".eval.mts",
|
|
48
|
-
".eval.cts",
|
|
49
|
-
".eval.js",
|
|
50
|
-
".eval.mjs",
|
|
51
|
-
".eval.cjs",
|
|
52
|
-
];
|
|
37
|
+
const source_kinds_js_1 = require("./source-kinds.js");
|
|
53
38
|
const IGNORE_MARKER = "vigiles:ignore-test";
|
|
54
39
|
/** Mirror of the DEFAULT_IGNORE globs (root-anchored), as a key predicate. */
|
|
55
40
|
function isIgnored(key) {
|
|
@@ -190,7 +175,7 @@ function discoverTests(files) {
|
|
|
190
175
|
for (const path of Object.keys(files)) {
|
|
191
176
|
if (isIgnored(path))
|
|
192
177
|
continue;
|
|
193
|
-
if (
|
|
178
|
+
if ((0, source_kinds_js_1.isVigilesTest)((0, posix_path_js_1.basename)(path))) {
|
|
194
179
|
out.push((0, coverage_evidence_js_1.prepareTest)(path));
|
|
195
180
|
}
|
|
196
181
|
}
|
package/dist/test-coverage.js
CHANGED
|
@@ -78,6 +78,7 @@ const ts_runner_caps_js_1 = require("./ts-runner-caps.js");
|
|
|
78
78
|
const glob_1 = require("glob");
|
|
79
79
|
const glob_ignore_js_1 = require("./core/glob-ignore.js");
|
|
80
80
|
const exclude_js_1 = require("./exclude.js");
|
|
81
|
+
const source_kinds_js_1 = require("./source-kinds.js");
|
|
81
82
|
const layout_js_1 = require("./core/layout.js");
|
|
82
83
|
const coverage_evidence_js_1 = require("./coverage-evidence.js");
|
|
83
84
|
const coverage_artifact_js_1 = require("./coverage-artifact.js");
|
|
@@ -98,9 +99,6 @@ function surfaceGlobs(dir, leaf, materializeRoot) {
|
|
|
98
99
|
// ---------------------------------------------------------------------------
|
|
99
100
|
// Internals
|
|
100
101
|
// ---------------------------------------------------------------------------
|
|
101
|
-
/** Every extension Node executes directly. `.mts`/`.cts` are real (TS 4.7+) and
|
|
102
|
-
* Node 22 strips their types with no toolchain — measured, not assumed. */
|
|
103
|
-
const RUNNABLE_EXTS = "{ts,mts,cts,js,mjs,cjs}";
|
|
104
102
|
/**
|
|
105
103
|
* 🔴 `*.test.*` USED TO BE HERE, AND REMOVING IT IS THE POINT.
|
|
106
104
|
*
|
|
@@ -128,10 +126,11 @@ const RUNNABLE_EXTS = "{ts,mts,cts,js,mjs,cjs}";
|
|
|
128
126
|
* for hooks/agents and dropping them for skills — because a rule with a per-kind
|
|
129
127
|
* exception is what this file just spent a day removing.
|
|
130
128
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
// Read off the table in `source-kinds.ts` — the same one `vigiles test`,
|
|
130
|
+
// `vigiles eval` and the hook/provider discovery classify by — so the patterns
|
|
131
|
+
// that credit a test and the files a hook directory refuses to compile cannot
|
|
132
|
+
// drift apart.
|
|
133
|
+
const DEFAULT_TEST_GLOBS = source_kinds_js_1.DEFAULT_TEST_GLOBS;
|
|
135
134
|
const DEFAULT_IGNORE = [
|
|
136
135
|
"node_modules/**",
|
|
137
136
|
"dist/**",
|
|
@@ -298,6 +297,10 @@ function discoverTests(basePath, globs, ignore) {
|
|
|
298
297
|
cwd: basePath,
|
|
299
298
|
ignore,
|
|
300
299
|
dot: true,
|
|
300
|
+
// `glob` defaults this to TRUE on macOS and Windows, so `a.hook.HARNESS.mjs`
|
|
301
|
+
// was a test there and unclaimed on Linux — and the classifier is
|
|
302
|
+
// case-sensitive. One answer on every OS.
|
|
303
|
+
nocase: false,
|
|
301
304
|
});
|
|
302
305
|
// Prepared ONCE per file (comment-strip + declaration parse), not once per
|
|
303
306
|
// (surface × file) pair — the matching below is quadratic by nature.
|
|
@@ -737,7 +740,7 @@ function staleRunNote(report) {
|
|
|
737
740
|
];
|
|
738
741
|
}
|
|
739
742
|
/** Names a default vitest/jest run collects — the suffixes vigiles will not use. */
|
|
740
|
-
const FOREIGN_RUNNER_SUFFIX =
|
|
743
|
+
const FOREIGN_RUNNER_SUFFIX = new RegExp(`\\.(test|spec)\\.(${source_kinds_js_1.RUNNABLE_EXTS.join("|")})$`);
|
|
741
744
|
/**
|
|
742
745
|
* The would-be colocated tests that only their NAME disqualifies.
|
|
743
746
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "33.0.0",
|
|
4
4
|
"description": "Audit, test and measure the harness your AI agent runs on — grade your CLAUDE.md / AGENTS.md, skills, subagents and hooks, run them against a scripted model, and measure whether they actually fire.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|