diff --git a/.claude/commands/release-patch.md b/.claude/commands/release-patch.md index 392692d55..c9c4233dd 100644 --- a/.claude/commands/release-patch.md +++ b/.claude/commands/release-patch.md @@ -13,13 +13,36 @@ $ARGUMENTS 3. Increment the **patch** component: `X.Y.Z` -> `X.Y.(Z+1)`. 4. Store the new version string (without `v` prefix) for later steps. -## Step 2 -- Create a release branch +## Step 2 -- Create a release branch in a worktree + +The main checkout MUST stay on `main` -- the release branch lives in a +dedicated worktree. All remaining steps (changelog edits, commit, +push, PR) run from that worktree. ```bash -git checkout main && git pull -git checkout -b release/vX.Y.Z +RELEASE_MAIN="$(git rev-parse --show-toplevel)" +git -C "$RELEASE_MAIN" fetch origin main +RELEASE_MAIN_BRANCH="$(git -C "$RELEASE_MAIN" branch --show-current)" +if [ "$RELEASE_MAIN_BRANCH" = "main" ]; then + git -C "$RELEASE_MAIN" pull --ff-only origin main +fi +git -C "$RELEASE_MAIN" worktree add \ + ".claude/worktrees/release-vX.Y.Z" -b "release/vX.Y.Z" origin/main +RELEASE_WT="$RELEASE_MAIN/.claude/worktrees/release-vX.Y.Z" +cd "$RELEASE_WT" ``` +Verify isolation -- assert ALL of the following before continuing: +- `$(pwd)` equals `$RELEASE_WT`. +- `git branch --show-current` is `release/vX.Y.Z`. +- `git -C "$RELEASE_MAIN" branch --show-current` is still `main` + (the main checkout's branch did NOT change). + +For every remaining step, use paths anchored at `$RELEASE_WT` for +Edit / Read / Write tool calls -- do NOT edit files under +`$RELEASE_MAIN`. Re-check `pwd` and the current branch before +every `git commit`. + ## Step 3 -- Update CHANGELOG.md 1. Run `git log --pretty=format:"- %s" ..HEAD` to collect @@ -61,14 +84,25 @@ gh pr merge --merge --delete-branch ## Step 7 -- Tag the release +Tagging happens from the main checkout (NOT the release worktree), +because the merged commit lives on `main`: + ```bash -git checkout main && git pull +cd "$RELEASE_MAIN" +git checkout main +git pull --ff-only origin main git tag -a vX.Y.Z -m "Version X.Y.Z" git push origin vX.Y.Z ``` Do **not** sign the tag (`-s` flag omitted). +After tagging, remove the release worktree -- the branch was already +deleted by `gh pr merge --delete-branch`: +```bash +git -C "$RELEASE_MAIN" worktree remove "$RELEASE_WT" --force +``` + ## Step 8 -- Create a GitHub release ```bash diff --git a/.claude/commands/review-pr.md b/.claude/commands/review-pr.md index 3cd092b69..b0168f03d 100644 --- a/.claude/commands/review-pr.md +++ b/.claude/commands/review-pr.md @@ -20,8 +20,71 @@ NumPy, Dask, CuPy, and Numba. The prompt is: $ARGUMENTS ```bash gh pr diff ``` -5. Read every changed file in full (not just the diff) to understand surrounding - context. + +## Step 1.5 -- Materialize the PR in a worktree + +The user's main checkout MUST stay on `main`. Read the PR's files +from a worktree on the PR's head branch so the review sees the +actual PR state, not whatever happens to be checked out in the +main directory. + +First, detect whether we are already inside a worktree on the PR's +head branch (this is the common case when `/review-pr` is invoked +from `/rockout` Step 9): + +```bash +REVIEW_PR_NUM= +REVIEW_HEAD_BRANCH="$(gh pr view "$REVIEW_PR_NUM" --json headRefName -q .headRefName)" +REVIEW_CUR_BRANCH="$(git branch --show-current)" +REVIEW_CUR_TOP="$(git rev-parse --show-toplevel)" +``` + +- If `$REVIEW_CUR_BRANCH` equals `$REVIEW_HEAD_BRANCH` AND + `$REVIEW_CUR_TOP` contains the segment `.claude/worktrees/`, + we are already in the right worktree. Set + `REVIEW_WT="$REVIEW_CUR_TOP"` and skip to step 4 below. Do NOT + create another worktree -- a second `git worktree add` on the + same branch will fail. + +- Otherwise, create a dedicated review worktree: + + 1. From any path, resolve the main checkout (use `--git-common-dir` + to find the shared repo even if we are inside another worktree): + ```bash + REVIEW_MAIN="$(git rev-parse --path-format=absolute --git-common-dir)" + REVIEW_MAIN="${REVIEW_MAIN%/.git}" + git -C "$REVIEW_MAIN" fetch origin "pull/$REVIEW_PR_NUM/head:pr-$REVIEW_PR_NUM-review" + git -C "$REVIEW_MAIN" worktree add \ + ".claude/worktrees/pr-$REVIEW_PR_NUM-review" "pr-$REVIEW_PR_NUM-review" + REVIEW_WT="$REVIEW_MAIN/.claude/worktrees/pr-$REVIEW_PR_NUM-review" + REVIEW_WT_CREATED=1 + ``` + + 2. Verify isolation -- assert ALL of the following. If any fails, + STOP and report it: + - `$REVIEW_WT` exists and is NOT equal to `$REVIEW_MAIN`. + - `git -C "$REVIEW_WT" branch --show-current` is + `pr-$REVIEW_PR_NUM-review`. + - `git -C "$REVIEW_MAIN" branch --show-current` is still + `main` (or `master`). + +3. `cd "$REVIEW_WT"` so subsequent reads happen inside the worktree. + +4. Read every changed file in full (not just the diff) from + `$REVIEW_WT`. Use paths anchored at `$REVIEW_WT` for all Read + tool calls -- never read the same file from the main checkout; + that path reflects `main` and will mislead the review. + +5. The review is read-only -- do NOT make commits in this worktree. + When the review is done (after Step 8), clean up only if Step + 1.5 created the worktree: + ```bash + if [ "${REVIEW_WT_CREATED:-0}" = "1" ]; then + cd "$REVIEW_MAIN" + git worktree remove ".claude/worktrees/pr-$REVIEW_PR_NUM-review" + git branch -D "pr-$REVIEW_PR_NUM-review" + fi + ``` ## Step 2 -- Correctness review diff --git a/.claude/commands/rockout.md b/.claude/commands/rockout.md index 3f672d2ef..8e627f7f0 100644 --- a/.claude/commands/rockout.md +++ b/.claude/commands/rockout.md @@ -23,13 +23,52 @@ through all ten steps below. The prompt is: $ARGUMENTS 5. Create the issue with `gh issue create` using the drafted title, body, and labels. 6. Capture the new issue number for later steps. -## Step 2 -- Create a Git Worktree +## Step 2 -- Create a Git Worktree (Isolation Contract) -1. Create a new branch and worktree using the issue number: - ``` +The user's main checkout MUST remain on `main` for the entire rockout +run. All implementation, tests, docs, commits, and the PR push happen +inside a dedicated worktree on a feature branch. If you ever commit +from the main checkout, you have breached this contract. + +1. From the main checkout, create a new branch and worktree using the + issue number: + ```bash git worktree add .claude/worktrees/issue- -b issue- ``` -2. Switch the working directory to the new worktree for all remaining steps. + +2. Capture the worktree path and verify isolation before doing + anything else. Run this exact block and check every assertion: + ```bash + ROCKOUT_WT="$(git -C .claude/worktrees/issue- rev-parse --show-toplevel)" + ROCKOUT_MAIN="$(git rev-parse --show-toplevel)" + ROCKOUT_BRANCH="$(git -C "$ROCKOUT_WT" branch --show-current)" + echo "wt=$ROCKOUT_WT main=$ROCKOUT_MAIN branch=$ROCKOUT_BRANCH" + ``` + + Assert ALL of the following. If any fails, STOP, do NOT touch + files or make commits, and report the failure to the user: + - `$ROCKOUT_WT` ends in `.claude/worktrees/issue-`. + - `$ROCKOUT_WT` is NOT equal to `$ROCKOUT_MAIN` (you are not in + the main checkout). + - `$ROCKOUT_BRANCH` is `issue-` (not `main`, not `master`). + - `git -C "$ROCKOUT_MAIN" branch --show-current` is still `main` + (or `master`) -- the main checkout's branch did NOT change. + +3. `cd "$ROCKOUT_WT"` so subsequent Bash calls run inside the + worktree by default. + +4. For every Read / Edit / Write tool call from this point on, use + paths anchored at `$ROCKOUT_WT` (or worktree-relative paths after + the `cd`). NEVER pass an absolute path that resolves to + `$ROCKOUT_MAIN/...` -- that bypasses the worktree and writes into + the user's main checkout. + +5. Before EVERY `git commit` you run (in any step below), re-check: + ```bash + [ "$(pwd)" = "$ROCKOUT_WT" ] || { echo "CWD drift"; exit 1; } + [ "$(git branch --show-current)" = "issue-" ] || { echo "branch drift"; exit 1; } + ``` + A failed re-check is an isolation breach. Stop and report it. ## Step 3 -- Implement the Change @@ -151,7 +190,11 @@ Address every Blocker, then work through Suggestions and Nits in that order. ## General Rules -- Work entirely within the worktree created in Step 2. +- Work entirely within the worktree created in Step 2. The main + checkout MUST stay on `main` for the duration of the run -- never + `git checkout`, `git switch`, `git commit`, `git add`, or edit a + file inside `$ROCKOUT_MAIN`. Run the Step 2.5 pre-commit re-check + before every commit. - Commit progress after each major step with a clear commit message referencing the issue number (e.g. `Add flood velocity function (#42)`). - Run `/humanizer` on any text destined for GitHub (issue body, PR description,