morning-recap
Recap merged PRs in a GitHub repo over the last N hours, skip ones the user authored or has already touched (any review state / comment / @-mention), and rank the rest by review priority.
The script filters server-side using GitHub's -involves:USER search qualifier — covers author, assignee, commenter, reviewer, and @-mentioned. Excluded PRs never come back over the wire.
Setup (one-time)
This skill is repo-agnostic but needs to know which repo to recap. Pick one:
Option A — set a default in your shell (recommended if you only watch one repo):
# ~/.zshrc or ~/.bashrc
export MORNING_RECAP_REPO="owner/repo"
Option B — pass it inline each time:
MORNING_RECAP_REPO=owner/repo /morning-recap 12
Option C — edit the default in run.sh (change the REPO default near the top).
You also need:
gh CLI installed and authenticated (gh auth status)
jq installed
The exclude-user defaults to your authenticated gh user; override with EXCLUDE_USER=someoneelse if needed.
Inputs
- Optional positional argument: lookback in hours (integer). Default
12.
Steps
Run the helper script. It does the GitHub queries, filters PRs you've touched, trims bodies, and returns compact JSON. Do not call gh directly — the script consolidates everything in one call.
bash "${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/skills}/morning-recap/run.sh" "${HOURS:-12}" 2>&1
If the skill is installed under ~/.claude/skills/morning-recap/, the absolute path works too. When loaded as a plugin, $CLAUDE_PLUGIN_ROOT points at the plugin's skill dir.
Stderr (one line) reports the window, the filters applied, and how many PRs came back. Stdout is a JSON array of the remaining PRs:
[
{
"number": 11652,
"title": "...",
"author": "yhpark",
"mergedAt": "...",
"url": "...",
"additions": 19,
"deletions": 4,
"changedFiles": 4,
"files": ["..."],
"body": "trimmed, comments stripped, capped at 500 chars"
}
]
Env overrides: MORNING_RECAP_REPO=... (or legacy REPO=...) and EXCLUDE_USER=....
Recap section — print a chronological markdown list:
## Merged in the last <N>h (<count> PRs — excludes ones you authored or already touched)
- [#1234](url) **Title** — @author — one-line summary
- …
- One-line summary derived from title + body. Be concrete (what changed), don't just rephrase the title.
- If body is empty/boilerplate after trimming, fall back to title alone.
- Never invent details that aren't in the title or body.
- If the JSON array is empty: print
No unreviewed PRs merged in the last <N>h. and stop.
- If the API returned exactly 100, mention the window may be truncated.
Review priority section — classify each PR High / Medium / Low using these heuristics. Lean on judgment; rules are signals, not a checklist.
High signals (any one is usually enough):
- Touches shared state stores (atoms, providers, query client config)
- Touches navigation root or tab navigators (
*Navigator.tsx, *ScreenStack*)
- Touches secure storage, MMKV/persistence, or react-query cache layer
- Performance-critical primitives: list virtualization, animation hooks, suspense boundaries, hooks consumed app-wide
- Large diff (>500 LOC) or >15 files changed
- Title/body mentions "refactor", "rendering cascade", "perf", "CPU", "stringify", "suspense", or removes a widely-used hook
Medium signals:
- Feature-area UI changes that touch a shared component used in >1 screen
- New animation logic, gesture handlers, or
useEffect with non-trivial deps in a hot path
- Inconsistent styling primitives (raw
View/Text/StyleSheet in a Tamagui/NativeWind-styled area)
- Diff 100–500 LOC
Low signals:
- Localized cosmetic / alignment / spacing fixes
- Single-file change under 100 LOC with no shared-state or list/animation files
- Title is
fix(...): center align …, fix padding, etc.
Adapt the heuristics to the target repo — these defaults assume a React Native app. For a backend or library repo, substitute the relevant hotspots (migrations, public API surface, auth, schema, etc.).
Print priorities sorted High → Medium → Low (drop the Low list if it's long, summarize):
## Review priority
### High
- [#NNNN] **Title** — concrete reason it's worth a look (1 line). Files: `path/a.ts`, `path/b.tsx` (+N more)
### Medium
- [#NNNN] **Title** — reason
### Low (skim)
- [#NNNN], [#NNNN] — single-file cosmetic fixes
The "why" line should name a concrete risk rather than restate the title. Cap file list at 3 paths + "(+N more)".
End with a one-line recommendation: "Top N to actually open: #X, #Y, #Z."
Notes
- The script consolidates all
gh calls into one — don't loop gh pr view per PR. If you need extra detail on a single PR, fetch only that one.
- Don't fetch full diffs — file paths + diff size + (trimmed) body is enough for a triage call.
- This is triage, not review — don't try to render verdicts on correctness.
- If the script errors with
gh auth issues, surface verbatim and stop.
- Heuristics are guidance: a small diff that swaps the root provider is High; a 600-line copy edit is Low. Use judgment.
Optional: scheduled daily recap
cron-runner.sh is a launchd-friendly wrapper that runs the recap headlessly, summarizes via claude -p, writes markdown to ~/Documents/Claude/morning-recap/YYYY-MM-DD.md, and fires a macOS notification.
It's personal-setup-flavored (hardcoded paths, macOS-only osascript, assumes the claude CLI is installed). Treat it as a starting point — copy it somewhere editable and adjust SKILL_DIR, OUT_DIR, MORNING_RECAP_REPO, and the schedule for your machine.
Example launchd plist snippet:
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/path/to/morning-recap/cron-runner.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>7</integer>
<key>Minute</key><integer>30</integer>
</dict>
<key>EnvironmentVariables</key>
<dict>
<key>MORNING_RECAP_REPO</key><string>owner/repo</string>
</dict>
1---2name: morning-recap3description: Summarize PRs merged in a GitHub repo over the last N hours (default 12), excluding ones you authored or have already touched, and rank what's worth reviewing. Use when user says "morning recap", "what did I miss", "what shipped overnight", or "what went on while I was sleeping".4---56# morning-recap78Recap merged PRs in a GitHub repo over the last N hours, skip ones the user authored or has already touched (any review state / comment / @-mention), and rank the rest by review priority.910The script filters server-side using GitHub's `-involves:USER` search qualifier — covers author, assignee, commenter, reviewer, and @-mentioned. Excluded PRs never come back over the wire.1112## Setup (one-time)1314This skill is repo-agnostic but needs to know **which repo** to recap. Pick one:1516**Option A — set a default in your shell** (recommended if you only watch one repo):1718```bash19# ~/.zshrc or ~/.bashrc20export MORNING_RECAP_REPO="owner/repo"21```2223**Option B — pass it inline each time**:2425```bash26MORNING_RECAP_REPO=owner/repo /morning-recap 1227```2829**Option C — edit the default** in `run.sh` (change the `REPO` default near the top).3031You also need:32- `gh` CLI installed and authenticated (`gh auth status`)33- `jq` installed3435The exclude-user defaults to your authenticated `gh` user; override with `EXCLUDE_USER=someoneelse` if needed.3637## Inputs3839- Optional positional argument: lookback in hours (integer). Default `12`.4041## Steps42431. Run the helper script. It does the GitHub queries, filters PRs you've touched, trims bodies, and returns compact JSON. **Do not call `gh` directly** — the script consolidates everything in one call.4445 ```bash46 bash "${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/skills}/morning-recap/run.sh" "${HOURS:-12}" 2>&147 ```4849 If the skill is installed under `~/.claude/skills/morning-recap/`, the absolute path works too. When loaded as a plugin, `$CLAUDE_PLUGIN_ROOT` points at the plugin's skill dir.5051 Stderr (one line) reports the window, the filters applied, and how many PRs came back. Stdout is a JSON array of the remaining PRs:5253 ```json54 [55 {56 "number": 11652,57 "title": "...",58 "author": "yhpark",59 "mergedAt": "...",60 "url": "...",61 "additions": 19,62 "deletions": 4,63 "changedFiles": 4,64 "files": ["..."],65 "body": "trimmed, comments stripped, capped at 500 chars"66 }67 ]68 ```6970 Env overrides: `MORNING_RECAP_REPO=...` (or legacy `REPO=...`) and `EXCLUDE_USER=...`.71722. **Recap section** — print a chronological markdown list:7374 ```75 ## Merged in the last <N>h (<count> PRs — excludes ones you authored or already touched)7677 - [#1234](url) **Title** — @author — one-line summary78 - …79 ```8081 - One-line summary derived from title + body. Be concrete (what changed), don't just rephrase the title.82 - If body is empty/boilerplate after trimming, fall back to title alone.83 - Never invent details that aren't in the title or body.84 - If the JSON array is empty: print `No unreviewed PRs merged in the last <N>h.` and stop.85 - If the API returned exactly 100, mention the window may be truncated.86873. **Review priority section** — classify each PR **High / Medium / Low** using these heuristics. Lean on judgment; rules are signals, not a checklist.8889 **High signals** (any one is usually enough):90 - Touches shared state stores (atoms, providers, query client config)91 - Touches navigation root or tab navigators (`*Navigator.tsx`, `*ScreenStack*`)92 - Touches secure storage, MMKV/persistence, or react-query cache layer93 - Performance-critical primitives: list virtualization, animation hooks, suspense boundaries, hooks consumed app-wide94 - Large diff (>500 LOC) or >15 files changed95 - Title/body mentions "refactor", "rendering cascade", "perf", "CPU", "stringify", "suspense", or removes a widely-used hook9697 **Medium signals**:98 - Feature-area UI changes that touch a shared component used in >1 screen99 - New animation logic, gesture handlers, or `useEffect` with non-trivial deps in a hot path100 - Inconsistent styling primitives (raw `View`/`Text`/`StyleSheet` in a Tamagui/NativeWind-styled area)101 - Diff 100–500 LOC102103 **Low signals**:104 - Localized cosmetic / alignment / spacing fixes105 - Single-file change under 100 LOC with no shared-state or list/animation files106 - Title is `fix(...): center align …`, `fix padding`, etc.107108 Adapt the heuristics to the target repo — these defaults assume a React Native app. For a backend or library repo, substitute the relevant hotspots (migrations, public API surface, auth, schema, etc.).1091104. Print priorities sorted High → Medium → Low (drop the Low list if it's long, summarize):111112 ```113 ## Review priority114115 ### High116 - [#NNNN] **Title** — concrete reason it's worth a look (1 line). Files: `path/a.ts`, `path/b.tsx` (+N more)117118 ### Medium119 - [#NNNN] **Title** — reason120121 ### Low (skim)122 - [#NNNN], [#NNNN] — single-file cosmetic fixes123 ```124125 The "why" line should name a concrete risk rather than restate the title. Cap file list at 3 paths + "(+N more)".1261275. End with a one-line recommendation: "Top N to actually open: #X, #Y, #Z."128129## Notes130131- The script consolidates all `gh` calls into one — don't loop `gh pr view` per PR. If you need extra detail on a single PR, fetch only that one.132- Don't fetch full diffs — file paths + diff size + (trimmed) body is enough for a triage call.133- This is triage, not review — don't try to render verdicts on correctness.134- If the script errors with `gh` auth issues, surface verbatim and stop.135- Heuristics are guidance: a small diff that swaps the root provider is High; a 600-line copy edit is Low. Use judgment.136137## Optional: scheduled daily recap138139`cron-runner.sh` is a launchd-friendly wrapper that runs the recap headlessly, summarizes via `claude -p`, writes markdown to `~/Documents/Claude/morning-recap/YYYY-MM-DD.md`, and fires a macOS notification.140141It's **personal-setup-flavored** (hardcoded paths, macOS-only `osascript`, assumes the `claude` CLI is installed). Treat it as a starting point — copy it somewhere editable and adjust `SKILL_DIR`, `OUT_DIR`, `MORNING_RECAP_REPO`, and the schedule for your machine.142143Example launchd plist snippet:144145```xml146<key>ProgramArguments</key>147<array>148 <string>/bin/bash</string>149 <string>/path/to/morning-recap/cron-runner.sh</string>150</array>151<key>StartCalendarInterval</key>152<dict>153 <key>Hour</key><integer>7</integer>154 <key>Minute</key><integer>30</integer>155</dict>156<key>EnvironmentVariables</key>157<dict>158 <key>MORNING_RECAP_REPO</key><string>owner/repo</string>159</dict>160```