vigiles 13.0.0 → 14.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 +10 -4
- package/dist/audit-report.d.ts +10 -4
- package/dist/audit-report.js +7 -2
- package/dist/audit-report.template.html +28 -28
- package/dist/cli.js +73 -15
- package/dist/core/rule-catalog.d.ts +56 -0
- package/dist/core/rule-catalog.js +146 -0
- package/dist/eval.d.ts +13 -1
- package/dist/eval.js +13 -1
- package/dist/instruction-sources.d.ts +39 -0
- package/dist/instruction-sources.js +71 -0
- package/dist/rule-inventory.d.ts +6 -0
- package/dist/rule-inventory.js +170 -1
- package/dist/rule-routing.d.ts +25 -2
- package/dist/rule-routing.js +337 -26
- package/dist/segment.d.ts +1 -1
- package/dist/segment.js +151 -17
- package/package.json +2 -2
- package/skills/linter-docs/clippy.md +1 -1
- package/skills/linter-docs/eslint.md +1 -1
- package/skills/linter-docs/pylint.md +1 -1
- package/skills/linter-docs/rubocop.md +1 -1
- package/skills/linter-docs/ruff.md +1 -1
- package/skills/linter-docs/stylelint.md +1 -1
- package/skills/strengthen/SKILL.md +4 -4
package/dist/cli.js
CHANGED
|
@@ -37,6 +37,8 @@ const audit_serve_js_1 = require("./audit-serve.js");
|
|
|
37
37
|
const audit_report_js_1 = require("./audit-report.js");
|
|
38
38
|
const rule_inventory_js_1 = require("./rule-inventory.js");
|
|
39
39
|
const rule_routing_js_1 = require("./rule-routing.js");
|
|
40
|
+
const instruction_sources_js_1 = require("./instruction-sources.js");
|
|
41
|
+
const rule_catalog_js_1 = require("./core/rule-catalog.js");
|
|
40
42
|
const adoptability_js_1 = require("./adoptability.js");
|
|
41
43
|
const compile_js_1 = require("./core/compile.js");
|
|
42
44
|
const proofs_js_1 = require("./core/proofs.js");
|
|
@@ -1513,7 +1515,11 @@ function collectLintConfigText(root) {
|
|
|
1513
1515
|
* NO model, NO config execution — safe on any repo. Composition-root. */
|
|
1514
1516
|
function computeRuleInventory(root, instructionFile) {
|
|
1515
1517
|
try {
|
|
1516
|
-
|
|
1518
|
+
// The inventory maps intents → rules; it has no per-file line provenance, so
|
|
1519
|
+
// the concatenated text is fine here (unlike the routing preview below).
|
|
1520
|
+
const instructionText = gatherInstructionFiles(root, instructionFile)
|
|
1521
|
+
.map((f) => f.text)
|
|
1522
|
+
.join("\n");
|
|
1517
1523
|
if (!instructionText.trim())
|
|
1518
1524
|
return [];
|
|
1519
1525
|
return (0, rule_inventory_js_1.buildRuleInventory)(instructionText, collectLintConfigText(root));
|
|
@@ -1522,27 +1528,79 @@ function computeRuleInventory(root, instructionFile) {
|
|
|
1522
1528
|
return [];
|
|
1523
1529
|
}
|
|
1524
1530
|
}
|
|
1525
|
-
/**
|
|
1526
|
-
* rules are often documented in AGENTS.md even under a claude-code harness
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1531
|
+
/** Gather EVERY agent instruction file present (not just the harness-native one) —
|
|
1532
|
+
* rules are often documented in AGENTS.md even under a claude-code harness — as a
|
|
1533
|
+
* list of {path, text} so each is routed SEPARATELY and keeps its OWN provenance
|
|
1534
|
+
* (concatenating first would corrupt per-file line numbers). Reads the ROOT
|
|
1535
|
+
* instruction files PLUS nested subdirectory-memory (`src/CLAUDE.md`,
|
|
1536
|
+
* `research/CLAUDE.md`, …), skipping fixture/demo/build/test dirs (`isFixturePath`)
|
|
1537
|
+
* so a repo's real memory is read without the test-fixture noise. `.claude/` rule
|
|
1538
|
+
* sources remain a future source. research/rule-compiler-multilang-design.md §0. */
|
|
1539
|
+
function gatherInstructionFiles(root, instructionFile) {
|
|
1540
|
+
const raw = [];
|
|
1541
|
+
const collect = (rel) => {
|
|
1542
|
+
const p = (0, node_path_1.resolve)(root, rel);
|
|
1543
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
1544
|
+
return;
|
|
1545
|
+
raw.push({
|
|
1546
|
+
path: rel,
|
|
1547
|
+
canonical: (0, node_fs_1.realpathSync)(p),
|
|
1548
|
+
text: (0, node_fs_1.readFileSync)(p, "utf-8"),
|
|
1549
|
+
});
|
|
1550
|
+
};
|
|
1551
|
+
// Root instruction files first (stable, deterministic order).
|
|
1552
|
+
for (const name of new Set([instructionFile, "CLAUDE.md", "AGENTS.md"]))
|
|
1553
|
+
collect(name);
|
|
1554
|
+
// Nested subdirectory memory, minus fixture/demo/build/test noise.
|
|
1555
|
+
try {
|
|
1556
|
+
const nested = (0, glob_1.globSync)(["**/CLAUDE.md", "**/AGENTS.md"], {
|
|
1557
|
+
cwd: root,
|
|
1558
|
+
ignore: [...IGNORE_NODE_MODULES, "dist/**", ".git/**"],
|
|
1559
|
+
})
|
|
1560
|
+
.filter((rel) => !(0, instruction_sources_js_1.isFixturePath)(rel))
|
|
1561
|
+
.sort();
|
|
1562
|
+
for (const rel of nested)
|
|
1563
|
+
collect(rel);
|
|
1533
1564
|
}
|
|
1534
|
-
|
|
1565
|
+
catch {
|
|
1566
|
+
// best-effort — a glob failure just means root-only, never breaks the audit
|
|
1567
|
+
}
|
|
1568
|
+
// Dedup a CLAUDE.md⇄AGENTS.md mirror (symlink or byte-identical sync) to ONE
|
|
1569
|
+
// artifact so its rules aren't double-counted (compose-with-sync-tools).
|
|
1570
|
+
return (0, instruction_sources_js_1.dedupeInstructionFiles)(raw);
|
|
1535
1571
|
}
|
|
1536
1572
|
/** The deterministic State-B routing preview for `audit`: segment the instruction
|
|
1537
|
-
* file(s) into atomic rules and route each (reuse / hook / semantic / unrouted) —
|
|
1573
|
+
* file(s) into atomic rules and route each (reuse / hook / meta / semantic / unrouted) —
|
|
1538
1574
|
* NO model, fs-only. `undefined` when there's nothing to segment (kept off the
|
|
1539
|
-
* report). Best-effort; a routing failure never breaks the audit.
|
|
1575
|
+
* report). Best-effort; a routing failure never breaks the audit.
|
|
1576
|
+
*
|
|
1577
|
+
* The DYNAMIC catalog (every rule the repo's ESLint actually has + its enabled
|
|
1578
|
+
* state) sharpens matching — but enumerating it EXECUTES the linter, so it is an
|
|
1579
|
+
* OWN-REPO / CONSENTED capability (audit-side-effect-free): gated on the same
|
|
1580
|
+
* sticky `audit.measure` consent as the other executing checks AND on own-repo
|
|
1581
|
+
* (never a stranger's toolchain). The textual routing is the foreign-safe default;
|
|
1582
|
+
* the catalog only ADDS enabled-state nudges and matches named-but-`/`-broken rules. */
|
|
1540
1583
|
function computeRuleRouting(root, instructionFile) {
|
|
1541
1584
|
try {
|
|
1542
|
-
const
|
|
1543
|
-
if (!
|
|
1585
|
+
const files = gatherInstructionFiles(root, instructionFile);
|
|
1586
|
+
if (files.every((f) => !f.text.trim()))
|
|
1544
1587
|
return undefined;
|
|
1545
|
-
|
|
1588
|
+
// Own-repo + consented → enumerate the live ESLint catalog. NOT gated on the
|
|
1589
|
+
// agent harness: the catalog is a property of the repo's LINTER (its ESLint
|
|
1590
|
+
// config on disk), not of Claude-Code-vs-Codex, so a Codex JS/TS repo gets the
|
|
1591
|
+
// same catalog match + enabled-state (adapter-aware-lint-rules: never gate a
|
|
1592
|
+
// harness-agnostic capability on CC). enumerateEslintCatalog returns null when
|
|
1593
|
+
// no ESLint config resolves (e.g. a pure-Python repo) → undefined, so a non-JS
|
|
1594
|
+
// repo simply falls back to the foreign-safe textual routing regardless.
|
|
1595
|
+
const ownRepo = (0, node_path_1.resolve)(root) === (0, node_path_1.resolve)(process.cwd());
|
|
1596
|
+
const consented = (0, validate_js_1.loadConfig)().audit?.measure === true;
|
|
1597
|
+
const availableRules = ownRepo && consented
|
|
1598
|
+
? ((0, rule_catalog_js_1.enumerateEslintCatalog)(root) ?? undefined)
|
|
1599
|
+
: undefined;
|
|
1600
|
+
// Route each source SEPARATELY (each rule keeps its own file + line numbers),
|
|
1601
|
+
// then merge — so a CLAUDE.md rule and an AGENTS.md rule carry correct
|
|
1602
|
+
// provenance instead of line numbers offset by a concatenation.
|
|
1603
|
+
const routing = (0, rule_routing_js_1.mergeRoutings)(files.map((f) => (0, rule_routing_js_1.routeRules)(f.text, f.path, { availableRules })));
|
|
1546
1604
|
return routing.segmented > 0 ? routing : undefined;
|
|
1547
1605
|
}
|
|
1548
1606
|
catch {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vigiles rule-catalog — the DYNAMIC available-rule catalog.
|
|
3
|
+
*
|
|
4
|
+
* Enumerates every lint rule the repo's linter ACTUALLY has — core built-ins PLUS
|
|
5
|
+
* every installed plugin's rules — so prose can be matched against the LIVE
|
|
6
|
+
* catalog instead of a static hand-curated map. On this repo one ESLint API call
|
|
7
|
+
* yields ~702 available rules (292 core + 410 plugin: typescript-eslint / sonarjs
|
|
8
|
+
* / boundaries), of which ~140 are enabled — vs the old static map's ~23. That
|
|
9
|
+
* makes an architecture norm enforceable too (`boundaries/dependencies` is in the
|
|
10
|
+
* catalog), which a static map never captured. See
|
|
11
|
+
* `research/rule-compiler-multilang-design.md` §0 (the spike this productizes).
|
|
12
|
+
*
|
|
13
|
+
* SAFETY — this EXECUTES the linter. Loading ESLint resolves the repo's real
|
|
14
|
+
* config (which can run plugin/config code), so `enumerateEslintCatalog` is an
|
|
15
|
+
* OWN-REPO / consented capability, NOT the foreign-safe default. The deterministic
|
|
16
|
+
* default rule-compile tier stays purely TEXTUAL (it parses config, never loads
|
|
17
|
+
* it); reach for this only where executing the repo's toolchain is already
|
|
18
|
+
* consented (own repo, on the user's machine). Mirrors the subprocess pattern of
|
|
19
|
+
* `discoverEslintRules` in `src/core/generate-types.ts`: ESLint is loaded in a
|
|
20
|
+
* child `node -e` process at the repo's cwd so it stays out of our process.
|
|
21
|
+
*/
|
|
22
|
+
/** One rule the repo's linter has available, with its enabled state. */
|
|
23
|
+
export interface AvailableRule {
|
|
24
|
+
/** The rule id: `no-console`, `@typescript-eslint/no-explicit-any`, `boundaries/dependencies`. */
|
|
25
|
+
id: string;
|
|
26
|
+
/** The plugin prefix (`@typescript-eslint`, `boundaries`), or null for a core rule. */
|
|
27
|
+
plugin: string | null;
|
|
28
|
+
/** Whether the rule is enabled (severity not 0/"off") in the resolved config. */
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** The full available-rule catalog for a repo's linter. */
|
|
32
|
+
export interface RuleCatalog {
|
|
33
|
+
linter: "eslint";
|
|
34
|
+
/** Total rules available (core + every installed plugin). */
|
|
35
|
+
available: number;
|
|
36
|
+
/** How many of those are enabled in the resolved config. */
|
|
37
|
+
enabled: number;
|
|
38
|
+
rules: AvailableRule[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse the enumeration subprocess's JSON payload into a typed {@link RuleCatalog}.
|
|
42
|
+
*
|
|
43
|
+
* Payload shape: `{ core: string[]; enabled: string[]; plugins: Record<prefix, string[]> }`.
|
|
44
|
+
* Returns null on `"null"` / malformed input / an empty catalog (mirrors
|
|
45
|
+
* `discoverEslintRules` returning null when nothing is found).
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseEslintCatalog(raw: string): RuleCatalog | null;
|
|
48
|
+
/**
|
|
49
|
+
* Enumerate the repo's available ESLint rules (core + every installed plugin).
|
|
50
|
+
*
|
|
51
|
+
* EXECUTES the repo's ESLint in a child process — an own-repo / consented
|
|
52
|
+
* capability (see the file header). Returns null if ESLint isn't resolvable, no
|
|
53
|
+
* config applies, or the subprocess fails.
|
|
54
|
+
*/
|
|
55
|
+
export declare function enumerateEslintCatalog(root: string): RuleCatalog | null;
|
|
56
|
+
//# sourceMappingURL=rule-catalog.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* vigiles rule-catalog — the DYNAMIC available-rule catalog.
|
|
4
|
+
*
|
|
5
|
+
* Enumerates every lint rule the repo's linter ACTUALLY has — core built-ins PLUS
|
|
6
|
+
* every installed plugin's rules — so prose can be matched against the LIVE
|
|
7
|
+
* catalog instead of a static hand-curated map. On this repo one ESLint API call
|
|
8
|
+
* yields ~702 available rules (292 core + 410 plugin: typescript-eslint / sonarjs
|
|
9
|
+
* / boundaries), of which ~140 are enabled — vs the old static map's ~23. That
|
|
10
|
+
* makes an architecture norm enforceable too (`boundaries/dependencies` is in the
|
|
11
|
+
* catalog), which a static map never captured. See
|
|
12
|
+
* `research/rule-compiler-multilang-design.md` §0 (the spike this productizes).
|
|
13
|
+
*
|
|
14
|
+
* SAFETY — this EXECUTES the linter. Loading ESLint resolves the repo's real
|
|
15
|
+
* config (which can run plugin/config code), so `enumerateEslintCatalog` is an
|
|
16
|
+
* OWN-REPO / consented capability, NOT the foreign-safe default. The deterministic
|
|
17
|
+
* default rule-compile tier stays purely TEXTUAL (it parses config, never loads
|
|
18
|
+
* it); reach for this only where executing the repo's toolchain is already
|
|
19
|
+
* consented (own repo, on the user's machine). Mirrors the subprocess pattern of
|
|
20
|
+
* `discoverEslintRules` in `src/core/generate-types.ts`: ESLint is loaded in a
|
|
21
|
+
* child `node -e` process at the repo's cwd so it stays out of our process.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.parseEslintCatalog = parseEslintCatalog;
|
|
25
|
+
exports.enumerateEslintCatalog = enumerateEslintCatalog;
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
27
|
+
const node_child_process_1 = require("node:child_process");
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Pure parse: subprocess JSON → typed catalog (covered by the unit test)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
function isStringArray(v) {
|
|
32
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
33
|
+
}
|
|
34
|
+
function parsePlugins(v) {
|
|
35
|
+
if (typeof v !== "object" || v === null)
|
|
36
|
+
return {};
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const [prefix, rules] of Object.entries(v)) {
|
|
39
|
+
if (isStringArray(rules))
|
|
40
|
+
out[prefix] = rules;
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function buildRules(core, plugins, enabledSet) {
|
|
45
|
+
const rules = [];
|
|
46
|
+
for (const id of core) {
|
|
47
|
+
rules.push({ id, plugin: null, enabled: enabledSet.has(id) });
|
|
48
|
+
}
|
|
49
|
+
for (const [prefix, pluginRules] of Object.entries(plugins)) {
|
|
50
|
+
for (const rule of pluginRules) {
|
|
51
|
+
const id = `${prefix}/${rule}`;
|
|
52
|
+
rules.push({ id, plugin: prefix, enabled: enabledSet.has(id) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return rules;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse the enumeration subprocess's JSON payload into a typed {@link RuleCatalog}.
|
|
59
|
+
*
|
|
60
|
+
* Payload shape: `{ core: string[]; enabled: string[]; plugins: Record<prefix, string[]> }`.
|
|
61
|
+
* Returns null on `"null"` / malformed input / an empty catalog (mirrors
|
|
62
|
+
* `discoverEslintRules` returning null when nothing is found).
|
|
63
|
+
*/
|
|
64
|
+
function parseEslintCatalog(raw) {
|
|
65
|
+
const trimmed = raw.trim();
|
|
66
|
+
if (!trimmed || trimmed === "null")
|
|
67
|
+
return null;
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(trimmed);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
76
|
+
return null;
|
|
77
|
+
const obj = parsed;
|
|
78
|
+
const core = isStringArray(obj.core) ? obj.core : [];
|
|
79
|
+
const enabledList = isStringArray(obj.enabled) ? obj.enabled : [];
|
|
80
|
+
const plugins = parsePlugins(obj.plugins);
|
|
81
|
+
const rules = buildRules(core, plugins, new Set(enabledList));
|
|
82
|
+
if (rules.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
const enabled = rules.reduce((n, r) => (r.enabled ? n + 1 : n), 0);
|
|
85
|
+
return { linter: "eslint", available: rules.length, enabled, rules };
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Real-IO seam: run ESLint in a child process at the repo's cwd
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
/* v8 ignore start -- spawns a `node -e` subprocess that LOADS ESLint in the
|
|
91
|
+
repo's cwd (the executes-the-linter seam); the pure JSON→typed parse is
|
|
92
|
+
parseEslintCatalog, covered by the unit test, and the gated integration test
|
|
93
|
+
drives this real path when eslint resolves. */
|
|
94
|
+
/**
|
|
95
|
+
* Enumerate the repo's available ESLint rules (core + every installed plugin).
|
|
96
|
+
*
|
|
97
|
+
* EXECUTES the repo's ESLint in a child process — an own-repo / consented
|
|
98
|
+
* capability (see the file header). Returns null if ESLint isn't resolvable, no
|
|
99
|
+
* config applies, or the subprocess fails.
|
|
100
|
+
*/
|
|
101
|
+
function enumerateEslintCatalog(root) {
|
|
102
|
+
try {
|
|
103
|
+
// A `.ts` path under src/ so the repo's flat config applies its TypeScript +
|
|
104
|
+
// plugin blocks (typescript-eslint / sonarjs / boundaries all scope to
|
|
105
|
+
// `src/**/*.ts`); the path need not exist — calculateConfigForFile resolves
|
|
106
|
+
// the config, it does not read the file.
|
|
107
|
+
const probeFile = (0, node_path_1.resolve)(root, "src/index.ts");
|
|
108
|
+
const script = `
|
|
109
|
+
const { loadESLint } = require("eslint");
|
|
110
|
+
const { builtinRules } = require("eslint/use-at-your-own-risk");
|
|
111
|
+
(async () => {
|
|
112
|
+
try {
|
|
113
|
+
const ESLint = await loadESLint();
|
|
114
|
+
const eslint = new ESLint({ cwd: ${JSON.stringify(root)} });
|
|
115
|
+
const cfg = await eslint.calculateConfigForFile(${JSON.stringify(probeFile)});
|
|
116
|
+
const core = [...builtinRules.keys()];
|
|
117
|
+
const enabled = Object.entries(cfg.rules || {})
|
|
118
|
+
.filter(([, v]) => {
|
|
119
|
+
const sev = Array.isArray(v) ? v[0] : v;
|
|
120
|
+
return sev !== 0 && sev !== "off";
|
|
121
|
+
})
|
|
122
|
+
.map(([k]) => k);
|
|
123
|
+
const plugins = {};
|
|
124
|
+
for (const [prefix, plugin] of Object.entries(cfg.plugins || {})) {
|
|
125
|
+
plugins[prefix] = Object.keys((plugin && plugin.rules) || {});
|
|
126
|
+
}
|
|
127
|
+
console.log(JSON.stringify({ core, enabled, plugins }));
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.log("null");
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
`;
|
|
133
|
+
const output = (0, node_child_process_1.execSync)(`node -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
134
|
+
encoding: "utf-8",
|
|
135
|
+
cwd: root,
|
|
136
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
137
|
+
timeout: 15000,
|
|
138
|
+
});
|
|
139
|
+
return parseEslintCatalog(output);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/* v8 ignore stop */
|
|
146
|
+
//# sourceMappingURL=rule-catalog.js.map
|
package/dist/eval.d.ts
CHANGED
|
@@ -755,7 +755,19 @@ export interface EvalDriver {
|
|
|
755
755
|
*/
|
|
756
756
|
readonly harness?: string;
|
|
757
757
|
}
|
|
758
|
-
/**
|
|
758
|
+
/**
|
|
759
|
+
* The default (Claude Code) eval driver: real `claude` + stream-json parsing.
|
|
760
|
+
*
|
|
761
|
+
* This lives at the COMPOSITION ROOT (`src/eval.ts`) on purpose, not in
|
|
762
|
+
* `src/adapters/claude-code/` — it is NOT a boundary leak. Claude Code is the
|
|
763
|
+
* wired DEFAULT (`measureTriggerRate`/`runEval` fall back to it), so `eval.ts`
|
|
764
|
+
* must reference it directly; wiring the default is precisely a composition
|
|
765
|
+
* root's job. `codexEvalDriver` lives in its adapter dir instead because Codex
|
|
766
|
+
* is caller-INJECTED (never a default), so `eval.ts` never imports it. Relocating
|
|
767
|
+
* this into the adapter would make `eval.ts → adapters/claude-code → eval.ts` a
|
|
768
|
+
* circular import (the shared `EvalDriver`/`ModelOutputParser` types live here).
|
|
769
|
+
* The asymmetry reflects default-vs-injected, not a hexagonal violation.
|
|
770
|
+
*/
|
|
759
771
|
export declare const claudeEvalDriver: EvalDriver;
|
|
760
772
|
/**
|
|
761
773
|
* Package loose `<skillsDir>/<name>/SKILL.md` skills into a throwaway plugin dir
|
package/dist/eval.js
CHANGED
|
@@ -1240,7 +1240,19 @@ function formatEvalReport(report) {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
return lines.join("\n");
|
|
1242
1242
|
}
|
|
1243
|
-
/**
|
|
1243
|
+
/**
|
|
1244
|
+
* The default (Claude Code) eval driver: real `claude` + stream-json parsing.
|
|
1245
|
+
*
|
|
1246
|
+
* This lives at the COMPOSITION ROOT (`src/eval.ts`) on purpose, not in
|
|
1247
|
+
* `src/adapters/claude-code/` — it is NOT a boundary leak. Claude Code is the
|
|
1248
|
+
* wired DEFAULT (`measureTriggerRate`/`runEval` fall back to it), so `eval.ts`
|
|
1249
|
+
* must reference it directly; wiring the default is precisely a composition
|
|
1250
|
+
* root's job. `codexEvalDriver` lives in its adapter dir instead because Codex
|
|
1251
|
+
* is caller-INJECTED (never a default), so `eval.ts` never imports it. Relocating
|
|
1252
|
+
* this into the adapter would make `eval.ts → adapters/claude-code → eval.ts` a
|
|
1253
|
+
* circular import (the shared `EvalDriver`/`ModelOutputParser` types live here).
|
|
1254
|
+
* The asymmetry reflects default-vs-injected, not a hexagonal violation.
|
|
1255
|
+
*/
|
|
1244
1256
|
exports.claudeEvalDriver = {
|
|
1245
1257
|
runner: spawnAgent,
|
|
1246
1258
|
parse: parseClaudeRun,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* instruction-sources.ts — the pure policy for WHICH instruction files the audit
|
|
3
|
+
* rule-routing preview reads.
|
|
4
|
+
*
|
|
5
|
+
* A repo's real subdirectory memory (`src/CLAUDE.md`, `research/CLAUDE.md`, a
|
|
6
|
+
* nested `AGENTS.md`) IS worth routing; a fixture/demo/build/test CLAUDE.md is
|
|
7
|
+
* NOISE that would flood the preview. `isFixturePath` is the precision-first
|
|
8
|
+
* discriminator (over-skip a legit `sample-service` before flooding with fixture
|
|
9
|
+
* rules). Pure + unit-tested; the fs discovery/glue lives in cli.ts
|
|
10
|
+
* (`gatherInstructionFiles`). See research/rule-compiler-multilang-design.md §0.
|
|
11
|
+
*/
|
|
12
|
+
/** One instruction file gathered from disk, with its canonical (symlink-resolved)
|
|
13
|
+
* path so a mirror can be detected. */
|
|
14
|
+
export interface RawInstructionFile {
|
|
15
|
+
/** The repo-relative path (kept for provenance). */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
/** The realpath (symlink-resolved) — a symlinked mirror shares this. */
|
|
18
|
+
readonly canonical: string;
|
|
19
|
+
readonly text: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Dedup instruction files so a `CLAUDE.md`⇄`AGENTS.md` MIRROR is routed ONCE, not
|
|
23
|
+
* double-counted (compose-with-sync-tools: a symlinked or byte-identical synced
|
|
24
|
+
* mirror is ONE logical artifact). Dedups by BOTH the canonical path (a symlink)
|
|
25
|
+
* AND the content hash (a byte-identical sync) — relative-path dedup alone caught
|
|
26
|
+
* neither. First occurrence wins (callers pass root files first). Pure.
|
|
27
|
+
*/
|
|
28
|
+
export declare function dedupeInstructionFiles(files: readonly RawInstructionFile[]): {
|
|
29
|
+
path: string;
|
|
30
|
+
text: string;
|
|
31
|
+
}[];
|
|
32
|
+
/**
|
|
33
|
+
* Is this (repo-relative) instruction-file path fixture/demo/build/test noise?
|
|
34
|
+
* True when any DIRECTORY segment (never the filename) is a build/deps/test dir
|
|
35
|
+
* OR starts with a demo/example/sample/fixture/bench/mock/scratch/tmp prefix.
|
|
36
|
+
* Conventional + general (not vigiles-specific), precision over recall.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isFixturePath(relPath: string): boolean;
|
|
39
|
+
//# sourceMappingURL=instruction-sources.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* instruction-sources.ts — the pure policy for WHICH instruction files the audit
|
|
4
|
+
* rule-routing preview reads.
|
|
5
|
+
*
|
|
6
|
+
* A repo's real subdirectory memory (`src/CLAUDE.md`, `research/CLAUDE.md`, a
|
|
7
|
+
* nested `AGENTS.md`) IS worth routing; a fixture/demo/build/test CLAUDE.md is
|
|
8
|
+
* NOISE that would flood the preview. `isFixturePath` is the precision-first
|
|
9
|
+
* discriminator (over-skip a legit `sample-service` before flooding with fixture
|
|
10
|
+
* rules). Pure + unit-tested; the fs discovery/glue lives in cli.ts
|
|
11
|
+
* (`gatherInstructionFiles`). See research/rule-compiler-multilang-design.md §0.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.dedupeInstructionFiles = dedupeInstructionFiles;
|
|
15
|
+
exports.isFixturePath = isFixturePath;
|
|
16
|
+
const hash_js_1 = require("./core/hash.js");
|
|
17
|
+
/**
|
|
18
|
+
* Dedup instruction files so a `CLAUDE.md`⇄`AGENTS.md` MIRROR is routed ONCE, not
|
|
19
|
+
* double-counted (compose-with-sync-tools: a symlinked or byte-identical synced
|
|
20
|
+
* mirror is ONE logical artifact). Dedups by BOTH the canonical path (a symlink)
|
|
21
|
+
* AND the content hash (a byte-identical sync) — relative-path dedup alone caught
|
|
22
|
+
* neither. First occurrence wins (callers pass root files first). Pure.
|
|
23
|
+
*/
|
|
24
|
+
function dedupeInstructionFiles(files) {
|
|
25
|
+
const seenReal = new Set();
|
|
26
|
+
const seenHash = new Set();
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const f of files) {
|
|
29
|
+
if (seenReal.has(f.canonical))
|
|
30
|
+
continue; // symlinked mirror
|
|
31
|
+
const hash = (0, hash_js_1.sha256short)(f.text);
|
|
32
|
+
if (seenHash.has(hash))
|
|
33
|
+
continue; // byte-identical synced mirror
|
|
34
|
+
seenReal.add(f.canonical);
|
|
35
|
+
seenHash.add(hash);
|
|
36
|
+
out.push({ path: f.path, text: f.text });
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/** Directory segments that are unambiguously build / deps / test noise. */
|
|
41
|
+
const FIXTURE_DIR_EXACT = new Set([
|
|
42
|
+
"node_modules",
|
|
43
|
+
"dist",
|
|
44
|
+
"build",
|
|
45
|
+
"out",
|
|
46
|
+
"coverage",
|
|
47
|
+
".git",
|
|
48
|
+
".next",
|
|
49
|
+
".cache",
|
|
50
|
+
"test",
|
|
51
|
+
"tests",
|
|
52
|
+
"__tests__",
|
|
53
|
+
"__fixtures__",
|
|
54
|
+
"__mocks__",
|
|
55
|
+
"vendor",
|
|
56
|
+
"third_party",
|
|
57
|
+
]);
|
|
58
|
+
/** Directory-name PREFIXES that mark a fixture / demo / sample / scratch dir.
|
|
59
|
+
* Case-INSENSITIVE so `Examples/`, `Demo/` are skipped too. */
|
|
60
|
+
const FIXTURE_DIR_PREFIX = /^(?:demo|example|sample|fixture|bench|benchmark|mock|stub|scratch|tmp|\.tmp)/i;
|
|
61
|
+
/**
|
|
62
|
+
* Is this (repo-relative) instruction-file path fixture/demo/build/test noise?
|
|
63
|
+
* True when any DIRECTORY segment (never the filename) is a build/deps/test dir
|
|
64
|
+
* OR starts with a demo/example/sample/fixture/bench/mock/scratch/tmp prefix.
|
|
65
|
+
* Conventional + general (not vigiles-specific), precision over recall.
|
|
66
|
+
*/
|
|
67
|
+
function isFixturePath(relPath) {
|
|
68
|
+
const segs = relPath.split(/[/\\]/).slice(0, -1); // directories only
|
|
69
|
+
return segs.some((s) => FIXTURE_DIR_EXACT.has(s.toLowerCase()) || FIXTURE_DIR_PREFIX.test(s));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=instruction-sources.js.map
|
package/dist/rule-inventory.d.ts
CHANGED
|
@@ -84,6 +84,12 @@ export interface RuleInventoryOptions {
|
|
|
84
84
|
* non-`[\w/@.-]` character on each side (so `no-console` matches in
|
|
85
85
|
* `` `no-console` `` and `enforce no-console;` but `no-console-x` does not,
|
|
86
86
|
* and prose containing the substring elsewhere never trips it).
|
|
87
|
+
*
|
|
88
|
+
* The TRAILING boundary is a lookahead, not a consuming class, so a keyword at
|
|
89
|
+
* SENTENCE END ("No wildcard imports.") matches: a `.` is allowed unless it
|
|
90
|
+
* CONTINUES a code token (`.log` in `console.log`), which still blocks a partial
|
|
91
|
+
* match. `(?![\w/@-])` rejects a word/`/`/`@`/`-` continuation; `(?!\.[\w/@-])`
|
|
92
|
+
* rejects a dotted continuation but permits a trailing sentence `.`.
|
|
87
93
|
*/
|
|
88
94
|
export declare function matchesWholeToken(text: string, keyword: string): boolean;
|
|
89
95
|
export declare function buildRuleInventory(instructionText: string, configText: string, options?: RuleInventoryOptions): RuleInventoryItem[];
|