Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions design/orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ rule there is *ask once, before doing anything, and record the answer*; the mist
forbids is asking repeatedly, not asking at all. A plan's recorded `autonomy` and `ci`
were given when the plan was **written**. Starting a loop that will open and merge pull
requests for hours is a different and larger thing to agree to, and it raises a
question authoring never had — worktree per group, or the checkout you are in.
question authoring never had — one pull request per group, or one at the end.

So `plan-run` asks three questions, once, **after reporting the groups**, because the
answers are only meaningful to someone who can see what they are agreeing to. Anything
Expand All @@ -492,16 +492,22 @@ them instead of asking again:
| Key | Values | Decides |
|---|---|---|
| `autonomy` | `auto` · `gated` | straight through, or stop at each group boundary |
| `worktree` | `per-group` · `in-place` | a worktree per group, or a branch in the current checkout |
| `pr` | `per-group` · `per-plan` | one pull request per group, or one at the end |
| `ci` | `wait` · `no-wait` | whether the checks have to settle before the merge |
| `merge` | `auto` · `manual` | whether the loop merges, or stops at the PR for the developer |

`worktree` and `merge` are new, plan-only, and validated by `plan` on exactly the terms
`pr` and `merge` are new, plan-only, and validated by `plan` on exactly the terms
`autonomy` and `ci` are — **only when present**, since a plan no loop has run over is
not a plan with a defect. They are validated at all because the skill writes them and
every later session reads them, so `worktree: yes` would silently decide how the next
every later session reads them, so `merge: whenever` would silently decide how the next
ten groups get built.

There was a fifth key, `worktree: per-group | in-place`, and it is gone. The worktree
existed for one case — several sessions at once on one repo — which is the user's own
setup, and charging every single-session run a directory to create, switch to and
remember to remove (and a stale checkout whenever the run died before the last step)
bought nothing back. §9 is a branch in the checkout you are already in.

The tempting alternative was for the loop to **override** `ci: no-wait`, on the
argument that the next group branches from the merge and an unverified base poisons
everything after it. Rejected: the argument is sound and the developer is still the one
Expand Down Expand Up @@ -688,30 +694,35 @@ interference, edit conflicts, resource collisions, and cross-task breakage — a
blind to the failure that partial context actually causes.

Sequential execution has a second benefit worth naming: nothing needs isolating, so
worktrees, per-agent resource namespacing, merge-conflict resolution, and
separate checkouts, per-agent resource namespacing, merge-conflict resolution, and
result attribution all stop being problems rather than being solved.

None of this rules out parallelism as such — only *dispatched, task-level*
parallelism. Feature-level parallelism is real and supported: the user runs several
Claude Code sessions, one per feature, each in its own worktree, and merges them into
`main`. See §9, which lays out why that version survives the objections above.
Claude Code sessions, one per feature, and merges them into `main`. See §9, which lays
out why that version survives the objections above.

## 9 · Delivery — branch, worktree, PR
## 9 · Delivery — branch, PR

Work does not happen on `main` and does not end with a green test run. It ends with a
pull request.

### Branch in a worktree — one per session
### Branch in the checkout you are in

Each unit of work — a spec, or a plan's leaf — gets its own branch, developed in its
own **git worktree**.
Each unit of work — a spec, or a plan's leaf — gets its own branch, cut from a green
`main` in the checkout the session is already sitting in, and the checkout goes back to
`main` and clean once the work has landed. That last part is the whole discipline: the
next unit of work starts where this one did.

The reason is that **the user may run several Claude Code sessions at once**, one per
feature, and merge them all into `main` when they land. The worktree is what makes
that possible: session A on `feat/billing` and session B on `feat/auth` each get their
own directory, neither disturbs the other, and neither touches the checkout the user
is actually sitting in. A shared tree with `git switch` cannot do this at all — two
sessions would fight over one working directory.
**A git worktree is not part of this, and used to be.** It was there for one case —
**the user running several Claude Code sessions at once**, one per feature, each
needing a directory of its own, since a shared tree with `git switch` would have two
sessions fighting over one working directory. That case is real and still supported.
But it is the user's setup to make, once, for the runs they actually parallelize, and
making it a step of the ordinary procedure charged every single-session run for it: a
directory to create, one to switch into, one to remember to remove, and a stale
checkout left behind whenever a run died before the last step. What survives is the
line the worktree was really carrying — leave the checkout clean, on `main`.

So parallelism is back, and it is worth being precise about why this version is fine
when §8's was not. **The user drives this one; the orchestrator drove that one.** Four
Expand All @@ -732,13 +743,13 @@ within each session the accumulated context prevents it outright. The risk drops

### What running several sessions still costs

Worktrees isolate files. They do not isolate the world outside them, so two of these
concerns from §8 survive at feature granularity and should be said plainly:
Separate checkouts isolate files. They do not isolate the world outside them, so two of
these concerns from §8 survive at feature granularity and should be said plainly:

