Skip to content

[issues/249] Release gating scripts#606

Merged
couimet merged 7 commits into
mainfrom
issues/249-finalize-release
May 27, 2026
Merged

[issues/249] Release gating scripts#606
couimet merged 7 commits into
mainfrom
issues/249-finalize-release

Conversation

@couimet
Copy link
Copy Markdown
Owner

@couimet couimet commented May 27, 2026

Summary

Closes the deferred-version loop from PR #605. Adds three release gating scripts that form a pipeline — soft-lock a version for QA, hard-finalize when QA passes, start the next cycle — and wires them into root-level pnpm scripts. Removes the nextTargetVersion field since the "Unreleased" convention is embedded in the scripts.

Changes

  • lock-version.sh — idempotent soft-lock (rename YAML, bump .version, regenerate versioned instructions). 11 BATS tests.
  • finalize-release.sh — one-way-door hard-finalize (CHANGELOG, README markers/banner, publishing instructions). 7 BATS tests.
  • start-release.sh — idempotent next-cycle setup (copy YAML, prepend CHANGELOG header, re-add README banner). 8 BATS tests.
  • Removed nextTargetVersion from package.json; simplified generate-qa-test-plan.sh, generate-release-testing-instructions.sh, and qa-suggest SKILL.md to always-on Unreleased mode.
  • Root-level pnpm scripts for all three (pnpm lock-version:vscode-extension X.Y.Z, etc.); examples no longer cd into package directories.
  • Documentation: TESTING.md diagram + table; RELEASE-STRATEGY.md deduplication and script-based examples.

Test Plan

  • All existing tests pass (1979 Jest + 172 BATS)
  • 26 new BATS tests across three suites
  • grep -r "nextTargetVersion" returns nothing in source files (scripts, package.json)

Related

Summary by CodeRabbit

Release Notes

  • Documentation

    • Updated release strategy documentation with refined release workflow and automation status integration
    • Enhanced testing documentation with new release QA lifecycle diagrams and scripted workflows
  • Tests

    • Added comprehensive test coverage for release finalization, version locking, and development cycle initialization processes
  • Chores

    • Introduced new release lifecycle management commands for version control, finalization, and next-cycle preparation
    • Updated package configuration with release automation enhancements

Review Change Stack

couimet added 6 commits May 26, 2026 22:44
…cripts

nextTargetVersion was a boolean disguised as a string: the SemVer code path in
the QA generators is dead now that lock-version.sh will handle version lock-in.
Dropped the field from package.json and hardcoded the Unreleased convention
directly into generate-qa-test-plan.sh and generate-release-testing-instructions.sh.
Updated qa-suggest SKILL.md to match. Also dropped the boilerplate Phase 0
section (environment setup) from the generated release testing instructions —
every contributor already has Node.js/pnpm/gh configured. BATS tests: deleted
4 SemVer-path tests (forward compat is no longer needed), removed the field
from 11 fixture JSONs.
Idempotent script that transitions from the deferred "Unreleased" version to a concrete SemVer. Takes an X.Y.Z argument and validates it, then renames qa-test-cases-unreleased.yaml → qa-test-cases-vX.Y.Z.yaml, bumps package.json .version, and regenerates versioned release testing instructions with internal references fixup'd. Detects the already-locked state (.version matches the target + versioned YAML exists) and prints a summary instead of re-running steps, so it's safe to re-run after adding bug-fix TCs during QA. 11 BATS tests cover the happy path, idempotency, error paths, and YAML content preservation.
One-way-door script that finalizes a release after QA passes. Reads .version from package.json (no argument — the version was already locked by lock-version.sh), validates prerequisites (versioned YAML exists, CHANGELOG has [Unreleased] section, README has <sup>Unreleased</sup> markers), then: replaces ## [Unreleased] → ## [X.Y.Z] - YYYY-MM-DD in CHANGELOG, strips all <sup>Unreleased</sup> markers from README, removes the [!IMPORTANT] banner block, formats both files with prettier, and runs generate-publishing-instructions.sh to produce the publishing guide. 7 BATS tests cover the happy path, content preservation, and error paths (missing .version, dirty tree, no versioned YAML, no [Unreleased] section, no markers).
Idempotent script that starts the next development cycle after a release. Reads .version from package.json, copies the versioned QA YAML → qa-test-cases-unreleased.yaml (skip if exists), prepends a fresh ## [Unreleased] header with empty Added/Changed/Fixed sections to CHANGELOG (skip if already present), and re-adds the > [!IMPORTANT] banner before the first ## header in README (skip if already present). Uses head/tail splitting with a fallback for the INSERT_LINE=1 edge case since macOS head -n 0 is illegal. 8 BATS tests cover the happy path, idempotency, partial re-run, YAML content preservation, CHANGELOG structure, banner content, and error paths.

