vigiles 25.0.0 → 26.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/adapters/claude-code/run-scripts.d.ts +5 -2
- package/dist/adapters/claude-code/run-scripts.js +6 -3
- package/dist/adapters/codex/eval.d.ts +18 -0
- package/dist/adapters/codex/eval.js +27 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +157 -74
- package/dist/core/adopt.d.ts +50 -1
- package/dist/core/adopt.js +100 -6
- package/dist/core/bash-effects.d.ts +11 -0
- package/dist/core/bash-effects.js +52 -12
- package/dist/core/bash-equivalents.d.ts +18 -0
- package/dist/core/bash-equivalents.js +239 -0
- package/dist/core/coverage.d.ts +3 -3
- package/dist/core/coverage.js +10 -6
- package/dist/core/hook-program.js +48 -2
- package/dist/core/orphans.d.ts +8 -1
- package/dist/core/orphans.js +8 -2
- package/dist/core/types.d.ts +14 -6
- package/dist/eval-cache.d.ts +7 -0
- package/dist/eval-lock.d.ts +20 -0
- package/dist/eval.d.ts +117 -4
- package/dist/eval.js +164 -32
- package/dist/exclude.d.ts +25 -0
- package/dist/exclude.js +132 -0
- package/dist/guardrail-check.d.ts +43 -24
- package/dist/guardrail-check.js +57 -0
- package/dist/scan-behavioral.d.ts +10 -0
- package/dist/scan-behavioral.js +1 -0
- package/dist/test.d.ts +1 -1
- package/dist/test.js +3 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -16,10 +16,10 @@ exports.lintTotals = lintTotals;
|
|
|
16
16
|
exports.discoverNestedBundles = discoverNestedBundles;
|
|
17
17
|
const node_fs_1 = require("node:fs");
|
|
18
18
|
const node_path_1 = require("node:path");
|
|
19
|
-
const minimatch_1 = require("minimatch");
|
|
20
19
|
const repo_path_js_1 = require("./core/repo-path.js");
|
|
21
20
|
const node_child_process_1 = require("node:child_process");
|
|
22
21
|
const glob_1 = require("glob");
|
|
22
|
+
const exclude_js_1 = require("./exclude.js");
|
|
23
23
|
const generate_types_js_1 = require("./core/generate-types.js");
|
|
24
24
|
const generate_harness_js_1 = require("./core/generate-harness.js");
|
|
25
25
|
const capability_diff_js_1 = require("./core/capability-diff.js");
|
|
@@ -87,18 +87,29 @@ const load_hook_js_1 = require("./load-hook.js");
|
|
|
87
87
|
// ---------------------------------------------------------------------------
|
|
88
88
|
// Constants
|
|
89
89
|
// ---------------------------------------------------------------------------
|
|
90
|
-
|
|
90
|
+
// The always-excluded floor (node_modules/dist/.git/.vigiles) lives in
|
|
91
|
+
// src/exclude.ts, folded into every ExcludeSet — no walk carries a private copy.
|
|
91
92
|
// ---------------------------------------------------------------------------
|
|
92
93
|
// Spec loading
|
|
93
94
|
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Every `*.md.spec.ts` under cwd that `.vigilesrc.json#exclude` does not drop.
|
|
97
|
+
*
|
|
98
|
+
* 🔴 `excludes` IS REQUIRED, NOT DEFAULTED (#192). This function hard-coded its
|
|
99
|
+
* own ignore list for a year while the loaded config's `exclude` never reached
|
|
100
|
+
* it — so a repo that excluded a directory of frozen, un-loadable fixtures got
|
|
101
|
+
* "Compilation complete with errors" on every recompile hook. Eight call sites
|
|
102
|
+
* each had the config in scope; none passed it. An optional parameter is how
|
|
103
|
+
* that happens again; a required one makes the ninth call site a tsc error.
|
|
104
|
+
*/
|
|
105
|
+
function findSpecs(excludes, pattern) {
|
|
95
106
|
const glob = pattern ?? "**/*.md.spec.ts";
|
|
96
107
|
return (0, glob_1.globSync)(glob, {
|
|
97
108
|
// `dot: true` so specs that live in a sync tool's source slot (e.g.
|
|
98
109
|
// `.ruler/AGENTS.md.spec.ts`, the redirect target) are discovered by
|
|
99
110
|
// compile/lint/the recompile hook — not just root-level specs.
|
|
100
111
|
dot: true,
|
|
101
|
-
ignore:
|
|
112
|
+
ignore: excludes.globIgnore,
|
|
102
113
|
cwd: process.cwd(),
|
|
103
114
|
});
|
|
104
115
|
}
|
|
@@ -464,16 +475,16 @@ function compileRailwayToFile(spec, specPath, knownAgents) {
|
|
|
464
475
|
return false;
|
|
465
476
|
}
|
|
466
477
|
/** Names of every compiled agent spec in the project — resolves delegate() targets. */
|
|
467
|
-
async function collectAgentNames() {
|
|
478
|
+
async function collectAgentNames(excludes) {
|
|
468
479
|
const names = [];
|
|
469
|
-
for (const p of findSpecs()) {
|
|
480
|
+
for (const p of findSpecs(excludes)) {
|
|
470
481
|
const s = await loadSpec(p);
|
|
471
482
|
if (s && s._specType === "agent")
|
|
472
483
|
names.push(s.name);
|
|
473
484
|
}
|
|
474
485
|
return names;
|
|
475
486
|
}
|
|
476
|
-
async function compile(specPaths, config, opts = {}) {
|
|
487
|
+
async function compile(specPaths, config, excludes, opts = {}) {
|
|
477
488
|
let allValid = true;
|
|
478
489
|
// Parse the declared harness set ONCE (alias-normalized) and feed both the
|
|
479
490
|
// dialect pick and the mirror from it — no re-parsing, no cwd-sniffing in the
|
|
@@ -540,7 +551,7 @@ async function compile(specPaths, config, opts = {}) {
|
|
|
540
551
|
allValid = false;
|
|
541
552
|
}
|
|
542
553
|
else if (spec._specType === "railway") {
|
|
543
|
-
knownAgents ??= await collectAgentNames();
|
|
554
|
+
knownAgents ??= await collectAgentNames(excludes);
|
|
544
555
|
if (!compileRailwayToFile(spec, specPath, knownAgents))
|
|
545
556
|
allValid = false;
|
|
546
557
|
}
|
|
@@ -663,12 +674,12 @@ function check(filePaths, silent = false) {
|
|
|
663
674
|
* Catches spec bloat — rules that likely say the same thing in different words.
|
|
664
675
|
* Uses information-theoretic distance (gzip-based) — no LLM, fully deterministic.
|
|
665
676
|
*/
|
|
666
|
-
async function findDuplicateRules(threshold = 0.3, silent = false, scopeFiles) {
|
|
677
|
+
async function findDuplicateRules(excludes, threshold = 0.3, silent = false, scopeFiles) {
|
|
667
678
|
const log = (msg) => {
|
|
668
679
|
if (!silent)
|
|
669
680
|
console.log(msg);
|
|
670
681
|
};
|
|
671
|
-
const allSpecs = findSpecs();
|
|
682
|
+
const allSpecs = findSpecs(excludes);
|
|
672
683
|
// If lint was invoked with explicit file arguments, only scan the specs
|
|
673
684
|
// for those files — otherwise an unrelated duplicate elsewhere in the
|
|
674
685
|
// repo would fail a targeted CI check (e.g. `vigiles lint path/foo.md`).
|
|
@@ -1173,7 +1184,7 @@ function sharedDirsRootFor(scanTarget) {
|
|
|
1173
1184
|
* third-party plugins — and scoring someone else's vendored plugin as if it were
|
|
1174
1185
|
* yours is the false-positive that gets a gate switched off.
|
|
1175
1186
|
*/
|
|
1176
|
-
function discoverNestedBundles(root,
|
|
1187
|
+
function discoverNestedBundles(root, excludes) {
|
|
1177
1188
|
const out = [];
|
|
1178
1189
|
const skip = new Set([
|
|
1179
1190
|
"node_modules",
|
|
@@ -1184,12 +1195,12 @@ function discoverNestedBundles(root, exclude = []) {
|
|
|
1184
1195
|
]);
|
|
1185
1196
|
const isBundle = (dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, ".claude-plugin", "plugin.json")) ||
|
|
1186
1197
|
(0, node_fs_1.existsSync)((0, node_path_1.join)(dir, "skills"));
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
(0, minimatch_1.minimatch)(rel, `${pattern}/**`));
|
|
1198
|
+
// Root-relative to the REPO (excludes.root), not to `root`: `lint some/dir`
|
|
1199
|
+
// still honours a repo-root `exclude`. One predicate for every walk (#192).
|
|
1200
|
+
const excluded = (dir) => excludes.matches((0, node_path_1.relative)(excludes.root, dir));
|
|
1191
1201
|
let entries;
|
|
1192
1202
|
try {
|
|
1203
|
+
// eslint-disable-next-line no-restricted-syntax -- the nested-bundle walk: every entry is filtered by excludes.matches() below
|
|
1193
1204
|
entries = (0, node_fs_1.readdirSync)(root, { withFileTypes: true })
|
|
1194
1205
|
.filter((e) => e.isDirectory() && !skip.has(e.name) && !e.name.startsWith("."))
|
|
1195
1206
|
.map((e) => e.name);
|
|
@@ -1199,7 +1210,7 @@ function discoverNestedBundles(root, exclude = []) {
|
|
|
1199
1210
|
}
|
|
1200
1211
|
for (const name of entries) {
|
|
1201
1212
|
const dir = (0, node_path_1.join)(root, name);
|
|
1202
|
-
if (excluded(
|
|
1213
|
+
if (excluded(dir))
|
|
1203
1214
|
continue;
|
|
1204
1215
|
// A container (`plugins/`) holds bundles; a bundle may also sit directly.
|
|
1205
1216
|
if (isBundle(dir)) {
|
|
@@ -1208,6 +1219,7 @@ function discoverNestedBundles(root, exclude = []) {
|
|
|
1208
1219
|
}
|
|
1209
1220
|
let inner;
|
|
1210
1221
|
try {
|
|
1222
|
+
// eslint-disable-next-line no-restricted-syntax -- the nested-bundle walk: every entry is filtered by excludes.matches() below
|
|
1211
1223
|
inner = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })
|
|
1212
1224
|
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
1213
1225
|
.map((e) => e.name);
|
|
@@ -1217,7 +1229,7 @@ function discoverNestedBundles(root, exclude = []) {
|
|
|
1217
1229
|
}
|
|
1218
1230
|
for (const child of inner) {
|
|
1219
1231
|
const sub = (0, node_path_1.join)(dir, child);
|
|
1220
|
-
if (excluded(
|
|
1232
|
+
if (excluded(sub))
|
|
1221
1233
|
continue;
|
|
1222
1234
|
if (isBundle(sub))
|
|
1223
1235
|
out.push(sub);
|
|
@@ -1266,12 +1278,12 @@ function overBundles(fn, config, silent, adapter, roots) {
|
|
|
1266
1278
|
* Cost is bounded: only specs whose compiled target actually EXISTS are loaded,
|
|
1267
1279
|
* so a repo with no specs does no extra work at all.
|
|
1268
1280
|
*/
|
|
1269
|
-
async function checkSpecRefs(config, silent, dialect) {
|
|
1281
|
+
async function checkSpecRefs(excludes, config, silent, dialect) {
|
|
1270
1282
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["spec-refs"]) ?? "error";
|
|
1271
1283
|
if (!sev)
|
|
1272
1284
|
return { issues: 0, errors: 0 };
|
|
1273
1285
|
const found = [];
|
|
1274
|
-
for (const specPath of findSpecs()) {
|
|
1286
|
+
for (const specPath of findSpecs(excludes)) {
|
|
1275
1287
|
const target = specPath.replace(/\.spec\.ts$/, "");
|
|
1276
1288
|
if (!(0, node_fs_1.existsSync)(target))
|
|
1277
1289
|
continue; // never compiled — `compile` reports it
|
|
@@ -1319,7 +1331,7 @@ async function checkSpecRefs(config, silent, dialect) {
|
|
|
1319
1331
|
function writeArtifact(outputPath, artifact) {
|
|
1320
1332
|
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), artifact);
|
|
1321
1333
|
}
|
|
1322
|
-
async function runLint(restArgs, flags, config) {
|
|
1334
|
+
async function runLint(restArgs, flags, excludes, config) {
|
|
1323
1335
|
const summary = flags.includes("--summary");
|
|
1324
1336
|
const json = flags.includes("--json");
|
|
1325
1337
|
const silent = summary || json;
|
|
@@ -1364,7 +1376,7 @@ async function runLint(restArgs, flags, config) {
|
|
|
1364
1376
|
// is the false positive that gets a gate turned off. So the DEFAULT fixes the
|
|
1365
1377
|
// SILENCE, and `bundles: "all"` fixes the COVERAGE — one exit code over the
|
|
1366
1378
|
// whole repo, which is what a CI gate needs.
|
|
1367
|
-
const nestedBundles = discoverNestedBundles(scanRoot,
|
|
1379
|
+
const nestedBundles = discoverNestedBundles(scanRoot, excludes);
|
|
1368
1380
|
const scoreAll = flags.includes("--bundles=all") || config?.bundles === "all";
|
|
1369
1381
|
const lintRoots = scoreAll ? [scanRoot, ...nestedBundles] : [scanRoot];
|
|
1370
1382
|
if (!silent && nestedBundles.length > 0) {
|
|
@@ -1381,7 +1393,7 @@ async function runLint(restArgs, flags, config) {
|
|
|
1381
1393
|
// hand-edit of a compiled subagent slipped past `lint`. A hand-written agent
|
|
1382
1394
|
// (no hash header) stays a no-op, and require-instructions-spec is filename-
|
|
1383
1395
|
// scoped to CLAUDE/AGENTS so agents are never falsely flagged as spec-less.
|
|
1384
|
-
const files = findInstructionFiles(restArgs,
|
|
1396
|
+
const files = findInstructionFiles(restArgs, excludes, adapter.layout.agentDir);
|
|
1385
1397
|
// 1. Verify hashes and structure
|
|
1386
1398
|
if (!silent) {
|
|
1387
1399
|
if (files.length > 0) {
|
|
@@ -1401,15 +1413,15 @@ async function runLint(restArgs, flags, config) {
|
|
|
1401
1413
|
// 2. Coverage gaps (discover)
|
|
1402
1414
|
if (!silent)
|
|
1403
1415
|
console.log("\nLinter rule coverage:\n");
|
|
1404
|
-
const coverage = discover(silent);
|
|
1416
|
+
const coverage = discover(excludes, silent);
|
|
1405
1417
|
// 3. Duplicate rule detection (NCD). Scope to the requested files when
|
|
1406
1418
|
// lint was invoked with explicit paths, so targeted CI checks don't
|
|
1407
1419
|
// fail on unrelated duplicates elsewhere in the repo.
|
|
1408
1420
|
if (!silent)
|
|
1409
1421
|
console.log("\nDuplicate rule detection:\n");
|
|
1410
|
-
const dups = await findDuplicateRules(0.3, silent, restArgs.length > 0 ? files : undefined);
|
|
1422
|
+
const dups = await findDuplicateRules(excludes, 0.3, silent, restArgs.length > 0 ? files : undefined);
|
|
1411
1423
|
// 4. Guidance rule count (strengthen suggestions moved to /strengthen skill)
|
|
1412
|
-
const guidanceCount = await countGuidanceRules(silent);
|
|
1424
|
+
const guidanceCount = await countGuidanceRules(excludes, silent);
|
|
1413
1425
|
// 5. Integrity check (hand-edit detection via SHA-256 hash)
|
|
1414
1426
|
const integritySeverity = config?.rules.integrity ?? "warn";
|
|
1415
1427
|
let integrityErrors = 0;
|
|
@@ -1419,7 +1431,7 @@ async function runLint(restArgs, flags, config) {
|
|
|
1419
1431
|
integrityErrors = checkIntegrityForFiles(files, integritySeverity, silent);
|
|
1420
1432
|
}
|
|
1421
1433
|
// 6. Coverage thresholds (gates CI when severity is "error")
|
|
1422
|
-
const coverageErrors = await checkCoverageThresholds(coverage, config, silent);
|
|
1434
|
+
const coverageErrors = await checkCoverageThresholds(excludes, coverage, config, silent);
|
|
1423
1435
|
// 7. Orphan docs check — OPT-IN (the `orphans` block in .vigilesrc.json is
|
|
1424
1436
|
// the on-switch). "Unreferenced" only means "rot" for a hand-cross-linked
|
|
1425
1437
|
// corpus; on a nav-managed doc site (Docusaurus/MkDocs) the page graph lives
|
|
@@ -1449,6 +1461,10 @@ async function runLint(restArgs, flags, config) {
|
|
|
1449
1461
|
basePath: process.cwd(),
|
|
1450
1462
|
include: orphansCfg.include,
|
|
1451
1463
|
exclude: orphansCfg.exclude,
|
|
1464
|
+
// The repo-wide `exclude` is the FLOOR under the rule's own `exclude`
|
|
1465
|
+
// (union, never override): an excluded corpus is neither an orphan
|
|
1466
|
+
// candidate nor a source of references that keep a doc alive (#192).
|
|
1467
|
+
repoExclude: excludes.ignore,
|
|
1452
1468
|
// Exempt every registered harness's surface files (instruction file,
|
|
1453
1469
|
// SKILL.md, subagents, commands) as orphan candidates — layout-driven so
|
|
1454
1470
|
// core carries no harness literal (see src/core/orphans.ts).
|
|
@@ -1468,8 +1484,8 @@ async function runLint(restArgs, flags, config) {
|
|
|
1468
1484
|
// hook} to "error" to gate CI. See src/test-coverage.ts and docs/rules/.
|
|
1469
1485
|
// A compiled artifact's refs, re-derived from its spec — the hash says the file
|
|
1470
1486
|
// is unchanged, not that what it names still exists (#173).
|
|
1471
|
-
const specRefs = await checkSpecRefs(config, silent, adapter.dialect);
|
|
1472
|
-
const untested = overBundles(checkUntestedSurfaces, config, silent, adapter, lintRoots);
|
|
1487
|
+
const specRefs = await checkSpecRefs(excludes, config, silent, adapter.dialect);
|
|
1488
|
+
const untested = overBundles((c, s, a, r) => checkUntestedSurfaces(excludes, c, s, a, r), config, silent, adapter, lintRoots);
|
|
1473
1489
|
// 7c. Subagent tool-contract check — cross-reference each subagent's `tools:`
|
|
1474
1490
|
// rail against the harness catalog (the moat). n/a on a harness with no
|
|
1475
1491
|
// subagents. Off by default unless a severity is configured; warning surfaces
|
|
@@ -1572,7 +1588,7 @@ async function runLint(restArgs, flags, config) {
|
|
|
1572
1588
|
// whose exit code is discarded gates nothing — after which hand-written CI steps
|
|
1573
1589
|
// grew to do the gating instead. One unpassed argument, that whole chain.
|
|
1574
1590
|
const docRefReport = docRefSeverity
|
|
1575
|
-
? (0, doc_refs_js_1.findDocRefs)({ basePath: process.cwd(), ignore:
|
|
1591
|
+
? (0, doc_refs_js_1.findDocRefs)({ basePath: process.cwd(), ignore: excludes.ignore })
|
|
1576
1592
|
: {
|
|
1577
1593
|
filesScanned: 0,
|
|
1578
1594
|
filesIgnored: 0,
|
|
@@ -1744,10 +1760,10 @@ function printLintSummary(report) {
|
|
|
1744
1760
|
console.log(`vigiles: ${parts.join(" / ")}`);
|
|
1745
1761
|
}
|
|
1746
1762
|
}
|
|
1747
|
-
function collectDocumentedRules() {
|
|
1763
|
+
function collectDocumentedRules(excludes) {
|
|
1748
1764
|
const documented = new Set();
|
|
1749
1765
|
const mdFiles = (0, glob_1.globSync)("**/CLAUDE.md", {
|
|
1750
|
-
ignore:
|
|
1766
|
+
ignore: excludes.globIgnore,
|
|
1751
1767
|
cwd: process.cwd(),
|
|
1752
1768
|
});
|
|
1753
1769
|
for (const mdFile of mdFiles) {
|
|
@@ -1789,14 +1805,14 @@ function printLinterCoverage(linter, documentedRules, silent = false) {
|
|
|
1789
1805
|
log("");
|
|
1790
1806
|
return { enabled: linter.rules.length, documented: documented.length };
|
|
1791
1807
|
}
|
|
1792
|
-
function discover(silent = false) {
|
|
1808
|
+
function discover(excludes, silent = false) {
|
|
1793
1809
|
const log = (msg) => {
|
|
1794
1810
|
if (!silent)
|
|
1795
1811
|
console.log(msg);
|
|
1796
1812
|
};
|
|
1797
1813
|
log("Scanning project for linter rules...\n");
|
|
1798
1814
|
const result = (0, generate_types_js_1.generateTypes)({ basePath: process.cwd() });
|
|
1799
|
-
const documentedRules = collectDocumentedRules();
|
|
1815
|
+
const documentedRules = collectDocumentedRules(excludes);
|
|
1800
1816
|
log("Detected linters:\n");
|
|
1801
1817
|
let totalEnabled = 0;
|
|
1802
1818
|
let totalDocumented = 0;
|
|
@@ -1889,6 +1905,7 @@ function discoverAdoptableSurfaces(cwd) {
|
|
|
1889
1905
|
const abs = (0, node_path_1.resolve)(cwd, root);
|
|
1890
1906
|
if (!(0, node_fs_1.existsSync)(abs))
|
|
1891
1907
|
continue;
|
|
1908
|
+
// eslint-disable-next-line no-restricted-syntax -- init's shallow adoptable-surface sweep, top level only (exclude.ts exceptions)
|
|
1892
1909
|
for (const e of (0, node_fs_1.readdirSync)(abs, { withFileTypes: true })) {
|
|
1893
1910
|
const rel = `${root}/${e.name}/SKILL.md`;
|
|
1894
1911
|
if (e.isDirectory() && unspecced(rel))
|
|
@@ -1899,6 +1916,7 @@ function discoverAdoptableSurfaces(cwd) {
|
|
|
1899
1916
|
const abs = (0, node_path_1.resolve)(cwd, root);
|
|
1900
1917
|
if (!(0, node_fs_1.existsSync)(abs))
|
|
1901
1918
|
continue;
|
|
1919
|
+
// eslint-disable-next-line no-restricted-syntax -- init's shallow adoptable-surface sweep, top level only (exclude.ts exceptions)
|
|
1902
1920
|
for (const e of (0, node_fs_1.readdirSync)(abs, { withFileTypes: true })) {
|
|
1903
1921
|
const rel = `${root}/${e.name}`;
|
|
1904
1922
|
if (e.isFile() && e.name.endsWith(".md") && unspecced(rel))
|
|
@@ -1970,6 +1988,7 @@ const RULE_INVENTORY_SKIP_DIRS = new Set([
|
|
|
1970
1988
|
/** readdir that returns [] instead of throwing (perms, races). */
|
|
1971
1989
|
function safeReaddir(dir) {
|
|
1972
1990
|
try {
|
|
1991
|
+
// eslint-disable-next-line no-restricted-syntax -- lint-config collection for the linter catalog, not repo policing (exclude.ts exceptions)
|
|
1973
1992
|
return (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
1974
1993
|
}
|
|
1975
1994
|
catch {
|
|
@@ -2011,11 +2030,11 @@ function collectLintConfigText(root) {
|
|
|
2011
2030
|
* file(s) + lint-config TEXT (never executed) and map documented intents to
|
|
2012
2031
|
* off-the-shelf rules + whether they're already configured. Best-effort, fs-only;
|
|
2013
2032
|
* NO model, NO config execution — safe on any repo. Composition-root. */
|
|
2014
|
-
function computeRuleInventory(root, instructionFile) {
|
|
2033
|
+
function computeRuleInventory(root, instructionFile, excludes) {
|
|
2015
2034
|
try {
|
|
2016
2035
|
// The inventory maps intents → rules; it has no per-file line provenance, so
|
|
2017
2036
|
// the concatenated text is fine here (unlike the routing preview below).
|
|
2018
|
-
const instructionText = gatherInstructionFiles(root, instructionFile)
|
|
2037
|
+
const instructionText = gatherInstructionFiles(root, instructionFile, excludes)
|
|
2019
2038
|
.map((f) => f.text)
|
|
2020
2039
|
.join("\n");
|
|
2021
2040
|
if (!instructionText.trim())
|
|
@@ -2034,7 +2053,7 @@ function computeRuleInventory(root, instructionFile) {
|
|
|
2034
2053
|
* `research/CLAUDE.md`, …), skipping fixture/demo/build/test dirs (`isFixturePath`)
|
|
2035
2054
|
* so a repo's real memory is read without the test-fixture noise. `.claude/` rule
|
|
2036
2055
|
* sources remain a future source. research/rule-enforcer-multilang-design.md §0. */
|
|
2037
|
-
function gatherInstructionFiles(root, instructionFile) {
|
|
2056
|
+
function gatherInstructionFiles(root, instructionFile, excludes) {
|
|
2038
2057
|
const raw = [];
|
|
2039
2058
|
const collect = (rel) => {
|
|
2040
2059
|
const p = (0, node_path_1.resolve)(root, rel);
|
|
@@ -2049,11 +2068,14 @@ function gatherInstructionFiles(root, instructionFile) {
|
|
|
2049
2068
|
// Root instruction files first (stable, deterministic order).
|
|
2050
2069
|
for (const name of new Set([instructionFile, "CLAUDE.md", "AGENTS.md"]))
|
|
2051
2070
|
collect(name);
|
|
2052
|
-
// Nested subdirectory memory, minus fixture/demo/build/test noise.
|
|
2071
|
+
// Nested subdirectory memory, minus fixture/demo/build/test noise. Two
|
|
2072
|
+
// filters, deliberately: `isFixturePath` is the zero-config floor (audit
|
|
2073
|
+
// reads any repo with no .vigilesrc.json), and the configured `exclude` is
|
|
2074
|
+
// unioned ON TOP for the dirs the heuristic cannot guess (#192).
|
|
2053
2075
|
try {
|
|
2054
2076
|
const nested = (0, glob_1.globSync)(["**/CLAUDE.md", "**/AGENTS.md"], {
|
|
2055
2077
|
cwd: root,
|
|
2056
|
-
ignore:
|
|
2078
|
+
ignore: excludes.globIgnore,
|
|
2057
2079
|
})
|
|
2058
2080
|
.filter((rel) => !(0, instruction_sources_js_1.isFixturePath)(rel))
|
|
2059
2081
|
.sort();
|
|
@@ -2111,9 +2133,9 @@ function hasPythonSurface(root) {
|
|
|
2111
2133
|
}
|
|
2112
2134
|
return false;
|
|
2113
2135
|
}
|
|
2114
|
-
function computeRuleRouting(root, instructionFile) {
|
|
2136
|
+
function computeRuleRouting(root, instructionFile, excludes) {
|
|
2115
2137
|
try {
|
|
2116
|
-
const files = gatherInstructionFiles(root, instructionFile);
|
|
2138
|
+
const files = gatherInstructionFiles(root, instructionFile, excludes);
|
|
2117
2139
|
if (files.every((f) => !f.text.trim()))
|
|
2118
2140
|
return undefined;
|
|
2119
2141
|
// Own-repo + consented → enumerate the live rule catalog of whichever
|
|
@@ -2246,16 +2268,23 @@ function scaffoldSpec(args) {
|
|
|
2246
2268
|
logAdoptedSurface(target, specPath, "subagent", unmappedKeys);
|
|
2247
2269
|
}
|
|
2248
2270
|
else {
|
|
2249
|
-
const { source, tier, sectionCount } = (0, adopt_js_1.adoptMarkdown)(md, (0, node_path_1.basename)(target)
|
|
2271
|
+
const { source, tier, sectionCount, adoptedRefs } = (0, adopt_js_1.adoptMarkdown)(md, (0, node_path_1.basename)(target),
|
|
2272
|
+
// RESOLVE-NOW, not a heuristic. The undecidable question is "is this OUR
|
|
2273
|
+
// path or one inside the repo this document DESCRIBES" — the wall that
|
|
2274
|
+
// disabled `doc-refs`. Asking "does it resolve here, right now?" sidesteps
|
|
2275
|
+
// it: a described repo's path does not exist locally, so only already-green
|
|
2276
|
+
// refs are emitted and adoption can never turn a passing file red.
|
|
2277
|
+
{ exists: (ref) => (0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), ref)) });
|
|
2250
2278
|
(0, node_fs_1.writeFileSync)(specAbs, source);
|
|
2251
2279
|
console.log(`Adopted ${target} → ${specPath} (${tier}, ${String(sectionCount)} section${sectionCount === 1 ? "" : "s"}). ` +
|
|
2280
|
+
(adoptedRefs && adoptedRefs.length > 0
|
|
2281
|
+
? `${String(adoptedRefs.length)} reference${adoptedRefs.length === 1 ? "" : "s"} resolved and became verified \`file()\` calls — they now fail the build if the file moves. `
|
|
2282
|
+
: "") +
|
|
2252
2283
|
`Run \`vigiles compile\` and review the diff; the \`/strengthen\` skill upgrades prose to verified rules.`);
|
|
2253
|
-
// Adoption
|
|
2254
|
-
//
|
|
2255
|
-
//
|
|
2256
|
-
//
|
|
2257
|
-
// rather than as not-yet-claimed. Deliberately not a heuristic extractor:
|
|
2258
|
-
// guessing refs out of prose is what got `doc-refs` disabled.
|
|
2284
|
+
// Adoption infers NO RULES (that stays `strengthen`'s job) but it DOES now
|
|
2285
|
+
// extract refs that resolve — measured 2026-08-28 on a 51-skill monorepo,
|
|
2286
|
+
// where extracting nothing meant the cost landed at once (build artifact,
|
|
2287
|
+
// edits into TS) while the payoff waited on a manual pass nobody ran.
|
|
2259
2288
|
if (tier === "raw")
|
|
2260
2289
|
console.log(` ℹ 0 refs extracted — this spec verifies nothing yet. Wrap paths in \`file()\` ` +
|
|
2261
2290
|
`and commands in \`cmd()\` to make \`compile\` check them.`);
|
|
@@ -2401,6 +2430,7 @@ function specReferencedElsewhere(specFile, ejectedFile) {
|
|
|
2401
2430
|
const dir = stack.pop();
|
|
2402
2431
|
let entries;
|
|
2403
2432
|
try {
|
|
2433
|
+
// eslint-disable-next-line no-restricted-syntax -- eject's is-this-spec-compiled-elsewhere safety check; wider is safer (exclude.ts exceptions)
|
|
2404
2434
|
entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
2405
2435
|
}
|
|
2406
2436
|
catch {
|
|
@@ -2904,15 +2934,17 @@ async function setupPillar1(detected, targetValue, harnesses) {
|
|
|
2904
2934
|
// run `npm install` yet, so compiling would just error; defer it with a clear
|
|
2905
2935
|
// next step instead of a scary stack-traceless "failed to load".
|
|
2906
2936
|
const canCompile = canResolveVigiles(cwd);
|
|
2937
|
+
const initConfig = (0, validate_js_1.loadConfig)();
|
|
2938
|
+
const excludes = (0, exclude_js_1.excludeSet)(cwd, initConfig.exclude);
|
|
2907
2939
|
const specs = canCompile
|
|
2908
|
-
? findSpecs().filter((s) => {
|
|
2940
|
+
? findSpecs(excludes).filter((s) => {
|
|
2909
2941
|
const tf = (0, node_path_1.resolve)(cwd, s.replace(/\.spec\.ts$/, ""));
|
|
2910
2942
|
return !(0, node_fs_1.existsSync)(tf) || targetHasHash(tf);
|
|
2911
2943
|
})
|
|
2912
2944
|
: [];
|
|
2913
2945
|
if (specs.length > 0) {
|
|
2914
2946
|
console.log("\nCompiling specs...");
|
|
2915
|
-
await compile(specs,
|
|
2947
|
+
await compile(specs, initConfig, excludes);
|
|
2916
2948
|
}
|
|
2917
2949
|
else if (!canCompile) {
|
|
2918
2950
|
// Honest, project-type-aware guidance. A JS repo just needs `npm install`
|
|
@@ -3476,7 +3508,7 @@ function untestedRules(config) {
|
|
|
3476
3508
|
* "warn" prints but never fails CI; "error" fails (exit 2). Returns the raw
|
|
3477
3509
|
* untested count plus the severity-gated error count.
|
|
3478
3510
|
*/
|
|
3479
|
-
function checkUntestedSurfaces(config, silent, adapter, scanRoot) {
|
|
3511
|
+
function checkUntestedSurfaces(excludes, config, silent, adapter, scanRoot) {
|
|
3480
3512
|
const { severity: sevFor, anyEnabled, options } = untestedRules(config);
|
|
3481
3513
|
if (!anyEnabled)
|
|
3482
3514
|
return { untested: 0, errors: 0 };
|
|
@@ -3484,6 +3516,10 @@ function checkUntestedSurfaces(config, silent, adapter, scanRoot) {
|
|
|
3484
3516
|
...options,
|
|
3485
3517
|
basePath: scanRoot,
|
|
3486
3518
|
layout: adapter.layout,
|
|
3519
|
+
// The rule's own `exclude` NARROWS; the repo-wide one is the floor under
|
|
3520
|
+
// it. Union, never override — a rule option must not re-admit a vendored
|
|
3521
|
+
// corpus the repo excluded (#192).
|
|
3522
|
+
exclude: [...(options.exclude ?? []), ...excludes.ignore],
|
|
3487
3523
|
});
|
|
3488
3524
|
if (!silent) {
|
|
3489
3525
|
console.log("\nUntested surfaces:\n");
|
|
@@ -4155,7 +4191,7 @@ function checkMcpToolResolves(config, silent, adapter, scanRoot) {
|
|
|
4155
4191
|
* avoids depending on a pre-built `dist/` tree, which the setup-generated
|
|
4156
4192
|
* CI step doesn't guarantee.
|
|
4157
4193
|
*/
|
|
4158
|
-
async function checkCoverageThresholds(coverage, config, silent) {
|
|
4194
|
+
async function checkCoverageThresholds(excludes, coverage, config, silent) {
|
|
4159
4195
|
const severity = (0, types_js_1.ruleSeverity)(config?.rules.coverage);
|
|
4160
4196
|
if (!severity)
|
|
4161
4197
|
return 0;
|
|
@@ -4181,9 +4217,9 @@ async function checkCoverageThresholds(coverage, config, silent) {
|
|
|
4181
4217
|
}
|
|
4182
4218
|
if (opts.scripts !== undefined) {
|
|
4183
4219
|
// Load all claude specs so coverage doesn't depend on a built dist/.
|
|
4184
|
-
const loaded = await Promise.all(findSpecs().map(loadSpec));
|
|
4220
|
+
const loaded = await Promise.all(findSpecs(excludes).map(loadSpec));
|
|
4185
4221
|
const claudeSpecs = loaded.filter((s) => s?._specType === "claude");
|
|
4186
|
-
const metric = (0, coverage_js_1.computeScriptCoverage)(process.cwd(), opts.scripts, claudeSpecs);
|
|
4222
|
+
const metric = (0, coverage_js_1.computeScriptCoverage)(process.cwd(), opts.scripts, claudeSpecs, excludes.ignore);
|
|
4187
4223
|
const ok = metric.passing;
|
|
4188
4224
|
if (!ok)
|
|
4189
4225
|
failing++;
|
|
@@ -4192,8 +4228,8 @@ async function checkCoverageThresholds(coverage, config, silent) {
|
|
|
4192
4228
|
}
|
|
4193
4229
|
return severity === "error" ? failing : 0;
|
|
4194
4230
|
}
|
|
4195
|
-
async function countGuidanceRules(silent = false) {
|
|
4196
|
-
const specs = findSpecs();
|
|
4231
|
+
async function countGuidanceRules(excludes, silent = false) {
|
|
4232
|
+
const specs = findSpecs(excludes);
|
|
4197
4233
|
if (specs.length === 0)
|
|
4198
4234
|
return 0;
|
|
4199
4235
|
let count = 0;
|
|
@@ -4214,7 +4250,22 @@ async function countGuidanceRules(silent = false) {
|
|
|
4214
4250
|
// ---------------------------------------------------------------------------
|
|
4215
4251
|
// Command handlers for main()
|
|
4216
4252
|
// ---------------------------------------------------------------------------
|
|
4217
|
-
|
|
4253
|
+
/**
|
|
4254
|
+
* An explicitly named path is processed even when `exclude` matches it, and ONE
|
|
4255
|
+
* line says so. `exclude` filters DISCOVERY; an argument is an instruction.
|
|
4256
|
+
* Measured 2026-09-03: ripgrep and tsc do this silently, ESLint skips the file
|
|
4257
|
+
* with a warning, prettier skips it and reports "all files use Prettier code
|
|
4258
|
+
* style" — the silent no-op. We take rg/tsc's semantics with ESLint's loudness.
|
|
4259
|
+
* Returns the path unchanged so it composes inside a `.map`.
|
|
4260
|
+
*/
|
|
4261
|
+
function noteExplicitOverride(excludes, path, verb) {
|
|
4262
|
+
const pattern = excludes.explain((0, node_path_1.relative)(excludes.root, (0, node_path_1.resolve)(path)));
|
|
4263
|
+
if (pattern !== null) {
|
|
4264
|
+
console.log(`note: ${path} matches exclude "${pattern}" — ${verb} because you named it`);
|
|
4265
|
+
}
|
|
4266
|
+
return path;
|
|
4267
|
+
}
|
|
4268
|
+
function findInstructionFiles(restArgs, excludes, agentDir = "") {
|
|
4218
4269
|
const patterns = ["**/CLAUDE.md", "**/AGENTS.md", "**/SKILL.md"];
|
|
4219
4270
|
// Include the active harness's subagent dir so a COMPILED `agents/<name>.md`
|
|
4220
4271
|
// (a vigiles-hashed file) is integrity-checked (dogfood E2). Empty for a
|
|
@@ -4222,12 +4273,17 @@ function findInstructionFiles(restArgs, exclude = [], agentDir = "") {
|
|
|
4222
4273
|
if (agentDir !== "")
|
|
4223
4274
|
patterns.push(`**/${agentDir}/*.md`);
|
|
4224
4275
|
// `exclude` (from .vigilesrc.json) drops vendored/benchmark fixtures the repo's
|
|
4225
|
-
// own lint shouldn't police — a third-party CLAUDE.md isn't held to
|
|
4226
|
-
//
|
|
4227
|
-
|
|
4276
|
+
// own lint shouldn't police — a third-party CLAUDE.md isn't held to
|
|
4277
|
+
// require-instructions-spec. The FUNCTION face of the ExcludeSet, not the
|
|
4278
|
+
// string list: `discoverIn` may glob from a subdirectory (`vigiles lint
|
|
4279
|
+
// some/dir`), where a root-relative string pattern would match nothing.
|
|
4228
4280
|
// Discover instruction files under one directory, as paths relative to cwd.
|
|
4229
4281
|
const discoverIn = (dirAbs) => patterns
|
|
4230
|
-
.flatMap((p) => (0, glob_1.globSync)(p, {
|
|
4282
|
+
.flatMap((p) => (0, glob_1.globSync)(p, {
|
|
4283
|
+
ignore: excludes.globIgnore,
|
|
4284
|
+
cwd: dirAbs,
|
|
4285
|
+
absolute: true,
|
|
4286
|
+
}))
|
|
4231
4287
|
.map((abs) => (0, node_path_1.relative)(process.cwd(), abs));
|
|
4232
4288
|
if (restArgs.length === 0)
|
|
4233
4289
|
return discoverIn(process.cwd());
|
|
@@ -4237,6 +4293,7 @@ function findInstructionFiles(restArgs, exclude = [], agentDir = "") {
|
|
|
4237
4293
|
const out = [];
|
|
4238
4294
|
for (const arg of restArgs) {
|
|
4239
4295
|
const abs = (0, node_path_1.resolve)(process.cwd(), arg);
|
|
4296
|
+
noteExplicitOverride(excludes, arg, "linting");
|
|
4240
4297
|
if ((0, node_fs_1.existsSync)(abs) && (0, node_fs_1.lstatSync)(abs).isDirectory()) {
|
|
4241
4298
|
out.push(...discoverIn(abs));
|
|
4242
4299
|
}
|
|
@@ -4674,9 +4731,14 @@ function resolveRecords(cwd, runs, tier, harnessFlag) {
|
|
|
4674
4731
|
}
|
|
4675
4732
|
return [...names];
|
|
4676
4733
|
}
|
|
4734
|
+
const recordsConfig = (0, validate_js_1.loadConfig)();
|
|
4677
4735
|
const scan = (0, test_coverage_js_1.findUntestedSurfaces)({
|
|
4678
4736
|
basePath: cwd,
|
|
4679
|
-
layout: harnessLayoutFor(cwd,
|
|
4737
|
+
layout: harnessLayoutFor(cwd, recordsConfig, harnessFlag),
|
|
4738
|
+
// The repo-wide `exclude` only — the per-rule severities/testGlobs stay out
|
|
4739
|
+
// of record resolution on purpose (a run's probes must map to a surface
|
|
4740
|
+
// whether or not the untested-* rule for its kind is on).
|
|
4741
|
+
exclude: (0, exclude_js_1.excludeSet)(cwd, recordsConfig.exclude).ignore,
|
|
4680
4742
|
});
|
|
4681
4743
|
return (0, coverage_artifact_js_1.recordsFrom)({
|
|
4682
4744
|
runs,
|
|
@@ -4754,7 +4816,7 @@ function gitHead(cwd) {
|
|
|
4754
4816
|
return "";
|
|
4755
4817
|
}
|
|
4756
4818
|
}
|
|
4757
|
-
async function handleRunScripts(kind, args, restArgs) {
|
|
4819
|
+
async function handleRunScripts(kind, args, restArgs, excludes) {
|
|
4758
4820
|
const cwd = process.cwd();
|
|
4759
4821
|
// Harness/eval scripts may be authored in JS or TS (see run-scripts.ts).
|
|
4760
4822
|
const defaultGlob = (0, run_scripts_js_1.scriptGlob)(kind === "test" ? "harness" : "eval");
|
|
@@ -4768,7 +4830,9 @@ async function handleRunScripts(kind, args, restArgs) {
|
|
|
4768
4830
|
return;
|
|
4769
4831
|
lockEnv = r;
|
|
4770
4832
|
}
|
|
4771
|
-
|
|
4833
|
+
// A script under an excluded path is not DISCOVERED (a vendored corpus's own
|
|
4834
|
+
// harness must not run as ours), but a script you NAME still runs (#192).
|
|
4835
|
+
const files = (0, run_scripts_js_1.discoverScripts)(restArgs.map((p) => noteExplicitOverride(excludes, p, "running")), defaultGlob, cwd, excludes.ignore);
|
|
4772
4836
|
// `--min=N`: a CI gate asserts at least N scripts actually RAN — so a bad path,
|
|
4773
4837
|
// a renamed file, or a glob that matched nothing fails LOUD instead of passing
|
|
4774
4838
|
// green with zero evals executed. Default 0 (off) keeps local runs ergonomic.
|
|
@@ -5481,8 +5545,18 @@ function evalLockNudgeHookCommand() {
|
|
|
5481
5545
|
// heard nothing at all, while a repo that already tests got reminded. That is
|
|
5482
5546
|
// backwards, and `untested-skill` already stated the missing half correctly;
|
|
5483
5547
|
// it just lived in `vigiles lint`, which someone has to run by hand.
|
|
5484
|
-
const msg = (0, test_coverage_js_1.skillTestNudge)(target, {
|
|
5485
|
-
|
|
5548
|
+
const msg = (0, test_coverage_js_1.skillTestNudge)(target, {
|
|
5549
|
+
...options,
|
|
5550
|
+
basePath: cwd,
|
|
5551
|
+
layout,
|
|
5552
|
+
// Same union `vigiles lint` applies: the rule's exclude narrows, the
|
|
5553
|
+
// repo-wide exclude is the floor (#192) — the nudge must not report a
|
|
5554
|
+
// vendored skill the linter would never list.
|
|
5555
|
+
exclude: [
|
|
5556
|
+
...(options.exclude ?? []),
|
|
5557
|
+
...(0, exclude_js_1.excludeSet)(cwd, config.exclude).ignore,
|
|
5558
|
+
],
|
|
5559
|
+
}) ?? (0, eval_lock_js_1.evalLockNudge)(target, (0, node_path_1.resolve)(cwd, eval_lock_js_1.DEFAULT_LOCK_DIR));
|
|
5486
5560
|
if (!msg)
|
|
5487
5561
|
return;
|
|
5488
5562
|
process.stdout.write(JSON.stringify({
|
|
@@ -6654,6 +6728,10 @@ async function main() {
|
|
|
6654
6728
|
// Shared flags (--max-rules, --catalog-only) override the loaded config so
|
|
6655
6729
|
// every GitHub Action input maps to a real CLI flag. See src/cli-flags.ts.
|
|
6656
6730
|
const config = (0, cli_flags_js_1.applyConfigFlags)((0, validate_js_1.loadConfig)(), args);
|
|
6731
|
+
// `.vigilesrc.json#exclude`, parsed ONCE here and threaded to every walk that
|
|
6732
|
+
// polices the repo (src/exclude.ts). Built where the config is loaded so a
|
|
6733
|
+
// command cannot re-derive it differently — the drift #192 measured.
|
|
6734
|
+
const excludes = (0, exclude_js_1.excludeSet)(process.cwd(), config.exclude);
|
|
6657
6735
|
switch (command) {
|
|
6658
6736
|
// --- Primary commands ---
|
|
6659
6737
|
case "init": {
|
|
@@ -6676,8 +6754,10 @@ async function main() {
|
|
|
6676
6754
|
// a hook program → its harness config + stamp (cohesive-cli-surface). With
|
|
6677
6755
|
// explicit args, partition by extension; bare, discover both.
|
|
6678
6756
|
const specs = restArgs.length > 0
|
|
6679
|
-
? restArgs
|
|
6680
|
-
|
|
6757
|
+
? restArgs
|
|
6758
|
+
.filter((f) => f.endsWith(".spec.ts"))
|
|
6759
|
+
.map((f) => noteExplicitOverride(excludes, f, "compiling"))
|
|
6760
|
+
: findSpecs(excludes);
|
|
6681
6761
|
const hooks = restArgs.length > 0
|
|
6682
6762
|
? restArgs.filter((f) => !f.endsWith(".spec.ts"))
|
|
6683
6763
|
: (0, hook_install_js_1.discoverHookFiles)(process.cwd());
|
|
@@ -6688,7 +6768,8 @@ async function main() {
|
|
|
6688
6768
|
}
|
|
6689
6769
|
let valid = true;
|
|
6690
6770
|
if (specs.length > 0)
|
|
6691
|
-
valid =
|
|
6771
|
+
valid =
|
|
6772
|
+
(await compile(specs, config, excludes, { harnessFlag })) && valid;
|
|
6692
6773
|
valid = (await installHooks(hooks, harnessFlag, config.harness)) && valid;
|
|
6693
6774
|
// Keep an existing whole-harness registry in sync (cheap, opt-in) so the
|
|
6694
6775
|
// user never hand-runs `generate-harness`. Skipped when no harness.gen.ts.
|
|
@@ -6712,7 +6793,7 @@ async function main() {
|
|
|
6712
6793
|
case "lint": {
|
|
6713
6794
|
// lint = verify references + discover + guidance count
|
|
6714
6795
|
const flags = args.slice(1).filter((a) => a.startsWith("--"));
|
|
6715
|
-
const report = await runLint(restArgs, flags, config);
|
|
6796
|
+
const report = await runLint(restArgs, flags, excludes, config);
|
|
6716
6797
|
annotateLintForGitHub(report, flags);
|
|
6717
6798
|
const exitCode = lintExitCode(report);
|
|
6718
6799
|
if (exitCode !== 0) {
|
|
@@ -6721,10 +6802,10 @@ async function main() {
|
|
|
6721
6802
|
break;
|
|
6722
6803
|
}
|
|
6723
6804
|
case "test":
|
|
6724
|
-
await handleRunScripts("test", args, restArgs);
|
|
6805
|
+
await handleRunScripts("test", args, restArgs, excludes);
|
|
6725
6806
|
break;
|
|
6726
6807
|
case "eval":
|
|
6727
|
-
await handleRunScripts("eval", args, restArgs);
|
|
6808
|
+
await handleRunScripts("eval", args, restArgs, excludes);
|
|
6728
6809
|
break;
|
|
6729
6810
|
case "audit": {
|
|
6730
6811
|
// The Lighthouse run: a plain `audit` is a deterministic READ — rings, each
|
|
@@ -6735,7 +6816,9 @@ async function main() {
|
|
|
6735
6816
|
// in `.vigilesrc.json`), headless it stays a read + a one-line nudge. There
|
|
6736
6817
|
// is NO execution flag — automation tests the harness via the vigiles testing
|
|
6737
6818
|
// API + skills, not the report verb. See the `audit-side-effect-free` rule.
|
|
6738
|
-
const dirs = restArgs.length > 0
|
|
6819
|
+
const dirs = restArgs.length > 0
|
|
6820
|
+
? restArgs.map((d) => noteExplicitOverride(excludes, d, "auditing"))
|
|
6821
|
+
: ["."];
|
|
6739
6822
|
const json = args.includes("--json");
|
|
6740
6823
|
// A single dir that's a marketplace (e.g. wshobson/agents' 80+ plugins
|
|
6741
6824
|
// under one marketplace.json) expands into its members and ranks them.
|
|
@@ -6905,7 +6988,7 @@ async function main() {
|
|
|
6905
6988
|
vigilesVersion: getVersion(),
|
|
6906
6989
|
adoptableSurfaces,
|
|
6907
6990
|
observations: (0, observe_js_1.summarizeObservations)(ledgerRecords),
|
|
6908
|
-
rulesInventory: computeRuleInventory(root, adapter.layout.instructionFile),
|
|
6991
|
+
rulesInventory: computeRuleInventory(root, adapter.layout.instructionFile, excludes),
|
|
6909
6992
|
firingMeasured,
|
|
6910
6993
|
});
|
|
6911
6994
|
const sc = auditReportBase.score;
|
|
@@ -6994,7 +7077,7 @@ async function main() {
|
|
|
6994
7077
|
// computeRuleRouting re-reads) — route the prose rules, enumerating the
|
|
6995
7078
|
// live catalog when consented + own-repo. Feeds the JSON report, the
|
|
6996
7079
|
// written artifacts, and the terminal rule-map summary.
|
|
6997
|
-
const ruleRouting = computeRuleRouting(root, adapter.layout.instructionFile);
|
|
7080
|
+
const ruleRouting = computeRuleRouting(root, adapter.layout.instructionFile, excludes);
|
|
6998
7081
|
const auditReport = ruleRouting
|
|
6999
7082
|
? { ...auditReportBase, ruleRouting }
|
|
7000
7083
|
: auditReportBase;
|