@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
209 lines (208 loc) • 9.94 kB
JavaScript
;
/**
* Impact Analysis Tool - Dependency & Blast Radius Analysis
*
* Accepts free-text input (kubectl commands, YAML manifests, or plain-English
* descriptions) and uses AI reasoning to discover dependencies and assess
* whether the operation is safe to proceed.
*
* PRD #405: Dependency & Impact Analysis
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.IMPACT_ANALYSIS_TOOL_INPUT_SCHEMA = exports.IMPACT_ANALYSIS_TOOL_DESCRIPTION = exports.IMPACT_ANALYSIS_TOOL_NAME = void 0;
exports.handleImpactAnalysisTool = handleImpactAnalysisTool;
const zod_1 = require("zod");
const error_handling_1 = require("../core/error-handling");
const ai_provider_factory_1 = require("../core/ai-provider-factory");
const capability_tools_1 = require("../core/capability-tools");
const resource_tools_1 = require("../core/resource-tools");
const generic_session_manager_1 = require("../core/generic-session-manager");
const shared_prompt_loader_1 = require("../core/shared-prompt-loader");
const internal_tools_1 = require("../core/internal-tools");
// Tool metadata for MCP registration
exports.IMPACT_ANALYSIS_TOOL_NAME = 'impact_analysis';
exports.IMPACT_ANALYSIS_TOOL_DESCRIPTION = 'Analyze the blast radius of a proposed Kubernetes operation. Accepts free-text input: kubectl commands (e.g., "kubectl delete pvc data-postgres-0 -n production"), YAML manifests, or plain-English descriptions (e.g., "what happens if I delete the postgres database?"). Returns whether the operation is safe and a detailed dependency analysis with confidence levels.';
// Zod schema for MCP registration
exports.IMPACT_ANALYSIS_TOOL_INPUT_SCHEMA = {
input: zod_1.z.string().min(1).max(5000).describe('The operation to analyze. Accepts kubectl commands, YAML manifests, or plain-English descriptions.'),
interaction_id: zod_1.z.string().optional().describe('INTERNAL ONLY - Do not populate. Used for evaluation dataset generation.'),
};
/**
* Parse the AI's final JSON response to extract safe and summary fields.
*/
function parseImpactAnalysis(aiResponse) {
try {
const firstBraceIndex = aiResponse.indexOf('{');
if (firstBraceIndex === -1) {
return { safe: false, summary: aiResponse.trim() || 'Analysis completed but no structured output was produced.' };
}
// Track brace depth to find complete JSON object
let braceCount = 0;
let inString = false;
let escapeNext = false;
let jsonEndIndex = -1;
for (let i = firstBraceIndex; i < aiResponse.length; i++) {
const char = aiResponse[i];
if (escapeNext) {
escapeNext = false;
continue;
}
if (char === '\\') {
escapeNext = true;
continue;
}
if (char === '"') {
inString = !inString;
continue;
}
if (inString)
continue;
if (char === '{')
braceCount++;
if (char === '}') {
braceCount--;
if (braceCount === 0) {
jsonEndIndex = i + 1;
break;
}
}
}
if (jsonEndIndex === -1) {
return { safe: false, summary: aiResponse.trim() || 'Analysis completed but no structured output was produced.' };
}
const jsonString = aiResponse.substring(firstBraceIndex, jsonEndIndex);
const parsed = JSON.parse(jsonString);
return {
safe: typeof parsed.safe === 'boolean' ? parsed.safe : false,
summary: parsed.summary || 'No summary provided',
};
}
catch {
return { safe: false, summary: aiResponse.trim() || 'Analysis completed but response could not be parsed.' };
}
}
async function handleImpactAnalysisTool(args, pluginManager) {
const requestId = `impact_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
const logger = new error_handling_1.ConsoleLogger('ImpactAnalysisTool');
try {
// Validate input
const input = args.input;
if (!input || typeof input !== 'string') {
throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.MEDIUM, 'Input is required and must be a string', { operation: 'input_validation', component: 'ImpactAnalysisTool' });
}
logger.info('Processing impact analysis', { requestId, inputLength: input.length });
// Initialize AI provider
const aiProvider = (0, ai_provider_factory_1.createAIProvider)();
// Load system prompt
const systemPrompt = (0, shared_prompt_loader_1.loadPrompt)('impact-analysis-system');
// Local executor for non-plugin tools
const localToolExecutor = async (toolName, toolInput) => {
if (toolName.startsWith('search_capabilities') || toolName.startsWith('query_capabilities')) {
return (0, capability_tools_1.executeCapabilityTools)(toolName, toolInput);
}
if (toolName.startsWith('search_resources') || toolName.startsWith('query_resources')) {
return (0, resource_tools_1.executeResourceTools)(toolName, toolInput);
}
return {
success: false,
error: `Unknown tool: ${toolName}`,
message: `Tool '${toolName}' is not implemented in impact analysis tool`
};
};
// Read-only kubectl tools from plugin
const KUBECTL_READONLY_TOOL_NAMES = [
'kubectl_api_resources',
'kubectl_get',
'kubectl_describe',
'kubectl_events',
'kubectl_get_crd_schema',
];
const pluginKubectlTools = pluginManager
? pluginManager.getDiscoveredTools().filter(t => KUBECTL_READONLY_TOOL_NAMES.includes(t.name))
: [];
// Build tool list (kubectl + knowledge base + git/fs for GitOps verification)
const tools = [...capability_tools_1.CAPABILITY_TOOLS, ...resource_tools_1.RESOURCE_TOOLS, ...pluginKubectlTools, ...(0, internal_tools_1.getInternalTools)()];
// Clean up old clone directories (non-blocking)
(0, internal_tools_1.cleanupOldClones)();
// Chain executors: plugin tools (kubectl) → internal tools (git/fs) → local tools (capabilities/resources)
// Internal executor handles git_clone, fs_read, fs_list; falls back to localToolExecutor for capability/resource tools
const internalToolNames = new Set(['git_clone', 'fs_list', 'fs_read']);
const internalExecutor = (0, internal_tools_1.createInternalToolExecutor)(requestId);
const combinedLocalExecutor = async (toolName, toolInput) => {
if (internalToolNames.has(toolName)) {
return internalExecutor(toolName, toolInput);
}
return localToolExecutor(toolName, toolInput);
};
const toolExecutor = pluginManager
? pluginManager.createToolExecutor(combinedLocalExecutor)
: combinedLocalExecutor;
// Execute tool loop
const result = await aiProvider.toolLoop({
systemPrompt,
userMessage: input,
tools,
toolExecutor,
maxIterations: 30,
operation: 'impact-analysis',
evaluationContext: {
user_intent: input
},
interaction_id: args.interaction_id
});
// Guard: if the AI call did not succeed, surface the real error instead of trying to parse
if (result.status && result.status !== 'success') {
throw new Error(`Impact analysis ${result.status}: ${result.finalMessage}`);
}
// Parse AI response
const { safe, summary } = parseImpactAnalysis(result.finalMessage);
// Extract tools used from execution record
const toolsUsed = [...new Set(result.toolCallsExecuted.map(tc => tc.tool))];
logger.info('Impact analysis completed', {
requestId,
safe,
iterations: result.iterations,
toolsUsed,
});
// Store session
const sessionManager = new generic_session_manager_1.GenericSessionManager('imp');
const session = sessionManager.createSession({
toolName: 'impact_analysis',
input,
safe,
summary,
toolsUsed,
iterations: result.iterations,
toolCallsExecuted: result.toolCallsExecuted,
});
const output = {
success: true,
safe,
summary,
sessionId: session.sessionId,
agentInstructions: safe
? 'Present the impact analysis summary to the user. The operation appears safe to proceed.'
: 'Present the impact analysis summary to the user. The operation is NOT safe — highlight the risks and affected resources before the user proceeds.',
};
return {
content: [
{
type: 'text',
text: JSON.stringify(output, null, 2)
}
]
};
}
catch (error) {
logger.error('Impact analysis failed', error, { requestId });
if (error instanceof Error && 'category' in error) {
throw error;
}
throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.UNKNOWN, error_handling_1.ErrorSeverity.HIGH, `Impact analysis tool failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
operation: 'impact_analysis_tool_execution',
component: 'ImpactAnalysisTool',
requestId,
input: { input: args.input }
});
}
}