@@ -99,6 +99,84 @@ export function hashContent(content: string): string {
9999 */
100100const 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