diff --git a/skills/pr-management-triage/SKILL.md b/skills/pr-management-triage/SKILL.md index 3d59e9c5..36a2b401 100644 --- a/skills/pr-management-triage/SKILL.md +++ b/skills/pr-management-triage/SKILL.md @@ -603,8 +603,9 @@ returns, for every PR on the page: triaged" detection), - `authorAssociation` and labels. -Accumulate every PR into a single in-memory list keyed by -number. Do not classify, do not present, do not prompt the +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 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 diff --git a/skills/pr-management-triage/fetch-and-batch.md b/skills/pr-management-triage/fetch-and-batch.md index 82240640..006df489 100644 --- a/skills/pr-management-triage/fetch-and-batch.md +++ b/skills/pr-management-triage/fetch-and-batch.md @@ -215,7 +215,15 @@ loop: break cursor = result.search.pageInfo.endCursor page += 1 -return all_prs + +seen_numbers = set() +deduped_prs_reversed = [] +for pr in reverse(all_prs): + if pr.number in seen_numbers: + continue + seen_numbers.add(pr.number) + deduped_prs_reversed.append(pr) +return reverse(deduped_prs_reversed) ``` Key invariants: @@ -231,8 +239,14 @@ Key invariants: the loop is advancing if they glance over. - **No classification, no prompts, no presentation inside the loop.** Classification runs once in Step 2 over the entire - `all_prs` set. Presentation runs once in Step 3 over the + deduplicated set. Presentation runs once in Step 3 over the classified set. The fetch loop is uninterrupted. +- **Deduplicate after the loop.** A PR updated during an + `updated-asc` search can move to a later page and be fetched + twice. Traverse the accumulated list from the end, keep the + 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. - **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 @@ -245,7 +259,7 @@ Key invariants: either a mis-targeted selector or an unhealthy backlog worth flagging. -Stash the accumulated `all_prs` in the session cache as +Stash the deduplicated list in the session cache as `fetched_prs` (see [`#session-cache`](#session-cache)) before returning to Step 2 — a re-invocation within the same session window can reuse the set if the maintainer wants to re-run with diff --git a/tools/skill-evals/README.md b/tools/skill-evals/README.md index ea7e6223..9d6e4e32 100644 --- a/tools/skill-evals/README.md +++ b/tools/skill-evals/README.md @@ -30,7 +30,7 @@ Suites are currently implemented for: - **pr-management-code-review** — 115 cases across 27 suites (selector-resolution, step-1-selectors-match-chips, 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** — 20 cases across 2 steps (tone-checks, hand-off) - **pr-management-stats** — 13 cases across 2 steps (classify, pressure-weight) -- **pr-management-triage** — 33 cases across 3 steps (pre-filter, decision-table, terminal-links) +- **pr-management-triage** — 35 cases across 4 steps (pre-filter, decision-table, terminal-links, pagination-dedup) - **list-skills** — 7 cases across 2 steps (step-1-command, step-2-present) - **setup-isolated-setup-verify** — 12 cases across 3 steps (runtime-routing, step-1-classify, step-2-recommend) - **setup-isolated-setup-update** — 14 cases across 4 steps (runtime-routing, step-snapshot-drift, step-tool-freshness, step-after-report) diff --git a/tools/skill-evals/evals/pr-management-triage/README.md b/tools/skill-evals/evals/pr-management-triage/README.md index 3c84d81b..b2ff7e22 100644 --- a/tools/skill-evals/evals/pr-management-triage/README.md +++ b/tools/skill-evals/evals/pr-management-triage/README.md @@ -5,13 +5,14 @@ Behavioral evals for the `pr-management-triage` skill. -## Suites (33 cases total) +## Suites (35 cases total) | Suite | Step | Cases | What it covers | |---|---|---|---| | pre-filter | Step 2 (pre-filters) | 10 | F1 (collaborator), F2 (bot), F3 (draft recent), F4 (already ready), F5a (active maintainer comment), F5b (maintainer ping unanswered), F6 (maintainer co-drafted), row-6 (viewer is author), row-7a (fresh PR); clean contributor continues | | decision-table | Step 2 (decision table) | 18 | Rows 3/4 (already-triaged via body-fold block→skip), 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 use the canonical target; `NO_COLOR` 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 | The two body-fold cases (`case-17-fold-already-triaged`, `case-18-fold-stale-after-push`) are the regression guard for the denoise diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-1-updated-pr-reappears/expected.json b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-1-updated-pr-reappears/expected.json new file mode 100644 index 00000000..7d25a872 --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-1-updated-pr-reappears/expected.json @@ -0,0 +1,7 @@ +{ + "prs": [ + {"number": 12, "updatedAt": "2026-08-05T10:05:00Z", "headRefOid": "head-12"}, + {"number": 13, "updatedAt": "2026-08-05T10:10:00Z", "headRefOid": "head-13"}, + {"number": 11, "updatedAt": "2026-08-05T10:15:00Z", "headRefOid": "fresh-11"} + ] +} diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-1-updated-pr-reappears/report.md b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-1-updated-pr-reappears/report.md new file mode 100644 index 00000000..5159af83 --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-1-updated-pr-reappears/report.md @@ -0,0 +1,24 @@ + + +The search is sorted by `updated-asc`. These pages were fetched serially: + +Page 1: + +```json +[ + {"number": 11, "updatedAt": "2026-08-05T10:00:00Z", "headRefOid": "old-11"}, + {"number": 12, "updatedAt": "2026-08-05T10:05:00Z", "headRefOid": "head-12"} +] +``` + +Page 2, after PR 11 was updated and moved to the end of the search: + +```json +[ + {"number": 13, "updatedAt": "2026-08-05T10:10:00Z", "headRefOid": "head-13"}, + {"number": 11, "updatedAt": "2026-08-05T10:15:00Z", "headRefOid": "fresh-11"} +] +``` + +Page 2 reports `hasNextPage: false`. diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-2-distinct-prs/expected.json b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-2-distinct-prs/expected.json new file mode 100644 index 00000000..1b24ab3a --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-2-distinct-prs/expected.json @@ -0,0 +1,7 @@ +{ + "prs": [ + {"number": 21, "updatedAt": "2026-08-05T11:00:00Z", "headRefOid": "head-21"}, + {"number": 22, "updatedAt": "2026-08-05T11:05:00Z", "headRefOid": "head-22"}, + {"number": 23, "updatedAt": "2026-08-05T11:10:00Z", "headRefOid": "head-23"} + ] +} diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-2-distinct-prs/report.md b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-2-distinct-prs/report.md new file mode 100644 index 00000000..03628894 --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/case-2-distinct-prs/report.md @@ -0,0 +1,23 @@ + + +The search is sorted by `updated-asc`. These pages were fetched serially: + +Page 1: + +```json +[ + {"number": 21, "updatedAt": "2026-08-05T11:00:00Z", "headRefOid": "head-21"}, + {"number": 22, "updatedAt": "2026-08-05T11:05:00Z", "headRefOid": "head-22"} +] +``` + +Page 2: + +```json +[ + {"number": 23, "updatedAt": "2026-08-05T11:10:00Z", "headRefOid": "head-23"} +] +``` + +Page 2 reports `hasNextPage: false`. diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/output-spec.md b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/output-spec.md new file mode 100644 index 00000000..e41e2edf --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/output-spec.md @@ -0,0 +1,21 @@ + + +## Output format + +Return ONLY valid JSON with this structure: + +```json +{ + "prs": [ + { + "number": 123, + "updatedAt": "", + "headRefOid": "" + } + ] +} +``` + +Return the PRs that should be handed to Step 2, in order. Do not include +any text outside the JSON object. diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/step-config.json b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/step-config.json new file mode 100644 index 00000000..dabef393 --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/step-config.json @@ -0,0 +1,4 @@ +{ + "skill_md": "skills/pr-management-triage/fetch-and-batch.md", + "step_heading": "## Full-pagination loop" +} diff --git a/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/user-prompt-template.md b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/user-prompt-template.md new file mode 100644 index 00000000..a05a9667 --- /dev/null +++ b/tools/skill-evals/evals/pr-management-triage/pagination-dedup/fixtures/user-prompt-template.md @@ -0,0 +1,9 @@ + + +## Pagination result + +{report} + +Apply the full-pagination loop and return the PRs handed to Step 2. Return +JSON only.