Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

#### Symbols, tests and the viewer

- `codegraph index <path>` now rebuilds exactly the project you name. A path without an index of its own used to be silently resolved to the nearest initialized parent — a monorepo container, an ancestor with a stale index — and rebuilt under a normal "Done"; it is now an error that names that parent and how to index the path on its own. (#1524)

- **Files under an `e2e/` directory count as tests.** Their calls no longer appear as production callers in Steps, dead-code and test badges.

- **Production code under a `samples` or `examples` package path is no longer treated as test code.** A Kotlin or Java project whose package path runs through `com/google/samples/…` (Now in Android, for one) had nearly every file counted as a fixture, so the Map opened on `build-logic`, the entry points hid the app, and dead-code and test badges were wrong. Only the project layout above a `src/` folder decides now; the package path below it never does.
Expand Down
64 changes: 64 additions & 0 deletions __tests__/cli-index-explicit-path.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
15 changes: 13 additions & 2 deletions src/bin/codegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand Down