A git worktree nested in a project (e.g. Claude Code's gitignored
`.claude/worktrees/<name>/`) was swept into the index as an embedded repo: its
`.git` is a FILE pointing into the host repo's `.git/worktrees/`, and embedded-
repo discovery treated any `.git` (file or directory) as a distinct repo to
index. Each worktree then duplicated the entire graph — one report went from
~1,850 files to 24,533, with search/explore flooded by stale copies.
classifyGitDir() now distinguishes:
- `.git` directory -> embedded clone, index (#193/#514/#622, unchanged)
- `.git` file → worktrees/ -> worktree, skip (#848)
- `.git` file → modules/ -> submodule, index (unchanged)
Applied at both embedded-repo entry points: findNestedGitRepos discovery (which
also covers the sync/change-detection path) and the untracked-subdir recursion
in collectGitFiles.
Verified: the reproduction drops from 6 files / betaHelper×3 to 3 files / ×1,
with a genuine embedded clone and submodules still indexed. Regression test added.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
A workspace holding several independent git repos failed two ways:
- Enumeration: a super-repo whose .gitignore hides its child repos
(/packages/) indexed 0 of their files — git never lists ignored dirs,
and the #193 embedded-repo recursion only fired for UNTRACKED dirs.
Gitignored embedded repos are now discovered (ignored-dirs listing +
bounded .git search) and enumerated by their own git ls-files.
- Change detection: git status in the parent says nothing about embedded
repos (untracked OR ignored), so codegraph sync missed every child
change. Status now recurses per embedded repo.
ScopeIgnore is the new single source of truth for indexer + watcher
scope: parent rules for ordinary paths, the child repo's own rules for
paths inside it, built-in defaults uniformly on full paths (a git repo
inside node_modules is an npm git-dependency, not project code), and
ancestors of embedded roots are never pruned (the Linux per-directory
watcher must descend to reach them).
Non-git workspace roots already worked via the per-directory gitignore
walk — locked in by test.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>