Releasing & contributing (modusbrain)
The full release + contributor process. CLAUDE.md keeps the ship-critical IRON RULES inline (the Version-locations table, branch=workspace, post-ship/document-release,
the Privacy + Responsible-disclosure rules, PR-title-version-first, never-hand-roll-ship)
and points here for everything else. Before any ship, read this in full. Use /ship —
never hand-roll a release.
Pre-ship requirements
Before shipping (/ship) or reviewing (/review), always run the full test suite. Two equivalent paths: Path A — local CI gate (recommended, v0.23.1+):bun run ci:localruns the entire stack inside Docker: gitleaks (host), guards + typecheck, then 4-shard parallel unit + E2E against four pgvector containers plus a transaction-mode PgBouncer service (unit phase keepsDATABASE_URLunset;--no-shardfor the legacy sequential flow). Stronger than PR CI’s 2-file Tier 1 set; closer to what nightly Tier 1 catches. Spins up + tears down postgres automatically viadocker-compose.ci.yml. Override the host port withMODUSBRAIN_CI_PG_PORT=5435 bun run ci:localif 5434 collides.bun run ci:local:diffruns only the E2E files matched by the diff selector (scripts/select-e2e.ts), falling back to ALL E2E files on unmapped src/ paths or schema/skills/package.json changes. Fast iteration during a focused branch.
bun test— unit tests (no database required)- Follow the “E2E test DB lifecycle” steps above to spin up the test DB,
run
bun run test:e2e, then tear it down.
bun test (the bun runner)
skips TypeScript type checking — it only enforces runtime behavior.
Three ways to actually gate on types:
bun run test(npm script inpackage.json) — includesbun run typecheckplus the four shell pre-checks (check-jsonb-pattern.sh,check-progress-to-stdout.sh,check-trailing-newline.sh,check-wasm-embedded.sh) before the runner. Use this mid-branch.bun run typecheck—tsc --noEmitstandalone. Fast (~5s on this repo).bun run ci:local— the full local CI gate from Path A.
bun test test/foo.test.ts,
seeing it pass, pushing — and CI’s separate typecheck stage rejects an
invalid type literal that the runner accepted. Caught one of these
shipping the v0.23.2 round-trip E2E (type: 'reflection' is not a
member of PageType). Run bun run typecheck once before push, even
when only test files changed.
CHANGELOG + VERSION are branch-scoped
VERSION and CHANGELOG describe what THIS branch adds vs master, not how we got here. Every feature branch that ships gets its own version bump and CHANGELOG entry. The entry is product release notes for users; it is not a log of internal decisions, review rounds, or codex findings. Write the CHANGELOG entry at /ship time, not during development. Mid-branch iterations, review rounds (CEO/Eng/Codex/DX), and implementation detours belong in the plan file at~/.claude/plans/, not in the CHANGELOG. One unified entry
per branch, covering what the branch added vs the base branch.
Never edit a CHANGELOG entry that already landed on master. If master has
v0.18.2 and your branch adds features, bump to the next version (v0.19.0, not
editing master’s v0.18.2). When merging master into your branch, master may
bring new CHANGELOG entries above yours — push your entry above master’s
latest and verify:
- Does CHANGELOG have your branch’s own entry separate from master’s entries?
- Is VERSION higher than master’s VERSION?
- Is your entry the topmost
## [X.Y.Z]entry? grep "^## \[" CHANGELOG.mdshows a contiguous version sequence?
- Lead with what the user can now do that they couldn’t before. Sell the capability.
- Plain language, not implementation details. “You can now…” not “Refactored the…”
- Never mention internal artifacts: plan file IDs, decision tags (D-CX-#, F-ENG-#), review rounds, codex findings, subcontractor credits. These are invisible to users.
- Put contributor-facing changes in a separate
### For contributorssection at the bottom. - Every entry should make someone think “oh nice, I want to try that.”
- “Codex caught X that the CEO review missed” — private process detail.
- “D-CX-3 split errors/warnings” — tag is meaningless to users; name the feature instead.
- “Fix-wave PR #N supersedes #M” — supersede chains belong in PR bodies, not release notes.
- “215 new cases, 3 decisions applied, 7 reviews cleared” — these are planning-mode metrics.
- The user-facing change: what commands exist now, what flag was added, what behavior fixed.
- Numbers that mean something to the user: TTHW, commands that timed out before, detection counts.
- Upgrade instructions:
modusbrain upgrade+ any manual step if needed. - Credit to external contributors when a community PR was incorporated.
CHANGELOG voice + release-summary format
IRON RULE: the CHANGELOG describes what the user gets, not how the work happened. Nobody reading release notes cares that codex caught a bug, that the plan went through CEO + eng review, that the migration was originally numbered v68 and renumbered to v79 during master merge, or that two review rounds caught architectural mistakes. The reader cares whatmodusbrain brainstorm does and how to use it. If a fact only exists because
of the development process, it does NOT belong in the CHANGELOG.
Specifically forbidden in CHANGELOG entries:
- Any mention of review processes (CEO review, eng review, codex review, plan-eng-review, outside voice, adversarial review, autoplan, /review).
- “What we caught and fixed before merging” sections. Bugs found pre-merge are not changes — they’re things that didn’t ship.
- Plan file references, plan IDs, plan decision tags (D1, D14, D-CDX-3).
- Migration version drama (“originally v68”, “renumbered to v77”, “claimed by parallel waves”) — just say “Migration v79 adds X.” If the user cares about migration ordering, they read the diff.
- Round counts, finding counts, decision counts (“25 findings across 2 rounds”, “8 architectural decisions”, “5/6 expansions accepted”).
- Names of internal collaborators (“codex caught”, “the reviewer flagged”, “Claude noticed”).
- “Plan + reviews” summary bullets. The plan lives in
~/.claude/plans/; if a future reader wants the backstory they can grep there. - Any wording that frames a shipped feature as a recovery from a planning mistake (“the first plan was wrong”, “we corrected the approach”, “the shipped version supersedes the original design”).
CHANGELOG.md MUST start with a release-summary section in
the GStack/Shubham voice — one viewport’s worth of prose + tables that lands like a
verdict, not marketing. The itemized changelog (subsections, bullets, files) goes
BELOW that summary, separated by a ### Itemized changes header.
The release-summary section gets read by humans, by the auto-update agent, and by
anyone deciding whether to upgrade. The itemized list is for agents that need to
know exactly what changed.
Release-summary template
Iron rule: lead ELI10, get precise after. The first ~150 words of every entry must be readable by someone who does NOT know modusbrain’s internals. No file paths, no function names, no internal constants, no acronyms (no “RRF”, no “knobsHash”, no “MODE_BUNDLES”, no “CDX-4”), no jargon that requires reading the codebase to parse. Lead with the user-visible behavior change, in everyday English, like you’re explaining it to a smart engineer who has never opened the repo. THEN, once the reader knows what shipped and why they’d care, drill into the precise details: real file paths, real function names, real config keys, real numbers. The precision part is required (the entry is also the technical record of what changed), but it lives AFTER the plain-English lead, never before it. The shape:- One-line bold headline. What changed for the user, in human English. No jargon. No internal terms. Example good: “Your search stops boosting weak pages just because they have a lot of links pointing at them.” Example bad: “PostFusionOpts gains floorRatio; KNOBS_HASH_VERSION bumped 2→3.”
- Plain-English opener (~3-5 sentences). Describe the problem this fixes in everyday terms. Pretend the reader has a brain full of meeting notes and people pages and wants to know if this release helps them. Concrete example beats abstract description.
- A “How to turn it on” or “How to use it” section with paste-ready commands. Real flags, real config keys. This is where precision starts.
- A “What you’d see in a concrete example” or “The X numbers that matter” section with a table. Use everyday-language column headers (“Page”, “Match quality”, “Has many backlinks?”) even when the underlying mechanism is technical. The table teaches what the feature does without requiring the reader to understand how.
- A “What’s safe to know about” or “Things to watch” section for caveats, side effects, cache invalidation, mid-deploy notes. Still in plain language.
- A “What we caught and fixed before merging” section if the work went through review (CEO/eng/codex/outside-voice). Translate review findings into plain English. “We caught a stale-cache bug” beats “knobsHash() did not include floorRatio in the v=2 hash input.”
### Itemized changes(precision lives here). File paths, function names, types, constants, line numbers. This section is for engineers who need to know exactly what moved.
- No em dashes (use commas, periods, ”…”).
- No AI vocabulary (delve, robust, comprehensive, nuanced, fundamental, etc.) or banned phrases (“here’s the kicker”, “the bottom line”, etc.).
- Real numbers, real file names, real commands AFTER the ELI10 lead. Not “fast” but “~30s on 30K pages.” In the ELI10 lead, “fast enough that you won’t notice” or “~30 seconds even on a big brain.”
- Short paragraphs, mix one-sentence punches with 2-3 sentence runs.
- Connect to user outcomes: “the agent does ~3x less reading” beats “improved precision.”
- Be direct about quality. “Well-designed” or “this is a mess.” No dancing.
- CHANGELOG.md previous entry for prior context
- Latest
modusbrain-evals/docs/benchmarks/[latest].mdfor headline numbers (sibling repo) - Recent commits (
git log <prev-version>..HEAD --oneline) for what shipped - Don’t make up numbers. If a metric isn’t in a benchmark or production data, don’t include it. Say “no measurement yet” if asked.
”To take advantage of v[version]” block (required, v0.13+)
After the release-summary and BEFORE### Itemized changes, every ## [X.Y.Z]
entry MUST include a human-readable self-repair block under the heading
## To take advantage of v[version].
Why: modusbrain upgrade runs modusbrain post-upgrade which runs modusbrain apply-migrations.
This chain has a known weak link — upgrade.ts catches post-upgrade failures as
best-effort (so the binary still works). When that chain silently fails, users end
up with half-upgraded brains. The self-repair block gives them a paste-ready
recovery path; the v0.13+ ~/.modusbrain/upgrade-errors.jsonl trail + modusbrain doctor
integration close the loop.
Template (adapt the verify commands per release):
-
Your agent reads
skills/migrations/v[version].mdthe next time you interact with it. [One sentence on whether headless agents need manual action, or whether the orchestrator already handled the mechanical side.] -
Verify the outcome:
-
If any step fails or the numbers look wrong, please file an issue:
https://github.com/thebuildceo/modusbrain/issues with:
- output of
modusbrain doctor - contents of
~/.modusbrain/upgrade-errors.jsonlif it exists - which step broke
- output of
PR descriptions cover the whole branch
Pull request titles and bodies must describe everything in the PR diff against the base branch, not just the most recent commit you made. When you open or update a PR, walk the full commit range withgit log --oneline <base>..<head> and write the
body to cover all of it. Group by feature area (schema, code, tests, docs) — not
chronologically by commit.
This matters because reviewers read the PR body to understand what’s shipping. If
the body only covers your last commit, they miss everything else and can’t review
properly. A 7-commit PR with a body that describes commit 7 is worse than no body
at all — it actively misleads.
When in doubt, run gh pr view <N> --json commits --jq '[.commits[].messageHeadline]'
to see what’s actually in the PR before writing the body.
Community PR wave process
Never merge external PRs directly into master. Instead, use the “fix wave” workflow:- Categorize — group PRs by theme (bug fixes, features, infra, docs)
- Deduplicate — if two PRs fix the same thing, pick the one that changes fewer lines. Close the other with a note pointing to the winner.
- Collector branch — create a feature branch (e.g.
thebuildceo/fix-wave-N), cherry-pick or manually re-implement the best fixes from each PR. Do NOT merge PR branches directly — read the diff, understand the fix, and write it yourself if needed. - Test the wave — verify with
bun test && bun run test:e2e(full E2E lifecycle). Every fix in the wave must have test coverage. - Close with context — every closed PR gets a comment explaining why and what (if anything) supersedes it. Contributors did real work; respect that with clear communication and thank them.
- Ship as one PR — single PR to master with all attributions preserved via
Co-Authored-By:trailers. Include a summary of what merged and what closed.
- Always AskUserQuestion before accepting commits that touch voice, tone, or promotional material (README intro, CHANGELOG voice, skill templates).
- Never auto-merge PRs that neutralize the Genthropic founder perspective.
- Preserve contributor attribution in commit messages.
Checking out PRs from thebuildceo-agents
thebuildceo-agents is the AI-authored PR account and is NOT a collaborator on
this repo. Its PRs live in a fork, so GitHub Actions triggered by
pull_request events on those PRs do not receive base-repo secrets. Any CI
job that needs ANTHROPIC_API_KEY, OPENAI_API_KEY, or similar will fail
with empty-env auth errors, regardless of what’s set on the base repo. This
is a GitHub security default, not a config bug.
When the user says “check out <PR link>” and the PR is from thebuildceo-agents
(or any other non-collaborator fork), move the branch into the base repo
before running CI:
gh pr checkout <N>— pull down the fork’s branch. Note the PR number and head branch name (gh pr view <N> --json headRefName --jq .headRefName).git push origin HEAD:<branch-name>— push the same branch to the base repo (origin points atthebuildceo/modusbrain, not the fork). This is the move that gives CI access to secrets.gh pr close <N> --comment "moving to base-repo branch for secret access"— close the fork PR so the queue stays clean.gh pr create --base master --head <branch-name>— open the replacement PR from the base-repo branch. Preserve the original PR’s title and body verbatim (gh pr view <N> --json title,body); contributor attribution moves to aCo-Authored-By:trailer if needed.
thebuildceo-agents as a collaborator, or
flipping the repo-wide “send secrets to fork PRs” toggle, both broaden
secret distribution to every fork PR from that account or any fork. Moving
the branch keeps secret scope tight to just the one PR being shipped.