chore(release): alias @next to @latest (no separate prerelease channel) - #366
Conversation
OIDC trusted publishing authorizes 'npm publish' but not 'npm dist-tag add', so the original invariant 'next must never fall behind latest' produced recurring drift that needed a manual fix after every stable release (the 0.44.1 release surfaced this; npm-oidc-publishing note). Policy change: @next is now an alias for @latest — both dist-tags point to the same tarball at all times, there is no separate prerelease channel. The recurring drift can no longer occur. Changes: - CLAUDE.md dist-tag mapping + invariant updated (next == latest; the 'latest or next?' question becomes 'stable or prerelease?'). - CLAUDE.md OIDC caveat: the manual alignment fallback is now the *only* mechanism (no more 'the next prerelease re-advances @next'). - CHANGELOG: Unreleased entry documenting the policy change. Verified: 'npm view @nforma.ai/nforma dist-tags --json' shows latest: 0.44.1, next: 0.44.1 (alias established 2026-07-09).
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughDocumentation-only update retiring the ChangesDist-tag policy documentation
Estimated code review effort: 1 (Trivial) | ~3 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR updates the repository’s release documentation to reflect a policy change where the npm dist-tag @next is no longer a separate prerelease channel and must always alias @latest, avoiding post-release dist-tag drift caused by OIDC limitations.
Changes:
- Update
CLAUDE.mdto document the new invariant (next == latest) and adjust release guidance/caveats accordingly. - Add an Unreleased changelog entry documenting the dist-tag policy change and the rationale (OIDC cannot reliably run
npm dist-tag add).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| CLAUDE.md | Updates dist-tag mapping/invariant and OIDC caveat to reflect @next being an alias of @latest. |
| CHANGELOG.md | Documents the dist-tag policy change under Unreleased. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
CHANGELOG.md (1)
9-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winKeep the changelog entry policy-level, not snapshot-level.
The hard-coded
0.44.1dist-tag readout will go stale on the next publish and makes this entry read like an ops log. Keep the invariant text here and move the live verification result to the release workflow or PR notes.Proposed fix
- Verified post-change: `npm view `@nforma.ai/nforma` dist-tags --json` → `latest: 0.44.1, next: 0.44.1`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CHANGELOG.md` around lines 9 - 11, The changelog entry in CHANGELOG.md is too snapshot-specific because it hard-codes the live dist-tag values from the post-change verification, which will go stale after the next release. Update the release note text to keep only the policy/invariant change around the `@next` alias for `@latest` and remove the concrete “0.44.1” verification readout; if needed, keep that runtime verification in the release workflow or PR notes instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CLAUDE.md`:
- Around line 13-21: The versioning section still contains the old prerelease
model, conflicting with the new `next` alias policy. Update the surrounding
release/versioning text in `CLAUDE.md` so it consistently says `@next` always
mirrors `@latest`, and remove or rewrite any mention that `-rc.N` publishes to a
separate `next` dist-tag. Keep the guidance around the release flow aligned with
the `next`/`latest` invariant and the `publish.yml` automation.
---
Nitpick comments:
In `@CHANGELOG.md`:
- Around line 9-11: The changelog entry in CHANGELOG.md is too snapshot-specific
because it hard-codes the live dist-tag values from the post-change
verification, which will go stale after the next release. Update the release
note text to keep only the policy/invariant change around the `@next` alias for
`@latest` and remove the concrete “0.44.1” verification readout; if needed, keep
that runtime verification in the release workflow or PR notes instead.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c0e17f20-fe22-4c33-9a69-cb12d14171ad
📒 Files selected for processing (2)
CHANGELOG.mdCLAUDE.md
- CLAUDE.md line 9: reword the rc.N description so it doesn't say 'for next dist-tag' (the alias policy means rc.N publishes to @next, which is the same tarball as @latest — there is no separate channel). - CLAUDE.md line 21: drop the misleading 'Both now publish to @latest/@next; the choice only changes the version string' line — with @next as alias, there is only one channel; the stable-vs-rc.N choice only affects the version string itself. - CHANGELOG: replace the 'npm-oidc-publishing note' pointer (which doesn't exist as a doc in the repo) with the concrete drift mechanism: npm dist-tag add under OIDC falls into a warning branch in publish.yml.
…as policy (#367) * feat(release): retire the prerelease flow under the @next=@latest alias policy Now that @next is an alias for @latest (PR #366), a separate prerelease channel is nonsensical: publishing 0.40.2-rc.1 to @latest would silently install the prerelease for every user, and @next == @latest means there is no other surface to ship it on. This PR removes the prerelease flow entirely. Changes: - .github/workflows/publish.yml: * Drop the v*-rc* / v*-next* tag-push trigger (publish only on main). * Drop the prerelease branch in the context job — context now resolves to mode=stable | mode=none only. * Drop the 'Stamp version from tag (prerelease only)' step (used to align package.json to a tag suffix). * Add an explicit prerelease-suffix rejection: if package.json has a '-' in the version, the workflow errors and refuses to publish (better message, before publish completes). * Tidy mode comments accordingly (mode=stable|none, dist_tag=latest). * Drop the obsolete 'next prerelease re-advances @next' comment in the @next alignment step (the alias is now done in the same job). - scripts/release.sh: DELETED — its sole job was the prerelease flow. - scripts/prepare-release.sh: drop the -rc suffix handling (cut -d- -f1) and update the 'prerelease versions are not supported' error to reflect the alias policy (no longer refers to the deleted release.sh). - scripts/publish.sh: refresh the dist-tag-invariant comment to the alias wording (the CI workflow is the preferred path; this is the manual fallback for the legacy token-based publish). - CLAUDE.md: * Drop the rc.N form from the versioning line; document that prerelease versions are not publishable under the alias policy. * Drop the 'next release (prerelease)' section in Release process. * Drop the prerelease-version regex note in 'CI gates to remember' — replaced with a 'No prerelease versions' gate. * Drop the 'release.sh' reference in Release scripts (only prepare-release.sh remains). * Drop the prerelease-rebase carveout in 'Git workflow'. * Tidy the Publishing section (single channel, alias wording). - core/workflows/update.md: drop the select_channel step entirely; both 'CHANNEL is latest' and 'CHANNEL is next' branches collapse to one path; the install command pins to @latest. - CHANGELOG: Unreleased 'Removed' entry documents each of the above. Older rc tags in git history (e.g. v0.44.0-rc.2) remain inert — the workflow no longer matches them. * fix(release): address review feedback on prerelease retirement PR Five valid concerns from Copilot + CodeRabbit on PR #367: 1. prepare-release.sh: --auto arithmetic can crash if CURRENT_VERSION contains a -rc suffix (PATCH becomes non-numeric, '$((PATCH + 1))' fails before the prerelease refusal below). Add an early guard at the top of the script that errors on a prerelease starting version before --auto touches it. 2. publish.sh: legacy token-based script still allowed publishing a prerelease to @latest. Refuse prereleases up front (under the @next=@latest alias policy, publishing 0.40.2-rc.1 to @latest would silently install the prerelease for every user). 3. package.json: 'release' npm script still pointed at the deleted scripts/release.sh. Remove both that line and the '!scripts/release.sh' entry in the published-files allowlist. 4. core/workflows/update.md: 'npm install -g ... || true' silently swallowed install failures — a stale updater could continue with the old version and no clear signal. Drop '|| true' and surface any install error. 5. package.json published-files: removed '!scripts/release.sh' (the file is gone; an allowlist entry for it is now a stale reference). * fix(release): round 2 review feedback on prerelease retirement PR Three more valid concerns on PR #367's review-fix commit: 1. publish.sh: 'Publishing @nforma.ai/nf...' was a stale typo — the package is @nforma.ai/nforma (was the original pre-rename name). Update the printed banner to the current package name. 2. core/workflows/update.md: my prior 'remove || true' fix was insufficient — the command also redirected stderr to /dev/null, which would still hide the install-failure reason. Drop the stderr redirect so the error actually surfaces. 3. agents/skills/shipping-and-launch/SKILL.md: still listed scripts/release.sh as a release script. Update to the alias- policy wording (only scripts/prepare-release.sh + the legacy scripts/publish.sh; release.sh was deleted). * fix(publish.sh): reject caller-supplied --tag under the @next=@latest alias Under the @next=@latest alias policy the publish MUST land on @latest. A caller passing '--tag next' (or any non-latest tag) would publish to a tag the alias invariant doesn't reach, and the downstream 'npm dist-tag add ... next' step would silently point @next at the wrong tarball (or, worse, at a tag that points somewhere else entirely). Refuse any --tag arg in $@, except an explicit '--tag=latest' (which is a no-op given the script now pins --tag=latest unconditionally). Bare '--tag latest' (space-separated) is also refused to keep the allowlist explicit. * fix(publish.sh): align --tag-allowlist comment with the actual parser The previous comment said 'Only a bare --tag latest (or no --tag at all) is allowed' but the parser only allows the --tag=latest form — bare '--tag latest' (space-separated) is refused. Update the comment to match the actual CLI surface so callers aren't misled. * fix(publish.sh): drop --tag=latest from the allowlist to avoid duplicate flags The previous version allowed '$@ --tag=latest' through, but the script then unconditionally added its own '--tag=latest' before 'npm publish' — resulting in duplicate flags. Strip the --tag=latest allowlist entirely (now the script refuses any --tag arg), which matches the comment too: 'callers who want to publish to @latest should drop --tag entirely'. * fix(publish.sh): cd into ROOT_DIR (pre-existing CWD bug surfaced by the prerelease gate) The script never cd'd into ROOT_DIR after computing it. This was a latent issue: the npmrc write goes to $ROOT_DIR/.npmrc (the right place), but 'npm publish' packages the current working directory, and the new prerelease gate reads ./package.json (both fail if the user invoked the script from anywhere other than the repo root). Add the missing 'cd $ROOT_DIR' immediately after computing it. Both the prerelease check and the 'npm publish' call now work regardless of the caller's CWD. --------- Co-authored-by: open-swe[bot] <jonathan@jonathanborduas.com>
…writer live path (#370) * test(release): red-proven gates for the alias-policy guards and goal-writer live path Closes a P4 violation: the anti-prerelease guards shipped in #366/#367 had zero tests, and /nf:goal-writer had no behavioural gate. By this repo's own recurrence record (config-path drift fixed 6x, null-CLI 4x), an ungated class comes back. bin/release-guards.test.cjs — 12 tests over all five guards: - publish.sh: prerelease package.json version refused - publish.sh: caller-supplied --tag refused (next, =next, latest, =latest) - prepare-release.sh: explicit prerelease target refused - prepare-release.sh: --auto with a prerelease CURRENT version refused cleanly (without the early guard, cut -d. -f3 yields "2-rc" and $((PATCH + 1)) is a bash arithmetic error) - publish.yml: version guard present AND its predicate actually matches a prerelease but not a stable version; no v*-rc*/v*-next* triggers; no dist_tag=next / mode=prerelease modes SAFETY: publish.sh ends in `npm publish`, so a test must never be able to reach it — including in the red case where a guard is broken. Each script runs in a throwaway sandbox (its own ROOT_DIR, package.json and .env) against a stub npm that only records argv and never touches the network. Every guard test asserts npm was never invoked with `publish`, so a broken guard fails an assertion rather than publishing. One positive control asserts a stable version DOES reach the publish step, proving the prerelease rejection is caused by the version and not by sandbox breakage. bin/goal-writer-blocks.test.cjs — 9 tests executing the skill's own blocks: - no `node -e` in an executable shell fence (the nf-node-eval-guard DENIES that form, so a documented `node -e` fails every run — the exact bug that reached main in #368 and that lint:isolation plus all four skill lints passed, because they check for arguments AFTER an eval, never -e itself) - every shell block is valid under `bash -n` - the recurrence-scan grep pattern runs under stock /usr/bin/grep, catching non-POSIX \w - the char-count verifier round-trips a condition containing quotes, backticks, parens and $VAR intact - the four /goal contract facts are pinned (4000-char limit, no @file.md syntax, mandatory turn cap, no per-tool permission grant) — drift in any of them yields an unsatisfiable generated condition All seven guards mutation-proven RED, not merely green: each guard was removed, the corresponding test observed to fail, and the file restored. The two goal-writer tests were proven against the actual bugs that shipped. Both files wired into test:ci — a test outside CI is not a gate. * fix(test): execute the SHIPPED char-count verifier, not a reimplementation CodeRabbit caught a P11 violation inside the very file whose purpose is P11: the char-count test ran a new inline Node program instead of extracting and executing the NF_EVAL verifier from goal-writer.md. A reimplementation stays green when the shipped verifier's input contract or count logic changes — exactly the live-path gap this file was written to close. Now extracts the NF_EVAL body from the skill and runs THAT, asserting: - the reported count equals the true condition length, using a condition containing quotes, backticks, parens and $VAR - a short condition reports OK - a 4001-char condition reports TOO LONG (new negative case) Executing the real block immediately surfaced a behavioural difference the reimplementation hid: console.log colourises numeric args via util.inspect, so the shipped verifier's count can arrive wrapped in ANSI escapes. The JSON.stringify-based copy never showed this. Two robustness fixes on the back of that: 1. ANSI stripping no longer relies on a raw ESC byte embedded in source. A literal ESC is invisible, and any formatter or copy-paste that drops it silently degrades the pattern to /\[[0-9;]*m/ — which strips "[33m" but leaves a bare ESC that then defeats the following \s* match. The test would pass or fail depending on whether the runner colourises. Now built with String.fromCharCode(27). 2. Colour is pinned OFF for the verifier subprocess (NO_COLOR=1, FORCE_COLOR removed), so the result no longer depends on the developer's shell. Both test files verified green under a plain run AND under FORCE_COLOR=3 — the environment that would previously have exposed the flakiness. 22/22 across both suites. --------- Co-authored-by: open-swe[bot] <jonathan@jonathanborduas.com>
Why
OIDC trusted publishing authorizes
npm publishbut notnpm dist-tag add, so the old invariant "next must never fall behind latest" produced recurring drift that needed a manual fix after every stable release (the 0.44.1 release surfaced this; seeproject_npm-oidc-publishing).Policy change:
@nextis now an alias for@latest— both dist-tags point to the same tarball at all times. There is no separate prerelease channel. The recurring drift can no longer occur.Changes
CLAUDE.md— dist-tag mapping + invariant updated (next == latest); the "latest or next?" question becomes "stable or prerelease?" (both publish to@latest/@next; the choice only changes the version string).CLAUDE.mdOIDC caveat — the manual alignment fallback is now the only mechanism (no more "the next prerelease re-advances@next").CHANGELOG.md— Unreleased entry documenting the policy change.Verified
The alias was established 2026-07-09 (same day as this PR).
Out of scope (still open)
publish.ymlprerelease path (rc/next tag) still tries to publish prereleases to@next. Since@next == @latest, prereleases would still go to the alias — but the rc branch is now redundant. A separate PR can retire it if desired.scripts/release.shprerelease helper and therelease/{VERSION}-rc.Nbranch flow are likewise dormant under the alias policy.🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
@nextnpm tag now matches@latest.Chores