code-auditor-mcp 2.6.2 → 3.0.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 (343) hide show
  1. package/README.md +319 -147
  2. package/dist/analyzers/analyzerUtils.d.ts +13 -11
  3. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  4. package/dist/analyzers/analyzerUtils.js +22 -56
  5. package/dist/analyzers/analyzerUtils.js.map +1 -1
  6. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  7. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
  8. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  9. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  10. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  11. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  12. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  13. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -1
  14. package/dist/analyzers/dataAccessAnalyzer.js +50 -24
  15. package/dist/analyzers/dataAccessAnalyzer.js.map +1 -1
  16. package/dist/analyzers/documentationAnalyzer.d.ts +4 -1
  17. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  18. package/dist/analyzers/documentationAnalyzer.js +210 -111
  19. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  20. package/dist/analyzers/dryAnalyzer.d.ts.map +1 -1
  21. package/dist/analyzers/dryAnalyzer.js +190 -58
  22. package/dist/analyzers/dryAnalyzer.js.map +1 -1
  23. package/dist/analyzers/invariantsAnalyzer.d.ts +20 -0
  24. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -0
  25. package/dist/analyzers/invariantsAnalyzer.js +148 -0
  26. package/dist/analyzers/invariantsAnalyzer.js.map +1 -0
  27. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  28. package/dist/analyzers/reactAnalyzer.js +2 -2
  29. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  30. package/dist/analyzers/schemaAnalyzer.d.ts +2 -0
  31. package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -1
  32. package/dist/analyzers/schemaAnalyzer.js +306 -147
  33. package/dist/analyzers/schemaAnalyzer.js.map +1 -1
  34. package/dist/analyzers/solidAnalyzer.d.ts.map +1 -1
  35. package/dist/analyzers/solidAnalyzer.js +145 -119
  36. package/dist/analyzers/solidAnalyzer.js.map +1 -1
  37. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +3 -1
  38. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  39. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +41 -1
  40. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  41. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  42. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +8 -8
  43. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  44. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  45. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +6 -6
  46. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  47. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  48. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  49. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +1 -1
  50. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  51. package/dist/api/index.js +1 -0
  52. package/dist/api/index.js.map +1 -1
  53. package/dist/auditRunner.d.ts.map +1 -1
  54. package/dist/auditRunner.js +367 -24
  55. package/dist/auditRunner.js.map +1 -1
  56. package/dist/cli.d.ts +1 -1
  57. package/dist/cli.d.ts.map +1 -1
  58. package/dist/cli.js +740 -18
  59. package/dist/cli.js.map +1 -1
  60. package/dist/codeIndexDB-enhanced.d.ts +35 -13
  61. package/dist/codeIndexDB-enhanced.d.ts.map +1 -1
  62. package/dist/codeIndexDB-enhanced.js +311 -214
  63. package/dist/codeIndexDB-enhanced.js.map +1 -1
  64. package/dist/{codeIndexDb.d.ts → codeIndexDB.d.ts} +51 -224
  65. package/dist/codeIndexDB.d.ts.map +1 -0
  66. package/dist/codeIndexDB.js +2042 -0
  67. package/dist/codeIndexDB.js.map +1 -0
  68. package/dist/codeIndexService.d.ts.map +1 -1
  69. package/dist/codeIndexService.js.map +1 -1
  70. package/dist/componentScanner.d.ts +14 -8
  71. package/dist/componentScanner.d.ts.map +1 -1
  72. package/dist/componentScanner.js +136 -113
  73. package/dist/componentScanner.js.map +1 -1
  74. package/dist/config/configLoader.d.ts.map +1 -1
  75. package/dist/config/configLoader.js +10 -7
  76. package/dist/config/configLoader.js.map +1 -1
  77. package/dist/config/defaults.d.ts.map +1 -1
  78. package/dist/constants.js.map +1 -1
  79. package/dist/fingerprint.d.ts +15 -0
  80. package/dist/fingerprint.d.ts.map +1 -0
  81. package/dist/fingerprint.js +32 -0
  82. package/dist/fingerprint.js.map +1 -0
  83. package/dist/functionScanner.d.ts +5 -0
  84. package/dist/functionScanner.d.ts.map +1 -1
  85. package/dist/functionScanner.js +417 -341
  86. package/dist/functionScanner.js.map +1 -1
  87. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
  88. package/dist/generators/AiderConfigGenerator.d.ts.map +1 -1
  89. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
  90. package/dist/generators/BaseConfigGenerator.js.map +1 -1
  91. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
  92. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
  93. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
  94. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
  95. package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
  96. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
  97. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
  98. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
  99. package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -1
  100. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
  101. package/dist/grammars/tree-sitter-go.wasm +0 -0
  102. package/dist/grammars/tree-sitter-javascript.wasm +0 -0
  103. package/dist/grammars/tree-sitter-tsx.wasm +0 -0
  104. package/dist/grammars/tree-sitter-typescript.wasm +0 -0
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -1
  108. package/dist/index.js.map +1 -1
  109. package/dist/invariants/index.d.ts +9 -0
  110. package/dist/invariants/index.d.ts.map +1 -0
  111. package/dist/invariants/index.js +4 -0
  112. package/dist/invariants/index.js.map +1 -0
  113. package/dist/invariants/invariant-rules.schema.json +137 -0
  114. package/dist/invariants/ruleEngine.d.ts +32 -0
  115. package/dist/invariants/ruleEngine.d.ts.map +1 -0
  116. package/dist/invariants/ruleEngine.js +536 -0
  117. package/dist/invariants/ruleEngine.js.map +1 -0
  118. package/dist/invariants/ruleValidator.d.ts +17 -0
  119. package/dist/invariants/ruleValidator.d.ts.map +1 -0
  120. package/dist/invariants/ruleValidator.js +264 -0
  121. package/dist/invariants/ruleValidator.js.map +1 -0
  122. package/dist/invariants/types.d.ts +78 -0
  123. package/dist/invariants/types.d.ts.map +1 -0
  124. package/dist/invariants/types.js +5 -0
  125. package/dist/invariants/types.js.map +1 -0
  126. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  127. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  128. package/dist/languages/LanguageRegistry.d.ts.map +1 -1
  129. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  130. package/dist/languages/RuntimeManager.js +1 -1
  131. package/dist/languages/RuntimeManager.js.map +1 -1
  132. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  133. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  134. package/dist/languages/adapterBridge.d.ts +102 -0
  135. package/dist/languages/adapterBridge.d.ts.map +1 -0
  136. package/dist/languages/adapterBridge.js +382 -0
  137. package/dist/languages/adapterBridge.js.map +1 -0
  138. package/dist/languages/go/GoAdapter.d.ts +37 -26
  139. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  140. package/dist/languages/go/GoAdapter.js +560 -281
  141. package/dist/languages/go/GoAdapter.js.map +1 -1
  142. package/dist/languages/index.d.ts +5 -2
  143. package/dist/languages/index.d.ts.map +1 -1
  144. package/dist/languages/index.js +11 -9
  145. package/dist/languages/index.js.map +1 -1
  146. package/dist/languages/tree-sitter/converter.d.ts +43 -0
  147. package/dist/languages/tree-sitter/converter.d.ts.map +1 -0
  148. package/dist/languages/tree-sitter/converter.js +150 -0
  149. package/dist/languages/tree-sitter/converter.js.map +1 -0
  150. package/dist/languages/tree-sitter/parser.d.ts +36 -0
  151. package/dist/languages/tree-sitter/parser.d.ts.map +1 -0
  152. package/dist/languages/tree-sitter/parser.js +114 -0
  153. package/dist/languages/tree-sitter/parser.js.map +1 -0
  154. package/dist/languages/types.d.ts +11 -0
  155. package/dist/languages/types.d.ts.map +1 -1
  156. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +92 -0
  157. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -0
  158. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +951 -0
  159. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -0
  160. package/dist/mcp-tools/projectTasks.d.ts +58 -22
  161. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  162. package/dist/mcp-tools/projectTasks.js +172 -10
  163. package/dist/mcp-tools/projectTasks.js.map +1 -1
  164. package/dist/mcp-tools/whitelistTools.d.ts +21 -21
  165. package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
  166. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  167. package/dist/mcp-tools/workflowGuide.js +130 -126
  168. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  169. package/dist/mcp-tools-shared.d.ts.map +1 -1
  170. package/dist/mcp-tools-shared.js.map +1 -1
  171. package/dist/mcp-ui-simple.js +1 -1
  172. package/dist/mcp-ui-simple.js.map +1 -1
  173. package/dist/mcp.d.ts +1 -1
  174. package/dist/mcp.d.ts.map +1 -1
  175. package/dist/mcp.js +1376 -1181
  176. package/dist/mcp.js.map +1 -1
  177. package/dist/mcpAuditJobs.d.ts +4 -0
  178. package/dist/mcpAuditJobs.d.ts.map +1 -1
  179. package/dist/mcpAuditJobs.js +49 -2
  180. package/dist/mcpAuditJobs.js.map +1 -1
  181. package/dist/mcpToolErrors.d.ts.map +1 -1
  182. package/dist/mcpToolErrors.js.map +1 -1
  183. package/dist/native/ast-grep-napi.darwin-arm64.node +0 -0
  184. package/dist/native/ast-grep-napi.darwin-x64.node +0 -0
  185. package/dist/native/ast-grep-napi.linux-arm64-gnu.node +0 -0
  186. package/dist/native/ast-grep-napi.linux-arm64-musl.node +0 -0
  187. package/dist/native/ast-grep-napi.linux-x64-gnu.node +0 -0
  188. package/dist/native/ast-grep-napi.linux-x64-musl.node +0 -0
  189. package/dist/native/ast-grep-napi.win32-x64-msvc.node +0 -0
  190. package/dist/native-bootstrap.d.ts +15 -0
  191. package/dist/native-bootstrap.d.ts.map +1 -0
  192. package/dist/native-bootstrap.js +75 -0
  193. package/dist/native-bootstrap.js.map +1 -0
  194. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  195. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  196. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  197. package/dist/reporting/reportGenerator.d.ts.map +1 -1
  198. package/dist/reporting/reportGenerator.js +4 -0
  199. package/dist/reporting/reportGenerator.js.map +1 -1
  200. package/dist/reporting/sarifReportGenerator.d.ts +39 -0
  201. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -0
  202. package/dist/reporting/sarifReportGenerator.js +229 -0
  203. package/dist/reporting/sarifReportGenerator.js.map +1 -0
  204. package/dist/search/QueryParser.d.ts +41 -0
  205. package/dist/search/QueryParser.d.ts.map +1 -1
  206. package/dist/search/QueryParser.js +200 -19
  207. package/dist/search/QueryParser.js.map +1 -1
  208. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  209. package/dist/services/CodeMapGenerator.js +4 -4
  210. package/dist/services/CodeMapGenerator.js.map +1 -1
  211. package/dist/services/ProjectTaskRepository.d.ts +15 -2
  212. package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
  213. package/dist/services/ProjectTaskRepository.js +16 -1
  214. package/dist/services/ProjectTaskRepository.js.map +1 -1
  215. package/dist/services/SchemaParser.d.ts.map +1 -1
  216. package/dist/services/SchemaParser.js +5 -8
  217. package/dist/services/SchemaParser.js.map +1 -1
  218. package/dist/services/auditJobService.d.ts +1 -1
  219. package/dist/services/projectTaskHelpers.js.map +1 -1
  220. package/dist/services/whitelistService.d.ts.map +1 -1
  221. package/dist/services/whitelistService.js +1 -1
  222. package/dist/services/whitelistService.js.map +1 -1
  223. package/dist/tool-registry.d.ts +79 -0
  224. package/dist/tool-registry.d.ts.map +1 -0
  225. package/dist/tool-registry.js +139 -0
  226. package/dist/tool-registry.js.map +1 -0
  227. package/dist/types/projectTask.d.ts +4 -0
  228. package/dist/types/projectTask.d.ts.map +1 -1
  229. package/dist/types.d.ts +26 -2
  230. package/dist/types.d.ts.map +1 -1
  231. package/dist/types.js.map +1 -1
  232. package/dist/ui/InteractivePrompts.d.ts.map +1 -1
  233. package/dist/ui/InteractivePrompts.js.map +1 -1
  234. package/dist/utils/astParser.d.ts +31 -38
  235. package/dist/utils/astParser.d.ts.map +1 -1
  236. package/dist/utils/astParser.js +138 -196
  237. package/dist/utils/astParser.js.map +1 -1
  238. package/dist/utils/astUtils.d.ts +87 -58
  239. package/dist/utils/astUtils.d.ts.map +1 -1
  240. package/dist/utils/astUtils.js +852 -581
  241. package/dist/utils/astUtils.js.map +1 -1
  242. package/dist/utils/componentPatterns.js.map +1 -1
  243. package/dist/utils/componentResponsibility.d.ts +13 -9
  244. package/dist/utils/componentResponsibility.d.ts.map +1 -1
  245. package/dist/utils/componentResponsibility.js +236 -206
  246. package/dist/utils/componentResponsibility.js.map +1 -1
  247. package/dist/utils/dependencyExtractor.d.ts +11 -6
  248. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  249. package/dist/utils/dependencyExtractor.js +200 -119
  250. package/dist/utils/dependencyExtractor.js.map +1 -1
  251. package/dist/utils/fileDiscovery.d.ts +1 -1
  252. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  253. package/dist/utils/fileDiscovery.js.map +1 -1
  254. package/dist/utils/jsDocParser.d.ts +27 -30
  255. package/dist/utils/jsDocParser.d.ts.map +1 -1
  256. package/dist/utils/jsDocParser.js +271 -207
  257. package/dist/utils/jsDocParser.js.map +1 -1
  258. package/dist/utils/reactDetection.d.ts +27 -12
  259. package/dist/utils/reactDetection.d.ts.map +1 -1
  260. package/dist/utils/reactDetection.js +331 -219
  261. package/dist/utils/reactDetection.js.map +1 -1
  262. package/dist/workers/auditWorker.js +17 -3
  263. package/dist/workers/auditWorker.js.map +1 -1
  264. package/package.json +54 -51
  265. package/dist/adapters/GoAdapter.d.ts +0 -53
  266. package/dist/adapters/GoAdapter.d.ts.map +0 -1
  267. package/dist/adapters/GoAdapter.js +0 -611
  268. package/dist/adapters/GoAdapter.js.map +0 -1
  269. package/dist/adapters/GoConfig.d.ts +0 -35
  270. package/dist/adapters/GoConfig.d.ts.map +0 -1
  271. package/dist/adapters/GoConfig.js +0 -155
  272. package/dist/adapters/GoConfig.js.map +0 -1
  273. package/dist/adapters/LanguageAdapter.d.ts +0 -135
  274. package/dist/adapters/LanguageAdapter.d.ts.map +0 -1
  275. package/dist/adapters/LanguageAdapter.js +0 -47
  276. package/dist/adapters/LanguageAdapter.js.map +0 -1
  277. package/dist/adapters/TypeScriptAdapter.d.ts +0 -43
  278. package/dist/adapters/TypeScriptAdapter.d.ts.map +0 -1
  279. package/dist/adapters/TypeScriptAdapter.js +0 -386
  280. package/dist/adapters/TypeScriptAdapter.js.map +0 -1
  281. package/dist/adapters/UniversalAnalyzer.d.ts +0 -42
  282. package/dist/adapters/UniversalAnalyzer.d.ts.map +0 -1
  283. package/dist/adapters/UniversalAnalyzer.js +0 -103
  284. package/dist/adapters/UniversalAnalyzer.js.map +0 -1
  285. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts +0 -48
  286. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts.map +0 -1
  287. package/dist/adapters/UniversalSOLIDAnalyzer.js +0 -178
  288. package/dist/adapters/UniversalSOLIDAnalyzer.js.map +0 -1
  289. package/dist/adapters/solidAnalyzerCompat.d.ts +0 -21
  290. package/dist/adapters/solidAnalyzerCompat.d.ts.map +0 -1
  291. package/dist/adapters/solidAnalyzerCompat.js +0 -78
  292. package/dist/adapters/solidAnalyzerCompat.js.map +0 -1
  293. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts +0 -20
  294. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts.map +0 -1
  295. package/dist/analyzers/dataAccessAnalyzerCompat.js +0 -59
  296. package/dist/analyzers/dataAccessAnalyzerCompat.js.map +0 -1
  297. package/dist/analyzers/documentationAnalyzerCompat.d.ts +0 -20
  298. package/dist/analyzers/documentationAnalyzerCompat.d.ts.map +0 -1
  299. package/dist/analyzers/documentationAnalyzerCompat.js +0 -52
  300. package/dist/analyzers/documentationAnalyzerCompat.js.map +0 -1
  301. package/dist/analyzers/dryAnalyzerCompat.d.ts +0 -20
  302. package/dist/analyzers/dryAnalyzerCompat.d.ts.map +0 -1
  303. package/dist/analyzers/dryAnalyzerCompat.js +0 -44
  304. package/dist/analyzers/dryAnalyzerCompat.js.map +0 -1
  305. package/dist/analyzers/schemaAnalyzerCompat.d.ts +0 -20
  306. package/dist/analyzers/schemaAnalyzerCompat.d.ts.map +0 -1
  307. package/dist/analyzers/schemaAnalyzerCompat.js +0 -69
  308. package/dist/analyzers/schemaAnalyzerCompat.js.map +0 -1
  309. package/dist/analyzers/solidAnalyzerCompat.d.ts +0 -20
  310. package/dist/analyzers/solidAnalyzerCompat.d.ts.map +0 -1
  311. package/dist/analyzers/solidAnalyzerCompat.js +0 -78
  312. package/dist/analyzers/solidAnalyzerCompat.js.map +0 -1
  313. package/dist/codeIndexDb.d.ts.map +0 -1
  314. package/dist/codeIndexDb.js +0 -2102
  315. package/dist/codeIndexDb.js.map +0 -1
  316. package/dist/languages/typescript/TypeScriptAdapter.d.ts +0 -108
  317. package/dist/languages/typescript/TypeScriptAdapter.d.ts.map +0 -1
  318. package/dist/languages/typescript/TypeScriptAdapter.js +0 -567
  319. package/dist/languages/typescript/TypeScriptAdapter.js.map +0 -1
  320. package/dist/languages/typescript/test-extraction.d.ts +0 -2
  321. package/dist/languages/typescript/test-extraction.d.ts.map +0 -1
  322. package/dist/languages/typescript/test-extraction.js +0 -36
  323. package/dist/languages/typescript/test-extraction.js.map +0 -1
  324. package/dist/mcp-hybrid.d.ts +0 -10
  325. package/dist/mcp-hybrid.d.ts.map +0 -1
  326. package/dist/mcp-hybrid.js +0 -41
  327. package/dist/mcp-hybrid.js.map +0 -1
  328. package/dist/mcp-index.d.ts +0 -13
  329. package/dist/mcp-index.d.ts.map +0 -1
  330. package/dist/mcp-index.js +0 -136
  331. package/dist/mcp-index.js.map +0 -1
  332. package/dist/mcp-standalone.d.ts +0 -3
  333. package/dist/mcp-standalone.d.ts.map +0 -1
  334. package/dist/mcp-standalone.js +0 -1324
  335. package/dist/mcp-standalone.js.map +0 -1
  336. package/dist/mcp-ui-server.d.ts +0 -27
  337. package/dist/mcp-ui-server.d.ts.map +0 -1
  338. package/dist/mcp-ui-server.js +0 -484
  339. package/dist/mcp-ui-server.js.map +0 -1
  340. package/dist/test-multi-strategy-search.d.ts +0 -3
  341. package/dist/test-multi-strategy-search.d.ts.map +0 -1
  342. package/dist/test-multi-strategy-search.js +0 -170
  343. package/dist/test-multi-strategy-search.js.map +0 -1
