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**

124 lines (116 loc) 5.19 kB
// File: src/agents/researchPlanGenStep.ts import { generate } from "../lib/generate.js"; import { cleanupModule } from "../pipeline/modules/cleanupModule.js"; import { logInputOutput } from "../utils/promptLogHelper.js"; const RESEARCH_ACTIONS = [ { action: "research-impact-map", description: "Map expected cross-file impact and affected areas before edits.", }, { action: "research-symbol-trace", description: "Trace key symbols and call paths across candidate files.", }, { action: "research-risk-check", description: "Identify risks, assumptions, and safety constraints.", }, { action: "research-architecture-synthesis", description: "Synthesize architecture, hotspots, and coupling points from findings.", }, ]; /** * RESEARCH PLAN GENERATOR * Produces ordered research steps tailored to the current query and scope. * Example: for repo-wide architecture questions, prioritize symbol-trace + synthesis. */ export const researchPlanGenStep = { name: "researchPlanGen", description: "Generates ordered research steps for repo-wide complex lanes.", requires: ["analysis.intent", "analysis.scopeType", "analysis.routingDecision"], produces: ["analysis.planSuggestion"], async run(context) { context.analysis || (context.analysis = {}); delete context.analysis.planSuggestion; const intentText = context.analysis.intent?.normalizedQuery ?? context.initContext?.userQuery ?? ""; const intentCategory = context.analysis.intent?.intentCategory ?? "request"; const scopeType = context.analysis.scopeType ?? "repo-wide"; const selectedFiles = context.analysis.focus?.selectedFiles ?? []; const candidateFiles = context.analysis.focus?.candidateFiles ?? []; const prompt = ` You are generating a research-only execution plan for a coding agent. User intent: ${intentText} Intent category: ${intentCategory} Scope: ${scopeType} Selected files (current): ${JSON.stringify(selectedFiles.slice(0, 20), null, 2)} Candidate files (current): ${JSON.stringify(candidateFiles.slice(0, 30), null, 2)} Allowed research actions (use only these): ${JSON.stringify(RESEARCH_ACTIONS, null, 2)} Rules: - Return 2-4 ordered steps. - Every step action must be one of the allowed research actions. - Include architecture synthesis as final step when scope is multi-file or repo-wide. - Prefer deterministic filePath values: - research-impact-map => "__research__/impact-map" - research-symbol-trace => "__research__/symbol-trace" - research-risk-check => "__research__/risk-check" - research-architecture-synthesis => "__research__/architecture-synthesis" - Return strict JSON only: { "steps": [ { "id": "research:1", "action": "research-impact-map", "targetFile": "__research__/impact-map", "description": "..." } ] } `.trim(); try { const input = { query: intentText, content: prompt }; const generated = await generate(input); const raw = typeof generated.data === "string" ? generated.data : JSON.stringify(generated.data ?? "{}"); const cleaned = await cleanupModule.run({ query: intentText, content: raw }); const jsonString = typeof cleaned.content === "string" ? cleaned.content : JSON.stringify(cleaned.content ?? "{}"); const parsed = JSON.parse(jsonString); const candidateSteps = Array.isArray(parsed.steps) ? parsed.steps : []; const allowedSet = new Set(RESEARCH_ACTIONS.map(a => a.action)); const normalized = candidateSteps .filter(step => typeof step?.action === "string" && allowedSet.has(step.action)) .map((step, index) => { const action = step.action; const defaultFile = action === "research-impact-map" ? "__research__/impact-map" : action === "research-symbol-trace" ? "__research__/symbol-trace" : action === "research-risk-check" ? "__research__/risk-check" : "__research__/architecture-synthesis"; return { id: step.id ?? `research:${index + 1}`, action, targetFile: step.targetFile ?? defaultFile, description: step.description ?? `Run ${action}`, metadata: step.metadata ?? {}, groups: ["analysis", "planning"], }; }) .slice(0, 4); context.analysis.planSuggestion = { plan: { steps: normalized } }; logInputOutput("researchPlanGen", "output", { steps: normalized }); } catch (err) { console.warn("[researchPlanGenStep] Failed to generate research plan:", err); context.analysis.planSuggestion = { plan: { steps: [] } }; logInputOutput("researchPlanGen", "output", { steps: [] }); } }, };