@mxf-dev/core 1.0.1 → 2.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 (298) hide show
  1. package/README.md +18 -0
  2. package/dist/adapters/FrameworkAdapter.js +1 -1
  3. package/dist/adapters/FrameworkAdapter.js.map +1 -1
  4. package/dist/config/ConfigManager.d.ts +9 -8
  5. package/dist/config/ConfigManager.d.ts.map +1 -1
  6. package/dist/config/ConfigManager.js +47 -17
  7. package/dist/config/ConfigManager.js.map +1 -1
  8. package/dist/config/memory-strata.config.d.ts +25 -0
  9. package/dist/config/memory-strata.config.d.ts.map +1 -0
  10. package/dist/config/memory-strata.config.js +107 -0
  11. package/dist/config/memory-strata.config.js.map +1 -0
  12. package/dist/constants/ToolNames.d.ts +1 -29
  13. package/dist/constants/ToolNames.d.ts.map +1 -1
  14. package/dist/constants/ToolNames.js +4 -17
  15. package/dist/constants/ToolNames.js.map +1 -1
  16. package/dist/database/DatabaseAdapterFactory.d.ts +14 -2
  17. package/dist/database/DatabaseAdapterFactory.d.ts.map +1 -1
  18. package/dist/database/DatabaseAdapterFactory.js +21 -6
  19. package/dist/database/DatabaseAdapterFactory.js.map +1 -1
  20. package/dist/database/adapters/mongodb/MongoBaseRepository.d.ts +7 -1
  21. package/dist/database/adapters/mongodb/MongoBaseRepository.d.ts.map +1 -1
  22. package/dist/database/adapters/mongodb/MongoBaseRepository.js +12 -2
  23. package/dist/database/adapters/mongodb/MongoBaseRepository.js.map +1 -1
  24. package/dist/events/ClientEventBus.d.ts +16 -77
  25. package/dist/events/ClientEventBus.d.ts.map +1 -1
  26. package/dist/events/ClientEventBus.js +38 -311
  27. package/dist/events/ClientEventBus.js.map +1 -1
  28. package/dist/events/EventBus.d.ts +1 -1
  29. package/dist/events/EventBus.d.ts.map +1 -1
  30. package/dist/events/EventBus.js +5 -4
  31. package/dist/events/EventBus.js.map +1 -1
  32. package/dist/events/EventBusBase.d.ts +139 -34
  33. package/dist/events/EventBusBase.d.ts.map +1 -1
  34. package/dist/events/EventBusBase.js +366 -88
  35. package/dist/events/EventBusBase.js.map +1 -1
  36. package/dist/events/EventNames.d.ts +10 -1
  37. package/dist/events/EventNames.d.ts.map +1 -1
  38. package/dist/events/EventNames.js +3 -0
  39. package/dist/events/EventNames.js.map +1 -1
  40. package/dist/events/PublicEvents.d.ts +6 -2
  41. package/dist/events/PublicEvents.d.ts.map +1 -1
  42. package/dist/events/PublicEvents.js +7 -4
  43. package/dist/events/PublicEvents.js.map +1 -1
  44. package/dist/events/ServerEventBus.d.ts +23 -112
  45. package/dist/events/ServerEventBus.d.ts.map +1 -1
  46. package/dist/events/ServerEventBus.js +58 -323
  47. package/dist/events/ServerEventBus.js.map +1 -1
  48. package/dist/events/event-definitions/LlmBudgetEvents.d.ts +68 -0
  49. package/dist/events/event-definitions/LlmBudgetEvents.d.ts.map +1 -0
  50. package/dist/events/event-definitions/LlmBudgetEvents.js +35 -0
  51. package/dist/events/event-definitions/LlmBudgetEvents.js.map +1 -0
  52. package/dist/interfaces/IMemoryPersistence.d.ts +41 -0
  53. package/dist/interfaces/IMemoryPersistence.d.ts.map +1 -0
  54. package/dist/interfaces/IMemoryPersistence.js +15 -0
  55. package/dist/interfaces/IMemoryPersistence.js.map +1 -0
  56. package/dist/middleware/MxpMiddleware.d.ts +8 -1
  57. package/dist/middleware/MxpMiddleware.d.ts.map +1 -1
  58. package/dist/middleware/MxpMiddleware.js +42 -20
  59. package/dist/middleware/MxpMiddleware.js.map +1 -1
  60. package/dist/models/channelKey.d.ts +1 -0
  61. package/dist/models/channelKey.d.ts.map +1 -1
  62. package/dist/models/channelKey.js +5 -0
  63. package/dist/models/channelKey.js.map +1 -1
  64. package/dist/models/memoryUtility.d.ts +39 -0
  65. package/dist/models/memoryUtility.d.ts.map +1 -0
  66. package/dist/models/memoryUtility.js +39 -0
  67. package/dist/models/memoryUtility.js.map +1 -0
  68. package/dist/models/user.d.ts.map +1 -1
  69. package/dist/models/user.js +23 -1
  70. package/dist/models/user.js.map +1 -1
  71. package/dist/mxp/BinaryProtocolLayer.d.ts +24 -8
  72. package/dist/mxp/BinaryProtocolLayer.d.ts.map +1 -1
  73. package/dist/mxp/BinaryProtocolLayer.js +90 -103
  74. package/dist/mxp/BinaryProtocolLayer.js.map +1 -1
  75. package/dist/mxp/ContextCompressionEngine.d.ts +57 -10
  76. package/dist/mxp/ContextCompressionEngine.d.ts.map +1 -1
  77. package/dist/mxp/ContextCompressionEngine.js +170 -128
  78. package/dist/mxp/ContextCompressionEngine.js.map +1 -1
  79. package/dist/mxp/MxpTokenOptimizer.d.ts +7 -3
  80. package/dist/mxp/MxpTokenOptimizer.d.ts.map +1 -1
  81. package/dist/mxp/MxpTokenOptimizer.js +8 -5
  82. package/dist/mxp/MxpTokenOptimizer.js.map +1 -1
  83. package/dist/prompts/MxfAgentSystemPrompt.d.ts +1 -1
  84. package/dist/prompts/MxfAgentSystemPrompt.d.ts.map +1 -1
  85. package/dist/prompts/MxfAgentSystemPrompt.js +47 -57
  86. package/dist/prompts/MxfAgentSystemPrompt.js.map +1 -1
  87. package/dist/protocols/mcp/ToolError.d.ts +123 -0
  88. package/dist/protocols/mcp/ToolError.d.ts.map +1 -0
  89. package/dist/protocols/mcp/ToolError.js +108 -0
  90. package/dist/protocols/mcp/ToolError.js.map +1 -0
  91. package/dist/protocols/mcp/defineTool.d.ts +133 -0
  92. package/dist/protocols/mcp/defineTool.d.ts.map +1 -0
  93. package/dist/protocols/mcp/defineTool.js +236 -0
  94. package/dist/protocols/mcp/defineTool.js.map +1 -0
  95. package/dist/protocols/mcp/security/HttpTargetGuard.d.ts +38 -0
  96. package/dist/protocols/mcp/security/HttpTargetGuard.d.ts.map +1 -0
  97. package/dist/protocols/mcp/security/HttpTargetGuard.js +227 -0
  98. package/dist/protocols/mcp/security/HttpTargetGuard.js.map +1 -0
  99. package/dist/protocols/mcp/security/McpSecurityGuard.d.ts +9 -1
  100. package/dist/protocols/mcp/security/McpSecurityGuard.d.ts.map +1 -1
  101. package/dist/protocols/mcp/security/McpSecurityGuard.js +10 -2
  102. package/dist/protocols/mcp/security/McpSecurityGuard.js.map +1 -1
  103. package/dist/protocols/mcp/security/McpToolPolicy.d.ts +81 -0
  104. package/dist/protocols/mcp/security/McpToolPolicy.d.ts.map +1 -0
  105. package/dist/protocols/mcp/security/McpToolPolicy.js +202 -0
  106. package/dist/protocols/mcp/security/McpToolPolicy.js.map +1 -0
  107. package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts +51 -18
  108. package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts.map +1 -1
  109. package/dist/protocols/mcp/services/ExternalMcpServerManager.js +256 -283
  110. package/dist/protocols/mcp/services/ExternalMcpServerManager.js.map +1 -1
  111. package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts +39 -6
  112. package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts.map +1 -1
  113. package/dist/protocols/mcp/services/ExternalServerConfigs.js +76 -18
  114. package/dist/protocols/mcp/services/ExternalServerConfigs.js.map +1 -1
  115. package/dist/protocols/mcp/tools/AnalyticsTools.js +1 -1
  116. package/dist/protocols/mcp/tools/AnalyticsTools.js.map +1 -1
  117. package/dist/protocols/mcp/tools/CodeAnalysisTools.d.ts.map +1 -1
  118. package/dist/protocols/mcp/tools/CodeAnalysisTools.js +15 -0
  119. package/dist/protocols/mcp/tools/CodeAnalysisTools.js.map +1 -1
  120. package/dist/protocols/mcp/tools/EffectivenessTools.js +3 -3
  121. package/dist/protocols/mcp/tools/EffectivenessTools.js.map +1 -1
  122. package/dist/protocols/mcp/tools/GitTools.d.ts +9 -369
  123. package/dist/protocols/mcp/tools/GitTools.d.ts.map +1 -1
  124. package/dist/protocols/mcp/tools/GitTools.js +222 -517
  125. package/dist/protocols/mcp/tools/GitTools.js.map +1 -1
  126. package/dist/protocols/mcp/tools/InfrastructureTools.d.ts +56 -24
  127. package/dist/protocols/mcp/tools/InfrastructureTools.d.ts.map +1 -1
  128. package/dist/protocols/mcp/tools/InfrastructureTools.js +117 -26
  129. package/dist/protocols/mcp/tools/InfrastructureTools.js.map +1 -1
  130. package/dist/protocols/mcp/tools/MemorySearchTools.d.ts +0 -3
  131. package/dist/protocols/mcp/tools/MemorySearchTools.d.ts.map +1 -1
  132. package/dist/protocols/mcp/tools/MemorySearchTools.js +58 -1
  133. package/dist/protocols/mcp/tools/MemorySearchTools.js.map +1 -1
  134. package/dist/protocols/mcp/tools/OrparTools.d.ts +8 -0
  135. package/dist/protocols/mcp/tools/OrparTools.d.ts.map +1 -1
  136. package/dist/protocols/mcp/tools/OrparTools.js +66 -0
  137. package/dist/protocols/mcp/tools/OrparTools.js.map +1 -1
  138. package/dist/protocols/mcp/tools/SafetyTools.d.ts +7 -525
  139. package/dist/protocols/mcp/tools/SafetyTools.d.ts.map +1 -1
  140. package/dist/protocols/mcp/tools/SafetyTools.js +349 -514
  141. package/dist/protocols/mcp/tools/SafetyTools.js.map +1 -1
  142. package/dist/protocols/mcp/tools/TaskPlanningTools.d.ts.map +1 -1
  143. package/dist/protocols/mcp/tools/TaskPlanningTools.js +56 -26
  144. package/dist/protocols/mcp/tools/TaskPlanningTools.js.map +1 -1
  145. package/dist/protocols/mcp/tools/TestTools.d.ts +15 -376
  146. package/dist/protocols/mcp/tools/TestTools.d.ts.map +1 -1
  147. package/dist/protocols/mcp/tools/TestTools.js +241 -363
  148. package/dist/protocols/mcp/tools/TestTools.js.map +1 -1
  149. package/dist/protocols/mcp/tools/TypeScriptTools.d.ts +6 -276
  150. package/dist/protocols/mcp/tools/TypeScriptTools.d.ts.map +1 -1
  151. package/dist/protocols/mcp/tools/TypeScriptTools.js +223 -337
  152. package/dist/protocols/mcp/tools/TypeScriptTools.js.map +1 -1
  153. package/dist/protocols/mcp/tools/WebTools.d.ts +15 -728
  154. package/dist/protocols/mcp/tools/WebTools.d.ts.map +1 -1
  155. package/dist/protocols/mcp/tools/WebTools.js +151 -257
  156. package/dist/protocols/mcp/tools/WebTools.js.map +1 -1
  157. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts +5 -3
  158. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts.map +1 -1
  159. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js +31 -6
  160. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js.map +1 -1
  161. package/dist/schemas/EventPayloadSchema.d.ts +21 -0
  162. package/dist/schemas/EventPayloadSchema.d.ts.map +1 -1
  163. package/dist/schemas/EventPayloadSchema.js +14 -0
  164. package/dist/schemas/EventPayloadSchema.js.map +1 -1
  165. package/dist/services/BackgroundTaskManager.d.ts.map +1 -1
  166. package/dist/services/BackgroundTaskManager.js +12 -4
  167. package/dist/services/BackgroundTaskManager.js.map +1 -1
  168. package/dist/services/BrowserManager.d.ts +1 -1
  169. package/dist/services/BrowserManager.d.ts.map +1 -1
  170. package/dist/services/BrowserManager.js +26 -3
  171. package/dist/services/BrowserManager.js.map +1 -1
  172. package/dist/services/MemoryCompressor.d.ts +11 -2
  173. package/dist/services/MemoryCompressor.d.ts.map +1 -1
  174. package/dist/services/MemoryCompressor.js +8 -26
  175. package/dist/services/MemoryCompressor.js.map +1 -1
  176. package/dist/services/MemoryService.d.ts +16 -3
  177. package/dist/services/MemoryService.d.ts.map +1 -1
  178. package/dist/services/MemoryService.js +79 -55
  179. package/dist/services/MemoryService.js.map +1 -1
  180. package/dist/services/PredictiveAnalyticsService.d.ts +34 -4
  181. package/dist/services/PredictiveAnalyticsService.d.ts.map +1 -1
  182. package/dist/services/PredictiveAnalyticsService.js +41 -25
  183. package/dist/services/PredictiveAnalyticsService.js.map +1 -1
  184. package/dist/services/QValueManager.d.ts +7 -0
  185. package/dist/services/QValueManager.d.ts.map +1 -1
  186. package/dist/services/QValueManager.js +9 -0
  187. package/dist/services/QValueManager.js.map +1 -1
  188. package/dist/services/RewardSignalProcessor.d.ts +38 -1
  189. package/dist/services/RewardSignalProcessor.d.ts.map +1 -1
  190. package/dist/services/RewardSignalProcessor.js +109 -7
  191. package/dist/services/RewardSignalProcessor.js.map +1 -1
  192. package/dist/services/SharedStateService.d.ts +16 -2
  193. package/dist/services/SharedStateService.d.ts.map +1 -1
  194. package/dist/services/SharedStateService.js +44 -3
  195. package/dist/services/SharedStateService.js.map +1 -1
  196. package/dist/services/TaskEffectivenessService.d.ts +34 -18
  197. package/dist/services/TaskEffectivenessService.d.ts.map +1 -1
  198. package/dist/services/TaskEffectivenessService.js +91 -155
  199. package/dist/services/TaskEffectivenessService.js.map +1 -1
  200. package/dist/services/WebSearchService.d.ts.map +1 -1
  201. package/dist/services/WebSearchService.js.map +1 -1
  202. package/dist/services/dag/TaskDagService.d.ts.map +1 -1
  203. package/dist/services/dag/TaskDagService.js +2 -0
  204. package/dist/services/dag/TaskDagService.js.map +1 -1
  205. package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts +7 -0
  206. package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts.map +1 -1
  207. package/dist/services/orpar-memory/OrparMemoryCoordinator.js +71 -10
  208. package/dist/services/orpar-memory/OrparMemoryCoordinator.js.map +1 -1
  209. package/dist/types/MemoryUtilityTypes.d.ts +2 -0
  210. package/dist/types/MemoryUtilityTypes.d.ts.map +1 -1
  211. package/dist/types/MemoryUtilityTypes.js.map +1 -1
  212. package/dist/utils/Logger.d.ts +43 -3
  213. package/dist/utils/Logger.d.ts.map +1 -1
  214. package/dist/utils/Logger.js +78 -36
  215. package/dist/utils/Logger.js.map +1 -1
  216. package/dist/utils/MxpEncryption.d.ts +19 -6
  217. package/dist/utils/MxpEncryption.d.ts.map +1 -1
  218. package/dist/utils/MxpEncryption.js +48 -16
  219. package/dist/utils/MxpEncryption.js.map +1 -1
  220. package/package.json +19 -4
  221. package/src/adapters/FrameworkAdapter.ts +1 -1
  222. package/src/config/ConfigManager.ts +52 -19
  223. package/src/config/memory-strata.config.ts +136 -0
  224. package/src/constants/ToolNames.ts +4 -18
  225. package/src/database/DatabaseAdapterFactory.ts +23 -6
  226. package/src/database/adapters/mongodb/MongoBaseRepository.ts +16 -2
  227. package/src/events/ClientEventBus.ts +68 -403
  228. package/src/events/EventBus.ts +7 -8
  229. package/src/events/EventBusBase.ts +479 -116
  230. package/src/events/EventNames.ts +6 -0
  231. package/src/events/PublicEvents.ts +7 -4
  232. package/src/events/ServerEventBus.ts +78 -473
  233. package/src/events/event-definitions/LlmBudgetEvents.ts +74 -0
  234. package/src/interfaces/IMemoryPersistence.ts +51 -0
  235. package/src/middleware/MxpMiddleware.ts +51 -24
  236. package/src/models/channelKey.ts +18 -4
  237. package/src/models/memoryUtility.ts +64 -0
  238. package/src/models/user.ts +36 -4
  239. package/src/mxp/BinaryProtocolLayer.ts +108 -124
  240. package/src/mxp/ContextCompressionEngine.ts +219 -161
  241. package/src/mxp/MxpTokenOptimizer.ts +10 -10
  242. package/src/prompts/MxfAgentSystemPrompt.ts +45 -55
  243. package/src/protocols/mcp/ToolError.ts +201 -0
  244. package/src/protocols/mcp/defineTool.ts +379 -0
  245. package/src/protocols/mcp/security/HttpTargetGuard.ts +267 -0
  246. package/src/protocols/mcp/security/McpSecurityGuard.ts +10 -3
  247. package/src/protocols/mcp/security/McpToolPolicy.ts +221 -0
  248. package/src/protocols/mcp/services/ExternalMcpServerManager.ts +340 -322
  249. package/src/protocols/mcp/services/ExternalServerConfigs.ts +82 -19
  250. package/src/protocols/mcp/tools/AnalyticsTools.ts +1 -1
  251. package/src/protocols/mcp/tools/CodeAnalysisTools.ts +15 -0
  252. package/src/protocols/mcp/tools/EffectivenessTools.ts +3 -3
  253. package/src/protocols/mcp/tools/GitTools.ts +290 -569
  254. package/src/protocols/mcp/tools/InfrastructureTools.ts +189 -43
  255. package/src/protocols/mcp/tools/MemorySearchTools.ts +77 -1
  256. package/src/protocols/mcp/tools/OrparTools.ts +87 -0
  257. package/src/protocols/mcp/tools/SafetyTools.ts +501 -579
  258. package/src/protocols/mcp/tools/TaskPlanningTools.ts +81 -39
  259. package/src/protocols/mcp/tools/TestTools.ts +339 -419
  260. package/src/protocols/mcp/tools/TypeScriptTools.ts +316 -376
  261. package/src/protocols/mcp/tools/WebTools.ts +209 -307
  262. package/src/protocols/mcp/tools/shell/ShellExecuteHandler.ts +53 -14
  263. package/src/schemas/EventPayloadSchema.ts +37 -0
  264. package/src/services/BackgroundTaskManager.ts +12 -4
  265. package/src/services/BrowserManager.ts +41 -1
  266. package/src/services/MemoryCompressor.ts +20 -32
  267. package/src/services/MemoryService.ts +96 -58
  268. package/src/services/PredictiveAnalyticsService.ts +55 -32
  269. package/src/services/QValueManager.ts +10 -0
  270. package/src/services/RewardSignalProcessor.ts +138 -9
  271. package/src/services/SharedStateService.ts +56 -8
  272. package/src/services/TaskEffectivenessService.ts +114 -187
  273. package/src/services/WebSearchService.ts +2 -1
  274. package/src/services/dag/TaskDagService.ts +2 -0
  275. package/src/services/orpar-memory/OrparMemoryCoordinator.ts +86 -10
  276. package/src/types/MemoryUtilityTypes.ts +2 -0
  277. package/src/utils/Logger.ts +116 -40
  278. package/src/utils/MxpEncryption.ts +69 -34
  279. package/dist/mxp/MxpEventForwardingEnhancer.d.ts +0 -134
  280. package/dist/mxp/MxpEventForwardingEnhancer.d.ts.map +0 -1
  281. package/dist/mxp/MxpEventForwardingEnhancer.js +0 -361
  282. package/dist/mxp/MxpEventForwardingEnhancer.js.map +0 -1
  283. package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts +0 -56
  284. package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts.map +0 -1
  285. package/dist/protocols/mcp/services/ToolSchemaEnhancements.js +0 -57
  286. package/dist/protocols/mcp/services/ToolSchemaEnhancements.js.map +0 -1
  287. package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts +0 -270
  288. package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts.map +0 -1
  289. package/dist/protocols/mcp/tools/ControlLoopLifecycle.js +0 -256
  290. package/dist/protocols/mcp/tools/ControlLoopLifecycle.js.map +0 -1
  291. package/dist/protocols/mcp/tools/LspTools.d.ts +0 -495
  292. package/dist/protocols/mcp/tools/LspTools.d.ts.map +0 -1
  293. package/dist/protocols/mcp/tools/LspTools.js +0 -469
  294. package/dist/protocols/mcp/tools/LspTools.js.map +0 -1
  295. package/src/mxp/MxpEventForwardingEnhancer.ts +0 -471
  296. package/src/protocols/mcp/services/ToolSchemaEnhancements.ts +0 -92
  297. package/src/protocols/mcp/tools/ControlLoopLifecycle.ts +0 -302
  298. package/src/protocols/mcp/tools/LspTools.ts +0 -476
