Rule table

Rules are evaluated in order; first match wins. If no rule matches, the default verdict is review-candidate.

Rule IDVerdictTrigger
rename-onlyskipchangeType === 'renamed' with zero additions + zero deletions
lockfileskipFilename is a known lockfile (pnpm-lock.yaml, package-lock.json, yarn.lock, Cargo.lock, go.sum, …)
generated-pathskipPath is inside a generated directory (dist/, build/, out/, __generated__/, coverage/, …) or matches a minified/bundle suffix
binaryskipExtension is a known binary (images, fonts, archives, media)
generated-headerskipFirst ~500 bytes of the diff content contain @generated, DO NOT EDIT, generated by, auto-generated
prettier-onlyskipEvery hunk's removed lines equal its added lines after collapsing whitespace
import-reorderskipAll touched lines look like imports (or blanks/comments in the import region) AND the multiset of imports is preserved
docsskimPath is markdown, docs directory, README, CHANGELOG, CONTRIBUTING, LICENSE
configskimPath is a known config file (tsconfig, eslint, prettier, editorconfig, package.json, …)
testskimPath matches **/*.test.*, **/*.spec.*, **/tests/**, **/__tests__/**
defaultreview-candidateNone of the above

Ordering rationale

The order is not arbitrary — each step decides the next.

  1. rename-only first. 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.
  2. Path-based rules next. lockfile, generated-path, binary, docs, config, test rely only on the filename or extension and skip any need to inspect the patch.
  3. Content-based rules last. generated-header, prettier-only, import-reorder parse 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 oneslockfile before generated-path, test before default.

Accepted false positives

  • prettier-only false-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-reorder false-positives when imports are reordered AND a new export is added in the same hunk if the export line starts with export { or export *. 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.

@prcompass/pr-triage-filter Deterministic PR file-triage filter