Skip to content

nkwib/pr-triage

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@prcompass/pr-triage-filter

A deterministic PR file-triage filter. Zero runtime dependencies. Written in TypeScript, runs on Node 22+.

Given the files in a pull request, classifies each as skip | skim | review-candidate so a reviewer's (or a downstream LLM's) attention lands where it matters. Uses path patterns, git metadata, and diff-level heuristics. No AST parsing. No network. No filesystem. Pure function.

Extracted from PR Compass and published standalone so any review pipeline can use it.

Why zero dependencies

  • Every transitive dep is a vulnerability surface we'd have to maintain.
  • Ecosystem adoption is easier if adding this package doesn't add 40 MB to your node_modules.
  • If the filter needs a library to work, it's probably over-engineered.

Install

npm install @prcompass/pr-triage-filter
# or pnpm / yarn / bun

Usage

import { classifyPrFiles } from "@prcompass/pr-triage-filter";

const result = classifyPrFiles({
  files: [
    {
      path: "pnpm-lock.yaml",
      changeType: "modified",
      additions: 120,
      deletions: 45,
    },
    {
      path: "src/pricing.ts",
      changeType: "modified",
      additions: 30,
      deletions: 5,
      patch: "@@ -10,3 +10,4 @@\n ...",
    },
  ],
});

for (const v of result.verdicts) {
  console.log(v.path, v.verdict, `(${v.ruleId})`, v.reason);
}
// pnpm-lock.yaml skip (lockfile) Package lockfile — content is auto-generated
// src/pricing.ts review-candidate (default) Source change in non-test, non-config code

Rules

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

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.

Target: ~85–90% accuracy against hand-reviewed fixtures. The goal is to efficiently remove 50–70% of files from the review budget, not to be perfect.

API

classifyPrFiles(input: ClassifyInput): ClassifyResult

Pure. No I/O. Deterministic.

Types

type Verdict = "skip" | "skim" | "review-candidate";

interface FileInput {
  readonly path: string;
  readonly previousPath?: string;
  readonly changeType: "added" | "modified" | "deleted" | "renamed";
  readonly additions: number;
  readonly deletions: number;
  readonly patch?: string;
}

interface ClassifyInput {
  readonly files: readonly FileInput[];
}

interface FileVerdict {
  readonly path: string;
  readonly verdict: Verdict;
  readonly reason: string;
  readonly ruleId: string;
}

interface ClassifyResult {
  readonly verdicts: readonly FileVerdict[];
}

Internal modules (rule implementations, diff parser) are not re-exported. Consumers import only from the top-level.

Performance

Processes a 100-file PR in well under 500ms on a modern laptop. Tested in CI; see tests/performance.test.ts.

License

MIT. See LICENSE.

About

PR Compass file-triage filter. Zero deps, deterministic, pure functions.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors