Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pr-queue

A review queue that knows which pull requests are actually yours.

GitHub gives you one list sorted by nothing useful. pr-queue pulls every pull request waiting on you, judges each one against rules you wrote, and serves the result on localhost where you can approve, comment, and flag.

The judgement happens in the pr-triage Claude Code skill: it fetches the reviews assigned to you, then applies your profile and your rules on top.

Node 20, gh, and Claude Code. No dependencies, no build step.


Setup

gh auth login          # if you have not already
node setup.js          # discovers your login, org, and teams

Then write the two files that make the queue yours:

  • reference/editorial-rules.md — everything you care about, in plain sentences. Triage refuses to run against the untouched template.
  • reference/profile.md — what you know deeply, what you do not, and the voice your comments are written in.

Using it

In Claude Code:

triage the PR queue

The skill refreshes the queue from GitHub, reads your rules, and writes a verdict per pull request: which bucket, what to do, one sentence on where the risk sits, and comment copy when something is missing.

Then read it:

node server.js         # http://127.0.0.1:8788

Tabs are your buckets. The repository picker and Changed since last refresh narrow further, and all three compose. Each row shows the score, chips for the mechanical signals, an effort estimate, and the triage reasoning.

Per row: Approve (gh pr review --approve), Comment (pre-filled with the drafted copy, shows you the body before posting), Flag (feeds the learning loop). Every write to GitHub takes a confirmation.

Refresh re-fetches facts and reports what changed. It does not re-judge — verdicts whose commit moved are dropped, and triage is how they come back. So re-run triage after editing your rules or your buckets.


Your rules

editorial-rules.md is the file that matters. Each rule gets an e-NNN id so triage can cite it and a bad call is traceable to the sentence that caused it:

### e-001 · API and contract changes
Anything that changes, adds, or proposes an API — a REST payload, a GraphQL schema,
a protobuf, an event shape, or a public client method. I own these contracts and a
mistake here is expensive to unwind. Applies even when the diff is tiny.

Escalations, things to bury, standards like "I want screenshots on anything visual", and the epics that matter all live there as prose. Say when a rule applies as well as what to conclude — a rule that fires on everything is worse than no rule. Do not write point values; triage decides the weight.

No regex, no keyword lists. Write "anything touching the provider inventory layer" and the skill decides what satisfies it. Every point it awards has to trace back to one of your rules — an invented preference gets dropped.

Path What it is
reference/editorial-rules.md Everything you care about
reference/profile.md What you know, and your review voice
reference/buckets.json Your buckets and what fills them
reference/learned-rules.md What your flags have taught the queue
reference/config.json Identity and bot accounts, written by setup

The Settings page edits the first four in place. All of them are gitignored, so each person's queue stays their own.


Buckets

Buckets are yours, not built in. A bucket is filled by a when condition on the signals, by a prose rule the skill applies, or by both.

Order matters: first match wins, top to bottom. A bucket showing zero almost always means one above it matched first.

{
  "id": "kotlin-conversions",
  "label": "Kotlin conversions",
  "hint": "Java to Kotlin work. You know this well and it goes wrong quietly.",
  "tone": "review",              // review | warn | safe | muted — colour only
  "suggestedAction": "comment",  // approve | comment | read
  "when": {
    "all": [
      { "field": "files", "endsWith": ".kt" },
      { "field": "title", "matches": "kotlin|convert|migrat" }
    ]
  }
}

when is "always", one condition, or an all / any / not group. A condition is a field plus one of is, eq, neq, gt, gte, lt, lte, in, includes (array contains), endsWith (case-insensitive), matches (regex).

Fields: ageDays, staleDays, humanReviewCount, approvalCount, myReviewState, myReviewStale, ciState, mergeable, sizeLabel, sizeLines, fileCount, filesTruncated, isDraft, isDependencyBump, hasImageOrVideo, hasLinkedIssue, descriptionChars, descriptionHeadingCount, sourceKind, familiarity.score, repo, repoName, author, title, labels, files, score.

Some buckets cannot be a condition without lying. No file extension tells you whether a change alters what a user sees. Give those a rule instead:

{
  "id": "needs-screenshots",
  "label": "Needs screenshots",
  "rule": "The change alters what a user sees and there is no image or video on the pull request. Judge it from the files and the description, not the extension: a rename or a test-only change with no visual result does not belong here."
}

A prose-only bucket reads zero until triage runs — that is the point of it, not a broken condition. Giving a bucket both makes the condition its default and the rule triage's licence to move things out.

Saving validates: bad JSON, unknown comparisons, duplicate ids, a bucket with neither a when nor a rule, and a missing catch-all are all refused with a message, and the file on disk is left alone.


How the ranking works

Two scores add up. Mechanical answers how ready and how neglected is this — facts, identical for every reviewer, computed on every fetch:

Signal Effect
Requested directly / re-review / via a team +20 / +15 / +5
Nobody has reviewed it, or exactly one has +20 / +5
Two or more reviews, or two approvals −10 / −25
Age, up to three weeks +0…+31
Untouched for a fortnight −10
Author pushed since your review +25
Your change request still stands −40
Conflicts, failing CI, pending CI −15 / −15 / −5
Draft, or you approved and nothing moved −1000 (a floor)

Editorial answers why is this one yours — taste, from your rules, clamped to [−25, +50]. The dividing line is whether two reviewers would agree: "nobody has reviewed this in six weeks" is a fact; "this touches an API" is a judgement, so it is not in the JavaScript at all.

Size, familiarity, a thin description, and whether a bump is worth your time are measured but not scored — they say which pull request you would rather read, not which one is neglected. They reach the ranking through your rules.

Without triage you still get a useful queue: longest-waiting and least-reviewed first.


Learning from flags

The pr-queue-learn skill turns flags into durable rules. A flag stores your note plus the signals at that moment and the rules that were applied — which is what lets a rule that keeps being wrong get retired instead of quietly reapplied.

Flag good calls too. A queue that only hears about failures gets steadily more anxious.


Things that will bite you

  • Editing rules changes nothing until you re-run triage. The prose is read at triage time, not at page load.
  • A new bucket reading zero is an ordering problem, or a prose bucket waiting for triage. Check for a when before debugging the condition.
  • Renaming a bucket id orphans existing verdicts. Re-run triage.
  • A regex in JSON needs doubled backslashes, and a wrongly escaped pattern is still valid regex — it just silently matches nothing. Prefer endsWith for extensions.
  • Bot logins differ by API: GraphQL says dependabot, REST says dependabot[bot]. config.json registers both; a missed bot inflates the review count and demotes real work.
  • The refresh log is state/refresh.log.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages