Pr To Lint Rule

zernie/vigiles/.claude/skills/pr-to-lint-rule

by zernie1d562c69566fNo license15 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated today

Synthesize a recurring code-review rule into a custom lint rule — gated by an independent soundness test that abstains rather than ship a checker it can't prove sound

AI-generated overview

Turns a recurring prose code-review rule into a custom lint rule, gated by an independent soundness test that abstains if it leaks.

What it does
This skill takes a prose rule that no off-the-shelf linter rule covers and walks through a synthesis pipeline: detect the language and linter, check whether an existing rule already fits, clarify the rule's intent against the codebase, then generate the custom rule plus an independent intent-encoding test with adversarial cases. A trust gate runs the checker against that test and requires precision and recall of 1.0; a passing rule is kept and wired into the instruction file, while a leaking rule is abstained and handed back as prose. It produces the rule, the test, gate evidence, and integration instructions, and asks before writing any files.
When to use it
Use it when a code-review convention keeps recurring but no existing linter rule matches, such as banning direct imports from a library or requiring a retry wrapper around outbound calls. It is also the hand-off target for a custom rule line from a vigiles audit rule map. It is invoked explicitly and does not run on its own.
Requirements
Runs in a repository with a detectable primary language and linter (ESLint, Ruff, Pylint, RuboCop, Clippy, Stylelint, or a Go analyzer); it may ask the user when the toolchain is ambiguous. It references external linter reference docs and a rule-enforcer gate with gold cases and an npm demo, and it ships no scripts of its own. Writing files or editing the spec or CLAUDE.md happens only after user confirmation.

Turn a prose rule that no off-the-shelf linter rule matches into a custom lint rule — the opt-in synthesis step. This is the hand-off target of the custom rule (⚙) lane in a vigiles audit rule map: audit maps a rule there when it looks enforceable but nothing off-the-shelf fits. You invoke this skill explicitly; nothing here runs on its own, and installing vigiles never starts synthesizing anything.

The point of this skill is not "write a checker." A model can write a plausible-looking checker in seconds. The point is the discipline that makes the result trustworthy: synthesize the rule and an independent test that encodes the rule's real intent, run the checker against adversarial cases it didn't author, and abstain — hand it back as prose — if it leaks. A checker that matches a rule's surface but not its intent gives false confidence (the measured failure mode: most naively-synthesized checkers silently leak). Shipping a green check nobody should trust is worse than shipping nothing.

Arguments

$ARGUMENTS — a prose rule to enforce. Either a custom rule (⚙) line copied from a vigiles audit report, or free text. Examples:

  • "we keep telling people not to import directly from antd — use our design-system barrel"
  • "people forget to use our custom logger instead of console.log"
  • "wrap outbound API calls in our withRetry helper"
  • "route handlers must go through the withAuth wrapper"

The pipeline