Added pnpm scripts for all three release scripts: lock-version, finalize-release, and start-release in the child package.json (alphabetically ordered), with delegating root-level scripts (e.g. pnpm lock-version:vscode-extension X.Y.Z). Updated all documentation references to use root-level pnpm commands instead of direct .sh script names, removed redundant -- separators (pnpm no longer needs them), and eliminated all cd commands from RELEASE-STRATEGY.md examples in favor of root-level pnpm scripts.

TESTING.md: rewrote the Release QA Cycle section — consolidated the prose list into the release scripts reference table placed before the Mermaid diagram, replaced the nextTargetVersion filename-mirror sentence with the deferred-version convention, and mapped all commands to pnpm scripts.

RELEASE-STRATEGY.md: deleted the "Stripping Markers at Release Time" section (duplicates what the scripts document), updated the High-Level Process with a nested numbered list under Prepare, rewrote Examples 2 & 3 using root-level pnpm scripts, removed the "Release v0.1.1 (Full Process)" Quick Reference, and marked "Release verification scripts" and "Pre-commit hooks" as done in Future Enhancements.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@couimet, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 24 minutes and 3 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d55711a6-cee9-4afb-8a0e-27c2727b57ef

📥 Commits

Reviewing files that changed from the base of the PR and between f7e608a and 1c7e1cc.

📒 Files selected for processing (6)
  • .claude/skills/qa-suggest/SKILL.md
  • docs/RELEASE-STRATEGY.md
  • packages/rangelink-vscode-extension/TESTING.md
  • packages/rangelink-vscode-extension/scripts/finalize-release.sh
  • packages/rangelink-vscode-extension/scripts/lock-version.sh
  • packages/rangelink-vscode-extension/scripts/start-release.sh

Walkthrough

This PR replaces the nextTargetVersion-based release workflow with a trunk-focused model where "Unreleased" placeholders remain embedded throughout development. Three new scripts manage the cycle: lock-version gates versioning, finalize-release strips markers, and start-release resets for the next cycle. QA generators now hardcode unreleased naming, deferring version assignment until the locking gate.

Changes

Release Workflow Restructuring

Layer / File(s) Summary
Workflow Architecture & Configuration
.claude/skills/qa-suggest/SKILL.md, docs/RELEASE-STRATEGY.md, packages/rangelink-vscode-extension/TESTING.md, package.json, packages/rangelink-vscode-extension/package.json
Documentation and configuration establish the three-phase release model: trunk (unreleased) → locking (SemVer gate) → finalization (marker stripping). The "Unreleased" convention is now embedded in tooling rather than config-driven. The nextTargetVersion field is removed from extension metadata. Four new scripts (lock-version, finalize-release, start-release) are added to package orchestration.
QA Artifact Generation
packages/rangelink-vscode-extension/scripts/generate-qa-test-plan.sh, packages/rangelink-vscode-extension/scripts/generate-release-testing-instructions.sh, tests/shell/generate-qa-test-plan.bats, tests/shell/generate-release-testing-instructions.bats
Scripts now hardcode "Unreleased" filenames and labels instead of deriving from nextTargetVersion. generate-qa-test-plan.sh always produces qa-test-cases-unreleased.yaml; generate-release-testing-instructions.sh always produces release-testing-instructions-unreleased.md. Tests verify unreleased output, idempotency, and correct title rendering when the version field is omitted from package.json.
Version Locking Gate
packages/rangelink-vscode-extension/scripts/lock-version.sh, tests/shell/lock-version.bats
lock-version.sh "soft-locks" deferred QA artifacts to a specific SemVer. It validates the target version argument, renames qa-test-cases-unreleased.yaml to versioned form, updates package.json's .version, regenerates release testing instructions with versioned names, and rewrites internal references. Tests cover happy-path locking, idempotency when already locked, partial-completion recovery, and error conditions (missing version, invalid SemVer, missing artifacts).
Release Finalization
packages/rangelink-vscode-extension/scripts/finalize-release.sh, tests/shell/finalize-release.bats
finalize-release.sh validates artifacts, computes release date, and strips trunk markers: converts CHANGELOG [Unreleased] to versioned heading, removes <sup>Unreleased</sup> badges from README, deletes the [!IMPORTANT] banner, formats with Prettier, and calls publishing instructions generation. Tests verify marker removal while preserving unrelated content, proper changelog versioning, and error detection for missing artifacts, dirty trees, or incomplete marker state.
Next Cycle Bootstrap
packages/rangelink-vscode-extension/scripts/start-release.sh, tests/shell/start-release.bats
start-release.sh prepares for the next development cycle: copies versioned QA YAML back to unreleased form, inserts a new [Unreleased] section in CHANGELOG with empty Added/Changed/Fixed subsections, adds a README release banner above the first heading, and formats with Prettier. Tests verify idempotency (detecting already-initialized state), partial-completion recovery, and proper error reporting for missing version or QA files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • couimet/rangeLink#605: Both PRs change the QA naming logic to use the "Unreleased" placeholder, modifying scripts like generate-qa-test-plan.sh and the related YAML filename/label handling to shift from SemVer-derived names to unreleased conventions.
  • couimet/rangeLink#498: Both PRs modify generate-release-testing-instructions.sh, with the retrieved PR introducing the initial version-aware script and the main PR changing it to always emit hardcoded "Unreleased" constants instead of deriving from nextTargetVersion.
  • couimet/rangeLink#388: Both PRs refactor the QA cycle logic by updating scripts like generate-qa-test-plan.sh and the QA skill instructions to remove reliance on the nextTargetVersion field and adopt the "Unreleased" convention as an embedded tooling constant.

