vigiles 22.0.0 → 24.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.
@@ -202,26 +202,28 @@ function isColocated(surface, testPath) {
202
202
  return (0, coverage_evidence_js_1.isColocatedTest)(surface, testPath);
203
203
  }
204
204
  /** Mirror of test-coverage.ts `coverageOf` — strongest evidence across tests. */
205
- function coverageOf(surface, tests) {
205
+ function coverageOf(surface, tests, globs) {
206
206
  let best = null;
207
207
  for (const t of tests) {
208
208
  if (t.path === surface.path)
209
209
  continue;
210
- const ev = (0, coverage_evidence_js_1.evidenceFor)(surface, t, isColocated(surface, t.path));
210
+ const ev = (0, coverage_evidence_js_1.evidenceFor)(surface, t, isColocated(surface, t.path), (0, coverage_evidence_js_1.matchesSurfaceGlob)(surface, t.path, globs));
211
211
  if (!ev)
212
212
  continue;
213
- if (!best)
213
+ // Rank, do not first-win: with a colocated harness AND a configured
214
+ // suite the reported provenance must not depend on glob order.
215
+ if (!best || (0, coverage_evidence_js_1.strongerEvidence)(ev, best.evidence))
214
216
  best = { surface, evidence: ev, by: t.path };
215
217
  }
216
218
  return best;
217
219
  }
218
220
  /** Mirror of test-coverage.ts `tierOf` — one tier's covered/untested split. */
219
- function tierOf(considered, tests) {
221
+ function tierOf(considered, tests, globs) {
220
222
  const covered = [];
221
223
  const untested = [];
222
224
  const decisions = [];
223
225
  for (const s of considered) {
224
- const decision = coverageOf(s, tests);
226
+ const decision = coverageOf(s, tests, globs);
225
227
  if (decision) {
226
228
  covered.push(s);
227
229
  decisions.push(decision);
@@ -252,12 +254,19 @@ repoName) {
252
254
  ];
253
255
  const considered = surfaces.filter((s) => !s.ignored);
254
256
  const tests = discoverTests(files);
255
- const union = tierOf(considered, tests);
257
+ // NO configured `{surface}` globs here, and that is a property of this twin
258
+ // rather than a gap: the browser engine reads a file MAP, not a repo, so there
259
+ // is no `.vigilesrc.json` for a user to have configured. Passing an empty list
260
+ // makes `matchesSurfaceGlob` false for everything, so this path behaves exactly
261
+ // as it did before the tier existed. Said out loud because a silent asymmetry
262
+ // between the twins is how they drift.
263
+ const noConfiguredGlobs = [];
264
+ const union = tierOf(considered, tests, noConfiguredGlobs);
256
265
  return {
257
266
  untested: [...union.untested],
258
267
  decisions: union.decisions,
259
- harness: tierOf(considered, tests.filter((t) => !(0, coverage_evidence_js_1.isEvalScript)((0, posix_path_js_1.basename)(t.path)))),
260
- evals: tierOf(considered, tests.filter((t) => (0, coverage_evidence_js_1.isEvalScript)((0, posix_path_js_1.basename)(t.path)))),
268
+ harness: tierOf(considered, tests.filter((t) => !(0, coverage_evidence_js_1.isEvalScript)((0, posix_path_js_1.basename)(t.path))), noConfiguredGlobs),
269
+ evals: tierOf(considered, tests.filter((t) => (0, coverage_evidence_js_1.isEvalScript)((0, posix_path_js_1.basename)(t.path))), noConfiguredGlobs),
261
270
  };
262
271
  }
263
272
  //# sourceMappingURL=test-coverage-files.js.map
@@ -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": "22.0.0",
3
+ "version": "24.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",