Four P2 findings, each with a regression test:
- The authored-body check could never fail. The tag workflow appends
GitHub's generated notes to the authored text (FULL_BODY in
build-and-make.yaml), so `release.body` is never empty and testing it for
emptiness proved nothing. It now compares the body against the local
CHANGELOG.md public section — the actual authored source — and reports an
internal-only release as such instead of warning about it.
- A release with no highlights failed the documented card step, which
necessarily included every internal-only release since validation forbids
highlights there. Now the hero card is still rendered when public notes
exist, an internal-only release writes nothing, and both exit 0.
- Card output is keyed by the exact version rather than the minor slug
(0.24.0 and 0.24.1 share a blog post but not a card set), and a run first
removes the cards a previous run wrote there, so a renamed or dropped
highlight cannot leave a stale image waiting to be published. Only files
matching what this tool writes are removed.
- Telegram could still truncate breaking changes into the counter when a
release had no highlights, since the fitting loop trims the tail. Dropping
a breaking entry now fails with an actionable error.
Docs: docs/architecture/release-pipeline.md, .changes/README.md, CLAUDE.md
and AGENTS.md updated for the new output path and the no-highlight and
internal-only behavior.
180 tests passing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seven defects found by an adversarial pass over the new tooling, each with
a regression test:
- `highlight:` was silently truncated at the first ` #`. The comment strip
was written when every field was a slug, enum or number; it now applies
only to those closed-vocabulary keys. `highlight: Sources #N chip` kept
parsing as "Sources" and shipped that to all four surfaces.
- Quote stripping removed a leading or trailing quote independently, so
`highlight: "Up Next" rail` lost its opening quote. Both ends must now
carry the same quote character.
- A whitespace-only quoted value passed validation and then crashed the
card generator mid-run, after PNGs were already written. Values are
trimmed after unquoting, so it fails validation instead; the hero card
also no longer indexes wrapText's empty result blindly.
- Feature cards painted the opaque screenshot frame over the last body
line whenever the headline wrapped to two lines. The body's line budget
is now derived from the space actually left above the frame.
- Un-highlighted breaking changes were folded into "…plus N more fixes and
improvements" on Telegram and never shown. They are always spelled out.
- Both CLI guards compared a non-realpath'd argv[1] against a realpath'd
import.meta.url, so reaching either script through a symlinked path (on
macOS, anything under /tmp or /var) made it a silent exit-0 no-op — which
for the publication gate reads exactly like a pass.
- spawnSync reports a missing gh binary and a signalled child as
`status: null`, both of which were blamed on the build.
The highlight cap drops from 80 to 60 characters: 60 is the hero card's
single-line budget, so a valid highlight now always renders in full rather
than being silently ellipsized.
Docs: new docs/architecture/release-pipeline.md carries the full contract —
surfaces, the ordering constraint around --consume, highlight semantics,
card layout rules, and the 27-asset draft verification table. release-cut
now references it instead of restating the asset list (474/500 words), and
CLAUDE.md, AGENTS.md and .changes/README.md point at it; the README's
release sequence no longer omits the announcement and card steps.
172 tests passing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeQL: the pacman rule built a regex from the interpolated version and
escaped only dots. It now compares plain strings, so the exported
requiredAssetRules() carries no escaping trap; only the literal compression
suffix stays a regex.
verify-draft-release:
- Never exits 0 once the release is published. The asset report still
prints (auditing after the fact is useful), but a pre-publication gate
must not report success for a boundary already crossed.
- Polls for the tag run before concluding it does not exist: `gh run list`
reports what is indexed right now, and a run pushed seconds ago routinely
is not. Adds injectable progress/sleep io, so runVerification is async.
- The live sleep timer is deliberately not unref'd — a pending promise does
not hold the event loop open, and an empty loop exits 0, which would turn
a mid-poll exit into a silent false success.
- `--repo` is shape-checked so a typo fails with the usage line.
Announcements: an internal-only release now returns null instead of
throwing a misleading "even a single entry exceeds the limit" error. The
CLI explains it on stderr, leaves stdout empty and exits 0 — the shape
extract-changelog-section.mjs --public already uses for the same case.
Highlight cards: card filenames derive from the note filename, never the
screenshot slug. Two highlights may legitimately share one manifest shot,
which previously made the second card overwrite the first.
Tests: the CLI harness moved to spawnSync — execFileSync discards stderr on
success, hiding exactly the messages these paths emit. Adds regressions for
each finding above (165 passing).
Docs: CLAUDE.md and AGENTS.md release sections (mirrored, verified
identical) now document the highlight field, the announcement/card
commands, and the draft verifier; .changes/README.md records the
internal-only announcement behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends the release tooling around the .changes notes pipeline:
- New optional `highlight:` frontmatter field marks a release's headline
changes (max 80 chars, rejected on `type: internal`).
- `--format telegram` renders a paste-ready plain-text post guaranteed to
fit the 4096-character limit (highlights lead, the rest collapses into a
counter); `--format reddit` renders markdown with a suggested title line,
a Highlights section, and a collapsed change list. Both are stdout dry
runs and must run before `--consume`.
- The blog scaffold uses the highlight as a ready section heading instead
of a TODO, and highlight notes without screenshots get their own section.
- `release:verify:draft` waits for the v* tag build via `gh run watch`,
then checks draft status, the authored body, and the complete 27-asset
set (verified against a real full matrix build). Read-only; publishing
stays manual.
- `release:cards:generate` renders branded 1200x630 highlight cards (with
a framed mock-server screenshot strip when the note names one, purely
typographic otherwise) plus a release hero card into dist/, matching the
website palette. Copying into the website tree stays a manual act.
Docs: .changes/README.md, release-notes and release-cut skills (both
copies byte-identical). No release note: release tooling only, no runtime
behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>