prose rule  → detect language + linter (ask if ambiguous)  → REUSE first: does an off-the-shelf rule already fit? → use it, STOP (no synthesis)  → clarify intent + read the codebase (what exactly is the violation? the fix?)  → SYNTHESIZE: the rule  +  an INDEPENDENT intent-encoding test (adversarial cases)  → TRUST GATE: run the checker on that test; precision AND recall must be 1.0        pass → KEPT     (safe to enforce; wire it in)        leak → ABSTAIN  (hand back as prose; never ship a checker it can't prove sound)

Instructions

Step 1 — Detect the language and toolchain

Read the repo to find the primary language, the linter in use (ESLint, Ruff, Pylint, Clippy, RuboCop, Stylelint…), the test framework, and any existing custom rules (to match conventions). If you cannot confidently pick one — polyglot repo, no linter config, several candidates — ask the user before generating anything.

Step 2 — REUSE before you synthesize (ADOPT > REUSE > SYNTHESIZE)

Synthesis is the last resort, not the first. Before writing any rule:

  1. Check existing plugins / built-in rules. Read the linter reference doc below (the plugin table + no-restricted-* families) — most one-off prohibitions are a config line, not custom code.
  2. Try a built-in construct rule. ESLint no-restricted-syntax / no-restricted-imports, Ruff/Pylint selects, RuboCop cops, etc.
  3. Only synthesize when the rule needs AST analysis, a fix, or options that no existing rule provides.

If an off-the-shelf rule fits, use it and stop — enable it (one config line) and jump to Step 6. That is the strengthen outcome, and it's the better one; don't synthesize a rule when a maintained one exists.

LanguageLinterReference doc
JavaScript/TypeScriptESLint../../../skills/linter-docs/eslint.md
PythonRuff../../../skills/linter-docs/ruff.md
PythonPylint../../../skills/linter-docs/pylint.md
RubyRuboCop../../../skills/linter-docs/rubocop.md
RustClippy../../../skills/linter-docs/clippy.md
CSSStylelint../../../skills/linter-docs/stylelint.md

For Go, generate a golang.org/x/tools/go/analysis analyzer with analysistest tests. For any other language, use the most idiomatic linting approach with test cases and integration notes.

Step 3 — Nail the intent, then read the codebase

Synthesis fails when the checker enforces what's easy to match instead of what the rule means. Before writing anything, pin down:

  • The exact violation. "No hardcoded secrets" is not "a variable named password" — it's apiKey, token, an env-less literal too. "Wrap API calls in retry" needs the real signature of the calls and the wrapper.
  • The fix. What does the compliant form look like, concretely, in this codebase? Read the real files — the helper's import path, its call shape.
  • The clean lookalikes. What looks like a violation but isn't? (the pattern inside a string, a comment, a JSDoc @example; an aliased/namespaced form.)

If any of this is ambiguous — and for anything like "wrap API calls in retry" it usually is — ask the user rather than guessing. A rule built on a guessed intent will fail the gate anyway; asking is cheaper.

Step 4 — Synthesize the rule AND an independent intent test

Write two artifacts:

  1. The rule — prefer AST/keyword analysis over text/substring scanning (text-scan checkers false-positive on the pattern appearing in prose; a name-based checker false-negatives on synonyms). Follow the reference doc's anatomy, fix/suggest, and registration guidance.

  2. An independent test that encodes the INTENT, not the checker's shortcut. This is the trust anchor — write it to the rule's meaning, then seed it with the adversarial cases that break naive synthesis:

    • valid (must NOT flag): the pattern in a string literal, a line comment, a JSDoc/docstring @example; a camelCase identifier that merely contains the keyword (consoleLog); a compliant call that looks close.
    • invalid (MUST flag): every real form of the violation — synonyms (apiKey/token, not just password), the aliased/namespaced form (window.console.log), the wrapped-differently case.

    Do not make the test agree with a shortcut the checker took. If the rule is "no hardcoded secret," the test asserts apiKey is a violation even if your first checker only catches password — so the gate can catch the leak.

Step 5 — The trust gate: precision AND recall must be 1.0

Run the synthesized checker against the Step-4 test (ESLint RuleTester, pytest, analysistest, whatever fits the linter).

  • Pass (every invalid flagged, every valid clean → precision = recall = 1.0): the rule is KEPT — safe to enforce. Continue to Step 6.
  • Leak (any false positive or false negative): the rule is ABSTAINED. Do not wire it in. Report honestly why it leaked (e.g. "matches the pattern in a comment," "misses apiKey") and either (a) hand the rule back as prose, or (b) if you can fix the checker, do so and re-run the gate — but never ship an un-passed checker.

The reference gate lives in rule-enforcer/gate.js (two-stage: self-test + a blind adversarial gold set), the canonical adversarial cases in rule-enforcer/gold/gold.json, and a runnable end-to-end demo (cd rule-enforcer && npm run demo — watch it keep the sound rules and abstain the two leaky ones). Read them for the case shapes and the abstain discipline, then mirror it here; don't reinvent the gate.

Step 6 — Wire a KEPT rule into the instruction file

Only for a rule that passed the gate (or an off-the-shelf rule reused in Step 2).

v2 specs (repo has CLAUDE.md.spec.ts): add an enforce() line and recompile —

typescript
"<rule-id>": enforce("<linter>/<rule-name>", "<why>"),

then npx vigiles compile.

v1 (hand-written CLAUDE.md): append —

markdown
### <Rule title — imperative, concise>
**Enforced by:** `<linter>/<rule-name>`**Why:** <one sentence — the architectural reason>

Step 7 — Present the outcome

Show the user, honestly:

  • Kept rules: the generated files, the gate evidence (the adversarial cases it passed), integration steps, the spec/CLAUDE.md block, and how to verify (run the linter, watch it catch a real violation).
  • Abstained rules: which rule, why it leaked, and that it stays prose — a refused checker is a correct outcome, not a failure to hide.

Then ask before writing any files or editing the spec/CLAUDE.md. Nothing is written for the user automatically.

Source and attribution

Source:zernie/vigilesin.claude/skills/pr-to-lint-ruleat commit1d562c6

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from zernie/vigiles

Linter Docs

zernie

Deep linter reference for authoring or debugging a vigiles enforce() rule — plugin tables, AST selectors, type-aware rules, auto-fix, and edge cases for ESLint, Ruff, Pylint, RuboCop, Stylelint, and Clippy. Use when you need the exact rule name or config for a specific linter, not for running a linter. (JVM/Go linters — detekt, ktlint, Checkstyle, golangci-lint — and Cedar have no deep-dive file yet; their reference lives in docs/linter-support.md.)

Awaiting classification15updated today

Debug My Harness

zernie

Diagnoses agent harness misbehavior by reading the local .vigiles/runs.jsonl flight-recorder ledger.

AI & Agents15updated today

Review Docs

zernie

Review the README or a documentation page from several real reader points of view at once. Fans out one parallel reviewer per audience — Claude Code newcomer, power user, plugin/skill author, skeptical senior engineer, non-running decision-maker — each scoring it x/5 and naming concrete, line-level fixes. Use when asked to review, critique, grade, or "get to N/5" the README or any front-door doc, or to check how a doc reads for real users. Not for code review (use /code-review for that).

Awaiting classification15updated today

Adopt Spec

zernie

Converts an existing hand-written CLAUDE.md into a typed CLAUDE.md.spec.ts using the vigiles spec library.

AI & Agents15updated today

Enforce Rules Format

zernie

Validates that project instruction-file rules carry proper enforcement classifications and fixes missing ones.

AI & Agents15updated today

Deep Research

zernie

Use when the user asks to research a topic in depth, map a competitive/market landscape, run a multi-source investigation, or "fan out" parallel research agents — anything where many findings must be gathered and then NOT lost. Enforces durable, detail-preserving research (write full findings to disk; keep a full appendix beside the synthesis).

Awaiting classification15updated today