🐰 Five scripts dance in rhythm true,
Unreleased, locked, and finalized through.
QA marks the gate with SemVer's key,
Then bootstrap springs forth wild and free.
One cycle ends, the next takes flight—
Trunk-based trunk, forever bright! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title '[issues/249] Release gating scripts' clearly and specifically summarizes the main change: implementing release gating scripts (lock-version.sh, finalize-release.sh, start-release.sh) and wiring them into pnpm scripts.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issues/249-finalize-release

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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/skills/qa-suggest/SKILL.md:
- Line 26: The docs still branch on nextTargetVersion in Step 7 causing
inconsistent filenames/headers despite the tooling using the "Unreleased"
convention; update Step 7 to remove any conditional branching on
nextTargetVersion and standardize on the fixed "Unreleased" naming (e.g.,
qa-test-cases-unreleased.yaml) and header text, and clarify that
package.json.version remains the last published SemVer; search for occurrences
of nextTargetVersion, Step 7, and references to filenames/headers in SKILL.md
and replace them with the single-source "Unreleased" convention and a short note
referencing package.json.version.

In `@docs/RELEASE-STRATEGY.md`:
- Line 139: Update the sentence that currently attributes bumping `.version` to
the `finalize-release` step: change it to assign `.version` updates to the
`lock-version` step so the text reads that `pnpm
finalize-release:vscode-extension` performs hard-finalize (updates CHANGELOG,
strips README markers, generates publishing instructions) while `lock-version`
is responsible for bumping/locking `.version`; reference the task names
`finalize-release` and `lock-version` and the `.version` file in the updated
sentence to make ownership explicit.

In `@packages/rangelink-vscode-extension/scripts/finalize-release.sh`:
- Around line 70-80: The release scripts use BSD-only sed in-place syntax (sed
-i '') which breaks on GNU sed; update finalize-release.sh (the sed calls that
edit "$CHANGELOG" and "$README") and lock-version.sh (the multi -e sed in-place
rewrite) to perform portable in-place edits by writing sed output to a temporary
file and atomically moving it back (or use sed -i.bak then remove the backup)
instead of relying on '' argument; locate the specific sed invocations in
finalize-release.sh and the multi -e sed block in lock-version.sh and replace
each in-place sed invocation with the temp-file + mv (or sed -i.bak + rm)
pattern so the scripts work on both BSD and GNU environments.

