vigiles 16.1.0 → 16.1.1
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/coverage-artifact.d.ts +56 -2
- package/dist/coverage-artifact.js +126 -8
- package/dist/coverage-evidence.d.ts +33 -0
- package/dist/coverage-evidence.js +47 -0
- package/dist/coverage-probe.d.ts +14 -6
- package/dist/coverage-probe.js +14 -6
- package/dist/test-coverage-files.js +10 -4
- package/dist/test-coverage.js +1 -6
- package/package.json +1 -1
|
@@ -12,6 +12,26 @@ export declare const COVERAGE_ARTIFACT_FILE = "coverage.json";
|
|
|
12
12
|
* silence "nothing ever measured whether this fires".
|
|
13
13
|
*/
|
|
14
14
|
export type CoverageTierName = "harness" | "eval";
|
|
15
|
+
/**
|
|
16
|
+
* How a RECORD came to name its surface.
|
|
17
|
+
*
|
|
18
|
+
* The three {@link ProbeOrigin} values are what the run REPORTED about itself
|
|
19
|
+
* from inside its own process — a command line it executed, a transcript it got
|
|
20
|
+
* back. `colocated` is the fourth and is different in kind: nothing inside the
|
|
21
|
+
* test said it. The RUNNER observed that the script it just executed is the
|
|
22
|
+
* surface's colocated test, by the same {@link isColocatedTest} rule that is
|
|
23
|
+
* already willing to credit that file for merely existing.
|
|
24
|
+
*
|
|
25
|
+
* 🔴 THE SPLIT IS THE POINT, AND IT IS WHY THIS IS NOT ON {@link ProbeOrigin}.
|
|
26
|
+
* `ProbeOrigin` is the wire vocabulary: `parseCheckReport` reads it out of a
|
|
27
|
+
* scratch file the CHILD writes. Putting `colocated` there would let a harness
|
|
28
|
+
* hand-write `{"how":"colocated","ref":"…"}` and mint execution coverage for a
|
|
29
|
+
* surface by declaring it — which is `vigiles:covers` returning through the back
|
|
30
|
+
* door, the tier this file's header records being deleted for exactly that. Kept
|
|
31
|
+
* off `ProbeOrigin`, a script cannot spell it at all: `toProbe` has no branch
|
|
32
|
+
* that produces it, so the only producer in the program is {@link recordsFrom}.
|
|
33
|
+
*/
|
|
34
|
+
export type RunAttribution = ProbeOrigin | "colocated";
|
|
15
35
|
/** One surface, exercised by one script, at one moment, against one version. */
|
|
16
36
|
export interface CoverageRun {
|
|
17
37
|
readonly kind: SurfaceKind;
|
|
@@ -19,8 +39,8 @@ export interface CoverageRun {
|
|
|
19
39
|
readonly path: string;
|
|
20
40
|
readonly name: string;
|
|
21
41
|
readonly tier: CoverageTierName;
|
|
22
|
-
/** How the run named it — see {@link
|
|
23
|
-
readonly how:
|
|
42
|
+
/** How the run named it — see {@link RunAttribution}. */
|
|
43
|
+
readonly how: RunAttribution;
|
|
24
44
|
/** The script file that did the exercising. */
|
|
25
45
|
readonly by: string;
|
|
26
46
|
/** ISO-8601 timestamp of the run. */
|
|
@@ -192,6 +212,18 @@ export interface ScriptRunRecord {
|
|
|
192
212
|
/** The script file that ran. */
|
|
193
213
|
readonly file: string;
|
|
194
214
|
readonly probes: readonly SurfaceProbe[];
|
|
215
|
+
/**
|
|
216
|
+
* How many checks the run REPORTED, or `undefined` when it reported nothing.
|
|
217
|
+
*
|
|
218
|
+
* The distinction is load-bearing and is not this file's invention — see
|
|
219
|
+
* `statusFor` in run-scripts.ts. `undefined` means the script never imported
|
|
220
|
+
* vigiles and therefore *could not* report; that silence is the legacy branch
|
|
221
|
+
* and is never a verdict. A positive number is the script saying, through the
|
|
222
|
+
* library, that it made an observation. Only the second is evidence, which is
|
|
223
|
+
* why {@link recordsFrom} attributes colocation on `> 0` rather than on
|
|
224
|
+
* "it exited 0".
|
|
225
|
+
*/
|
|
226
|
+
readonly checks?: number;
|
|
195
227
|
}
|
|
196
228
|
/**
|
|
197
229
|
* The runs worth recording, out of a whole `vigiles test` / `vigiles eval`.
|
|
@@ -226,10 +258,26 @@ export interface ScriptRunRecord {
|
|
|
226
258
|
* CLI would destroy a real result. A skip must not write, because it did not
|
|
227
259
|
* finish. Both follow from "a skip is not an execution"; only the direction of
|
|
228
260
|
* the conclusion differs.
|
|
261
|
+
*
|
|
262
|
+
* ## 🔴 A RUN WITH NO PROBES IS NO LONGER DISCARDED
|
|
263
|
+
*
|
|
264
|
+
* The second clause used to be `surfaces.length > 0`: a passing run that named no
|
|
265
|
+
* surface contributed nothing. That threw away the ordinary case. MEASURED on a
|
|
266
|
+
* 52-surface consumer repo, 23 of its 27 covered surfaces were carried by a
|
|
267
|
+
* colocated `*.harness.mjs` that `vigiles test` RUNS on every push — and every one
|
|
268
|
+
* of them reported `colocated` evidence, *"this says the file EXISTS, not that it
|
|
269
|
+
* ran"*, because a harness asserting through `node:assert` reports a CHECK COUNT
|
|
270
|
+
* and no probe. The run happened, the runner watched it happen, and the metric
|
|
271
|
+
* described it as a directory listing.
|
|
272
|
+
*
|
|
273
|
+
* So a passing run that reported at least one CHECK is kept even with no probes:
|
|
274
|
+
* {@link recordsFrom} can attribute it by where the script sits. `checks` travels
|
|
275
|
+
* with it because "exited 0" is not the bar — see {@link ScriptRunRecord.checks}.
|
|
229
276
|
*/
|
|
230
277
|
export declare function runsFromResults(results: readonly {
|
|
231
278
|
readonly file: string;
|
|
232
279
|
readonly status: string;
|
|
280
|
+
readonly checks?: number;
|
|
233
281
|
readonly surfaces?: readonly SurfaceProbe[];
|
|
234
282
|
}[]): ScriptRunRecord[];
|
|
235
283
|
/**
|
|
@@ -238,6 +286,12 @@ export declare function runsFromResults(results: readonly {
|
|
|
238
286
|
* run); a surface it cannot read is skipped — an unhashable record could never
|
|
239
287
|
* be checked for staleness and would therefore be permanent, unfalsifiable
|
|
240
288
|
* coverage.
|
|
289
|
+
*
|
|
290
|
+
* Two sources of attribution, in one pass: the probes a run reported, and — for a
|
|
291
|
+
* run that reported a check — the surface its own script is colocated with
|
|
292
|
+
* ({@link colocatedSurface}). The dedupe key already carries `how`, so a run that
|
|
293
|
+
* BOTH probed a surface and sits beside it records once per attribution and the
|
|
294
|
+
* report can still say which kinds of evidence exist.
|
|
241
295
|
*/
|
|
242
296
|
export declare function recordsFrom(opts: ResolveOptions & {
|
|
243
297
|
readonly runs: readonly ScriptRunRecord[];
|
|
@@ -57,10 +57,26 @@ exports.indexRuns = indexRuns;
|
|
|
57
57
|
const node_crypto_1 = require("node:crypto");
|
|
58
58
|
const node_fs_1 = require("node:fs");
|
|
59
59
|
const node_path_1 = require("node:path");
|
|
60
|
+
const coverage_evidence_js_1 = require("./coverage-evidence.js");
|
|
60
61
|
/** Bumped when the record shape changes in a non-additive way. */
|
|
61
62
|
exports.COVERAGE_ARTIFACT_VERSION = 1;
|
|
62
63
|
/** The artifact filename under `.vigiles/`. */
|
|
63
64
|
exports.COVERAGE_ARTIFACT_FILE = "coverage.json";
|
|
65
|
+
/**
|
|
66
|
+
* Every {@link RunAttribution}, as data — the artifact's validator reads it, so
|
|
67
|
+
* the accepted set and the type cannot drift apart into a record shape the
|
|
68
|
+
* program can produce and the reader silently discards. The `satisfies` is the
|
|
69
|
+
* lock: drop a member and this stops compiling.
|
|
70
|
+
*/
|
|
71
|
+
const ATTRIBUTIONS = new Set([
|
|
72
|
+
"command",
|
|
73
|
+
"fired",
|
|
74
|
+
"dispatched",
|
|
75
|
+
"colocated",
|
|
76
|
+
]);
|
|
77
|
+
function isAttribution(value) {
|
|
78
|
+
return typeof value === "string" && ATTRIBUTIONS.has(value);
|
|
79
|
+
}
|
|
64
80
|
/** Content hash of a surface file — the staleness key. */
|
|
65
81
|
function surfaceSha(content) {
|
|
66
82
|
return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -347,11 +363,83 @@ function only(matches) {
|
|
|
347
363
|
* CLI would destroy a real result. A skip must not write, because it did not
|
|
348
364
|
* finish. Both follow from "a skip is not an execution"; only the direction of
|
|
349
365
|
* the conclusion differs.
|
|
366
|
+
*
|
|
367
|
+
* ## 🔴 A RUN WITH NO PROBES IS NO LONGER DISCARDED
|
|
368
|
+
*
|
|
369
|
+
* The second clause used to be `surfaces.length > 0`: a passing run that named no
|
|
370
|
+
* surface contributed nothing. That threw away the ordinary case. MEASURED on a
|
|
371
|
+
* 52-surface consumer repo, 23 of its 27 covered surfaces were carried by a
|
|
372
|
+
* colocated `*.harness.mjs` that `vigiles test` RUNS on every push — and every one
|
|
373
|
+
* of them reported `colocated` evidence, *"this says the file EXISTS, not that it
|
|
374
|
+
* ran"*, because a harness asserting through `node:assert` reports a CHECK COUNT
|
|
375
|
+
* and no probe. The run happened, the runner watched it happen, and the metric
|
|
376
|
+
* described it as a directory listing.
|
|
377
|
+
*
|
|
378
|
+
* So a passing run that reported at least one CHECK is kept even with no probes:
|
|
379
|
+
* {@link recordsFrom} can attribute it by where the script sits. `checks` travels
|
|
380
|
+
* with it because "exited 0" is not the bar — see {@link ScriptRunRecord.checks}.
|
|
350
381
|
*/
|
|
351
382
|
function runsFromResults(results) {
|
|
352
383
|
return results
|
|
353
|
-
.filter((r) => r.status === "pass" &&
|
|
354
|
-
|
|
384
|
+
.filter((r) => r.status === "pass" &&
|
|
385
|
+
((r.surfaces?.length ?? 0) > 0 || (r.checks ?? 0) > 0))
|
|
386
|
+
.map((r) => ({
|
|
387
|
+
file: r.file,
|
|
388
|
+
probes: r.surfaces ?? [],
|
|
389
|
+
...(r.checks === undefined ? {} : { checks: r.checks }),
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* The surface a RUN's own script is the colocated test of, or `null`.
|
|
394
|
+
*
|
|
395
|
+
* This is the fourth attribution ({@link RunAttribution}), and the only one the
|
|
396
|
+
* script does not report about itself. The runner knows two facts the script
|
|
397
|
+
* cannot: which file it just executed, and which surfaces this repo has. Put
|
|
398
|
+
* together by {@link isColocatedTest} — the SAME predicate that already grants
|
|
399
|
+
* `colocated` evidence for that file merely existing — they say which surface the
|
|
400
|
+
* run was about.
|
|
401
|
+
*
|
|
402
|
+
* ## The bar is a REPORTED CHECK, not exit zero
|
|
403
|
+
*
|
|
404
|
+
* 🔴 WITHOUT THAT, THIS WOULD BE THE ORIGINAL DEFECT WEARING THE STRONGER LABEL.
|
|
405
|
+
* Measured 2026-08-17 on a two-skill fixture: `touch
|
|
406
|
+
* .claude/skills/argument-arc/argument-arc.harness.mjs` — a ZERO-BYTE file —
|
|
407
|
+
* drops the untested count by one, and `vigiles test` then RUNS it and prints
|
|
408
|
+
* `✓ … passed`. An empty script exits 0. Attributing on "it ran and exited 0"
|
|
409
|
+
* would promote that file from `colocated` ("the file EXISTS, not that it ran")
|
|
410
|
+
* to "MEASURED BY A RUN", which is strictly worse than the hole being closed:
|
|
411
|
+
* the same emptiness, now wearing execution's name.
|
|
412
|
+
*
|
|
413
|
+
* A zero-byte file cannot report a check — it never imports vigiles — so its
|
|
414
|
+
* `checks` is `undefined` and it earns nothing here, falling back to colocation
|
|
415
|
+
* exactly as before. This is `statusFor`'s distinction reused, not a new one:
|
|
416
|
+
* silence is the legacy branch, `0` is `vacuous` (never a `pass`, so it never
|
|
417
|
+
* reaches this function), and a positive count is the script saying through the
|
|
418
|
+
* library that it observed something.
|
|
419
|
+
*
|
|
420
|
+
* ⚠️ WHAT THIS STILL CANNOT SEE, stated rather than implied: a harness that
|
|
421
|
+
* asserts entirely on its own and never imports vigiles reports nothing, so it is
|
|
422
|
+
* indistinguishable here from the empty file. It keeps its colocated credit and
|
|
423
|
+
* gains no execution credit. That is deliberate and it is `check-count.ts`'s
|
|
424
|
+
* standing decision — force-loading the counter into every child was considered
|
|
425
|
+
* and rejected there, because it would report `0` for a blameless hand-rolled
|
|
426
|
+
* harness. The cost is a missed upgrade; the alternative was a false one.
|
|
427
|
+
*
|
|
428
|
+
* 🔴 AMBIGUITY RESOLVES TO NOTHING, the same rule {@link only} applies to probes.
|
|
429
|
+
* Two surfaces in one directory can both claim a script when one name prefixes
|
|
430
|
+
* the other (`foo` and `foo.bar` both match `foo.bar.harness.mjs`, since
|
|
431
|
+
* colocation asks only that the basename START with `<name>.`). Exactly one of
|
|
432
|
+
* them is what the test is about, and nothing here can say which — so crediting
|
|
433
|
+
* both would INVENT a record, which is the failure the rest of this file spends
|
|
434
|
+
* its length refusing.
|
|
435
|
+
*/
|
|
436
|
+
function colocatedSurface(run, surfaces, alreadyProbed) {
|
|
437
|
+
if ((run.checks ?? 0) <= 0)
|
|
438
|
+
return null;
|
|
439
|
+
const matches = surfaces.filter((s) => s.path !== run.file &&
|
|
440
|
+
!alreadyProbed.has(s.path) &&
|
|
441
|
+
(0, coverage_evidence_js_1.isColocatedTest)(s, run.file));
|
|
442
|
+
return matches.length === 1 ? (matches[0] ?? null) : null;
|
|
355
443
|
}
|
|
356
444
|
/**
|
|
357
445
|
* Turn resolved probes into records. `readSurface` supplies the current content
|
|
@@ -359,15 +447,45 @@ function runsFromResults(results) {
|
|
|
359
447
|
* run); a surface it cannot read is skipped — an unhashable record could never
|
|
360
448
|
* be checked for staleness and would therefore be permanent, unfalsifiable
|
|
361
449
|
* coverage.
|
|
450
|
+
*
|
|
451
|
+
* Two sources of attribution, in one pass: the probes a run reported, and — for a
|
|
452
|
+
* run that reported a check — the surface its own script is colocated with
|
|
453
|
+
* ({@link colocatedSurface}). The dedupe key already carries `how`, so a run that
|
|
454
|
+
* BOTH probed a surface and sits beside it records once per attribution and the
|
|
455
|
+
* report can still say which kinds of evidence exist.
|
|
362
456
|
*/
|
|
363
457
|
function recordsFrom(opts) {
|
|
364
458
|
const out = [];
|
|
365
459
|
const seen = new Set();
|
|
366
|
-
const pairs =
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
460
|
+
const pairs = [];
|
|
461
|
+
for (const run of opts.runs) {
|
|
462
|
+
const probed = new Set();
|
|
463
|
+
for (const probe of run.probes)
|
|
464
|
+
for (const surface of resolveProbe(probe, opts.surfaces, opts)) {
|
|
465
|
+
pairs.push({ run, probe, surface });
|
|
466
|
+
probed.add(surface.path);
|
|
467
|
+
}
|
|
468
|
+
// 🔴 COLOCATION IS THE FALLBACK ATTRIBUTION, NOT AN ADDITIONAL ONE, and
|
|
469
|
+
// skipping what this run already named is what keeps it from DOWNGRADING the
|
|
470
|
+
// record. MEASURED 2026-08-17 on a 52-surface repo the first time this tier
|
|
471
|
+
// ran: `.claude/hooks/paper-lint.mjs` went from `command` — the harness
|
|
472
|
+
// literally executed that path — to `colocated`, and three `fired` skill
|
|
473
|
+
// activations went the same way. The cause is one key: `mergeRuns` dedupes on
|
|
474
|
+
// (surface, tier, script) and does NOT include `how`, so a second attribution
|
|
475
|
+
// of the same pair does not sit beside the first, it REPLACES it — and with
|
|
476
|
+
// both records stamped the same `at`, `held.at <= run.at` hands the win to
|
|
477
|
+
// whichever was pushed last.
|
|
478
|
+
//
|
|
479
|
+
// The ordering is the same one this file already applies one level up: a
|
|
480
|
+
// direct observation of the surface being exercised outranks an inference
|
|
481
|
+
// from where a file sits. Emitting both and teaching the merge to keep the
|
|
482
|
+
// stronger was the alternative; it costs a wider key, a second record per
|
|
483
|
+
// pair, and a ranking function — to store a fact that adds nothing, since
|
|
484
|
+
// both resolve to the same `executed` evidence.
|
|
485
|
+
const beside = colocatedSurface(run, opts.surfaces, probed);
|
|
486
|
+
if (beside)
|
|
487
|
+
pairs.push({ run, probe: { how: "colocated" }, surface: beside });
|
|
488
|
+
}
|
|
371
489
|
for (const { run, probe, surface } of pairs) {
|
|
372
490
|
const key = `${surface.path}\u0000${run.file}\u0000${probe.how}`;
|
|
373
491
|
if (seen.has(key))
|
|
@@ -492,7 +610,7 @@ function isCoverageRun(value) {
|
|
|
492
610
|
typeof r.path === "string" &&
|
|
493
611
|
typeof r.name === "string" &&
|
|
494
612
|
(r.tier === "harness" || r.tier === "eval") &&
|
|
495
|
-
(r.how
|
|
613
|
+
isAttribution(r.how) &&
|
|
496
614
|
typeof r.by === "string" &&
|
|
497
615
|
typeof r.at === "string" &&
|
|
498
616
|
typeof r.sha === "string");
|
|
@@ -27,6 +27,39 @@ export interface CoverableSurface {
|
|
|
27
27
|
export interface PreparedTest {
|
|
28
28
|
readonly path: string;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Is `testPath` the colocated test OF this surface — NAMED after it, SITTING
|
|
32
|
+
* BESIDE it?
|
|
33
|
+
*
|
|
34
|
+
* 🔴 THIS USED TO BE WRITTEN TWICE, and the second copy said so in its own
|
|
35
|
+
* comment: `test-coverage-files.ts` carried a function whose docstring was
|
|
36
|
+
* *"Mirror of test-coverage.ts `isColocated`"*. A mirror is a promise that two
|
|
37
|
+
* bodies stay equal, kept by whoever remembers — and this file already documents
|
|
38
|
+
* three separate occasions where a change landed in one report builder and not
|
|
39
|
+
* the other ({@link declaredSurfaceName}, {@link hookScriptRefs}). The rule now
|
|
40
|
+
* has ONE body and three callers: the disk detector, the browser twin, and the
|
|
41
|
+
* runner that turns an executed script into an execution record
|
|
42
|
+
* (`coverage-artifact.ts`). There is nothing left to mirror wrong.
|
|
43
|
+
*
|
|
44
|
+
* That third caller is why this became shared rather than merely deduplicated.
|
|
45
|
+
* Colocation and execution were answering the same question — *which surface is
|
|
46
|
+
* this test about?* — with two different pieces of code, so a script vigiles had
|
|
47
|
+
* just RUN could not be attributed by the very rule that was already willing to
|
|
48
|
+
* credit it for existing.
|
|
49
|
+
*
|
|
50
|
+
* SEPARATORS ARE NORMALISED FIRST, then POSIX semantics apply. The disk detector
|
|
51
|
+
* feeds paths from `globSync`, which yields `\` on Windows; the browser twin
|
|
52
|
+
* feeds file-map keys, which are always `/`. Folding `\` to `/` makes one body
|
|
53
|
+
* correct for both instead of asking each caller to bring its own path module —
|
|
54
|
+
* which is exactly the seam the mirror lived in.
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ THE COST, STATED: a POSIX file whose NAME literally contains a backslash is
|
|
57
|
+
* now read as a directory boundary. That is not reachable for either input here —
|
|
58
|
+
* a skill directory or a test filename with a `\` in it does not survive the glob
|
|
59
|
+
* patterns that discover it in the first place — and it is the same trade
|
|
60
|
+
* `hookScriptRefs` already makes one function up.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isColocatedTest(surface: Pick<CoverableSurface, "name" | "path">, testPath: string): boolean;
|
|
30
63
|
/** Wrap a discovered path. Kept as a function so both twins share one shape. */
|
|
31
64
|
export declare function prepareTest(path: string): PreparedTest;
|
|
32
65
|
/** @param filename a BASENAME — callers strip the directory with their own path
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isColocatedTest = isColocatedTest;
|
|
3
4
|
exports.prepareTest = prepareTest;
|
|
4
5
|
exports.isEvalScript = isEvalScript;
|
|
5
6
|
exports.hookScriptRefs = hookScriptRefs;
|
|
@@ -91,6 +92,52 @@ exports.declaredSurfaceName = declaredSurfaceName;
|
|
|
91
92
|
* impossible there and its count is 0 — see `test-coverage-files.ts`.
|
|
92
93
|
*/
|
|
93
94
|
const frontmatter_read_js_1 = require("./core/frontmatter-read.js");
|
|
95
|
+
const posix_path_js_1 = require("./posix-path.js");
|
|
96
|
+
/**
|
|
97
|
+
* Is `testPath` the colocated test OF this surface — NAMED after it, SITTING
|
|
98
|
+
* BESIDE it?
|
|
99
|
+
*
|
|
100
|
+
* 🔴 THIS USED TO BE WRITTEN TWICE, and the second copy said so in its own
|
|
101
|
+
* comment: `test-coverage-files.ts` carried a function whose docstring was
|
|
102
|
+
* *"Mirror of test-coverage.ts `isColocated`"*. A mirror is a promise that two
|
|
103
|
+
* bodies stay equal, kept by whoever remembers — and this file already documents
|
|
104
|
+
* three separate occasions where a change landed in one report builder and not
|
|
105
|
+
* the other ({@link declaredSurfaceName}, {@link hookScriptRefs}). The rule now
|
|
106
|
+
* has ONE body and three callers: the disk detector, the browser twin, and the
|
|
107
|
+
* runner that turns an executed script into an execution record
|
|
108
|
+
* (`coverage-artifact.ts`). There is nothing left to mirror wrong.
|
|
109
|
+
*
|
|
110
|
+
* That third caller is why this became shared rather than merely deduplicated.
|
|
111
|
+
* Colocation and execution were answering the same question — *which surface is
|
|
112
|
+
* this test about?* — with two different pieces of code, so a script vigiles had
|
|
113
|
+
* just RUN could not be attributed by the very rule that was already willing to
|
|
114
|
+
* credit it for existing.
|
|
115
|
+
*
|
|
116
|
+
* SEPARATORS ARE NORMALISED FIRST, then POSIX semantics apply. The disk detector
|
|
117
|
+
* feeds paths from `globSync`, which yields `\` on Windows; the browser twin
|
|
118
|
+
* feeds file-map keys, which are always `/`. Folding `\` to `/` makes one body
|
|
119
|
+
* correct for both instead of asking each caller to bring its own path module —
|
|
120
|
+
* which is exactly the seam the mirror lived in.
|
|
121
|
+
*
|
|
122
|
+
* ⚠️ THE COST, STATED: a POSIX file whose NAME literally contains a backslash is
|
|
123
|
+
* now read as a directory boundary. That is not reachable for either input here —
|
|
124
|
+
* a skill directory or a test filename with a `\` in it does not survive the glob
|
|
125
|
+
* patterns that discover it in the first place — and it is the same trade
|
|
126
|
+
* `hookScriptRefs` already makes one function up.
|
|
127
|
+
*/
|
|
128
|
+
function isColocatedTest(surface, testPath) {
|
|
129
|
+
const test = posixly(testPath);
|
|
130
|
+
const home = posixly(surface.path);
|
|
131
|
+
if (!(0, posix_path_js_1.basename)(test).startsWith(`${surface.name}.`))
|
|
132
|
+
return false;
|
|
133
|
+
// A root `SKILL.md` (single-skill-dir target) lives at ".", and discovery
|
|
134
|
+
// returns top-level files without a "./" prefix — so `dirname` is "." on both
|
|
135
|
+
// sides and the comparison holds without a special case.
|
|
136
|
+
return (0, posix_path_js_1.dirname)(test) === (0, posix_path_js_1.dirname)(home);
|
|
137
|
+
}
|
|
138
|
+
function posixly(p) {
|
|
139
|
+
return p.replaceAll("\\", "/");
|
|
140
|
+
}
|
|
94
141
|
/** Wrap a discovered path. Kept as a function so both twins share one shape. */
|
|
95
142
|
function prepareTest(path) {
|
|
96
143
|
return { path };
|
package/dist/coverage-probe.d.ts
CHANGED
|
@@ -50,12 +50,20 @@
|
|
|
50
50
|
* file is named by anyone. That API contract is the feature — it is how you
|
|
51
51
|
* test a hook you did not write, verbatim as its plugin ships it.
|
|
52
52
|
*
|
|
53
|
-
* - **The in-process tier CAN
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
53
|
+
* - **The in-process tier CAN — and since 2026-08-13 it DOES.** ⚠️ This bullet
|
|
54
|
+
* used to read *"`harness-assert.ts` / `load-hook.ts` contain ZERO
|
|
55
|
+
* `recordSurfaceProbe` calls — the tier where attribution needs no parsing at
|
|
56
|
+
* all is the tier that attributes nothing"*, and that is no longer true:
|
|
57
|
+
* `runCountedHookProgram` in harness-assert.ts records a `command` probe for a
|
|
58
|
+
* hook it is about to evaluate, when `loadHook` knew the file. Verified by
|
|
59
|
+
* count, not by memory — `harness-assert.ts` has the call, `load-hook.ts`
|
|
60
|
+
* still does not, and deliberately so (loading a hook is not running it; see
|
|
61
|
+
* that function's note). Left corrected rather than deleted because a header
|
|
62
|
+
* that advertises a gap somebody already closed sends the next reader to build
|
|
63
|
+
* a second recorder.
|
|
64
|
+
*
|
|
65
|
+
* It did not retire this parser, as predicted: it ADDS a source, and
|
|
66
|
+
* `runHook(command)` is still public.
|
|
59
67
|
*
|
|
60
68
|
* So parsing stays, and the lesson taken instead is about DIRECTION. The scan was
|
|
61
69
|
* a deny-list — skip what we recognise, attribute what is left — so every gap in
|
package/dist/coverage-probe.js
CHANGED
|
@@ -56,12 +56,20 @@ exports.probeTrace = probeTrace;
|
|
|
56
56
|
* file is named by anyone. That API contract is the feature — it is how you
|
|
57
57
|
* test a hook you did not write, verbatim as its plugin ships it.
|
|
58
58
|
*
|
|
59
|
-
* - **The in-process tier CAN
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
59
|
+
* - **The in-process tier CAN — and since 2026-08-13 it DOES.** ⚠️ This bullet
|
|
60
|
+
* used to read *"`harness-assert.ts` / `load-hook.ts` contain ZERO
|
|
61
|
+
* `recordSurfaceProbe` calls — the tier where attribution needs no parsing at
|
|
62
|
+
* all is the tier that attributes nothing"*, and that is no longer true:
|
|
63
|
+
* `runCountedHookProgram` in harness-assert.ts records a `command` probe for a
|
|
64
|
+
* hook it is about to evaluate, when `loadHook` knew the file. Verified by
|
|
65
|
+
* count, not by memory — `harness-assert.ts` has the call, `load-hook.ts`
|
|
66
|
+
* still does not, and deliberately so (loading a hook is not running it; see
|
|
67
|
+
* that function's note). Left corrected rather than deleted because a header
|
|
68
|
+
* that advertises a gap somebody already closed sends the next reader to build
|
|
69
|
+
* a second recorder.
|
|
70
|
+
*
|
|
71
|
+
* It did not retire this parser, as predicted: it ADDS a source, and
|
|
72
|
+
* `runHook(command)` is still public.
|
|
65
73
|
*
|
|
66
74
|
* So parsing stays, and the lesson taken instead is about DIRECTION. The scan was
|
|
67
75
|
* a deny-list — skip what we recognise, attribute what is left — so every gap in
|
|
@@ -184,11 +184,17 @@ function discoverTests(files) {
|
|
|
184
184
|
}
|
|
185
185
|
return out;
|
|
186
186
|
}
|
|
187
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* Named after the surface, beside it — the SHARED rule, no longer a copy of it.
|
|
189
|
+
*
|
|
190
|
+
* 🔴 THIS WAS A MIRROR, and its own docstring said so. Two bodies, one promise
|
|
191
|
+
* that they stay equal, kept by whoever remembers — and this file's header
|
|
192
|
+
* already records the last time a coverage change landed in one engine and not
|
|
193
|
+
* the other. It now calls `isColocatedTest`, which the disk detector and the
|
|
194
|
+
* run-record builder call too.
|
|
195
|
+
*/
|
|
188
196
|
function isColocated(surface, testPath) {
|
|
189
|
-
|
|
190
|
-
return false;
|
|
191
|
-
return (0, posix_path_js_1.dirname)(testPath) === (0, posix_path_js_1.dirname)(surface.path);
|
|
197
|
+
return (0, coverage_evidence_js_1.isColocatedTest)(surface, testPath);
|
|
192
198
|
}
|
|
193
199
|
/** Mirror of test-coverage.ts `coverageOf` — strongest evidence across tests. */
|
|
194
200
|
function coverageOf(surface, tests) {
|
package/dist/test-coverage.js
CHANGED
|
@@ -287,12 +287,7 @@ function discoverTests(basePath, globs, ignore) {
|
|
|
287
287
|
* to remove.
|
|
288
288
|
*/
|
|
289
289
|
function isColocated(surface, testPath) {
|
|
290
|
-
|
|
291
|
-
return false;
|
|
292
|
-
// A root `SKILL.md` (single-skill-dir target) lives at ".", and globSync returns
|
|
293
|
-
// top-level files without a "./" prefix — so `dirname` is "." on both sides and
|
|
294
|
-
// the comparison holds without a special case.
|
|
295
|
-
return (0, node_path_1.dirname)(testPath) === (0, node_path_1.dirname)(surface.path);
|
|
290
|
+
return (0, coverage_evidence_js_1.isColocatedTest)(surface, testPath);
|
|
296
291
|
}
|
|
297
292
|
/**
|
|
298
293
|
* The STRONGEST evidence any discovered test provides for this surface, or null.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "16.1.
|
|
3
|
+
"version": "16.1.1",
|
|
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",
|