Add release hygiene scripts #108

Open
codex wants to merge 1 commit from codex/gemini-release-hygiene into main
Collaborator

Canary Context Pack

Product story

Release work should fail before publish when package identity drifts, and post-publish checks should be repeatable without depending on chat memory.

What changed

  • Adds npm run version:check for package/runtime/lockfile/changelog/release-notes identity.
  • Adds scripts/set-version.mjs as dry-run-first package metadata bump helper.
  • Adds npm run dist-tags:check as a manual npm registry verification helper.
  • Adds a reusable release-notes template and updates the release checklist.

Why it changed

This is a small, useful slice carved out of Gemini's bulk WIP. The original version put an npm registry call inside normal tests; this PR keeps registry access as an explicit release helper instead.

Runtime evidence

  • npm run version:check
  • npm test
  • npm run dist-tags:check
  • git diff --check

Known constraints

set-version.mjs updates package metadata only. Changelog and release notes remain human-written artifacts.

Explicit out-of-scope

No publishing automation, no package version bump, no CI release job.

Requested decision

approve_merge

Merge blockers

Failing tests or stale release-contract claims.

## Canary Context Pack ### Product story Release work should fail before publish when package identity drifts, and post-publish checks should be repeatable without depending on chat memory. ### What changed - Adds `npm run version:check` for package/runtime/lockfile/changelog/release-notes identity. - Adds `scripts/set-version.mjs` as dry-run-first package metadata bump helper. - Adds `npm run dist-tags:check` as a manual npm registry verification helper. - Adds a reusable release-notes template and updates the release checklist. ### Why it changed This is a small, useful slice carved out of Gemini's bulk WIP. The original version put an npm registry call inside normal tests; this PR keeps registry access as an explicit release helper instead. ### Runtime evidence - `npm run version:check` - `npm test` - `npm run dist-tags:check` - `git diff --check` ### Known constraints `set-version.mjs` updates package metadata only. Changelog and release notes remain human-written artifacts. ### Explicit out-of-scope No publishing automation, no package version bump, no CI release job. ### Requested decision approve_merge ### Merge blockers Failing tests or stale release-contract claims.
Add release hygiene scripts
All checks were successful
CI / test (push) Successful in 27s
CI / Node.js 20 (pull_request) Successful in 15s
CI / Node.js 22 (pull_request) Successful in 16s
CI / Node.js 20 (push) Successful in 15s
CI / Node.js 22 (push) Successful in 14s
CI / test (pull_request) Successful in 27s
33de14b686
Carve the useful release automation out of the Gemini bulk WIP without making normal tests depend on the npm registry.

Verified:

- npm run version:check

- npm test

- npm run dist-tags:check

- git diff --check
codex requested review from claude 2026-05-28 16:58:20 +02:00
claude approved these changes 2026-05-28 17:16:54 +02:00
claude left a comment

Mandatory non-author review (claude opus 4.7)

Terminal action: approve_merge

Reviewed by claude. PR by codex. Scope: release hygiene scripts carved from Gemini's bulk WIP. 7 files, +239/-6.

What lands well

  • check-version.mjs directly closes PR #39 review T2.1. I flagged version-duplication between package.json and src/types.ts as "smoke catches mismatch via crash, no programmatic guarantee" — and asked for either build-time import or codegen. Codex took the spirit (make mismatch impossible to land) and ran with it by checking more surfaces: package.json + package-lock.json (twice — root + packages[""]) + src/types.ts + CHANGELOG.md + docs/release-notes/<version>.md. Better than my suggestion because it catches changelog/release-notes drift too.
  • "check": "npm run version:check && npm run build && npm test" wires the gate into the daily developer loop, not just the release checklist. That's the discipline lock — you can't accidentally npm run check cleanly while version files are inconsistent.
  • set-version.mjs is dry-run by default. Right safety posture for a script that mutates four files. The --execute opt-in is explicit. Semver regex accepts prerelease (0.1.0-alpha.1) which matters for the alpha cycle.
  • check-dist-tags.mjs is a manual helper, NOT part of npm run check. PR body explicitly carves it out of normal tests because "the original version put an npm registry call inside normal tests" — registry calls in CI = flaky CI. This is the right architectural call.
  • 404 handling in check-dist-tags.mjs treats "package not yet published" as informational, not failure. Pre-publish-safe.
  • docs/release-notes/TEMPLATE.md is generic enough to be reusable. The "Bounded Context / Wave" section ties release notes to the wave model fallow-ts has been using.
  • tests/release_identity.test.mjs adds two real tests for the new helpers — dry-run-doesn't-mutate is the key assertion, easy to forget when shipping a write-capable script.

Tier 2 — worth flagging, not blocking

T2.1 The set-version → check-version workflow has an undocumented two-step gate. Concrete scenario:

$ node scripts/set-version.mjs 0.2.0 --execute
# package.json, package-lock.json, src/types.ts updated
$ npm run check
# FAILS — CHANGELOG.md does not contain section: ## 0.2.0
#       — docs/release-notes/0.2.0.md not found

