From 17bb9f8a252fb437259b25ee62e3849cc7da8c9a Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 10 Apr 2026 12:07:13 -0400 Subject: [PATCH] fix(worktree): use reset --hard in worktree_branch_check to correctly set base (#2015) (#2028) The worktree_branch_check in execute-phase.md and quick.md used git reset --soft as the fallback when EnterWorktree created a branch from main/master instead of the current feature branch HEAD. --soft moves the HEAD pointer but leaves working tree files from main unchanged, so the executor worked against stale code and produced commits containing the entire feature branch diff as deletions. Fix: replace git reset --soft with git reset --hard in both workflow files. --hard resets both the HEAD pointer and the working tree to the expected base commit. It is safe in a fresh worktree that has no user changes. Adds 4 regression tests (2 per workflow) verifying that the check uses --hard and does not contain --soft. Co-authored-by: Claude Sonnet 4.6 --- get-shit-done/workflows/execute-phase.md | 3 + tests/bug-2015-worktree-base-branch.test.cjs | 75 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/bug-2015-worktree-base-branch.test.cjs diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md index e1416917f..0605361ac 100644 --- a/get-shit-done/workflows/execute-phase.md +++ b/get-shit-done/workflows/execute-phase.md @@ -362,6 +362,9 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT fi ``` + `reset --hard` is safe here because this is a fresh worktree with no user changes. It + resets both the HEAD pointer AND the working tree to the correct base commit (#2015). + If `ACTUAL_BASE` == `{EXPECTED_BASE}`: the branch base is correct, proceed immediately. This check fixes a known issue where `EnterWorktree` creates branches from diff --git a/tests/bug-2015-worktree-base-branch.test.cjs b/tests/bug-2015-worktree-base-branch.test.cjs new file mode 100644 index 000000000..c122fba12 --- /dev/null +++ b/tests/bug-2015-worktree-base-branch.test.cjs @@ -0,0 +1,75 @@ +/** + * Regression test for #2015: worktree executor creates branch from master + * instead of the current feature branch HEAD. + * + * The worktree_branch_check in execute-phase.md and quick.md used + * `git reset --soft {EXPECTED_BASE}` as the recovery action when the + * worktree was created from the wrong base. `reset --soft` moves the HEAD + * pointer but leaves the working tree files from main/master unchanged — + * the executor then works against stale code and its commits contain an + * enormous diff (the entire feature branch) as deletions. + * + * Fix: use `git reset --hard {EXPECTED_BASE}` in the worktree_branch_check. + * In a fresh worktree with no user changes, --hard is safe and correct. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const EXECUTE_PHASE_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'execute-phase.md'); +const QUICK_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'quick.md'); + +describe('worktree_branch_check must use reset --hard not reset --soft (#2015)', () => { + + test('execute-phase.md worktree_branch_check does not use reset --soft', () => { + const content = fs.readFileSync(EXECUTE_PHASE_PATH, 'utf-8'); + + // Extract the worktree_branch_check block + const blockMatch = content.match(/([\s\S]*?)<\/worktree_branch_check>/); + assert.ok(blockMatch, 'execute-phase.md must contain a block'); + + const block = blockMatch[1]; + assert.ok( + !block.includes('reset --soft'), + 'worktree_branch_check must not use reset --soft (leaves working tree files unchanged). Use reset --hard instead.' + ); + }); + + test('execute-phase.md worktree_branch_check uses reset --hard for base correction', () => { + const content = fs.readFileSync(EXECUTE_PHASE_PATH, 'utf-8'); + const blockMatch = content.match(/([\s\S]*?)<\/worktree_branch_check>/); + assert.ok(blockMatch, 'execute-phase.md must contain a block'); + + const block = blockMatch[1]; + assert.ok( + block.includes('reset --hard'), + 'worktree_branch_check must use reset --hard to correctly reset both HEAD and working tree to the expected base' + ); + }); + + test('quick.md worktree_branch_check does not use reset --soft', () => { + const content = fs.readFileSync(QUICK_PATH, 'utf-8'); + const blockMatch = content.match(/([\s\S]*?)<\/worktree_branch_check>/); + assert.ok(blockMatch, 'quick.md must contain a block'); + + const block = blockMatch[1]; + assert.ok( + !block.includes('reset --soft'), + 'quick.md worktree_branch_check must not use reset --soft. Use reset --hard instead.' + ); + }); + + test('quick.md worktree_branch_check uses reset --hard for base correction', () => { + const content = fs.readFileSync(QUICK_PATH, 'utf-8'); + const blockMatch = content.match(/([\s\S]*?)<\/worktree_branch_check>/); + assert.ok(blockMatch, 'quick.md must contain a block'); + + const block = blockMatch[1]; + assert.ok( + block.includes('reset --hard'), + 'quick.md worktree_branch_check must use reset --hard to correctly reset both HEAD and working tree' + ); + }); +});