Skip to content

Commit 832f906

Browse files
colbymchenryclaude
andauthored
feat(index): default-ignore dependency/build/cache dirs (colbymchenry#407) (colbymchenry#417)
node_modules and other dependency/build/cache dirs were indexed whenever a project lacked a .gitignore excluding them (common in non-git projects), flooding context/search with third-party symbols. Added a built-in default-ignore set spanning every supported language/framework (node_modules, vendor, dist, build, target, .venv, __pycache__, Pods, .next, etc.), applied UNIFORMLY in both the git and non-git enumeration paths — including to tracked files, since committing/vendoring a dependency dir doesn't make it project code. The only opt-in is an explicit .gitignore negation (e.g. !vendor/). First-party-prone names (packages, lib, app, bin, src, deps, env) are deliberately excluded from the list. Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 4a94696 commit 832f906

2 files changed

Lines changed: 98 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2222
reconciles anything that changed while it wasn't running (e.g. a `git pull` from
2323
the terminal), so the first query reflects the current code instead of a stale
2424
snapshot — rather than waiting for the next live edit.
25+
- **Dependency, build, and cache directories are now excluded by default**
26+
`node_modules`, `vendor`, `dist`, `build`, `target`, `.venv`, `__pycache__`,
27+
`Pods`, `.next`, and the like across every supported language/framework — so
28+
`context` and `search` reflect your code instead of third-party noise, even in a
29+
project with no `.gitignore` (#407). The defaults apply uniformly, including to
30+
committed files (vendoring a dependency into the repo doesn't make it project
31+
code). To index one anyway, add a `.gitignore` negation (e.g. `!vendor/`).
32+
First-party-prone names like `packages/`, `lib/`, `app/`, and `src/` are never on
33+
the default list.
2534

2635
## [0.9.4] - 2026-05-24
2736

src/extraction/index.ts

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,84 @@ export function hashContent(content: string): string {
9999
*/
100100
const MAX_FILE_SIZE = 1024 * 1024;
101101

102+
/**
103+
* Directory names that are dependency, build, cache, or tooling output across the
104+
* languages/frameworks CodeGraph supports — curated from the canonical
105+
* github/gitignore templates. Excluded by default so the graph reflects your code,
106+
* not third-party noise, without requiring a `.gitignore` (issue #407). The
107+
* exclusion applies uniformly (git or not, tracked or not); the only opt-in is an
108+
* explicit `.gitignore` negation (e.g. `!vendor/`). First-party-prone or generic
109+
* names (`packages`, `lib`, `app`, `bin`, `src`, `deps`, `env`, `tmp`, `storage`,
110+
* `Library`) are deliberately NOT listed, to avoid ever hiding real source.
111+
*
112+
* Only dirs that actually contain *indexable source* (or are enormous) earn a slot
113+
* — IDE/state dirs like `.idea`/`.vs` are omitted because CodeGraph indexes only
114+
* recognized source extensions, so they produce no symbols regardless.
115+
*/
116+
const DEFAULT_IGNORE_DIRS: ReadonlySet<string> = new Set([
117+
// JS / TS — dependency directories
118+
'node_modules', 'bower_components', 'jspm_packages', 'web_modules',
119+
'.yarn', '.pnpm-store',
120+
// JS / TS — framework & bundler build / cache / deploy output
121+
'.next', '.nuxt', '.svelte-kit', '.turbo', '.vite', '.parcel-cache', '.angular',
122+
'.docusaurus', 'storybook-static', '.vinxi', '.nitro', 'out-tsc',
123+
'.vercel', '.netlify', '.wrangler',
124+
// Build output (common across ecosystems)
125+
'dist', 'build', 'out', '.output',
126+
// Test / coverage
127+
'coverage', '.nyc_output',
128+
// Python
129+
'__pycache__', '__pypackages__', '.venv', 'venv', '.pixi', '.pdm-build',
130+
'.mypy_cache', '.pytest_cache', '.ruff_cache', '.tox', '.nox', '.hypothesis',
131+
'.ipynb_checkpoints', '.eggs',
132+
// Rust / JVM (Maven, Gradle, Scala)
133+
'target', '.gradle',
134+
// .NET
135+
'obj',
136+
// Vendored deps (Go, PHP/Composer, Ruby/Bundler)
137+
'vendor',
138+
// Swift / iOS
139+
'.build', 'Pods', 'Carthage', 'DerivedData', '.swiftpm',
140+
// Dart / Flutter
141+
'.dart_tool', '.pub-cache',
142+
// Native (Android NDK, C/C++ deps)
143+
'.cxx', '.externalNativeBuild', 'vcpkg_installed',
144+
// Scala tooling
145+
'.bloop', '.metals',
146+
// Lua / Luau (LuaRocks)
147+
'lua_modules', '.luarocks',
148+
// Delphi / RAD Studio IDE backups (duplicate .pas source — would double-count)
149+
'__history', '__recovery',
150+
// Generic cache
151+
'.cache',
152+
]);
153+
154+
/** Gitignore-style patterns for the `ignore` matcher: the dirs above plus a few globs. */
155+
const DEFAULT_IGNORE_PATTERNS: string[] = [
156+
...Array.from(DEFAULT_IGNORE_DIRS, (d) => `${d}/`),
157+
'*.egg-info/', // Python packaging metadata
158+
'cmake-build-*/', // CLion / CMake build trees
159+
'bazel-*/', // Bazel output symlink trees
160+
];
161+
162+
/**
163+
* An `ignore` matcher seeded with the built-in defaults, merged with the project's
164+
* root .gitignore so a negation there (e.g. `!vendor/`) overrides a default. Shared
165+
* by both enumeration paths so behavior is identical with or without git — and so
166+
* the defaults apply to tracked files too (committing a dependency dir doesn't make
167+
* it project code; the explicit `.gitignore` negation is the only opt-in).
168+
*/
169+
function buildDefaultIgnore(rootDir: string): Ignore {
170+
const ig = ignore().add(DEFAULT_IGNORE_PATTERNS);
171+
try {
172+
const rootGitignore = path.join(rootDir, '.gitignore');
173+
if (fs.existsSync(rootGitignore)) ig.add(fs.readFileSync(rootGitignore, 'utf-8'));
174+
} catch {
175+
// Unreadable root .gitignore — the built-in defaults still apply.
176+
}
177+
return ig;
178+
}
179+
102180
/**
103181
* Collect git-visible files (tracked + untracked, .gitignore-respected) from the
104182
* git repository rooted at `repoDir`, adding each to `files` with `prefix`
@@ -183,7 +261,11 @@ function getGitVisibleFiles(rootDir: string): Set<string> | null {
183261

184262
const files = new Set<string>();
185263
collectGitFiles(rootDir, '', files);
186-
return files;
264+
// Apply built-in default ignores uniformly — to tracked files too, since
265+
// committing a dependency/build dir doesn't make it project code. A
266+
// `.gitignore` negation (e.g. `!vendor/`) is the explicit opt-in. (issue #407)
267+
const ig = buildDefaultIgnore(rootDir);
268+
return new Set([...files].filter((f) => !ig.ignores(f)));
187269
} catch {
188270
return null;
189271
}
@@ -358,7 +440,9 @@ function scanDirectoryWalk(
358440
visitedDirs.add(realDir);
359441

360442
// This directory's own .gitignore (if present) applies to everything below it.
361-
const own = loadIgnore(dir);
443+
// The root's .gitignore is already merged into the seeded base matcher (so a
444+
// negation there can override a built-in default), so skip it here.
445+
const own = dir === rootDir ? null : loadIgnore(dir);
362446
const active = own ? [...matchers, own] : matchers;
363447

364448
let entries: fs.Dirent[];
@@ -411,7 +495,9 @@ function scanDirectoryWalk(
411495
}
412496
}
413497

414-
walk(rootDir, []);
498+
// Seed a base matcher with the built-in default ignores (merged with the root
499+
// .gitignore so a negation can override). Nested .gitignores still layer per-dir.
500+
walk(rootDir, [{ dir: rootDir, ig: buildDefaultIgnore(rootDir) }]);
415501
return files;
416502
}
417503

0 commit comments

Comments
 (0)