Skip to content

Commit bab9b40

Browse files
marcusgollclaude
andcommitted
feat: add git worktree integration for isolated feature/epic development
- Create worktree-context.sh with root orchestration utilities - Add worktree awareness to worker.md agent (cd-first pattern) - Update feature.md, implement-epic.md, ship.md for worktree support - Enable worktrees by default (auto_create: true) - Add load_worktree_preferences() to preference loader - Fix PowerShell/bash syntax mixing (2>$null -> 2>/dev/null) Worktrees reduce merge conflicts ~90% by isolating git state per feature/epic. Root orchestrator handles merges/PRs/cleanup while Task() agents work in isolation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 2fca67c commit bab9b40

File tree

13 files changed

+774
-34
lines changed

13 files changed

+774
-34
lines changed

.claude/agents/domain/worker.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,56 @@ You are a disciplined engineer who:
3737
You will receive:
3838
1. **feature_dir**: Path to feature directory (e.g., `specs/001-auth`)
3939
2. **domain_memory_path**: Path to domain-memory.yaml
40+
3. **worktree_path** (optional): Path to git worktree if operating in worktree mode
4041

4142
That's it. Everything else comes from reading disk.
4243
</inputs>
4344

45+
<worktree_awareness>
46+
## Worktree Operation Mode
47+
48+
When spawned with a `worktree_path` in your prompt, you are operating in an **isolated git worktree**.
49+
50+
### Step 0: Switch to Worktree (BEFORE Boot-up Ritual)
51+
52+
If `worktree_path` is provided, execute this FIRST:
53+
54+
```bash
55+
cd "${worktree_path}"
56+
```
57+
58+
Then verify you're in the correct location:
59+
60+
```bash
61+
# Should output the worktree path
62+
git rev-parse --show-toplevel
63+
```
64+
65+
### Worktree Rules
66+
67+
1. **All paths are relative to worktree root**
68+
- `specs/001-auth/` is at `${worktree_path}/specs/001-auth/`
69+
- Read/Write operations use worktree as base
70+
71+
2. **Git commits stay LOCAL to worktree branch**
72+
- Commit freely within your worktree
73+
- The root orchestrator handles merges to main
74+
75+
3. **Shared memory is symlinked**
76+
- `.spec-flow/memory/` points to root repo's memory
77+
- Changes you make to memory are visible to other worktrees
78+
- `domain-memory.yaml` is worktree-local (in feature/epic dir)
79+
80+
4. **Do NOT merge or push**
81+
- Your commits stay on the worktree branch
82+
- Root orchestrator merges when ready
83+
- This prevents merge conflicts
84+
85+
5. **EXIT when done**
86+
- Same as non-worktree mode: complete ONE feature, then EXIT
87+
- Orchestrator will handle worktree cleanup
88+
</worktree_awareness>
89+
4490
<boot_up_ritual>
4591
**YOU MUST FOLLOW THIS EXACT SEQUENCE:**
4692

.claude/commands/core/feature.md

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ Active workflow state (if any):
4040

4141
Deployment model detection:
4242
!`git branch -r | grep -q "staging" && echo "staging-prod" || (git remote -v | grep -q "origin" && echo "direct-prod" || echo "local-only")`
43+
44+
Worktree context:
45+
!`bash .spec-flow/scripts/bash/worktree-context.sh info 2>/dev/null || echo '{"is_worktree": false}'`
46+
47+
Worktree preference:
48+
!`bash .spec-flow/scripts/utils/load-preferences.sh --key "worktrees.auto_create" --default "true" 2>/dev/null || echo "true"`
4349
</context>
4450

