| name | weekly-recap |
| description | Use when the user wants a weekly recap, "what shipped this week", a team digest, or an "in case you missed it" roundup — merged PRs + closed tickets + deploys formatted into one update for Slack or a changelog. Especially use it when a recap is recurring and should cover only the window since the last recap (no overlap, no gaps), and when the raw lists are noisy with bot PRs and duplicate PR/ticket pairs that need collapsing. |
weekly-recap
Overview
A weekly recap answers "what actually shipped since the last recap" — merged PRs, closed tickets, and deploys, formatted into one digestible update. The hard part is not the formatting; it's the window and the noise. If each recap doesn't start exactly where the last one ended, you get overlap (the same PR in two recaps) or a gap (a week silently missing). And the raw activity is full of bot PRs, automated dependency bumps, and the same work counted twice as a PR and a ticket.
This skill keeps an append-only log of prior recaps so every run covers a clean, non-overlapping window, then dedups and de-noises before formatting.
When to Use
Use this skill when the user is:
- Asking for a weekly recap / digest / "what shipped this week" / "ICYMI".
- Producing a changelog or release-notes-style roundup for a window.
- Setting up a recurring recap that should cover only the time since the last one.
Do NOT use this for: a single-day standup (use standup-post), a single release's notes tied to one tag/deploy, or forward-looking sprint planning. This skill summarizes a multi-day window of work that already shipped.
Setup (no config file — ask when unset)
This skill ships no config.json. The org/team/repos and the Slack destination it needs are collected at run time via the AskUserQuestion tool whenever they're unknown (e.g. which GitHub org/repos, which Linear team, and where to post). Present the user's recent repos/channels as options where you can. Keep the recap source-of-truth in the memory log (below), not a config file.
Memory (window-since-last-recap)
Durable recap history lives outside this skill dir at ${CLAUDE_PLUGIN_DATA}/recaps.log (the skill dir is wiped on upgrade; ${CLAUDE_PLUGIN_DATA} survives). It is append-only — one line per published recap:
{"ts":"2026-06-20T16:00:00Z","window_start":"2026-06-13T16:00:00Z","window_end":"2026-06-20T16:00:00Z","counts":{"prs":14,"tickets":9,"deploys":3}}
The window loop, every run:
- Read the last line of
recaps.log. Its window_end (UTC) is this run's window_start — exactly, so there is no overlap and no gap. If the file is missing/empty, default to the trailing 7 days and note it.
window_end = now (or the user-specified cutoff).
- Pull merged PRs, closed tickets, and deploys in
[window_start, window_end).
- De-noise and dedup (see Gotchas), format, publish.
- Append a new line with this run's
ts, window_start, window_end, and counts.
This is what makes consecutive recaps tile the timeline cleanly instead of guessing "the last 7 days" each time.
Typical Flow
- Resolve org/team/repos/destination (AskUserQuestion if unknown).
- Read last
recaps.log line → window_start = previous window_end (UTC).
- Gather in
[window_start, window_end):
- Merged PRs in the repos.
- Closed/Done tickets in the Linear team.
- Deploys (release tags / deploy events).
- Exclude bot/automated PRs and dedup across sources (see Gotchas).
- Format the recap — group by theme or by repo/area:
- Shipped — notable merged PRs + closed tickets, deduped.
- Deploys — what went to prod and when.
- By the numbers — counts (PRs merged, tickets closed, deploys).
- Publish to the chosen destination (or hand back a draft).
- Append the new
recaps.log line.
Gotchas
ALWAYS treat these as real, observed failure modes.
-
The window is [last recap's window_end, now) in UTC — not "the last 7 days". Chaining off the previous window_end is what guarantees consecutive recaps tile the timeline with no overlap and no gap. A rolling "last 7 days" run a day late double-reports a day's work; run a day early and a day vanishes forever. Use a half-open interval [start, end) so an item exactly on a boundary lands in exactly one recap, never both. Store/compare in UTC — a local-time boundary drifts an hour at DST.
-
Exclude bot and automated PRs. Dependabot/renovate bumps, release-please/changeset bot PRs, and auto-merge chores drown the real work. Filter by author (bot accounts) and by label/title conventions before counting. Keep a human-meaningful recap; a recap that's 80% "chore(deps): bump X" is noise.
-
The same work appears as a merged PR AND a closed ticket — collapse it. A PR that closes ENG-318 and the ENG-318 ticket flipping to Done are one shipped item. Dedup on the PR→ticket link (Fixes ENG-318, branch name, Linear reference) and report it once. Also dedup multiple PRs that close the same ticket into a single line under that ticket.
-
Append, never rewrite, recaps.log. The previous window_end is the only anchor for the next window. Editing or truncating the log corrupts the next recap's start. One new line per published recap.
-
A deploy is not a PR — count it separately, but attribute it. Multiple PRs can ride one deploy, and a deploy can be a rollback. Don't equate "PRs merged" with "things deployed"; list deploys as their own line and, where possible, note which PRs/tickets they carried.
-
Don't recap an empty window as a full one. If window_start is moments ago (someone re-ran it), there's nothing new — say "no activity since the last recap at " instead of emitting an empty-but-official-looking digest.
Files
${CLAUDE_PLUGIN_DATA}/recaps.log — runtime memory (NOT in this dir). Append-only, one line per published recap; each run's window_start is the prior run's window_end. Created on first run.