fix: the release suite accepts the ceremony's own tree #109

Merged
dan-claude-bot merged 2 commits from fix/release-suite-stamped-tree into main 2026-07-19 14:22:27 +00:00
dan-claude-bot commented 2026-07-19 13:50:08 +00:00 (Migrated from github.com)

Fixes #108. The cast twin of heavy-duty/rig#45 (rig#44) — same disease, same cure.

What

test/release.test.ts:148-152 demanded the REAL CHANGELOG.md's literal Unreleased section extract non-empty and contain #96. The ceremony's own release: X.Y.Z PR stamps that heading into ## X.Y.Z — date, so the first real release PR turned CI red by construction — and post-release main stays red until the next feature PR re-adds the section. Invisible to both drill rehearsals: a fork rehearsal tags a branch, which runs release.yml and never ci.yml.

How

The test now reads the top ## heading's token — Unreleased between releases, the stamped X.Y.Z on and right after one — and asserts the exact release-notes.sh the workflow runs extracts it non-empty. The hardcoded issue-number grep is gone (no format coverage, rots per release).

Tests

Verified on both legitimate tree states: main's shape and a stamped copy (## 0.1.0 — 2026-07-19) — 24/24 both ways; full suite 606/606; biome clean.

Unblocks the release: 0.1.0 PR, which follows once this lands.

🤖 Generated with Claude Code

Fixes #108. The cast twin of heavy-duty/rig#45 (rig#44) — same disease, same cure. ## What `test/release.test.ts:148-152` demanded the REAL `CHANGELOG.md`'s literal `Unreleased` section extract non-empty **and contain `#96`**. The ceremony's own `release: X.Y.Z` PR stamps that heading into `## X.Y.Z — date`, so the first real release PR turned CI red by construction — and post-release main stays red until the next feature PR re-adds the section. Invisible to both drill rehearsals: a fork rehearsal tags a branch, which runs `release.yml` and never `ci.yml`. ## How The test now reads the top `## ` heading's token — `Unreleased` between releases, the stamped `X.Y.Z` on and right after one — and asserts the exact `release-notes.sh` the workflow runs extracts it non-empty. The hardcoded issue-number grep is gone (no format coverage, rots per release). ## Tests Verified on **both** legitimate tree states: main's shape and a stamped copy (`## 0.1.0 — 2026-07-19`) — 24/24 both ways; full suite 606/606; biome clean. Unblocks the `release: 0.1.0` PR, which follows once this lands. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
danmt (Migrated from github.com) reviewed 2026-07-19 13:50:08 +00:00
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-19 14:03:59 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: I agree with everything and have no additional feedback.

Verdict: I agree with everything and have no additional feedback.
claude-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-19 14:07:22 +00:00
claude-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: approve.

The diagnosis in #108 checks out against the code. release.yml stamps ## X.Y.Z — date over ## Unreleased via the ceremony's own PR, and the old test (test/release.test.ts, pre-diff lines 148-152) hardcoded both the literal Unreleased token and a #96 grep — false by construction on the release tree and on post-release main. The fix targets exactly that and nothing else.

Why the new acceptance stays narrow:

  • Token extraction matches the script's own parsing by construction. The test's /^## (\S+)/m (test/release.test.ts:159) yields the first whitespace-delimited token of the top heading, and release-notes.sh compares awk's $2 of ^## lines whole (.github/scripts/release-notes.sh:25-28). Same delimiter, same anchor — Unreleased and 0.1.0 — 2026-07-19 both round-trip correctly.
  • Fail-closed is preserved in both directions. A changelog with no ## heading throws explicitly (test/release.test.ts:160); an empty top section still fails, because the script exits 1 on empty extraction (release-notes.sh:30) and the test pins code === 0 plus non-empty output (test/release.test.ts:162-163). I verified this: stamping the real changelog's top heading to ## 0.1.0 — 2026-07-19 passes 24/24, and an empty stamped top section fails the test.
  • The dropped #96 grep loses no coverage. The #96 ritual citation is still pinned by the missing-version test (test/release.test.ts:126), where it belongs — in the script's own error message, not in rotting changelog content.
  • A junk top heading can't sneak a release through. This test never gated the release body; release.yml extracts by $GITHUB_REF_NAME after asserting tag == package.json version, and a section missing for that version fails before gh release create runs. The test's job is drift detection on the extraction tool against the real file, which it still does.

Ran the suite in a worktree at head d7d715a: build clean, 606/606 tests pass, biome check --error-on-warnings clean, and the stamped-tree state passes as claimed in the PR body.

One optional tightening for a future pass, not blocking: the test accepts any \S+ top token, so a top heading that is neither Unreleased nor package.json's version would still pass. Asserting top[1] is one of those two would close that last sliver, at the cost of coupling the test to the version-bump ordering inside the release PR — reasonable to leave as is.

automated review by claude-bot-andresmgsl · heavy-duty-review-bot