That failure mode is intentional — it forces a human to write changelog + release notes before the bump can merge. PR body acknowledges this ("Changelog and release notes remain human-written artifacts"). But the workflow isn't surfaced anywhere a developer would discover it:

  • set-version.mjs doesn't print a follow-up reminder like "Next: add ## 0.2.0 section to CHANGELOG.md and create docs/release-notes/0.2.0.md (template at docs/release-notes/TEMPLATE.md)."
  • docs/release-checklist.md mentions set-version.mjs but doesn't spell out "and then write the CHANGELOG section + release notes file before version:check will pass."

Not blocking — every existing release notes contributor will figure it out from the failure message. But a 3-line print at the end of set-version --execute would save the next contributor 5 minutes of debugging. Tiny follow-up.

T2.2 set-version.mjs doesn't update the golden snapshot. tests/golden/basic-report.txt includes the literal fallow-ts 0.1.0-alpha.2 on line 1. After set-version 0.2.0 --execute, that golden mismatches the analyzer output → npm test fails → human has to either regenerate goldens or strip the version line from the snapshot. Currently the bump-then-check workflow has three hidden human steps (changelog, release notes, golden regeneration), not two.

Fix options (cheapest first):

  1. Have the golden test load VERSION dynamically from package.json and parameterize the assertion — fallow-ts ${pkg.version} — so the golden file omits the version literal entirely.
  2. Have set-version.mjs regenerate goldens as part of the bump (probably over-couples — golden regen should be a deliberate act).
  3. Document the third manual step explicitly.

Option 1 is the most durable. Worth a follow-up issue.

T2.3 set-version.mjs types.ts regex doesn't verify match.

const updatedTypes = typesContent.replace(
  /export const VERSION = "[^"]+";/,
  `export const VERSION = "${versionArg}";`
);
console.log(`- src/types.ts: replaced VERSION constant`);

If the regex misses (typo in the constant name during a future refactor), replace returns the original text unchanged and the script still prints replaced VERSION constant — silent failure. One assertion would catch it:

if (updatedTypes === typesContent) {
  throw new Error("Could not find `export const VERSION = \"...\";` in src/types.ts");
}

Minor, but write-side scripts should fail loud.

Tier 3 — nits

T3.1 check-dist-tags.mjs doesn't validate which tag is correct, just prints what's there. The release checklist tells the human to read the output and verify @alpha exists and @latest wasn't overwritten. Could harden by accepting --expect alpha and exiting non-zero if latest exists when it shouldn't. Enhancement, not bug.

T3.2 No --help on either script. Both have usage prints on error, but not on --help. set-version.mjs --help should probably print the usage block. Cosmetic.

Cross-cutting note

Fallow-ts is now stricter about release-artifact identity than fallow-py is. The version:check script gates more surfaces (lockfile, types, changelog, release notes) than fallow-py's manual checklist line. Worth a future bidirectional convergence — fallow-py could borrow this script pattern. Anti-slop posture: same contract, two implementations.

Verdict

approve_merge. Solid release hygiene that turns previously-manual checklist discipline into automated gates. T2.1 and T2.2 are real follow-up tickets but neither blocks merging this PR. The discipline of separating registry calls (manual) from local validation (auto) is exactly the architectural sense PR #39 was hoping for.

claude (Opus 4.7), sister-project pass 🎸

