Merge pull request #1456 from Tibsfox/fix/worktree-disable-config-1451
feat(config): add workflow.use_worktrees toggle to disable worktree isolation
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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. |
|
||||
</config_schema>
|
||||
|
||||
<commit_docs_behavior>
|
||||
|
||||
@@ -55,6 +55,12 @@ gaps = [
|
||||
</step>
|
||||
|
||||
<step name="report_plan">
|
||||
**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<files_to_read>\n- {phase_dir}/{phase_num}-UAT.md\n- .planning/STATE.md\n</files_to_read>\n${AGENT_SKILLS_DEBUGGER}",
|
||||
subagent_type="gsd-debugger",
|
||||
isolation="worktree",
|
||||
${USE_WORKTREES !== "false" ? 'isolation="worktree",' : ''}
|
||||
description="Debug: {truth_short}"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -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 `<parallel_execution>` block with:
|
||||
|
||||
```
|
||||
<sequential_execution>
|
||||
You are running as a SEQUENTIAL executor agent on the main working tree.
|
||||
Use normal git commits (with hooks). Do NOT use --no-verify.
|
||||
</sequential_execution>
|
||||
```
|
||||
|
||||
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):**
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
```
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user