vigiles 14.0.0 → 14.2.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 +16 -13
- package/dist/adapters/claude-code/dialect.d.ts +1 -1
- package/dist/audit-report.js +4 -1
- package/dist/audit-report.template.html +28 -28
- package/dist/cli.js +194 -35
- package/dist/core/rule-catalog.d.ts +49 -4
- package/dist/core/rule-catalog.js +151 -3
- package/dist/instruction-sources.d.ts +1 -1
- package/dist/instruction-sources.js +1 -1
- package/dist/rule-inventory.js +151 -2
- package/dist/rule-routing.d.ts +63 -1
- package/dist/rule-routing.js +231 -98
- package/dist/rule-signals.d.ts +46 -0
- package/dist/rule-signals.js +49 -0
- package/dist/segment.d.ts +35 -2
- package/dist/segment.js +233 -171
- package/package.json +1 -1
package/dist/rule-inventory.js
CHANGED
|
@@ -42,7 +42,7 @@ exports.buildRuleInventory = buildRuleInventory;
|
|
|
42
42
|
// the DYNAMIC available-rule catalog — enumerate the rules the repo's linter
|
|
43
43
|
// ACTUALLY has (spike: 702 for this repo vs ~23 here) and match prose against
|
|
44
44
|
// THAT, own-repo/consented since it executes the linter. Do NOT keep growing this
|
|
45
|
-
// by hand. See research/rule-
|
|
45
|
+
// by hand. See research/rule-enforcer-multilang-design.md §0.
|
|
46
46
|
exports.INTENT_MAP = [
|
|
47
47
|
{
|
|
48
48
|
intent: "no console.log / use the logger",
|
|
@@ -235,12 +235,93 @@ exports.INTENT_MAP = [
|
|
|
235
235
|
rule: "no-only-tests/no-only-tests",
|
|
236
236
|
configFix: '"no-only-tests/no-only-tests": "error"',
|
|
237
237
|
},
|
|
238
|
+
// --- Ruff (Python) — the modern default (allow-list, like ESLint). These are
|
|
239
|
+
// the NET-NEW intents Pylint does NOT already cover (imports-inside-functions,
|
|
240
|
+
// annotations, logger.exception, print, import-sorting). The SHARED Python
|
|
241
|
+
// intents (bare-except, broad-except, mutable-default, wildcard, global,
|
|
242
|
+
// too-many-*, line-length, unused, f-strings, docstrings) stay with the Pylint
|
|
243
|
+
// entries below — one intent, one linter, so keyword disjointness holds (the
|
|
244
|
+
// `rule-routing-dogfood` invariant test) and prose never double-routes. Whether
|
|
245
|
+
// shared Python NL prose should later PREFER Ruff over Pylint is an open
|
|
246
|
+
// ownership decision, deferred. ROUTE-ONLY like Pylint: config-state
|
|
247
|
+
// (buildRuleInventory) stays gated to eslint — Ruff's select/ignore config shape
|
|
248
|
+
// differs from the eslint rules-map, so the eslint-shaped check would MISLABEL;
|
|
249
|
+
// accurate Ruff enabled-state is the catalog/ConfigProbe follow-up
|
|
250
|
+
// (rule-enforcer-multilang-design.md §3). Every CODE was verified to exist
|
|
251
|
+
// against ruff 0.15.8 (`ruff rule <CODE>`); keywords are Python-diagnostic (no
|
|
252
|
+
// cross-language phrase that would mis-attribute the linter in a JS/TS doc).
|
|
253
|
+
{
|
|
254
|
+
intent: "no imports inside functions (Python)",
|
|
255
|
+
linter: "ruff",
|
|
256
|
+
keywords: [
|
|
257
|
+
"PLC0415",
|
|
258
|
+
"inline import",
|
|
259
|
+
"inline imports",
|
|
260
|
+
"imports inside functions",
|
|
261
|
+
"import inside a function",
|
|
262
|
+
"import inside functions",
|
|
263
|
+
],
|
|
264
|
+
rule: "PLC0415",
|
|
265
|
+
configFix: 'add "PLC0415" (import-outside-top-level) to [tool.ruff.lint] select',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
// E402 flags module-level code BEFORE the imports; the "no imports inside
|
|
269
|
+
// functions" reading is PLC0415 above. Code-only keyword ON PURPOSE: the
|
|
270
|
+
// phrase "imports at the top" is ambiguous between the two AND collides with
|
|
271
|
+
// ESLint `import/first` in a JS doc, so it is NOT a keyword here (a missing
|
|
272
|
+
// route is recoverable; a wrong "enforceable" claim is not).
|
|
273
|
+
intent: "no module code before imports (Python)",
|
|
274
|
+
linter: "ruff",
|
|
275
|
+
keywords: ["E402"],
|
|
276
|
+
rule: "E402",
|
|
277
|
+
configFix: 'add "E402" (module-import-not-at-top-of-file) to [tool.ruff.lint] select',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
intent: "require argument type annotations (Python)",
|
|
281
|
+
linter: "ruff",
|
|
282
|
+
keywords: [
|
|
283
|
+
"ANN001",
|
|
284
|
+
"type hints required",
|
|
285
|
+
"type hints are required",
|
|
286
|
+
"require type hints",
|
|
287
|
+
],
|
|
288
|
+
rule: "ANN001",
|
|
289
|
+
configFix: 'add "ANN001" (missing-type-function-argument) to [tool.ruff.lint] select',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
intent: "require return type annotations (Python)",
|
|
293
|
+
linter: "ruff",
|
|
294
|
+
keywords: ["ANN201"],
|
|
295
|
+
rule: "ANN201",
|
|
296
|
+
configFix: 'add "ANN201" (missing-return-type-undocumented-public-function) to [tool.ruff.lint] select',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
intent: "use logger.exception in handlers (Python)",
|
|
300
|
+
linter: "ruff",
|
|
301
|
+
keywords: ["TRY400", "logger.exception", "logging.exception"],
|
|
302
|
+
rule: "TRY400",
|
|
303
|
+
configFix: 'add "TRY400" (error-instead-of-exception) to [tool.ruff.lint] select',
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
intent: "no print statements — use logging (Python)",
|
|
307
|
+
linter: "ruff",
|
|
308
|
+
keywords: ["T201", "print statement", "print statements"],
|
|
309
|
+
rule: "T201",
|
|
310
|
+
configFix: 'add "T201" (print) to [tool.ruff.lint] select',
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
intent: "keep imports sorted (Python)",
|
|
314
|
+
linter: "ruff",
|
|
315
|
+
keywords: ["I001", "sorted imports", "isort"],
|
|
316
|
+
rule: "I001",
|
|
317
|
+
configFix: 'add "I001" (unsorted-imports) to [tool.ruff.lint] select',
|
|
318
|
+
},
|
|
238
319
|
// --- Pylint (Python) — routing basics. These feed classify() (routing → reuse);
|
|
239
320
|
// buildRuleInventory is gated to eslint (see below) because pylint is
|
|
240
321
|
// ON-BY-DEFAULT (deny-list), so the eslint-shaped config-state check would
|
|
241
322
|
// MISLABEL it (a symbol in `disable=` reads as "in-config", an absent one as
|
|
242
323
|
// "enable it"). Accurate pylint enabled-state needs the inverted-polarity
|
|
243
|
-
// ConfigProbe (research/rule-
|
|
324
|
+
// ConfigProbe (research/rule-enforcer-multilang-design.md §3), deferred —
|
|
244
325
|
// classify() needs NO enabled-state, so pylint prose still routes honestly.
|
|
245
326
|
// Keywords are code-shaped symbols + Python-UNAMBIGUOUS compounds (singular AND
|
|
246
327
|
// plural, since matchesWholeToken is boundary-exact); bare ambiguous words
|
|
@@ -351,6 +432,74 @@ exports.INTENT_MAP = [
|
|
|
351
432
|
rule: "unused-import",
|
|
352
433
|
configFix: "pylint enables unused-import (W0611) by default; keep it out of the disable list",
|
|
353
434
|
},
|
|
435
|
+
// --- Clippy (Rust) — routing basics (backlog #3: the 0%→ win for Rust
|
|
436
|
+
// rulebooks; codex/ghostty routed 0% purely because clippy was unmapped, yet
|
|
437
|
+
// codex literally says "avoid patterns that require `panic!`, `unreachable!`,
|
|
438
|
+
// or `.unwrap()`" and "make `match` exhaustive … avoid wildcard arms"). Every
|
|
439
|
+
// lint is a real clippy restriction lint. Route-only, like ruff/pylint —
|
|
440
|
+
// config-state stays eslint-gated (L639) because clippy's lint levels live in
|
|
441
|
+
// Cargo.toml `[lints.clippy]` / crate attrs, a different shape (accurate
|
|
442
|
+
// enabled-state is the ConfigProbe follow-up, rule-enforcer-multilang-design
|
|
443
|
+
// §3). Keywords are Rust-UNAMBIGUOUS — macro `!` forms, `.method` call forms,
|
|
444
|
+
// `clippy::` symbols, and Rust-only compounds ("wildcard arm(s)", NOT bare
|
|
445
|
+
// "wildcard" which collides with pylint `wildcard-import`) — so a Python/JS
|
|
446
|
+
// doc never mis-attributes clippy (the cross-language-FP test guards this).
|
|
447
|
+
{
|
|
448
|
+
intent: "no .unwrap() (Rust)",
|
|
449
|
+
linter: "clippy",
|
|
450
|
+
keywords: ["clippy::unwrap_used", "unwrap_used", ".unwrap"],
|
|
451
|
+
rule: "clippy::unwrap_used",
|
|
452
|
+
configFix: 'set unwrap_used = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::unwrap_used)]',
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
intent: "no .expect() (Rust)",
|
|
456
|
+
linter: "clippy",
|
|
457
|
+
keywords: ["clippy::expect_used", "expect_used", ".expect"],
|
|
458
|
+
rule: "clippy::expect_used",
|
|
459
|
+
configFix: 'set expect_used = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::expect_used)]',
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
intent: "no panic! (Rust)",
|
|
463
|
+
linter: "clippy",
|
|
464
|
+
keywords: ["clippy::panic", "panic!"],
|
|
465
|
+
rule: "clippy::panic",
|
|
466
|
+
configFix: 'set panic = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::panic)]',
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
intent: "no unreachable! (Rust)",
|
|
470
|
+
linter: "clippy",
|
|
471
|
+
keywords: ["clippy::unreachable", "unreachable!"],
|
|
472
|
+
rule: "clippy::unreachable",
|
|
473
|
+
configFix: 'set unreachable = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::unreachable)]',
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
intent: "no todo! (Rust)",
|
|
477
|
+
linter: "clippy",
|
|
478
|
+
keywords: ["clippy::todo", "todo!"],
|
|
479
|
+
rule: "clippy::todo",
|
|
480
|
+
configFix: 'set todo = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::todo)]',
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
intent: "no dbg! (Rust)",
|
|
484
|
+
linter: "clippy",
|
|
485
|
+
keywords: ["clippy::dbg_macro", "dbg!"],
|
|
486
|
+
rule: "clippy::dbg_macro",
|
|
487
|
+
configFix: 'set dbg_macro = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::dbg_macro)]',
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
intent: "exhaustive match — no wildcard enum arm (Rust)",
|
|
491
|
+
linter: "clippy",
|
|
492
|
+
keywords: [
|
|
493
|
+
"clippy::wildcard_enum_match_arm",
|
|
494
|
+
"wildcard_enum_match_arm",
|
|
495
|
+
"wildcard match arm",
|
|
496
|
+
"wildcard match arms",
|
|
497
|
+
"wildcard arm",
|
|
498
|
+
"wildcard arms",
|
|
499
|
+
],
|
|
500
|
+
rule: "clippy::wildcard_enum_match_arm",
|
|
501
|
+
configFix: 'set wildcard_enum_match_arm = "warn" in [lints.clippy] (Cargo.toml), or #![warn(clippy::wildcard_enum_match_arm)]',
|
|
502
|
+
},
|
|
354
503
|
];
|
|
355
504
|
/** Escape a keyword for use inside a RegExp. */
|
|
356
505
|
function escapeRe(s) {
|
package/dist/rule-routing.d.ts
CHANGED
|
@@ -1,8 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rule-routing.ts — the deterministic (no-model) State-B routing PREVIEW.
|
|
3
|
+
*
|
|
4
|
+
* `rule-inventory.ts` answers a narrow question ("which prose lines name an
|
|
5
|
+
* off-the-shelf lint rule, and is it enabled?"). This goes one honest step
|
|
6
|
+
* further: it SEGMENTS the whole instruction file into atomic rules
|
|
7
|
+
* ({@link segmentInstructions}) and routes each one into the class that a real
|
|
8
|
+
* enforcement path would take — WITHOUT running a model:
|
|
9
|
+
*
|
|
10
|
+
* reuse → the rule text names an off-the-shelf lint rule ({@link INTENT_MAP})
|
|
11
|
+
* → mechanism: flip one config line. The "narrow list we compile
|
|
12
|
+
* very well" — everything else is honestly labelled, not force-fit.
|
|
13
|
+
* hook → an ACTION rule a linter can't see (git push, rm -rf, "before you
|
|
14
|
+
* commit") → mechanism: a pre-commit / PreToolUse hook.
|
|
15
|
+
* meta → an agent-instruction, not a code rule ("read X first", "tell the
|
|
16
|
+
* user", "you are …") → mechanism: stays prose. Split out of
|
|
17
|
+
* `unrouted` so it never reads as "compilable but hard" (it isn't).
|
|
18
|
+
* semantic → a judgment call ("readable", "single responsibility") no checker
|
|
19
|
+
* can honestly decide → mechanism: stays prose.
|
|
20
|
+
* unrouted → looks like a code rule but matched no off-the-shelf rule: HARD to
|
|
21
|
+
* codify → mechanism `synthesize`: the opt-in SYNTHESIS tier (a
|
|
22
|
+
* skill on your subscription) MIGHT write a custom checker, gated —
|
|
23
|
+
* but it is NOT guaranteed (the gate may abstain). This is the
|
|
24
|
+
* bucket audit must present clearly as "hard", never as done.
|
|
25
|
+
*
|
|
26
|
+
* NB "compile" is NOT used here — `vigiles compile` is the unrelated spec→markdown
|
|
27
|
+
* verb. Synthesis is its own opt-in tier; the mechanism value is `synthesize`.
|
|
28
|
+
*
|
|
29
|
+
* HONESTY BY CONSTRUCTION: the deterministic tier NEVER claims a rule is
|
|
30
|
+
* "synthesizable" — deciding that a custom rule can be written (and gating it)
|
|
31
|
+
* is exactly the work the opt-in model tier does. `unrouted` means "hard — a
|
|
32
|
+
* synthesis skill may try", never a promise; `meta`/`semantic` mean "not an
|
|
33
|
+
* enforceable code rule at all" (a different, honest kind of no).
|
|
34
|
+
*
|
|
35
|
+
* Pure, deterministic, dependency-free. Reuses `rule-inventory`'s hardened
|
|
36
|
+
* whole-token matcher + `INTENT_MAP`, and `segment`'s Tier-A segmenter.
|
|
37
|
+
*/
|
|
38
|
+
import { type SkippedBullet } from "./segment.js";
|
|
1
39
|
import { type LinterName } from "./rule-inventory.js";
|
|
2
40
|
import type { RuleCatalog } from "./core/rule-catalog.js";
|
|
3
41
|
/** How a routed rule would be enforced (a MECHANISM ladder, not a 1-10 score). */
|
|
4
42
|
export type RuleCategory = "reuse" | "hook" | "meta" | "semantic" | "unrouted";
|
|
5
43
|
export type RuleMechanism = "config-line" | "hook" | "prose" | "synthesize";
|
|
44
|
+
/**
|
|
45
|
+
* The user-facing presentation of each routing category — its glyph + lane
|
|
46
|
+
* label. The SINGLE SOURCE the terminal summary reads (and the HTML report
|
|
47
|
+
* mirrors), so the category-name → lane-label mapping lives in one place.
|
|
48
|
+
*
|
|
49
|
+
* NB the type name `unrouted` is a WIRE value (it appears in the versioned
|
|
50
|
+
* `AuditReport` JSON), which is why it isn't renamed to its lane label `custom`;
|
|
51
|
+
* this table is where the human-facing name is resolved. The category meanings
|
|
52
|
+
* are documented in the file header; the mapping is tabled in
|
|
53
|
+
* `research/rule-enforcer-design.md` §4.
|
|
54
|
+
*/
|
|
55
|
+
export declare const LANE_META: Record<RuleCategory, {
|
|
56
|
+
readonly glyph: string;
|
|
57
|
+
readonly label: string;
|
|
58
|
+
}>;
|
|
6
59
|
/** One segmented, deterministically-routed rule with provenance. */
|
|
7
60
|
export interface RoutedRule {
|
|
8
61
|
/** Normalized atomic rule text (from the segmenter). */
|
|
@@ -29,10 +82,19 @@ export interface RoutedRule {
|
|
|
29
82
|
readonly source?: "marker" | "heuristic";
|
|
30
83
|
}
|
|
31
84
|
export interface RuleRouting {
|
|
32
|
-
/** How many atomic rules were routed (
|
|
85
|
+
/** How many CONFIDENT atomic rules were routed (high or rescued). */
|
|
33
86
|
readonly segmented: number;
|
|
34
87
|
readonly counts: Record<RuleCategory, number>;
|
|
88
|
+
/** The CONFIDENT tier — cleared the precision bar; these are the routed rules. */
|
|
35
89
|
readonly rules: readonly RoutedRule[];
|
|
90
|
+
/** The POSSIBLE tier — rule-ish bullets (medium confidence) that did NOT clear
|
|
91
|
+
* the bar, still classified so a human can review + promote them. Detection is
|
|
92
|
+
* precision-first, so this is where a declarative rule ("Every X must Y") that
|
|
93
|
+
* the confident tier misses shows up. See `research/rule-enforcer-design.md` §2. */
|
|
94
|
+
readonly possible: readonly RoutedRule[];
|
|
95
|
+
/** Bullets the segmenter decided were NOT rules, each with a reason — so the
|
|
96
|
+
* report is honest about what it set aside (§3). */
|
|
97
|
+
readonly skipped: readonly SkippedBullet[];
|
|
36
98
|
}
|
|
37
99
|
export interface RouteOptions {
|
|
38
100
|
/**
|