@mxf-dev/core 1.0.1 → 2.0.1

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 (303) 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/MxfMLService.d.ts +5 -0
  181. package/dist/services/MxfMLService.d.ts.map +1 -1
  182. package/dist/services/MxfMLService.js +32 -6
  183. package/dist/services/MxfMLService.js.map +1 -1
  184. package/dist/services/PredictiveAnalyticsService.d.ts +45 -9
  185. package/dist/services/PredictiveAnalyticsService.d.ts.map +1 -1
  186. package/dist/services/PredictiveAnalyticsService.js +82 -44
  187. package/dist/services/PredictiveAnalyticsService.js.map +1 -1
  188. package/dist/services/QValueManager.d.ts +7 -0
  189. package/dist/services/QValueManager.d.ts.map +1 -1
  190. package/dist/services/QValueManager.js +9 -0
  191. package/dist/services/QValueManager.js.map +1 -1
  192. package/dist/services/RewardSignalProcessor.d.ts +38 -1
  193. package/dist/services/RewardSignalProcessor.d.ts.map +1 -1
  194. package/dist/services/RewardSignalProcessor.js +109 -7
  195. package/dist/services/RewardSignalProcessor.js.map +1 -1
  196. package/dist/services/SharedStateService.d.ts +16 -2
  197. package/dist/services/SharedStateService.d.ts.map +1 -1
  198. package/dist/services/SharedStateService.js +44 -3
  199. package/dist/services/SharedStateService.js.map +1 -1
  200. package/dist/services/TaskEffectivenessService.d.ts +34 -18
  201. package/dist/services/TaskEffectivenessService.d.ts.map +1 -1
  202. package/dist/services/TaskEffectivenessService.js +91 -155
  203. package/dist/services/TaskEffectivenessService.js.map +1 -1
  204. package/dist/services/WebSearchService.d.ts.map +1 -1
  205. package/dist/services/WebSearchService.js.map +1 -1
  206. package/dist/services/dag/TaskDagService.d.ts.map +1 -1
  207. package/dist/services/dag/TaskDagService.js +2 -0
  208. package/dist/services/dag/TaskDagService.js.map +1 -1
  209. package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts +7 -0
  210. package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts.map +1 -1
  211. package/dist/services/orpar-memory/OrparMemoryCoordinator.js +71 -10
  212. package/dist/services/orpar-memory/OrparMemoryCoordinator.js.map +1 -1
  213. package/dist/types/MemoryUtilityTypes.d.ts +2 -0
  214. package/dist/types/MemoryUtilityTypes.d.ts.map +1 -1
  215. package/dist/types/MemoryUtilityTypes.js.map +1 -1
  216. package/dist/utils/Logger.d.ts +43 -3
  217. package/dist/utils/Logger.d.ts.map +1 -1
  218. package/dist/utils/Logger.js +78 -36
  219. package/dist/utils/Logger.js.map +1 -1
  220. package/dist/utils/MxpEncryption.d.ts +19 -6
  221. package/dist/utils/MxpEncryption.d.ts.map +1 -1
  222. package/dist/utils/MxpEncryption.js +48 -16
  223. package/dist/utils/MxpEncryption.js.map +1 -1
  224. package/package.json +19 -4
  225. package/src/adapters/FrameworkAdapter.ts +1 -1
  226. package/src/config/ConfigManager.ts +52 -19
  227. package/src/config/memory-strata.config.ts +136 -0
  228. package/src/constants/ToolNames.ts +4 -18
  229. package/src/database/DatabaseAdapterFactory.ts +23 -6
  230. package/src/database/adapters/mongodb/MongoBaseRepository.ts +16 -2
  231. package/src/events/ClientEventBus.ts +68 -403
  232. package/src/events/EventBus.ts +7 -8
  233. package/src/events/EventBusBase.ts +479 -116
  234. package/src/events/EventNames.ts +6 -0
  235. package/src/events/PublicEvents.ts +7 -4
  236. package/src/events/ServerEventBus.ts +78 -473
  237. package/src/events/event-definitions/LlmBudgetEvents.ts +74 -0
  238. package/src/interfaces/IMemoryPersistence.ts +51 -0
  239. package/src/middleware/MxpMiddleware.ts +51 -24
  240. package/src/models/channelKey.ts +18 -4
  241. package/src/models/memoryUtility.ts +64 -0
  242. package/src/models/user.ts +36 -4
  243. package/src/mxp/BinaryProtocolLayer.ts +108 -124
  244. package/src/mxp/ContextCompressionEngine.ts +219 -161
  245. package/src/mxp/MxpTokenOptimizer.ts +10 -10
  246. package/src/prompts/MxfAgentSystemPrompt.ts +45 -55
  247. package/src/protocols/mcp/ToolError.ts +201 -0
  248. package/src/protocols/mcp/defineTool.ts +379 -0
  249. package/src/protocols/mcp/security/HttpTargetGuard.ts +267 -0
  250. package/src/protocols/mcp/security/McpSecurityGuard.ts +10 -3
  251. package/src/protocols/mcp/security/McpToolPolicy.ts +221 -0
  252. package/src/protocols/mcp/services/ExternalMcpServerManager.ts +340 -322
  253. package/src/protocols/mcp/services/ExternalServerConfigs.ts +82 -19
  254. package/src/protocols/mcp/tools/AnalyticsTools.ts +1 -1
  255. package/src/protocols/mcp/tools/CodeAnalysisTools.ts +15 -0
  256. package/src/protocols/mcp/tools/EffectivenessTools.ts +3 -3
  257. package/src/protocols/mcp/tools/GitTools.ts +290 -569
  258. package/src/protocols/mcp/tools/InfrastructureTools.ts +189 -43
  259. package/src/protocols/mcp/tools/MemorySearchTools.ts +77 -1
  260. package/src/protocols/mcp/tools/OrparTools.ts +87 -0
  261. package/src/protocols/mcp/tools/SafetyTools.ts +501 -579
  262. package/src/protocols/mcp/tools/TaskPlanningTools.ts +81 -39
  263. package/src/protocols/mcp/tools/TestTools.ts +339 -419
  264. package/src/protocols/mcp/tools/TypeScriptTools.ts +316 -376
  265. package/src/protocols/mcp/tools/WebTools.ts +209 -307
  266. package/src/protocols/mcp/tools/shell/ShellExecuteHandler.ts +53 -14
  267. package/src/schemas/EventPayloadSchema.ts +37 -0
  268. package/src/services/BackgroundTaskManager.ts +12 -4
  269. package/src/services/BrowserManager.ts +41 -1
  270. package/src/services/MemoryCompressor.ts +20 -32
  271. package/src/services/MemoryService.ts +96 -58
  272. package/src/services/MxfMLService.ts +41 -7
  273. package/src/services/PredictiveAnalyticsService.ts +101 -51
  274. package/src/services/QValueManager.ts +10 -0
  275. package/src/services/RewardSignalProcessor.ts +138 -9
  276. package/src/services/SharedStateService.ts +56 -8
  277. package/src/services/TaskEffectivenessService.ts +114 -187
  278. package/src/services/WebSearchService.ts +2 -1
  279. package/src/services/dag/TaskDagService.ts +2 -0
  280. package/src/services/orpar-memory/OrparMemoryCoordinator.ts +86 -10
  281. package/src/types/MemoryUtilityTypes.ts +2 -0
  282. package/src/utils/Logger.ts +116 -40
  283. package/src/utils/MxpEncryption.ts +69 -34
  284. package/dist/mxp/MxpEventForwardingEnhancer.d.ts +0 -134
  285. package/dist/mxp/MxpEventForwardingEnhancer.d.ts.map +0 -1
  286. package/dist/mxp/MxpEventForwardingEnhancer.js +0 -361
  287. package/dist/mxp/MxpEventForwardingEnhancer.js.map +0 -1
  288. package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts +0 -56
  289. package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts.map +0 -1
  290. package/dist/protocols/mcp/services/ToolSchemaEnhancements.js +0 -57
  291. package/dist/protocols/mcp/services/ToolSchemaEnhancements.js.map +0 -1
  292. package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts +0 -270
  293. package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts.map +0 -1
  294. package/dist/protocols/mcp/tools/ControlLoopLifecycle.js +0 -256
  295. package/dist/protocols/mcp/tools/ControlLoopLifecycle.js.map +0 -1
  296. package/dist/protocols/mcp/tools/LspTools.d.ts +0 -495
  297. package/dist/protocols/mcp/tools/LspTools.d.ts.map +0 -1
  298. package/dist/protocols/mcp/tools/LspTools.js +0 -469
  299. package/dist/protocols/mcp/tools/LspTools.js.map +0 -1
  300. package/src/mxp/MxpEventForwardingEnhancer.ts +0 -471
  301. package/src/protocols/mcp/services/ToolSchemaEnhancements.ts +0 -92
  302. package/src/protocols/mcp/tools/ControlLoopLifecycle.ts +0 -302
  303. package/src/protocols/mcp/tools/LspTools.ts +0 -476