@@ -18,22 +18,154 @@
18
18
  * @documentation https://mxf-dev.github.io/mxf/
19
19
  */
20
20
 
21
- import { Logger } from '../../../utils/Logger.js';
21
+ /**
22
+ * TestTools.ts
23
+ *
24
+ * Run a project's test suite with Jest, Mocha or Vitest, or let test_runner pick
25
+ * the one the project actually uses.
26
+ *
27
+ * On exit codes: a test runner exits non-zero when tests fail. That is the tool
28
+ * working — it ran the suite and the suite failed. So a failing suite comes back
29
+ * with `passed: false` and `isError: false`, and `isError` is reserved for the
30
+ * runner not being usable at all.
31
+ *
32
+ * Watch mode is deliberately not offered. These tools are request/response: a
33
+ * watching runner never exits, so the call would hang until it timed out.
34
+ */
35
+
36
+ import * as fs from 'fs/promises';
37
+ import * as path from 'path';
38
+
39
+ import { TOOL_CATEGORIES } from '../../../constants/ToolNames.js';
40
+ import { defineTool, ToolRunContext } from '../defineTool.js';
41
+ import { ToolError } from '../ToolError.js';
22
42
  import { executeShellCommand } from './InfrastructureTools.js';
23
- import { McpToolHandlerContext, McpToolHandlerResult, McpToolResultContent } from '../McpServerTypes.js';
24
43
 
