vigiles 13.0.0 → 14.1.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 +11 -3
- package/dist/audit-report.template.html +29 -29
- package/dist/cli.js +256 -41
- package/dist/core/rule-catalog.d.ts +101 -0
- package/dist/core/rule-catalog.js +294 -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 +73 -3
- package/dist/rule-routing.js +437 -27
- package/dist/segment.d.ts +23 -1
- package/dist/segment.js +182 -26
- 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/rule-routing.d.ts
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
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";
|
|
40
|
+
import type { RuleCatalog } from "./core/rule-catalog.js";
|
|
2
41
|
/** How a routed rule would be enforced (a MECHANISM ladder, not a 1-10 score). */
|
|
3
|
-
export type RuleCategory = "reuse" | "hook" | "semantic" | "unrouted";
|
|
4
|
-
export type RuleMechanism = "config-line" | "hook" | "prose" | "
|
|
42
|
+
export type RuleCategory = "reuse" | "hook" | "meta" | "semantic" | "unrouted";
|
|
43
|
+
export type RuleMechanism = "config-line" | "hook" | "prose" | "synthesize";
|
|
5
44
|
/** One segmented, deterministically-routed rule with provenance. */
|
|
6
45
|
export interface RoutedRule {
|
|
7
46
|
/** Normalized atomic rule text (from the segmenter). */
|
|
@@ -19,12 +58,28 @@ export interface RoutedRule {
|
|
|
19
58
|
readonly rule?: string;
|
|
20
59
|
/** reuse only: the linter that rule belongs to. */
|
|
21
60
|
readonly linter?: LinterName;
|
|
61
|
+
/** reuse via the DYNAMIC catalog only: whether the rule is currently enabled in
|
|
62
|
+
* the repo's config (a disabled match is the "documented but OFF" nudge). */
|
|
63
|
+
readonly enabled?: boolean;
|
|
64
|
+
/** How this rule was found: `"marker"` = an explicit `**Enforced by:**` /
|
|
65
|
+
* `**Guard:**` / `**Guidance only**` marker (definitive, zero-heuristic — a
|
|
66
|
+
* compiled/marked doc); `"heuristic"` = the Tier-A segmenter. Absent ⇒ heuristic. */
|
|
67
|
+
readonly source?: "marker" | "heuristic";
|
|
22
68
|
}
|
|
23
69
|
export interface RuleRouting {
|
|
24
|
-
/** How many atomic rules were routed (
|
|
70
|
+
/** How many CONFIDENT atomic rules were routed (high or rescued). */
|
|
25
71
|
readonly segmented: number;
|
|
26
72
|
readonly counts: Record<RuleCategory, number>;
|
|
73
|
+
/** The CONFIDENT tier — cleared the precision bar; these are the routed rules. */
|
|
27
74
|
readonly rules: readonly RoutedRule[];
|
|
75
|
+
/** The POSSIBLE tier — rule-ish bullets (medium confidence) that did NOT clear
|
|
76
|
+
* the bar, still classified so a human can review + promote them. Detection is
|
|
77
|
+
* 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. */
|
|
79
|
+
readonly possible: readonly RoutedRule[];
|
|
80
|
+
/** Bullets the segmenter decided were NOT rules, each with a reason — so the
|
|
81
|
+
* report is honest about what it set aside (§3). */
|
|
82
|
+
readonly skipped: readonly SkippedBullet[];
|
|
28
83
|
}
|
|
29
84
|
export interface RouteOptions {
|
|
30
85
|
/**
|
|
@@ -36,6 +91,14 @@ export interface RouteOptions {
|
|
|
36
91
|
* `"medium"` to include both.
|
|
37
92
|
*/
|
|
38
93
|
readonly minConfidence?: "high" | "medium";
|
|
94
|
+
/**
|
|
95
|
+
* The repo's DYNAMIC available-rule catalog (from `enumerateEslintCatalog`).
|
|
96
|
+
* When provided (OWN-REPO / consented — it executes the linter), a bullet that
|
|
97
|
+
* NAMES any of the repo's real rules routes to `reuse` with its enabled state —
|
|
98
|
+
* not just the ~23 static `INTENT_MAP` aliases. Absent = foreign-safe default
|
|
99
|
+
* (static map only, no execution).
|
|
100
|
+
*/
|
|
101
|
+
readonly availableRules?: RuleCatalog;
|
|
39
102
|
}
|
|
40
103
|
/**
|
|
41
104
|
* Segment the instruction file and route every atomic rule deterministically.
|
|
@@ -43,4 +106,11 @@ export interface RouteOptions {
|
|
|
43
106
|
* source path for provenance). Returns per-category counts + the routed rules.
|
|
44
107
|
*/
|
|
45
108
|
export declare function routeRules(instructionText: string, file?: string, options?: RouteOptions): RuleRouting;
|
|
109
|
+
/**
|
|
110
|
+
* Merge per-file routings into one. Each instruction source is routed SEPARATELY
|
|
111
|
+
* (so every rule keeps its OWN file path + line numbers — concatenating first
|
|
112
|
+
* would corrupt the provenance the preview promises), then folded here: rules
|
|
113
|
+
* concatenated, counts + segmented summed. Pure. `[]` → an empty routing.
|
|
114
|
+
*/
|
|
115
|
+
export declare function mergeRoutings(routings: readonly RuleRouting[]): RuleRouting;
|
|
46
116
|
//# sourceMappingURL=rule-routing.d.ts.map
|
package/dist/rule-routing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.routeRules = routeRules;
|
|
4
|
+
exports.mergeRoutings = mergeRoutings;
|
|
4
5
|
/**
|
|
5
6
|
* rule-routing.ts — the deterministic (no-model) State-B routing PREVIEW.
|
|
6
7
|
*
|
|
@@ -11,18 +12,29 @@ exports.routeRules = routeRules;
|
|
|
11
12
|
* enforcement path would take — WITHOUT running a model:
|
|
12
13
|
*
|
|
13
14
|
* reuse → the rule text names an off-the-shelf lint rule ({@link INTENT_MAP})
|
|
14
|
-
* → mechanism: flip one config line.
|
|
15
|
+
* → mechanism: flip one config line. The "narrow list we compile
|
|
16
|
+
* very well" — everything else is honestly labelled, not force-fit.
|
|
15
17
|
* hook → an ACTION rule a linter can't see (git push, rm -rf, "before you
|
|
16
18
|
* commit") → mechanism: a pre-commit / PreToolUse hook.
|
|
19
|
+
* meta → an agent-instruction, not a code rule ("read X first", "tell the
|
|
20
|
+
* user", "you are …") → mechanism: stays prose. Split out of
|
|
21
|
+
* `unrouted` so it never reads as "compilable but hard" (it isn't).
|
|
17
22
|
* semantic → a judgment call ("readable", "single responsibility") no checker
|
|
18
23
|
* can honestly decide → mechanism: stays prose.
|
|
19
|
-
* unrouted →
|
|
20
|
-
*
|
|
24
|
+
* unrouted → looks like a code rule but matched no off-the-shelf rule: HARD to
|
|
25
|
+
* codify → mechanism `synthesize`: the opt-in SYNTHESIS tier (a
|
|
26
|
+
* skill on your subscription) MIGHT write a custom checker, gated —
|
|
27
|
+
* but it is NOT guaranteed (the gate may abstain). This is the
|
|
28
|
+
* bucket audit must present clearly as "hard", never as done.
|
|
29
|
+
*
|
|
30
|
+
* NB "compile" is NOT used here — `vigiles compile` is the unrelated spec→markdown
|
|
31
|
+
* verb. Synthesis is its own opt-in tier; the mechanism value is `synthesize`.
|
|
21
32
|
*
|
|
22
33
|
* HONESTY BY CONSTRUCTION: the deterministic tier NEVER claims a rule is
|
|
23
34
|
* "synthesizable" — deciding that a custom rule can be written (and gating it)
|
|
24
|
-
* is exactly the work the opt-in model tier does.
|
|
25
|
-
*
|
|
35
|
+
* is exactly the work the opt-in model tier does. `unrouted` means "hard — a
|
|
36
|
+
* synthesis skill may try", never a promise; `meta`/`semantic` mean "not an
|
|
37
|
+
* enforceable code rule at all" (a different, honest kind of no).
|
|
26
38
|
*
|
|
27
39
|
* Pure, deterministic, dependency-free. Reuses `rule-inventory`'s hardened
|
|
28
40
|
* whole-token matcher + `INTENT_MAP`, and `segment`'s Tier-A segmenter.
|
|
@@ -33,33 +45,74 @@ const rule_inventory_js_1 = require("./rule-inventory.js");
|
|
|
33
45
|
const MECHANISM = {
|
|
34
46
|
reuse: "config-line",
|
|
35
47
|
hook: "hook",
|
|
48
|
+
meta: "prose",
|
|
36
49
|
semantic: "prose",
|
|
37
|
-
unrouted: "
|
|
50
|
+
unrouted: "synthesize",
|
|
38
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* A deontic/norm signal ANYWHERE in a bullet — the marker of a rule the imperative
|
|
54
|
+
* gate missed because the norm isn't at the head ("every function MUST have a
|
|
55
|
+
* docstring", "public APIs SHOULD stay stable"). Used to keep the POSSIBLE review
|
|
56
|
+
* tier to genuine rule-candidates instead of arbitrary unparsed prose. Deliberately
|
|
57
|
+
* narrow (modal verbs only) so it doesn't re-admit the noise it exists to exclude.
|
|
58
|
+
*/
|
|
59
|
+
const NORM_SIGNAL = /\b(?:must(?:n't)?|should(?:n't)?|shall|never|always|avoids?|require[sd]?|forbidden|disallow(?:ed)?|prohibited|banned?|prefers?|do not|don't)\b/i;
|
|
39
60
|
/**
|
|
40
61
|
* ACTION-rule cues — things a linter never sees (git, filesystem, shell,
|
|
41
|
-
* process). A hook is the right gate, not a lint rule.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
62
|
+
* process). A hook is the right gate, not a lint rule. Widened to the article's
|
|
63
|
+
* measured surfaces (vcs / process / shell / redirect) — the narrow original
|
|
64
|
+
* (git-push + rm-rf only) reported ~2% hooks where the 252-rule hand-sort found
|
|
65
|
+
* **37%** (the largest bucket); it was missing push-to-branch, before-push,
|
|
66
|
+
* after-edit, tool-substitution, amend/rebase-pushed, and dependency guards.
|
|
67
|
+
* See `research/rule-compiler-multilang-design.md` §5b (the hook lane).
|
|
44
68
|
*/
|
|
45
69
|
const HOOK_CUES = [
|
|
70
|
+
// — branch / push guards (vcs) —
|
|
46
71
|
/\bgit\s+push\b/i,
|
|
47
|
-
// "push … to main/master/prod" — tolerate backticks/adverbs between
|
|
48
|
-
|
|
49
|
-
/\bpush\w*\b[^.\n]{0,24}\b(main|master|prod)\b/i,
|
|
72
|
+
// "push … to main/master/prod/origin" — tolerate backticks/adverbs between.
|
|
73
|
+
/\bpush\w*\b[^.\n]{0,24}\b(main|master|prod|production|origin|development)\b/i,
|
|
50
74
|
/\bforce[- ]?push/i,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/\
|
|
75
|
+
/\bpush\w*\b[^.\n]{0,16}\bbranch/i,
|
|
76
|
+
// — protected paths (vcs) —
|
|
77
|
+
/\b(don'?t|do not|never)\s+(edit|modify|touch|change)\b[^.\n]{0,30}\b(generated|vendored|lock(file)?|\.pb\.|proto-gen|_mock|snapshot)/i,
|
|
78
|
+
/\bgenerated\s+files?\b/i,
|
|
79
|
+
// — sequencing / tests-before / after-edit (process) —
|
|
80
|
+
/\b(run|execute)\b[^.\n]{0,30}\b(tests?|lint|check|type-?check|format|prettier|ruff)\b[^.\n]{0,20}\bbefore\b/i,
|
|
81
|
+
/\bbefore\s+(you\s+|each\s+|every\s+)?(commit|push|merg|committing|pushing)/i,
|
|
55
82
|
/\brun\b[^.\n]{0,20}\btests?\b[^.\n]{0,14}\bbefore\b/i,
|
|
83
|
+
/\b(format|lint|check|run)\b[^.\n]{0,30}\b(after|immediately after)\b[^.\n]{0,16}\b(writ|edit)/i,
|
|
84
|
+
// — tool substitution / redirect —
|
|
85
|
+
/\b(never|do not|don'?t)\s+run\b[^.\n]{0,30}\b(directly|instead)\b/i,
|
|
86
|
+
/\b(never|do not|don'?t)\s+run\b[^.\n]{0,20}`?(eslint|prettier|npm|npx|cargo|yarn|pnpm|pip|black|ruff)\b/i,
|
|
87
|
+
/\buse\b\s+`?[\w:.-]+`?\s+(instead of|not|over|rather than)\s+`?(npm|npx|cargo|yarn|pnpm|eslint|prettier)\b/i,
|
|
88
|
+
// — regenerate-on-change guard (vigiles's own guard() path→cmd shape: "run X
|
|
89
|
+
// after/when Y changes") — the single biggest missed-hook pattern in the OSS
|
|
90
|
+
// dogfood. Both clause orders; the trigger MUST be a change-verb so a benign
|
|
91
|
+
// "run it when ready" doesn't match.
|
|
92
|
+
/\b(re-?run|regenerate|re-?generate|rebuild|regen|run|update)\b[^.\n]{0,48}\b(after|when|whenever|once|if)\b[^.\n]{0,24}\b(chang|add(?:ing|ed)?|modif|updat|edit|new\b)/i,
|
|
93
|
+
/\b(after|when|whenever|once)\b[^.\n]{0,24}\b(chang|add(?:ing|ed)?|modif|updat|edit)[^.\n]{0,48}\b(re-?run|regenerate|rebuild|regen|run|update)\b/i,
|
|
94
|
+
// — commit content / history (vcs) —
|
|
95
|
+
/\b(amend|squash|rebase)\b[^.\n]{0,40}\b(pushed|review|remote|shared)\b/i,
|
|
96
|
+
/--no-verify/i,
|
|
97
|
+
/\b(never|do not|don'?t)\s+commit\b/i,
|
|
56
98
|
/\bsigned-off-by\b/i,
|
|
57
|
-
/\
|
|
58
|
-
|
|
59
|
-
|
|
99
|
+
/\bco-?author(?:s|ed|ing|ed-by)?\b/i,
|
|
100
|
+
// commit/PR METADATA hygiene phrased without a push verb (attribution, PR
|
|
101
|
+
// title, commit message, semantic/conventional commits) — a VCS-surface gate.
|
|
102
|
+
/\b(generated with|attribution)\b[^.\n]{0,24}\b(claude|ai|footer|commit|pr)\b/i,
|
|
103
|
+
/\b(ai|claude|assistant)\b[^.\n]{0,16}\battribution\b/i,
|
|
104
|
+
/\b(semantic|conventional)\s+(commit|pr|pull request)/i,
|
|
105
|
+
// NB: deliberately NO broad `(commit|pr) (title|message)` cue — it mislabels
|
|
106
|
+
// style sentences ("use sentence case for PR titles", "keep commit messages
|
|
107
|
+
// concise") as gates. The enforceable format/attribution rules are caught by
|
|
108
|
+
// the semantic/conventional + attribution cues above; the rest stay prose.
|
|
109
|
+
// — dependency / config guard —
|
|
110
|
+
/\b(never|do not|don'?t)\b[^.\n]{0,20}\bupdate\b[^.\n]{0,20}\b(depend|package\.json|lock)/i,
|
|
111
|
+
// — destructive / shell —
|
|
60
112
|
/\brm\s+-rf\b/i,
|
|
61
113
|
/\bcurl\b.*\|\s*(sh|bash)/i,
|
|
62
114
|
/\bchmod\b/i,
|
|
115
|
+
/\bdestructive\s+git\b/i,
|
|
63
116
|
];
|
|
64
117
|
/**
|
|
65
118
|
* Judgment / no-checker cues — a rule no linter can honestly decide, so it stays
|
|
@@ -83,22 +136,264 @@ const SEMANTIC_CUES = [
|
|
|
83
136
|
/\bclean code\b/i,
|
|
84
137
|
];
|
|
85
138
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
139
|
+
* META cues — an instruction to the AGENT, not a norm about the CODE ("read X
|
|
140
|
+
* first", "tell the user", "you are …", "when in doubt ask"). It is not a lint
|
|
141
|
+
* rule and never will be, so it must NOT land in `unrouted` (which reads as
|
|
142
|
+
* "compilable, just hard"). High-precision by design — specific phrasings a real
|
|
143
|
+
* code rule would not use.
|
|
144
|
+
*/
|
|
145
|
+
const META_CUES = [
|
|
146
|
+
// allow `.` in the gap — the referenced thing is often a filename (CLAUDE.md)
|
|
147
|
+
/\bread\b[^\n]{0,30}\bfirst\b/i,
|
|
148
|
+
/\bwhen in doubt\b/i,
|
|
149
|
+
/\bif (you'?re |you are )?unsure\b/i,
|
|
150
|
+
/\bask (the user|first|before)\b/i,
|
|
151
|
+
/\btell (the user|me)\b/i,
|
|
152
|
+
/\byou are\b[^.\n]{0,40}\b(assistant|agent|engineer|claude|model)\b/i,
|
|
153
|
+
/\byour (job|task|role) is\b/i,
|
|
154
|
+
/\b(do not|don'?t|never) (tell|mention|reveal|say)\b/i,
|
|
155
|
+
/\bin (chat|your (reply|response|answer))\b/i,
|
|
156
|
+
// H5 agent-ATTENTION norms (Fable's hook-lane taxonomy): re-read / re-run /
|
|
157
|
+
// re-fetch "without code changes". NOTHING reliably gates these — blocking a
|
|
158
|
+
// re-read breaks post-compaction recovery (false safety worse than an
|
|
159
|
+
// under-blocking guard) — so they are agent-guidance (meta), never a gate. The
|
|
160
|
+
// right instrument is MEASUREMENT (the flight recorder), not enforcement.
|
|
161
|
+
/\bre-?read(?:ing)?\b/i,
|
|
162
|
+
/\bre-?run(?:ning)?\b[^\n]{0,30}\b(?:test|command|suite)\b/i,
|
|
163
|
+
/\bre-?fetch(?:ing)?\b/i,
|
|
164
|
+
/\bwithout code changes\b/i,
|
|
165
|
+
];
|
|
166
|
+
const PATTERN_RULE_MAP = [
|
|
167
|
+
{
|
|
168
|
+
construct: "default exports",
|
|
169
|
+
rule: "no-restricted-syntax",
|
|
170
|
+
linter: "eslint",
|
|
171
|
+
pattern: /\b(?:no|never|avoid|don'?t\s+use|do\s+not\s+use|disallow|ban|forbid|prefer\s+named\s+(?:exports?\s+)?over)\b[^.\n]{0,24}\bdefault\s+exports?\b/i,
|
|
172
|
+
configFix: '"no-restricted-syntax": ["error", { "selector": "ExportDefaultDeclaration", "message": "Use named exports." }]',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
construct: "enums",
|
|
176
|
+
rule: "no-restricted-syntax",
|
|
177
|
+
linter: "eslint",
|
|
178
|
+
pattern: /\b(?:no|never|avoid|don'?t\s+use|do\s+not\s+use|disallow|ban|forbid)\b[^.\n]{0,24}\benums?\b/i,
|
|
179
|
+
configFix: '"no-restricted-syntax": ["error", { "selector": "TSEnumDeclaration", "message": "Use a union or const object instead of an enum." }]',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
construct: "for...in",
|
|
183
|
+
rule: "no-restricted-syntax",
|
|
184
|
+
linter: "eslint",
|
|
185
|
+
pattern: /\b(?:no|never|avoid|don'?t\s+use|do\s+not\s+use|disallow|ban|forbid)\b[^.\n]{0,16}\bfor[\s.]{0,3}in\b/i,
|
|
186
|
+
configFix: '"no-restricted-syntax": ["error", { "selector": "ForInStatement", "message": "Use for...of or Object.keys()." }]',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
construct: "namespaces",
|
|
190
|
+
rule: "no-restricted-syntax",
|
|
191
|
+
linter: "eslint",
|
|
192
|
+
pattern: /\b(?:no|never|avoid|don'?t\s+use|do\s+not\s+use|disallow|ban|forbid)\b[^.\n]{0,24}\bnamespaces?\b/i,
|
|
193
|
+
configFix: '"no-restricted-syntax": ["error", { "selector": "TSModuleDeclaration", "message": "Use ES modules instead of namespaces." }]',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
construct: "classes",
|
|
197
|
+
rule: "no-restricted-syntax",
|
|
198
|
+
linter: "eslint",
|
|
199
|
+
pattern: /\b(?:no|never|avoid|don'?t\s+use|do\s+not\s+use|disallow|ban|forbid)\b[^.\n]{0,12}\b(?<!css |style |styling |utility |tailwind |dom |react |component )(?:es6?\s+|javascript\s+)?class(?:es)?\b(?![\s-]*(?:name|attribute|selector|list))/i,
|
|
200
|
+
configFix: '"no-restricted-syntax": ["error", { "selector": ":matches(ClassDeclaration, ClassExpression)", "message": "Prefer functions and closures over classes." }]',
|
|
201
|
+
},
|
|
202
|
+
// Pylint: REQUIRE docstrings → missing-function-docstring. A PRESENCE context is
|
|
203
|
+
// required (a presence verb near "docstring", or "docstrings required/for each")
|
|
204
|
+
// so a bare "docstring" mention does NOT over-fire: the dogfood caught langchain
|
|
205
|
+
// routing docstring-CONTENT/STYLE rules ("docstring warnings", "backticks in
|
|
206
|
+
// docstrings", "don't repeat the default in the docstring") to this presence
|
|
207
|
+
// check — those are pydocstyle/ruff-D territory, not missing-docstring.
|
|
208
|
+
{
|
|
209
|
+
construct: "docstrings (presence)",
|
|
210
|
+
rule: "missing-function-docstring",
|
|
211
|
+
linter: "pylint",
|
|
212
|
+
pattern: /\b(?:add|require|requires?|write|writing|include|need|needs?|use|using|provide|document|must\s+have)\b[^.\n]{0,24}\bdocstrings?\b|\bdocstrings?\b[^.\n]{0,24}\b(?:required|mandatory|for\s+(?:all|every|each|every|public)|on\s+(?:all|every|each))\b/i,
|
|
213
|
+
configFix: "pylint enables missing-function-docstring (C0116) by default; keep it out of the disable list",
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
/**
|
|
217
|
+
* Backticked rule-id-shaped tokens a bullet names — `curly`, `curly: error` →
|
|
218
|
+
* `curly`, `@typescript-eslint/consistent-type-imports`, `no-only-tests/no-only-tests`.
|
|
219
|
+
* The leading id is taken (severity/args after a `:`/space are dropped), so the
|
|
220
|
+
* token can be looked up against the dynamic catalog.
|
|
89
221
|
*/
|
|
90
|
-
|
|
222
|
+
const CODE_SPAN_RE = /`([^`]+)`/g;
|
|
223
|
+
function namedRuleTokens(text) {
|
|
224
|
+
const out = [];
|
|
225
|
+
for (const m of text.matchAll(CODE_SPAN_RE)) {
|
|
226
|
+
const id = m[1].trim().match(/^@?[a-z][\w-]*(?:\/[a-z][\w-]*)*/i);
|
|
227
|
+
if (id)
|
|
228
|
+
out.push(id[0]);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
/** Combine two hits that a doc-token resolves to (a cross-linter id collision).
|
|
233
|
+
* enabled OR-s — a "**Enforced by:** X" claim is satisfied if ANY linter has X
|
|
234
|
+
* on, so we never cry "documented but OFF" when one linter enforces it — and
|
|
235
|
+
* provenance follows the enforcing linter. */
|
|
236
|
+
function combineHits(a, b) {
|
|
237
|
+
if (a.enabled === b.enabled)
|
|
238
|
+
return { enabled: a.enabled, linter: a.linter };
|
|
239
|
+
return a.enabled ? a : b; // exactly one is on → it wins (enabled OR-s to true)
|
|
240
|
+
}
|
|
241
|
+
/** Build the doc-token → hit lookup from a (possibly polyglot) rule list. A rule
|
|
242
|
+
* is matchable by its id AND, for Pylint, its numeric code. A bare id CAN collide
|
|
243
|
+
* across linters (`no-else-return` is in both ESLint and Pylint) → combine
|
|
244
|
+
* conservatively. A numeric code is unique to its linter, so it never collides
|
|
245
|
+
* and KEEPS its own (linter, enabled) — a doc naming the Pylint code `R1705`
|
|
246
|
+
* still surfaces "documented but OFF" even when the symbol is enabled in ESLint. */
|
|
247
|
+
function buildCatalogLookup(rules) {
|
|
248
|
+
const map = new Map();
|
|
249
|
+
const put = (key, hit) => {
|
|
250
|
+
const prev = map.get(key);
|
|
251
|
+
map.set(key, prev ? combineHits(prev, hit) : hit);
|
|
252
|
+
};
|
|
253
|
+
for (const r of rules) {
|
|
254
|
+
const hit = { enabled: r.enabled, linter: r.linter };
|
|
255
|
+
put(r.id, hit);
|
|
256
|
+
if (r.code)
|
|
257
|
+
put(r.code, hit);
|
|
258
|
+
}
|
|
259
|
+
return map;
|
|
260
|
+
}
|
|
261
|
+
function classify(text, catalog) {
|
|
91
262
|
if (HOOK_CUES.some((re) => re.test(text)))
|
|
92
263
|
return { category: "hook" };
|
|
264
|
+
if (META_CUES.some((re) => re.test(text)))
|
|
265
|
+
return { category: "meta" };
|
|
266
|
+
// Dynamic catalog: a bullet that NAMES one of the repo's real rules → reuse,
|
|
267
|
+
// carrying its linter + whether it's currently enabled (a disabled hit = the
|
|
268
|
+
// "documented but OFF" nudge). Own-repo only — catalog is present only when the
|
|
269
|
+
// linter was enumerated with consent.
|
|
270
|
+
if (catalog) {
|
|
271
|
+
for (const tok of namedRuleTokens(text)) {
|
|
272
|
+
const hit = catalog.get(tok);
|
|
273
|
+
if (hit !== undefined)
|
|
274
|
+
return {
|
|
275
|
+
category: "reuse",
|
|
276
|
+
rule: tok,
|
|
277
|
+
enabled: hit.enabled,
|
|
278
|
+
linter: hit.linter,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
93
282
|
for (const m of rule_inventory_js_1.INTENT_MAP) {
|
|
94
283
|
if (m.keywords.some((kw) => (0, rule_inventory_js_1.matchesWholeToken)(text, kw))) {
|
|
95
284
|
return { category: "reuse", rule: m.rule, linter: m.linter };
|
|
96
285
|
}
|
|
97
286
|
}
|
|
287
|
+
// Pattern → a built-in parameterized rule (eslint no-restricted-syntax
|
|
288
|
+
// construct-prohibitions; pylint docstring-presence). These LOOK custom but
|
|
289
|
+
// are reuse; each carries its own linter.
|
|
290
|
+
for (const r of PATTERN_RULE_MAP) {
|
|
291
|
+
if (r.pattern.test(text))
|
|
292
|
+
return { category: "reuse", rule: r.rule, linter: r.linter };
|
|
293
|
+
}
|
|
98
294
|
if (SEMANTIC_CUES.some((re) => re.test(text)))
|
|
99
295
|
return { category: "semantic" };
|
|
100
296
|
return { category: "unrouted" };
|
|
101
297
|
}
|
|
298
|
+
// --- Structured-marker pre-pass (S0/S1) ------------------------------------
|
|
299
|
+
const ENFORCED_RE = /^\*\*Enforced by:\*\*\s*`([^`]+)`/;
|
|
300
|
+
const GUARD_RE = /^\*\*Guard:\*\*/;
|
|
301
|
+
const GUIDANCE_RE = /^\*\*Guidance only\*\*/;
|
|
302
|
+
const MARK_HEADING = /^(#{2,6})\s+(.*)$/;
|
|
303
|
+
const RULE_ID_SHAPE = /^@?[a-z][a-z0-9._/-]*$/;
|
|
304
|
+
// Pylint's numeric alias (C0116, W9006) — the catalog advertises these as
|
|
305
|
+
// matchable, so a marker using one must parse as a rule id, not a prose claim.
|
|
306
|
+
const PYLINT_CODE_SHAPE = /^[A-Z]\d+$/;
|
|
307
|
+
/** Does this `**Enforced by:**` value parse as a lint-rule id (vs a prose claim
|
|
308
|
+
* like "CI" or "the linter")? A hand-written marker is a CLAIM — only a rule-id
|
|
309
|
+
* shape is treated as a real reuse rule. */
|
|
310
|
+
function looksLikeRuleId(s) {
|
|
311
|
+
const t = s.trim();
|
|
312
|
+
return (t.length >= 3 && RULE_ID_SHAPE.test(t)) || PYLINT_CODE_SHAPE.test(t);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Extract rules from EXPLICIT structured markers (`**Enforced by:** \`rule\``,
|
|
316
|
+
* `**Guard:**`, `**Guidance only**`) — the S0/S1 tier. A compiled/marked doc
|
|
317
|
+
* declares its own routing, so these are definitive (zero-heuristic) and are
|
|
318
|
+
* CONSUMED before the heuristic segmenter runs (the returned `skip` line set) so
|
|
319
|
+
* a marked rule is never double-counted. Each marker → ONE atom named by its
|
|
320
|
+
* `##`/`###` heading; a `**Guidance only**` body is still routed through
|
|
321
|
+
* `classify` (the promote-prose signal: a guidance whose body says "before
|
|
322
|
+
* commit" surfaces as a would-be hook). Foreign hand-written `**Enforced by:**`
|
|
323
|
+
* is a CLAIM — only a rule-id-shaped value becomes a reuse rule (gated + verified
|
|
324
|
+
* against the catalog when present; never an inferred contradiction).
|
|
325
|
+
*/
|
|
326
|
+
function extractMarkedRules(text, file, catalog) {
|
|
327
|
+
const lines = text.split("\n");
|
|
328
|
+
const rules = [];
|
|
329
|
+
const skip = new Set();
|
|
330
|
+
for (let i = 0; i < lines.length; i++) {
|
|
331
|
+
const h = MARK_HEADING.exec(lines[i]);
|
|
332
|
+
if (!h)
|
|
333
|
+
continue;
|
|
334
|
+
let j = i + 1;
|
|
335
|
+
while (j < lines.length && !MARK_HEADING.test(lines[j]))
|
|
336
|
+
j++;
|
|
337
|
+
const section = lines.slice(i, j); // [heading … next-heading)
|
|
338
|
+
const heading = h[2].trim();
|
|
339
|
+
let marked = null;
|
|
340
|
+
for (const raw of section.slice(1)) {
|
|
341
|
+
const bl = raw.trim();
|
|
342
|
+
const em = ENFORCED_RE.exec(bl);
|
|
343
|
+
if (em) {
|
|
344
|
+
if (!looksLikeRuleId(em[1]))
|
|
345
|
+
break; // a prose claim, not a rule id
|
|
346
|
+
const hit = catalog?.get(em[1].trim());
|
|
347
|
+
marked = {
|
|
348
|
+
category: "reuse",
|
|
349
|
+
rule: em[1].trim(),
|
|
350
|
+
...(hit !== undefined
|
|
351
|
+
? { enabled: hit.enabled, linter: hit.linter }
|
|
352
|
+
: {}),
|
|
353
|
+
};
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
if (GUARD_RE.test(bl)) {
|
|
357
|
+
marked = { category: "hook" };
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
if (GUIDANCE_RE.test(bl)) {
|
|
361
|
+
// Route the guidance BODY through classify (promote-prose): a guidance
|
|
362
|
+
// whose text is really an action shows up as a would-be hook.
|
|
363
|
+
const body = section.slice(1).join(" ");
|
|
364
|
+
const c = classify(body, catalog);
|
|
365
|
+
// A guidance body that names a catalog rule is still "documented as
|
|
366
|
+
// guidance" — keep it prose unless it's a genuine action/agent cue.
|
|
367
|
+
marked =
|
|
368
|
+
c.category === "hook" || c.category === "meta"
|
|
369
|
+
? c
|
|
370
|
+
: { category: "semantic" };
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!marked)
|
|
375
|
+
continue;
|
|
376
|
+
// Consume the section BODY lines (heading stays a non-candidate) so the
|
|
377
|
+
// heuristic segmenter never re-emits this marked rule. (1-based.)
|
|
378
|
+
for (let k = i + 1; k < j; k++)
|
|
379
|
+
skip.add(k + 1);
|
|
380
|
+
rules.push({
|
|
381
|
+
text: heading,
|
|
382
|
+
quote: lines[i],
|
|
383
|
+
file,
|
|
384
|
+
lineStart: i + 1,
|
|
385
|
+
lineEnd: j,
|
|
386
|
+
confidence: "high",
|
|
387
|
+
category: marked.category,
|
|
388
|
+
mechanism: MECHANISM[marked.category],
|
|
389
|
+
source: "marker",
|
|
390
|
+
...(marked.rule ? { rule: marked.rule } : {}),
|
|
391
|
+
...(marked.linter ? { linter: marked.linter } : {}),
|
|
392
|
+
...(marked.enabled !== undefined ? { enabled: marked.enabled } : {}),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return { rules, skip };
|
|
396
|
+
}
|
|
102
397
|
/**
|
|
103
398
|
* Segment the instruction file and route every atomic rule deterministically.
|
|
104
399
|
* Pure: the caller passes the concatenated instruction text (and an optional
|
|
@@ -106,9 +401,40 @@ function classify(text) {
|
|
|
106
401
|
*/
|
|
107
402
|
function routeRules(instructionText, file, options = {}) {
|
|
108
403
|
const minConfidence = options.minConfidence ?? "high";
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
404
|
+
const catalog = options.availableRules
|
|
405
|
+
? buildCatalogLookup(options.availableRules.rules)
|
|
406
|
+
: undefined;
|
|
407
|
+
// A MEDIUM segment that NAMES a rule the repo's catalog actually has is
|
|
408
|
+
// enforceable — the catalog is ground truth, so it's higher-precision than the
|
|
409
|
+
// segmenter's imperative-head cue. This rescues declarative-subject bullets
|
|
410
|
+
// ("The core layer must not import X (`boundaries/dependencies`)") that score
|
|
411
|
+
// medium (context+shape, no imperative head) and are otherwise dropped by the
|
|
412
|
+
// high-only default. Own-repo only (catalog present ⇒ enumerated with consent);
|
|
413
|
+
// the foreign-safe textual path stays conservative by design.
|
|
414
|
+
const namesCatalogRule = (text) => catalog !== undefined &&
|
|
415
|
+
namedRuleTokens(text).some((tok) => catalog.has(tok));
|
|
416
|
+
// A MEDIUM segment matching a construct-prohibition ("No default exports")
|
|
417
|
+
// scores medium ("No" is a prohibition head, not a verb) but is a real reuse
|
|
418
|
+
// rule (no-restricted-syntax) — rescue it, same as the catalog rescue. The
|
|
419
|
+
// patterns are their own precision gate (prohibition + construct proximity).
|
|
420
|
+
const matchesPatternRule = (text) => PATTERN_RULE_MAP.some((r) => r.pattern.test(text));
|
|
421
|
+
// A MEDIUM segment that matches an INTENT_MAP keyword (code-shaped, high-
|
|
422
|
+
// precision) is a real reuse rule — rescue it, same as catalog/restricted-
|
|
423
|
+
// syntax. Fixes construct-prohibitions with no verb ("No bare except clauses")
|
|
424
|
+
// that score medium and would otherwise drop before classify() reuses them.
|
|
425
|
+
const matchesIntentMap = (text) => rule_inventory_js_1.INTENT_MAP.some((m) => m.keywords.some((kw) => (0, rule_inventory_js_1.matchesWholeToken)(text, kw)));
|
|
426
|
+
// A segment is CONFIDENT if it's high, rescued by the catalog/pattern/intent, or
|
|
427
|
+
// the caller opted into medium. Everything else the segmenter emitted is a
|
|
428
|
+
// POSSIBLE rule (medium, unrescued) — surfaced for review, not routed as fact.
|
|
429
|
+
// A RESCUE — the text NAMES/matches a real rule (catalog / restricted-syntax /
|
|
430
|
+
// intent). This promotes even a gate-rejected bullet to confident, because it
|
|
431
|
+
// provably maps to an off-the-shelf rule; independent of the medium opt-in.
|
|
432
|
+
const isRescued = (text) => namesCatalogRule(text) ||
|
|
433
|
+
matchesPatternRule(text) ||
|
|
434
|
+
matchesIntentMap(text);
|
|
435
|
+
const isConfident = (s) => minConfidence === "medium" || s.confidence === "high" || isRescued(s.text);
|
|
436
|
+
const toRouted = (s) => {
|
|
437
|
+
const c = classify(s.text, catalog);
|
|
112
438
|
return {
|
|
113
439
|
text: s.text,
|
|
114
440
|
quote: s.exactQuote,
|
|
@@ -118,18 +444,102 @@ function routeRules(instructionText, file, options = {}) {
|
|
|
118
444
|
confidence: s.confidence,
|
|
119
445
|
category: c.category,
|
|
120
446
|
mechanism: MECHANISM[c.category],
|
|
447
|
+
source: "heuristic",
|
|
121
448
|
...(c.rule ? { rule: c.rule } : {}),
|
|
122
449
|
...(c.linter ? { linter: c.linter } : {}),
|
|
450
|
+
...(c.enabled !== undefined ? { enabled: c.enabled } : {}),
|
|
123
451
|
};
|
|
452
|
+
};
|
|
453
|
+
// S0/S1 pre-pass: explicit markers are definitive and are CONSUMED (their body
|
|
454
|
+
// lines are skipped) so the heuristic segmenter can't double-count them.
|
|
455
|
+
const marked = extractMarkedRules(instructionText, file, catalog);
|
|
456
|
+
const { segments, skipped: rawSkipped } = (0, segment_js_1.segmentInstructions)(instructionText, file, marked.skip);
|
|
457
|
+
// A bullet the gate rejected as `no-signal` is a rule CANDIDATE only if it
|
|
458
|
+
// carries a deontic/norm signal (a modal like must/should/never/avoid) — that
|
|
459
|
+
// keeps the POSSIBLE review tier to genuine recall-misses ("every function must
|
|
460
|
+
// have a docstring") instead of flooding it with prose ("README.md documents
|
|
461
|
+
// v2"). A no-signal bullet WITHOUT a norm signal is confidently not a rule, so
|
|
462
|
+
// it stays SKIPPED alongside the index/description/section rejects. A folded
|
|
463
|
+
// candidate that NAMES/matches a real rule is still rescued to CONFIDENT.
|
|
464
|
+
const asCandidate = (s) => ({
|
|
465
|
+
text: s.text,
|
|
466
|
+
exactQuote: s.text,
|
|
467
|
+
file: s.file,
|
|
468
|
+
lineStart: s.lineStart,
|
|
469
|
+
lineEnd: s.lineEnd,
|
|
470
|
+
confidence: "medium",
|
|
124
471
|
});
|
|
472
|
+
// Real SEGMENTS route by the full confident check (incl. the medium opt-in).
|
|
473
|
+
// Gate-rejected `no-signal` bullets are folded back in as candidates, but they
|
|
474
|
+
// are promoted to confident ONLY by a real RESCUE — NEVER by the blanket medium
|
|
475
|
+
// opt-in, which must not resurrect bullets the gate explicitly rejected.
|
|
476
|
+
const noSignal = rawSkipped.filter((s) => s.reason === "no-signal");
|
|
477
|
+
const folds = noSignal.map(asCandidate);
|
|
478
|
+
const heuristicRules = [
|
|
479
|
+
...segments.filter(isConfident),
|
|
480
|
+
...folds.filter((s) => isRescued(s.text)),
|
|
481
|
+
].map(toRouted);
|
|
482
|
+
// The non-confident leftovers split by the norm signal: a rule-ish bullet
|
|
483
|
+
// (carries a deontic modal) is a genuine recall-miss → POSSIBLE (review); the
|
|
484
|
+
// rest is prose → SKIPPED with a `no-signal` reason (visible, not dropped).
|
|
485
|
+
const leftover = [
|
|
486
|
+
...segments.filter((s) => !isConfident(s)),
|
|
487
|
+
...folds.filter((s) => !isRescued(s.text)),
|
|
488
|
+
];
|
|
489
|
+
const possible = leftover
|
|
490
|
+
.filter((s) => NORM_SIGNAL.test(s.text))
|
|
491
|
+
.map(toRouted);
|
|
492
|
+
const skipped = [
|
|
493
|
+
...rawSkipped.filter((s) => s.reason !== "no-signal"),
|
|
494
|
+
...leftover
|
|
495
|
+
.filter((s) => !NORM_SIGNAL.test(s.text))
|
|
496
|
+
.map((s) => ({
|
|
497
|
+
text: s.text,
|
|
498
|
+
file: s.file,
|
|
499
|
+
lineStart: s.lineStart,
|
|
500
|
+
lineEnd: s.lineEnd,
|
|
501
|
+
reason: "no-signal",
|
|
502
|
+
})),
|
|
503
|
+
];
|
|
504
|
+
// Marker rules first (definitive), then the heuristic residue.
|
|
505
|
+
const rules = [...marked.rules, ...heuristicRules];
|
|
125
506
|
const counts = {
|
|
126
507
|
reuse: 0,
|
|
127
508
|
hook: 0,
|
|
509
|
+
meta: 0,
|
|
128
510
|
semantic: 0,
|
|
129
511
|
unrouted: 0,
|
|
130
512
|
};
|
|
131
513
|
for (const r of rules)
|
|
132
514
|
counts[r.category]++;
|
|
133
|
-
return { segmented:
|
|
515
|
+
return { segmented: rules.length, counts, rules, possible, skipped };
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Merge per-file routings into one. Each instruction source is routed SEPARATELY
|
|
519
|
+
* (so every rule keeps its OWN file path + line numbers — concatenating first
|
|
520
|
+
* would corrupt the provenance the preview promises), then folded here: rules
|
|
521
|
+
* concatenated, counts + segmented summed. Pure. `[]` → an empty routing.
|
|
522
|
+
*/
|
|
523
|
+
function mergeRoutings(routings) {
|
|
524
|
+
const counts = {
|
|
525
|
+
reuse: 0,
|
|
526
|
+
hook: 0,
|
|
527
|
+
meta: 0,
|
|
528
|
+
semantic: 0,
|
|
529
|
+
unrouted: 0,
|
|
530
|
+
};
|
|
531
|
+
const rules = [];
|
|
532
|
+
const possible = [];
|
|
533
|
+
const skipped = [];
|
|
534
|
+
let segmented = 0;
|
|
535
|
+
for (const r of routings) {
|
|
536
|
+
segmented += r.segmented;
|
|
537
|
+
rules.push(...r.rules);
|
|
538
|
+
possible.push(...r.possible);
|
|
539
|
+
skipped.push(...r.skipped);
|
|
540
|
+
for (const k of Object.keys(counts))
|
|
541
|
+
counts[k] += r.counts[k];
|
|
542
|
+
}
|
|
543
|
+
return { segmented, counts, rules, possible, skipped };
|
|
134
544
|
}
|
|
135
545
|
//# sourceMappingURL=rule-routing.js.map
|