fix(resolution): stop "Resolving refs" wedge on theme-vendoring repos; add exclude config + index watchdogs (#999) (#1009)
Three fixes for a repo that commits a large JS/TS theme/SDK (Metronic under static/, ~1,600 tracked files): 1. A SECOND "Resolving refs" quadratic that #915 didn't cover. #915 capped import-name collisions; this caps method-name collisions (init/update/render re-declared on every widget), which flow through matchMethodCall Strategy 3 and findBestMatch instead. New AMBIGUOUS_NAME_CEILING (default 500, env CODEGRAPH_AMBIGUOUS_NAME_CEILING): above it the fuzzy strategies decline rather than score K candidates — no proximity score can pick the one true target among thousands anyway. Resolving drops from O(K^2) to linear in refs (e.g. 900-file synthetic: 28.7s -> 3.4s), edge counts unchanged, and the cap never fires on normal repos (max real method-collision ~40). 2. A new `exclude` array in codegraph.json keeps git-TRACKED paths out of the index, which .gitignore can't do (enumeration is `git ls-files`). Mirrors the existing includeIgnored plumbing across the git, sync, and non-git-walk paths. 3. `index`/`init` now install the #850 liveness + #277 ppid watchdogs (which were serve-only), so a wedged or orphaned indexer self-terminates instead of pinning a core. The --liftoff-only relaunch's spawnSync can't forward signals, so killing the parent shim used to orphan the worker. Tests: ubiquitous-name ceiling, exclude (incl. tracked-file exclusion on git + non-git), orphan self-termination (POSIX), and ppid-parser units. Shared the ppid parsers out of mcp/index.ts into mcp/ppid-watchdog.ts. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d3179f5004
commit
45d3293c6a
+68
-54
@@ -34,6 +34,7 @@ import { getGlyphs } from '../ui/glyphs';
|
||||
import { buildNode25BlockBanner, buildNodeTooOldBanner, MIN_NODE_MAJOR } from './node-version-check';
|
||||
import { installFatalHandlers } from './fatal-handler';
|
||||
import { relaunchWithWasmRuntimeFlagsIfNeeded } from '../extraction/wasm-runtime-flags';
|
||||
import { installCommandSupervision } from './command-supervision';
|
||||
import { EXTRACTION_VERSION } from '../extraction/extraction-version';
|
||||
import { getTelemetry, TELEMETRY_DOCS, recordIndexEvent } from '../telemetry';
|
||||
|
||||
@@ -506,19 +507,25 @@ program
|
||||
// Indexing runs by default now. The legacy -i/--index flag is still
|
||||
// accepted (so existing muscle memory and scripts don't break) but is a
|
||||
// no-op — initializing always builds the initial index.
|
||||
// Supervise the index: self-terminate if orphaned or wedged (#999).
|
||||
const supervision = installCommandSupervision('init');
|
||||
let result: IndexResult;
|
||||
if (options.verbose) {
|
||||
result = await cg.indexAll({
|
||||
onProgress: createVerboseProgress(),
|
||||
verbose: true,
|
||||
});
|
||||
} else {
|
||||
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
||||
const progress = createShimmerProgress();
|
||||
result = await cg.indexAll({
|
||||
onProgress: progress.onProgress,
|
||||
});
|
||||
await progress.stop();
|
||||
try {
|
||||
if (options.verbose) {
|
||||
result = await cg.indexAll({
|
||||
onProgress: createVerboseProgress(),
|
||||
verbose: true,
|
||||
});
|
||||
} else {
|
||||
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
||||
const progress = createShimmerProgress();
|
||||
result = await cg.indexAll({
|
||||
onProgress: progress.onProgress,
|
||||
});
|
||||
await progress.stop();
|
||||
}
|
||||
} finally {
|
||||
supervision.stop();
|
||||
}
|
||||
printIndexResult(clack, result, projectPath);
|
||||
await recordIndexTelemetry(cg, result);
|
||||
@@ -627,51 +634,58 @@ program
|
||||
const { default: CodeGraph } = await loadCodeGraph();
|
||||
const cg = await CodeGraph.open(projectPath);
|
||||
|
||||
if (options.quiet) {
|
||||
// Quiet mode: no UI, just run. `index` is a full re-index, so clear the
|
||||
// existing graph and rebuild from scratch (see the note below — #874).
|
||||
// Supervise the indexer: self-terminate if orphaned (parent shim killed)
|
||||
// or if the main thread wedges — neither was guarded on this path (#999).
|
||||
const supervision = installCommandSupervision('index');
|
||||
try {
|
||||
if (options.quiet) {
|
||||
// Quiet mode: no UI, just run. `index` is a full re-index, so clear the
|
||||
// existing graph and rebuild from scratch (see the note below — #874).
|
||||
cg.clear();
|
||||
const result = await cg.indexAll();
|
||||
if (!result.success) process.exit(1);
|
||||
cg.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const clack = await importESM('@clack/prompts');
|
||||
clack.intro('Indexing project');
|
||||
|
||||
// `index` is a FULL re-index: clear the existing graph and rebuild it from
|
||||
// scratch so the result is identical to a fresh `init`. Without the clear,
|
||||
// indexAll() skips every unchanged file by its content hash and reports
|
||||
// "0 nodes, 0 edges" against the already-populated graph — which reads as
|
||||
// "index wiped my index" (#874). For fast incremental updates use `sync`.
|
||||
cg.clear();
|
||||
const result = await cg.indexAll();
|
||||
if (!result.success) process.exit(1);
|
||||
|
||||
let result: IndexResult;
|
||||
|
||||
if (options.verbose) {
|
||||
result = await cg.indexAll({
|
||||
onProgress: createVerboseProgress(),
|
||||
verbose: true,
|
||||
});
|
||||
} else {
|
||||
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
||||
const progress = createShimmerProgress();
|
||||
result = await cg.indexAll({
|
||||
onProgress: progress.onProgress,
|
||||
});
|
||||
await progress.stop();
|
||||
}
|
||||
|
||||
printIndexResult(clack, result, projectPath);
|
||||
await recordIndexTelemetry(cg, result);
|
||||
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
clack.outro('Done');
|
||||
cg.destroy();
|
||||
return;
|
||||
} finally {
|
||||
supervision.stop();
|
||||
}
|
||||
|
||||
const clack = await importESM('@clack/prompts');
|
||||
clack.intro('Indexing project');
|
||||
|
||||
// `index` is a FULL re-index: clear the existing graph and rebuild it from
|
||||
// scratch so the result is identical to a fresh `init`. Without the clear,
|
||||
// indexAll() skips every unchanged file by its content hash and reports
|
||||
// "0 nodes, 0 edges" against the already-populated graph — which reads as
|
||||
// "index wiped my index" (#874). For fast incremental updates use `sync`.
|
||||
cg.clear();
|
||||
|
||||
let result: IndexResult;
|
||||
|
||||
if (options.verbose) {
|
||||
result = await cg.indexAll({
|
||||
onProgress: createVerboseProgress(),
|
||||
verbose: true,
|
||||
});
|
||||
} else {
|
||||
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
||||
const progress = createShimmerProgress();
|
||||
result = await cg.indexAll({
|
||||
onProgress: progress.onProgress,
|
||||
});
|
||||
await progress.stop();
|
||||
}
|
||||
|
||||
printIndexResult(clack, result, projectPath);
|
||||
await recordIndexTelemetry(cg, result);
|
||||
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
clack.outro('Done');
|
||||
cg.destroy();
|
||||
} catch (err) {
|
||||
error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user