vigiles 21.0.2 → 23.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.
@@ -273,7 +273,14 @@ function discoverTests(basePath, globs, ignore) {
273
273
  // skill (matched by the explicit-dot `.claude/skills/*/SKILL.md` pattern) is
274
274
  // still discovered — so the surface looks untested even after the user adds
275
275
  // exactly the suggested file. DEFAULT_IGNORE still drops .git/node_modules/etc.
276
- const found = (0, glob_1.globSync)([...globs], { cwd: basePath, ignore, dot: true });
276
+ // `{surface}` is widened to `*` for DISCOVERY so one pass finds every
277
+ // candidate; the narrowing back to a specific surface happens at match time
278
+ // (matchesSurfaceGlob). Globbing once per surface would be quadratic in I/O.
279
+ const found = (0, glob_1.globSync)(globs.map(coverage_evidence_js_1.discoveryGlob), {
280
+ cwd: basePath,
281
+ ignore,
282
+ dot: true,
283
+ });
277
284
  // Prepared ONCE per file (comment-strip + declaration parse), not once per
278
285
  // (surface × file) pair — the matching below is quadratic by nature.
279
286
  return found.map((path) => (0, coverage_evidence_js_1.prepareTest)(path));
@@ -318,15 +325,17 @@ function isColocated(surface, testPath) {
318
325
  * explicitly declared is reported as declared; otherwise the provenance summary
319
326
  * would depend on glob order.
320
327
  */
321
- function coverageOf(surface, tests) {
328
+ function coverageOf(surface, tests, globs) {
322
329
  let best = null;
323
330
  for (const t of tests) {
324
331
  if (t.path === surface.path)
325
332
  continue;
326
- const ev = (0, coverage_evidence_js_1.evidenceFor)(surface, t, isColocated(surface, t.path));
333
+ const ev = (0, coverage_evidence_js_1.evidenceFor)(surface, t, isColocated(surface, t.path), (0, coverage_evidence_js_1.matchesSurfaceGlob)(surface, t.path, globs));
327
334
  if (!ev)
328
335
  continue;
329
- if (!best)
336
+ // Rank, do not first-win: with a colocated harness AND a configured suite
337
+ // the reported provenance must not depend on glob order.
338
+ if (!best || (0, coverage_evidence_js_1.strongerEvidence)(ev, best.evidence))
330
339
  best = { surface, evidence: ev, by: t.path };
331
340
  }
332
341
  return best;
@@ -370,12 +379,12 @@ function executedOf(surface, index, tier) {
370
379
  * first, a colocated test second. Execution outranks the name because the name
371
380
  * was only ever a stand-in for it.
372
381
  */
373
- function tierOf(considered, tests, index, tier) {
382
+ function tierOf(considered, tests, index, tier, globs) {
374
383
  const covered = [];
375
384
  const untested = [];
376
385
  const decisions = [];
377
386
  for (const s of considered) {
378
- const decision = executedOf(s, index, tier) ?? coverageOf(s, tests);
387
+ const decision = executedOf(s, index, tier) ?? coverageOf(s, tests, globs);
379
388
  if (decision) {
380
389
  covered.push(s);
381
390
  decisions.push(decision);
@@ -432,7 +441,7 @@ function findUntestedSurfaces(options = {}) {
432
441
  // file has several legitimate spellings (`x.mjs`, `./x.mjs`, absolute), and
433
442
  // the artifact records whichever one was typed.
434
443
  (by) => (0, node_fs_1.existsSync)((0, node_path_1.join)(basePath, (0, coverage_artifact_js_1.canonicalScript)(by, basePath))));
435
- const union = tierOf(considered, tests, runIndex, undefined);
444
+ const union = tierOf(considered, tests, runIndex, undefined, globs);
436
445
  return {
437
446
  total: considered.length,
438
447
  covered: union.covered,
@@ -456,8 +465,8 @@ function findUntestedSurfaces(options = {}) {
456
465
  .filter((path) => read((0, node_path_1.join)(basePath, path)).includes(LEGACY_COVERS)),
457
466
  retiredTestNames: retiredTestNamesFor(basePath, union.untested),
458
467
  decisions: union.decisions,
459
- harness: tierOf(considered, split.harness, runIndex, "harness"),
460
- evals: tierOf(considered, split.evals, runIndex, "eval"),
468
+ harness: tierOf(considered, split.harness, runIndex, "harness", globs),
469
+ evals: tierOf(considered, split.evals, runIndex, "eval", globs),
461
470
  };
462
471
  }
463
472
  /**
@@ -813,13 +822,17 @@ function formatUntestedReport(report) {
813
822
  lines.push(` ${provenance}`);
814
823
  lines.push(...coverageCaveats(report));
815
824
  // Already testing these another way (a promptfoo suite, a home-grown evals
816
- // file)? Point `testGlobs` at it so it counts toward coverage (issue #113)
817
- // and put the file NEXT TO the surface, which is the only placement that
818
- // counts now. See docs/rules/untested-skill.md.
819
- lines.push(` Testing these another way (promptfoo / a home-grown eval loop)? Add its ` +
820
- `files to \`testGlobs\` in .vigilesrc.json AND name each after the surface ` +
821
- `it covers, next to it (\`<surface>/<surface>.eval.mjs\`) placement says ` +
822
- `where a file sits, the name says what it is about. ` +
825
+ // file)? Two shapes are accepted, and the message names BOTH it used to
826
+ // name only `testGlobs`, which does not by itself make a centralized suite
827
+ // count, so a reader who followed it exactly saw the number not move (#175.2).
828
+ // See docs/rules/untested-skill.md.
829
+ lines.push(` Testing these another way (promptfoo / a home-grown eval loop)? Either ` +
830
+ `put the file NEXT TO the surface and name it after it ` +
831
+ `(\`<surface>/<surface>.eval.mjs\`), or for a centralized layout point ` +
832
+ `\`testGlobs\` at it USING THE \`{surface}\` placeholder, e.g. ` +
833
+ `\`"tests/{surface}/evals/promptfooconfig*.yaml"\`. A \`testGlobs\` entry ` +
834
+ `WITHOUT \`{surface}\` widens what counts as a test file but never says ` +
835
+ `which surface it covers, so it credits nothing on its own. ` +
823
836
  `See docs/rules/untested-skill.md.`);
824
837
  return lines.join("\n");
825
838
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "21.0.2",
3
+ "version": "23.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",