**Verdict: approve.** The diagnosis in #108 checks out against the code. `release.yml` stamps `## X.Y.Z — date` over `## Unreleased` via the ceremony's own PR, and the old test (`test/release.test.ts`, pre-diff lines 148-152) hardcoded both the literal `Unreleased` token and a `#96` grep — false by construction on the release tree and on post-release main. The fix targets exactly that and nothing else. Why the new acceptance stays narrow: - **Token extraction matches the script's own parsing by construction.** The test's `/^## (\S+)/m` (test/release.test.ts:159) yields the first whitespace-delimited token of the top heading, and `release-notes.sh` compares awk's `$2` of `^## ` lines whole (`.github/scripts/release-notes.sh:25-28`). Same delimiter, same anchor — `Unreleased` and `0.1.0 — 2026-07-19` both round-trip correctly. - **Fail-closed is preserved in both directions.** A changelog with no `## ` heading throws explicitly (test/release.test.ts:160); an empty top section still fails, because the script exits 1 on empty extraction (release-notes.sh:30) and the test pins `code === 0` plus non-empty output (test/release.test.ts:162-163). I verified this: stamping the real changelog's top heading to `## 0.1.0 — 2026-07-19` passes 24/24, and an empty stamped top section fails the test. - **The dropped `#96` grep loses no coverage.** The `#96` ritual citation is still pinned by the missing-version test (test/release.test.ts:126), where it belongs — in the script's own error message, not in rotting changelog content. - **A junk top heading can't sneak a release through.** This test never gated the release body; `release.yml` extracts by `$GITHUB_REF_NAME` after asserting tag == `package.json` version, and a section missing for that version fails before `gh release create` runs. The test's job is drift detection on the extraction tool against the real file, which it still does. Ran the suite in a worktree at head d7d715a: build clean, **606/606 tests pass**, `biome check --error-on-warnings` clean, and the stamped-tree state passes as claimed in the PR body. One optional tightening for a future pass, not blocking: the test accepts any `\S+` top token, so a top heading that is neither `Unreleased` nor `package.json`'s version would still pass. Asserting `top[1]` is one of those two would close that last sliver, at the cost of coupling the test to the version-bump ordering inside the release PR — reasonable to leave as is. — _automated review by `claude-bot-andresmgsl` · heavy-duty-review-bot_
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-19 14:09:56 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: Approve — I agree with this as-is.

Why

  • Correct diagnosis: the old guard hardcoded literal Unreleased plus a rotting issue-number grep (#96), both false by construction on the ceremony's release: X.Y.Z tree (heading stamped to ## X.Y.Z — date) and on post-release main.
  • Replacement stays narrow: read top ## token via /^## (\S+)/m (same first-field rule as release-notes.sh awk $2), fail closed if no heading, assert the real extractor returns non-empty. Format-drift guard preserved; release-PR unshippable self-block gone.
  • Scope is test + CHANGELOG only; CI green. Twin of rig#45.

No blockers.

**Verdict: Approve** — I agree with this as-is. ### Why - Correct diagnosis: the old guard hardcoded literal `Unreleased` plus a rotting issue-number grep (`#96`), both false by construction on the ceremony's `release: X.Y.Z` tree (heading stamped to `## X.Y.Z — date`) and on post-release main. - Replacement stays narrow: read top `## ` token via `/^## (\S+)/m` (same first-field rule as `release-notes.sh` awk `$2`), fail closed if no heading, assert the real extractor returns non-empty. Format-drift guard preserved; release-PR unshippable self-block gone. - Scope is test + CHANGELOG only; CI green. Twin of rig#45. No blockers.
dan-claude-bot commented 2026-07-19 14:14:24 +00:00 (Migrated from github.com)

@danmt — handing this one to you. Final state:

What it fixes: cast#108 — test/release.test.ts demanded the real CHANGELOG's literal Unreleased section extract non-empty and contain #96: false by construction on the tree the ceremony's own release: X.Y.Z PR produces, so the first real release PR turned CI red and the flow blocked itself. Invisible to the fork rehearsals (a tag push runs release.yml, never ci.yml).

The fix: the test reads the top ## heading's token — Unreleased between releases, the stamped X.Y.Z on and right after one — and asserts the exact release-notes.sh the workflow runs extracts it non-empty. Verified on both tree states (24/24 both ways); full suite 606/606; biome clean (one round-trip with CI over a non-null assertion biome rejects — replaced with a narrowing guard).

Review history: one round — all three bots approved, no changes requested.

This PR gates the release: 0.1.0 PR, which I'll open the moment this merges. (rig's twin is heavy-duty/rig#45; box needed no fix — its suite pins already-released sections.)

🤖 Generated with Claude Code

@danmt — handing this one to you. Final state: **What it fixes:** cast#108 — `test/release.test.ts` demanded the real CHANGELOG's literal `Unreleased` section extract non-empty and contain `#96`: false by construction on the tree the ceremony's own `release: X.Y.Z` PR produces, so the first real release PR turned CI red and the flow blocked itself. Invisible to the fork rehearsals (a tag push runs `release.yml`, never `ci.yml`). **The fix:** the test reads the top `## ` heading's token — `Unreleased` between releases, the stamped `X.Y.Z` on and right after one — and asserts the exact `release-notes.sh` the workflow runs extracts it non-empty. Verified on both tree states (24/24 both ways); full suite 606/606; biome clean (one round-trip with CI over a non-null assertion biome rejects — replaced with a narrowing guard). **Review history:** one round — all three bots approved, no changes requested. **This PR gates the `release: 0.1.0` PR**, which I'll open the moment this merges. (rig's twin is heavy-duty/rig#45; box needed no fix — its suite pins already-released sections.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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: heavy-duty/cast#109
No description provided.