Skip to content

chore(release): alias @next to @latest (no separate prerelease channel) - #366

Merged
jobordu merged 2 commits into
mainfrom
nf/quick-426-dist-tag-next-alias-policy
Jul 9, 2026
Merged

chore(release): alias @next to @latest (no separate prerelease channel)#366
jobordu merged 2 commits into
mainfrom
nf/quick-426-dist-tag-next-alias-policy

Conversation

@jobordu

@jobordu jobordu commented Jul 9, 2026

Copy link
Copy Markdown

Why

OIDC trusted publishing authorizes npm publish but not npm 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; see project_npm-oidc-publishing).

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?" (both publish to @latest/@next; the choice only changes the version string).
  • CLAUDE.md OIDC 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

$ npm view @nforma.ai/nforma dist-tags --json
{ "latest": "0.44.1", "next": "0.44.1" }

The alias was established 2026-07-09 (same day as this PR).

Out of scope (still open)

  • publish.yml prerelease 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.
  • The scripts/release.sh prerelease helper and the release/{VERSION}-rc.N branch flow are likewise dormant under the alias policy.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Updated release and publishing guidance to reflect that the @next npm tag now matches @latest.
    • Clarified how to verify both tags point to the same version and what to do if tag alignment needs manual adjustment.
  • Chores

    • Added a changelog note confirming the new version-tag behavior and the current verified tag state.

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).
Copilot AI review requested due to automatic review settings July 9, 2026 06:38
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jobordu, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b1acdfbe-b69b-46eb-a7de-4a7b1d4bcfcf

📥 Commits

Reviewing files that changed from the base of the PR and between 47b4b8e and 699fa37.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • CLAUDE.md

Walkthrough

Documentation-only update retiring the @next npm dist-tag as a separate prerelease channel, making it an alias of @latest. CHANGELOG.md gains an Unreleased entry, and CLAUDE.md's versioning invariants and OIDC publishing caveat are revised accordingly.

Changes

Dist-tag policy documentation

Layer / File(s) Summary
Changelog entry for dist-tag alias change
CHANGELOG.md
Adds an Unreleased "Changed" entry documenting that @next is now an alias of @latest, with verified dist-tag state noted.
Release guidance rewrite
CLAUDE.md
Updates versioning invariants and verification instructions so next always matches latest, and rewords the OIDC publishing caveat to describe best-effort, non-fatal @next alignment with a manual fallback command.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

  • nForma-AI/nForma#354: Both PRs update CLAUDE.md to document the same @next-vs-@latest dist-tag alignment behavior.
  • nForma-AI/nForma#362: Both PRs update CHANGELOG.md around the same 0.44.1 release and @next dist-tag alignment.
  • nForma-AI/nForma#280: Modifies the same release/dist-tag policy this PR changes, previously publishing prereleases to @next.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: aliasing @next to @latest and removing the separate prerelease channel.
Description check ✅ Passed Covers the main change, rationale, verification, and out-of-scope notes; only the template's What, Testing, and Checklist sections are missing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nf/quick-426-dist-tag-next-alias-policy

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md to 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.

Comment thread CLAUDE.md Outdated
Comment thread CLAUDE.md Outdated
Comment thread CHANGELOG.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
CHANGELOG.md (1)

9-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the changelog entry policy-level, not snapshot-level.

The hard-coded 0.44.1 dist-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

📥 Commits

Reviewing files that changed from the base of the PR and between 3626931 and 47b4b8e.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • CLAUDE.md

Comment thread CLAUDE.md Outdated
- 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.
Copilot AI review requested due to automatic review settings July 9, 2026 06:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment thread CLAUDE.md
Comment thread CLAUDE.md
Comment thread CLAUDE.md
Comment thread CHANGELOG.md
@jobordu
jobordu merged commit 57cce41 into main Jul 9, 2026
17 checks passed
@jobordu
jobordu deleted the nf/quick-426-dist-tag-next-alias-policy branch July 9, 2026 07:07
jobordu added a commit that referenced this pull request Jul 9, 2026
…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>
jobordu added a commit that referenced this pull request Jul 22, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants