Skip to content

feat(plan): run a serial plan in the checkout instead of a worktree per feat - #93

Merged
protonspy merged 1 commit into
mainfrom
feat/serial-run-in-place
Jul 28, 2026
Merged

feat(plan): run a serial plan in the checkout instead of a worktree per feat#93
protonspy merged 1 commit into
mainfrom
feat/serial-run-in-place

Conversation

@protonspy

@protonspy protonspy commented Jul 28, 2026

Copy link
Copy Markdown
Owner

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 .gitignore excludes. 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-limit defaults 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-limit where feats work
1 (default) the repository itself — serial, environment already installed
2..6 one git worktree per feat, cut from and merged back into the base (unchanged)

inPlaceTrees.Integrate and .Discard are 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:

  • The git preflight. It resolves the base feats are cut from and merged into, and refuses a repository where that would be ambiguous. An in-place run asks neither question — it lands nothing — so it no longer requires a clean repository.
  • The plan-session CLAUDE.md injection. The swap works by marking the file skip-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's CLAUDE.md, whose scaffolded template already carries the "One sanctioned exception — the autonomous csdd plan run loop" paragraph that authorizes self-approval and points at the plan-dev skill.

Consequence worth knowing

In-place, the git contract is the plan-dev skill'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 check green (gofmt + vet + go test -race, every package).
  • New TestSerialRunWorksInTheCheckoutItself pins both ends against a real repository: at limit 1 every feat resolves to the root, integrate/discard are no-ops, and no worktree and no csdd/* branch is created; at limit 2 the keeper is still gitTrees.
  • --squad-limit help 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

    • Serial plan runs now execute directly in the current checkout by default.
    • Concurrent runs continue to use separate worktrees for each feat.
  • Documentation

    • Clarified --squad-limit behavior, including serial execution, concurrency, worktree isolation, and repository requirements.
    • Updated the csdd plan run command synopsis to reflect the default limit of 1.

…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
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

plan run now executes serial runs directly in the current checkout, while concurrent squads continue using isolated git worktrees. Runner selection, tests, CLI help, and README documentation were updated to describe and validate this behavior.

Changes

Plan tree strategy

Layer / File(s) Summary
In-place tree keeper
internal/plan/tree.go, internal/plan/tree_test.go
Adds an inPlaceTrees implementation for serial runs and tests that it avoids worktrees and csdd/* branches.
Concurrency-based tree resolution
internal/plan/runner.go
Selects in-place execution when SquadLimit <= 1; concurrent runs retain git preflight and per-feat worktrees.
CLI and README semantics
internal/cli/plan.go, README.md
Documents serial execution in the current checkout and isolated worktrees for higher squad limits.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • protonspy/csdd#84: Introduces the per-feat worktree behavior used for concurrent squad execution.
  • protonspy/csdd#87: Updates runner worktree setup intersecting with the new strategy selection.
  • protonspy/csdd#89: Modifies runner behavior inside per-feat worktrees.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: serial plan runs now execute in the checkout instead of per-feat worktrees.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/serial-run-in-place

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between eee5bb5 and 4ad3ddc.

📒 Files selected for processing (5)
  • README.md
  • internal/cli/plan.go
  • internal/plan/runner.go
  • internal/plan/tree.go
  • internal/plan/tree_test.go

Comment thread README.md
Comment on lines +311 to +313
**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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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`.

@protonspy
protonspy merged commit 286c278 into main Jul 28, 2026
7 checks passed
@protonspy
protonspy deleted the feat/serial-run-in-place branch July 28, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant