@zuvia-software-solutions/code-mapper 1.4.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 (213) hide show
  1. package/README.md +215 -0
  2. package/dist/cli/ai-context.d.ts +19 -0
  3. package/dist/cli/ai-context.js +168 -0
  4. package/dist/cli/analyze.d.ts +7 -0
  5. package/dist/cli/analyze.js +325 -0
  6. package/dist/cli/augment.d.ts +7 -0
  7. package/dist/cli/augment.js +27 -0
  8. package/dist/cli/clean.d.ts +5 -0
  9. package/dist/cli/clean.js +56 -0
  10. package/dist/cli/eval-server.d.ts +25 -0
  11. package/dist/cli/eval-server.js +365 -0
  12. package/dist/cli/index.d.ts +6 -0
  13. package/dist/cli/index.js +102 -0
  14. package/dist/cli/lazy-action.d.ts +6 -0
  15. package/dist/cli/lazy-action.js +19 -0
  16. package/dist/cli/list.d.ts +2 -0
  17. package/dist/cli/list.js +27 -0
  18. package/dist/cli/mcp.d.ts +8 -0
  19. package/dist/cli/mcp.js +35 -0
  20. package/dist/cli/refresh.d.ts +12 -0
  21. package/dist/cli/refresh.js +165 -0
  22. package/dist/cli/serve.d.ts +5 -0
  23. package/dist/cli/serve.js +8 -0
  24. package/dist/cli/setup.d.ts +6 -0
  25. package/dist/cli/setup.js +218 -0
  26. package/dist/cli/status.d.ts +2 -0
  27. package/dist/cli/status.js +33 -0
  28. package/dist/cli/tool.d.ts +28 -0
  29. package/dist/cli/tool.js +87 -0
  30. package/dist/config/ignore-service.d.ts +32 -0
  31. package/dist/config/ignore-service.js +282 -0
  32. package/dist/config/supported-languages.d.ts +23 -0
  33. package/dist/config/supported-languages.js +52 -0
  34. package/dist/core/augmentation/engine.d.ts +22 -0
  35. package/dist/core/augmentation/engine.js +232 -0
  36. package/dist/core/embeddings/embedder.d.ts +35 -0
  37. package/dist/core/embeddings/embedder.js +171 -0
  38. package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
  39. package/dist/core/embeddings/embedding-pipeline.js +402 -0
  40. package/dist/core/embeddings/index.d.ts +5 -0
  41. package/dist/core/embeddings/index.js +6 -0
  42. package/dist/core/embeddings/text-generator.d.ts +20 -0
  43. package/dist/core/embeddings/text-generator.js +159 -0
  44. package/dist/core/embeddings/types.d.ts +60 -0
  45. package/dist/core/embeddings/types.js +23 -0
  46. package/dist/core/graph/graph.d.ts +4 -0
  47. package/dist/core/graph/graph.js +65 -0
  48. package/dist/core/graph/types.d.ts +69 -0
  49. package/dist/core/graph/types.js +3 -0
  50. package/dist/core/incremental/child-process.d.ts +8 -0
  51. package/dist/core/incremental/child-process.js +649 -0
  52. package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
  53. package/dist/core/incremental/refresh-coordinator.js +147 -0
  54. package/dist/core/incremental/types.d.ts +78 -0
  55. package/dist/core/incremental/types.js +153 -0
  56. package/dist/core/incremental/watcher.d.ts +63 -0
  57. package/dist/core/incremental/watcher.js +338 -0
  58. package/dist/core/ingestion/ast-cache.d.ts +12 -0
  59. package/dist/core/ingestion/ast-cache.js +34 -0
  60. package/dist/core/ingestion/call-processor.d.ts +34 -0
  61. package/dist/core/ingestion/call-processor.js +937 -0
  62. package/dist/core/ingestion/call-routing.d.ts +40 -0
  63. package/dist/core/ingestion/call-routing.js +97 -0
  64. package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
  65. package/dist/core/ingestion/cluster-enricher.js +151 -0
  66. package/dist/core/ingestion/community-processor.d.ts +26 -0
  67. package/dist/core/ingestion/community-processor.js +272 -0
  68. package/dist/core/ingestion/constants.d.ts +5 -0
  69. package/dist/core/ingestion/constants.js +8 -0
  70. package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
  71. package/dist/core/ingestion/entry-point-scoring.js +317 -0
  72. package/dist/core/ingestion/export-detection.d.ts +11 -0
  73. package/dist/core/ingestion/export-detection.js +203 -0
  74. package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
  75. package/dist/core/ingestion/filesystem-walker.js +64 -0
  76. package/dist/core/ingestion/framework-detection.d.ts +42 -0
  77. package/dist/core/ingestion/framework-detection.js +405 -0
  78. package/dist/core/ingestion/heritage-processor.d.ts +15 -0
  79. package/dist/core/ingestion/heritage-processor.js +237 -0
  80. package/dist/core/ingestion/import-processor.d.ts +31 -0
  81. package/dist/core/ingestion/import-processor.js +416 -0
  82. package/dist/core/ingestion/language-config.d.ts +32 -0
  83. package/dist/core/ingestion/language-config.js +161 -0
  84. package/dist/core/ingestion/mro-processor.d.ts +32 -0
  85. package/dist/core/ingestion/mro-processor.js +343 -0
  86. package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
  87. package/dist/core/ingestion/named-binding-extraction.js +343 -0
  88. package/dist/core/ingestion/parsing-processor.d.ts +20 -0
  89. package/dist/core/ingestion/parsing-processor.js +282 -0
  90. package/dist/core/ingestion/pipeline.d.ts +3 -0
  91. package/dist/core/ingestion/pipeline.js +416 -0
  92. package/dist/core/ingestion/process-processor.d.ts +42 -0
  93. package/dist/core/ingestion/process-processor.js +357 -0
  94. package/dist/core/ingestion/resolution-context.d.ts +40 -0
  95. package/dist/core/ingestion/resolution-context.js +171 -0
  96. package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
  97. package/dist/core/ingestion/resolvers/csharp.js +101 -0
  98. package/dist/core/ingestion/resolvers/go.d.ts +8 -0
  99. package/dist/core/ingestion/resolvers/go.js +33 -0
  100. package/dist/core/ingestion/resolvers/index.d.ts +14 -0
  101. package/dist/core/ingestion/resolvers/index.js +10 -0
  102. package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
  103. package/dist/core/ingestion/resolvers/jvm.js +74 -0
  104. package/dist/core/ingestion/resolvers/php.d.ts +7 -0
  105. package/dist/core/ingestion/resolvers/php.js +30 -0
  106. package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
  107. package/dist/core/ingestion/resolvers/ruby.js +13 -0
  108. package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
  109. package/dist/core/ingestion/resolvers/rust.js +62 -0
  110. package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
  111. package/dist/core/ingestion/resolvers/standard.js +144 -0
  112. package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
  113. package/dist/core/ingestion/resolvers/utils.js +113 -0
  114. package/dist/core/ingestion/structure-processor.d.ts +4 -0
  115. package/dist/core/ingestion/structure-processor.js +39 -0
  116. package/dist/core/ingestion/symbol-table.d.ts +34 -0
  117. package/dist/core/ingestion/symbol-table.js +48 -0
  118. package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
  119. package/dist/core/ingestion/tree-sitter-queries.js +691 -0
  120. package/dist/core/ingestion/type-env.d.ts +52 -0
  121. package/dist/core/ingestion/type-env.js +349 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
  123. package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
  124. package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
  125. package/dist/core/ingestion/type-extractors/csharp.js +224 -0
  126. package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
  127. package/dist/core/ingestion/type-extractors/go.js +261 -0
  128. package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
  129. package/dist/core/ingestion/type-extractors/index.js +30 -0
  130. package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
  131. package/dist/core/ingestion/type-extractors/jvm.js +386 -0
  132. package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
  133. package/dist/core/ingestion/type-extractors/php.js +280 -0
  134. package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
  135. package/dist/core/ingestion/type-extractors/python.js +175 -0
  136. package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
  137. package/dist/core/ingestion/type-extractors/ruby.js +218 -0
  138. package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
  139. package/dist/core/ingestion/type-extractors/rust.js +290 -0
  140. package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
  141. package/dist/core/ingestion/type-extractors/shared.js +322 -0
  142. package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
  143. package/dist/core/ingestion/type-extractors/swift.js +140 -0
  144. package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
  145. package/dist/core/ingestion/type-extractors/types.js +4 -0
  146. package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
  147. package/dist/core/ingestion/type-extractors/typescript.js +227 -0
  148. package/dist/core/ingestion/utils.d.ts +73 -0
  149. package/dist/core/ingestion/utils.js +992 -0
  150. package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
  151. package/dist/core/ingestion/workers/parse-worker.js +1055 -0
  152. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
  153. package/dist/core/ingestion/workers/worker-pool.js +123 -0
  154. package/dist/core/lbug/csv-generator.d.ts +28 -0
  155. package/dist/core/lbug/csv-generator.js +355 -0
  156. package/dist/core/lbug/lbug-adapter.d.ts +96 -0
  157. package/dist/core/lbug/lbug-adapter.js +753 -0
  158. package/dist/core/lbug/schema.d.ts +46 -0
  159. package/dist/core/lbug/schema.js +402 -0
  160. package/dist/core/search/bm25-index.d.ts +20 -0
  161. package/dist/core/search/bm25-index.js +123 -0
  162. package/dist/core/search/hybrid-search.d.ts +32 -0
  163. package/dist/core/search/hybrid-search.js +131 -0
  164. package/dist/core/search/query-cache.d.ts +18 -0
  165. package/dist/core/search/query-cache.js +47 -0
  166. package/dist/core/search/query-expansion.d.ts +19 -0
  167. package/dist/core/search/query-expansion.js +75 -0
  168. package/dist/core/search/reranker.d.ts +29 -0
  169. package/dist/core/search/reranker.js +122 -0
  170. package/dist/core/search/types.d.ts +154 -0
  171. package/dist/core/search/types.js +51 -0
  172. package/dist/core/semantic/tsgo-service.d.ts +67 -0
  173. package/dist/core/semantic/tsgo-service.js +355 -0
  174. package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
  175. package/dist/core/tree-sitter/parser-loader.js +71 -0
  176. package/dist/lib/memory-guard.d.ts +35 -0
  177. package/dist/lib/memory-guard.js +70 -0
  178. package/dist/lib/utils.d.ts +3 -0
  179. package/dist/lib/utils.js +6 -0
  180. package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
  181. package/dist/mcp/compatible-stdio-transport.js +209 -0
  182. package/dist/mcp/core/embedder.d.ts +24 -0
  183. package/dist/mcp/core/embedder.js +168 -0
  184. package/dist/mcp/core/lbug-adapter.d.ts +29 -0
  185. package/dist/mcp/core/lbug-adapter.js +330 -0
  186. package/dist/mcp/local/local-backend.d.ts +188 -0
  187. package/dist/mcp/local/local-backend.js +2759 -0
  188. package/dist/mcp/resources.d.ts +22 -0
  189. package/dist/mcp/resources.js +379 -0
  190. package/dist/mcp/server.d.ts +10 -0
  191. package/dist/mcp/server.js +217 -0
  192. package/dist/mcp/staleness.d.ts +10 -0
  193. package/dist/mcp/staleness.js +25 -0
  194. package/dist/mcp/tools.d.ts +21 -0
  195. package/dist/mcp/tools.js +202 -0
  196. package/dist/server/api.d.ts +5 -0
  197. package/dist/server/api.js +340 -0
  198. package/dist/server/mcp-http.d.ts +7 -0
  199. package/dist/server/mcp-http.js +95 -0
  200. package/dist/storage/git.d.ts +6 -0
  201. package/dist/storage/git.js +35 -0
  202. package/dist/storage/repo-manager.d.ts +87 -0
  203. package/dist/storage/repo-manager.js +249 -0
  204. package/dist/types/pipeline.d.ts +35 -0
  205. package/dist/types/pipeline.js +20 -0
  206. package/hooks/claude/code-mapper-hook.cjs +238 -0
  207. package/hooks/claude/pre-tool-use.sh +79 -0
  208. package/hooks/claude/session-start.sh +42 -0
  209. package/models/mlx-embedder.py +185 -0
  210. package/package.json +100 -0
  211. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  212. package/vendor/leiden/index.cjs +355 -0
  213. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,21 @@