package/dist/mcp.js CHANGED
@@ -1,649 +1,103 @@
1
1
  #!/usr/bin/env node
2
- // MCP servers use stdio: stdout for protocol messages, stderr for logging
3
- // All log messages must go to stderr to avoid interfering with MCP protocol
2
+ // MUST be first import: sets NAPI_RS_NATIVE_LIBRARY_PATH before @ast-grep/napi loads
3
+ import './native-bootstrap.js';
4
+ // ── Bootstrap: parse --data-dir before any module loads CodeIndexDB ──────────
5
+ import path from 'node:path';
6
+ let autoIndexPath;
7
+ let uiMode = false;
8
+ let stdioMode = false;
9
+ let showHelp = false;
10
+ const argv = process.argv.slice(2);
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const a = argv[i];
13
+ if (a === '--data-dir' || a === '--dataDir') {
14
+ const val = argv[i + 1];
15
+ if (val && !val.startsWith('-')) {
16
+ process.env.CODE_AUDITOR_DATA_DIR = path.resolve(val);
17
+ }
18
+ else {
19
+ console.error('[code-auditor] --data-dir requires a directory path (next argv). Using CODE_AUDITOR_DATA_DIR env or default.');
20
+ }
21
+ }
22
+ else if (a === '--auto-index') {
23
+ const val = argv[i + 1];
24
+ if (val && !val.startsWith('-')) {
25
+ autoIndexPath = path.resolve(val);
26
+ }
27
+ else {
28
+ autoIndexPath = process.cwd();
29
+ }
30
+ }
31
+ else if (a === '--ui') {
32
+ uiMode = true;
33
+ }
34
+ else if (a === '--stdio') {
35
+ stdioMode = true;
36
+ }
37
+ else if (a === '--help' || a === '-h') {
38
+ showHelp = true;
39
+ }
40
+ }
41
+ if (showHelp) {
42
+ console.error(`code-auditor-mcp v0.0.0
43
+ Usage: code-auditor-mcp [--stdio] [--ui] [--auto-index <path>] [--data-dir <dir>]
44
+
45
+ --stdio Start MCP stdio server (default mode)
46
+ --ui Start the HTTP UI server on port 3001 (or MCP_UI_PORT)
47
+ --auto-index Sync the index from <path> and exit
48
+ --data-dir Directory for persistent data (index.db, tasks, configs)
49
+ --help, -h Show this message
50
+ `);
51
+ process.exit(0);
52
+ }
53
+ // ── Imports ──────────────────────────────────────────────────────────────────
4
54
  import chalk from 'chalk';
