Skip to content

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:

planning_local_path: "../finnest-planning"

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-planningbroken.

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:

$FINNEST_PLANNING/_bmad-output/sprint-status.yaml

/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-042 and /dev-story STORY-043 can run in separate worktrees simultaneously with zero interference.
  • Main checkout stays pristine — always on main, always clean. It's the "read-only reference" surface for git log / git diff main / context checking.
  • Cross-repo paths are stable$FINNEST_PLANNING resolves 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-out offering to git worktree remove after a merged PR, and by gwtclean alias 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.

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.

  • 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-out in all three repos; /dev-story, /convergence (future Elixir adaptations)