435a7fd37ab503bd87f95161a2b7af986ad5f3e3
1
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a5c2709e6d |
fix(mcp): adopt a single indexed sub-project below the server root + say when no project resolves (#1606, #1607) (#1614)
Fixes #1606 and #1607 together — the MCP server's root resolution never got the sub-project down-scan `planFrontload` gained in #964, and the resulting no-default state was completely silent. ## What changed **Adoption (#1606).** A new `resolveServerRoot()` in `src/directory.ts` is the single resolution every server entry point now uses: up-walk first (`findNearestCodeGraphRoot`, the common case, unchanged), and when that misses, the existing bounded down-scan (`findIndexedSubprojectRoots` — depth 4, max 64, heavy dirs skipped). **Exactly one** indexed sub-project is unambiguous and is adopted as the default project — `open` → `startWatching` → `catchUpSync` → query pool, the full normal path. Zero or several candidates → no default, never a guess. Wired into: - `MCPEngine.doInitialize()` and `retryInitializeSync()` — the retry path also picks up a child indexed *after* the server started (its down-scan is throttled to once per 5s so the persistent no-default state doesn't pay a directory walk per tool call; the up-walk still runs every time). - `resolveDaemonRoot()` — the adopted root gets the shared daemon (one watcher, one writer, socket keyed on the child) instead of a direct-mode server per host, exactly as the issue suggested. - `MCPSession.handleInitialize()` — the instructions variant is picked with the same resolution, so a workspace whose single child becomes the default gets the full single-project playbook. Race-free by construction: handshake and engine compute it independently, no ordering assumed. **Workspace-root gate (the open question in #1606).** Decided deliberately: the down-scan runs only when the base has a workspace manifest (`looksLikeProjectRoot`, unchanged list) **or a `.git` entry** — the exact container shape that motivated the report — and never when the base is `$HOME` or the filesystem root. The gate lives in the new helper only; `planFrontload` and the prompt-hook are untouched, so #1454's surface is not widened. **Diagnostics (#1607).** The no-root branch is no longer silent: ``` [CodeGraph MCP] No .codegraph/ at or above <searchFrom>: no default project, live sync disabled. [CodeGraph MCP] Indexed sub-projects found: service-a, service-b. Pass `projectPath` per call, or launch with --path. ``` (second line only when the scan found candidates), plus one line naming the adopted child when adoption happens. The same fact is protocol-reachable: the "No CodeGraph project is loaded" tool response now lists the discovered sub-projects with `projectPath` guidance. The list is engine-maintained (initial resolve + throttled retry) — tool calls never scan — and the response stays SUCCESS-shaped (`NotIndexedError` → `textResult`, never `isError`). ## Tested - New `__tests__/mcp-subproject-adoption.test.ts` (real spawned server over stdio, same harness as `mcp-roots.test.ts`): single child → tool call answers from it, full instructions, adoption stderr; two children → no default, both listed in the tool response and stderr, per-project instructions; no manifest/no `.git` → gate holds, no scan, plain one-line message. - `mcp-subproject-adoption` + `mcp-roots` + `mcp-initialize` + `daemon-bind-failure`: **13/13 pass**. - End-to-end repro harness against the built `dist/` on an unmodified-main build first (confirmed: empty stderr, no adoption, NO_ROOT instructions even with one adoptable child), then on this branch (all three shapes behave as above; catch-up sync runs on the adopted child). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LxZj6W6Y1SHXwvpT3uwJpK |