feat: zero-config indexing driven by .gitignore (#283) (#285)

Remove .codegraph/config.json and the entire config surface. CodeGraph now
indexes every file whose extension maps to a supported language and respects
.gitignore everywhere — git repos via git itself, non-git projects via the
`ignore` library (root + nested .gitignore files, the same way git does).

- Remove CodeGraphConfig/DEFAULT_CONFIG, src/config.ts, and the public config
  API (the `config` option on init, getConfig/updateConfig/getConfigPath).
- Derive the source-file allowlist from EXTENSION_MAP (isSourceFile); maxFileSize
  is now a constant. Drop the .codegraphignore marker.
- Behavior change: committed, non-gitignored dirs (vendor/, a committed dist/)
  are now indexed — .gitignore is the single source of truth.

Earlier inert fields (languages, frameworks, extractDocstrings, trackCallSites,
customPatterns) and their dead helpers are removed as part of this.

Resolves #283.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-05-21 18:06:02 -05:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 5b71a89574
commit f6772dac7c
16 changed files with 223 additions and 996 deletions
+18 -72
View File
@@ -15,9 +15,7 @@ import * as os from 'os';
import { FileLock } from '../src/utils';
import CodeGraph from '../src/index';
import { ToolHandler, tools } from '../src/mcp/tools';
import { shouldIncludeFile, scanDirectory } from '../src/extraction';
import { shouldIncludeFile as configShouldInclude } from '../src/config';
import { CodeGraphConfig, DEFAULT_CONFIG } from '../src/types';
import { scanDirectory, isSourceFile } from '../src/extraction';
import { DatabaseConnection, getDatabasePath } from '../src/db';
import { QueryBuilder } from '../src/db/queries';
@@ -298,58 +296,24 @@ describe('Atomic Writes', () => {
});
});
describe('Glob Matching (picomatch)', () => {
const makeConfig = (include: string[], exclude: string[]): CodeGraphConfig => ({
...DEFAULT_CONFIG,
rootDir: '/test',
include,
exclude,
describe('Source file detection (isSourceFile)', () => {
it('selects files by supported extension', () => {
expect(isSourceFile('src/index.ts')).toBe(true);
expect(isSourceFile('src/deep/nested/file.ts')).toBe(true);
expect(isSourceFile('src/component.tsx')).toBe(true);
expect(isSourceFile('lib/util.js')).toBe(true);
expect(isSourceFile('src/main.py')).toBe(true);
});
it('should match standard glob patterns in extraction', () => {
const config = makeConfig(['**/*.ts'], ['node_modules/**']);
expect(shouldIncludeFile('src/index.ts', config)).toBe(true);
expect(shouldIncludeFile('src/deep/nested/file.ts', config)).toBe(true);
expect(shouldIncludeFile('src/index.js', config)).toBe(false);
expect(shouldIncludeFile('node_modules/lib/index.ts', config)).toBe(false);
it('rejects unsupported extensions and extensionless files', () => {
expect(isSourceFile('src/component.css')).toBe(false);
expect(isSourceFile('README.md')).toBe(false);
expect(isSourceFile('Makefile')).toBe(false);
expect(isSourceFile('.gitignore')).toBe(false);
});
it('should match standard glob patterns in config', () => {
const config = makeConfig(['**/*.py'], ['__pycache__/**']);
expect(configShouldInclude('src/main.py', config)).toBe(true);
expect(configShouldInclude('src/main.ts', config)).toBe(false);
expect(configShouldInclude('__pycache__/module.py', config)).toBe(false);
});
it('should handle complex glob patterns correctly', () => {
const config = makeConfig(['src/**/*.{ts,tsx}', 'lib/**/*.js'], []);
expect(shouldIncludeFile('src/component.ts', config)).toBe(true);
expect(shouldIncludeFile('src/component.tsx', config)).toBe(true);
expect(shouldIncludeFile('lib/util.js', config)).toBe(true);
expect(shouldIncludeFile('src/component.css', config)).toBe(false);
});
it('should handle patterns that previously caused ReDoS', () => {
// This pattern would cause catastrophic backtracking with hand-rolled regex
const evilPattern = '**/**/**/**/**/**/**/**/**/**/**/**/**/**/a';
const config = makeConfig([evilPattern], []);
const start = Date.now();
// This should return quickly, not hang
shouldIncludeFile('x/x/x/x/x/x/x/x/x/x/x/x/x/x/b', config);
const elapsed = Date.now() - start;
// Should complete in under 100ms, not seconds
expect(elapsed).toBeLessThan(100);
});
it('should handle dot files correctly', () => {
const config = makeConfig(['**/*.ts'], []);
expect(shouldIncludeFile('.hidden/index.ts', config)).toBe(true);
it('matches regardless of leading dot directories', () => {
expect(isSourceFile('.hidden/index.ts')).toBe(true);
});
});
@@ -464,15 +428,9 @@ describe('Symlink Cycle Detection', () => {
return;
}
const config: CodeGraphConfig = {
...DEFAULT_CONFIG,
rootDir: tempDir,
include: ['**/*.ts'],
exclude: [],
};
// This should complete without hanging
const files = scanDirectory(tempDir, config);
const files = scanDirectory(tempDir);
// Should find the real file but not loop infinitely
expect(files).toContain('src/index.ts');
@@ -496,14 +454,8 @@ describe('Symlink Cycle Detection', () => {
return;
}
const config: CodeGraphConfig = {
...DEFAULT_CONFIG,
rootDir: tempDir,
include: ['**/*.ts'],
exclude: [],
};
const files = scanDirectory(tempDir, config);
const files = scanDirectory(tempDir);
// Should find files from both the real dir and via the symlink
// But deduplicate since they resolve to the same real path
@@ -521,15 +473,9 @@ describe('Symlink Cycle Detection', () => {
return;
}
const config: CodeGraphConfig = {
...DEFAULT_CONFIG,
rootDir: tempDir,
include: ['**/*.ts'],
exclude: [],
};
// Should not throw
const files = scanDirectory(tempDir, config);
const files = scanDirectory(tempDir);
expect(files).toContain('src/valid.ts');
});
});