Ship Feed
Purpose
A shipping log is great prose, but prose can't be embedded, subscribed to, or read by code.
This skill makes the same release history machine-readable and embeddable:
docs/changelog.json — structured releases (version, date, title, summary, items by category)
docs/changelog.xml — an RSS 2.0 (or Atom 1.0) feed of releases, for feed readers + bots
docs/ship-changelog.js — a self-contained <ship-changelog> web component (full or badge)
docs/whats-new.embed.html — a static, data-inlined page for an <iframe> or a no-JS host
All four are static, brand-themed from docs/brand.json, and derive from one source of truth —
your existing shipping log. Write the prose once (with shipping-log); syndicate it everywhere.
Part of the /ship-* pack
ship-feed is the distribution layer of the docs + release pack. It consumes what the other
skills produce; it never invents content.
docs/VERSION ── single semver anchor ──┐
docs/brand.json ── brand as data ───────┤
docs/SHIPPING-LOG.md (← shipping-log) │
│ parse │
▼ ▼
docs/changelog.json ──► docs/changelog.xml (RSS/Atom feed)
│ ──► docs/ship-changelog.js (<ship-changelog> component)
│ ──► docs/whats-new.embed.html (iframe / no-JS embed)
shipping-log writes/owns docs/SHIPPING-LOG.md and the prose
docs/whats-new.html. Run it first — this skill parses its output. If the log is missing,
stop and offer to run it.
user-guide-builder + screenshot-capture
share the same docs/VERSION anchor so "what changed" and "how it works" stay in lockstep.
logo-pack and the renderers all read the same docs/brand.json.
The widget links and the feed point back to whats-new.html#v<version>, so the data layer and
the prose page reinforce each other.
Discovery first (don't act blind)
Before generating anything, inspect what already exists and report it:
- Inputs present?
node <skill>/scripts/version.mjs get → current semver (or "no anchor").
- Read
docs/brand.json (colors + fonts). If absent, the renderers fall back to a neutral
slate+blue theme (never purple) — note that in your report and offer to create brand.json.
ls docs/SHIPPING-LOG.md — required. If missing, stop and offer to run shipping-log.
- Prior outputs?
ls docs/changelog.json docs/changelog.xml docs/ship-changelog.js docs/whats-new.embed.html — note what you'll be regenerating so the user knows what changes.
- Deploy target? Ask (or infer) the public site URL for absolute feed links and whether they
want RSS or Atom, and whether they need the component, the iframe embed, or both.
version.mjs is shared with the pack — call it, don't reinvent version handling.
Workflow
Run scripts from the repo root; reference them by absolute skill path. They are
dependency-free .mjs (Node ≥ 16, cross-platform) — run them, never inline their logic.
Parse the log into data.
node <skill>/scripts/parse-changelog.mjs --pretty --site-url https://<your-site>
Reads docs/SHIPPING-LOG.md (+ docs/VERSION, docs/brand.json), writes
docs/changelog.json. It prints N release(s), M item(s). Read the printed counts — if
it warns "parsed 0 releases", the log isn't in the pack's ## v1.2.3 — YYYY-MM-DD shape; fix
the headings (or regenerate via shipping-log) rather than hand-editing JSON.
Emit the feed.
node <skill>/scripts/emit-feed.mjs --format rss --site-url https://<your-site>
# Atom instead: --format atom --out docs/changelog.atom.xml
One <item>/<entry> per release; categories become <category> tags; the release body is
HTML-in-CDATA so readers render it. Pass --site-url for portable absolute links.
Render the widget(s).
node <skill>/scripts/render-widget.mjs --mode both
Writes docs/ship-changelog.js (the <ship-changelog> component) and
docs/whats-new.embed.html (static embed). Both are themed from docs/brand.json. Use
--mode component or --mode page to emit just one. --src <url> sets the JSON URL the
component fetches at runtime (default ./changelog.json).
Verify before declaring done.
node --check docs/ship-changelog.js (valid JS).
- Confirm
<item> open/close counts match in the XML and that --site-url produced absolute
links.
- Grep the outputs for
backdrop-filter / feTurbulence — there must be none in real
CSS (they hang renderers).
- Spot-check
changelog.json stats against the log (release + item counts).
Wire it up. Read references/embedding.md and give the user the exact snippets for their
case: company-page component, in-app badge (badge limit="1" + the unread-dot pattern), the
<iframe> embed, and the <link rel="alternate"> feed auto-discovery tag.
Report honestly. State what was generated, the release/item counts, whether brand.json
themed it or defaults were used, whether links are absolute (site URL known) or relative, and
which categories appeared. If you regenerated existing files, say so.
Worked example (real input → real output)
Input — docs/SHIPPING-LOG.md (MyFieldTime), heading + first bullet:
## v0.8.0 — 2026-06-17 · Money & decisions
_The homeowner finally sees the money — and signs off without the email chase._
### ✨ New
- **Money & Progress portal for homeowners.** Clients open one page to see how much of the
budget is spent, what's left, and how far along the job is — no spreadsheet, no phone call.
Run: parse-changelog.mjs --pretty --site-url https://myfieldtime.com
Output — docs/changelog.json (excerpt): 3 release(s), 18 item(s), the bold lead split
from its body, the italic line captured as summary:
{
"$schema": "ship-feed/changelog@1",
"product": "MyFieldTime", "tagline": "Run your jobs. Not your inbox.",
"currentVersion": "0.8.0",
"stats": { "releases": 3, "items": 18, "latestVersion": "0.8.0", "latestDate": "2026-06-17" },
"releases": [{
"version": "0.8.0", "date": "2026-06-17", "title": "Money & decisions", "id": "v0.8.0",
"summary": "The homeowner finally sees the money — and signs off without the email chase.",
"items": [{
"category": "new",
"title": "Money & Progress portal for homeowners",
"body": "Clients open one page to see how much of the budget is spent, what's left, and how far along the job is — no spreadsheet, no phone call."
}]
}]
}
Then emit-feed.mjs turns the same release into:
<item>
<title>MyFieldTime 0.8.0 — Money & decisions</title>
<link>https://myfieldtime.com/whats-new.html#v0.8.0</link>
<guid isPermaLink="false">v0.8.0</guid>
<pubDate>Wed, 17 Jun 2026 12:00:00 GMT</pubDate>
<category>new</category> …
</item>
…and render-widget.mjs themes the <ship-changelog> component with MyFieldTime's tokens
(--sc-brand: #2f7dff; --sc-accent: #FFDD00;) — on-brand, never purple.
Quality bar
Hold the output to this standard before you call it done:
- Faithful, not generative. Every datum traces to a line in
SHIPPING-LOG.md. The scripts
parse; they never write copy. If the data looks thin, the log is thin — fix it upstream with
shipping-log, don't pad the JSON.
- The JSON is a real contract. Matches
references/data-contract.md, carries $schema,
newest-first releases, canonical category ids. A downstream app can depend on its shape.
- Valid, well-formed feeds. XML escapes correctly, dates are RFC-822 (RSS) / RFC-3339 (Atom),
links are absolute when a site URL is known. It validates in a feed reader.
- Self-contained, themeable, on-brand. The widget inlines all its CSS/JS (Shadow DOM), pulls
colors + fonts from
brand.json, exposes --sc-* tokens for host overrides, and defaults to a
neutral slate+blue — never purple. No backdrop-filter, no SVG feTurbulence.
- Honest about freshness + status. The feed reflects the log at parse time; re-run after each
release (or wire into
/ship-release). Don't imply a live API where there's a static file.
- Cross-project. Nothing is hardcoded to one app — product name, tagline, colors, version,
and site URL all come from that project's
docs/. Deploy targets are referenced via adapters
(any static host / CDN / CI), never assumed.
Reusable contents
scripts/parse-changelog.mjs — SHIPPING-LOG.md (+ VERSION + brand) → changelog.json.
scripts/emit-feed.mjs — changelog.json → changelog.xml (RSS 2.0 or Atom 1.0).
scripts/render-widget.mjs — changelog.json → ship-changelog.js + whats-new.embed.html.
scripts/version.mjs — the shared pack semver anchor manager (get/init/set/bump/date).
references/data-contract.md — the changelog.json schema + downstream-stability rules.
references/embedding.md — copy-paste snippets: company page, in-app badge, iframe, RSS link,
host theming, and the renderer-safety rules.
1---2name: ship-feed3description: Turns a project's shipping log into machine-readable data plus an embeddable changelog widget. Parses docs/SHIPPING-LOG.md + docs/VERSION into a structured docs/changelog.json (releases with version, date, and items grouped by category), then emits an RSS/Atom feed (docs/changelog.xml) and a self-contained, brand-themed <ship-changelog> web component + static HTML embed that read that JSON. For a company-page "What's new" section, an in-app "What's new" badge, and a subscribe-able feed. Use when the user wants a changelog API or JSON, an RSS/Atom changelog feed, a "what's new" widget or web component, an embeddable changelog, a release feed, or to make their shipping log machine-readable / syndicatable. Triggers on "changelog as data", "changelog.json", "RSS feed for releases", "what's new widget", "embed the changelog", "release feed", "ship-feed". Part of the /ship-* pack.4---56# Ship Feed78## Purpose910A shipping log is great prose, but prose can't be embedded, subscribed to, or read by code.11This skill makes the same release history **machine-readable and embeddable**:1213- `docs/changelog.json` — structured releases (version, date, title, summary, items by category)14- `docs/changelog.xml` — an RSS 2.0 (or Atom 1.0) feed of releases, for feed readers + bots15- `docs/ship-changelog.js` — a self-contained `<ship-changelog>` web component (full or badge)16- `docs/whats-new.embed.html` — a static, data-inlined page for an `<iframe>` or a no-JS host1718All four are static, brand-themed from `docs/brand.json`, and derive from one source of truth —19your existing shipping log. Write the prose once (with `shipping-log`); syndicate it everywhere.2021## Part of the /ship-* pack2223`ship-feed` is the **distribution** layer of the docs + release pack. It consumes what the other24skills produce; it never invents content.2526```27docs/VERSION ── single semver anchor ──┐28docs/brand.json ── brand as data ───────┤29docs/SHIPPING-LOG.md (← shipping-log) │30 │ parse │31 ▼ ▼32 docs/changelog.json ──► docs/changelog.xml (RSS/Atom feed)33 │ ──► docs/ship-changelog.js (<ship-changelog> component)34 │ ──► docs/whats-new.embed.html (iframe / no-JS embed)35```3637- **[`shipping-log`](../shipping-log/SKILL.md)** writes/owns `docs/SHIPPING-LOG.md` and the prose38 `docs/whats-new.html`. **Run it first** — this skill parses its output. If the log is missing,39 stop and offer to run it.40- **[`user-guide-builder`](../user-guide-builder/SKILL.md)** + **[`screenshot-capture`](../screenshot-capture/SKILL.md)**41 share the same `docs/VERSION` anchor so "what changed" and "how it works" stay in lockstep.42- **[`logo-pack`](../logo-pack/SKILL.md)** and the renderers all read the same `docs/brand.json`.4344The widget links and the feed point back to `whats-new.html#v<version>`, so the data layer and45the prose page reinforce each other.4647## Discovery first (don't act blind)4849Before generating anything, inspect what already exists and report it:50511. **Inputs present?**52 - `node <skill>/scripts/version.mjs get` → current semver (or "no anchor").53 - Read `docs/brand.json` (colors + fonts). If absent, the renderers fall back to a neutral54 slate+blue theme (never purple) — note that in your report and offer to create `brand.json`.55 - `ls docs/SHIPPING-LOG.md` — **required**. If missing, stop and offer to run `shipping-log`.562. **Prior outputs?** `ls docs/changelog.json docs/changelog.xml docs/ship-changelog.js57 docs/whats-new.embed.html` — note what you'll be regenerating so the user knows what changes.583. **Deploy target?** Ask (or infer) the public site URL for absolute feed links and whether they59 want **RSS or Atom**, and whether they need the **component**, the **iframe embed**, or both.6061`version.mjs` is shared with the pack — call it, don't reinvent version handling.6263## Workflow6465Run scripts from the **repo root**; reference them by absolute skill path. They are66dependency-free `.mjs` (Node ≥ 16, cross-platform) — run them, never inline their logic.67681. **Parse the log into data.**69 ```bash70 node <skill>/scripts/parse-changelog.mjs --pretty --site-url https://<your-site>71 ```72 Reads `docs/SHIPPING-LOG.md` (+ `docs/VERSION`, `docs/brand.json`), writes73 `docs/changelog.json`. It prints `N release(s), M item(s)`. **Read the printed counts** — if74 it warns "parsed 0 releases", the log isn't in the pack's `## v1.2.3 — YYYY-MM-DD` shape; fix75 the headings (or regenerate via `shipping-log`) rather than hand-editing JSON.76772. **Emit the feed.**78 ```bash79 node <skill>/scripts/emit-feed.mjs --format rss --site-url https://<your-site>80 # Atom instead: --format atom --out docs/changelog.atom.xml81 ```82 One `<item>`/`<entry>` per release; categories become `<category>` tags; the release body is83 HTML-in-CDATA so readers render it. Pass `--site-url` for portable absolute links.84853. **Render the widget(s).**86 ```bash87 node <skill>/scripts/render-widget.mjs --mode both88 ```89 Writes `docs/ship-changelog.js` (the `<ship-changelog>` component) and90 `docs/whats-new.embed.html` (static embed). Both are themed from `docs/brand.json`. Use91 `--mode component` or `--mode page` to emit just one. `--src <url>` sets the JSON URL the92 component fetches at runtime (default `./changelog.json`).93944. **Verify before declaring done.**95 - `node --check docs/ship-changelog.js` (valid JS).96 - Confirm `<item>` open/close counts match in the XML and that `--site-url` produced absolute97 links.98 - Grep the outputs for `backdrop-filter` / `feTurbulence` — there must be **none** in real99 CSS (they hang renderers).100 - Spot-check `changelog.json` `stats` against the log (release + item counts).1011025. **Wire it up.** Read `references/embedding.md` and give the user the exact snippets for their103 case: company-page component, in-app badge (`badge limit="1"` + the unread-dot pattern), the104 `<iframe>` embed, and the `<link rel="alternate">` feed auto-discovery tag.1051066. **Report honestly.** State what was generated, the release/item counts, whether `brand.json`107 themed it or defaults were used, whether links are absolute (site URL known) or relative, and108 which categories appeared. If you regenerated existing files, say so.109110## Worked example (real input → real output)111112**Input** — `docs/SHIPPING-LOG.md` (MyFieldTime), heading + first bullet:113114```markdown115## v0.8.0 — 2026-06-17 · Money & decisions116117_The homeowner finally sees the money — and signs off without the email chase._118119### ✨ New120- **Money & Progress portal for homeowners.** Clients open one page to see how much of the121 budget is spent, what's left, and how far along the job is — no spreadsheet, no phone call.122```123124Run: `parse-changelog.mjs --pretty --site-url https://myfieldtime.com`125126**Output** — `docs/changelog.json` (excerpt): `3 release(s), 18 item(s)`, the bold lead split127from its body, the italic line captured as `summary`:128129```json130{131 "$schema": "ship-feed/changelog@1",132 "product": "MyFieldTime", "tagline": "Run your jobs. Not your inbox.",133 "currentVersion": "0.8.0",134 "stats": { "releases": 3, "items": 18, "latestVersion": "0.8.0", "latestDate": "2026-06-17" },135 "releases": [{136 "version": "0.8.0", "date": "2026-06-17", "title": "Money & decisions", "id": "v0.8.0",137 "summary": "The homeowner finally sees the money — and signs off without the email chase.",138 "items": [{139 "category": "new",140 "title": "Money & Progress portal for homeowners",141 "body": "Clients open one page to see how much of the budget is spent, what's left, and how far along the job is — no spreadsheet, no phone call."142 }]143 }]144}145```146147Then `emit-feed.mjs` turns the same release into:148149```xml150<item>151 <title>MyFieldTime 0.8.0 — Money & decisions</title>152 <link>https://myfieldtime.com/whats-new.html#v0.8.0</link>153 <guid isPermaLink="false">v0.8.0</guid>154 <pubDate>Wed, 17 Jun 2026 12:00:00 GMT</pubDate>155 <category>new</category> …156</item>157```158159…and `render-widget.mjs` themes the `<ship-changelog>` component with MyFieldTime's tokens160(`--sc-brand: #2f7dff; --sc-accent: #FFDD00;`) — on-brand, **never purple**.161162## Quality bar163164Hold the output to this standard before you call it done:165166- **Faithful, not generative.** Every datum traces to a line in `SHIPPING-LOG.md`. The scripts167 parse; they never write copy. If the data looks thin, the *log* is thin — fix it upstream with168 `shipping-log`, don't pad the JSON.169- **The JSON is a real contract.** Matches `references/data-contract.md`, carries `$schema`,170 newest-first releases, canonical category ids. A downstream app can depend on its shape.171- **Valid, well-formed feeds.** XML escapes correctly, dates are RFC-822 (RSS) / RFC-3339 (Atom),172 links are absolute when a site URL is known. It validates in a feed reader.173- **Self-contained, themeable, on-brand.** The widget inlines all its CSS/JS (Shadow DOM), pulls174 colors + fonts from `brand.json`, exposes `--sc-*` tokens for host overrides, and defaults to a175 neutral slate+blue — **never purple**. **No `backdrop-filter`, no SVG `feTurbulence`.**176- **Honest about freshness + status.** The feed reflects the log at parse time; re-run after each177 release (or wire into `/ship-release`). Don't imply a live API where there's a static file.178- **Cross-project.** Nothing is hardcoded to one app — product name, tagline, colors, version,179 and site URL all come from that project's `docs/`. Deploy targets are referenced via adapters180 (any static host / CDN / CI), never assumed.181182## Reusable contents183184- `scripts/parse-changelog.mjs` — `SHIPPING-LOG.md` (+ VERSION + brand) → `changelog.json`.185- `scripts/emit-feed.mjs` — `changelog.json` → `changelog.xml` (RSS 2.0 or Atom 1.0).186- `scripts/render-widget.mjs` — `changelog.json` → `ship-changelog.js` + `whats-new.embed.html`.187- `scripts/version.mjs` — the shared pack semver anchor manager (get/init/set/bump/date).188- `references/data-contract.md` — the `changelog.json` schema + downstream-stability rules.189- `references/embedding.md` — copy-paste snippets: company page, in-app badge, iframe, RSS link,190 host theming, and the renderer-safety rules.