Files
codegraph/__tests__/node-file-view.test.ts
T
7175dc456c feat(mcp): steer agents to codegraph during implementation + file-view node mode (#733)
* feat(mcp): steer agents to codegraph during implementation, not just Q&A

Two changes targeting agents that reach for Read during edits instead of codegraph:

1. Reframe the agent-facing steering (server-instructions + codegraph_node/explore
   descriptions): drop "consult BEFORE ... not during"; position codegraph_node as
   the Read upgrade for a named symbol (verbatim current on-disk source, safe to
   Edit from, + caller/callee trail), explore PRIMARY / node SECONDARY, with the
   "cached intelligence — better context, fewer tokens" framing.

2. File-view mode: codegraph_node now accepts a `file` with no `symbol` and returns
   that file's symbol map + graph role (its dependents), plus verbatim bodies with
   includeCode — so it can displace a path-keyed Read, not just a symbol lookup.
   Resolves a path or basename; dedups nested members; budget-capped.

To be A/B'd on an implementation task before shipping (per the retrieval doctrine:
steering changes must be measured, not assumed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(changelog): note codegraph_node file-view + implementation steering

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:47:23 -04:00

70 lines
2.7 KiB
TypeScript

/**
* codegraph_node FILE-VIEW mode: a bare `file` (no `symbol`) returns that file's
* symbol map + graph role (dependents), and verbatim bodies with includeCode —
* a Read replacement for a source file that also surfaces the blast radius.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import CodeGraph from '../src/index';
import { ToolHandler } from '../src/mcp/tools';
describe('codegraph_node file-view (Read replacement)', () => {
let dir: string;
let cg: CodeGraph;
let h: ToolHandler;
beforeEach(async () => {
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-fileview-'));
fs.mkdirSync(path.join(dir, 'src'));
fs.writeFileSync(
path.join(dir, 'src', 'a.ts'),
'export function helper(x: number) {\n return x + 1;\n}\nexport class Widget {\n build() { return helper(1); }\n}\n',
);
fs.writeFileSync(
path.join(dir, 'src', 'b.ts'),
"import { helper } from './a';\nexport function useHelper() { return helper(2); }\n",
);
cg = CodeGraph.initSync(dir, { config: { include: ['**/*.ts'], exclude: [] } });
await cg.indexAll();
h = new ToolHandler(cg);
});
afterEach(() => {
if (cg) cg.close();
fs.rmSync(dir, { recursive: true, force: true });
});
const text = async (args: Record<string, unknown>): Promise<string> =>
(await h.execute('codegraph_node', args)).content.map((c) => c.text).join('\n');
it("a bare file (no symbol) returns the file's symbols + dependents", async () => {
const out = await text({ file: 'a.ts' });
expect(out).toContain('src/a.ts');
expect(out).toContain('helper');
expect(out).toContain('Widget');
expect(out).toMatch(/depended on by 1 file/i);
expect(out).toContain('src/b.ts'); // the dependent file (blast radius)
});
it('resolves by basename and returns verbatim bodies with includeCode', async () => {
const out = await text({ file: 'a.ts', includeCode: true });
expect(out).toContain('return x + 1'); // helper body
expect(out).toContain('class Widget'); // class body, verbatim
// It must NOT steer the agent back to Read — it is the Read replacement.
expect(out.toLowerCase()).not.toContain('read `src/a.ts`');
});
it('still works as a normal symbol lookup (no regression)', async () => {
const out = await text({ symbol: 'helper', includeCode: true });
expect(out).toContain('helper');
expect(out).toContain('return x + 1');
});
it('a miss returns a helpful message, not a crash', async () => {
const out = await text({ file: 'does-not-exist.ts' });
expect(out).toMatch(/no indexed file matches/i);
});
});