5
55
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
56
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
- import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
57
+ import { CallToolRequestSchema, ListToolsRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
58
+ import { createWriteStream } from 'node:fs';
59
+ import fs from 'node:fs/promises';
8
60
  import { createAuditRunner } from './auditRunner.js';
9
61
  import { searchFunctions, findDefinition, syncFileIndex, getDatabase } from './codeIndexService.js';
10
62
  import { CodeMapGenerator } from './services/CodeMapGenerator.js';
11
63
  import { analyzeDocumentation } from './analyzers/documentationAnalyzer.js';
12
64
  import { ConfigGeneratorFactory } from './generators/ConfigGeneratorFactory.js';
13
65
  import { DEFAULT_SERVER_URL, IS_DEV_MODE, PACKAGE_VERSION } from './constants.js';
14
- import { getAuditJobStatus, getAuditResultsPage, startAuditJob } from './mcpAuditJobs.js';
15
- import path from 'node:path';
16
- import fs from 'node:fs/promises';
17
- import { createWriteStream } from 'node:fs';
66
+ import { getAuditJobStatus, getAuditResultsPage, getAuditResultsAsSarif, startAuditJob } from './mcpAuditJobs.js';
18
67
  import { CodeIndexDB } from './codeIndexDB.js';
19
68
  import { logMcpDebug, logMcpInfo, mcpDebugStderr, mcpTraceStderr } from './mcpDiagnostics.js';
20
69
  import { assertAuditPathExists, formatMcpToolErrorPayload } from './mcpToolErrors.js';
21
- // Set up file logging (only in development mode)
70
+ import { ToolRegistry } from './tool-registry.js';
71
+ import { initParsers } from './languages/index.js';
72
+ // ── Console / logging setup ──────────────────────────────────────────────────
73
+ const originalConsoleError = console.error;
22
74
  let logStream = null;
23
75
  if (IS_DEV_MODE) {
24
76
  const logFilePath = path.join(process.cwd(), 'mcp-server.log');
25
77
  logStream = createWriteStream(logFilePath, { flags: 'a' });
26
78
  console.error(chalk.blue('[INFO]'), `Development mode: Logging to file: ${logFilePath}`);
27
79
  }
28
- // Save original console.error (log is redirected to stderr; see below)
29
- const originalConsoleError = console.error;
30
- // Override console.log — MCP uses stdout only for JSON-RPC; log() must never touch stdout.
31
80
  console.log = (...args) => {
32
81
  if (logStream) {
33
- const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
82
+ const message = args
83
+ .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
84
+ .join(' ');
34
85
  const timestamp = new Date().toISOString();
35
86
  logStream.write(`[${timestamp}] [LOG] ${message}\n`);
36
87
  }
37
88
  originalConsoleError.apply(console, args);
38
89
  };
39
- // Override console.error (with conditional file logging)
40
90
  console.error = (...args) => {
41
91
  if (logStream) {
42
- const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
92
+ const message = args
93
+ .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
94
+ .join(' ');
43
95
  const timestamp = new Date().toISOString();
44
96
  logStream.write(`[${timestamp}] [ERROR] ${message}\n`);
45
97
  }
46
98
  originalConsoleError.apply(console, args);
47
99
  };
48
- const tools = [
49
- // Core Audit Tools
50
- {
51
- name: 'audit',
52
- description: 'Fetch paginated results for a completed audit by resultId (or auditId alias). This tool never starts a new audit.',
53
- parameters: [
54
- {
55
- name: 'limit',
56
- type: 'number',
57
- required: false,
58
- description: 'Maximum number of violations to return per page (default: 50, max: 100)',
59
- default: 50,
60
- },
61
- {
62
- name: 'offset',
63
- type: 'number',
64
- required: false,
65
- description: 'Violation offset for pagination (default: 0).',
66
- default: 0,
67
- },
68
- {
69
- name: 'resultId',
70
- type: 'string',
71
- required: false,
72
- description: 'Result ID returned by audit_status when the background audit job completes.',
73
- },
74
- {
75
- name: 'auditId',
76
- type: 'string',
77
- required: false,
78
- description: 'Backward-compatible alias for resultId.',
79
- },
80
- ],
81
- },
82
- {
83
- name: 'start_audit',
84
- description: 'Start a background audit job. Returns immediately with a jobId. Poll audit_status until completed, then fetch pages with audit or audit_results.',
85
- parameters: [
86
- {
87
- name: 'path',
88
- type: 'string',
89
- required: false,
90
- description: 'File or directory path to audit (defaults to current directory).',
91
- default: process.cwd(),
92
- },
93
- {
94
- name: 'analyzers',
95
- type: 'array',
96
- required: false,
97
- description: 'Analyzers to run (default: solid, dry, documentation, react, data-access).',
98
- default: ['solid', 'dry', 'documentation', 'react', 'data-access'],
99
- },
100
- {
101
- name: 'minSeverity',
102
- type: 'string',
103
- required: false,
104
- description: 'Minimum severity level to report.',
105
- default: 'warning',
106
- enum: ['info', 'warning', 'critical'],
107
- },
108
- {
109
- name: 'indexFunctions',
110
- type: 'boolean',
111
- required: false,
112
- description: 'Index functions during audit (default: true).',
113
- default: true,
114
- },
115
- {
116
- name: 'analyzerConfigs',
117
- type: 'object',
118
- required: false,
119
- description: 'Analyzer-specific configuration overrides.',
120
- },
121
- {
122
- name: 'analyzerConcurrency',
123
- type: 'number',
124
- required: false,
125
- description: 'Max analyzers to run in parallel (default: 1).',
126
- default: 1,
127
- },
128
- {
129
- name: 'partitionStrategy',
130
- type: 'string',
131
- required: false,
132
- description: 'Partition mode: none | auto | top-level. auto shards when large source trees are detected.',
133
- default: 'auto',
134
- enum: ['none', 'auto', 'top-level'],
135
- },
136
- {
137
- name: 'maxPartitions',
138
- type: 'number',
139
- required: false,
140
- description: 'Maximum number of folder partitions for sharded audits (default: 4).',
141
- default: 4,
142
- },
143
- {
144
- name: 'partitionThresholdFiles',
145
- type: 'number',
146
- required: false,
147
- description: 'Minimum discovered files before auto partitioning is enabled (default: 250).',
148
- default: 250,
149
- },
150
- {
151
- name: 'workerCount',
152
- type: 'number',
153
- required: false,
154
- description: 'Number of worker processes for shard execution (default: min(4, CPU-1)).',
155
- },
156
- {
157
- name: 'maxRetries',
158
- type: 'number',
159
- required: false,
160
- description: 'Retry attempts per shard for retryable failures (default: 1).',
161
- default: 1,
162
- },
163
- {
164
- name: 'shardTimeoutMs',
165
- type: 'number',
166
- required: false,
167
- description: 'Per-shard timeout in milliseconds (default: 180000).',
168
- default: 180000,
169
- },
170
- {
171
- name: 'retryBackoffMs',
172
- type: 'number',
173
- required: false,
174
- description: 'Base retry backoff in milliseconds (default: 500).',
175
- default: 500,
176
- },
177
- {
178
- name: 'jobTimeoutMs',
179
- type: 'number',
180
- required: false,
181
- description: 'Maximum wall time for the entire audit job (default 30m, env CODE_AUDITOR_JOB_TIMEOUT_MS, cap 4h). Aborts workers cooperatively then tears down.',
182
- },
183
- {
184
- name: 'maxFilesPerRun',
185
- type: 'number',
186
- required: false,
187
- description: 'Per worker chunk: if more files match than this, the worker finishes one chunk and the parent queues the rest on another worker (optional).',
188
- },
189
- {
190
- name: 'shardSoftBudgetMs',
191
- type: 'number',
192
- required: false,
193
- description: 'Per worker soft wall-clock budget; aborts in-process analysis cooperatively via AbortSignal so the parent can assign the next chunk to a fresh worker.',
194
- },
195
- {
196
- name: 'generateCodeMap',
197
- type: 'boolean',
198
- required: false,
199
- description: 'Generate code map artifacts during audit (default: false).',
200
- default: false,
201
- },
202
- ],
203
- },
204
- {
205
- name: 'audit_status',
206
- description: 'Get current status for a previously started background audit job. Returns resultId when completed.',
207
- parameters: [
208
- {
209
- name: 'jobId',
210
- type: 'string',
211
- required: true,
212
- description: 'Job ID returned by start_audit.',
213
- },
214
- ],
215
- },
216
- {
217
- name: 'audit_results',
218
- description: 'Fetch paginated violations for a completed audit result by resultId.',
219
- parameters: [
220
- {
221
- name: 'resultId',
222
- type: 'string',
223
- required: true,
224
- description: 'Result ID returned by audit_status when status is completed.',
225
- },
226
- {
227
- name: 'limit',
228
- type: 'number',
229
- required: false,
230
- description: 'Maximum number of violations to return per page (default: 50, max: 100).',
231
- default: 50,
232
- },
233
- {
234
- name: 'offset',
235
- type: 'number',
236
- required: false,
237
- description: 'Violation offset for pagination (default: 0).',
238
- default: 0,
239
- },
240
- ],
241
- },
242
- {
243
- name: 'audit_health',
244
- description: 'Quick health check of a codebase with key metrics',
245
- parameters: [
246
- {
247
- name: 'path',
248
- type: 'string',
249
- required: false,
250
- description: 'The directory path to check',
251
- default: process.cwd(),
252
- },
253
- {
254
- name: 'threshold',
255
- type: 'number',
256
- required: false,
257
- description: 'Health score threshold (0-100) for pass/fail',
258
- default: 70,
259
- },
260
- {
261
- name: 'indexFunctions',
262
- type: 'boolean',
263
- required: false,
264
- description: 'Automatically index functions during health check',
265
- default: true,
266
- },
267
- {
268
- name: 'analyzerConfigs',
269
- type: 'object',
270
- required: false,
271
- description: 'Analyzer-specific configuration overrides (e.g., SOLID thresholds, DRY settings)',
272
- },
273
- {
274
- name: 'generateCodeMap',
275
- type: 'boolean',
276
- required: false,
277
- description: 'Generate and return a human-readable code map as part of the health check results',
278
- default: true,
279
- },
280
- ],
281
- },
282
- // Code Index Tools
283
- {
284
- name: 'search_code',
285
- description: 'Search indexed functions and React components with natural language queries. Supports operators: entity:component, component:functional|class|memo|forwardRef, hook:useState|useEffect|etc, prop:propName, dep:packageName, dependency:lodash, uses:express, calls:functionName, calledby:functionName, dependents-of:functionName, used-by:functionName, depends-on:module, imports-from:file, unused-imports, dead-imports, type:fileType, file:path, lang:language, complexity:1-10, jsdoc:true|false',
286
- parameters: [
287
- {
288
- name: 'query',
289
- type: 'string',
290
- required: true,
291
- description: 'Search query with natural language and/or operators. Examples: "Button component:functional", "entity:component hook:useState", "render prop:onClick", "dep:lodash", "calls:validateUser", "unused-imports", "dependents-of:authenticate"',
292
- },
293
- {
294
- name: 'filters',
295
- type: 'object',
296
- required: false,
297
- description: 'Optional filters (language, filePath, dependencies, componentType, entityType, searchMode). Set searchMode to "content" to search within function bodies, "metadata" for names/signatures only, or "both" for combined search',
298
- },
299
- {
300
- name: 'limit',
301
- type: 'number',
302
- required: false,
303
- description: 'Maximum results to return',
304
- default: 50,
305
- },
306
- {
307
- name: 'offset',
308
- type: 'number',
309
- required: false,
310
- description: 'Offset for pagination',
311
- default: 0,
312
- },
313
- ],
314
- },
315
- {
316
- name: 'find_definition',
317
- description: 'Find the exact definition of a specific function or React component',
318
- parameters: [
319
- {
320
- name: 'name',
321
- type: 'string',
322
- required: true,
323
- description: 'Function or component name to find',
324
- },
325
- {
326
- name: 'filePath',
327
- type: 'string',
328
- required: false,
329
- description: 'Optional file path to narrow search',
330
- },
331
- ],
332
- },
333
- {
334
- name: 'sync_index',
335
- description: 'Synchronize, cleanup, or reset analysis-derived data (indexed functions, FlexSearch, cached audits, code maps, schema overlays). Project tasks, analyzer configs, and whitelist entries are preserved on reset.',
336
- parameters: [
337
- {
338
- name: 'mode',
339
- type: 'string',
340
- required: false,
341
- description: 'sync: update index from files; cleanup: remove index rows for deleted files; reset: clear all analysis-derived data (not tasks/config/whitelist)',
342
- default: 'sync',
343
- enum: ['sync', 'cleanup', 'reset'],
344
- },
345
- {
346
- name: 'path',
347
- type: 'string',
348
- required: false,
349
- description: 'Optional specific path to sync',
350
- },
351
- ],
352
- },
353
- // AI Configuration Tool
354
- {
355
- name: 'generate_ai_config',
356
- description: 'Generate configuration files for AI coding assistants',
357
- parameters: [
358
- {
359
- name: 'tools',
360
- type: 'array',
361
- required: true,
362
- description: 'AI tools to configure (cursor, continue, copilot, claude, zed, windsurf, cody, aider, cline, pearai)',
363
- },
364
- {
365
- name: 'outputDir',
366
- type: 'string',
367
- required: false,
368
- description: 'Output directory for configuration files',
369
- default: '.',
370
- },
371
- ],
372
- },
373
- // Workflow Guide Tool
374
- {
375
- name: 'get_workflow_guide',
376
- description: 'Get recommended workflows and best practices for using code auditor tools effectively',
377
- parameters: [
378
- {
379
- name: 'scenario',
380
- type: 'string',
381
- required: false,
382
- description: 'Specific scenario: initial-setup, react-development, code-review, find-patterns, maintenance. Leave empty to see all.',
383
- },
384
- ],
385
- },
386
- // Analyzer Configuration Tools
387
- {
388
- name: 'set_analyzer_config',
389
- description: 'Set or update analyzer configuration that persists across audit runs',
390
- parameters: [
391
- {
392
- name: 'analyzerName',
393
- type: 'string',
394
- required: true,
395
- description: 'The analyzer to configure (solid, dry, security, etc.)',
396
- },
397
- {
398
- name: 'config',
399
- type: 'object',
400
- required: true,
401
- description: 'Configuration object for the analyzer (e.g., thresholds, rules)',
402
- },
403
- {
404
- name: 'projectPath',
405
- type: 'string',
406
- required: false,
407
- description: 'Optional project path for project-specific config (defaults to global)',
408
- },
409
- ],
410
- },
411
- {
412
- name: 'get_analyzer_config',
413
- description: 'Get current configuration for an analyzer',
414
- parameters: [
415
- {
416
- name: 'analyzerName',
417
- type: 'string',
418
- required: false,
419
- description: 'Specific analyzer name, or omit to get all configs',
420
- },
421
- {
422
- name: 'projectPath',
423
- type: 'string',
424
- required: false,
425
- description: 'Optional project path to get project-specific config',
426
- },
427
- ],
428
- },
429
- {
430
- name: 'reset_analyzer_config',
431
- description: 'Reset analyzer configuration to defaults',
432
- parameters: [
433
- {
434
- name: 'analyzerName',
435
- type: 'string',
436
- required: false,
437
- description: 'Specific analyzer to reset, or omit to reset all',
438
- },
439
- {
440
- name: 'projectPath',
441
- type: 'string',
442
- required: false,
443
- description: 'Optional project path to reset only project-specific config',
444
- },
445
- ],
446
- },
447
- {
448
- name: 'get_code_map_section',
449
- description: 'Retrieve a specific section of a previously generated code map',
450
- parameters: [
451
- {
452
- name: 'mapId',
453
- type: 'string',
454
- required: true,
455
- description: 'The map ID returned from a previous audit with code map generation',
456
- },
457
- {
458
- name: 'sectionType',
459
- type: 'string',
460
- required: true,
461
- description: 'The section type to retrieve (e.g., overview, files, dependencies, documentation)',
462
- },
463
- ],
464
- },
465
- {
466
- name: 'list_code_map_sections',
467
- description: 'List all available sections for a code map',
468
- parameters: [
469
- {
470
- name: 'mapId',
471
- type: 'string',
472
- required: true,
473
- description: 'The map ID returned from a previous audit',
474
- },
475
- ],
476
- },
477
- {
478
- name: 'project_tasks',
479
- description: 'Manage a persistent per-project task queue. Tasks and analyzer configs survive sync_index reset; reset clears function index, cached audits, code maps, and schema overlays (no ghost code references). Use delete to remove a task.',
480
- parameters: [
481
- {
482
- name: 'action',
483
- type: 'string',
484
- required: true,
485
- description: 'list | list_tree | create | get | update | complete_task | delete. list/list_tree/create use projectPath or default to process.cwd(); create needs title; get/update/complete_task/delete need taskId; update needs patch object.',
486
- enum: [
487
- 'list',
488
- 'list_tree',
489
- 'create',
490
- 'get',
491
- 'update',
492
- 'complete_task',
493
- 'delete'
494
- ],
495
- },
496
- {
497
- name: 'projectPath',
498
- type: 'string',
499
- required: false,
500
- description: 'Project root (resolved). Omit to use the MCP server working directory (same default idea as audit path); response includes projectPathDefaulted when omitted.',
501
- },
502
- {
503
- name: 'taskId',
504
- type: 'string',
505
- required: false,
506
- description: 'Stable task id (UUID). Required for get, update, complete_task, delete.',
507
- },
508
- {
509
- name: 'title',
510
- type: 'string',
511
- required: false,
512
- description: 'Task title. Required for create.',
513
- },
514
- {
515
- name: 'description',
516
- type: 'string',
517
- required: false,
518
- description: 'Longer description / notes.',
519
- },
520
- {
521
- name: 'status',
522
- type: 'string',
523
- required: false,
524
- description: 'pending | in_progress | blocked | done | cancelled. Filter for list; initial status for create; can be set via update patch.',
525
- enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'],
526
- },
527
- {
528
- name: 'priority',
529
- type: 'string',
530
- required: false,
531
- description: 'Optional priority for create/update patch.',
532
- enum: ['low', 'medium', 'high'],
533
- },
534
- {
535
- name: 'labels',
536
- type: 'array',
537
- required: false,
538
- description: 'String tags (e.g. ["audit","refactor"]).',
539
- },
540
- {
541
- name: 'metadata',
542
- type: 'object',
543
- required: false,
544
- description: 'Arbitrary JSON object: related file paths, links, audit IDs, etc.',
545
- },
546
- {
547
- name: 'parentTaskId',
548
- type: 'string',
549
- required: false,
550
- description: 'Optional parent task for subtasks.',
551
- },
552
- {
553
- name: 'source',
554
- type: 'string',
555
- required: false,
556
- description: 'manual | audit | mcp. Filter for list; provenance for create; can be set via update patch.',
557
- enum: ['manual', 'audit', 'mcp'],
558
- },
559
- {
560
- name: 'blockedBy',
561
- type: 'array',
562
- required: false,
563
- description: 'Task IDs this item is blocked by (waiting-on).',
564
- },
565
- {
566
- name: 'label',
567
- type: 'string',
568
- required: false,
569
- description: 'Filter/search by label value.',
570
- },
571
- {
572
- name: 'blockedByTaskId',
573
- type: 'string',
574
- required: false,
575
- description: 'Filter tasks blocked by this task ID.',
576
- },
577
- {
578
- name: 'query',
579
- type: 'string',
580
- required: false,
581
- description: 'Case-insensitive text search across title/description/labels/files/symbols.',
582
- },
583
- {
584
- name: 'hasChildren',
585
- type: 'boolean',
586
- required: false,
587
- description: 'Filter tasks that do/do not have direct subtasks.',
588
- },
589
- {
590
- name: 'overdueOnly',
591
- type: 'boolean',
592
- required: false,
593
- description: 'Filter tasks with dueAt in the past and not closed.',
594
- },
595
- {
596
- name: 'actionableOnly',
597
- type: 'boolean',
598
- required: false,
599
- description: 'Filter tasks that are open and unblocked by open dependencies.',
600
- },
601
- {
602
- name: 'dueAt',
603
- type: 'string',
604
- required: false,
605
- description: 'ISO 8601 due datetime for create; omit or null in patch to clear.',
606
- },
607
- {
608
- name: 'sortOrder',
609
- type: 'number',
610
- required: false,
611
- description: 'Lower numbers sort first within a project (default 0).',
612
- },
613
- {
614
- name: 'relatedFiles',
615
- type: 'array',
616
- required: false,
617
- description: 'Repo-relative or absolute file paths tied to the task.',
618
- },
619
- {
620
- name: 'relatedSymbols',
621
- type: 'array',
622
- required: false,
623
- description: 'Function, class, or component names for cross-linking.',
624
- },
625
- {
626
- name: 'patch',
627
- type: 'object',
628
- required: false,
629
- description: 'For update: partial fields (title, description, status, priority, labels, metadata, parentTaskId, source, blockedBy, dueAt, sortOrder, relatedFiles, relatedSymbols).',
630
- },
631
- {
632
- name: 'limit',
633
- type: 'number',
634
- required: false,
635
- description: 'Max tasks to return for list (default 500, max 1000).',
636
- },
637
- {
638
- name: 'mode',
639
- type: 'string',
640
- required: false,
641
- description: 'Delete mode: reject (default), detach subtasks, or cascade descendants.',
642
- enum: ['reject', 'detach', 'cascade'],
643
- },
644
- ],
645
- },
646
- ];
100
+ // ── Helpers ──────────────────────────────────────────────────────────────────
647
101
  class RequestAbortedError extends Error {
648
102
  constructor(message = 'MCP request aborted') {
649
103
  super(message);
@@ -651,25 +105,21 @@ class RequestAbortedError extends Error {
651
105
  }
652
106
  }
653
107
  function throwIfRequestAborted(signal, phase) {
654
- if (!signal?.aborted) {
655
- return;
108
+ if (signal?.aborted) {
109
+ throw new RequestAbortedError(`Request aborted${phase ? ` during ${phase}` : ''}`);
656
110
  }
657
- throw new RequestAbortedError(`Request aborted${phase ? ` during ${phase}` : ''}`);
658
111
  }
659
112
  async function withAbortSignal(signal, phase, op) {
660
113
  throwIfRequestAborted(signal, phase);
661
- if (!signal) {
114
+ if (!signal)
662
115
  return op();
663
- }
664
- return await new Promise((resolve, reject) => {
116
+ return new Promise((resolve, reject) => {
665
117
  const onAbort = () => reject(new RequestAbortedError(`Request aborted during ${phase}`));
666
118
  signal.addEventListener('abort', onAbort, { once: true });
667
119
  void op()
668
120
  .then(resolve)
669
121
  .catch(reject)
670
- .finally(() => {
671
- signal.removeEventListener('abort', onAbort);
672
- });
122
+ .finally(() => signal.removeEventListener('abort', onAbort));
673
123
  });
674
124
  }
675
125
  function serializeForMcp(value) {
@@ -679,7 +129,7 @@ function serializeForMcp(value) {
679
129
  catch (error) {
680
130
  return JSON.stringify({
681
131
  success: false,
682
- error: `Failed to serialize MCP response: ${error instanceof Error ? error.message : String(error)}`
132
+ error: `Failed to serialize MCP response: ${error instanceof Error ? error.message : String(error)}`,
683
133
  });
684
134
  }
685
135
  }
@@ -691,6 +141,26 @@ function safeJson(value) {
691
141
  return `[unserializable: ${error instanceof Error ? error.message : String(error)}]`;
692
142
  }
693
143
  }
144
+ function calculateHealthScore(result) {
145
+ const filesAnalyzed = result.metadata?.filesAnalyzed || 1;
146
+ const critical = result.summary.criticalIssues || 0;
147
+ const warnings = result.summary.warnings || 0;
148
+ const suggestions = result.summary.suggestions || 0;
149
+ const weights = { critical: 10, warning: 3, suggestion: 0.5 };
150
+ const weightedViolations = critical * weights.critical + warnings * weights.warning + suggestions * weights.suggestion;
151
+ const violationsPerFile = weightedViolations / filesAnalyzed;
152
+ const score = 100 - violationsPerFile * 2;
153
+ return Math.max(0, Math.round(Math.min(100, score)));
154
+ }
155
+ function getHealthRecommendation(score, result) {
156
+ if (score >= 90)
157
+ return 'Excellent code health!';
158
+ if (score >= 70)
159
+ return 'Good code health with room for improvement';
160
+ if (result.summary.criticalIssues > 0)
161
+ return `Fix ${result.summary.criticalIssues} critical violations first`;
162
+ return 'Code health needs attention - run detailed audit';
163
+ }
694
164
  function registerProcessReliabilityHandlers() {
695
165
  const g = globalThis;
696
166
  const exitOnFatal = process.env.CODE_AUDITOR_EXIT_ON_FATAL === '1' ||
@@ -700,9 +170,8 @@ function registerProcessReliabilityHandlers() {
700
170
  console.error(chalk.red('[ERROR]'), 'Uncaught exception:', error);
701
171
  console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
702
172
  console.error(chalk.red('[ERROR]'), 'Error details:', safeJson(error));
703
- if (exitOnFatal) {
173
+ if (exitOnFatal)
704
174
  process.exit(1);
705
- }
706
175
  });
707
176
  g.__codeAuditorUncaughtExceptionHandlerInstalled = true;
708
177
  }
@@ -711,285 +180,826 @@ function registerProcessReliabilityHandlers() {
711
180
  console.error(chalk.red('[ERROR]'), 'Unhandled rejection at:', promise);
712
181
  console.error(chalk.red('[ERROR]'), 'Reason:', reason);
713
182
  console.error(chalk.red('[ERROR]'), 'Rejection details:', safeJson(reason));
714
- if (exitOnFatal) {
183
+ if (exitOnFatal)
715
184
  process.exit(1);
716
- }
717
185
  });
718
186
  g.__codeAuditorUnhandledRejectionHandlerInstalled = true;
719
187
  }
720
188
  }
721
- async function startMcpServer() {
722
- logMcpInfo('startup', `code-auditor-mcp ${PACKAGE_VERSION} (stdio)`, {
723
- pid: process.pid,
724
- cwd: process.cwd(),
725
- dataDir: process.env.CODE_AUDITOR_DATA_DIR ?? '(default)',
726
- debug: process.env.CODE_AUDITOR_DEBUG ?? '0',
727
- logFile: process.env.CODE_AUDITOR_LOG_FILE ?? '(none)'
728
- });
729
- mcpDebugStderr(chalk.blue('[DEBUG]'), `Node ${process.version} · cwd ${process.cwd()}`);
730
- const server = new Server({
731
- name: 'code-auditor',
732
- version: PACKAGE_VERSION,
733
- }, {
734
- capabilities: {
735
- tools: {},
736
- },
737
- });
738
- // Add error handler
739
- server.onerror = (error) => {
740
- console.error(chalk.red('[ERROR]'), 'Server error occurred:', error);
741
- console.error(chalk.red('[ERROR]'), 'Error stack:', error.stack);
742
- };
743
- server.setRequestHandler(ListToolsRequestSchema, async (request) => {
744
- mcpTraceStderr(chalk.blue('[DEBUG]'), 'ListTools', JSON.stringify(request, null, 2));
745
- const response = {
746
- tools: tools.map(tool => ({
747
- name: tool.name,
748
- description: tool.description,
749
- inputSchema: {
189
+ // ── Tool registration ────────────────────────────────────────────────────────
190
+ const DEFAULT_ANALYZERS = ['solid', 'dry', 'documentation', 'react', 'data-access'];
191
+ function registerAllTools(registry) {
192
+ // ── audit ──────────────────────────────────────────────────────────────────
193
+ const auditActions = [
194
+ {
195
+ name: 'run',
196
+ description: 'Run a synchronous audit on the specified path. Returns a full audit result with violations.',
197
+ parameters: [
198
+ {
199
+ name: 'path',
200
+ type: 'string',
201
+ required: false,
202
+ description: 'File or directory path to audit (defaults to current directory).',
203
+ default: process.cwd(),
204
+ },
205
+ {
206
+ name: 'analyzers',
207
+ type: 'array',
208
+ required: false,
209
+ description: `Analyzers to run (default: ${DEFAULT_ANALYZERS.join(', ')}).`,
210
+ default: DEFAULT_ANALYZERS,
211
+ },
212
+ {
213
+ name: 'minSeverity',
214
+ type: 'string',
215
+ required: false,
216
+ description: 'Minimum severity level to report.',
217
+ default: 'warning',
218
+ enum: ['info', 'warning', 'critical'],
219
+ },
220
+ {
221
+ name: 'indexFunctions',
222
+ type: 'boolean',
223
+ required: false,
224
+ description: 'Index functions during audit (default: true).',
225
+ default: true,
226
+ },
227
+ {
228
+ name: 'analyzerConfigs',
750
229
  type: 'object',
751
- properties: tool.parameters.reduce((acc, param) => {
752
- acc[param.name] = {
753
- type: param.type,
754
- description: param.description,
755
- ...(param.default !== undefined && { default: param.default }),
756
- ...(param.enum && { enum: param.enum }),
757
- };
758
- return acc;
759
- }, {}),
760
- required: tool.parameters.filter(p => p.required).map(p => p.name),
230
+ required: false,
231
+ description: 'Analyzer-specific configuration overrides.',
761
232
  },
762
- })),
763
- };
764
- mcpTraceStderr(chalk.blue('[DEBUG]'), `ListTools → ${response.tools.length} tools`);
765
- return response;
766
- });
767
- server.setRequestHandler(InitializeRequestSchema, async (request) => {
768
- mcpTraceStderr(chalk.blue('[DEBUG]'), 'initialize', JSON.stringify(request, null, 2));
769
- const response = {
770
- protocolVersion: '2024-11-05',
771
- capabilities: {
772
- tools: {},
773
- },
774
- serverInfo: {
775
- name: 'code-auditor',
776
- version: PACKAGE_VERSION,
777
- },
778
- };
779
- mcpTraceStderr(chalk.blue('[DEBUG]'), 'initialize response', JSON.stringify(response, null, 2));
780
- return response;
781
- });
782
- server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
783
- const { name, arguments: args } = request.params;
784
- mcpTraceStderr(chalk.blue('[DEBUG]'), `CallTool ${name}`, JSON.stringify({
785
- argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
786
- hasArgs: !!args
787
- }));
788
- const requestSignal = extra?.signal;
789
- const toolStartedAt = Date.now();
790
- try {
791
- if (name !== 'audit_status') {
792
- logMcpDebug('tool', `call ${name}`, {
793
- argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
794
- pathArg: args?.path,
795
- cwd: process.cwd()
796
- });
797
- }
798
- let result;
799
- throwIfRequestAborted(requestSignal, `start ${name}`);
800
- switch (name) {
801
- case 'start_audit': {
802
- result = await withAbortSignal(requestSignal, 'start_audit', () => startAuditJob(args, {
803
- defaultAnalyzers: ['solid', 'dry', 'documentation', 'react', 'data-access'],
804
- defaultMinSeverity: 'warning',
805
- defaultGenerateCodeMap: false,
806
- }));
807
- break;
808
- }
809
- case 'audit_status': {
810
- const jobId = args.jobId;
811
- if (!jobId) {
812
- throw new Error('audit_status requires jobId');
813
- }
814
- result = getAuditJobStatus(jobId);
815
- break;
816
- }
817
- case 'audit_results': {
818
- result = await withAbortSignal(requestSignal, 'audit_results', () => getAuditResultsPage(args));
819
- break;
820
- }
821
- case 'audit': {
822
- result = await withAbortSignal(requestSignal, 'audit', () => getAuditResultsPage(args));
823
- break;
824
- }
825
- case 'audit_health': {
233
+ {
234
+ name: 'scope',
235
+ type: 'string',
236
+ required: false,
237
+ description: 'Audit scope: "all" (default), "changed" (files differing from index), "git:<ref>" (diff against a git ref), or a comma-separated list of file paths.',
238
+ default: 'all',
239
+ },
240
+ ],
241
+ handler: async (args, signal) => {
242
+ return withAbortSignal(signal, 'audit.run', async () => {
826
243
  const auditPath = path.resolve(args.path || process.cwd());
827
244
  await assertAuditPathExists(auditPath);
828
- const threshold = args.threshold || 70;
829
- const indexFunctions = args.indexFunctions !== false; // Default true
830
- const generateCodeMap = args.generateCodeMap !== false; // Default true
831
- // Get stored analyzer configs from database
245
+ const analyzers = args.analyzers ?? DEFAULT_ANALYZERS;
246
+ const minSeverity = (args.minSeverity || 'warning');
247
+ const indexFunctions = args.indexFunctions !== false;
832
248
  const db = CodeIndexDB.getInstance();
833
249
  await db.initialize();
834
250
  const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
835
- // Merge stored configs with any provided configs
836
251
  const analyzerConfigs = {
837
252
  ...storedConfigs,
838
- ...(args.analyzerConfigs || {})
253
+ ...(args.analyzerConfigs || {}),
839
254
  };
255
+ const scope = args.scope || 'all';
840
256
  const runner = createAuditRunner({
841
257
  projectRoot: auditPath,
842
- enabledAnalyzers: ['solid', 'dry', 'documentation', 'react', 'data-access'],
843
- minSeverity: 'warning',
258
+ enabledAnalyzers: analyzers,
259
+ minSeverity,
844
260
  verbose: false,
845
- indexFunctions, // Pass the flag to the runner
261
+ indexFunctions,
262
+ scope: scope !== 'all' ? scope : undefined,
846
263
  ...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
847
- progressCallback: (p) => {
848
- if (p.phase === 'function-indexing' &&
849
- typeof p.current === 'number' &&
850
- typeof p.total === 'number' &&
851
- p.total > 0 &&
852
- p.current % 50 !== 0 &&
853
- p.current !== p.total) {
854
- return;
855
- }
856
- logMcpDebug('audit_health', p.message ?? p.phase ?? 'progress', {
857
- phase: p.phase,
858
- analyzer: p.analyzer,
859
- current: p.current,
860
- total: p.total
861
- });
862
- }
863
264
  });
864
265
  const auditResult = await runner.run();
865
- const healthScore = calculateHealthScore(auditResult);
866
- // Handle function indexing if enabled and functions were collected
867
- let indexingResult = null;
868
266
  if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
869
267
  try {
870
- const syncStats = { added: 0, updated: 0, removed: 0 };
871
- // Sync each file's functions to handle additions, updates, and removals
872
268
  for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
873
- const fileStats = await syncFileIndex(filePath, functions);
874
- syncStats.added += fileStats.added;
875
- syncStats.updated += fileStats.updated;
876
- syncStats.removed += fileStats.removed;
269
+ await syncFileIndex(filePath, functions);
877
270
  }
878
- indexingResult = {
879
- success: true,
880
- registered: syncStats.added + syncStats.updated,
881
- failed: 0,
882
- syncStats
883
- };
884
- mcpDebugStderr(chalk.blue('[INFO]'), `Synced functions: ${syncStats.added} added, ${syncStats.updated} updated, ${syncStats.removed} removed`);
885
271
  }
886
- catch (error) {
887
- console.error(chalk.yellow('[WARN]'), 'Failed to sync functions:', error);
272
+ catch {
273
+ // indexing is best-effort for sync audit
888
274
  }
889
275
  }
890
- // Generate code map if requested and functions were indexed
891
- let codeMapResult = null;
892
- if (generateCodeMap && indexingResult && indexingResult.success) {
276
+ return {
277
+ success: true,
278
+ summary: auditResult.summary,
279
+ violations: auditResult.analyzerResults,
280
+ metadata: auditResult.metadata,
281
+ };
282
+ });
283
+ },
284
+ },
285
+ {
286
+ name: 'start',
287
+ description: 'Start a background audit job. Returns immediately with a jobId. Poll `audit.status` until completed, then fetch pages with `audit.results`.',
288
+ parameters: [
289
+ {
290
+ name: 'path',
291
+ type: 'string',
292
+ required: false,
293
+ description: 'File or directory path to audit (defaults to current directory).',
294
+ default: process.cwd(),
295
+ },
296
+ {
297
+ name: 'analyzers',
298
+ type: 'array',
299
+ required: false,
300
+ description: `Analyzers to run (default: ${DEFAULT_ANALYZERS.join(', ')}).`,
301
+ default: DEFAULT_ANALYZERS,
302
+ },
303
+ {
304
+ name: 'minSeverity',
305
+ type: 'string',
306
+ required: false,
307
+ description: 'Minimum severity level to report.',
308
+ default: 'warning',
309
+ enum: ['info', 'warning', 'critical'],
310
+ },
311
+ {
312
+ name: 'indexFunctions',
313
+ type: 'boolean',
314
+ required: false,
315
+ description: 'Index functions during audit (default: true).',
316
+ default: true,
317
+ },
318
+ {
319
+ name: 'analyzerConfigs',
320
+ type: 'object',
321
+ required: false,
322
+ description: 'Analyzer-specific configuration overrides.',
323
+ },
324
+ {
325
+ name: 'scope',
326
+ type: 'string',
327
+ required: false,
328
+ description: 'Audit scope: "all" (default), "changed" (files differing from index), "git:<ref>" (diff against a git ref), or a comma-separated list of file paths.',
329
+ default: 'all',
330
+ },
331
+ {
332
+ name: 'analyzerConcurrency',
333
+ type: 'number',
334
+ required: false,
335
+ description: 'Max analyzers to run in parallel (default: 1).',
336
+ default: 1,
337
+ },
338
+ {
339
+ name: 'partitionStrategy',
340
+ type: 'string',
341
+ required: false,
342
+ description: 'Partition mode: none | auto | top-level.',
343
+ default: 'auto',
344
+ enum: ['none', 'auto', 'top-level'],
345
+ },
346
+ {
347
+ name: 'maxPartitions',
348
+ type: 'number',
349
+ required: false,
350
+ description: 'Maximum number of folder partitions (default: 4).',
351
+ default: 4,
352
+ },
353
+ {
354
+ name: 'partitionThresholdFiles',
355
+ type: 'number',
356
+ required: false,
357
+ description: 'Minimum files before auto partitioning (default: 250).',
358
+ default: 250,
359
+ },
360
+ {
361
+ name: 'workerCount',
362
+ type: 'number',
363
+ required: false,
364
+ description: 'Number of worker processes (default: min(4, CPU-1)).',
365
+ },
366
+ {
367
+ name: 'maxRetries',
368
+ type: 'number',
369
+ required: false,
370
+ description: 'Retry attempts per shard (default: 1).',
371
+ default: 1,
372
+ },
373
+ {
374
+ name: 'shardTimeoutMs',
375
+ type: 'number',
376
+ required: false,
377
+ description: 'Per-shard timeout in ms (default: 180000).',
378
+ default: 180000,
379
+ },
380
+ {
381
+ name: 'retryBackoffMs',
382
+ type: 'number',
383
+ required: false,
384
+ description: 'Base retry backoff in ms (default: 500).',
385
+ default: 500,
386
+ },
387
+ {
388
+ name: 'jobTimeoutMs',
389
+ type: 'number',
390
+ required: false,
391
+ description: 'Max wall time for the audit job (default 30m, cap 4h).',
392
+ },
393
+ {
394
+ name: 'maxFilesPerRun',
395
+ type: 'number',
396
+ required: false,
397
+ description: 'Per worker chunk size for file processing.',
398
+ },
399
+ {
400
+ name: 'shardSoftBudgetMs',
401
+ type: 'number',
402
+ required: false,
403
+ description: 'Per worker soft wall-clock budget.',
404
+ },
405
+ {
406
+ name: 'generateCodeMap',
407
+ type: 'boolean',
408
+ required: false,
409
+ description: 'Generate code map artifacts during audit (default: false).',
410
+ default: false,
411
+ },
412
+ ],
413
+ handler: async (args, signal) => {
414
+ return withAbortSignal(signal, 'audit.start', () => startAuditJob(args, {
415
+ defaultAnalyzers: DEFAULT_ANALYZERS,
416
+ defaultMinSeverity: 'warning',
417
+ defaultGenerateCodeMap: false,
418
+ }));
419
+ },
420
+ },
421
+ {
422
+ name: 'status',
423
+ description: 'Get current status for a previously started background audit job.',
424
+ parameters: [
425
+ {
426
+ name: 'jobId',
427
+ type: 'string',
428
+ required: true,
429
+ description: 'Job ID returned by `audit start`.',
430
+ },
431
+ ],
432
+ handler: async (args) => {
433
+ const jobId = args.jobId;
434
+ if (!jobId)
435
+ throw new Error('audit.status requires jobId');
436
+ return getAuditJobStatus(jobId);
437
+ },
438
+ },
439
+ {
440
+ name: 'results',
441
+ description: 'Fetch paginated violations for a completed audit result by resultId.',
442
+ parameters: [
443
+ {
444
+ name: 'resultId',
445
+ type: 'string',
446
+ required: false,
447
+ description: 'Result ID returned by `audit status` when completed. Also accepts legacy `auditId`.',
448
+ },
449
+ {
450
+ name: 'auditId',
451
+ type: 'string',
452
+ required: false,
453
+ description: 'Backward-compatible alias for resultId.',
454
+ },
455
+ {
456
+ name: 'limit',
457
+ type: 'number',
458
+ required: false,
459
+ description: 'Maximum violations per page (default: 50, max: 100).',
460
+ default: 50,
461
+ },
462
+ {
463
+ name: 'offset',
464
+ type: 'number',
465
+ required: false,
466
+ description: 'Violation offset for pagination (default: 0).',
467
+ default: 0,
468
+ },
469
+ {
470
+ name: 'format',
471
+ type: 'string',
472
+ required: false,
473
+ description: 'Output format. "json" (default, structured data) or "sarif" (SARIF 2.1.0 JSON string).',
474
+ },
475
+ ],
476
+ handler: async (args, signal) => {
477
+ const format = args.format || 'json';
478
+ if (format === 'sarif') {
479
+ return withAbortSignal(signal, 'audit.results', () => getAuditResultsAsSarif(args));
480
+ }
481
+ return withAbortSignal(signal, 'audit.results', () => getAuditResultsPage(args));
482
+ },
483
+ },
484
+ {
485
+ name: 'health',
486
+ description: 'Quick health check of a codebase with key metrics and optional code map generation.',
487
+ parameters: [
488
+ {
489
+ name: 'path',
490
+ type: 'string',
491
+ required: false,
492
+ description: 'The directory path to check.',
493
+ default: process.cwd(),
494
+ },
495
+ {
496
+ name: 'threshold',
497
+ type: 'number',
498
+ required: false,
499
+ description: 'Health score threshold (0-100) for pass/fail.',
500
+ default: 70,
501
+ },
502
+ {
503
+ name: 'indexFunctions',
504
+ type: 'boolean',
505
+ required: false,
506
+ description: 'Automatically index functions during health check.',
507
+ default: true,
508
+ },
509
+ {
510
+ name: 'analyzerConfigs',
511
+ type: 'object',
512
+ required: false,
513
+ description: 'Analyzer-specific configuration overrides.',
514
+ },
515
+ {
516
+ name: 'generateCodeMap',
517
+ type: 'boolean',
518
+ required: false,
519
+ description: 'Generate and return a human-readable code map.',
520
+ default: true,
521
+ },
522
+ ],
523
+ handler: async (args) => {
524
+ const auditPath = path.resolve(args.path || process.cwd());
525
+ await assertAuditPathExists(auditPath);
526
+ const threshold = args.threshold || 70;
527
+ const indexFunctions = args.indexFunctions !== false;
528
+ const generateCodeMap = args.generateCodeMap !== false;
529
+ const db = CodeIndexDB.getInstance();
530
+ await db.initialize();
531
+ const storedConfigs = await db.getAllAnalyzerConfigs(auditPath);
532
+ const analyzerConfigs = {
533
+ ...storedConfigs,
534
+ ...(args.analyzerConfigs || {}),
535
+ };
536
+ const runner = createAuditRunner({
537
+ projectRoot: auditPath,
538
+ enabledAnalyzers: DEFAULT_ANALYZERS,
539
+ minSeverity: 'warning',
540
+ verbose: false,
541
+ indexFunctions,
542
+ ...(Object.keys(analyzerConfigs).length > 0 && { analyzerConfigs }),
543
+ progressCallback: (p) => {
544
+ if (p.phase === 'function-indexing' &&
545
+ typeof p.current === 'number' &&
546
+ typeof p.total === 'number' &&
547
+ p.total > 0 &&
548
+ p.current % 50 !== 0 &&
549
+ p.current !== p.total)
550
+ return;
551
+ logMcpDebug('audit.health', p.message ?? p.phase ?? 'progress', {
552
+ phase: p.phase,
553
+ analyzer: p.analyzer,
554
+ current: p.current,
555
+ total: p.total,
556
+ });
557
+ },
558
+ });
559
+ const auditResult = await runner.run();
560
+ const healthScore = calculateHealthScore(auditResult);
561
+ let indexingResult = null;
562
+ if (indexFunctions && auditResult.metadata.fileToFunctionsMap) {
563
+ try {
564
+ const syncStats = { added: 0, updated: 0, removed: 0 };
565
+ for (const [filePath, functions] of Object.entries(auditResult.metadata.fileToFunctionsMap)) {
566
+ const fileStats = await syncFileIndex(filePath, functions);
567
+ syncStats.added += fileStats.added;
568
+ syncStats.updated += fileStats.updated;
569
+ syncStats.removed += fileStats.removed;
570
+ }
571
+ indexingResult = {
572
+ success: true,
573
+ registered: syncStats.added + syncStats.updated,
574
+ failed: 0,
575
+ syncStats,
576
+ };
577
+ }
578
+ catch {
579
+ // best-effort
580
+ }
581
+ }
582
+ let codeMapResult = null;
583
+ if (generateCodeMap && indexingResult && indexingResult.success) {
584
+ try {
585
+ const mapGenerator = new CodeMapGenerator();
586
+ const mapOptions = {
587
+ includeComplexity: true,
588
+ includeDocumentation: true,
589
+ includeDependencies: true,
590
+ includeUsage: false,
591
+ groupByDirectory: true,
592
+ maxDepth: 8,
593
+ showUnusedImports: true,
594
+ minComplexity: 7,
595
+ };
596
+ let documentation;
893
597
  try {
894
- const mapGenerator = new CodeMapGenerator();
895
- const mapOptions = {
896
- includeComplexity: true,
897
- includeDocumentation: true,
898
- includeDependencies: true,
899
- includeUsage: false,
900
- groupByDirectory: true,
901
- maxDepth: 8, // Slightly smaller for health check
902
- showUnusedImports: true,
903
- minComplexity: 7,
904
- };
905
- // Generate documentation metrics
906
- let documentation = undefined;
907
- try {
908
- // Use existing file discovery from audit result
909
- const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
910
- if (files.length > 0) {
911
- const docResult = await analyzeDocumentation(files);
912
- documentation = docResult.metrics;
913
- }
598
+ const files = Object.keys(auditResult.metadata.fileToFunctionsMap || {});
599
+ if (files.length > 0) {
600
+ const docResult = await analyzeDocumentation(files);
601
+ documentation = docResult.metrics;
914
602
  }
915
- catch (docError) {
916
- console.error(chalk.yellow('[WARN]'), 'Failed to analyze documentation:', docError);
917
- }
918
- // Use paginated code map generation for health check too
919
- const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
920
- ...mapOptions,
921
- includeDocumentation: !!documentation
922
- });
923
- codeMapResult = {
924
- success: true,
925
- mapId: paginatedResult.mapId,
926
- summary: paginatedResult.summary,
927
- quickPreview: paginatedResult.quickPreview,
928
- sections: paginatedResult.summary.sectionsAvailable,
929
- documentationCoverage: documentation?.coverageScore
930
- };
931
- mcpDebugStderr(chalk.blue('[INFO]'), `Generated paginated code map: ${paginatedResult.summary.stats.totalFiles} files, ${paginatedResult.summary.totalSections} sections`);
932
603
  }
933
- catch (error) {
934
- console.error(chalk.yellow('[WARN]'), 'Failed to generate code map:', error);
935
- codeMapResult = {
936
- success: false,
937
- error: error instanceof Error ? error.message : 'Failed to generate code map'
938
- };
604
+ catch {
605
+ // best-effort
939
606
  }
607
+ const paginatedResult = await mapGenerator.generatePaginatedCodeMap(auditPath, {
608
+ ...mapOptions,
609
+ includeDocumentation: !!documentation,
610
+ });
611
+ codeMapResult = {
612
+ success: true,
613
+ mapId: paginatedResult.mapId,
614
+ summary: paginatedResult.summary,
615
+ quickPreview: paginatedResult.quickPreview,
616
+ sections: paginatedResult.summary.sectionsAvailable,
617
+ documentationCoverage: documentation?.coverageScore,
618
+ };
619
+ }
620
+ catch (error) {
621
+ codeMapResult = {
622
+ success: false,
623
+ error: error instanceof Error ? error.message : 'Failed to generate code map',
624
+ };
940
625
  }
941
- result = {
942
- healthScore,
943
- threshold,
944
- passed: healthScore >= threshold,
945
- status: healthScore >= threshold ? 'healthy' : 'needs-attention',
946
- metrics: {
947
- filesAnalyzed: auditResult.metadata.filesAnalyzed,
948
- totalViolations: auditResult.summary.totalViolations,
949
- criticalViolations: auditResult.summary.criticalIssues,
950
- warningViolations: auditResult.summary.warnings,
951
- },
952
- recommendation: getHealthRecommendation(healthScore, auditResult),
953
- ...(indexingResult && { functionIndexing: indexingResult }),
954
- ...(codeMapResult && { codeMap: codeMapResult }),
955
- };
956
- break;
957
626
  }
958
- case 'search_code': {
627
+ return {
628
+ healthScore,
629
+ threshold,
630
+ passed: healthScore >= threshold,
631
+ status: healthScore >= threshold ? 'healthy' : 'needs-attention',
632
+ metrics: {
633
+ filesAnalyzed: auditResult.metadata.filesAnalyzed,
634
+ totalViolations: auditResult.summary.totalViolations,
635
+ criticalViolations: auditResult.summary.criticalIssues,
636
+ warningViolations: auditResult.summary.warnings,
637
+ },
638
+ recommendation: getHealthRecommendation(healthScore, auditResult),
639
+ ...(indexingResult && { functionIndexing: indexingResult }),
640
+ ...(codeMapResult && { codeMap: codeMapResult }),
641
+ };
642
+ },
643
+ },
644
+ ];
645
+ registry.register({
646
+ name: 'audit',
647
+ description: 'Run code quality audits (synchronous or background), check status, fetch paginated results, and get health checks. Actions: run (sync audit, returns full results), start (background job), status (poll a job), results (paginated violations), health (score + code map).',
648
+ actions: auditActions,
649
+ });
650
+ // ── search ─────────────────────────────────────────────────────────────────
651
+ registry.register({
652
+ name: 'search',
653
+ description: 'Search indexed functions and components. Actions: query (natural language + operators like entity:, component:, hook:, dep:, calls:, lang:, complexity:, exported:, jsdoc:, file:, unused-imports, name:), definition (exact function/component lookup).',
654
+ actions: [
655
+ {
656
+ name: 'query',
657
+ description: 'Search indexed functions with natural language queries and filter operators.',
658
+ parameters: [
659
+ {
660
+ name: 'query',
661
+ type: 'string',
662
+ required: true,
663
+ description: 'Search query with natural language and/or operators. Examples: "Button component:functional", "calls:validateUser", "unused-imports".',
664
+ },
665
+ {
666
+ name: 'filters',
667
+ type: 'object',
668
+ required: false,
669
+ description: 'Optional filters (language, filePath, dependencies, componentType, entityType, searchMode).',
670
+ },
671
+ {
672
+ name: 'limit',
673
+ type: 'number',
674
+ required: false,
675
+ description: 'Maximum results to return.',
676
+ default: 50,
677
+ },
678
+ {
679
+ name: 'offset',
680
+ type: 'number',
681
+ required: false,
682
+ description: 'Offset for pagination.',
683
+ default: 0,
684
+ },
685
+ ],
686
+ handler: async (args) => {
959
687
  const query = args.query;
960
688
  const filters = args.filters;
961
689
  const limit = args.limit || 50;
962
690
  const offset = args.offset || 0;
963
- // Allow empty query to return all results
964
- if (query !== undefined && typeof query !== 'string') {
691
+ if (query !== undefined && typeof query !== 'string')
965
692
  throw new Error('query must be a string');
966
- }
967
- result = await searchFunctions({
968
- query,
969
- filters,
970
- limit,
971
- offset
972
- });
973
- break;
974
- }
975
- case 'find_definition': {
693
+ return searchFunctions({ query, filters, limit, offset });
694
+ },
695
+ },
696
+ {
697
+ name: 'definition',
698
+ description: 'Find the exact definition of a specific function or React component.',
699
+ parameters: [
700
+ {
701
+ name: 'name',
702
+ type: 'string',
703
+ required: true,
704
+ description: 'Function or component name to find.',
705
+ },
706
+ {
707
+ name: 'filePath',
708
+ type: 'string',
709
+ required: false,
710
+ description: 'Optional file path to narrow search.',
711
+ },
712
+ ],
713
+ handler: async (args) => {
976
714
  const name = args.name;
977
715
  const filePath = args.filePath;
978
- if (!name || typeof name !== 'string') {
716
+ if (!name || typeof name !== 'string')
979
717
  throw new Error('name must be a non-empty string');
980
- }
981
718
  const definition = await findDefinition(name, filePath);
982
- result = definition || { error: 'Function not found' };
983
- break;
984
- }
985
- case 'generate_ai_config': {
719
+ return definition || { error: 'Function not found' };
720
+ },
721
+ },
722
+ ],
723
+ });
724
+ // ── index ──────────────────────────────────────────────────────────────────
725
+ registry.register({
726
+ name: 'index',
727
+ description: 'Manage the code index. Actions: sync (update from files with optional auto-whitelist), cleanup (remove deleted files), reset (clear analysis data, preserve tasks/config/whitelist), status (index statistics).',
728
+ actions: [
729
+ {
730
+ name: 'sync',
731
+ description: 'Synchronize index from filesystem. Discovers TypeScript/JavaScript functions and updates the index.',
732
+ parameters: [
733
+ {
734
+ name: 'path',
735
+ type: 'string',
736
+ required: false,
737
+ description: 'Optional specific path to sync. Omit for full deep sync.',
738
+ },
739
+ ],
740
+ handler: async (args) => {
741
+ const targetPath = args.path;
742
+ const db = CodeIndexDB.getInstance();
743
+ await db.initialize();
744
+ if (targetPath) {
745
+ const syncResult = await db.synchronizeFile(path.resolve(targetPath));
746
+ return {
747
+ mode: 'sync',
748
+ success: true,
749
+ path: targetPath,
750
+ ...(syncResult || { message: 'File not found' }),
751
+ };
752
+ }
753
+ const syncResult = await db.deepSync();
754
+ const result = {
755
+ mode: 'sync',
756
+ success: true,
757
+ syncedFiles: syncResult.syncedFiles,
758
+ addedFunctions: syncResult.addedFunctions,
759
+ updatedFunctions: syncResult.updatedFunctions,
760
+ removedFunctions: syncResult.removedFunctions,
761
+ errors: syncResult.errors,
762
+ message: `Synced ${syncResult.syncedFiles} files: ${syncResult.addedFunctions} added, ${syncResult.updatedFunctions} updated, ${syncResult.removedFunctions} removed`,
763
+ };
764
+ // Auto-detect and populate whitelist entries (from standalone)
765
+ try {
766
+ const { WhitelistService } = await import('./services/whitelistService.js');
767
+ const whitelistService = WhitelistService.getInstance();
768
+ const whitelistResult = await whitelistService.whitelistAllDependencies(targetPath || process.cwd());
769
+ if (whitelistResult.added > 0) {
770
+ result.whitelistAdded = whitelistResult.added;
771
+ result.whitelistMessage = `Auto-added ${whitelistResult.added} whitelist entries`;
772
+ }
773
+ }
774
+ catch {
775
+ // whitelist detection is best-effort
776
+ }
777
+ return result;
778
+ },
779
+ },
780
+ {
781
+ name: 'cleanup',
782
+ description: 'Remove index entries for files that no longer exist on disk.',
783
+ parameters: [],
784
+ handler: async () => {
785
+ const db = CodeIndexDB.getInstance();
786
+ await db.initialize();
787
+ const cleanupResult = await db.bulkCleanup();
788
+ return {
789
+ mode: 'cleanup',
790
+ success: true,
791
+ scannedFiles: cleanupResult.scannedCount,
792
+ removedEntries: cleanupResult.removedCount,
793
+ removedFiles: cleanupResult.removedFiles,
794
+ errors: cleanupResult.errors,
795
+ message: `Cleaned up ${cleanupResult.removedCount} entries from ${cleanupResult.removedFiles.length} deleted files`,
796
+ };
797
+ },
798
+ },
799
+ {
800
+ name: 'reset',
801
+ description: 'Clear all analysis-derived data (functions, search index, cached audits, code maps, schemas). Project tasks, whitelist, and analyzer configs are preserved.',
802
+ parameters: [],
803
+ handler: async () => {
804
+ const db = CodeIndexDB.getInstance();
805
+ await db.initialize();
806
+ await db.clearIndex();
807
+ return {
808
+ mode: 'reset',
809
+ success: true,
810
+ message: 'Analysis-derived data cleared (functions, search index, cached audits, code maps, schemas). Project tasks, whitelist, and analyzer configs were preserved.',
811
+ };
812
+ },
813
+ },
814
+ {
815
+ name: 'status',
816
+ description: 'Get index statistics (function count, file count, last sync time).',
817
+ parameters: [],
818
+ handler: async () => {
819
+ const db = CodeIndexDB.getInstance();
820
+ await db.initialize();
821
+ const funcs = await db.getAllFunctions();
822
+ return {
823
+ success: true,
824
+ functionCount: Array.isArray(funcs) ? funcs.length : 0,
825
+ };
826
+ },
827
+ },
828
+ ],
829
+ });
830
+ // ── config ─────────────────────────────────────────────────────────────────
831
+ registry.register({
832
+ name: 'config',
833
+ description: 'Manage analyzer configurations, generate AI tool configs, manage whitelists, and manage invariant rules. Actions: get, set, reset (analyzer configs), generate (AI tool config files), whitelist_list, whitelist_add, whitelist_update, whitelist_detect, rules_list, rules_check.',
834
+ actions: [
835
+ {
836
+ name: 'get',
837
+ description: 'Get current configuration for an analyzer (or all).',
838
+ parameters: [
839
+ {
840
+ name: 'analyzerName',
841
+ type: 'string',
842
+ required: false,
843
+ description: 'Specific analyzer name, or omit to get all configs.',
844
+ },
845
+ {
846
+ name: 'projectPath',
847
+ type: 'string',
848
+ required: false,
849
+ description: 'Optional project path for project-specific config.',
850
+ },
851
+ ],
852
+ handler: async (args) => {
853
+ const analyzerName = args.analyzerName;
854
+ const projectPath = args.projectPath;
855
+ const db = CodeIndexDB.getInstance();
856
+ await db.initialize();
857
+ if (analyzerName) {
858
+ const config = await db.getAnalyzerConfig(analyzerName, projectPath);
859
+ return {
860
+ success: true,
861
+ analyzer: analyzerName,
862
+ config: config || null,
863
+ scope: projectPath ? 'project' : 'global',
864
+ message: config ? 'Configuration found' : 'No custom configuration found, using defaults',
865
+ };
866
+ }
867
+ const configs = await db.getAllAnalyzerConfigs(projectPath);
868
+ return {
869
+ success: true,
870
+ configs,
871
+ scope: projectPath ? 'project' : 'global',
872
+ message: `Found ${Object.keys(configs).length} analyzer configurations`,
873
+ };
874
+ },
875
+ },
876
+ {
877
+ name: 'set',
878
+ description: 'Set or update analyzer configuration that persists across audit runs.',
879
+ parameters: [
880
+ {
881
+ name: 'analyzerName',
882
+ type: 'string',
883
+ required: true,
884
+ description: 'The analyzer to configure (solid, dry, security, etc.).',
885
+ },
886
+ {
887
+ name: 'config',
888
+ type: 'object',
889
+ required: true,
890
+ description: 'Configuration object for the analyzer (e.g., thresholds, rules).',
891
+ },
892
+ {
893
+ name: 'projectPath',
894
+ type: 'string',
895
+ required: false,
896
+ description: 'Optional project path for project-specific config.',
897
+ },
898
+ ],
899
+ handler: async (args) => {
900
+ const analyzerName = args.analyzerName;
901
+ const config = args.config;
902
+ const projectPath = args.projectPath;
903
+ const db = CodeIndexDB.getInstance();
904
+ await db.initialize();
905
+ await db.storeAnalyzerConfig(analyzerName, config, {
906
+ projectPath,
907
+ isGlobal: !projectPath,
908
+ });
909
+ return {
910
+ success: true,
911
+ message: `Configuration for ${analyzerName} analyzer has been saved${projectPath ? ` for project ${projectPath}` : ' globally'}`,
912
+ analyzer: analyzerName,
913
+ scope: projectPath ? 'project' : 'global',
914
+ config,
915
+ };
916
+ },
917
+ },
918
+ {
919
+ name: 'reset',
920
+ description: 'Reset analyzer configuration to defaults.',
921
+ parameters: [
922
+ {
923
+ name: 'analyzerName',
924
+ type: 'string',
925
+ required: false,
926
+ description: 'Specific analyzer to reset, or omit to reset all.',
927
+ },
928
+ {
929
+ name: 'projectPath',
930
+ type: 'string',
931
+ required: false,
932
+ description: 'Optional project path to reset only project-specific config.',
933
+ },
934
+ ],
935
+ handler: async (args) => {
936
+ const analyzerName = args.analyzerName;
937
+ const projectPath = args.projectPath;
938
+ const db = CodeIndexDB.getInstance();
939
+ await db.initialize();
940
+ if (analyzerName) {
941
+ const deleted = await db.deleteAnalyzerConfig(analyzerName, {
942
+ projectPath,
943
+ isGlobal: !projectPath,
944
+ });
945
+ return {
946
+ success: deleted,
947
+ message: deleted
948
+ ? `Configuration for ${analyzerName} analyzer has been reset${projectPath ? ` for project ${projectPath}` : ' globally'}`
949
+ : `No configuration found for ${analyzerName} analyzer`,
950
+ analyzer: analyzerName,
951
+ scope: projectPath ? 'project' : 'global',
952
+ };
953
+ }
954
+ await db.resetAnalyzerConfigs(projectPath);
955
+ return {
956
+ success: true,
957
+ message: projectPath
958
+ ? `All project-specific configurations for ${projectPath} have been reset`
959
+ : 'All analyzer configurations have been reset to defaults',
960
+ scope: projectPath ? 'project' : 'global',
961
+ };
962
+ },
963
+ },
964
+ {
965
+ name: 'generate',
966
+ description: 'Generate configuration files for AI coding assistants (Cursor, Claude, Copilot, etc.).',
967
+ parameters: [
968
+ {
969
+ name: 'tools',
970
+ type: 'array',
971
+ required: true,
972
+ description: 'AI tools to configure (cursor, continue, copilot, claude, zed, windsurf, cody, aider, cline, pearai).',
973
+ },
974
+ {
975
+ name: 'outputDir',
976
+ type: 'string',
977
+ required: false,
978
+ description: 'Output directory for configuration files.',
979
+ default: '.',
980
+ },
981
+ {
982
+ name: 'serverUrl',
983
+ type: 'string',
984
+ required: false,
985
+ description: 'MCP server URL (default: auto-detected).',
986
+ default: DEFAULT_SERVER_URL,
987
+ },
988
+ {
989
+ name: 'overwrite',
990
+ type: 'boolean',
991
+ required: false,
992
+ description: 'Overwrite existing files (default: false).',
993
+ default: false,
994
+ },
995
+ ],
996
+ handler: async (args) => {
986
997
  const tools = args.tools;
987
998
  const serverUrl = args.serverUrl || DEFAULT_SERVER_URL;
988
999
  const outputDir = args.outputDir || '.';
989
1000
  const overwrite = args.overwrite || false;
990
- if (!Array.isArray(tools) || tools.length === 0) {
1001
+ if (!Array.isArray(tools) || tools.length === 0)
991
1002
  throw new Error('tools parameter must be a non-empty array');
992
- }
993
1003
  const factory = new ConfigGeneratorFactory(serverUrl);
994
1004
  const generatedFiles = [];
995
1005
  const errors = [];
@@ -1002,25 +1012,21 @@ async function startMcpServer() {
1002
1012
  }
1003
1013
  const config = generator.generateConfig();
1004
1014
  const outputPath = path.resolve(outputDir, config.filename);
1005
- // Check if file exists
1006
1015
  let fileExists = false;
1007
1016
  try {
1008
1017
  await fs.access(outputPath);
1009
1018
  fileExists = true;
1010
1019
  }
1011
1020
  catch {
1012
- // File doesn't exist
1021
+ // doesn't exist
1013
1022
  }
1014
1023
  if (fileExists && !overwrite) {
1015
1024
  errors.push(`File already exists: ${config.filename} (use overwrite: true to replace)`);
1016
1025
  continue;
1017
1026
  }
1018
- // Ensure directory exists
1019
1027
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
1020
- // Write main config file
1021
1028
  await fs.writeFile(outputPath, config.content);
1022
1029
  generatedFiles.push(config.filename);
1023
- // Write additional files if any
1024
1030
  if (config.additionalFiles) {
1025
1031
  for (const additionalFile of config.additionalFiles) {
1026
1032
  const additionalPath = path.resolve(outputDir, additionalFile.filename);
@@ -1034,276 +1040,516 @@ async function startMcpServer() {
1034
1040
  errors.push(`Failed to generate config for ${tool}: ${error instanceof Error ? error.message : 'Unknown error'}`);
1035
1041
  }
1036
1042
  }
1037
- result = {
1043
+ return {
1038
1044
  success: errors.length === 0,
1039
1045
  generatedFiles,
1040
1046
  errors: errors.length > 0 ? errors : undefined,
1041
1047
  totalRequested: tools.length,
1042
- totalGenerated: generatedFiles.length
1048
+ totalGenerated: generatedFiles.length,
1043
1049
  };
1044
- break;
1045
- }
1046
- case 'get_workflow_guide': {
1047
- const scenario = args.scenario;
1048
- const { getWorkflowGuide, getWorkflowTips } = await import('./mcp-tools/workflowGuide.js');
1049
- try {
1050
- const workflows = getWorkflowGuide(scenario);
1051
- const tips = getWorkflowTips();
1052
- result = {
1053
- success: true,
1054
- ...(scenario ? { workflow: workflows } : { workflows }),
1055
- tips
1056
- };
1057
- }
1058
- catch (error) {
1059
- result = {
1060
- success: false,
1061
- error: error instanceof Error ? error.message : 'Unknown error',
1062
- availableScenarios: ['initial-setup', 'react-development', 'code-review', 'find-patterns', 'maintenance', 'analyzer-configuration']
1063
- };
1064
- }
1065
- break;
1066
- }
1067
- case 'sync_index': {
1068
- const mode = args.mode || 'sync';
1069
- const targetPath = args.path;
1070
- const db = CodeIndexDB.getInstance();
1071
- await db.initialize();
1072
- switch (mode) {
1073
- case 'cleanup': {
1074
- const cleanupResult = await db.bulkCleanup();
1075
- result = {
1076
- mode: 'cleanup',
1077
- success: true,
1078
- scannedFiles: cleanupResult.scannedCount,
1079
- removedEntries: cleanupResult.removedCount,
1080
- removedFiles: cleanupResult.removedFiles,
1081
- errors: cleanupResult.errors,
1082
- message: `Cleaned up ${cleanupResult.removedCount} entries from ${cleanupResult.removedFiles.length} deleted files`
1083
- };
1084
- break;
1085
- }
1086
- case 'reset': {
1087
- await db.clearIndex();
1088
- result = {
1089
- mode: 'reset',
1090
- success: true,
1091
- message: 'Analysis-derived data cleared (functions, search index, cached audits, code maps, schemas). Project tasks, whitelist, and analyzer configs were preserved.'
1092
- };
1093
- break;
1094
- }
1095
- case 'sync':
1096
- default: {
1097
- if (targetPath) {
1098
- // Sync specific file
1099
- const syncResult = await db.synchronizeFile(path.resolve(targetPath));
1100
- result = {
1101
- mode: 'sync',
1102
- success: true,
1103
- path: targetPath,
1104
- ...(syncResult || { message: 'File not found' })
1105
- };
1106
- }
1107
- else {
1108
- // Deep sync all files
1109
- const syncResult = await db.deepSync();
1110
- result = {
1111
- mode: 'sync',
1112
- success: true,
1113
- syncedFiles: syncResult.syncedFiles,
1114
- addedFunctions: syncResult.addedFunctions,
1115
- updatedFunctions: syncResult.updatedFunctions,
1116
- removedFunctions: syncResult.removedFunctions,
1117
- errors: syncResult.errors,
1118
- message: `Synced ${syncResult.syncedFiles} files: ${syncResult.addedFunctions} added, ${syncResult.updatedFunctions} updated, ${syncResult.removedFunctions} removed`
1119
- };
1120
- }
1121
- break;
1122
- }
1123
- }
1124
- break;
1125
- }
1126
- case 'set_analyzer_config': {
1127
- const analyzerName = args.analyzerName;
1128
- const config = args.config;
1129
- const projectPath = args.projectPath;
1130
- const db = CodeIndexDB.getInstance();
1131
- await db.initialize();
1050
+ },
1051
+ },
1052
+ {
1053
+ name: 'whitelist_list',
1054
+ description: 'Get current whitelist entries for dependency and class instantiation checks.',
1055
+ parameters: [
1056
+ {
1057
+ name: 'type',
1058
+ type: 'string',
1059
+ required: false,
1060
+ description: 'Filter by type: platform-api, framework-class, project-dep, shared-library, node-builtin.',
1061
+ enum: ['platform-api', 'framework-class', 'project-dep', 'shared-library', 'node-builtin'],
1062
+ },
1063
+ {
1064
+ name: 'status',
1065
+ type: 'string',
1066
+ required: false,
1067
+ description: 'Filter by status: active, pending, rejected, disabled.',
1068
+ enum: ['active', 'pending', 'rejected', 'disabled'],
1069
+ },
1070
+ ],
1071
+ handler: async (args) => {
1072
+ const { handleWhitelistGet } = await import('./mcp-tools/whitelistTools.js');
1073
+ return handleWhitelistGet(args);
1074
+ },
1075
+ },
1076
+ {
1077
+ name: 'whitelist_add',
1078
+ description: 'Add a new entry to the whitelist.',
1079
+ parameters: [
1080
+ {
1081
+ name: 'name',
1082
+ type: 'string',
1083
+ required: true,
1084
+ description: 'Class name or import path to whitelist.',
1085
+ },
1086
+ {
1087
+ name: 'type',
1088
+ type: 'string',
1089
+ required: true,
1090
+ description: 'Type of whitelist entry.',
1091
+ enum: ['platform-api', 'framework-class', 'project-dep', 'shared-library', 'node-builtin'],
1092
+ },
1093
+ {
1094
+ name: 'description',
1095
+ type: 'string',
1096
+ required: false,
1097
+ description: 'Explanation of why this is whitelisted.',
1098
+ },
1099
+ {
1100
+ name: 'patterns',
1101
+ type: 'array',
1102
+ required: false,
1103
+ description: 'Additional patterns to match (e.g., ["fs/*", "node:fs"]).',
1104
+ },
1105
+ ],
1106
+ handler: async (args) => {
1107
+ const { handleWhitelistAdd } = await import('./mcp-tools/whitelistTools.js');
1108
+ return handleWhitelistAdd(args);
1109
+ },
1110
+ },
1111
+ {
1112
+ name: 'whitelist_update',
1113
+ description: 'Update the status of a whitelist entry.',
1114
+ parameters: [
1115
+ {
1116
+ name: 'name',
1117
+ type: 'string',
1118
+ required: true,
1119
+ description: 'Name of the whitelist entry to update.',
1120
+ },
1121
+ {
1122
+ name: 'status',
1123
+ type: 'string',
1124
+ required: true,
1125
+ description: 'New status for the entry.',
1126
+ enum: ['active', 'pending', 'rejected', 'disabled'],
1127
+ },
1128
+ ],
1129
+ handler: async (args) => {
1130
+ const { handleWhitelistUpdateStatus } = await import('./mcp-tools/whitelistTools.js');
1131
+ return handleWhitelistUpdateStatus(args);
1132
+ },
1133
+ },
1134
+ {
1135
+ name: 'whitelist_detect',
1136
+ description: 'Detect potential whitelist candidates from package.json and usage patterns.',
1137
+ parameters: [
1138
+ {
1139
+ name: 'path',
1140
+ type: 'string',
1141
+ required: false,
1142
+ description: 'Project path to analyze (defaults to current directory).',
1143
+ default: process.cwd(),
1144
+ },
1145
+ {
1146
+ name: 'includePackageJson',
1147
+ type: 'boolean',
1148
+ required: false,
1149
+ description: 'Include dependencies from package.json.',
1150
+ default: true,
1151
+ },
1152
+ {
1153
+ name: 'autoPopulate',
1154
+ type: 'boolean',
1155
+ required: false,
1156
+ description: 'Automatically add high-confidence entries.',
1157
+ default: false,
1158
+ },
1159
+ ],
1160
+ handler: async (args) => {
1161
+ const { handleWhitelistDetect } = await import('./mcp-tools/whitelistTools.js');
1162
+ return handleWhitelistDetect(args);
1163
+ },
1164
+ },
1165
+ {
1166
+ name: 'rules_list',
1167
+ description: 'List all configured invariant rules from .codeauditor.json. Shows rule IDs, kinds, severity, and messages.',
1168
+ parameters: [
1169
+ {
1170
+ name: 'configPath',
1171
+ type: 'string',
1172
+ required: false,
1173
+ description: 'Path to .codeauditor.json (defaults to auto-detect in current working tree).',
1174
+ },
1175
+ ],
1176
+ handler: async (args) => {
1177
+ const { readFileSync } = await import('fs');
1178
+ const path = await import('path');
1179
+ const configPath = args.configPath ||
1180
+ path.join(process.cwd(), '.codeauditor.json');
1132
1181
  try {
1133
- await db.storeAnalyzerConfig(analyzerName, config, {
1134
- projectPath,
1135
- isGlobal: !projectPath
1136
- });
1137
- result = {
1138
- success: true,
1139
- message: `Configuration for ${analyzerName} analyzer has been saved${projectPath ? ` for project ${projectPath}` : ' globally'}`,
1140
- analyzer: analyzerName,
1141
- scope: projectPath ? 'project' : 'global',
1142
- config
1182
+ const raw = readFileSync(configPath, 'utf-8');
1183
+ const config = JSON.parse(raw);
1184
+ const rules = config?.rules ?? [];
1185
+ return {
1186
+ rules: rules.map((r) => ({
1187
+ id: r.id,
1188
+ kind: r.kind,
1189
+ severity: r.severity,
1190
+ message: r.message || null,
1191
+ })),
1192
+ count: rules.length,
1193
+ configPath,
1143
1194
  };
1144
1195
  }
1145
- catch (error) {
1146
- result = {
1147
- success: false,
1148
- error: error instanceof Error ? error.message : 'Failed to save configuration'
1196
+ catch (err) {
1197
+ return {
1198
+ error: `Failed to read rules: ${err.message}`,
1199
+ configPath,
1200
+ rules: [],
1201
+ count: 0,
1149
1202
  };
1150
1203
  }
1151
- break;
1152
- }
1153
- case 'get_analyzer_config': {
1154
- const analyzerName = args.analyzerName;
1155
- const projectPath = args.projectPath;
1156
- const db = CodeIndexDB.getInstance();
1157
- await db.initialize();
1204
+ },
1205
+ },
1206
+ {
1207
+ name: 'rules_check',
1208
+ description: 'Validate the current .codeauditor.json rules (schema, duplicate IDs, valid globs/regex, mutual exclusivity). Returns any config errors.',
1209
+ parameters: [
1210
+ {
1211
+ name: 'configPath',
1212
+ type: 'string',
1213
+ required: false,
1214
+ description: 'Path to .codeauditor.json (defaults to auto-detect in current working tree).',
1215
+ },
1216
+ ],
1217
+ handler: async (args) => {
1218
+ const { validateRulesConfig } = await import('./invariants/ruleValidator.js');
1219
+ const { readFileSync } = await import('fs');
1220
+ const path = await import('path');
1221
+ const configPath = args.configPath ||
1222
+ path.join(process.cwd(), '.codeauditor.json');
1158
1223
  try {
1159
- if (analyzerName) {
1160
- const config = await db.getAnalyzerConfig(analyzerName, projectPath);
1161
- result = {
1162
- success: true,
1163
- analyzer: analyzerName,
1164
- config: config || null,
1165
- scope: projectPath ? 'project' : 'global',
1166
- message: config ? 'Configuration found' : 'No custom configuration found, using defaults'
1167
- };
1168
- }
1169
- else {
1170
- const configs = await db.getAllAnalyzerConfigs(projectPath);
1171
- result = {
1172
- success: true,
1173
- configs,
1174
- scope: projectPath ? 'project' : 'global',
1175
- message: `Found ${Object.keys(configs).length} analyzer configurations`
1176
- };
1177
- }
1178
- }
1179
- catch (error) {
1180
- result = {
1181
- success: false,
1182
- error: error instanceof Error ? error.message : 'Failed to get configuration'
1224
+ const raw = readFileSync(configPath, 'utf-8');
1225
+ const config = JSON.parse(raw);
1226
+ const rulesArray = config?.rules;
1227
+ const errors = validateRulesConfig({ rules: rulesArray ?? [] });
1228
+ return {
1229
+ valid: errors.length === 0,
1230
+ errors: errors.map(e => ({ ruleId: e.ruleId || null, message: e.message })),
1231
+ configPath,
1183
1232
  };
1184
1233
  }
1185
- break;
1186
- }
1187
- case 'reset_analyzer_config': {
1188
- const analyzerName = args.analyzerName;
1189
- const projectPath = args.projectPath;
1190
- const db = CodeIndexDB.getInstance();
1191
- await db.initialize();
1192
- try {
1193
- if (analyzerName) {
1194
- const deleted = await db.deleteAnalyzerConfig(analyzerName, {
1195
- projectPath,
1196
- isGlobal: !projectPath
1197
- });
1198
- result = {
1199
- success: deleted,
1200
- message: deleted
1201
- ? `Configuration for ${analyzerName} analyzer has been reset${projectPath ? ` for project ${projectPath}` : ' globally'}`
1202
- : `No configuration found for ${analyzerName} analyzer`,
1203
- analyzer: analyzerName,
1204
- scope: projectPath ? 'project' : 'global'
1205
- };
1206
- }
1207
- else {
1208
- await db.resetAnalyzerConfigs(projectPath);
1209
- result = {
1210
- success: true,
1211
- message: projectPath
1212
- ? `All project-specific configurations for ${projectPath} have been reset`
1213
- : 'All analyzer configurations have been reset to defaults',
1214
- scope: projectPath ? 'project' : 'global'
1215
- };
1216
- }
1217
- }
1218
- catch (error) {
1219
- result = {
1220
- success: false,
1221
- error: error instanceof Error ? error.message : 'Failed to reset configuration'
1234
+ catch (err) {
1235
+ return {
1236
+ valid: false,
1237
+ errors: [{ message: `Failed to read/parse config: ${err.message}` }],
1238
+ configPath,
1222
1239
  };
1223
1240
  }
1224
- break;
1225
- }
1226
- case 'get_code_map_section': {
1241
+ },
1242
+ },
1243
+ ],
1244
+ });
1245
+ // ── code_map ───────────────────────────────────────────────────────────────
1246
+ registry.register({
1247
+ name: 'code_map',
1248
+ description: 'Retrieve sections of previously generated code maps. Actions: get (retrieve a specific section), list (list all available sections for a map).',
1249
+ actions: [
1250
+ {
1251
+ name: 'get',
1252
+ description: 'Retrieve a specific section of a previously generated code map.',
1253
+ parameters: [
1254
+ {
1255
+ name: 'mapId',
1256
+ type: 'string',
1257
+ required: true,
1258
+ description: 'The map ID returned from a previous audit with code map generation.',
1259
+ },
1260
+ {
1261
+ name: 'sectionType',
1262
+ type: 'string',
1263
+ required: true,
1264
+ description: 'The section type to retrieve (e.g., overview, files, dependencies, documentation).',
1265
+ },
1266
+ ],
1267
+ handler: async (args) => {
1227
1268
  const mapId = args.mapId;
1228
1269
  const sectionType = args.sectionType;
1229
- if (!mapId || !sectionType) {
1230
- result = {
1231
- success: false,
1232
- error: 'Both mapId and sectionType are required'
1233
- };
1234
- break;
1235
- }
1236
- try {
1237
- const db = await getDatabase();
1238
- const section = await db.getCodeMapSection(mapId, sectionType);
1239
- if (section) {
1240
- result = {
1241
- success: true,
1242
- mapId,
1243
- sectionType,
1244
- content: section.content,
1245
- metadata: section.metadata
1246
- };
1247
- }
1248
- else {
1249
- result = {
1250
- success: false,
1251
- error: `Section '${sectionType}' not found for map '${mapId}'`
1252
- };
1253
- }
1254
- }
1255
- catch (error) {
1256
- result = {
1257
- success: false,
1258
- error: error instanceof Error ? error.message : 'Failed to retrieve code map section'
1259
- };
1270
+ if (!mapId || !sectionType)
1271
+ return { success: false, error: 'Both mapId and sectionType are required' };
1272
+ const db = await getDatabase();
1273
+ const section = await db.getCodeMapSection(mapId, sectionType);
1274
+ if (section) {
1275
+ return { success: true, mapId, sectionType, content: section.content, metadata: section.metadata };
1260
1276
  }
1261
- break;
1262
- }
1263
- case 'list_code_map_sections': {
1277
+ return { success: false, error: `Section '${sectionType}' not found for map '${mapId}'` };
1278
+ },
1279
+ },
1280
+ {
1281
+ name: 'list',
1282
+ description: 'List all available sections for a code map.',
1283
+ parameters: [
1284
+ {
1285
+ name: 'mapId',
1286
+ type: 'string',
1287
+ required: true,
1288
+ description: 'The map ID returned from a previous audit.',
1289
+ },
1290
+ ],
1291
+ handler: async (args) => {
1264
1292
  const mapId = args.mapId;
1265
- if (!mapId) {
1266
- result = {
1267
- success: false,
1268
- error: 'mapId is required'
1269
- };
1270
- break;
1271
- }
1293
+ if (!mapId)
1294
+ return { success: false, error: 'mapId is required' };
1295
+ const db = await getDatabase();
1296
+ const sections = await db.listCodeMapSections(mapId);
1297
+ return { success: true, mapId, sections, totalSections: sections.length };
1298
+ },
1299
+ },
1300
+ ],
1301
+ });
1302
+ // ── tasks ──────────────────────────────────────────────────────────────────
1303
+ registry.register({
1304
+ name: 'tasks',
1305
+ description: 'Manage a persistent per-project task queue. Tasks survive index resets. Actions: create, list, list_tree, get, update, complete, delete, from_audit (create tasks from audit violations with deduplication).',
1306
+ actions: [
1307
+ {
1308
+ name: 'create',
1309
+ description: 'Create a new task.',
1310
+ parameters: [
1311
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1312
+ { name: 'title', type: 'string', required: true, description: 'Task title.' },
1313
+ { name: 'description', type: 'string', required: false, description: 'Longer description.' },
1314
+ { name: 'status', type: 'string', required: false, description: 'Initial status.', enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'] },
1315
+ { name: 'priority', type: 'string', required: false, description: 'Priority.', enum: ['low', 'medium', 'high'] },
1316
+ { name: 'labels', type: 'array', required: false, description: 'String tags.' },
1317
+ { name: 'metadata', type: 'object', required: false, description: 'Arbitrary JSON.' },
1318
+ { name: 'parentTaskId', type: 'string', required: false, description: 'Optional parent.' },
1319
+ { name: 'source', type: 'string', required: false, description: 'Provenance.', enum: ['manual', 'audit', 'mcp'] },
1320
+ { name: 'blockedBy', type: 'array', required: false, description: 'Blocking task IDs.' },
1321
+ { name: 'dueAt', type: 'string', required: false, description: 'ISO 8601 due datetime.' },
1322
+ { name: 'sortOrder', type: 'number', required: false, description: 'Sort position (default 0).' },
1323
+ { name: 'relatedFiles', type: 'array', required: false, description: 'File paths.' },
1324
+ { name: 'relatedSymbols', type: 'array', required: false, description: 'Symbol names.' },
1325
+ ],
1326
+ handler: async (args, signal) => {
1327
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1328
+ return handleProjectTasks({ ...args, action: 'create' }, { signal });
1329
+ },
1330
+ },
1331
+ {
1332
+ name: 'list',
1333
+ description: 'List tasks (flat). Supports filtering by status, priority, labels, source, query text search.',
1334
+ parameters: [
1335
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1336
+ { name: 'status', type: 'string', required: false, description: 'Filter by status.', enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'] },
1337
+ { name: 'priority', type: 'string', required: false, description: 'Filter by priority.', enum: ['low', 'medium', 'high'] },
1338
+ { name: 'label', type: 'string', required: false, description: 'Filter by label.' },
1339
+ { name: 'source', type: 'string', required: false, description: 'Filter by source.', enum: ['manual', 'audit', 'mcp'] },
1340
+ { name: 'query', type: 'string', required: false, description: 'Text search across title/description.' },
1341
+ { name: 'hasChildren', type: 'boolean', required: false, description: 'Filter by subtask presence.' },
1342
+ { name: 'overdueOnly', type: 'boolean', required: false, description: 'Only overdue tasks.' },
1343
+ { name: 'actionableOnly', type: 'boolean', required: false, description: 'Only open + unblocked.' },
1344
+ { name: 'limit', type: 'number', required: false, description: 'Max results (default 500).' },
1345
+ ],
1346
+ handler: async (args, signal) => {
1347
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1348
+ return handleProjectTasks({ ...args, action: 'list' }, { signal });
1349
+ },
1350
+ },
1351
+ {
1352
+ name: 'list_tree',
1353
+ description: 'List tasks as a tree (parent-child hierarchy).',
1354
+ parameters: [
1355
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1356
+ { name: 'status', type: 'string', required: false, description: 'Filter by status.', enum: ['pending', 'in_progress', 'blocked', 'done', 'cancelled'] },
1357
+ { name: 'limit', type: 'number', required: false, description: 'Max tasks (default 500).' },
1358
+ ],
1359
+ handler: async (args, signal) => {
1360
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1361
+ return handleProjectTasks({ ...args, action: 'list_tree' }, { signal });
1362
+ },
1363
+ },
1364
+ {
1365
+ name: 'get',
1366
+ description: 'Get a single task by ID.',
1367
+ parameters: [
1368
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1369
+ { name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
1370
+ ],
1371
+ handler: async (args, signal) => {
1372
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1373
+ return handleProjectTasks({ ...args, action: 'get' }, { signal });
1374
+ },
1375
+ },
1376
+ {
1377
+ name: 'update',
1378
+ description: 'Update a task with a patch of partial fields.',
1379
+ parameters: [
1380
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1381
+ { name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
1382
+ { name: 'patch', type: 'object', required: true, description: 'Partial fields to update.' },
1383
+ ],
1384
+ handler: async (args, signal) => {
1385
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1386
+ return handleProjectTasks({ ...args, action: 'update' }, { signal });
1387
+ },
1388
+ },
1389
+ {
1390
+ name: 'complete',
1391
+ description: 'Mark a task as completed.',
1392
+ parameters: [
1393
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1394
+ { name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
1395
+ ],
1396
+ handler: async (args, signal) => {
1397
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1398
+ return handleProjectTasks({ ...args, action: 'complete_task' }, { signal });
1399
+ },
1400
+ },
1401
+ {
1402
+ name: 'delete',
1403
+ description: 'Delete a task. Mode: reject (default, refuse if subtasks exist), detach (move subtasks up), cascade (delete subtree).',
1404
+ parameters: [
1405
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1406
+ { name: 'taskId', type: 'string', required: true, description: 'Task ID.' },
1407
+ { name: 'mode', type: 'string', required: false, description: 'Delete mode.', enum: ['reject', 'detach', 'cascade'] },
1408
+ ],
1409
+ handler: async (args, signal) => {
1410
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1411
+ return handleProjectTasks({ ...args, action: 'delete' }, { signal });
1412
+ },
1413
+ },
1414
+ {
1415
+ name: 'from_audit',
1416
+ description: 'Create tasks from audit violations with deduplication. Each violation gets a stable fingerprint; violations matching existing open tasks are skipped.',
1417
+ parameters: [
1418
+ { name: 'projectPath', type: 'string', required: false, description: 'Project root.' },
1419
+ { name: 'auditJobId', type: 'string', required: false, description: 'Audit job ID. Omit to use most recent completed audit.' },
1420
+ { name: 'severities', type: 'array', required: false, description: 'Severities to include (default: critical, warning).' },
1421
+ { name: 'analyzers', type: 'array', required: false, description: 'Filter violations by analyzer name.' },
1422
+ { name: 'paths', type: 'array', required: false, description: 'Filter violations by file path globs.' },
1423
+ ],
1424
+ handler: async (args, signal) => {
1425
+ const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1426
+ return handleProjectTasks({ ...args, action: 'from_audit' }, { signal });
1427
+ },
1428
+ },
1429
+ ],
1430
+ });
1431
+ // ── guide ──────────────────────────────────────────────────────────────────
1432
+ registry.register({
1433
+ name: 'guide',
1434
+ description: 'Get recommended workflows and best practices for using the code auditor tools effectively. Single action: get.',
1435
+ actions: [
1436
+ {
1437
+ name: 'get',
1438
+ description: 'Get workflow guide for a specific scenario or all scenarios.',
1439
+ parameters: [
1440
+ {
1441
+ name: 'scenario',
1442
+ type: 'string',
1443
+ required: false,
1444
+ description: 'Specific scenario: initial-setup, react-development, code-review, find-patterns, maintenance. Leave empty to see all.',
1445
+ },
1446
+ ],
1447
+ handler: async (args) => {
1448
+ const scenario = args.scenario;
1449
+ const { getWorkflowGuide, getWorkflowTips } = await import('./mcp-tools/workflowGuide.js');
1272
1450
  try {
1273
- const db = await getDatabase();
1274
- const sections = await db.listCodeMapSections(mapId);
1275
- result = {
1451
+ const workflows = getWorkflowGuide(scenario);
1452
+ const tips = getWorkflowTips();
1453
+ return {
1276
1454
  success: true,
1277
- mapId,
1278
- sections,
1279
- totalSections: sections.length
1455
+ ...(scenario ? { workflow: workflows } : { workflows }),
1456
+ tips,
1280
1457
  };
1281
1458
  }
1282
1459
  catch (error) {
1283
- result = {
1460
+ return {
1284
1461
  success: false,
1285
- error: error instanceof Error ? error.message : 'Failed to list code map sections'
1462
+ error: error instanceof Error ? error.message : 'Unknown error',
1463
+ availableScenarios: [
1464
+ 'initial-setup',
1465
+ 'react-development',
1466
+ 'code-review',
1467
+ 'find-patterns',
1468
+ 'maintenance',
1469
+ 'analyzer-configuration',
1470
+ ],
1286
1471
  };
1287
1472
  }
1288
- break;
1289
- }
1290
- case 'project_tasks': {
1291
- const { handleProjectTasks } = await import('./mcp-tools/projectTasks.js');
1292
- result = await withAbortSignal(requestSignal, 'project_tasks', () => handleProjectTasks((args || {}), {
1293
- signal: requestSignal
1294
- }));
1295
- break;
1296
- }
1297
- default:
1298
- throw new Error(`Unknown tool: ${name}`);
1473
+ },
1474
+ },
1475
+ ],
1476
+ });
1477
+ }
1478
+ // ── MCP Server ───────────────────────────────────────────────────────────────
1479
+ async function startMcpServer() {
1480
+ logMcpInfo('startup', `code-auditor-mcp ${PACKAGE_VERSION} (stdio)`, {
1481
+ pid: process.pid,
1482
+ cwd: process.cwd(),
1483
+ dataDir: process.env.CODE_AUDITOR_DATA_DIR ?? '(default)',
1484
+ debug: process.env.CODE_AUDITOR_DEBUG ?? '0',
1485
+ logFile: process.env.CODE_AUDITOR_LOG_FILE ?? '(none)',
1486
+ });
1487
+ mcpDebugStderr(chalk.blue('[DEBUG]'), `Node ${process.version} · cwd ${process.cwd()}`);
1488
+ const registry = new ToolRegistry();
1489
+ registerAllTools(registry);
1490
+ const allTools = registry.getAllTools();
1491
+ const toolCount = allTools.length;
1492
+ const server = new Server({ name: 'code-auditor', version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
1493
+ server.onerror = (error) => {
1494
+ console.error(chalk.red('[ERROR]'), 'Server error occurred:', error);
1495
+ console.error(chalk.red('[ERROR]'), 'Error stack:', error.stack);
1496
+ };
1497
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1498
+ const schemas = registry.getMCPToolSchemas();
1499
+ mcpTraceStderr(chalk.blue('[DEBUG]'), `ListTools → ${schemas.length} tools`);
1500
+ return { tools: schemas };
1501
+ });
1502
+ server.setRequestHandler(InitializeRequestSchema, async (request) => {
1503
+ mcpTraceStderr(chalk.blue('[DEBUG]'), 'initialize', JSON.stringify(request, null, 2));
1504
+ return {
1505
+ protocolVersion: '2024-11-05',
1506
+ capabilities: { tools: {} },
1507
+ serverInfo: { name: 'code-auditor', version: PACKAGE_VERSION },
1508
+ };
1509
+ });
1510
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1511
+ const { name, arguments: args = {} } = request.params;
1512
+ mcpTraceStderr(chalk.blue('[DEBUG]'), `CallTool ${name}`, JSON.stringify({
1513
+ argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
1514
+ hasArgs: !!args,
1515
+ }));
1516
+ const requestSignal = extra?.signal;
1517
+ const toolStartedAt = Date.now();
1518
+ try {
1519
+ if (name !== 'notify') {
1520
+ logMcpDebug('tool', `call ${name}`, {
1521
+ argKeys: args && typeof args === 'object' ? Object.keys(args) : [],
1522
+ pathArg: args?.path,
1523
+ cwd: process.cwd(),
1524
+ });
1525
+ }
1526
+ throwIfRequestAborted(requestSignal, `start ${name}`);
1527
+ const tool = registry.getTool(name);
1528
+ if (!tool) {
1529
+ return {
1530
+ content: [
1531
+ {
1532
+ type: 'text',
1533
+ text: serializeForMcp({
1534
+ success: false,
1535
+ error: `Unknown tool: "${name}". Available tools: ${allTools.map((t) => t.name).join(', ')}.`,
1536
+ }),
1537
+ },
1538
+ ],
1539
+ isError: true,
1540
+ };
1541
+ }
1542
+ const action = args.action;
1543
+ const result = await registry.dispatch(name, action, args, requestSignal);
1544
+ // Check if registry returned a structured error
1545
+ if (result && typeof result === 'object' && 'success' in result && result.success === false && 'validActions' in result) {
1546
+ return {
1547
+ content: [{ type: 'text', text: serializeForMcp(result) }],
1548
+ isError: true,
1549
+ };
1299
1550
  }
1300
1551
  const response = {
1301
- content: [
1302
- {
1303
- type: 'text',
1304
- text: serializeForMcp(result),
1305
- },
1306
- ],
1552
+ content: [{ type: 'text', text: serializeForMcp(result) }],
1307
1553
  };
1308
1554
  mcpTraceStderr(chalk.blue('[DEBUG]'), `Tool ${name} ok`, `responseBytes=${response.content[0].text.length}`);
1309
1555
  throwIfRequestAborted(requestSignal, `before response ${name}`);
@@ -1312,9 +1558,7 @@ async function startMcpServer() {
1312
1558
  catch (error) {
1313
1559
  if (error instanceof RequestAbortedError) {
1314
1560
  if (name !== 'audit_status') {
1315
- logMcpDebug('tool', `call ${name} aborted`, {
1316
- ms: Date.now() - toolStartedAt
1317
- });
1561
+ logMcpDebug('tool', `call ${name} aborted`, { ms: Date.now() - toolStartedAt });
1318
1562
  }
1319
1563
  throw error;
1320
1564
  }
@@ -1323,16 +1567,11 @@ async function startMcpServer() {
1323
1567
  if (name !== 'audit_status') {
1324
1568
  logMcpDebug('tool', `call ${name} failed`, {
1325
1569
  ms: Date.now() - toolStartedAt,
1326
- error: error instanceof Error ? error.message : String(error)
1570
+ error: error instanceof Error ? error.message : String(error),
1327
1571
  });
1328
1572
  }
1329
1573
  return {
1330
- content: [
1331
- {
1332
- type: 'text',
1333
- text: serializeForMcp(formatMcpToolErrorPayload(name, error)),
1334
- },
1335
- ],
1574
+ content: [{ type: 'text', text: serializeForMcp(formatMcpToolErrorPayload(name, error)) }],
1336
1575
  isError: true,
1337
1576
  };
1338
1577
  }
@@ -1343,84 +1582,23 @@ async function startMcpServer() {
1343
1582
  }
1344
1583
  });
1345
1584
  const transport = new StdioServerTransport();
1346
- // Add transport event handlers if available
1347
1585
  if (transport.onclose) {
1348
- transport.onclose = () => {
1349
- console.error(chalk.yellow('[WARN]'), 'Transport closed');
1350
- };
1586
+ transport.onclose = () => console.error(chalk.yellow('[WARN]'), 'Transport closed');
1351
1587
  }
1352
1588
  if (transport.onerror) {
1353
- transport.onerror = (error) => {
1354
- console.error(chalk.red('[ERROR]'), 'Transport error:', error);
1355
- };
1589
+ transport.onerror = (error) => console.error(chalk.red('[ERROR]'), 'Transport error:', error);
1356
1590
  }
1357
1591
  await server.connect(transport);
1358
1592
  console.error(chalk.green('✓ Code Auditor MCP Server started'));
1359
- console.error(chalk.gray(`Listening on stdio · ${tools.length} tools · ${PACKAGE_VERSION}`));
1593
+ console.error(chalk.gray(`Listening on stdio · ${toolCount} tools · ${PACKAGE_VERSION}`));
1360
1594
  mcpDebugStderr(chalk.blue('[DEBUG]'), 'Server ready', {
1361
1595
  name: 'code-auditor',
1362
1596
  transport: 'stdio',
1363
- toolCount: tools.length
1597
+ toolCount,
1364
1598
  });
1365
1599
  }
1366
- function getAllViolations(result) {
1367
- const violations = [];
1368
- for (const [analyzerName, analyzerResult] of Object.entries(result.analyzerResults)) {
1369
- for (const violation of analyzerResult.violations) {
1370
- violations.push({
1371
- ...violation,
1372
- analyzer: analyzerName,
1373
- });
1374
- }
1375
- }
1376
- return violations;
1377
- }
1378
- function calculateHealthScore(result) {
1379
- const filesAnalyzed = result.metadata?.filesAnalyzed || 1;
1380
- const critical = result.summary.criticalIssues || 0;
1381
- const warnings = result.summary.warnings || 0;
1382
- const suggestions = result.summary.suggestions || 0;
1383
- // Weight factors for different severity levels
1384
- const weights = {
1385
- critical: 10,
1386
- warning: 3,
1387
- suggestion: 0.5
1388
- };
1389
- // Calculate weighted violations per file
1390
- const weightedViolations = (critical * weights.critical) +
1391
- (warnings * weights.warning) +
1392
- (suggestions * weights.suggestion);
1393
- const violationsPerFile = weightedViolations / filesAnalyzed;
1394
- // Score calculation: 100 points minus deductions
1395
- // Each weighted violation per file reduces score
1396
- // Critical-heavy codebases drop faster than suggestion-heavy ones
1397
- let score = 100 - (violationsPerFile * 2);
1398
- return Math.max(0, Math.round(Math.min(100, score)));
1399
- }
1400
- function generateRecommendations(result) {
1401
- const recommendations = [];
1402
- if (result.summary.criticalIssues > 0) {
1403
- recommendations.push({
1404
- priority: 'high',
1405
- title: 'Fix critical violations immediately',
1406
- description: `${result.summary.criticalIssues} critical issues require immediate attention`,
1407
- });
1408
- }
1409
- // Add more recommendation logic based on patterns
1410
- return recommendations;
1411
- }
1412
- function getHealthRecommendation(score, result) {
1413
- if (score >= 90)
1414
- return 'Excellent code health!';
1415
- if (score >= 70)
1416
- return 'Good code health with room for improvement';
1417
- if (result.summary.criticalIssues > 0) {
1418
- return `Fix ${result.summary.criticalIssues} critical violations first`;
1419
- }
1420
- return 'Code health needs attention - run detailed audit';
1421
- }
1600
+ // ── Process setup ────────────────────────────────────────────────────────────
1422
1601
  registerProcessReliabilityHandlers();
1423
- // Add SIGTERM and SIGINT handlers
1424
1602
  process.on('SIGTERM', () => {
1425
1603
  console.error(chalk.yellow('[WARN]'), 'Received SIGTERM, shutting down gracefully...');
1426
1604
  process.exitCode = 0;
@@ -1429,15 +1607,32 @@ process.on('SIGINT', () => {
1429
1607
  console.error(chalk.yellow('[WARN]'), 'Received SIGINT, shutting down gracefully...');
1430
1608
  process.exitCode = 0;
1431
1609
  });
1432
- // Log when stdin/stdout events occur
1433
1610
  process.stdin.on('error', (error) => {
1434
1611
  console.error(chalk.red('[ERROR]'), 'stdin error:', error);
1435
1612
  });
1436
1613
  process.stdout.on('error', (error) => {
1437
1614
  console.error(chalk.red('[ERROR]'), 'stdout error:', error);
1438
1615
  });
1439
- startMcpServer().catch(error => {
1440
- console.error(chalk.red('[ERROR]'), 'Failed to start MCP server:', error);
1616
+ // ── Mode dispatch ────────────────────────────────────────────────────────────
1617
+ async function main() {
1618
+ await initParsers();
1619
+ if (autoIndexPath) {
1620
+ // --auto-index: sync the index and exit
1621
+ const { runAutoIndex } = await import('./mcpAutoIndex.js');
1622
+ await runAutoIndex(autoIndexPath);
1623
+ process.exit(0);
1624
+ }
1625
+ if (uiMode) {
1626
+ // --ui: start the HTTP UI server
1627
+ const { startMcpUIServer } = await import('./mcp-ui-simple.js');
1628
+ await startMcpUIServer();
1629
+ return;
1630
+ }
1631
+ // --stdio (default): start the MCP server
1632
+ await startMcpServer();
1633
+ }
1634
+ main().catch((error) => {
1635
+ console.error(chalk.red('[ERROR]'), 'Failed to start:', error);
1441
1636
  console.error(chalk.red('[ERROR]'), 'Stack:', error.stack);
1442
1637
  process.exitCode = 1;
1443
1638
  });