Uh oh!
There was an error while loading. Please reload this page.
Replace breeze pr stats with the pr-stats skill - #65698
Conversation
dc05bf3 to
7156f96Comparebreeze pr stats with the pr-stats skillpotiuk
commented
Apr 26, 2026
This one will remove the |
potiuk
commented
Apr 28, 2026
Anyone :) ? |
Uh oh!
There was an error while loading. Please reload this page.
Companion to the pr-triage skill — read-only aggregation of open PRs on apache/airflow (or another target repo), producing the two tables that 'breeze pr stats' prints: 'Triaged PRs — Final State since <cutoff>' and 'Triaged PRs — Still Open', both grouped by area:* label with per-area age-bucket histograms. Shape follows the airflow-translations / pr-triage convention: - .github/skills/pr-stats/ holds the canonical content — SKILL.md as entry point plus four topic files (fetch.md, classify.md, aggregate.md, render.md). - .claude/skills/pr-stats is a committed symlink so Claude Code picks up the skill without per-developer setup; .gitignore is updated to let the symlink through. - .pre-commit-config.yaml adds pr-stats/SKILL.md to the agentic- markdown license-header exclusion (same treatment as pr-triage). Design choices: - Triage-detection logic is the same as pr-triage's: the canonical 'Pull Request quality criteria' marker authored by any maintainer. The skill deliberately refers back to pr-triage/classify.md so the two stay in sync. - No mutations, no per-PR drill-in, no timeline/trend charts. Single snapshot, two tables, one legend, one summary line. - Budget: roughly 10 GraphQL calls regardless of backlog size (~6 paginated for open PRs, ~2 for closed-since, 1 pre-flight, 1 viewer login). No per-PR REST calls.
…mode Two gotchas encountered while actually running the skill against apache/airflow: - GitHub's search 'issueCount' for closed/merged date predicates is heavily under-reported compared to the REST /pulls endpoint (37 vs. ~30/day reality). Free-text search for the triage marker 'Pull Request quality criteria' is even further behind (164 PRs matched despite dozens of posts per day). Calling out that the search-driven path is a biased sample, and suggesting a REST-plus-aliased-GraphQL hybrid for accurate closed-since numbers when the maintainer needs them. - Python 3.14 json.load is strict about control characters in strings by default, and GitHub comment bodyText routinely contains raw tabs/newlines. Document strict=False, the 'gh api --jq' alternative, and the save-to-file fallback. Explicitly warn against replacing control characters pre-parse — that silently corrupts genuine escape sequences.
Two fixes after running the skill end-to-end and seeing the output fall apart on a real terminal: - Markdown tables did not survive width. Table 2 has 25 columns; most Markdown viewers collapsed it into wrapped pipe-delimited prose that no longer reads as a table. Switch the default render path to rich.table.Table — same library breeze pr stats and breeze pr auto-triage use — which computes column widths from the actual terminal, overflows horizontally, and supports inline colour markup. Markdown is now a fallback (pipe/emoji mode), used only when the maintainer asks for shareable output or when stdout is not a tty. - Add an explicit 'By triage state' panel after Table 2 that slices the full open-PR set into the same four markers auto-triage uses: Ready for review / Responded / Waiting for Author / not yet triaged. Colour scheme mirrors auto-triage exactly (bold green / bright_cyan / yellow / blue) so the marker vocabulary transfers between skills. render.md is rewritten to describe the new shape in full: the colour mapping table, the state-breakdown panel with its counting rules, the Rich table setup (show_lines, show_footer, TOTAL-row styling that matches breeze pr stats), and the Markdown-fallback emoji rules for the cases when Rich colour is unavailable.
…s path The breeze pr auto-triage tool has two triage-comment templates: - Violations path — embeds a visible Markdown link whose anchor text is 'Pull Request quality criteria' (matches the pr-triage skill). - Staleness path — ends the body with an HTML comment '<!-- Pull Request quality criteria -->' as a hidden marker, but the visible body contains no 'Pull Request quality criteria' text at all. GitHub's GraphQL exposes two fields: bodyText strips HTML comments, body preserves them. The previous skill fetch used bodyText, so any PR that was only auto-triaged via the staleness path was invisible to the classifier. On a 40-PR sample from apache/airflow, ~10% of triaged-marker comments were HTML-comment-only. Changes: - fetch.md: switch the comments subfield from bodyText to body in both the open-PR query and the closed-since query. Add a new 'Why body, not bodyText' section explaining the breeze staleness case with the exact template shape so future edits don't revert. - classify.md: rewrite the 'Triage marker' rule to specify the body field explicitly, and add a table enumerating the three marker-producing paths (pr-triage skill, breeze violations, breeze staleness) so both forms of the marker are recognised as legitimate triage.
The six-bucket split (<1d, 1-3d, 3-7d, 1-2w, 2-4w, >4w) was exposing a distinction that doesn't map to different maintainer decisions. In practice an author who replied 2 days ago vs. 5 days ago is in the same state — actively iterating — and the next question is whether they're inside the review week at all. Same for 2-4w vs >4w: both mean 'stale enough to nudge'. New four-bucket split: <1d fresh push / just active 1-7d within the current review week 1-4w inside the triage-response window >4w stale; needs maintainer intervention That drops eight columns from Table 2 (two from Drafted, two from Author resp, each of which appeared twice in the column set), taking the open-PR table from 25 columns down to 21. Rich can still render it horizontally, but the noise-to-signal ratio improves — every remaining bucket maps to a distinct intervention.
The search-based path was returning 28 triaged+closed PRs over a six- week apache/airflow window. The real count (verified by scanning every non-bot closed/merged PR's comments) is 204 — a 7.3x undercount. The hybrid path documented earlier in fetch.md is now the default: 1. Enumerate the closed/merged set via REST /pulls?state=closed&sort= updated, paginate until closed_at crosses the cutoff, filter bots. 2. Batch-fetch comments in aliased GraphQL queries of 30 PRs each (each alias pulls the last 25 comments of one PR). Budget on apache/airflow: ~20 REST + ~54 GraphQL calls, about 60 seconds end-to-end for a six-week window. Keep the free-text-search path as an explicit fast-closed opt-in that prints a caveat above Table 1 so a maintainer who takes the fast path knows the numbers are under-reported.
Reorder and rescope Table 2 so the columns answer 'how is the contributor queue doing', not 'how many open PRs exist in this area mixed together'. Concretely: - Move Contrib. and %Contrib. to the second and third columns, right after Total. They describe the area composition — that belongs up front so every subsequent column reads as 'of the contributor work'. - Make every counter below Contrib. contributor-only: Draft, Non-Draft, Triaged, Responded, Ready, Drafted by triager, age-bucket histograms. Total is kept as a reference-only column but does not feed any denominator except %Contrib. - %Draft, %Ready and the age-bucket semantics switch to the contributor denominator, so a 35% draft rate means 35% of contributor work in the area is in draft — not 35% of the area including maintainer PRs. aggregate.md now documents scope per field (all-PR vs contributor-only) plus invariants (contributors == drafts + non_drafts, etc.) and a compact percentage-rules section. render.md lists the new column order with an explicit denominator column, and a one-line rationale explaining why contributor-scoping keeps the percentages meaningful.
The new `pr-stats` skill is a drop-in replacement for `breeze pr stats`
— same two tables (Triaged Final State, Triaged Still Open), same
area grouping, same colour scheme, just produced by a Claude Code
skill instead of a Python click command. With this PR's earlier
commits removing `breeze pr auto-triage`, that leaves the `breeze pr`
group with no subcommands, so the entire group is removed.
Code removal:
- `dev/breeze/src/airflow_breeze/commands/pr_commands.py` (1,176 lines)
and `pr_commands_config.py` deleted.
- `dev/breeze/src/airflow_breeze/utils/pr_cache.py`,
`pr_models.py`, `pr_vault.py` deleted (used only by the removed
command).
- `dev/breeze/tests/test_pr_cache.py`, `test_pr_models.py`,
`test_pr_vault.py`, `test_author_cache.py` deleted.
- `dev/breeze/src/airflow_breeze/breeze.py` no longer imports
`pr_group`.
- `dev/breeze/src/airflow_breeze/configure_rich_click.py` no longer
references `PR_COMMANDS` / `PR_PARAMETERS` and the "PR commands"
group is dropped from the breeze help layout.
- `dev/breeze/doc/images/output_pr{,_stats}.{svg,txt}` deleted; the
top-level help images (`output-commands.svg`,
`output_setup_check-all-params-in-groups.{svg,txt}`,
`output_setup_regenerate-command-images.{svg,txt}`) regenerate to
drop the `pr` row.
Documentation:
- `dev/breeze/doc/13_pr_tasks.rst` deleted (no breeze pr commands
remain). Index entries in `dev/breeze/doc/README.rst` and the
"Next step" pointer in `dev/breeze/doc/12_issues_tasks.rst` are
updated to skip straight to advanced breeze topics.
- `contributing-docs/25_maintainer_pr_triage.md` adds a "Backlog
statistics — the `pr-stats` skill" section that introduces the
skill, lists the recognised invocation phrasings, and notes that
the legacy HTML-comment markers left by the removed breeze command
are still detected.
- `contributing-docs/README.rst` entry expanded to mention both
skills.
- `.github/skills/pr-stats/{SKILL,render,classify,fetch}.md` updated
to describe `breeze pr stats` and `breeze pr auto-triage` in past
tense ("now-removed", "predecessor", "legacy"), since they no
longer exist as commands.
Validation: 727 breeze tests pass; prek pre-commit and manual stages
green; `breeze` CLI no longer registers a `pr` subcommand group.Backport failed to create: v3-2-test. View the failure log Run detailsNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
You can attempt to backport this manually by running: cherry_picker 888d3f2 v3-2-testThis should apply the commit to the v3-2-test branch and leave the commit in conflict state marking After you have resolved the conflicts, you can continue the backport process by running: cherry_picker --continueIf you don't have cherry-picker installed, see the installation guide. |
Adds a new Claude Code skill,
pr-stats, that produces the two backlog-summary tables thatbreeze pr statsprinted (grouped byarea:*label, read-only). It is the statistical complement of thepr-triageskill: same repo, same triage-detection logic, no mutations.Scope
.github/skills/pr-stats/— the new skill (SKILL.mdplus four topic files:fetch.md,classify.md,aggregate.md,render.md), plus the.claude/skills/pr-statssymlink, the.gitignoreexception, and the license-header exclusion in.pre-commit-config.yaml(mirrorspr-triage/SKILL.md).breeze pr stats— and, sinceprwas its only remaining subcommand after Remove breeze pr auto-triage, replaced by pr-triage skill #65865, removal of the entirebreeze prgroup. Last commit deletespr_commands.py(1,176 lines),pr_commands_config.py,pr_cache.py,pr_models.py,pr_vault.py, the four corresponding test files, and the auto-generated help images.breeze.pyandconfigure_rich_click.pyno longer reference theprgroup.dev/breeze/doc/13_pr_tasks.rstdeleted,dev/breeze/doc/README.rstand12_issues_tasks.rstupdated to skip past it.contributing-docs/25_maintainer_pr_triage.mdgains a "Backlog statistics — thepr-statsskill" section that introduces the skill, lists the recognised invocation phrasings, and notes that legacy HTML-comment markers from the removed breeze command are still detected.contributing-docs/README.rstentry expanded to mention both skills.Output shape
Two Rich-rendered colored tables plus a short legend, a state-breakdown panel, and a one-line summary:
<cutoff>— closed / merged PRs that had a triage comment posted at some point, broken down by area label. Columns: Triaged Total, Closed / %Closed, Merged / %Merged, Responded / %Responded (author replied before close).<1d,1-7d,1-4w,>4w) and 4 "Author resp" age-bucket columns on the same boundaries.A bold
TOTALrow on each table counts every PR exactly once (not the column-wise sum of per-area rows — a PR with twoarea:*labels would otherwise double-count). The one-line summary at the end is the format a maintainer can paste into a weekly status update.Design choices
Pull Request quality criteriamarker thatpr-triageposts on every triage comment;pr-stats/classify.mdexplicitly referencespr-triage/classify.md#c4-already_triagedas the single source of truth. Any rule change in one skill ships simultaneously in the other — that coupling is intentional. The same detector also catches the legacy HTML-comment markers left by the removedbreeze pr auto-triagestaleness path, so historical triaged PRs continue to be counted.pr-triage label:area:<X>.batchSize=50, 2 for the closed-since cutoff, 1 pre-flight, 1 viewer login. No per-PR REST calls — the triage-marker scan is satisfied bycomments(last: 10)in the open-PR query andcomments(last: 25)for closed PRs./tmp/pr-stats-cache-<repo-slug>.jsonso re-running the skill inside a single interactive session (e.g. stats → triage → stats) is cheap, with explicitclear-cacheinvalidation.breeze pr statsandbreeze pr auto-triagecommands used, so the colour scheme and state names transfer without relearning. A Markdown fallback is produced when the maintainer asks for shareable output.Test plan
pr-triage—SKILL.mdfrontmatter, golden rules, detail-file split — so the skill catalog stays consistent.prek run --from-ref main --stage pre-commitand--stage manualboth pass on this branch.breezeCLI no longer registers aprsubcommand group;breeze --helpno longer lists "PR commands".Was generative AI tooling used to co-author this PR?
Generated-by: Claude Opus 4.7 (1M context) following the guidelines