@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.
- package/README.md +18 -0
- package/dist/adapters/FrameworkAdapter.js +1 -1
- package/dist/adapters/FrameworkAdapter.js.map +1 -1
- package/dist/config/ConfigManager.d.ts +9 -8
- package/dist/config/ConfigManager.d.ts.map +1 -1
- package/dist/config/ConfigManager.js +47 -17
- package/dist/config/ConfigManager.js.map +1 -1
- package/dist/config/memory-strata.config.d.ts +25 -0
- package/dist/config/memory-strata.config.d.ts.map +1 -0
- package/dist/config/memory-strata.config.js +107 -0
- package/dist/config/memory-strata.config.js.map +1 -0
- package/dist/constants/ToolNames.d.ts +1 -29
- package/dist/constants/ToolNames.d.ts.map +1 -1
- package/dist/constants/ToolNames.js +4 -17
- package/dist/constants/ToolNames.js.map +1 -1
- package/dist/database/DatabaseAdapterFactory.d.ts +14 -2
- package/dist/database/DatabaseAdapterFactory.d.ts.map +1 -1
- package/dist/database/DatabaseAdapterFactory.js +21 -6
- package/dist/database/DatabaseAdapterFactory.js.map +1 -1
- package/dist/database/adapters/mongodb/MongoBaseRepository.d.ts +7 -1
- package/dist/database/adapters/mongodb/MongoBaseRepository.d.ts.map +1 -1
- package/dist/database/adapters/mongodb/MongoBaseRepository.js +12 -2
- package/dist/database/adapters/mongodb/MongoBaseRepository.js.map +1 -1
- package/dist/events/ClientEventBus.d.ts +16 -77
- package/dist/events/ClientEventBus.d.ts.map +1 -1
- package/dist/events/ClientEventBus.js +38 -311
- package/dist/events/ClientEventBus.js.map +1 -1
- package/dist/events/EventBus.d.ts +1 -1
- package/dist/events/EventBus.d.ts.map +1 -1
- package/dist/events/EventBus.js +5 -4
- package/dist/events/EventBus.js.map +1 -1
- package/dist/events/EventBusBase.d.ts +139 -34
- package/dist/events/EventBusBase.d.ts.map +1 -1
- package/dist/events/EventBusBase.js +366 -88
- package/dist/events/EventBusBase.js.map +1 -1
- package/dist/events/EventNames.d.ts +10 -1
- package/dist/events/EventNames.d.ts.map +1 -1
- package/dist/events/EventNames.js +3 -0
- package/dist/events/EventNames.js.map +1 -1
- package/dist/events/PublicEvents.d.ts +6 -2
- package/dist/events/PublicEvents.d.ts.map +1 -1
- package/dist/events/PublicEvents.js +7 -4
- package/dist/events/PublicEvents.js.map +1 -1
- package/dist/events/ServerEventBus.d.ts +23 -112
- package/dist/events/ServerEventBus.d.ts.map +1 -1
- package/dist/events/ServerEventBus.js +58 -323
- package/dist/events/ServerEventBus.js.map +1 -1
- package/dist/events/event-definitions/LlmBudgetEvents.d.ts +68 -0
- package/dist/events/event-definitions/LlmBudgetEvents.d.ts.map +1 -0
- package/dist/events/event-definitions/LlmBudgetEvents.js +35 -0
- package/dist/events/event-definitions/LlmBudgetEvents.js.map +1 -0
- package/dist/interfaces/IMemoryPersistence.d.ts +41 -0
- package/dist/interfaces/IMemoryPersistence.d.ts.map +1 -0
- package/dist/interfaces/IMemoryPersistence.js +15 -0
- package/dist/interfaces/IMemoryPersistence.js.map +1 -0
- package/dist/middleware/MxpMiddleware.d.ts +8 -1
- package/dist/middleware/MxpMiddleware.d.ts.map +1 -1
- package/dist/middleware/MxpMiddleware.js +42 -20
- package/dist/middleware/MxpMiddleware.js.map +1 -1
- package/dist/models/channelKey.d.ts +1 -0
- package/dist/models/channelKey.d.ts.map +1 -1
- package/dist/models/channelKey.js +5 -0
- package/dist/models/channelKey.js.map +1 -1
- package/dist/models/memoryUtility.d.ts +39 -0
- package/dist/models/memoryUtility.d.ts.map +1 -0
- package/dist/models/memoryUtility.js +39 -0
- package/dist/models/memoryUtility.js.map +1 -0
- package/dist/models/user.d.ts.map +1 -1
- package/dist/models/user.js +23 -1
- package/dist/models/user.js.map +1 -1
- package/dist/mxp/BinaryProtocolLayer.d.ts +24 -8
- package/dist/mxp/BinaryProtocolLayer.d.ts.map +1 -1
- package/dist/mxp/BinaryProtocolLayer.js +90 -103
- package/dist/mxp/BinaryProtocolLayer.js.map +1 -1
- package/dist/mxp/ContextCompressionEngine.d.ts +57 -10
- package/dist/mxp/ContextCompressionEngine.d.ts.map +1 -1
- package/dist/mxp/ContextCompressionEngine.js +170 -128
- package/dist/mxp/ContextCompressionEngine.js.map +1 -1
- package/dist/mxp/MxpTokenOptimizer.d.ts +7 -3
- package/dist/mxp/MxpTokenOptimizer.d.ts.map +1 -1
- package/dist/mxp/MxpTokenOptimizer.js +8 -5
- package/dist/mxp/MxpTokenOptimizer.js.map +1 -1
- package/dist/prompts/MxfAgentSystemPrompt.d.ts +1 -1
- package/dist/prompts/MxfAgentSystemPrompt.d.ts.map +1 -1
- package/dist/prompts/MxfAgentSystemPrompt.js +47 -57
- package/dist/prompts/MxfAgentSystemPrompt.js.map +1 -1
- package/dist/protocols/mcp/ToolError.d.ts +123 -0
- package/dist/protocols/mcp/ToolError.d.ts.map +1 -0
- package/dist/protocols/mcp/ToolError.js +108 -0
- package/dist/protocols/mcp/ToolError.js.map +1 -0
- package/dist/protocols/mcp/defineTool.d.ts +133 -0
- package/dist/protocols/mcp/defineTool.d.ts.map +1 -0
- package/dist/protocols/mcp/defineTool.js +236 -0
- package/dist/protocols/mcp/defineTool.js.map +1 -0
- package/dist/protocols/mcp/security/HttpTargetGuard.d.ts +38 -0
- package/dist/protocols/mcp/security/HttpTargetGuard.d.ts.map +1 -0
- package/dist/protocols/mcp/security/HttpTargetGuard.js +227 -0
- package/dist/protocols/mcp/security/HttpTargetGuard.js.map +1 -0
- package/dist/protocols/mcp/security/McpSecurityGuard.d.ts +9 -1
- package/dist/protocols/mcp/security/McpSecurityGuard.d.ts.map +1 -1
- package/dist/protocols/mcp/security/McpSecurityGuard.js +10 -2
- package/dist/protocols/mcp/security/McpSecurityGuard.js.map +1 -1
- package/dist/protocols/mcp/security/McpToolPolicy.d.ts +81 -0
- package/dist/protocols/mcp/security/McpToolPolicy.d.ts.map +1 -0
- package/dist/protocols/mcp/security/McpToolPolicy.js +202 -0
- package/dist/protocols/mcp/security/McpToolPolicy.js.map +1 -0
- package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts +51 -18
- package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts.map +1 -1
- package/dist/protocols/mcp/services/ExternalMcpServerManager.js +256 -283
- package/dist/protocols/mcp/services/ExternalMcpServerManager.js.map +1 -1
- package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts +39 -6
- package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts.map +1 -1
- package/dist/protocols/mcp/services/ExternalServerConfigs.js +76 -18
- package/dist/protocols/mcp/services/ExternalServerConfigs.js.map +1 -1
- package/dist/protocols/mcp/tools/AnalyticsTools.js +1 -1
- package/dist/protocols/mcp/tools/AnalyticsTools.js.map +1 -1
- package/dist/protocols/mcp/tools/CodeAnalysisTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/CodeAnalysisTools.js +15 -0
- package/dist/protocols/mcp/tools/CodeAnalysisTools.js.map +1 -1
- package/dist/protocols/mcp/tools/EffectivenessTools.js +3 -3
- package/dist/protocols/mcp/tools/EffectivenessTools.js.map +1 -1
- package/dist/protocols/mcp/tools/GitTools.d.ts +9 -369
- package/dist/protocols/mcp/tools/GitTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/GitTools.js +222 -517
- package/dist/protocols/mcp/tools/GitTools.js.map +1 -1
- package/dist/protocols/mcp/tools/InfrastructureTools.d.ts +56 -24
- package/dist/protocols/mcp/tools/InfrastructureTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/InfrastructureTools.js +117 -26
- package/dist/protocols/mcp/tools/InfrastructureTools.js.map +1 -1
- package/dist/protocols/mcp/tools/MemorySearchTools.d.ts +0 -3
- package/dist/protocols/mcp/tools/MemorySearchTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/MemorySearchTools.js +58 -1
- package/dist/protocols/mcp/tools/MemorySearchTools.js.map +1 -1
- package/dist/protocols/mcp/tools/OrparTools.d.ts +8 -0
- package/dist/protocols/mcp/tools/OrparTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/OrparTools.js +66 -0
- package/dist/protocols/mcp/tools/OrparTools.js.map +1 -1
- package/dist/protocols/mcp/tools/SafetyTools.d.ts +7 -525
- package/dist/protocols/mcp/tools/SafetyTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/SafetyTools.js +349 -514
- package/dist/protocols/mcp/tools/SafetyTools.js.map +1 -1
- package/dist/protocols/mcp/tools/TaskPlanningTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/TaskPlanningTools.js +56 -26
- package/dist/protocols/mcp/tools/TaskPlanningTools.js.map +1 -1
- package/dist/protocols/mcp/tools/TestTools.d.ts +15 -376
- package/dist/protocols/mcp/tools/TestTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/TestTools.js +241 -363
- package/dist/protocols/mcp/tools/TestTools.js.map +1 -1
- package/dist/protocols/mcp/tools/TypeScriptTools.d.ts +6 -276
- package/dist/protocols/mcp/tools/TypeScriptTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/TypeScriptTools.js +223 -337
- package/dist/protocols/mcp/tools/TypeScriptTools.js.map +1 -1
- package/dist/protocols/mcp/tools/WebTools.d.ts +15 -728
- package/dist/protocols/mcp/tools/WebTools.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/WebTools.js +151 -257
- package/dist/protocols/mcp/tools/WebTools.js.map +1 -1
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts +5 -3
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts.map +1 -1
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js +31 -6
- package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js.map +1 -1
- package/dist/schemas/EventPayloadSchema.d.ts +21 -0
- package/dist/schemas/EventPayloadSchema.d.ts.map +1 -1
- package/dist/schemas/EventPayloadSchema.js +14 -0
- package/dist/schemas/EventPayloadSchema.js.map +1 -1
- package/dist/services/BackgroundTaskManager.d.ts.map +1 -1
- package/dist/services/BackgroundTaskManager.js +12 -4
- package/dist/services/BackgroundTaskManager.js.map +1 -1
- package/dist/services/BrowserManager.d.ts +1 -1
- package/dist/services/BrowserManager.d.ts.map +1 -1
- package/dist/services/BrowserManager.js +26 -3
- package/dist/services/BrowserManager.js.map +1 -1
- package/dist/services/MemoryCompressor.d.ts +11 -2
- package/dist/services/MemoryCompressor.d.ts.map +1 -1
- package/dist/services/MemoryCompressor.js +8 -26
- package/dist/services/MemoryCompressor.js.map +1 -1
- package/dist/services/MemoryService.d.ts +16 -3
- package/dist/services/MemoryService.d.ts.map +1 -1
- package/dist/services/MemoryService.js +79 -55
- package/dist/services/MemoryService.js.map +1 -1
- package/dist/services/MxfMLService.d.ts +5 -0
- package/dist/services/MxfMLService.d.ts.map +1 -1
- package/dist/services/MxfMLService.js +32 -6
- package/dist/services/MxfMLService.js.map +1 -1
- package/dist/services/PredictiveAnalyticsService.d.ts +45 -9
- package/dist/services/PredictiveAnalyticsService.d.ts.map +1 -1
- package/dist/services/PredictiveAnalyticsService.js +82 -44
- package/dist/services/PredictiveAnalyticsService.js.map +1 -1
- package/dist/services/QValueManager.d.ts +7 -0
- package/dist/services/QValueManager.d.ts.map +1 -1
- package/dist/services/QValueManager.js +9 -0
- package/dist/services/QValueManager.js.map +1 -1
- package/dist/services/RewardSignalProcessor.d.ts +38 -1
- package/dist/services/RewardSignalProcessor.d.ts.map +1 -1
- package/dist/services/RewardSignalProcessor.js +109 -7
- package/dist/services/RewardSignalProcessor.js.map +1 -1
- package/dist/services/SharedStateService.d.ts +16 -2
- package/dist/services/SharedStateService.d.ts.map +1 -1
- package/dist/services/SharedStateService.js +44 -3
- package/dist/services/SharedStateService.js.map +1 -1
- package/dist/services/TaskEffectivenessService.d.ts +34 -18
- package/dist/services/TaskEffectivenessService.d.ts.map +1 -1
- package/dist/services/TaskEffectivenessService.js +91 -155
- package/dist/services/TaskEffectivenessService.js.map +1 -1
- package/dist/services/WebSearchService.d.ts.map +1 -1
- package/dist/services/WebSearchService.js.map +1 -1
- package/dist/services/dag/TaskDagService.d.ts.map +1 -1
- package/dist/services/dag/TaskDagService.js +2 -0
- package/dist/services/dag/TaskDagService.js.map +1 -1
- package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts +7 -0
- package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts.map +1 -1
- package/dist/services/orpar-memory/OrparMemoryCoordinator.js +71 -10
- package/dist/services/orpar-memory/OrparMemoryCoordinator.js.map +1 -1
- package/dist/types/MemoryUtilityTypes.d.ts +2 -0
- package/dist/types/MemoryUtilityTypes.d.ts.map +1 -1
- package/dist/types/MemoryUtilityTypes.js.map +1 -1
- package/dist/utils/Logger.d.ts +43 -3
- package/dist/utils/Logger.d.ts.map +1 -1
- package/dist/utils/Logger.js +78 -36
- package/dist/utils/Logger.js.map +1 -1
- package/dist/utils/MxpEncryption.d.ts +19 -6
- package/dist/utils/MxpEncryption.d.ts.map +1 -1
- package/dist/utils/MxpEncryption.js +48 -16
- package/dist/utils/MxpEncryption.js.map +1 -1
- package/package.json +19 -4
- package/src/adapters/FrameworkAdapter.ts +1 -1
- package/src/config/ConfigManager.ts +52 -19
- package/src/config/memory-strata.config.ts +136 -0
- package/src/constants/ToolNames.ts +4 -18
- package/src/database/DatabaseAdapterFactory.ts +23 -6
- package/src/database/adapters/mongodb/MongoBaseRepository.ts +16 -2
- package/src/events/ClientEventBus.ts +68 -403
- package/src/events/EventBus.ts +7 -8
- package/src/events/EventBusBase.ts +479 -116
- package/src/events/EventNames.ts +6 -0
- package/src/events/PublicEvents.ts +7 -4
- package/src/events/ServerEventBus.ts +78 -473
- package/src/events/event-definitions/LlmBudgetEvents.ts +74 -0
- package/src/interfaces/IMemoryPersistence.ts +51 -0
- package/src/middleware/MxpMiddleware.ts +51 -24
- package/src/models/channelKey.ts +18 -4
- package/src/models/memoryUtility.ts +64 -0
- package/src/models/user.ts +36 -4
- package/src/mxp/BinaryProtocolLayer.ts +108 -124
- package/src/mxp/ContextCompressionEngine.ts +219 -161
- package/src/mxp/MxpTokenOptimizer.ts +10 -10
- package/src/prompts/MxfAgentSystemPrompt.ts +45 -55
- package/src/protocols/mcp/ToolError.ts +201 -0
- package/src/protocols/mcp/defineTool.ts +379 -0
- package/src/protocols/mcp/security/HttpTargetGuard.ts +267 -0
- package/src/protocols/mcp/security/McpSecurityGuard.ts +10 -3
- package/src/protocols/mcp/security/McpToolPolicy.ts +221 -0
- package/src/protocols/mcp/services/ExternalMcpServerManager.ts +340 -322
- package/src/protocols/mcp/services/ExternalServerConfigs.ts +82 -19
- package/src/protocols/mcp/tools/AnalyticsTools.ts +1 -1
- package/src/protocols/mcp/tools/CodeAnalysisTools.ts +15 -0
- package/src/protocols/mcp/tools/EffectivenessTools.ts +3 -3
- package/src/protocols/mcp/tools/GitTools.ts +290 -569
- package/src/protocols/mcp/tools/InfrastructureTools.ts +189 -43
- package/src/protocols/mcp/tools/MemorySearchTools.ts +77 -1
- package/src/protocols/mcp/tools/OrparTools.ts +87 -0
- package/src/protocols/mcp/tools/SafetyTools.ts +501 -579
- package/src/protocols/mcp/tools/TaskPlanningTools.ts +81 -39
- package/src/protocols/mcp/tools/TestTools.ts +339 -419
- package/src/protocols/mcp/tools/TypeScriptTools.ts +316 -376
- package/src/protocols/mcp/tools/WebTools.ts +209 -307
- package/src/protocols/mcp/tools/shell/ShellExecuteHandler.ts +53 -14
- package/src/schemas/EventPayloadSchema.ts +37 -0
- package/src/services/BackgroundTaskManager.ts +12 -4
- package/src/services/BrowserManager.ts +41 -1
- package/src/services/MemoryCompressor.ts +20 -32
- package/src/services/MemoryService.ts +96 -58
- package/src/services/MxfMLService.ts +41 -7
- package/src/services/PredictiveAnalyticsService.ts +101 -51
- package/src/services/QValueManager.ts +10 -0
- package/src/services/RewardSignalProcessor.ts +138 -9
- package/src/services/SharedStateService.ts +56 -8
- package/src/services/TaskEffectivenessService.ts +114 -187
- package/src/services/WebSearchService.ts +2 -1
- package/src/services/dag/TaskDagService.ts +2 -0
- package/src/services/orpar-memory/OrparMemoryCoordinator.ts +86 -10
- package/src/types/MemoryUtilityTypes.ts +2 -0
- package/src/utils/Logger.ts +116 -40
- package/src/utils/MxpEncryption.ts +69 -34
- package/dist/mxp/MxpEventForwardingEnhancer.d.ts +0 -134
- package/dist/mxp/MxpEventForwardingEnhancer.d.ts.map +0 -1
- package/dist/mxp/MxpEventForwardingEnhancer.js +0 -361
- package/dist/mxp/MxpEventForwardingEnhancer.js.map +0 -1
- package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts +0 -56
- package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts.map +0 -1
- package/dist/protocols/mcp/services/ToolSchemaEnhancements.js +0 -57
- package/dist/protocols/mcp/services/ToolSchemaEnhancements.js.map +0 -1
- package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts +0 -270
- package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts.map +0 -1
- package/dist/protocols/mcp/tools/ControlLoopLifecycle.js +0 -256
- package/dist/protocols/mcp/tools/ControlLoopLifecycle.js.map +0 -1
- package/dist/protocols/mcp/tools/LspTools.d.ts +0 -495
- package/dist/protocols/mcp/tools/LspTools.d.ts.map +0 -1
- package/dist/protocols/mcp/tools/LspTools.js +0 -469
- package/dist/protocols/mcp/tools/LspTools.js.map +0 -1
- package/src/mxp/MxpEventForwardingEnhancer.ts +0 -471
- package/src/protocols/mcp/services/ToolSchemaEnhancements.ts +0 -92
- package/src/protocols/mcp/tools/ControlLoopLifecycle.ts +0 -302
- 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
|
-
*
|
|
294
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
|
387
|
+
description: 'Generate a coverage report (jest only)'
|
|
391
388
|
},
|
|
392
389
|
bail: {
|
|
393
390
|
type: 'boolean',
|
|
394
391
|
default: false,
|
|
395
|
-
description: 'Stop
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
435
|
-
if (
|
|
436
|
-
testCmd
|
|
416
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
417
|
+
if (pkg.scripts?.test) {
|
|
418
|
+
testCmd = ['npm', 'test'];
|
|
437
419
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
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: '
|
|
474
|
+
description: 'Benchmark command (defaults to the project benchmark script)'
|
|
483
475
|
},
|
|
484
476
|
baselineFile: {
|
|
485
477
|
type: 'string',
|
|
486
|
-
description: 'Path to baseline
|
|
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
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
508
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
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: '
|
|
564
|
+
description: 'What to roll back to'
|
|
588
565
|
},
|
|
589
566
|
targetCommit: {
|
|
590
567
|
type: 'string',
|
|
591
|
-
description: 'Commit
|
|
568
|
+
description: 'Commit to reset to (required when rollbackType is "commit")'
|
|
592
569
|
},
|
|
593
570
|
targetBranch: {
|
|
594
571
|
type: 'string',
|
|
595
|
-
description: 'Branch to
|
|
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: '
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
|
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
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
717
|
+
description: 'Files to scan (required when reviewType is "files")'
|
|
862
718
|
},
|
|
863
719
|
focusAreas: {
|
|
864
720
|
type: 'array',
|
|
865
|
-
items: {
|
|
721
|
+
items: {
|
|
722
|
+
type: 'string',
|
|
723
|
+
enum: ['quality', 'security', 'performance', 'maintainability']
|
|
724
|
+
},
|
|
866
725
|
default: ['quality', 'security', 'performance', 'maintainability'],
|
|
867
|
-
description: '
|
|
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: '
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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,
|