diff --git a/.gitignore b/.gitignore index ff20ed00c..98b3d6d4a 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/content/guides/multi-agent/index.html b/content/guides/multi-agent/index.html index 75cbed651..8f0b35ef9 100644 --- a/content/guides/multi-agent/index.html +++ b/content/guides/multi-agent/index.html @@ -1380,11 +1380,11 @@
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:
~/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 .worktrees/:
+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:
agents never overwrite each other's working files, but a PR merged from one
@@ -1398,20 +1398,21 @@
Setup — setup-agent-worktree.shalpha, it:
- 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.
- Creates the workspace branch
<slug>-workspace if it does not already
-exist scripts/setup-agent-worktree.sh:40, then adds the worktree on
-that branch scripts/setup-agent-worktree.sh:44. Re-running for an
+exist scripts/setup-agent-worktree.sh:62, then adds the worktree on
+that branch scripts/setup-agent-worktree.sh:65. Re-running for an
existing worktree is a safe no-op.
- Writes
.scaffold/identity.json — the stable identity that build
observability stamps onto every event this worktree records. The script
-creates .scaffold/ scripts/setup-agent-worktree.sh:52 and, only if no
+creates .scaffold/ 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
-scripts/setup-agent-worktree.sh:71.
+scripts/setup-agent-worktree.sh:92.
- Re-syncs Beads with a fail-soft
bd doctor --fix when a .beads/
-directory is present scripts/setup-agent-worktree.sh:88, reconciling the
+directory is present 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
@@ -1467,8 +1468,8 @@ Working in parallel
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
When an agent's work is merged, retire its worktree. The single command for this
is scripts/teardown-agent-worktree.sh <worktree-path>, and the order of
@@ -1529,7 +1530,7 @@ Resuming after a break
morning — does not mean re-running setup. The worktree and its
identity.json persist on disk. Instead:
-- Return to the agent's worktree directory (
../<repo>-<agent>).
+- Return to the agent's worktree directory (
.worktrees/<agent>).
- 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
diff --git a/content/guides/multi-agent/index.md b/content/guides/multi-agent/index.md
index 623813b08..b51d59b95 100644
--- a/content/guides/multi-agent/index.md
+++ b/content/guides/multi-agent/index.md
@@ -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**:
@@ -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: `../-`.
+ `Agent_1` becomes `agent-1`), then derives the worktree directory project-local
+ under the primary repo: `.worktrees/`. It also ensures `.worktrees/` is
+ gitignored so the worktree's checkout is never accidentally committed.
2. **Creates the workspace branch** `-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
@@ -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
@@ -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 (`../-`).
+1. Return to the agent's worktree directory (`.worktrees/`).
2. Run **`multi-agent-resume `** (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
diff --git a/content/knowledge/execution/worktree-management.md b/content/knowledge/execution/worktree-management.md
index e5bbef75e..9f3be5c58 100644
--- a/content/knowledge/execution/worktree-management.md
+++ b/content/knowledge/execution/worktree-management.md
@@ -17,7 +17,7 @@ Expert knowledge for managing git worktrees to enable parallel multi-agent execu
### Setup
-Use `scripts/setup-agent-worktree.sh ` to create a worktree at `../-/`. Each agent gets its own isolated working directory and workspace branch.
+Use `scripts/setup-agent-worktree.sh ` to create a worktree at `.worktrees//` (project-local). Each agent gets its own isolated working directory and workspace branch.
### Branching Conventions
@@ -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:
-# ../-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
@@ -154,7 +154,7 @@ git rebase origin/main
```bash
# From the main repository (not from inside the worktree)
-git worktree remove ../-agent-1
+git worktree remove .worktrees/agent-1
```
**Pruning stale worktree references:**
diff --git a/content/pipeline/environment/git-workflow.md b/content/pipeline/environment/git-workflow.md
index c81275a8c..ff74e4305 100644
--- a/content/pipeline/environment/git-workflow.md
+++ b/content/pipeline/environment/git-workflow.md
@@ -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 `/.worktrees/` (a single,
+ consistent location — never as repo siblings like `../-`). 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,
@@ -37,7 +41,9 @@ parallel agents, CI pipeline, branch protection, and conflict prevention rules.
- (mvp) Commit format is consistent (Beads: [bd-] 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 `/.worktrees/` 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
diff --git a/docs/git-workflow.md b/docs/git-workflow.md
index 361b0973f..6868dcc06 100644
--- a/docs/git-workflow.md
+++ b/docs/git-workflow.md
@@ -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
@@ -164,7 +164,9 @@ For parallel development, each agent gets a git worktree — an independent work
scripts/setup-agent-worktree.sh
```
-This creates `../-` with a workspace branch.
+This creates `.worktrees/` with a workspace branch. The script
+also ensures `.worktrees/` is gitignored so worktree checkouts are never
+accidentally committed.
### Worktree Workflow
@@ -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- # Remove a worktree
+git worktree remove .worktrees/ # Remove a worktree
git worktree prune # Prune stale references
```
diff --git a/scripts/setup-agent-worktree.sh b/scripts/setup-agent-worktree.sh
index 8290b1d4a..345af7760 100755
--- a/scripts/setup-agent-worktree.sh
+++ b/scripts/setup-agent-worktree.sh
@@ -28,11 +28,32 @@ if [ -z "$agent_suffix" ]; then
fi
# ─── Resolve paths ──────────────────────────────────────────
+# Worktrees live project-local under /.worktrees/ 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
diff --git a/src/project/gitignore.test.ts b/src/project/gitignore.test.ts
index 266a33270..5f03f36a9 100644
--- a/src/project/gitignore.test.ts
+++ b/src/project/gitignore.test.ts
@@ -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', () => {
diff --git a/src/project/gitignore.ts b/src/project/gitignore.ts
index 96afdb0d7..2b5e4de40 100644
--- a/src/project/gitignore.ts
+++ b/src/project/gitignore.ts
@@ -11,6 +11,10 @@ const MANAGED_LINES = [
'.scaffold/lock.json',
'.scaffold/*.tmp',
'.scaffold/**/*.tmp',
+ // Parallel-agent worktrees live at /.worktrees/; 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([
diff --git a/tests/setup-agent-worktree.bats b/tests/setup-agent-worktree.bats
index 042660f60..fe3c06133 100644
--- a/tests/setup-agent-worktree.bats
+++ b/tests/setup-agent-worktree.bats
@@ -29,9 +29,9 @@ setup() {
}
teardown() {
+ # Worktrees now live inside the repo at /.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" {
@@ -40,10 +40,34 @@ teardown() {
[[ "$output" == *"Usage:"* ]]
}
-@test "creates worktree at expected path" {
+@test "creates worktree under /.worktrees/" {
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" {
@@ -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" ]
}