vigiles 14.1.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/dist/cli.js CHANGED
@@ -1535,7 +1535,7 @@ function computeRuleInventory(root, instructionFile) {
1535
1535
  * instruction files PLUS nested subdirectory-memory (`src/CLAUDE.md`,
1536
1536
  * `research/CLAUDE.md`, …), skipping fixture/demo/build/test dirs (`isFixturePath`)
1537
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. */
1538
+ * sources remain a future source. research/rule-enforcer-multilang-design.md §0. */
1539
1539
  function gatherInstructionFiles(root, instructionFile) {
1540
1540
  const raw = [];
1541
1541
  const collect = (rel) => {
@@ -1696,10 +1696,12 @@ function formatRuleMapSummary(routing) {
1696
1696
  counts.meta;
1697
1697
  if (confident === 0 && possible.length === 0 && skipped.length === 0)
1698
1698
  return "";
1699
+ // Lane counts, rendered from the single-source LANE_META (glyph + label).
1700
+ const lane = (c) => `${rule_routing_js_1.LANE_META[c].glyph} ${String(counts[c])} ${rule_routing_js_1.LANE_META[c].label}`;
1699
1701
  const lines = [
1700
- "Rule map — how your prose rules could be enforced (heuristic, precision-first):",
1701
- ` ✓ ${String(counts.reuse)} enforceable · ⛓ ${String(counts.hook)} hook · ⚙ ${String(counts.unrouted)} custom · ✎ ${String(counts.semantic)} judgment` +
1702
- (counts.meta > 0 ? ` · ☰ ${String(counts.meta)} agent-note` : ""),
1702
+ "Rule map [experimental] — how your prose rules could be enforced (heuristic, precision-first; misses some rules):",
1703
+ ` ${lane("reuse")} · ${lane("hook")} · ${lane("unrouted")} · ${lane("semantic")}` +
1704
+ (counts.meta > 0 ? ` · ${lane("meta")}` : ""),
1703
1705
  ];
1704
1706
  if (possible.length > 0)
1705
1707
  lines.push(` ? ${String(possible.length)} possible — rule-ish, but below the confidence bar (review these)`);
@@ -8,7 +8,7 @@
8
8
  * / boundaries), of which ~140 are enabled — vs the old static map's ~23. That
9
9
  * makes an architecture norm enforceable too (`boundaries/dependencies` is in the
10
10
  * catalog), which a static map never captured. See
11
- * `research/rule-compiler-multilang-design.md` §0 (the spike this productizes).
11
+ * `research/rule-enforcer-multilang-design.md` §0 (the spike this productizes).
12
12
  *
13
13
  * SAFETY — this EXECUTES the linter. Loading ESLint resolves the repo's real
14
14
  * config (which can run plugin/config code), so `enumerateEslintCatalog` is an
@@ -9,7 +9,7 @@
9
9
  * / boundaries), of which ~140 are enabled — vs the old static map's ~23. That
10
10
  * makes an architecture norm enforceable too (`boundaries/dependencies` is in the
11
11
  * catalog), which a static map never captured. See
12
- * `research/rule-compiler-multilang-design.md` §0 (the spike this productizes).
12
+ * `research/rule-enforcer-multilang-design.md` §0 (the spike this productizes).
13
13
  *
14
14
  * SAFETY — this EXECUTES the linter. Loading ESLint resolves the repo's real
15
15
  * config (which can run plugin/config code), so `enumerateEslintCatalog` is an
@@ -7,7 +7,7 @@
7
7
  * NOISE that would flood the preview. `isFixturePath` is the precision-first
8
8
  * discriminator (over-skip a legit `sample-service` before flooding with fixture
9
9
  * rules). Pure + unit-tested; the fs discovery/glue lives in cli.ts
10
- * (`gatherInstructionFiles`). See research/rule-compiler-multilang-design.md §0.
10
+ * (`gatherInstructionFiles`). See research/rule-enforcer-multilang-design.md §0.
11
11
  */
12
12
  /** One instruction file gathered from disk, with its canonical (symlink-resolved)
13
13
  * path so a mirror can be detected. */
@@ -8,7 +8,7 @@
8
8
  * NOISE that would flood the preview. `isFixturePath` is the precision-first
9
9
  * discriminator (over-skip a legit `sample-service` before flooding with fixture
10
10
  * rules). Pure + unit-tested; the fs discovery/glue lives in cli.ts
11
- * (`gatherInstructionFiles`). See research/rule-compiler-multilang-design.md §0.
11
+ * (`gatherInstructionFiles`). See research/rule-enforcer-multilang-design.md §0.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.dedupeInstructionFiles = dedupeInstructionFiles;
@@ -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-compiler-multilang-design.md §0.
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-compiler-multilang-design.md §3), deferred —
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) {
@@ -41,6 +41,21 @@ import type { RuleCatalog } from "./core/rule-catalog.js";
41
41
  /** How a routed rule would be enforced (a MECHANISM ladder, not a 1-10 score). */
42
42
  export type RuleCategory = "reuse" | "hook" | "meta" | "semantic" | "unrouted";
43
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
+ }>;
44
59
  /** One segmented, deterministically-routed rule with provenance. */
45
60
  export interface RoutedRule {
46
61
  /** Normalized atomic rule text (from the segmenter). */
@@ -75,7 +90,7 @@ export interface RuleRouting {
75
90
  /** The POSSIBLE tier — rule-ish bullets (medium confidence) that did NOT clear
76
91
  * the bar, still classified so a human can review + promote them. Detection is
77
92
  * precision-first, so this is where a declarative rule ("Every X must Y") that
78
- * the confident tier misses shows up. See `research/rule-compiler-design.md` §2. */
93
+ * the confident tier misses shows up. See `research/rule-enforcer-design.md` §2. */
79
94
  readonly possible: readonly RoutedRule[];
80
95
  /** Bullets the segmenter decided were NOT rules, each with a reason — so the
81
96
  * report is honest about what it set aside (§3). */