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:
Colby Mchenry
2026-06-26 20:25:47 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent d3179f5004
commit 45d3293c6a
12 changed files with 805 additions and 100 deletions
+30 -1
View File
@@ -10,7 +10,7 @@
* stubbing `isAlive` and `platform`.
*/
import { describe, it, expect } from 'vitest';
import { supervisionLostReason } from '../src/mcp/ppid-watchdog';
import { supervisionLostReason, parsePpidPollMs, parseHostPpid, DEFAULT_PPID_POLL_MS } from '../src/mcp/ppid-watchdog';
const alive = () => true;
const dead = () => false;
@@ -136,3 +136,32 @@ describe('supervisionLostReason', () => {
});
});
});
describe('parsePpidPollMs', () => {
it('defaults when unset / empty / non-numeric / negative', () => {
expect(parsePpidPollMs(undefined)).toBe(DEFAULT_PPID_POLL_MS);
expect(parsePpidPollMs('')).toBe(DEFAULT_PPID_POLL_MS);
expect(parsePpidPollMs('abc')).toBe(DEFAULT_PPID_POLL_MS);
expect(parsePpidPollMs('-5')).toBe(DEFAULT_PPID_POLL_MS);
});
it('honours a positive override and floors it', () => {
expect(parsePpidPollMs('200')).toBe(200);
expect(parsePpidPollMs('150.9')).toBe(150);
});
it('treats 0 as the explicit "disable" sentinel (caller skips the timer)', () => {
expect(parsePpidPollMs('0')).toBe(0);
});
});
describe('parseHostPpid', () => {
it('returns null for unset / empty / non-integer / orphan-sentinel pids', () => {
expect(parseHostPpid(undefined)).toBeNull();
expect(parseHostPpid('')).toBeNull();
expect(parseHostPpid('x')).toBeNull();
expect(parseHostPpid('0')).toBeNull(); // unknown
expect(parseHostPpid('1')).toBeNull(); // init = already orphaned
});
it('returns a real positive pid', () => {
expect(parseHostPpid('4242')).toBe(4242);
});
});