Onboard a since-you-have-been-gone release ledger into any product: capture merged work, analyse and categorise it, and show each user what changed since they last looked.
A user who was away for two weeks comes back and cannot tell what shipped. The
changelog is written for the team, the commit log is unreadable, and the release
notes stopped in March. A release ledger fixes that one thing: it records every
merged change, describes it in plain language, and shows each user only what
landed since they last looked.
This skill is the onboarding procedure. It does not ship a library. Its output is
a written implementation map, in the host repository, saying where each part of
the system lands on the stack that is actually there.
Three skills divide the work: release-ledger orchestrates, github-webhooks
captures, describe-changes writes the entries. Install the companions from
the same pack with npx skills add crissmoldovan/agent-skills, and name them in
the implementation map rather than reimplementing what they cover.
The system model
Six stages. Each is a boundary you can build and verify alone.
Capture writes the raw merge payload and nothing else. It never calls a
model, never blocks, and never drops a row because a later stage might not
like it.
The queue is the unanalysed set. It exists so capture and analysis fail
independently: a model outage costs a delayed entry, not a lost one.
Nightly analysis turns each queued payload into one ledger entry with a
classification and three registers of description. This is where
describe-changes is invoked.
The popup is per user. Each user carries a watermark — the timestamp they
last acknowledged — and sees entries newer than it, capped at a window.
The digest is per channel. It posts entries created since the previous
digest to one chat or mail surface, and records the send.
Announcements are hand-written entries that bypass capture and analysis.
They carry a schedule and an audience, and render in the same surfaces.
Read the system model reference for the stage
contracts, the failure modes at each boundary, and the sequences in text form.
When to Use
A product has users who log in intermittently and no reliable way to tell them
what changed while they were away.
A team ships continuously and the release notes have fallen behind, or were
never written for the people who use the product.
Someone asks for "a what's-new popup" and you need the parts behind it named
before anyone writes a migration.
A ledger exists partially — capture but no analysis, entries but no per-user
watermark — and you need to know which stage to finish next.
Do not use it to write a single set of release notes; that is describe-changes
on a release range, on its own. Do not use it to build a curated marketing
changelog — a ledger is derived from merged work, and a page nobody derives is a
CMS, not this. Do not use it to notify users of incidents or outages; those need
a status surface with different latency guarantees.
Prerequisites
A merged-work source you can name. Either a forge that emits merge events,
or a human process that will write entries by hand. Both are supported; a
ledger with neither has no input.
Complete when: you can state the source and, for automatic capture, point
at the endpoint that will receive the events.
A durable store with a unique constraint. Capture must be able to insert
the same event twice and keep one row — forges replay deliveries.
Complete when: you have named the store and the natural key (repository
plus change number is the usual one).
A scheduler that can run a job once a day. Cron, a hosted job runner, or a
queue with delayed jobs. Analysis and digest are two separate schedules.
Complete when: you have run a no-op job on that scheduler and seen it fire.
An authenticated user identity. The watermark hangs on it. Anonymous
sessions can be given a ledger, but it degrades to "latest N entries" and you
should say so out loud rather than pretend otherwise.
Complete when: you can name the column the watermark row will key on.
A decision on the analysis model. Which model, reached how, and who pays.
Complete when: the model is reachable from the job runner, not only from a
developer machine, and a per-run cost estimate can be printed before the first
batch.
Procedure
Investigate the host application against a bounded checklist. Framework
and version; datastore and migration idiom; job runner and how a schedule is
declared; auth and the user table; the chat or mail surface and its posting
helper; existing inbound webhook handling; how markdown is rendered in the UI;
how roles or groups are represented. Work
the onboarding checklist and stop when
its items are answered. Do not read the whole codebase.
Write docs/release-ledger/implementation.md in the host repository. One
section per stage of the system model, each naming the concrete file, table,
schedule, and helper that will carry it on this stack. Record what you could
not determine as open questions with a named owner. This document is the
deliverable of onboarding; everything after it is implementation against a map
that already exists.
Design the tables from the reference schema. Queue, entries, dismissals,
announcements, sends. Adapt names to the host's conventions and keep the
invariants. The data model reference carries
portable SQL and the reason each constraint is there.
Choose the capture mode, and offer the choice. Automatic capture from a
forge is the default; the github-webhooks skill covers endpoint setup,
signature verification, and event routing for it — install it from the same
pack with npx skills add crissmoldovan/agent-skills and follow it rather than
hand-rolling a verifier. Manual-only is a first-class mode: announcements plus
agent-written entries, no endpoint, no secret to rotate. Ask, and record the
answer in the implementation map. The user configures the webhook itself later,
from that document.
Delegate entry authoring to describe-changes. The analysis job owns
batching, retry, cost control, and persistence. What an entry says — the
classification and the three registers, anchored in the diff — belongs to
describe-changes, and its output contract is what the entry columns should
mirror. Do not invent a second description format.
Build capture first and let it run before analysis exists. A day of
captured payloads is the test fixture for everything downstream, and it proves
the queue survives replays before a model bill is attached to it.
Add analysis, then the popup, then the digest, then announcements. Each
stage is useful on its own. Stop wherever it stops paying.
State the limitations in the implementation map. If capture stores only
the event payload, entries cannot cite files the payload does not carry, and
changes with no linked ticket will have no reporter. Write that down; do not
let the popup render an empty "reported by" stub instead.
Core invariants
The dismissal watermark is capped at a window — thirty days is a sensible
default. A user returning after a year gets the last thirty days, not four
hundred entries.
Every text field is markdown, in every stage. One renderer, one escaping
story, no HTML passthrough.
Announcements are not analysed entries. Separate table, separate authoring
path, same rendering. A pinned announcement carries a start date, an optional
end date, and an audience.
Digest sends are recorded and fail loud. Every attempt writes a row with a
status. A zero-entry day records skipped with a reason. A run that posted
nothing and reports success is the bug this invariant exists to prevent.
Capture is idempotent on the natural key. Insert, on conflict do nothing.
Usage Examples
Onboard a release ledger into this app. Run the stack investigation first and
show me the answers before you write anything, then produce
docs/release-ledger/implementation.md mapping every stage onto what is actually
here. Do not create tables yet.
We already capture merged pull requests into a queue table but nothing reads it.
Add the nightly analysis stage only: batch the queue, classify and describe each
row with describe-changes, and write one ledger entry per change. Print a cost
estimate for the backlog before the first model call.
Manual ledger, no webhook. Set up the entries, dismissals, and announcements
tables plus the per-user popup with a 30-day watermark cap, and give me an admin
form to write an announcement with a start date and an audience.
Pitfalls
Capture placed inside an existing handler that returns early. If merge
events already flow into a handler that filters them — for tickets, for labels,
for a naming convention — capture placed inside it inherits that filter and
silently loses whatever the filter drops. Measure how many events the existing
handler discards before deciding where to hook in. Capture belongs beside such
a handler, not within it.
Analysing at capture time. It couples a model call to a webhook response,
which has a hard timeout, and makes every replayed delivery cost money. Capture
writes a row; analysis runs later.
A watermark stored per device instead of per user. Local storage looks
simpler and gives the same user a fresh backlog in every browser. The watermark
is server state.
Looping and awaiting over the queue. One model call at a time turns a
fifteen-minute batch into hours and gives no partial progress on failure.
Batch, cap the batch size, and let each item fail on its own.
A digest that treats "nothing to send" as success. Record it as skipped,
with the reason, so an empty channel and a broken job look different in the
send table.
Rendering markdown with raw HTML enabled. Entry text derives from change
titles and bodies that anyone with commit access can write. Render markdown,
disable HTML passthrough, use an explicit component map.
Targeting announcements from the client. If the browser asks for "all
announcements" and filters by audience locally, the audience is advisory.
Filter server-side and return only what the caller may see.
Backfilling without an estimate. A month of merged work is a large model
bill arriving as a surprise. Count the input first, print the estimate, cap the
batch, and run it through the same path as the nightly job.
Verification
The stack investigation was completed against the checklist and its answers reported.
docs/release-ledger/implementation.md exists in the host repository and names a concrete file, table, schedule, or helper for every stage.
Capture mode was offered as a choice and the answer recorded.
Capture is idempotent: the same event delivered twice produces one row, verified by replaying a delivery.
Capture does not sit behind a filter that discards events the ledger needs.
No model call happens on the capture path.
Entry text follows the describe-changes output contract, not a second format.
The watermark is server-side, per user, and capped at a window.
Announcements are stored separately from analysed entries and carry schedule and audience.
Audience filtering happens server-side.
Every digest attempt writes a send row, and a zero-entry day is recorded as skipped with a reason.
Markdown rendering has raw HTML disabled.
Known limitations, including missing reporters, are written down rather than rendered as empty stubs.
Deeper reading
System model: the six stages in depth, the
contract at each boundary, sequences in text form, and what each stage may do
when the next one is down.
Onboarding checklist: the bounded stack
investigation, one question per line, with what each answer decides.
Data model: portable reference schema for the
queue, entries, dismissals, announcements, and sends, with the reason behind
each constraint and the watermark query.
1---2name: release-ledger3description: Onboard a since-you-have-been-gone release ledger into any product: capture merged work, analyse and categorise it, and show each user what changed since they last looked.4license: MIT5---67# Release ledger89A user who was away for two weeks comes back and cannot tell what shipped. The10changelog is written for the team, the commit log is unreadable, and the release11notes stopped in March. A release ledger fixes that one thing: it records every12merged change, describes it in plain language, and shows each user only what13landed since *they* last looked.1415This skill is the onboarding procedure. It does not ship a library. Its output is16a written implementation map, in the host repository, saying where each part of17the system lands on the stack that is actually there.1819Three skills divide the work: **`release-ledger`** orchestrates, **`github-webhooks`**20captures, **`describe-changes`** writes the entries. Install the companions from21the same pack with `npx skills add crissmoldovan/agent-skills`, and name them in22the implementation map rather than reimplementing what they cover.2324## The system model2526Six stages. Each is a boundary you can build and verify alone.2728```text29 merge event ──► capture ──► queue row (raw payload, unanalysed)30 │31 nightly ▼32 analysis ──► ledger entry {kind, title, short, medium, detail}33 │34 announcements ────┤35 (hand-written, │36 scheduled, ▼37 audience-targeted) ├──► per-user popup (entries newer than the user's watermark)38 └──► daily digest (entries since the previous digest)39```40411. **Capture** writes the raw merge payload and nothing else. It never calls a42 model, never blocks, and never drops a row because a later stage might not43 like it.442. **The queue** is the unanalysed set. It exists so capture and analysis fail45 independently: a model outage costs a delayed entry, not a lost one.463. **Nightly analysis** turns each queued payload into one ledger entry with a47 classification and three registers of description. This is where48 `describe-changes` is invoked.494. **The popup** is per user. Each user carries a watermark — the timestamp they50 last acknowledged — and sees entries newer than it, capped at a window.515. **The digest** is per channel. It posts entries created since the previous52 digest to one chat or mail surface, and records the send.536. **Announcements** are hand-written entries that bypass capture and analysis.54 They carry a schedule and an audience, and render in the same surfaces.5556Read [the system model reference](references/system-model.md) for the stage57contracts, the failure modes at each boundary, and the sequences in text form.5859## When to Use6061- A product has users who log in intermittently and no reliable way to tell them62 what changed while they were away.63- A team ships continuously and the release notes have fallen behind, or were64 never written for the people who use the product.65- Someone asks for "a what's-new popup" and you need the parts behind it named66 before anyone writes a migration.67- A ledger exists partially — capture but no analysis, entries but no per-user68 watermark — and you need to know which stage to finish next.6970Do not use it to write a single set of release notes; that is `describe-changes`71on a release range, on its own. Do not use it to build a curated marketing72changelog — a ledger is derived from merged work, and a page nobody derives is a73CMS, not this. Do not use it to notify users of incidents or outages; those need74a status surface with different latency guarantees.7576## Prerequisites77781. **A merged-work source you can name.** Either a forge that emits merge events,79 or a human process that will write entries by hand. Both are supported; a80 ledger with neither has no input.81 **Complete when:** you can state the source and, for automatic capture, point82 at the endpoint that will receive the events.832. **A durable store with a unique constraint.** Capture must be able to insert84 the same event twice and keep one row — forges replay deliveries.85 **Complete when:** you have named the store and the natural key (repository86 plus change number is the usual one).873. **A scheduler that can run a job once a day.** Cron, a hosted job runner, or a88 queue with delayed jobs. Analysis and digest are two separate schedules.89 **Complete when:** you have run a no-op job on that scheduler and seen it fire.904. **An authenticated user identity.** The watermark hangs on it. Anonymous91 sessions can be given a ledger, but it degrades to "latest N entries" and you92 should say so out loud rather than pretend otherwise.93 **Complete when:** you can name the column the watermark row will key on.945. **A decision on the analysis model.** Which model, reached how, and who pays.95 **Complete when:** the model is reachable from the job runner, not only from a96 developer machine, and a per-run cost estimate can be printed before the first97 batch.9899## Procedure1001011. **Investigate the host application against a bounded checklist.** Framework102 and version; datastore and migration idiom; job runner and how a schedule is103 declared; auth and the user table; the chat or mail surface and its posting104 helper; existing inbound webhook handling; how markdown is rendered in the UI;105 how roles or groups are represented. Work106 [the onboarding checklist](references/onboarding-checklist.md) and stop when107 its items are answered. Do not read the whole codebase.1082. **Write `docs/release-ledger/implementation.md` in the host repository.** One109 section per stage of the system model, each naming the concrete file, table,110 schedule, and helper that will carry it on *this* stack. Record what you could111 not determine as open questions with a named owner. This document is the112 deliverable of onboarding; everything after it is implementation against a map113 that already exists.1143. **Design the tables from the reference schema.** Queue, entries, dismissals,115 announcements, sends. Adapt names to the host's conventions and keep the116 invariants. [The data model reference](references/data-model.md) carries117 portable SQL and the reason each constraint is there.1184. **Choose the capture mode, and offer the choice.** Automatic capture from a119 forge is the default; the `github-webhooks` skill covers endpoint setup,120 signature verification, and event routing for it — install it from the same121 pack with `npx skills add crissmoldovan/agent-skills` and follow it rather than122 hand-rolling a verifier. Manual-only is a first-class mode: announcements plus123 agent-written entries, no endpoint, no secret to rotate. Ask, and record the124 answer in the implementation map. The user configures the webhook itself later,125 from that document.1265. **Delegate entry authoring to `describe-changes`.** The analysis job owns127 batching, retry, cost control, and persistence. What an entry *says* — the128 classification and the three registers, anchored in the diff — belongs to129 `describe-changes`, and its output contract is what the entry columns should130 mirror. Do not invent a second description format.1316. **Build capture first and let it run before analysis exists.** A day of132 captured payloads is the test fixture for everything downstream, and it proves133 the queue survives replays before a model bill is attached to it.1347. **Add analysis, then the popup, then the digest, then announcements.** Each135 stage is useful on its own. Stop wherever it stops paying.1368. **State the limitations in the implementation map.** If capture stores only137 the event payload, entries cannot cite files the payload does not carry, and138 changes with no linked ticket will have no reporter. Write that down; do not139 let the popup render an empty "reported by" stub instead.140141### Core invariants142143- **The dismissal watermark is capped at a window** — thirty days is a sensible144 default. A user returning after a year gets the last thirty days, not four145 hundred entries.146- **Every text field is markdown**, in every stage. One renderer, one escaping147 story, no HTML passthrough.148- **Announcements are not analysed entries.** Separate table, separate authoring149 path, same rendering. A pinned announcement carries a start date, an optional150 end date, and an audience.151- **Digest sends are recorded and fail loud.** Every attempt writes a row with a152 status. A zero-entry day records `skipped` with a reason. A run that posted153 nothing and reports success is the bug this invariant exists to prevent.154- **Capture is idempotent on the natural key.** Insert, on conflict do nothing.155156## Usage Examples157158```text159Onboard a release ledger into this app. Run the stack investigation first and160show me the answers before you write anything, then produce161docs/release-ledger/implementation.md mapping every stage onto what is actually162here. Do not create tables yet.163```164165```text166We already capture merged pull requests into a queue table but nothing reads it.167Add the nightly analysis stage only: batch the queue, classify and describe each168row with describe-changes, and write one ledger entry per change. Print a cost169estimate for the backlog before the first model call.170```171172```text173Manual ledger, no webhook. Set up the entries, dismissals, and announcements174tables plus the per-user popup with a 30-day watermark cap, and give me an admin175form to write an announcement with a start date and an audience.176```177178## Pitfalls179180- **Capture placed inside an existing handler that returns early.** If merge181 events already flow into a handler that filters them — for tickets, for labels,182 for a naming convention — capture placed inside it inherits that filter and183 silently loses whatever the filter drops. Measure how many events the existing184 handler discards before deciding where to hook in. Capture belongs beside such185 a handler, not within it.186- **Analysing at capture time.** It couples a model call to a webhook response,187 which has a hard timeout, and makes every replayed delivery cost money. Capture188 writes a row; analysis runs later.189- **A watermark stored per device instead of per user.** Local storage looks190 simpler and gives the same user a fresh backlog in every browser. The watermark191 is server state.192- **Looping and awaiting over the queue.** One model call at a time turns a193 fifteen-minute batch into hours and gives no partial progress on failure.194 Batch, cap the batch size, and let each item fail on its own.195- **A digest that treats "nothing to send" as success.** Record it as skipped,196 with the reason, so an empty channel and a broken job look different in the197 send table.198- **Rendering markdown with raw HTML enabled.** Entry text derives from change199 titles and bodies that anyone with commit access can write. Render markdown,200 disable HTML passthrough, use an explicit component map.201- **Targeting announcements from the client.** If the browser asks for "all202 announcements" and filters by audience locally, the audience is advisory.203 Filter server-side and return only what the caller may see.204- **Backfilling without an estimate.** A month of merged work is a large model205 bill arriving as a surprise. Count the input first, print the estimate, cap the206 batch, and run it through the same path as the nightly job.207208## Verification209210- [ ] The stack investigation was completed against the checklist and its answers reported.211- [ ] `docs/release-ledger/implementation.md` exists in the host repository and names a concrete file, table, schedule, or helper for every stage.212- [ ] Capture mode was offered as a choice and the answer recorded.213- [ ] Capture is idempotent: the same event delivered twice produces one row, verified by replaying a delivery.214- [ ] Capture does not sit behind a filter that discards events the ledger needs.215- [ ] No model call happens on the capture path.216- [ ] Entry text follows the `describe-changes` output contract, not a second format.217- [ ] The watermark is server-side, per user, and capped at a window.218- [ ] Announcements are stored separately from analysed entries and carry schedule and audience.219- [ ] Audience filtering happens server-side.220- [ ] Every digest attempt writes a send row, and a zero-entry day is recorded as skipped with a reason.221- [ ] Markdown rendering has raw HTML disabled.222- [ ] Known limitations, including missing reporters, are written down rather than rendered as empty stubs.223224## Deeper reading225226- [System model](references/system-model.md): the six stages in depth, the227 contract at each boundary, sequences in text form, and what each stage may do228 when the next one is down.229- [Onboarding checklist](references/onboarding-checklist.md): the bounded stack230 investigation, one question per line, with what each answer decides.231- [Data model](references/data-model.md): portable reference schema for the232 queue, entries, dismissals, announcements, and sends, with the reason behind233 each constraint and the watermark query.
Run npx skillmds@latest add crissmoldovan/release-ledger in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Onboard a since-you-have-been-gone release ledger into any product: capture merged work, analyse and categorise it, and show each user what changed since they last looked. It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
crissmoldovan (@crissmoldovan) published this skill. Their other Agent Skills are listed on their SkillMD profile.