@optave/codegraph 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Node role classification — pure logic, no DB.
3
3
  *
4
- * Roles: entry, core, utility, adapter, leaf, dead
4
+ * Roles: entry, core, utility, adapter, leaf, dead, test-only
5
5
  */
6
6
 
7
7
  export const FRAMEWORK_ENTRY_PREFIXES = ['route:', 'event:', 'command:'];
@@ -15,7 +15,7 @@ function median(sorted) {
15
15
  /**
16
16
  * Classify nodes into architectural roles based on fan-in/fan-out metrics.
17
17
  *
18
- * @param {{ id: string, name: string, fanIn: number, fanOut: number, isExported: boolean }[]} nodes
18
+ * @param {{ id: string, name: string, fanIn: number, fanOut: number, isExported: boolean, testOnlyFanIn?: number }[]} nodes
19
19
  * @returns {Map<string, string>} nodeId → role
20
20
  */
21
21
  export function classifyRoles(nodes) {
@@ -38,15 +38,18 @@ export function classifyRoles(nodes) {
38
38
  for (const node of nodes) {
39
39
  const highIn = node.fanIn >= medFanIn && node.fanIn > 0;
40
40
  const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
41
+ const hasProdFanIn = typeof node.productionFanIn === 'number';
41
42
 
42
43
  let role;
43
44
  const isFrameworkEntry = FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p));
44
45
  if (isFrameworkEntry) {
45
46
  role = 'entry';
46
47
  } else if (node.fanIn === 0 && !node.isExported) {
47
- role = 'dead';
48
+ role = node.testOnlyFanIn > 0 ? 'test-only' : 'dead';
48
49
  } else if (node.fanIn === 0 && node.isExported) {
49
50
  role = 'entry';
51
+ } else if (hasProdFanIn && node.fanIn > 0 && node.productionFanIn === 0) {
52
+ role = 'test-only';
50
53
  } else if (highIn && !highOut) {
51
54
  role = 'core';
52
55
  } else if (highIn && highOut) {
package/src/index.cjs ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * CJS compatibility wrapper — delegates to ESM via dynamic import().
3
+ *
4
+ * This wrapper always returns a Promise on every Node version, because
5
+ * import() is unconditionally async. You must always await the result:
6
+ *
7
+ * const codegraph = await require('@optave/codegraph');
8
+ *
9
+ * // Named destructuring at require-time does NOT work — always await the full result first.
10
+ * // BAD: const { buildGraph } = require('@optave/codegraph'); // buildGraph is undefined
11
+ * // GOOD: const { buildGraph } = await require('@optave/codegraph');
12
+ */
13
+ // Note: if import() rejects (e.g. missing dependency), the rejected Promise is cached
14
+ // by the CJS module system and every subsequent require() call will re-surface the same
15
+ // rejection without re-attempting the load.
16
+ module.exports = import('./index.js');
package/src/index.js CHANGED
@@ -9,42 +9,10 @@
9
9
  * import { buildGraph, queryNameData, findCycles, exportDOT } from '@optave/codegraph';
10
10
  */
11
11
 
12
- export { astQueryData } from './ast.js';
13
- export { auditData } from './audit.js';
14
- export { batchData } from './batch.js';
15
- export { branchCompareData } from './branch-compare.js';
16
- export { buildGraph } from './builder.js';
17
- export { cfgData } from './cfg.js';
18
- export { checkData } from './check.js';
19
- export { coChangeData } from './cochange.js';
20
- export { communitiesData } from './communities.js';
21
- export { complexityData } from './complexity.js';
22
- export { loadConfig } from './config.js';
23
- export { EXTENSIONS, IGNORE_DIRS } from './constants.js';
24
- export { findCycles } from './cycles.js';
25
- export { dataflowData } from './dataflow.js';
26
- export {
27
- buildEmbeddings,
28
- hybridSearchData,
29
- multiSearchData,
30
- searchData,
31
- } from './embeddings/index.js';
32
- export {
33
- AnalysisError,
34
- BoundaryError,
35
- CodegraphError,
36
- ConfigError,
37
- DbError,
38
- EngineError,
39
- ParseError,
40
- ResolutionError,
41
- } from './errors.js';
42
- export { exportDOT, exportJSON, exportMermaid } from './export.js';
43
- export { flowData, listEntryPointsData } from './flow.js';
44
- export { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND } from './kinds.js';
45
- export { manifestoData } from './manifesto.js';
46
- export { ownersData } from './owners.js';
12
+ export { buildGraph } from './domain/graph/builder.js';
13
+ export { findCycles } from './domain/graph/cycles.js';
47
14
  export {
15
+ briefData,
48
16
  childrenData,
49
17
  contextData,
50
18
  diffImpactData,
@@ -60,7 +28,40 @@ export {
60
28
  rolesData,
61
29
  statsData,
62
30
  whereData,
63
- } from './queries.js';
64
- export { sequenceData } from './sequence.js';
65
- export { hotspotsData, moduleBoundariesData, structureData } from './structure.js';
66
- export { triageData } from './triage.js';
31
+ } from './domain/queries.js';
32
+ export {
33
+ buildEmbeddings,
34
+ hybridSearchData,
35
+ multiSearchData,
36
+ searchData,
37
+ } from './domain/search/index.js';
38
+ export { astQueryData } from './features/ast.js';
39
+ export { auditData } from './features/audit.js';
40
+ export { batchData } from './features/batch.js';
41
+ export { branchCompareData } from './features/branch-compare.js';
42
+ export { cfgData } from './features/cfg.js';
43
+ export { checkData } from './features/check.js';
44
+ export { coChangeData } from './features/cochange.js';
45
+ export { communitiesData } from './features/communities.js';
46
+ export { complexityData } from './features/complexity.js';
47
+ export { dataflowData } from './features/dataflow.js';
48
+ export { exportDOT, exportJSON, exportMermaid } from './features/export.js';
49
+ export { flowData, listEntryPointsData } from './features/flow.js';
50
+ export { manifestoData } from './features/manifesto.js';
51
+ export { ownersData } from './features/owners.js';
52
+ export { sequenceData } from './features/sequence.js';
53
+ export { hotspotsData, moduleBoundariesData, structureData } from './features/structure.js';
54
+ export { triageData } from './features/triage.js';
55
+ export { loadConfig } from './infrastructure/config.js';
56
+ export { EXTENSIONS, IGNORE_DIRS } from './shared/constants.js';
57
+ export {
58
+ AnalysisError,
59
+ BoundaryError,
60
+ CodegraphError,
61
+ ConfigError,
62
+ DbError,
63
+ EngineError,
64
+ ParseError,
65
+ ResolutionError,
66
+ } from './shared/errors.js';
67
+ export { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND } from './shared/kinds.js';
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { createRequire } from 'node:module';
10
10
  import os from 'node:os';
11
- import { EngineError } from './errors.js';
11
+ import { EngineError } from '../shared/errors.js';
12
12
 
13
13
  let _cached; // undefined = not yet tried, null = failed, object = module
14
14
  let _loadError = null;
@@ -2,7 +2,7 @@
2
2
  * MCP middleware helpers — pagination defaults and limits.
3
3
  */
4
4
 
5
- import { MCP_DEFAULTS, MCP_MAX_LIMIT } from '../paginate.js';
5
+ import { MCP_DEFAULTS, MCP_MAX_LIMIT } from '../shared/paginate.js';
6
6
 
7
7
  export { MCP_DEFAULTS, MCP_MAX_LIMIT };
8
8
 
package/src/mcp/server.js CHANGED
@@ -6,9 +6,9 @@
6
6
  */
7
7
 
8
8
  import { createRequire } from 'node:module';
9
- import { findDbPath } from '../db.js';
10
- import { CodegraphError, ConfigError } from '../errors.js';
11
- import { MCP_MAX_LIMIT } from '../paginate.js';
9
+ import { findDbPath } from '../db/index.js';
10
+ import { CodegraphError, ConfigError } from '../shared/errors.js';
11
+ import { MCP_MAX_LIMIT } from '../shared/paginate.js';
12
12
  import { buildToolList } from './tool-registry.js';
13
13
  import { TOOL_HANDLERS } from './tools/index.js';
14
14
 
@@ -21,45 +21,84 @@ import { TOOL_HANDLERS } from './tools/index.js';
21
21
  * @param {boolean} [options.multiRepo] - Enable multi-repo access (default: false)
22
22
  * @param {string[]} [options.allowedRepos] - Restrict access to these repo names only
23
23
  */
24
- export async function startMCPServer(customDbPath, options = {}) {
25
- const { allowedRepos } = options;
26
- const multiRepo = options.multiRepo || !!allowedRepos;
27
- let Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema;
24
+ async function loadMCPSdk() {
28
25
  try {
29
26
  const sdk = await import('@modelcontextprotocol/sdk/server/index.js');
30
- Server = sdk.Server;
31
27
  const transport = await import('@modelcontextprotocol/sdk/server/stdio.js');
32
- StdioServerTransport = transport.StdioServerTransport;
33
28
  const types = await import('@modelcontextprotocol/sdk/types.js');
34
- ListToolsRequestSchema = types.ListToolsRequestSchema;
35
- CallToolRequestSchema = types.CallToolRequestSchema;
29
+ return {
30
+ Server: sdk.Server,
31
+ StdioServerTransport: transport.StdioServerTransport,
32
+ ListToolsRequestSchema: types.ListToolsRequestSchema,
33
+ CallToolRequestSchema: types.CallToolRequestSchema,
34
+ };
36
35
  } catch {
37
36
  throw new ConfigError(
38
37
  'MCP server requires @modelcontextprotocol/sdk.\nInstall it with: npm install @modelcontextprotocol/sdk',
39
38
  );
40
39
  }
40
+ }
41
41
 
42
- // Connect transport FIRST so the server can receive the client's
43
- // `initialize` request while heavy modules (queries, better-sqlite3)
44
- // are still loading. These are lazy-loaded on the first tool call
45
- // and cached for subsequent calls.
42
+ function createLazyLoaders() {
46
43
  let _queries;
47
44
  let _Database;
45
+ return {
46
+ async getQueries() {
47
+ if (!_queries) _queries = await import('../domain/queries.js');
48
+ return _queries;
49
+ },
50
+ getDatabase() {
51
+ if (!_Database) {
52
+ const require = createRequire(import.meta.url);
53
+ _Database = require('better-sqlite3');
54
+ }
55
+ return _Database;
56
+ },
57
+ };
58
+ }
48
59
 
49
- async function getQueries() {
50
- if (!_queries) {
51
- _queries = await import('../queries.js');
60
+ async function resolveDbPath(customDbPath, args, allowedRepos) {
61
+ let dbPath = customDbPath || undefined;
62
+ if (args.repo) {
63
+ if (allowedRepos && !allowedRepos.includes(args.repo)) {
64
+ throw new ConfigError(`Repository "${args.repo}" is not in the allowed repos list.`);
52
65
  }
53
- return _queries;
66
+ const { resolveRepoDbPath } = await import('../infrastructure/registry.js');
67
+ const resolved = resolveRepoDbPath(args.repo);
68
+ if (!resolved)
69
+ throw new ConfigError(
70
+ `Repository "${args.repo}" not found in registry or its database is missing.`,
71
+ );
72
+ dbPath = resolved;
54
73
  }
74
+ return dbPath;
75
+ }
55
76
 
56
- function getDatabase() {
57
- if (!_Database) {
58
- const require = createRequire(import.meta.url);
59
- _Database = require('better-sqlite3');
60
- }
61
- return _Database;
77
+ function validateMultiRepoAccess(multiRepo, name, args) {
78
+ if (!multiRepo && args.repo) {
79
+ throw new ConfigError(
80
+ 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
81
+ );
82
+ }
83
+ if (!multiRepo && name === 'list_repos') {
84
+ throw new ConfigError(
85
+ 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
86
+ );
62
87
  }
88
+ }
89
+
90
+ export async function startMCPServer(customDbPath, options = {}) {
91
+ const { allowedRepos } = options;
92
+ const multiRepo = options.multiRepo || !!allowedRepos;
93
+
94
+ const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
95
+ await loadMCPSdk();
96
+
97
+ // Connect transport FIRST so the server can receive the client's
98
+ // `initialize` request while heavy modules (queries, better-sqlite3)
99
+ // are still loading. These are lazy-loaded on the first tool call
100
+ // and cached for subsequent calls.
101
+ const { getQueries, getDatabase } = createLazyLoaders();
63
102
 
64
103
  const server = new Server(
65
104
  { name: 'codegraph', version: '1.0.0' },
@@ -73,47 +112,17 @@ export async function startMCPServer(customDbPath, options = {}) {
73
112
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
74
113
  const { name, arguments: args } = request.params;
75
114
  try {
76
- if (!multiRepo && args.repo) {
77
- throw new ConfigError(
78
- 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
79
- );
80
- }
81
- if (!multiRepo && name === 'list_repos') {
82
- throw new ConfigError(
83
- 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
84
- );
85
- }
86
-
87
- let dbPath = customDbPath || undefined;
88
- if (args.repo) {
89
- if (allowedRepos && !allowedRepos.includes(args.repo)) {
90
- throw new ConfigError(`Repository "${args.repo}" is not in the allowed repos list.`);
91
- }
92
- const { resolveRepoDbPath } = await import('../registry.js');
93
- const resolved = resolveRepoDbPath(args.repo);
94
- if (!resolved)
95
- throw new ConfigError(
96
- `Repository "${args.repo}" not found in registry or its database is missing.`,
97
- );
98
- dbPath = resolved;
99
- }
115
+ validateMultiRepoAccess(multiRepo, name, args);
116
+ const dbPath = await resolveDbPath(customDbPath, args, allowedRepos);
100
117
 
101
118
  const toolEntry = TOOL_HANDLERS.get(name);
102
119
  if (!toolEntry) {
103
120
  return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
104
121
  }
105
122
 
106
- const ctx = {
107
- dbPath,
108
- getQueries,
109
- getDatabase,
110
- findDbPath,
111
- allowedRepos,
112
- MCP_MAX_LIMIT,
113
- };
114
-
123
+ const ctx = { dbPath, getQueries, getDatabase, findDbPath, allowedRepos, MCP_MAX_LIMIT };
115
124
  const result = await toolEntry.handler(args, ctx);
116
- if (result?.content) return result; // pass-through MCP responses
125
+ if (result?.content) return result;
117
126
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
118
127
  } catch (err) {
119
128
  const code = err instanceof CodegraphError ? err.code : 'UNKNOWN_ERROR';
@@ -4,8 +4,8 @@
4
4
  * Owns BASE_TOOLS, LIST_REPOS_TOOL, buildToolList(), and the backward-compatible TOOLS export.
5
5
  */
6
6
 
7
- import { AST_NODE_KINDS } from '../ast.js';
8
- import { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND, VALID_ROLES } from '../queries.js';
7
+ import { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND, VALID_ROLES } from '../domain/queries.js';
8
+ import { AST_NODE_KINDS } from '../features/ast.js';
9
9
 
10
10
  const REPO_PROP = {
11
11
  repo: {
@@ -99,6 +99,19 @@ const BASE_TOOLS = [
99
99
  required: ['file'],
100
100
  },
101
101
  },
102
+ {
103
+ name: 'brief',
104
+ description:
105
+ 'Token-efficient file summary: symbols with roles and transitive caller counts, importer counts, and file risk tier (high/medium/low). Designed for context injection.',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {
109
+ file: { type: 'string', description: 'File path (partial match supported)' },
110
+ no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
111
+ },
112
+ required: ['file'],
113
+ },
114
+ },
102
115
  {
103
116
  name: 'file_exports',
104
117
  description:
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
3
3
  export const name = 'ast_query';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { astQueryData } = await import('../../ast.js');
6
+ const { astQueryData } = await import('../../features/ast.js');
7
7
  return astQueryData(args.pattern, ctx.dbPath, {
8
8
  kind: args.kind,
9
9
  file: args.file,
@@ -11,7 +11,7 @@ export async function handler(args, ctx) {
11
11
  offset: effectiveOffset(args),
12
12
  });
13
13
  }
14
- const { auditData } = await import('../../audit.js');
14
+ const { auditData } = await import('../../features/audit.js');
15
15
  return auditData(args.target, ctx.dbPath, {
16
16
  depth: args.depth,
17
17
  file: args.file,
@@ -1,7 +1,7 @@
1
1
  export const name = 'batch_query';
2
2
 
3
3
  export async function handler(args, ctx) {
4
- const { batchData } = await import('../../batch.js');
4
+ const { batchData } = await import('../../features/batch.js');
5
5
  return batchData(args.command, args.targets, ctx.dbPath, {
6
6
  depth: args.depth,
7
7
  file: args.file,
@@ -1,7 +1,9 @@
1
1
  export const name = 'branch_compare';
2
2
 
3
3
  export async function handler(args, _ctx) {
4
- const { branchCompareData, branchCompareMermaid } = await import('../../branch-compare.js');
4
+ const { branchCompareData, branchCompareMermaid } = await import(
5
+ '../../features/branch-compare.js'
6
+ );
5
7
  const bcData = await branchCompareData(args.base, args.target, {
6
8
  depth: args.depth,
7
9
  noTests: args.no_tests,
@@ -0,0 +1,8 @@
1
+ export const name = 'brief';
2
+
3
+ export async function handler(args, ctx) {
4
+ const { briefData } = await ctx.getQueries();
5
+ return briefData(args.file, ctx.dbPath, {
6
+ noTests: args.no_tests,
7
+ });
8
+ }
@@ -3,7 +3,7 @@ import { effectiveOffset, MCP_DEFAULTS } from '../middleware.js';
3
3
  export const name = 'cfg';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { cfgData, cfgToDOT, cfgToMermaid } = await import('../../cfg.js');
6
+ const { cfgData, cfgToDOT, cfgToMermaid } = await import('../../features/cfg.js');
7
7
  const cfgResult = cfgData(args.name, ctx.dbPath, {
8
8
  file: args.file,
9
9
  kind: args.kind,
@@ -6,7 +6,7 @@ export async function handler(args, ctx) {
6
6
  const isDiffMode = args.ref || args.staged;
7
7
 
8
8
  if (!isDiffMode && !args.rules) {
9
- const { manifestoData } = await import('../../manifesto.js');
9
+ const { manifestoData } = await import('../../features/manifesto.js');
10
10
  return manifestoData(ctx.dbPath, {
11
11
  file: args.file,
12
12
  noTests: args.no_tests,
@@ -16,7 +16,7 @@ export async function handler(args, ctx) {
16
16
  });
17
17
  }
18
18
 
19
- const { checkData } = await import('../../check.js');
19
+ const { checkData } = await import('../../features/check.js');
20
20
  const checkResult = checkData(ctx.dbPath, {
21
21
  ref: args.ref,
22
22
  staged: args.staged,
@@ -29,7 +29,7 @@ export async function handler(args, ctx) {
29
29
  });
30
30
 
31
31
  if (args.rules) {
32
- const { manifestoData } = await import('../../manifesto.js');
32
+ const { manifestoData } = await import('../../features/manifesto.js');
33
33
  const manifestoResult = manifestoData(ctx.dbPath, {
34
34
  file: args.file,
35
35
  noTests: args.no_tests,
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
3
3
  export const name = 'co_changes';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { coChangeData, coChangeTopData } = await import('../../cochange.js');
6
+ const { coChangeData, coChangeTopData } = await import('../../features/cochange.js');
7
7
  return args.file
8
8
  ? coChangeData(args.file, ctx.dbPath, {
9
9
  limit: effectiveLimit(args, name),
@@ -1,7 +1,7 @@
1
1
  export const name = 'code_owners';
2
2
 
3
3
  export async function handler(args, ctx) {
4
- const { ownersData } = await import('../../owners.js');
4
+ const { ownersData } = await import('../../features/owners.js');
5
5
  return ownersData(ctx.dbPath, {
6
6
  file: args.file,
7
7
  owner: args.owner,
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
3
3
  export const name = 'communities';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { communitiesData } = await import('../../communities.js');
6
+ const { communitiesData } = await import('../../features/communities.js');
7
7
  return communitiesData(ctx.dbPath, {
8
8
  functions: args.functions,
9
9
  resolution: args.resolution,
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
3
3
  export const name = 'complexity';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { complexityData } = await import('../../complexity.js');
6
+ const { complexityData } = await import('../../features/complexity.js');
7
7
  return complexityData(ctx.dbPath, {
8
8
  target: args.name,
9
9
  file: args.file,
@@ -5,7 +5,7 @@ export const name = 'dataflow';
5
5
  export async function handler(args, ctx) {
6
6
  const dfMode = args.mode || 'edges';
7
7
  if (dfMode === 'impact') {
8
- const { dataflowImpactData } = await import('../../dataflow.js');
8
+ const { dataflowImpactData } = await import('../../features/dataflow.js');
9
9
  return dataflowImpactData(args.name, ctx.dbPath, {
10
10
  depth: args.depth,
11
11
  file: args.file,
@@ -15,7 +15,7 @@ export async function handler(args, ctx) {
15
15
  offset: effectiveOffset(args),
16
16
  });
17
17
  }
18
- const { dataflowData } = await import('../../dataflow.js');
18
+ const { dataflowData } = await import('../../features/dataflow.js');
19
19
  return dataflowData(args.name, ctx.dbPath, {
20
20
  file: args.file,
21
21
  kind: args.kind,
@@ -4,7 +4,7 @@ export const name = 'execution_flow';
4
4
 
5
5
  export async function handler(args, ctx) {
6
6
  if (args.list) {
7
- const { listEntryPointsData } = await import('../../flow.js');
7
+ const { listEntryPointsData } = await import('../../features/flow.js');
8
8
  return listEntryPointsData(ctx.dbPath, {
9
9
  noTests: args.no_tests,
10
10
  limit: effectiveLimit(args, name),
@@ -14,7 +14,7 @@ export async function handler(args, ctx) {
14
14
  if (!args.name) {
15
15
  return { error: 'Provide a name or set list=true' };
16
16
  }
17
- const { flowData } = await import('../../flow.js');
17
+ const { flowData } = await import('../../features/flow.js');
18
18
  return flowData(args.name, ctx.dbPath, {
19
19
  depth: args.depth,
20
20
  file: args.file,
@@ -1,11 +1,11 @@
1
- import { findDbPath } from '../../db.js';
1
+ import { findDbPath } from '../../db/index.js';
2
2
  import { effectiveOffset, MCP_DEFAULTS, MCP_MAX_LIMIT } from '../middleware.js';
3
3
 
4
4
  export const name = 'export_graph';
5
5
 
6
6
  export async function handler(args, ctx) {
7
7
  const { exportDOT, exportGraphML, exportGraphSON, exportJSON, exportMermaid, exportNeo4jCSV } =
8
- await import('../../export.js');
8
+ await import('../../features/export.js');
9
9
  const Database = ctx.getDatabase();
10
10
  const db = new Database(findDbPath(ctx.dbPath), { readonly: true });
11
11
  const fileLevel = args.file_level !== false;
@@ -1,5 +1,5 @@
1
- import { findCycles } from '../../cycles.js';
2
- import { findDbPath } from '../../db.js';
1
+ import { findDbPath } from '../../db/index.js';
2
+ import { findCycles } from '../../domain/graph/cycles.js';
3
3
 
4
4
  export const name = 'find_cycles';
5
5
 
@@ -6,6 +6,7 @@ import * as astQuery from './ast-query.js';
6
6
  import * as audit from './audit.js';
7
7
  import * as batchQuery from './batch-query.js';
8
8
  import * as branchCompare from './branch-compare.js';
9
+ import * as brief from './brief.js';
9
10
  import * as cfg from './cfg.js';
10
11
  import * as check from './check.js';
11
12
  import * as coChanges from './co-changes.js';
@@ -67,5 +68,6 @@ export const TOOL_HANDLERS = new Map([
67
68
  [dataflow.name, dataflow],
68
69
  [check.name, check],
69
70
  [astQuery.name, astQuery],
71
+ [brief.name, brief],
70
72
  [listRepos.name, listRepos],
71
73
  ]);
@@ -1,7 +1,7 @@
1
1
  export const name = 'list_repos';
2
2
 
3
3
  export async function handler(_args, ctx) {
4
- const { listRepos, pruneRegistry } = await import('../../registry.js');
4
+ const { listRepos, pruneRegistry } = await import('../../infrastructure/registry.js');
5
5
  pruneRegistry();
6
6
  let repos = listRepos();
7
7
  if (ctx.allowedRepos) {
@@ -3,7 +3,7 @@ import { effectiveOffset, MCP_DEFAULTS } from '../middleware.js';
3
3
  export const name = 'sequence';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { sequenceData, sequenceToMermaid } = await import('../../sequence.js');
6
+ const { sequenceData, sequenceToMermaid } = await import('../../features/sequence.js');
7
7
  const seqResult = sequenceData(args.name, ctx.dbPath, {
8
8
  depth: args.depth,
9
9
  file: args.file,
@@ -3,7 +3,7 @@ import { effectiveLimit, effectiveOffset } from '../middleware.js';
3
3
  export const name = 'structure';
4
4
 
5
5
  export async function handler(args, ctx) {
6
- const { structureData } = await import('../../structure.js');
6
+ const { structureData } = await import('../../features/structure.js');
7
7
  return structureData(ctx.dbPath, {
8
8
  directory: args.directory,
9
9
  depth: args.depth,
@@ -4,7 +4,7 @@ export const name = 'triage';
4
4
 
5
5
  export async function handler(args, ctx) {
6
6
  if (args.level === 'file' || args.level === 'directory') {
7
- const { hotspotsData } = await import('../../structure.js');
7
+ const { hotspotsData } = await import('../../features/structure.js');
8
8
  const TRIAGE_TO_HOTSPOT = {
9
9
  risk: 'fan-in',
10
10
  complexity: 'density',
@@ -20,7 +20,7 @@ export async function handler(args, ctx) {
20
20
  noTests: args.no_tests,
21
21
  });
22
22
  }
23
- const { triageData } = await import('../../triage.js');
23
+ const { triageData } = await import('../../features/triage.js');
24
24
  return triageData(ctx.dbPath, {
25
25
  sort: args.sort,
26
26
  minScore: args.min_score,
@@ -1,6 +1,6 @@
1
- import { auditData } from '../audit.js';
1
+ import { kindIcon } from '../domain/queries.js';
2
+ import { auditData } from '../features/audit.js';
2
3
  import { outputResult } from '../infrastructure/result-formatter.js';
3
- import { kindIcon } from '../queries.js';
4
4
 
5
5
  /**
6
6
  * CLI formatter for the audit command.
@@ -1,4 +1,4 @@
1
- import { batchData, multiBatchData } from '../batch.js';
1
+ import { batchData, multiBatchData } from '../features/batch.js';
2
2
 
3
3
  /**
4
4
  * CLI wrapper — calls batchData and prints JSON to stdout.
@@ -1,6 +1,6 @@
1
- import { branchCompareData, branchCompareMermaid } from '../branch-compare.js';
1
+ import { kindIcon } from '../domain/queries.js';
2
+ import { branchCompareData, branchCompareMermaid } from '../features/branch-compare.js';
2
3
  import { outputResult } from '../infrastructure/result-formatter.js';
3
- import { kindIcon } from '../queries.js';
4
4
 
5
5
  // ─── Text Formatting ────────────────────────────────────────────────────
6
6