carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

πŸ€– 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.json or 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, run nice -n 19, package by path, never ./....

Review focus

  1. 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.
  2. A #N in 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.
  3. 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.
  4. 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.
  5. 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 string json:"person,omitempty"; Agents []string json:"agents,omitempty" }; func ParseIdentity(name, email string, people People) (person, agent string, skip bool, err error); func CoAuthors(message string) [][2]string (name, email from Co-Authored-By: trailers, case-insensitive); func Tally(commits []CommitIdent, people People) ([]Credit, error) where type 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 a carlos vX (platform line; 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 code carlos … β†’ 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 site has that page and anchor.
  • Email guard over the marshalled output.
  • gitexec.go: the real Git over git subprocesses; scratch-repo test builds v0.1.0, a direct commit, a two-parent merge, a squash with a refs/amadan/closed/x tombstone, 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 (default carlos/platform); real Releases over gh release view <tag> -R carlosframework/releases --json body, real Discussions over amadan discuss show|list … -json; loads docs/site/people.json, docs/site/changelog-links.json (missing = empty), docsite.Load("docs/site").
  • docs/site/people.json from 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/website on branch platform-changelog: hack/sync-docs.mjs also vendors changelog.json β†’ src/_data/docschangelog.json (and --check compares it); src/changelog.njk; a markdown filter for notes; check-docs.mjs checks the page; a link from src/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.