fix: add idempotency guards to /ship Steps 4, 7, 8 (#649)

If git push succeeds but gh pr create fails, re-running /ship would
double-bump VERSION and duplicate CHANGELOG entries. Now:
- Step 4: check if VERSION already differs from base branch
- Step 7: fetch only the specific branch, skip push if already up to date
- Step 8: if PR exists, update body via gh pr edit instead of creating duplicate

No CHANGELOG guard needed — Step 5 is already idempotent by design
("replace existing entries with one unified entry").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-30 19:38:52 -07:00
parent b2b380bfcc
commit d20df88470
2 changed files with 74 additions and 4 deletions
+37 -2
View File
@@ -1755,6 +1755,17 @@ already knows. A good test: would this insight save time in a future session? If
## Step 4: Version bump (auto-decide)
**Idempotency check:** Before bumping, compare VERSION against the base branch.
```bash
BASE_VERSION=$(git show origin/<base>:VERSION 2>/dev/null || echo "0.0.0.0")
CURRENT_VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0.0")
echo "BASE: $BASE_VERSION HEAD: $CURRENT_VERSION"
if [ "$CURRENT_VERSION" != "$BASE_VERSION" ]; then echo "ALREADY_BUMPED"; fi
```
If output shows `ALREADY_BUMPED`, VERSION was already bumped on this branch (prior `/ship` run). Skip the rest of Step 4 and use the current VERSION. Otherwise proceed with the bump.
1. Read the current `VERSION` file (4-digit format: `MAJOR.MINOR.PATCH.MICRO`)
2. **Auto-decide the bump level based on the diff:**
@@ -1934,7 +1945,17 @@ Claiming work is complete without verification is dishonesty, not efficiency.
## Step 7: Push
Push to the remote with upstream tracking:
**Idempotency check:** Check if the branch is already pushed and up to date.
```bash
git fetch origin <branch-name> 2>/dev/null
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/<branch-name> 2>/dev/null || echo "none")
echo "LOCAL: $LOCAL REMOTE: $REMOTE"
[ "$LOCAL" = "$REMOTE" ] && echo "ALREADY_PUSHED" || echo "PUSH_NEEDED"
```
If `ALREADY_PUSHED`, skip the push. Otherwise push with upstream tracking:
```bash
git push -u origin <branch-name>
@@ -1944,7 +1965,21 @@ git push -u origin <branch-name>
## Step 8: Create PR/MR
Create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0.
**Idempotency check:** Check if a PR/MR already exists for this branch.
**If GitHub:**
```bash
gh pr view --json url,number -q '"PR #\(.number): \(.url)"' 2>/dev/null || echo "NO_PR"
```
**If GitLab:**
```bash
glab mr view -F json 2>/dev/null && echo "MR_EXISTS" || echo "NO_MR"
```
If a PR/MR already exists: **update** the PR body with the latest test results, coverage, and review findings using `gh pr edit --body "..."` (GitHub) or `glab mr update -d "..."` (GitLab). Print the existing URL and continue to Step 8.5.
If no PR/MR exists: create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0.
The PR/MR body should contain these sections:
+37 -2
View File
@@ -328,6 +328,17 @@ For each classified comment:
## Step 4: Version bump (auto-decide)
**Idempotency check:** Before bumping, compare VERSION against the base branch.
```bash
BASE_VERSION=$(git show origin/<base>:VERSION 2>/dev/null || echo "0.0.0.0")
CURRENT_VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0.0")
echo "BASE: $BASE_VERSION HEAD: $CURRENT_VERSION"
if [ "$CURRENT_VERSION" != "$BASE_VERSION" ]; then echo "ALREADY_BUMPED"; fi
```
If output shows `ALREADY_BUMPED`, VERSION was already bumped on this branch (prior `/ship` run). Skip the rest of Step 4 and use the current VERSION. Otherwise proceed with the bump.
1. Read the current `VERSION` file (4-digit format: `MAJOR.MINOR.PATCH.MICRO`)
2. **Auto-decide the bump level based on the diff:**
@@ -467,7 +478,17 @@ Claiming work is complete without verification is dishonesty, not efficiency.
## Step 7: Push
Push to the remote with upstream tracking:
**Idempotency check:** Check if the branch is already pushed and up to date.
```bash
git fetch origin <branch-name> 2>/dev/null
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/<branch-name> 2>/dev/null || echo "none")
echo "LOCAL: $LOCAL REMOTE: $REMOTE"
[ "$LOCAL" = "$REMOTE" ] && echo "ALREADY_PUSHED" || echo "PUSH_NEEDED"
```
If `ALREADY_PUSHED`, skip the push. Otherwise push with upstream tracking:
```bash
git push -u origin <branch-name>
@@ -477,7 +498,21 @@ git push -u origin <branch-name>
## Step 8: Create PR/MR
Create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0.
**Idempotency check:** Check if a PR/MR already exists for this branch.
**If GitHub:**
```bash
gh pr view --json url,number -q '"PR #\(.number): \(.url)"' 2>/dev/null || echo "NO_PR"
```
**If GitLab:**
```bash
glab mr view -F json 2>/dev/null && echo "MR_EXISTS" || echo "NO_MR"
```
If a PR/MR already exists: **update** the PR body with the latest test results, coverage, and review findings using `gh pr edit --body "..."` (GitHub) or `glab mr update -d "..."` (GitLab). Print the existing URL and continue to Step 8.5.
If no PR/MR exists: create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0.
The PR/MR body should contain these sections: