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

Parallel Agents & Worktrees

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:

  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>.
  2. +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.
  3. 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.
  4. 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.
  5. +scripts/setup-agent-worktree.sh:92.
  6. 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:

      -
    1. Return to the agent's worktree directory (../<repo>-<agent>).
    2. +
    3. Return to the agent's worktree directory (.worktrees/<agent>).
    4. 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" ] }