feat(mcp): line numbers in explore output + per-file cluster fixes (#188)
* feat(mcp): line numbers in explore output + per-file cluster fixes Follow-up to #185. Three changes to codegraph_explore: 1. Source sections now carry cat -n style line-number prefixes (<num>\t<code>), so the agent can cite file:line straight from the payload instead of re-Reading the file just to recover a line number. Isolated A/B: the no-line-numbers arm spent 2 Reads + a grep to find a line number the line-numbered arm cited with zero follow-up calls. Payload cost ~3-5%. Toggle off with CODEGRAPH_EXPLORE_LINENUMS=0. 2. Per-file cluster selection now ranks clusters containing a query entry point ahead of dense declaration blocks. Density-only ranking buried the relevant methods (perform/didCreateURLRequest/task in Alamofire's Session.swift) under the top-of-file class header + property list. 3. Whole-file "envelope" nodes (a class/struct/etc. spanning >50% of the file) are excluded from clustering. The Session class spans ~1,400 lines; keeping it collapsed every method into one giant cluster that tail-trimmed down to just the class header, hiding the methods. Net vs the 0.7.10 baseline, line numbers on: Alamofire -60%, Excalidraw -32%, VS Code -12% per explore call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): language-neutral omission markers in explore output The gap separator and the two tail-trim markers used C-style `//` comments, which aren't comments in Python, Ruby, etc. Switch to plain `... (gap) ...` / `... (trimmed) ...` so they read correctly inside any language's fenced source block. With line numbers on, the line-number jump already corroborates a gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): language-neutral truncation marker in codegraph_context Sibling to the explore marker fix: codegraph_context's code-block truncation used a C-style `// ... truncated ...`. Switch to `... (truncated) ...` so it reads correctly in any language's fenced source block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): bump version to 0.7.11 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
93e53e7c69
commit
2c1a314b84
@@ -188,4 +188,47 @@ describe('codegraph_explore output respects the adaptive budget', () => {
|
||||
const sourceFollowsHeader = text.indexOf('### Source Code') > 0;
|
||||
expect(hasRelationships || sourceFollowsHeader).toBe(true);
|
||||
});
|
||||
|
||||
it('prefixes source lines with line numbers by default (cat -n style)', async () => {
|
||||
delete process.env.CODEGRAPH_EXPLORE_LINENUMS;
|
||||
const result = await handler.execute('codegraph_explore', { query: 'Session method helper' });
|
||||
const text = result.content?.[0]?.text ?? '';
|
||||
// At least one fenced source line should look like `<digits>\t<code>`.
|
||||
expect(/\n\d+\t/.test(text)).toBe(true);
|
||||
});
|
||||
|
||||
it('omits line numbers when CODEGRAPH_EXPLORE_LINENUMS=0', async () => {
|
||||
process.env.CODEGRAPH_EXPLORE_LINENUMS = '0';
|
||||
try {
|
||||
const result = await handler.execute('codegraph_explore', { query: 'Session method helper' });
|
||||
const text = result.content?.[0]?.text ?? '';
|
||||
// The synthetic source has no tab-prefixed numeric lines of its own,
|
||||
// so none should appear when the toggle is off.
|
||||
expect(/\n\d+\t(?:export| )/.test(text)).toBe(false);
|
||||
} finally {
|
||||
delete process.env.CODEGRAPH_EXPLORE_LINENUMS;
|
||||
}
|
||||
});
|
||||
|
||||
it('uses language-neutral omission markers (no C-style // in the output)', async () => {
|
||||
// The gap/trimmed separators must not assume `//` is a comment — that's
|
||||
// wrong in Python, Ruby, etc. They render inside fenced source blocks.
|
||||
const result = await handler.execute('codegraph_explore', { query: 'Session method helper' });
|
||||
const text = result.content?.[0]?.text ?? '';
|
||||
expect(text).not.toContain('// ... (gap)');
|
||||
expect(text).not.toContain('// ... trimmed');
|
||||
});
|
||||
|
||||
it('does not collapse a whole-file class into just its header (envelope filter)', async () => {
|
||||
// The synthetic `Session` class spans the entire file. Without the
|
||||
// envelope filter it would form one giant cluster that tail-trims to
|
||||
// the class declaration, hiding the methods. Confirm real method bodies
|
||||
// make it into the output. Regression guard for the #185 follow-up.
|
||||
const result = await handler.execute('codegraph_explore', { query: 'Session method helper' });
|
||||
const text = result.content?.[0]?.text ?? '';
|
||||
// A method body line (`methodN(arg: string)`) should appear, not just
|
||||
// the `export class Session {` opener.
|
||||
const hasMethodBody = /method\d+\(arg: string\)/.test(text);
|
||||
expect(hasMethodBody).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user