1
+ /** @file tools.ts
2
+ * @description MCP tool definitions exposed to external AI agents
3
+ * All tools support an optional `repo` parameter for multi-repo setups */
4
+ export interface ToolDefinition {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: {
8
+ type: 'object';
9
+ properties: Record<string, {
10
+ type: string;
11
+ description?: string;
12
+ default?: any;
13
+ items?: {
14
+ type: string;
15
+ };
16
+ enum?: string[];
17
+ }>;
18
+ required: string[];
19
+ };
20
+ }
21
+ export declare const CODE_MAPPER_TOOLS: ToolDefinition[];
@@ -0,0 +1,202 @@
1
+ // code-mapper/src/mcp/tools.ts
2
+ /** @file tools.ts
3
+ * @description MCP tool definitions exposed to external AI agents
4
+ * All tools support an optional `repo` parameter for multi-repo setups */
5
+ export const CODE_MAPPER_TOOLS = [
6
+ {
7
+ name: 'list_repos',
8
+ description: `List all indexed repositories available to Code Mapper.
9
+
10
+ Returns each repo's name, path, indexed date, last commit, and stats.
11
+
12
+ WHEN TO USE: First step when multiple repos are indexed, or to discover available repos.
13
+ AFTER THIS: READ code-mapper://repo/{name}/context for the repo you want to work with.
14
+
15
+ When multiple repos are indexed, you MUST specify the "repo" parameter
16
+ on other tools (query, context, impact, etc.) to target the correct one.`,
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {},
20
+ required: [],
21
+ },
22
+ },
23
+ {
24
+ name: 'query',
25
+ description: `Query the code knowledge graph for execution flows related to a concept.
26
+ Returns processes (call chains) ranked by relevance, each with its symbols and file locations.
27
+
28
+ WHEN TO USE: Understanding how code works together. Use this when you need execution flows and relationships, not just file matches. Complements grep/IDE search.
29
+ AFTER THIS: Use context() on a specific symbol for 360-degree view (callers, callees, categorized refs).
30
+
31
+ Returns results grouped by process (execution flow):
32
+ - processes: ranked execution flows with relevance priority
33
+ - process_symbols: all symbols in those flows with file locations and module (functional area)
34
+ - definitions: standalone types/interfaces not in any process
35
+
36
+ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.`,
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ query: { type: 'string', description: 'Natural language or keyword search query' },
41
+ task_context: { type: 'string', description: 'What you are working on (e.g., "adding OAuth support"). Helps ranking.' },
42
+ goal: { type: 'string', description: 'What you want to find (e.g., "existing auth validation logic"). Helps ranking.' },
43
+ file_path: { type: 'string', description: 'Filter results to symbols in this file path (substring match)' },
44
+ limit: { type: 'number', description: 'Max processes to return (default: 5)', default: 5 },
45
+ max_symbols: { type: 'number', description: 'Max symbols per process (default: 10)', default: 10 },
46
+ include_content: { type: 'boolean', description: 'Include full symbol source code (default: false)', default: false },
47
+ repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
48
+ },
49
+ required: ['query'],
50
+ },
51
+ },
52
+ {
53
+ name: 'cypher',
54
+ description: `Execute Cypher query against the code knowledge graph.
55
+
56
+ WHEN TO USE: Complex structural queries that search/explore can't answer. READ code-mapper://repo/{name}/schema first for the full schema.
57
+ AFTER THIS: Use context() on result symbols for deeper context.
58
+
59
+ SCHEMA:
60
+ - Nodes: File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process
61
+ - Multi-language nodes (use backticks): \`Struct\`, \`Enum\`, \`Trait\`, \`Impl\`, etc.
62
+ - All edges via single CodeRelation table with 'type' property
63
+ - Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, OVERRIDES, MEMBER_OF, STEP_IN_PROCESS
64
+ - Edge properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)
65
+
66
+ EXAMPLES:
67
+ • Find callers of a function:
68
+ MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
69
+
70
+ • Find community members:
71
+ MATCH (f)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community) WHERE c.heuristicLabel = "Auth" RETURN f.name
72
+
73
+ • Trace a process:
74
+ MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process) WHERE p.heuristicLabel = "UserLogin" RETURN s.name, r.step ORDER BY r.step
75
+
76
+ • Find all methods of a class:
77
+ MATCH (c:Class {name: "UserService"})-[r:CodeRelation {type: 'HAS_METHOD'}]->(m:Method) RETURN m.name, m.parameterCount, m.returnType
78
+
79
+ • Find method overrides (MRO resolution):
80
+ MATCH (winner:Method)-[r:CodeRelation {type: 'OVERRIDES'}]->(loser:Method) RETURN winner.name, winner.filePath, loser.filePath, r.reason
81
+
82
+ • Detect diamond inheritance:
83
+ MATCH (d:Class)-[:CodeRelation {type: 'EXTENDS'}]->(b1), (d)-[:CodeRelation {type: 'EXTENDS'}]->(b2), (b1)-[:CodeRelation {type: 'EXTENDS'}]->(a), (b2)-[:CodeRelation {type: 'EXTENDS'}]->(a) WHERE b1 <> b2 RETURN d.name, b1.name, b2.name, a.name
84
+
85
+ OUTPUT: Returns { markdown, row_count } — results formatted as a Markdown table for easy reading.
86
+
87
+ TIPS:
88
+ - All relationships use single CodeRelation table — filter with {type: 'CALLS'} etc.
89
+ - Community = auto-detected functional area (Leiden algorithm)
90
+ - Process = execution flow trace from entry point to terminal
91
+ - Use heuristicLabel (not label) for human-readable community/process names`,
92
+ inputSchema: {
93
+ type: 'object',
94
+ properties: {
95
+ query: { type: 'string', description: 'Cypher query to execute' },
96
+ repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
97
+ },
98
+ required: ['query'],
99
+ },
100
+ },
101
+ {
102
+ name: 'context',
103
+ description: `360-degree view of a single code symbol.
104
+ Shows categorized incoming/outgoing references (calls, imports, extends, implements), process participation, and file location.
105
+
106
+ WHEN TO USE: After query() to understand a specific symbol in depth. When you need to know all callers, callees, and what execution flows a symbol participates in.
107
+ AFTER THIS: Use impact() if planning changes, or READ code-mapper://repo/{name}/process/{processName} for full execution trace.
108
+
109
+ Handles disambiguation: if multiple symbols share the same name, returns candidates for you to pick from. Use uid param for zero-ambiguity lookup from prior results.`,
110
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {
113
+ name: { type: 'string', description: 'Symbol name (e.g., "validateUser", "AuthService")' },
114
+ names: { type: 'array', items: { type: 'string' }, description: 'Multiple symbol names for bulk lookup (returns compact context for each)' },
115
+ uid: { type: 'string', description: 'Direct symbol UID from prior tool results (zero-ambiguity lookup)' },
116
+ file_path: { type: 'string', description: 'File path to disambiguate common names' },
117
+ include_content: { type: 'boolean', description: 'Include full symbol source code (default: false)', default: false },
118
+ repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
119
+ },
120
+ required: [],
121
+ },
122
+ },
123
+ {
124
+ name: 'detect_changes',
125
+ description: `Analyze uncommitted git changes and find affected execution flows.
126
+ Maps git diff hunks to indexed symbols, then traces which processes are impacted.
127
+
128
+ WHEN TO USE: Before committing — to understand what your changes affect. Pre-commit review, PR preparation.
129
+ AFTER THIS: Review affected processes. Use context() on high-risk symbols. READ code-mapper://repo/{name}/process/{name} for full traces.
130
+
131
+ Returns: changed symbols, affected processes, and a risk summary.`,
132
+ inputSchema: {
133
+ type: 'object',
134
+ properties: {
135
+ scope: { type: 'string', description: 'What to analyze: "unstaged" (default), "staged", "all", or "compare"', enum: ['unstaged', 'staged', 'all', 'compare'], default: 'unstaged' },
136
+ base_ref: { type: 'string', description: 'Branch/commit for "compare" scope (e.g., "main")' },
137
+ repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
138
+ },
139
+ required: [],
140
+ },
141
+ },
142
+ {
143
+ name: 'rename',
144
+ description: `Multi-file coordinated rename using the knowledge graph + text search.
145
+ Finds all references via graph (high confidence) and regex text search (lower confidence). Preview by default.
146
+
147
+ WHEN TO USE: Renaming a function, class, method, or variable across the codebase. Safer than find-and-replace.
148
+ AFTER THIS: Run detect_changes() to verify no unexpected side effects.
149
+
150
+ Each edit is tagged with confidence:
151
+ - "graph": found via knowledge graph relationships (high confidence, safe to accept)
152
+ - "text_search": found via regex text search (lower confidence, review carefully)`,
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ symbol_name: { type: 'string', description: 'Current symbol name to rename' },
157
+ symbol_uid: { type: 'string', description: 'Direct symbol UID from prior tool results (zero-ambiguity)' },
158
+ new_name: { type: 'string', description: 'The new name for the symbol' },
159
+ file_path: { type: 'string', description: 'File path to disambiguate common names' },
160
+ dry_run: { type: 'boolean', description: 'Preview edits without modifying files (default: true)', default: true },
161
+ repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
162
+ },
163
+ required: ['new_name'],
164
+ },
165
+ },
166
+ {
167
+ name: 'impact',
168
+ description: `Analyze the blast radius of changing a code symbol.
169
+ Returns affected symbols grouped by depth, plus risk assessment, affected execution flows, and affected modules.
170
+
171
+ WHEN TO USE: Before making code changes — especially refactoring, renaming, or modifying shared code. Shows what would break.
172
+ AFTER THIS: Review d=1 items (WILL BREAK). Use context() on high-risk symbols.
173
+
174
+ Output includes:
175
+ - risk: LOW / MEDIUM / HIGH / CRITICAL
176
+ - summary: direct callers, processes affected, modules affected
177
+ - affected_processes: which execution flows break and at which step
178
+ - affected_modules: which functional areas are hit (direct vs indirect)
179
+ - byDepth: all affected symbols grouped by traversal depth
180
+
181
+ Depth groups:
182
+ - d=1: WILL BREAK (direct callers/importers)
183
+ - d=2: LIKELY AFFECTED (indirect)
184
+ - d=3: MAY NEED TESTING (transitive)
185
+
186
+ EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, OVERRIDES
187
+ Confidence: 1.0 = certain, <0.8 = fuzzy match`,
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ target: { type: 'string', description: 'Name of function, class, or file to analyze' },
192
+ direction: { type: 'string', description: 'upstream (what depends on this) or downstream (what this depends on)' },
193
+ maxDepth: { type: 'number', description: 'Max relationship depth (default: 3)', default: 3 },
194
+ relationTypes: { type: 'array', items: { type: 'string' }, description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, OVERRIDES (default: usage-based)' },
195
+ includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
196
+ minConfidence: { type: 'number', description: 'Minimum confidence 0-1 (default: 0.7)' },
197
+ repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
198
+ },
199
+ required: ['target', 'direction'],
200
+ },
201
+ },
202
+ ];
@@ -0,0 +1,5 @@
1
+ /** @file api.ts
2
+ * @description REST API server for browser clients to query local .code-mapper/ indexes
3
+ * Also hosts MCP server over StreamableHTTP for remote AI tool access
4
+ * Binds to 127.0.0.1 by default with CORS restricted to localhost */
5
+ export declare const createServer: (port: number, host?: string) => Promise<void>;
@@ -0,0 +1,340 @@
1
+ // code-mapper/src/server/api.ts
2
+ /** @file api.ts
3
+ * @description REST API server for browser clients to query local .code-mapper/ indexes
4
+ * Also hosts MCP server over StreamableHTTP for remote AI tool access
5
+ * Binds to 127.0.0.1 by default with CORS restricted to localhost */
6
+ import express from 'express';
7
+ import cors from 'cors';
8
+ import path from 'path';
9
+ import fs from 'fs/promises';
10
+ import { loadMeta, listRegisteredRepos } from '../storage/repo-manager.js';
11
+ import { executeQuery, closeLbug, withLbugDb } from '../core/lbug/lbug-adapter.js';
12
+ import { NODE_TABLES } from '../core/lbug/schema.js';
13
+ import { searchFTSFromLbug } from '../core/search/bm25-index.js';
14
+ import { hybridSearch } from '../core/search/hybrid-search.js';
15
+ // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
16
+ // at server startup — crashes on unsupported Node ABI versions (#89)
17
+ import { LocalBackend } from '../mcp/local/local-backend.js';
18
+ import { mountMCPEndpoints } from './mcp-http.js';
19
+ const buildGraph = async () => {
20
+ const nodes = [];
21
+ for (const table of NODE_TABLES) {
22
+ try {
23
+ let query = '';
24
+ if (table === 'File') {
25
+ query = `MATCH (n:File) RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.content AS content`;
26
+ }
27
+ else if (table === 'Folder') {
28
+ query = `MATCH (n:Folder) RETURN n.id AS id, n.name AS name, n.filePath AS filePath`;
29
+ }
30
+ else if (table === 'Community') {
31
+ query = `MATCH (n:Community) RETURN n.id AS id, n.label AS label, n.heuristicLabel AS heuristicLabel, n.cohesion AS cohesion, n.symbolCount AS symbolCount`;
32
+ }
33
+ else if (table === 'Process') {
34
+ query = `MATCH (n:Process) RETURN n.id AS id, n.label AS label, n.heuristicLabel AS heuristicLabel, n.processType AS processType, n.stepCount AS stepCount, n.communities AS communities, n.entryPointId AS entryPointId, n.terminalId AS terminalId`;
35
+ }
36
+ else {
37
+ query = `MATCH (n:${table}) RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.content AS content`;
38
+ }
39
+ const rows = await executeQuery(query);
40
+ for (const row of rows) {
41
+ nodes.push({
42
+ id: row.id ?? row[0],
43
+ label: table,
44
+ properties: {
45
+ name: row.name ?? row.label ?? row[1],
46
+ filePath: row.filePath ?? row[2],
47
+ startLine: row.startLine,
48
+ endLine: row.endLine,
49
+ content: row.content,
50
+ heuristicLabel: row.heuristicLabel,
51
+ cohesion: row.cohesion,
52
+ symbolCount: row.symbolCount,
53
+ processType: row.processType,
54
+ stepCount: row.stepCount,
55
+ communities: row.communities,
56
+ entryPointId: row.entryPointId,
57
+ terminalId: row.terminalId,
58
+ },
59
+ });
60
+ }
61
+ }
62
+ catch {
63
+ // ignore empty tables
64
+ }
65
+ }
66
+ const relationships = [];
67
+ const relRows = await executeQuery(`MATCH (a)-[r:CodeRelation]->(b) RETURN a.id AS sourceId, b.id AS targetId, r.type AS type, r.confidence AS confidence, r.reason AS reason, r.step AS step`);
68
+ for (const row of relRows) {
69
+ relationships.push({
70
+ id: `${row.sourceId}_${row.type}_${row.targetId}`,
71
+ type: row.type,
72
+ sourceId: row.sourceId,
73
+ targetId: row.targetId,
74
+ confidence: row.confidence,
75
+ reason: row.reason,
76
+ step: row.step,
77
+ });
78
+ }
79
+ return { nodes, relationships };
80
+ };
81
+ const statusFromError = (err) => {
82
+ const msg = String(err?.message ?? '');
83
+ if (msg.includes('No indexed repositories') || msg.includes('not found'))
84
+ return 404;
85
+ if (msg.includes('Multiple repositories'))
86
+ return 400;
87
+ return 500;
88
+ };
89
+ const requestedRepo = (req) => {
90
+ const fromQuery = typeof req.query.repo === 'string' ? req.query.repo : undefined;
91
+ if (fromQuery)
92
+ return fromQuery;
93
+ if (req.body && typeof req.body === 'object' && typeof req.body.repo === 'string') {
94
+ return req.body.repo;
95
+ }
96
+ return undefined;
97
+ };
98
+ export const createServer = async (port, host = '127.0.0.1') => {
99
+ const app = express();
100
+ // CORS: localhost origins + deployed site only
101
+ // Non-browser requests (curl, server-to-server) have no origin and are allowed
102
+ app.use(cors({
103
+ origin: (origin, callback) => {
104
+ if (!origin
105
+ || origin.startsWith('http://localhost:')
106
+ || origin.startsWith('http://127.0.0.1:')
107
+ || origin === 'https://code-mapper.vercel.app') {
108
+ callback(null, true);
109
+ }
110
+ else {
111
+ callback(new Error('Not allowed by CORS'));
112
+ }
113
+ }
114
+ }));
115
+ app.use(express.json({ limit: '10mb' }));
116
+ // Initialize MCP backend (multi-repo, shared across all MCP sessions)
117
+ const backend = new LocalBackend();
118
+ await backend.init();
119
+ const cleanupMcp = mountMCPEndpoints(app, backend);
120
+ // Resolve a repo by name from the global registry, or default to first
121
+ const resolveRepo = async (repoName) => {
122
+ const repos = await listRegisteredRepos();
123
+ if (repos.length === 0)
124
+ return null;
125
+ if (repoName)
126
+ return repos.find(r => r.name === repoName) || null;
127
+ return repos[0]; // default to first
128
+ };
129
+ // List all registered repos
130
+ app.get('/api/repos', async (_req, res) => {
131
+ try {
132
+ const repos = await listRegisteredRepos();
133
+ res.json(repos.map(r => ({
134
+ name: r.name, path: r.path, indexedAt: r.indexedAt,
135
+ lastCommit: r.lastCommit, stats: r.stats,
136
+ })));
137
+ }
138
+ catch (err) {
139
+ res.status(500).json({ error: err.message || 'Failed to list repos' });
140
+ }
141
+ });
142
+ // Get repo info
143
+ app.get('/api/repo', async (req, res) => {
144
+ try {
145
+ const entry = await resolveRepo(requestedRepo(req));
146
+ if (!entry) {
147
+ res.status(404).json({ error: 'Repository not found. Run: code-mapper analyze' });
148
+ return;
149
+ }
150
+ const meta = await loadMeta(entry.storagePath);
151
+ res.json({
152
+ name: entry.name,
153
+ repoPath: entry.path,
154
+ indexedAt: meta?.indexedAt ?? entry.indexedAt,
155
+ stats: meta?.stats ?? entry.stats ?? {},
156
+ });
157
+ }
158
+ catch (err) {
159
+ res.status(500).json({ error: err.message || 'Failed to get repo info' });
160
+ }
161
+ });
162
+ // Get full graph
163
+ app.get('/api/graph', async (req, res) => {
164
+ try {
165
+ const entry = await resolveRepo(requestedRepo(req));
166
+ if (!entry) {
167
+ res.status(404).json({ error: 'Repository not found' });
168
+ return;
169
+ }
170
+ const lbugPath = path.join(entry.storagePath, 'lbug');
171
+ const graph = await withLbugDb(lbugPath, async () => buildGraph());
172
+ res.json(graph);
173
+ }
174
+ catch (err) {
175
+ res.status(500).json({ error: err.message || 'Failed to build graph' });
176
+ }
177
+ });
178
+ // Execute Cypher query
179
+ app.post('/api/query', async (req, res) => {
180
+ try {
181
+ const cypher = req.body.cypher;
182
+ if (!cypher) {
183
+ res.status(400).json({ error: 'Missing "cypher" in request body' });
184
+ return;
185
+ }
186
+ const entry = await resolveRepo(requestedRepo(req));
187
+ if (!entry) {
188
+ res.status(404).json({ error: 'Repository not found' });
189
+ return;
190
+ }
191
+ const lbugPath = path.join(entry.storagePath, 'lbug');
192
+ const result = await withLbugDb(lbugPath, () => executeQuery(cypher));
193
+ res.json({ result });
194
+ }
195
+ catch (err) {
196
+ res.status(500).json({ error: err.message || 'Query failed' });
197
+ }
198
+ });
199
+ // Search
200
+ app.post('/api/search', async (req, res) => {
201
+ try {
202
+ const query = (req.body.query ?? '').trim();
203
+ if (!query) {
204
+ res.status(400).json({ error: 'Missing "query" in request body' });
205
+ return;
206
+ }
207
+ const entry = await resolveRepo(requestedRepo(req));
208
+ if (!entry) {
209
+ res.status(404).json({ error: 'Repository not found' });
210
+ return;
211
+ }
212
+ const lbugPath = path.join(entry.storagePath, 'lbug');
213
+ const parsedLimit = Number(req.body.limit ?? 10);
214
+ const limit = Number.isFinite(parsedLimit)
215
+ ? Math.max(1, Math.min(100, Math.trunc(parsedLimit)))
216
+ : 10;
217
+ const results = await withLbugDb(lbugPath, async () => {
218
+ const { isEmbedderReady } = await import('../core/embeddings/embedder.js');
219
+ if (isEmbedderReady()) {
220
+ const { semanticSearch } = await import('../core/embeddings/embedding-pipeline.js');
221
+ return hybridSearch(query, limit, executeQuery, semanticSearch);
222
+ }
223
+ // FTS-only fallback when embeddings aren't loaded
224
+ return searchFTSFromLbug(query, limit);
225
+ });
226
+ res.json({ results });
227
+ }
228
+ catch (err) {
229
+ res.status(500).json({ error: err.message || 'Search failed' });
230
+ }
231
+ });
232
+ // Read file (with path traversal guard)
233
+ app.get('/api/file', async (req, res) => {
234
+ try {
235
+ const entry = await resolveRepo(requestedRepo(req));
236
+ if (!entry) {
237
+ res.status(404).json({ error: 'Repository not found' });
238
+ return;
239
+ }
240
+ const filePath = req.query.path;
241
+ if (!filePath) {
242
+ res.status(400).json({ error: 'Missing path' });
243
+ return;
244
+ }
245
+ // Prevent path traversal: resolve and verify path stays within repo root
246
+ const repoRoot = path.resolve(entry.path);
247
+ const fullPath = path.resolve(repoRoot, filePath);
248
+ if (!fullPath.startsWith(repoRoot + path.sep) && fullPath !== repoRoot) {
249
+ res.status(403).json({ error: 'Path traversal denied' });
250
+ return;
251
+ }
252
+ const content = await fs.readFile(fullPath, 'utf-8');
253
+ res.json({ content });
254
+ }
255
+ catch (err) {
256
+ if (err.code === 'ENOENT') {
257
+ res.status(404).json({ error: 'File not found' });
258
+ }
259
+ else {
260
+ res.status(500).json({ error: err.message || 'Failed to read file' });
261
+ }
262
+ }
263
+ });
264
+ // List all processes
265
+ app.get('/api/processes', async (req, res) => {
266
+ try {
267
+ const result = await backend.queryProcesses(requestedRepo(req));
268
+ res.json(result);
269
+ }
270
+ catch (err) {
271
+ res.status(statusFromError(err)).json({ error: err.message || 'Failed to query processes' });
272
+ }
273
+ });
274
+ // Process detail
275
+ app.get('/api/process', async (req, res) => {
276
+ try {
277
+ const name = String(req.query.name ?? '').trim();
278
+ if (!name) {
279
+ res.status(400).json({ error: 'Missing "name" query parameter' });
280
+ return;
281
+ }
282
+ const result = await backend.queryProcessDetail(name, requestedRepo(req));
283
+ if (result?.error) {
284
+ res.status(404).json({ error: result.error });
285
+ return;
286
+ }
287
+ res.json(result);
288
+ }
289
+ catch (err) {
290
+ res.status(statusFromError(err)).json({ error: err.message || 'Failed to query process detail' });
291
+ }
292
+ });
293
+ // List all clusters
294
+ app.get('/api/clusters', async (req, res) => {
295
+ try {
296
+ const result = await backend.queryClusters(requestedRepo(req));
297
+ res.json(result);
298
+ }
299
+ catch (err) {
300
+ res.status(statusFromError(err)).json({ error: err.message || 'Failed to query clusters' });
301
+ }
302
+ });
303
+ // Cluster detail
304
+ app.get('/api/cluster', async (req, res) => {
305
+ try {
306
+ const name = String(req.query.name ?? '').trim();
307
+ if (!name) {
308
+ res.status(400).json({ error: 'Missing "name" query parameter' });
309
+ return;
310
+ }
311
+ const result = await backend.queryClusterDetail(name, requestedRepo(req));
312
+ if (result?.error) {
313
+ res.status(404).json({ error: result.error });
314
+ return;
315
+ }
316
+ res.json(result);
317
+ }
318
+ catch (err) {
319
+ res.status(statusFromError(err)).json({ error: err.message || 'Failed to query cluster detail' });
320
+ }
321
+ });
322
+ // Global error handler
323
+ app.use((err, _req, res, _next) => {
324
+ console.error('Unhandled error:', err);
325
+ res.status(500).json({ error: 'Internal server error' });
326
+ });
327
+ const server = app.listen(port, host, () => {
328
+ console.log(`Code Mapper server running on http://${host}:${port}`);
329
+ });
330
+ // Graceful shutdown
331
+ const shutdown = async () => {
332
+ server.close();
333
+ await cleanupMcp();
334
+ await closeLbug();
335
+ await backend.disconnect();
336
+ process.exit(0);
337
+ };
338
+ process.once('SIGINT', shutdown);
339
+ process.once('SIGTERM', shutdown);
340
+ };
@@ -0,0 +1,7 @@
1
+ /** @file mcp-http.ts
2
+ * @description Mounts Code Mapper MCP server on Express using StreamableHTTP transport
3
+ * Each client gets a stateful session; LocalBackend is shared (thread-safe)
4
+ * Sessions are evicted on close or after idle timeout */
5
+ import type { Express } from 'express';
6
+ import type { LocalBackend } from '../mcp/local/local-backend.js';
7
+ export declare function mountMCPEndpoints(app: Express, backend: LocalBackend): () => Promise<void>;
@@ -0,0 +1,95 @@
1
+ // code-mapper/src/server/mcp-http.ts
2
+ /** @file mcp-http.ts
3
+ * @description Mounts Code Mapper MCP server on Express using StreamableHTTP transport
4
+ * Each client gets a stateful session; LocalBackend is shared (thread-safe)
5
+ * Sessions are evicted on close or after idle timeout */
6
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
7
+ import { createMCPServer } from '../mcp/server.js';
8
+ import { randomUUID } from 'crypto';
9
+ /** Idle sessions are evicted after 30 minutes */
10
+ const SESSION_TTL_MS = 30 * 60 * 1000;
11
+ /** Cleanup sweep runs every 5 minutes */
12
+ const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
13
+ export function mountMCPEndpoints(app, backend) {
14
+ const sessions = new Map();
15
+ // Periodic cleanup of idle sessions
16
+ const cleanupTimer = setInterval(() => {
17
+ const now = Date.now();
18
+ for (const [id, session] of sessions) {
19
+ if (now - session.lastActivity > SESSION_TTL_MS) {
20
+ try {
21
+ session.server.close();
22
+ }
23
+ catch { }
24
+ sessions.delete(id);
25
+ }
26
+ }
27
+ }, CLEANUP_INTERVAL_MS);
28
+ if (cleanupTimer && typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
29
+ cleanupTimer.unref();
30
+ }
31
+ const handleMcpRequest = async (req, res) => {
32
+ const sessionId = req.headers['mcp-session-id'];
33
+ if (sessionId && sessions.has(sessionId)) {
34
+ // Existing session — delegate to its transport
35
+ const session = sessions.get(sessionId);
36
+ session.lastActivity = Date.now();
37
+ await session.transport.handleRequest(req, res, req.body);
38
+ }
39
+ else if (sessionId) {
40
+ // Unknown/expired session ID (per MCP spec, tell client to re-initialize)
41
+ res.status(404).json({
42
+ jsonrpc: '2.0',
43
+ error: { code: -32001, message: 'Session not found. Re-initialize.' },
44
+ id: null,
45
+ });
46
+ }
47
+ else if (req.method === 'POST') {
48
+ // No session ID — new client initializing
49
+ const transport = new StreamableHTTPServerTransport({
50
+ sessionIdGenerator: () => randomUUID(),
51
+ });
52
+ const server = createMCPServer(backend);
53
+ await server.connect(transport);
54
+ await transport.handleRequest(req, res, req.body);
55
+ if (transport.sessionId) {
56
+ sessions.set(transport.sessionId, { server, transport, lastActivity: Date.now() });
57
+ transport.onclose = () => {
58
+ sessions.delete(transport.sessionId);
59
+ };
60
+ }
61
+ }
62
+ else {
63
+ res.status(400).json({
64
+ jsonrpc: '2.0',
65
+ error: { code: -32000, message: 'No valid session. Send a POST to initialize.' },
66
+ id: null,
67
+ });
68
+ }
69
+ };
70
+ app.all('/api/mcp', (req, res) => {
71
+ void handleMcpRequest(req, res).catch((err) => {
72
+ console.error('MCP HTTP request failed:', err);
73
+ if (res.headersSent)
74
+ return;
75
+ res.status(500).json({
76
+ jsonrpc: '2.0',
77
+ error: { code: -32000, message: 'Internal MCP server error' },
78
+ id: null,
79
+ });
80
+ });
81
+ });
82
+ const cleanup = async () => {
83
+ clearInterval(cleanupTimer);
84
+ const closers = [...sessions.values()].map(async (session) => {
85
+ try {
86
+ await Promise.resolve(session.server.close());
87
+ }
88
+ catch { }
89
+ });
90
+ sessions.clear();
91
+ await Promise.allSettled(closers);
92
+ };
93
+ console.log('MCP HTTP endpoints mounted at /api/mcp');
94
+ return cleanup;
95
+ }