chore(release)!: adopt versionless-main release model - #1450
Conversation
- main carries the 0.0.0 placeholder across all packages/*/package.json, bun.lock, the @bastani/atomic-natives pin, native/index.js, and Cargo manifests/lock - add scripts/cut-release.ts: stamps the real version onto a throwaway off-main 'Release <version>' commit in a detached git worktree, tags it, pushes only the tag; main is never advanced (commit uses --no-verify since a worktree has no node_modules) - publish.yml: refuse to publish the 0.0.0 placeholder (tag checkout already builds the real-version commit, so existing version validations are unchanged) - publish-release workflow: open a CHANGELOG-only release-notes PR to main, then cut+tag off-main via cut-release.ts; verify tag parent == main HEAD and the tagged @bastani/atomic version == target instead of merging a version bump into main - docs: CLAUDE.md/AGENTS.md, DEV_SETUP.md, docs/ci.md, coding-agent CHANGELOG Assistant-model: Claude Opus 4.8
|
PR Review: chore(release): adopt versionless-main release model Reviewed the full diff (19 files, +382/-152). A well-designed, well-documented change — the codex-style versionless-main model is coherently implemented across the script, the publish workflow, the Actions guard, and the docs. The publish.yml placeholder guard, the upgraded tag verification (parent + stamped-version checks), and the clean-tree / existing-tag / finally-based worktree cleanup in cut-release.ts are all solid. Findings below, by priority. 🟡 VERIFY BEFORE MERGE — bun install --frozen-lockfile from the tagged commit This is the one path the PRs local validation ("2508 unit tests pass", "cut-release validated end-to-end") does NOT appear to exercise. On the tagged commit, bump-version.ts rewrites both: (a) every workspace version field, AND (b) the first-party dependency specifier @bastani/atomic-natives in packages/coding-agent/package.json (see bumpFirstPartyDependencyRanges, scripts/bump-version.ts:204) — while bun.lock is intentionally left at the 0.0.0 placeholders. publish.yml then runs bun install --frozen-lockfile (lines 212, 304) from that tagged commit. A specifier drift (not just a version-field drift) is exactly what frozen-lockfile is strictest about. Mitigating evidence (supports the authors claim): the OLD bun.lock already carried a specifier mismatch — coding-agent depended on @bastani/atomic-natives 0.8.31-alpha.1 in the lockfile while package.json pinned 0.8.31-alpha.5 (visible in this diff) — and releases published fine, so bun very likely tolerates this for workspace deps. Still, because this is the single failure mode that would break EVERY publish and it is not covered by the local run, please confirm with a throwaway prerelease-tag dry-run that actually reaches publish.ymls install step (or, belt-and-suspenders, relock inside the worktree before committing — at the cost of the network round-trip the comment deliberately avoids). 🟢 MINOR
CONFIRMED WHILE REVIEWING
Nice work overall — the design is sound and the docs (CLAUDE.md, DEV_SETUP.md, ci.md, CHANGELOG) are thorough and consistent. The frozen-lockfile dry-run is the one thing I would want green before relying on this for a real publish. Reviewed by Claude Opus 4.8 |
Thread a base_ref parameter through the publish-release workflow so a release
can be cut from a maintenance/integration branch instead of main:
- new optional input base_ref (default "main")
- release-notes PR now targets base_ref; the base-readiness gate verifies
current branch == base_ref, local == origin/base_ref, merge-commit ancestry
- cut-release-tag stage runs cut-release.ts --base ${base_ref}
- verifyReleaseTagPublished still checks tag parent == the resolved base HEAD
Default behavior (main) is unchanged. The lib verification helpers already
parameterized the base branch, so no lib/test changes were needed.
Assistant-model: Claude Opus 4.8
|
Review: versionless- Thorough, well-documented change. Decoupling version from branch is clean, the worktree-based stamping is the right mechanism, and the docs ( 🔴 Highest risk — verify
The PR claims Please confirm by pushing a throwaway tag through 🟠 No test coverage for
An end-to-end test against a temp git repo (clean-tree refusal, existing-tag refusal, correct release-commit parent, 🟠
🟡 Minor
✅ Looks correct
Overall a strong, careful PR — the one item I would gate merge on is empirically confirming the frozen-lockfile behavior, since that is the single point that would silently break the whole release pipeline. 🤖 Automated review — generated with Claude Code. |
Add an optional from_ref input that releases from any commit/tag/branch without touching main: - auto-create release/<version> (or prerelease/<version>) from from_ref and commit the CHANGELOG entry on it (changelog lives on the tag only) - gate on that branch's own test.yml CI run (headBranch + headSha + success) - cut + publish the tag off the branch (cut-release.ts --base <branch>) - delete the ephemeral branch after publish (the tag preserves the commits) Supporting changes: - generalize verifyPublishWorkflowSucceeded -> verifyWorkflowRunSucceeded so it also gates the branch test run; add verifyReleaseBranchCiSucceeded wrapper - verifyReleasePreparation gains a checkManifestVersions flag (off for ephemeral, since an arbitrary from_ref may carry real versions that cut-release stamps over) - test.yml now triggers on push to release/** and prerelease/** so the branch gate has a CI run to wait on When from_ref is unset the existing main/base_ref flow is unchanged. Assistant-model: Claude Opus 4.8
Review: versionless-
|
Resolve versionless-main (#1450) vs file-length split conflict: - re-apply #1450's versionless / base_ref / from_ref / ephemeral logic onto the split publish-release lib modules (helpers, gates) and the workflow file - extract the ephemeral release flow into lib/publish-release-ephemeral.ts so every file stays under the 500-line limit - combine both CHANGELOG [Unreleased] entries Assistant-model: Claude Opus 4.8
* chore(release): adopt versionless-main release model
- main carries the 0.0.0 placeholder across all packages/*/package.json,
bun.lock, the @bastani/atomic-natives pin, native/index.js, and Cargo manifests/lock
- add scripts/cut-release.ts: stamps the real version onto a throwaway off-main
'Release <version>' commit in a detached git worktree, tags it, pushes only the
tag; main is never advanced (commit uses --no-verify since a worktree has no node_modules)
- publish.yml: refuse to publish the 0.0.0 placeholder (tag checkout already builds
the real-version commit, so existing version validations are unchanged)
- publish-release workflow: open a CHANGELOG-only release-notes PR to main, then
cut+tag off-main via cut-release.ts; verify tag parent == main HEAD and the tagged
@bastani/atomic version == target instead of merging a version bump into main
- docs: CLAUDE.md/AGENTS.md, DEV_SETUP.md, docs/ci.md, coding-agent CHANGELOG
Assistant-model: Claude Opus 4.8
* feat(publish-release): add optional base_ref input (defaults to main)
Thread a base_ref parameter through the publish-release workflow so a release
can be cut from a maintenance/integration branch instead of main:
- new optional input base_ref (default "main")
- release-notes PR now targets base_ref; the base-readiness gate verifies
current branch == base_ref, local == origin/base_ref, merge-commit ancestry
- cut-release-tag stage runs cut-release.ts --base ${base_ref}
- verifyReleaseTagPublished still checks tag parent == the resolved base HEAD
Default behavior (main) is unchanged. The lib verification helpers already
parameterized the base branch, so no lib/test changes were needed.
Assistant-model: Claude Opus 4.8
* feat(publish-release): add from_ref ephemeral release-from-any-ref path
Add an optional from_ref input that releases from any commit/tag/branch
without touching main:
- auto-create release/<version> (or prerelease/<version>) from from_ref and
commit the CHANGELOG entry on it (changelog lives on the tag only)
- gate on that branch's own test.yml CI run (headBranch + headSha + success)
- cut + publish the tag off the branch (cut-release.ts --base <branch>)
- delete the ephemeral branch after publish (the tag preserves the commits)
Supporting changes:
- generalize verifyPublishWorkflowSucceeded -> verifyWorkflowRunSucceeded so it
also gates the branch test run; add verifyReleaseBranchCiSucceeded wrapper
- verifyReleasePreparation gains a checkManifestVersions flag (off for ephemeral,
since an arbitrary from_ref may carry real versions that cut-release stamps over)
- test.yml now triggers on push to release/** and prerelease/** so the branch gate
has a CI run to wait on
When from_ref is unset the existing main/base_ref flow is unchanged.
Assistant-model: Claude Opus 4.8
Summary
Adopts a versionless `main` release model (inspired by openai/codex): all `packages/*/package.json` manifests on `main` permanently hold the `0.0.0` placeholder. The real version is materialized only on a throwaway, off-`main` `Release ` commit that is tagged but never merged back. Stable releases and ahead-of-stable prereleases can coexist on the same trunk with no branch gymnastics.
Also adds two new `publish-release` workflow inputs: optional `base_ref` (release from a maintenance/integration branch instead of `main`) and optional `from_ref` (ephemeral release-from-any-ref path — the workflow auto-creates the release branch, gates on its CI, publishes the tag, then deletes the branch; `main` is never touched).
Key Changes
`scripts/cut-release.ts` (new)
`packages/*/package.json`, `bun.lock`, Cargo manifests
`.github/workflows/publish.yml`
`.atomic/workflows/publish-release.ts`
Docs
Breaking Changes / Migration Notes
Verification