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 3df0451  pr-management-stats: fix doc anchors and split Action/Detail 
columns (#106)
3df0451 is described below

commit 3df0451ef4d0bf6c2a390069c843b892116f040e
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sun May 10 20:13:02 2026 +0200

    pr-management-stats: fix doc anchors and split Action/Detail columns (#106)
    
    Two doc-quality fixes for the pr-management-stats skill, mirroring the
    review feedback from @choo121600 on the corresponding apache/airflow PR
    (apache/airflow#66464):
    
    1. **Anchor links drop the fragment in the URL.**
       Cross-references like `[`render.md#recommendation-rules`](render.md)`
       show an anchor in the visible text but the URL omits it, so GFM never
       scrolls to the target section. Mechanically copied each visible
       anchor into the URL across 26 references in 5 files. Three target
       headings were tweaked so the GFM-generated anchors actually exist:
         - `aggregate.md#counters` → `#counters-per-area` (matches heading
           "Counters (per area)")
         - `fetch.md`: "Known limitation — GitHub search-index lag for
           closed-since counts" renamed to "Known limitation"
         - `fetch.md`: "Closed / merged triaged PRs (since cutoff)" renamed
           to "Closed-merged-triaged PRs"
    
    2. **Some `action` templates aren't paste-clean.**
       The recommendation-rules table in `render.md` conflated paste-ready
       slash commands with parentheticals, prose, and unicode arrows in a
       single "Action template" column — directly contradicting the rule
       on the same page that says "the action template must be a literal
       slash-command the maintainer can paste". Split into two columns:
       `Action` (slash command, or `—` when no command applies) and
       `Detail` (prose explanation that goes in the card body). Card
       rendering description and SKILL.md schema description updated to
       match.
---
 .claude/skills/pr-management-stats/SKILL.md     | 30 +++++++++---------
 .claude/skills/pr-management-stats/aggregate.md |  6 ++--
 .claude/skills/pr-management-stats/classify.md  |  4 +--
 .claude/skills/pr-management-stats/fetch.md     |  6 ++--
 .claude/skills/pr-management-stats/render.md    | 42 +++++++++++++------------
 5 files changed, 45 insertions(+), 43 deletions(-)

diff --git a/.claude/skills/pr-management-stats/SKILL.md 
b/.claude/skills/pr-management-stats/SKILL.md
index bf500ee..7af81c1 100644
--- a/.claude/skills/pr-management-stats/SKILL.md
+++ b/.claude/skills/pr-management-stats/SKILL.md
@@ -118,7 +118,7 @@ read-only and inherits everything from 
`pr-management-triage`'s contract.
 
 **Golden rule 5 — state the input scope up front.** Before rendering, print 
one line summarising what the stats cover: repo name, total open PR count, 
closed-since cutoff date, and viewer login. The numbers only make sense in 
context.
 
-**Golden rule 6 — recommendations are deterministic, not opinions.** Every 
action surfaced in the "What needs attention" panel comes from a fixed rule in 
[`render.md#recommendation-rules`](render.md). The skill never editorialises 
("queue is doing well", "you should focus on X") — it surfaces the rule's 
trigger and the suggested next-step command. The maintainer reads the trigger 
and decides; the skill never decides for them. New rules are added by editing 
the rules table, not by adding  [...]
+**Golden rule 6 — recommendations are deterministic, not opinions.** Every 
action surfaced in the "What needs attention" panel comes from a fixed rule in 
[`render.md#recommendation-rules`](render.md#recommendation-rules). The skill 
never editorialises ("queue is doing well", "you should focus on X") — it 
surfaces the rule's trigger and the suggested next-step command. The maintainer 
reads the trigger and decides; the skill never decides for them. New rules are 
added by editing the rules  [...]
 
 **Golden rule 7 — actions link to other skills, never mutate.** Every 
recommendation's `action` field is the *exact* slash-command the maintainer can 
paste to do the work — almost always `/pr-management-triage`, 
`/maintainer-review`, or a focused variant with a label/PR-number filter. The 
stats skill itself remains pure-read (Golden rule 1); the dashboard makes 
downstream skills *one paste away* from running.
 
@@ -143,7 +143,7 @@ No per-PR drill-in — this skill is aggregate-only.
 
 1. `gh auth status` must succeed; capture the viewer login (needed for the 
triage-marker check in step 2).
 2. Run one GraphQL query that asks both for `viewer { login }` and for 
`repository(owner, name) { name }` to confirm the repo is reachable. 
`viewerPermission` is NOT required (this skill doesn't mutate) — skip the 
write-check that `pr-management-triage` does.
-3. Read or initialise the scratch cache at 
`/tmp/pr-management-stats-cache-<repo-slug>.json` (see 
[`aggregate.md#cache`](aggregate.md)). The cache stores the viewer login and a 
map of `pr_number → (head_sha, triage_status)` so a re-run inside the same 
session skips the per-PR enrichment.
+3. Read or initialise the scratch cache at 
`/tmp/pr-management-stats-cache-<repo-slug>.json` (see 
[`aggregate.md#cache`](aggregate.md#cache)). The cache stores the viewer login 
and a map of `pr_number → (head_sha, triage_status)` so a re-run inside the 
same session skips the per-PR enrichment.
 
 A failure at step 1 is a **stop**. Steps 2 and 3 degrade with warnings.
 
@@ -151,7 +151,7 @@ A failure at step 1 is a **stop**. Steps 2 and 3 degrade 
with warnings.
 
 ## Step 1 — Fetch open PRs
 
-Use the query template in [`fetch.md#open-prs`](fetch.md) to get every open PR 
with the fields needed for classification (labels, `isDraft`, 
`authorAssociation`, `createdAt`, last commit `committedDate`, last 10 comments 
for the triage-marker scan).
+Use the query template in [`fetch.md#open-prs`](fetch.md#open-prs) to get 
every open PR with the fields needed for classification (labels, `isDraft`, 
`authorAssociation`, `createdAt`, last commit `committedDate`, last 10 comments 
for the triage-marker scan).
 
 Paginate until `pageInfo.hasNextPage == false`. Batch size of 50 is safe (the 
open-PR selection set is lighter than `pr-management-triage`'s — no 
`statusCheckRollup`, no `reviewThreads`, no `latestReviews`). For a 300-PR 
backlog that's six GraphQL calls.
 
@@ -163,7 +163,7 @@ For each open PR, determine:
 
 - `is_triaged_waiting` — viewer's (or any collaborator's) comment contains the 
`Pull Request quality criteria` marker, the comment post-dates the PR's last 
commit, AND the author has NOT commented after it.
 - `is_triaged_responded` — same marker found, but the author HAS commented 
after it.
-- `is_drafted_by_triager` — the PR was converted to draft by the viewer at or 
after the triage comment (from the `ConvertToDraftEvent` timeline, optional — 
see [`classify.md#drafted-by-triager`](classify.md) for the cheaper heuristic).
+- `is_drafted_by_triager` — the PR was converted to draft by the viewer at or 
after the triage comment (from the `ConvertToDraftEvent` timeline, optional — 
see [`classify.md#drafted-by-triager`](classify.md#drafted-by-triager) for the 
cheaper heuristic).
 - `last_author_interaction_at` — most recent `commit.committedDate` OR author 
comment `createdAt`, whichever is later.
 
 Cache these per `(pr_number, head_sha)` so a subsequent run skips the scan.
@@ -172,7 +172,7 @@ Cache these per `(pr_number, head_sha)` so a subsequent run 
skips the scan.
 
 ## Step 3 — Fetch closed / merged triaged PRs since cutoff
 
-The second table is a separate search. Fetch closed or merged PRs whose 
comment history contains the triage marker since the configured cutoff date. 
Use the template in [`fetch.md#closed-merged-triaged`](fetch.md).
+The second table is a separate search. Fetch closed or merged PRs whose 
comment history contains the triage marker since the configured cutoff date. 
Use the template in 
[`fetch.md#closed-merged-triaged-prs`](fetch.md#closed-merged-triaged-prs).
 
 Cutoff defaults to `today - 6 weeks`. The cutoff should be configurable 
because a maintainer asking "how did last week's sweep do" wants 
`since:today-7d`, while a monthly report wants `since:today-30d`.
 
@@ -182,7 +182,7 @@ Cutoff defaults to `today - 6 weeks`. The cutoff should be 
configurable because
 
 Group each PR by every `area:*` label it carries. A PR with `area:UI` and 
`area:scheduler` contributes to both groups. A PR with no `area:*` labels lands 
in a pseudo-area `(no area)`.
 
-Per area, compute the counters in [`aggregate.md#counters`](aggregate.md): 
total, drafts, non-drafts, contributors, triaged-waiting, triaged-responded, 
ready-for-review, drafted-by-triager, plus age-bucket histograms.
+Per area, compute the counters in 
[`aggregate.md#counters-per-area`](aggregate.md#counters-per-area): total, 
drafts, non-drafts, contributors, triaged-waiting, triaged-responded, 
ready-for-review, drafted-by-triager, plus age-bucket histograms.
 
 Also compute a `TOTAL` row where each PR is counted exactly once (NOT the sum 
of per-area counters — PRs with multiple `area:*` labels would double-count).
 
@@ -192,8 +192,8 @@ Also compute a `TOTAL` row where each PR is counted exactly 
once (NOT the sum of
 
 Pure function of the classified open-PR set. No network.
 
-1. Apply the **health rating** thresholds from 
[`aggregate.md#health-rating`](aggregate.md): each fired threshold is a "issue 
point". Map total points → `✅ Healthy` / `⚠️ Needs attention` / `🔥 Action 
needed`.
-2. Walk the **recommendation rules** from 
[`render.md#recommendation-rules`](render.md) in declared order. Each rule that 
fires produces one entry with `priority`, `icon`, `title`, `detail`, `action` 
(the exact slash command), and a count.
+1. Apply the **health rating** thresholds from 
[`aggregate.md#health-rating`](aggregate.md#health-rating): each fired 
threshold is a "issue point". Map total points → `✅ Healthy` / `⚠️ Needs 
attention` / `🔥 Action needed`.
+2. Walk the **recommendation rules** from 
[`render.md#recommendation-rules`](render.md#recommendation-rules) in declared 
order. Each rule that fires produces one entry with `priority`, `icon`, 
`title`, `detail`, `action` (an exact slash command, or `—` when no paste-clean 
command applies), and a count. `action` and `detail` are kept in separate 
columns so prose / parentheticals stay out of the slash command.
 3. The recommendation list is the input to the dashboard's "What needs 
attention" panel. If zero rules fire, surface the explicit "no urgent actions 
detected" panel — never leave the section empty.
 
 ---
@@ -204,7 +204,7 @@ Pure function of the closed/merged-since-cutoff PR set.
 
 For each of the last 6 weeks (rolling, anchored on the fetch-start `<now>`), 
bucket PRs by `closedAt` and count `merged` and `closed` separately. Also count 
the triaged-then-merged / triaged-then-closed / triaged-then-responded subsets 
— those are what feed the trend mini-stats below the velocity bars.
 
-See [`aggregate.md#weekly-velocity`](aggregate.md) for the exact bucket 
boundaries and the avg/peak summary computation.
+See [`aggregate.md#weekly-velocity`](aggregate.md#weekly-velocity) for the 
exact bucket boundaries and the avg/peak summary computation.
 
 ---
 
@@ -218,17 +218,17 @@ For each of the same six rolling weekly windows, compute:
 - `closed_total` — PR was closed/merged in the window (reuses the velocity 
buckets from Step 5b)
 - `net_delta = opened - closed_total`
 
-These per-week numbers feed the dashboard's "Opened vs closed momentum" line 
chart and the two-line "Net delta" summary below it. See 
[`aggregate.md#opened-vs-closed-weekly-buckets`](aggregate.md) for the exact 
spec.
+These per-week numbers feed the dashboard's "Opened vs closed momentum" line 
chart and the two-line "Net delta" summary below it. See 
[`aggregate.md#opened-vs-closed-weekly-buckets`](aggregate.md#opened-vs-closed-weekly-buckets)
 for the exact spec.
 
 ---
 
 ## Step 5d — Compute ready-for-review trend by top areas
 
-Needs one extra fetch (per [`fetch.md#ready-label-timeline`](fetch.md)): for 
each currently-`ready for maintainer review` PR, the timestamp of its most 
recent `LabeledEvent` adding that label. Aliased GraphQL, ~30 PRs per call.
+Needs one extra fetch (per 
[`fetch.md#ready-label-timeline`](fetch.md#ready-label-timeline)): for each 
currently-`ready for maintainer review` PR, the timestamp of its most recent 
`LabeledEvent` adding that label. Aliased GraphQL, ~30 PRs per call.
 
 Then for each top-pressure area (top 5 by Step 5f's score, filtered to areas 
with ≥ 3 currently-ready PRs), compute a 6-bucket cumulative count: 
`ready_count[a][w] = count of currently-ready PRs in area a where labeled_at <= 
w.end`.
 
-Feeds the dashboard's "Ready-for-review trend" multi-line chart. See 
[`aggregate.md#ready-for-review-trend-by-top-areas`](aggregate.md) for the 
exact spec and rendering rules.
+Feeds the dashboard's "Ready-for-review trend" multi-line chart. See 
[`aggregate.md#ready-for-review-trend-by-top-areas`](aggregate.md#ready-for-review-trend-by-top-areas)
 for the exact spec and rendering rules.
 
 ---
 
@@ -238,7 +238,7 @@ Pure function of the closed/merged-since-cutoff PR set 
(Step 3) — reuses the e
 
 For each weekly bucket, classify each closed PR into exactly one of four 
categories: `merged`, `closed-after-responded`, 
`closed-after-triage-no-response`, `closed-no-triage`. Sum per category per 
week.
 
-Feeds the dashboard's "Closed-by-triage-reason per week" stacked bar chart. 
See [`aggregate.md#closed-by-triage-reason-per-week`](aggregate.md) for the 
category definitions, colour map, and summary line.
+Feeds the dashboard's "Closed-by-triage-reason per week" stacked bar chart. 
See 
[`aggregate.md#closed-by-triage-reason-per-week`](aggregate.md#closed-by-triage-reason-per-week)
 for the category definitions, colour map, and summary line.
 
 ---
 
@@ -246,7 +246,7 @@ Feeds the dashboard's "Closed-by-triage-reason per week" 
stacked bar chart. See
 
 Pure function of the classified open-PR set.
 
-Per area, compute a **pressure score** = weighted sum of urgent PR conditions. 
The weights are defined in [`aggregate.md#pressure-score`](aggregate.md):
+Per area, compute a **pressure score** = weighted sum of urgent PR conditions. 
The weights are defined in 
[`aggregate.md#pressure-score`](aggregate.md#pressure-score):
 
 - untriaged non-draft, > 4 weeks old → 5 pts
 - untriaged non-draft, 1–4 weeks old → 3 pts
@@ -261,7 +261,7 @@ Sort areas by score descending; render the top 8 (filtering 
areas with < 3 contr
 
 ## Step 6 — Render dashboard
 
-Render the maintainer dashboard per the layout in 
[`render.md#dashboard-layout`](render.md):
+Render the maintainer dashboard per the layout in 
[`render.md#dashboard-layout`](render.md#dashboard-layout):
 
 1. **Context line** — repo, open count, cutoff, viewer, timestamp.
 2. **Hero cards (4)** — health rating, total open, ready count, 
untriaged-non-draft count.
diff --git a/.claude/skills/pr-management-stats/aggregate.md 
b/.claude/skills/pr-management-stats/aggregate.md
index 3d4aafc..1f729d2 100644
--- a/.claude/skills/pr-management-stats/aggregate.md
+++ b/.claude/skills/pr-management-stats/aggregate.md
@@ -126,7 +126,7 @@ The score is also used to bucket the area into a severity 
colour for the dashboa
 | 15–29 | medium | amber |
 | < 15 | low | grey |
 
-Tune the weights here in lockstep with the recommendation rules in 
[`render.md#recommendation-rules`](render.md) — they share the same notion of 
"what's urgent" and drift between the two would produce contradictory dashboard 
sections.
+Tune the weights here in lockstep with the recommendation rules in 
[`render.md#recommendation-rules`](render.md#recommendation-rules) — they share 
the same notion of "what's urgent" and drift between the two would produce 
contradictory dashboard sections.
 
 ---
 
@@ -149,7 +149,7 @@ Per window, count three metrics:
 |---|---|
 | `merged` | PR has `merged == true` AND `closedAt` falls in the window |
 | `closed` | PR has `merged == false` AND `closedAt` falls in the window |
-| `triaged_then_responded` | PR is triaged (per 
[`classify.md#triage-marker`](classify.md)) AND `closedAt` falls in the window 
AND the author commented after the triage comment |
+| `triaged_then_responded` | PR is triaged (per 
[`classify.md#triage-marker`](classify.md#triage-marker)) AND `closedAt` falls 
in the window AND the author commented after the triage comment |
 
 Render the bars oldest → newest (so the eye sweeps left-to-right matching 
natural time order). Each bar is a stacked `merged` (green) + `closed` (grey) 
segment, normalised to the maximum total in the 6-week window.
 
@@ -198,7 +198,7 @@ Net alone hides activity. A week with 100 opened and 100 
closed has the same net
 
 The dashboard's "Ready-for-review trend" panel shows the cumulative count of 
currently-`ready for maintainer review` PRs over the last 6 weeks, broken down 
by the top-N highest-pressure areas (default N = 5; areas with fewer than 3 
currently-ready PRs are excluded as noise).
 
-For each PR currently carrying the label, the **labeled-at timestamp** is the 
`createdAt` of the most recent `LabeledEvent` where `label.name == "ready for 
maintainer review"` from the PR's timeline (see 
[`fetch.md#ready-label-timeline`](fetch.md)).
+For each PR currently carrying the label, the **labeled-at timestamp** is the 
`createdAt` of the most recent `LabeledEvent` where `label.name == "ready for 
maintainer review"` from the PR's timeline (see 
[`fetch.md#ready-label-timeline`](fetch.md#ready-label-timeline)).
 
 For each top area `a` and each weekly bucket `w` in `0..5`:
 
diff --git a/.claude/skills/pr-management-stats/classify.md 
b/.claude/skills/pr-management-stats/classify.md
index 556b7b9..20361f4 100644
--- a/.claude/skills/pr-management-stats/classify.md
+++ b/.claude/skills/pr-management-stats/classify.md
@@ -150,7 +150,7 @@ Count the PR as responded if it has the marker AND an 
author comment between tri
 
 ## Pressure weight
 
-Per-PR helper used by [`aggregate.md#pressure-score`](aggregate.md). Returns 
the integer weight a single contributor PR contributes to its area's pressure 
score. Pure function of fields already populated above; no extra fetches.
+Per-PR helper used by 
[`aggregate.md#pressure-score`](aggregate.md#pressure-score). Returns the 
integer weight a single contributor PR contributes to its area's pressure 
score. Pure function of fields already populated above; no extra fetches.
 
 ```text
 def pressure_weight(pr) -> int:
@@ -169,7 +169,7 @@ def pressure_weight(pr) -> int:
     return 1
 ```text
 
-The first-match-wins ordering matters: a ready-for-review PR that's also a 
stale triaged draft scores 1 (ready takes precedence — once it has the label, 
the maintainer is the gate, not the author). Keep this function in lockstep 
with the table in [`aggregate.md#pressure-score`](aggregate.md).
+The first-match-wins ordering matters: a ready-for-review PR that's also a 
stale triaged draft scores 1 (ready takes precedence — once it has the label, 
the maintainer is the gate, not the author). Keep this function in lockstep 
with the table in [`aggregate.md#pressure-score`](aggregate.md#pressure-score).
 
 ---
 
diff --git a/.claude/skills/pr-management-stats/fetch.md 
b/.claude/skills/pr-management-stats/fetch.md
index bbe49d9..bc727aa 100644
--- a/.claude/skills/pr-management-stats/fetch.md
+++ b/.claude/skills/pr-management-stats/fetch.md
@@ -77,7 +77,7 @@ gh api graphql \
 
 ---
 
-## Closed / merged triaged PRs (since cutoff)
+## Closed-merged-triaged PRs
 
 Table 1 needs PRs that were closed or merged since the cutoff date AND had a 
triage comment posted at some point in their lifetime. Use GitHub's search with 
an `-is:open` filter plus a `closed:>=<cutoff>` date predicate, then 
client-side scan the `comments` subfield for the `Pull Request quality 
criteria` marker.
 
@@ -145,7 +145,7 @@ In this case the visible body contains no "Pull Request 
quality criteria" text a
 
 Raw bodies are slightly noisier (Markdown formatting characters) but the 
marker string is distinctive enough that false positives are not a concern on 
`<upstream>`.
 
-### Known limitation — GitHub search-index lag for closed-since counts
+### Known limitation
 
 Two GitHub-search behaviours conspire to make Table 1 hard to get right:
 
@@ -286,7 +286,7 @@ sequence (e.g. `\z`, `\e`), even `strict=False` fails with
 
 ## Ready-label timeline
 
-Needed for the dashboard's "Ready-for-review trend" chart (see 
[`aggregate.md#ready-for-review-trend-by-top-areas`](aggregate.md)). The 
open-PRs query above tells us *which* PRs currently carry the `ready for 
maintainer review` label, but not *when* the label was added. Without that 
timestamp the trend chart can't show growth.
+Needed for the dashboard's "Ready-for-review trend" chart (see 
[`aggregate.md#ready-for-review-trend-by-top-areas`](aggregate.md#ready-for-review-trend-by-top-areas)).
 The open-PRs query above tells us *which* PRs currently carry the `ready for 
maintainer review` label, but not *when* the label was added. Without that 
timestamp the trend chart can't show growth.
 
 Run an aliased GraphQL query, **30 PRs per call**, that fetches each PR's 
`LabeledEvent` timeline filtered to the relevant label:
 
diff --git a/.claude/skills/pr-management-stats/render.md 
b/.claude/skills/pr-management-stats/render.md
index e9981fb..3a5ecb2 100644
--- a/.claude/skills/pr-management-stats/render.md
+++ b/.claude/skills/pr-management-stats/render.md
@@ -39,7 +39,7 @@ Four equally-sized cards, each one big number with a 
sub-label:
 
 | Card | Big number | Sub-label | Colour rule |
 |---|---|---|---|
-| **Repo Health** | the rating label (`✅ Healthy` / `⚠️ Needs attention` / `🔥 
Action needed`) | "based on triage backlog + queue size" | green / amber / red, 
per [`aggregate.md#health-rating`](aggregate.md) |
+| **Repo Health** | the rating label (`✅ Healthy` / `⚠️ Needs attention` / `🔥 
Action needed`) | "based on triage backlog + queue size" | green / amber / red, 
per [`aggregate.md#health-rating`](aggregate.md#health-rating) |
 | **Open PRs (non-bot)** | total open count | `<contrib_count> from 
contributors · <collab_count> collaborator-authored` | blue (informational) |
 | **Ready for review** | `len(ready_open)` | `<pct>% of contributor queue` | 
green |
 | **Untriaged non-drafts** | `len(untriaged_nondraft)` | `<X> are >4 weeks 
old` | red if >0 are >4w, amber if total > 30, green otherwise |
@@ -48,7 +48,7 @@ Card layout is responsive: 4-column on wide screens, 2-column 
on narrow, 1-colum
 
 ### 3. What needs attention (action panel)
 
-A vertical list of action cards built from the recommendation rules in 
[`#recommendation-rules`](#recommendation-rules) below. Each card has a 
coloured left border (red = high, amber = medium, grey = low), an icon, a 
one-line title, a 1–2-line detail explanation, and a monospace `code` block 
holding the exact slash-command the maintainer can paste.
+A vertical list of action cards built from the recommendation rules in 
[`#recommendation-rules`](#recommendation-rules) below. Each card has a 
coloured left border (red = high, amber = medium, grey = low), an icon, a 
one-line title, a 1–2-line detail explanation, and (when applicable) a 
monospace `code` block holding the exact slash-command the maintainer can 
paste. When a rule's `action` is `—` (no paste-clean command applies), the card 
omits the code block and shows title + detail only.
 
 If zero rules fire, render a single low-priority card with a `✨` icon and the 
body "No urgent actions detected. Queue is in healthy shape — periodic 
/pr-management-triage when convenient." Never leave the section visually empty.
 
@@ -99,7 +99,7 @@ Title: **Ready-for-review trend (last 6 weeks, top areas)**
 
 An inline SVG line chart with one line per top-pressure area (default: top 5, 
filtered to areas with ≥ 3 currently-ready PRs). Each line is **cumulative**: 
at week W it shows the count of PRs that *currently* carry the `ready for 
maintainer review` label and were labelled on or before W.
 
-Each area's line uses its pressure-band colour (red, amber, or grey per 
[`aggregate.md#pressure-score`](aggregate.md)). Same SVG dimensions as the 
opened-vs-closed chart (720×220px). The chart legend in the top-left lists each 
area name with its line colour swatch.
+Each area's line uses its pressure-band colour (red, amber, or grey per 
[`aggregate.md#pressure-score`](aggregate.md#pressure-score)). Same SVG 
dimensions as the opened-vs-closed chart (720×220px). The chart legend in the 
top-left lists each area name with its line colour swatch.
 
 Below the chart, a per-area summary list:
 
@@ -138,7 +138,7 @@ A healthy week is mostly green with thin amber/red 
segments. A red-dominated wee
 
 Title: **Pressure by area** (with a sub-line *"Pressure score = weighted sum 
of untriaged-old PRs per area. Higher score = more maintainer attention 
needed."*)
 
-Up to 8 rows, sorted by pressure score descending (filtering areas with < 3 
contributor PRs). Each row is a horizontally-laid-out card with a coloured left 
border (red / amber / grey, severity bands per 
[`aggregate.md#pressure-score`](aggregate.md)) containing:
+Up to 8 rows, sorted by pressure score descending (filtering areas with < 3 
contributor PRs). Each row is a horizontally-laid-out card with a coloured left 
border (red / amber / grey, severity bands per 
[`aggregate.md#pressure-score`](aggregate.md#pressure-score)) containing:
 
 - area name (cyan, bold, e.g. `providers`)
 - one-line stat: `<contrib_total> contributor PRs · <red>untriaged_4w</red> 
>4w · <amber>untriaged_1_4w</amber> 1-4w · <grey>untriaged_recent</grey> recent 
· <green>ready_pending</green> ready for review`
@@ -179,26 +179,28 @@ A bordered panel at the bottom explaining all the 
colours, columns, and computed
 
 The "What needs attention" panel is built from this fixed rule set, evaluated 
in order. Each rule that fires produces one entry in the panel.
 
-| # | Trigger | Priority | Icon | Title template | Action template |
-|---|---|---|---|---|---|
-| 1 | `len(untriaged_old) > 0` (any contributor non-draft >4w) | high | 🔥 | 
`Triage <N> non-draft contributor PRs older than 4 weeks` | 
`/pr-management-triage all PR issues  (focus on >4w bucket)` |
-| 2 | `len(untriaged_old) == 0 AND len(untriaged_med) > 0` (1-4w bucket 
non-empty) | medium | 👀 | `Triage <N> non-draft PRs aged 1-4 weeks` | 
`/pr-management-triage all PR issues` |
-| 3 | `len(stale_triaged_drafts) > 0` (drafts triaged ≥ 7d ago, no reply) | 
medium | 🗑️ | `Close <N> stale-triaged drafts (≥7d, no response)` | 
`/pr-management-triage stale  → sweep 1a` |
-| 4 | `len(ready_open) >= 50` | high | 📥 | `<N> PRs labeled "ready for 
maintainer review"` | `/maintainer-review (filter: ready for maintainer 
review)` |
-| 5 | `20 <= len(ready_open) < 50` | medium | 📥 | `<N> PRs in "ready for 
maintainer review" queue` | same as rule 4 |
-| 6 | `len(responded_no_ready) > 0` (triaged + responded but not 
ready-for-review) | medium | 🔄 | `<N> triaged PRs have author responses 
awaiting re-triage` | `/pr-management-triage all PR issues  (will surface as 
mark-ready-with-ping)` |
-| 7 | top area's `untriaged_4w + untriaged_1_4w >= 5` | medium | 📍 | `Area 
"<area>" has <total> contributor PRs (<X> untriaged >4w)` | 
`/pr-management-triage label:area:<area>` |
-| 8 | `velocity_drop > 30` (last_wk total - this_wk total) | low | 📉 | `PR 
closure velocity dropped <N> this week` | `No immediate action — check next 
week.` |
-| 9 | top ready-trend area's growth in last 7d ≥ 10 PRs | low | 📈 | 
`Ready-for-review queue in "<area>" grew by <N> this week` | 
`/maintainer-review label:area:<area>` |
-| 10 | weekly closed-by-reason `closed_no_response > merged` for 2+ recent 
weeks | medium | 🧹 | `Stale-sweep is dominating closures (last 2 weeks: <N> 
sweep-close vs <M> merged)` | review `/pr-management-triage stale` cadence — 
too many PRs reaching the sweep |
+`Action` and `Detail` are separate columns by design: `Action` is a literal 
paste-clean slash-command the maintainer runs (or `—` when no command applies); 
`Detail` is the prose explanation that goes in the card body. Mixing prose into 
`Action` would make the slash-command non-paste-clean and re-introduce the 
editorialising the skill is supposed to avoid.
+
+| # | Trigger | Priority | Icon | Title template | Detail template | Action |
+|---|---|---|---|---|---|---|
+| 1 | `len(untriaged_old) > 0` (any contributor non-draft >4w) | high | 🔥 | 
`Triage <N> non-draft contributor PRs older than 4 weeks` | Focus on the >4w 
bucket — those are the ones rotting longest. | `/pr-management-triage all PR 
issues` |
+| 2 | `len(untriaged_old) == 0 AND len(untriaged_med) > 0` (1-4w bucket 
non-empty) | medium | 👀 | `Triage <N> non-draft PRs aged 1-4 weeks` | The 1–4w 
bucket is the queue's leading edge; staying on top of it stops PRs from rolling 
into >4w. | `/pr-management-triage all PR issues` |
+| 3 | `len(stale_triaged_drafts) > 0` (drafts triaged ≥ 7d ago, no reply) | 
medium | 🗑️ | `Close <N> stale-triaged drafts (≥7d, no response)` | Closure 
path lives under the `stale` flow (sweep step 1a). | `/pr-management-triage 
stale` |
+| 4 | `len(ready_open) >= 50` | high | 📥 | `<N> PRs labeled "ready for 
maintainer review"` | The `ready for maintainer review` queue is past the 
triage stage; it needs maintainer review attention, not triage. | 
`/pr-management-code-review ready` |
+| 5 | `20 <= len(ready_open) < 50` | medium | 📥 | `<N> PRs in "ready for 
maintainer review" queue` | Same trigger family as rule 4 — banded by queue 
size so the priority drops once the queue is comfortable. | 
`/pr-management-code-review ready` |
+| 6 | `len(responded_no_ready) > 0` (triaged + responded but not 
ready-for-review) | medium | 🔄 | `<N> triaged PRs have author responses 
awaiting re-triage` | These will surface as mark-ready-with-ping inside the 
regular triage sweep. | `/pr-management-triage all PR issues` |
+| 7 | top area's `untriaged_4w + untriaged_1_4w >= 5` | medium | 📍 | `Area 
"<area>" has <total> contributor PRs (<X> untriaged >4w)` | One area is 
dominating the untriaged queue; scoping a triage pass to it clears the bulk of 
the load. | `/pr-management-triage label:area:<area>` |
+| 8 | `velocity_drop > 30` (last_wk total - this_wk total) | low | 📉 | `PR 
closure velocity dropped <N> this week` | No immediate action — re-check next 
week to see if the drop persists or was a one-off. | — |
+| 9 | top ready-trend area's growth in last 7d ≥ 10 PRs | low | 📈 | 
`Ready-for-review queue in "<area>" grew by <N> this week` | Growth 
concentrated in one area suggests it'd benefit from a focused review pass. | 
`/pr-management-code-review label:area:<area>` |
+| 10 | weekly closed-by-reason `closed_no_response > merged` for 2+ recent 
weeks | medium | 🧹 | `Stale-sweep is dominating closures (last 2 weeks: <N> 
sweep-close vs <M> merged)` | Too many PRs are reaching the stale sweep — 
review the `/pr-management-triage stale` cadence and whether earlier-stage 
interventions (mark-ready, ping) are firing. | — |
 
 Rules 1 and 2 are **mutually exclusive** (only one fires depending on whether 
any >4w PRs exist). Rules 4 and 5 are **mutually exclusive** (banding on 
`ready_open` count). All other rules can fire independently.
 
 When adding a new rule:
 
 - Prefer count-based triggers over percentage-based ones (counts are easier to 
reason about for the maintainer).
-- The action template must be a literal slash-command the maintainer can paste 
— never instructions like "consider running …".
-- Detail string should explain *why* the rule fired in one sentence, plus what 
command will help.
+- The `Action` cell must be a literal slash-command the maintainer can paste, 
with no parentheticals, prose, or unicode arrows. If the rule has no 
paste-clean command (e.g. "wait and re-check"), set `Action` to `—` and put the 
explanation in `Detail`.
+- The `Detail` cell explains *why* the rule fired and any context the 
maintainer needs to act on it; this is where parentheticals and 
cross-references live.
 
 ---
 
@@ -264,7 +266,7 @@ Plain text, before the first hero card:
 
 Structure: `<repo> — <open_count> open PRs (non-bot) · closed/merged since 
<cutoff> · viewer @<login> · <now>`.
 
-If the closed-since counts came from the lagging search index (see 
[`fetch.md#known-limitation`](fetch.md)), prepend a one-line caveat before the 
hero cards:
+If the closed-since counts came from the lagging search index (see 
[`fetch.md#known-limitation`](fetch.md#known-limitation)), prepend a one-line 
caveat before the hero cards:
 
 ```text
 ⚠ Closed-PR table built from GitHub's free-text search of the quality-criteria 
marker. The index lags — older triaged+merged PRs are likely undercounted. Pass 
accurate-closed for the hybrid REST + GraphQL path.
@@ -297,7 +299,7 @@ Lives inside the second collapsed `<details>` block. Title: 
`Triaged PRs — Sti
 
 One row per area where `total > 0`, sorted by `total` descending. `(no area)` 
last. Append a bold **TOTAL** row.
 
-`Total` is a **reference-only** column — it counts every open PR in the area 
(collaborator + contributor alike). Every other numeric column is 
**contributor-only** (see [`aggregate.md#counters`](aggregate.md)). This keeps 
draft-rate, triage-rate, and response-rate percentages meaningful: collaborator 
PRs bypass the triage funnel, so including them in the denominators would 
systematically understate how much of the contributor queue is ready, drafted, 
responded, etc.
+`Total` is a **reference-only** column — it counts every open PR in the area 
(collaborator + contributor alike). Every other numeric column is 
**contributor-only** (see 
[`aggregate.md#counters-per-area`](aggregate.md#counters-per-area)). This keeps 
draft-rate, triage-rate, and response-rate percentages meaningful: collaborator 
PRs bypass the triage funnel, so including them in the denominators would 
systematically understate how much of the contributor queue is ready, drafted, 
responded, etc.
 
 | Column | Source | Denominator | Colour |
 |---|---|---|---|

Reply via email to