Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@

<rule id="QA002" priority="critical">
<title>QA YAML is a single file per release cycle</title>
<do>During trunk-based development, edit `qa-test-cases-unreleased.yaml` — add TCs, update automated status, adjust preconditions. The version is deferred until `finalize-release` locks it in.</do>
<do>During trunk-based development, edit `qa-test-cases-unreleased.yaml` — add TCs, update automated status, adjust preconditions. The version is deferred until `release:lock` locks it in.</do>
<do>When starting a new release cycle, use `pnpm generate:qa-test-plan:vscode-extension` to create a fresh `qa-test-cases-unreleased.yaml` carrying forward all TCs from the previous version's YAML</do>
<never>Edit a YAML file from a past release (e.g., don't touch v1.0.0.yaml after it has shipped)</never>
<exception>Fixing typos or updating `automated` status (`true`/`assisted`/`false`) in the current file is always allowed</exception>
Expand Down
46 changes: 29 additions & 17 deletions docs/RELEASE-STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,31 @@ Skip marking cosmetic renames or rewording of existing features — only mark ge

## Release Workflow

### Lifecycle

```mermaid
flowchart TD
A[Unreleased (deferred)] -->|release:lock| B[Version locked]
B --> C[QA pass]
C -->|release:prepare| D[Docs ready for publish]
D --> E[publish]
E -->|release:start| A
```

Three verbs, three phases: **lock** (freeze the version for QA), **prepare** (make documentation publication-ready), **start** (open the next development cycle).

### High-Level Process

Releasing a package involves these phases:

1. **Prepare**
1. `pnpm lock-version:vscode-extension X.Y.Z` — soft-lock the version for QA
2. Run QA pass (manual + automated TCs)
3. `pnpm finalize-release:vscode-extension` — hard-finalize: updates CHANGELOG, strips README markers, generates publishing instructions
2. **Build & Test** - Package and validate locally
3. **Publish** - Deploy to marketplace(s) and create GitHub release
4. **Tag** - Create annotated git tag following [tagging convention](#tagging-convention)
5. **Verify** - Confirm publication and test installation
6. **Next cycle** — `pnpm start-release:vscode-extension` to begin the next development cycle
1. **Lock** — `pnpm release:lock:vscode-extension X.Y.Z` — soft-lock the version for QA
2. **QA Pass** — manual + automated TCs against the versioned YAML
3. **Prepare** — `pnpm release:prepare:vscode-extension` — date-stamp CHANGELOG, strip `<sup>Unreleased</sup>` markers, remove `[!IMPORTANT]` banner, generate publishing instructions
4. **Build & Test** - Package and validate locally
5. **Publish** - Deploy to marketplace(s) and create GitHub release
6. **Tag** - Create annotated git tag following [tagging convention](#tagging-convention)
7. **Verify** - Confirm publication and test installation
8. **Next cycle** — `pnpm release:start:vscode-extension` to begin the next development cycle

### Package-Specific Instructions

Expand Down Expand Up @@ -321,9 +333,9 @@ git push origin vscode-extension-v0.1.0

```bash
# 1. Prepare: lock version, run QA, then finalize
pnpm lock-version:vscode-extension 0.1.1
pnpm release:lock:vscode-extension 0.1.1
# ... QA pass ...
pnpm finalize-release:vscode-extension
pnpm release:prepare:vscode-extension

# 2. Build, test, package locally
pnpm package:vscode-extension
Expand All @@ -337,7 +349,7 @@ git tag -a vscode-extension-v0.1.1 -m "Release vscode-extension v0.1.1"
git push origin vscode-extension-v0.1.1

# 5. Start next development cycle
pnpm start-release:vscode-extension
pnpm release:start:vscode-extension

# 6. Create GitHub release with CHANGELOG content
```
Expand All @@ -346,15 +358,15 @@ pnpm start-release:vscode-extension

```bash
# Same process as Example 2, with the new version number.
pnpm lock-version:vscode-extension 1.0.0
pnpm release:lock:vscode-extension 1.0.0
# ... QA pass ...
pnpm finalize-release:vscode-extension
pnpm release:prepare:vscode-extension
pnpm package:vscode-extension
pnpm install-local:vscode-extension:both
pnpm publish:vscode-extension:vsix
git tag -a vscode-extension-v1.0.0 -m "Release vscode-extension v1.0.0"
git push origin vscode-extension-v1.0.0
pnpm start-release:vscode-extension
pnpm release:start:vscode-extension
```

### Example 4: Multiple Package Release
Expand Down Expand Up @@ -419,7 +431,7 @@ These items are planned but not yet implemented:
- Better monorepo version coordination

- [x] **Pre-commit hooks** — working tree cleanliness enforced by the release scripts; remaining hook work: prevent commits that introduce version/CHANGELOG mismatches.
- [x] **Release verification scripts** — `pnpm lock-version:vscode-extension`, `pnpm finalize-release:vscode-extension`, and `pnpm generate:publish-instructions:vscode-extension` validate the working tree, version numbers, CHANGELOG, and unreleased markers before allowing each phase to proceed.
- [x] **Release verification scripts** — `pnpm release:lock:vscode-extension`, `pnpm release:prepare:vscode-extension`, and `pnpm generate:publish-instructions:vscode-extension` validate the working tree, version numbers, CHANGELOG, and unreleased markers before allowing each phase to proceed.

---

Expand All @@ -444,4 +456,4 @@ git push origin vscode-extension-v0.1.0
---

**Last Updated:** 2026-05-26
**Status:** Active — automated via `pnpm lock-version:vscode-extension` → `pnpm finalize-release:vscode-extension` → `pnpm start-release:vscode-extension`
**Status:** Active — automated via `pnpm release:lock:vscode-extension` → `pnpm release:prepare:vscode-extension` → `pnpm release:start:vscode-extension`
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"clean:all": "pnpm -r clean:all",
"compile": "pnpm clean && find . -name '*.tsbuildinfo' -delete && pnpm -r --workspace-concurrency=1 compile",
"enable-pnpm": "corepack enable",
"finalize-release:vscode-extension": "pnpm --filter rangelink-vscode-extension run finalize-release",
"fix": "pnpm lint:fix && pnpm format:fix",
"format": "prettier --check .",
"format:fix": "prettier --write .",
Expand All @@ -26,12 +25,13 @@
"install-local:vscode-extension:vscode": "pnpm --filter rangelink-vscode-extension install-local:vscode",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"lock-version:vscode-extension": "pnpm --filter rangelink-vscode-extension run lock-version",
"package:prepare": "pnpm clean:all && pnpm install",
"package:vscode-extension": "pnpm --filter rangelink-vscode-extension clean && pnpm --filter rangelink-vscode-extension compile && pnpm --filter rangelink-vscode-extension test && pnpm --filter rangelink-vscode-extension package",
"package:vscode-extension:withInstall:both": "pnpm package:vscode-extension && pnpm install-local:vscode-extension:both",
"publish:vscode-extension:vsix": "pnpm --filter rangelink-vscode-extension publish:vsix",
"start-release:vscode-extension": "pnpm --filter rangelink-vscode-extension run start-release",
"release:lock:vscode-extension": "pnpm --filter rangelink-vscode-extension run release:lock",
"release:prepare:vscode-extension": "pnpm --filter rangelink-vscode-extension run release:prepare",
"release:start:vscode-extension": "pnpm --filter rangelink-vscode-extension run release:start",
"test": "pnpm -r test",
"test:bats": "bats tests/shell/",
"test:release": "pnpm --filter rangelink-vscode-extension test:release",
Expand Down
23 changes: 5 additions & 18 deletions packages/rangelink-vscode-extension/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,11 @@ All `test:release*` commands accept `--label <tag>` (include TCs with QA YAML la

| Script | When | Re-runnable? | What it does |
| ------------------------------------------ | ---------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
| `pnpm lock-version:vscode-extension X.Y.Z` | Ready to start QA | Yes (idempotent) | Renames QA YAML → versioned, bumps `.version`, regenerates instructions |
| `pnpm finalize-release:vscode-extension` | QA passed, ready to ship | No (one-way door) | Finalizes CHANGELOG, strips README markers/banner, generates publishing instructions |
| `pnpm start-release:vscode-extension` | After publish, starting next cycle | Yes (idempotent) | Copies versioned YAML → unreleased, adds `[Unreleased]` CHANGELOG header, re-adds README banner |
| `pnpm release:lock:vscode-extension X.Y.Z` | Ready to start QA | Yes (idempotent) | Renames QA YAML → versioned, bumps `.version`, regenerates instructions, generates QA issue |
| `pnpm release:prepare:vscode-extension` | QA passed, ready to ship | No (one-way door) | Date-stamps CHANGELOG, strips README markers/banner, generates publishing instructions |
| `pnpm release:start:vscode-extension` | After publish, starting next cycle | Yes (idempotent) | Copies versioned YAML → unreleased, adds `[Unreleased]` CHANGELOG header, re-adds README banner |

```mermaid
flowchart TD
A[Version: Unreleased (deferred)] --> B[lock-version.sh X.Y.Z]
B --> C[QA pass — manual + automated TCs]
C --> D{All TCs pass?}
D -- No --> E[Fix bugs]
E --> C
D -- Yes --> F[finalize-release.sh]
F --> G[build VSIX + publish]
G --> H[start-release.sh]
H --> A
```
The release lifecycle is documented in [RELEASE-STRATEGY.md](../../docs/RELEASE-STRATEGY.md#release-workflow).

---

Expand Down Expand Up @@ -277,9 +266,7 @@ The QA test plan is a version-scoped YAML file that tracks both automated and ma
qa/qa-test-cases-unreleased.yaml
```

During trunk-based development the file is `qa/qa-test-cases-unreleased.yaml`. At release time `pnpm lock-version:vscode-extension` renames it to `qa/qa-test-cases-v<version>.yaml`.

The filename is always `qa-test-cases-unreleased.yaml` during trunk-based development — the version is deferred until `pnpm lock-version:vscode-extension` locks it in at QA time. It is parsed automatically by the `generate-qa-issue` script — no extra flags needed. One file per release — Git tracks history across versions.
During trunk-based development the file is `qa/qa-test-cases-unreleased.yaml`. At release time `pnpm release:lock:vscode-extension` renames it to `qa/qa-test-cases-v<version>.yaml` and generates the QA issue automatically. One file per release — Git tracks history across versions.

New QA YAML files are created by `pnpm generate:qa-test-plan`. The script carries forward all TCs from the most recent YAML, resets `status:` fields to `pending`, and preserves `automated:` flags.

Expand Down
6 changes: 3 additions & 3 deletions packages/rangelink-vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"clean:all": "pnpm clean && rm -rf node_modules .eslintcache *.log .vscode-test",
"compile": "pnpm compile:deps && pnpm generate-version:all && node esbuild.config.js",
"compile:deps": "cd ../barebone-logger && pnpm clean && pnpm compile && cd ../barebone-logger-testing && pnpm clean && pnpm compile && cd ../rangelink-core-ts && pnpm clean && pnpm compile",
"finalize-release": "./scripts/finalize-release.sh",
"generate-version": "node scripts/generate-version.js --copy-to out",
"generate-version:all": "node scripts/generate-version.js --copy-to out,dist",
"generate:publish-instructions": "./scripts/generate-publishing-instructions.sh",
Expand All @@ -46,10 +45,11 @@
"install-local": "./scripts/install-local.sh",
"install-local:cursor": "./scripts/install-local.sh cursor",
"install-local:vscode": "./scripts/install-local.sh vscode",
"lock-version": "./scripts/lock-version.sh",
"package": "rm -rf *.vsix && ../../scripts/sync-assets.sh && rm -f rangelink-vscode-extension-*.vsix && vsce package --no-dependencies",
"publish:vsix": "./scripts/publish-from-vsix.sh",
"start-release": "./scripts/start-release.sh",
"release:lock": "./scripts/orchestrate-release-lock.sh",
"release:prepare": "./scripts/finalize-release.sh",
"release:start": "./scripts/start-release.sh",
"test": "jest --coverage",
"test:coverage": "jest --coverage --coverageReporters=text --coverageReporters=text-summary --coverageReporters=html",
"test:fast": "jest --coverage --testPathIgnorePatterns '<rootDir>/src/__integration-tests__/' '\\.integration\\.test\\.ts$'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fi

# --- Working tree must be clean ---

if ! git -C "$REPO_ROOT" diff-index --quiet HEAD --; then
if [[ -n "$(git -C "$REPO_ROOT" status --porcelain)" ]]; then
echo -e "${RED}Error: working tree is dirty. Commit or stash changes first.${NC}" >&2
exit 1
fi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,45 @@ The following were validated when generating this file:

---

## Phase 1: Create Git Tag
## Phase 1: Smoke Test

Install the extension locally and verify it works before publishing. This catches packaging
errors (missing files, broken builds) before they reach the marketplace.

### Install Locally

Run from **monorepo root**:

\`\`\`bash
pnpm install-local:vscode-extension:both
\`\`\`

This installs the VSIX in both VS Code and Cursor. Restart any running instances first.

### Verify Version Number

1. Open VS Code (\`code\`), then the Command Palette (\`Cmd+Shift+P\` / \`Ctrl+Shift+P\`)
2. Run: **RangeLink: Show Version Info**
3. Confirm the version shown is **${VERSION}**
Comment thread
coderabbitai[bot] marked this conversation as resolved.
4. Confirm \`isDirty\` is **false** — a dirty build should never be published

### Spot-Check Core Commands and Content

Verify these commands work and the content is OK:

- [ ] **R-L**
- [ ] **R-F**
- [ ] **Status bar menu**
- [ ] **No README artifacts** — open the extension's README in the Extensions panel. No \`<sup>Unreleased</sup>\` markers, no \`> [!IMPORTANT]\` banner.

### If Anything Fails

- Fix the issue, rebuild (\`pnpm package:vscode-extension\`), and re-run the smoke test.
- Regenerate these instructions afterward: \`pnpm generate:publish-instructions:vscode-extension\`

---

## Phase 2: Create Git Tag

### Tag the Release

Expand All @@ -459,7 +497,7 @@ git show ${GIT_TAG} --stat

---

## Phase 2: Create GitHub Release
## Phase 3: Create GitHub Release

### Navigate to GitHub Releases

Expand All @@ -479,7 +517,7 @@ Click "Publish release"

---

## Phase 3: Publish to VS Code Marketplace
## Phase 4: Publish to VS Code Marketplace

### Ensure Logged In

Expand All @@ -506,7 +544,7 @@ Wait 5-10 minutes, then check:

---

## Phase 4: Publish to Open-VSX Registry
## Phase 5: Publish to Open-VSX Registry

### Login (First Time Setup)

Expand Down Expand Up @@ -541,7 +579,7 @@ Check:

---

## Phase 5: Post-Publishing Verification
## Phase 6: Post-Publishing Verification

### Check All Locations

Expand Down
31 changes: 14 additions & 17 deletions packages/rangelink-vscode-extension/scripts/generate-qa-issue.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,15 @@ if [[ -z "$YAML_FILE" ]]; then
fi

YAML_FILE="$QA_DIR/$LATEST"
printf 'Use %s? [Y/n] ' "qa/$LATEST"
read -r REPLY
if [[ -n "$REPLY" && ! "$REPLY" =~ ^[Yy]$ ]]; then
echo "Aborted." >&2
exit 0
if [[ -t 0 ]]; then
printf 'Use %s? [Y/n] ' "qa/$LATEST"
read -r REPLY
if [[ -n "$REPLY" && ! "$REPLY" =~ ^[Yy]$ ]]; then
echo "Aborted." >&2
exit 0
fi
else
echo "Auto-selected latest QA YAML: qa/$LATEST"
fi
fi

Expand Down Expand Up @@ -169,11 +173,12 @@ if [[ "$UBUNTU_COUNT" -gt 0 ]]; then
GROUP_CHECKBOXES+="${UBUNTU_SECTION}"$'\n'
fi

ISSUE_BODY="${ISSUE_BODY}${GROUP_CHECKBOXES}"

TOTAL_ASSISTED=$(echo "$GROUPS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync(0,'utf8')).total_assisted))")
TOTAL_MANUAL=$(echo "$GROUPS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync(0,'utf8')).total_manual))")
ISSUE_BODY="${ISSUE_BODY}"$'\n'"**Total: ${TOTAL_ASSISTED} assisted, ${TOTAL_MANUAL} manual**"

BODY_FOOTER="${GROUP_CHECKBOXES}"$'\n\n'"**Total: ${TOTAL_ASSISTED} assisted, ${TOTAL_MANUAL} manual**"$'\n\n'"---"$'\n\n'"Once all checkboxes above are checked, the release is ready to prepare for publication:"$'\n\n'"- [ ] **Release Ready** — Run \`pnpm release:prepare:vscode-extension\` to date-stamp the CHANGELOG, strip unreleased markers, and generate publishing instructions."

ISSUE_BODY="${ISSUE_BODY}${BODY_FOOTER}"

# --- Local mode: write to a markdown file ---
if [[ "$LOCAL_MODE" == true ]]; then
Expand All @@ -182,20 +187,12 @@ if [[ "$LOCAL_MODE" == true ]]; then
TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S")
LOCAL_FILE="$OUTPUT_DIR/qa-checklist-${VERSION}-${TIMESTAMP}.md"

TOTAL_ASSISTED=$(echo "$GROUPS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync(0,'utf8')).total_assisted))")
TOTAL_MANUAL=$(echo "$GROUPS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync(0,'utf8')).total_manual))")

{
echo "# QA Checklist — ${VERSION}"
echo ""
echo "Generated from \`$(basename "$YAML_FILE")\` on $(date -u +"%Y-%m-%d %H:%M:%S UTC")"
echo ""
echo "CI runs fully automated tests (\`test:release:automated\`). The checkboxes below cover assisted and manual tests only."
echo ""
echo "${GROUP_CHECKBOXES}"
echo ""
echo "**Total: ${TOTAL_ASSISTED} assisted, ${TOTAL_MANUAL} manual**"

echo "${ISSUE_BODY}"
} > "$LOCAL_FILE"

REPO_ROOT="$(git rev-parse --show-toplevel)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ QA_DIR="$PACKAGE_DIR/qa"

# --- Validation ---

if ! git -C "$REPO_ROOT" diff-index --quiet HEAD --; then
if [[ -n "$(git -C "$REPO_ROOT" status --porcelain)" ]]; then
echo -e "${RED}Error: working tree is dirty. Commit or stash changes first.${NC}" >&2
exit 1
fi
Expand Down
Loading
Loading