UNPKG

scai

Version:

> **A local-first AI CLI for understanding, querying, and iterating on large codebases.** > **100% local • No token costs • No cloud • No prompt injection • Private by design**

172 lines (154 loc) 6.72 kB
// File: src/agents/transformPlanGenStep.ts import { generate } from '../lib/generate.js'; import { PLAN_ACTIONS } from '../utils/planActions.js'; import { logInputOutput } from '../utils/promptLogHelper.js'; import { cleanupModule } from '../pipeline/modules/cleanupModule.js'; /** * TRANSFORM PLAN GENERATOR * Generates one or more code transformation steps per iteration, * strictly scoped to the current TaskStep file. */ export const transformPlanGenStep = { name: 'transformPlanGen', description: 'Generates code transformation steps for a single target file.', requires: ['analysis.intent', 'analysis.focus', 'analysis.fileAnalysis', 'task.currentStep'], produces: ['analysis.planSuggestion'], async run(context) { // ✅ Fail early if no task if (!context.task) { throw new Error("transformPlanGen: missing context.task"); } // ✅ Fail early if no current step const currentStep = context.task.currentStep; if (!currentStep) { throw new Error("transformPlanGen: missing context.task.currentStep"); } context.analysis || (context.analysis = {}); context.execution || (context.execution = {}); // Always clear previous transform plan delete context.analysis.planSuggestion; // Restrict actions to TRANSFORM only const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('transform')); if (!effectiveActions.length) { context.analysis.planSuggestion = { plan: { steps: [] } }; logInputOutput('transformPlanGen', 'output', { steps: [] }); return; } const actionsJson = JSON.stringify(effectiveActions, null, 2); const intentText = context.analysis.intent?.normalizedQuery ?? ''; const intentCategory = context.analysis.intent?.intentCategory ?? ''; const fileAnalysis = context.analysis.fileAnalysis ?? {}; // ✅ Use currentStep file const targetFile = currentStep.filePath; // Guard: nothing to transform if (!targetFile || !fileAnalysis[targetFile]) { context.analysis.planSuggestion = { plan: { steps: [] } }; logInputOutput('transformPlanGen', 'output', { steps: [] }); return; } const fa = fileAnalysis[targetFile]; const excerpts = fa.excerpts?.map(e => ({ description: e.description, startLine: e.startLine, endLine: e.endLine, symbols: e.symbols, code: e.code })) ?? []; const hasExcerpts = excerpts.length > 0; // Build prompt const prompt = hasExcerpts ? ` You are an autonomous coding agent. Your task is to generate a transform plan using ONLY: - the user intent - the provided excerpts as localized hints IMPORTANT CONSTRAINTS: - All steps MUST target the same file: ${targetFile} - Do NOT reference or modify any other file - Do NOT invent file structure beyond what excerpts explicitly show - Do NOT infer missing excerpt fields Intent: ${intentText} Task category: ${intentCategory} Allowed actions (transform only): ${actionsJson} Excerpts (localized hints): ${JSON.stringify(excerpts, null, 2)} Rules: - Use excerpts ONLY to infer WHERE changes occur - You MAY reference startLine/endLine ONLY if they are present - You MAY reference description or symbols if useful - Treat code snippets as context, not as the full file - If the intent implies multiple operations (e.g. move = remove + append), generate multiple steps in logical order - Only include transform steps - Each step must include: action, targetFile, description, metadata Return strictly valid JSON: { "steps": [ { "action": "stepName", "targetFile": "${targetFile}", "description": "what to do and where (using excerpt hints)", "metadata": {} } ] } `.trim() : ` You are an autonomous coding agent. Your task is to generate a transform plan using ONLY the user intent. IMPORTANT CONSTRAINTS: - All steps MUST target the same file: ${targetFile} - Do NOT reference or modify any other file - Do NOT invent line numbers, sections, symbols, or file structure Intent: ${intentText} Task category: ${intentCategory} Allowed actions (transform only): ${actionsJson} Rules: - Plan purely from intent - If the intent implies multiple operations (e.g. move = remove + append), generate multiple steps in logical order - Do NOT include any location hints or references - Only include transform steps - Each step must include: action, targetFile, description, metadata Return strictly valid JSON: { "steps": [ { "action": "stepName", "targetFile": "${targetFile}", "description": "what to do (no location hints)", "metadata": {} } ] } `.trim(); try { const genInput = { query: intentText, content: prompt }; const genOutput = await generate(genInput); const raw = typeof genOutput.data === 'string' ? genOutput.data : JSON.stringify(genOutput.data ?? '{}'); const cleaned = await cleanupModule.run({ query: intentText, content: raw }); const jsonString = typeof cleaned.content === 'string' ? cleaned.content : JSON.stringify(cleaned.content ?? '{}'); let parsed = null; try { parsed = JSON.parse(jsonString); } catch { parsed = null; } const steps = Array.isArray(parsed?.steps) ? parsed.steps : []; // Normalize steps: enforce targetFile, groups, metadata const normalizedSteps = steps.map((step, index) => { step.id = `transform:${targetFile}:${index + 1}`; step.targetFile = targetFile; const actionDef = PLAN_ACTIONS.find(a => a.action === step.action); step.groups = actionDef?.groups ?? ['transform']; const confidence = context.analysis?.routingDecision?.confidence; step.metadata = { ...step.metadata, ...(typeof confidence === 'number' && confidence > 0 ? { routingConfidence: confidence } : {}) }; return step; }); context.analysis.planSuggestion = { plan: { steps: normalizedSteps } }; logInputOutput('transformPlanGen', 'output', normalizedSteps); } catch (err) { console.warn('⚠️ Failed to generate transform plan:', err); context.analysis.planSuggestion = { plan: { steps: [] } }; } } };