25
- const logger = new Logger('info', 'TestTools', 'server');
44
+ /** Exit code the shell reports when a command could not be found or spawned. */
45
+ const COMMAND_NOT_FOUND = 127;
46
+
47
+ const workingDirectoryProperty = {
48
+ type: 'string',
49
+ description: 'Working directory path (defaults to the server working directory)'
50
+ };
51
+
52
+ /** Outcome of a test run, in the shape every runner here reports. */
53
+ export interface TestRunResult {
54
+ runner: 'jest' | 'mocha' | 'vitest';
55
+ passed: boolean;
56
+ testsRun: number;
57
+ testsPassed: number;
58
+ testsFailed: number;
59
+ executionTimeMs: number;
60
+ output: string;
61
+ }
26
62
 
27
63
  /**
28
- * Test Tools for MXF
29
- *
30
- * Provides comprehensive testing capabilities for multiple test runners
31
- * and testing frameworks including Jest, Mocha, Vitest, and others
64
+ * Run a test runner via npx and normalize its output.
65
+ *
66
+ * Throws only when the runner could not be executed. A failing suite is a
67
+ * result, and comes back with passed: false.
32
68
  */
69
+ async function runTestRunner(
70
+ runner: 'jest' | 'mocha' | 'vitest',
71
+ runnerArgs: string[],
72
+ workingDirectory: string | undefined,
73
+ context: ToolRunContext
74
+ ): Promise<TestRunResult> {
75
+ const startedAt = Date.now();
76
+
77
+ const result = await executeShellCommand('npx', [runner, ...runnerArgs], {
78
+ context: {
79
+ agentId: context.agentId,
80
+ channelId: context.channelId,
81
+ requestId: context.requestId
82
+ },
83
+ workingDirectory: workingDirectory || process.cwd(),
84
+ captureOutput: true
85
+ });
86
+
87
+ if (result.exitCode === COMMAND_NOT_FOUND) {
88
+ throw ToolError.preconditionFailed(
89
+ `Could not run ${runner} — the command was not found. ` +
90
+ `Install it in the project before running this tool.`,
91
+ { runner }
92
+ );
93
+ }
94
+
95
+ // Jest and Vitest write their summary to stderr; Mocha writes to stdout.
96
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
97
+ const counts = parseTestCounts(runner, output);
33
98
 
34
- export const jestTestTool = {
99
+ return {
100
+ runner,
101
+ passed: result.exitCode === 0,
102
+ ...counts,
103
+ executionTimeMs: Date.now() - startedAt,
104
+ output
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Pull pass/fail counts out of a runner's summary.
110
+ *
111
+ * Each runner prints a different summary, so each gets its own pattern rather
112
+ * than one regex that half-matches all three.
113
+ */
114
+ function parseTestCounts(
115
+ runner: 'jest' | 'mocha' | 'vitest',
116
+ output: string
117
+ ): { testsRun: number; testsPassed: number; testsFailed: number } {
118
+ if (runner === 'jest') {
119
+ // "Tests: 1 failed, 2 skipped, 17 passed, 20 total"
120
+ const summary = output.match(/^Tests:\s+(.+)$/m)?.[1] ?? '';
121
+ const read = (label: string): number => {
122
+ const match = summary.match(new RegExp(`(\\d+)\\s+${label}`));
123
+ return match ? parseInt(match[1], 10) : 0;
124
+ };
125
+ return {
126
+ testsRun: read('total'),
127
+ testsPassed: read('passed'),
128
+ testsFailed: read('failed')
129
+ };
130
+ }
131
+
132
+ if (runner === 'vitest') {
133
+ // "Tests 1 failed | 17 passed (18)"
134
+ const summary = output.match(/^\s*Tests\s+(.+)$/m)?.[1] ?? '';
135
+ const passed = parseInt(summary.match(/(\d+)\s+passed/)?.[1] ?? '0', 10);
136
+ const failed = parseInt(summary.match(/(\d+)\s+failed/)?.[1] ?? '0', 10);
137
+ const total = parseInt(summary.match(/\((\d+)\)/)?.[1] ?? '0', 10);
138
+ return {
139
+ testsRun: total || passed + failed,
140
+ testsPassed: passed,
141
+ testsFailed: failed
142
+ };
143
+ }
144
+
145
+ // Mocha: " 17 passing (1s)" / " 1 failing"
146
+ const passed = parseInt(output.match(/(\d+)\s+passing/)?.[1] ?? '0', 10);
147
+ const failed = parseInt(output.match(/(\d+)\s+failing/)?.[1] ?? '0', 10);
148
+ return {
149
+ testsRun: passed + failed,
150
+ testsPassed: passed,
151
+ testsFailed: failed
152
+ };
153
+ }
154
+
155
+ export const jestTestTool = defineTool<
156
+ {
157
+ testFiles?: string[];
158
+ testNamePattern?: string;
159
+ coverage?: boolean;
160
+ verbose?: boolean;
161
+ maxWorkers?: number;
162
+ workingDirectory?: string;
163
+ },
164
+ TestRunResult
165
+ >({
35
166
  name: 'test_jest',
36
- description: 'Run Jest tests with comprehensive options',
167
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
168
+ description: 'Run Jest tests. passed is false when any test fails.',
37
169
  inputSchema: {
38
170
  type: 'object',
39
171
  properties: {
@@ -44,159 +176,63 @@ export const jestTestTool = {
44
176
  },
45
177
  testNamePattern: {
46
178
  type: 'string',
47
- description: 'Run tests with names matching this pattern'
179
+ description: 'Run only tests whose name matches this pattern'
48
180
  },
49
181
  coverage: {
50
182
  type: 'boolean',
51
183
  default: false,
52
- description: 'Generate test coverage report'
53
- },
54
- watchMode: {
55
- type: 'boolean',
56
- default: false,
57
- description: 'Run tests in watch mode'
184
+ description: 'Generate a coverage report'
58
185
  },
59
186
  verbose: {
60
187
  type: 'boolean',
61
188
  default: false,
62
- description: 'Display individual test results'
189
+ description: 'Report each test individually'
63
190
  },
64
191
  maxWorkers: {
65
192
  type: 'number',
193
+ minimum: 1,
66
194
  description: 'Maximum number of worker processes'
67
195
  },
68
- workingDirectory: {
69
- type: 'string',
70
- description: 'Working directory path (defaults to current directory)'
71
- }
196
+ workingDirectory: workingDirectoryProperty
72
197
  }
73
198
  },
74
- handler: async (args: {
75
- testFiles?: string[];
76
- testNamePattern?: string;
77
- coverage?: boolean;
78
- watchMode?: boolean;
79
- verbose?: boolean;
80
- maxWorkers?: number;
81
- workingDirectory?: string;
82
- }, context: McpToolHandlerContext): Promise<McpToolHandlerResult> => {
83
-
84
- try {
85
- const jestArgs = ['--no-watchman', '--passWithNoTests'];
86
-
87
- if (args.coverage) {
88
- jestArgs.push('--coverage');
89
- }
90
-
91
- if (args.verbose) {
92
- jestArgs.push('--verbose');
93
- }
94
-
95
- if (args.maxWorkers) {
96
- jestArgs.push('--maxWorkers', args.maxWorkers.toString());
97
- }
98
-
99
- if (args.testNamePattern) {
100
- jestArgs.push('--testNamePattern', args.testNamePattern);
101
- }
102
-
103
- if (args.watchMode) {
104
- jestArgs.push('--watch');
105
- }
106
-
107
- if (args.testFiles && args.testFiles.length > 0) {
108
- jestArgs.push(...args.testFiles);
109
- }
110
-
111
- const result = await executeShellCommand('npx', ['jest', ...jestArgs], {
112
- workingDirectory: args.workingDirectory || process.cwd(),
113
- captureOutput: true
114
- });
115
-
116
- // Parse Jest output for test results
117
- let testsRun = 0;
118
- let testsPassed = 0;
119
- let testsFailed = 0;
120
- let testSuites = 0;
121
- let testSuitesPassed = 0;
122
- let testSuitesFailed = 0;
123
-
124
- if (result.stdout) {
125
- // Parse test results
126
- const runMatch = result.stdout.match(/Tests:\s+(\d+)\s+total/);
127
- const passMatch = result.stdout.match(/Tests:\s+\d+\s+failed,\s+(\d+)\s+passed/);
128
- const failMatch = result.stdout.match(/Tests:\s+(\d+)\s+failed/);
129
-
130
- // Parse test suites
131
- const suitesMatch = result.stdout.match(/Test Suites:\s+(\d+)\s+total/);
132
- const suitesPassMatch = result.stdout.match(/Test Suites:\s+\d+\s+failed,\s+(\d+)\s+passed/);
133
- const suitesFailMatch = result.stdout.match(/Test Suites:\s+(\d+)\s+failed/);
134
-
135
- if (runMatch) testsRun = parseInt(runMatch[1]);
136
- if (passMatch) testsPassed = parseInt(passMatch[1]);
137
- if (failMatch) testsFailed = parseInt(failMatch[1]);
138
-
139
- if (suitesMatch) testSuites = parseInt(suitesMatch[1]);
140
- if (suitesPassMatch) testSuitesPassed = parseInt(suitesPassMatch[1]);
141
- if (suitesFailMatch) testSuitesFailed = parseInt(suitesFailMatch[1]);
142
-
143
- // Alternative parsing for different Jest output formats
144
- if (testsRun === 0) {
145
- const totalMatch = result.stdout.match(/(\d+)\s+tests?\s+passed/);
146
- if (totalMatch) {
147
- testsRun = parseInt(totalMatch[1]);
148
- testsPassed = testsRun;
149
- }
150
- }
151
- }
152
-
153
- const content: McpToolResultContent = {
154
- type: 'application/json',
155
- data: {
156
- success: result.exitCode === 0,
157
- runner: 'jest',
158
- output: result.stdout || '',
159
- errorOutput: result.stderr || '',
160
- testsRun,
161
- testsPassed,
162
- testsFailed,
163
- testSuites,
164
- testSuitesPassed,
165
- testSuitesFailed,
166
- executionTime: result.executionTime,
167
- coverage: args.coverage ? 'Coverage report generated' : null,
168
- error: result.exitCode !== 0 ? result.stderr : null
169
- }
170
- };
171
- return { content };
172
- } catch (error) {
173
- logger.error('Jest test failed', { error });
174
- const content: McpToolResultContent = {
175
- type: 'application/json',
176
- data: {
177
- success: false,
178
- runner: 'jest',
179
- output: '',
180
- errorOutput: '',
181
- testsRun: 0,
182
- testsPassed: 0,
183
- testsFailed: 0,
184
- testSuites: 0,
185
- testSuitesPassed: 0,
186
- testSuitesFailed: 0,
187
- executionTime: 0,
188
- coverage: null,
189
- error: error instanceof Error ? error.message : String(error)
190
- }
191
- };
192
- return { content };
199
+ run: async (input, context) => {
200
+ const args = ['--no-watchman', '--passWithNoTests'];
201
+
202
+ if (input.coverage) {
203
+ args.push('--coverage');
204
+ }
205
+ if (input.verbose) {
206
+ args.push('--verbose');
207
+ }
208
+ if (input.maxWorkers) {
209
+ args.push('--maxWorkers', String(input.maxWorkers));
210
+ }
211
+ if (input.testNamePattern) {
212
+ args.push('--testNamePattern', input.testNamePattern);
213
+ }
214
+ if (input.testFiles && input.testFiles.length > 0) {
215
+ args.push(...input.testFiles);
193
216
  }
217
+
218
+ return runTestRunner('jest', args, input.workingDirectory, context);
194
219
  }
195
- };
220
+ });
196
221
 
197
- export const mochaTestTool = {
222
+ export const mochaTestTool = defineTool<
223
+ {
224
+ testFiles?: string[];
225
+ grep?: string;
226
+ reporter?: string;
227
+ timeout?: number;
228
+ recursive?: boolean;
229
+ workingDirectory?: string;
230
+ },
231
+ TestRunResult
232
+ >({
198
233
  name: 'test_mocha',
199
- description: 'Run Mocha tests with comprehensive options',
234
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
235
+ description: 'Run Mocha tests. passed is false when any test fails.',
200
236
  inputSchema: {
201
237
  type: 'object',
202
238
  properties: {
@@ -207,119 +243,62 @@ export const mochaTestTool = {
207
243
  },
208
244
  grep: {
209
245
  type: 'string',
210
- description: 'Run tests matching this pattern'
246
+ description: 'Run only tests matching this pattern'
211
247
  },
212
248
  reporter: {
213
249
  type: 'string',
214
- enum: ['spec', 'json', 'tap', 'dot', 'nyan', 'landing', 'list', 'progress', 'json-stream', 'min'],
250
+ enum: ['spec', 'dot', 'nyan', 'tap', 'json', 'min'],
215
251
  default: 'spec',
216
- description: 'Test reporter format'
252
+ description: 'Reporter format'
217
253
  },
218
254
  timeout: {
219
255
  type: 'number',
220
- default: 2000,
221
- description: 'Test timeout in milliseconds'
256
+ minimum: 1,
257
+ description: 'Per-test timeout in milliseconds'
222
258
  },
223
259
  recursive: {
224
260
  type: 'boolean',
225
261
  default: false,
226
262
  description: 'Look for tests in subdirectories'
227
263
  },
228
- workingDirectory: {
229
- type: 'string',
230
- description: 'Working directory path (defaults to current directory)'
231
- }
264
+ workingDirectory: workingDirectoryProperty
232
265
  }
233
266
  },
234
- handler: async (args: {
235
- testFiles?: string[];
236
- grep?: string;
237
- reporter?: string;
238
- timeout?: number;
239
- recursive?: boolean;
240
- workingDirectory?: string;
241
- }, context: McpToolHandlerContext): Promise<McpToolHandlerResult> => {
242
-
243
- try {
244
- const mochaArgs = ['--reporter', args.reporter || 'spec'];
245
-
246
- if (args.timeout) {
247
- mochaArgs.push('--timeout', args.timeout.toString());
248
- }
249
-
250
- if (args.grep) {
251
- mochaArgs.push('--grep', args.grep);
252
- }
253
-
254
- if (args.recursive) {
255
- mochaArgs.push('--recursive');
256
- }
257
-
258
- if (args.testFiles && args.testFiles.length > 0) {
259
- mochaArgs.push(...args.testFiles);
260
- } else {
261
- mochaArgs.push('test/**/*.js');
262
- }
263
-
264
- const result = await executeShellCommand('npx', ['mocha', ...mochaArgs], {
265
- workingDirectory: args.workingDirectory || process.cwd(),
266
- captureOutput: true
267
- });
268
-
269
- // Parse Mocha output for test results
270
- let testsRun = 0;
271
- let testsPassed = 0;
272
- let testsFailed = 0;
273
-
274
- if (result.stdout) {
275
- const passMatch = result.stdout.match(/(\d+)\s+passing/);
276
- const failMatch = result.stdout.match(/(\d+)\s+failing/);
277
-
278
- if (passMatch) testsPassed = parseInt(passMatch[1]);
279
- if (failMatch) testsFailed = parseInt(failMatch[1]);
280
-
281
- testsRun = testsPassed + testsFailed;
282
- }
283
-
284
- const content: McpToolResultContent = {
285
- type: 'application/json',
286
- data: {
287
- success: result.exitCode === 0,
288
- runner: 'mocha',
289
- output: result.stdout || '',
290
- errorOutput: result.stderr || '',
291
- testsRun,
292
- testsPassed,
293
- testsFailed,
294
- executionTime: result.executionTime,
295
- error: result.exitCode !== 0 ? result.stderr : null
296
- }
297
- };
298
- return { content };
299
- } catch (error) {
300
- logger.error('Mocha test failed', { error });
301
- const content: McpToolResultContent = {
302
- type: 'application/json',
303
- data: {
304
- success: false,
305
- runner: 'mocha',
306
- output: '',
307
- errorOutput: '',
308
- testsRun: 0,
309
- testsPassed: 0,
310
- testsFailed: 0,
311
- executionTime: 0,
312
- error: error instanceof Error ? error.message : String(error)
313
- }
314
- };
315
- return { content };
267
+ run: async (input, context) => {
268
+ const args: string[] = [];
269
+
270
+ if (input.reporter) {
271
+ args.push('--reporter', input.reporter);
272
+ }
273
+ if (input.grep) {
274
+ args.push('--grep', input.grep);
275
+ }
276
+ if (input.timeout) {
277
+ args.push('--timeout', String(input.timeout));
316
278
  }
279
+ if (input.recursive) {
280
+ args.push('--recursive');
281
+ }
282
+ if (input.testFiles && input.testFiles.length > 0) {
283
+ args.push(...input.testFiles);
284
+ }
285
+
286
+ return runTestRunner('mocha', args, input.workingDirectory, context);
317
287
  }
318
- };
288
+ });
319
289
 
320
- export const vitestTestTool = {
290
+ export const vitestTestTool = defineTool<
291
+ {
292
+ testFiles?: string[];
293
+ coverage?: boolean;
294
+ reporter?: string;
295
+ workingDirectory?: string;
296
+ },
297
+ TestRunResult
298
+ >({
321
299
  name: 'test_vitest',
322
- description: 'Run Vitest tests with comprehensive options',
300
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
301
+ description: 'Run Vitest tests once (not in watch mode). passed is false when any test fails.',
323
302
  inputSchema: {
324
303
  type: 'object',
325
304
  properties: {
@@ -331,114 +310,99 @@ export const vitestTestTool = {
331
310
  coverage: {
332
311
  type: 'boolean',
333
312
  default: false,
334
- description: 'Generate test coverage report'
335
- },
336
- watchMode: {
337
- type: 'boolean',
338
- default: false,
339
- description: 'Run tests in watch mode'
313
+ description: 'Generate a coverage report'
340
314
  },
341
315
  reporter: {
342
316
  type: 'string',
343
- enum: ['default', 'verbose', 'dot', 'json', 'junit', 'html'],
317
+ enum: ['default', 'verbose', 'dot', 'json', 'tap'],
344
318
  default: 'default',
345
- description: 'Test reporter format'
319
+ description: 'Reporter format'
346
320
  },
347
- workingDirectory: {
348
- type: 'string',
349
- description: 'Working directory path (defaults to current directory)'
350
- }
321
+ workingDirectory: workingDirectoryProperty
351
322
  }
352
323
  },
353
- handler: async (args: {
354
- testFiles?: string[];
355
- coverage?: boolean;
356
- watchMode?: boolean;
357
- reporter?: string;
358
- workingDirectory?: string;
359
- }, context: McpToolHandlerContext): Promise<McpToolHandlerResult> => {
360
-
361
- try {
362
- const vitestArgs = ['--run']; // Force single run (no watch by default)
363
-
364
- if (args.coverage) {
365
- vitestArgs.push('--coverage');
366
- }
367
-
368
- if (args.watchMode) {
369
- vitestArgs.pop(); // Remove --run
370
- vitestArgs.push('--watch');
371
- }
372
-
373
- if (args.reporter) {
374
- vitestArgs.push('--reporter', args.reporter);
375
- }
376
-
377
- if (args.testFiles && args.testFiles.length > 0) {
378
- vitestArgs.push(...args.testFiles);
379
- }
380
-
381
- const result = await executeShellCommand('npx', ['vitest', ...vitestArgs], {
382
- workingDirectory: args.workingDirectory || process.cwd(),
383
- captureOutput: true
384
- });
385
-
386
- // Parse Vitest output for test results
387
- let testsRun = 0;
388
- let testsPassed = 0;
389
- let testsFailed = 0;
390
-
391
- if (result.stdout) {
392
- const testsMatch = result.stdout.match(/Test Files\s+(\d+)\s+passed/);
393
- const failMatch = result.stdout.match(/Test Files\s+(\d+)\s+failed/);
394
-
395
- if (testsMatch) testsPassed = parseInt(testsMatch[1]);
396
- if (failMatch) testsFailed = parseInt(failMatch[1]);
397
-
398
- testsRun = testsPassed + testsFailed;
399
- }
400
-
401
- const content: McpToolResultContent = {
402
- type: 'application/json',
403
- data: {
404
- success: result.exitCode === 0,
405
- runner: 'vitest',
406
- output: result.stdout || '',
407
- errorOutput: result.stderr || '',
408
- testsRun,
409
- testsPassed,
410
- testsFailed,
411
- executionTime: result.executionTime,
412
- coverage: args.coverage ? 'Coverage report generated' : null,
413
- error: result.exitCode !== 0 ? result.stderr : null
414
- }
415
- };
416
- return { content };
417
- } catch (error) {
418
- logger.error('Vitest test failed', { error });
419
- const content: McpToolResultContent = {
420
- type: 'application/json',
421
- data: {
422
- success: false,
423
- runner: 'vitest',
424
- output: '',
425
- errorOutput: '',
426
- testsRun: 0,
427
- testsPassed: 0,
428
- testsFailed: 0,
429
- executionTime: 0,
430
- coverage: null,
431
- error: error instanceof Error ? error.message : String(error)
432
- }
433
- };
434
- return { content };
324
+ run: async (input, context) => {
325
+ // `run` is Vitest's non-watching mode. Without it Vitest watches by
326
+ // default and the process would never exit.
327
+ const args = ['run'];
328
+
329
+ if (input.coverage) {
330
+ args.push('--coverage');
435
331
  }
332
+ if (input.reporter) {
333
+ args.push('--reporter', input.reporter);
334
+ }
335
+ if (input.testFiles && input.testFiles.length > 0) {
336
+ args.push(...input.testFiles);
337
+ }
338
+
339
+ return runTestRunner('vitest', args, input.workingDirectory, context);
436
340
  }
437
- };
341
+ });
342
+
343
+ /**
344
+ * Read the project's package.json and work out which runner it uses.
345
+ *
346
+ * Throws when there is no package.json or no known runner in it — guessing
347
+ * "jest" for a project that does not use jest produces a confusing failure two
348
+ * steps later.
349
+ */
350
+ async function detectFramework(workingDirectory: string): Promise<'jest' | 'mocha' | 'vitest'> {
351
+ const packageJsonPath = path.join(workingDirectory, 'package.json');
438
352
 
439
- export const testRunnerTool = {
353
+ let raw: string;
354
+ try {
355
+ raw = await fs.readFile(packageJsonPath, 'utf-8');
356
+ } catch {
357
+ throw ToolError.notFound(
358
+ `No package.json in ${workingDirectory}, so the test framework cannot be detected. ` +
359
+ `Pass framework explicitly, or run this from the project root.`
360
+ );
361
+ }
362
+
363
+ let packageJson: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };
364
+ try {
365
+ packageJson = JSON.parse(raw);
366
+ } catch (parseError) {
367
+ throw ToolError.executionFailed(
368
+ `package.json in ${workingDirectory} is not valid JSON: ` +
369
+ `${parseError instanceof Error ? parseError.message : String(parseError)}`
370
+ );
371
+ }
372
+
373
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
374
+
375
+ if (deps.vitest) {
376
+ return 'vitest';
377
+ }
378
+ if (deps.jest) {
379
+ return 'jest';
380
+ }
381
+ if (deps.mocha) {
382
+ return 'mocha';
383
+ }
384
+
385
+ throw ToolError.notFound(
386
+ `No supported test framework found in ${packageJsonPath}. ` +
387
+ `Expected one of jest, mocha or vitest in dependencies or devDependencies. ` +
388
+ `Pass framework explicitly to override.`
389
+ );
390
+ }
391
+
392
+ export const testRunnerTool = defineTool<
393
+ {
394
+ framework?: 'auto' | 'jest' | 'mocha' | 'vitest';
395
+ testFiles?: string[];
396
+ coverage?: boolean;
397
+ workingDirectory?: string;
398
+ },
399
+ TestRunResult & { detected: boolean }
400
+ >({
440
401
  name: 'test_runner',
441
- description: 'Universal test runner that detects and runs the appropriate test framework',
402
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
403
+ description:
404
+ 'Run the project\'s tests, detecting whether it uses Jest, Mocha or Vitest from ' +
405
+ 'package.json. passed is false when any test fails.',
442
406
  inputSchema: {
443
407
  type: 'object',
444
408
  properties: {
@@ -446,7 +410,7 @@ export const testRunnerTool = {
446
410
  type: 'string',
447
411
  enum: ['auto', 'jest', 'mocha', 'vitest'],
448
412
  default: 'auto',
449
- description: 'Test framework to use (auto-detects if not specified)'
413
+ description: 'Test framework. "auto" reads it from package.json.'
450
414
  },
451
415
  testFiles: {
452
416
  type: 'array',
@@ -456,98 +420,54 @@ export const testRunnerTool = {
456
420
  coverage: {
457
421
  type: 'boolean',
458
422
  default: false,
459
- description: 'Generate test coverage report'
460
- },
461
- watchMode: {
462
- type: 'boolean',
463
- default: false,
464
- description: 'Run tests in watch mode'
423
+ description: 'Generate a coverage report'
465
424
  },
466
- workingDirectory: {
467
- type: 'string',
468
- description: 'Working directory path (defaults to current directory)'
469
- }
425
+ workingDirectory: workingDirectoryProperty
470
426
  }
471
427
  },
472
- handler: async (args: {
473
- framework?: 'auto' | 'jest' | 'mocha' | 'vitest';
474
- testFiles?: string[];
475
- coverage?: boolean;
476
- watchMode?: boolean;
477
- workingDirectory?: string;
478
- }, context: McpToolHandlerContext): Promise<McpToolHandlerResult> => {
479
-
480
- try {
481
- let framework = args.framework;
482
-
483
- // Auto-detect framework if not specified
484
- if (framework === 'auto' || !framework) {
485
- // Check package.json for test scripts and dependencies
486
- const packageJsonResult = await executeShellCommand('cat', ['package.json'], {
487
- workingDirectory: args.workingDirectory || process.cwd(),
488
- captureOutput: true
489
- });
490
-
491
- if (packageJsonResult.exitCode === 0 && packageJsonResult.stdout) {
492
- try {
493
- const packageJson = JSON.parse(packageJsonResult.stdout);
494
-
495
- // Check dependencies and devDependencies
496
- const allDeps = {
497
- ...packageJson.dependencies,
498
- ...packageJson.devDependencies
499
- };
500
-
501
- if (allDeps.vitest) {
502
- framework = 'vitest';
503
- } else if (allDeps.jest) {
504
- framework = 'jest';
505
- } else if (allDeps.mocha) {
506
- framework = 'mocha';
507
- } else {
508
- framework = 'jest'; // Default fallback
509
- }
510
- } catch (parseError) {
511
- framework = 'jest'; // Default fallback
512
- }
513
- } else {
514
- framework = 'jest'; // Default fallback
428
+ run: async (input, context) => {
429
+ const workingDirectory = input.workingDirectory || process.cwd();
430
+ const requested = input.framework ?? 'auto';
431
+
432
+ const detected = requested === 'auto';
433
+ const framework = detected
434
+ ? await detectFramework(workingDirectory)
435
+ : requested;
436
+
437
+ const args: string[] = [];
438
+
439
+ switch (framework) {
440
+ case 'jest':
441
+ args.push('--no-watchman', '--passWithNoTests');
442
+ if (input.coverage) {
443
+ args.push('--coverage');
515
444
  }
516
- }
517
-
518
- // Delegate to the appropriate test tool
519
- switch (framework) {
520
- case 'jest':
521
- return await jestTestTool.handler(args, context);
522
- case 'mocha':
523
- return await mochaTestTool.handler(args, context);
524
- case 'vitest':
525
- return await vitestTestTool.handler(args, context);
526
- default:
527
- throw new Error(`Unknown test framework: ${framework}`);
528
- }
529
- } catch (error) {
530
- logger.error('Universal test runner failed', { error });
531
- const content: McpToolResultContent = {
532
- type: 'application/json',
533
- data: {
534
- success: false,
535
- runner: 'unknown',
536
- output: '',
537
- errorOutput: '',
538
- testsRun: 0,
539
- testsPassed: 0,
540
- testsFailed: 0,
541
- executionTime: 0,
542
- error: error instanceof Error ? error.message : String(error)
445
+ break;
446
+ case 'vitest':
447
+ args.push('run');
448
+ if (input.coverage) {
449
+ args.push('--coverage');
543
450
  }
544
- };
545
- return { content };
451
+ break;
452
+ case 'mocha':
453
+ if (input.coverage) {
454
+ throw ToolError.notImplemented(
455
+ 'Mocha does not produce coverage on its own — run it under nyc or c8 instead.'
456
+ );
457
+ }
458
+ break;
459
+ }
460
+
461
+ if (input.testFiles && input.testFiles.length > 0) {
462
+ args.push(...input.testFiles);
546
463
  }
464
+
465
+ const result = await runTestRunner(framework, args, workingDirectory, context);
466
+
467
+ return { ...result, detected };
547
468
  }
548
- };
469
+ });
549
470
 
550
- // Export all test tools
551
471
  export const testTools = [
552
472
  jestTestTool,
553
473
  mochaTestTool,