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/segment.d.ts
CHANGED
|
@@ -22,6 +22,28 @@ export interface SegmentedRule {
|
|
|
22
22
|
/** 3/3 cues => "high"; 2/3 => "medium". (Rejected candidates are never emitted.) */
|
|
23
23
|
confidence: "high" | "medium";
|
|
24
24
|
}
|
|
25
|
+
/** Why the segmenter decided a bullet is NOT a rule (the transparency signal —
|
|
26
|
+
* see `research/rule-compiler-design.md` §3). `index`/`description`/`no-signal`
|
|
27
|
+
* come from the gate; `section` means it sits under a non-rule heading
|
|
28
|
+
* (Setup / Commands / Key Files / Architecture …). */
|
|
29
|
+
export type RejectReason = "index" | "description" | "no-signal" | "section";
|
|
30
|
+
/** A BULLET the segmenter saw but did NOT treat as a rule, with the reason — so
|
|
31
|
+
* the audit report can be honest about what it set aside (a heuristic misses
|
|
32
|
+
* declarative rules; showing skips lets a human eyeball a wrong drop). Bounded to
|
|
33
|
+
* list items on purpose; rejected paragraph prose is not reported (too noisy). */
|
|
34
|
+
export interface SkippedBullet {
|
|
35
|
+
readonly text: string;
|
|
36
|
+
readonly file: string | undefined;
|
|
37
|
+
readonly lineStart: number;
|
|
38
|
+
readonly lineEnd: number;
|
|
39
|
+
readonly reason: RejectReason;
|
|
40
|
+
}
|
|
41
|
+
/** The segmenter's full output: the confident/medium candidate rules PLUS the
|
|
42
|
+
* bullets it rejected (with reasons), so nothing is silently dropped. */
|
|
43
|
+
export interface SegmentResult {
|
|
44
|
+
readonly segments: SegmentedRule[];
|
|
45
|
+
readonly skipped: SkippedBullet[];
|
|
46
|
+
}
|
|
25
47
|
/**
|
|
26
48
|
* Split a CLAUDE.md / AGENTS.md into atomic candidate rules with provenance.
|
|
27
49
|
*
|
|
@@ -29,5 +51,5 @@ export interface SegmentedRule {
|
|
|
29
51
|
* candidacy. Candidate units are (a) list items with attached continuation
|
|
30
52
|
* lines and (b) sentences of paragraphs under a rule-ish heading.
|
|
31
53
|
*/
|
|
32
|
-
export declare function segmentInstructions(markdown: string, file?: string):
|
|
54
|
+
export declare function segmentInstructions(markdown: string, file?: string, skipLines?: ReadonlySet<number>): SegmentResult;
|
|
33
55
|
//# sourceMappingURL=segment.d.ts.map
|
package/dist/segment.js
CHANGED
|
@@ -11,10 +11,108 @@
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.segmentInstructions = segmentInstructions;
|
|
13
13
|
// --- Heuristic vocabulary --------------------------------------------------
|
|
14
|
-
/** Imperative/prohibitive head the candidate must START with (form cue).
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/** Imperative/prohibitive head the candidate must START with (form cue). The
|
|
15
|
+
* deontic verbs (require/disallow/forbid/ban/enforce) are common rule leads —
|
|
16
|
+
* "Require `curly` braces", "Disallow `var`" — so they belong here. NB "no" is
|
|
17
|
+
* `no\s` (the prohibition word + whitespace) NOT the old `no\s+\S`, which — via
|
|
18
|
+
* the shared trailing `\b` — only matched when the word after "No " began at a
|
|
19
|
+
* boundary, so "No bare except" / "No default exports" silently failed the form
|
|
20
|
+
* cue. Bare `no` + the shared `\b` (checked right after "no", a boundary before
|
|
21
|
+
* a space OR a backtick) matches "No bare" AND "No `any`", while "Note"/"Nowhere"
|
|
22
|
+
* (no boundary after "no") are still rejected. */
|
|
23
|
+
const FORM_HEAD = /^(?:use|avoid|prefer|never|always|don'?t|do not|no|must|should|keep|run|write|add|remove|only|require|requires?|disallow|forbid|ban|enforce)\b/i;
|
|
24
|
+
/**
|
|
25
|
+
* Rule-ish heading gate for prose-under-heading candidacy. Word-bounded so the
|
|
26
|
+
* `do` alternate can't match inside `Documentation`/`Adoption`/`Download` (the
|
|
27
|
+
* measured bug). Accept-heading vocabulary grounded in the OSS-corpus survey
|
|
28
|
+
* (`## Coding Standards`/`## Code Style`/`## Naming`/`## Good practices`/
|
|
29
|
+
* `## Error Handling` are the real code-norm sections).
|
|
30
|
+
*/
|
|
31
|
+
const RULE_HEADING = /\b(?:rules?|conventions?|code[ -]?style|style|guidelines?|standards?|naming|good practices?|error handling|do'?s?\s*(?:and|&|\/)\s*don'?ts?|don'?ts?|never|always|must|require)\b/i;
|
|
32
|
+
/**
|
|
33
|
+
* Anti-context heading: a section whose content is overwhelmingly index /
|
|
34
|
+
* command / setup / narrative, not enforceable norms (the corpus's #1
|
|
35
|
+
* false-positive source). Content under one of these is rejected outright —
|
|
36
|
+
* UNLESS the heading is also rule-ish (`## Testing conventions` keeps its
|
|
37
|
+
* bullets), so the accept signal wins a tie.
|
|
38
|
+
*/
|
|
39
|
+
const ANTI_HEADING = /\b(?:commands?|setup|install(?:ation)?|usage|getting started|quick ?start|examples?|key files|(?:code)?base structure|project structure|repository structure|architecture|overview|directory|layout|environment|commits?|pull requests?|testing|scripts?|dependencies|roadmap|changelog|table of contents|where to look)\b/i;
|
|
40
|
+
/**
|
|
41
|
+
* INDEX-SMELL veto: a bullet whose content is a code span followed by a
|
|
42
|
+
* separator + description (`` `src/x.ts` — Type system ``, `` `npm test` — run ``)
|
|
43
|
+
* is a keyFiles/command INDEX entry, never a rule. The single highest-value
|
|
44
|
+
* rejection signal (the corpus's dominant false positive).
|
|
45
|
+
*/
|
|
46
|
+
const INDEX_SMELL = /^`[^`]+`\s*[:—–-]\s/;
|
|
47
|
+
/**
|
|
48
|
+
* Broader index/reference-entry shapes the backtick INDEX_SMELL misses — from
|
|
49
|
+
* real-corpus leakage: a path MAPPING (`next-dev.ts → next-dev-server.ts`), a
|
|
50
|
+
* bullet LED by a file path + em-dash (`packages/x/ — Session replay`), or a
|
|
51
|
+
* `Label: <path>` pointer (`Skill file: .agents/skills/…`). Paths in these are
|
|
52
|
+
* backtick-wrapped, so we test a backtick-stripped shadow. The path DISCRIMINATOR
|
|
53
|
+
* — a file extension (`.ts`) or a trailing slash — is what keeps a rule id
|
|
54
|
+
* (`@scope/no-explicit-any`, no extension) from being mistaken for a path, so a
|
|
55
|
+
* rule-naming bullet is never rejected as an index entry.
|
|
56
|
+
*/
|
|
57
|
+
const INDEX_ARROW = /[\w./@-]*\.[a-z]{1,6}\b\s*(?:→|->|=>)/;
|
|
58
|
+
// A multi-segment slash PATH before an arrow is a path-mapping/index row
|
|
59
|
+
// (`node_modules/@astrojs/react/… → packages/…`) — ≥2 slashes keeps it path-
|
|
60
|
+
// specific so a prose "A → B" isn't caught.
|
|
61
|
+
const INDEX_ARROW_PATH = /[\w@.-]+(?:\/[\w@.*-]+){2,}\s*(?:→|->|=>)/;
|
|
62
|
+
const INDEX_LABEL_PATH = /^[A-Za-z][\w ]{0,24}:\s+\.?[\w@-]*\/[\w@./-]*(?:\.[a-z0-9]{1,6}\b|\/)/;
|
|
63
|
+
const INDEX_PATH_LED = /^[\w@.-]+\/[\w@./-]*(?:\.[a-z0-9]{1,6}\b|\/)/;
|
|
64
|
+
function looksLikeIndexEntry(t) {
|
|
65
|
+
if (INDEX_SMELL.test(t))
|
|
66
|
+
return true;
|
|
67
|
+
const bare = t.replace(/`/g, " ").trim();
|
|
68
|
+
if (INDEX_ARROW.test(bare) || INDEX_ARROW_PATH.test(bare))
|
|
69
|
+
return true;
|
|
70
|
+
if (INDEX_LABEL_PATH.test(bare))
|
|
71
|
+
return true;
|
|
72
|
+
return INDEX_PATH_LED.test(bare) && /\s[—–]\s/.test(bare);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* DESCRIPTION-LED reject: a segment that DESCRIBES a code entity rather than
|
|
76
|
+
* instructing about it. It leads with a backticked identifier/path, then a
|
|
77
|
+
* copula / code-KIND noun / descriptive verb ("`Foo` class in `x` executes …",
|
|
78
|
+
* "`bar` is the loader", "`apps/x` handles …"). A real rule leads with a VERB
|
|
79
|
+
* ("Use `Foo`", "Never `bar`") — never the code span itself — so a code-span
|
|
80
|
+
* lead-in followed by a descriptive word is an architecture/index sentence, the
|
|
81
|
+
* dogfood's #1 segmenter false positive (39% of the "hard" bucket was this
|
|
82
|
+
* kind of noise). High-precision: only when the descriptive word IMMEDIATELY
|
|
83
|
+
* follows the leading code span.
|
|
84
|
+
*/
|
|
85
|
+
const DESCRIPTION_LED = /^`[^`]+`\s+(?:is|are|was|were|lives?|live|contains?|holds?|handles?|executes?|provides?|represents?|maps?|points?|implements?|exports?|defines?|wraps?|stores?|returns?|the|a|an|class|function|module|component|file|package|hook|utility|helper|type|interface|enum|constant|method|directory|folder|dir)\b/i;
|
|
86
|
+
// A deontic predicate makes a code-span-led sentence a RULE, not a description
|
|
87
|
+
// ("`const` is preferred over `let`", "`AbstractBase` class must be extended") —
|
|
88
|
+
// so the description reject must NOT fire. Guards the copula/kind-noun ambiguity.
|
|
89
|
+
const RULE_PREDICATE = /\b(?:must|should|shall|never|always|require|avoid|prefer|banned|forbidden|prohibited|allowed|disallowed|deprecated|discouraged|mandatory|do not|don'?t|only|instead)\b/i;
|
|
90
|
+
function looksLikeDescription(t) {
|
|
91
|
+
const s = t.trim();
|
|
92
|
+
return DESCRIPTION_LED.test(s) && !RULE_PREDICATE.test(s);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* RULE-NAME cue: a backticked token that is SHAPED like an off-the-shelf lint
|
|
96
|
+
* rule — a scoped/plugin rule (`@typescript-eslint/consistent-type-imports`,
|
|
97
|
+
* `import/no-cycle`) or a ≥3-segment kebab id (`no-floating-promises`). Requiring
|
|
98
|
+
* the backticks kills prose false positives (`up-to-date`, `state-of-the-art`,
|
|
99
|
+
* a file path). Naming a rule is a STRONG signal a bullet is an enforceable rule
|
|
100
|
+
* even when it has no imperative verb — the corpus's rule-naming bullets
|
|
101
|
+
* ("No floating promises (`@ts.../no-floating-promises`)") otherwise score
|
|
102
|
+
* "medium" and get dropped by the high-only default.
|
|
103
|
+
*/
|
|
104
|
+
const RULE_NAME_IN_CODE = /`[^`]*(?:@[a-z][\w-]*\/[a-z][\w-]*|[a-z][\w-]*\/[a-z][\w-]*-[\w-]+|(?:no|prefer|require|consistent|max|min|func|id|sort|valid|padding|dot|array|object)-[a-z][a-z0-9-]+)[^`]*`/i;
|
|
105
|
+
/**
|
|
106
|
+
* Leading markdown decoration a rule may be wrapped in — emphasis (`**bold**`),
|
|
107
|
+
* blockquote, checkbox, or a status emoji. Stripped on a SHADOW string before
|
|
108
|
+
* the imperative-head test so `- **Never** …` / `✅ Use const` still read as
|
|
109
|
+
* imperative. Provenance (exactQuote/offsets) is unaffected — only the form cue
|
|
110
|
+
* sees the stripped text.
|
|
111
|
+
*/
|
|
112
|
+
const LEAD_DECORATION = /^(?:\s+|>+|\*+|_+|~+|\[[ xX]\]\s*|[✅❌☑✔✖✗⚠ℹ])+/u;
|
|
113
|
+
function stripLeadDecoration(s) {
|
|
114
|
+
return s.replace(LEAD_DECORATION, "").trimStart();
|
|
115
|
+
}
|
|
18
116
|
/** Declarative subjects — these signal a statement, not an instruction. */
|
|
19
117
|
const DECLARATION = /^(?:this|these|those|it|we|our|there)\b/i;
|
|
20
118
|
/** Line consisting only of a bare URL. */
|
|
@@ -101,14 +199,9 @@ const VERBS = new Set([
|
|
|
101
199
|
"filters",
|
|
102
200
|
"merge",
|
|
103
201
|
"merges",
|
|
104
|
-
|
|
105
|
-
"
|
|
106
|
-
|
|
107
|
-
"have",
|
|
108
|
-
"has",
|
|
109
|
-
"may",
|
|
110
|
-
"should",
|
|
111
|
-
"must",
|
|
202
|
+
// Copulas/modals (be/is/are/have/has/may/should/must) are deliberately NOT
|
|
203
|
+
// here: as "shape" verbs they made the cue near-vacuous (almost any English
|
|
204
|
+
// sentence passed). Deontic modals still live in FORM_HEAD (the form cue).
|
|
112
205
|
"pin",
|
|
113
206
|
"pins",
|
|
114
207
|
"lint",
|
|
@@ -157,6 +250,13 @@ const VERBS = new Set([
|
|
|
157
250
|
"squash",
|
|
158
251
|
"enforce",
|
|
159
252
|
"enforces",
|
|
253
|
+
"regenerate",
|
|
254
|
+
"regenerates",
|
|
255
|
+
"regen",
|
|
256
|
+
"rebuild",
|
|
257
|
+
"rebuilds",
|
|
258
|
+
"generate",
|
|
259
|
+
"generates",
|
|
160
260
|
"define",
|
|
161
261
|
"defines",
|
|
162
262
|
"declare",
|
|
@@ -210,27 +310,50 @@ function isLinkOnly(text) {
|
|
|
210
310
|
const t = text.trim();
|
|
211
311
|
return URL_ONLY.test(t) || LINK_ONLY.test(t);
|
|
212
312
|
}
|
|
313
|
+
/** The accept/reject view of a gate result, for sites that only need the split
|
|
314
|
+
* (atomize, the sub-span loop) and don't care about the reason. */
|
|
315
|
+
function confidenceOf(g) {
|
|
316
|
+
return "confidence" in g ? g.confidence : null;
|
|
317
|
+
}
|
|
213
318
|
/**
|
|
214
|
-
* Score the 3 cues. Returns confidence
|
|
319
|
+
* Score the 3 cues. Returns a confidence OR a reject reason.
|
|
215
320
|
* - form: starts with an imperative/prohibitive head (or "No X").
|
|
216
321
|
* - context: is a bullet OR sits under a rule-ish heading.
|
|
217
322
|
* - shape: 15–300 chars, has a verb-ish token, not link-only, not a declaration.
|
|
218
323
|
*/
|
|
219
324
|
function gate(text, isBullet, underRuleHeading) {
|
|
220
325
|
const t = text.trim();
|
|
221
|
-
|
|
326
|
+
// Reject an index/command/reference entry outright (`` `path` — description ``,
|
|
327
|
+
// `a.ts → b.ts`, `dir/x — …`, `Label: path`) — the corpus's dominant false
|
|
328
|
+
// positive. No cue count can rescue it.
|
|
329
|
+
if (looksLikeIndexEntry(t))
|
|
330
|
+
return { reject: "index" };
|
|
331
|
+
// Reject a DESCRIPTION-led sentence (`` `Foo` class in `x` executes … ``) — an
|
|
332
|
+
// architecture/index sentence, not a rule (the dogfood's #1 false positive).
|
|
333
|
+
if (looksLikeDescription(t))
|
|
334
|
+
return { reject: "description" };
|
|
222
335
|
const context = isBullet || underRuleHeading;
|
|
336
|
+
// RULE-NAME cue: a bullet/section line that NAMES an off-the-shelf rule is a
|
|
337
|
+
// strong signal it's enforceable, even without an imperative verb — promote it
|
|
338
|
+
// to high so the high-only default doesn't drop it (recovers rule-naming
|
|
339
|
+
// bullets like "No floating promises (`@ts.../no-floating-promises`)").
|
|
340
|
+
if (context && RULE_NAME_IN_CODE.test(t))
|
|
341
|
+
return { confidence: "high" };
|
|
342
|
+
// The form/declaration cues see the text with leading decoration stripped, so
|
|
343
|
+
// `- **Never** …` reads as imperative and `**We** …` still reads declarative.
|
|
344
|
+
const head = stripLeadDecoration(t);
|
|
345
|
+
const form = FORM_HEAD.test(head);
|
|
223
346
|
const shape = t.length >= 15 &&
|
|
224
347
|
t.length <= 300 &&
|
|
225
348
|
hasVerbish(t) &&
|
|
226
349
|
!isLinkOnly(t) &&
|
|
227
|
-
!DECLARATION.test(
|
|
350
|
+
!DECLARATION.test(head);
|
|
228
351
|
const cues = (form ? 1 : 0) + (context ? 1 : 0) + (shape ? 1 : 0);
|
|
229
352
|
if (cues >= 3)
|
|
230
|
-
return "high";
|
|
353
|
+
return { confidence: "high" };
|
|
231
354
|
if (cues === 2)
|
|
232
|
-
return "medium";
|
|
233
|
-
return
|
|
355
|
+
return { confidence: "medium" };
|
|
356
|
+
return { reject: "no-signal" };
|
|
234
357
|
}
|
|
235
358
|
// --- Atomicity split -------------------------------------------------------
|
|
236
359
|
/** Never split when an exception clause carries polarity/meaning. */
|
|
@@ -286,7 +409,7 @@ function atomize(src, contentSpan, isBullet, underRuleHeading) {
|
|
|
286
409
|
// Both/all halves must independently pass the gate, else keep whole.
|
|
287
410
|
for (const p of pieces) {
|
|
288
411
|
const text = normalize(src.slice(p.start, p.end));
|
|
289
|
-
if (gate(text, isBullet, underRuleHeading) === null)
|
|
412
|
+
if (confidenceOf(gate(text, isBullet, underRuleHeading)) === null)
|
|
290
413
|
return [whole];
|
|
291
414
|
}
|
|
292
415
|
return pieces.length > 1 ? pieces : [whole];
|
|
@@ -304,7 +427,9 @@ function emitFromSpan(src, lineOffsets, file, span, confidence) {
|
|
|
304
427
|
};
|
|
305
428
|
}
|
|
306
429
|
// --- Scanner ---------------------------------------------------------------
|
|
307
|
-
|
|
430
|
+
// Ordered (`1.`/`1)`) and emoji (`✅`/`❌`) bullets count as list items too —
|
|
431
|
+
// the native `-*+` class missed them, so shouted/numbered rules were invisible.
|
|
432
|
+
const LIST_ITEM = /^(\s*)([-*+]|\d+[.)]|[✅❌☑✔✖✗])(\s+)(.*)$/u;
|
|
308
433
|
const HEADING = /^(#{1,6})\s+(.*)$/;
|
|
309
434
|
const FENCE = /^\s*(```|~~~)/;
|
|
310
435
|
const TABLE_LINE = /^\s*\|/;
|
|
@@ -315,12 +440,14 @@ const TABLE_LINE = /^\s*\|/;
|
|
|
315
440
|
* candidacy. Candidate units are (a) list items with attached continuation
|
|
316
441
|
* lines and (b) sentences of paragraphs under a rule-ish heading.
|
|
317
442
|
*/
|
|
318
|
-
function segmentInstructions(markdown, file) {
|
|
443
|
+
function segmentInstructions(markdown, file, skipLines) {
|
|
319
444
|
const lines = markdown.split("\n");
|
|
320
445
|
const lineOffsets = computeLineOffsets(lines);
|
|
321
446
|
const out = [];
|
|
447
|
+
const skipped = [];
|
|
322
448
|
let inFence = false;
|
|
323
449
|
let currentHeadingIsRuleish = false;
|
|
450
|
+
let currentHeadingIsAntiContext = false;
|
|
324
451
|
let i = 0;
|
|
325
452
|
const lineSpan = (a, b) => ({
|
|
326
453
|
start: lineOffsets[a],
|
|
@@ -342,6 +469,10 @@ function segmentInstructions(markdown, file) {
|
|
|
342
469
|
const h = HEADING.exec(line);
|
|
343
470
|
if (h) {
|
|
344
471
|
currentHeadingIsRuleish = RULE_HEADING.test(h[2]);
|
|
472
|
+
// Anti-context only when it is NOT also rule-ish, so an accept word wins a
|
|
473
|
+
// tie (`## Testing conventions` keeps its bullets; `## Testing` drops them).
|
|
474
|
+
currentHeadingIsAntiContext =
|
|
475
|
+
ANTI_HEADING.test(h[2]) && !currentHeadingIsRuleish;
|
|
345
476
|
i++;
|
|
346
477
|
continue;
|
|
347
478
|
}
|
|
@@ -350,6 +481,13 @@ function segmentInstructions(markdown, file) {
|
|
|
350
481
|
i++;
|
|
351
482
|
continue;
|
|
352
483
|
}
|
|
484
|
+
// A line already CONSUMED by the structured-marker pre-pass (a marked
|
|
485
|
+
// section's body) is not re-segmented — this is the span-consumption that
|
|
486
|
+
// stops a marked rule being double-counted by the heuristic. (1-based.)
|
|
487
|
+
if (skipLines?.has(i + 1)) {
|
|
488
|
+
i++;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
353
491
|
// List items (with attached continuation lines).
|
|
354
492
|
const li = LIST_ITEM.exec(line);
|
|
355
493
|
if (li) {
|
|
@@ -381,8 +519,11 @@ function segmentInstructions(markdown, file) {
|
|
|
381
519
|
const contentEnd = lineOffsets[endLine] + lines[endLine].length;
|
|
382
520
|
const contentSpan = { start: contentStart, end: contentEnd };
|
|
383
521
|
const wholeText = normalize(markdown.slice(contentStart, contentEnd));
|
|
384
|
-
const
|
|
385
|
-
|
|
522
|
+
const g = gate(wholeText, true, currentHeadingIsRuleish);
|
|
523
|
+
const conf = confidenceOf(g);
|
|
524
|
+
// Reject bullets under an anti-context heading (Commands/Setup/Key Files/
|
|
525
|
+
// Architecture/…) — the corpus's dominant false-positive locus.
|
|
526
|
+
if (conf !== null && !currentHeadingIsAntiContext) {
|
|
386
527
|
// Only attempt splitting for single-line items (keeps offsets exact).
|
|
387
528
|
const spans = multiLine
|
|
388
529
|
? [trimSpan(markdown, contentSpan)]
|
|
@@ -394,12 +535,27 @@ function segmentInstructions(markdown, file) {
|
|
|
394
535
|
else {
|
|
395
536
|
for (const s of spans) {
|
|
396
537
|
const text = normalize(markdown.slice(s.start, s.end));
|
|
397
|
-
const c = gate(text, true, currentHeadingIsRuleish);
|
|
538
|
+
const c = confidenceOf(gate(text, true, currentHeadingIsRuleish));
|
|
398
539
|
if (c !== null)
|
|
399
540
|
out.push(emitFromSpan(markdown, lineOffsets, file, s, c));
|
|
400
541
|
}
|
|
401
542
|
}
|
|
402
543
|
}
|
|
544
|
+
else {
|
|
545
|
+
// This bullet was NOT treated as a rule — record it + why, so the audit
|
|
546
|
+
// report can be honest about what it set aside (transparency, §3). A
|
|
547
|
+
// rejection under an anti-context heading is a "section" skip; otherwise
|
|
548
|
+
// it's the gate's own reason.
|
|
549
|
+
skipped.push({
|
|
550
|
+
text: wholeText,
|
|
551
|
+
file,
|
|
552
|
+
lineStart: offsetToLine(lineOffsets, contentStart),
|
|
553
|
+
lineEnd: offsetToLine(lineOffsets, contentEnd - 1),
|
|
554
|
+
reason: currentHeadingIsAntiContext || "confidence" in g
|
|
555
|
+
? "section"
|
|
556
|
+
: g.reject,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
403
559
|
i = endLine + 1;
|
|
404
560
|
continue;
|
|
405
561
|
}
|
|
@@ -439,7 +595,7 @@ function segmentInstructions(markdown, file) {
|
|
|
439
595
|
if (s.start >= s.end)
|
|
440
596
|
continue;
|
|
441
597
|
const text = normalize(markdown.slice(s.start, s.end));
|
|
442
|
-
const c = gate(text, false, true);
|
|
598
|
+
const c = confidenceOf(gate(text, false, true));
|
|
443
599
|
if (c !== null)
|
|
444
600
|
out.push(emitFromSpan(markdown, lineOffsets, file, s, c));
|
|
445
601
|
}
|
|
@@ -449,6 +605,6 @@ function segmentInstructions(markdown, file) {
|
|
|
449
605
|
}
|
|
450
606
|
i++;
|
|
451
607
|
}
|
|
452
|
-
return out;
|
|
608
|
+
return { segments: out, skipped };
|
|
453
609
|
}
|
|
454
610
|
//# sourceMappingURL=segment.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigiles",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.1.0",
|
|
4
4
|
"description": "Lint & test the harness your AI agent runs on — verify the references in your CLAUDE.md / AGENTS.md and test that your hooks and skills actually work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@eslint/js": "^10.0.1",
|
|
93
|
+
"@jackchuka/mdschema": "^0.12.8",
|
|
93
94
|
"@microsoft/api-extractor": "^7.58.9",
|
|
94
95
|
"@types/js-yaml": "^4.0.9",
|
|
95
96
|
"@types/minimatch": "^5.1.2",
|
|
@@ -127,7 +128,6 @@
|
|
|
127
128
|
"@ast-grep/lang-rust": "^0.0.7",
|
|
128
129
|
"@ast-grep/napi": "^0.43.0",
|
|
129
130
|
"@iarna/toml": "^2.2.5",
|
|
130
|
-
"@jackchuka/mdschema": "^0.12.8",
|
|
131
131
|
"ci-info": "^4.4.0",
|
|
132
132
|
"cosmiconfig": "^9.0.1",
|
|
133
133
|
"glob": "^13.0.6",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Clippy — Reference
|
|
2
2
|
|
|
3
|
-
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and the planned rule-synthesis skill (write custom lints, not yet shipped).
|
|
4
4
|
|
|
5
5
|
## Check Existing Lints First
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ESLint — Reference
|
|
2
2
|
|
|
3
|
-
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and the planned rule-synthesis skill (write custom rules, not yet shipped).
|
|
4
4
|
|
|
5
5
|
## Check Existing Plugins First
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pylint — Reference
|
|
2
2
|
|
|
3
|
-
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and the planned rule-synthesis skill (write custom checkers, not yet shipped).
|
|
4
4
|
|
|
5
5
|
## Check Existing Plugins First
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RuboCop — Reference
|
|
2
2
|
|
|
3
|
-
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and the planned rule-synthesis skill (write custom cops, not yet shipped).
|
|
4
4
|
|
|
5
5
|
## Check Existing Gems First
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ruff — Reference
|
|
2
2
|
|
|
3
|
-
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and the planned rule-synthesis skill (write custom rules, not yet shipped).
|
|
4
4
|
|
|
5
5
|
## Check Existing Rules First
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Stylelint — Reference
|
|
2
2
|
|
|
3
|
-
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and
|
|
3
|
+
Shared linter reference for vigiles skills. Used by `strengthen` (find existing rules) and the planned rule-synthesis skill (write custom rules, not yet shipped).
|
|
4
4
|
|
|
5
5
|
## Check Existing Plugins First
|
|
6
6
|
|
|
@@ -142,12 +142,12 @@ Group the output into tiers:
|
|
|
142
142
|
→ enforce("eslint/sonarjs/cognitive-complexity", "Keep functions simple")
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
**Tier 4: No match** (stays as guidance
|
|
145
|
+
**Tier 4: No match** (stays as guidance — candidate for a future rule-synthesis skill)
|
|
146
146
|
|
|
147
147
|
```
|
|
148
148
|
"research-first": guidance("Google unfamiliar APIs first.")
|
|
149
|
-
→ No linter rule can enforce this. Stays as guidance.
|
|
150
|
-
→
|
|
149
|
+
→ No linter rule can enforce this. Stays as guidance for now.
|
|
150
|
+
→ (Custom-rule synthesis is planned but not yet shipped.)
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
### Step 6: Apply Changes
|
|
@@ -168,4 +168,4 @@ Group the output into tiers:
|
|
|
168
168
|
4. Run `npm run build && npx vigiles compile` to verify
|
|
169
169
|
5. If compilation fails, report the error and revert
|
|
170
170
|
|
|
171
|
-
**For Tier 4 (no match):**
|
|
171
|
+
**For Tier 4 (no match):** Tell the user these rules stay as guidance — custom-rule synthesis is a planned skill, not yet available.
|