Generates a personal markdown changelog of merged or closed pull requests authored by the current user and Linear tickets the user closed or worked on, over a configurable window (default 7 days), grouped by feature area (e.g. Dashboards, Agent0). Inputs sourced from `gh search prs --author=@me` and the Linear MCP. Use for weekly recaps, status updates, performance reviews, or end-of-sprint summaries. Triggers on "changelog", "what have I done", "weekly summary", "my recent work", "recap my week", "/changelog".
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Generate a markdown changelog of the current user's recent work — merged
or closed pull requests, plus Linear tickets they closed or worked on —
grouped by feature area. Render the result against the template in
templates/changelog.md and print it to the
chat.
Three orthogonal arguments. Flag order is free. days is positional.
days (positional, optional)Controls the window in days.
| Input | Resolved window |
|---|---|
| (none) | 7 days, ending today (UTC) |
14 | 14 days, ending today |
30 | 30 days, ending today |
| Any non-integer | Reject with an error; do not assume a value |
--scope=current|all (default: current)current (DEFAULT) — restrict PRs to the GitHub repo of the current
working directory, resolved via
gh repo view --json nameWithOwner -q .nameWithOwner. Linear tickets
are not narrowed (Linear has no repo concept) — say so in the
rendered output.all — query every repo the user authored PRs in. Output is grouped
by repo first, then by feature bucket inside each repo (see
Output rules).If --scope=current is passed but the cwd is not a GitHub-linked git
repo, error and exit 1 — do not silently fall back to all. The error
must read:
Current directory is not a GitHub-linked git repo. Re-run with --scope=all or cd into a repo.
--audience=technical|general (default: technical)technical (DEFAULT) — existing rendering: conventional-commit scopes
as buckets, identifier-level summaries, full PR numbers inline.general — re-render for a non-engineering audience (see
Audience modes).Parse flags in any order; the first non-flag positional integer is
days. Unknown flags must error. Compute the window once at start with:
DAYS=7
SCOPE=current
AUDIENCE=technical
# Slash-command skills receive the raw argument string in $ARGUMENTS,
# not positional parameters — split it on whitespace before iterating.
read -r -a ARGS <<< "$ARGUMENTS"
for arg in "${ARGS[@]}"; do
case "$arg" in
--scope=current|--scope=all) SCOPE="${arg#--scope=}" ;;
--audience=technical|--audience=general) AUDIENCE="${arg#--audience=}" ;;
--*) echo "Unknown flag: $arg" >&2; exit 1 ;;
*) DAYS="$arg" ;;
esac
done
[[ "$DAYS" =~ ^[1-9][0-9]*$ ]] || {
echo "Argument must be a positive integer number of days (got \"$DAYS\")." >&2
exit 1
}
SINCE="$(date -u -v-"${DAYS}"d +%Y-%m-%d 2>/dev/null \
|| date -u --date="${DAYS} days ago" +%Y-%m-%d)" # BSD then GNU
UNTIL="$(date -u +%Y-%m-%d)"
case "$SCOPE" in
current)
REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)" || {
echo "Current directory is not a GitHub-linked git repo. Re-run with --scope=all or cd into a repo." >&2
exit 1
}
SCOPE_FILTER="repo:${REPO}"
;;
all)
SCOPE_FILTER=""
;;
esacgh search treats --merged-at and --closed date ranges as inclusive
on both ends. Document the inclusive semantics in the rendered output so
back-to-back invocations on consecutive weeks do not silently double-count
the boundary day.
SINCE and UNTIL (UTC). Print them once.gh search prs
query, partitioned agent-side into merged and closed by state.Other bucket which always sorts last), sort items within a feature by
closedAt descending.````) so the user can copy it cleanly without the
inner triple-backtick blocks (links, headings, inline code in PR
titles) breaking the fence.Do not write the output to a file unless the user explicitly asks.
Use gh search prs (cross-repo, scoped to the current user as author).
The CLI is required.
gh search prs exposes state, closedAt, createdAt, and updatedAt
in its JSON output — but not mergedAt. Partition merged vs.
closed-not-merged agent-side using the state field (merged or
closed). Run a single query, then split:
LIMIT=100
gh search prs \
--author=@me \
--state=closed \
--closed="${SINCE}..${UNTIL}" \
${SCOPE_FILTER:+$SCOPE_FILTER} \
--limit "${LIMIT}" \
--json title,number,url,state,closedAt,repository,labels,bodyWhen $SCOPE_FILTER is set (scope=current), gh search prs accepts the
repo:<owner>/<name> qualifier as a free-form search token alongside
the typed flags.
In the agent:
state == "merged" → "Shipped" section.state == "closed" → "Closed without merge" section.Truncation guard: if the result set length equals LIMIT, prepend a
> Warning: PR results truncated at ${LIMIT}. Narrow the window with /changelog <smaller-days>. line above the rendered output. Do not
silently drop work.
If gh is missing or unauthenticated, print the install / auth hint and
continue with Linear-only data — do not fail the whole skill.
Use whichever Linear MCP server is connected in the active environment.
Tool names vary by server (mcp__claude_ai_Linear__list_issues,
linear__search_issues, mcp__linear__list_issues, …) — resolve the
list-issues and get-issue tools at runtime from the available-tools list,
do not hard-code the namespace.
Query for issues where:
assignee is the current viewer (use me / current user filter), andupdatedAt >= SINCE, andcompleted or canceled, or the issue had activity in the
window (comment / status change).If no Linear MCP tool is available, print a one-line notice and proceed with PR-only data — do not fail.
Bucket each PR and ticket into a feature area. Apply this lookup in order; the first match wins.
| Signal | Bucket |
|---|---|
Conventional-commit scope in PR title (feat(<scope>): ...) | The <scope> (Title-cased) |
| Linear project name | The project name |
| Linear team label that names a product area | The label (Title-cased) |
PR label matching area:*, feature:*, scope:* | The suffix (Title-cased) |
| Repository name (single-repo / monorepo apps) | The repo or top-level path |
| Top-level directory of changed files (monorepos) | The directory (Title-cased) |
| No signal | Other |
Title-casing rule: keep brand names verbatim (Agent0, OTel,
Dash0); only Title-case generic scopes (dashboards → Dashboards,
auth → Auth). Maintain a small allow-list of brand spellings inferred
from the data — do not invent capitalisation.
Two items in different sources (a PR and a Linear ticket) that describe the same work should appear under the same bucket. Cross-reference by:
<TICKET-ID>-...).Closes <TICKET-ID> / Fixes <TICKET-ID> in the PR body.When the PR body field is empty (private-repo body the authenticated
user cannot read), fall back to the branch-name heuristic only — do not
drop the entry.
When a PR closes a ticket, render the PR as the primary entry and append the ticket ID inline (see template). Do not double-list.
Known boundary: --author=@me returns PRs the user opened.
Co-authored-by attributions are not captured. This is intentional for a
personal recap, but state it so the empty-looking week is not a bug.
scope=current (default): feature buckets at H2 (existing behavior).scope=all: repo at H2, feature buckets at H3 inside each
repo. Repos sorted alphabetically; an Other repo bucket (for
unattributable items) sorts last. Feature buckets inside each repo
follow the existing alphabetical rule, with Other last.technical (default) — unchanged. All existing rules in this
section apply.
general — re-render the same data for a non-engineering reader
(use case: weekly all-hands recap). Apply these substitutions:
perf → Performance, fix → Reliability,
feat → New features, chore / docs / refactor → Polish. Items
without a type are placed by judgement.memoize, parsedExpression, useEventSource,
transformBody, rAF, jsonHash, isEqual, etc.). Lead with the
outcome a non-engineer would notice.Closed without merge section.([#123]({{PR_URL}})) format as technical mode. Do not emit a
## References footer.scope=all: repos are H2, themes are H3 inside
each repo. Repos ordered by PR count descending (largest contribution
first).templates/changelog.md verbatim, wrapped
in a 4-backtick outer fence.Other bucket always sorts
last.#123), the repo if cross-repo, and the
Linear ticket ID where applicable.{{ONE_PARAGRAPH_SUMMARY}}
set to No activity in this window., {{TOTAL_PRS}} and
{{TOTAL_TICKETS}} set to 0, and all feature buckets omitted. Do
not fabricate work, and do not stretch the window.The literal output template lives in
templates/changelog.md. The user edits that
file to adjust shape, headings, or summary line; the skill never edits the
template itself.
/changelogResolves SINCE=2026-05-04, UNTIL=2026-05-11, --scope=current,
--audience=technical. Fetches the user's PRs in the current repo
only and renders the technical template inside a fenced block.
/changelog 30Defaults to --scope=current --audience=technical. 30-day window for
the repo of the current directory.
/changelog 7 --scope=all --audience=generalCross-repo, general-audience rendering. Repos are H2, themes
(Performance / Reliability / New features / Polish) are H3
inside each. Every bullet ends with a short inline ([#123](...))
link — no separate references footer. Repos ordered by PR count
descending.
/changelog 14 --scope=current14-day window for the current repo, technical rendering. Equivalent to the default behavior with a wider window.
/changelog last-monthReject: Argument must be a positive integer number of days (got "last-month").
Do not silently coerce to 7.
--scope=current outside a GitHub-linked repoRe-run with --scope=all or cd into a repo. The skill must not
silently fall back.
agent0 → Agent 0, dash0 → Dash 0).
Keep brand spellings verbatim.--scope=current to --scope=all when cwd
is not a GitHub-linked git repo. Error explicitly.--audience=general output. The audience
switch is load-bearing — strip identifier-level terms ruthlessly.Closed without merge section in --audience=general.
Abandoned branches are noise for a non-engineering audience.SINCE and UNTIL resolved (UTC) and printed once.gh search prs --author=@me queried for both merged and closed sets
(or a one-line skip notice printed if gh is missing).--scope resolved (default current); when current, the
gh search prs query carries a repo:<owner>/<name> filter;
when all, output is grouped by repo (H2) then feature (H3).--audience resolved (default technical); when general, theme
buckets / user-visible impact / inline-PR-links / no-Closed-section
rules applied and the jargon strip-list enforced.templates/changelog.md and printed inside
a 4-backtick outer fence (so inner triple-backtick blocks don't
escape).Other last.39b3f44
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.