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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ node_modules/
# Git worktrees
.worktrees/

# Claude Code transient runtime locks
.claude/scheduled_tasks.lock
.claude/*.lock

# Playwright MCP test artifacts
tests/screenshots/current/
tests/screenshots/diff/
Expand Down
31 changes: 16 additions & 15 deletions content/guides/multi-agent/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1380,11 +1380,11 @@ <h1>Parallel Agents &amp; Worktrees</h1>
<p>A <strong>git worktree</strong> solves this: it is an independent working directory backed by
the <em>same</em> <code>.git</code> repository. Each agent gets its own files and its own checked-out
branch, but commits, refs and history are shared. The layout is one primary
checkout plus one sibling directory per agent:</p>
<pre><code class="language-text">~/projects/
── scaffold/ # primary checkout (you work here)
├── scaffold-alpha/ # worktree for agent "alpha"
└── scaffold-beta/ # worktree for agent "beta"
checkout with all agent worktrees project-local under <code>.worktrees/</code>:</p>
<pre><code class="language-text">scaffold/ # primary checkout (you work here)
── .worktrees/ # agent worktrees (gitignored)
├── alpha/ # worktree for agent "alpha"
└── beta/ # worktree for agent "beta"
</code></pre>
<p>So worktrees give you <strong>filesystem isolation with a shared object store</strong>:
agents never overwrite each other's working files, but a PR merged from one
Expand All @@ -1398,20 +1398,21 @@ <h2 id="setup-setup-agent-worktreesh">Setup — <code>setup-agent-worktree.sh</c
one parallel agent. Given a name like <code>alpha</code>, it:</p>
<ol>
<li><strong>Normalizes the name</strong> to a lowercase, hyphenated, alphanumeric slug (so
<code>Agent_1</code> becomes <code>agent-1</code>), then derives the worktree directory as a
<em>sibling</em> of the primary repo: <code>../&#x3C;repo-name>-&#x3C;slug></code>.</li>
<code>Agent_1</code> becomes <code>agent-1</code>), then derives the worktree directory project-local
under the primary repo: <code>.worktrees/&#x3C;slug></code>. It also ensures <code>.worktrees/</code> is
gitignored so the worktree's checkout is never accidentally committed.</li>
<li><strong>Creates the workspace branch</strong> <code>&#x3C;slug>-workspace</code> if it does not already
exist <span class="fp" data-path="scripts/setup-agent-worktree.sh:40">scripts/setup-agent-worktree.sh:40</span>, then adds the worktree on
that branch <span class="fp" data-path="scripts/setup-agent-worktree.sh:44">scripts/setup-agent-worktree.sh:44</span>. Re-running for an
exist <span class="fp" data-path="scripts/setup-agent-worktree.sh:62">scripts/setup-agent-worktree.sh:62</span>, then adds the worktree on
that branch <span class="fp" data-path="scripts/setup-agent-worktree.sh:65">scripts/setup-agent-worktree.sh:65</span>. Re-running for an
existing worktree is a safe no-op.</li>
<li><strong>Writes <code>.scaffold/identity.json</code></strong> — the stable identity that build
observability stamps onto every event this worktree records. The script
creates <code>.scaffold/</code> <span class="fp" data-path="scripts/setup-agent-worktree.sh:52">scripts/setup-agent-worktree.sh:52</span> and, only if no
creates <code>.scaffold/</code> <span class="fp" data-path="scripts/setup-agent-worktree.sh:73">scripts/setup-agent-worktree.sh:73</span> and, only if no
identity file exists yet, writes <code>worktree_id</code> (a UUID), <code>worktree_label</code>
(the agent slug), and <code>created_at</code>
<span class="fp" data-path="scripts/setup-agent-worktree.sh:71">scripts/setup-agent-worktree.sh:71</span>.</li>
<span class="fp" data-path="scripts/setup-agent-worktree.sh:92">scripts/setup-agent-worktree.sh:92</span>.</li>
<li><strong>Re-syncs Beads</strong> with a fail-soft <code>bd doctor --fix</code> when a <code>.beads/</code>
directory is present <span class="fp" data-path="scripts/setup-agent-worktree.sh:88">scripts/setup-agent-worktree.sh:88</span>, reconciling the
directory is present <span class="fp" data-path="scripts/setup-agent-worktree.sh:109">scripts/setup-agent-worktree.sh:109</span>, reconciling the
worktree's Beads git hooks and project config against the installed <code>bd</code>
version. (Beads DB sharing is automatic — worktrees discover the main repo's
task DB via git's common directory, so there is nothing for <code>bd doctor</code> to
Expand Down Expand Up @@ -1467,8 +1468,8 @@ <h2 id="working-in-parallel">Working in parallel</h2>
</ul>
<p>Each agent otherwise follows the standard PR workflow from its own worktree:
branch, commit, push, <code>gh pr create</code>, wait for CI, squash-merge. The shared
object store means a merge from <code>scaffold-alpha</code> is on <code>main</code> for everyone the
moment it lands.</p>
object store means a merge from agent <code>alpha</code>'s worktree is on <code>main</code> for
everyone the moment it lands.</p>
<h2 id="teardown-harvest">Teardown &#x26; harvest</h2>
<p>When an agent's work is merged, retire its worktree. The single command for this
is <code>scripts/teardown-agent-worktree.sh &#x3C;worktree-path></code>, and the <strong>order of
Expand Down Expand Up @@ -1529,7 +1530,7 @@ <h2 id="resuming-after-a-break">Resuming after a break</h2>
morning — does <strong>not</strong> mean re-running setup. The worktree and its
<code>identity.json</code> persist on disk. Instead:</p>
<ol>
<li>Return to the agent's worktree directory (<code>../&#x3C;repo>-&#x3C;agent></code>).</li>
<li>Return to the agent's worktree directory (<code>.worktrees/&#x3C;agent></code>).</li>
<li>Run <strong><code>multi-agent-resume &#x3C;agent-name></code></strong> (or <code>single-agent-resume</code> for the
non-worktree case). It verifies the worktree environment, syncs with <code>main</code>,
reconciles task status against any PRs merged while you were away, and
Expand Down
31 changes: 16 additions & 15 deletions content/guides/multi-agent/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ test run.
A **git worktree** solves this: it is an independent working directory backed by
the *same* `.git` repository. Each agent gets its own files and its own checked-out
branch, but commits, refs and history are shared. The layout is one primary
checkout plus one sibling directory per agent:
checkout with all agent worktrees project-local under `.worktrees/`:

```text
~/projects/
── scaffold/ # primary checkout (you work here)
├── scaffold-alpha/ # worktree for agent "alpha"
└── scaffold-beta/ # worktree for agent "beta"
scaffold/ # primary checkout (you work here)
── .worktrees/ # agent worktrees (gitignored)
├── alpha/ # worktree for agent "alpha"
└── beta/ # worktree for agent "beta"
```

So worktrees give you **filesystem isolation with a shared object store**:
Expand All @@ -44,20 +44,21 @@ branch.
one parallel agent. Given a name like `alpha`, it:

1. **Normalizes the name** to a lowercase, hyphenated, alphanumeric slug (so
`Agent_1` becomes `agent-1`), then derives the worktree directory as a
*sibling* of the primary repo: `../<repo-name>-<slug>`.
`Agent_1` becomes `agent-1`), then derives the worktree directory project-local
under the primary repo: `.worktrees/<slug>`. It also ensures `.worktrees/` is
gitignored so the worktree's checkout is never accidentally committed.
2. **Creates the workspace branch** `<slug>-workspace` if it does not already
exist :cite[scripts/setup-agent-worktree.sh:40], then adds the worktree on
that branch :cite[scripts/setup-agent-worktree.sh:44]. Re-running for an
exist :cite[scripts/setup-agent-worktree.sh:62], then adds the worktree on
that branch :cite[scripts/setup-agent-worktree.sh:65]. Re-running for an
existing worktree is a safe no-op.
3. **Writes `.scaffold/identity.json`** — the stable identity that build
observability stamps onto every event this worktree records. The script
creates `.scaffold/` :cite[scripts/setup-agent-worktree.sh:52] and, only if no
creates `.scaffold/` :cite[scripts/setup-agent-worktree.sh:73] and, only if no
identity file exists yet, writes `worktree_id` (a UUID), `worktree_label`
(the agent slug), and `created_at`
:cite[scripts/setup-agent-worktree.sh:71].
:cite[scripts/setup-agent-worktree.sh:92].
4. **Re-syncs Beads** with a fail-soft `bd doctor --fix` when a `.beads/`
directory is present :cite[scripts/setup-agent-worktree.sh:88], reconciling the
directory is present :cite[scripts/setup-agent-worktree.sh:109], reconciling the
worktree's Beads git hooks and project config against the installed `bd`
version. (Beads DB sharing is automatic — worktrees discover the main repo's
task DB via git's common directory, so there is nothing for `bd doctor` to
Expand Down Expand Up @@ -148,8 +149,8 @@ footprint small and its branches short. The conflict-prevention rules from

Each agent otherwise follows the standard PR workflow from its own worktree:
branch, commit, push, `gh pr create`, wait for CI, squash-merge. The shared
object store means a merge from `scaffold-alpha` is on `main` for everyone the
moment it lands.
object store means a merge from agent `alpha`'s worktree is on `main` for
everyone the moment it lands.

## Teardown & harvest

Expand Down Expand Up @@ -225,7 +226,7 @@ Coming back to parallel work — a context reset, a paused session, the next
morning — does **not** mean re-running setup. The worktree and its
`identity.json` persist on disk. Instead:

1. Return to the agent's worktree directory (`../<repo>-<agent>`).
1. Return to the agent's worktree directory (`.worktrees/<agent>`).
2. Run **`multi-agent-resume <agent-name>`** (or `single-agent-resume` for the
non-worktree case). It verifies the worktree environment, syncs with `main`,
reconciles task status against any PRs merged while you were away, and
Expand Down
8 changes: 4 additions & 4 deletions content/knowledge/execution/worktree-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Expert knowledge for managing git worktrees to enable parallel multi-agent execu

### Setup

Use `scripts/setup-agent-worktree.sh <agent-name>` to create a worktree at `../<project>-<agent-name>/`. Each agent gets its own isolated working directory and workspace branch.
Use `scripts/setup-agent-worktree.sh <agent-name>` to create a worktree at `.worktrees/<agent-name>/` (project-local). Each agent gets its own isolated working directory and workspace branch.

### Branching Conventions

Expand All @@ -40,12 +40,12 @@ After all agents finish, remove worktrees and prune stale references. Delete mer
scripts/setup-agent-worktree.sh agent-1

# This creates:
# ../<project>-agent-1/ (working directory)
# .worktrees/agent-1/ (working directory, project-local)
# Branch: agent-1-workspace (workspace branch)
```

**What the setup script does:**
1. Creates a new worktree directory adjacent to the main repo
1. Creates a new worktree directory project-local under `.worktrees/`
2. Creates a workspace branch for the agent
3. Sets up the working directory with a clean state
4. Installs dependencies if a package manager is detected
Expand Down Expand Up @@ -154,7 +154,7 @@ git rebase origin/main

```bash
# From the main repository (not from inside the worktree)
git worktree remove ../<project>-agent-1
git worktree remove .worktrees/agent-1
```

**Pruning stale worktree references:**
Expand Down
10 changes: 8 additions & 2 deletions content/pipeline/environment/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ parallel agents, CI pipeline, branch protection, and conflict prevention rules.
- docs/git-workflow.md — branching strategy, commit standards, rebase strategy,
PR workflow (8 sub-steps), task closure, agent crash recovery, branch protection,
conflict prevention, and worktree documentation
- scripts/setup-agent-worktree.sh — permanent worktree creation script
- scripts/setup-agent-worktree.sh — permanent worktree creation script. Create
worktrees project-local at `<repo>/.worktrees/<agent-slug>` (a single,
consistent location — never as repo siblings like `../<repo>-<agent>`). The
script must ensure `.worktrees/` is gitignored before creating the worktree so
the worktree's checkout is never accidentally committed.
- .github/workflows/ci.yml — CI workflow with lint and test jobs
- .github/pull_request_template.md — PR template with task ID format
- CLAUDE.md updated with Committing/PR Workflow, Task Closure, Parallel Sessions,
Expand All @@ -37,7 +41,9 @@ parallel agents, CI pipeline, branch protection, and conflict prevention rules.
- (mvp) Commit format is consistent (Beads: [bd-<id>] type(scope): desc. Non-Beads: type(scope): desc)
- (deep) PR workflow includes all 8 sub-steps (commit, AI review, rebase, push, create,
auto-merge with --delete-branch, watch CI, confirm merge)
- (deep) Worktree script creates permanent worktrees with workspace branches
- (deep) Worktree script creates permanent worktrees with workspace branches at
the project-local path `<repo>/.worktrees/<agent-slug>` and ensures
`.worktrees/` is gitignored
- (deep) If Beads: BEADS_ACTOR environment variable documented for agent identity
- (deep) CI workflow job name matches branch protection context
- (mvp) Branch cleanup documented for both single-agent and worktree-agent variants
Expand Down
16 changes: 9 additions & 7 deletions docs/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ Quality gates run both locally (`make check-all`) and in CI. The CI workflow is

## 7. Advanced: Parallel Agents (Worktrees)

For parallel development, each agent gets a git worktree — an independent working directory sharing the same `.git` repository.
For parallel development, each agent gets a git worktree — an independent working directory sharing the same `.git` repository. Worktrees live project-local under `.worktrees/` for a single, consistent location:

```
~/projects/
── scaffold/ # Main repo
├── scaffold-agent-1/ # Worktree for agent 1
└── scaffold-agent-2/ # Worktree for agent 2
scaffold/ # Main repo
── .worktrees/ # Agent worktrees (gitignored)
├── agent-1/ # Worktree for agent 1
└── agent-2/ # Worktree for agent 2
```

### Creating Worktrees
Expand All @@ -164,7 +164,9 @@ For parallel development, each agent gets a git worktree — an independent work
scripts/setup-agent-worktree.sh <agent-name>
```

This creates `../<repo-name>-<agent-suffix>` with a workspace branch.
This creates `.worktrees/<agent-suffix>` with a workspace branch. The script
also ensures `.worktrees/` is gitignored so worktree checkouts are never
accidentally committed.

### Worktree Workflow

Expand All @@ -178,6 +180,6 @@ Each agent follows the same PR workflow (section 3) from its worktree. Additiona

```bash
git worktree list # List all worktrees
git worktree remove ../scaffold-<agent> # Remove a worktree
git worktree remove .worktrees/<agent> # Remove a worktree
git worktree prune # Prune stale references
```
25 changes: 23 additions & 2 deletions scripts/setup-agent-worktree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,32 @@ if [ -z "$agent_suffix" ]; then
fi

# ─── Resolve paths ──────────────────────────────────────────
# Worktrees live project-local under <repo>/.worktrees/<agent> for a single,
# consistent location (see docs/git-workflow.md §7). This matches the
# superpowers `using-git-worktrees` convention and keeps every agent worktree
# discoverable from one place rather than scattered as repo siblings.

repo_name="$(basename "$REPO_DIR")"
worktree_dir="$(cd "$REPO_DIR/.." && pwd)/${repo_name}-${agent_suffix}"
worktree_dir="$REPO_DIR/.worktrees/${agent_suffix}"
branch_name="${agent_suffix}-workspace"

# ─── Ensure .worktrees/ is gitignored ───────────────────────
# Critical: a project-local worktree dir must be ignored or its contents (a full
# checkout) would show as untracked and could be committed. Add the rule if the
# repo does not already ignore it.
#
# Use the trailing-slash path ('.worktrees/') in the check: a directory-only
# pattern like '.worktrees/' only matches a path git knows to be a directory.
# Without the slash, `git check-ignore` false-negatives here because this runs
# *before* the worktree dir exists, which would append a duplicate ignore rule.
if ! git -C "$REPO_DIR" check-ignore -q .worktrees/ 2>/dev/null; then
gitignore_file="$REPO_DIR/.gitignore"
if [ -f "$gitignore_file" ] && [ -s "$gitignore_file" ] && [ "$(tail -c1 "$gitignore_file")" != "" ]; then
printf '\n' >> "$gitignore_file"
fi
printf '# Git worktrees (scaffold parallel agents)\n.worktrees/\n' >> "$gitignore_file"
echo "Added .worktrees/ to $gitignore_file"
fi

# ─── Create worktree ────────────────────────────────────────

if [ ! -d "$worktree_dir" ]; then
Expand Down
1 change: 1 addition & 0 deletions src/project/gitignore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('ensureScaffoldGitignore', () => {
expect(content).toContain('.scaffold/lock.json')
expect(content).toContain('.scaffold/*.tmp')
expect(content).toContain('.scaffold/**/*.tmp')
expect(content).toContain('.worktrees/')
})

