scai 0.1.117 → 0.1.119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -503
- package/dist/agents/MainAgent.js +255 -0
- package/dist/agents/contextReviewStep.js +104 -0
- package/dist/agents/finalPlanGenStep.js +123 -0
- package/dist/agents/infoPlanGenStep.js +126 -0
- package/dist/agents/planGeneratorStep.js +118 -0
- package/dist/agents/planResolverStep.js +95 -0
- package/dist/agents/planTargetFilesStep.js +48 -0
- package/dist/agents/preFileSearchCheckStep.js +95 -0
- package/dist/agents/selectRelevantSourcesStep.js +100 -0
- package/dist/agents/semanticAnalysisStep.js +144 -0
- package/dist/agents/structuralAnalysisStep.js +46 -0
- package/dist/agents/transformPlanGenStep.js +107 -0
- package/dist/agents/understandIntentStep.js +72 -0
- package/dist/agents/validationAnalysisStep.js +87 -0
- package/dist/commands/AskCmd.js +47 -116
- package/dist/commands/ChangeLogUpdateCmd.js +11 -5
- package/dist/commands/CommitSuggesterCmd.js +50 -75
- package/dist/commands/DaemonCmd.js +119 -29
- package/dist/commands/IndexCmd.js +41 -24
- package/dist/commands/InspectCmd.js +0 -1
- package/dist/commands/ReadlineSingleton.js +18 -0
- package/dist/commands/ResetDbCmd.js +20 -21
- package/dist/commands/ReviewCmd.js +89 -54
- package/dist/commands/SummaryCmd.js +12 -18
- package/dist/commands/WorkflowCmd.js +41 -0
- package/dist/commands/factory.js +254 -0
- package/dist/config.js +67 -15
- package/dist/constants.js +20 -4
- package/dist/context.js +10 -11
- package/dist/daemon/daemonQueues.js +63 -0
- package/dist/daemon/daemonWorker.js +40 -63
- package/dist/daemon/generateSummaries.js +58 -0
- package/dist/daemon/runFolderCapsuleBatch.js +247 -0
- package/dist/daemon/runIndexingBatch.js +147 -0
- package/dist/daemon/runKgBatch.js +104 -0
- package/dist/db/fileIndex.js +168 -63
- package/dist/db/functionExtractors/extractFromJava.js +210 -6
- package/dist/db/functionExtractors/extractFromJs.js +173 -214
- package/dist/db/functionExtractors/extractFromTs.js +159 -160
- package/dist/db/functionExtractors/index.js +7 -5
- package/dist/db/schema.js +55 -20
- package/dist/db/sqlTemplates.js +50 -19
- package/dist/fileRules/builtins.js +31 -14
- package/dist/fileRules/codeAllowedExtensions.js +4 -0
- package/dist/fileRules/fileExceptions.js +0 -13
- package/dist/fileRules/ignoredExtensions.js +10 -0
- package/dist/index.js +128 -325
- package/dist/lib/generate.js +37 -14
- package/dist/lib/generateFolderCapsules.js +109 -0
- package/dist/lib/spinner.js +12 -5
- package/dist/modelSetup.js +1 -11
- package/dist/pipeline/modules/changeLogModule.js +16 -19
- package/dist/pipeline/modules/chunkManagerModule.js +24 -0
- package/dist/pipeline/modules/cleanupModule.js +95 -91
- package/dist/pipeline/modules/codeTransformModule.js +208 -0
- package/dist/pipeline/modules/commentModule.js +20 -11
- package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
- package/dist/pipeline/modules/contextReviewModule.js +52 -0
- package/dist/pipeline/modules/fileReaderModule.js +72 -0
- package/dist/pipeline/modules/fileSearchModule.js +136 -0
- package/dist/pipeline/modules/finalAnswerModule.js +53 -0
- package/dist/pipeline/modules/gatherInfoModule.js +176 -0
- package/dist/pipeline/modules/generateTestsModule.js +63 -54
- package/dist/pipeline/modules/kgModule.js +26 -11
- package/dist/pipeline/modules/preserveCodeModule.js +91 -49
- package/dist/pipeline/modules/refactorModule.js +19 -7
- package/dist/pipeline/modules/repairTestsModule.js +44 -36
- package/dist/pipeline/modules/reviewModule.js +23 -13
- package/dist/pipeline/modules/summaryModule.js +27 -35
- package/dist/pipeline/modules/writeFileModule.js +86 -0
- package/dist/pipeline/registry/moduleRegistry.js +38 -93
- package/dist/pipeline/runModulePipeline.js +22 -19
- package/dist/scripts/dbcheck.js +143 -228
- package/dist/utils/buildContextualPrompt.js +245 -172
- package/dist/utils/debugContext.js +24 -0
- package/dist/utils/fileTree.js +16 -6
- package/dist/utils/loadRelevantFolderCapsules.js +64 -0
- package/dist/utils/log.js +2 -0
- package/dist/utils/normalizeData.js +23 -0
- package/dist/utils/planActions.js +60 -0
- package/dist/utils/promptBuilderHelper.js +67 -0
- package/dist/utils/promptLogHelper.js +52 -0
- package/dist/utils/sanitizeQuery.js +20 -8
- package/dist/utils/sleep.js +3 -0
- package/dist/utils/splitCodeIntoChunk.js +65 -32
- package/dist/utils/vscode.js +49 -0
- package/dist/workflow/workflowResolver.js +14 -0
- package/dist/workflow/workflowRunner.js +103 -0
- package/package.json +6 -5
- package/dist/agent/agentManager.js +0 -39
- package/dist/agent/workflowManager.js +0 -95
- package/dist/commands/ModulePipelineCmd.js +0 -31
- package/dist/daemon/daemonBatch.js +0 -186
- package/dist/fileRules/scoreFiles.js +0 -71
- package/dist/lib/generateEmbedding.js +0 -22
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { builtInModules } from "../pipeline/registry/moduleRegistry.js";
|
|
2
|
+
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
3
|
+
import { planResolverStep } from "./planResolverStep.js";
|
|
4
|
+
import { infoPlanGen } from "./infoPlanGenStep.js";
|
|
5
|
+
import { understandIntentStep } from "./understandIntentStep.js";
|
|
6
|
+
import { structuralAnalysisStep } from "./structuralAnalysisStep.js";
|
|
7
|
+
import { contextReviewStep } from "./contextReviewStep.js";
|
|
8
|
+
import { planTargetFilesStep } from "./planTargetFilesStep.js";
|
|
9
|
+
import { validationAnalysisStep } from "./validationAnalysisStep.js";
|
|
10
|
+
import { preFileSearchCheckStep } from "./preFileSearchCheckStep.js";
|
|
11
|
+
import { semanticAnalysisStep } from "./semanticAnalysisStep.js";
|
|
12
|
+
import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
13
|
+
import { transformPlanGenStep } from "./transformPlanGenStep.js";
|
|
14
|
+
import { finalPlanGenStep } from "./finalPlanGenStep.js";
|
|
15
|
+
/** Build a registry of all built-in modules */
|
|
16
|
+
const MODULE_REGISTRY = Object.fromEntries(Object.entries(builtInModules).map(([name, mod]) => [name, mod]));
|
|
17
|
+
/** Resolve a module from the registry */
|
|
18
|
+
function resolveModuleForAction(action) {
|
|
19
|
+
const mod = MODULE_REGISTRY[action];
|
|
20
|
+
if (!mod)
|
|
21
|
+
console.warn(`⚠️ Missing module for action "${action}" — skipping`);
|
|
22
|
+
return mod;
|
|
23
|
+
}
|
|
24
|
+
export class MainAgent {
|
|
25
|
+
constructor(context) {
|
|
26
|
+
this.runCount = 0;
|
|
27
|
+
this.maxRuns = 2;
|
|
28
|
+
this.context = context;
|
|
29
|
+
this.query = context.initContext?.userQuery ?? '';
|
|
30
|
+
}
|
|
31
|
+
/** Generic step executor */
|
|
32
|
+
async executeStep(step, input) {
|
|
33
|
+
console.log(`\n`);
|
|
34
|
+
console.log(`\n =====================================================================================`);
|
|
35
|
+
console.log(`\n⚡ Executing step: ${step.action}`);
|
|
36
|
+
if (step.description)
|
|
37
|
+
console.log(`\n --> Description: ${step.description}`);
|
|
38
|
+
console.log(`\n =====================================================================================`);
|
|
39
|
+
// attach the current step to the context
|
|
40
|
+
if (input.context) {
|
|
41
|
+
input.context.currentStep = step; // <-- new property
|
|
42
|
+
}
|
|
43
|
+
const mod = resolveModuleForAction(step.action);
|
|
44
|
+
if (!mod) {
|
|
45
|
+
return { query: input.query, content: input.content, data: { skipped: true }, context: input.context };
|
|
46
|
+
}
|
|
47
|
+
const moduleInput = {
|
|
48
|
+
query: step.description ?? input.query,
|
|
49
|
+
content: input.data ?? input.content,
|
|
50
|
+
context: input.context // shared reference
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
console.log(` ▶ Running module: ${mod.name}`);
|
|
54
|
+
const output = await mod.run(moduleInput);
|
|
55
|
+
if (!output)
|
|
56
|
+
throw new Error(`Module '${mod.name}' returned empty output`);
|
|
57
|
+
// Return only what matters — context is always the same reference
|
|
58
|
+
return {
|
|
59
|
+
query: step.description ?? input.query,
|
|
60
|
+
data: output.data,
|
|
61
|
+
context: input.context
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error(` ❌ Module "${mod?.name}" failed:`, err);
|
|
66
|
+
return {
|
|
67
|
+
query: input.query,
|
|
68
|
+
content: input.content,
|
|
69
|
+
data: { error: String(err) },
|
|
70
|
+
context: input.context
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Main run sequence */
|
|
75
|
+
async run() {
|
|
76
|
+
var _a;
|
|
77
|
+
this.runCount++;
|
|
78
|
+
console.log(`\n =====================================================================================`);
|
|
79
|
+
console.log(`🚀 ================= [AutonomousAgent] Starting run #${this.runCount} =================`);
|
|
80
|
+
logInputOutput("GlobalContext (structured)", "input", this.context);
|
|
81
|
+
// ---------------------------------------------------------------------
|
|
82
|
+
// GENERATE FOLDER-CAPSULE SUMMARIES
|
|
83
|
+
// ---------------------------------------------------------------------
|
|
84
|
+
if (this.context.initContext?.folderCapsules?.length) {
|
|
85
|
+
const capsulesSummary = this.context.initContext.folderCapsules.map(fc => ({
|
|
86
|
+
path: fc.path,
|
|
87
|
+
fileCount: fc.stats?.fileCount ?? 0,
|
|
88
|
+
depth: fc.depth ?? 0,
|
|
89
|
+
confidence: fc.confidence ?? 0,
|
|
90
|
+
roles: fc.roles ?? [],
|
|
91
|
+
concerns: fc.concerns ?? []
|
|
92
|
+
}));
|
|
93
|
+
// Add a human-readable summary for the LLM
|
|
94
|
+
(_a = this.context).analysis ?? (_a.analysis = {});
|
|
95
|
+
this.context.analysis.folderCapsulesSummary = capsulesSummary;
|
|
96
|
+
const humanReadable = capsulesSummary.map(fc => `- ${fc.path}: ${fc.fileCount} files, depth ${fc.depth}, confidence ${fc.confidence}`).join('\n');
|
|
97
|
+
this.context.analysis.folderCapsulesHuman = humanReadable;
|
|
98
|
+
console.log('\n📂 Folder Capsules Summary (for agent steps):\n', humanReadable);
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------
|
|
101
|
+
// 0️⃣ UNDERSTAND INTENT — First-pass interpretation of query
|
|
102
|
+
// ---------------------------------------------------------------------
|
|
103
|
+
console.log("\n🧠 Running understandIntentStep (pre-analysis fixture)\n");
|
|
104
|
+
await understandIntentStep.run({
|
|
105
|
+
context: this.context
|
|
106
|
+
});
|
|
107
|
+
// ---------------------------------------------------------------------
|
|
108
|
+
// 1️⃣ PLAN RESOLVER — Fast path (context-driven routing)
|
|
109
|
+
// ---------------------------------------------------------------------
|
|
110
|
+
await planResolverStep.run(this.context);
|
|
111
|
+
// --------------------------------------------------
|
|
112
|
+
// 🚦 ROUTING DECISION
|
|
113
|
+
// --------------------------------------------------
|
|
114
|
+
const routing = this.context.analysis?.routingDecision;
|
|
115
|
+
// ✅ FAST PATH: if planResolver thinks we have a final answer, wrap it in ModuleIO and return
|
|
116
|
+
if (routing?.decision === 'final-answer' && routing.answer) {
|
|
117
|
+
console.log("⚡ Fast path hit — returning final answer directly");
|
|
118
|
+
return {
|
|
119
|
+
query: this.query,
|
|
120
|
+
data: {
|
|
121
|
+
finalAnswer: routing.answer,
|
|
122
|
+
source: "planResolver",
|
|
123
|
+
},
|
|
124
|
+
context: this.context
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// --------------------------------------------------
|
|
128
|
+
// ⏩ FALL THROUGH: continue with plan generation
|
|
129
|
+
// --------------------------------------------------
|
|
130
|
+
let textPlan;
|
|
131
|
+
// Use rationale from routing if available (temporary compatibility)
|
|
132
|
+
if (typeof routing?.rationale === 'string' && routing.rationale.trim()) {
|
|
133
|
+
textPlan = routing.rationale;
|
|
134
|
+
}
|
|
135
|
+
// Fallback: use the original query if no plan/rationale is available
|
|
136
|
+
if (!textPlan || textPlan.trim().length === 0) {
|
|
137
|
+
textPlan = this.query;
|
|
138
|
+
}
|
|
139
|
+
// textPlan can now be passed to planGeneratorStep
|
|
140
|
+
// ---------------------------------------------------------------------
|
|
141
|
+
// PRE-FILE-SEARCH CHECK (do we have the files required to continue )
|
|
142
|
+
// ---------------------------------------------------------------------
|
|
143
|
+
await preFileSearchCheckStep(this.context);
|
|
144
|
+
// ---------------------------------------------------------------------
|
|
145
|
+
// 2️⃣ INFO PLAN GENERATION (information-only steps)
|
|
146
|
+
// ---------------------------------------------------------------------
|
|
147
|
+
await infoPlanGen.run(this.context);
|
|
148
|
+
const infoPlan = this.context?.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
149
|
+
const infoSteps = infoPlan.steps.filter(s => s.groups?.includes("info"));
|
|
150
|
+
console.log("information gathering steps:\n");
|
|
151
|
+
console.dir(infoSteps);
|
|
152
|
+
let stepIO = {
|
|
153
|
+
query: this.query,
|
|
154
|
+
context: this.context
|
|
155
|
+
};
|
|
156
|
+
// =====================================================
|
|
157
|
+
// INFORMATION ACQUISITION PHASE
|
|
158
|
+
// Purpose: gather raw information, no interpretation
|
|
159
|
+
// =====================================================
|
|
160
|
+
//let count = 0;
|
|
161
|
+
for (const step of infoSteps) {
|
|
162
|
+
//count++;
|
|
163
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
164
|
+
/* if (count === 4) {
|
|
165
|
+
debugContext(this.context, {
|
|
166
|
+
step: "first step",
|
|
167
|
+
note: "searchFiles?"
|
|
168
|
+
});
|
|
169
|
+
} */
|
|
170
|
+
}
|
|
171
|
+
// =====================================================
|
|
172
|
+
// ANALYSIS PHASE
|
|
173
|
+
// Purpose: understand what we have and what is being asked
|
|
174
|
+
// =====================================================
|
|
175
|
+
// 🎯 RELEVANT SOURCE SELECTION (authoritative narrowing)
|
|
176
|
+
console.log("\n📁 Selecting relevant source files\n");
|
|
177
|
+
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
178
|
+
// ANALYSIS PHASE (deterministic, durable)
|
|
179
|
+
console.log("\n🏗️ Running structural analysis\n");
|
|
180
|
+
await structuralAnalysisStep.run({ query: this.query, context: this.context });
|
|
181
|
+
// SEMANTIC ANALYSIS PHASE (LLM-driven)
|
|
182
|
+
console.log("\n🧠 Running semantic analysis\n");
|
|
183
|
+
await semanticAnalysisStep.run({ query: this.query, context: this.context });
|
|
184
|
+
// SEMANTIC PLAN TARGET FILES (moves focus -> plan.targetFiles)
|
|
185
|
+
console.log("\n🧠 Running plan target files\n");
|
|
186
|
+
await planTargetFilesStep.run({ query: this.query, context: this.context });
|
|
187
|
+
/* debugContext(this.context, {
|
|
188
|
+
step: "Semantic analysis step",
|
|
189
|
+
note: "semantic meaning?"
|
|
190
|
+
}); */
|
|
191
|
+
// VALIDATION PHASE
|
|
192
|
+
console.log("\n🧠 Running validation analysis\n");
|
|
193
|
+
await validationAnalysisStep.run({ query: this.query, context: this.context });
|
|
194
|
+
// =====================================================
|
|
195
|
+
// ROUTING / PLANNING PHASE
|
|
196
|
+
// =====================================================
|
|
197
|
+
const review = await contextReviewStep(this.context);
|
|
198
|
+
if (review.decision === "loopAgain" && this.runCount < this.maxRuns) {
|
|
199
|
+
console.log("🔄 Looping for additional context");
|
|
200
|
+
// Clear noisy bootstrap artifacts before looping
|
|
201
|
+
this.resetInitContextForLoop();
|
|
202
|
+
return this.run();
|
|
203
|
+
}
|
|
204
|
+
// =====================================================
|
|
205
|
+
// TRANSFORM PLAN GENERATION (after context review)
|
|
206
|
+
// Purpose: produce actionable transform steps
|
|
207
|
+
// =====================================================
|
|
208
|
+
console.log("\n🛠️ Generating transform plan\n");
|
|
209
|
+
await transformPlanGenStep.run(this.context);
|
|
210
|
+
// Filter transform steps
|
|
211
|
+
const transformPlan = this.context?.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
212
|
+
const transformSteps = transformPlan.steps.filter(s => s.groups?.includes("transform"));
|
|
213
|
+
console.log("transform steps:\n");
|
|
214
|
+
console.dir(transformSteps);
|
|
215
|
+
// =====================================================
|
|
216
|
+
// TRANSFORM PHASE
|
|
217
|
+
// Purpose: produce concrete changes or artifacts
|
|
218
|
+
// =====================================================
|
|
219
|
+
console.log("\n⚡ Running transform steps\n");
|
|
220
|
+
for (const step of transformSteps) {
|
|
221
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
222
|
+
}
|
|
223
|
+
// =====================================================
|
|
224
|
+
// FINAL PLAN GENERATION
|
|
225
|
+
// Purpose: produce finalize / commit steps
|
|
226
|
+
// =====================================================
|
|
227
|
+
console.log("\n✅ Generating final plan\n");
|
|
228
|
+
await finalPlanGenStep.run(this.context);
|
|
229
|
+
// Filter finalize steps
|
|
230
|
+
const finalPlan = this.context?.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
231
|
+
const finalizeSteps = finalPlan.steps.filter(s => s.groups?.includes("finalize"));
|
|
232
|
+
console.log("finalize steps:\n");
|
|
233
|
+
console.dir(finalizeSteps);
|
|
234
|
+
// =====================================================
|
|
235
|
+
// FINALIZE PHASE
|
|
236
|
+
// Purpose: commit results and respond to the user
|
|
237
|
+
// =====================================================
|
|
238
|
+
for (const step of finalizeSteps) {
|
|
239
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
240
|
+
}
|
|
241
|
+
console.log("🏁 Agent completed.");
|
|
242
|
+
console.log("---------------------------------------------\n");
|
|
243
|
+
console.log("Show final context\n", this.context);
|
|
244
|
+
return stepIO;
|
|
245
|
+
}
|
|
246
|
+
// =====================================================
|
|
247
|
+
// Helper: reset initContext for clean loop
|
|
248
|
+
// =====================================================
|
|
249
|
+
resetInitContextForLoop() {
|
|
250
|
+
if (this.context.initContext) {
|
|
251
|
+
this.context.initContext.relatedFiles = [];
|
|
252
|
+
console.log(" 🧹 Cleared initContext.relatedFiles for clean loop");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// File: src/modules/contextReviewStep.ts
|
|
2
|
+
import { generate } from "../lib/generate.js";
|
|
3
|
+
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
4
|
+
export async function contextReviewStep(context) {
|
|
5
|
+
if (!context.plan?.targetFiles || context.plan.targetFiles.length === 0) {
|
|
6
|
+
throw new Error("[contextReviewStep] No targetFiles defined in plan.");
|
|
7
|
+
}
|
|
8
|
+
// ------------------------------
|
|
9
|
+
// Authoritative view: only target files
|
|
10
|
+
// ------------------------------
|
|
11
|
+
const authoritativeFiles = (context.workingFiles ?? [])
|
|
12
|
+
.filter(f => context.plan.targetFiles.includes(f.path))
|
|
13
|
+
.map((f) => ({
|
|
14
|
+
path: f.path,
|
|
15
|
+
hasCode: Boolean(f.code),
|
|
16
|
+
code: f.code ?? null,
|
|
17
|
+
summary: f.summary ?? null,
|
|
18
|
+
}));
|
|
19
|
+
// ------------------------------
|
|
20
|
+
// Structural verification: only target files
|
|
21
|
+
// ------------------------------
|
|
22
|
+
const structuralProof = context.analysis?.structure
|
|
23
|
+
? {
|
|
24
|
+
fileCount: context.analysis.structure.files.filter(f => context.plan.targetFiles.includes(f.path)).length,
|
|
25
|
+
files: context.analysis.structure.files
|
|
26
|
+
.filter(f => context.plan.targetFiles.includes(f.path))
|
|
27
|
+
.map(f => ({
|
|
28
|
+
path: f.path,
|
|
29
|
+
hasCode: Boolean(authoritativeFiles.find(af => af.path === f.path)?.code),
|
|
30
|
+
})),
|
|
31
|
+
}
|
|
32
|
+
: null;
|
|
33
|
+
// ------------------------------
|
|
34
|
+
// Include combined architecture analysis
|
|
35
|
+
// ------------------------------
|
|
36
|
+
const combinedAnalysis = context.analysis?.combinedAnalysis ?? {};
|
|
37
|
+
const architectureSummary = combinedAnalysis.architectureSummary ?? "";
|
|
38
|
+
const hotspots = combinedAnalysis.hotspots ?? [];
|
|
39
|
+
// ------------------------------
|
|
40
|
+
// Build prompt
|
|
41
|
+
// ------------------------------
|
|
42
|
+
const prompt = `
|
|
43
|
+
You are a meta-reasoning agent responsible for determining whether the agent
|
|
44
|
+
has sufficient information to proceed.
|
|
45
|
+
|
|
46
|
+
IMPORTANT CONTRACT:
|
|
47
|
+
- Any file listed below with a non-null "code" field MUST be treated as the
|
|
48
|
+
complete and authoritative source code for that file.
|
|
49
|
+
- Do NOT request file contents again if "code" is present.
|
|
50
|
+
|
|
51
|
+
User intent:
|
|
52
|
+
${JSON.stringify(context.analysis?.intent ?? {}, null, 2)}
|
|
53
|
+
|
|
54
|
+
Authoritative source files:
|
|
55
|
+
${JSON.stringify(authoritativeFiles, null, 2)}
|
|
56
|
+
|
|
57
|
+
Structural verification:
|
|
58
|
+
${JSON.stringify(structuralProof, null, 2)}
|
|
59
|
+
|
|
60
|
+
Combined architecture analysis:
|
|
61
|
+
{
|
|
62
|
+
"architectureSummary": ${JSON.stringify(architectureSummary, null, 2)}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Your task:
|
|
66
|
+
1. Determine whether the available information is sufficient to fulfill the user intent.
|
|
67
|
+
2. If NOT sufficient, explicitly list what is missing.
|
|
68
|
+
3. Output STRICT JSON with the following shape:
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
"decision": "loopAgain" | "stop",
|
|
72
|
+
"reason": "string",
|
|
73
|
+
"missing": string[]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
- If the intent involves modifying, commenting, refactoring, or analyzing code,
|
|
78
|
+
and at least one relevant file includes full source code, the context SHOULD
|
|
79
|
+
be considered sufficient.
|
|
80
|
+
- Do NOT request information that is already present in the authoritative files.
|
|
81
|
+
`.trim();
|
|
82
|
+
// ------------------------------
|
|
83
|
+
// Call LLM
|
|
84
|
+
// ------------------------------
|
|
85
|
+
const ai = await generate({
|
|
86
|
+
query: context.initContext?.userQuery ?? '',
|
|
87
|
+
content: prompt,
|
|
88
|
+
});
|
|
89
|
+
const text = typeof ai.data === "string" ? ai.data : JSON.stringify(ai.data);
|
|
90
|
+
logInputOutput("contextReviewHelper", "output", text);
|
|
91
|
+
// ------------------------------
|
|
92
|
+
// Parse JSON or fallback
|
|
93
|
+
// ------------------------------
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return {
|
|
99
|
+
decision: text.toLowerCase().includes("loop") ? "loopAgain" : "stop",
|
|
100
|
+
reason: text,
|
|
101
|
+
missing: [],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// File: src/agents/finalPlanGenStep.ts
|
|
2
|
+
import { generate } from '../lib/generate.js';
|
|
3
|
+
import { PLAN_ACTIONS } from '../utils/planActions.js';
|
|
4
|
+
import { logInputOutput } from '../utils/promptLogHelper.js';
|
|
5
|
+
import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
|
|
6
|
+
const MAX_STEPS = 100;
|
|
7
|
+
/**
|
|
8
|
+
* FINAL PLAN GENERATOR
|
|
9
|
+
* Generates steps that finalize results, commit changes, or prepare outputs.
|
|
10
|
+
*/
|
|
11
|
+
export const finalPlanGenStep = {
|
|
12
|
+
name: 'finalPlanGen',
|
|
13
|
+
description: 'Generates finalization steps to wrap up and commit results.',
|
|
14
|
+
requires: ['analysis.intent', 'analysis.focus'],
|
|
15
|
+
produces: ['analysis.planSuggestion'],
|
|
16
|
+
async run(context) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
context.analysis || (context.analysis = {});
|
|
19
|
+
(_a = context.analysis).planSuggestion || (_a.planSuggestion = {});
|
|
20
|
+
(_b = context.analysis.planSuggestion).plan || (_b.plan = { steps: [] });
|
|
21
|
+
// Restrict actions to FINALIZE group only
|
|
22
|
+
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('finalize'));
|
|
23
|
+
const actionsJson = JSON.stringify(effectiveActions, null, 2);
|
|
24
|
+
const intentText = context.analysis.intent?.normalizedQuery ?? '';
|
|
25
|
+
const intentCategory = context.analysis.intent?.intentCategory ?? '';
|
|
26
|
+
const prompt = `
|
|
27
|
+
You are an autonomous coding agent.
|
|
28
|
+
Your task is to produce a structured plan describing finalization steps
|
|
29
|
+
to wrap up work, commit changes, or produce outputs safely.
|
|
30
|
+
|
|
31
|
+
Intent / task description:
|
|
32
|
+
${intentText}
|
|
33
|
+
|
|
34
|
+
If the intent indicates that no finalization is required, return an empty plan object with an empty "steps" array:
|
|
35
|
+
{ "steps": [] }
|
|
36
|
+
|
|
37
|
+
Allowed actions (finalize only):
|
|
38
|
+
${actionsJson}
|
|
39
|
+
|
|
40
|
+
Task category:
|
|
41
|
+
${intentCategory}
|
|
42
|
+
|
|
43
|
+
Folder structure:
|
|
44
|
+
${context.analysis.folderCapsulesHuman ?? ''}
|
|
45
|
+
|
|
46
|
+
Existing relevant files:
|
|
47
|
+
${JSON.stringify(context.analysis.focus?.relevantFiles ?? {}, null, 2)}
|
|
48
|
+
|
|
49
|
+
⚡ Phase guidance:
|
|
50
|
+
- Actions are grouped into phases: info, transform, finalize.
|
|
51
|
+
- Only include finalize steps in this phase.
|
|
52
|
+
- Each step must include: "action", "targetFile" (optional), "description", "metadata"
|
|
53
|
+
|
|
54
|
+
Return a strictly valid JSON plan:
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
"steps": [
|
|
58
|
+
{ "action": "stepName", "targetFile": "optional/path.ts", "description": "explanation", "metadata": {} }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
`.trim();
|
|
62
|
+
try {
|
|
63
|
+
console.log('FinalPlanGenStep prompt', prompt);
|
|
64
|
+
const genInput = { query: intentText, content: prompt };
|
|
65
|
+
const genOutput = await generate(genInput);
|
|
66
|
+
const raw = typeof genOutput.data === 'string'
|
|
67
|
+
? genOutput.data
|
|
68
|
+
: JSON.stringify(genOutput.data ?? '{}');
|
|
69
|
+
const cleaned = await cleanupModule.run({ query: intentText, content: raw });
|
|
70
|
+
const jsonString = typeof cleaned.content === 'string'
|
|
71
|
+
? cleaned.content
|
|
72
|
+
: JSON.stringify(cleaned.content ?? '{}');
|
|
73
|
+
let plan = JSON.parse(jsonString);
|
|
74
|
+
if (!plan || !Array.isArray(plan.steps))
|
|
75
|
+
throw new Error('Invalid final plan structure');
|
|
76
|
+
if (plan.steps.length > MAX_STEPS)
|
|
77
|
+
plan.steps = plan.steps.slice(0, MAX_STEPS);
|
|
78
|
+
// Map groups & metadata
|
|
79
|
+
plan.steps = plan.steps.map(step => {
|
|
80
|
+
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
81
|
+
return {
|
|
82
|
+
...step,
|
|
83
|
+
metadata: {
|
|
84
|
+
...step.metadata,
|
|
85
|
+
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
|
|
86
|
+
},
|
|
87
|
+
groups: actionDef?.groups ?? ['finalize']
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
// Ensure at least one summary step if plan is empty
|
|
91
|
+
if (plan.steps.length === 0) {
|
|
92
|
+
plan.steps.push({
|
|
93
|
+
action: 'finalizeSummary',
|
|
94
|
+
description: 'Summarize what actions were taken and provide a response to the user.',
|
|
95
|
+
metadata: {
|
|
96
|
+
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
|
|
97
|
+
},
|
|
98
|
+
groups: ['finalize']
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Replace existing finalize steps in planSuggestion
|
|
102
|
+
context.analysis.planSuggestion.plan.steps = [
|
|
103
|
+
...context.analysis.planSuggestion.plan.steps.filter(s => !s.groups?.includes('finalize')),
|
|
104
|
+
...plan.steps
|
|
105
|
+
];
|
|
106
|
+
logInputOutput('finalPlanGen', 'output', plan);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.warn('⚠️ Failed to generate final plan:', err);
|
|
110
|
+
context.analysis.planSuggestion.plan.steps = [
|
|
111
|
+
{
|
|
112
|
+
action: 'finalizeSummary',
|
|
113
|
+
description: 'Summarize what actions were taken and provide a response to the user.',
|
|
114
|
+
metadata: {
|
|
115
|
+
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
|
|
116
|
+
},
|
|
117
|
+
groups: ['finalize']
|
|
118
|
+
}
|
|
119
|
+
];
|
|
120
|
+
logInputOutput('finalPlanGen', 'output', context.analysis.planSuggestion.plan);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { generate } from '../lib/generate.js';
|
|
2
|
+
import { PLAN_ACTIONS } from '../utils/planActions.js';
|
|
3
|
+
import { logInputOutput } from '../utils/promptLogHelper.js';
|
|
4
|
+
import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
|
|
5
|
+
const MAX_STEPS = 100;
|
|
6
|
+
/**
|
|
7
|
+
* INFO PLAN GENERATOR
|
|
8
|
+
* Generates information-gathering steps only.
|
|
9
|
+
*/
|
|
10
|
+
export const infoPlanGen = {
|
|
11
|
+
name: 'infoPlanGen',
|
|
12
|
+
description: 'Generates an information-acquisition plan.',
|
|
13
|
+
requires: ['userQuery', 'analysis.intent'],
|
|
14
|
+
produces: ['analysis.planSuggestion'],
|
|
15
|
+
async run(context) {
|
|
16
|
+
context.analysis || (context.analysis = {});
|
|
17
|
+
// --------------------------------------------------
|
|
18
|
+
// Always discard any existing info plan
|
|
19
|
+
// (planner may loop; old plans must not survive)
|
|
20
|
+
// --------------------------------------------------
|
|
21
|
+
delete context.analysis.planSuggestion?.plan;
|
|
22
|
+
// --------------------------------------------------
|
|
23
|
+
// Check for missing files
|
|
24
|
+
// --------------------------------------------------
|
|
25
|
+
const missingFiles = Array.isArray(context.analysis.focus?.missingFiles)
|
|
26
|
+
? context.analysis.focus.missingFiles
|
|
27
|
+
: [];
|
|
28
|
+
if (missingFiles.length === 0) {
|
|
29
|
+
console.log('ℹ️ No missing files — returning empty info plan.');
|
|
30
|
+
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
31
|
+
logInputOutput('infoPlanGen', 'output', { steps: [] });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// --------------------------------------------------
|
|
35
|
+
// Restrict actions to INFO phase only
|
|
36
|
+
// --------------------------------------------------
|
|
37
|
+
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('info'));
|
|
38
|
+
const actionsJson = JSON.stringify(effectiveActions, null, 2);
|
|
39
|
+
console.log('Actions: ', actionsJson);
|
|
40
|
+
const intentText = context.analysis.intent?.normalizedQuery ??
|
|
41
|
+
context.initContext?.userQuery ??
|
|
42
|
+
'';
|
|
43
|
+
const intentCategory = context.analysis.intent?.intentCategory ?? '';
|
|
44
|
+
const prompt = `
|
|
45
|
+
You are an autonomous coding agent.
|
|
46
|
+
Based on the allowed actions you task is to produce a structured plan describing what information should be gathered
|
|
47
|
+
in order to understand and solve the task.
|
|
48
|
+
|
|
49
|
+
Allowed actions (information acquisition only):
|
|
50
|
+
${actionsJson}
|
|
51
|
+
|
|
52
|
+
Intent / task description:
|
|
53
|
+
${intentText}
|
|
54
|
+
|
|
55
|
+
Below is gathered information about the repository:
|
|
56
|
+
|
|
57
|
+
Task category:
|
|
58
|
+
${intentCategory}
|
|
59
|
+
|
|
60
|
+
Folder structure:
|
|
61
|
+
${context.analysis.folderCapsulesHuman ?? ''}
|
|
62
|
+
|
|
63
|
+
Existing relevant files:
|
|
64
|
+
${JSON.stringify(context.analysis.focus?.relevantFiles ?? {}, null, 2)}
|
|
65
|
+
|
|
66
|
+
Missing files:
|
|
67
|
+
${JSON.stringify(missingFiles, null, 2)}
|
|
68
|
+
|
|
69
|
+
Return a structured JSON plan describing which information to gather.
|
|
70
|
+
|
|
71
|
+
JSON schema:
|
|
72
|
+
{
|
|
73
|
+
"steps": [
|
|
74
|
+
{
|
|
75
|
+
"action": "stepName",
|
|
76
|
+
"targetFile": "optional/path.ts",
|
|
77
|
+
"description": "explanation",
|
|
78
|
+
"metadata": {}
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
`.trim();
|
|
83
|
+
try {
|
|
84
|
+
const genInput = {
|
|
85
|
+
query: intentText,
|
|
86
|
+
content: prompt
|
|
87
|
+
};
|
|
88
|
+
const genOutput = await generate(genInput);
|
|
89
|
+
const raw = typeof genOutput.data === 'string'
|
|
90
|
+
? genOutput.data
|
|
91
|
+
: JSON.stringify(genOutput.data ?? '{}');
|
|
92
|
+
const cleaned = await cleanupModule.run({
|
|
93
|
+
query: intentText,
|
|
94
|
+
content: raw
|
|
95
|
+
});
|
|
96
|
+
const jsonString = typeof cleaned.content === 'string'
|
|
97
|
+
? cleaned.content
|
|
98
|
+
: JSON.stringify(cleaned.content ?? '{}');
|
|
99
|
+
let plan = JSON.parse(jsonString);
|
|
100
|
+
if (!plan || !Array.isArray(plan.steps)) {
|
|
101
|
+
throw new Error('Invalid info plan structure');
|
|
102
|
+
}
|
|
103
|
+
if (plan.steps.length > MAX_STEPS) {
|
|
104
|
+
plan.steps = plan.steps.slice(0, MAX_STEPS);
|
|
105
|
+
}
|
|
106
|
+
plan.steps = plan.steps.map((step) => {
|
|
107
|
+
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
108
|
+
return {
|
|
109
|
+
...step,
|
|
110
|
+
metadata: {
|
|
111
|
+
...step.metadata,
|
|
112
|
+
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
|
|
113
|
+
},
|
|
114
|
+
groups: actionDef?.groups ?? []
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
context.analysis.planSuggestion = { plan };
|
|
118
|
+
logInputOutput('infoPlanGen', 'output', plan);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.warn('⚠️ Failed to generate info plan:', err);
|
|
122
|
+
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
123
|
+
logInputOutput('infoPlanGen', 'output', { steps: [] });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|