Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/magpie-pr-management/skills/pr-triage/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ argument-hint: "[pr:N] [label:LBL] [author:LOGIN] [review-for-me] [stale] [repo:
capability: capability:triage
surface_hash: sha256:5c92df54aab39ad7
license: Apache-2.0
measured_tokens: 5005
measured_tokens: 5027
---
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
Expand Down Expand Up @@ -316,7 +316,7 @@ Selector semantics (`triage pr:<N>` / `label:<LBL>` / `author:<LOGIN>` / `review
---
**Step 0.7 — backport check:** only when `backport_branches` is configured — for every open PR targeting a release branch (any author, drafts included), verify it is a direct cherry-pick of a default-branch commit and, under `backport_policy: fixes-only`, that the source change is a fix rather than a feature, behaviour change, deprecation, removal or refactor; the spec is in [`backport-check.md`](backport-check.md).

**Step 1 — fetch:** resolve the selector per [`fetch-and-batch.md#inputs`](fetch-and-batch.md#inputs), walk every page of the aliased PR-list query until `pageInfo.hasNextPage` is false, deduplicate at the end, and prefetch the `action_required` run index and the recent main-branch failures once per session — the canonical loop is in [`fetch-and-batch.md#full-pagination-loop`](fetch-and-batch.md#full-pagination-loop).
**Step 1 — fetch:** resolve the selector per [`fetch-and-batch.md#inputs`](fetch-and-batch.md#inputs), walk every page of the aliased PR-list query until `pageInfo.hasNextPage` is false, deduplicate at the end, silently suppress PRs the session cache already holds under a terminal `action_taken` with an unchanged head SHA, and prefetch the `action_required` run index and the recent main-branch failures once per session — the canonical loop is in [`fetch-and-batch.md#full-pagination-loop`](fetch-and-batch.md#full-pagination-loop).

**Step 2 — classify:** run **every PR fetched in Step 1** through
[`classify-and-act.md`](classify-and-act.md), once — the pre-filters
Expand Down
42 changes: 40 additions & 2 deletions plugins/magpie-pr-management/skills/pr-triage/fetch-and-batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,10 @@ returns, for every PR on the page:

Accumulate every PR into a single in-memory list, then deduplicate
it by number after the final page, keeping each PR's last (freshest)
occurrence. Do not classify, do not present, do not prompt the
occurrence, then silently suppress PRs the session cache already
holds under a terminal `action_taken` with an unchanged head SHA (see
[`#full-pagination-loop`](#full-pagination-loop)). Do not classify,
do not present, do not prompt the
maintainer between pages — the fetch loop is uninterrupted,
runs to completion, and emits one progress line per page so
the maintainer can step away during the wait. See
Expand Down Expand Up @@ -355,6 +358,8 @@ loop serially until `pageInfo.hasNextPage` is false, then hand
the accumulated list to Step 2.

```text
cache = load_session_cache().prs # may be empty — a fresh session starts empty

all_prs = []
cursor = null
page = 1
Expand All @@ -374,7 +379,18 @@ for pr in reverse(all_prs):
continue
seen_numbers.add(pr.number)
deduped_prs_reversed.append(pr)
return reverse(deduped_prs_reversed)
queue = reverse(deduped_prs_reversed)

# Session suppression: a PR already taken to a terminal action earlier
# in this session, whose head SHA is unchanged, has nothing new to
# read. Drop it silently — no queue entry, no progress line, no prompt.
kept = []
for pr in queue:
entry = cache.get(pr.number)
if entry and entry.get("action_taken") and entry.get("head_sha") == pr.head_sha:
continue
kept.append(pr)
return kept
```

Key invariants:
Expand All @@ -398,6 +414,19 @@ Key invariants:
first record for each PR number, then restore the list order.
This keeps the last (freshest) occurrence and its position in
the fetched ordering. Leave an already-unique list unchanged.
- **Suppress already-acted-on, unchanged-head entries.** After
deduplication, drop every PR the session cache holds under a
terminal `action_taken` whose cached `head_sha` equals the
freshly fetched head SHA — the decision was already made and
nothing has changed since, so re-surfacing the PR only invites
a second, possibly different decision. The drop is silent: the
PR must not appear in any group, progress line, or the Step 6
summary. A PR whose head SHA **differs** does not match the
suppression and is re-classified as usual — the invalidation
rule in [`#session-cache`](#session-cache) and this suppression
are complementary, not conflicting. The suppression is session
state: it dies with the cache, so a fresh session re-surfaces
everything.
- **Skip prefetch heuristics.** With pages fetched serially up
front, there is no per-page maintainer wait to overlap with
a next-page prefetch — the old prefetch-during-interaction
Expand Down Expand Up @@ -686,6 +715,15 @@ anything that isn't needed. Schema:
- An entry's `head_sha` must match the head SHA returned by the
current fetch — if it doesn't, the contributor pushed since
and the entry is stale. Drop it and re-classify.
- Conversely, an entry carrying a terminal `action_taken` whose
`head_sha` **does** match the current fetch suppresses the PR
for the rest of the session: Step 1 drops it silently before
handing the queue to Step 2 (see
[`#full-pagination-loop`](#full-pagination-loop)). Unchanged
head plus already-acted-on means nothing new to read; a changed
head falls through to the staleness rule above, so the two
rules never fire against each other. The suppression dies with
the session cache — a fresh session re-surfaces everything.
- The `recent_main_failures` block is valid for 4 hours; after
that, re-fetch via the canary/main-branch failure query
(see below).
Expand Down
2 changes: 1 addition & 1 deletion tools/skill-evals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Suites are currently implemented for:
- **pr-management-code-review**: 126 cases across 28 suites (selector-resolution, step-1-selectors-match-chips, step-2-reviewer-resolution, step-2.5-slop-detection, step-3-security-disclosure-scan, step-3-ai-authorship-disclosure, step-4-* checks, step-5-adversarial-integration, step-6-disposition, step-7b-review-body-attribution, review-risk-classify, injection-guard, review-disposition, review-handoff)
- **pr-management-mentor** — 29 cases across 3 steps (tone-checks, hand-off)
- **pr-management-stats** — 13 cases across 2 steps (classify, pressure-weight)
- **pr-management-triage** — 59 cases across 6 steps (backport-check, pre-filter, decision-table, terminal-links, pagination-dedup, interaction-progress)
- **pr-management-triage** — 62 cases across 6 steps (backport-check, pre-filter, decision-table, terminal-links, pagination-dedup, interaction-progress)
- **list-skills** — 8 cases across 2 steps (step-1-command, step-2-present)
- **setup-isolated-setup-verify** — 17 cases across 3 steps (runtime-routing, step-1-classify, step-2-recommend)
- **setup-isolated-setup-update** — 15 cases across 4 steps (runtime-routing, step-snapshot-drift, step-tool-freshness, step-after-report)
Expand Down
4 changes: 2 additions & 2 deletions tools/skill-evals/evals/pr-management-triage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

Behavioral evals for the `pr-management-triage` skill.

## Suites (59 cases total)
## Suites (62 cases total)

| Suite | Step | Cases | What it covers |
|---|---|---|---|
| backport-check | Step 0.7 (backport check) | 5 | Direct cherry-pick of a fix→hand-off, hand-adapted backport (`-U0` patch-id mismatch)→surface, faithful cherry-pick of a behaviour change/deprecation→close under `fixes-only`, every commit already on the base→close, no resolvable source commit→surface |
| pre-filter | Step 2 (pre-filters) | 21 | F1 (collaborator), F2 (bot), F3 (draft recent), F4 (already ready), F5a (active maintainer feedback — general comments, review-thread comments, **and submitted top-level reviews with a non-empty body**, including the 72-hour and last-commit boundaries), F5b (maintainer ping unanswered; a ping answered by a review does not fire), F6 (maintainer co-drafted), row-6 (viewer is author), row-7a (fresh PR); clean contributor continues |
| decision-table | Step 2 (decision table) | 22 | Rows 3/4 (already-triaged via a cross-triager comment marker→skip, via body-fold block→skip, and via a fold carrying `by=<another triager>`→skip with the reason naming that triager), plus the negative case (a non-triager comment quoting the QC link is not a marker), 7b (security signal), 9 (conflict→draft), 10 (all systemic→rerun), 11 (partial systemic→rerun), 12 (static-only→comment), 13 (flaky ≤2→rerun), 14a (author confirmed→mark-ready), 14b (pending confirmation→skip), 14c (threads addressed→request-author-confirmation), 15 (threads→ping), 16 (no CI→rebase), 18 (changes-requested+new-commits→ping), 19 (already ready→skip), 20 (passing→mark-ready), 21 (stale draft sweep→close), 22 (rollup anomaly→skip) |
| terminal-links | Golden rule 10 | 5 | Short, context-short, and full PR references retain their visible form and use the canonical target; `NO_COLOR` (including an empty value) and `TERM=dumb` select the plain-text fallback |
| pagination-dedup | Step 1 (full pagination) | 2 | A PR that moves to a later page is emitted once at its freshest occurrence; distinct PRs keep their fetched order |
| pagination-dedup | Step 1 (full pagination) | 5 | A PR that moves to a later page is emitted once at its freshest occurrence; distinct PRs keep their fetched order; a PR already acted on earlier in the session is silently suppressed while its head SHA is unchanged, re-classified once it moved, and never suppressed from a cache entry that carries no terminal `action_taken` |
| interaction-progress | Interaction loop | 4 | Per-PR drill-ins retain the original one-based group position at the first, middle, and last rows and show the active classify-to-propose transition for `[E]` and `[P]` entry |

The two body-fold cases (`case-17-fold-already-triaged`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"prs": [
{"number": 42, "updatedAt": "2026-08-05T12:05:00Z", "headRefOid": "head-42"}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->

The search is sorted by `updated-asc`. These pages were fetched serially:

Page 1:

```json
[
{"number": 41, "updatedAt": "2026-08-05T12:00:00Z", "headRefOid": "head-41"},
{"number": 42, "updatedAt": "2026-08-05T12:05:00Z", "headRefOid": "head-42"}
]
```

Page 1 reports `hasNextPage: false`.

The session cache for this repository holds:

```json
{
"prs": {
"41": {
"head_sha": "head-41",
"classification": "deterministic_flag",
"suggested_action": "comment",
"action_taken": "comment",
"action_at": "2026-08-05T09:30:00Z"
}
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"prs": [
{"number": 51, "updatedAt": "2026-08-05T12:10:00Z", "headRefOid": "fresh-51"},
{"number": 52, "updatedAt": "2026-08-05T12:15:00Z", "headRefOid": "head-52"}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->

The search is sorted by `updated-asc`. These pages were fetched serially:

Page 1:

```json
[
{"number": 51, "updatedAt": "2026-08-05T12:10:00Z", "headRefOid": "fresh-51"},
{"number": 52, "updatedAt": "2026-08-05T12:15:00Z", "headRefOid": "head-52"}
]
```

Page 1 reports `hasNextPage: false`. PR 51 was acted on earlier in this
session, but its head SHA differs from the cached one — the contributor
pushed since.

The session cache for this repository holds:

```json
{
"prs": {
"51": {
"head_sha": "old-51",
"classification": "deterministic_flag",
"suggested_action": "rebase",
"action_taken": "rebase",
"action_at": "2026-08-05T09:45:00Z"
}
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"prs": [
{"number": 61, "updatedAt": "2026-08-05T12:20:00Z", "headRefOid": "head-61"},
{"number": 62, "updatedAt": "2026-08-05T12:25:00Z", "headRefOid": "head-62"}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->

The search is sorted by `updated-asc`. These pages were fetched serially:

Page 1:

```json
[
{"number": 61, "updatedAt": "2026-08-05T12:20:00Z", "headRefOid": "head-61"},
{"number": 62, "updatedAt": "2026-08-05T12:25:00Z", "headRefOid": "head-62"}
]
```

Page 1 reports `hasNextPage: false`. PR 61 was classified earlier in this
session but no action was taken on it — the cache entry has no terminal
`action_taken`.

The session cache for this repository holds:

```json
{
"prs": {
"61": {
"head_sha": "head-61",
"classification": "deterministic_flag",
"suggested_action": "draft"
}
}
}
```