/** * rule-routing.ts — the deterministic (no-model) State-B routing PREVIEW. * * `rule-inventory.ts` answers a narrow question ("which prose lines name an * off-the-shelf lint rule, and is it enabled?"). This goes one honest step * further: it SEGMENTS the whole instruction file into atomic rules * ({@link segmentInstructions}) and routes each one into the class that a real * enforcement path would take — WITHOUT running a model: * * reuse → the rule text names an off-the-shelf lint rule ({@link INTENT_MAP}) * → mechanism: flip one config line. The "narrow list we compile * very well" — everything else is honestly labelled, not force-fit. * hook → an ACTION rule a linter can't see (git push, rm -rf, "before you * commit") → mechanism: a pre-commit / PreToolUse hook. * meta → an agent-instruction, not a code rule ("read X first", "tell the * user", "you are …") → mechanism: stays prose. Split out of * `unrouted` so it never reads as "compilable but hard" (it isn't). * semantic → a judgment call ("readable", "single responsibility") no checker * can honestly decide → mechanism: stays prose. * unrouted → looks like a code rule but matched no off-the-shelf rule: HARD to * codify → mechanism `synthesize`: the opt-in SYNTHESIS tier (a * skill on your subscription) MIGHT write a custom checker, gated — * but it is NOT guaranteed (the gate may abstain). This is the * bucket audit must present clearly as "hard", never as done. * * NB "compile" is NOT used here — `vigiles compile` is the unrelated spec→markdown * verb. Synthesis is its own opt-in tier; the mechanism value is `synthesize`. * * HONESTY BY CONSTRUCTION: the deterministic tier NEVER claims a rule is * "synthesizable" — deciding that a custom rule can be written (and gating it) * is exactly the work the opt-in model tier does. `unrouted` means "hard — a * synthesis skill may try", never a promise; `meta`/`semantic` mean "not an * enforceable code rule at all" (a different, honest kind of no). * * Pure, deterministic, dependency-free. Reuses `rule-inventory`'s hardened * whole-token matcher + `INTENT_MAP`, and `segment`'s Tier-A segmenter. */ import { type SkippedBullet } from "./segment.js"; import { type LinterName } from "./rule-inventory.js"; import type { RuleCatalog } from "./core/rule-catalog.js"; /** How a routed rule would be enforced (a MECHANISM ladder, not a 1-10 score). */ export type RuleCategory = "reuse" | "hook" | "meta" | "semantic" | "unrouted"; export type RuleMechanism = "config-line" | "hook" | "prose" | "synthesize"; /** * The user-facing presentation of each routing category — its glyph + lane * label. The SINGLE SOURCE the terminal summary reads (and the HTML report * mirrors), so the category-name → lane-label mapping lives in one place. * * NB the type name `unrouted` is a WIRE value (it appears in the versioned * `AuditReport` JSON), which is why it isn't renamed to its lane label `custom`; * this table is where the human-facing name is resolved. The category meanings * are documented in the file header; the mapping is tabled in * `research/rule-enforcer-design.md` §4. */ export declare const LANE_META: Record; /** One segmented, deterministically-routed rule with provenance. */ export interface RoutedRule { /** Normalized atomic rule text (from the segmenter). */ readonly text: string; /** Verbatim source slice — for a UI highlight. */ readonly quote: string; readonly file: string | undefined; readonly lineStart: number; readonly lineEnd: number; /** Segmenter confidence that this IS a rule (3/3 cues → high, 2/3 → medium). */ readonly confidence: "high" | "medium"; readonly category: RuleCategory; readonly mechanism: RuleMechanism; /** reuse only: the off-the-shelf rule that enforces it. */ readonly rule?: string; /** reuse only: the linter that rule belongs to. */ readonly linter?: LinterName; /** reuse via the DYNAMIC catalog only: whether the rule is currently enabled in * the repo's config (a disabled match is the "documented but OFF" nudge). */ readonly enabled?: boolean; /** How this rule was found: `"marker"` = an explicit `**Enforced by:**` / * `**Guard:**` / `**Guidance only**` marker (definitive, zero-heuristic — a * compiled/marked doc); `"heuristic"` = the Tier-A segmenter. Absent ⇒ heuristic. */ readonly source?: "marker" | "heuristic"; } export interface RuleRouting { /** How many CONFIDENT atomic rules were routed (high or rescued). */ readonly segmented: number; readonly counts: Record; /** The CONFIDENT tier — cleared the precision bar; these are the routed rules. */ readonly rules: readonly RoutedRule[]; /** The POSSIBLE tier — rule-ish bullets (medium confidence) that did NOT clear * the bar, still classified so a human can review + promote them. Detection is * precision-first, so this is where a declarative rule ("Every X must Y") that * the confident tier misses shows up. See `research/rule-enforcer-design.md` §2. */ readonly possible: readonly RoutedRule[]; /** Bullets the segmenter decided were NOT rules, each with a reason — so the * report is honest about what it set aside (§3). */ readonly skipped: readonly SkippedBullet[]; } export interface RouteOptions { /** * Minimum segmenter confidence to route. The segmenter emits `high` (3/3 cues * — an imperative rule) and `medium` (2/3). For the audit PREVIEW we default to * `high` only: precision over recall. A doc-heavy instruction file (e.g. a * keyFiles index of "`path` — description" bullets) trips the medium tier with * non-rules, which would bury the real rules and overstate "unrouted". Pass * `"medium"` to include both. */ readonly minConfidence?: "high" | "medium"; /** * The repo's DYNAMIC available-rule catalog (from `enumerateEslintCatalog`). * When provided (OWN-REPO / consented — it executes the linter), a bullet that * NAMES any of the repo's real rules routes to `reuse` with its enabled state — * not just the ~23 static `INTENT_MAP` aliases. Absent = foreign-safe default * (static map only, no execution). */ readonly availableRules?: RuleCatalog; } /** * Segment the instruction file and route every atomic rule deterministically. * Pure: the caller passes the concatenated instruction text (and an optional * source path for provenance). Returns per-category counts + the routed rules. */ export declare function routeRules(instructionText: string, file?: string, options?: RouteOptions): RuleRouting; /** * Merge per-file routings into one. Each instruction source is routed SEPARATELY * (so every rule keeps its OWN file path + line numbers — concatenating first * would corrupt the provenance the preview promises), then folded here: rules * concatenated, counts + segmented summed. Pure. `[]` → an empty routing. */ export declare function mergeRoutings(routings: readonly RuleRouting[]): RuleRouting; //# sourceMappingURL=rule-routing.d.ts.map