diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs index fefb63c96..6b66d2668 100644 --- a/get-shit-done/bin/lib/config.cjs +++ b/get-shit-done/bin/lib/config.cjs @@ -22,6 +22,7 @@ const VALID_CONFIG_KEYS = new Set([ 'workflow.discuss_mode', 'workflow.skip_discuss', 'workflow._auto_chain_active', + 'workflow.use_worktrees', 'git.branching_strategy', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template', 'planning.commit_docs', 'planning.search_gitignored', 'hooks.context_warnings', diff --git a/get-shit-done/references/planning-config.md b/get-shit-done/references/planning-config.md index f8276c761..20ee56f37 100644 --- a/get-shit-done/references/planning-config.md +++ b/get-shit-done/references/planning-config.md @@ -24,6 +24,7 @@ Configuration options for `.planning/` directory behavior. | `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy | | `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy | | `git.quick_branch_template` | `null` | Optional branch template for quick-task runs | +| `workflow.use_worktrees` | `true` | Whether executor agents run in isolated git worktrees. Set to `false` to disable worktrees — agents execute sequentially on the main working tree instead. Recommended for solo developers or when worktree merges cause issues. | diff --git a/get-shit-done/workflows/diagnose-issues.md b/get-shit-done/workflows/diagnose-issues.md index ea53fe26e..47f3e09d6 100644 --- a/get-shit-done/workflows/diagnose-issues.md +++ b/get-shit-done/workflows/diagnose-issues.md @@ -55,6 +55,12 @@ gaps = [ +**Read worktree config:** + +```bash +USE_WORKTREES=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.use_worktrees 2>/dev/null || echo "true") +``` + **Report diagnosis plan to user:** ``` @@ -92,7 +98,7 @@ For each gap, fill the debug-subagent-prompt template and spawn: Task( prompt=filled_debug_subagent_prompt + "\n\n\n- {phase_dir}/{phase_num}-UAT.md\n- .planning/STATE.md\n\n${AGENT_SKILLS_DEBUGGER}", subagent_type="gsd-debugger", - isolation="worktree", + ${USE_WORKTREES !== "false" ? 'isolation="worktree",' : ''} description="Debug: {truth_short}" ) ``` diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md index d770f6c80..d763325e9 100644 --- a/get-shit-done/workflows/execute-phase.md +++ b/get-shit-done/workflows/execute-phase.md @@ -68,6 +68,14 @@ AGENT_SKILLS=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" agent-skills Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`. +Read worktree config: + +```bash +USE_WORKTREES=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.use_worktrees 2>/dev/null || echo "true") +``` + +When `USE_WORKTREES` is `false`, all executor agents run without `isolation="worktree"` — they execute sequentially on the main working tree instead of in parallel worktrees. + **If `phase_found` is false:** Error — phase directory not found. **If `plan_count` is 0:** Error — no plans found in phase. **If `state_exists` is false but `.planning/` exists:** Offer reconstruct or continue. @@ -223,6 +231,8 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT For 200k models, this keeps orchestrator context lean (~10-15%). For 1M+ models (Opus 4.6, Sonnet 4.6), richer context can be passed directly. + **Worktree mode** (`USE_WORKTREES` is not `false`): + ``` Task( subagent_type="gsd-executor", @@ -279,6 +289,19 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT ) ``` + **Sequential mode** (`USE_WORKTREES` is `false`): + + Omit `isolation="worktree"` from the Task call. Replace the `` block with: + + ``` + + You are running as a SEQUENTIAL executor agent on the main working tree. + Use normal git commits (with hooks). Do NOT use --no-verify. + + ``` + + When worktrees are disabled, execute plans **one at a time within each wave** (sequential) regardless of the `PARALLELIZATION` setting — multiple agents writing to the same working tree concurrently would cause conflicts. + 3. **Wait for all agents in wave to complete.** **Completion signal fallback (Copilot and runtimes where Task() may not return):** diff --git a/get-shit-done/workflows/execute-plan.md b/get-shit-done/workflows/execute-plan.md index cbbf71330..920e92ab9 100644 --- a/get-shit-done/workflows/execute-plan.md +++ b/get-shit-done/workflows/execute-plan.md @@ -72,7 +72,7 @@ grep -n "type=\"checkpoint" .planning/phases/XX-name/{phase}-{plan}-PLAN.md | Verify-only | B (segmented) | Segments between checkpoints. After none/human-verify → SUBAGENT. After decision/human-action → MAIN | | Decision | C (main) | Execute entirely in main context | -**Pattern A:** init_agent_tracking → spawn Task(subagent_type="gsd-executor", model=executor_model, isolation="worktree") with prompt: execute plan at [path], autonomous, all tasks + SUMMARY + commit, follow deviation/auth rules, report: plan name, tasks, SUMMARY path, commit hash → track agent_id → wait → update tracking → report. +**Pattern A:** init_agent_tracking → spawn Task(subagent_type="gsd-executor", model=executor_model) with prompt: execute plan at [path], autonomous, all tasks + SUMMARY + commit, follow deviation/auth rules, report: plan name, tasks, SUMMARY path, commit hash → track agent_id → wait → update tracking → report. **Include `isolation="worktree"` only if `workflow.use_worktrees` is not `false`** (read via `config-get workflow.use_worktrees`). **Pattern B:** Execute segment-by-segment. Autonomous segments: spawn subagent for assigned tasks only (no SUMMARY/commit). Checkpoints: main context. After all segments: aggregate, create SUMMARY, commit. See segment_execution. diff --git a/get-shit-done/workflows/quick.md b/get-shit-done/workflows/quick.md index eafe92447..0c0bbf0cf 100644 --- a/get-shit-done/workflows/quick.md +++ b/get-shit-done/workflows/quick.md @@ -138,6 +138,10 @@ AGENT_SKILLS_VERIFIER=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" age Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`. +```bash +USE_WORKTREES=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.use_worktrees 2>/dev/null || echo "true") +``` + **If `roadmap_exists` is false:** Error — Quick mode requires an active project with ROADMAP.md. Run `/gsd:new-project` first. Quick tasks can run mid-phase - validation only checks ROADMAP.md exists, not phase status. @@ -571,7 +575,7 @@ ${AGENT_SKILLS_EXECUTOR} ", subagent_type="gsd-executor", model="{executor_model}", - isolation="worktree", + ${USE_WORKTREES !== "false" ? 'isolation="worktree",' : ''} description="Execute: ${DESCRIPTION}" ) ``` diff --git a/tests/config.test.cjs b/tests/config.test.cjs index 31a2da656..6280df25e 100644 --- a/tests/config.test.cjs +++ b/tests/config.test.cjs @@ -232,6 +232,16 @@ describe('config-set command', () => { assert.strictEqual(config.workflow.text_mode, true); }); + test('sets workflow.use_worktrees to disable worktree isolation', () => { + writeConfig(tmpDir, {}); + + const result = runGsdTools('config-set workflow.use_worktrees false', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const config = readConfig(tmpDir); + assert.strictEqual(config.workflow.use_worktrees, false); + }); + test('errors when no key path provided', () => { const result = runGsdTools('config-set', tmpDir); assert.strictEqual(result.success, false); @@ -765,3 +775,58 @@ describe('config-set workflow.skip_discuss', () => { assert.strictEqual(output, true); }); }); + +// ─── config-set/config-get workflow.use_worktrees ──────────────────────────── + +describe('config-set/config-get workflow.use_worktrees', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + runGsdTools('config-ensure-section', tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir }); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('config-get workflow.use_worktrees returns false after setting to false', () => { + runGsdTools('config-set workflow.use_worktrees false', tmpDir); + const result = runGsdTools('config-get workflow.use_worktrees', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output, false); + }); + + test('config-get workflow.use_worktrees errors when not set (default config)', () => { + // config-ensure-section does NOT include use_worktrees in hardcoded defaults, + // so config-get should error with "Key not found". This is the expected behavior + // that workflows rely on: the shell fallback `|| echo "true"` provides the default. + const result = runGsdTools('config-get workflow.use_worktrees', tmpDir); + assert.strictEqual(result.success, false); + assert.ok( + result.error.includes('Key not found'), + `Expected "Key not found" in error: ${result.error}` + ); + }); + + test('config-get workflow.use_worktrees returns true after setting to true', () => { + runGsdTools('config-set workflow.use_worktrees true', tmpDir); + const result = runGsdTools('config-get workflow.use_worktrees', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output, true); + }); + + test('use_worktrees can be toggled back and forth', () => { + runGsdTools('config-set workflow.use_worktrees false', tmpDir); + runGsdTools('config-set workflow.use_worktrees true', tmpDir); + const result = runGsdTools('config-get workflow.use_worktrees', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output, true); + }); +}); diff --git a/tests/execute-phase-wave.test.cjs b/tests/execute-phase-wave.test.cjs index 871e9904c..345bd50cc 100644 --- a/tests/execute-phase-wave.test.cjs +++ b/tests/execute-phase-wave.test.cjs @@ -105,4 +105,80 @@ describe('execute-phase docs: user-facing wave flag', () => { 'help.md should include wave-filter usage' ); }); + + test('workflow supports use_worktrees config toggle', () => { + const content = fs.readFileSync(WORKFLOW_PATH, 'utf-8'); + assert.ok( + content.includes('USE_WORKTREES'), + 'workflow should reference USE_WORKTREES variable' + ); + assert.ok( + content.includes('config-get workflow.use_worktrees'), + 'workflow should read use_worktrees from config' + ); + assert.ok( + content.includes('Sequential mode'), + 'workflow should document sequential mode when worktrees disabled' + ); + }); +}); + +describe('use_worktrees config: cross-workflow structural coverage', () => { + const QUICK_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'quick.md'); + const DIAGNOSE_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'diagnose-issues.md'); + const EXECUTE_PLAN_PATH = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'execute-plan.md'); + const PLANNING_CONFIG_PATH = path.join(__dirname, '..', 'get-shit-done', 'references', 'planning-config.md'); + const CONFIG_CJS_PATH = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'config.cjs'); + + test('quick workflow reads USE_WORKTREES from config', () => { + const content = fs.readFileSync(QUICK_PATH, 'utf-8'); + assert.ok( + content.includes('config-get workflow.use_worktrees'), + 'quick.md should read use_worktrees from config' + ); + assert.ok( + content.includes('USE_WORKTREES'), + 'quick.md should reference USE_WORKTREES variable' + ); + }); + + test('diagnose-issues workflow reads USE_WORKTREES from config', () => { + const content = fs.readFileSync(DIAGNOSE_PATH, 'utf-8'); + assert.ok( + content.includes('config-get workflow.use_worktrees'), + 'diagnose-issues.md should read use_worktrees from config' + ); + assert.ok( + content.includes('USE_WORKTREES'), + 'diagnose-issues.md should reference USE_WORKTREES variable' + ); + }); + + test('execute-plan workflow references use_worktrees config', () => { + const content = fs.readFileSync(EXECUTE_PLAN_PATH, 'utf-8'); + assert.ok( + content.includes('workflow.use_worktrees'), + 'execute-plan.md should reference workflow.use_worktrees' + ); + }); + + test('planning-config reference documents use_worktrees', () => { + const content = fs.readFileSync(PLANNING_CONFIG_PATH, 'utf-8'); + assert.ok( + content.includes('workflow.use_worktrees'), + 'planning-config.md should document workflow.use_worktrees' + ); + assert.ok( + content.includes('worktree'), + 'planning-config.md should describe worktree behavior' + ); + }); + + test('config.cjs includes workflow.use_worktrees in VALID_CONFIG_KEYS', () => { + const content = fs.readFileSync(CONFIG_CJS_PATH, 'utf-8'); + assert.ok( + content.includes("'workflow.use_worktrees'"), + 'config.cjs VALID_CONFIG_KEYS should include workflow.use_worktrees' + ); + }); });