Install
$ agentstack add skill-mthines-agent-skills-changelog ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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](./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.
| 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](#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](#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 &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
- Resolve window — compute
SINCEandUNTIL(UTC). Print them once. - Fetch PRs — see [Data sources](#data-sources); one
gh search prs
query, partitioned agent-side into merged and closed by state.
- Fetch Linear tickets — closed or updated in the window where the
current user is assignee or contributor.
- Classify each item by feature area (see [Feature grouping](#feature-grouping)).
- Render the template, sort features alphabetically (except a fixed
Other bucket which always sorts last), sort items within a feature by closedAt descending.
- 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:/ 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 . 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:
assigneeis the current viewer (useme/ current user filter), andupdatedAt >= SINCE, and- state is
completedorcanceled, 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.
| Signal | Bucket | | ------------------------------------------------------------ | ---------------------------- | | Conventional-commit scope in PR title (feat(): ...) | The ` (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:
- Branch name embedded in the PR (often
-...). - Explicit
Closes/Fixesin 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 mergesection. - 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](./templates/changelog.md) verbatim, wrapped in a 4-backtick outer fence.
- Feature buckets sort alphabetically; the
Otherbucket 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](./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=currentto--scope=allwhen cwd
is not a GitHub-linked git repo. Error explicitly.
- Mixing technical jargon into
--audience=generaloutput. The audience
switch is load-bearing — strip identifier-level terms ruthlessly.
- Keeping the
Closed without mergesection in--audience=general.
Abandoned branches are noise for a non-engineering audience.
Definition of done
- [ ]
SINCEandUNTILresolved (UTC) and printed once. - [ ]
gh search prs --author=@mequeried 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).
- [ ]
--scoperesolved (defaultcurrent); whencurrent, the
gh search prs query carries a repo:/ filter; when all, output is grouped by repo (H2) then feature (H3).
- [ ]
--audienceresolved (defaulttechnical); whengeneral, 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.mdand printed inside
a 4-backtick outer fence (so inner triple-backtick blocks don't escape).
- [ ] Feature buckets sorted alphabetically with
Otherlast. - [ ] Empty windows produce the empty-state message — never fabricated
items.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mthines
- Source: mthines/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.