In `@packages/rangelink-vscode-extension/scripts/lock-version.sh`:
- Around line 67-70: The script currently exits if the UNRELEASED_YAML file is
missing; change that check to allow partial-run recovery by first testing for
any existing versioned YAML (e.g., files matching qa-test-cases-v*.yaml) before
failing: if UNRELEASED_YAML is missing but a versioned file exists, treat that
as a successful prior run and continue (skip the hard exit), otherwise keep the
error and exit; update the conditional around UNRELEASED_YAML to look for
matching versioned files (using the UNRELEASED_YAML variable and a glob like
"${UNRELEASED_YAML%unreleased.yaml}-v"*" .yaml" or explicit pattern) and only
call exit 1 when neither the unreleased nor any versioned file is present.

In `@packages/rangelink-vscode-extension/scripts/start-release.sh`:
- Around line 69-71: The numeric test uses INSERT_LINE (set by grep -n) without
guarding for an empty value; if grep finds no match INSERT_LINE is empty and the
[[ "$INSERT_LINE" -eq 1 ]] test will fail at runtime. Fix by validating
INSERT_LINE before numeric comparison (e.g., check [[ -n "$INSERT_LINE" ]] or
use a regex/ctype test) and only perform -eq when INSERT_LINE is numeric; update
the same pattern around the other occurrence (the block around INSERT_LINE usage
at the later section) so both places guard against empty/non-numeric
INSERT_LINE.

In `@packages/rangelink-vscode-extension/TESTING.md`:
- Line 282: Update the YAML rename ownership text so both statements reference
the same script: replace the mention of finalize-release with pnpm
lock-version:vscode-extension in this section (the line that currently ties
version locking to pnpm lock-version:vscode-extension is correct—ensure the
other sentence(s) that claim finalize-release performs the rename instead point
to pnpm lock-version:vscode-extension) and keep the note that the filename
during trunk development is qa-test-cases-unreleased.yaml and that
generate-qa-issue parses it automatically.
🪄 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

Run ID: dae4fb4f-1227-402b-be39-e54dcf208536

📥 Commits

Reviewing files that changed from the base of the PR and between 6c4f9ec and f7e608a.

📒 Files selected for processing (15)
  • .claude/skills/qa-suggest/SKILL.md
  • docs/RELEASE-STRATEGY.md
  • package.json
  • packages/rangelink-vscode-extension/TESTING.md
  • packages/rangelink-vscode-extension/package.json
  • packages/rangelink-vscode-extension/scripts/finalize-release.sh
  • packages/rangelink-vscode-extension/scripts/generate-qa-test-plan.sh
  • packages/rangelink-vscode-extension/scripts/generate-release-testing-instructions.sh
  • packages/rangelink-vscode-extension/scripts/lock-version.sh
  • packages/rangelink-vscode-extension/scripts/start-release.sh
  • tests/shell/finalize-release.bats
  • tests/shell/generate-qa-test-plan.bats
  • tests/shell/generate-release-testing-instructions.bats
  • tests/shell/lock-version.bats
  • tests/shell/start-release.bats

Comment thread .claude/skills/qa-suggest/SKILL.md
Comment thread docs/RELEASE-STRATEGY.md Outdated
Comment thread packages/rangelink-vscode-extension/scripts/finalize-release.sh Outdated
Comment thread packages/rangelink-vscode-extension/scripts/lock-version.sh
Comment thread packages/rangelink-vscode-extension/scripts/start-release.sh
Comment thread packages/rangelink-vscode-extension/TESTING.md
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

…guards

Corrected several places that attributed .version bumping and YAML renaming to finalize-release when lock-version performs those steps. Replaced BSD-only sed -i '' with portable sed -i.bak + rm .bak in finalize-release.sh and lock-version.sh. Added INSERT_LINE guard and partial-run recovery to start-release.sh and lock-version.sh.

Benefits:
- Scripts work on both macOS (BSD sed) and Ubuntu CI (GNU sed)
- Docs accurately reflect which script owns each release step
- Lock and start scripts handle edge cases (empty grep results, partial prior runs)
- SKILL.md no longer references the removed nextTargetVersion field

Ref: #606 (review)
@github-actions
Copy link
Copy Markdown

✅ CI / Integration Tests (with extensions) — run summary

Duration 10m 1s
Unit tests Ran in Test & Validate job
Integration tests passing 160
QA TC IDs exercised clipboard-preservation-011, clipboard-preservation-012, custom-ai-assistant-003, claude-code-001, claude-code-006, claude-code-007, gemini-code-assist-001, gemini-code-assist-005, gemini-code-assist-006
Report View run & artifacts

