fix: issue-triage quick wins (extraction, MCP probes, gitignore, CJK, impact) (#654)

Batch of small, localized fixes from an open-issue triage:

- .codegraph/.gitignore now ignores everything but itself, so the database,
  daemon.pid, sockets, and logs stop showing up in git status (#492, #484)
- MCP server answers resources/list and prompts/list with empty lists instead
  of -32601, clearing scary log lines in opencode/Codex (#621)
- index SAP HANA .xsjs/.xsjslib as JavaScript (#556) and TS .mts/.cts (#366)
- visit anonymous AMD/CommonJS/IIFE wrapper bodies so their inner functions and
  calls are indexed instead of coming up empty (#528)
- batch the changed-file lookup so a huge first sync no longer hits
  "too many SQL variables" (#540)
- list files with `git ls-files -z` so non-ASCII/CJK paths survive
  core.quotepath and are no longer silently skipped (#541)
- attach Go methods on generic receivers (*T[P]) to their type (#583, RC1)
- impact no longer climbs the structural `contains` edge, so a leaf symbol
  stops dragging in its sibling methods (#536)
- README: explicit `codegraph install` step, run in a new shell (#631)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-02 17:49:15 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 2a22f9f55a
commit ddb1a8f72d
15 changed files with 197 additions and 45 deletions
+47 -1
View File
@@ -10,7 +10,7 @@ import * as path from 'path';
import * as os from 'os';
import { CodeGraph } from '../src';
import { extractFromSource, scanDirectory } from '../src/extraction';
import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars, loadAllGrammars } from '../src/extraction/grammars';
import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars, loadAllGrammars, isSourceFile } from '../src/extraction/grammars';
import { normalizePath } from '../src/utils';
beforeAll(async () => {
@@ -4387,3 +4387,49 @@ void helperFunction(int count) {
expect(getSupportedLanguages()).toContain('objc');
});
});
describe('Regression: issue-specific extraction fixes', () => {
it('indexes inner functions of an anonymous AMD/CommonJS module wrapper (#528)', () => {
const code = `
define(['dep'], function (dep) {
function innerHelper(x) { return x + 1; }
function compute(y) { return innerHelper(y); }
return { compute: compute };
});
`;
const result = extractFromSource('amd-module.js', code);
const fns = result.nodes.filter((n) => n.kind === 'function').map((n) => n.name);
expect(fns).toContain('innerHelper');
expect(fns).toContain('compute');
});
it('attaches Go methods on generic receivers to their type (#583)', () => {
const code = `
package main
type Stack[T any] struct { items []T }
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s Stack[T]) Len() int { return len(s.items) }
`;
const result = extractFromSource('stack.go', code);
const methods = result.nodes.filter((n) => n.kind === 'method');
expect(methods.find((m) => m.name === 'Push')?.qualifiedName).toBe('Stack::Push');
expect(methods.find((m) => m.name === 'Len')?.qualifiedName).toBe('Stack::Len');
});
it('indexes new module extensions: .mts/.cts (TS) and .xsjs/.xsjslib (JS) (#366, #556)', () => {
expect(isSourceFile('mod.mts')).toBe(true);
expect(isSourceFile('mod.cts')).toBe(true);
expect(isSourceFile('service.xsjs')).toBe(true);
expect(isSourceFile('lib.xsjslib')).toBe(true);
expect(detectLanguage('mod.mts')).toBe('typescript');
expect(detectLanguage('service.xsjs')).toBe('javascript');
// End-to-end: a .mts file is parsed as TS, a .xsjs file as JS.
const ts = extractFromSource('mod.mts', 'export function hello(): number { return 1; }');
expect(ts.nodes.find((n) => n.name === 'hello' && n.kind === 'function')).toBeDefined();
const js = extractFromSource('service.xsjs', 'function handleRequest() { return 1; }');
expect(js.nodes.find((n) => n.name === 'handleRequest' && n.kind === 'function')).toBeDefined();
});
});
+4 -1
View File
@@ -54,7 +54,10 @@ describe('CodeGraph Foundation', () => {
expect(fs.existsSync(gitignorePath)).toBe(true);
const content = fs.readFileSync(gitignorePath, 'utf-8');
expect(content).toContain('*.db');
// Ignore everything in .codegraph/ except this file itself, so transient
// files (db, daemon.pid, sockets, logs) never show up in git. (#492, #484)
expect(content).toContain('*');
expect(content).toContain('!.gitignore');
cg.close();
});
+13
View File
@@ -309,6 +309,19 @@ export { main };
expect(impact.nodes.size).toBeGreaterThan(0);
expect(impact.nodes.has(formatValue.id)).toBe(true);
});
it('does not drag in sibling members via the structural contains edge (#536)', () => {
const getName = cg.getNodesByKind('method').find((n) => n.name === 'getName');
const derived = cg.getNodesByKind('class').find((n) => n.name === 'DerivedClass');
expect(getName).toBeDefined();
expect(derived).toBeDefined();
const impact = cg.getImpactRadius(getName!.id, 3);
// The containing class must NOT be pulled into impact just because it
// *contains* getName — climbing that contains edge would re-expand every
// sibling method and explode impact for a leaf symbol. (#536)
expect(impact.nodes.has(derived!.id)).toBe(false);
});
});
describe('findPath()', () => {
+26
View File
@@ -154,4 +154,30 @@ describe('MCP initialize handshake (issue #172)', () => {
expect(json.id).toBe(0);
expect(json.result.serverInfo.name).toBe('codegraph');
}, 20000);
it('answers resources/list and prompts/list with empty lists, not -32601 (issue #621)', async () => {
child = spawnServer(tempDir);
const events = tagStreams(child);
sendInitialize(child, tempDir);
await waitFor(events, (e) => e.stream === 'stdout', 5000); // initialize reply
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'resources/list', params: {} }) + '\n');
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'prompts/list', params: {} }) + '\n');
const replyFor = async (id: number) => {
const ev = await waitFor(events, (e) => {
if (e.stream !== 'stdout') return false;
try { return JSON.parse(e.text).id === id; } catch { return false; }
}, 5000);
return JSON.parse(ev.text);
};
const resources = await replyFor(1);
expect(resources.error).toBeUndefined();
expect(resources.result.resources).toEqual([]);
const prompts = await replyFor(2);
expect(prompts.error).toBeUndefined();
expect(prompts.result.prompts).toEqual([]);
}, 15000);
});