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.
@@ -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 { preFileSearchCheckStep } from "./preFileSearchCheckStep.js";
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.spinner.update(`Running step: ${step.action}`);
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
- /* ───────────────────────── execution gates ───────────────────────── */
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=${this.context.executionControl?.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 (initial_query, created_at, updated_at)
188
- VALUES (?, ?, ?)
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=${this.taskId}"`);
162
+ this.logLine("TASK", `created task id = ${this.taskId} "`);
192
163
  }
193
- /* ================= INFORMATION ACQUISITION ================= */
194
- let t = startTimer();
195
- await preFileSearchCheckStep(this.context);
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
- // -------------------- SUMMARIZE FOLDERCAPSULES FOR PLANNING --------------------
180
+ // -------------------- summarize folder capsules --------------------
211
181
  logFolderCapsulesSummary(this.context);
212
- /* ================= FAST-PATH CHECK ================= */
182
+ this.logLine("BOOT", "folderCapsulesSummary", t(), `📂 ${this.context.initContext?.folderCapsules?.length} folders summarized`);
213
183
  {
214
- await planResolverStep.run(this.context);
215
- const routing = this.context.analysis?.routingDecision;
216
- if (routing?.decision === "final-answer" && routing.answer) {
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
- // -------------------- PLANNING --------------------
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
- if (this.canExecutePhase("analysis")) {
234
- let t = startTimer();
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
- t = startTimer();
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 === "gatherData") {
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.spinner.stop();
296
- userOutput("All input/output logs can be found at ~/.scai/input_output.log");
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
- export async function preFileSearchCheckStep(context) {
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 = extractedFiles.filter(f => knownFiles.has(f));
17
- const missingFiles = extractedFiles.filter(f => !knownFiles.has(f));
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 = `Pre-check: ${relevantFiles.length} files already available, ${missingFiles.length} missing.`;
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
- context.analysis.focus.relevantFiles = Array.isArray(parsed.relevantFiles)
81
- ? parsed.relevantFiles
82
- : context.analysis.focus.relevantFiles;
83
- context.analysis.focus.missingFiles = Array.isArray(parsed.missingFiles)
84
- ? parsed.missingFiles
85
- : context.analysis.focus.missingFiles;
86
- context.analysis.focus.rationale = typeof parsed.rationale === "string"
87
- ? parsed.rationale
88
- : context.analysis.focus.rationale;
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
- (_a = context.analysis).planSuggestion || (_a.planSuggestion = {});
20
- (_b = context.analysis.planSuggestion).plan || (_b.plan = { steps: [] });
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 (!plan || !Array.isArray(plan.steps))
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 if plan is empty
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 existing finalize steps in planSuggestion
106
- context.analysis.planSuggestion.plan.steps = [
107
- ...context.analysis.planSuggestion.plan.steps.filter(s => !s.groups?.includes('finalize')),
108
- ...plan.steps
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.plan.steps = [
116
- {
117
- action: 'finalAnswer',
118
- description: 'Generate final user-facing response summarizing results.',
119
- metadata: {
120
- routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
121
- },
122
- groups: ['finalize']
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
  }