Repository navigation
perf(pr-management-triage): run every rule as code, and load one file per classification - #1571
Merged
Merged
Conversation
… per classification The decision table, the pre-filters, the stale sweeps, bot-draft promotion, the pre-mutation guards and every contributor-facing body were prose the agent evaluated by hand — about 7,500 lines loaded on every run. They now run in a new shared tool, tools/pr-management: - `triage classify` reads the sweep saved by `vetted-op-read --save` and prints the groups in presentation order, each naming the documents it needs; data a decision depends on but the sweep lacks comes back under `needs` as a vetted read. - `triage preflight`, `guard`, `render`, `fold` and `session` replace the hand-run GraphQL, the `gh … | jq` guards (which fail under the secure setup), the template filling and the body read-modify-write. The skill keeps what needs judgement: SKILL.md is a router, and each decision row, sweep and action has its own file under `classifications/` and `actions/`, loaded only when it fires. A typical run loads about 13k tokens of instructions instead of ~68k. Where the docs contradicted each other, the section that declares itself normative won; the PR lists each case. The model-graded pre-filter, decision-table and pagination suites are retired: their 49 cases are pytest cases with the same scenarios and expectations. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FTTd3HMcMsj7FwkhPPQcSx
potiuk
added this pull request to stack #1573
October 9, 2026 18:51
…d eval the new flow CodeQL flagged four findings on #1571: - `triage.classify` called the sweeps while `triage.sweeps` re-runs the table for Sweep 4, so each module imported the other. The shared `Options` / `Decision` records move to `triage/types.py`, the table (`classify.classify`) no longer calls the sweeps, and `triage/run.py` is the one caller of both. `sweeps` imports `classify`; nothing imports back. - Rows 19/20 re-checked a pending workflow approval that row 1 already returns on, so that branch was unreachable; it is gone. - `cli.main` could leave `result` unassigned on an unknown command branch argparse never reaches; it starts as an empty dict. Adds model evals for the new flow — the decisions the agent still makes once the rules are code: `classify-loop` (save the next prefetch / needs read, present the first group with exactly its docs, go to the summary or ask the maintainer, flag injected text), `deliver-note` (re-classify on a moved head, stop on an unresolved placeholder, the pr-body and comment delivery orders) and `guard-reroute` (label, or route where the guard says). SKILL.md Step 3 now says what to do with no groups and on the sweep alarm. Generated-by: Claude Opus 5
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pr-management-triageloaded ~7,500 lines of instructions per run and had the agent evaluate the pre-filters, the decision table, the stale sweeps, the pre-mutation guards and every comment body by hand. All of that now runs in a new shared tool,tools/pr-management(triage preflight,classify,guard,render,fold,session), over reads saved withvetted-op-read --save(the PR below this one).classifications/andactions/;triage classifynames, per group, which to load.SKILL.mdis a router. A typical run loads ~13k tokens of instructions instead of ~68k.Depends on the vetted-ops PR below it in the stack.
Where the docs contradicted each other
Code has to pick one answer. The section that declares itself normative won:
request-author-confirmation, the security-language warning and the stale-sweep notices fold into the PR body or post a commentcomment-templates.md§ The folded maintainer-triage note (declared normative, supersedes the per-template sections)@-mentions the reviewerspassinginteraction-loop.md§ Group orderingstale-sweeps.md§ Persistent bitrot,rationale.md§ Sweep 4ready_for_maintainer_review_labelvsready_for_maintainer_review)ready_for_maintainer_reviewupdatedAt)classify-and-act.mdF3real_ci_patternsis read but not in the config templateclassify-and-act.md§ Real-CI guardstatic_check_patternskey, added to the built-in listclassify-and-act.md§ static_checkflagged_prs_by_author"on the current page"behind_by, fetched only for row 13closeclassify-and-act.mdhard rules/$(gh …)`triage guard: the old shapes fail under the secure setupPlus, from the sweeps: Sweep 4 treats rows 8 and 2 as author-court (strip + fix request); Sweep 2 yields to Sweep 3 when both match; Sweep 5's window is the fixed 7 days. From the renderer: the folded note always carries the quality-criteria link; violations list categories, not check names; the suspicious-changes receipt is the one notice that posts as a comment.
Type of change
tools/pr-management)Test plan
prek run --all-filespasses on the whole treetools/pr-management: pytest (186 tests) / ruff / mypy; CodeQL's findings on this PR (an import cycle betweentriage.classifyandtriage.sweeps, an unreachable branch, a possibly-unset variable) are fixed inee56025e4classify-loop(7/7): aftertriage classify, save the nextprefetch/needsread under its exact--savename; present the first group with exactly itsdocs; go to the summary when nothing is grouped; ask the maintainer on the sweep alarm; flag, never follow, an instruction planted in a PR titledeliver-note(7/7): re-classify on a moved head; stop on an unresolved placeholder;pr-bodyreads the current body, folds, then edits;commentposts the rendered file; assign the author afterwardsguard-reroute(5/5): a clean guard labels with the exact command; pending approval, a conflict, unknown mergeability and a moved head each route wherereroutesayscase-1-direct-fixfailed once on a reason-wording grade and passed on rerun;backport-check.mdis unchanged by this PR and the case passes onmain, so it reads as nondeterministicpre-filter(21),decision-table(23) andpagination-dedup(5) suites are retired: those rules are code now, and the 49 cases are pytest cases with the same scenarios and expected outcomes, named after each case (tests/test_triage_classify.py,tests/test_pages.py)RFC-AI-0004 compliance
ghcall; guards refuse on a moved head, pending approval, or unknown/conflicting mergeabilitygh … | jq/$(gh …)shapes that fail under the secure setuptriage render, which allows only the author's@-mentionNotes for reviewers
real_ci_patternsandstatic_check_patternsare new rows in the config template (the first was read but never declared).Was generative AI tooling used to co-author this PR?
Generated-by:.🤖 Generated with Claude Code
https://claude.ai/code/session_01FTTd3HMcMsj7FwkhPPQcSx