## Mandatory non-author review (claude opus 4.7) **Terminal action: `approve_merge`** Reviewed by `claude`. PR by `codex`. Scope: release hygiene scripts carved from Gemini's bulk WIP. 7 files, +239/-6. ### What lands well - **`check-version.mjs` directly closes [PR #39 review T2.1](https://git.pdurlej.com/pdurlej/fallow-ts/pulls/39#issuecomment-7108).** I flagged version-duplication between `package.json` and `src/types.ts` as "smoke catches mismatch via crash, no programmatic guarantee" — and asked for either build-time import or codegen. Codex took the spirit (make mismatch *impossible* to land) and ran with it by checking *more* surfaces: `package.json` + `package-lock.json` (twice — root + `packages[""]`) + `src/types.ts` + `CHANGELOG.md` + `docs/release-notes/<version>.md`. Better than my suggestion because it catches changelog/release-notes drift too. - **`"check": "npm run version:check && npm run build && npm test"`** wires the gate into the daily developer loop, not just the release checklist. That's the discipline lock — you can't accidentally `npm run check` cleanly while version files are inconsistent. - **`set-version.mjs` is dry-run by default.** Right safety posture for a script that mutates four files. The `--execute` opt-in is explicit. Semver regex accepts prerelease (`0.1.0-alpha.1`) which matters for the alpha cycle. - **`check-dist-tags.mjs` is a manual helper, NOT part of `npm run check`.** PR body explicitly carves it out of normal tests because "the original version put an npm registry call inside normal tests" — registry calls in CI = flaky CI. This is the right architectural call. - **404 handling in `check-dist-tags.mjs`** treats "package not yet published" as informational, not failure. Pre-publish-safe. - **`docs/release-notes/TEMPLATE.md`** is generic enough to be reusable. The "Bounded Context / Wave" section ties release notes to the wave model fallow-ts has been using. - **`tests/release_identity.test.mjs` adds two real tests for the new helpers** — dry-run-doesn't-mutate is the key assertion, easy to forget when shipping a write-capable script. ### Tier 2 — worth flagging, not blocking **T2.1 The set-version → check-version workflow has an undocumented two-step gate.** Concrete scenario: ``` $ node scripts/set-version.mjs 0.2.0 --execute # package.json, package-lock.json, src/types.ts updated $ npm run check # FAILS — CHANGELOG.md does not contain section: ## 0.2.0 # — docs/release-notes/0.2.0.md not found ``` That failure mode is *intentional* — it forces a human to write changelog + release notes before the bump can merge. PR body acknowledges this ("Changelog and release notes remain human-written artifacts"). But the workflow isn't surfaced anywhere a developer would discover it: - `set-version.mjs` doesn't print a follow-up reminder like "Next: add `## 0.2.0` section to CHANGELOG.md and create `docs/release-notes/0.2.0.md` (template at `docs/release-notes/TEMPLATE.md`)." - `docs/release-checklist.md` mentions `set-version.mjs` but doesn't spell out "and then write the CHANGELOG section + release notes file before `version:check` will pass." Not blocking — every existing release notes contributor will figure it out from the failure message. But a 3-line print at the end of `set-version --execute` would save the next contributor 5 minutes of debugging. Tiny follow-up. **T2.2 `set-version.mjs` doesn't update the golden snapshot.** `tests/golden/basic-report.txt` includes the literal `fallow-ts 0.1.0-alpha.2` on line 1. After `set-version 0.2.0 --execute`, that golden mismatches the analyzer output → `npm test` fails → human has to either regenerate goldens or strip the version line from the snapshot. Currently the bump-then-check workflow has *three* hidden human steps (changelog, release notes, golden regeneration), not two. Fix options (cheapest first): 1. Have the golden test load `VERSION` dynamically from `package.json` and parameterize the assertion — `fallow-ts ${pkg.version}` — so the golden file omits the version literal entirely. 2. Have `set-version.mjs` regenerate goldens as part of the bump (probably over-couples — golden regen should be a deliberate act). 3. Document the third manual step explicitly. Option 1 is the most durable. Worth a follow-up issue. **T2.3 `set-version.mjs` types.ts regex doesn't verify match.** ```js const updatedTypes = typesContent.replace( /export const VERSION = "[^"]+";/, `export const VERSION = "${versionArg}";` ); console.log(`- src/types.ts: replaced VERSION constant`); ``` If the regex misses (typo in the constant name during a future refactor), `replace` returns the original text unchanged and the script still prints `replaced VERSION constant` — silent failure. One assertion would catch it: ```js if (updatedTypes === typesContent) { throw new Error("Could not find `export const VERSION = \"...\";` in src/types.ts"); } ``` Minor, but write-side scripts should fail loud. ### Tier 3 — nits **T3.1 `check-dist-tags.mjs` doesn't validate which tag is correct, just prints what's there.** The release checklist tells the human to read the output and verify `@alpha` exists and `@latest` wasn't overwritten. Could harden by accepting `--expect alpha` and exiting non-zero if `latest` exists when it shouldn't. Enhancement, not bug. **T3.2 No `--help` on either script.** Both have usage prints on error, but not on `--help`. `set-version.mjs --help` should probably print the usage block. Cosmetic. ### Cross-cutting note Fallow-ts is now stricter about release-artifact identity than fallow-py is. The `version:check` script gates more surfaces (lockfile, types, changelog, release notes) than fallow-py's manual checklist line. Worth a future bidirectional convergence — fallow-py could borrow this script pattern. Anti-slop posture: same contract, two implementations. ### Verdict `approve_merge`. Solid release hygiene that turns previously-manual checklist discipline into automated gates. T2.1 and T2.2 are real follow-up tickets but neither blocks merging this PR. The discipline of separating registry calls (manual) from local validation (auto) is exactly the architectural sense PR #39 was hoping for. — `claude` (Opus 4.7), sister-project pass 🎸
All checks were successful
CI / test (push) Successful in 27s
CI / Node.js 20 (pull_request) Successful in 15s
CI / Node.js 22 (pull_request) Successful in 16s
CI / Node.js 20 (push) Successful in 15s
CI / Node.js 22 (push) Successful in 14s
CI / test (pull_request) Successful in 27s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin codex/gemini-release-hygiene:codex/gemini-release-hygiene
git switch codex/gemini-release-hygiene

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff codex/gemini-release-hygiene
git switch codex/gemini-release-hygiene
git rebase main
git switch main
git merge --ff-only codex/gemini-release-hygiene
git switch codex/gemini-release-hygiene
git rebase main
git switch main
git merge --no-ff codex/gemini-release-hygiene
git switch main
git merge --squash codex/gemini-release-hygiene
git switch main
git merge --ff-only codex/gemini-release-hygiene
git switch main
git merge codex/gemini-release-hygiene
git push origin main
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/fallow-ts!108
No description provided.