Skip to content
Back to skills

Changelog

ASecurity

Generate a Keep a Changelog entry from git log since the last tag

  • 21 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 3, 2026
ai-agentsgobashnodegitapisecurityperformancedocumentation

Works with

  • api
  • mcp

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add bloknayrb/tandem --skill changelog --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Changelog?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Changelog
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/bloknayrb-changelog/badge)](https://www.skillsdirectory.com/skills/bloknayrb-changelog)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: changelog
description: Generate a Keep a Changelog entry from git log since the last tag
disable-model-invocation: true
---

# Generate Changelog Entry

Generate a formatted CHANGELOG entry from commits since the last release tag.

## Steps

1. Find the last release tag:

```bash
git describe --tags --abbrev=0
```

2. List commits since that tag:

```bash
git log $(git describe --tags --abbrev=0)..HEAD --oneline --no-merges
```

3. **Read the diff of each change, not its subject line.** `git show <sha> -- <path>` for
   anything you are about to describe. The commit prefix tells you where an entry goes; only
   the diff tells you what it says. v0.20.0 shipped five defective entries composed from PR
   titles — the worst promised a detection capability that did not exist. Where a change
   landed across several commits (a fix, then its review corrections), describe the **final**
   state: `git log --oneline v<prev>..HEAD -- <path>` shows whether what you just read was
   later amended, and `git show v<prev>:<file>` settles "is this actually new?"

4. Group into Keep a Changelog categories based on conventional commit prefixes:
   - `feat(...)` → **Added** (new features) or **Changed** (enhancements to existing)
   - `fix(...)` → **Fixed**
   - `refactor(...)` → **Changed**
   - `docs(...)` → **Documentation**
   - `test(...)` → **Tests**
   - `chore(...)` → **Maintenance**
   - `perf(...)` → **Performance**
   - Security-related commits → **Security**

5. **Write the entries** (see *Writing the prose* below), ordered within each section by user
   impact, most impactful first. Not by issue number, not by merge order, not by subsystem. A
   reader who stops after the first bullet of each section should still have the release's
   substance.

6. **Then write a `### What's New` section and place it first**, above every other subsection.
   It is written last and read first — you cannot summarise entries you have not drafted.

   - 3–6 bullets, most impactful first, one line each (≤20 words).
   - No PR numbers, no subsystem names, no file paths, no bounds — those live in the entries.
   - Each bullet is derived from an entry's bold clause. **Re-derive the bullet whenever you
     edit that entry**; nothing checks that the two agree, so this is the only thing keeping
     the summary from drifting away from the detail it summarises.
   - If the release is mostly internal, one bullet says so plainly.

   **Lead with 1–2 sentences of prose above the bullets only when a genuinely large feature or
   change headlines the release** — something that changes how Tandem is used or how a release
   should be approached, not a good bug fix. Otherwise go straight to the bullets.

7. Format as a `## [Unreleased]` section. Each entry is:
   - Bold summary with PR number: `- **Description** (#N)`
   - Related commits grouped into a single entry
   - Imperative mood ("Add", "Fix", "Remove" — not "Added", "Fixes")

8. Output the formatted block for the user to review and paste into `CHANGELOG.md`.

## Writing the prose

Entries earned their length historically by carrying honest qualifications, and they also
accumulated a lot of narration. Tighten the narration; keep the qualifications.

**The shape is the discipline, not a word count.** An entry is:

1. A bold clause: what is different now.
2. One sentence of consequence: what the user saw, or would have seen, when it was wrong.
3. Zero or more **bounds**, one sentence each — see below.

Nothing else. If a sentence is doing a fourth job, it belongs in the PR. **120 words is a
runaway backstop, not a target**, and bounds do not count toward it — an entry that needs
three honest bounds gets three sentences and is still correct. Draft the entry, then cut it to
shape; do not aim short and stop early, because the first draft is where you find out what
actually mattered.

### Bounds

A bound is a sentence that changes what the reader would **do or believe**. Label it
**Bound:** (or a more specific label when it is not a scope limit — **Unverified:**, **Not
exercised before release:**). Real fixes often carry more than one, and each gets its own
sentence; do not merge two bounds about two different populations of files into one, and do
not pick a winner between them.

Keep a bound when it answers any of:

- Am I affected? — including *when* the fault existed and what produces it, where that is the
  test for "are my files affected". ("A file Word wrote was never affected; it needs a `.docx`
  built by something else.")
- Will something visibly change for me on upgrade?
- Is there residue I should clean up?
- Is this narrower than it sounds? — say what it does **not** cover, especially when the
  obvious reading is that it now does.
- Was this actually verified? — an unexercised path, or one smoke-tested on one platform, says
  so.

Drop a hedge that only qualifies a mechanism the entry no longer describes.

### Cut

- **Mechanism archaeology.** How the bug worked, which field held the wrong value, which two
  code paths disagreed, which one was written first. That is what the PR is for. Duration is
  *not* archaeology when it is the "am I affected" test — see Bounds.
- **Self-narration.** "Two changes, neither of which alters…", "This was the more serious of
  the two", "found by a test written to check the opposite."
- **Reassurance about what was not affected**, when nobody would have assumed otherwise. When
  they would, it is a bound: one sentence, not a paragraph.
- **Discovery attribution**, unless it is a severity signal — "not caught by any scanner" tells
  a security-minded reader that sibling instances may be undiscovered, and stays.
- Hedging stacks ("could potentially sometimes"), and "the system" where "Tandem" is meant.

Voice: active; present tense for current behaviour, past for what it used to do.

### Worked example — an ordinary fix

Before (247 words):

> - **Accepting a suggestion that came from a Word comment now marks that comment resolved in
>   the `.docx`.** When you open a `.docx`, its reviewer comments come in as personal notes;
>   send one to Claude, take Claude's suggested wording, accept it, and `tandem_applyChanges`
>   writes the edit back as a tracked change. It was supposed to tick the original Word comment
>   off as done in the same pass, and never did — not for some comments, for all of them.
>   Tandem was working out which Word comment a suggestion came from by reading its own
>   internal id, in a shape those ids stopped using four months ago, twelve days after the
>   resolving step was written, so the answer was always "no comment" and the resolving step
>   quietly did nothing. Reopening the file in Word showed every comment still outstanding,
>   including the ones you had just addressed. Tandem now reads the original comment number off
>   the note itself, which is where it has been stored all along.

After (44 words):

> - **Accepting a suggestion from a Word comment now resolves that comment in the `.docx`
>   (#1234).** `tandem_applyChanges` wrote the tracked change but never ticked the original
>   comment off, so reopening in Word showed every comment still outstanding — including ones
>   you had just addressed.

### Worked example — a security entry with two bounds

This is the shape that goes wrong. The entry compresses; the bounds do not.

> - **Claude can no longer choose the name of a file Tandem writes for you — only the folder
>   (#1234).** The annotation export and the `.docx`-to-Markdown conversion both took a
>   caller-supplied filename, so a session talked into it could drop a `CLAUDE.md` into a
>   project that had none — a file every future session there loads as instructions. Exports
>   must now end `.annotations.md` or `.annotations.json`, and a conversion takes a folder,
>   naming the file after the document. **Bound:** this narrows rather than closes — renaming
>   your document first still reaches a chosen name, but that changes the tab in front of you.
>   **Bound:** nothing in Tandem's own interface passed these values, so no workflow of yours
>   changes.

## Important

- Do NOT write directly to CHANGELOG.md — output the block for editorial review
- Check the existing CHANGELOG.md for **formatting** style (indentation, heading levels, PR
  references). Do **not** match the length of pre-2026-09 entries; they predate the shape above.
- If there's already an `[Unreleased]` section, show what to append, not a replacement
- Omit empty categories (don't show "### Security" if there are no security commits)
- **Never retighten an already-released entry.** Three doc-claim tests grep CHANGELOG prose for
  exact phrasing inside historical sections — `tests/docs/wake-availability-claims.test.ts`,
  `tests/docs/monitor-arming-claims.test.ts`, `tests/docs/native-theme-claims.test.ts` — so a
  tidy-up of old versions turns `check` red for reasons the diff will not explain. Shipped
  entries are also a record of what users were told. Editorial cleanup is forward-only.
- **List spacing is load-bearing.** `CHANGELOG.md` has a byte-identical `serializeMdast`
  round-trip golden test (`tests/server/file-io/markdown-escaping.test.ts`). The serializer
  decides tight-vs-loose **per list node** (`spread` on the list, not on the item): one
  multi-paragraph bullet makes every bullet in that list loose. So spacing is uniform within a
  section, and a single entry spilling into a second paragraph forces blank lines between all
  of that section's entries. One-paragraph entries keep every list tight. `### What's New` is
  its own list and does not propagate. Run that test after editing, not at the end.
- **The in-app View Changelog surface renders this file** as an ordinary read-only document,
  scrolled linearly. An HTML comment is invisible there, so caveats go in the prose or they
  reach nobody — and a reader meets each `What's New` bullet again in full a few inches later,
  which is why the bullets stay one short line.

## Releasing

See `.claude/skills/release/SKILL.md` for the full release sequence (version bump across all
six surfaces, tag, GitHub Release publish, smoke checklist).

## Conventions

Going forward, changelog entries follow [ADR-038](../../../docs/decisions.md#adr-038-mcp-first-integration-policy-claude-as-default-integration) framing — write "your AI" / "the AI" generically; use "Claude" as the concrete example when a feature is Claude-specific (e.g. channel push, plugin monitor, cowork, auto-launcher, plugin marketplace). Past entries (v0.12.0 and earlier) are historical record and not rewritten.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.