4551
<process>
@@ -67,6 +73,50 @@ echo "Feature directory: $FEATURE_DIR"
6773
cat "$FEATURE_DIR/state.yaml"
6874
```
6975

76+
### Step 1.1.5: Create Worktree (If Preference Enabled)
77+
78+
Check worktree context and preference:
79+
80+
```bash
81+
# Check if already in a worktree
82+
IS_WORKTREE=$(bash .spec-flow/scripts/bash/worktree-context.sh in-worktree && echo "true" || echo "false")
83+
84+
# Check worktree auto-create preference
85+
WORKTREE_AUTO=$(bash .spec-flow/scripts/utils/load-preferences.sh --key "worktrees.auto_create" --default "true" 2>/dev/null)
86+
87+
echo "In worktree: $IS_WORKTREE"
88+
echo "Auto-create worktree: $WORKTREE_AUTO"
89+
```
90+
91+
**If NOT in worktree AND auto_create is true:**
92+
93+
```bash
94+
if [ "$IS_WORKTREE" = "false" ] && [ "$WORKTREE_AUTO" = "true" ]; then
95+
# Get feature slug and branch
96+
FEATURE_SLUG=$(yq eval '.slug' "$FEATURE_DIR/state.yaml")
97+
FEATURE_BRANCH="feature/$FEATURE_SLUG"
98+
99+
# Create worktree
100+
WORKTREE_PATH=$(bash .spec-flow/scripts/bash/worktree-context.sh create "feature" "$FEATURE_SLUG" "$FEATURE_BRANCH")
101+
102+
if [ -n "$WORKTREE_PATH" ]; then
103+
# Update state.yaml with worktree info
104+
yq eval ".git.worktree_enabled = true" -i "$FEATURE_DIR/state.yaml"
105+
yq eval ".git.worktree_path = \"$WORKTREE_PATH\"" -i "$FEATURE_DIR/state.yaml"
106+
echo "Created worktree at: $WORKTREE_PATH"
107+
fi
108+
fi
109+
```
110+
111+
**Read worktree path for Task() agents:**
112+
113+
```bash
114+
WORKTREE_PATH=$(yq eval '.git.worktree_path // ""' "$FEATURE_DIR/state.yaml")
115+
WORKTREE_ENABLED=$(yq eval '.git.worktree_enabled // false' "$FEATURE_DIR/state.yaml")
116+
echo "Worktree enabled: $WORKTREE_ENABLED"
117+
echo "Worktree path: $WORKTREE_PATH"
118+
```
119+
70120
### Step 1.2: Initialize Interaction State
71121

72122
```bash
@@ -262,7 +312,13 @@ Task tool call:
262312

263313
When current phase is "implement":
264314

265-
1. Read domain-memory.yaml to find next incomplete feature
315+
1. Read worktree and domain-memory state:
316+
```bash
317+
FEATURE_DIR=$(ls -td specs/[0-9]*-* 2>/dev/null | head -1)
318+
WORKTREE_PATH=$(yq eval '.git.worktree_path // ""' "$FEATURE_DIR/state.yaml")
319+
WORKTREE_ENABLED=$(yq eval '.git.worktree_enabled // false' "$FEATURE_DIR/state.yaml")
320+
```
321+
266322
2. Spawn a worker agent for ONE feature only:
267323

