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/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): SegmentedRule[];
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
- const FORM_HEAD = /^(?:use|avoid|prefer|never|always|don'?t|do not|no\s+\S|must|should|keep|run|write|add|remove|only)\b/i;
16
- /** Rule-ish heading gate for prose-under-heading candidacy. */
17
- const RULE_HEADING = /rules?|conventions?|style|guidelines?|standards?|do(?:n'?ts?)?s?|never|always|must|require/i;
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
- "be",
105
- "is",
106
- "are",
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 or null (reject).
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
- const form = FORM_HEAD.test(t);
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(t);
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 null;
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
- const LIST_ITEM = /^(\s*)([-*+])(\s+)(.*)$/;
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 conf = gate(wholeText, true, currentHeadingIsRuleish);
385
- if (conf !== null) {
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": "13.0.0",
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 `pr-to-lint-rule` (write custom lints).
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 `pr-to-lint-rule` (write custom rules).
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 `pr-to-lint-rule` (write custom checkers).
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 `pr-to-lint-rule` (write custom cops).
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 `pr-to-lint-rule` (write custom rules).
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 `pr-to-lint-rule` (write custom rules).
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, or candidate for `/pr-to-lint-rule`)
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
- → Want me to run /pr-to-lint-rule to create a custom rule?
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):** Ask the user if they want to run `/pr-to-lint-rule` for any of the unmatched rules to create custom rules.
171
+ **For Tier 4 (no match):** Tell the user these rules stay as guidance — custom-rule synthesis is a planned skill, not yet available.