From 64b47ad1ace8630fb12b56c8a0ff8193eb1b28d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1lcan=20=C3=87ak=C4=B1r?= Date: Thu, 3 Sep 2026 19:28:24 +0300 Subject: [PATCH 1/3] chore(worktree): put worktrees under .claude and carry the overrides into them Worktrees used to sit beside the checkout as -, which put them in the workspace directory next to the real repositories and left EnterWorktree unusable, since that tool writes to a fixed .claude/worktrees/. Pattern proven on fluttersdk/magic_starter#124. --- .gitignore | 5 +++++ .worktreeinclude | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 .worktreeinclude diff --git a/.gitignore b/.gitignore index f5164d7..33fb766 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,8 @@ pubspec_overrides.yaml # first `flutter pub get` writes a correct lock against the hosted constraints # in pubspec.yaml. pubspec.lock + +# Worktrees Claude Code creates for parallel sessions and isolated subagents. +# Only this subdirectory: the rest of `.claude/` is tracked, because the rules +# under it are part of the repository. +.claude/worktrees/ diff --git a/.worktreeinclude b/.worktreeinclude new file mode 100644 index 0000000..1c9f3cf --- /dev/null +++ b/.worktreeinclude @@ -0,0 +1,21 @@ +# Gitignored files copied into every worktree Claude Code creates, for `--worktree`, +# for `EnterWorktree`, and for a subagent with `isolation: worktree`. +# +# A worktree is a fresh checkout, so `pubspec_overrides.yaml` is absent from it, and +# its absence fails silently rather than loudly: the siblings resolve from pub.dev +# instead of the working trees beside this one, `flutter pub get` succeeds, and the +# suite then passes against the PUBLISHED packages while the diff under review is of +# the local ones. An unreleased sibling API is where that bites. +# +# `.gitignore` syntax, and only files that match AND are gitignored are copied, so a +# tracked file can never be duplicated by this list. +# +# The paths inside `pubspec_overrides.yaml` have to be ABSOLUTE for this to work. +# Worktrees live under `.claude/worktrees/`, so a relative `../magic` resolves +# to `.claude/worktrees/magic`, which does not exist, and version solving fails on +# the first path dependency. +# +# NOT processed when a WorktreeCreate hook replaces the default git logic; such a +# hook has to copy these itself. + +pubspec_overrides.yaml From 7b00dc794a4cfcc6d602e7fd8897849eda35ec72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1lcan=20=C3=87ak=C4=B1r?= Date: Thu, 3 Sep 2026 20:01:36 +0300 Subject: [PATCH 2/3] fix(worktree): copy pubspec_overrides.yaml in bin/check, not only on the Claude Code path --- .github/copilot-instructions.md | 4 +++- .gitignore | 6 +++++- AGENTS.md | 4 +++- bin/check | 8 +++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9c8c7cc..07fb6f7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -24,7 +24,8 @@ That override file is also why a green local run can be a red CI: with it, this ## One task, one worktree, one PR - Branch from `main` as `feature/` or `fix/`, and work in a worktree under `.claude/worktrees/`. -- A fresh worktree lacks three gitignored files it needs in order to run: `pubspec_overrides.yaml`, `backend/.env`, `.artisan/plugins.json`. `bin/check` copies them from the main worktree on first run; do not hand-author them. +- A fresh worktree lacks three gitignored files it needs in order to run: `pubspec_overrides.yaml`, `backend/.env`, `.artisan/plugins.json`. Two mechanisms copy them from the main worktree and neither covers every path on its own: `.worktreeinclude` runs when Claude Code creates the worktree, `bin/check` on its first run there. Do not hand-author them. +- The paths inside `pubspec_overrides.yaml` must be ABSOLUTE. A worktree lives at `.claude/worktrees/`, so the conventional relative `../magic` resolves to `.claude/worktrees/magic` and version solving fails on the first path dependency. That failure is loud, unlike the one above it. - Land the work as a PR. A suite that only ran on one machine is not evidence. ## Verifying a change @@ -69,6 +70,7 @@ This file is canonical. Everything else either points at it or is generated from | `.github/copilot-instructions.md` | generated copy, for Copilot's repo-wide instructions and its PR review bot | | `.claude/rules/.md` | path-scoped rules with `paths:` frontmatter; Claude Code loads one when you touch a matching file | | `.github/instructions/.instructions.md` | generated from those rules with `applyTo:` frontmatter, so Copilot's PR review applies the same rules | +| `.worktreeinclude` | which gitignored files a new worktree receives, and why each one fails silently without it. Consulted by Claude Code when it creates the worktree, not by `git worktree add` | | `docs/verification-loop.md` | how a change is proven: static, visual, and dusk E2E | Other agent infrastructure: skills under `.claude/skills/` (`frontend-design`, `make-component`, `design-first-workflow`), the `component-visual-reviewer` reviewer under `.claude/agents/`, design-culture references under `docs/design-culture/` (Apple HIG, Material 3, Refactoring UI, WCAG, motion, Wind responsive), and the component inventory at `docs/component-registry.md`. `.mcp.json` wires `./bin/fsa mcp:serve` as a project MCP server, which is the same dusk, telescope and artisan surface `docs/verification-loop.md` drives from the shell, offered as tools instead. That entry is the POSIX shape, since `bin/fsa` is a `sh` script: on Windows, run `dart run :dispatcher mcp:install` to rewrite it into a shape that machine can spawn. diff --git a/.gitignore b/.gitignore index 33fb766..0859ece 100644 --- a/.gitignore +++ b/.gitignore @@ -66,5 +66,9 @@ pubspec.lock # Worktrees Claude Code creates for parallel sessions and isolated subagents. # Only this subdirectory: the rest of `.claude/` is tracked, because the rules -# under it are part of the repository. +# under it are part of the repository.# +# They sit inside the working tree rather than beside it, so `git clean -xdf` here +# wipes a live worktree's contents while `.git/worktrees/` survives, leaving a +# registration that needs `git worktree prune`. A worktree's `.git` is a file rather +# than a directory, so git's nested-repo guard does not stop it. .claude/worktrees/ diff --git a/AGENTS.md b/AGENTS.md index 08ada0d..17c27ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,8 @@ That override file is also why a green local run can be a red CI: with it, this ## One task, one worktree, one PR - Branch from `main` as `feature/` or `fix/`, and work in a worktree under `.claude/worktrees/`. -- A fresh worktree lacks three gitignored files it needs in order to run: `pubspec_overrides.yaml`, `backend/.env`, `.artisan/plugins.json`. `bin/check` copies them from the main worktree on first run; do not hand-author them. +- A fresh worktree lacks three gitignored files it needs in order to run: `pubspec_overrides.yaml`, `backend/.env`, `.artisan/plugins.json`. Two mechanisms copy them from the main worktree and neither covers every path on its own: `.worktreeinclude` runs when Claude Code creates the worktree, `bin/check` on its first run there. Do not hand-author them. +- The paths inside `pubspec_overrides.yaml` must be ABSOLUTE. A worktree lives at `.claude/worktrees/`, so the conventional relative `../magic` resolves to `.claude/worktrees/magic` and version solving fails on the first path dependency. That failure is loud, unlike the one above it. - Land the work as a PR. A suite that only ran on one machine is not evidence. ## Verifying a change @@ -67,6 +68,7 @@ This file is canonical. Everything else either points at it or is generated from | `.github/copilot-instructions.md` | generated copy, for Copilot's repo-wide instructions and its PR review bot | | `.claude/rules/.md` | path-scoped rules with `paths:` frontmatter; Claude Code loads one when you touch a matching file | | `.github/instructions/.instructions.md` | generated from those rules with `applyTo:` frontmatter, so Copilot's PR review applies the same rules | +| `.worktreeinclude` | which gitignored files a new worktree receives, and why each one fails silently without it. Consulted by Claude Code when it creates the worktree, not by `git worktree add` | | `docs/verification-loop.md` | how a change is proven: static, visual, and dusk E2E | Other agent infrastructure: skills under `.claude/skills/` (`frontend-design`, `make-component`, `design-first-workflow`), the `component-visual-reviewer` reviewer under `.claude/agents/`, design-culture references under `docs/design-culture/` (Apple HIG, Material 3, Refactoring UI, WCAG, motion, Wind responsive), and the component inventory at `docs/component-registry.md`. `.mcp.json` wires `./bin/fsa mcp:serve` as a project MCP server, which is the same dusk, telescope and artisan surface `docs/verification-loop.md` drives from the shell, offered as tools instead. That entry is the POSIX shape, since `bin/fsa` is a `sh` script: on Windows, run `dart run :dispatcher mcp:install` to rewrite it into a shape that machine can spawn. diff --git a/bin/check b/bin/check index 7d59c3b..77cc126 100755 --- a/bin/check +++ b/bin/check @@ -87,7 +87,13 @@ bootstrap_ignored_files() { [ "$main_root" = "$REPO_ROOT" ] && return 0 [ -d "$main_root" ] || return 0 - for file in backend/.env .artisan/plugins.json backend/public/build; do + # `pubspec_overrides.yaml` is here as well as in `.worktreeinclude`, and the + # duplication is deliberate: that file is only consulted when Claude Code creates + # the worktree, so a plain `git worktree add` followed by `bin/check` would get + # the backend files and no overrides. That combination is the silent one, since + # the siblings then resolve from pub.dev and the suite passes against the + # published packages rather than the working trees the diff is about. + for file in pubspec_overrides.yaml backend/.env .artisan/plugins.json backend/public/build; do if [ ! -e "$REPO_ROOT/$file" ] && [ -e "$main_root/$file" ]; then mkdir -p "$(dirname "$REPO_ROOT/$file")" cp -R "$main_root/$file" "$REPO_ROOT/$file" From 0f142351069d44d7f90449c76bc408ea42a68875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1lcan=20=C3=87ak=C4=B1r?= Date: Thu, 3 Sep 2026 20:31:04 +0300 Subject: [PATCH 3/3] fix(worktree): git clean -xdf skips a worktree, only -xdff does not The comment claimed plain -xdf wipes a live worktree because its .git is a file rather than a directory. Measured on a scratch repo: the dry run prints "Skipping repository .claude/worktrees/slug", the worktree survives -xdf with its untracked files intact, and only -xdff removes it and leaves the registration prunable. The guard keys on the gitlink, not on the form of .git. Also splits the stray # that joined the two comment blocks. --- .gitignore | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 0859ece..5a973bf 100644 --- a/.gitignore +++ b/.gitignore @@ -66,9 +66,11 @@ pubspec.lock # Worktrees Claude Code creates for parallel sessions and isolated subagents. # Only this subdirectory: the rest of `.claude/` is tracked, because the rules -# under it are part of the repository.# -# They sit inside the working tree rather than beside it, so `git clean -xdf` here -# wipes a live worktree's contents while `.git/worktrees/` survives, leaving a -# registration that needs `git worktree prune`. A worktree's `.git` is a file rather -# than a directory, so git's nested-repo guard does not stop it. +# under it are part of the repository. +# +# They sit inside the working tree rather than beside it. `git clean -xdf` leaves them +# alone: git's nested-repository guard resolves a gitfile too, so it reports "Skipping +# repository .claude/worktrees/" and the contents survive. `git clean -xdff` does +# not skip them, and wipes a live worktree while `.git/worktrees/` survives, +# leaving a registration that `git worktree prune` then has to clear. .claude/worktrees/