feat(plan): run a serial plan in the checkout instead of a worktree per feat - #93
Conversation
…er feat A worktree carries only tracked files, so it starts without everything git ignores and a suite needs: node_modules/, .venv/, build caches. On a real project that is not a detail — the session cannot run a gate until it has rebuilt the environment, and it pays that per feat and again per attempt. That cost buys isolation from concurrent peers, which is worth it for a squad and worth nothing for one session: there is no other index to collide with. So the squad size now decides where feats work. At --squad-limit 1 (the default) the run is serial and works in the repository itself; above it the worktrees are unchanged. The git preflight moves with them, deliberately: it resolves the base feats are cut from and merged back into and refuses a repository where that would be ambiguous — questions an in-place run does not ask, because it lands nothing. The plan-session CLAUDE.md is not injected either; that swap works by marking the file skip-worktree, and doing it to the entry file a human maintains in the checkout they work in would hide their own file from them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBSaXgJ7ssW1hvpy6r64y
📝 WalkthroughWalkthrough
ChangesPlan tree strategy
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@README.md`:
- Around line 311-313: Update the README guidance around the `plan run` Git
precondition to clarify that refusal of non-Git, detached, or dirty workspaces
applies only to squad runs using `--squad-limit 2..6`; preserve the existing
explanation that serial runs bypass `preflightGit`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5c72f602-541d-4197-b915-3077f1d05ac9
📒 Files selected for processing (5)
README.mdinternal/cli/plan.gointernal/plan/runner.gointernal/plan/tree.gointernal/plan/tree_test.go
| **A serial run works in your checkout.** At `--squad-limit 1` (the default) one session runs at a time, in the repository itself — the environment your suite needs is already installed there. The worktrees below are what a *squad* pays for isolation: a worktree carries only tracked files, so it starts without `node_modules/`, `.venv/` or any other build cache git ignores, and rebuilding those per feat buys a serial run nothing, since it has no peer to collide with. | ||
|
|
||
| `--squad-limit N` (2..6) runs up to **N sessions at once, each on its own feat and each in its own git worktree**. Filesystem isolation is what makes it safe: every concurrent session gets a worktree under `.csdd/plan/<slug>/trees/<feat>` on branch `csdd/<slug>/<feat>`, so no two agents ever share an index, a build, or a half-written file. Scheduling is the `Depends` graph alone — the `(P)` column is **not** consulted, because it used to mean "consents to share a working tree" and feats no longer share one. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Scope the Git precondition to squad runs.
Line 319 still says plan run always refuses non-Git, detached, or dirty workspaces, but serial runs bypass preflightGit. Qualify that statement as applying to --squad-limit 2..6 worktree runs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 311 - 313, Update the README guidance around the
`plan run` Git precondition to clarify that refusal of non-Git, detached, or
dirty workspaces applies only to squad runs using `--squad-limit 2..6`; preserve
the existing explanation that serial runs bypass `preflightGit`.
The problem
A git worktree contains only tracked files. On a real project that means a fresh one starts without
node_modules/,.venv/,.next/— everything the suite needs and.gitignoreexcludes. A session cannot run a test, a lint or a build until it has rebuilt that environment, and it pays the cost per feat, and again per attempt.That is what isolation costs. What it buys is protection from concurrent peers sharing one index — real for a squad, and nothing at all for a single session, which has no peer to collide with. Since
--squad-limitdefaults to 1, the default run was paying the full price for a guarantee it did not need.The change
The squad size decides where feats work (
resolveTrees):--squad-limitinPlaceTrees.Integrateand.Discardare no-ops rather than stubs: the work is already on the branch the human is standing on, and no directory belongs to the feat.Two things move with the worktrees, on purpose:
CLAUDE.mdinjection. The swap works by marking the fileskip-worktree; doing that to the entry file a human maintains, in the checkout they work in, would hide their own file from them. An in-place session reads the repository'sCLAUDE.md, whose scaffolded template already carries the "One sanctioned exception — the autonomouscsdd plan runloop" paragraph that authorizes self-approval and points at theplan-devskill.Consequence worth knowing
In-place, the git contract is the
plan-devskill's — branch per feat, PR at the end — rather than the runner merging a branch per feat. That is the pre-squad behaviour restored, not a new one.Verification
make checkgreen (gofmt + vet +go test -race, every package).TestSerialRunWorksInTheCheckoutItselfpins both ends against a real repository: at limit 1 every feat resolves to the root, integrate/discard are no-ops, and no worktree and nocsdd/*branch is created; at limit 2 the keeper is stillgitTrees.--squad-limithelp and the README's plan-run section now state which mode you are in and why.🤖 Generated with Claude Code
https://claude.ai/code/session_01ARBSaXgJ7ssW1hvpy6r64y
Summary by CodeRabbit
New Features
Documentation
--squad-limitbehavior, including serial execution, concurrency, worktree isolation, and repository requirements.csdd plan runcommand synopsis to reflect the default limit of1.