scai 0.1.143 → 0.1.144
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/dist/agents/MainAgent.js +107 -112
- package/dist/agents/analysisPlanGenStep.js +118 -0
- package/dist/agents/contextReviewStep.js +2 -2
- package/dist/agents/{preFileSearchCheckStep.js → fileCheckStep.js} +36 -13
- package/dist/agents/finalPlanGenStep.js +22 -23
- package/dist/agents/infoPlanGenStep.js +32 -16
- package/dist/agents/readinessGateStep.js +96 -0
- package/dist/agents/transformPlanGenStep.js +6 -8
- package/dist/agents/writeFileStep.js +3 -15
- package/dist/commands/AskCmd.js +19 -1
- package/dist/db/fileIndex.js +6 -2
- package/dist/index.js +1 -0
- package/dist/pipeline/modules/codeTransformModule.js +17 -0
- package/dist/pipeline/modules/fileSearchModule.js +28 -46
- package/dist/{agents/semanticAnalysisStep.js → pipeline/modules/semanticAnalysisModule.js} +63 -87
- package/dist/pipeline/registry/moduleRegistry.js +14 -1
- package/dist/utils/planActions.js +10 -11
- package/package.json +1 -1
- package/dist/agents/planGeneratorStep.js +0 -117
- package/dist/agents/planResolverStep.js +0 -94
package/dist/agents/MainAgent.js
CHANGED
|
@@ -1,70 +1,20 @@
|
|
|
1
1
|
import { builtInModules } from "../pipeline/registry/moduleRegistry.js";
|
|
2
2
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
3
|
-
import { planResolverStep } from "./planResolverStep.js";
|
|
4
3
|
import { infoPlanGen } from "./infoPlanGenStep.js";
|
|
5
4
|
import { understandIntentStep } from "./understandIntentStep.js";
|
|
6
|
-
import { structuralAnalysisStep } from "./structuralAnalysisStep.js";
|
|
7
5
|
import { contextReviewStep } from "./contextReviewStep.js";
|
|
8
6
|
import { planTargetFilesStep } from "./planTargetFilesStep.js";
|
|
9
|
-
import { validationAnalysisStep } from "./validationAnalysisStep.js";
|
|
10
|
-
import { semanticAnalysisStep } from "./semanticAnalysisStep.js";
|
|
11
7
|
import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
12
8
|
import { transformPlanGenStep } from "./transformPlanGenStep.js";
|
|
13
9
|
import { finalPlanGenStep } from "./finalPlanGenStep.js";
|
|
14
|
-
import { Spinner } from "../lib/spinner.js";
|
|
15
10
|
import { getDbForRepo } from "../db/client.js";
|
|
16
11
|
import { writeFileStep } from "./writeFileStep.js";
|
|
17
12
|
import { resolveExecutionModeStep } from "./resolveExecutionModeStep.js";
|
|
18
|
-
import {
|
|
13
|
+
import { fileCheckStep } from "./fileCheckStep.js";
|
|
14
|
+
import { analysisPlanGenStep } from "./analysisPlanGenStep.js";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { readinessGateStep } from "./readinessGateStep.js";
|
|
19
17
|
/* ───────────────────────── helpers ───────────────────────── */
|
|
20
|
-
let activeSpinner = null;
|
|
21
|
-
/**
|
|
22
|
-
* Called implicitly by MainAgent via side-effect:
|
|
23
|
-
* the first spinner.start() will register itself here.
|
|
24
|
-
*/
|
|
25
|
-
function registerSpinner(spinner) {
|
|
26
|
-
activeSpinner = spinner;
|
|
27
|
-
}
|
|
28
|
-
function startTimer() {
|
|
29
|
-
const start = Date.now();
|
|
30
|
-
return () => Date.now() - start;
|
|
31
|
-
}
|
|
32
|
-
function withSpinnerPaused(fn) {
|
|
33
|
-
if (!activeSpinner) {
|
|
34
|
-
fn();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const wasRunning = typeof activeSpinner.isRunning === "function"
|
|
38
|
-
? activeSpinner.isRunning()
|
|
39
|
-
: true;
|
|
40
|
-
if (wasRunning)
|
|
41
|
-
activeSpinner.stop();
|
|
42
|
-
try {
|
|
43
|
-
fn();
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
if (wasRunning)
|
|
47
|
-
activeSpinner.start();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function logLine(phase, step, ms, desc) {
|
|
51
|
-
withSpinnerPaused(() => {
|
|
52
|
-
process.stdout.write('\r\x1b[K');
|
|
53
|
-
const suffix = desc ? ` — ${desc}` : "";
|
|
54
|
-
const timing = typeof ms === "number" ? ` (${ms}ms)` : "";
|
|
55
|
-
console.log(`[AGENT] ${phase} :: ${step}${suffix}${timing}`);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
function userOutput(message) {
|
|
59
|
-
withSpinnerPaused(() => {
|
|
60
|
-
console.log(`[USER OUTPUT] ${message}`);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
function userPhaseOutput(phase, message) {
|
|
64
|
-
withSpinnerPaused(() => {
|
|
65
|
-
console.log(`[USER OUTPUT] [${phase}] ${message}`);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
18
|
/* ───────────────────────── registry ───────────────────────── */
|
|
69
19
|
const MODULE_REGISTRY = Object.fromEntries(Object.entries(builtInModules).map(([name, mod]) => [name, mod]));
|
|
70
20
|
function resolveModuleForAction(action) {
|
|
@@ -72,24 +22,46 @@ function resolveModuleForAction(action) {
|
|
|
72
22
|
}
|
|
73
23
|
/* ───────────────────────── agent ───────────────────────── */
|
|
74
24
|
export class MainAgent {
|
|
75
|
-
constructor(context) {
|
|
76
|
-
this.spinner = new Spinner();
|
|
25
|
+
constructor(context, ui) {
|
|
77
26
|
this.runCount = 0;
|
|
78
27
|
this.maxRuns = 2;
|
|
79
28
|
this.context = context;
|
|
80
29
|
this.query = context.initContext?.userQuery ?? "";
|
|
30
|
+
this.ui = ui;
|
|
31
|
+
}
|
|
32
|
+
/* ───────────── timers ───────────── */
|
|
33
|
+
startTimer() {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
return () => Date.now() - start;
|
|
36
|
+
}
|
|
37
|
+
/* ───────────── spinner helpers ───────────── */
|
|
38
|
+
withSpinnerPaused(fn) {
|
|
39
|
+
this.ui.pause(fn);
|
|
40
|
+
}
|
|
41
|
+
logLine(phase, step, ms, desc) {
|
|
42
|
+
this.withSpinnerPaused(() => {
|
|
43
|
+
process.stdout.write('\r\x1b[K');
|
|
44
|
+
const suffix = desc ? ` — ${desc}` : "";
|
|
45
|
+
const timing = typeof ms === "number" ? ` (${ms}ms)` : "";
|
|
46
|
+
console.log(`[AGENT] ${phase} :: ${step}${suffix}${timing}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
userOutput(message) {
|
|
50
|
+
this.withSpinnerPaused(() => {
|
|
51
|
+
console.log(`[USER OUTPUT] ${message}`);
|
|
52
|
+
});
|
|
81
53
|
}
|
|
82
54
|
/* ───────────── step executor ───────────── */
|
|
83
55
|
async executeStep(step, input) {
|
|
84
|
-
const stop = startTimer();
|
|
56
|
+
const stop = this.startTimer();
|
|
85
57
|
this.context.currentStep = step;
|
|
86
58
|
const mod = resolveModuleForAction(step.action);
|
|
87
59
|
if (!mod) {
|
|
88
|
-
logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
|
|
60
|
+
this.logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
|
|
89
61
|
return { query: input.query, content: input.content, data: { skipped: true } };
|
|
90
62
|
}
|
|
91
63
|
try {
|
|
92
|
-
this.
|
|
64
|
+
this.ui.update(`Running step: ${step.action}`);
|
|
93
65
|
const output = await mod.run({
|
|
94
66
|
query: step.description ?? input.query,
|
|
95
67
|
content: input.data ?? input.content,
|
|
@@ -97,15 +69,15 @@ export class MainAgent {
|
|
|
97
69
|
});
|
|
98
70
|
if (!output)
|
|
99
71
|
throw new Error(`Module "${mod.name}" returned empty output`);
|
|
100
|
-
logLine("EXECUTE", step.action, stop());
|
|
72
|
+
this.logLine("EXECUTE", step.action, stop());
|
|
101
73
|
return { query: step.description ?? input.query, data: output.data };
|
|
102
74
|
}
|
|
103
75
|
catch (err) {
|
|
104
|
-
logLine("EXECUTE", step.action, stop(), "failed");
|
|
76
|
+
this.logLine("EXECUTE", step.action, stop(), "failed");
|
|
105
77
|
throw err;
|
|
106
78
|
}
|
|
107
79
|
}
|
|
108
|
-
/*
|
|
80
|
+
/* ───────────── execution gates ───────────── */
|
|
109
81
|
canExecutePhase(phase) {
|
|
110
82
|
const mode = this.context.executionControl?.mode ?? "transform";
|
|
111
83
|
switch (phase) {
|
|
@@ -126,18 +98,17 @@ export class MainAgent {
|
|
|
126
98
|
async run() {
|
|
127
99
|
var _a, _b;
|
|
128
100
|
this.runCount++;
|
|
129
|
-
const stopRun = startTimer();
|
|
130
|
-
logLine("RUN", `start #${this.runCount}`);
|
|
101
|
+
const stopRun = this.startTimer();
|
|
102
|
+
this.logLine("RUN", `start #${this.runCount}`);
|
|
131
103
|
logInputOutput("GlobalContext (structured)", "input", this.context);
|
|
132
|
-
this.spinner.start();
|
|
133
104
|
/* ================= BOOT ================= */
|
|
134
105
|
{
|
|
135
|
-
const t1 = startTimer();
|
|
106
|
+
const t1 = this.startTimer();
|
|
136
107
|
await understandIntentStep.run({ context: this.context });
|
|
137
|
-
logLine("BOOT", "understandIntent", t1());
|
|
138
|
-
const t2 = startTimer();
|
|
108
|
+
this.logLine("BOOT", "understandIntent", t1());
|
|
109
|
+
const t2 = this.startTimer();
|
|
139
110
|
await resolveExecutionModeStep.run(this.context);
|
|
140
|
-
logLine("BOOT", "resolveExecutionMode", t2(), `mode
|
|
111
|
+
this.logLine("BOOT", "resolveExecutionMode", t2(), `mode = ${this.context.executionControl?.mode} `);
|
|
141
112
|
// Ensure executionControl exists first
|
|
142
113
|
(_a = this.context).executionControl ?? (_a.executionControl = {
|
|
143
114
|
mode: "transform", // default mode if missing
|
|
@@ -184,16 +155,16 @@ export class MainAgent {
|
|
|
184
155
|
const now = new Date().toISOString();
|
|
185
156
|
const userQuery = this.context.initContext?.userQuery ?? "unknown query";
|
|
186
157
|
const result = db.prepare(`
|
|
187
|
-
INSERT INTO tasks
|
|
188
|
-
|
|
158
|
+
INSERT INTO tasks(initial_query, created_at, updated_at)
|
|
159
|
+
VALUES(?, ?, ?)
|
|
189
160
|
`).run(userQuery, now, now);
|
|
190
161
|
this.taskId = result.lastInsertRowid;
|
|
191
|
-
logLine("TASK", `created task id
|
|
162
|
+
this.logLine("TASK", `created task id = ${this.taskId} "`);
|
|
192
163
|
}
|
|
193
|
-
|
|
194
|
-
let t = startTimer();
|
|
195
|
-
await
|
|
196
|
-
logLine("PRECHECK", "preFileSearch", t());
|
|
164
|
+
// -------------------- INITIAL PRE-FILE CHECK --------------------
|
|
165
|
+
let t = this.startTimer();
|
|
166
|
+
await fileCheckStep(this.context);
|
|
167
|
+
this.logLine("PRECHECK", "preFileSearch", t());
|
|
197
168
|
// -------------------- EXPLAIN MODE HANDLING --------------------
|
|
198
169
|
if (this.canExecutePhase("explain")) {
|
|
199
170
|
const explainMod = resolveModuleForAction("explain");
|
|
@@ -203,57 +174,83 @@ export class MainAgent {
|
|
|
203
174
|
query: this.query,
|
|
204
175
|
context: this.context
|
|
205
176
|
});
|
|
206
|
-
logLine("MODE", "explain", undefined, "returning AI-generated explanation after preFileSearchCheck");
|
|
207
|
-
this.spinner.stop();
|
|
177
|
+
this.logLine("MODE", "explain", undefined, "returning AI-generated explanation after preFileSearchCheck");
|
|
208
178
|
return explainOutput;
|
|
209
179
|
}
|
|
210
|
-
// --------------------
|
|
180
|
+
// -------------------- summarize folder capsules --------------------
|
|
211
181
|
logFolderCapsulesSummary(this.context);
|
|
212
|
-
|
|
182
|
+
this.logLine("BOOT", "folderCapsulesSummary", t(), `📂 ${this.context.initContext?.folderCapsules?.length} folders summarized`);
|
|
213
183
|
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
logLine("ROUTING", "fastPathHit", undefined, "returning final answer early");
|
|
218
|
-
return { query: this.query, data: { finalAnswer: routing.answer, source: "planResolver" } };
|
|
219
|
-
}
|
|
184
|
+
let t = this.startTimer();
|
|
185
|
+
await readinessGateStep.run(this.context);
|
|
186
|
+
this.logLine("HASINFO", "routing", t());
|
|
220
187
|
}
|
|
221
|
-
|
|
188
|
+
/* ================= INFORMATION ACQUISITION PHASE ================= */
|
|
222
189
|
if (this.canExecutePhase("planning")) {
|
|
223
|
-
t = startTimer();
|
|
190
|
+
t = this.startTimer();
|
|
224
191
|
await infoPlanGen.run(this.context);
|
|
225
|
-
logLine("PLAN", "infoPlanGen", t());
|
|
192
|
+
this.logLine("PLAN", "infoPlanGen", t());
|
|
226
193
|
const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
227
194
|
let stepIO = { query: this.query };
|
|
228
195
|
for (const step of infoPlan.steps.filter(s => s.groups?.includes("info"))) {
|
|
229
196
|
stepIO = await this.executeStep(step, stepIO);
|
|
230
197
|
}
|
|
198
|
+
// -------------------- POST-FILE DISCOVERY CHECK --------------------
|
|
199
|
+
// Only run if infoPlan actually generated steps / new files
|
|
200
|
+
const newFilesFound = infoPlan.steps.length > 0;
|
|
201
|
+
if (newFilesFound) {
|
|
202
|
+
t = this.startTimer();
|
|
203
|
+
await fileCheckStep(this.context);
|
|
204
|
+
this.logLine("PRECHECK", "postFileSearch", t());
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.logLine("PRECHECK", "postFileSearch", undefined, chalk.gray("skipped (no new info search steps)"));
|
|
208
|
+
}
|
|
231
209
|
}
|
|
232
210
|
/* ================= ANALYSIS PHASE ================= */
|
|
233
|
-
|
|
234
|
-
|
|
211
|
+
{
|
|
212
|
+
t = this.startTimer();
|
|
235
213
|
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
236
|
-
logLine("ANALYSIS", "selectRelevantSources", t());
|
|
214
|
+
this.logLine("ANALYSIS", "selectRelevantSources", t());
|
|
215
|
+
}
|
|
216
|
+
if (this.canExecutePhase("analysis")) {
|
|
217
|
+
t = this.startTimer();
|
|
218
|
+
await analysisPlanGenStep.run(this.context);
|
|
219
|
+
this.logLine("PLAN", "analysisPlanGen", t());
|
|
220
|
+
const analysisPlan = this.context.analysis?.planSuggestion?.plan?.steps ?? [];
|
|
221
|
+
let stepIO = { query: this.query };
|
|
222
|
+
for (const step of analysisPlan.filter(s => s.groups?.includes("analysis"))) {
|
|
223
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
{
|
|
227
|
+
t = this.startTimer();
|
|
228
|
+
await planTargetFilesStep.run({ query: this.query, context: this.context });
|
|
229
|
+
this.logLine("ANALYSIS", "planTargetFiles", t());
|
|
230
|
+
}
|
|
231
|
+
/* // ── OPTIONAL DEBUGGING ──
|
|
232
|
+
debugContext(this.context, {
|
|
233
|
+
step: "After writefile ",
|
|
234
|
+
note: "Does transform exist?"
|
|
235
|
+
}); */
|
|
236
|
+
/* TO BE MOVED TO PLAN_ACTIONS
|
|
237
|
+
|
|
238
|
+
if (this.canExecutePhase("analysis")) {
|
|
237
239
|
t = startTimer();
|
|
238
240
|
await structuralAnalysisStep.run({ query: this.query, context: this.context });
|
|
239
241
|
logLine("ANALYSIS", "structuralAnalysis", t());
|
|
240
|
-
|
|
241
|
-
await semanticAnalysisStep.run({ query: this.query, context: this.context });
|
|
242
|
-
logLine("ANALYSIS", "semanticAnalysis", t());
|
|
243
|
-
t = startTimer();
|
|
244
|
-
await planTargetFilesStep.run({ query: this.query, context: this.context });
|
|
245
|
-
logLine("ANALYSIS", "planTargetFiles", t());
|
|
242
|
+
|
|
246
243
|
t = startTimer();
|
|
247
244
|
await validationAnalysisStep.run({ query: this.query, context: this.context });
|
|
248
245
|
logLine("VALIDATE", "validationAnalysis", t());
|
|
249
|
-
}
|
|
246
|
+
} */
|
|
250
247
|
const review = await contextReviewStep(this.context);
|
|
251
|
-
if (review.decision === "
|
|
248
|
+
if (review.decision === "has") {
|
|
252
249
|
if (this.runCount >= this.maxRuns) {
|
|
253
|
-
logLine("ROUTING", "gatherData", undefined, "max runs reached, proceeding anyway");
|
|
250
|
+
this.logLine("ROUTING", "gatherData", undefined, chalk.yellow("max runs reached, proceeding anyway"));
|
|
254
251
|
}
|
|
255
252
|
else {
|
|
256
|
-
logLine("ROUTING", "gatherData", undefined, review.reason);
|
|
253
|
+
this.logLine("ROUTING", "gatherData", undefined, chalk.yellow(review.reason));
|
|
257
254
|
this.runCount++;
|
|
258
255
|
this.resetInitContextForLoop();
|
|
259
256
|
return this.run();
|
|
@@ -261,9 +258,9 @@ export class MainAgent {
|
|
|
261
258
|
}
|
|
262
259
|
/* ================= TRANSFORM PHASE ================= */
|
|
263
260
|
if (this.canExecutePhase("transform")) {
|
|
264
|
-
let t = startTimer();
|
|
261
|
+
let t = this.startTimer();
|
|
265
262
|
await transformPlanGenStep.run(this.context);
|
|
266
|
-
logLine("PLAN", "transformPlanGen", t());
|
|
263
|
+
this.logLine("PLAN", "transformPlanGen", t());
|
|
267
264
|
const transformSteps = (this.context.analysis?.planSuggestion?.plan?.steps ?? [])
|
|
268
265
|
.filter(s => s.groups?.includes("transform"));
|
|
269
266
|
let stepIO = { query: this.query };
|
|
@@ -272,29 +269,28 @@ export class MainAgent {
|
|
|
272
269
|
}
|
|
273
270
|
/* ================= WRITE FILES ================= */
|
|
274
271
|
if (this.canExecutePhase("write")) {
|
|
275
|
-
const tWrite = startTimer();
|
|
272
|
+
const tWrite = this.startTimer();
|
|
276
273
|
stepIO = await writeFileStep.run({
|
|
277
274
|
query: this.query,
|
|
278
275
|
context: this.context,
|
|
279
276
|
data: stepIO.data
|
|
280
277
|
});
|
|
281
|
-
logLine("EXECUTE", "writeFile", tWrite());
|
|
278
|
+
this.logLine("EXECUTE", "writeFile", tWrite());
|
|
282
279
|
}
|
|
283
280
|
}
|
|
284
281
|
/* ================= FINALIZE PHASE ================= */
|
|
285
282
|
{
|
|
286
|
-
const t = startTimer();
|
|
283
|
+
const t = this.startTimer();
|
|
287
284
|
await finalPlanGenStep.run(this.context);
|
|
288
|
-
logLine("PLAN", "finalPlanGen", t());
|
|
285
|
+
this.logLine("PLAN", "finalPlanGen", t());
|
|
289
286
|
const finalPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
290
287
|
let stepIO = { query: this.query };
|
|
291
288
|
for (const step of finalPlan.steps.filter(s => s.groups?.includes("finalize"))) {
|
|
292
289
|
stepIO = await this.executeStep(step, stepIO);
|
|
293
290
|
}
|
|
294
291
|
}
|
|
295
|
-
this.
|
|
296
|
-
|
|
297
|
-
logLine("RUN", "complete", stopRun());
|
|
292
|
+
this.userOutput("All input/output logs can be found at ~/.scai/input_output.log");
|
|
293
|
+
this.logLine("RUN", "complete", stopRun());
|
|
298
294
|
/* console.log("\n[DEBUG] Final MainAgent context:");
|
|
299
295
|
console.dir(this.context, { depth: null, colors: true }); */
|
|
300
296
|
return { query: this.query, data: {} };
|
|
@@ -333,5 +329,4 @@ export function logFolderCapsulesSummary(context) {
|
|
|
333
329
|
return body ? `${header}\n${body}` : header;
|
|
334
330
|
}).join("\n\n"); // extra newline for readability
|
|
335
331
|
logInputOutput('FolderCapsule summarized', 'output', context.analysis.folderCapsulesHuman = humanReadable);
|
|
336
|
-
logLine("BOOT", "folderCapsulesSummary", undefined, `📂 ${capsules.length} folders summarized`);
|
|
337
332
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/agents/analysisPlanGenStep.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
|
+
* ANALYSIS PLAN GENERATOR
|
|
9
|
+
* Generates analysis reasoning steps only.
|
|
10
|
+
*/
|
|
11
|
+
export const analysisPlanGenStep = {
|
|
12
|
+
name: "analysisPlanGen",
|
|
13
|
+
description: "Generates an analysis reasoning plan.",
|
|
14
|
+
requires: ["analysis.intent", "analysis.focus", "analysis.routingDecision"],
|
|
15
|
+
produces: ["analysis.planSuggestion"],
|
|
16
|
+
async run(context) {
|
|
17
|
+
context.analysis || (context.analysis = {});
|
|
18
|
+
// 🔁 Always overwrite previous planSuggestion for analysis phase
|
|
19
|
+
delete context.analysis.planSuggestion;
|
|
20
|
+
// --- Skip if routingDecision indicates task is already resolved ---
|
|
21
|
+
if (context.analysis.routingDecision?.decision === "has-info") {
|
|
22
|
+
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
23
|
+
logInputOutput("analysisPlanGen", "output", { steps: [] });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Restrict actions to ANALYSIS only
|
|
27
|
+
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes("analysis"));
|
|
28
|
+
if (!effectiveActions.length) {
|
|
29
|
+
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
30
|
+
logInputOutput("analysisPlanGen", "output", { steps: [] });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const actionsJson = JSON.stringify(effectiveActions, null, 2);
|
|
34
|
+
const intentText = context.analysis.intent?.normalizedQuery ??
|
|
35
|
+
context.initContext?.userQuery ??
|
|
36
|
+
"";
|
|
37
|
+
const intentCategory = context.analysis.intent?.intentCategory ?? "";
|
|
38
|
+
const relevantFiles = JSON.stringify(context.analysis.focus?.relevantFiles ?? [], null, 2);
|
|
39
|
+
const rationaleText = context.analysis.focus?.rationale ?? "";
|
|
40
|
+
const understandingText = context.analysis.understanding
|
|
41
|
+
? JSON.stringify(context.analysis.understanding, null, 2)
|
|
42
|
+
: "";
|
|
43
|
+
const prompt = `
|
|
44
|
+
You are an autonomous coding agent.
|
|
45
|
+
Your task is to produce a structured plan describing what analysis or reasoning
|
|
46
|
+
steps are required to understand the codebase well enough to safely plan transformations.
|
|
47
|
+
|
|
48
|
+
Intent / task description:
|
|
49
|
+
${intentText}
|
|
50
|
+
|
|
51
|
+
Task category:
|
|
52
|
+
${intentCategory}
|
|
53
|
+
|
|
54
|
+
Allowed actions (analysis only):
|
|
55
|
+
${actionsJson}
|
|
56
|
+
|
|
57
|
+
Existing relevant files:
|
|
58
|
+
${relevantFiles}
|
|
59
|
+
|
|
60
|
+
Rationale / notes from pre-file check:
|
|
61
|
+
${rationaleText}
|
|
62
|
+
|
|
63
|
+
Existing understanding of the codebase:
|
|
64
|
+
${understandingText}
|
|
65
|
+
|
|
66
|
+
If the intent indicates that this is NOT a coding, refactoring, or inline commenting task,
|
|
67
|
+
then return an empty plan object with an empty "steps" array:
|
|
68
|
+
{ "steps": [] }
|
|
69
|
+
|
|
70
|
+
Return a strictly valid JSON plan:
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
"steps": [
|
|
74
|
+
{
|
|
75
|
+
"action": "analysisAction",
|
|
76
|
+
"targetFile": "optional/path.ts",
|
|
77
|
+
"description": "what this analysis step achieves",
|
|
78
|
+
"metadata": {}
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
`.trim();
|
|
83
|
+
try {
|
|
84
|
+
logInputOutput('analysis prompt', 'output', prompt);
|
|
85
|
+
const genInput = { query: intentText, content: prompt };
|
|
86
|
+
const genOutput = await generate(genInput);
|
|
87
|
+
const raw = typeof genOutput.data === "string"
|
|
88
|
+
? genOutput.data
|
|
89
|
+
: JSON.stringify(genOutput.data ?? "{}");
|
|
90
|
+
const cleaned = await cleanupModule.run({ query: intentText, content: raw });
|
|
91
|
+
const jsonString = typeof cleaned.content === "string"
|
|
92
|
+
? cleaned.content
|
|
93
|
+
: JSON.stringify(cleaned.content ?? "{}");
|
|
94
|
+
let plan = JSON.parse(jsonString);
|
|
95
|
+
if (!plan || !Array.isArray(plan.steps)) {
|
|
96
|
+
throw new Error("Invalid analysis plan structure");
|
|
97
|
+
}
|
|
98
|
+
if (plan.steps.length > MAX_STEPS) {
|
|
99
|
+
plan.steps = plan.steps.slice(0, MAX_STEPS);
|
|
100
|
+
}
|
|
101
|
+
plan.steps = plan.steps.map((step) => {
|
|
102
|
+
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
103
|
+
return {
|
|
104
|
+
...step,
|
|
105
|
+
groups: actionDef?.groups ?? ["analysis"],
|
|
106
|
+
metadata: step.metadata ?? {}
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
context.analysis.planSuggestion = { plan };
|
|
110
|
+
logInputOutput("analysisPlanGen", "output", plan);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.warn("⚠️ Failed to generate analysis plan:", err);
|
|
114
|
+
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
115
|
+
logInputOutput("analysisPlanGen", "output", { steps: [] });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
@@ -49,6 +49,8 @@ Decision meanings:
|
|
|
49
49
|
User Intent:
|
|
50
50
|
${JSON.stringify(intent, null, 2)}
|
|
51
51
|
|
|
52
|
+
Plan Suggestion Status: ${planDecision}
|
|
53
|
+
|
|
52
54
|
Relevant Files (paths only):
|
|
53
55
|
${JSON.stringify(focus.relevantFiles, null, 2)}
|
|
54
56
|
|
|
@@ -61,7 +63,6 @@ ${focus.rationale ?? "No rationale provided."}
|
|
|
61
63
|
Relevant File Summaries (high-signal only):
|
|
62
64
|
${JSON.stringify(summarizedFiles, null, 2)}
|
|
63
65
|
|
|
64
|
-
Plan Suggestion Status: ${planDecision}
|
|
65
66
|
|
|
66
67
|
Question:
|
|
67
68
|
Based on the above, is there enough information to answer the user's question directly?
|
|
@@ -74,7 +75,6 @@ Output STRICT JSON with shape:
|
|
|
74
75
|
"missing": string[]
|
|
75
76
|
}
|
|
76
77
|
`.trim();
|
|
77
|
-
logInputOutput("contextReviewStep", "output", prompt);
|
|
78
78
|
const ai = await generate({
|
|
79
79
|
query: context.initContext?.userQuery ?? "",
|
|
80
80
|
content: prompt,
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { generate } from "../lib/generate.js";
|
|
3
3
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
4
4
|
import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
|
|
5
|
-
|
|
5
|
+
import path from "path";
|
|
6
|
+
export async function fileCheckStep(context) {
|
|
6
7
|
var _a;
|
|
7
8
|
context.analysis ?? (context.analysis = {});
|
|
8
9
|
(_a = context.analysis).focus ?? (_a.focus = { relevantFiles: [], missingFiles: [], rationale: "" });
|
|
@@ -13,11 +14,23 @@ export async function preFileSearchCheckStep(context) {
|
|
|
13
14
|
// Step 2: extract file names from normalizedQuery or planSuggestion
|
|
14
15
|
const extractedFiles = extractFilesFromAnalysis(context.analysis);
|
|
15
16
|
// Step 3: populate focus with safe defaults
|
|
16
|
-
const relevantFiles =
|
|
17
|
-
const missingFiles =
|
|
17
|
+
const relevantFiles = [];
|
|
18
|
+
const missingFiles = [];
|
|
19
|
+
for (const file of extractedFiles) {
|
|
20
|
+
// Try to find full path in knownFiles by matching basename
|
|
21
|
+
const matchedPath = [...knownFiles].find(f => path.basename(f) === file);
|
|
22
|
+
if (matchedPath) {
|
|
23
|
+
relevantFiles.push(matchedPath);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Store as full path if possible, here just keeping relative for now
|
|
27
|
+
missingFiles.push(file);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
18
30
|
context.analysis.focus.relevantFiles = relevantFiles;
|
|
19
31
|
context.analysis.focus.missingFiles = missingFiles;
|
|
20
|
-
context.analysis.focus.rationale =
|
|
32
|
+
context.analysis.focus.rationale =
|
|
33
|
+
`Pre-check: ${relevantFiles.length} files already available, ${missingFiles.length} missing.`;
|
|
21
34
|
// Optional: LLM call for semantic understanding (assumptions, constraints, risks)
|
|
22
35
|
const prompt = `
|
|
23
36
|
You are an AI meta-agent assisting with context verification. The user intent and plan suggestion are below.
|
|
@@ -77,15 +90,25 @@ Task:
|
|
|
77
90
|
console.warn("[preFileSearchCheckStep] Failed to parse cleanup output, using defaults", parseErr);
|
|
78
91
|
}
|
|
79
92
|
// Merge parsed output safely
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
if (Array.isArray(parsed.relevantFiles)) {
|
|
94
|
+
const existing = new Set(context.analysis.focus.relevantFiles);
|
|
95
|
+
context.analysis.focus.relevantFiles = [
|
|
96
|
+
...context.analysis.focus.relevantFiles,
|
|
97
|
+
...parsed.relevantFiles.filter((f) => !existing.has(f))
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(parsed.missingFiles)) {
|
|
101
|
+
const existing = new Set(context.analysis.focus.missingFiles);
|
|
102
|
+
context.analysis.focus.missingFiles = [
|
|
103
|
+
...context.analysis.focus.missingFiles,
|
|
104
|
+
...parsed.missingFiles.filter((f) => !existing.has(f))
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
if (typeof parsed.rationale === "string") {
|
|
108
|
+
context.analysis.focus.rationale =
|
|
109
|
+
(context.analysis.focus.rationale ?? "") +
|
|
110
|
+
"\n[Post-check] " + parsed.rationale;
|
|
111
|
+
}
|
|
89
112
|
if (parsed.understanding && typeof parsed.understanding === "object") {
|
|
90
113
|
context.analysis.understanding = {
|
|
91
114
|
...context.analysis.understanding,
|
|
@@ -14,10 +14,9 @@ export const finalPlanGenStep = {
|
|
|
14
14
|
requires: ['analysis.intent', 'analysis.focus'],
|
|
15
15
|
produces: ['analysis.planSuggestion'],
|
|
16
16
|
async run(context) {
|
|
17
|
-
var _a, _b;
|
|
18
17
|
context.analysis || (context.analysis = {});
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// 🔁 Always overwrite any previous planSuggestion
|
|
19
|
+
delete context.analysis.planSuggestion;
|
|
21
20
|
// Only allow actions in the FINALIZE group
|
|
22
21
|
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('finalize'));
|
|
23
22
|
const actionsJson = JSON.stringify(effectiveActions, null, 2);
|
|
@@ -62,11 +61,9 @@ Example format:
|
|
|
62
61
|
const cleaned = await cleanupModule.run({ query: intentText, content: raw });
|
|
63
62
|
let plan = null;
|
|
64
63
|
if (cleaned.data && typeof cleaned.data === 'object') {
|
|
65
|
-
// ✅ cleanup succeeded
|
|
66
64
|
plan = cleaned.data;
|
|
67
65
|
}
|
|
68
66
|
else if (typeof cleaned.data === 'string') {
|
|
69
|
-
// ⚠️ fallback: try extracting JSON
|
|
70
67
|
const match = cleaned.data.match(/\{[\s\S]*\}/);
|
|
71
68
|
if (match) {
|
|
72
69
|
plan = JSON.parse(match[0]);
|
|
@@ -75,10 +72,9 @@ Example format:
|
|
|
75
72
|
if (!plan || !Array.isArray(plan.steps)) {
|
|
76
73
|
throw new Error('Invalid final plan structure');
|
|
77
74
|
}
|
|
78
|
-
if (
|
|
79
|
-
throw new Error('Invalid final plan structure');
|
|
80
|
-
if (plan.steps.length > MAX_STEPS)
|
|
75
|
+
if (plan.steps.length > MAX_STEPS) {
|
|
81
76
|
plan.steps = plan.steps.slice(0, MAX_STEPS);
|
|
77
|
+
}
|
|
82
78
|
// Map groups & metadata
|
|
83
79
|
plan.steps = plan.steps.map(step => {
|
|
84
80
|
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
@@ -91,7 +87,7 @@ Example format:
|
|
|
91
87
|
groups: actionDef?.groups ?? ['finalize']
|
|
92
88
|
};
|
|
93
89
|
});
|
|
94
|
-
// Ensure at least one finalAnswer step
|
|
90
|
+
// Ensure at least one finalAnswer step
|
|
95
91
|
if (!plan.steps.some(s => s.action === 'finalAnswer')) {
|
|
96
92
|
plan.steps.push({
|
|
97
93
|
action: 'finalAnswer',
|
|
@@ -102,26 +98,29 @@ Example format:
|
|
|
102
98
|
groups: ['finalize']
|
|
103
99
|
});
|
|
104
100
|
}
|
|
105
|
-
// Replace
|
|
106
|
-
context.analysis.planSuggestion
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
];
|
|
101
|
+
// 🔁 Replace planSuggestion entirely for this phase
|
|
102
|
+
context.analysis.planSuggestion = {
|
|
103
|
+
plan
|
|
104
|
+
};
|
|
110
105
|
logInputOutput('finalPlanGen', 'output', plan);
|
|
111
106
|
}
|
|
112
107
|
catch (err) {
|
|
113
108
|
console.warn('⚠️ Failed to generate final plan:', err);
|
|
114
109
|
// Fallback: always include finalAnswer
|
|
115
|
-
context.analysis.planSuggestion
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
context.analysis.planSuggestion = {
|
|
111
|
+
plan: {
|
|
112
|
+
steps: [
|
|
113
|
+
{
|
|
114
|
+
action: 'finalAnswer',
|
|
115
|
+
description: 'Generate final user-facing response summarizing results.',
|
|
116
|
+
metadata: {
|
|
117
|
+
routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
|
|
118
|
+
},
|
|
119
|
+
groups: ['finalize']
|
|
120
|
+
}
|
|
121
|
+
]
|
|
123
122
|
}
|
|
124
|
-
|
|
123
|
+
};
|
|
125
124
|
logInputOutput('finalPlanGen', 'output', context.analysis.planSuggestion.plan);
|
|
126
125
|
}
|
|
127
126
|
}
|