ADR-015-F: Git Worktree Development Flow with Planning Repo as Source of Truth¶
Status: Accepted Date: 2026-04-17 Decision Makers: Gautham Chellappa
Context¶
Finnest development spans three repositories:
finnest— Elixir/Phoenix backend (code)finnest-mobile— Flutter companion app (code)finnest-planning— PRDs, architecture, ADRs, brainstorms, research, stories (docs)
Two problems surfaced when picking a development flow:
Problem 1: Parallel story development¶
With BMAD's /dev-story workflow, it is normal to have multiple stories in flight — one being implemented, one under PR review, one being rebased. Traditional single-checkout flows force git stash gymnastics between them. Git worktrees solve this cleanly: each story gets its own directory + branch, shared .git.
Problem 2: Cross-repo path resolution under worktrees¶
The existing BMAD configs used relative paths:
From the main checkout at ~/Documents/GitHub/finnest/, ../finnest-planning resolves to ~/Documents/GitHub/finnest-planning — correct.
From a worktree at ~/Documents/GitHub/finnest-worktrees/story-042-auth/, ../finnest-planning resolves to ~/Documents/GitHub/finnest-worktrees/finnest-planning — broken.
Every command that reads stories, architecture docs, or sprint state from the planning repo would fail or reference the wrong location.
Problem 3: Sprint state under parallel worktrees¶
_bmad-output/sprint-status.yaml (tracking which stories are in which status) currently lives in the code repo. Under worktrees, each worktree gets its own copy of that file, and divergent edits across parallel sessions create merge conflicts and stale state. The file is fundamentally sprint-level, not story-level — it should not fork per worktree.
Decision¶
1. Adopt git worktrees with a sibling layout¶
Each code repo (finnest, finnest-mobile) uses worktrees created in a sibling directory:
~/Documents/GitHub/
├── finnest/ ← main checkout (always on main, always clean)
├── finnest-worktrees/
│ ├── story-001-auth/ ← git worktree add ../finnest-worktrees/story-001-auth feat/story-001
│ ├── story-002-roster/
│ └── story-003-timekeep/
├── finnest-mobile/ ← main checkout
├── finnest-mobile-worktrees/
│ └── story-M01-clock/
└── finnest-planning/ ← single checkout; NO worktrees
Why sibling, not nested: worktrees nested inside a main checkout (finnest/worktrees/…) confuse both git (nested .git file pointers) and IDE tooling (indexers treat them as subdirectories). Sibling directories keep each worktree a first-class top-level repo surface.
Why a separate dir (not flat top-level): keeps ~/Documents/GitHub/ from ballooning with per-story directories.
2. finnest-planning is never worktreed¶
The planning repo is docs-only. Docs are authored, reviewed, merged. There is no reason to maintain parallel branches — and worktreeing planning would reintroduce the same cross-repo resolution problem (which planning to resolve against?). One stable checkout at ~/Documents/GitHub/finnest-planning/.
3. Use FINNEST_PLANNING (and siblings) environment variable for cross-repo paths¶
All BMAD configs, /first-in, /last-out, /dev-story, and any tooling that references the planning or sibling code repos resolves paths via environment variables:
export FINNEST_PLANNING="$HOME/Documents/GitHub/finnest-planning"
export FINNEST_BACKEND="$HOME/Documents/GitHub/finnest"
export FINNEST_MOBILE="$HOME/Documents/GitHub/finnest-mobile"
export FINNEST_MOBILE_REFERENCE_APP="$HOME/Documents/GitHub/rnd_asg_central_app"
Export these in ~/.zshrc (one-time setup). Every shell, every worktree, inherits them.
Configs reference them as ${FINNEST_PLANNING} and fall back to $HOME/Documents/GitHub/finnest-planning when the env var is unset.
4. sprint-status.yaml moves to finnest-planning¶
The authoritative sprint-state file lives at:
/last-out sessions (from any worktree, any code repo) commit sprint-status updates directly to the planning repo as a cross-repo commit. This is explicit and intentional — the planning repo is the shared source of truth, and cross-repo commits to it are a first-class part of the workflow, not an exception.
Code repos keep their own PROGRESS.md (per-repo CI status) and CHANGELOG.md (per-repo release history) — these are inherently local and do not belong in planning.
5. Documents follow this ownership split¶
| Artifact | Repo | Rationale |
|---|---|---|
Architecture docs (architecture/) |
finnest-planning |
Shared across backend + mobile; pre-exists here |
ADRs (adrs/) |
finnest-planning |
Authoritative decision record; pre-exists here |
PRDs (prd/) |
finnest-planning |
Pre-exists |
| Brainstorms, research | finnest-planning |
Pre-exists |
Epics, stories (stories/) |
finnest-planning |
Source of truth; consumed by both code repos |
sprint-status.yaml |
finnest-planning (_bmad-output/) |
Single authoritative sprint state; see §4 |
bmm-workflow-status.yaml |
finnest-planning (_bmad-output/) |
Planning workflow pipeline state; pre-exists |
PROGRESS.md |
Each code repo | Per-repo CI status; inherently local |
CHANGELOG.md |
Each code repo | Per-repo release history; inherently local |
| Local story cache | Each code repo (_bmad-output/stories/) |
Optional per-repo cache; source of truth is planning |
Consequences¶
Easier¶
- Parallel story development —
/dev-story STORY-042and/dev-story STORY-043can run in separate worktrees simultaneously with zero interference. - Main checkout stays pristine — always on
main, always clean. It's the "read-only reference" surface forgit log/git diff main/ context checking. - Cross-repo paths are stable —
$FINNEST_PLANNINGresolves the same way from any worktree, any session, any machine (once exported). - Sprint state is unambiguous — one file, one source of truth, one canonical location.
- Onboarding portability — a new dev on a new machine exports the four env vars and everything works.
Harder¶
- Must export env vars — the new required setup step. Mitigated by the worktree-guide.md and by commands reading fallback defaults.
- Cross-repo commits in
/last-out— a session ending in a code-repo worktree also commits to the planning repo. The ritual now spans two repos. Mitigated by making this explicit in last-out (it confirms before committing to planning) and in ADR + guide. - Worktree hygiene required — dead worktrees accumulate if not cleaned up. Mitigated by
/last-outoffering togit worktree removeafter a merged PR, and bygwtcleanalias in the shell snippet. - Tooling must be worktree-aware — CLAUDE.md instructions, hooks, and any path-resolving script must read env vars, not relative paths. Anyone writing new tooling for this project must follow the convention.
Neutral¶
- Commit velocity in planning goes up — more frequent small commits (sprint-status bumps, new story drafts, ADR additions). This is fine; planning is meant to track the work, and a richer commit history is an asset.
Alternatives Considered¶
Alt 1: Nested worktrees inside each code repo (finnest/worktrees/…)¶
Rejected. Git tolerates nested worktrees but IDEs, file watchers, and indexers often misbehave. The main checkout's .gitignore has to exclude worktrees/. Nesting also conflates the main checkout and its worktrees at the filesystem level — the very problem worktrees were meant to decouple.
Alt 2: Flat top-level per worktree (finnest-story-042/)¶
Rejected. Clutters ~/Documents/GitHub/ rapidly — a dozen active stories across two code repos becomes a 24-directory wall. Also makes "which repo does this worktree belong to?" ambiguous at a glance.
Alt 3: Keep relative ../finnest-planning and symlink inside each worktree¶
Rejected. Requires creating a symlink in every new worktree. Error-prone (easy to forget), brittle (ln -s absolute-vs-relative confusion), and clutters every worktree directory with a symlink that isn't part of the repo.
Alt 4: Hard-code absolute paths (/Users/gg/Documents/GitHub/finnest-planning) in configs¶
Rejected. Not portable. Breaks for any other developer, any CI environment, any container build. An env var with a sensible default covers both single-dev and multi-dev scenarios.
Alt 5: Keep sprint-status.yaml per code repo, discipline about which checkout writes it¶
Rejected. Discipline is the cost. Humans forget. Worktrees are designed for parallel work; requiring mental single-threading on one file defeats the purpose. Single authoritative file in planning is both correct and lower-friction.
Alt 6: Keep sprint-status.yaml per code repo, sync to planning via separate script¶
Rejected. More moving parts. Sync scripts drift. Every /last-out would need to invoke it. Directly committing to planning is simpler and the audit trail is clearer (the planning commit history is the sync log).
Alt 7: Don't use worktrees at all; stick with single-checkout + git stash¶
Rejected as the default. For a single-story-at-a-time pace, single-checkout is fine — but BMAD explicitly supports /convergence (multi-story execution) and the mobile strategy mentions the same. Paying the one-time setup cost of worktrees unlocks the parallel pattern for the whole 44-week build.
Related¶
- Practical how-to:
workflows/worktree-guide.md— setup, daily commands, troubleshooting - Commandments cited: #4 (solve the problem you have), #13 (every decision needs an exit path — worktrees are reversible; worktree dirs deletable), #15 (prefer boring technology — git worktrees are a standard feature since 2015), #21 (build for your team — this is a 3-person team pattern, not a 50-person team pattern)
- Affected BMAD configs:
finnest/.claude/config/bmad/config.yaml,finnest-mobile/.claude/config/bmad/config.yaml - Affected commands:
/first-in,/last-outin all three repos;/dev-story,/convergence(future Elixir adaptations)