@optave/codegraph 3.1.3 → 3.1.5

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 (232) hide show
  1. package/README.md +38 -84
  2. package/package.json +13 -8
  3. package/src/ast-analysis/engine.js +32 -12
  4. package/src/ast-analysis/shared.js +6 -5
  5. package/src/cli/commands/ast.js +22 -0
  6. package/src/cli/commands/audit.js +45 -0
  7. package/src/cli/commands/batch.js +68 -0
  8. package/src/cli/commands/branch-compare.js +21 -0
  9. package/src/cli/commands/build.js +26 -0
  10. package/src/cli/commands/cfg.js +26 -0
  11. package/src/cli/commands/check.js +74 -0
  12. package/src/cli/commands/children.js +28 -0
  13. package/src/cli/commands/co-change.js +67 -0
  14. package/src/cli/commands/communities.js +19 -0
  15. package/src/cli/commands/complexity.js +46 -0
  16. package/src/cli/commands/context.js +30 -0
  17. package/src/cli/commands/cycles.js +32 -0
  18. package/src/cli/commands/dataflow.js +28 -0
  19. package/src/cli/commands/deps.js +12 -0
  20. package/src/cli/commands/diff-impact.js +26 -0
  21. package/src/cli/commands/embed.js +30 -0
  22. package/src/cli/commands/export.js +78 -0
  23. package/src/cli/commands/exports.js +14 -0
  24. package/src/cli/commands/flow.js +32 -0
  25. package/src/cli/commands/fn-impact.js +26 -0
  26. package/src/cli/commands/impact.js +12 -0
  27. package/src/cli/commands/info.js +76 -0
  28. package/src/cli/commands/map.js +19 -0
  29. package/src/cli/commands/mcp.js +18 -0
  30. package/src/cli/commands/models.js +19 -0
  31. package/src/cli/commands/owners.js +25 -0
  32. package/src/cli/commands/path.js +36 -0
  33. package/src/cli/commands/plot.js +89 -0
  34. package/src/cli/commands/query.js +45 -0
  35. package/src/cli/commands/registry.js +100 -0
  36. package/src/cli/commands/roles.js +30 -0
  37. package/src/cli/commands/search.js +42 -0
  38. package/src/cli/commands/sequence.js +28 -0
  39. package/src/cli/commands/snapshot.js +66 -0
  40. package/src/cli/commands/stats.js +15 -0
  41. package/src/cli/commands/structure.js +33 -0
  42. package/src/cli/commands/triage.js +78 -0
  43. package/src/cli/commands/watch.js +12 -0
  44. package/src/cli/commands/where.js +20 -0
  45. package/src/cli/index.js +124 -0
  46. package/src/cli/shared/open-graph.js +13 -0
  47. package/src/cli/shared/options.js +59 -0
  48. package/src/cli/shared/output.js +1 -0
  49. package/src/cli.js +11 -1522
  50. package/src/db/connection.js +130 -7
  51. package/src/{db.js → db/index.js} +17 -5
  52. package/src/db/migrations.js +42 -1
  53. package/src/db/query-builder.js +20 -12
  54. package/src/db/repository/base.js +201 -0
  55. package/src/db/repository/graph-read.js +7 -4
  56. package/src/db/repository/in-memory-repository.js +575 -0
  57. package/src/db/repository/index.js +5 -1
  58. package/src/db/repository/nodes.js +60 -6
  59. package/src/db/repository/sqlite-repository.js +219 -0
  60. package/src/domain/analysis/context.js +408 -0
  61. package/src/domain/analysis/dependencies.js +341 -0
  62. package/src/domain/analysis/exports.js +134 -0
  63. package/src/domain/analysis/impact.js +466 -0
  64. package/src/domain/analysis/module-map.js +322 -0
  65. package/src/domain/analysis/roles.js +45 -0
  66. package/src/domain/analysis/symbol-lookup.js +238 -0
  67. package/src/domain/graph/builder/context.js +85 -0
  68. package/src/domain/graph/builder/helpers.js +218 -0
  69. package/src/domain/graph/builder/incremental.js +178 -0
  70. package/src/domain/graph/builder/pipeline.js +130 -0
  71. package/src/domain/graph/builder/stages/build-edges.js +297 -0
  72. package/src/domain/graph/builder/stages/build-structure.js +113 -0
  73. package/src/domain/graph/builder/stages/collect-files.js +44 -0
  74. package/src/domain/graph/builder/stages/detect-changes.js +413 -0
  75. package/src/domain/graph/builder/stages/finalize.js +139 -0
  76. package/src/domain/graph/builder/stages/insert-nodes.js +195 -0
  77. package/src/domain/graph/builder/stages/parse-files.js +28 -0
  78. package/src/domain/graph/builder/stages/resolve-imports.js +143 -0
  79. package/src/domain/graph/builder/stages/run-analyses.js +44 -0
  80. package/src/domain/graph/builder.js +11 -0
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/domain/graph/cycles.js +82 -0
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +3 -3
  85. package/src/{watcher.js → domain/graph/watcher.js} +10 -150
  86. package/src/{parser.js → domain/parser.js} +5 -5
  87. package/src/domain/queries.js +48 -0
  88. package/src/domain/search/generator.js +163 -0
  89. package/src/domain/search/index.js +13 -0
  90. package/src/domain/search/models.js +218 -0
  91. package/src/domain/search/search/cli-formatter.js +151 -0
  92. package/src/domain/search/search/filters.js +46 -0
  93. package/src/domain/search/search/hybrid.js +121 -0
  94. package/src/domain/search/search/keyword.js +68 -0
  95. package/src/domain/search/search/prepare.js +66 -0
  96. package/src/domain/search/search/semantic.js +145 -0
  97. package/src/domain/search/stores/fts5.js +27 -0
  98. package/src/domain/search/stores/sqlite-blob.js +24 -0
  99. package/src/domain/search/strategies/source.js +14 -0
  100. package/src/domain/search/strategies/structured.js +43 -0
  101. package/src/domain/search/strategies/text-utils.js +43 -0
  102. package/src/extractors/csharp.js +10 -2
  103. package/src/extractors/go.js +3 -1
  104. package/src/extractors/helpers.js +71 -0
  105. package/src/extractors/java.js +9 -2
  106. package/src/extractors/javascript.js +39 -2
  107. package/src/extractors/php.js +3 -1
  108. package/src/extractors/python.js +14 -3
  109. package/src/extractors/rust.js +3 -1
  110. package/src/{ast.js → features/ast.js} +8 -8
  111. package/src/{audit.js → features/audit.js} +16 -44
  112. package/src/{batch.js → features/batch.js} +6 -5
  113. package/src/{boundaries.js → features/boundaries.js} +2 -2
  114. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  115. package/src/{cfg.js → features/cfg.js} +11 -12
  116. package/src/{check.js → features/check.js} +13 -30
  117. package/src/{cochange.js → features/cochange.js} +5 -5
  118. package/src/{communities.js → features/communities.js} +18 -90
  119. package/src/{complexity.js → features/complexity.js} +13 -13
  120. package/src/{dataflow.js → features/dataflow.js} +12 -13
  121. package/src/features/export.js +378 -0
  122. package/src/{flow.js → features/flow.js} +4 -4
  123. package/src/features/graph-enrichment.js +327 -0
  124. package/src/{manifesto.js → features/manifesto.js} +6 -6
  125. package/src/{owners.js → features/owners.js} +2 -2
  126. package/src/{sequence.js → features/sequence.js} +16 -52
  127. package/src/{snapshot.js → features/snapshot.js} +8 -7
  128. package/src/{structure.js → features/structure.js} +20 -45
  129. package/src/{triage.js → features/triage.js} +27 -79
  130. package/src/graph/algorithms/bfs.js +49 -0
  131. package/src/graph/algorithms/centrality.js +16 -0
  132. package/src/graph/algorithms/index.js +5 -0
  133. package/src/graph/algorithms/louvain.js +26 -0
  134. package/src/graph/algorithms/shortest-path.js +41 -0
  135. package/src/graph/algorithms/tarjan.js +49 -0
  136. package/src/graph/builders/dependency.js +110 -0
  137. package/src/graph/builders/index.js +3 -0
  138. package/src/graph/builders/structure.js +40 -0
  139. package/src/graph/builders/temporal.js +33 -0
  140. package/src/graph/classifiers/index.js +2 -0
  141. package/src/graph/classifiers/risk.js +85 -0
  142. package/src/graph/classifiers/roles.js +64 -0
  143. package/src/graph/index.js +13 -0
  144. package/src/graph/model.js +230 -0
  145. package/src/index.cjs +16 -0
  146. package/src/index.js +42 -219
  147. package/src/{native.js → infrastructure/native.js} +3 -1
  148. package/src/infrastructure/result-formatter.js +2 -21
  149. package/src/mcp/index.js +2 -0
  150. package/src/mcp/middleware.js +26 -0
  151. package/src/mcp/server.js +128 -0
  152. package/src/{mcp.js → mcp/tool-registry.js} +6 -675
  153. package/src/mcp/tools/ast-query.js +14 -0
  154. package/src/mcp/tools/audit.js +21 -0
  155. package/src/mcp/tools/batch-query.js +11 -0
  156. package/src/mcp/tools/branch-compare.js +12 -0
  157. package/src/mcp/tools/cfg.js +21 -0
  158. package/src/mcp/tools/check.js +43 -0
  159. package/src/mcp/tools/co-changes.js +20 -0
  160. package/src/mcp/tools/code-owners.js +12 -0
  161. package/src/mcp/tools/communities.js +15 -0
  162. package/src/mcp/tools/complexity.js +18 -0
  163. package/src/mcp/tools/context.js +17 -0
  164. package/src/mcp/tools/dataflow.js +26 -0
  165. package/src/mcp/tools/diff-impact.js +24 -0
  166. package/src/mcp/tools/execution-flow.js +26 -0
  167. package/src/mcp/tools/export-graph.js +57 -0
  168. package/src/mcp/tools/file-deps.js +12 -0
  169. package/src/mcp/tools/file-exports.js +13 -0
  170. package/src/mcp/tools/find-cycles.js +15 -0
  171. package/src/mcp/tools/fn-impact.js +15 -0
  172. package/src/mcp/tools/impact-analysis.js +12 -0
  173. package/src/mcp/tools/index.js +71 -0
  174. package/src/mcp/tools/list-functions.js +14 -0
  175. package/src/mcp/tools/list-repos.js +11 -0
  176. package/src/mcp/tools/module-map.js +6 -0
  177. package/src/mcp/tools/node-roles.js +14 -0
  178. package/src/mcp/tools/path.js +12 -0
  179. package/src/mcp/tools/query.js +30 -0
  180. package/src/mcp/tools/semantic-search.js +65 -0
  181. package/src/mcp/tools/sequence.js +17 -0
  182. package/src/mcp/tools/structure.js +15 -0
  183. package/src/mcp/tools/symbol-children.js +14 -0
  184. package/src/mcp/tools/triage.js +35 -0
  185. package/src/mcp/tools/where.js +13 -0
  186. package/src/{commands → presentation}/audit.js +2 -2
  187. package/src/{commands → presentation}/batch.js +1 -1
  188. package/src/{commands → presentation}/branch-compare.js +2 -2
  189. package/src/{commands → presentation}/cfg.js +1 -1
  190. package/src/{commands → presentation}/check.js +6 -6
  191. package/src/presentation/colors.js +44 -0
  192. package/src/{commands → presentation}/communities.js +1 -1
  193. package/src/{commands → presentation}/complexity.js +1 -1
  194. package/src/{commands → presentation}/dataflow.js +1 -1
  195. package/src/presentation/export.js +444 -0
  196. package/src/{commands → presentation}/flow.js +2 -2
  197. package/src/{commands → presentation}/manifesto.js +4 -4
  198. package/src/{commands → presentation}/owners.js +1 -1
  199. package/src/presentation/queries-cli/exports.js +46 -0
  200. package/src/presentation/queries-cli/impact.js +198 -0
  201. package/src/presentation/queries-cli/index.js +5 -0
  202. package/src/presentation/queries-cli/inspect.js +334 -0
  203. package/src/presentation/queries-cli/overview.js +197 -0
  204. package/src/presentation/queries-cli/path.js +58 -0
  205. package/src/presentation/queries-cli.js +27 -0
  206. package/src/{commands → presentation}/query.js +1 -1
  207. package/src/presentation/result-formatter.js +144 -0
  208. package/src/presentation/sequence-renderer.js +43 -0
  209. package/src/{commands → presentation}/sequence.js +2 -2
  210. package/src/{commands → presentation}/structure.js +2 -2
  211. package/src/presentation/table.js +47 -0
  212. package/src/{commands → presentation}/triage.js +1 -1
  213. package/src/{viewer.js → presentation/viewer.js} +68 -382
  214. package/src/{constants.js → shared/constants.js} +1 -1
  215. package/src/shared/errors.js +78 -0
  216. package/src/shared/file-utils.js +153 -0
  217. package/src/shared/generators.js +125 -0
  218. package/src/shared/hierarchy.js +27 -0
  219. package/src/shared/normalize.js +59 -0
  220. package/src/builder.js +0 -1486
  221. package/src/cycles.js +0 -137
  222. package/src/embedder.js +0 -1097
  223. package/src/export.js +0 -681
  224. package/src/queries-cli.js +0 -866
  225. package/src/queries.js +0 -2289
  226. /package/src/{config.js → infrastructure/config.js} +0 -0
  227. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  228. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  229. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  230. /package/src/{commands → presentation}/cochange.js +0 -0
  231. /package/src/{kinds.js → shared/kinds.js} +0 -0
  232. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -1,16 +1,11 @@
