Rule table
Rules are evaluated in order; first match wins. If no rule matches, the default verdict is review-candidate.
| Rule ID | Verdict | Trigger |
|---|---|---|
rename-only | skip | changeType === 'renamed' with zero additions + zero deletions |
lockfile | skip | Filename is a known lockfile (pnpm-lock.yaml, package-lock.json, yarn.lock, Cargo.lock, go.sum, …) |
generated-path | skip | Path is inside a generated directory (dist/, build/, out/, __generated__/, coverage/, …) or matches a minified/bundle suffix |
binary | skip | Extension is a known binary (images, fonts, archives, media) |
generated-header | skip | First ~500 bytes of the diff content contain @generated, DO NOT EDIT, generated by, auto-generated |
prettier-only | skip | Every hunk's removed lines equal its added lines after collapsing whitespace |
import-reorder | skip | All touched lines look like imports (or blanks/comments in the import region) AND the multiset of imports is preserved |
docs | skim | Path is markdown, docs directory, README, CHANGELOG, CONTRIBUTING, LICENSE |
config | skim | Path is a known config file (tsconfig, eslint, prettier, editorconfig, package.json, …) |
test | skim | Path matches **/*.test.*, **/*.spec.*, **/tests/**, **/__tests__/** |
default | review-candidate | None of the above |
Ordering rationale
The order is not arbitrary — each step decides the next.
rename-onlyfirst. A pure rename trumps every path-based rule (a renamed lockfile is still a rename, but more importantly: a renamed source file with zero diff is also nothing to look at). Cheap to detect; fires before content rules touch the diff.- Path-based rules next.
lockfile,generated-path,binary,docs,config,testrely only on the filename or extension and skip any need to inspect the patch. - Content-based rules last.
generated-header,prettier-only,import-reorderparse the unified diff. Parsing is the expensive step; we only do it when path lookups didn't already settle the verdict.
Within each group, more specific rules fire before more general ones — lockfile before generated-path, test before default.
Accepted false positives
prettier-onlyfalse-positives when a change is semantically whitespace-sensitive (e.g., a space inside a string literal). Tier 2 of PR Compass catches this; here it's a deliberate trade.import-reorderfalse-positives when imports are reordered AND a new export is added in the same hunk if the export line starts withexport {orexport *. We treat those as import-region lines.
The cost of this trade is correctly understood: this filter aims for ~85–90 % accuracy against hand-reviewed fixtures, and a small false-positive rate on skip is acceptable when the alternative is dragging every reviewer's eye through 15-line lockfile diffs and prettier sweeps.
Stable rule IDs
The ruleId field on each FileVerdict is part of the public contract. Code like:
if (verdict.ruleId === 'lockfile') {
// …
} is safe across minor versions. New rules may be added; existing rule IDs will not be renamed without a major bump.
The reason field is the opposite — human-readable, may be reworded between versions. Do not pattern-match on it.
Default verdict
If no rule fires, the default is:
{
verdict: 'review-candidate',
ruleId: 'default',
reason: 'Production source code outside tests, docs, and config paths.'
} That's the conservative choice on purpose — unfamiliar = review-candidate, never skip.