@@ -18,6 +18,9 @@
18
18
  * @documentation https://mxf-dev.github.io/mxf/
19
19
  */
20
20
  import { Logger } from '../../../utils/Logger.js';
21
+ import { TOOL_CATEGORIES } from '../../../constants/ToolNames.js';
22
+ import { defineTool } from '../defineTool.js';
23
+ import { ToolError } from '../ToolError.js';
21
24
  import { executeShellCommand } from './InfrastructureTools.js';
22
25
  import * as fs from 'fs/promises';
23
26
  import * as path from 'path';
@@ -290,17 +293,45 @@ function calculateReviewScore(issues, strictness) {
290
293
  /**
291
294
  * Safety Tools for MXF
292
295
  *
293
- * Provides safety mechanisms for self-modification including rollback,
294
- * backup, and validation tools to ensure safe autonomous code changes.
296
+ * Branch, backup, rollback, test and benchmark tools used to keep autonomous
297
+ * code changes recoverable.
298
+ *
299
+ * Every git invocation here goes through executeShellCommand, so it is validated
300
+ * by the security guard first. That matters most for rollback_changes, which
301
+ * runs `git reset --hard` and `git checkout` — the guard is what stands between
302
+ * a model's rollback request and an unrecoverable working tree.
295
303
  */
296
- export const createFeatureBranchTool = {
304
+ /** Run a git subcommand, throwing a ToolError when git reports failure. */
305
+ async function runGit(args, workingDirectory, context) {
306
+ const result = await executeShellCommand('git', args, {
307
+ context: {
308
+ agentId: context.agentId,
309
+ channelId: context.channelId,
310
+ requestId: context.requestId
311
+ },
312
+ workingDirectory,
313
+ captureOutput: true
314
+ });
315
+ if (result.exitCode !== 0) {
316
+ throw ToolError.executionFailed(`git ${args.join(' ')} failed (exit ${result.exitCode}): ` +
317
+ `${result.stderr?.trim() || 'no error output'}`, { details: { exitCode: result.exitCode, args } });
318
+ }
319
+ return result;
320
+ }
321
+ const workingDirectoryProperty = {
322
+ type: 'string',
323
+ description: 'Working directory (defaults to the server working directory)'
324
+ };
325
+ export const createFeatureBranchTool = defineTool({
297
326
  name: 'create_feature_branch',
298
- description: 'Create a new feature branch to isolate experimental changes',
327
+ category: TOOL_CATEGORIES.VERSION_CONTROL,
328
+ description: 'Create and switch to a new branch off a base branch, to isolate changes.',
299
329
  inputSchema: {
300
330
  type: 'object',
301
331
  properties: {
302
332
  branchName: {
303
333
  type: 'string',
334
+ minLength: 1,
304
335
  description: 'Name for the new feature branch'
305
336
  },
306
337
  baseBranch: {
@@ -308,275 +339,221 @@ export const createFeatureBranchTool = {
308
339
  default: 'main',
309
340
  description: 'Base branch to create the feature branch from'
310
341
  },
311
- workingDirectory: {
312
- type: 'string',
313
- description: 'Working directory (defaults to current directory)'
314
- }
342
+ workingDirectory: workingDirectoryProperty
315
343
  },
316
344
  required: ['branchName']
317
345
  },
318
- handler: async (args, context) => {
346
+ run: async (input, context) => {
347
+ const workingDir = input.workingDirectory || process.cwd();
348
+ const baseBranch = input.baseBranch ?? 'main';
349
+ await runGit(['checkout', baseBranch], workingDir, context);
350
+ // A pull can fail for reasons that do not invalidate the branch (no
351
+ // remote, offline). Record whether it worked rather than failing the tool
352
+ // or pretending it succeeded.
353
+ let pulled = true;
319
354
  try {
320
- const workingDir = args.workingDirectory || process.cwd();
321
- // Ensure we're on the base branch and it's up to date
322
- const checkoutResult = await executeShellCommand('git', ['checkout', args.baseBranch || 'main'], {
323
- workingDirectory: workingDir,
324
- captureOutput: true
325
- });
326
- if (checkoutResult.exitCode !== 0) {
327
- return {
328
- success: false,
329
- branchName: args.branchName,
330
- error: `Failed to checkout base branch: ${checkoutResult.stderr}`
331
- };
332
- }
333
- // Pull latest changes
334
- const pullResult = await executeShellCommand('git', ['pull', 'origin', args.baseBranch || 'main'], {
335
- workingDirectory: workingDir,
336
- captureOutput: true
337
- });
338
- if (pullResult.exitCode !== 0) {
339
- logger.warn('Failed to pull latest changes', { error: pullResult.stderr });
340
- }
341
- // Create and checkout new branch
342
- const branchResult = await executeShellCommand('git', ['checkout', '-b', args.branchName], {
343
- workingDirectory: workingDir,
344
- captureOutput: true
345
- });
346
- if (branchResult.exitCode !== 0) {
347
- return {
348
- success: false,
349
- branchName: args.branchName,
350
- error: `Failed to create branch: ${branchResult.stderr}`
351
- };
352
- }
353
- // Get current commit hash for reference
354
- const hashResult = await executeShellCommand('git', ['rev-parse', 'HEAD'], {
355
- workingDirectory: workingDir,
356
- captureOutput: true
357
- });
358
- const currentHash = hashResult.stdout?.trim() || 'unknown';
359
- return {
360
- success: true,
361
- branchName: args.branchName,
362
- baseBranch: args.baseBranch || 'main',
363
- currentHash,
364
- error: null
365
- };
355
+ await runGit(['pull', 'origin', baseBranch], workingDir, context);
366
356
  }
367
357
  catch (error) {
368
- logger.error('Create feature branch failed', { error });
369
- return {
370
- success: false,
371
- branchName: args.branchName,
372
- error: error instanceof Error ? error.message : String(error)
373
- };
358
+ pulled = false;
359
+ logger.warn(`Could not pull ${baseBranch} before branching: ` +
360
+ `${error instanceof Error ? error.message : String(error)}`);
374
361
  }
362
+ await runGit(['checkout', '-b', input.branchName], workingDir, context);
363
+ const hashResult = await runGit(['rev-parse', 'HEAD'], workingDir, context);
364
+ return {
365
+ branchName: input.branchName,
366
+ baseBranch,
367
+ headCommit: hashResult.stdout?.trim() ?? '',
368
+ pulled
369
+ };
375
370
  }
376
- };
377
- export const runFullTestSuiteTool = {
371
+ });
372
+ export const runFullTestSuiteTool = defineTool({
378
373
  name: 'run_full_test_suite',
379
- description: 'Run the complete test suite to validate changes',
374
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
375
+ description: 'Run the project test suite. passed is false when tests fail. ' +
376
+ 'Uses the project test script unless a command is given.',
380
377
  inputSchema: {
381
378
  type: 'object',
382
379
  properties: {
383
380
  testCommand: {
384
381
  type: 'string',
385
- description: 'Custom test command (defaults to npm test)'
382
+ description: 'Test command to run (defaults to the project test script)'
386
383
  },
387
384
  coverage: {
388
385
  type: 'boolean',
389
386
  default: true,
390
- description: 'Generate test coverage report'
387
+ description: 'Generate a coverage report (jest only)'
391
388
  },
392
389
  bail: {
393
390
  type: 'boolean',
394
391
  default: false,
395
- description: 'Stop on first test failure'
392
+ description: 'Stop at the first failure (jest only)'
396
393
  },
397
- workingDirectory: {
398
- type: 'string',
399
- description: 'Working directory (defaults to current directory)'
400
- }
394
+ workingDirectory: workingDirectoryProperty
401
395
  }
402
396
  },
403
- handler: async (args, context) => {
404
- try {
405
- const workingDir = args.workingDirectory || process.cwd();
406
- // Determine test command
407
- let testCmd;
408
- if (args.testCommand) {
409
- testCmd = args.testCommand.split(' ');
397
+ run: async (input, context) => {
398
+ const workingDir = input.workingDirectory || process.cwd();
399
+ // Work out what to run. An explicit command wins; otherwise read
400
+ // package.json. A project with no test script and no known runner is a
401
+ // precondition failure — running `npm test` anyway would fail confusingly.
402
+ let testCmd;
403
+ if (input.testCommand) {
404
+ testCmd = input.testCommand.split(/\s+/).filter(Boolean);
405
+ }
406
+ else {
407
+ const packageJsonPath = path.join(workingDir, 'package.json');
408
+ let pkg;
409
+ try {
410
+ pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
410
411
  }
411
- else {
412
- // Try to detect test framework
413
- const packageJsonPath = path.join(workingDir, 'package.json');
414
- try {
415
- const packageJson = await fs.readFile(packageJsonPath, 'utf-8');
416
- const pkg = JSON.parse(packageJson);
417
- if (pkg.scripts?.test) {
418
- testCmd = ['npm', 'test'];
419
- }
420
- else if (pkg.devDependencies?.jest || pkg.dependencies?.jest) {
421
- testCmd = ['npx', 'jest'];
422
- }
423
- else if (pkg.devDependencies?.mocha || pkg.dependencies?.mocha) {
424
- testCmd = ['npx', 'mocha'];
425
- }
426
- else {
427
- testCmd = ['npm', 'test'];
428
- }
429
- }
430
- catch {
431
- testCmd = ['npm', 'test'];
432
- }
412
+ catch {
413
+ throw ToolError.notFound(`Could not read ${packageJsonPath}. ` +
414
+ `Pass testCommand explicitly, or run this from the project root.`);
433
415
  }
434
- // Add coverage if requested
435
- if (args.coverage && testCmd.includes('jest')) {
436
- testCmd.push('--coverage');
416
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
417
+ if (pkg.scripts?.test) {
418
+ testCmd = ['npm', 'test'];
437
419
  }
438
- // Add bail if requested
439
- if (args.bail && testCmd.includes('jest')) {
440
- testCmd.push('--bail');
420
+ else if (deps.jest) {
421
+ testCmd = ['npx', 'jest'];
422
+ }
423
+ else if (deps.mocha) {
424
+ testCmd = ['npx', 'mocha'];
425
+ }
426
+ else if (deps.vitest) {
427
+ testCmd = ['npx', 'vitest', 'run'];
428
+ }
429
+ else {
430
+ throw ToolError.notFound(`${packageJsonPath} has no "test" script and no jest, mocha or vitest dependency. ` +
431
+ `Pass testCommand explicitly.`);
441
432
  }
442
- const startTime = Date.now();
443
- const result = await executeShellCommand(testCmd[0], testCmd.slice(1), {
444
- workingDirectory: workingDir,
445
- captureOutput: true
446
- });
447
- const duration = Date.now() - startTime;
448
- // Parse test results
449
- const testResults = parseTestResults(result.stdout || '');
450
- return {
451
- success: result.exitCode === 0,
452
- output: result.stdout || '',
453
- errors: result.stderr || '',
454
- duration,
455
- testResults,
456
- coverage: args.coverage ? parseCoverageResults(result.stdout || '') : null,
457
- error: result.exitCode !== 0 ? result.stderr : null
458
- };
459
433
  }
460
- catch (error) {
461
- logger.error('Run full test suite failed', { error });
462
- return {
463
- success: false,
464
- output: '',
465
- errors: '',
466
- duration: 0,
467
- testResults: null,
468
- coverage: null,
469
- error: error instanceof Error ? error.message : String(error)
470
- };
434
+ const isJest = testCmd.includes('jest');
435
+ if (input.coverage !== false && isJest) {
436
+ testCmd.push('--coverage');
437
+ }
438
+ if (input.bail && isJest) {
439
+ testCmd.push('--bail');
471
440
  }
441
+ const startedAt = Date.now();
442
+ const result = await executeShellCommand(testCmd[0], testCmd.slice(1), {
443
+ context: {
444
+ agentId: context.agentId,
445
+ channelId: context.channelId,
446
+ requestId: context.requestId
447
+ },
448
+ workingDirectory: workingDir,
449
+ captureOutput: true
450
+ });
451
+ // A failing suite exits non-zero. That is the tool working, so it comes
452
+ // back as passed: false rather than as a tool error.
453
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
454
+ return {
455
+ passed: result.exitCode === 0,
456
+ command: testCmd.join(' '),
457
+ durationMs: Date.now() - startedAt,
458
+ testResults: parseTestResults(output),
459
+ coverage: input.coverage !== false ? parseCoverageResults(output) : null,
460
+ output
461
+ };
472
462
  }
473
- };
474
- export const performanceBenchmarkTool = {
463
+ });
464
+ export const performanceBenchmarkTool = defineTool({
475
465
  name: 'performance_benchmark',
476
- description: 'Run performance benchmarks to ensure changes don\'t degrade performance',
466
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
467
+ description: 'Run the project benchmark script and, when a baseline file is given, compare ' +
468
+ 'against it and flag regressions past the threshold.',
477
469
  inputSchema: {
478
470
  type: 'object',
479
471
  properties: {
480
472
  benchmarkCommand: {
481
473
  type: 'string',
482
- description: 'Custom benchmark command (defaults to npm run benchmark)'
474
+ description: 'Benchmark command (defaults to the project benchmark script)'
483
475
  },
484
476
  baselineFile: {
485
477
  type: 'string',
486
- description: 'Path to baseline performance file'
478
+ description: 'Path to a JSON baseline file to compare against'
487
479
  },
488
480
  thresholdPercent: {
489
481
  type: 'number',
490
482
  default: 10,
491
- description: 'Performance degradation threshold percentage'
483
+ minimum: 0,
484
+ description: 'Regression threshold, as a percentage'
492
485
  },
493
- workingDirectory: {
494
- type: 'string',
495
- description: 'Working directory (defaults to current directory)'
496
- }
486
+ workingDirectory: workingDirectoryProperty
497
487
  }
498
488
  },
499
- handler: async (args, context) => {
500
- try {
501
- const workingDir = args.workingDirectory || process.cwd();
502
- // Determine benchmark command
503
- let benchmarkCmd;
504
- if (args.benchmarkCommand) {
505
- benchmarkCmd = args.benchmarkCommand.split(' ');
489
+ run: async (input, context) => {
490
+ const workingDir = input.workingDirectory || process.cwd();
491
+ let benchmarkCmd;
492
+ if (input.benchmarkCommand) {
493
+ benchmarkCmd = input.benchmarkCommand.split(/\s+/).filter(Boolean);
494
+ }
495
+ else {
496
+ const packageJsonPath = path.join(workingDir, 'package.json');
497
+ let pkg;
498
+ try {
499
+ pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
506
500
  }
507
- else {
508
- // Check for common benchmark scripts
509
- const packageJsonPath = path.join(workingDir, 'package.json');
510
- try {
511
- const packageJson = await fs.readFile(packageJsonPath, 'utf-8');
512
- const pkg = JSON.parse(packageJson);
513
- if (pkg.scripts?.benchmark) {
514
- benchmarkCmd = ['npm', 'run', 'benchmark'];
515
- }
516
- else if (pkg.scripts?.perf) {
517
- benchmarkCmd = ['npm', 'run', 'perf'];
518
- }
519
- else {
520
- return {
521
- success: false,
522
- benchmarkResults: null,
523
- baselineComparison: null,
524
- error: 'No benchmark script found in package.json'
525
- };
526
- }
527
- }
528
- catch {
529
- return {
530
- success: false,
531
- benchmarkResults: null,
532
- baselineComparison: null,
533
- error: 'Could not read package.json'
534
- };
535
- }
501
+ catch {
502
+ throw ToolError.notFound(`Could not read ${packageJsonPath}. Pass benchmarkCommand explicitly.`);
536
503
  }
537
- const startTime = Date.now();
538
- const result = await executeShellCommand(benchmarkCmd[0], benchmarkCmd.slice(1), {
539
- workingDirectory: workingDir,
540
- captureOutput: true
541
- });
542
- const duration = Date.now() - startTime;
543
- // Parse benchmark results
544
- const benchmarkResults = parseBenchmarkResults(result.stdout || '');
545
- // Compare with baseline if provided
546
- let baselineComparison = null;
547
- if (args.baselineFile) {
548
- try {
549
- const baselineContent = await fs.readFile(args.baselineFile, 'utf-8');
550
- const baseline = JSON.parse(baselineContent);
551
- baselineComparison = compareWithBaseline(benchmarkResults, baseline, args.thresholdPercent);
552
- }
553
- catch (error) {
554
- logger.warn('Could not read baseline file', { error });
555
- }
504
+ if (pkg.scripts?.benchmark) {
505
+ benchmarkCmd = ['npm', 'run', 'benchmark'];
506
+ }
507
+ else if (pkg.scripts?.perf) {
508
+ benchmarkCmd = ['npm', 'run', 'perf'];
509
+ }
510
+ else {
511
+ throw ToolError.notFound(`${packageJsonPath} has no "benchmark" or "perf" script. ` +
512
+ `Pass benchmarkCommand explicitly.`);
556
513
  }
557
- return {
558
- success: result.exitCode === 0,
559
- output: result.stdout || '',
560
- benchmarkResults,
561
- baselineComparison,
562
- duration,
563
- error: result.exitCode !== 0 ? result.stderr : null
564
- };
565
514
  }
566
- catch (error) {
567
- logger.error('Performance benchmark failed', { error });
568
- return {
569
- success: false,
570
- benchmarkResults: null,
571
- baselineComparison: null,
572
- error: error instanceof Error ? error.message : String(error)
573
- };
515
+ const startedAt = Date.now();
516
+ const result = await executeShellCommand(benchmarkCmd[0], benchmarkCmd.slice(1), {
517
+ context: {
518
+ agentId: context.agentId,
519
+ channelId: context.channelId,
520
+ requestId: context.requestId
521
+ },
522
+ workingDirectory: workingDir,
523
+ captureOutput: true
524
+ });
525
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
526
+ const benchmarkResults = parseBenchmarkResults(output);
527
+ // A baseline the caller named but that cannot be read is an error: a
528
+ // silent null here would read as "no regression".
529
+ let baselineComparison = null;
530
+ if (input.baselineFile) {
531
+ let baseline;
532
+ try {
533
+ baseline = JSON.parse(await fs.readFile(input.baselineFile, 'utf-8'));
534
+ }
535
+ catch (error) {
536
+ throw ToolError.notFound(`Could not read the baseline file ${input.baselineFile}: ` +
537
+ `${error instanceof Error ? error.message : String(error)}`);
538
+ }
539
+ baselineComparison = compareWithBaseline(benchmarkResults, baseline, input.thresholdPercent ?? 10);
574
540
  }
541
+ return {
542
+ passed: result.exitCode === 0,
543
+ command: benchmarkCmd.join(' '),
544
+ durationMs: Date.now() - startedAt,
545
+ benchmarkResults,
546
+ baselineComparison,
547
+ output
548
+ };
575
549
  }
576
- };
577
- export const rollbackChangesTool = {
550
+ });
551
+ export const rollbackChangesTool = defineTool({
578
552
  name: 'rollback_changes',
579
- description: 'Rollback changes to a previous safe state',
553
+ category: TOOL_CATEGORIES.VERSION_CONTROL,
554
+ description: 'Roll the repository back to a previous state: reset to a commit, switch to a ' +
555
+ 'branch, or pop the latest stash. With preserveChanges the working tree is kept ' +
556
+ '(soft reset / stash); without it, a commit rollback discards uncommitted work.',
580
557
  inputSchema: {
581
558
  type: 'object',
582
559
  properties: {
@@ -584,127 +561,67 @@ export const rollbackChangesTool = {
584
561
  type: 'string',
585
562
  enum: ['commit', 'branch', 'stash'],
586
563
  default: 'commit',
587
- description: 'Type of rollback to perform'
564
+ description: 'What to roll back to'
588
565
  },
589
566
  targetCommit: {
590
567
  type: 'string',
591
- description: 'Commit hash to rollback to (for commit rollback)'
568
+ description: 'Commit to reset to (required when rollbackType is "commit")'
592
569
  },
593
570
  targetBranch: {
594
571
  type: 'string',
595
- description: 'Branch to rollback to (for branch rollback)'
572
+ description: 'Branch to switch to (required when rollbackType is "branch")'
596
573
  },
597
574
  preserveChanges: {
598
575
  type: 'boolean',
599
576
  default: false,
600
- description: 'Preserve changes in working directory'
577
+ description: 'Keep uncommitted work (soft reset, or stash before switching)'
601
578
  },
602
- workingDirectory: {
603
- type: 'string',
604
- description: 'Working directory (defaults to current directory)'
605
- }
579
+ workingDirectory: workingDirectoryProperty
606
580
  }
607
581
  },
608
- handler: async (args, context) => {
609
- try {
610
- const workingDir = args.workingDirectory || process.cwd();
611
- let result;
612
- switch (args.rollbackType) {
613
- case 'commit':
614
- if (!args.targetCommit) {
615
- return {
616
- success: false,
617
- rollbackType: args.rollbackType,
618
- error: 'Target commit required for commit rollback'
619
- };
620
- }
621
- if (args.preserveChanges) {
622
- // Soft reset to preserve changes
623
- result = await executeShellCommand('git', ['reset', '--soft', args.targetCommit], {
624
- workingDirectory: workingDir,
625
- captureOutput: true
626
- });
627
- }
628
- else {
629
- // Hard reset to discard changes
630
- result = await executeShellCommand('git', ['reset', '--hard', args.targetCommit], {
631
- workingDirectory: workingDir,
632
- captureOutput: true
633
- });
634
- }
635
- break;
636
- case 'branch':
637
- if (!args.targetBranch) {
638
- return {
639
- success: false,
640
- rollbackType: args.rollbackType,
641
- error: 'Target branch required for branch rollback'
642
- };
643
- }
644
- // Stash current changes if preserving
645
- if (args.preserveChanges) {
646
- await executeShellCommand('git', ['stash', 'push', '-m', 'Rollback preservation stash'], {
647
- workingDirectory: workingDir,
648
- captureOutput: true
649
- });
650
- }
651
- // Checkout target branch
652
- result = await executeShellCommand('git', ['checkout', args.targetBranch], {
653
- workingDirectory: workingDir,
654
- captureOutput: true
655
- });
656
- break;
657
- case 'stash':
658
- // Pop the latest stash
659
- result = await executeShellCommand('git', ['stash', 'pop'], {
660
- workingDirectory: workingDir,
661
- captureOutput: true
662
- });
663
- break;
664
- default:
665
- return {
666
- success: false,
667
- rollbackType: args.rollbackType,
668
- error: 'Invalid rollback type'
669
- };
582
+ run: async (input, context) => {
583
+ const workingDir = input.workingDirectory || process.cwd();
584
+ const rollbackType = input.rollbackType ?? 'commit';
585
+ const preserveChanges = input.preserveChanges ?? false;
586
+ switch (rollbackType) {
587
+ case 'commit': {
588
+ if (!input.targetCommit) {
589
+ throw ToolError.invalidInput('targetCommit is required when rollbackType is "commit".');
590
+ }
591
+ // --hard discards uncommitted work irrecoverably; --soft keeps it.
592
+ const mode = preserveChanges ? '--soft' : '--hard';
593
+ await runGit(['reset', mode, input.targetCommit], workingDir, context);
594
+ break;
670
595
  }
671
- if (result.exitCode !== 0) {
672
- return {
673
- success: false,
674
- rollbackType: args.rollbackType,
675
- error: result.stderr || 'Rollback failed'
676
- };
596
+ case 'branch': {
597
+ if (!input.targetBranch) {
598
+ throw ToolError.invalidInput('targetBranch is required when rollbackType is "branch".');
599
+ }
600
+ if (preserveChanges) {
601
+ await runGit(['stash', 'push', '-m', 'Rollback preservation stash'], workingDir, context);
602
+ }
603
+ await runGit(['checkout', input.targetBranch], workingDir, context);
604
+ break;
677
605
  }
678
- // Get current status after rollback
679
- const statusResult = await executeShellCommand('git', ['status', '--porcelain'], {
680
- workingDirectory: workingDir,
681
- captureOutput: true
682
- });
683
- const currentBranchResult = await executeShellCommand('git', ['branch', '--show-current'], {
684
- workingDirectory: workingDir,
685
- captureOutput: true
686
- });
687
- return {
688
- success: true,
689
- rollbackType: args.rollbackType,
690
- currentBranch: currentBranchResult.stdout?.trim() || 'unknown',
691
- hasUncommittedChanges: (statusResult.stdout || '').trim().length > 0,
692
- error: null
693
- };
694
- }
695
- catch (error) {
696
- logger.error('Rollback changes failed', { error });
697
- return {
698
- success: false,
699
- rollbackType: args.rollbackType,
700
- error: error instanceof Error ? error.message : String(error)
701
- };
606
+ case 'stash':
607
+ await runGit(['stash', 'pop'], workingDir, context);
608
+ break;
702
609
  }
610
+ const statusResult = await runGit(['status', '--porcelain'], workingDir, context);
611
+ const branchResult = await runGit(['branch', '--show-current'], workingDir, context);
612
+ return {
613
+ rollbackType,
614
+ currentBranch: branchResult.stdout?.trim() ?? '',
615
+ hasUncommittedChanges: (statusResult.stdout ?? '').trim().length > 0,
616
+ preservedChanges: preserveChanges
617
+ };
703
618
  }
704
- };
705
- export const createBackupTool = {
619
+ });
620
+ export const createBackupTool = defineTool({
706
621
  name: 'create_backup',
707
- description: 'Create a backup of the current state before making changes',
622
+ category: TOOL_CATEGORIES.VERSION_CONTROL,
623
+ description: 'Snapshot the current state before making changes, as a commit, a branch, a stash, ' +
624
+ 'or a tar.gz archive. Returns the identifier needed to restore it.',
708
625
  inputSchema: {
709
626
  type: 'object',
710
627
  properties: {
@@ -712,140 +629,79 @@ export const createBackupTool = {
712
629
  type: 'string',
713
630
  enum: ['commit', 'branch', 'stash', 'archive'],
714
631
  default: 'commit',
715
- description: 'Type of backup to create'
632
+ description: 'How to store the backup'
716
633
  },
717
634
  backupName: {
718
635
  type: 'string',
719
- description: 'Name for the backup'
636
+ description: 'Name for the backup (defaults to a timestamp)'
720
637
  },
721
638
  includeUntracked: {
722
639
  type: 'boolean',
723
640
  default: true,
724
- description: 'Include untracked files in backup'
641
+ description: 'Include untracked files'
725
642
  },
726
- workingDirectory: {
727
- type: 'string',
728
- description: 'Working directory (defaults to current directory)'
729
- }
643
+ workingDirectory: workingDirectoryProperty
730
644
  }
731
645
  },
732
- handler: async (args, context) => {
733
- try {
734
- const workingDir = args.workingDirectory || process.cwd();
735
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
736
- const backupName = args.backupName || `backup-${timestamp}`;
737
- let result;
738
- let backupId;
739
- switch (args.backupType) {
740
- case 'commit':
741
- // Add all changes
742
- if (args.includeUntracked) {
743
- await executeShellCommand('git', ['add', '-A'], {
744
- workingDirectory: workingDir,
745
- captureOutput: true
746
- });
747
- }
748
- else {
749
- await executeShellCommand('git', ['add', '-u'], {
750
- workingDirectory: workingDir,
751
- captureOutput: true
752
- });
753
- }
754
- // Create commit
755
- result = await executeShellCommand('git', ['commit', '-m', `Backup: ${backupName}`], {
756
- workingDirectory: workingDir,
757
- captureOutput: true
758
- });
759
- if (result.exitCode === 0) {
760
- const hashResult = await executeShellCommand('git', ['rev-parse', 'HEAD'], {
761
- workingDirectory: workingDir,
762
- captureOutput: true
763
- });
764
- backupId = hashResult.stdout?.trim();
765
- }
766
- break;
767
- case 'branch':
768
- // Create backup branch
769
- result = await executeShellCommand('git', ['checkout', '-b', backupName], {
770
- workingDirectory: workingDir,
771
- captureOutput: true
772
- });
773
- if (result.exitCode === 0) {
774
- backupId = backupName;
775
- // Switch back to original branch
776
- const originalBranchResult = await executeShellCommand('git', ['checkout', '-'], {
777
- workingDirectory: workingDir,
778
- captureOutput: true
779
- });
780
- if (originalBranchResult.exitCode !== 0) {
781
- logger.warn('Could not switch back to original branch');
782
- }
783
- }
784
- break;
785
- case 'stash':
786
- // Create stash
787
- const stashArgs = ['stash', 'push', '-m', backupName];
788
- if (args.includeUntracked) {
789
- stashArgs.push('--include-untracked');
790
- }
791
- result = await executeShellCommand('git', stashArgs, {
792
- workingDirectory: workingDir,
793
- captureOutput: true
794
- });
795
- if (result.exitCode === 0) {
796
- backupId = backupName;
797
- }
798
- break;
799
- case 'archive':
800
- // Create archive file
801
- const archiveName = `${backupName}.tar.gz`;
802
- const archivePath = path.join(workingDir, archiveName);
803
- result = await executeShellCommand('git', ['archive', '--format=tar.gz', '--output', archivePath, 'HEAD'], {
804
- workingDirectory: workingDir,
805
- captureOutput: true
806
- });
807
- if (result.exitCode === 0) {
808
- backupId = archivePath;
809
- }
810
- break;
811
- default:
812
- return {
813
- success: false,
814
- backupType: args.backupType,
815
- error: 'Invalid backup type'
816
- };
646
+ run: async (input, context) => {
647
+ const workingDir = input.workingDirectory || process.cwd();
648
+ const backupType = input.backupType ?? 'commit';
649
+ const includeUntracked = input.includeUntracked ?? true;
650
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
651
+ const backupName = input.backupName || `backup-${timestamp}`;
652
+ let backupId;
653
+ switch (backupType) {
654
+ case 'commit': {
655
+ await runGit(['add', includeUntracked ? '-A' : '-u'], workingDir, context);
656
+ await runGit(['commit', '-m', `Backup: ${backupName}`], workingDir, context);
657
+ const hashResult = await runGit(['rev-parse', 'HEAD'], workingDir, context);
658
+ backupId = hashResult.stdout?.trim() ?? '';
659
+ break;
817
660
  }
818
- if (result.exitCode !== 0) {
819
- return {
820
- success: false,
821
- backupType: args.backupType,
822
- backupName,
823
- error: result.stderr || 'Backup creation failed'
824
- };
661
+ case 'branch': {
662
+ await runGit(['checkout', '-b', backupName], workingDir, context);
663
+ // Return to where we were — a backup that leaves the caller on the
664
+ // backup branch is a trap.
665
+ await runGit(['checkout', '-'], workingDir, context);
666
+ backupId = backupName;
667
+ break;
668
+ }
669
+ case 'stash': {
670
+ const stashArgs = ['stash', 'push', '-m', backupName];
671
+ if (includeUntracked) {
672
+ stashArgs.push('--include-untracked');
673
+ }
674
+ await runGit(stashArgs, workingDir, context);
675
+ backupId = backupName;
676
+ break;
677
+ }
678
+ case 'archive': {
679
+ const archivePath = path.join(workingDir, `${backupName}.tar.gz`);
680
+ await runGit(['archive', '--format=tar.gz', '--output', archivePath, 'HEAD'], workingDir, context);
681
+ backupId = archivePath;
682
+ break;
825
683
  }
826
- return {
827
- success: true,
828
- backupType: args.backupType,
829
- backupName,
830
- backupId,
831
- timestamp,
832
- error: null
833
- };
834
- }
835
- catch (error) {
836
- logger.error('Create backup failed', { error });
837
- return {
838
- success: false,
839
- backupType: args.backupType,
840
- backupName: args.backupName,
841
- error: error instanceof Error ? error.message : String(error)
842
- };
843
684
  }
685
+ return {
686
+ backupType,
687
+ backupName,
688
+ backupId,
689
+ timestamp
690
+ };
844
691
  }
845
- };
846
- export const codeReviewAgentTool = {
692
+ });
693
+ export const codeReviewAgentTool = defineTool({
847
694
  name: 'code_review_agent',
848
- description: 'AI-powered code review agent that analyzes changes for quality, security, and best practices',
695
+ category: TOOL_CATEGORIES.INFRASTRUCTURE,
696
+ // What this actually does: match a fixed set of regex patterns per focus area
697
+ // (eval(, innerHTML=, hardcoded secrets, TODO/FIXME, debugger, console.log,
698
+ // `any`, long functions...) and score the hits. No model is involved. The old
699
+ // description called it an "AI-powered code review agent", which set an
700
+ // expectation the implementation cannot meet.
701
+ description: 'Scan changed files for a fixed set of known-bad patterns (eval, innerHTML assignment, ' +
702
+ 'hardcoded secrets, debugger statements, TODO comments, long functions) and return the ' +
703
+ 'hits with a score. This is pattern matching, not a semantic review — it will not catch ' +
704
+ 'logic errors, and it will flag patterns that are fine in context.',
849
705
  inputSchema: {
850
706
  type: 'object',
851
707
  properties: {
@@ -853,101 +709,80 @@ export const codeReviewAgentTool = {
853
709
  type: 'string',
854
710
  enum: ['diff', 'files', 'pull_request'],
855
711
  default: 'diff',
856
- description: 'Type of code review to perform'
712
+ description: 'Which files to scan: the working diff, an explicit list, or the diff against main'
857
713
  },
858
714
  files: {
859
715
  type: 'array',
860
716
  items: { type: 'string' },
861
- description: 'Specific files to review (for files review type)'
717
+ description: 'Files to scan (required when reviewType is "files")'
862
718
  },
863
719
  focusAreas: {
864
720
  type: 'array',
865
- items: { type: 'string' },
721
+ items: {
722
+ type: 'string',
723
+ enum: ['quality', 'security', 'performance', 'maintainability']
724
+ },
866
725
  default: ['quality', 'security', 'performance', 'maintainability'],
867
- description: 'Areas to focus on during review'
726
+ description: 'Which pattern sets to apply'
868
727
  },
869
728
  strictness: {
870
729
  type: 'string',
871
730
  enum: ['low', 'medium', 'high'],
872
731
  default: 'medium',
873
- description: 'Review strictness level'
732
+ description: 'How heavily each finding counts against the score'
874
733
  },
875
- workingDirectory: {
876
- type: 'string',
877
- description: 'Working directory (defaults to current directory)'
878
- }
734
+ workingDirectory: workingDirectoryProperty
879
735
  }
880
736
  },
881
- handler: async (args, context) => {
882
- try {
883
- const workingDir = args.workingDirectory || process.cwd();
884
- const reviewResults = {
885
- summary: {},
886
- issues: [],
887
- recommendations: [],
888
- score: 0
889
- };
890
- let filesToReview = [];
891
- // Get files to review based on type
892
- switch (args.reviewType) {
893
- case 'diff':
894
- const diffResult = await executeShellCommand('git', ['diff', '--name-only', 'HEAD'], {
895
- workingDirectory: workingDir,
896
- captureOutput: true
897
- });
898
- if (diffResult.exitCode === 0) {
899
- filesToReview = diffResult.stdout?.split('\n').filter(f => f.trim()) || [];
900
- }
901
- break;
902
- case 'files':
903
- filesToReview = args.files || [];
904
- break;
905
- case 'pull_request':
906
- // Compare with main branch
907
- const prDiffResult = await executeShellCommand('git', ['diff', '--name-only', 'main...HEAD'], {
908
- workingDirectory: workingDir,
909
- captureOutput: true
910
- });
911
- if (prDiffResult.exitCode === 0) {
912
- filesToReview = prDiffResult.stdout?.split('\n').filter(f => f.trim()) || [];
913
- }
914
- break;
737
+ run: async (input, context) => {
738
+ const workingDir = input.workingDirectory || process.cwd();
739
+ const reviewType = input.reviewType ?? 'diff';
740
+ const strictness = input.strictness ?? 'medium';
741
+ const focusAreas = input.focusAreas ?? ['quality', 'security', 'performance', 'maintainability'];
742
+ let filesToReview;
743
+ switch (reviewType) {
744
+ case 'diff': {
745
+ const diffResult = await runGit(['diff', '--name-only', 'HEAD'], workingDir, context);
746
+ filesToReview = (diffResult.stdout ?? '').split('\n').filter(f => f.trim());
747
+ break;
915
748
  }
916
- // Review each file
917
- for (const file of filesToReview) {
918
- const fileReview = await reviewFile(file, args.focusAreas, args.strictness, workingDir);
919
- reviewResults.issues.push(...fileReview.issues);
920
- reviewResults.recommendations.push(...fileReview.recommendations);
749
+ case 'pull_request': {
750
+ const prDiffResult = await runGit(['diff', '--name-only', 'main...HEAD'], workingDir, context);
751
+ filesToReview = (prDiffResult.stdout ?? '').split('\n').filter(f => f.trim());
752
+ break;
921
753
  }
922
- // Calculate overall score
923
- reviewResults.score = calculateReviewScore(reviewResults.issues, args.strictness);
924
- // Generate summary
925
- reviewResults.summary = {
926
- filesReviewed: filesToReview.length,
927
- totalIssues: reviewResults.issues.length,
928
- criticalIssues: reviewResults.issues.filter(i => i.severity === 'critical').length,
929
- highIssues: reviewResults.issues.filter(i => i.severity === 'high').length,
930
- mediumIssues: reviewResults.issues.filter(i => i.severity === 'medium').length,
931
- lowIssues: reviewResults.issues.filter(i => i.severity === 'low').length,
932
- overallScore: reviewResults.score
933
- };
934
- return {
935
- success: true,
936
- reviewResults,
937
- error: null
938
- };
754
+ case 'files':
755
+ default:
756
+ if (!input.files || input.files.length === 0) {
757
+ throw ToolError.invalidInput('files is required and must be non-empty when reviewType is "files".');
758
+ }
759
+ filesToReview = input.files;
760
+ break;
939
761
  }
940
- catch (error) {
941
- logger.error('Code review agent failed', { error });
942
- return {
943
- success: false,
944
- reviewResults: null,
945
- error: error instanceof Error ? error.message : String(error)
946
- };
762
+ const issues = [];
763
+ const recommendations = [];
764
+ for (const file of filesToReview) {
765
+ const fileReview = await reviewFile(file, focusAreas, strictness, workingDir);
766
+ issues.push(...fileReview.issues);
767
+ recommendations.push(...fileReview.recommendations);
947
768
  }
769
+ const score = calculateReviewScore(issues, strictness);
770
+ return {
771
+ summary: {
772
+ filesReviewed: filesToReview.length,
773
+ totalIssues: issues.length,
774
+ criticalIssues: issues.filter(i => i.severity === 'critical').length,
775
+ highIssues: issues.filter(i => i.severity === 'high').length,
776
+ mediumIssues: issues.filter(i => i.severity === 'medium').length,
777
+ lowIssues: issues.filter(i => i.severity === 'low').length,
778
+ overallScore: score
779
+ },
780
+ issues,
781
+ recommendations,
782
+ score
783
+ };
948
784
  }
949
- };
950
- // Export all safety tools
785
+ });
951
786
  export const safetyTools = [
952
787
  createFeatureBranchTool,
953
788
  runFullTestSuiteTool,