feat(changelog): curated CHANGELOG with show-once teaser + real release notes#381
Open
schmitthub wants to merge 9 commits into
Open
feat(changelog): curated CHANGELOG with show-once teaser + real release notes#381schmitthub wants to merge 9 commits into
schmitthub wants to merge 9 commits into
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Contributor
There was a problem hiding this comment.
Pull request overview
Introduces a curated, user-facing changelog system that powers (1) a show-once “What’s new” teaser in the CLI after upgrades and (2) curated release-notes headers prepended to GoReleaser’s auto-generated commit groups. This reshapes update checking into a pure fetch+compare unit, centralizes persisted CLI runtime state, and adds semver helpers and storage support for time.Time.
Changes:
- Add
internal/changelog(pure parse/range + fetch/cache/TTL loader) and wire a background “What’s new” teaser intointernal/clawker/Main. - Add
internal/stateas the single owner ofupdate-state.yaml(update-check cache + changelog cursor + changelog fetch TTL) usingstorage.Storefield-merge writes. - Consolidate semver utilities into
internal/semver, addstoragesupport fortime.Time, and update release tooling to extract curated release headers fromCHANGELOG.md.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/update/update.go | Makes update checking pure (no persistence) and switches semver compare to internal/semver. |
| internal/update/update_test.go | Updates tests for new update API contract (always returns CheckResult, IsNewer flag). |
| internal/update/CLAUDE.md | Documents the new “pure update checker” contract and integration. |
| internal/storage/write.go | Ensures time.Time serializes as a scalar instead of flattening to {}. |
| internal/storage/field.go | Adds KindTime and treats time.Time as an opaque leaf field. |
| internal/storage/field_test.go | Extends schema normalization tests to cover time.Time. |
| internal/storage/CLAUDE.md | Documents KindTime and its serialization behavior. |
| internal/state/state.go | Adds CLI runtime state facade over storage.Store[CliState]. |
| internal/state/state_test.go | Adds round-trip, non-clobber, legacy-read, and migration-wiring tests for CLI state. |
| internal/state/CLAUDE.md | Documents state schema, API, and field-merge invariants. |
| internal/semver/semver.go | Adds CompareStrings, IsValidLoose, and supporting helpers. |
| internal/semver/semver_test.go | Adds tests for CompareStrings behavior and invalid input ordering. |
| internal/semver/CLAUDE.md | Documents semver package role as a stdlib-only leaf shared across the DAG. |
| internal/consts/consts.go | Adds GitHub repo/base URL consts, DEV sentinel, state/cache filenames. |
| internal/cmdutil/factory.go | Adds new factory nouns: State() and Changelog(). |
| internal/cmdutil/CLAUDE.md | Updates Factory documentation to include new nouns and their semantics. |
| internal/cmd/factory/default.go | Wires State and Changelog lazy constructors into the default factory. |
| internal/cmd/factory/CLAUDE.md | Documents new stateFunc() wiring and behavior. |
| internal/clawker/cmd.go | Adds background changelog load + show-once teaser, updates update persistence flow. |
| internal/clawker/cmd_test.go | Adds extensive tests for changelog teaser cursor/suppression behavior and IsNewer gating. |
| internal/clawker/CLAUDE.md | Documents show-once teaser algorithm and background loading discipline. |
| internal/changelog/testdata/CHANGELOG.md | Adds fixture changelog for parser tests. |
| internal/changelog/parse.go | Implements Keep-a-Changelog parsing + metadata extraction. |
| internal/changelog/loader.go | Adds loader orchestration: fetch + cache + TTL gate + parse + degrade behavior. |
| internal/changelog/loader_test.go | Tests loader TTL behavior, cache fallback, nil-state behavior, etc. |
| internal/changelog/fetch.go | Adds HTTP fetch helper with short timeout and non-200 errors. |
| internal/changelog/fetch_test.go | Tests fetch success, non-200 errors, context cancellation, nil client. |
| internal/changelog/consts.go | Centralizes parsing tokens and sets ChangelogURL from internal/consts. |
| internal/changelog/CLAUDE.md | Documents changelog format, API, loader behavior, and tests. |
| internal/changelog/changelog.go | Defines public Entry, Parse, and Between API. |
| internal/changelog/changelog_test.go | Tests parsing rules, metadata, title extraction, and range filtering. |
| internal/bundler/versions.go | Switches bundler to the consolidated internal/semver. |
| internal/bundler/semver/CLAUDE.md | Removes obsolete docs for the deleted bundler semver subpackage. |
| internal/bundler/registry/types.go | Switches registry types to internal/semver. |
| internal/bundler/registry/CLAUDE.md | Updates dependency notes to reference internal/semver. |
| internal/bundler/CLAUDE.md | Updates docs to remove bundler semver subpackage references. |
| docs/installation.mdx | Documents the “What’s new” teaser behavior and suppression conditions. |
| CONTRIBUTING.md | Adds changelog entry requirements and formatting guidance. |
| claude-plugin/clawker-support/skills/clawker-support/reference/troubleshooting.md | Adds troubleshooting entry for missing “What’s new” note after upgrades. |
| claude-plugin/clawker-support/.claude-plugin/plugin.json | Bumps plugin version. |
| CHANGELOG.md | Adds curated root changelog content and metadata format. |
| .serena/memories/release-guide.md | Updates release guide to describe curated header + auto changelog composition. |
| .serena/memories/changelog-system.md | Adds system memory doc for the changelog design and release flow. |
| .goreleaser.yaml | Adds grouped changelog config and documents --release-header usage. |
| .github/workflows/release-build.yml | Extracts tag section from CHANGELOG.md and passes it via --release-header. |
| .clawker.yaml | Adds contributor-assistant GitHub action allowlist paths. |
| .claude/rules/storage-schema.md | Documents time.Time schema behavior as KindTime. |
| .claude/rules/dependency-placement.md | Updates dependency placement notes for consolidated semver. |
| .claude/docs/changelog-system-design.md | Adds shipped design doc for curated changelog system. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…notes Add a curated, hand-maintained CHANGELOG.md (Keep a Changelog format with per-entry HTML-comment metadata) and a `clawker changelog` command so users learn about user-facing changes after upgrading instead of silently missing them. The CLI fetches CHANGELOG.md over the network from `main` (like the update checker) — not embedded in the binary — so a forgotten entry committed after a release is picked up without re-releasing; the installed version is the display ceiling. internal/changelog splits into a pure core (Parse/Between/ForVersion) and an I/O layer (Fetch/Loader with on-disk cache, TTL gate, silent degrade). A cursor-driven show-once teaser runs in a non-blocking background goroutine, TTL-gated and suppressed on CI/non-TTY. State moves to storage.Store[CliState] (field-merge, no clobber) with a ChangelogFetchedAt timestamp; new Factory nouns f.State and f.Changelog. Release notes: the release workflow awk-extracts the tag's section from CHANGELOG.md and passes it to goreleaser via --release-header (coexists with the auto commit-group changelog). Consolidate three semver implementations into one internal/semver leaf package (moved from internal/bundler/semver). Add v-tolerant CompareStrings and IsValidLoose; delete the changelog and update copies. update.IsNewer keeps an IsValidLoose guard to preserve the conservative "unparseable -> not newer" contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The curated changelog surfaces through a single automatic one-time "what's new" teaser after a command runs — there is no on-demand `clawker changelog` command. The teaser lists the entry titles gained since the last shown version with a per-entry docs link; the first run with no catch-up seeds the cursor silently (no welcome). The network loader, cursor state, and curated CHANGELOG.md / release-notes flow are unchanged. Harden both background goroutines in Main(): each now sends on its channel exactly once from a deferred function that also recovers from panic, so a panic in the update-check or changelog-load goroutine can neither crash a command that already succeeded nor deadlock the foreground blocking read. Also: drop dead code (changelog.ForVersion had no remaining caller), fix comment-policy violations (historical narration + a hard-spelled state filename), dedup the DEV sentinel to consts.DevVersion, and close three coverage gaps (partial-semver header rejection, the tagFromSubsection mapping table, and the fresh-cache-but-missing-file refetch branch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…racy, test cleanup - loader: thread a *logger.Logger so a fetch failure that falls back to the on-disk cache is recorded to the file log instead of vanishing (this was the one genuine silent swallow — a 404 before CHANGELOG.md lands on main, or a DNS/firewall block, now leaves a breadcrumb) - update: extract an unexported checkForUpdate(...url) core that CheckForUpdate delegates to; tests now drive the real gate→fetch→assemble path instead of a parallel checkForUpdateWithURL reimplementation. Drop that helper, the dead fetchLatestRelease, and the test that only exercised Go channel semantics - firewall: add the missing leading slash on the raw.githubusercontent.com contributor-assistant/github-action path rule — without it the stored path never prefix-matched under path_default: deny, so the allow was dead - docs: correct the maybeShowChangelog signature (5 args, entries third); fix the design-doc cursor pseudocode to use the priorCurrentVersion snapshot, not a live current_version read that races the update goroutine; add the missing entries==nil guard to every pseudocode rendering; retire the Entry.Body "rendered verbatim by the CLI" claim (the teaser renders Title, not Body); drop legacy / deleted-command narration - release workflow: strip the clawker metadata HTML comment from the awk- extracted release-notes header (invisible on GitHub, but present in the raw release body) - tests: add corrupt-fresh-cache degrade coverage and a nil-entries-with-cursor case; drop the redundant persisted-yaml-keys test (the round-trip tests prove serialization more strongly) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9d2e555 to
0c69fd9
Compare
CI unit tests failed only under GitHub Actions: newChangelogTestFactory did not neutralize ambient teaser-suppression env, so CI=true (set by Actions) tripped changelogSuppressed and silenced every teaser-expecting case. The helper now clears CI + CLAWKER_NO_UPDATE_NOTIFIER; the opt-out test re-sets its env after the helper. Review-round fixes: - changelog.stale(): honor the documented contract — a non-positive TTL is now treated as "always stale" (was: relied on incidental now>last). - .goreleaser.yaml: de-lazy the changelog.group regexps (.*? -> .*, ?? -> ?). Behavior-identical (GoReleaser contains-matches via Go RE2 MatchString); drops the non-greedy constructs that read as RE2-invalid. - docs/memories: correct the NewLoader signature (now takes log *logger.Logger) in changelog/CLAUDE.md, cmdutil/CLAUDE.md, changelog-system-design.md, and the changelog-system memory; rewrite the stale release-guide release-header section to the awk -> release-notes.md -> --release-header flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+94
to
+97
| after = strings.TrimSpace(after) | ||
| if sep := strings.Index(after, strings.TrimSpace(dateSeparator)); sep >= 0 { | ||
| date = strings.TrimSpace(after[sep+len(strings.TrimSpace(dateSeparator)):]) | ||
| } |
Comment on lines
+73
to
+78
| awk -v ver="## [${VERSION}]" ' | ||
| index($0, ver) == 1 { collecting = 1; print; next } | ||
| collecting && /^## \[/ { exit } | ||
| collecting && /^<!-- clawker:/ { next } | ||
| collecting { print } | ||
| ' CHANGELOG.md > release-notes.md |
…ata layer
A release spans many merged PRs and mixed change kinds, so a single
tag/title/docs scalar per release was the wrong model — and the Tag field was
parsed but never rendered anywhere. Shrink changelog.Entry to {Version, Date,
Body} and render the whole Keep-a-Changelog section as markdown in the
"what's new" teaser, so multi-section releases (Added + Fixed + ...) surface
fully instead of collapsing to one derived headline.
- Add iostreams.RenderMarkdown (glamour, compact margin-free style, soft-wrap;
theme/color follow IOStreams detection, ASCII base when color disabled).
- Strip the parser's metadata/tag/title machinery (parseMetaComment,
tagFromSubsection, titleFromBody, Tag type, section consts); bodies keep all
### sections and inline links, with HTML comments and the link-ref block
stripped.
- Curate root CHANGELOG.md: drop the <!-- clawker: --> lines, inline docs links
into bullets.
- Generalize the release-notes awk to drop any single-line HTML comment.
- Add glamour deps to NOTICE; sync package docs + clawker-support plugin
(1.0.15); end-user docs describe the teaser in user terms without naming
output channels.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ven coverage The changelog teaser suite asserted only on presence (version strings, entry counts) and hand-fed Entry structs that bypassed the parser, so the body-render path the teaser exists for could break without a single failing test. - Drive maybeShowChangelog tests through real changelog.Parse (teaserChangelogFixture) instead of hand-built Entry structs, exercising the parse→cursor seam. - Add TestMaybeShowChangelog_E2E_RendersParsedBody: real bytes over httptest → Loader.Load → maybeShowChangelog → Between → printChangelogTeaser → RenderMarkdown, asserting gained release bodies render and non-gained / Unreleased bodies don't. - Assert parsed Body content (not just len/version) in the loader fetch + cache -fallback tests, closing the gap where a dropped Body stayed green. - Merge the redundant TestParse_HTMLCommentStripped into TestParse_Body (same isHTMLComment branch); cover the non-clawker comment flavor via a testdata line. - Fix the stale loadFixture doc comment and drop a dead fixture comment. Each new/changed assertion was verified to go red under mutation (deleted body render, zeroed parse Body, disabled comment stripping, render-all-not-gained). Comment/link-ref stripping is asserted at the parsed-Body level, not teaser output, because glamour also strips them on render (a teaser-output check would stay green if the parser's stripping broke). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Users miss user-facing changes after
brew upgrade, and GoReleaser ships a flat commit list as release notes. This adds a curated changelog with two payoffs:CHANGELOG.mdfrommainover the network (like the update checker — clawker runs on the host, always online), and after a command completes prints a one-time, cursor-driven teaser of entries gained since the last shown version. TTY-only, suppressed onCI/CLAWKER_NO_UPDATE_NOTIFIER/ DEV, loaded in a background goroutine so it never blocks the command.## [x.y.z]section fromCHANGELOG.mdand feeds it to GoReleaser via--release-header(curated section above the auto commit groups, not replacing them).There is no on-demand command — only the automatic teaser. The metadata fetch is async to the release, so a forgotten entry self-heals: commit it to
CHANGELOG.mdlater and the live fetch picks it up without a re-release.Shape
internal/changelog— pure core (Parse/Between, no I/O) + I/O layer (Fetch,Loader= fetch + on-disk cache + TTL gate + graceful degrade; degrade-path failures now log to the file log).internal/state—storage.Store[CliState]facade (checked_at / versions /last_seen_changelog/changelog_fetched_at); field-merge writes so the update goroutine and the changelog cursor never clobber each other. Reads the existingupdate-state.yamlin place.internal/update— made pure (fetch + compare, no persistence); the caller owns state. URL-parameterized unexported core so tests drive real code.internal/semver— consolidated leaf (moved out ofinternal/bundler/semver);CompareStrings(v-tolerant, total) +IsValidLoose.internal/storage—KindTimesupport fortime.Timefields (RFC3339 scalar).Verification
golangci-lintclean;go build ./...clean.--release-header) is fully exercised only by a real tag;CHANGELOG.mdmust land onmainfor the teaser fetch (pre-merge it degrades 404 → cache → empty, as designed).Review
Two review rounds run; all findings actioned or explained in commit
9d2e555a(silent-failure log on the loader degrade path, doc/signature accuracy, test cleanup, a dead firewall path rule).🤖 Generated with Claude Code