feat: add release prep workflow#72
Conversation
|
/coder-agents-review |
There was a problem hiding this comment.
First-pass review (Netero). This is a mechanical first-pass only; the full review panel has not yet reviewed this PR.
The release-prep/finalize scripts are well-structured: clean separation of helpers, explicit fail-fast assertions, defensive allowlist staging, and a clear boundary between prep tooling and the publish pipeline. The dogfood proof is thorough.
1 P2, 2 P3, 1 Note. The P2 is a test coverage gap in the safety-critical guardrails; the P3s are dead code. Addressing these before the panel review will let the panel focus on design and architecture rather than mechanical issues.
"Code:test ratio is 979:265 (3.7:1, under 5:1), so the gap is in scenario breadth, not volume." (Netero)
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
Panel review (17 reviewers). All R1 findings verified as addressed in 56dc625.
The wrapper scripts are the real value here: branch management, fail-fast assertions, allowlisted staging, clean-tree enforcement, synced-main checks, and tag safety. The design boundary between local prep and CI publishing is well-drawn. The dogfood proof is thorough and reproducible. The test suite is comprehensive after the R1 fixes.
1 P2, 8 P3, 4 P4, 4 Nit. The P2 is a design question about whether release-it earns its 143-package dependency cost. Several P3s are convergent findings from 3+ reviewers.
Interaction note: DEREM-5 (release-it dependency), DEREM-10 (hook injection), and DEREM-19 (version validation at runReleaseIt boundary) are linked. Removing release-it would eliminate DEREM-10 and DEREM-19 and reduce 143 transitive packages.
Additional P4s/Notes not posted as inline comments: DEREM-15 (no spawnSync timeout for agent automation), DEREM-17 (run/assertString/formatCommand duplication across scripts, follows pre-existing codebase pattern), DEREM-19 (runReleaseIt doesn't validate version at function boundary), DEREM-22-24 (naming/constant nits).
"The problem was disconnected manual steps. The solution is the composition scripts. Release-it is a passenger." (Pariston)
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
Re-review (4 reviewers: Bisky, Mafuuu, Kite, Chopper). All 14 R2 findings verified as addressed in 187ba83.
The wrapper scripts are solid: 21 integration tests against real temp repos, every guardrail exercised, fail-fast assertions with specific error messages, defensive allowlist staging, recovery docs for every identified failure mode. The ADR honestly documents the release-it tradeoff including its own exit strategy. Clean decomposition across helpers, prep, and finalize.
1 P3, 2 P4. All are test gaps in an already-strong test suite; no correctness issues remain. The code works correctly across both scripts and all documented failure modes.
"The decomposition between release-helpers, release-prep, and release-finalize is clean. Each module has a single job, the assertion functions compose well, and the test suite covers both success and failure for each guardrail." (Kite)
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
Re-review (3 reviewers: Bisky, Mafuuu, Meruem). All 3 R3 findings verified as addressed in 4be9833.
Twenty-three integration tests against real temp repos. Every guardrail in both scripts has coverage. The verify-failure-blocks-tag safety property is now proven. Git identity rejection is tested with proper env isolation. Annotated vs lightweight tag distinction is verified. The test suite is thorough and genuine.
1 P4 (finalize-side git identity test gap). Meruem notes the shared function contract covers both call sites. The risk of this gap is minimal.
This is the fourth round. 27 findings raised across 4 rounds; 24 fixed, 3 dropped. The PR improved meaningfully at each round. Clean approve.
"Twenty-three tests, all genuine. Every guardrail in release-prep.mjs has coverage. Every guardrail in release-finalize.mjs has coverage except the one above." (Bisky)
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
Final re-review (3 reviewers: Bisky, Mafuuu, Kite). DEREM-28 verified as addressed in 9323634.
29 findings across 5 rounds. 25 author-fixed, 3 dropped, 1 open P4. No correctness issues. Every guardrail in both scripts has test coverage. The test suite is genuine and thorough: 23 integration tests against real temp repos, real git operations, real error assertions.
The remaining P4 is a final polish item: finalize's parseSemver build-metadata guard has no dedicated test (the same function is proven correct through the prep path). The PR is ready to merge.
"Twenty-three tests, each with its own temporary git repo, bare remote, real commits and pushes. Every guard test checks both the error message AND the absence of side effects." (Bisky)
🤖 This review was automatically generated with Coder Agents.
|
/coder-agents-review |
There was a problem hiding this comment.
Clean approve (2 reviewers: Bisky, Mafuuu). DEREM-29 verified as addressed in b95d6f9. Zero open findings.
29 findings across 6 rounds. 26 author-fixed, 3 dropped. 0 remaining. Every finding addressed, every fix verified. CI green (pending completion). The review is converged.
"Twenty-five tests, each with its own disposable git repo, real file I/O, real git commands. No mocks in sight. Every guardrail in both release-prep.mjs and release-finalize.mjs has a test that triggers the error, checks the message, and confirms no side effects leaked." (Bisky)
🤖 This review was automatically generated with Coder Agents.
Summary
release:prepandrelease:finalizescripts with release-specific git/package guardrailsBehavior
release:preprequires--version <exact-semver>and--changelog local|ci, creates a localrelease/<version>branch, updates package version files, stages only allowlisted files, and creates one local commit.release:finalizeonly works from clean, syncedmain, derivesv${package.json.version}, creates an annotated tag, and pushes only that tag.Validation
node --check scripts/release-helpers.mjsnode --check scripts/release-prep.mjsnode --check scripts/release-finalize.mjsnpx vitest run --maxWorkers=1 test/integration/release-scripts.test.tsnpm run format:checknpm run lintnpm run typechecknpm run test:unitnpm run test:integrationnpm run test:e2enpm run buildnpm run smoke:install -- --skip-buildmise run workflow-lintnpm run format:checknpx vitest run --maxWorkers=1 test/integration/release-scripts.test.tsDogfood proof
dogfood/20260429-release-it-prep/transcript.txtdogfood/20260429-release-it-prep/screenshot.pngdogfood/20260429-release-it-prep/release-it-prep.webmdogfood/20260429-release-it-prep/release-it-prep.castdogfood/20260429-release-it-prep/index.md📋 Implementation Plan
Plan: Introduce release-it for release prep only
Recommendation
Adopt
release-itnarrowly as the implementation detail behind a project-owned Release Prep Workflow, and do not replace the existing publish pipeline.The first working slice should add:
Keep
.github/workflows/release.ymlas the authoritative tag-triggered Publish Pipeline for CI, deterministic tarball creation, checksum assets, GitHub Release creation, Communique release notes, npm OIDC publishing, and prerelease dist-tag handling.Evidence and constraints
Repo facts verified by exploration:
docs/RELEASE-PROCESS.mdcurrently documents manual release prep withnpm version ... --no-git-tag-version, release branches, optional local Communique changelog generation, and manual annotated tag creation after merge..github/workflows/release.ymlvalidates tag/package version alignment, checks tag ancestry againstmain, runsmise run ci, creates one verified tarball viascripts/pack-release.mjs, uploads tarball/checksum assets, generates Communique release notes, and publishes to npm through trusted publishing/OIDC..github/workflows/release-changelog.ymlalready supports a release PR that either includes a localCHANGELOG.mdentry or lets CI generate one withcommunique generate "v<version>" --changelog --repo coder/agent-tty.package.jsonhas no release-it dependency today and defines existing quality gates (verify,pack:release,smoke:install, etc.).communique.tomlconfigures maintainer-focused release notes and changelog behavior.Important invariants to preserve:
v${package.json.version}.main.0.1.1-beta.0continue to publish to the matching npm dist-tag (beta).Resolved design decisions
main.release-itparticipates only in release prep.release:finalizeuses repo-specific guardrails plus plaingit tag -a/git push, not release-it.release-itcommands.--versiononly; no--increment/--preidyet.release:prepcreates/switches torelease/<version>from a clean, up-to-datemain.release:prepcreates exactly one local commit with only allowlisted release-prep files.--changelog local: run Communique locally and require a cleanCHANGELOG.mdupdate.--changelog ci: skip local changelog and require the local commit to contain only version files.release:prep --verifyandrelease:finalize --verifymay run full validation, but default scripts only run release-specific invariant checks.Implementation plan
Phase 0 — Technical spike: prove release-it is useful here
Before implementing wrappers, verify the pinned
release-itversion can reliably operate as a version-file-only engine for this repo:package.jsonandpackage-lock.jsoncorrectly,Run this spike in a disposable clone/worktree, or reset all version-file changes afterward, and verify the spike does not trigger unintended lifecycle hooks or release side effects.
If release-it cannot do this cleanly, stop and reconsider the implementation boundary before proceeding. The fallback design would keep the same project-owned
release:prep/release:finalizeinterface but use a repo-owned wrapper aroundnpm version <version> --no-git-tag-versionfor the version-file update.Phase 1 — Documentation/domain records
CONTEXT.mdwith release workflow language if not already present:docs/adr/0001-use-release-it-only-for-release-prep.mdusing the project ADR style.docs/RELEASE-PROCESS.mdto make the new scripts the primary path while retaining manual commands as fallback/troubleshooting.Phase 2 — Add pinned release-it prep tooling
release-itas a pinneddevDependencyand updatepackage-lock.json..release-it.cjsif supported and useful for comments; otherwise use.release-it.json.{ "release:prep": "node ./scripts/release-prep.mjs", "release:finalize": "node ./scripts/release-finalize.mjs" }Phase 3 — Implement
scripts/release-prep.mjsRequired behavior:
--version <exact-semver>is required.--changelog local|ciis required.--verifyis optional.main,origin/mainexists and localHEADequalsorigin/main,+...) in the first iteration,user.nameanduser.email) before any changes are made,release/<version>does not exist,origin/release/<version>does not exist.release/<version>.release-itthrough the project dependency/script path in CI/non-interactive mode so it updates package version files without committing, tagging, pushing, publishing, or creating GitHub releases.package.json.versionequals the requested version.package-lock.jsontop-level version andpackages[""].versionalso match when present.--changelog local:ANTHROPIC_API_KEYorOPENAI_API_KEY.COMMUNIQUE_MODELwhen using onlyOPENAI_API_KEY, matching current workflow behavior.communiqueto be available through the expected toolchain/PATH.GITHUB_TOKENor a clearly verified authenticatedghsession if Communique supports it locally.--changelog ci.communique generate "v${version}" --changelog --repo coder/agent-tty.CHANGELOG.mdto change.--changelog ci:CHANGELOG.mdis changed.--verifyis supplied, runmise run ciwhenmiseis available; otherwise runnpm run verify.--verifyrun, assert the working tree is clean again before reporting success.Defensive programming notes:
node:assert/strictfor impossible internal states.execFile/spawnwith argv arrays rather than shell command strings for git, release-it, npm, mise, and Communique calls.git add ..Phase 4 — Implement
scripts/release-finalize.mjsRequired behavior:
--verifyoptional.main,origin mainsucceeds,HEADequalsorigin/main,package.json.versionis non-empty and valid enough to form a tag,package-lock.jsontop-level version andpackages[""].versionmatch package version when present,+...) in the first iteration,user.nameanduser.email),v${version},--verifyis supplied, runmise run ciwhen available; otherwise runnpm run verify.--verifyrun, assert the working tree is still clean before creating a tag.git push origin "v${version}"Phase 5 — Tests
Before coding tests, inspect existing script/integration test style and keep changes minimal.
Recommended automated coverage:
release:prep --changelog cifrom clean syncedmaincreatesrelease/<version>, updates package files, and creates exactly one commit.release:prep --changelog cifails ifCHANGELOG.mdis dirty/changed.release:prep --changelog localfails clearly when credentials are missing.release:preprefuses dirty trees, non-main branches, stale main, existing local branch, and existing remote branch.release:finalizerefuses dirty trees, non-main branches, stale main, existing local tag, and existing remote tag.release:finalizesucceeds in a disposable repo with a bareoriginand pushes exactlyv<version>.release-itin tests is too slow or brittle, test wrapper safety logic with a narrow fake/stub boundary and cover real release-it behavior in dogfooding.Phase 6 — Documentation updates
Update
docs/RELEASE-PROCESS.md:npm version ... --no-git-tag-versionflow with:.github/workflows/release.ymlstill owns packaging, GitHub Release creation, checksum assets, and npm publishing.Phase 7 — Failure and recovery documentation
Add a concise recovery section to
docs/RELEASE-PROCESS.mdcovering:release:prepfails after creatingrelease/<version>, inspect the branch, reset/delete it if no wanted work remains, and rerun from clean syncedmain.release:finalizepushes a tag but the release workflow fails before any GitHub Release or npm publish, fix the underlying issue onmain, delete/recreate the failed tag only if maintainers explicitly decide it is safe, and document the action.Acceptance criteria
release-itcan update version files without commit/tag/push/publish/release side effects, or the implementation stops to reconsider the version-update engine.release-itis pinned indevDependenciesand lockfile changes are committed.npm run release:prep -- --version <version> --changelog cicreates a localrelease/<version>branch and one commit touching onlypackage.json/package-lock.json.npm run release:prep -- --version <version> --changelog localrequires Communique credentials and commitsCHANGELOG.mdonly when Communique changes it cleanly.npm run release:finalizeonly works from clean, syncedmain, creates an annotatedv${package.json.version}tag, and pushes only that tag..github/workflows/release.ymlpublish behavior is not replaced or weakened.Validation plan
Run the narrowest useful checks during implementation, then broaden before handoff:
If script integration tests touch full CLI/build behavior or if release docs/tooling changes are broad, run:
Fallback if
miseis unavailable:Also run targeted dry/safe checks in a disposable git repo, not against the real release remote.
Dogfooding / proof bundle
Because this is CLI release automation, dogfooding should happen in a disposable clone with a disposable bare
origin, never against the real repository remote.Suggested dogfood environment:
Dogfood versions must be greater than the current
package.jsonversion becauserelease:preprejects same or lower versions.release/999.0.0-dogfood.0,mainin the disposable repo and run:refs/tags/v999.0.0-dogfood.0.npm run release:prep -- --version 999.0.0-dogfood.1 --changelog localIf credentials are unavailable, capture the intentional failure message for missing credentials and rely on automated/stub tests for the clean local changelog path.
Reviewer-facing proof should include:
git log --oneline --decorate --graph --allbefore/after,git diff --name-only HEAD^..HEAD,git ls-remote --tags origin,agent-ttyscreenshot of the terminal state,agent-ttyshowing the happy-path dogfood flow.Use an isolated
AGENT_TTY_HOMEfor dogfood captures so no real~/.agent-ttystate is mutated.Risks and mitigations
--changelog local|ci, hard failure for dirty/unexpected changes, and CI fallback.release-it, reviewing lockfile changes, and running full verification before merge.Explicit non-goals for the first slice
.github/workflows/release.yml.release:prep.Generated with
mux• Model:openai:gpt-5.5• Thinking:xhigh