A GitHub Action that comments the estimated review burden of every pull request, in minutes of focused reviewer attention.
An example comment for a 133-effective-line PR (note, the message could drift slightly):
π’ 133 effective lines β about 16β40 min (down from 24β60 min) of focused review (based on 200β500 lines/hour). This is within the range where reviewers find the most issues per line, and small changes usually receive feedback the fastest.
142 lines across 1 generated/lockfile file were excluded from the price.
Why these numbers?
These minutes are what careful defect-finding costs at 200β500 lines/hour β the rate review studies report, not how long a skim takes. "Effective lines" already exclude generated files and lockfiles. Treat the rates and the 200/400 thresholds as guardrails, not laws.
Defect detection per line falls as a PR grows, and published review studies put a size ceiling around 400 changed lines. This action makes that cost visible at the moment a PR is created, which nudges authors toward smaller PRs.
name: π·οΈ ProQuo
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read # read .proquo.yml config from a checkout (built-in defaults apply if absent)
pull-requests: write # list the PR's changed files and post/update the price comment
issues: write # create and apply the proquo: small/medium/large size labels
jobs:
price:
name: Review Price Tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bolshakov/proquo@v2.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}Use pull_request_target, not pull_request: GitHub forces the automatic GITHUB_TOKEN to
read-only for pull_request-triggered runs on pull requests from forks, so the comment/label
steps above would fail there. pull_request_target runs the workflow file as committed on the
base branch β a fork PR can never alter its steps β and ProQuo itself never checks out or
executes anything from the PR's branch; it only reads diff metadata (file names, +/- counts,
patches) through the GitHub API, so the elevated token this trigger provides is never handed to
fork-authored code. Leave the actions/checkout step's ref unset so it checks out the base
branch (the default for pull_request_target) rather than the fork's.
Every run also logs a full calculation breakdown β per-file exclusion reasons, weights and whether they
came from .proquo.yml or a built-in default, and comment down-weighting β under a collapsed
"proquo: calculation breakdown" group in the workflow run's log. It doesn't appear in the PR comment
itself; expand the group in the Actions log when a result needs double-checking.
Every run also applies a size label to the PR β proquo: small, proquo: medium, or proquo: large,
matching the green/yellow/red tier β so PRs can be filtered or scanned by cost straight from the PR
list, without opening each one. Exactly one of these labels is kept on a PR at a time; it's removed and
replaced when the tier changes on a later push. The three labels are created automatically in the repo
the first time they're needed. Labeling requires the issues: write permission shown above β if it's
missing, ProQuo logs a warning in the Actions log and still posts the price comment normally.
Price a diff before you open the PR β a pre-push self-check:
npx proquo main...HEADOr install it once with npm install --global proquo and run proquo main...HEAD. proquo --version prints the
release it came from. It always matches the GitHub Action release with the same number. proquo --help lists the
options.
With no range argument it prices the working-tree diff. The CLI and the GitHub Action share the same pricing engine (@proquo/core), so a diff prices identically whether you check it locally or in CI.
Add --explain to see, per file, whether it was excluded (and by which pattern), what weight was applied
and whether it came from .proquo.yml or a built-in default, and how much comment down-weighting changed
its contribution:
proquo --explain main...HEAD| Code | Meaning |
|---|---|
0 |
The diff was priced, and it is below failOn, or failOn is not set. |
1 |
The diff reached the failOn tier. The last line of the report names the tier. |
2 |
ProQuo could not run, such as on an invalid .proquo.yml or a bad git range. One proquo: line on stderr says why. |
So with failOn in .proquo.yml, the CLI works as a git hook. This .git/hooks/pre-push stops a push that reached
failOn, but lets the push through when ProQuo itself cannot run:
#!/bin/sh
proquo origin/main...HEAD
[ $? -ne 1 ]Drop the last line to stop the push on code 2 as well.
- Effective size = added + deleted lines, excluding lockfiles, vendored/build/generated paths, and minified artifacts. Pure renames count as zero.
- The price is a range: lower bound = effective lines / 500 x 60, upper bound = effective lines / 200 x 60, floored at 5 minutes. These reflect published review inspection rates of 200-500 effective lines/hour β the time a defect-finding review takes, not a guess at how fast a skim would go.
- PRs fall into three zones. Green (up to 200 effective lines) is the size band with the best per-line defect detection. Yellow (201-400) is within the ceiling review studies recommend, and also flags when its upper bound passes a 60-minute focused-review session. Red (above 400) is past that ceiling.
- Red-zone PRs get a split suggestion: breaking the PR into β€200-line pieces restores per-line detection quality.
- PRs that touch an outlier number of files also get an informational note about the added review context β this never changes the minutes range or the tier.
- Comment-only lines within a change are also down-weighted, for the same reason test files are β
reviewed, but faster per line than core logic β configurable in
.proquo.yml.
Drop a .proquo.yml in the repo root to tune what counts. Both the Action and the CLI read the same file.
exclude:
- "**/*.generated.ts"
- "**/e2e/**"
weights:
- pattern: "**/e2e/**"
weight: 0.3
commentWeight: 0.3
failOn: redexclude: extra glob patterns added to the built-in defaults (lockfiles,node_modules,vendor,dist/build, generated/snapshot files, minified assets). You cannot remove a built-in exclusion, only add to it.weights: glob patterns mapped to a per-line weight. Matched in order, with your patterns checked before the built-ins, so a rule here overrides a default for the same file. Defaults already down-weight test files (*.test.*,*.spec.*,test/,tests/,spec/,__tests__/) at 0.5 β tests are still reviewed, just faster per line than core logic. Any file not matched by a pattern keeps a weight of 1.commentWeight: the weight applied to comment-only lines within a changed file, on top of that file's own weight (so a comment inside an already down-weighted test file counts even less). Defaults to0.3. Only applied when line-level diff content is available, the file's language is recognized, and the patch data is complete; otherwise the file's ordinary per-file weight is used, unchanged.failOn:redoryellow. When a pull request reaches that tier or above, the Action fails its check after it posts the comment and the label, and the CLI exits with status 1.yellowfails on yellow and red. Leave it out and ProQuo never fails. The CLI exits with status 2 when ProQuo itself cannot run, such as on an invalid.proquo.ymlor a bad git range, so a hook can tell that apart from a diff that reachedfailOn.
Published research backs why ProQuo prices by size and rate at all, not just the general instinct that smaller PRs are easier to review.
- Cohen, Teleki & Brown, "Code Review at Cisco Systems," in Best Kept Secrets of Peer Code Review (SmartBear Software, 2006) β a vendor-published field study of 2,500 reviews across 3.2 million lines of code at Cisco, not a peer-reviewed paper. It found that defect detection falls once a review grows past a certain size, that reviewing too fast lowers the fraction of defects caught, and that a review session has a point past which attention degrades.
- The older, separate tradition of formal Fagan-style code inspections (e.g. Basili & Pericone; Russell, as summarized in McConnell's Code Complete) independently established that inspection rate strongly affects defects found per line β a second, earlier line of evidence pointing the same direction as the Cisco study.
ProQuo's specific size band, rate range, and session length are tuned from this literature, not copied from it β the exact numbers live in the code, not here, so this section can't drift out of sync with it.
- Bosu, Greiler & Bird, "Characteristics of Useful Code Reviews: An Empirical Study at Microsoft" (MSR 2015) β found that the proportion of useful review comments declines as a change touches more files, a direction-only finding with no reported magnitude or functional form, from a single company's internal tooling.
- Abdelsalam, Peitek, Bergum & Apel, "The Effect of Comments on Program Comprehension: An Eye-Tracking Study"
(2025, eye-tracking study on how developers visually attend to comments vs. code) and
Brysbaert, "How Many Words Do We Read per Minute?" (2019, reading-rate meta-analysis) β neither
study measures comment down-weighting during code review directly. Combined, they bound a
derived, triangulated estimate (roughly 0.05-0.29 from reading-rate ratios, up to ~0.5 from the
eye-tracking attention ratio) that ProQuo's
commentWeightdefault of0.3sits inside β a reasoned estimate, not a measured one. - Sadowski et al., "Modern Code Review: A Case Study at Google" (ICSE-SEIP 2018) β found that the large majority of code changes touch only a handful of files, a distribution later independently confirmed on ordinary GitHub pull requests, which is what makes a change touching many files a genuine outlier rather than typical.
Together these say a many-file change is a genuine outlier, and outliers are harder to review β but neither study hands us a formula, so ProQuo flags file spread as context for the reviewer rather than pricing it. The exact threshold lives in the code, not here.
npm install
npm test
npm run build # regenerates dist/, commit it