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 @@ -25,6 +25,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### New Features

- `codegraph_explore` finds code containing storage keys, command-line flags, and event names; rebuild existing indexes to enable these matches.

- **Codex and Astra read project guidance from `AGENTS.md`.** The canonical agent guide now lives in `AGENTS.md` (with a nested `docs/AGENTS.md` for long validation notes); `CLAUDE.md` is a thin `@AGENTS.md` wrapper for Claude Code. Codex/Astra no longer miss the old CLAUDE-only instructions.

- **A big screen's picture stops wrapping into a column.** How wide a screen's lines run before they wrap was worked out with a formula, and the formula was wrong for the way these pictures are actually drawn: a part of a screen spends lines on its own structure — a step that fires things gets a line to itself, and what it fires starts another — so estimating the lines from the boxes alone badly undercounted them, and one screen's 98 boxes wrapped into a 4,356px column. Laying a picture out is cheap and exact, so the widths are now simply tried and the one that comes out closest to the shape of a window is kept. Across one app's 51 screens the tallest picture went from 4,356px to 3,796px, total height fell 8%, and — because a shorter picture is also a picture whose lines have less far to go — lines running over other boxes fell by a third and lines crossing each other went from 13 to 5.
Expand Down
3 changes: 2 additions & 1 deletion __tests__/kernel-retry-materialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe.skipIf(!kernelBuilt)('kernel buffer-transport storage (#1541)', () => {
it('storeExtractionResult persists the decoded nodes of a raw kernel result', async () => {
const source =
'def target_fn(root, mission_path):\n' +
' return (root, mission_path)\n' +
' return (root, mission_path, "adapter.mission")\n' +
'\n' +
'class Adapter:\n' +
' def adapt(self):\n' +
Expand Down Expand Up @@ -93,6 +93,7 @@ describe.skipIf(!kernelBuilt)('kernel buffer-transport storage (#1541)', () => {
expect(nodes.length).toBe(raw!.counts.nodes);
expect(nodes.map((n) => n.name)).toContain('target_fn');
expect(nodes.map((n) => n.name)).toContain('Adapter');
expect(cg.findLiteralSeedIds('"adapter.mission"').map(id => cg.getNode(id)?.name)).toEqual(['target_fn']);
});
});

Expand Down
78 changes: 78 additions & 0 deletions __tests__/literal-capture.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { describe, it, expect } from 'vitest';
import type { Node } from '../src/types';
import { captureLiterals, isSeedLiteral, seedLiteralsInQuery } from '../src/extraction/literal-capture';

function node(id: string, kind: Node['kind'], startLine: number, endLine: number): Node {
return {
id, kind, name: id, qualifiedName: id, filePath: 'src/a.ts', language: 'typescript',
startLine, endLine, startColumn: 0, endColumn: Number.MAX_SAFE_INTEGER, updatedAt: 0,
};
}

describe('isSeedLiteral', () => {
it('keeps storage keys, flags, dotted names and paths', () => {
for (const v of ['bompus_custom_ds_players', '--start', '-v', 'draft.pick', 'api/v1/users', 'ns:event'])
expect(isSeedLiteral(v), v).toBe(v !== '-v');
});
it('drops plain words, prose, and values that start with a separator', () => {
for (const v of ['ready', 'Error', 'not found', './utils', '../x', '', 'a_b'])
expect(isSeedLiteral(v), v).toBe(false);
});
});

describe('seedLiteralsInQuery', () => {
it('finds quoted spans and bare runs, stripping surrounding punctuation', () => {
expect(seedLiteralsInQuery('who writes "bompus_custom_ds_players" (via --start)?'))
.toEqual(['bompus_custom_ds_players', '--start']);
});
it('returns nothing for a symbol-anchored question', () => {
expect(seedLiteralsInQuery('callers of espnPlayerKey in shared')).toEqual([]);
});
});

describe('captureLiterals', () => {
const source = [
`import { x } from './utils/helpers';`, // 1: path starts with '.', never qualifies
`const KEY = 'bompus_custom_ds_players';`, // 2: top level → file node
`export function save() {`, // 3
` storage.set('bompus_custom_ds_players', 1);`, // 4
` log('ready');`, // 5: plain word
` emit(\`draft.pick\`); emit(\`draft.\${n}\`);`, // 6: second one interpolates
`}`, // 7
`export class Boot { start() { run('--start'); } }`,// 8
].join('\n');

it('attributes each literal to the innermost enclosing symbol, else the file', () => {
const file = node('file:src/a.ts', 'file', 1, 8);
const save = node('save', 'function', 3, 7);
const boot = node('Boot', 'class', 8, 8);
const start = node('start', 'method', 8, 8);
const imp = node('./utils/helpers', 'import', 1, 1);
const nodes = [file, imp, save, boot, start];
captureLiterals(source, nodes);
expect(file.literals).toEqual(['bompus_custom_ds_players']);
expect(save.literals).toEqual(['bompus_custom_ds_players', 'draft.pick']);
expect(start.literals).toEqual(['--start']);
expect(boot.literals).toBeUndefined();
expect(imp.literals).toBeUndefined();
});

it('dedupes per node and caps at 32', () => {
const many = Array.from({ length: 40 }, (_, i) => `k('key_${i}'); k('key_${i}');`).join('\n');
const fn = node('f', 'function', 1, 40);
captureLiterals(many, [node('file:src/a.ts', 'file', 1, 40), fn]);
expect(fn.literals).toHaveLength(32);
expect(new Set(fn.literals).size).toBe(32);
});

it('uses UTF-16 columns to distinguish same-line siblings after non-ASCII source', () => {
const prefix = '/* café 😀 */ ';
const first = "function writer(){return 'cache.write';}";
const second = "function reader(){return 'cache.read';}";
const writer = { ...node('writer', 'function', 1, 1), startColumn: prefix.length, endColumn: (prefix + first).length };
const reader = { ...node('reader', 'function', 1, 1), startColumn: (prefix + first).length, endColumn: (prefix + first + second).length };
captureLiterals(prefix + first + second, [writer, reader]);
expect(writer.literals).toEqual(['cache.write']);
expect(reader.literals).toEqual(['cache.read']);
});
});
59 changes: 59 additions & 0 deletions __tests__/literal-compiled-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import type CodeGraph from '../src';
import type { QueryBuilder } from '../src/db/queries';

const built = path.join(__dirname, '..', 'dist', 'index.js');
const kernel = path.join(__dirname, '..', 'codegraph-kernel', 'prebuilds', `${process.platform}-${process.arch}`, 'codegraph-kernel.node');

// Source-mode suites cannot exercise the compiled parse/store worker boundary.
describe.runIf(fs.existsSync(built))('literal persistence through compiled indexing', () => {
let dir: string | undefined;
let cg: CodeGraph | undefined;
afterEach(() => {
cg?.destroy();
cg = undefined;
if (dir) fs.rmSync(dir, { recursive: true, force: true });
dir = undefined;
vi.unstubAllEnvs();
});

for (const mode of ['native-worker', 'native-main', 'wasm-worker']) {
it.runIf(mode === 'wasm-worker' || fs.existsSync(kernel))(`${mode}: fresh, unchanged, and migrated indexes retain exact owners`, async () => {
vi.stubEnv('CODEGRAPH_PARSE_WORKERS', '1');
vi.stubEnv('CODEGRAPH_KERNEL', mode === 'wasm-worker' ? '0' : '1');
vi.stubEnv('CODEGRAPH_NO_STORE_WORKER', mode === 'native-main' ? '1' : '0');
const BuiltCodeGraph: typeof CodeGraph = require(built).default;
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-literal-compiled-'));
fs.writeFileSync(path.join(dir, 'cache.py'), "def persist():\n return 'cache.write'\n");
fs.writeFileSync(path.join(dir, 'siblings.ts'), "/* café 😀 */ export function writer(){return 'sibling.write';} export function reader(){return 'sibling.read';}\n");
cg = await BuiltCodeGraph.init(dir, { silent: true });
const names = (key: string) => cg!.findLiteralSeedIds(key).map(id => cg!.getNode(id)?.name);
const assertOwners = () => {
expect(names('cache.write')).toEqual(['persist']);
expect(names('sibling.write')).toEqual(['writer']);
expect(names('sibling.read')).toEqual(['reader']);
};
await cg.indexAll();
assertOwners();
await cg.indexAll();
assertOwners();

// A schema-only migration has no literal data; unchanged source still needs backfill.
const queries = (cg as unknown as { queries: QueryBuilder }).queries;
queries.replaceLiteralsForFile('cache.py', []);
queries.replaceLiteralsForFile('siblings.ts', []);
queries.setMetadata('indexed_with_extraction_version', '26');
expect(cg.isIndexStale()).toBe(true);
await cg.indexAll();
assertOwners();
expect(cg.isIndexStale()).toBe(false);
fs.rmSync(path.join(dir, 'cache.py'));
await cg.indexAll();
expect(names('cache.write')).toEqual([]);
expect(names('sibling.write')).toEqual(['writer']);
});
}
});
Loading