- **Shared external resources.** Two sessions running the suite at the same time will
fight over a fixed port, one test database, or a shared temp path. Either the suite
namespaces those per worktree, or the test runs have to be serialized. Nothing about
a worktree fixes this.
namespaces those per checkout, or the test runs have to be serialized. Nothing about
a separate directory fixes this.
- **Cross-feature breakage.** Two features green on their own branches can be broken
together. Only CI on `main` after the merge sees that — which is a good reason to
care about the merge order and about `main` staying green.
Expand Down Expand Up @@ -783,8 +794,8 @@ stays reproducible from the file, and nobody gets asked twice.

- **No remote, or no `gh`** — commit on the branch and stop there, saying so. A branch
the user can push themselves is a real deliverable; silently skipping the PR is not.
- **Worktrees left behind accumulate.** Remove the worktree once its branch is merged;
keep it if it still holds unmerged work, and say which.
- **A checkout left dirty or off `main`** is what this shape can leave behind. Say what
is still uncommitted rather than starting the next unit of work on top of it.

## 10 · The three spec artifacts

Expand Down
2 changes: 1 addition & 1 deletion design/plan-format-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ Em plano com `status: approved`:
| `patch add` | exige `--reason`; **recusa `--number`** (o scc aloca, §7); exige `--group N` ou `--new-group` |
| `patch rm` | **remoção lógica**: reescreve a task com `_Status removed_` + `_Reason`; nunca apaga linhas |
| `patch check`/`uncheck` | permitido |
| `patch fm` | permitido (respostas do loop: `pr`, `worktree`, `merge`) |
| `patch fm` | permitido (respostas do loop: `pr`, `merge`) |
| `patch task --text/--method/--number` | **recusado** — muda conteúdo funcional |
| `patch append`/`prepend`/`replace` | **recusado em plano aprovado** |

Expand Down
2 changes: 1 addition & 1 deletion design/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ whole point is that a rule is read when it is relevant:
| `methodology.md` | §3 — impact analysis first, then Unit or TDD; when TDD is mandatory. |
| `tasks.md` | §4 — the grammar, and "independently verifiable" as the size rule. |
| `verification.md` | §7 — build, scoped tests, lint, fix; scope not suite. |
| `delivery.md` | §9 — branch in a worktree, review before the PR, the CI question, degrading. |
| `delivery.md` | §9 — branch in the checkout, review before the PR, the CI question, degrading. |
| `specs.md` | §10–§11 — EARS, deltas, design scaled by complexity, spec-anchored. |
| `knowledge-base.md` | `docs/` — what belongs in the wiki, in an ADR, in the glossary, in the stack. |
| `project.md` | The project's own test and lint commands. Shipped as a stub, owned by the user from first edit. This file is why scc needs no config file. |
Expand Down
8 changes: 5 additions & 3 deletions design/research.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,11 @@ multi-agent workflows run ~4–7× the tokens of a single-agent session, agent t

### 3.2 Worktree-per-session is the community pattern, caveats included

