How it works
Your project’s conventions are written down in AGENTS.md or CLAUDE.md, and your coding agent read them once, forty tool calls ago. rulecast delivers them again at the moment they matter: when the agent is about to break one.
A rule pairs a check with a message and a pointer to the doc section it enforces. rulecast runs as an agent hook, so when the agent changes a file that breaks a rule — with its edit tools or with Bash — it gets told, in the same turn, before it moves on.
rulecast: 1 rule violated in app/services/users.py
error backend/no-httpexception app/services/users.py:2 raises HTTPException(404) in the service layer. Raise a domain exception; the API layer maps it to a response.
--- conventions/backend.md#errors ---## Errors
Services raise domain exceptions. `app/api/errors.py` maps each one to a status code.That is real output. The agent sees the finding and the paragraph of your documentation that explains it, without having to go looking.
A file the agent rewrites with sed -i or a script is checked like one it changes with Edit: rulecast compares the working tree before and after each Bash call. A change no call explains — your own editor, a formatter, a job the agent left running — is reported when the agent stops, and never blocks it.
Where it sits
Section titled “Where it sits”rulecast runs as your agent’s hooks. Each tool call the agent makes passes through it on the way out, on the way back, or both.
- Session starts
start - rulecast records the working tree, so it can tell later what changed.
- Reads a file
touch - The conventions for that file arrive once, with their doc sections.
- Edits a file
guard · edit - A protected file is refused before the write. Anything else is checked after it, and new findings come back in the same turn.
- Runs a command
shell-before · shell-after - What the command wrote is checked like an edit. A protected file it touched must be reverted.
- Tries to stop
verify - It is held back while a new error stands: once per prompt by default, or up to ten times in an unattended run, after which what's left goes into the agent's final report. Changes no tool call explains are reported and never block.
From rule to message
Section titled “From rule to message”.rulecast-config.yamlrepos: - 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"
rulecast: 1 rule violated in app/services/users.pyerror backend/no-httpexception app/services/users.py:2 raises HTTPException(404) in the service layer. Raise a domain exception; the API layer maps it to a response.--- conventions/backend.md#errors ---## ErrorsServices raise domain exceptions. `app/api/errors.py` maps each one to a status code.
Performance
Section titled “Performance”The edit hook is budgeted to finish in under 500 ms at p95 — measured from process start to exit, on a 30-rule project with warm caches, excluding llm rules. pnpm perf replays 50 edit events and checks it. The last measurement, on an Apple M3 Pro with all six detectors configured, was p50 170 ms and p95 187 ms.
The hook before every Bash call only records the working tree: it is held to 150 ms over a hook that does nothing, and adds 135–150 ms at p95 on a 20,000-file repository (microsoft/vscode), nearly all of it git finding untracked files. The hook after a Bash call that changed a file is an edit event, under the same 500 ms budget.
A new worktree’s first edit hook took p50 477 ms and p95 490 ms, against 252 ms for a warm edit in the same project (10 worktrees, report only). A new session in an existing checkout costs nothing extra (253 ms), and neither does an empty per-project detector cache (477 ms). The fixture pins no rule repos, so this says nothing about a missing one: hooks never fetch, and its rules do not run until rulecast install (see Fresh environments). The difference is macOS checking each executable the first time it runs from a new path: the project’s own command script and the node_modules/.bin linter shim each took about 200 ms on their first run and 10–60 ms after that. Run them once beforehand and the first edit costs the same as a warm one. SessionStart in a new worktree takes 174 ms. A worktree needs no preparation.
The budget is what shapes the design: one detector run per kind per event, kinds in parallel, slow tools defaulted to verify, content-addressed caches on disk, no daemon. When an edit runs long, rulecast delivers what finished and builds the rest in the background rather than making the agent wait.