Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Personal PR inbox for LI.FI engineers — outgoing PRs (yours) and incoming review queue (others'). One dry-run dashboard sourced from a deterministic script, with Slack thread cross-referencing only where the ball-in-whose-court question is open. Use when the user says "check open PRs", "check my open PRs", "PR triage", "what PRs need me", "review queue", "where do my PRs stand", "/check-open-prs", "quick PR check" (quick mode), "refresh the PR dashboard" (refresh mode), or asks for a personal PR
.claude/skills/lifinance-check-open-prs/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 148% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 153% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 164% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 205% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 98% | 0% |
> Usage: /check-open-prs
All mechanical data collection lives in a deterministic script — never loop gh pr view / gh api / Slack calls per PR yourself. The model's job is only the judgment layer on top: Slack thread state for ambiguous PRs, what needs the user's attention, and suggested next actions.
The script is also directly usable from a terminal for zero tokens:
bashbunx tsx script/utils/check-open-prs.ts # human-readable dashboard bunx tsx script/utils/check-open-prs.ts --json # compact JSON (what this skill consumes) bunx tsx script/utils/check-open-prs.ts --quick # own PRs only, skips incoming queue
GitHub scope is env-overridable (LI.FI defaults baked in), so the script is org-agnostic without code edits:
PR_DASH_ORGS — comma list of owners to search for your own PRs (default lifinance,lifinance-tron).PR_DASH_INCOMING_REPOS — comma list of owner/repo whose PRs form your review queue (default lifinance/contracts,lifinance/contracts-tron).bashbunx tsx script/utils/check-open-prs.ts --json
(Add --quick for quick mode.) The script returns, per PR: repo/number/title/url, kind (own/incoming), bucket, draft, CI rollup + failing checks tagged review/audit vs restartable vs core-dev-gate, conflicts, reviewDecision, mergeStateStatus, last commit, last non-author human comment, my last review (myReview: {state, at, sha} — sha is the commit my review was submitted against, used by Phase 2D), and slackCheck: true where Slack thread state is needed to finish classification. Archived-repo PRs and stale incoming PRs are already excluded (listed under excluded).
Refresh mode ("refresh the dashboard"): just re-run the script — it's cheap. Never re-render from data fetched earlier in the conversation; a PR may have been replied to, merged, or re-pinged since. Stamp every dashboard with the script's asOf (convert to +07).
The script assigns each PR exactly one native bucket (the bucket field). Rows flagged slackCheck: true are _provisional_ until Phase 1.5 resolves them into the display sections rendered in Phase 1.6; every other bucket maps 1:1 to its section with no enrichment. If Phase 1.5 is skipped, a slackCheck PR renders under its raw native bucket name.
| Script native bucket | Needs Phase 1.5 (Slack)? | Phase 1.6 display section | | -------------------------------------------------------- | ------------------------------ | ----------------------------------------------------------- | | WAITING-ON-TEAM | yes — 48h cooldown split | 🔔 REMIND-DUE (≥48h) / ⏳ REMIND-RECENTLY-PINGED (<48h) | | WAITING-ON-ME | yes — confirm ball-in-court | 👀 YOUR-ACTION | | CI-RED | no | 🔴 CI-RED | | CONFLICTS | no | ⚔️ CONFLICTS | | APPROVED-BLOCKED | no | 🚧 APPROVED-BLOCKED | | READY-TO-MERGE | no | ✅ READY-TO-MERGE | | OWN-DRAFT/{READY-TO-FLIP,NEEDS-WORK,DORMANT,SYNC-PR} | no | 📝 OWN-DRAFTS | | STALE | no | 🧹 STALE | | INBOX-UNREVIEWED | no | 📥 INCOMING-UNREVIEWED | | MAYBE-REREVIEW | yes — confirm author hand-back | 🔁 INCOMING-REREVIEW (else suppressed) | | WAITING-ON-AUTHOR / WAITING-ON-OTHERS / DONE-BY-ME | no | suppressed counts (Phase 1.6 §6) |
--quick filters at the source — the script omits the incoming queue entirely (INBOX-UNREVIEWED / MAYBE-REREVIEW / WAITING-ON-AUTHOR / WAITING-ON-OTHERS / DONE-BY-ME are never collected), and the skill then renders only display groups 1–2. It is not post-filtering of the full JSON.
slackCheck: true PRs)Skip this entirely for PRs without the flag. For the flagged set:
#dev-sc-review (C088UJWC8PR) history (last 6 weeks) serves all contracts-repo PRs — match by PR URL. Fall back to slack_search_public (in:#dev-sc-review pull/<NUMBER>) only for PRs not in the scraped window. Only assert "not posted for review yet" when the scan window provably covers the PR (its created is inside the 6-week scrape and the fallback search returned nothing) — otherwise the absence is unconfirmed, so emit no Slack note rather than a false negative. Never word a note as "Posted today" (ambiguous with the Slack action): for a freshly-opened PR say "Created today", reserving "posted" for a confirmed #dev-sc-review message.slack_read_thread (response_format="detailed"), batched 5–8 per message in parallel. Paginate until reply_count is exhausted — the missed reply is usually the newest, which decides the bucket.<48h since that message → REMIND-RECENTLY-PINGED (cooldown), ≥48h → REMIND-DUE. Zero replies → apply the 48h rule to the parent post.MAYBE-REREVIEW PRs become INBOX-REREVIEW only if the PR author posted an explicit hand-back after the user's review (ready for re-review | PTAL | addressed | rebased | all comments addressed, case-insensitive) AND the last human reply isn't the user's. New commits alone are not a signal; a re-ping the user already answered is not a signal.Message TS of the actual last reply.slack_read_thread from THIS invocation.slackCheck: true; it never produces audit lines) as a visible line in the conversation, printed during Phase 1.5 immediately above the Phase 1.6 dashboard, one per PR that carried slackCheck. Mandatory format and fields: Audit: #<pr> last_reply_ts=<ISO Message TS> (by=<me|login>, <Δh> ago) replies=<read>/<reply_count> → <DISPLAY-BUCKET>. Each field must trace to a slack_read_thread result from THIS invocation: last_reply_ts is a real Message TS (never estimated — invariant 1), replies=<read>/<reply_count> proves pagination was exhausted (read must equal the thread's reply_count), and → <DISPLAY-BUCKET> is the resolved Phase 1.6 section. A Slack-classified PR rendered without its audit line — or with read < reply_count — is a bug.Header: As of <YYYY-MM-DD HH:MM +07> — full | quick | refresh. Sections in order, tables with PR / title / key timestamps / notes:
mergeStateStatus != CLEAN; render the script's note verbatim) · ✅ READY-TO-MERGE (only mergeStateStatus == CLEAN — actually mergeable now)WAITING-ON-OTHERS / WAITING-ON-AUTHOR / DONE-BY-ME: N each) for auditability.Each section's table must be fully closed (its last row followed by a blank line) before the next section heading — otherwise the following separator gets swallowed and the prior table's header bleeds into the next section. Group 6 (Excluded + suppressed counts) is a plain bullet/inline list, never a table.
In quick mode render only group 1–2 and note: "Quick mode — drafts, stale, and incoming queue not scanned."
End with the action menu and STOP — no actions without explicit approval:
textProceed? (a) Bump REMIND-DUE threads (e) Open INCOMING-UNREVIEWED for review (b) Investigate YOUR-ACTION PRs (f) Open INCOMING-REREVIEW with diff-since-my-review (c) Walk through OWN-DRAFTS (g) All of the above (d) Walk through STALE PRs (h) Cancel
Immediately before posting, re-read each target thread; skip (and say why) if the last message is no longer the user's or is now <48h old. Post one reply per PR via slack_send_message with thread_ts=<parent_ts> — exact text, do not paraphrase:
textfriendly bump <!subteam^S096X6MCB0C>
(The <!subteam^…> syntax is mandatory — plain @smartcontract_core doesn't notify.) S096X6MCB0C is LI.FI's @smartcontract_core subteam ID — org-specific, like the #dev-sc-review channel this skill targets; another org swaps in its own reviewer subteam ID, looked up once via Slack usergroups.list (or the group's "Copy mention" handle). Confirm each post with its permalink.
Per PR, one short prompt with the script's bucket and suggested next step; execute the chosen disposition:
gh pr ready <n> + invoke post-pr-for-review) (2) keep working (3) close with reason (4) investigate (Phase 3) (5) skip.gh pr ready --undo) (5) investigate (6) skip.gh pr diff <n> --commit-range <sha>..HEAD, where <sha> is the PR's myReview.sha from the script JSON — the commit my last review was submitted against). If myReview.sha is null (review predates the commit or it was detached), fall back to the full gh pr diff <n>. Quote the dev's hand-back message, list my unresolved inline comments; offer browser / sub-agent verify-comments-addressed / skip.Never auto-approve, auto-comment, or auto-request-changes — the skill routes; the human reviews.
One sub-agent per PR, in parallel, self-contained prompt: PR URL + pasted Slack thread + "pull review state via gh pr view --json reviewDecision,reviews,comments,statusCheckRollup,mergeable + inline comments, identify what the team is blocking on and ONE concrete next step, report <300 words". Consolidate, then offer: implement interactively / in parallel / list only / done.
gh pr checkout <n> → apply → lint/test → push → reply in the Slack thread linking the commit.
gh/Slack calls when the script (or a batch) covers it.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 15,205 | 3,911 | -74% | 1 | 1 | 0% | 2,569 | 3,415 | +33% | 0 | 0 | — |
case-02 | fail→fail | 9,129 | 4,606 | -50% | 1 | 1 | 0% | 1,612 | 3,443 | +114% | 0 | 0 | — |
case-03 | pass→fail | 15,877 | 3,806 | -76% | 1 | 1 | 0% | 2,747 | 3,398 | +24% | 0 | 0 | — |
case-04 | fail→pass | 9,296 | 3,691 | -60% | 1 | 1 | 0% | 1,529 | 3,794 | +148% | 0 | 0 | — |
case-05 | fail→pass | 11,282 | 6,420 | -43% | 1 | 1 | 0% | 1,717 | 4,350 | +153% | 0 | 0 | — |
case-06 | fail→pass | 9,469 | 3,873 | -59% | 1 | 1 | 0% | 1,468 | 3,876 | +164% | 0 | 0 | — |
case-07 | pass→pass | 3,963 | 3,190 | -20% | 1 | 1 | 0% | 715 | 3,794 | +431% | 0 | 0 | — |
case-08 | fail→pass | 8,357 | 5,062 | -39% | 1 | 1 | 0% | 1,353 | 4,124 | +205% | 0 | 0 | — |
case-09 | fail→pass | 11,050 | 2,025 | -82% | 1 | 1 | 0% | 1,798 | 3,553 | +98% | 0 | 0 | — |
case-10 | fail→pass | 9,691 | 3,098 | -68% | 1 | 1 | 0% | 1,469 | 3,717 | +153% | 0 | 0 | — |
case-11 | fail→pass | 9,275 | 2,562 | -72% | 1 | 1 | 0% | 1,558 | 3,679 | +136% | 0 | 0 | — |
case-12 | fail→fail | 6,848 | 3,683 | -46% | 1 | 1 | 0% | 1,045 | 3,784 | +262% | 0 | 0 | — |
case-13 | pass→pass | 4,608 | 2,364 | -49% | 1 | 1 | 0% | 648 | 3,621 | +459% | 0 | 0 | — |
case-14 | pass→pass | 5,800 | 2,497 | -57% | 1 | 1 | 0% | 966 | 3,650 | +278% | 0 | 0 | — |
case-15 | pass→pass | 6,421 | 3,311 | -48% | 1 | 1 | 0% | 1,116 | 3,803 | +241% | 0 | 0 | — |
case-16 | pass→fail | 11,686 | 2,040 | -83% | 1 | 1 | 0% | 1,859 | 3,516 | +89% | 0 | 0 | — |
case-17 | fail→fail | 9,817 | 2,810 | -71% | 1 | 1 | 0% | 1,501 | 3,747 | +150% | 0 | 0 | — |
case-18 | fail→fail | 10,555 | 2,428 | -77% | 1 | 1 | 0% | 1,611 | 3,609 | +124% | 0 | 0 | — |
case-19 | pass→pass | 9,757 | 2,783 | -71% | 1 | 1 | 0% | 1,699 | 3,664 | +116% | 0 | 0 | — |
case-20 | pass→fail | 7,322 | 5,681 | -22% | 1 | 1 | 0% | 1,334 | 3,544 | +166% | 0 | 0 | — |
case-21 | pass→fail | 3,344 | 8,341 | +149% | 1 | 1 | 0% | 293 | 3,729 | +1173% | 0 | 0 | — |
case-22 | pass→fail | 7,078 | 6,779 | -4% | 1 | 1 | 0% | 1,336 | 3,561 | +167% | 0 | 0 | — |
case-23 | fail→fail | 5,551 | 7,819 | +41% | 1 | 1 | 0% | 446 | 3,975 | +791% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 16 counted toward the lift figure. The other 7 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +9 percentage points is the difference between those two pass rates over the 16 comparable cases. 6 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.