it('updates existing managed block idempotently without changing user content', () => {
Expand Down
4 changes: 4 additions & 0 deletions src/project/gitignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const MANAGED_LINES = [
'.scaffold/lock.json',
'.scaffold/*.tmp',
'.scaffold/**/*.tmp',
// Parallel-agent worktrees live at <repo>/.worktrees/<agent>; ignore so a
// worktree's full checkout is never accidentally committed (see
// scripts/setup-agent-worktree.sh and docs/git-workflow.md §7).
'.worktrees/',
]

const DANGEROUS_RULES = new Set([
Expand Down
36 changes: 30 additions & 6 deletions tests/setup-agent-worktree.bats
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ setup() {
}

teardown() {
# Worktrees now live inside the repo at <repo>/.worktrees/, so removing
# CLONE_DIR (including its .git and worktree metadata) also removes them.
rm -rf "$ORIG_DIR" "$CLONE_DIR"
# Clean up any worktrees that were created
rm -rf "$RESOLVED_TMPDIR"/scaffold-$$-*
}

@test "exits 1 with usage message when no arguments provided" {
Expand All @@ -40,10 +40,34 @@ teardown() {
[[ "$output" == *"Usage:"* ]]
}

@test "creates worktree at expected path" {
@test "creates worktree under <repo>/.worktrees/<name>" {
run "$CLONE_DIR/scripts/setup-agent-worktree.sh" "agent-alpha"
[ "$status" -eq 0 ]
[ -d "$RESOLVED_TMPDIR/scaffold-$$-agent-alpha" ]
[ -d "$CLONE_DIR/.worktrees/agent-alpha" ]
}

@test "does not create a sibling worktree directory" {
run "$CLONE_DIR/scripts/setup-agent-worktree.sh" "agent-alpha"
[ "$status" -eq 0 ]
[ ! -d "$RESOLVED_TMPDIR/scaffold-$$-agent-alpha" ]
}

@test "ensures .worktrees/ is gitignored" {
run "$CLONE_DIR/scripts/setup-agent-worktree.sh" "agent-alpha"
[ "$status" -eq 0 ]
run git -C "$CLONE_DIR" check-ignore -q .worktrees/
[ "$status" -eq 0 ]
}

@test "does not duplicate .worktrees/ when it is already gitignored" {
# .worktrees/ already ignored, but the directory does not exist yet — the
# check must use the trailing-slash form or it false-negatives and appends
# a duplicate rule. Regression guard for that.
printf '.worktrees/\n' > "$CLONE_DIR/.gitignore"
run "$CLONE_DIR/scripts/setup-agent-worktree.sh" "agent-echo"
[ "$status" -eq 0 ]
run grep -c '^\.worktrees/$' "$CLONE_DIR/.gitignore"
[ "$output" = "1" ]
}

@test "idempotent: succeeds if worktree already exists" {
Expand All @@ -60,14 +84,14 @@ teardown() {
@test "normalizes agent name to lowercase with hyphens" {
run "$CLONE_DIR/scripts/setup-agent-worktree.sh" "Agent_Charlie"
[ "$status" -eq 0 ]
[ -d "$RESOLVED_TMPDIR/scaffold-$$-agent-charlie" ]
[ -d "$CLONE_DIR/.worktrees/agent-charlie" ]
}

@test "creates workspace branch for the agent" {
run "$CLONE_DIR/scripts/setup-agent-worktree.sh" "agent-delta"
[ "$status" -eq 0 ]
# Check that the branch exists in the worktree
local worktree_dir="$RESOLVED_TMPDIR/scaffold-$$-agent-delta"
local worktree_dir="$CLONE_DIR/.worktrees/agent-delta"
run git -C "$worktree_dir" branch --show-current
[ "$output" = "agent-delta-workspace" ]
}
Loading