with one click
code-police
// Review code for quality, simplicity, and common mistakes before declaring work complete.
// Review code for quality, simplicity, and common mistakes before declaring work complete.
Use this when writing or reviewing Haskell code. Covers error handling, type safety, idiomatic patterns, HLint compliance, Aeson usage, and testing.
Enter talk mode — conversation and research, no repo changes. ONLY invoke when the user explicitly types `/talk` or `$talk`; never auto-select from a natural-language question or design discussion.
Vira's design system — TailwindCSS conventions, color palette, typography, spacing, components, and accessibility rules. Use when working on UI components, styling, or layout in this project (Lucid HTML in Haskell sources under packages/vira).
| name | code-police |
| description | Review code for quality, simplicity, and common mistakes before declaring work complete. |
Review the current changes (scoped to the current branch/PR) against the rules below plus any additional rules from the project, then run three passes in order.
Before Pass 1, read .agency/code-police.md if it exists. Treat any rules declared there — whether inline or as a pointer to another file (See ./code-police-rules.md) — as additions to the built-in rules below. They appear as separate rows in the Pass 1 checklist with the project's chosen rule IDs.
If .agency/code-police.md is missing, proceed with only the built-in rules. The file is project-defined and free-form (Markdown, no required frontmatter).
Two similar instances are fine — don't abstract prematurely. Three is the threshold for extraction. But identical content that must stay in sync (same HTML, same version string) should be deduplicated immediately regardless of count. Versions, ports, paths — define once, reference everywhere.
Before hand-rolling a utility (string tokenizer, quoted-string parser, date helper, semver comparator, URL builder, CLI arg parser, tree walker, regex-based matcher, path normalizer, etc.), check whether a focused library solves the same problem. If one exists with a matching scope and a reasonable bundle cost, prefer it — even if it's a new dependency. Hand-rolling is only justified when the library would add capabilities you actively don't want (tagging, env expansion, i18n layers, etc. you'd have to ignore), or when the hand-roll is genuinely a handful of lines with no branching.
Rationale: "Zero deps" is an easiness judgment dressed as a simplicity judgment. Code you don't own is genuinely simpler than code you do own — it doesn't accumulate private test fixtures, it doesn't bitrot when requirements shift, and its edge cases are someone else's problem to fix. Hand-rolled utilities routinely grow past the library they displaced as new edge cases surface. A small focused library with a single exported function is the same complexity to your reader as a one-line utility import, and less complexity than a 40-line loop with state variables.
How to apply: When you're about to write a loop with nested state-machine variables, a tokenizer, a parser, a semver comparator, a date math helper, a quoted-string handler, or a path normalizer — stop and search for the focused library first. Only fall back to hand-roll after seeing a concrete library and judging that its scope genuinely exceeds what you need.
Anti-patterns in this rule's application:
Use discriminated unions, not booleans or stringly-typed fields. If two fields can't both be undefined at the same time, model that in the type.
Aggressively remove unused code. No commented-out blocks, no "just in case" leftovers.
Never silently swallow errors. Empty catch {} blocks, bare catch: pass, and || true hide failures. At minimum, log the error. If the catch is intentional (best-effort operation), add a comment explaining why the error is safe to ignore.
Rationale: Silent swallowing masks bugs — failures disappear without a trace, making debugging impossible.
Collections, buffers, and listeners that grow with usage must have a bound or a cleanup path. Common violations:
fs.watch, resize, scroll, mousemove, WebSocket onmessage, or any event that can fire many times per second. Each invocation that does non-trivial work (I/O, parsing, DOM mutation, allocation) needs a debounce or throttle. A bare handler is only acceptable if the work is O(1) and allocation-free.Rationale: LLM-generated code defaults to the simplest correct implementation, which is often O(n) in session lifetime. These patterns silently degrade performance over hours/days and surface as "the app got slow" with no obvious cause. The fix is almost always straightforward (cap, debounce, stream, share) but must be applied at write time — it's rarely caught in review because the code is functionally correct.
Add comments where the why isn't obvious from the code. Don't comment the what. Also comment where the what isn't obvious — non-obvious guards, CSS workarounds, platform-specific behavior. Non-obvious workarounds (temp files, wrapper scripts, env var shims) must have a comment explaining why they exist.
Present a table with every rule above:
| Rule ID | Violation found? | What was identified | Action taken |
|---|
If no violation was found for a rule, mark it as "No" with a brief note on what was checked. Every rule must appear in the table — no skipping.
Audit the changes for correctness and rigor. This is not a style review — it's a logic review. Find places where the code lies to itself.
Flag:
try/catch: pass, empty catch {}, || true, errors caught but not propagated, Result/Option silently defaulted.For each finding: file, line, one-line risk, concrete fix. If no issues, say so — don't invent problems.
Principles:
Anti-patterns in YOUR review (strictly banned):
Skip on tiny diffs. Run git diff origin/HEAD...HEAD --shortstat (or the appropriate base-branch ref). If the diff is under 10 lines, skip this pass and report Elegance | 0 | Skipped (tiny diff) in the summary. The elegance pass's three-lens fan-out has overhead that's disproportionate to a few-line change; rules and fact-check still run. If the diff exceeds the threshold, proceed below.
Review the changes for elegance and simplicity.
If running under Claude Code (the Skill tool is available): invoke the bundled /simplify skill via the Skill tool. It runs three parallel lenses — reuse, quality, efficiency — over the current diff and applies fixes. Prefer this path.
On other harnesses (no /simplify available), run the inline loop instead. For each iteration (run 3 iterations):
Principles:
After all three passes, present a combined summary:
| Pass | Issues found | Details |
|---|---|---|
| Rules | N | Brief summary or "Clean" |
| Fact-check | N | Brief summary or "Clean" |
| Elegance | N | Brief summary or "Clean" |
If ANY pass found issues, clearly state: "Violations or issues found" so the workflow can route to a fix step.
If all passes are clean, state: "All clear".
Simple means not interleaved. Each module does one thing. Data flows through arguments and return values, not shared mutable state or indirection.
# above the recipe name).Group code by rate of change, not by technical layer. Things that change together live together; things that change independently get separate modules.