Răsfoiți Sursa

fix(cli): index <path> never rebuilds an ancestor instead (#1524) (#1797)

An explicit path names the project to rebuild; it is not a hint to go
looking for one. resolveProjectPath's upward walk is right for a query run
from a subdirectory, but for a full re-index it silently rebuilt the
nearest initialized parent's graph when <path> had no index of its own.
Refuse with the parent's path and the way to index <path> itself; a bare
`codegraph index` still resolves from cwd as before.

Fixes #1524

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Colby Mchenry 1 zi în urmă
părinte
comite
4fd816b610
3 a modificat fișierele cu 79 adăugiri și 2 ștergeri
  1. 2 0
      CHANGELOG.md
  2. 64 0
      __tests__/cli-index-explicit-path.test.ts
  3. 13 2
      src/bin/codegraph.ts

+ 2 - 0
CHANGELOG.md

@@ -137,6 +137,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 #### MCP / indexing
 
+- `codegraph index <path>` now refuses uninitialized paths and names the nearest initialized parent instead of silently rebuilding it; thanks @danusha2345. (#1524, #1689)
+
 - Sync now recovers the same connections as a clean index after interrupted reference resolution, including inherited calls and callbacks that previously stayed missing. (#1577)
 
 - `codegraph_explore` now re-serves source to fresh subagents and after context compaction, with cross-call dedup available only through an explicit `CODEGRAPH_EXPLORE_DEDUP=1` opt-in; thanks @danusha2345. (#1620, #1624)

+ 64 - 0
__tests__/cli-index-explicit-path.test.ts

@@ -0,0 +1,64 @@
+/**
+ * `codegraph index <path>` rebuilds <path>, never an ancestor (#1524).
+ *
+ * The command used to resolve an uninitialized <path> upward to the nearest
+ * initialized parent and rebuild THAT under a normal "Done" — so
+ * `codegraph index child` from a monorepo re-indexed the whole container and
+ * never said so. An explicit path that is not initialized is now an error that
+ * names the ancestor it would have picked.
+ */
+import { describe, it, expect, beforeAll, afterAll } from 'vitest';
+import { spawnSync } from 'child_process';
+import * as fs from 'fs';
+import * as os from 'os';
+import * as path from 'path';
+import { CodeGraph } from '../src';
+
+const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
+
+function run(cwd: string, args: string[]) {
+  const r = spawnSync(process.execPath, [BIN, ...args], {
+    cwd,
+    encoding: 'utf-8',
+    env: { ...process.env, CODEGRAPH_NO_DAEMON: '1', CODEGRAPH_WASM_RELAUNCHED: '1', NO_COLOR: '1' },
+  });
+  return { status: r.status, out: (r.stdout ?? '') + (r.stderr ?? '') };
+}
+
+describe('codegraph index <path> (#1524)', () => {
+  let root: string;
+  let parent: string;
+  let child: string;
+
+  beforeAll(async () => {
+    root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-index-path-'));
+    parent = path.join(root, 'parent');
+    child = path.join(parent, 'child');
+    fs.mkdirSync(child, { recursive: true });
+    fs.writeFileSync(path.join(parent, 'p.py'), 'def parent_only():\n    return 1\n');
+    fs.writeFileSync(path.join(child, 'c.py'), 'def child_only():\n    return 2\n');
+    const cg = CodeGraph.initSync(parent);
+    await cg.indexAll();
+    cg.close();
+  });
+
+  afterAll(() => {
+    fs.rmSync(root, { recursive: true, force: true });
+  });
+
+  it('refuses an explicit path that has no index of its own, naming the ancestor it would have rebuilt', () => {
+    const before = fs.statSync(path.join(parent, '.codegraph', 'codegraph.db')).mtimeMs;
+    const r = run(root, ['index', child, '--quiet']);
+    expect(r.status).toBe(1);
+    expect(r.out).toContain(`not initialized in ${child}`);
+    expect(r.out).toContain(parent);
+    // The parent's index was not touched.
+    expect(fs.statSync(path.join(parent, '.codegraph', 'codegraph.db')).mtimeMs).toBe(before);
+    expect(fs.existsSync(path.join(child, '.codegraph'))).toBe(false);
+  });
+
+  it('rebuilds the explicit path when it is initialized, and a bare `index` still resolves upward from a subdirectory', () => {
+    expect(run(root, ['index', parent, '--quiet']).status).toBe(0);
+    expect(run(child, ['index', '--quiet']).status).toBe(0);
+  });
+});

+ 13 - 2
src/bin/codegraph.ts

@@ -791,7 +791,13 @@ program
   .option('-q, --quiet', 'Suppress progress output')
   .option('-v, --verbose', 'Show detailed worker lifecycle and memory info')
   .action(async (pathArg: string | undefined, options: { force?: boolean; quiet?: boolean; verbose?: boolean }) => {
-    const projectPath = resolveProjectPath(pathArg);
+    // An EXPLICIT path names the project to rebuild — it is never a hint to go
+    // looking for one. resolveProjectPath walks up to the nearest initialized
+    // ancestor, which is right for `codegraph query` run from a subdirectory,
+    // but for a full re-index it silently rebuilt the parent's graph under a
+    // normal "Done" when <path> had no index of its own (#1524). Only a bare
+    // `codegraph index` (cwd) may resolve upward.
+    const projectPath = pathArg ? path.resolve(pathArg) : resolveProjectPath();
 
     try {
       // Don't (re)index your home directory / a filesystem root (#845). --force
@@ -804,7 +810,12 @@ program
 
       if (!isInitialized(projectPath)) {
         error(`CodeGraph not initialized in ${projectPath}`);
-        info('Run "codegraph init" first');
+        const ancestor = pathArg ? resolveProjectPath(pathArg) : projectPath;
+        if (ancestor !== projectPath) {
+          info(`The nearest initialized project is ${ancestor} — pass that path to rebuild it, or run "codegraph init" in ${projectPath} to index it on its own.`);
+        } else {
+          info('Run "codegraph init" first');
+        }
         process.exit(1);
       }