268324
```
@@ -274,12 +330,27 @@ Task tool call:
274330
275331
Feature directory: [FEATURE_DIR]
276332
333+
${WORKTREE_ENABLED == "true" ? "
334+
**WORKTREE CONTEXT**
335+
Path: $WORKTREE_PATH
336+
337+
CRITICAL: Execute this FIRST before any other commands:
338+
```bash
339+
cd \"$WORKTREE_PATH\"
340+
```
341+
342+
All paths are relative to this worktree.
343+
Git commits stay local to worktree branch.
344+
Do NOT merge or push - orchestrator handles that.
345+
" : ""}
346+
277347
Instructions:
278-
1. Read domain-memory.yaml
279-
2. Find the first feature with status "pending" or "in_progress"
280-
3. Implement ONLY that one feature using TDD
281-
4. Update domain-memory.yaml with your progress
282-
5. EXIT immediately after completing the feature
348+
1. ${WORKTREE_ENABLED == "true" ? "cd to worktree path" : ""}
349+
2. Read domain-memory.yaml
350+
3. Find the first feature with status "pending" or "in_progress"
351+
4. Implement ONLY that one feature using TDD
352+
5. Update domain-memory.yaml with your progress
353+
6. EXIT immediately after completing the feature
283354
284355
Return:
285356
---WORKER_COMPLETED---

.claude/commands/deployment/ship.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ updated: 2025-12-09
1818

1919
**Interaction State**: !`cat specs/*/interaction-state.yaml 2>/dev/null | head -10 || echo "none"`
2020

21-
**Deployment Model**: !`test -f .github/workflows/deploy-staging.yml && echo "staging-prod" || (git remote 2>$null && echo "direct-prod" || echo "local-only")`
21+
**Deployment Model**: !`test -f .github/workflows/deploy-staging.yml && echo "staging-prod" || (git remote 2>/dev/null && echo "direct-prod" || echo "local-only")`
2222
</context>
2323

2424
<objective>
@@ -488,6 +488,71 @@ python .spec-flow/scripts/spec-cli.py ship-finalize preflight --feature-dir "$FE
488488
- Tell user to resolve conflicts and run `/ship continue`
489489
- **EXIT**
490490

491+
### Step 3.5: Worktree Merge and Cleanup (If Enabled)
492+
493+
**Check if feature was developed in a worktree:**
494+
495+
```bash
496+
WORKTREE_ENABLED=$(yq eval '.git.worktree_enabled // false' "$FEATURE_DIR/state.yaml")
497+
WORKTREE_PATH=$(yq eval '.git.worktree_path // ""' "$FEATURE_DIR/state.yaml")
498+
echo "Worktree enabled: $WORKTREE_ENABLED"
499+
echo "Worktree path: $WORKTREE_PATH"
500+
```
501+
502+
**If worktree was used:**
503+
504+
```bash
505+
if [ "$WORKTREE_ENABLED" = "true" ] && [ -n "$WORKTREE_PATH" ]; then
506+
echo "Merging worktree branch to main..."
507+
508+
# Load cleanup preference
509+
CLEANUP_ON_FINALIZE=$(bash .spec-flow/scripts/utils/load-preferences.sh \
510+
--key "worktrees.cleanup_on_finalize" --default "true" 2>/dev/null)
511+
512+
# Get feature slug for merge
513+
FEATURE_SLUG=$(yq eval '.slug' "$FEATURE_DIR/state.yaml")
514+
515+
# Merge worktree branch back to main
516+
bash .spec-flow/scripts/bash/worktree-context.sh merge "$FEATURE_SLUG"
517+
518+
# Cleanup worktree if preference enabled
519+
if [ "$CLEANUP_ON_FINALIZE" = "true" ]; then
520+
echo "Cleaning up worktree: $FEATURE_SLUG"
521+
bash .spec-flow/scripts/bash/worktree-manager.sh remove "$FEATURE_SLUG" --force
522+
echo "Worktree removed: $WORKTREE_PATH"
523+
524+
# Update state.yaml to reflect cleanup
525+
yq eval '.git.worktree_enabled = false' -i "$FEATURE_DIR/state.yaml"
526+
yq eval '.git.worktree_path = ""' -i "$FEATURE_DIR/state.yaml"
527+
yq eval '.git.worktree_merged_at = "'$(date -Iseconds)'"' -i "$FEATURE_DIR/state.yaml"
528+
else
529+
echo "Worktree preserved (cleanup_on_finalize=false): $WORKTREE_PATH"
530+
fi
531+
fi
532+
```
533+
534+
**Epic sprint worktrees:**
535+
536+
For epics with multiple sprint worktrees, merge each sprint branch:
537+
538+
```bash
539+
if [ -d "epics/$EPIC_SLUG/sprints" ]; then
540+
for sprint_dir in epics/$EPIC_SLUG/sprints/*/; do
541+
SPRINT_ID=$(basename "$sprint_dir")
542+
SPRINT_WORKTREE_ENABLED=$(yq eval '.git.worktree_enabled // false' "${sprint_dir}state.yaml")
543+
544+
if [ "$SPRINT_WORKTREE_ENABLED" = "true" ]; then
545+
SPRINT_SLUG="${EPIC_SLUG}-${SPRINT_ID}"
546+
bash .spec-flow/scripts/bash/worktree-context.sh merge "$SPRINT_SLUG"
547+
548+
if [ "$CLEANUP_ON_FINALIZE" = "true" ]; then
549+
bash .spec-flow/scripts/bash/worktree-manager.sh remove "$SPRINT_SLUG" --force
550+
fi
551+
fi
552+
done
553+
fi
554+
```
555+
491556
### Step 4: Essential Finalization (All Models)
492557

493558
1. Update TodoWrite: Mark "Run essential finalization" as `in_progress`

.claude/commands/epic/implement-epic.md

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,23 @@ allowed-tools:
2121
<context>
2222
**User Input**: $ARGUMENTS
2323

24-
**Current Branch**: !`git branch --show-current 2>$null || echo "none"`
24+
**Current Branch**: !`git branch --show-current 2>/dev/null || echo "none"`
2525

26-
**Epic Directory**: !`dir /b /ad epics 2>$null | head -1 || echo "none"`
26+
**Epic Directory**: !`ls -d epics/*/ 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "none"`
27+
28+
**Worktree Context**: !`bash .spec-flow/scripts/bash/worktree-context.sh info 2>/dev/null || echo '{"is_worktree": false}'`
29+
30+
**Worktree Auto-Create**: !`bash .spec-flow/scripts/utils/load-preferences.sh --key "worktrees.auto_create" --default "true" 2>/dev/null || echo "true"`
2731

2832
**Sprint Plan**: @epics/\*/sprint-plan.md
2933

