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
JavaScript
// 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: [] } };
}
}
};