scai
Version:
> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.
119 lines (107 loc) • 5.12 kB
JavaScript
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';
const MAX_STEPS = 100;
export const planGeneratorStep = {
name: 'planGenerator',
description: 'Converts a textual plan into structured JSON steps.',
requires: ['userQuery', 'intent', 'analysis.routingDecision'],
produces: ['analysis.planSuggestion.plan'],
async run(context) {
var _a, _b;
context.analysis || (context.analysis = {});
// We check for missing files detected in the preFileSearchCheckStep
// If none we filter out fileSearch actions.
const missingFiles = Array.isArray(context.analysis.focus?.missingFiles)
? context.analysis.focus.missingFiles
: [];
const hasMissingFiles = missingFiles.length > 0;
const effectiveActions = PLAN_ACTIONS.filter(a => {
if (!hasMissingFiles && a.action === 'fileSearch') {
return false;
}
return true;
});
const actionsJson = JSON.stringify(effectiveActions, null, 2);
const intentText = context.analysis?.intent?.normalizedQuery ?? context.initContext?.userQuery;
const intentCategory = context.analysis?.intent?.intentCategory ?? '';
const suggestionText = context.analysis?.planSuggestion?.text ?? '';
const prompt = `
You are an autonomous coding agent. Convert the following textual suggestion into a structured JSON plan.
Intent / task description:
${intentText}
Suggest task type:
${intentCategory};
Suggested plan / guidance:
${suggestionText}
Folder info:
${context.analysis?.folderCapsulesHuman}
Allowed actions and their groups (JSON format):
${actionsJson}
Existing relevant files:
${JSON.stringify(context.analysis?.focus?.relevantFiles ?? {}, null, 2)}
Only implement search if there are missing files:
${JSON.stringify(context.analysis?.focus?.missingFiles ?? {}, null, 2)}
⚡ Phase guidance:
- Actions are grouped into phases: info, transform, finalize.
- Respect group boundaries: do all "info" steps first, then "transform", then "finalize".
- Each step must include: "action", "targetFile" (optional), "description", "metadata"
❌ IMPORTANT: Do NOT use "codeTransform" as an analysis step. All analysis tasks are already handled by semantic/structural analysis modules. Only use "codeTransform" if you are generating actual code changes in the transform phase.
Rules:
- Use only the actions listed above.
- Limit to a maximum of ${MAX_STEPS} steps.
- Return strictly valid JSON matching:
{
"steps": [
{ "action": "stepName", "targetFile": "optional/path.ts", "description": "explanation", "metadata": {} }
]
}
`.trim();
try {
console.dir("planGeneratorPrompt: ", prompt);
const genInput = { query: intentText ?? '', content: prompt };
const genOutput = await generate(genInput);
const llmOutput = typeof genOutput.data === 'string'
? genOutput.data
: JSON.stringify(genOutput.data ?? '{}');
const cleaned = await cleanupModule.run({
query: intentText ?? '',
content: llmOutput,
});
const jsonString = typeof cleaned.content === 'string'
? cleaned.content
: JSON.stringify(cleaned.content ?? '{}');
let plan = JSON.parse(jsonString);
if (!plan || typeof plan !== 'object' || !Array.isArray(plan.steps)) {
throw new Error('Invalid plan structure');
}
if (plan.steps.length > MAX_STEPS) {
console.warn(`⚠️ Truncating plan steps: got ${plan.steps.length}, limiting to ${MAX_STEPS}`);
plan.steps = plan.steps.slice(0, MAX_STEPS);
}
plan.steps = plan.steps.map(step => {
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
return {
...step,
metadata: {
...step.metadata,
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
},
groups: actionDef?.groups ?? []
};
});
context.analysis ?? (context.analysis = {});
(_a = context.analysis).planSuggestion ?? (_a.planSuggestion = { text: '' });
context.analysis.planSuggestion.plan = plan;
logInputOutput('planGenerator', 'output', { generatedPlan: plan });
}
catch (err) {
console.warn('⚠️ Failed to parse plan JSON:', err);
context.analysis ?? (context.analysis = {});
(_b = context.analysis).planSuggestion ?? (_b.planSuggestion = { text: '' });
context.analysis.planSuggestion.plan = { steps: [] };
logInputOutput('planGenerator', 'output', { generatedPlan: { steps: [] } });
}
},
};