CtrlK
BlogDocsLog inGet started
Tessl Logo

changelog

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

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

SKILL.md
Quality
Evals
Security

Changelog

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.

Arguments

Three orthogonal arguments. Flag order is free. days is positional.

days (positional, optional)

Controls the window in days.

InputResolved window
(none)7 days, ending today (UTC)
1414 days, ending today
3030 days, ending today
Any non-integerReject 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).

Argument parsing

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=""
    ;;
esac

gh 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.

Workflow

  1. Resolve window — compute SINCE and UNTIL (UTC). Print them once.
  2. Fetch PRs — see Data sources; one gh search prs query, partitioned agent-side into merged and closed by state.
  3. Fetch Linear tickets — closed or updated in the window where the current user is assignee or contributor.
  4. Classify each item by feature area (see Feature grouping).
  5. Render the template, sort features alphabetically (except a fixed Other bucket which always sorts last), sort items within a feature by closedAt descending.
  6. Print the rendered markdown to the chat. Wrap it in a 4-backtick outer fence (````) 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.

Data sources

Pull requests — GitHub

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,body

When $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.

Linear tickets — MCP

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), and
  • updatedAt >= SINCE, and
  • state is completed 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.

Feature grouping

Bucket each PR and ticket into a feature area. Apply this lookup in order; the first match wins.

SignalBucket
Conventional-commit scope in PR title (feat(<scope>): ...)The <scope> (Title-cased)
Linear project nameThe project name
Linear team label that names a product areaThe 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 signalOther

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:

  • Branch name embedded in the PR (often <TICKET-ID>-...).
  • Explicit 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.

Output rules

Scope-aware structure

  • 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.

Audience modes

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:

  • Theme buckets replace feature buckets: Performance, Reliability & bug fixes, New features, Polish & UX. Map by conventional-commit type: perf → Performance, fix → Reliability, feat → New features, chore / docs / refactor → Polish. Items without a type are placed by judgement.
  • User-visible impact replaces identifier-level summaries. Strip jargon (memoize, parsedExpression, useEventSource, transformBody, rAF, jsonHash, isEqual, etc.). Lead with the outcome a non-engineer would notice.
  • Omit the Closed without merge section.
  • Keep a short inline link at the end of every bullet using the same ([#123]({{PR_URL}})) format as technical mode. Do not emit a ## References footer.
  • The Summary paragraph is 2–3 plain-English sentences focused on user-visible change for the week.
  • Skip the inclusive-date footnote.
  • When combined with scope=all: repos are H2, themes are H3 inside each repo. Repos ordered by PR count descending (largest contribution first).

General rules

  • Always emit a single Markdown block — render against templates/changelog.md verbatim, wrapped in a 4-backtick outer fence.
  • Feature buckets sort alphabetically; the Other bucket always sorts last.
  • Within a bucket, sort by merged / closed date descending.
  • Each line cites the PR number (#123), the repo if cross-repo, and the Linear ticket ID where applicable.
  • One-line summary per item — strip emoji from titles; keep imperative voice; drop trailing punctuation.
  • Empty window: render the template with {{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.

Template

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.

Examples

Good — invoked with default window

/changelog

Resolves 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.

Good — invoked with a custom window

/changelog 30

Defaults to --scope=current --audience=technical. 30-day window for the repo of the current directory.

Good — explicit org-wide weekly summary

/changelog 7 --scope=all --audience=general

Cross-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.

Good — explicit single-repo technical recap

/changelog 14 --scope=current

14-day window for the current repo, technical rendering. Equivalent to the default behavior with a wider window.

Bad — invoked with a non-integer

/changelog last-month

Reject: Argument must be a positive integer number of days (got "last-month"). Do not silently coerce to 7.

Bad — --scope=current outside a GitHub-linked repo

Re-run with --scope=all or cd into a repo. The skill must not silently fall back.

Anti-patterns

  • Fabricating items because the window is empty — print the empty-state message instead.
  • Re-Title-casing brand names (agent0 → Agent 0, dash0 → Dash 0). Keep brand spellings verbatim.
  • Listing the same work twice when a PR closes a Linear ticket — merge into one entry.
  • Writing the output to a file by default. Print to chat unless asked.
  • Stretching the window to "fill" a short list. The window is the user's contract.
  • Silently falling back from --scope=current to --scope=all when cwd is not a GitHub-linked git repo. Error explicitly.
  • Mixing technical jargon into --audience=general output. The audience switch is load-bearing — strip identifier-level terms ruthlessly.
  • Keeping the Closed without merge section in --audience=general. Abandoned branches are noise for a non-engineering audience.

Definition of done

  • 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).
  • Linear MCP queried for the current viewer's tickets in the window (or a one-line skip notice printed if MCP is unavailable).
  • --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.
  • Each item bucketed by feature using the lookup table above.
  • PR / ticket pairs merged into a single entry where one closes the other.
  • Output rendered against templates/changelog.md and printed inside a 4-backtick outer fence (so inner triple-backtick blocks don't escape).
  • Feature buckets sorted alphabetically with Other last.
  • Empty windows produce the empty-state message — never fabricated items.
Repository
mthines/agent-skills
Last updated
First committed

Is this your skill?

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.