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
+56
-2
@@ -42,12 +42,24 @@ export interface ProjectConfig {
|
||||
* are never discovered or indexed (#970, #976).
|
||||
*/
|
||||
includeIgnored?: string[];
|
||||
/**
|
||||
* Gitignore-style patterns for paths to keep OUT of the index — even when
|
||||
* they are git-TRACKED, which `.gitignore` cannot do (#999). The escape hatch
|
||||
* for a committed vendor/theme/SDK directory (e.g. a checked-in Metronic theme
|
||||
* under `static/`) that bloats the graph and slows indexing but isn't really
|
||||
* your code. Matched against project-root-relative paths, so a directory like
|
||||
* `"static/"`, a double-star vendor glob, or `"assets/theme"` all work.
|
||||
* Absent/empty (the default) excludes nothing beyond the built-in defaults
|
||||
* and your `.gitignore`.
|
||||
*/
|
||||
exclude?: string[];
|
||||
}
|
||||
|
||||
/** Parsed, validated view of a project's `codegraph.json`. */
|
||||
interface ParsedConfig {
|
||||
extensions: Record<string, Language>;
|
||||
includeIgnored: string[];
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
interface CacheEntry {
|
||||
@@ -68,6 +80,7 @@ const EMPTY_EXTENSIONS: Record<string, Language> = Object.freeze({});
|
||||
const EMPTY_CONFIG: ParsedConfig = Object.freeze({
|
||||
extensions: EMPTY_EXTENSIONS,
|
||||
includeIgnored: Object.freeze([]) as unknown as string[],
|
||||
exclude: Object.freeze([]) as unknown as string[],
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -118,8 +131,11 @@ function parseConfig(file: string): ParsedConfig {
|
||||
|
||||
const extensions = extractExtensions(parsed, file);
|
||||
const includeIgnored = extractIncludeIgnored(parsed, file);
|
||||
if (extensions === EMPTY_EXTENSIONS && includeIgnored.length === 0) return EMPTY_CONFIG;
|
||||
return { extensions, includeIgnored };
|
||||
const exclude = extractExclude(parsed, file);
|
||||
if (extensions === EMPTY_EXTENSIONS && includeIgnored.length === 0 && exclude.length === 0) {
|
||||
return EMPTY_CONFIG;
|
||||
}
|
||||
return { extensions, includeIgnored, exclude };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,6 +188,32 @@ function extractIncludeIgnored(parsed: object, file: string): string[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the `exclude` patterns: an array of non-empty gitignore-style
|
||||
* strings naming paths to keep out of the index even when git-tracked (#999). A
|
||||
* non-array value or a non-string/blank entry warns-and-skips; never throws.
|
||||
* Patterns are kept verbatim (trimmed) so they match exactly as a `.gitignore`
|
||||
* line would, against project-root-relative paths.
|
||||
*/
|
||||
function extractExclude(parsed: object, file: string): string[] {
|
||||
const raw = (parsed as ProjectConfig).exclude;
|
||||
if (raw === undefined) return [];
|
||||
if (!Array.isArray(raw)) {
|
||||
logWarn(`Ignoring "exclude" in ${PROJECT_CONFIG_FILENAME}: must be an array of gitignore-style patterns`, { file });
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
for (const entry of raw) {
|
||||
if (typeof entry !== 'string' || !entry.trim()) {
|
||||
logWarn(`Ignoring an "exclude" entry in ${PROJECT_CONFIG_FILENAME}: every pattern must be a non-empty string`, { file });
|
||||
continue;
|
||||
}
|
||||
out.push(entry.trim());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the parsed `codegraph.json` for a project, mtime-cached. A missing or
|
||||
* malformed file yields the zero-config default. One `stat` (and at most one
|
||||
@@ -221,6 +263,18 @@ export function loadIncludeIgnoredPatterns(rootDir: string): string[] {
|
||||
return loadParsedConfig(rootDir).includeIgnored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the validated `exclude` patterns for a project, mtime-cached.
|
||||
*
|
||||
* These name paths to keep OUT of the index even when git-tracked — the escape
|
||||
* hatch for a committed vendor/theme/SDK directory `.gitignore` can't drop
|
||||
* (#999). An empty result — the zero-config default — excludes nothing beyond
|
||||
* the built-in defaults and the project's `.gitignore`.
|
||||
*/
|
||||
export function loadExcludePatterns(rootDir: string): string[] {
|
||||
return loadParsedConfig(rootDir).exclude;
|
||||
}
|
||||
|
||||
/** Test/maintenance hook: forget cached config (e.g. after rewriting it in a test). */
|
||||
export function clearProjectConfigCache(): void {
|
||||
cache.clear();
|
||||
|
||||
Reference in New Issue
Block a user