@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
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { Logger } from '../../../utils/Logger.js';
|
|
22
|
-
import {
|
|
22
|
+
import { TOOL_CATEGORIES } from '../../../constants/ToolNames.js';
|
|
23
|
+
import { defineTool, ToolRunContext } from '../defineTool.js';
|
|
24
|
+
import { ToolError } from '../ToolError.js';
|
|
25
|
+
import { executeShellCommand, ShellCommandResult } from './InfrastructureTools.js';
|
|
23
26
|
import * as fs from 'fs/promises';
|
|
24
27
|
import * as path from 'path';
|
|
25
28
|
|
|
@@ -340,19 +343,61 @@ function calculateReviewScore(issues: any[], strictness: string): number {
|
|
|
340
343
|
|
|
341
344
|
/**
|
|
342
345
|
* Safety Tools for MXF
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
+
*
|
|
347
|
+
* Branch, backup, rollback, test and benchmark tools used to keep autonomous
|
|
348
|
+
* code changes recoverable.
|
|
349
|
+
*
|
|
350
|
+
* Every git invocation here goes through executeShellCommand, so it is validated
|
|
351
|
+
* by the security guard first. That matters most for rollback_changes, which
|
|
352
|
+
* runs `git reset --hard` and `git checkout` — the guard is what stands between
|
|
353
|
+
* a model's rollback request and an unrecoverable working tree.
|
|
346
354
|
*/
|
|
347
355
|
|
|
348
|
-
|
|
356
|
+
/** Run a git subcommand, throwing a ToolError when git reports failure. */
|
|
357
|
+
async function runGit(
|
|
358
|
+
args: string[],
|
|
359
|
+
workingDirectory: string,
|
|
360
|
+
context: ToolRunContext
|
|
361
|
+
): Promise<ShellCommandResult> {
|
|
362
|
+
const result = await executeShellCommand('git', args, {
|
|
363
|
+
context: {
|
|
364
|
+
agentId: context.agentId,
|
|
365
|
+
channelId: context.channelId,
|
|
366
|
+
requestId: context.requestId
|
|
367
|
+
},
|
|
368
|
+
workingDirectory,
|
|
369
|
+
captureOutput: true
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (result.exitCode !== 0) {
|
|
373
|
+
throw ToolError.executionFailed(
|
|
374
|
+
`git ${args.join(' ')} failed (exit ${result.exitCode}): ` +
|
|
375
|
+
`${result.stderr?.trim() || 'no error output'}`,
|
|
376
|
+
{ details: { exitCode: result.exitCode, args } }
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const workingDirectoryProperty = {
|
|
384
|
+
type: 'string',
|
|
385
|
+
description: 'Working directory (defaults to the server working directory)'
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export const createFeatureBranchTool = defineTool<
|
|
389
|
+
{ branchName: string; baseBranch?: string; workingDirectory?: string },
|
|
390
|
+
{ branchName: string; baseBranch: string; headCommit: string; pulled: boolean }
|
|
391
|
+
>({
|
|
349
392
|
name: 'create_feature_branch',
|
|
350
|
-
|
|
393
|
+
category: TOOL_CATEGORIES.VERSION_CONTROL,
|
|
394
|
+
description: 'Create and switch to a new branch off a base branch, to isolate changes.',
|
|
351
395
|
inputSchema: {
|
|
352
396
|
type: 'object',
|
|
353
397
|
properties: {
|
|
354
398
|
branchName: {
|
|
355
399
|
type: 'string',
|
|
400
|
+
minLength: 1,
|
|
356
401
|
description: 'Name for the new feature branch'
|
|
357
402
|
},
|
|
358
403
|
baseBranch: {
|
|
@@ -360,294 +405,284 @@ export const createFeatureBranchTool = {
|
|
|
360
405
|
default: 'main',
|
|
361
406
|
description: 'Base branch to create the feature branch from'
|
|
362
407
|
},
|
|
363
|
-
workingDirectory:
|
|
364
|
-
type: 'string',
|
|
365
|
-
description: 'Working directory (defaults to current directory)'
|
|
366
|
-
}
|
|
408
|
+
workingDirectory: workingDirectoryProperty
|
|
367
409
|
},
|
|
368
410
|
required: ['branchName']
|
|
369
411
|
},
|
|
370
|
-
|
|
371
|
-
|
|
412
|
+
run: async (input, context) => {
|
|
413
|
+
const workingDir = input.workingDirectory || process.cwd();
|
|
414
|
+
const baseBranch = input.baseBranch ?? 'main';
|
|
415
|
+
|
|
416
|
+
await runGit(['checkout', baseBranch], workingDir, context);
|
|
417
|
+
|
|
418
|
+
// A pull can fail for reasons that do not invalidate the branch (no
|
|
419
|
+
// remote, offline). Record whether it worked rather than failing the tool
|
|
420
|
+
// or pretending it succeeded.
|
|
421
|
+
let pulled = true;
|
|
372
422
|
try {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// Ensure we're on the base branch and it's up to date
|
|
376
|
-
const checkoutResult = await executeShellCommand('git', ['checkout', args.baseBranch || 'main'], {
|
|
377
|
-
workingDirectory: workingDir,
|
|
378
|
-
captureOutput: true
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
if (checkoutResult.exitCode !== 0) {
|
|
382
|
-
return {
|
|
383
|
-
success: false,
|
|
384
|
-
branchName: args.branchName,
|
|
385
|
-
error: `Failed to checkout base branch: ${checkoutResult.stderr}`
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Pull latest changes
|
|
390
|
-
const pullResult = await executeShellCommand('git', ['pull', 'origin', args.baseBranch || 'main'], {
|
|
391
|
-
workingDirectory: workingDir,
|
|
392
|
-
captureOutput: true
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
if (pullResult.exitCode !== 0) {
|
|
396
|
-
logger.warn('Failed to pull latest changes', { error: pullResult.stderr });
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Create and checkout new branch
|
|
400
|
-
const branchResult = await executeShellCommand('git', ['checkout', '-b', args.branchName], {
|
|
401
|
-
workingDirectory: workingDir,
|
|
402
|
-
captureOutput: true
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
if (branchResult.exitCode !== 0) {
|
|
406
|
-
return {
|
|
407
|
-
success: false,
|
|
408
|
-
branchName: args.branchName,
|
|
409
|
-
error: `Failed to create branch: ${branchResult.stderr}`
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Get current commit hash for reference
|
|
414
|
-
const hashResult = await executeShellCommand('git', ['rev-parse', 'HEAD'], {
|
|
415
|
-
workingDirectory: workingDir,
|
|
416
|
-
captureOutput: true
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
const currentHash = hashResult.stdout?.trim() || 'unknown';
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
success: true,
|
|
423
|
-
branchName: args.branchName,
|
|
424
|
-
baseBranch: args.baseBranch || 'main',
|
|
425
|
-
currentHash,
|
|
426
|
-
error: null
|
|
427
|
-
};
|
|
423
|
+
await runGit(['pull', 'origin', baseBranch], workingDir, context);
|
|
428
424
|
} catch (error) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
};
|
|
425
|
+
pulled = false;
|
|
426
|
+
logger.warn(
|
|
427
|
+
`Could not pull ${baseBranch} before branching: ` +
|
|
428
|
+
`${error instanceof Error ? error.message : String(error)}`
|
|
429
|
+
);
|
|
435
430
|
}
|
|
431
|
+
|
|
432
|
+
await runGit(['checkout', '-b', input.branchName], workingDir, context);
|
|
433
|
+
|
|
434
|
+
const hashResult = await runGit(['rev-parse', 'HEAD'], workingDir, context);
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
branchName: input.branchName,
|
|
438
|
+
baseBranch,
|
|
439
|
+
headCommit: hashResult.stdout?.trim() ?? '',
|
|
440
|
+
pulled
|
|
441
|
+
};
|
|
436
442
|
}
|
|
437
|
-
};
|
|
443
|
+
});
|
|
438
444
|
|
|
439
|
-
export const runFullTestSuiteTool =
|
|
445
|
+
export const runFullTestSuiteTool = defineTool<
|
|
446
|
+
{ testCommand?: string; coverage?: boolean; bail?: boolean; workingDirectory?: string },
|
|
447
|
+
{
|
|
448
|
+
passed: boolean;
|
|
449
|
+
command: string;
|
|
450
|
+
durationMs: number;
|
|
451
|
+
testResults: ReturnType<typeof parseTestResults>;
|
|
452
|
+
coverage: ReturnType<typeof parseCoverageResults> | null;
|
|
453
|
+
output: string;
|
|
454
|
+
}
|
|
455
|
+
>({
|
|
440
456
|
name: 'run_full_test_suite',
|
|
441
|
-
|
|
457
|
+
category: TOOL_CATEGORIES.INFRASTRUCTURE,
|
|
458
|
+
description:
|
|
459
|
+
'Run the project test suite. passed is false when tests fail. ' +
|
|
460
|
+
'Uses the project test script unless a command is given.',
|
|
442
461
|
inputSchema: {
|
|
443
462
|
type: 'object',
|
|
444
463
|
properties: {
|
|
445
464
|
testCommand: {
|
|
446
465
|
type: 'string',
|
|
447
|
-
description: '
|
|
466
|
+
description: 'Test command to run (defaults to the project test script)'
|
|
448
467
|
},
|
|
449
468
|
coverage: {
|
|
450
469
|
type: 'boolean',
|
|
451
470
|
default: true,
|
|
452
|
-
description: 'Generate
|
|
471
|
+
description: 'Generate a coverage report (jest only)'
|
|
453
472
|
},
|
|
454
473
|
bail: {
|
|
455
474
|
type: 'boolean',
|
|
456
475
|
default: false,
|
|
457
|
-
description: 'Stop
|
|
476
|
+
description: 'Stop at the first failure (jest only)'
|
|
458
477
|
},
|
|
459
|
-
workingDirectory:
|
|
460
|
-
type: 'string',
|
|
461
|
-
description: 'Working directory (defaults to current directory)'
|
|
462
|
-
}
|
|
478
|
+
workingDirectory: workingDirectoryProperty
|
|
463
479
|
}
|
|
464
480
|
},
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
testCmd = ['npx', 'mocha'];
|
|
487
|
-
} else {
|
|
488
|
-
testCmd = ['npm', 'test'];
|
|
489
|
-
}
|
|
490
|
-
} catch {
|
|
491
|
-
testCmd = ['npm', 'test'];
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Add coverage if requested
|
|
496
|
-
if (args.coverage && testCmd.includes('jest')) {
|
|
497
|
-
testCmd.push('--coverage');
|
|
481
|
+
run: async (input, context) => {
|
|
482
|
+
const workingDir = input.workingDirectory || process.cwd();
|
|
483
|
+
|
|
484
|
+
// Work out what to run. An explicit command wins; otherwise read
|
|
485
|
+
// package.json. A project with no test script and no known runner is a
|
|
486
|
+
// precondition failure — running `npm test` anyway would fail confusingly.
|
|
487
|
+
let testCmd: string[];
|
|
488
|
+
|
|
489
|
+
if (input.testCommand) {
|
|
490
|
+
testCmd = input.testCommand.split(/\s+/).filter(Boolean);
|
|
491
|
+
} else {
|
|
492
|
+
const packageJsonPath = path.join(workingDir, 'package.json');
|
|
493
|
+
let pkg: { scripts?: Record<string, string>; dependencies?: Record<string, string>; devDependencies?: Record<string, string> };
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
497
|
+
} catch {
|
|
498
|
+
throw ToolError.notFound(
|
|
499
|
+
`Could not read ${packageJsonPath}. ` +
|
|
500
|
+
`Pass testCommand explicitly, or run this from the project root.`
|
|
501
|
+
);
|
|
498
502
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
+
|
|
504
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
505
|
+
|
|
506
|
+
if (pkg.scripts?.test) {
|
|
507
|
+
testCmd = ['npm', 'test'];
|
|
508
|
+
} else if (deps.jest) {
|
|
509
|
+
testCmd = ['npx', 'jest'];
|
|
510
|
+
} else if (deps.mocha) {
|
|
511
|
+
testCmd = ['npx', 'mocha'];
|
|
512
|
+
} else if (deps.vitest) {
|
|
513
|
+
testCmd = ['npx', 'vitest', 'run'];
|
|
514
|
+
} else {
|
|
515
|
+
throw ToolError.notFound(
|
|
516
|
+
`${packageJsonPath} has no "test" script and no jest, mocha or vitest dependency. ` +
|
|
517
|
+
`Pass testCommand explicitly.`
|
|
518
|
+
);
|
|
503
519
|
}
|
|
504
|
-
|
|
505
|
-
const startTime = Date.now();
|
|
506
|
-
|
|
507
|
-
const result = await executeShellCommand(testCmd[0], testCmd.slice(1), {
|
|
508
|
-
workingDirectory: workingDir,
|
|
509
|
-
captureOutput: true
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
const duration = Date.now() - startTime;
|
|
513
|
-
|
|
514
|
-
// Parse test results
|
|
515
|
-
const testResults = parseTestResults(result.stdout || '');
|
|
516
|
-
|
|
517
|
-
return {
|
|
518
|
-
success: result.exitCode === 0,
|
|
519
|
-
output: result.stdout || '',
|
|
520
|
-
errors: result.stderr || '',
|
|
521
|
-
duration,
|
|
522
|
-
testResults,
|
|
523
|
-
coverage: args.coverage ? parseCoverageResults(result.stdout || '') : null,
|
|
524
|
-
error: result.exitCode !== 0 ? result.stderr : null
|
|
525
|
-
};
|
|
526
|
-
} catch (error) {
|
|
527
|
-
logger.error('Run full test suite failed', { error });
|
|
528
|
-
return {
|
|
529
|
-
success: false,
|
|
530
|
-
output: '',
|
|
531
|
-
errors: '',
|
|
532
|
-
duration: 0,
|
|
533
|
-
testResults: null,
|
|
534
|
-
coverage: null,
|
|
535
|
-
error: error instanceof Error ? error.message : String(error)
|
|
536
|
-
};
|
|
537
520
|
}
|
|
521
|
+
|
|
522
|
+
const isJest = testCmd.includes('jest');
|
|
523
|
+
if (input.coverage !== false && isJest) {
|
|
524
|
+
testCmd.push('--coverage');
|
|
525
|
+
}
|
|
526
|
+
if (input.bail && isJest) {
|
|
527
|
+
testCmd.push('--bail');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const startedAt = Date.now();
|
|
531
|
+
|
|
532
|
+
const result = await executeShellCommand(testCmd[0], testCmd.slice(1), {
|
|
533
|
+
context: {
|
|
534
|
+
agentId: context.agentId,
|
|
535
|
+
channelId: context.channelId,
|
|
536
|
+
requestId: context.requestId
|
|
537
|
+
},
|
|
538
|
+
workingDirectory: workingDir,
|
|
539
|
+
captureOutput: true
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// A failing suite exits non-zero. That is the tool working, so it comes
|
|
543
|
+
// back as passed: false rather than as a tool error.
|
|
544
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
passed: result.exitCode === 0,
|
|
548
|
+
command: testCmd.join(' '),
|
|
549
|
+
durationMs: Date.now() - startedAt,
|
|
550
|
+
testResults: parseTestResults(output),
|
|
551
|
+
coverage: input.coverage !== false ? parseCoverageResults(output) : null,
|
|
552
|
+
output
|
|
553
|
+
};
|
|
538
554
|
}
|
|
539
|
-
};
|
|
555
|
+
});
|
|
540
556
|
|
|
541
|
-
export const performanceBenchmarkTool =
|
|
557
|
+
export const performanceBenchmarkTool = defineTool<
|
|
558
|
+
{ benchmarkCommand?: string; baselineFile?: string; thresholdPercent?: number; workingDirectory?: string },
|
|
559
|
+
{
|
|
560
|
+
passed: boolean;
|
|
561
|
+
command: string;
|
|
562
|
+
durationMs: number;
|
|
563
|
+
benchmarkResults: ReturnType<typeof parseBenchmarkResults>;
|
|
564
|
+
baselineComparison: ReturnType<typeof compareWithBaseline> | null;
|
|
565
|
+
output: string;
|
|
566
|
+
}
|
|
567
|
+
>({
|
|
542
568
|
name: 'performance_benchmark',
|
|
543
|
-
|
|
569
|
+
category: TOOL_CATEGORIES.INFRASTRUCTURE,
|
|
570
|
+
description:
|
|
571
|
+
'Run the project benchmark script and, when a baseline file is given, compare ' +
|
|
572
|
+
'against it and flag regressions past the threshold.',
|
|
544
573
|
inputSchema: {
|
|
545
574
|
type: 'object',
|
|
546
575
|
properties: {
|
|
547
576
|
benchmarkCommand: {
|
|
548
577
|
type: 'string',
|
|
549
|
-
description: '
|
|
578
|
+
description: 'Benchmark command (defaults to the project benchmark script)'
|
|
550
579
|
},
|
|
551
580
|
baselineFile: {
|
|
552
581
|
type: 'string',
|
|
553
|
-
description: 'Path to baseline
|
|
582
|
+
description: 'Path to a JSON baseline file to compare against'
|
|
554
583
|
},
|
|
555
584
|
thresholdPercent: {
|
|
556
585
|
type: 'number',
|
|
557
586
|
default: 10,
|
|
558
|
-
|
|
587
|
+
minimum: 0,
|
|
588
|
+
description: 'Regression threshold, as a percentage'
|
|
559
589
|
},
|
|
560
|
-
workingDirectory:
|
|
561
|
-
type: 'string',
|
|
562
|
-
description: 'Working directory (defaults to current directory)'
|
|
563
|
-
}
|
|
590
|
+
workingDirectory: workingDirectoryProperty
|
|
564
591
|
}
|
|
565
592
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
593
|
+
run: async (input, context) => {
|
|
594
|
+
const workingDir = input.workingDirectory || process.cwd();
|
|
595
|
+
|
|
596
|
+
let benchmarkCmd: string[];
|
|
597
|
+
|
|
598
|
+
if (input.benchmarkCommand) {
|
|
599
|
+
benchmarkCmd = input.benchmarkCommand.split(/\s+/).filter(Boolean);
|
|
600
|
+
} else {
|
|
601
|
+
const packageJsonPath = path.join(workingDir, 'package.json');
|
|
602
|
+
let pkg: { scripts?: Record<string, string> };
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
606
|
+
} catch {
|
|
607
|
+
throw ToolError.notFound(
|
|
608
|
+
`Could not read ${packageJsonPath}. Pass benchmarkCommand explicitly.`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (pkg.scripts?.benchmark) {
|
|
613
|
+
benchmarkCmd = ['npm', 'run', 'benchmark'];
|
|
614
|
+
} else if (pkg.scripts?.perf) {
|
|
615
|
+
benchmarkCmd = ['npm', 'run', 'perf'];
|
|
575
616
|
} else {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const pkg = JSON.parse(packageJson);
|
|
581
|
-
|
|
582
|
-
if (pkg.scripts?.benchmark) {
|
|
583
|
-
benchmarkCmd = ['npm', 'run', 'benchmark'];
|
|
584
|
-
} else if (pkg.scripts?.perf) {
|
|
585
|
-
benchmarkCmd = ['npm', 'run', 'perf'];
|
|
586
|
-
} else {
|
|
587
|
-
return {
|
|
588
|
-
success: false,
|
|
589
|
-
benchmarkResults: null,
|
|
590
|
-
baselineComparison: null,
|
|
591
|
-
error: 'No benchmark script found in package.json'
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
} catch {
|
|
595
|
-
return {
|
|
596
|
-
success: false,
|
|
597
|
-
benchmarkResults: null,
|
|
598
|
-
baselineComparison: null,
|
|
599
|
-
error: 'Could not read package.json'
|
|
600
|
-
};
|
|
601
|
-
}
|
|
617
|
+
throw ToolError.notFound(
|
|
618
|
+
`${packageJsonPath} has no "benchmark" or "perf" script. ` +
|
|
619
|
+
`Pass benchmarkCommand explicitly.`
|
|
620
|
+
);
|
|
602
621
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const startedAt = Date.now();
|
|
625
|
+
|
|
626
|
+
const result = await executeShellCommand(benchmarkCmd[0], benchmarkCmd.slice(1), {
|
|
627
|
+
context: {
|
|
628
|
+
agentId: context.agentId,
|
|
629
|
+
channelId: context.channelId,
|
|
630
|
+
requestId: context.requestId
|
|
631
|
+
},
|
|
632
|
+
workingDirectory: workingDir,
|
|
633
|
+
captureOutput: true
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
637
|
+
const benchmarkResults = parseBenchmarkResults(output);
|
|
638
|
+
|
|
639
|
+
// A baseline the caller named but that cannot be read is an error: a
|
|
640
|
+
// silent null here would read as "no regression".
|
|
641
|
+
let baselineComparison = null;
|
|
642
|
+
if (input.baselineFile) {
|
|
643
|
+
let baseline: unknown;
|
|
644
|
+
try {
|
|
645
|
+
baseline = JSON.parse(await fs.readFile(input.baselineFile, 'utf-8'));
|
|
646
|
+
} catch (error) {
|
|
647
|
+
throw ToolError.notFound(
|
|
648
|
+
`Could not read the baseline file ${input.baselineFile}: ` +
|
|
649
|
+
`${error instanceof Error ? error.message : String(error)}`
|
|
650
|
+
);
|
|
626
651
|
}
|
|
627
|
-
|
|
628
|
-
return {
|
|
629
|
-
success: result.exitCode === 0,
|
|
630
|
-
output: result.stdout || '',
|
|
652
|
+
baselineComparison = compareWithBaseline(
|
|
631
653
|
benchmarkResults,
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
};
|
|
636
|
-
} catch (error) {
|
|
637
|
-
logger.error('Performance benchmark failed', { error });
|
|
638
|
-
return {
|
|
639
|
-
success: false,
|
|
640
|
-
benchmarkResults: null,
|
|
641
|
-
baselineComparison: null,
|
|
642
|
-
error: error instanceof Error ? error.message : String(error)
|
|
643
|
-
};
|
|
654
|
+
baseline,
|
|
655
|
+
input.thresholdPercent ?? 10
|
|
656
|
+
);
|
|
644
657
|
}
|
|
658
|
+
|
|
659
|
+
return {
|
|
660
|
+
passed: result.exitCode === 0,
|
|
661
|
+
command: benchmarkCmd.join(' '),
|
|
662
|
+
durationMs: Date.now() - startedAt,
|
|
663
|
+
benchmarkResults,
|
|
664
|
+
baselineComparison,
|
|
665
|
+
output
|
|
666
|
+
};
|
|
645
667
|
}
|
|
646
|
-
};
|
|
668
|
+
});
|
|
647
669
|
|
|
648
|
-
export const rollbackChangesTool =
|
|
670
|
+
export const rollbackChangesTool = defineTool<
|
|
671
|
+
{
|
|
672
|
+
rollbackType?: 'commit' | 'branch' | 'stash';
|
|
673
|
+
targetCommit?: string;
|
|
674
|
+
targetBranch?: string;
|
|
675
|
+
preserveChanges?: boolean;
|
|
676
|
+
workingDirectory?: string;
|
|
677
|
+
},
|
|
678
|
+
{ rollbackType: string; currentBranch: string; hasUncommittedChanges: boolean; preservedChanges: boolean }
|
|
679
|
+
>({
|
|
649
680
|
name: 'rollback_changes',
|
|
650
|
-
|
|
681
|
+
category: TOOL_CATEGORIES.VERSION_CONTROL,
|
|
682
|
+
description:
|
|
683
|
+
'Roll the repository back to a previous state: reset to a commit, switch to a ' +
|
|
684
|
+
'branch, or pop the latest stash. With preserveChanges the working tree is kept ' +
|
|
685
|
+
'(soft reset / stash); without it, a commit rollback discards uncommitted work.',
|
|
651
686
|
inputSchema: {
|
|
652
687
|
type: 'object',
|
|
653
688
|
properties: {
|
|
@@ -655,138 +690,90 @@ export const rollbackChangesTool = {
|
|
|
655
690
|
type: 'string',
|
|
656
691
|
enum: ['commit', 'branch', 'stash'],
|
|
657
692
|
default: 'commit',
|
|
658
|
-
description: '
|
|
693
|
+
description: 'What to roll back to'
|
|
659
694
|
},
|
|
660
695
|
targetCommit: {
|
|
661
696
|
type: 'string',
|
|
662
|
-
description: 'Commit
|
|
697
|
+
description: 'Commit to reset to (required when rollbackType is "commit")'
|
|
663
698
|
},
|
|
664
699
|
targetBranch: {
|
|
665
700
|
type: 'string',
|
|
666
|
-
description: 'Branch to
|
|
701
|
+
description: 'Branch to switch to (required when rollbackType is "branch")'
|
|
667
702
|
},
|
|
668
703
|
preserveChanges: {
|
|
669
704
|
type: 'boolean',
|
|
670
705
|
default: false,
|
|
671
|
-
description: '
|
|
706
|
+
description: 'Keep uncommitted work (soft reset, or stash before switching)'
|
|
672
707
|
},
|
|
673
|
-
workingDirectory:
|
|
674
|
-
type: 'string',
|
|
675
|
-
description: 'Working directory (defaults to current directory)'
|
|
676
|
-
}
|
|
708
|
+
workingDirectory: workingDirectoryProperty
|
|
677
709
|
}
|
|
678
710
|
},
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if (args.preserveChanges) {
|
|
696
|
-
// Soft reset to preserve changes
|
|
697
|
-
result = await executeShellCommand('git', ['reset', '--soft', args.targetCommit], {
|
|
698
|
-
workingDirectory: workingDir,
|
|
699
|
-
captureOutput: true
|
|
700
|
-
});
|
|
701
|
-
} else {
|
|
702
|
-
// Hard reset to discard changes
|
|
703
|
-
result = await executeShellCommand('git', ['reset', '--hard', args.targetCommit], {
|
|
704
|
-
workingDirectory: workingDir,
|
|
705
|
-
captureOutput: true
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
break;
|
|
709
|
-
|
|
710
|
-
case 'branch':
|
|
711
|
-
if (!args.targetBranch) {
|
|
712
|
-
return {
|
|
713
|
-
success: false,
|
|
714
|
-
rollbackType: args.rollbackType,
|
|
715
|
-
error: 'Target branch required for branch rollback'
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Stash current changes if preserving
|
|
720
|
-
if (args.preserveChanges) {
|
|
721
|
-
await executeShellCommand('git', ['stash', 'push', '-m', 'Rollback preservation stash'], {
|
|
722
|
-
workingDirectory: workingDir,
|
|
723
|
-
captureOutput: true
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Checkout target branch
|
|
728
|
-
result = await executeShellCommand('git', ['checkout', args.targetBranch], {
|
|
729
|
-
workingDirectory: workingDir,
|
|
730
|
-
captureOutput: true
|
|
731
|
-
});
|
|
732
|
-
break;
|
|
733
|
-
|
|
734
|
-
case 'stash':
|
|
735
|
-
// Pop the latest stash
|
|
736
|
-
result = await executeShellCommand('git', ['stash', 'pop'], {
|
|
737
|
-
workingDirectory: workingDir,
|
|
738
|
-
captureOutput: true
|
|
739
|
-
});
|
|
740
|
-
break;
|
|
741
|
-
|
|
742
|
-
default:
|
|
743
|
-
return {
|
|
744
|
-
success: false,
|
|
745
|
-
rollbackType: args.rollbackType,
|
|
746
|
-
error: 'Invalid rollback type'
|
|
747
|
-
};
|
|
711
|
+
run: async (input, context) => {
|
|
712
|
+
const workingDir = input.workingDirectory || process.cwd();
|
|
713
|
+
const rollbackType = input.rollbackType ?? 'commit';
|
|
714
|
+
const preserveChanges = input.preserveChanges ?? false;
|
|
715
|
+
|
|
716
|
+
switch (rollbackType) {
|
|
717
|
+
case 'commit': {
|
|
718
|
+
if (!input.targetCommit) {
|
|
719
|
+
throw ToolError.invalidInput(
|
|
720
|
+
'targetCommit is required when rollbackType is "commit".'
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
// --hard discards uncommitted work irrecoverably; --soft keeps it.
|
|
724
|
+
const mode = preserveChanges ? '--soft' : '--hard';
|
|
725
|
+
await runGit(['reset', mode, input.targetCommit], workingDir, context);
|
|
726
|
+
break;
|
|
748
727
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
728
|
+
|
|
729
|
+
case 'branch': {
|
|
730
|
+
if (!input.targetBranch) {
|
|
731
|
+
throw ToolError.invalidInput(
|
|
732
|
+
'targetBranch is required when rollbackType is "branch".'
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (preserveChanges) {
|
|
736
|
+
await runGit(
|
|
737
|
+
['stash', 'push', '-m', 'Rollback preservation stash'],
|
|
738
|
+
workingDir,
|
|
739
|
+
context
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
await runGit(['checkout', input.targetBranch], workingDir, context);
|
|
743
|
+
break;
|
|
756
744
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
captureOutput: true
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
const currentBranchResult = await executeShellCommand('git', ['branch', '--show-current'], {
|
|
765
|
-
workingDirectory: workingDir,
|
|
766
|
-
captureOutput: true
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
return {
|
|
770
|
-
success: true,
|
|
771
|
-
rollbackType: args.rollbackType,
|
|
772
|
-
currentBranch: currentBranchResult.stdout?.trim() || 'unknown',
|
|
773
|
-
hasUncommittedChanges: (statusResult.stdout || '').trim().length > 0,
|
|
774
|
-
error: null
|
|
775
|
-
};
|
|
776
|
-
} catch (error) {
|
|
777
|
-
logger.error('Rollback changes failed', { error });
|
|
778
|
-
return {
|
|
779
|
-
success: false,
|
|
780
|
-
rollbackType: args.rollbackType,
|
|
781
|
-
error: error instanceof Error ? error.message : String(error)
|
|
782
|
-
};
|
|
745
|
+
|
|
746
|
+
case 'stash':
|
|
747
|
+
await runGit(['stash', 'pop'], workingDir, context);
|
|
748
|
+
break;
|
|
783
749
|
}
|
|
750
|
+
|
|
751
|
+
const statusResult = await runGit(['status', '--porcelain'], workingDir, context);
|
|
752
|
+
const branchResult = await runGit(['branch', '--show-current'], workingDir, context);
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
rollbackType,
|
|
756
|
+
currentBranch: branchResult.stdout?.trim() ?? '',
|
|
757
|
+
hasUncommittedChanges: (statusResult.stdout ?? '').trim().length > 0,
|
|
758
|
+
preservedChanges: preserveChanges
|
|
759
|
+
};
|
|
784
760
|
}
|
|
785
|
-
};
|
|
761
|
+
});
|
|
786
762
|
|
|
787
|
-
export const createBackupTool =
|
|
763
|
+
export const createBackupTool = defineTool<
|
|
764
|
+
{
|
|
765
|
+
backupType?: 'commit' | 'branch' | 'stash' | 'archive';
|
|
766
|
+
backupName?: string;
|
|
767
|
+
includeUntracked?: boolean;
|
|
768
|
+
workingDirectory?: string;
|
|
769
|
+
},
|
|
770
|
+
{ backupType: string; backupName: string; backupId: string; timestamp: string }
|
|
771
|
+
>({
|
|
788
772
|
name: 'create_backup',
|
|
789
|
-
|
|
773
|
+
category: TOOL_CATEGORIES.VERSION_CONTROL,
|
|
774
|
+
description:
|
|
775
|
+
'Snapshot the current state before making changes, as a commit, a branch, a stash, ' +
|
|
776
|
+
'or a tar.gz archive. Returns the identifier needed to restore it.',
|
|
790
777
|
inputSchema: {
|
|
791
778
|
type: 'object',
|
|
792
779
|
properties: {
|
|
@@ -794,157 +781,114 @@ export const createBackupTool = {
|
|
|
794
781
|
type: 'string',
|
|
795
782
|
enum: ['commit', 'branch', 'stash', 'archive'],
|
|
796
783
|
default: 'commit',
|
|
797
|
-
description: '
|
|
784
|
+
description: 'How to store the backup'
|
|
798
785
|
},
|
|
799
786
|
backupName: {
|
|
800
787
|
type: 'string',
|
|
801
|
-
description: 'Name for the backup'
|
|
788
|
+
description: 'Name for the backup (defaults to a timestamp)'
|
|
802
789
|
},
|
|
803
790
|
includeUntracked: {
|
|
804
791
|
type: 'boolean',
|
|
805
792
|
default: true,
|
|
806
|
-
description: 'Include untracked files
|
|
793
|
+
description: 'Include untracked files'
|
|
807
794
|
},
|
|
808
|
-
workingDirectory:
|
|
809
|
-
type: 'string',
|
|
810
|
-
description: 'Working directory (defaults to current directory)'
|
|
811
|
-
}
|
|
795
|
+
workingDirectory: workingDirectoryProperty
|
|
812
796
|
}
|
|
813
797
|
},
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
});
|
|
832
|
-
} else {
|
|
833
|
-
await executeShellCommand('git', ['add', '-u'], {
|
|
834
|
-
workingDirectory: workingDir,
|
|
835
|
-
captureOutput: true
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// Create commit
|
|
840
|
-
result = await executeShellCommand('git', ['commit', '-m', `Backup: ${backupName}`], {
|
|
841
|
-
workingDirectory: workingDir,
|
|
842
|
-
captureOutput: true
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
if (result.exitCode === 0) {
|
|
846
|
-
const hashResult = await executeShellCommand('git', ['rev-parse', 'HEAD'], {
|
|
847
|
-
workingDirectory: workingDir,
|
|
848
|
-
captureOutput: true
|
|
849
|
-
});
|
|
850
|
-
backupId = hashResult.stdout?.trim();
|
|
851
|
-
}
|
|
852
|
-
break;
|
|
853
|
-
|
|
854
|
-
case 'branch':
|
|
855
|
-
// Create backup branch
|
|
856
|
-
result = await executeShellCommand('git', ['checkout', '-b', backupName], {
|
|
857
|
-
workingDirectory: workingDir,
|
|
858
|
-
captureOutput: true
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
if (result.exitCode === 0) {
|
|
862
|
-
backupId = backupName;
|
|
863
|
-
|
|
864
|
-
// Switch back to original branch
|
|
865
|
-
const originalBranchResult = await executeShellCommand('git', ['checkout', '-'], {
|
|
866
|
-
workingDirectory: workingDir,
|
|
867
|
-
captureOutput: true
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
if (originalBranchResult.exitCode !== 0) {
|
|
871
|
-
logger.warn('Could not switch back to original branch');
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
break;
|
|
875
|
-
|
|
876
|
-
case 'stash':
|
|
877
|
-
// Create stash
|
|
878
|
-
const stashArgs = ['stash', 'push', '-m', backupName];
|
|
879
|
-
if (args.includeUntracked) {
|
|
880
|
-
stashArgs.push('--include-untracked');
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
result = await executeShellCommand('git', stashArgs, {
|
|
884
|
-
workingDirectory: workingDir,
|
|
885
|
-
captureOutput: true
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
if (result.exitCode === 0) {
|
|
889
|
-
backupId = backupName;
|
|
890
|
-
}
|
|
891
|
-
break;
|
|
892
|
-
|
|
893
|
-
case 'archive':
|
|
894
|
-
// Create archive file
|
|
895
|
-
const archiveName = `${backupName}.tar.gz`;
|
|
896
|
-
const archivePath = path.join(workingDir, archiveName);
|
|
897
|
-
|
|
898
|
-
result = await executeShellCommand('git', ['archive', '--format=tar.gz', '--output', archivePath, 'HEAD'], {
|
|
899
|
-
workingDirectory: workingDir,
|
|
900
|
-
captureOutput: true
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
if (result.exitCode === 0) {
|
|
904
|
-
backupId = archivePath;
|
|
905
|
-
}
|
|
906
|
-
break;
|
|
907
|
-
|
|
908
|
-
default:
|
|
909
|
-
return {
|
|
910
|
-
success: false,
|
|
911
|
-
backupType: args.backupType,
|
|
912
|
-
error: 'Invalid backup type'
|
|
913
|
-
};
|
|
798
|
+
run: async (input, context) => {
|
|
799
|
+
const workingDir = input.workingDirectory || process.cwd();
|
|
800
|
+
const backupType = input.backupType ?? 'commit';
|
|
801
|
+
const includeUntracked = input.includeUntracked ?? true;
|
|
802
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
803
|
+
const backupName = input.backupName || `backup-${timestamp}`;
|
|
804
|
+
|
|
805
|
+
let backupId: string;
|
|
806
|
+
|
|
807
|
+
switch (backupType) {
|
|
808
|
+
case 'commit': {
|
|
809
|
+
await runGit(['add', includeUntracked ? '-A' : '-u'], workingDir, context);
|
|
810
|
+
await runGit(['commit', '-m', `Backup: ${backupName}`], workingDir, context);
|
|
811
|
+
|
|
812
|
+
const hashResult = await runGit(['rev-parse', 'HEAD'], workingDir, context);
|
|
813
|
+
backupId = hashResult.stdout?.trim() ?? '';
|
|
814
|
+
break;
|
|
914
815
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
816
|
+
|
|
817
|
+
case 'branch': {
|
|
818
|
+
await runGit(['checkout', '-b', backupName], workingDir, context);
|
|
819
|
+
// Return to where we were — a backup that leaves the caller on the
|
|
820
|
+
// backup branch is a trap.
|
|
821
|
+
await runGit(['checkout', '-'], workingDir, context);
|
|
822
|
+
backupId = backupName;
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
case 'stash': {
|
|
827
|
+
const stashArgs = ['stash', 'push', '-m', backupName];
|
|
828
|
+
if (includeUntracked) {
|
|
829
|
+
stashArgs.push('--include-untracked');
|
|
830
|
+
}
|
|
831
|
+
await runGit(stashArgs, workingDir, context);
|
|
832
|
+
backupId = backupName;
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
case 'archive': {
|
|
837
|
+
const archivePath = path.join(workingDir, `${backupName}.tar.gz`);
|
|
838
|
+
await runGit(
|
|
839
|
+
['archive', '--format=tar.gz', '--output', archivePath, 'HEAD'],
|
|
840
|
+
workingDir,
|
|
841
|
+
context
|
|
842
|
+
);
|
|
843
|
+
backupId = archivePath;
|
|
844
|
+
break;
|
|
923
845
|
}
|
|
924
|
-
|
|
925
|
-
return {
|
|
926
|
-
success: true,
|
|
927
|
-
backupType: args.backupType,
|
|
928
|
-
backupName,
|
|
929
|
-
backupId,
|
|
930
|
-
timestamp,
|
|
931
|
-
error: null
|
|
932
|
-
};
|
|
933
|
-
} catch (error) {
|
|
934
|
-
logger.error('Create backup failed', { error });
|
|
935
|
-
return {
|
|
936
|
-
success: false,
|
|
937
|
-
backupType: args.backupType,
|
|
938
|
-
backupName: args.backupName,
|
|
939
|
-
error: error instanceof Error ? error.message : String(error)
|
|
940
|
-
};
|
|
941
846
|
}
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
backupType,
|
|
850
|
+
backupName,
|
|
851
|
+
backupId,
|
|
852
|
+
timestamp
|
|
853
|
+
};
|
|
942
854
|
}
|
|
943
|
-
};
|
|
855
|
+
});
|
|
944
856
|
|
|
945
|
-
export const codeReviewAgentTool =
|
|
857
|
+
export const codeReviewAgentTool = defineTool<
|
|
858
|
+
{
|
|
859
|
+
reviewType?: 'diff' | 'files' | 'pull_request';
|
|
860
|
+
files?: string[];
|
|
861
|
+
focusAreas?: string[];
|
|
862
|
+
strictness?: 'low' | 'medium' | 'high';
|
|
863
|
+
workingDirectory?: string;
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
summary: {
|
|
867
|
+
filesReviewed: number;
|
|
868
|
+
totalIssues: number;
|
|
869
|
+
criticalIssues: number;
|
|
870
|
+
highIssues: number;
|
|
871
|
+
mediumIssues: number;
|
|
872
|
+
lowIssues: number;
|
|
873
|
+
overallScore: number;
|
|
874
|
+
};
|
|
875
|
+
issues: any[];
|
|
876
|
+
recommendations: any[];
|
|
877
|
+
score: number;
|
|
878
|
+
}
|
|
879
|
+
>({
|
|
946
880
|
name: 'code_review_agent',
|
|
947
|
-
|
|
881
|
+
category: TOOL_CATEGORIES.INFRASTRUCTURE,
|
|
882
|
+
// What this actually does: match a fixed set of regex patterns per focus area
|
|
883
|
+
// (eval(, innerHTML=, hardcoded secrets, TODO/FIXME, debugger, console.log,
|
|
884
|
+
// `any`, long functions...) and score the hits. No model is involved. The old
|
|
885
|
+
// description called it an "AI-powered code review agent", which set an
|
|
886
|
+
// expectation the implementation cannot meet.
|
|
887
|
+
description:
|
|
888
|
+
'Scan changed files for a fixed set of known-bad patterns (eval, innerHTML assignment, ' +
|
|
889
|
+
'hardcoded secrets, debugger statements, TODO comments, long functions) and return the ' +
|
|
890
|
+
'hits with a score. This is pattern matching, not a semantic review — it will not catch ' +
|
|
891
|
+
'logic errors, and it will flag patterns that are fine in context.',
|
|
948
892
|
inputSchema: {
|
|
949
893
|
type: 'object',
|
|
950
894
|
properties: {
|
|
@@ -952,117 +896,95 @@ export const codeReviewAgentTool = {
|
|
|
952
896
|
type: 'string',
|
|
953
897
|
enum: ['diff', 'files', 'pull_request'],
|
|
954
898
|
default: 'diff',
|
|
955
|
-
description: '
|
|
899
|
+
description: 'Which files to scan: the working diff, an explicit list, or the diff against main'
|
|
956
900
|
},
|
|
957
901
|
files: {
|
|
958
902
|
type: 'array',
|
|
959
903
|
items: { type: 'string' },
|
|
960
|
-
description: '
|
|
904
|
+
description: 'Files to scan (required when reviewType is "files")'
|
|
961
905
|
},
|
|
962
906
|
focusAreas: {
|
|
963
907
|
type: 'array',
|
|
964
|
-
items: {
|
|
908
|
+
items: {
|
|
909
|
+
type: 'string',
|
|
910
|
+
enum: ['quality', 'security', 'performance', 'maintainability']
|
|
911
|
+
},
|
|
965
912
|
default: ['quality', 'security', 'performance', 'maintainability'],
|
|
966
|
-
description: '
|
|
913
|
+
description: 'Which pattern sets to apply'
|
|
967
914
|
},
|
|
968
915
|
strictness: {
|
|
969
916
|
type: 'string',
|
|
970
917
|
enum: ['low', 'medium', 'high'],
|
|
971
918
|
default: 'medium',
|
|
972
|
-
description: '
|
|
919
|
+
description: 'How heavily each finding counts against the score'
|
|
973
920
|
},
|
|
974
|
-
workingDirectory:
|
|
975
|
-
type: 'string',
|
|
976
|
-
description: 'Working directory (defaults to current directory)'
|
|
977
|
-
}
|
|
921
|
+
workingDirectory: workingDirectoryProperty
|
|
978
922
|
}
|
|
979
923
|
},
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
score: 0
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
let filesToReview: string[] = [];
|
|
997
|
-
|
|
998
|
-
// Get files to review based on type
|
|
999
|
-
switch (args.reviewType) {
|
|
1000
|
-
case 'diff':
|
|
1001
|
-
const diffResult = await executeShellCommand('git', ['diff', '--name-only', 'HEAD'], {
|
|
1002
|
-
workingDirectory: workingDir,
|
|
1003
|
-
captureOutput: true
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
if (diffResult.exitCode === 0) {
|
|
1007
|
-
filesToReview = diffResult.stdout?.split('\n').filter(f => f.trim()) || [];
|
|
1008
|
-
}
|
|
1009
|
-
break;
|
|
1010
|
-
|
|
1011
|
-
case 'files':
|
|
1012
|
-
filesToReview = args.files || [];
|
|
1013
|
-
break;
|
|
1014
|
-
|
|
1015
|
-
case 'pull_request':
|
|
1016
|
-
// Compare with main branch
|
|
1017
|
-
const prDiffResult = await executeShellCommand('git', ['diff', '--name-only', 'main...HEAD'], {
|
|
1018
|
-
workingDirectory: workingDir,
|
|
1019
|
-
captureOutput: true
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
if (prDiffResult.exitCode === 0) {
|
|
1023
|
-
filesToReview = prDiffResult.stdout?.split('\n').filter(f => f.trim()) || [];
|
|
1024
|
-
}
|
|
1025
|
-
break;
|
|
924
|
+
run: async (input, context) => {
|
|
925
|
+
const workingDir = input.workingDirectory || process.cwd();
|
|
926
|
+
const reviewType = input.reviewType ?? 'diff';
|
|
927
|
+
const strictness = input.strictness ?? 'medium';
|
|
928
|
+
const focusAreas = input.focusAreas ?? ['quality', 'security', 'performance', 'maintainability'];
|
|
929
|
+
|
|
930
|
+
let filesToReview: string[];
|
|
931
|
+
|
|
932
|
+
switch (reviewType) {
|
|
933
|
+
case 'diff': {
|
|
934
|
+
const diffResult = await runGit(['diff', '--name-only', 'HEAD'], workingDir, context);
|
|
935
|
+
filesToReview = (diffResult.stdout ?? '').split('\n').filter(f => f.trim());
|
|
936
|
+
break;
|
|
1026
937
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
938
|
+
|
|
939
|
+
case 'pull_request': {
|
|
940
|
+
const prDiffResult = await runGit(
|
|
941
|
+
['diff', '--name-only', 'main...HEAD'],
|
|
942
|
+
workingDir,
|
|
943
|
+
context
|
|
944
|
+
);
|
|
945
|
+
filesToReview = (prDiffResult.stdout ?? '').split('\n').filter(f => f.trim());
|
|
946
|
+
break;
|
|
1033
947
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
};
|
|
1054
|
-
} catch (error) {
|
|
1055
|
-
logger.error('Code review agent failed', { error });
|
|
1056
|
-
return {
|
|
1057
|
-
success: false,
|
|
1058
|
-
reviewResults: null,
|
|
1059
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1060
|
-
};
|
|
948
|
+
|
|
949
|
+
case 'files':
|
|
950
|
+
default:
|
|
951
|
+
if (!input.files || input.files.length === 0) {
|
|
952
|
+
throw ToolError.invalidInput(
|
|
953
|
+
'files is required and must be non-empty when reviewType is "files".'
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
filesToReview = input.files;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const issues: any[] = [];
|
|
961
|
+
const recommendations: any[] = [];
|
|
962
|
+
|
|
963
|
+
for (const file of filesToReview) {
|
|
964
|
+
const fileReview = await reviewFile(file, focusAreas, strictness, workingDir);
|
|
965
|
+
issues.push(...fileReview.issues);
|
|
966
|
+
recommendations.push(...fileReview.recommendations);
|
|
1061
967
|
}
|
|
968
|
+
|
|
969
|
+
const score = calculateReviewScore(issues, strictness);
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
summary: {
|
|
973
|
+
filesReviewed: filesToReview.length,
|
|
974
|
+
totalIssues: issues.length,
|
|
975
|
+
criticalIssues: issues.filter(i => i.severity === 'critical').length,
|
|
976
|
+
highIssues: issues.filter(i => i.severity === 'high').length,
|
|
977
|
+
mediumIssues: issues.filter(i => i.severity === 'medium').length,
|
|
978
|
+
lowIssues: issues.filter(i => i.severity === 'low').length,
|
|
979
|
+
overallScore: score
|
|
980
|
+
},
|
|
981
|
+
issues,
|
|
982
|
+
recommendations,
|
|
983
|
+
score
|
|
984
|
+
};
|
|
1062
985
|
}
|
|
1063
|
-
};
|
|
986
|
+
});
|
|
1064
987
|
|
|
1065
|
-
// Export all safety tools
|
|
1066
988
|
export const safetyTools = [
|
|
1067
989
|
createFeatureBranchTool,
|
|
1068
990
|
runFullTestSuiteTool,
|
|
@@ -1070,4 +992,4 @@ export const safetyTools = [
|
|
|
1070
992
|
rollbackChangesTool,
|
|
1071
993
|
createBackupTool,
|
|
1072
994
|
codeReviewAgentTool
|
|
1073
|
-
];
|
|
995
|
+
];
|