@github-actions
Copy link
Copy Markdown

✅ CI / Test & Validate — run summary

Duration 11m 33s
Unit tests passed 1979 / 1979
Integration tests passing 151
QA TC IDs exercised status-bar-menu-002, status-bar-menu-003, status-bar-menu-005, status-bar-menu-006, bind-to-destination-010, bind-to-destination-013, terminal-picker-001, terminal-picker-002, terminal-picker-003, terminal-picker-004, terminal-picker-005, terminal-picker-007, terminal-picker-008, terminal-picker-011, terminal-picker-012, terminal-picker-013, terminal-picker-014, terminal-picker-015, terminal-picker-016, file-picker-001, file-picker-002, file-picker-003, file-picker-004, file-picker-005, file-picker-009, file-picker-011, file-picker-012, clipboard-preservation-001, clipboard-preservation-002, clipboard-preservation-003, clipboard-preservation-004, clipboard-preservation-005, clipboard-preservation-006, clipboard-preservation-007, clipboard-preservation-008, clipboard-preservation-009, clipboard-preservation-010, clipboard-preservation-013, clipboard-preservation-014, clipboard-preservation-015, clipboard-preservation-016, send-file-path-001, send-file-path-002, send-file-path-004, send-file-path-005, send-file-path-006, send-file-path-007, send-file-path-008, send-file-path-010, send-file-path-011, send-file-path-012, dirty-buffer-warning-004, dirty-buffer-warning-006, dirty-buffer-warning-007, dirty-buffer-warning-019, send-terminal-selection-003, send-terminal-selection-006, send-terminal-selection-007, go-to-link-001, unbind-001, unbind-003, unbind-004, editor-binding-validation-004, full-line-navigation-001, full-line-navigation-002, char-navigation-001, char-navigation-002, full-line-link-generation-001, wrapped-link-navigation-001, wrapped-link-navigation-002, wrapped-link-navigation-003, wrapped-link-navigation-004, markdown-link-navigation-001, url-exclusion-001, stale-viewcolumn-001, hidden-tab-paste-001, hidden-tab-paste-002, full-line-selection-validation-001, core-send-commands-r-l-001, core-send-commands-r-l-002, core-send-commands-r-l-003, core-send-commands-r-c-001, core-send-commands-r-l-004, core-send-commands-r-c-002, core-send-commands-r-l-005, core-send-commands-r-p-001, core-send-commands-r-v-001, clickable-file-paths-001, clickable-file-paths-002, clickable-file-paths-003, clickable-file-paths-004, clickable-file-paths-005, clickable-file-paths-006, clickable-file-paths-007, clickable-file-paths-008, clickable-file-paths-009, clickable-file-paths-010, clickable-file-paths-011, clickable-file-paths-012, smart-padding-001, smart-padding-003, smart-padding-005, smart-padding-006, smart-padding-007, smart-padding-008, smart-padding-011, duplicate-tab-group-001, duplicate-tab-group-002, duplicate-tab-group-003, duplicate-tab-group-004, langswitch-binding-001, langswitch-binding-002, navigation-clamping-001, navigation-clamping-002, navigation-clamping-003, navigation-clamping-004, untitled-navigation-001, untitled-navigation-002, untitled-navigation-003, untitled-navigation-004, untitled-navigation-005, untitled-navigation-006, navigation-toast-settings-001, navigation-toast-settings-002, navigation-toast-settings-003, filename-fallback-navigation-001, filename-fallback-navigation-002, filename-fallback-navigation-003, filename-fallback-navigation-004, custom-ai-assistant-001, custom-ai-assistant-002, custom-ai-assistant-004, custom-ai-assistant-005, custom-ai-assistant-006, custom-ai-assistant-007, custom-ai-assistant-008, custom-ai-assistant-009, custom-ai-assistant-010, custom-ai-assistant-011, custom-ai-assistant-012, custom-ai-assistant-013, custom-ai-assistant-014, custom-ai-assistant-015, custom-ai-assistant-016, custom-ai-assistant-017, github-copilot-chat-001, release-notifier-001, release-notifier-002, status-bar-appearance-001, status-bar-appearance-002
Report View run & artifacts

@couimet couimet merged commit 6e29373 into main May 27, 2026
5 of 6 checks passed
@couimet couimet deleted the issues/249-finalize-release branch May 27, 2026 12:56
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.

1 participant