Files
Jarek Potiuk 888d3f26fa Replace breeze pr stats with the pr-stats skill (#65698)
* Add pr-stats skill for open-PR backlog statistics

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.

* pr-stats: document GitHub search-index lag + Python 3.14 JSON strict-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.

* pr-stats: render with Rich (not Markdown), add auto-triage state markers

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.

* pr-stats: detect triage markers from 'breeze pr auto-triage' staleness 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.

* pr-stats: collapse Table 2 age buckets from 6 to 4

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.

* pr-stats: make hybrid REST+GraphQL the default path for Table 1

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.

* pr-stats: contributor-only counts in Table 2, Contrib. right after Total

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.

* fixup! pr-stats: contributor-only counts in Table 2, Contrib. right after Total

* Remove `breeze pr stats`, replaced by the `pr-stats` skill

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.
2026-04-28 13:33:31 +02:00

60 lines
3.3 KiB
ReStructuredText

.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
.. http://www.apache.org/licenses/LICENSE-2.0
.. Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
.. raw:: html
<div align="center">
<img src="images/AirflowBreeze_logo.png"
alt="Airflow Breeze - Development and Test Environment for Apache Airflow">
</div>
Airflow Breeze CI environment
=============================
Airflow Breeze is an easy-to-use development and test environment using
`Docker Compose <https://docs.docker.com/compose/>`_.
The environment is available for local use and is also used in Airflow's CI tests.
We call it *Airflow Breeze* as **It's a Breeze to contribute to Airflow**.
The advantages and disadvantages of using the Breeze environment vs. other ways of testing Airflow
are described in
`Integration test development environments </contributing-docs/06_development_environments.rst>`_.
You can use the Breeze environment to run Airflow's tests locally and reproduce CI failures.
The following documents describe how to use the Breeze environment:
* `Installation <01_installation.rst>`_ - describes how to install the Breeze environment on your machine.
* `Customizing <02_customizing.rst>`_ - describes how to customize the Breeze environment.
* `Developer tasks <03_developer_tasks.rst>`_ - describes how to use Breeze for regular developer tasks.
* `Troubleshooting <04_troubleshooting.rst>`_ - describes how to troubleshoot the Breeze environment.
* `Test commands <05_test_commands.rst>`_ - describes how to run Airflow's tests (and reproduce CI failures) using the Breeze environment.
* `Managing Docker images <06_managing_docker_images.rst>`_ - describes how to manage the Breeze images.
* `Breeze maintenance tasks <07_breeze_maintenance_tasks.rst>`_ - describes how to perform maintenance tasks when you develop Breeze itself.
* `CI tasks <08_ci_tasks.rst>`_ - describes how Breeze commands are used in our CI.
* `Release management tasks <09_release_management_tasks.rst>`_ - describes how to use Breeze for release management tasks.
* `UI tasks <10_ui_tasks.rst>`_ - describes how Breeze commands are used to support Apache Airflow project UI.
* `Registry tasks <11_registry_tasks.rst>`_ - describes how to use Breeze for provider registry data extraction.
* `Issues tasks <12_issues_tasks.rst>`_ - describes how Breeze commands are used to manage GitHub issues.
* `Advanced Breeze topics <14_advanced_breeze_topics.rst>`_ - describes advanced Breeze topics/internals of Breeze.
You can also learn more context and Architecture Decisions taken when developing Breeze in the
`Architecture Decision Records <adr>`_.
Next step: Follow the `Installation <01_installation.rst>`__ instructions to install the Breeze environment.