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