carlos / platform Public
π€ Platform changelog implementation plan
> For agentic workers: executed natively by the session that wrote it > (Paul, 2026-10-06: "work overnight until this is done"), with an Astra > whole-branch review at the end. Steps use checkbox syntax.
Goal: docs/site/changelog.json generated from git, release notes and Amadan, gated in CI, and rendered at carlosframework.com/platform/changelog.
Architecture: internal/changelog holds pure parsers and resolvers plus a Build that drives three source interfaces (Git, Releases, Discussions); cmd/changelog wires the real sources (git, gh, amadan) and writes the file. internal/docsite gates the committed file. The website vendors it through hack/sync-docs.mjs and renders it with one Nunjucks template.
Tech stack: Go 1.26 stdlib; Eleventy 3 + markdown-it (website).
Spec: docs/superpowers/specs/2026-10-06-platform-changelog-design.md
Global constraints
- No email address in
changelog.jsonor on the page. Unknown identity = generator failure. - No GitHub PR links (the mirror is private). Links only to amadan.net, github.com/carlosframework/releases and /docs.
- Change lines are verbatim records; the page carries one visible π€ on its h1.
- Website: no browser JS beyond the existing disclaimer; light and dark both work; no em dashes in new copy.
- Copy review gates the page's framing strings and the README step before anything ships.
- Tests:
export GOCACHE=$TMPDIR/gocache GOFLAGS=-mod=mod GOPROXY=off, runnice -n 19, package by path, never./....
Review focus
- A squash whose branch was later re-used by name (two tombstones over time): own-commit range must stop at the squash's parent, never pull in later work. Test: tombstone range is
<squash>^..<tombstone>filtered to commits not on main. - A
#Nin a GitHub-era message that collides with a live Amadan id: rejected by the created-before-commit rule. Test with a fake discussion created after the commit. - A release-note bullet containing an email-like string (
bill@): must not trip the email guard unless it is a full address from people.json or a keymail. Test both. - A tag with zero first-parent commits in range (v0.24.1 had one, a re-tag could have none): renders "No changes" rather than failing. Test.
- A heading in old notes (
## carlos deploy) inside a markdown body: demoted to####so the page outline stays intact. Test.
---
Task 1: identities and credit (internal/changelog/credit.go)
- Create
credit.go,credit_test.go. - Produces:
type People map[string]string(email or keymail β display name, keys lower-cased);type Credit struct{ Person stringjson:"person,omitempty"; Agents []stringjson:"agents,omitempty"};func ParseIdentity(name, email string, people People) (person, agent string, skip bool, err error);func CoAuthors(message string) [][2]string(name, email fromCo-Authored-By:trailers, case-insensitive);func Tally(commits []CommitIdent, people People) ([]Credit, error)wheretype CommitIdent struct{ AuthorName, AuthorEmail, Message string }. - Rules from the spec's Credit paragraph. Tests: keymail agent with known person;
claude-codeβclaude;noreply@anthropic.com; codex name; plain person by email; unknown email β error naming it; GitHub/ledger skipped; agent-only trailer attaches to a person author; ordering by count; an agent with no person anywhere stands alone.
Task 2: messages, ranges and notes (branch.go, notes.go, refs.go)
func BranchName(message string) string(three patterns, first match).func DiscussionRefs(message string) []int(#N,discussion N,amadan #N; deduped, sorted).type Notes struct{ Form string; Sections []NoteSection; MD string },type NoteSection struct{ Title string; Bullets []Bullet },type Bullet struct{ MD string; Lead string; Branches []string; Docs []string };func ParseNotes(body string) *Notes(bullets form when a- **bullet follows acarlos vX (platformline; else markdown form with boilerplate stripped and headings demoted by two, capped at h6).Lead= text inside the first**β¦**of a bullet.func CLIDocLinks(md string, cliHeadings map[string]string) []string(inline codecarlos β¦β longest heading match β/docs/cli#anchor).- Tests per Review Focus 3 and 5, plus each operator-heading variant and the v0.1.0 one-paragraph body.
Task 3: Build on fakes and on a scratch repo (build.go)
- Interfaces:
Git{ Tags() ([]Tag, error); FirstParent(from, to string) ([]Commit, error); Range(from, to string) ([]Commit, error); OnMain(sha string) bool; Tombstone(branch string) (string, bool); DocsHunks(sha string) ([]DocsHunk, error); Branches() map[string]bool },Releases{ Body(tag string) (string, bool, error) },Discussions{ Show(id int) (*Discussion, error); ForBranch(b string) ([]Discussion, error) }. func Build(src Sources, people People, links Links, site *docsite.Site) (*Changelog, error).- Docs links from hunks: nearest heading at or above each added line, kept only if
sitehas that page and anchor. - Email guard over the marshalled output.
gitexec.go: the realGitovergitsubprocesses; scratch-repo test builds v0.1.0, a direct commit, a two-parent merge, a squash with arefs/amadan/closed/xtombstone, and tag v0.2.0, then checks ranges, branches, credit and docs links.
Task 4: cmd/changelog, people.json, real sources
cmd/changelog/main.go: flags-out,-repo(defaultcarlos/platform); realReleasesovergh release view <tag> -R carlosframework/releases --json body, realDiscussionsoveramadan discuss show|list β¦ -json; loadsdocs/site/people.json,docs/site/changelog-links.json(missing = empty),docsite.Load("docs/site").docs/site/people.jsonfrom the identities found in history (names only from git's own author names).- Run it; read the output for every release; fix what reads wrong.
Task 5: bullet links backfill (docs/site/changelog-links.json)
- For v0.19.0 on, map each bullet's lead to the branches behind it by reading each change's subject and diff. Regenerate.
Task 6: the gate (internal/docsite/changelog_test.go)
- Checks from the spec's "Gates and upkeep". Mutation check: break one docs anchor and one email in a temp copy and watch each fail.
Task 7: website
- In a worktree of
carlosframework/websiteon branchplatform-changelog:hack/sync-docs.mjsalso vendorschangelog.jsonβsrc/_data/docschangelog.json(and--checkcompares it);src/changelog.njk; a markdown filter for notes;check-docs.mjschecks the page; a link fromsrc/platform/index.html.npm run check, then look at it in a browser at both colour schemes.
Task 8: README release step, copy review, Astra code review, merge
- README "Releasing the platform" step 10. Copy review of the page framing and the README step. Astra review of both branches; fix;
amadan branch merge; website PR (GitHub) left for Paul unless copy approved.