This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git
The following commit(s) were added to refs/heads/main by this push:
new b20a3f2 feat(pr-management-triage): pre-classify and pre-render next
page during prefetch (#211)
b20a3f2 is described below
commit b20a3f2ec47447fe4d50a50884db7970d2beac35
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon May 18 20:01:00 2026 +0200
feat(pr-management-triage): pre-classify and pre-render next page during
prefetch (#211)
Extend Golden rule 4's prefetch plan beyond fetching the next page's
GraphQL payload: once it lands, run pre-filters + decision table on
it (pure function, zero further GraphQL) and pre-render the first
group's screen. Stash the bundle under `prefetched_pages.<page_num>`
so Step 5's page-turn collapses to a cache read with no
classification latency at the page boundary.
Per-PR invalidation rides on the existing optimistic-lock head-SHA
re-check at execute time; a stale tuple is dropped + re-classified
inline, the rest of the bundle survives. Bundle discarded on
session exit.
Generated-by: Claude Code (Opus 4.7)
---
.claude/skills/pr-management-triage/SKILL.md | 43 +++++++++++++----
.../skills/pr-management-triage/fetch-and-batch.md | 56 ++++++++++++++++++++++
.../pr-management-triage/interaction-loop.md | 48 ++++++++++++++++++-
3 files changed, 137 insertions(+), 10 deletions(-)
diff --git a/.claude/skills/pr-management-triage/SKILL.md
b/.claude/skills/pr-management-triage/SKILL.md
index c6c59e3..499a183 100644
--- a/.claude/skills/pr-management-triage/SKILL.md
+++ b/.claude/skills/pr-management-triage/SKILL.md
@@ -181,14 +181,22 @@ PR will quickly blow the maintainer's 5000-point/h GraphQL
budget. See [`fetch-and-batch.md`](fetch-and-batch.md) for the
canonical query templates.
-**Golden rule 4 — prefetch while the maintainer is reading.** The
-next page of PRs, and the deeper-data calls (failed-job log
-snippets, diff previews for workflow-approval PRs) are issued in
-parallel with the maintainer's current decision, not serialised
-behind it. Concretely: when you present group N to the
-maintainer, the same tool-call turn also fires off the GraphQL
-enrichment for group N+1 and the diff fetch for any workflow-
-approval PRs the maintainer is likely to see next. See
+**Golden rule 4 — prefetch *and pre-classify* while the
+maintainer is reading.** The next page of PRs, and the
+deeper-data calls (failed-job log snippets, diff previews for
+workflow-approval PRs) are issued in parallel with the
+maintainer's current decision, not serialised behind it.
+Concretely: when you present group N to the maintainer, the
+same tool-call turn also fires off the GraphQL enrichment for
+group N+1 and the diff fetch for any workflow-approval PRs the
+maintainer is likely to see next. **Pre-classification rides
+along for free** — the moment the next-page payload arrives,
+run pre-filters + decision table on it (a pure function over
+the fetched data — zero further GraphQL) and pre-render the
+first group's screen. Stash the bundle under
+`prefetched_pages.<page_num>` in the session cache so Step 5's
+page-turn collapses to a cache read with no classification
+latency. See
[`interaction-loop.md#prefetch-plan`](interaction-loop.md).
**Golden rule 5 — scope is triage, not review.** The skill
@@ -445,7 +453,24 @@ window skips the PRs we just handled.
## Step 5 — Paginate and sweep
If the page had `has_next_page=true` and the maintainer hasn't
-quit, advance to the next page and repeat Steps 1–4.
+quit, advance to the next page. Two cases:
+
+- **Prefetched** (the common case — see
+ [Golden rule 4](#golden-rules) and
+
[`interaction-loop.md#pre-classification-and-pre-rendering-of-the-next-page`](interaction-loop.md#pre-classification-and-pre-rendering-of-the-next-page)):
+ the next page's PR-list + rollup payload, the
+ `(classification, action, reason)` tuples, and the first
+ group's pre-rendered screen are already in the session cache
+ under `prefetched_pages.<page_num>`. Steps 1 and 2 collapse
+ to a cache read; present the first group immediately and
+ re-enter Step 3.
+- **Not prefetched** (last page, prefetch skipped per the
+ budget rule, or cache miss after invalidation): fall back to
+ re-running Steps 1–4 synchronously.
+
+In either branch, before presenting page N+1's first group,
+fire the prefetch for page N+2 in parallel — Golden rule 4
+applies to every page boundary, not just the first.
When the maintainer has worked through every interactive group
(or supplied `triage stale`), run the stale sweeps from
diff --git a/.claude/skills/pr-management-triage/fetch-and-batch.md
b/.claude/skills/pr-management-triage/fetch-and-batch.md
index 18cacb6..bfacba8 100644
--- a/.claude/skills/pr-management-triage/fetch-and-batch.md
+++ b/.claude/skills/pr-management-triage/fetch-and-batch.md
@@ -348,6 +348,37 @@ skip the prefetch — it's wasted budget. Heuristic: if
`has_next_page` is false and there's no larger pending work,
don't prefetch.
+### Pre-classify on arrival
+
+Once Call B's result lands (the next-page PR-list payload), do
+**not** wait for the maintainer to finish the current page
+before running classification on it. Classification is a pure
+function over the fetched data (no further network — see
+[`classify-and-act.md`](classify-and-act.md)), so the moment
+the prefetched data arrives:
+
+1. Apply pre-filters F1–F5b to drop collaborator PRs, bot
+ accounts, fresh drafts, already-marked-ready PRs without
+ regression, and PRs with an active maintainer conversation.
+2. Evaluate the decision table top-to-bottom for each
+ surviving PR.
+3. Apply the Real-CI guard for `passing` rows.
+4. Group the resulting `(pr, classification, action, reason)`
+ tuples by `(classification, action)` in the order from
+ [`interaction-loop.md#group-ordering`](interaction-loop.md#group-ordering).
+5. Pre-render the first group's presentation screen from the
+ [group-presentation template](interaction-loop.md#group-presentation).
+6. Stash the bundle under `prefetched_pages.<page_num>` in the
+ session cache (schema below).
+
+The cost is zero GraphQL points; the saving is the entire
+classification "think time" between page N's last group and
+page N+1's first group. Step 5 of [`SKILL.md`](SKILL.md) reads
+the prefetched bundle and presents page N+1's first group
+immediately, with no fresh fetch or re-classification. See
+[`interaction-loop.md#pre-classification-and-pre-rendering-of-the-next-page`](interaction-loop.md#pre-classification-and-pre-rendering-of-the-next-page)
+for the full sequence diagram and invalidation rules.
+
---
## Session cache
@@ -376,6 +407,23 @@ anything that isn't needed. Schema:
"recent_main_failures": {
"fetched_at": "2026-04-22T08:00:00Z",
"failing_check_names": ["Helm tests (1.29)", "..."]
+ },
+ "prefetched_pages": {
+ "2": {
+ "end_cursor": "Y3Vyc29yOnYyOpHOA...",
+ "fetched_at": "2026-04-22T09:18:14Z",
+ "has_next_page": true,
+ "groups": [
+ {
+ "classification": "deterministic_flag",
+ "action": "draft",
+ "prs": [
+ {"number": 65401, "head_sha": "abc123...", "reason": "CI failed +
2 unresolved threads past grace window"}
+ ],
+ "rendered_screen": "─────────────...\nGroup 1 of 5 —
deterministic_flag → draft — 3 PRs\n..."
+ }
+ ]
+ }
}
}
```
@@ -388,6 +436,14 @@ anything that isn't needed. Schema:
- The `recent_main_failures` block is valid for 4 hours; after
that, re-fetch via the canary/main-branch failure query
(see below).
+- A `prefetched_pages.<n>` bundle's PR tuples are validated
+ against live head SHAs by the optimistic-lock re-check at
+ execute time (see
+
[`interaction-loop.md#optimistic-lock-re-check-before-mutate`](interaction-loop.md#optimistic-lock-re-check-before-mutate)).
+ A per-PR mismatch drops that PR's tuple from the bundle
+ and triggers an inline re-classification; the rest of the
+ bundle survives. Discard the entire bundle on session exit —
+ do not persist across sessions.
- The whole cache is discardable — losing it only costs one
extra enrichment round.
diff --git a/.claude/skills/pr-management-triage/interaction-loop.md
b/.claude/skills/pr-management-triage/interaction-loop.md
index 39110f4..1fd8946 100644
--- a/.claude/skills/pr-management-triage/interaction-loop.md
+++ b/.claude/skills/pr-management-triage/interaction-loop.md
@@ -296,7 +296,7 @@ Concrete prefetches:
| Currently showing | Prefetch |
|---|---|
-| Any group | Next page's PR-list + rollup query (if `has_next_page` and
`page_num < max_num / 50`) |
+| Any group | Next page's PR-list + rollup query (if `has_next_page` and
`page_num < max_num / 50`), then **pre-classify and pre-render the first
group** of that page — see
[`#pre-classification-and-pre-rendering-of-the-next-page`](#pre-classification-and-pre-rendering-of-the-next-page)
below |
| `pending_workflow_approval` group | `gh pr diff <N>` for the first 2 PRs in
the group |
| `deterministic_flag → draft/comment` group, one PR at a time | Failed-job
log snippets for the current PR and the next PR in the queue |
| `close` group (per-PR) | Author's full open-PR list (for the "you have N
flagged PRs" line in the body) |
@@ -316,6 +316,52 @@ When a prefetched result lands before the maintainer acts,
store
it in the session cache; when the maintainer eventually triggers
the drill-in, it's instant.
+### Pre-classification and pre-rendering of the next page
+
+The next-page prefetch is most valuable when it carries the page
+all the way through to a presentable form, not just to raw
+GraphQL nodes. Classification is a pure function over the
+fetched data (no further GraphQL, no prompts — see
+[`classify-and-act.md`](classify-and-act.md)), and so is the
+group-screen template ([`#group-presentation`](#group-presentation));
+both can run eagerly the moment the prefetch resolves. Pipeline:
+
+1. **Turn N** (presenting page N's current group): fire the
+ page-(N+1) GraphQL call in parallel with the group screen,
+ as the table above documents.
+2. **Turn N+1** (or whenever the prefetch resolves, before the
+ maintainer's decision lands): apply pre-filters F1–F5b, walk
+ the decision table top-to-bottom, run the Real-CI guard, and
+ group the resulting `(pr, classification, action, reason)`
+ tuples — exactly as Steps 2 and 3 of [`SKILL.md`](SKILL.md)
+ would have done synchronously at page-turn time. Build the
+ first group's screen text from the
+ [group-presentation template](#group-presentation). Stash
+ the bundle under `prefetched_pages.<page_num>` in the
+ session cache — see
+ [`fetch-and-batch.md#session-cache`](fetch-and-batch.md#session-cache)
+ for the schema.
+3. **Page-turn moment** (current page exhausted): instead of
+ re-fetching and re-classifying, read the prefetched bundle
+ and present the first group immediately. The maintainer
+ sees zero classification latency at the page boundary. See
+
[`SKILL.md#step-5--paginate-and-sweep`](SKILL.md#step-5--paginate-and-sweep).
+
+Invalidation: if the optimistic-lock re-check at execute time
+(see
[`#optimistic-lock-re-check-before-mutate`](#optimistic-lock-re-check-before-mutate))
+finds a head-SHA mismatch for a PR in the prefetched bundle,
+drop that PR's tuple and re-classify it inline. The bundle as
+a whole survives — a single stale PR does not poison the page.
+
+If the maintainer quits (`[Q]`) on the current page, the
+prefetched bundle is discarded on session exit. The work was
+wasted, but the GraphQL cost was the same one query that would
+have happened at the page-turn anyway — the downside is
+small. Skip the pre-classification (not just the prefetch) only
+when the prefetch itself was skipped per the "last page or no
+larger pending work" heuristic in
+[`fetch-and-batch.md#prefetch-plan`](fetch-and-batch.md#prefetch-plan).
+
---
## Batch execution status