mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
changesets/action copies a CHANGELOG.md section into the release as it stands, so a whole release rendered as one bullet behind its commit hash, with `### Thanks` as literal text and "Minor Changes" as the only real heading. The Release workflow now rebuilds the body from that section: Thanks first, then What's new with a heading per change, Fixes, Also in this release and a compare link, with "Behaviour change:" and "Breaking:" in bold. The release is titled `Codeman <x.y.z>`; the tag stays `codeman@<x.y.z>`, which is what the updater and the update toast read. This cannot be written into CHANGELOG.md instead: `### <Type> Changes` is hardcoded upstream, and a `## ` line would end the section for changesets/action and versionSection(). So the changelog keeps its format and the toast is unaffected. npm has already published when the step runs, so a formatter failure leaves the changesets text with a warning and the retag still happens. SECTION_LABELS is now shared with whats-new.mjs so the toast and the page agree on what counts as a headline. docs/releasing.md records the changeset shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
11 KiB
Markdown
81 lines
11 KiB
Markdown
# Releasing Codeman (the COM procedure)
|
|
|
|
> Moved from the root `CLAUDE.md`, which keeps a pointer here. What SemVer covers is defined in `docs/versioning-policy.md`. The root file's Session Safety rules (branch check, explicit paths, never `git add -A`) apply to every step.
|
|
|
|
When user says "COM":
|
|
|
|
1. **Determine bump type**: `COM` = patch (default), `COM minor` = minor, `COM major` = major
|
|
2. **Create a changeset file** (no interactive prompts). Write a `.md` file in `.changeset/` with a random filename:
|
|
|
|
```bash
|
|
cat > .changeset/$(openssl rand -hex 4).md << 'CHANGESET'
|
|
---
|
|
"aicodeman": patch
|
|
---
|
|
|
|
Detailed description of ALL changes since last release (not just the most recent commit — review full git log since last version tag)
|
|
CHANGESET
|
|
```
|
|
|
|
Replace `patch` with `minor` or `major` as needed. Include `"xterm-zerolag-input": patch` on a separate line if that package changed too.
|
|
|
|
**Write it in the shape the release page is built from.** `CHANGELOG.md` keeps changesets' format, and the `Release` workflow rebuilds the GitHub release from that section (`scripts/generate-release-notes.mjs`, which also titles it `Codeman <x.y.z>`). The in-app update toast reads the same bold lead-ins:
|
|
|
|
```markdown
|
|
### Thanks
|
|
- @someone for what they did (#123).
|
|
|
|
**What the change does for the user (#123).** Two or three plain sentences.
|
|
|
|
**Fixes.**
|
|
- **What works now (#124).** What was wrong. Behaviour change: what a user has to do differently.
|
|
|
|
**Fixes applied while landing.** One sentence each.
|
|
|
|
**Docs and tests.** One sentence.
|
|
```
|
|
|
|
`### Thanks` stays first and opens the page. A bold lead-in that ends in punctuation becomes a heading under What's new, so make it a short headline. `**Fixes.**` and its list become a Fixes section, and the last two labels share an Also in this release section (the labels are `SECTION_LABELS` in `scripts/lib/whats-new.mjs`). `Behaviour change:` and `Breaking:` are put in bold wherever they appear, so use those words for anything a user has to act on. Keep each change short: how it works belongs in the docs, and a long paragraph is a wall of text even under a heading. Text the script does not recognise stays on the page as plain paragraphs. After step 3, `node scripts/generate-release-notes.mjs` prints the page. If the workflow warns that it kept the changesets notes, fix the changelog and run `gh release edit codeman@<x.y.z> --notes-file <file>`.
|
|
|
|
3. **Consume the changeset**: `npm run version-packages` (auto-bumps `package.json` files, updates `CHANGELOG.md`, runs `npm install --package-lock-only`, and verifies lockfile sync via `scripts/check-lockfile-sync.mjs` — all in one command; never hand-edit `CHANGELOG.md` or `package-lock.json` versions. It also writes `src/web/public/whats-new.json` (`scripts/generate-whats-new.mjs`): the bold lead-in of each change in the new `CHANGELOG.md` section, which the browser announces once per version in a toast. Commit it with the release; `test/whats-new.test.ts` fails when its version differs from `package.json`, so a hand bump needs `node scripts/generate-whats-new.mjs && npx prettier --write src/web/public/whats-new.json` too. A release whose notes have no bold lead-ins ships an empty list and shows no toast)
|
|
4. **Sync CLAUDE.md version**: Update the `**Version**` line in the root `CLAUDE.md` (under COM Shorthand) to match the new version from `package.json`
|
|
5. **Commit and deploy**: verify the branch first (`git branch --show-current`), then stage EXPLICIT paths — never `git add -A`, which has swept another session's WIP into a release. `git status --short` and account for every line before committing:
|
|
`git add <paths> && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
|
|
6. **Refresh the getcodeman.com version badge**: the landing page's status bar carries the release version (`v<x.y.z> · getcodeman.com · MIT`), so it goes stale on every release if nobody bumps it. The site source and its deploy script are maintained outside this repository, on the maintainer's machine only; follow the local site handbook there, which also covers the numbers strip and `sitemap.xml` refresh that belong in the same pass. Poll production (`curl -s https://getcodeman.com/ | grep v<x.y.z>`) before calling it done, since the edge lags a deploy by up to a minute. Not applicable to contributor clones — skip it and say so.
|
|
7. **Wait for CI**: after `git push`, TWO workflows fire per master push: `CI` and `Release` (the npm publish, the GitHub release and the MCP registry publish). List both runs for the pushed commit with `gh run list --commit $(git rev-parse HEAD) --json databaseId,workflowName` and watch EACH with `gh run watch <id> --exit-status`. Confirm both pass before considering the release done (`gh run list -L 1` returns only one of the two).
|
|
|
|
8. **Check the MCP registry**: `codeman mcp` is listed in the official MCP registry as `io.github.Ark0N/codeman`, and the registry does not follow npm, so every release is published there. The `Release` workflow does this itself in its `MCP registry` job (`.github/workflows/publish-mcp.yml`): it waits until npm serves the version, because the registry checks that the npm package exists and carries `mcpName: io.github.Ark0N/codeman`, signs in with the workflow's GitHub OIDC token (no secret, no browser) and publishes the `config/mcp-registry/server.json` that `npm run version-packages` wrote (`scripts/sync-plugin.mjs`). A version the registry already has is skipped. Confirm with `curl -s 'https://registry.modelcontextprotocol.io/v0/servers?search=io.github.Ark0N/codeman' | jq -r '.servers[] | "\(.server.version) latest=\(._meta["io.modelcontextprotocol.registry/official"].isLatest)"'`: the new version must be the one with `latest=true`. If the job failed, `gh workflow run publish-mcp.yml` publishes the version on master again. Its publisher is pinned: bump `MCP_PUBLISHER_VERSION` and `MCP_PUBLISHER_SHA256` together, the digest from the release's asset. The local fallback is `cd config/mcp-registry && ~/.local/bin/mcp-publisher login github && ~/.local/bin/mcp-publisher publish`, signed in as the account that owns the `io.github.Ark0N/` namespace, with mcp-publisher 1.8.1 or newer, the release binary from https://github.com/modelcontextprotocol/registry/releases: the Snap Store package is stuck at 1.1.0 and rejects the current `$schema` with a false "deprecated schema detected", so never edit `server.json` for that error. Older publishers also save their login as `.mcpregistry_github_token` and `.mcpregistry_registry_token` (live tokens) in the current directory; move those out of the repo, never commit them.
|
|
|
|
9. **Announce it in Discussions**: every release gets a post in the [Announcements](https://github.com/Ark0N/Codeman/discussions/categories/announcements) category, shaped like #418 and #302: the short version first (one bold lead-in per theme, features before fixes, what it does for the user rather than how it works), contributor @-mentions inline where their work is described (a mention notifies them, and a contributor reposting is the cheapest reach this project has), a link to the release, and the Thanks names at the end. Casual first-person voice, no em-dashes, humanizer pass when the skill is available. A same-day follow-on patch (1.28.1 after 1.28.0) is folded into the previous post as an edit, never a second thread. Post it with `gh api graphql -F body=@<file> -f title='Codeman <x.y.z>: <hook>' -f repo=R_kgDOQ-SMDg -f cat=DIC_kwDOQ-SMDs4DCHZE -f query='mutation($repo:ID!,$cat:ID!,$title:String!,$body:String!){createDiscussion(input:{repositoryId:$repo,categoryId:$cat,title:$title,body:$body}){discussion{number url}}}'` (the repo's node id and its Announcements category id; `pinDiscussion` does not exist in the API, so pinning stays a click in the UI). This step exists because announcements stopped at 1.18 (#302) while ten releases shipped with nobody notified; #418 is the backfill covering 1.19.0 to 1.28.1, and release notes only count as content once they reach a surface people are subscribed to.
|
|
|
|
CI runs `npm run check:lockfile` on every push/PR, so lockfile drift fails the build even if the `version-packages` script is bypassed.
|
|
|
|
## Security notices
|
|
|
|
A security notice is a sticky warning card about something outside Codeman that its users should act on (the first: OpenCode's Console breach, October 2026). On a browser that gets the release's update toast it appears inside that toast instead, so nobody sees it twice. Notices live in `src/web/public/security-notices.json`, which is maintained BY HAND; never put one in `whats-new.json`, which `npm run version-packages` rewrites. A notice reaches users only with a release, so add it before the COM that should carry it.
|
|
|
|
**Adding one**: append an entry to `notices`:
|
|
|
|
```json
|
|
{
|
|
"id": "opencode-console-breach-2026-10",
|
|
"severity": "warning",
|
|
"cli": "opencode",
|
|
"published": "2026-10-17",
|
|
"expires": "2026-12-31",
|
|
"title": "OpenCode Console breach: check your API keys",
|
|
"message": "A few short plain sentences, about 50 words at most: what happened and what to do.",
|
|
"link": { "label": "Read OpenCode's notice", "url": "https://..." },
|
|
"i18n": { "zh-CN": { "title": "...", "message": "...", "label": "..." } }
|
|
}
|
|
```
|
|
|
|
- `id` is stable and unique, and never reused: a browser that closed a notice remembers its id (`codeman-security-notices-seen` in localStorage), so a reworded notice under the same id is NOT shown again to anyone who closed it. Give a materially new warning a new id.
|
|
- `cli` (optional) is a registry CLI id. The notice is then shown only on a server where that CLI is installed and enabled; without `cli` it is shown to everyone.
|
|
- `published` and `expires` are inclusive `YYYY-MM-DD` days in the viewer's local time. Set `published` a day before the release ships, so the notice is already live in every timezone when it lands (a browser still on the previous day would otherwise take the plain update toast and see the notice later as a separate card). `expires` is what keeps an old notice from alarming a browser that first opens Codeman months later, so always set one.
|
|
- Keep `message` to about 50 words and do not repeat the card's chrome (the "Security notice" eyebrow and the title are already on it): on a phone the card shares the screen with the update toast's text, and a longer one has to be scrolled to reach its buttons. State only what the affected vendor has said.
|
|
- `title` (optional) is the card's bold line; keep it short. `link.url` must be https. `i18n` is optional; a missing string falls back to the English one.
|
|
- Run `npx prettier --write src/web/public/security-notices.json` and `npm test -- test/security-notices.test.ts`, which validates the file (unique ids, real dates, https links, every `cli` a stock registry id, a zh-CN title wherever there is an English one).
|
|
|
|
**Retiring one**: let it expire, or delete the entry once it has. Removing it early stops it on the next release only; browsers already showing it keep the card until they close it or reload. Until then a fresh install gets it too, because a new browser has no seen list, so give an `expires` no later than the warning stays useful. An empty list (`{ "notices": [] }`) is a valid file.
|