1
1
  /**
2
- * MCP (Model Context Protocol) server for codegraph.
3
- * Exposes codegraph queries as tools that AI coding assistants can call.
2
+ * MCP tool schema registry.
4
3
  *
5
- * Requires: npm install @modelcontextprotocol/sdk
4
+ * Owns BASE_TOOLS, LIST_REPOS_TOOL, buildToolList(), and the backward-compatible TOOLS export.
6
5
  */
7
6
 
8
- import { createRequire } from 'node:module';
9
- import { AST_NODE_KINDS } from './ast.js';
10
- import { findCycles } from './cycles.js';
11
- import { findDbPath } from './db.js';
12
- import { MCP_DEFAULTS, MCP_MAX_LIMIT } from './paginate.js';
13
- import { diffImpactMermaid, 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';
14
9
 
15
10
  const REPO_PROP = {
16
11
  repo: {
@@ -788,7 +783,7 @@ const LIST_REPOS_TOOL = {
788
783
  * @param {boolean} multiRepo - If true, inject `repo` prop into each tool and append `list_repos`
789
784
  * @returns {object[]}
790
785
  */
791
- function buildToolList(multiRepo) {
786
+ export function buildToolList(multiRepo) {
792
787
  if (!multiRepo) return BASE_TOOLS;
793
788
  return [
794
789
  ...BASE_TOOLS.map((tool) => ({
@@ -803,668 +798,4 @@ function buildToolList(multiRepo) {
803
798
  }
804
799
 
805
800
  // Backward-compatible export: full multi-repo tool list
806
- const TOOLS = buildToolList(true);
807
-
808
- export { TOOLS, buildToolList };
809
-
810
- /**
811
- * Start the MCP server.
812
- * This function requires @modelcontextprotocol/sdk to be installed.
813
- *
814
- * @param {string} [customDbPath] - Path to a specific graph.db
815
- * @param {object} [options]
816
- * @param {boolean} [options.multiRepo] - Enable multi-repo access (default: false)
817
- * @param {string[]} [options.allowedRepos] - Restrict access to these repo names only
818
- */
819
- export async function startMCPServer(customDbPath, options = {}) {
820
- const { allowedRepos } = options;
821
- const multiRepo = options.multiRepo || !!allowedRepos;
822
- let Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema;
823
- try {
824
- const sdk = await import('@modelcontextprotocol/sdk/server/index.js');
825
- Server = sdk.Server;
826
- const transport = await import('@modelcontextprotocol/sdk/server/stdio.js');
827
- StdioServerTransport = transport.StdioServerTransport;
828
- const types = await import('@modelcontextprotocol/sdk/types.js');
829
- ListToolsRequestSchema = types.ListToolsRequestSchema;
830
- CallToolRequestSchema = types.CallToolRequestSchema;
831
- } catch {
832
- console.error(
833
- 'MCP server requires @modelcontextprotocol/sdk.\n' +
834
- 'Install it with: npm install @modelcontextprotocol/sdk',
835
- );
836
- process.exit(1);
837
- }
838
-
839
- // Connect transport FIRST so the server can receive the client's
840
- // `initialize` request while heavy modules (queries, better-sqlite3)
841
- // are still loading. These are lazy-loaded on the first tool call
842
- // and cached for subsequent calls.
843
- let _queries;
844
- let _Database;
845
-
846
- async function getQueries() {
847
- if (!_queries) {
848
- _queries = await import('./queries.js');
849
- }
850
- return _queries;
851
- }
852
-
853
- function getDatabase() {
854
- if (!_Database) {
855
- const require = createRequire(import.meta.url);
856
- _Database = require('better-sqlite3');
857
- }
858
- return _Database;
859
- }
860
-
861
- const server = new Server(
862
- { name: 'codegraph', version: '1.0.0' },
863
- { capabilities: { tools: {} } },
864
- );
865
-
866
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
867
- tools: buildToolList(multiRepo),
868
- }));
869
-
870
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
871
- const { name, arguments: args } = request.params;
872
- try {
873
- const {
874
- impactAnalysisData,
875
- moduleMapData,
876
- fileDepsData,
877
- exportsData,
878
- fnDepsData,
879
- fnImpactData,
880
- pathData,
881
- contextData,
882
- childrenData,
883
- explainData,
884
- whereData,
885
- diffImpactData,
886
- listFunctionsData,
887
- rolesData,
888
- } = await getQueries();
889
- const Database = getDatabase();
890
- if (!multiRepo && args.repo) {
891
- throw new Error(
892
- 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
893
- );
894
- }
895
- if (!multiRepo && name === 'list_repos') {
896
- throw new Error(
897
- 'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
898
- );
899
- }
900
-
901
- let dbPath = customDbPath || undefined;
902
- if (args.repo) {
903
- if (allowedRepos && !allowedRepos.includes(args.repo)) {
904
- throw new Error(`Repository "${args.repo}" is not in the allowed repos list.`);
905
- }
906
- const { resolveRepoDbPath } = await import('./registry.js');
907
- const resolved = resolveRepoDbPath(args.repo);
908
- if (!resolved)
909
- throw new Error(
910
- `Repository "${args.repo}" not found in registry or its database is missing.`,
911
- );
912
- dbPath = resolved;
913
- }
914
-
915
- let result;
916
- switch (name) {
917
- case 'query': {
918
- const qMode = args.mode || 'deps';
919
- if (qMode === 'path') {
920
- if (!args.to) {
921
- result = { error: 'path mode requires a "to" argument' };
922
- break;
923
- }
924
- result = pathData(args.name, args.to, dbPath, {
925
- maxDepth: args.depth ?? 10,
926
- edgeKinds: args.edge_kinds,
927
- reverse: args.reverse,
928
- fromFile: args.from_file,
929
- toFile: args.to_file,
930
- kind: args.kind,
931
- noTests: args.no_tests,
932
- });
933
- } else {
934
- result = fnDepsData(args.name, dbPath, {
935
- depth: args.depth,
936
- file: args.file,
937
- kind: args.kind,
938
- noTests: args.no_tests,
939
- limit: Math.min(args.limit ?? MCP_DEFAULTS.query, MCP_MAX_LIMIT),
940
- offset: args.offset ?? 0,
941
- });
942
- }
943
- break;
944
- }
945
- case 'path':
946
- result = pathData(args.from, args.to, dbPath, {
947
- maxDepth: args.depth ?? 10,
948
- edgeKinds: args.edge_kinds,
949
- fromFile: args.from_file,
950
- toFile: args.to_file,
951
- noTests: args.no_tests,
952
- });
953
- break;
954
- case 'file_deps':
955
- result = fileDepsData(args.file, dbPath, {
956
- noTests: args.no_tests,
957
- limit: Math.min(args.limit ?? MCP_DEFAULTS.file_deps, MCP_MAX_LIMIT),
958
- offset: args.offset ?? 0,
959
- });
960
- break;
961
- case 'file_exports':
962
- result = exportsData(args.file, dbPath, {
963
- noTests: args.no_tests,
964
- unused: args.unused,
965
- limit: Math.min(args.limit ?? MCP_DEFAULTS.file_exports, MCP_MAX_LIMIT),
966
- offset: args.offset ?? 0,
967
- });
968
- break;
969
- case 'impact_analysis':
970
- result = impactAnalysisData(args.file, dbPath, {
971
- noTests: args.no_tests,
972
- limit: Math.min(args.limit ?? MCP_DEFAULTS.impact_analysis, MCP_MAX_LIMIT),
973
- offset: args.offset ?? 0,
974
- });
975
- break;
976
- case 'find_cycles': {
977
- const db = new Database(findDbPath(dbPath), { readonly: true });
978
- const cycles = findCycles(db);
979
- db.close();
980
- result = { cycles, count: cycles.length };
981
- break;
982
- }
983
- case 'module_map':
984
- result = moduleMapData(dbPath, args.limit || 20, { noTests: args.no_tests });
985
- break;
986
- case 'fn_impact':
987
- result = fnImpactData(args.name, dbPath, {
988
- depth: args.depth,
989
- file: args.file,
990
- kind: args.kind,
991
- noTests: args.no_tests,
992
- limit: Math.min(args.limit ?? MCP_DEFAULTS.fn_impact, MCP_MAX_LIMIT),
993
- offset: args.offset ?? 0,
994
- });
995
- break;
996
- case 'context':
997
- result = contextData(args.name, dbPath, {
998
- depth: args.depth,
999
- file: args.file,
1000
- kind: args.kind,
1001
- noSource: args.no_source,
1002
- noTests: args.no_tests,
1003
- includeTests: args.include_tests,
1004
- limit: Math.min(args.limit ?? MCP_DEFAULTS.context, MCP_MAX_LIMIT),
1005
- offset: args.offset ?? 0,
1006
- });
1007
- break;
1008
- case 'symbol_children':
1009
- result = childrenData(args.name, dbPath, {
1010
- file: args.file,
1011
- kind: args.kind,
1012
- noTests: args.no_tests,
1013
- limit: Math.min(args.limit ?? MCP_DEFAULTS.context, MCP_MAX_LIMIT),
1014
- offset: args.offset ?? 0,
1015
- });
1016
- break;
1017
- case 'where':
1018
- result = whereData(args.target, dbPath, {
1019
- file: args.file_mode,
1020
- noTests: args.no_tests,
1021
- limit: Math.min(args.limit ?? MCP_DEFAULTS.where, MCP_MAX_LIMIT),
1022
- offset: args.offset ?? 0,
1023
- });
1024
- break;
1025
- case 'diff_impact':
1026
- if (args.format === 'mermaid') {
1027
- result = diffImpactMermaid(dbPath, {
1028
- staged: args.staged,
1029
- ref: args.ref,
1030
- depth: args.depth,
1031
- noTests: args.no_tests,
1032
- });
1033
- } else {
1034
- result = diffImpactData(dbPath, {
1035
- staged: args.staged,
1036
- ref: args.ref,
1037
- depth: args.depth,
1038
- noTests: args.no_tests,
1039
- limit: Math.min(args.limit ?? MCP_DEFAULTS.diff_impact, MCP_MAX_LIMIT),
1040
- offset: args.offset ?? 0,
1041
- });
1042
- }
1043
- break;
1044
- case 'semantic_search': {
1045
- const mode = args.mode || 'hybrid';
1046
- const searchOpts = {
1047
- limit: Math.min(args.limit ?? MCP_DEFAULTS.semantic_search, MCP_MAX_LIMIT),
1048
- offset: args.offset ?? 0,
1049
- minScore: args.min_score,
1050
- };
1051
-
1052
- if (mode === 'keyword') {
1053
- const { ftsSearchData } = await import('./embedder.js');
1054
- result = ftsSearchData(args.query, dbPath, searchOpts);
1055
- if (result === null) {
1056
- return {
1057
- content: [
1058
- {
1059
- type: 'text',
1060
- text: 'No FTS5 index found. Run `codegraph embed` to build the keyword index.',
1061
- },
1062
- ],
1063
- isError: true,
1064
- };
1065
- }
1066
- } else if (mode === 'semantic') {
1067
- const { searchData } = await import('./embedder.js');
1068
- result = await searchData(args.query, dbPath, searchOpts);
1069
- if (result === null) {
1070
- return {
1071
- content: [
1072
- {
1073
- type: 'text',
1074
- text: 'Semantic search unavailable. Run `codegraph embed` first.',
1075
- },
1076
- ],
1077
- isError: true,
1078
- };
1079
- }
1080
- } else {
1081
- // hybrid (default) — falls back to semantic if no FTS5
1082
- const { hybridSearchData, searchData } = await import('./embedder.js');
1083
- result = await hybridSearchData(args.query, dbPath, searchOpts);
1084
- if (result === null) {
1085
- result = await searchData(args.query, dbPath, searchOpts);
1086
- if (result === null) {
1087
- return {
1088
- content: [
1089
- {
1090
- type: 'text',
1091
- text: 'Semantic search unavailable. Run `codegraph embed` first.',
1092
- },
1093
- ],
1094
- isError: true,
1095
- };
1096
- }
1097
- }
1098
- }
1099
- break;
1100
- }
1101
- case 'export_graph': {
1102
- const {
1103
- exportDOT,
1104
- exportGraphML,
1105
- exportGraphSON,
1106
- exportJSON,
1107
- exportMermaid,
1108
- exportNeo4jCSV,
1109
- } = await import('./export.js');
1110
- const db = new Database(findDbPath(dbPath), { readonly: true });
1111
- const fileLevel = args.file_level !== false;
1112
- const exportLimit = args.limit
1113
- ? Math.min(args.limit, MCP_MAX_LIMIT)
1114
- : MCP_DEFAULTS.export_graph;
1115
- switch (args.format) {
1116
- case 'dot':
1117
- result = exportDOT(db, { fileLevel, limit: exportLimit });
1118
- break;
1119
- case 'mermaid':
1120
- result = exportMermaid(db, { fileLevel, limit: exportLimit });
1121
- break;
1122
- case 'json':
1123
- result = exportJSON(db, {
1124
- limit: exportLimit,
1125
- offset: args.offset ?? 0,
1126
- });
1127
- break;
1128
- case 'graphml':
1129
- result = exportGraphML(db, { fileLevel, limit: exportLimit });
1130
- break;
1131
- case 'graphson':
1132
- result = exportGraphSON(db, {
1133
- fileLevel,
1134
- limit: exportLimit,
1135
- offset: args.offset ?? 0,
1136
- });
1137
- break;
1138
- case 'neo4j':
1139
- result = exportNeo4jCSV(db, { fileLevel, limit: exportLimit });
1140
- break;
1141
- default:
1142
- db.close();
1143
- return {
1144
- content: [
1145
- {
1146
- type: 'text',
1147
- text: `Unknown format: ${args.format}. Use dot, mermaid, json, graphml, graphson, or neo4j.`,
1148
- },
1149
- ],
1150
- isError: true,
1151
- };
1152
- }
1153
- db.close();
1154
- break;
1155
- }
1156
- case 'list_functions':
1157
- result = listFunctionsData(dbPath, {
1158
- file: args.file,
1159
- pattern: args.pattern,
1160
- noTests: args.no_tests,
1161
- limit: Math.min(args.limit ?? MCP_DEFAULTS.list_functions, MCP_MAX_LIMIT),
1162
- offset: args.offset ?? 0,
1163
- });
1164
- break;
1165
- case 'node_roles':
1166
- result = rolesData(dbPath, {
1167
- role: args.role,
1168
- file: args.file,
1169
- noTests: args.no_tests,
1170
- limit: Math.min(args.limit ?? MCP_DEFAULTS.node_roles, MCP_MAX_LIMIT),
1171
- offset: args.offset ?? 0,
1172
- });
1173
- break;
1174
- case 'structure': {
1175
- const { structureData } = await import('./structure.js');
1176
- result = structureData(dbPath, {
1177
- directory: args.directory,
1178
- depth: args.depth,
1179
- sort: args.sort,
1180
- full: args.full,
1181
- limit: Math.min(args.limit ?? MCP_DEFAULTS.structure, MCP_MAX_LIMIT),
1182
- offset: args.offset ?? 0,
1183
- });
1184
- break;
1185
- }
1186
- case 'co_changes': {
1187
- const { coChangeData, coChangeTopData } = await import('./cochange.js');
1188
- result = args.file
1189
- ? coChangeData(args.file, dbPath, {
1190
- limit: Math.min(args.limit ?? MCP_DEFAULTS.co_changes, MCP_MAX_LIMIT),
1191
- offset: args.offset ?? 0,
1192
- minJaccard: args.min_jaccard,
1193
- noTests: args.no_tests,
1194
- })
1195
- : coChangeTopData(dbPath, {
1196
- limit: Math.min(args.limit ?? MCP_DEFAULTS.co_changes, MCP_MAX_LIMIT),
1197
- offset: args.offset ?? 0,
1198
- minJaccard: args.min_jaccard,
1199
- noTests: args.no_tests,
1200
- });
1201
- break;
1202
- }
1203
- case 'execution_flow': {
1204
- if (args.list) {
1205
- const { listEntryPointsData } = await import('./flow.js');
1206
- result = listEntryPointsData(dbPath, {
1207
- noTests: args.no_tests,
1208
- limit: Math.min(args.limit ?? MCP_DEFAULTS.execution_flow, MCP_MAX_LIMIT),
1209
- offset: args.offset ?? 0,
1210
- });
1211
- } else {
1212
- if (!args.name) {
1213
- result = { error: 'Provide a name or set list=true' };
1214
- break;
1215
- }
1216
- const { flowData } = await import('./flow.js');
1217
- result = flowData(args.name, dbPath, {
1218
- depth: args.depth,
1219
- file: args.file,
1220
- kind: args.kind,
1221
- noTests: args.no_tests,
1222
- limit: Math.min(args.limit ?? MCP_DEFAULTS.execution_flow, MCP_MAX_LIMIT),
1223
- offset: args.offset ?? 0,
1224
- });
1225
- }
1226
- break;
1227
- }
1228
- case 'sequence': {
1229
- const { sequenceData, sequenceToMermaid } = await import('./sequence.js');
1230
- const seqResult = sequenceData(args.name, dbPath, {
1231
- depth: args.depth,
1232
- file: args.file,
1233
- kind: args.kind,
1234
- dataflow: args.dataflow,
1235
- noTests: args.no_tests,
1236
- limit: Math.min(args.limit ?? MCP_DEFAULTS.execution_flow, MCP_MAX_LIMIT),
1237
- offset: args.offset ?? 0,
1238
- });
1239
- result =
1240
- args.format === 'json'
1241
- ? seqResult
1242
- : { text: sequenceToMermaid(seqResult), ...seqResult };
1243
- break;
1244
- }
1245
- case 'complexity': {
1246
- const { complexityData } = await import('./complexity.js');
1247
- result = complexityData(dbPath, {
1248
- target: args.name,
1249
- file: args.file,
1250
- limit: Math.min(args.limit ?? MCP_DEFAULTS.complexity, MCP_MAX_LIMIT),
1251
- offset: args.offset ?? 0,
1252
- sort: args.sort,
1253
- aboveThreshold: args.above_threshold,
1254
- health: args.health,
1255
- noTests: args.no_tests,
1256
- kind: args.kind,
1257
- });
1258
- break;
1259
- }
1260
- case 'communities': {
1261
- const { communitiesData } = await import('./communities.js');
1262
- result = communitiesData(dbPath, {
1263
- functions: args.functions,
1264
- resolution: args.resolution,
1265
- drift: args.drift,
1266
- noTests: args.no_tests,
1267
- limit: Math.min(args.limit ?? MCP_DEFAULTS.communities, MCP_MAX_LIMIT),
1268
- offset: args.offset ?? 0,
1269
- });
1270
- break;
1271
- }
1272
- case 'code_owners': {
1273
- const { ownersData } = await import('./owners.js');
1274
- result = ownersData(dbPath, {
1275
- file: args.file,
1276
- owner: args.owner,
1277
- boundary: args.boundary,
1278
- kind: args.kind,
1279
- noTests: args.no_tests,
1280
- });
1281
- break;
1282
- }
1283
- case 'audit': {
1284
- if (args.quick) {
1285
- result = explainData(args.target, dbPath, {
1286
- noTests: args.no_tests,
1287
- limit: Math.min(args.limit ?? MCP_DEFAULTS.explain, MCP_MAX_LIMIT),
1288
- offset: args.offset ?? 0,
1289
- });
1290
- } else {
1291
- const { auditData } = await import('./audit.js');
1292
- result = auditData(args.target, dbPath, {
1293
- depth: args.depth,
1294
- file: args.file,
1295
- kind: args.kind,
1296
- noTests: args.no_tests,
1297
- });
1298
- }
1299
- break;
1300
- }
1301
- case 'batch_query': {
1302
- const { batchData } = await import('./batch.js');
1303
- result = batchData(args.command, args.targets, dbPath, {
1304
- depth: args.depth,
1305
- file: args.file,
1306
- kind: args.kind,
1307
- noTests: args.no_tests,
1308
- });
1309
- break;
1310
- }
1311
- case 'triage': {
1312
- if (args.level === 'file' || args.level === 'directory') {
1313
- const { hotspotsData } = await import('./structure.js');
1314
- const TRIAGE_TO_HOTSPOT = {
1315
- risk: 'fan-in',
1316
- complexity: 'density',
1317
- churn: 'coupling',
1318
- mi: 'fan-in',
1319
- };
1320
- const metric = TRIAGE_TO_HOTSPOT[args.sort] ?? args.sort;
1321
- result = hotspotsData(dbPath, {
1322
- metric,
1323
- level: args.level,
1324
- limit: Math.min(args.limit ?? MCP_DEFAULTS.hotspots, MCP_MAX_LIMIT),
1325
- offset: args.offset ?? 0,
1326
- noTests: args.no_tests,
1327
- });
1328
- } else {
1329
- const { triageData } = await import('./triage.js');
1330
- result = triageData(dbPath, {
1331
- sort: args.sort,
1332
- minScore: args.min_score,
1333
- role: args.role,
1334
- file: args.file,
1335
- kind: args.kind,
1336
- noTests: args.no_tests,
1337
- weights: args.weights,
1338
- limit: Math.min(args.limit ?? MCP_DEFAULTS.triage, MCP_MAX_LIMIT),
1339
- offset: args.offset ?? 0,
1340
- });
1341
- }
1342
- break;
1343
- }
1344
- case 'branch_compare': {
1345
- const { branchCompareData, branchCompareMermaid } = await import('./branch-compare.js');
1346
- const bcData = await branchCompareData(args.base, args.target, {
1347
- depth: args.depth,
1348
- noTests: args.no_tests,
1349
- });
1350
- result = args.format === 'mermaid' ? branchCompareMermaid(bcData) : bcData;
1351
- break;
1352
- }
1353
- case 'cfg': {
1354
- const { cfgData, cfgToDOT, cfgToMermaid } = await import('./cfg.js');
1355
- const cfgResult = cfgData(args.name, dbPath, {
1356
- file: args.file,
1357
- kind: args.kind,
1358
- noTests: args.no_tests,
1359
- limit: Math.min(args.limit ?? MCP_DEFAULTS.query, MCP_MAX_LIMIT),
1360
- offset: args.offset ?? 0,
1361
- });
1362
- if (args.format === 'dot') {
1363
- result = { text: cfgToDOT(cfgResult) };
1364
- } else if (args.format === 'mermaid') {
1365
- result = { text: cfgToMermaid(cfgResult) };
1366
- } else {
1367
- result = cfgResult;
1368
- }
1369
- break;
1370
- }
1371
- case 'dataflow': {
1372
- const dfMode = args.mode || 'edges';
1373
- if (dfMode === 'impact') {
1374
- const { dataflowImpactData } = await import('./dataflow.js');
1375
- result = dataflowImpactData(args.name, dbPath, {
1376
- depth: args.depth,
1377
- file: args.file,
1378
- kind: args.kind,
1379
- noTests: args.no_tests,
1380
- limit: Math.min(args.limit ?? MCP_DEFAULTS.fn_impact, MCP_MAX_LIMIT),
1381
- offset: args.offset ?? 0,
1382
- });
1383
- } else {
1384
- const { dataflowData } = await import('./dataflow.js');
1385
- result = dataflowData(args.name, dbPath, {
1386
- file: args.file,
1387
- kind: args.kind,
1388
- noTests: args.no_tests,
1389
- limit: Math.min(args.limit ?? MCP_DEFAULTS.query, MCP_MAX_LIMIT),
1390
- offset: args.offset ?? 0,
1391
- });
1392
- }
1393
- break;
1394
- }
1395
- case 'check': {
1396
- const isDiffMode = args.ref || args.staged;
1397
-
1398
- if (!isDiffMode && !args.rules) {
1399
- // No ref, no staged → run manifesto rules on whole codebase
1400
- const { manifestoData } = await import('./manifesto.js');
1401
- result = manifestoData(dbPath, {
1402
- file: args.file,
1403
- noTests: args.no_tests,
1404
- kind: args.kind,
1405
- limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
1406
- offset: args.offset ?? 0,
1407
- });
1408
- } else {
1409
- const { checkData } = await import('./check.js');
1410
- const checkResult = checkData(dbPath, {
1411
- ref: args.ref,
1412
- staged: args.staged,
1413
- cycles: args.cycles,
1414
- blastRadius: args.blast_radius,
1415
- signatures: args.signatures,
1416
- boundaries: args.boundaries,
1417
- depth: args.depth,
1418
- noTests: args.no_tests,
1419
- });
1420
-
1421
- if (args.rules) {
1422
- const { manifestoData } = await import('./manifesto.js');
1423
- const manifestoResult = manifestoData(dbPath, {
1424
- file: args.file,
1425
- noTests: args.no_tests,
1426
- kind: args.kind,
1427
- limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
1428
- offset: args.offset ?? 0,
1429
- });
1430
- result = { check: checkResult, manifesto: manifestoResult };
1431
- } else {
1432
- result = checkResult;
1433
- }
1434
- }
1435
- break;
1436
- }
1437
- case 'ast_query': {
1438
- const { astQueryData } = await import('./ast.js');
1439
- result = astQueryData(args.pattern, dbPath, {
1440
- kind: args.kind,
1441
- file: args.file,
1442
- noTests: args.no_tests,
1443
- limit: Math.min(args.limit ?? MCP_DEFAULTS.ast_query, MCP_MAX_LIMIT),
1444
- offset: args.offset ?? 0,
1445
- });
1446
- break;
1447
- }
1448
- case 'list_repos': {
1449
- const { listRepos, pruneRegistry } = await import('./registry.js');
1450
- pruneRegistry();
1451
- let repos = listRepos();
1452
- if (allowedRepos) {
1453
- repos = repos.filter((r) => allowedRepos.includes(r.name));
1454
- }
1455
- result = { repos };
1456
- break;
1457
- }
1458
- default:
1459
- return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
1460
- }
1461
-
1462
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1463
- } catch (err) {
1464
- return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
1465
- }
1466
- });
1467
-
1468
- const transport = new StdioServerTransport();
1469
- await server.connect(transport);
1470
- }
801
+ export const TOOLS = buildToolList(true);