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
+6
View File
@@ -46,9 +46,15 @@ const WASM_GRAMMAR_FILES: Record<GrammarLanguage, string> = {
export const EXTENSION_MAP: Record<string, Language> = {
'.ts': 'typescript',
'.tsx': 'tsx',
// ESM/CJS TypeScript module extensions — parsed as TS (no JSX). (#366)
'.mts': 'typescript',
'.cts': 'typescript',
'.js': 'javascript',
'.mjs': 'javascript',
'.cjs': 'javascript',
// SAP HANA XS Classic server-side JavaScript. (#556)
'.xsjs': 'javascript',
'.xsjslib': 'javascript',
'.jsx': 'jsx',
'.py': 'python',
'.pyw': 'python',
+14 -14
View File
@@ -198,32 +198,32 @@ function collectGitFiles(repoDir: string, prefix: string, files: Set<string>): v
// Without this, monorepos using submodules index 0 files. (See issue #147.)
// Note: --recurse-submodules only supports -c/--cached and --stage modes — it
// can't be combined with -o, so untracked files are gathered separately below.
const tracked = execFileSync('git', ['ls-files', '-c', '--recurse-submodules'], gitOpts);
for (const line of tracked.split('\n')) {
const trimmed = line.trim();
if (trimmed) {
files.add(normalizePath(prefix + trimmed));
}
// -z gives NUL-separated, unquoted output so non-ASCII (e.g. CJK) paths
// survive verbatim. Without it git octal-escapes and double-quotes such paths
// (the core.quotepath default), and the quoted form never matches a real file
// on disk → those files are silently dropped from the index. (#541)
const tracked = execFileSync('git', ['ls-files', '-z', '-c', '--recurse-submodules'], gitOpts);
for (const rel of tracked.split('\0')) {
if (rel) files.add(normalizePath(prefix + rel));
}
// Untracked files (submodules manage their own untracked state). Embedded git
// repos surface here as a single "subdir/" entry that git refuses to descend
// into — recurse into those as their own repos so their source gets indexed.
const untracked = execFileSync('git', ['ls-files', '-o', '--exclude-standard'], gitOpts);
for (const line of untracked.split('\n')) {
const trimmed = line.trim();
if (!trimmed) continue;
if (trimmed.endsWith('/')) {
const untracked = execFileSync('git', ['ls-files', '-z', '-o', '--exclude-standard'], gitOpts);
for (const rel of untracked.split('\0')) {
if (!rel) continue;
if (rel.endsWith('/')) {
// git only emits a trailing-slash directory entry for an embedded repo.
// Guard with a .git check anyway, and skip anything else exactly as git
// itself skips it (we never descend into a non-repo opaque dir).
const childDir = path.join(repoDir, trimmed);
const childDir = path.join(repoDir, rel);
if (fs.existsSync(path.join(childDir, '.git'))) {
collectGitFiles(childDir, prefix + trimmed, files);
collectGitFiles(childDir, prefix + rel, files);
}
continue;
}
files.add(normalizePath(prefix + trimmed));
files.add(normalizePath(prefix + rel));
}
}
+6 -2
View File
@@ -56,8 +56,12 @@ export const goExtractor: LanguageExtractor = {
if (!receiver) return undefined;
// Find the type identifier inside the receiver
const text = getNodeText(receiver, source);
// Extract type name from patterns like "(sl *Type)", "(sl Type)", "(*Type)", "(Type)"
const match = text.match(/\*?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/);
// Extract type name from "(sl *Type)", "(sl Type)", "(*Type)", "(Type)" and
// generic receivers "(s *Stack[T])". Anchor on the opening "(" and skip an
// optional receiver var name; the old `name)`-anchored pattern never matched
// the `[T])` suffix, so generic-type methods were orphaned from their type
// (no struct→method `contains` edge). (#583)
const match = text.match(/\(\s*(?:[A-Za-z_]\w*\s+)?\*?\s*([A-Za-z_]\w*)/);
return match?.[1];
},
};
+13 -1
View File
@@ -630,7 +630,19 @@ export class TreeSitterExtractor {
}
}
}
if (name === '<anonymous>') return; // Skip anonymous functions
if (name === '<anonymous>') {
// Don't emit a node for the anonymous wrapper itself, but still visit its
// body: AMD/RequireJS and CommonJS module wrappers (`define([], function(){…})`,
// `(function(){…})()`) hold named inner functions and calls that would
// otherwise be lost — the dispatcher set skipChildren, so nothing else
// descends into this subtree. (#528)
const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
?? getChildByField(node, this.extractor.bodyField);
if (body) {
this.visitFunctionBody(body, '');
}
return;
}
// Check for misparse artifacts (e.g. C++ macros causing "namespace detail" functions)
// Skip the node but still visit the body for calls and structural nodes