* test: make Windows load-sensitive tests wait on the tree, not the clock (#1773)
Each failure from the loaded Windows run was traced to its mechanism and
reproduced deterministically before changing it. No assertion is weakened.
- daemon-lock-refresh: the PowerShell holder slept 200ms against a 375ms
retry budget. Windows refuses a replace-rename while any handle to the
target is open, even a Node read handle, so hold one in-process and close
it inside the rename mock after the first real failure. Also asserts the
first rename really failed with a retryable code.
- mcp-projectpath-lifecycle: the owner daemon armed a 500ms idle timer at
start and exited (code 0) before a starved engine reached it. It now starts
with idle exit off and is armed over IPC once the engines hold a session.
The file waits for catch-up instead of the gate's 3s serve-anyway deadline,
and its index-heavy hooks and eviction case get explicit timeouts.
- mcp-daemon / mcp-writer-lock teardown: racing launchers spawn detached
daemon candidates whose cwd is the fixture; a loser still starting at
teardown pinned it (EBUSY) or took over after the winner was stopped. A
--require preload records candidate pids, and teardown waits for the
losers before stopping the winner.
- mcp-writer-lock: writer.pid appears before the daemon binds and rewrites
daemon.pid, so cleanup could probe a daemon mid-start ('unverified'); wait
for both proxies to attach. Removal uses fs.promises.rm, because fs.rmSync
gives up on the first Windows access-denied error despite maxRetries.
- mcp-daemon #1963: connect only after the proxy attached (lock before bind).
- sync-rebuild-convergence / multi-repo-workspace: explicit timeouts scoped
to the cases that build real indexes; the global timeout is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(daemon): give the Windows pid-file replace a longer retry budget (#1773)
Windows refuses to replace a file while any handle to it is open, including a
plain Node read handle, so an antivirus scan, an indexer, or another session
reading daemon.pid can each block the daemon's startup refresh for a moment,
and for longer on a loaded machine. The retry waited 375ms in all; it now backs
off 25, 50, 100, 200, then 400ms three times, about 1.6s over eight attempts,
still well inside a launcher's ~6s connect window. Permanent failures and
non-Windows errors still abort at once, and an ownership change still stops
the retry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(explore): claim complete source only for sections that are complete
On the tiers with a completeness signal (>= 500 indexed files) every
codegraph_explore response ended with "Complete source for N files is
included above — do NOT re-read them", whatever the render had cut. A
windowed spine method, a shrunk or dropped cluster and the per-symbol view
all went out under that line; the oversize-spine window never even set the
trim flag the small tiers key off, which is how a 62-line slice of vscode's
968-line rpcProtocol.ts was called complete. The line also said "Reserve
Read for a single specific line range", which explore output must never
say.
Completeness is now measured at emission from the ranges actually sent
(plus back-referenced spans) against the symbols each section set out to
deliver. A trimmed response keeps the verbatim / already-Read guarantee,
names the trimmed files and the most relevant elided symbols, and steers to
another codegraph_explore. The note is offered from most to least specific
and fitted to the leftover room (CG-26); a trimmed note yields the pointer
list's first entry. The lost-pointer, cut and truncation notes drop
"complete" the same way, at no greater length, so the epilogue floor is
unchanged. The small tiers' trimmed note now also fires for the cuts the
flag missed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(explore): offer an elided method as Owner.member in the completeness note
The note offered `as_sql` for django's elided SQLCompiler.as_sql — one of
110 definitions — so agents appended a path and line to disambiguate it,
and that query shape returns none of the body. `SQLCompiler.as_sql` alone
returns most of it. Methods are now offered as `Owner.member` from the
indexed qualified name; everything else keeps its bare name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(explore): fit the completeness note and pointer list in one tested function
A trimmed note's optional detail (the files, the elided names) never costs a
pointer entry the least specific note would have left. The fit moves out of
handleExplore into fitExploreEpilogue so both rules are pinned by tests; the
second-try branch it had could never fire and is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(explore): completeness note wording after #2068 flags the spine window
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(explore): record the completeness-note replay and agent A/B
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(explore): address review of the completeness note
- Judge completeness AFTER the dedup fold: a remainder too small to fence
is sent in neither call, so a section with one is trimmed, not complete.
The whole-file render now declares its symbols too (clamped to the lines
it prints; the file node is not one), so a fold there registers.
- Label trimmed files unique among every path the response can name, the
pointer list included.
- "1 file", and no "0 files" when every section was held from an earlier call.
- The lost-pointer, cut and truncation notes are one exported table,
EXPLORE_FALLBACK_NOTES, with the length rule tested; the lost note picks
its wording from trimmedShown instead of re-joining the response.
- The design doc's follow-up list marks the overclaim fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A bare basename several directories share (`editorOptions.ts`, `worker.rs`,
`compiler.py`) pinned every match, and the matches split the pinned
reservation: on vscode `editorOptions.ts clampedInt cursorStyleToString
cursorStyleFromString` gave the file holding all three 8,626 chars, against
15,181 without the path, and spent the rest on a workbench helper defining
none of them.
Path extraction now takes an injected `symbolFiles` lookup (the module stays
DB-free, like `existsOnDisk`). When a span matches several files, the matches
defining a precise symbol token the query names (camelCase, PascalCase,
snake_case, qualified — the seeder's NL-stopword shape test) are pinned and the
rest set aside; the summary line names them. When no match defines one, or
every match does, the span resolves exactly as before. The same test resolves
a basename shared past the ambiguity budget (django's `models.py`) when it
narrows into budget, and a line anchor binds once the span narrows to one file.
Replay of 196 queries over 8 indexed repos, main vs this branch: every
unambiguous pin is byte-identical (38 unique basenames, 13 dir/basename, 8
file:line, 38 earlier-sweep path queries), as are 26 path-free queries, 13
where every match defines a named symbol and 8 naming no symbol. All 52
changed responses are shared basenames — named FULL 207 -> 231 (11 up, 0 down), named lines 6,045 -> 7,577,
none over 25K.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(explore): return named bodies from merged spine clusters; let named files use spare budget
Re-applied onto main after #2062 and #2063 landed in the same render path
(originally e31cc9fc on claude/inspiring-grothendieck-8a7b3e):
- the merged-spine hold-back and its top-up are dropped: #2062's hold-back
(every cluster leaves room for the protected members ranked below it)
subsumes them, and the past-the-pin-cap test passes on it;
- #2063's "don't window a cluster holding an exact target" becomes the
member rule's "don't window a spine method that is or holds one";
- the shrink orders exact targets ahead of spine members, and the exact
branch prices member windows (sectionRangesOf) rather than whole bodies;
- the final ceiling fit shortens the file header back to its estimate
before it cuts source. With an exact target's overshoot (#2063) spending
a named file to its funded line, a lower file's header running past its
estimate cost it 109 chars of source (922 of 1,031; 995 on main). The
valve test now asserts funding in full and delivery to the line, since
#2062's exact rendering stops at whole lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(explore): record the agent A/B for the named-concentration fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(explore): record landing named concentration on main after #2062/#2063
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A file named by path is pinned: its first 300 symbols by start line enter
the gather. Past that cap the file splits into a head cluster of pinned
filler and, one cluster down, the named symbols the cap cut off, which
used to render nothing (`protocol.ts serializeAck`: 0 of 3 lines; the
same query unpinned: all of them).
#2062's cross-cluster hold-back fixes this shape without knowing about
pinning; its own fixture is an unpinned file. This fixture pins the
pinned entry path to it: red on tools.ts before #2062 (2 of 6), green on
main.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`SQLCompiler.as_sql pre_sql_setup get_select` on django returned get_select and
pre_sql_setup in full and SQLCompiler.as_sql -- qualified, step 1 of the Flow,
226 lines -- as one signature line. compiler.py renders as a per-symbol
focused view, which picked bodies in source order within a tier, so the
unnamed bridge get_qualify_sql and get_select (both above as_sql in the file)
took the cap. The follow-ups agents issue (`compiler.py:776`,
`compiler.py lines 900-1003`) pinned the file and dropped the line numbers.
Every agent run ended in a Read of compiler.py.
- query-paths: keep `:776` / `:12-40` / `#L88-L120` suffixes as line anchors
and bind prose ranges (`lines 900-1003`, `L900-L1003`, `lines 900 to 1003`)
to the nearest single-file path.
- EXACT targets: a qualified name resolving to <=3 callables, or the callable
enclosing a single-line anchor. Multi-line anchors render as that span.
- Focused view: tiers exact > named spine step > unnamed bridge > unique named >
family co-named, spine in call order. A tier 0-1 body that does not fit is
windowed (head + calls into the question's other symbols) with the explore
range that returns each hole, never reduced to a signature. Priced in
rendered chars net of the signature lines, so the section keeps its promise.
- Cluster path: exact clusters rank first with the spine's bounded overshoot,
shrink by exact rendered size, and hold back what lower-ranked exact
clusters still owe; files holding one are funded like spine files.
- Blast radius leads with exact targets.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Clusters of equal max importance rank by density, and density counts
every member, so a named function with unrelated helpers merged around
it outranked a cluster holding a named function alone. Taken whole, the
higher clusters spent the file's reservation on the helpers and the
isolated named function rendered nothing (lib/response.ts repro:
sendBody 0 of 36 lines). Reordering alone only moves the victim.
Two rules, both scoped to incidental members (not named, not an entry
point, not on the call path):
- a cluster's incidental members may only use what is left once every
lower-ranked cluster's protected members are paid; its own protected
members are never held back, so named-vs-named stays rank's call;
- in a cluster holding a protected member, incidental members are never
kept past the render's ceiling in rendered size, so the source-order
window back to the ceiling no longer cuts a named body's tail (gin's
handleHTTPRequest) on selection or on any re-render.
47 deterministic queries over the 7 README repos plus express vs main:
named bodies complete 198 -> 234 (17 responses up, 0 down), named lines
+10%, responses over the 25K cap 5 -> 1. One new final-cut truncation
(django-bag) comes from owedPayableBelow's MIN_CHARS threshold, recorded
in the design doc. On main + the named-concentration branch: 235 -> 279.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
When Windows-native and WSL CodeGraph both open the same
.codegraph/codegraph.db under /mnt/<drive>/, SQLite's locking and -shm
shared memory don't hold across the 9p/DrvFs bridge and WSL fails with a
bare "disk I/O error".
- A SQLite I/O error on the default index of a WSL /mnt/<drive> project is
rewritten (in the SQLite adapter, so every open and query is covered) into
the CODEGRAPH_DIR=.codegraph-wsl instruction. MCP answers it success-shaped,
and a failed default-project open is reported instead of "no project loaded".
- On WSL + /mnt/<drive> with CODEGRAPH_DIR unset, the data dir is resolved per
root: an existing .codegraph-wsl/ wins, then an existing .codegraph/ index
(kept, no silent re-index), else a fresh index goes in .codegraph-wsl/.
`codegraph init` says where it went. Every other host is unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`open() itself kicks off the heal` polled the WAL size for 30s and then
asserted it. On a loaded machine the heal was still running when the
deadline passed, so a slow heal failed as a size assertion, reading like a
regression (#1773). Await the pass open() started instead: it still fails
at once if open() stops kicking off the heal, and reports the heal's own
result rather than the clock's.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The pre-release agent A/B (main vs v1.6.0, 7 README repos, 3 runs each)
had main returning 15% less source on vscode for the same queries, with
Reads in 2 of 3 runs where v1.6.0 had none. Replaying every query both
builds' agents issued traced it to three causes:
1. Named gaps (#1711) displaced source. Every cluster fit measured the gap
names, which on a long path run to hundreds of chars, so a class shrunk
into its file's room overran it by the names alone and was dropped
whole: rpcProtocol.ts emitted 222 of 5,496 funded chars (v1.6.0: 5,194),
and the test fixture came back as an empty fence. Fits now measure bare
gaps; names are added at assembly from what the file's budget has left.
2. Interface members (#1638) corroborated English words. The NL-stopword
guard lets a bare word seed a definition when another query word names a
symbol in the same file; `readonly host` in an options interface made
"main" seed `main()` in agentHostServerMain.ts, which took the named-first
tier from the answer files. Members declared inside an interface no
longer count as corroboration.
3. The interface-dispatch note announced dispatch through any common base,
declared or not: "`extension` -> runtime dispatch to 2706 types
implementing Disposable" opened 9 of 31 vscode answers. A supertype now
has to declare the member (it, an ancestor, or a same-named Swift
extension). When absence can't be judged, because a protocol's
requirements aren't indexed, the note stays as before.
Replayed queries, source lines vs v1.6.0 (main -> fixed): vscode 10,744 ->
12,577 (12,617), excalidraw 13,065 -> 13,698 (13,764), django 8,420 ->
8,803 (8,954), tokio 13,618 -> 14,151 (14,600), okhttp 6,511 -> 6,883
(7,090), alamofire 7,019 -> 7,249 (7,120); no query below 60% of v1.6.0.
Real dispatch notes (Alamofire adapt/retry/asURLRequest, django, okhttp)
are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The inheritance target-kind gate (#1796) rejected any extends/implements
resolution whose target cannot be a supertype. VS Code declares every
service twice under one name, `export const IFoo = createDecorator<IFoo>(…)`
beside `export interface IFoo`, and the import resolver takes the first
export of that name, which is the value. The gate then dropped the edge
instead of taking the interface the same file declares.
Found by the pre-release v1.6.0 vs main comparison: 864 `implements` edges
lost on vscode (AccessibilityService -> IAccessibilityService and ~860 like
it), plus the ~5.8k interface-dispatch call edges synthesized from them.
The gate now moves a TypeScript constant/variable target to the one
same-named supertype in its file; with no such sibling it still drops the
edge. vscode: implements 4,139 -> 5,003 (v1.6.0: 5,436, 254 of them on
non-type targets; now 0), node count unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: close resolution graphs before removing Windows fixtures
Close ArkTS and JVM graph connections in afterEach, including failed assertions. Verified both files on Windows 11 and macOS (32 tests).
* test: compare explore source lines across LF and CRLF checkouts
Normalize only line endings when matching emitted source against checked-out fixtures. Preserve the closure coverage and whole-line assertions. Both files pass on Windows 11 and macOS (15 tests).
* test: exercise old Git fallback without a POSIX shell shim
Inject only the unsupported ls-files invocation and run every other Git command against real repositories. Assert that each rejected call retries, use portable graph paths, and clean up fixtures. Passes on Windows 11 and macOS.
* test: normalize Python fixture before generating CRLF cases
The tests added in #2004 produced CRCRLF from a Windows checkout. Normalize the input to LF before constructing either variant. All 28 native and WASM cases pass on Windows 11 and macOS.
* test: allow time for eviction fixture indexes on Windows
The #2036 eviction test builds and reconciles nine real indexes and consistently exceeds its default five-second timeout on the VM. Allow 15 seconds for this test alone, preserving every assertion. The full file passes on Windows 11 and macOS (21 tests).
* test: finish MCP processes before resetting Windows fixtures
Launch actual servers with their runtime flags and await termination before directory removal. Use asynchronous removal retries for daemon fixtures. Establish a ready daemon and stop its first proxy before simulating PID reuse, then make source stale without opening a competing SQLite writer. Preserve lock and database byte-equality assertions. All touched files validated on Windows 11 and macOS.
* test: allow Windows named-pipe fallback polling to finish
The VM takes up to 25.7 seconds to complete the nominal six-second retry loop. Allow 30 seconds for the three fallback waits and 40 seconds for the two shorter enclosing tests, retaining their lock-preservation and read-only assertions. The full daemon file is validated on Windows 11 and macOS.
* fix(daemon): retry transient Windows PID-file sharing violations
A transient EPERM replacing daemon.pid aborted startup, observed in the quiet-client test and reproduced with a real locked handle at ba3c21e5. Retry only Windows sharing errors with five bounded delays, rechecking ownership each time and cleaning temporary files. Keep publication synchronous before client processing. Cover transient/permanent failures, changed ownership, non-retryable errors, and a real Windows handle; require a confirmed daemon round-trip before measuring a quiet session. Validated on Windows 11 and macOS.
* fix: safely stop daemon before reindex
* fix(index): coordinate rebuilds with running MCP daemons (#1325)
Stop verified project daemons and confirm termination before recreating SQLite.
Fence daemon and proxy reopening throughout rebuilds while preserving foreign and newer locks.
Add Windows lifecycle coverage and cross-platform shutdown regressions.
Co-authored-by: Harry B ✦ <drewavesx0@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): fence project opens during index rebuilds after rebase
Carry the #1325 rebuild fence through the upstream explicit-project lifecycle, read-only engine opens, synchronous initialization retries, and direct writer acquisition. Preserve shared project leases, read-only fallback behavior, freshness handling, and direct-mode query pools.
Cover watched, unwatched, and read-only engines blocking during a foreign rebuild and recovering after the fence is released.
* fix(mcp): answer a running index rebuild as guidance, not a tool error (#1325)
A projectPath call during `codegraph index` hit assertNoRebuild's plain Error
and returned isError; it is an expected, temporary condition, so it now uses a
RebuildInProgressError that the tool handler answers success-shaped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Harry B ✦ <drewavesx0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix Windows watcher false edits from NTFS atime
* fix(watcher): ignore Windows access-only notifications (#1451)
Check indexed size and mtime before recording Windows watcher edits.
Preserve scope reconciliation and fail open when metadata cannot be verified.
Add native Windows regression coverage and validate on Windows, macOS, and Linux.
Co-authored-by: JJordan0K <69581081+JJordan0C@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: JJordan0K <69581081+JJordan0C@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): arm liveness watchdog in local proxy
* fix(mcp): terminate wedged local proxies with watchdog (#943)
Install the independent liveness watchdog in the local handshake proxy and stop it during shutdown.
Build on PR #979 while preserving existing daemon assertions.
Add process-termination, slow fallback, and watchdog cleanup coverage.
Validate the repro and targeted tests on Windows, macOS, and Linux.
Co-authored-by: Haoqian Li <haoqian.li@finalroundai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Haoqian Li <haoqian.li@finalroundai.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Recognize MINGW, MSYS, and Cygwin and print the exact PowerShell installer command.
Keep Darwin/Linux behavior unchanged and exit non-zero before downloading.
Cover shell dispatch and real Windows ARM64 Git Bash behavior.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Ship an extensionless shell launcher alongside codegraph.cmd for Windows.
Use bundled node.exe with matching runtime flags and preserve arguments, exit status, and inherited host PID.
Validate on Windows 11, macOS, and Linux, including CLI/hooks and archive contents.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A vitest pool worker is the one launch path that never received
`--liftoff-only`, so the suite compiled tree-sitter grammars on the
turboshaft tier. Once ~640 extraction tests have warmed a grammar function
up, its background tier-up job (Turboshaft LoopUnrollingPhase, symbolized
from the native stack) exhausts a compiler Zone and aborts the worker:
`Fatal process out of memory: Zone`, which vitest reports only as "Worker
exited unexpectedly" with the file's remaining tests unrun.
Pass WASM_RUNTIME_FLAGS through poolOptions.forks.execArgv, so the test
process matches the bundled launcher, the CLI re-exec and refresh-launcher.
V8 flags are process-global, so parse worker threads are covered too.
Linux x64, Node 24.15, `__tests__/extraction.test.ts` (655 tests):
without the flag 3 of 4 runs died at the same test (639 passed);
with it 2 of 2 passed, in 19–24s instead of 34–41s. Neither
`--no-wasm-loop-unrolling` nor `--no-wasm-dynamic-tiering` prevents it.
(cherry picked from commit 565ec815e6)
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The live-holder case wrote `pid: 1` and leaned on its own comment, "On Linux,
PID 1 is almost always alive". Windows has no PID 1, so tryAcquireWriterLock
read the holder as dead and acquired — the correct behaviour for a dead
holder, and a failing assertion. The platform-specific part was the fixture,
not the lock.
A parked child process is alive on every platform, and not stealing from a
live holder is what the lock actually promises, so the case now tests the
promise rather than a number that happens to name a running process. The child
is reaped in afterEach.
Fixes#1752.
(cherry picked from commit bee140288d)
Co-authored-by: Aaron Queen <bompus@users.noreply.github.com>
* fix(lock): keep a live writer lock regardless of file age (#1959)
* fix(watcher): re-arm after lock contention and reconcile before trusting index (#1959)
* feat(mcp): report exact index freshness without blocking the daemon (#1959)
* fix(mcp): refuse drifted explore source after watcher degradation (#1959)
* fix(mcp): refuse graph answers that name a drifted file after watcher degradation (#1959)
Explore already refuses to render source from a file that changed while
auto-sync was off. search, callers, callees and impact answer from the same
frozen graph but kept replying, naming the changed file as if its edges were
current. They now take the same refusal: the indexed files an answer names
as locations are hashed against the index, and a drifted one is named
instead of the answer. codegraph_node keeps its own drift gate, which serves
the changed file's current bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sync): recover index freshness after lock contention (#1959)
Keep a live writer's lock regardless of its age.
Re-arm lock-degraded watchers with throttling and retain stale state until full reconciliation succeeds.
Validate contributing files through structured paths and hashes, refusing incomplete checks without tool errors.
Report bounded freshness measurements and cover Git fallback exclusions.
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): close pending sockets and allow read-only fallback (#1963)
* fix(mcp): log when a fallback serves reads without auto-sync (#1963)
A read-only fallback starts no watcher and holds no writer lock, so a
session that quietly stopped syncing looked the same as a healthy one in
the logs. Name the holder on stderr when the fallback goes read-only.
Suggested by @bompus in #1979.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): stop pending connections and serve read-only fallbacks (#1963)
Pending handshakes could block daemon shutdown or register phantom sessions after disconnect.
Track accepted sockets and reject late hello continuations.
Use explicit read-only engines and SQLite connections for blocked fallbacks, suppressing synchronization, watching, migrations, and repairs.
Preserve writer ownership and the existing project lifecycle, with regression coverage for #1963 and #1356.
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Workers whose project open failed were enrolled as idle and returned errors indefinitely.
Route failed readiness through the existing idempotent retirement and replacement path.
Ignore retired-worker messages and duplicate readiness handshakes while preserving the crash budget and startup limits.
Cover mixed pools, repeated failures, lifecycle duplicates, and real SQLite recovery.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bisect identifies 5eaa6fed (#2034, #1820) as the first bad commit: its Python named-import fallback resolves task.delay() to the task function itself, hiding the queue effect in API steps. Require member resolution before returning a named Python import's target for attribute access.
Keep direct calls, callbacks, constructors, and known members working. Add absolute/relative aliased-import regression coverage without changing the existing steps assertions.
Validation: git bisect with preserved steps test, tsc and copy-assets at every step, CODEGRAPH_KERNEL=0 throughout bisect. 338 tests pass across ui-steps-api-servers, function-ref, rebuilt kernel-tsjs-parity, resolution, and ui-effects. Issue #1820 repro and tsc --noEmit pass.
Node runs a setTimeout delay above 2^31-1 ms after 1 ms instead, so a huge
CODEGRAPH_WATCHDOG_TIMEOUT_MS SIGKILLed a healthy server at once and a huge
CODEGRAPH_STARTUP_HANDSHAKE_TIMEOUT_MS abandoned the launch within
milliseconds. Both parsers now cap at 2147483647.
(cherry picked from commit a242f579ce)
Co-authored-by: Eric Minish <eric.minish@gmail.com>
* fix(db): guard synthesis metadata and migrate the expression index
Bisect identifies 2a2f71ec (#2033) as the malformed JSON regression. Guard index expressions, ownership predicates, wiring lookups, and the legacy Go backfill; schema v11 replaces existing v10 indexes. Cover malformed base edges, staged publication, upgrade replay, and indexed range lookup.
* fix(db): preserve call precedence in deterministic edge traversal
Independent bisect identifies 2a2f71ec (#2033): source-first ordering puts importing file IDs ahead of function callers and consumes the default caller limit. Restore kind-first incoming and outgoing traversal with deterministic endpoint/position ties. Keep both original regression tests unchanged and cover filtered, cached, and provenance-filtered queries across reverse insertion order.
Validation: 25 test files passed (564 tests, 3 skipped), including security, MCP caller truncation, sync, migration, graph, and DB/query coverage; npx tsc --noEmit passed. Both bisects rebuilt tsc and copied assets at every step with CODEGRAPH_KERNEL=0 to avoid using the HEAD kernel on historical extraction code. Final validation used the normal kernel setting.
* chore: no changelog entry for an unreleased regression
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): keep an explicit projectPath project in sync (#1835)
A project opened through a tool call's `projectPath` (a repository other
than the server's default — e.g. an indexed child of an un-indexed
workspace) was opened read-only: no catch-up sync on open and no file
watcher, so its answers went stale until someone ran `codegraph sync`.
The engine now owns the lifecycle of every project the ToolHandler opens
for an explicit path, mirroring the default project:
- the project's writer lock is acquired (#1740 single-writer rule); if
another live process holds it, that process keeps syncing and we only
read;
- a catch-up `sync()` runs on open and the first call against that
project awaits it, time-boxed like the default gate (#905);
- a file watcher runs for as long as the project stays cached.
Bounds: the cache is keyed by the canonical (realpath) root, so a
symlinked spelling shares one connection and one watcher; it is LRU with
`MAX_CACHED_PROJECTS = 8` — opening a ninth closes the least recently
used (watcher stopped, writer lock released, DB closed); `stop()` closes
them all. Un-indexed paths still get the success-shaped guidance.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(mcp): keep explicit projects synchronized (#1835)
Explicit projectPath connections bypassed catch-up and synchronization ownership.
Share canonical project leases and catch-up gates, retain daemon sessions, and retry ownership after a writer exits.
Advertise direct-writer readiness and drain active work before eviction or shutdown.
Add exact SQLite regressions for concurrent sessions, aliases, no-watch behavior, and teardown.
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): retry catch-up quietly on sync lock contention
Handle LockUnavailableError without marking catch-up complete or logging an alarming failure, and verify timer-driven recovery against a real indexing lock.
Make the eviction regression deterministic by applying its short timeout only to the held catch-up. Other projects must finish reconciling before the test can assert that the oldest eligible graph was closed.
---------
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(cpp): macro invocations are not calls; local objects call their constructor
A function-like macro invocation (`TRACE_POINT(1)`) parses as a call, and
the resolver bound it to a same-named free function in another translation
unit — a caller and a callee that never existed (#1838). And local object
initialization (`Widget w;` / `Widget w(1);` / `Widget w{1};`) reached the
class node instead of a constructor, or nothing at all for the default
form (#1839).
Extraction (wasm walker and the Rust kernel, in parity):
- every `preproc_function_def` becomes a `constant` node whose signature
is the directive, so the index knows which names are macros;
- a C++ declaration emits one `calls` ref per constructed object, shaped
`ns::T::T/<arity>`; pointer, reference and function declarators,
`extern`, and array element braces construct nothing; the #1035
`instantiates` ref is unchanged;
- constructors carry their parameter list as the signature.
Resolution:
- `cpp-macro-visibility.ts` walks the translation unit (root file plus
in-repo includes, each file once) over per-file summaries of
function-like defines, undefs and includes, evaluating what each file
can decide itself (literals, its own defines, the include-guard idiom).
A call whose name is definitely a macro at that point, or a name that
only macros bear, resolves to nothing. A macro that exists in one build
configuration only (CMSIS's `__DSB()` under the ARM compilers, a
target's HAL compat header) keeps the call to the function the other
configuration compiles; a wrapper macro that calls its own name is how
that function gets called and hides nothing.
- `cpp-constructor.ts` resolves the constructor refs: the type in the
lexical namespaces of the call site, then the single overload whose
parameter count admits the arguments; two admitting overloads, an
initializer_list overload, or an aggregate yield no edge.
- a `#define` constant is never a `calls` target and does not count
toward the same-name ceiling.
Measured on a betaflight tree (4,017 files, wasm arm): 153 `calls` edges
lost — all fabricated (`MIN`/`MAX` bound to a CLI enum member, macros
misparsed as functions, a cross-vendor `SWAPBYTE`) — and 34 constructor
edges gained; indexing 34s vs 30s.
Fixes#1838Fixes#1839
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(cpp): resolve local constructors and macro visibility (#1839)
Local construction lacked constructor calls, while macro invocations could bind unrelated functions.
Resolve constructors using lexical types and overload signatures, preserving separately declared defaults and array element construction.
Evaluate macro directives in translation-unit order, respecting reinclusion, guards and pragma once.
Mirror extraction in Rust and WASM, preserve type dependencies, and distinguish them in CLI output.
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(extraction): record callers for methods passed as values (#1820)
pool.submit(obj.method), thread_pool_exec(self.store.fetch), and Go
Submit(c.store.Fetch) produced no callers/impact edge because only
obj.method(...) call expressions were captured. Member values are now
emitted as *.method function-refs and resolve unique-or-drop
(same-file first). Two methods of the same name still produce no edge.
Go go c.store.Fetch(ids) remains a calls edge (it is a call expression).
EXTRACTION_VERSION 27: MCP daemon catch-up rebuilds a stale index on
start so the watcher does not keep serving the hole after upgrade.
* fix(resolution): do not let test mocks veto #1820 method-value callers
A unique production method plus a unit-test double of the same name
was treated as ambiguous, so retrieve_vision_vectors still looked
callback-invisible on a real repo. Prefer the non-test file when
exactly one production target remains.
* fix(resolution): prefer a real override over a NotImplementedError base
#1820 unique-or-drop treated DocStoreConnection.delete_payload_fields
(the ABC stub) and QdrantConnection.delete_payload_fields as ambiguous,
so gc_stale_member_stamps stayed invisible. Skip short not-implemented
stubs when exactly one real implementation remains.
* fix(extraction): preserve receivers for method values (#1820)
Python attributes and Go selectors used as callback values were dropped or reduced to bare method names.
Preserve receiver paths in native and WASM extraction, resolve through type/import scope, and reject ambiguous or noncallable targets.
Cover callback, assignment, collection, and query behavior, with extraction version 27 signaling the required reindex.
Co-authored-by: Josh66 <info@aska-systems.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Josh66 <info@aska-systems.de>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(sync): refresh synthesized edges after incremental changes (#1988)
Scoped sync skipped synthesis and retained obsolete dispatch edges.
Refresh all synthesis passes after base resolution when dependencies or source patterns change.
Stage and atomically replace owned edges, preserve ordinary edges, and retry interrupted refreshes.
Stabilize traversal order and migrate existing indexes for rebuild convergence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: preserve structural Go containment during synthesis refresh
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): stop expression-receiver calls from binding to unrelated methods
A method called on an expression receiver reached the resolver as the bare
method name, and exact-match bound it to any project method of that name:
`(await list()).map(...)` in TypeScript onto a UI adapter class's `map`
(0.9), and `v.iter().map(...)` in Rust onto the same TypeScript method
across languages (0.5).
TS/JS extraction (mirrored in the kernel): look through receiver wrappers
that keep the same object — parentheses, `!`, `as`/`satisfies`, `<T>x`,
`await` (including the grammar's `(a && b)!` parse of `a && b!`) — so
`x!.m()` is `x.m` and `(await f()).m()` is `f().m`; any other receiver with
no static type (`(a ?? b).m()`, `f().list.m()`, `arr[0].m()`) emits nothing
instead of the bare name. `this`/`super` (and chains rooted at them),
`window.*` chains and `new C().m()` keep their existing bare form.
Resolution: a bare-named call may cross a language family only through the
C ABI (a C/C++ symbol, or a free function a C/Objective-C/Swift caller
reaches, e.g. a cgo `//export`). A class member of another family, or a
function/class/variable outside that pair, is rejected as the candidate a
strategy commits to (exact-match single/best and fuzzy), never filtered out
of the pool, so no lone survivor is manufactured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): reject unrelated cross-language matches (#1986)
Name-based calls, constructors, and inheritance could select unrelated foreign-language symbols.
Apply a shared interoperability policy to selected results without manufacturing replacement matches, including JSX/Vue synthesis.
Preserve framework exports and ABI-backed calls, building on PR #1944's TS/Rust expression-receiver fixes.
Validate with both repro scripts, 529 targeted tests, kernel/wasm parity, and clean TypeScript checks.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): resolve same-file calls through object-literal aliases (#1932)
`api.getUser()` where `api` is `const api = { getUser }` (or
`{ getUser: fetchUser }`) and the function is declared outside the literal
resolved to nothing in the literal's own file: the #1573 containment lookup
only finds members defined inside the literal, and the binding-following
alias path existed only on the import side, so same-file callers were
missing from callers / impact / blast radius.
- Extract the member-binding read into `objectLiteralMemberBinding`, which
splits the literal into its own top-level members on comment- and
string-blanked source, so a nested object, a member body, a comment or a
string never donates a binding, and `{ fn: 1 }` names nothing. The import
path now uses it too.
- In the same-file object-literal receiver strategy, fall back to that
binding: a symbol of the file, else one of its imports, skipped when a
parameter or nested declaration shadows the name where the literal is
written.
Resolution-only change; extraction output (and kernel parity) unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(resolution): a literal wrapped in Object.freeze still names its members (#1932)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): resolve object-literal function references safely (#1932)
Same-file object-member calls only found functions defined inside the literal.
Share lexical binding resolution across same-file, import, and alias-forwarding paths.
Respect exact source columns, top-level membership, shadowing, and property overwrites while preserving wrappers and inline members.
Add JavaScript and TypeScript regressions and verify kernel/WASM parity.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Same-kind, same-name declarations on one line shared an ID and silently overwrote each other in SQLite.
Use per-extraction collision tracking to append a UTF-16 column only to later distinct declarations.
Apply the same rule across wasm, Rust language walkers, Liquid, and CFML while preserving file IDs and edge endpoints.
Add persistence, call-edge, identity-vector, and Unicode parity regressions.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(scala): resolve extends/with to the trait, not its companion object
The Scala trait + companion object idiom puts two same-named symbols in
one file. Both were indexed as kind 'class', so an 'extends X' reference
tied between the trait and its companion and the winner was arbitrary.
When the companion won, every subtype was detached from the trait's
inheritance chain, and impact analysis on a widely-used trait stopped at
depth 1.
- classify Scala object_definition as 'module' (a singleton value, not a
type - 'extends' can never target it)
- add 'module' to the classifyClassNode contract and handle it in the
extractor dispatch
- in name matching, bias extends/implements references toward actual
type definitions and penalize 'module' candidates, so the trait wins
the tie deterministically
Regression test: trait + companion in one file, subclass in another -
the extends edge must land on the trait and impact must reach the
subtype through it.
* fix(scala): resolve companion inheritance to type definitions (#1824)
Scala objects and their companion types could tie during inheritance resolution, disconnecting subtype impact paths.
Classify objects as modules in both extraction engines and reject Scala singleton inheritance targets across resolution strategies.
Preserve object method lookup and inherited receiver methods, with declaration-order, impact, and native/WASM parity regressions.
Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Hubert Tarnacki <hubert.tarnacki@comarch.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): resolve this.#field.method() on the field's type (#1987)
A call through an ES private field fell outside the `this.<field>` shape
(#1496): the extractor accepted only a property_identifier, so
`this.#items.add(x)` was emitted as the bare `add` and exact-matched
whichever project method shared the name, often the calling method itself.
The extractor and its kernel mirror now keep `this.#items.add`, and the
resolver matches that shape and reads the field's type off the class
declaration. The field regexes open with a lookbehind instead of `\b`,
which never matches before `#`. A builtin or external field type
(`new Set()`, a `Map`, an array) now yields no edge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): require receiver evidence for built-in method calls (#1987)
Unknown JavaScript receivers could match unrelated methods by name or class-name similarity.
Require validated receiver evidence for built-in method names while retaining typed, imported, object-literal and class resolution.
Preserve private-field receivers in TypeScript and Rust extraction and keep public/private field declarations distinct.
Build on PR #1990 with regression coverage across all four JS/TS variants and kernel/WASM parity.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The #1465 change read the default project root on every tool call, before
checking whether a query worker would serve it. Resolve it only for a pooled
call; in-process dispatch no longer depends on it (restores the oversized-output
truncation test).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Empty explore results returned no explanation or retry guidance.
Check FTS and live name segments with two bounded SQL queries to distinguish matched and unmatched words and suggest indexed names.
Preserve success-shaped responses, unresolved-path notes, ranking, and non-empty output; cap added diagnostics below 1.5K characters.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(watcher): auto-sync changes inside symlinked directories (#770)
The file watcher (Linux inotify strategy) did not follow directory
symlinks when building its watch set, so a project dir symlinked into
the workspace root was silently invisible to the watcher.
Changes:
1. watcher.ts: watchTree() now detects isSymbolicLink() entries,
resolves real targets with realpathSync(), and recursively watches
directory symlinks. Guards against cycles with a visitedRealDirs Set
— tracking regular dirs too so a symlink→already-visited-path is
also detected. Mirrors the indexer's walk() in extraction/index.ts.
2. utils.ts: Added allowSymlinkedDirs parameter to validatePathWithinRoot().
When true, skips the realpath check that would reject watched symlinks
whose targets live outside the lexical project root. All internal
sync/index paths pass true here; user-facing paths (MCP layer) keep
the existing strict validation.
3. extraction/index.ts: All internal sync/index paths now pass
allowSymlinkedDirs: true so the watcher can read files from
symlinked directories.
4. __tests__/watcher.test.ts: Added integration test for symlinked
directory auto-sync, plus a symlink-cycle detection test.
Signed-off-by: dengzhongyuan <dengzhongyuan@uniontech.com>
* fix(watcher): auto-sync symlinked directory changes across platforms (#770)
Directory indexing followed symlinks that native watchers did not cover.
Discover targets with realpath cycle guards, preserve logical paths, and install bounded supplemental watches.
Reconcile watches after topology and scope changes, and preserve changes arriving during a full sync.
Retain strict content-serving containment and cover lifecycle, resource limits, and cleanup.
Co-authored-by: dengzhongyuan <dengzhongyuan@uniontech.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(watcher): auto-sync symlinked directory changes across platforms (#770)
Directory indexing followed symlinks that native watchers did not cover.
Discover targets with realpath cycle guards, preserve logical paths, and install bounded supplemental watches.
Reconcile watches after topology and scope changes, and preserve changes arriving during a full sync.
Retain strict content-serving containment and cover lifecycle, resource limits, and cleanup.
Co-authored-by: dengzhongyuan <dengzhongyuan@uniontech.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(watcher): auto-sync symlinked directory changes across platforms (#770)
Directory indexing followed symlinks that native watchers did not cover.
Discover targets with realpath cycle guards, preserve logical paths, and install bounded supplemental watches.
Reconcile watches after topology and scope changes, and preserve changes arriving during a full sync.
Retain strict content-serving containment and cover lifecycle, resource limits, and cleanup.
Co-authored-by: dengzhongyuan <dengzhongyuan@uniontech.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Signed-off-by: dengzhongyuan <dengzhongyuan@uniontech.com>
Co-authored-by: dengzhongyuan <dengzhongyuan@uniontech.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The orphan sweep discarded resolver statistics, so unchanged files caused the CLI to report a no-op after recovery.
Carry processed, resolved, and unresolved sweep counts into SyncResult and report recovery without changing quiet mode.
Cover successful recovery, unresolved attempts, and genuine no-ops with real SQLite regression tests.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test(mcp): reap the daemon-mode writer so the temp project can be removed
In daemon mode the writer is a third process spawned by the proxies and never recorded in children -- the test asserts its pid is neither child's. Killing only the proxies left it holding the index, so fs.rmSync failed and its catch swallowed the error: one temp directory and one live serve --mcp process leaked per run while both tests passed.
writer.pid already names the daemon and the test already parses it, so afterEach now reads it before killing the proxies and signals the writer too. The removal is no longer silenced, because the swallowed error is what made this invisible.
Measured on Windows 11 with TEMP redirected to a private directory: before, 1 leaked directory and 1 surviving process per run; after, 0 and 0, both tests still passing. Fixes#1782.
* fix(mcp): reap writer-lock test daemon and verify cleanup (#1782)
The writer-lock test stopped its proxies but left their daemon running, hiding directory removal failures.
Preserve the contributor fix and add verified daemon shutdown, bounded exit polling, and cleanup assertions.
Ensure the daemon test overrides inherited direct-mode configuration.
Co-authored-by: Aaron Queen <bompus@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Aaron Queen <bompus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): enable query workers in direct mode (#1465)
Direct and proxy-fallback engines never enabled the query pool, serializing concurrent reads despite CODEGRAPH_QUERY_POOL_SIZE.
Enable bounded workers in both paths and support sessions without a default index, including indexes discovered later.
Preserve main-thread catch-up, staleness notices, and session accounting, with regression coverage for concurrency, parity, configuration, and teardown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): enable query workers in direct mode (#1465)
Direct and proxy-fallback engines never enabled the query pool, serializing concurrent reads despite CODEGRAPH_QUERY_POOL_SIZE.
Enable bounded workers in both paths and support sessions without a default index, including indexes discovered later.
Preserve main-thread catch-up, staleness notices, and session accounting, with regression coverage for concurrency, parity, configuration, and teardown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): enable query workers in direct mode (#1465)
Direct and proxy-fallback engines never enabled the query pool, serializing concurrent reads despite CODEGRAPH_QUERY_POOL_SIZE.
Enable bounded workers in both paths and support sessions without a default index, including indexes discovered later.
Preserve main-thread catch-up, staleness notices, and session accounting, with regression coverage for concurrency, parity, configuration, and teardown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: build dist/ before the suite when it is missing or stale (#1879)
About thirty suites spawn dist/bin/codegraph.js, the parallel resolver loads
its worker from dist/, and the `codegraph ui` suites serve dist/viewer/. On a
fresh checkout `npm ci && npm test` failed them on spawn timeouts and a
missing-viewer error that never mentioned the build; after an edit without a
rebuild they quietly exercised the old code.
A Vitest globalSetup now rebuilds each half only when it is missing or older
than its sources: `tsc` + copy-assets when anything under src/ is newer than
dist/bin/codegraph.js, `build:ui` when ui/src/ is newer than
dist/viewer/index.html. A current dist/ costs one mtime walk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(tests): build missing or stale test prerequisites (#1879)
Dist-dependent tests launched missing or stale compiled outputs without a build prerequisite.
Retain the contributor's shared setup and track build inputs and individual outputs for reliable invalidation.
Resolve TypeScript locally, preserve asset copying and engine/UI separation, and report build failures directly.
Add regression coverage for cold builds, warm runs, configuration changes, and missing assets.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(mcp): share one connection across two spellings of one root (#1057)
A symlinked checkout, or a case-variant of a root on a case-insensitive
mount (macOS, NTFS, WSL DrvFs), resolved to two path strings and opened two
connections to the same .codegraph/codegraph.db.
The connection cache stays keyed by path, so a root recreated at the same
path still heals in place (#925). On a miss, an open root that is the same
index NOW (both data directories stat to one dev:ino, read as bigints) is
reused and the new spelling filed as its alias. Comparing live identities
means a deleted root never matches, even if a new directory reuses its
inode. Windows has no usable inode, so it compares the case-folded
realpath.native instead. statInode moves to directory.ts so the DB layer
and the cache share one reading, and closeAll closes an aliased instance
once.
* fix(mcp): keep root aliases out of the connection cache (#1057)
A second spelling of an open root was filed in projectCache under its own key,
so one CodeGraph sat under two keys. Anything that drops one key and closes its
connection (a bounded cache's eviction, as proposed in #1929) then left the
other key holding a closed handle: every query on that spelling, and through
the alias scan on the original spelling too, failed with "database is not
open" until restart.
Aliases now live in their own map (spelling -> projectCache key). projectCache
is back to one key per instance, and an alias whose entry is gone is dropped
and re-resolved instead of served.
* fix(mcp): deduplicate equivalent project paths (#1057)
Equivalent project paths could open duplicate cached connections.
Compare current filesystem identities using bigint inodes and native Windows paths.
Revalidate aliases and pin cached connection owners to symlink targets.
Preserve recreation recovery and single-owner cleanup.
Co-authored-by: Eric Minish <eric.minish@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Eric Minish <eric.minish@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
MSVC COM interface declarations were parsed as functions, losing their owners and methods.
Normalize declaration-position interface keywords to padded struct keywords when alias or declaration evidence is present.
Preserve source offsets and protected text through the shared native/WASM preprocessing hook.
Cover real-file indexing, inheritance, source retrieval, and parser parity.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Rust command attributes were not retained as decorators, so runtime entry points could appear as dead code.
Preserve tauri::command metadata in both native and fallback extraction and reuse the existing decorated exclusion.
Add SQLite regression coverage and native/fallback parity checks, including negative cases.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Single-argument function macros were indexed under parentheses in C or the macro name in C++, breaking caller resolution.
Recover both parser shapes only when a preceding local macro definition establishes the function name, and mirror the recovery in the Rust kernel.
Preserve existing multi-argument recovery and ordinary declarations; add extraction, resolution, explore-flow, and parity regressions.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Path search marked nodes visited only on dequeue, allowing pending targets to be queued repeatedly with copied paths.
Track enqueued nodes from the starting node and exclude them from target lookups and subsequent pushes.
Add real-SQLite operation-count and path-correctness regression coverage.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>