The published playbooks say what §9 says: one worktree per parallel session, each running
its own tests, diffs reviewed and merged in dependency order. They also independently hit
our §9 resource-collision caveat — recommending `.env.local` per worktree (gitignored) and
The published playbooks agree on the shape §9 supports for *parallel* work: one worktree
per parallel session, each running its own tests, diffs reviewed and merged in dependency
order. (§9 has since stopped making that a step of the ordinary single-session procedure —
the worktree is the user's setup for the runs they actually parallelize.) They also
independently hit our §9 resource-collision caveat — recommending `.env.local` per worktree (gitignored) and
explicit port/database isolation, because a committed shared `.env` is read by every
worktree. Our "worktrees isolate files, not the world outside them" is the same lesson.

Expand Down
2 changes: 1 addition & 1 deletion internal/artifact/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func (e *Editor) Prepend(ref, text string) {
}

// SetFrontmatter writes one key in the leading block, adding the block if the file
// has none. It is how the plan-run loop records its answers — worktree, merge, pr
// has none. It is how the plan-run loop records its answers — pr, merge, ci
// without the skill having to hold the file.
func (e *Editor) SetFrontmatter(key, value string) {
if e.err != nil {
Expand Down
8 changes: 4 additions & 4 deletions internal/artifact/edit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,16 @@ func writeAt(path, content string) error {
func TestSetFrontmatterKeepsTheOrderAsked(t *testing.T) {
_, content := edited(t, func(e *Editor) {
e.SetFrontmatter("pr", "per-plan")
e.SetFrontmatter("worktree", "per-group")
e.SetFrontmatter("lang", "en")
e.SetFrontmatter("merge", "auto")
})
pr := strings.Index(content, "pr: per-plan")
wt := strings.Index(content, "worktree: per-group")
lg := strings.Index(content, "lang: en")
mg := strings.Index(content, "merge: auto")
if pr < 0 || wt < 0 || mg < 0 {
if pr < 0 || lg < 0 || mg < 0 {
t.Fatalf("a key is missing:\n%s", firstLines(content, 10))
}
if !(pr < wt && wt < mg) {
if !(pr < lg && lg < mg) {
t.Errorf("keys came out reversed:\n%s", firstLines(content, 10))
}
}
Expand Down
10 changes: 9 additions & 1 deletion internal/assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,15 @@ import (
// file-order one. And the rules stop offering to read the plan at all: `map brief`
// once plus `map tasks --next` per task is the whole reading surface, which is what
// gives "never read the plan" the authority to be a rule.
const Version = "14"
// 15: delivery is a branch in the checkout you are in, and the worktree is gone from
// the procedure. It was there for one reason — several sessions at once on one repo —
// and that is the user's setup to make, not a step every single-session run pays for:
// a directory to create, one to switch to, and one to remember to remove, with a
// checkout left behind whenever the run dies before the last step. So `plan-run` asks
// three questions instead of four, `worktree:` stops being a frontmatter answer, and
// what survives is the one line the worktree was really carrying — leave the checkout
// back on `main` and clean, because that is where the next unit of work starts.
const Version = "15"

// The embedded tree. "all:" so nothing is silently dropped for having a name the
// default embed pattern skips.
Expand Down
2 changes: 1 addition & 1 deletion internal/assets/templates/agents/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ git diff --stat main...HEAD
line, so re-reading a file to look at them buys nothing. Open a file only when the diff
is genuinely not enough to judge a change, only if the diff touches it, and only once —
a review that fetches the same source three times spent its context on what it was
handed. When the work is in a worktree, read that path, never the main checkout's copy.
handed.

Then read what the work was supposed to be, on the same terms: `scc map <artifact>` for
its shape and `scc map show <artifact> <address>` for the part you need. **The artifact
Expand Down
2 changes: 1 addition & 1 deletion internal/assets/templates/agents/security-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ issues in untouched code are worth one line at the end, not the body of the revi

That scope is also your read budget. The diff carries the changed lines already: open a
file only to follow reachability the diff cannot show you, only if the diff touches it,
and once. In a worktree, read that path rather than the main checkout's copy.
and once.

## The method — four passes, in this order

Expand Down
3 changes: 1 addition & 2 deletions internal/assets/templates/commands/scc-plan-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ Then take every answer the line above already gave and ask only for what is left
most of it; re-asking what someone just typed is the friction that stops people using
this at all. Restate what you took so a wrong reading is cheap to correct, then put
the remaining questions in one exchange — automatic or gated, one PR at the end or one
per group, a worktree per group or the current checkout, and what happens once a PR is
open. **These are the developer's calls.** Anything the plan's frontmatter already
per group, and what happens once a PR is open. **These are the developer's calls.** Anything the plan's frontmatter already
records is a proposed answer to confirm, not a decision already made.

The plan is delivered when CI is green on its pull request — never on the strength of
Expand Down
24 changes: 12 additions & 12 deletions internal/assets/templates/rules/delivery.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Delivery — branch, worktree, PR
# Delivery — branch, PR

Work does not happen on `main` and does not end with a green test run. It ends with a
pull request. Each unit of work gets its own branch in its own worktree:
pull request. Each unit of work gets its own branch, in the checkout you are in:

```
git worktree add ../<repo>-<slug> -b <type>/<slug>
git switch -c <type>/<slug>
```

The user may run several sessions at once, one per feature, merging them as they land.
The worktree is what makes that possible: each session gets its own directory and none
touches the checkout the user is in. A shared tree with `git switch` cannot — two
sessions would fight over one working directory.
Branch from a green `main`, and leave the checkout back on `main` and clean once the
work lands. Nothing here needs a second directory: a user running several sessions
against one repo may give each its own `git worktree`, and that is theirs to set up.

## Implementation is sequential — you write the code

Expand All @@ -27,9 +26,10 @@ other way first, and rejected:
execution cannot produce this: the later task sees the earlier task's code.

Feature-level parallelism has none of that and is supported — a *human* picks the split
and each session has full context. Worktrees isolate files, not the world: suites
fighting over a fixed port or one test database must be namespaced or serialized, and
two features green separately can still break together, which only CI on `main` sees.
and each session has full context. Separate sessions isolate files, not the world:
suites fighting over a fixed port or one test database must be namespaced or
serialized, and two features green separately can still break together, which only CI
on `main` sees.

## The delivery sequence

Expand Down Expand Up @@ -58,5 +58,5 @@ PR is the finish line.

**No remote, or no `gh`** — commit on the branch and stop there, saying so. A branch
the user can push themselves is a real deliverable; silently skipping the PR is not.
**Worktrees accumulate** — remove one once its branch is merged, keep it if it holds
unmerged work, and say which you did.
**A checkout left dirty or off `main`** is what this shape can leave behind — say what
is still uncommitted rather than starting the next unit of work on top of it.
Loading
Loading