Skip to content

Writing rules

Rules live in .rulecast-config.yaml:

repos:
- repo: local
rules:
- id: backend/no-httpexception
name: Services raise domain exceptions, not HTTPException
files: '^app/services/.*\.py$'
detect:
regex:
pattern: 'raise\s+HTTPException\((?<args>[^)]*)\)'
message: "{{file}}:{{line}} raises HTTPException({{args}}) in the service layer. Raise a domain exception; the API layer maps it to a response."
context:
- "@conventions/backend.md#errors"
  • files selects what the rule applies to; detect names one detector and its config.
  • message is what the agent reads. Named capture groups become template variables.
  • context points at the doc section that explains the rule — delivered alongside the finding.

A rule with context and no detect delivers guidance the first time an agent touches a matching file, with nothing to catch. That is how you carry over a convention no pattern can express.

The full format is in agents/reference/rule-format.md.

Some rules are not advice. refuse_write: true refuses the edit before it happens, and the agent gets your message and the doc section in place of the tool’s result:

- id: codegen/no-edit-client
files: '^src/client/'
detect:
path: {}
refuse_write: true
message: "{{file}} is generated from openapi.yaml. Edit the schema and run `pnpm codegen`."

A refusal only ever rests on the text the agent is writing, never on a reconstruction rulecast is unsure of, and it happens once per file per session — so a rule can stop a mistake without trapping the agent. Generated code, vendored directories, files that must not change. Everything else is better reported after the write, which is what the other rules do.

That covers the edit tools. A write through Bash cannot be refused before it runs, because nothing knows what a command will write, so rulecast catches it right after: the agent is told the file is protected and how to revert it, it cannot stop until it has, and you are told which file it changed.

  • A whole rule: enabled: false on the rule. Under a rule repo’s entry, - id: <rule> with enabled: false switches a catalog rule off and keeps the line.

  • One finding: a comment on the matched line or the line above it, in the file’s own comment syntax. The reason is required; an ignore without one suppresses nothing.

    # rulecast-ignore: python/no-httpexception-in-services legacy endpoint, removed in #412
    raise HTTPException(404)

rulecast run --all-files --summary counts the ignores, and an ignore or a config change made during an agent session is reported to you when the agent stops: the agent cannot quietly edit its way past a rule.