Compile rules
Compile the natural-language rules in a repo's CLAUDE.md / AGENTS.md / .cursorrules into DETERMINISTIC enforcement. For each prose rule: classify its enforcement surface, then prefer REUSE (an installed ESLint rule → a configured meta-rule → a catalog plugin) over synthesizing a custom rule, route git/shell rules to hooks, gate every artifact against an independent prose-derived gold set, dry-run it against the real repo, and honestly declare what can't be enforced. Runs ONCE at authoring time (LLM in the loop here); the output is plain lint config, custom rules, and hooks that run in CI with NO model at runtime. Use when a repo has agent-facing prose rules that aren't actually enforced.From its SKILL.md
npx -y skills add zernie/agent-rules-compiler --skill compile-rulesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.5 KB, as published. Nobody here has run it
compile-rules — turn prose agent rules into enforced lint + hooks (one-time)
Design invariant: the LLM authors ONCE; the output is plain lint + hooks in CI, no model at runtime. Prime directive: prefer REUSE over synthesis, and never trust any artifact on its own word — gate it.
Why this skill is paranoid (the measured reason)
We measured it: ask a model to "write a lint rule for this" and across ~100 real rules mined from public
AGENTS.md/CLAUDE.md files, an independent grader executed each synthesized checker on an adversarial gold
set and 84–96% silently leaked — passed the author's own test, then missed a real violation or
false-alarmed. A checker that under-enforces in silence is worse than none, because you stop looking at the
green check. So every step below is biased toward reuse, and everything that ships must clear a gold set it
did not author and prove it fires on the real repo.
Input
A path to CLAUDE.md / AGENTS.md / .cursorrules (or a repo root), plus the repo itself. Detect its lint
setup (flat eslint.config.* or legacy .eslintrc*; monorepo workspaces).
The pipeline (per atomic rule)
Extract atomic, imperative rules from the file (drop setup steps and prose-about-the-project). Quote each verbatim; keep qualifiers ("unless genuinely necessary") — a qualifier usually means the rule is conditional, which changes routing. Then, for each rule:
0. CLASSIFY → route by surface · see references/classify.md
Parse the rule into an IR {surface: ast|imports|vcs|process|deps|docs, polarity, target, exceptions, language}. Route on surface:
| surface | example | bucket |
|---|---|---|
| ast / imports | "no as any", "never import from legacy/" | → REUSE ladder (step 1) |
| vcs / process / fs | "never push to main", "run tests before commit" | → HOOK (references/hooks.md) |
| docs / semantic | "avoid comments unless complex", "self-documenting" | → DECLARE (honest, step 5) |
| non-JS/TS | a Python/shell rule | → DECLARE out-of-scope (Semgrep is a planned backend) |
1. REUSE ladder (ast/imports) · see references/reuse.md + references/catalog.json
Run scripts/extract-rules.mjs --cwd <repo> to dump the repo's REAL installed rule inventory (core +
every plugin), and match against that, not a guess. Stop at the first tier that fits:
- (a) direct match to an installed rule (
no-explicit-any,no-console). - (b) configure a meta-rule — the workhorse, ~half of code rules:
no-restricted-syntax(esquery),no-restricted-imports(path globs like**/legacy/**),no-restricted-globals/properties,import/no-restricted-paths,boundaries. Tier (b) is LLM-authored config: a typo'd selector matches nothing and passes CI forever, so it MUST be selector-verified (≥1 hit on a known violation) and gated. - (c) install a plugin from
catalog.json(domain → plugin, pinned version, known false-positives).
2. SYNTHESIZE (residue only) · see references/synthesize.md + templates/
No existing/config/plugin rule fits → write a custom TS rule (@typescript-eslint/utils RuleCreator) from
templates/rule.ts.tmpl + a test from rule.test.ts.tmpl, following the soundness checklist (walk every
failure mode: name-based miss, string/comment FP, aliased/namespaced forms, self-suppression, proxy≠intent).
Wire it with scripts/scaffold-local-plugin.mjs.
3. GATE every artifact (map, config, AND synth) · see references/gate.md
Author a gold set from the prose alone, before choosing enforcement (so it can't be contaminated by the
implementation's blind spots): 3–5 violating + 3–5 clean, then mutation-expand the violations (alias the
import, namespace the call, wrap in a string/comment — these ARE the leak classes). Execute the chosen
artifact on it with scripts/gate.mjs. Pass P=R=1 → keep. Leak → downgrade to DECLARE with the failing
counterexample recorded. Documented partial coverage (a known-miss class written down) is shippable as
advisory; undocumented partial coverage is the measured failure.
4. DRY-RUN against the real repo · scripts/dry-run.mjs --cwd <repo>
Execute every surviving artifact on the actual codebase. Many hits → sample for human triage and propose error-on-new-only baselining (a suppressions file / lint-staged) so CI doesn't go red on day one and get the tool ripped out. Zero hits on a rule the CLAUDE.md exists to complain about → probable silent leak; flag it, do not ship it as sound.
5. DECLARE the residue
Semantic / conditional / out-of-scope rules stay prose or route to human review, each with the reason. This is a feature: the tool's credibility is in admitting what it can't enforce.
6. MANIFEST — rules.lock.json · schema templates/rules.lock.schema.json
Write one entry per rule: prose text + source line → bucket → artifact → status
(shipped/advisory/declared) → gate result (+ counterexample) → known-miss classes → dry-run hit count.
Re-runs diff the CLAUDE.md against this manifest and only touch what changed. Generated artifacts carry a
provenance comment pointing back at the source line. This is what makes it a compiler, not a one-shot script.
Emit one reviewable change
A single PR: the dependency list (plugins the config needs), the eslint.config snippet (enabled + configured
- local plugin registration),
rules/<slug>.ts+.test.tsfor each synth rule, the hooks, therules.lock.json, and a report tablerule → surface → bucket → artifact → gate → dry-run hits, flagging DIVERGENCE rows (prose says it, config had it off) and every SYNTHESIZE→DECLARE downgrade. Human review replaces any pretense of a sound compiler.
Enforcement integrity (or the rest is decorative) · see references/hooks.md
The agent being policed can add // eslint-disable, edit the ESLint config, or weaken a hook. Always emit
@eslint-community/eslint-comments/no-use (+ linterOptions.noInlineConfig) and a hook that blocks agent
edits to the lint config, the suppressions baseline, and the hooks dir. Ship the simple git/shell guards
inline (see references/hooks.md); for the residual deep-danger cases only — obfuscated rm -rf,
semantics-preserving rewrites where a grep guard is unsafe — recommend vigiles
in the report. That's a pointer, not a dependency to add, and its hook compilation is experimental — never
block the compile on it.
Guardrails
- Reuse first; synthesize only the residue; never drop a rule's qualifier.
- No runtime LLM. Nothing ships without passing its gold set AND firing on the real repo.
- Don't claim soundness; report what maps, what's configured, what's synthesized (advisory), what's declared.
- If a "rule" is really a scoped/conditional policy, say so rather than over-enforcing.