30-
**Total Sprints**: !`grep -c "^## Sprint S" epics/*/sprint-plan.md 2>$null || echo "0"`
34+
**Total Sprints**: !`grep -c "^## Sprint S" epics/*/sprint-plan.md 2>/dev/null || echo "0"`
3135

32-
**Execution Layers**: !`grep -c "^| Layer |" epics/*/sprint-plan.md 2>$null || echo "0"`
36+
**Execution Layers**: !`grep -c "^| Layer |" epics/*/sprint-plan.md 2>/dev/null || echo "0"`
3337

34-
**Locked Contracts**: !`grep -c "^####.*Contract" epics/*/sprint-plan.md 2>$null || echo "0"`
38+
**Locked Contracts**: !`grep -c "^####.*Contract" epics/*/sprint-plan.md 2>/dev/null || echo "0"`
3539

36-
**Git Status**: !`git status --short 2>$null || echo "clean"`
40+
**Git Status**: !`git status --short 2>/dev/null || echo "clean"`
3741

3842
**Epic Artifacts** (after execution):
3943

@@ -471,6 +475,68 @@ async function executeFixStrategy(sprintId, strategy) {
471475
}
472476
```
473477
478+
### Step 2.6: Create Sprint Worktrees (If Preference Enabled)
479+
480+
**Check worktree preference and create isolated worktrees for each sprint:**
481+
482+
```javascript
483+
// Load worktree preferences
484+
const worktreeAutoCreate = await execCommand(
485+
`bash .spec-flow/scripts/utils/load-preferences.sh --key "worktrees.auto_create" --default "true"`
486+
);
487+
const isInWorktree = await execCommand(
488+
`bash .spec-flow/scripts/bash/worktree-context.sh in-worktree && echo "true" || echo "false"`
489+
);
490+
491+
// Store worktree paths for later use
492+
const sprintWorktreePaths = {};
493+
494+
if (worktreeAutoCreate.stdout.trim() === "true" && isInWorktree.stdout.trim() === "false") {
495+
log("🌳 Creating isolated worktrees for each sprint...");
496+
497+
for (const sprint of sprints) {
498+
const sprintSlug = `${metadata.epic_slug}-${sprint.id}`;
499+
const sprintBranch = `epic/${metadata.epic_slug}/${sprint.id}`;
500+
501+
// Create worktree for this sprint
502+
const createResult = await execCommand(
503+
`bash .spec-flow/scripts/bash/worktree-context.sh create "epic" "${sprintSlug}" "${sprintBranch}"`
504+
);
505+
506+
if (createResult.stdout.trim()) {
507+
const worktreePath = createResult.stdout.trim();
508+
sprintWorktreePaths[sprint.id] = worktreePath;
509+
510+
// Update sprint state.yaml with worktree info
511+
const sprintStateFile = `${EPIC_DIR}/sprints/${sprint.id}/state.yaml`;
512+
await execCommand(
513+
`yq eval '.git.worktree_enabled = true' -i "${sprintStateFile}"`
514+
);
515+
await execCommand(
516+
`yq eval '.git.worktree_path = "${worktreePath}"' -i "${sprintStateFile}"`
517+
);
518+
519+
log(`${sprint.id}: ${worktreePath}`);
520+
}
521+
}
522+
523+
log(`🌳 Created ${Object.keys(sprintWorktreePaths).length} worktrees for parallel execution`);
524+
} else {
525+
log("📁 Worktrees disabled or already in worktree - sprints will share main repository");
526+
}
527+
528+
// Store for Task() agent prompts
529+
global.SPRINT_WORKTREE_PATHS = sprintWorktreePaths;
530+
```
531+
532+
**Benefits of sprint worktrees:**
533+
- Each sprint has isolated git state (no staging conflicts)
534+
- Parallel agents can commit without coordination
535+
- Root orchestrator handles merges after completion
536+
- Clean rollback per sprint if needed
537+
538+
---
539+
474540
### Step 2.75: Frontend Mockup Approval Gate (Optional)
475541
476542
**If epic has Frontend subsystem and mockups exist**, provide optional approval gate:
@@ -867,6 +933,20 @@ Execute Sprint {SPRINT_ID}: {SPRINT_NAME}
867933
- Subsystems: {SUBSYSTEMS}
868934
- Dependencies: {DEPENDENCIES} (from previous layers)
869935
936+
{IF SPRINT_WORKTREE_PATH}
937+
**WORKTREE CONTEXT**
938+
Path: {SPRINT_WORKTREE_PATH}
939+
940+
CRITICAL: Execute this as your FIRST action before any other commands:
941+
```bash
942+
cd "{SPRINT_WORKTREE_PATH}"
943+
```
944+
945+
All paths are relative to this worktree.
946+
Git commits stay LOCAL to this worktree's branch.
947+
Do NOT merge or push - the orchestrator handles that after all sprints complete.
948+
{ENDIF}
949+
870950
**Contracts:**
871951
{IF contracts_to_lock}
872952
- Lock these contracts (producer role):

.claude/commands/phases/debug.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ allowed-tools: [Bash(python .spec-flow/scripts/spec-cli.py:*), Read, Grep, Glob]
1010
<context>
1111
**User Input**: $ARGUMENTS
1212

13-
**Current Branch**: !`git branch --show-current 2>$null || echo "none"`
13+
**Current Branch**: !`git branch --show-current 2>/dev/null || echo "none"`
1414

15-
**Feature Slug**: !`git branch --show-current 2>$null | sed 's/^feature\///' || basename $(pwd)`
15+
**Feature Slug**: !`git branch --show-current 2>/dev/null | sed 's/^feature\///' || basename $(pwd)`
1616

17-
**Recent Debug Sessions**: !`ls -1t specs/*/debug-session.json 2>$null | head -3 || echo "none"`
17+
**Recent Debug Sessions**: !`ls -1t specs/*/debug-session.json 2>/dev/null | head -3 || echo "none"`
1818

19-
**Recent Errors**: !`tail -20 specs/*/error-log.md 2>$null | grep "^###" | head -3 || echo "none"`
19+
**Recent Errors**: !`tail -20 specs/*/error-log.md 2>/dev/null | grep "^###" | head -3 || echo "none"`
2020

21-
**Git Status**: !`git status --short 2>$null || echo "clean"`
21+
**Git Status**: !`git status --short 2>/dev/null || echo "clean"`
2222

2323
**Debug Session Artifacts** (after script execution):
2424
- @specs/*/debug-session.json

0 commit comments

Comments
 (0)