From 021694ffb01c70e6ec21bfdbb32daa9edd038b0d Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Wed, 20 May 2026 20:45:48 -0600 Subject: [PATCH] fix(cli): accept -d/--db on build to match every other DB-scoped command Build silently wrote to `/.codegraph/graph.db` with no way to override while stats/query/where/watch all honor --db. The asymmetry blocked dogfooding workflows that need an isolated DB and surprised users who expected the flag to work everywhere. Wire opts.db through BuildGraphOpts.dbPath; setupPipeline resolves it absolute and falls back to the previous default. Mirrors watch's pattern (#987). Closes #1177. --- src/cli/commands/build.ts | 2 ++ src/domain/graph/builder/pipeline.ts | 4 ++- src/types.ts | 6 ++++ tests/integration/build.test.ts | 42 ++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/cli/commands/build.ts b/src/cli/commands/build.ts index 25b5d849b..6e85ae246 100644 --- a/src/cli/commands/build.ts +++ b/src/cli/commands/build.ts @@ -7,6 +7,7 @@ export const command: CommandDefinition = { name: 'build [dir]', description: 'Parse repo and build graph in .codegraph/graph.db', options: [ + ['-d, --db ', 'Path to graph.db (default: /.codegraph/graph.db)'], ['--no-incremental', 'Force full rebuild (ignore file hashes)'], ['--no-ast', 'Skip AST node extraction (calls, new, string, regex, throw, await)'], ['--no-complexity', 'Skip complexity metrics computation'], @@ -23,6 +24,7 @@ export const command: CommandDefinition = { engine: engine as EngineMode, dataflow: opts.dataflow as boolean, cfg: opts.cfg as boolean, + dbPath: opts.db ? path.resolve(opts.db as string) : undefined, }); }, }; diff --git a/src/domain/graph/builder/pipeline.ts b/src/domain/graph/builder/pipeline.ts index 566d3ecf5..b18d3c473 100644 --- a/src/domain/graph/builder/pipeline.ts +++ b/src/domain/graph/builder/pipeline.ts @@ -167,7 +167,9 @@ function loadAliases(ctx: PipelineContext): void { function setupPipeline(ctx: PipelineContext): void { ctx.rootDir = path.resolve(ctx.rootDir); - ctx.dbPath = path.join(ctx.rootDir, '.codegraph', 'graph.db'); + ctx.dbPath = ctx.opts.dbPath + ? path.resolve(ctx.opts.dbPath) + : path.join(ctx.rootDir, '.codegraph', 'graph.db'); // Detect whether native engine is available. const enginePref = ctx.opts.engine || 'auto'; diff --git a/src/types.ts b/src/types.ts index 542b81bc0..63c51c7eb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1072,6 +1072,12 @@ export interface BuildGraphOpts { */ exclude?: string[]; skipRegistry?: boolean; + /** + * Override the graph.db location. Resolved absolute. When omitted, the + * pipeline writes to `/.codegraph/graph.db` — same default as + * `findDbPath` for every other DB-scoped command. + */ + dbPath?: string; } /** Build timing result from buildGraph. */ diff --git a/tests/integration/build.test.ts b/tests/integration/build.test.ts index e75ae447f..cab845a0c 100644 --- a/tests/integration/build.test.ts +++ b/tests/integration/build.test.ts @@ -153,6 +153,48 @@ describe('buildGraph', () => { }); }); +describe('buildGraph with custom dbPath (issue #1177)', () => { + let customDir: string, customDbPath: string; + + beforeAll(async () => { + customDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-custom-db-')); + for (const [name, content] of Object.entries(FIXTURE_FILES)) { + fs.writeFileSync(path.join(customDir, name), content); + } + customDbPath = path.join(os.tmpdir(), `codegraph-custom-${Date.now()}.db`); + await buildGraph(customDir, { skipRegistry: true, dbPath: customDbPath }); + }); + + afterAll(() => { + if (customDir) fs.rmSync(customDir, { recursive: true, force: true }); + if (customDbPath && fs.existsSync(customDbPath)) { + fs.rmSync(customDbPath, { force: true }); + // Clean up sidecar WAL/SHM files + for (const ext of ['-wal', '-shm', '.lock']) { + const sidecar = `${customDbPath}${ext}`; + if (fs.existsSync(sidecar)) fs.rmSync(sidecar, { force: true }); + } + } + }); + + test('writes DB to the custom path, not /.codegraph/graph.db', () => { + expect(fs.existsSync(customDbPath)).toBe(true); + expect(fs.existsSync(path.join(customDir, '.codegraph', 'graph.db'))).toBe(false); + }); + + test('custom DB contains expected nodes and edges', () => { + const db = new Database(customDbPath, { readonly: true }); + const files = db + .prepare("SELECT file FROM nodes WHERE kind = 'file'") + .all() + .map((r) => r.file); + db.close(); + expect(files).toContain('math.js'); + expect(files).toContain('utils.js'); + expect(files).toContain('index.js'); + }); +}); + describe('three-tier incremental builds', () => { let incrDir: string, incrDbPath: string;