From 9ea9676dc15ad969cacd4131701b3b6d46b185c7 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Mon, 3 Aug 2026 12:44:21 +0000 Subject: [PATCH] perf(mcp): lazy-import syncGroup so MCP startup skips the group extractor closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `core/group/service.ts` statically imported `./sync.js`, which pulls all six contract extractors, five of which statically import the native `tree-sitter` binding. That put the whole parser stack on every MCP server start, for a server that never syncs. Only `groupSync` needs it. The other seven group tools — `group_list`, `group_impact`, `group_query`, `group_contracts`, `group_status`, `group_trace`, `group_context` — do not, and now never load it. `syncGroup` has a single call site, already inside an `async` method, so this is a lazy `await import(...)` at that call site and nothing else: no signature change, no async ripple, no change to `local-backend.ts`. The pattern is already established on this exact module — `cli/group.ts`'s sync command lazy-imports `sync.js` the same way. `service.ts` was the outlier. Measured on a native filesystem (overlayfs; /workspace is a 9p mount that inflates ESM resolve, so it is not a valid measurement surface), 5 cold runs, medians: dist/mcp/server.js 521 ms -> 133 ms (-75%) dist/mcp/local/local-backend.js 453 ms -> 66 ms (-85%) tree-sitter modules at both entries: 11 -> 0 Same defect class as #2802, which cut the language-provider registry from the same startup path; this is what remained. The cost is moved rather than deleted: the first `group_sync` call now pays the module load. That is the right trade — `group_sync` is already a long-running operation, and sessions that never sync pay nothing. Refs #2802 Co-Authored-By: Claude Opus 5 (1M context) --- gitnexus/src/core/group/service.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/gitnexus/src/core/group/service.ts b/gitnexus/src/core/group/service.ts index c18587ce2..f1356dcec 100644 --- a/gitnexus/src/core/group/service.ts +++ b/gitnexus/src/core/group/service.ts @@ -14,7 +14,10 @@ import { repoInSubgroup, } from './group-path-utils.js'; import { getDefaultGitnexusDir, getGroupDir, listGroups, readContractRegistry } from './storage.js'; -import { syncGroup } from './sync.js'; +// `./sync.js` is imported LAZILY in `groupSync` — see the comment at its call +// site. It statically pulls the six contract extractors and, through them, the +// native tree-sitter binding; a static import here puts all of that on MCP +// server startup, which never syncs. import { logger } from '../logger.js'; import type { ContractRegistry, @@ -338,6 +341,12 @@ export class GroupService { return { error: `Group "${name}" not found. Run group_list to see configured groups.` }; throw err; } + // Lazy: `sync.js` reaches the six contract extractors and the native + // tree-sitter binding. `groupSync` is the ONLY consumer — the other seven + // group tools never need it — so deferring it here keeps that closure off + // MCP server startup entirely and off every non-sync group call. The CLI + // already does exactly this at `cli/group.ts`'s sync command. + const { syncGroup } = await import('./sync.js'); const result = await syncGroup(config, { groupDir, exactOnly: Boolean(params.exactOnly),