scai 0.1.160 → 0.1.162
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 +196 -94
- package/dist/agents/analysisPlanGenStep.js +15 -26
- package/dist/agents/evidenceVerifierStep.js +25 -13
- package/dist/agents/finalPlanGenStep.js +8 -2
- package/dist/agents/infoPlanGenStep.js +10 -3
- package/dist/agents/iterationFileSelector.js +59 -53
- package/dist/agents/reasonNextStep.js +53 -0
- package/dist/agents/reasonNextTaskStep.js +133 -0
- package/dist/agents/resolveExecutionModeStep.js +18 -33
- package/dist/agents/selectRelevantSourcesStep.js +61 -22
- package/dist/agents/transformPlanGenStep.js +86 -48
- package/dist/agents/validateChangesStep.js +19 -9
- package/dist/agents/writeFileStep.js +7 -6
- package/dist/commands/TasksCmd.js +23 -7
- package/dist/config.js +1 -1
- package/dist/db/schema.js +45 -0
- package/dist/pipeline/modules/codeTransformModule.js +111 -75
- package/dist/pipeline/modules/semanticAnalysisModule.js +18 -1
- package/dist/scripts/dbcheck.js +40 -0
- package/dist/utils/buildContextualPrompt.js +20 -2
- package/dist/utils/checkModel.js +52 -0
- package/dist/utils/parseTaggedContent.js +27 -0
- package/package.json +1 -1
- package/dist/agents/planTargetFilesStep.js +0 -62
- package/dist/agents/reasonNextIterationStep.js +0 -104
package/dist/agents/MainAgent.js
CHANGED
|
@@ -2,7 +2,6 @@ import { builtInModules } from "../pipeline/registry/moduleRegistry.js";
|
|
|
2
2
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
3
3
|
import { infoPlanGenStep } from "./infoPlanGenStep.js";
|
|
4
4
|
import { understandIntentStep } from "./understandIntentStep.js";
|
|
5
|
-
import { contextReviewStep } from "./contextReviewStep.js";
|
|
6
5
|
import { transformPlanGenStep } from "./transformPlanGenStep.js";
|
|
7
6
|
import { getDbForRepo } from "../db/client.js";
|
|
8
7
|
import { writeFileStep } from "./writeFileStep.js";
|
|
@@ -13,12 +12,13 @@ import { readinessGateStep } from "./readinessGateStep.js";
|
|
|
13
12
|
import { scopeClassificationStep } from "./scopeClassificationStep.js";
|
|
14
13
|
import { evidenceVerifierStep } from "./evidenceVerifierStep.js";
|
|
15
14
|
import { validateChangesStep } from './validateChangesStep.js';
|
|
16
|
-
import {
|
|
15
|
+
import { reasonNextTaskStep } from './reasonNextTaskStep.js';
|
|
17
16
|
import { collaboratorStep } from './collaboratorStep.js';
|
|
18
17
|
import { integrateFeedbackStep } from './integrateFeedbackStep.js';
|
|
19
18
|
import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
20
19
|
import { iterationFileSelector } from "./iterationFileSelector.js";
|
|
21
20
|
import { finalAnswerModule } from "../pipeline/modules/finalAnswerModule.js";
|
|
21
|
+
import { reasonNextStep } from "./reasonNextStep.js";
|
|
22
22
|
/* ───────────────────────── registry ───────────────────────── */
|
|
23
23
|
/**
|
|
24
24
|
* Registry mapping action names to their corresponding Module implementations.
|
|
@@ -65,42 +65,40 @@ export class MainAgent {
|
|
|
65
65
|
/* ───────────── main run ───────────── */
|
|
66
66
|
async run() {
|
|
67
67
|
this.runCount = 0;
|
|
68
|
-
|
|
68
|
+
await this.runBoot();
|
|
69
|
+
await this.runPrecheck();
|
|
70
|
+
await this.runScope();
|
|
71
|
+
await this.runGrounding();
|
|
72
|
+
await this.runWorkLoop();
|
|
73
|
+
await this.runFinalize();
|
|
74
|
+
}
|
|
75
|
+
/* ───────────── boot ───────────── */
|
|
76
|
+
async runBoot() {
|
|
77
|
+
var _a;
|
|
69
78
|
await understandIntentStep.run({ context: this.context });
|
|
70
79
|
await resolveExecutionModeStep.run(this.context);
|
|
80
|
+
// Boot the task and get the real DB taskId
|
|
71
81
|
this.taskId = bootTaskForRepo(this.context, getDbForRepo(), this.logLine.bind(this));
|
|
72
|
-
|
|
82
|
+
// Ensure context.task exists and has all required fields
|
|
83
|
+
(_a = this.context).task || (_a.task = {
|
|
84
|
+
id: this.taskId,
|
|
85
|
+
projectId: 0, // optionally set to a real projectId if available
|
|
86
|
+
status: "active",
|
|
87
|
+
initialQuery: this.context.initContext?.userQuery ?? "",
|
|
88
|
+
createdAt: new Date().toISOString(),
|
|
89
|
+
updatedAt: new Date().toISOString(),
|
|
90
|
+
taskSteps: [],
|
|
91
|
+
});
|
|
92
|
+
// If task existed but id wasn’t set, set it now
|
|
93
|
+
this.context.task.id = this.taskId;
|
|
94
|
+
}
|
|
95
|
+
/* ───────────── precheck ───────────── */
|
|
96
|
+
async runPrecheck() {
|
|
73
97
|
await fileCheckStep(this.context);
|
|
74
|
-
|
|
98
|
+
}
|
|
99
|
+
/* ───────────── scope ───────────── */
|
|
100
|
+
async runScope() {
|
|
75
101
|
await scopeClassificationStep.run(this.context);
|
|
76
|
-
/* ===== GROUNDING / INFO ACQUISITION ===== */
|
|
77
|
-
await this.runGrounding();
|
|
78
|
-
/* ===== WORK LOOP ===== */
|
|
79
|
-
const MAX_ITERATIONS = 5;
|
|
80
|
-
let loopCount = 0;
|
|
81
|
-
// Helper: determine next iteration action
|
|
82
|
-
const getNextIterationAction = () => {
|
|
83
|
-
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
84
|
-
if (!nextAction)
|
|
85
|
-
return "complete"; // default safe exit
|
|
86
|
-
return nextAction;
|
|
87
|
-
};
|
|
88
|
-
while (loopCount < MAX_ITERATIONS) {
|
|
89
|
-
this.runCount++;
|
|
90
|
-
loopCount++;
|
|
91
|
-
await this.runWorkIteration();
|
|
92
|
-
const nextAction = getNextIterationAction();
|
|
93
|
-
this.logLine("LOOP", "nextAction", undefined, nextAction);
|
|
94
|
-
if (nextAction === "complete")
|
|
95
|
-
break;
|
|
96
|
-
if (nextAction === "request-feedback")
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
/* ===== FINALIZE ===== */
|
|
100
|
-
// Directly generate the final answer for the user
|
|
101
|
-
await finalAnswerModule.run({ query: this.query, context: this.context });
|
|
102
|
-
// Persist task as usual
|
|
103
|
-
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
104
102
|
}
|
|
105
103
|
/* ───────────── grounding / info acquisition loop ───────────── */
|
|
106
104
|
async runGrounding() {
|
|
@@ -146,25 +144,106 @@ export class MainAgent {
|
|
|
146
144
|
}
|
|
147
145
|
}
|
|
148
146
|
}
|
|
147
|
+
/* ───────────── finalize ───────────── */
|
|
148
|
+
async runFinalize() {
|
|
149
|
+
// generate final answer
|
|
150
|
+
await finalAnswerModule.run({ query: this.query, context: this.context });
|
|
151
|
+
// persist task and steps
|
|
152
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
153
|
+
}
|
|
154
|
+
/* ───────────── work loop ───────────── */
|
|
155
|
+
async runWorkLoop() {
|
|
156
|
+
const MAX_TASK_STEPS = 5;
|
|
157
|
+
let stepCount = 0;
|
|
158
|
+
while (stepCount < MAX_TASK_STEPS) {
|
|
159
|
+
await reasonNextTaskStep.run(this.context);
|
|
160
|
+
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
161
|
+
if (nextAction === "complete") {
|
|
162
|
+
this.logLine("TASK", "All selected files processed — task complete");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const taskStep = await iterationFileSelector.run(this.context);
|
|
166
|
+
if (!taskStep) {
|
|
167
|
+
this.logLine("TASK", "No eligible taskStep found — task complete");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Set current step in context
|
|
171
|
+
if (this.context.task) {
|
|
172
|
+
this.context.task.currentStep = taskStep;
|
|
173
|
+
}
|
|
174
|
+
stepCount++;
|
|
175
|
+
taskStep.taskId = this.taskId;
|
|
176
|
+
taskStep.stepIndex = stepCount;
|
|
177
|
+
taskStep.status = "pending";
|
|
178
|
+
// ⬇️ Persist newly created step
|
|
179
|
+
persistTaskStepInsert(taskStep, getDbForRepo());
|
|
180
|
+
logInputOutput("Step to process", "input", taskStep);
|
|
181
|
+
this.logLine("TASK", `Processing taskStep ${stepCount}/${MAX_TASK_STEPS}`, undefined, taskStep.filePath);
|
|
182
|
+
// ---------------------------
|
|
183
|
+
// Start the step
|
|
184
|
+
// ---------------------------
|
|
185
|
+
taskStep.startTime = Date.now();
|
|
186
|
+
persistTaskStepStart(taskStep, getDbForRepo());
|
|
187
|
+
// ---------------------------
|
|
188
|
+
// Step-level iterations: reason only about this file
|
|
189
|
+
// ---------------------------
|
|
190
|
+
const stepAction = await this.runStepIterations(taskStep);
|
|
191
|
+
if (stepAction === "complete") {
|
|
192
|
+
taskStep.status = "completed";
|
|
193
|
+
taskStep.endTime = Date.now();
|
|
194
|
+
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// optionally mark as pending or redo-step
|
|
198
|
+
taskStep.status = "pending";
|
|
199
|
+
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.logLine("TASK", "Max task step limit reached — stopping work loop");
|
|
203
|
+
}
|
|
204
|
+
/* ───────────── step iterations ───────────── */
|
|
205
|
+
async runStepIterations(taskStep) {
|
|
206
|
+
const MAX_ITERATIONS = 5;
|
|
207
|
+
let loopCount = 0;
|
|
208
|
+
const getNextIterationAction = () => {
|
|
209
|
+
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
210
|
+
if (nextAction !== "continue" &&
|
|
211
|
+
nextAction !== "redo-step" &&
|
|
212
|
+
nextAction !== "expand-scope" &&
|
|
213
|
+
nextAction !== "request-feedback" &&
|
|
214
|
+
nextAction !== "complete") {
|
|
215
|
+
return "complete"; // fallback
|
|
216
|
+
}
|
|
217
|
+
return nextAction;
|
|
218
|
+
};
|
|
219
|
+
// Loop through iterations for a single task step (file)
|
|
220
|
+
while (loopCount < MAX_ITERATIONS) {
|
|
221
|
+
this.runCount++;
|
|
222
|
+
loopCount++;
|
|
223
|
+
// 🔴 HARD RESET — no carryover allowed
|
|
224
|
+
if (this.context.analysis?.iterationReasoning) {
|
|
225
|
+
this.context.analysis.iterationReasoning.nextAction = undefined;
|
|
226
|
+
}
|
|
227
|
+
await this.runWorkIteration(taskStep);
|
|
228
|
+
const nextAction = getNextIterationAction();
|
|
229
|
+
this.logLine("STEP-LOOP", "nextAction", undefined, nextAction);
|
|
230
|
+
if (nextAction === "complete")
|
|
231
|
+
return "complete";
|
|
232
|
+
if (nextAction === "request-feedback")
|
|
233
|
+
return "request-feedback";
|
|
234
|
+
// else: continue
|
|
235
|
+
}
|
|
236
|
+
return "continue";
|
|
237
|
+
}
|
|
149
238
|
/* ───────────── work iteration ───────────── */
|
|
150
|
-
async runWorkIteration() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
await iterationFileSelector.run(this.context); // <-- sets context.analysis.currentTargetFile
|
|
154
|
-
this.logLine("LOOP", "iterationFileSelector", tSelect());
|
|
239
|
+
async runWorkIteration(taskStep) {
|
|
240
|
+
if (!this.context.analysis)
|
|
241
|
+
this.context.analysis = {};
|
|
155
242
|
// ---------------- ANALYSIS ----------------
|
|
156
243
|
if (this.canExecutePhase("analysis") && this.canExecuteScope("analysis")) {
|
|
157
244
|
const tAnalysis = this.startTimer();
|
|
158
245
|
await analysisPlanGenStep.run(this.context);
|
|
159
246
|
this.logLine("PLAN", "analysisPlanGen", tAnalysis());
|
|
160
|
-
/* // 🔎 DEBUG BREAKPOINT — inspect context BEFORE analysis planning
|
|
161
|
-
debugContext(this.context, {
|
|
162
|
-
step: "pre-analysisPlanGen",
|
|
163
|
-
note: "After iterationFileSelector, before analysisPlanGen",
|
|
164
|
-
exit: true, // set to false if you want execution to continue
|
|
165
|
-
depth: 6
|
|
166
|
-
});
|
|
167
|
-
*/
|
|
168
247
|
const analysisPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
169
248
|
for (const step of analysisPlan.steps) {
|
|
170
249
|
const tStep = this.startTimer();
|
|
@@ -181,13 +260,11 @@ export class MainAgent {
|
|
|
181
260
|
await transformPlanGenStep.run(this.context);
|
|
182
261
|
this.logLine("PLAN", "transformPlanGen", tTransform());
|
|
183
262
|
const transformPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
184
|
-
|
|
185
|
-
|
|
263
|
+
const firstStep = transformPlan.steps[0];
|
|
264
|
+
if (firstStep) {
|
|
186
265
|
const tStep = this.startTimer();
|
|
187
|
-
await this.executeStep(
|
|
188
|
-
|
|
189
|
-
this.logLine("STEP", `#${stepCounter} - ${step.action || "unnamedStep"}`, tStep());
|
|
190
|
-
stepCounter++; // 🔹 increment counter
|
|
266
|
+
await this.executeStep(firstStep, { query: this.query });
|
|
267
|
+
this.logLine("STEP", `#1 (only) - ${firstStep.action || "unnamedStep"}`, tStep());
|
|
191
268
|
}
|
|
192
269
|
if (this.context.analysis) {
|
|
193
270
|
this.context.analysis.planSuggestion = undefined;
|
|
@@ -205,8 +282,8 @@ export class MainAgent {
|
|
|
205
282
|
}
|
|
206
283
|
// ---------------- REASONING ----------------
|
|
207
284
|
const tReason = this.startTimer();
|
|
208
|
-
await
|
|
209
|
-
this.logLine("REASONING", "
|
|
285
|
+
await reasonNextStep.run(this.context, taskStep);
|
|
286
|
+
this.logLine("REASONING", "reasonNextStep", tReason());
|
|
210
287
|
// ---------------- COLLABORATOR FEEDBACK ----------------
|
|
211
288
|
const tCollab = this.startTimer();
|
|
212
289
|
await collaboratorStep.run(this.context);
|
|
@@ -215,22 +292,6 @@ export class MainAgent {
|
|
|
215
292
|
const tIntegrate = this.startTimer();
|
|
216
293
|
await integrateFeedbackStep.run(this.context);
|
|
217
294
|
this.logLine("FEEDBACK", "integrateFeedbackStep", tIntegrate());
|
|
218
|
-
// ---------------- REVIEW / RECOVERY ----------------
|
|
219
|
-
const tReview = this.startTimer();
|
|
220
|
-
const review = await contextReviewStep(this.context);
|
|
221
|
-
this.logLine("REVIEW", "contextReviewStep", tReview());
|
|
222
|
-
if (review.decision === "gatherData" && this.runCount < this.maxRuns) {
|
|
223
|
-
this.runCount++;
|
|
224
|
-
if (this.context.analysis) {
|
|
225
|
-
this.context.analysis.readiness = {
|
|
226
|
-
decision: 'not-ready',
|
|
227
|
-
confidence: 0,
|
|
228
|
-
rationale: 'Additional data gathering required.'
|
|
229
|
-
};
|
|
230
|
-
this.context.analysis.planSuggestion = undefined;
|
|
231
|
-
}
|
|
232
|
-
this.logLine("REVIEW", "contextReviewStep", undefined, "Not ready, looping back for additional data gathering");
|
|
233
|
-
}
|
|
234
295
|
}
|
|
235
296
|
/* ───────────── step executor ───────────── */
|
|
236
297
|
/**
|
|
@@ -276,29 +337,22 @@ export class MainAgent {
|
|
|
276
337
|
* @returns True if the phase can be executed, false otherwise.
|
|
277
338
|
*/
|
|
278
339
|
canExecutePhase(phase) {
|
|
279
|
-
const mode = this.context.executionControl?.mode ?? "transform";
|
|
280
340
|
const constraints = this.context.executionControl?.constraints;
|
|
281
|
-
let allowed;
|
|
282
|
-
// If docsOnly is true, prevent any non-doc actions
|
|
283
341
|
const docsOnly = constraints?.docsOnly ?? false;
|
|
342
|
+
let allowed = false;
|
|
284
343
|
switch (phase) {
|
|
285
344
|
case "analysis":
|
|
286
|
-
allowed = !docsOnly && (constraints?.allowAnalysis ?? (mode !== "explain"));
|
|
287
|
-
break;
|
|
288
345
|
case "planning":
|
|
289
|
-
|
|
346
|
+
// Read-only phases are always allowed
|
|
347
|
+
allowed = !docsOnly;
|
|
290
348
|
break;
|
|
291
349
|
case "transform":
|
|
292
350
|
case "write":
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
case "explain":
|
|
296
|
-
allowed = mode === "explain"; // explain is purely mode-based
|
|
351
|
+
// Side-effect phases require explicit permission
|
|
352
|
+
allowed = constraints?.allowFileWrites ?? false;
|
|
297
353
|
break;
|
|
298
|
-
default:
|
|
299
|
-
allowed = false;
|
|
300
354
|
}
|
|
301
|
-
this.logLine("EXEC", "canExecutePhase", undefined, `phase=${phase},
|
|
355
|
+
this.logLine("EXEC", "canExecutePhase", undefined, `phase=${phase}, docsOnly=${docsOnly}, allowed=${allowed}`);
|
|
302
356
|
return allowed;
|
|
303
357
|
}
|
|
304
358
|
/* ───────────── scope gates ───────────── */
|
|
@@ -310,24 +364,17 @@ export class MainAgent {
|
|
|
310
364
|
*/
|
|
311
365
|
canExecuteScope(phase) {
|
|
312
366
|
const scope = this.context.analysis?.scopeType ?? "repo-wide";
|
|
313
|
-
let allowed;
|
|
367
|
+
let allowed = false;
|
|
314
368
|
switch (scope) {
|
|
315
369
|
case "none":
|
|
316
|
-
// Non-repo
|
|
317
|
-
allowed = phase === "
|
|
370
|
+
// Non-repo questions: analysis/planning only
|
|
371
|
+
allowed = phase === "analysis" || phase === "planning";
|
|
318
372
|
break;
|
|
319
373
|
case "single-file":
|
|
320
374
|
case "multi-file":
|
|
321
|
-
// Local scopes: all phases are safe
|
|
322
|
-
allowed = true;
|
|
323
|
-
break;
|
|
324
375
|
case "repo-wide":
|
|
325
|
-
|
|
326
|
-
// but analysis/planning/transform are allowed
|
|
327
|
-
allowed = phase !== "explain";
|
|
376
|
+
allowed = true;
|
|
328
377
|
break;
|
|
329
|
-
default:
|
|
330
|
-
allowed = false;
|
|
331
378
|
}
|
|
332
379
|
this.logLine("EXEC", "canExecuteScope", undefined, `phase=${phase}, scope=${scope}, allowed=${allowed}`);
|
|
333
380
|
return allowed;
|
|
@@ -505,3 +552,58 @@ export function bootTaskForRepo(context, db, logLine) {
|
|
|
505
552
|
logLine("TASK", `created task id = ${taskId}`);
|
|
506
553
|
return taskId;
|
|
507
554
|
}
|
|
555
|
+
export function persistTaskStepInsert(taskStep, db) {
|
|
556
|
+
if (taskStep.id)
|
|
557
|
+
return;
|
|
558
|
+
const nowIso = new Date().toISOString();
|
|
559
|
+
const result = db.prepare(`
|
|
560
|
+
INSERT INTO task_steps (
|
|
561
|
+
task_id,
|
|
562
|
+
file_path,
|
|
563
|
+
action,
|
|
564
|
+
status,
|
|
565
|
+
step_index,
|
|
566
|
+
start_time,
|
|
567
|
+
result_json,
|
|
568
|
+
notes,
|
|
569
|
+
created_at,
|
|
570
|
+
updated_at
|
|
571
|
+
)
|
|
572
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
573
|
+
`).run(taskStep.taskId, taskStep.filePath, taskStep.action ?? null, taskStep.status, taskStep.stepIndex ?? null, taskStep.startTime ?? null, taskStep.result != null ? JSON.stringify(taskStep.result) : null, taskStep.notes ?? null, nowIso, nowIso);
|
|
574
|
+
taskStep.id = result.lastInsertRowid;
|
|
575
|
+
}
|
|
576
|
+
export function persistTaskStepStart(taskStep, db) {
|
|
577
|
+
if (!taskStep.id)
|
|
578
|
+
return;
|
|
579
|
+
if (!taskStep.startTime) {
|
|
580
|
+
taskStep.startTime = Date.now();
|
|
581
|
+
}
|
|
582
|
+
db.prepare(`
|
|
583
|
+
UPDATE task_steps
|
|
584
|
+
SET
|
|
585
|
+
start_time = ?,
|
|
586
|
+
action = ?,
|
|
587
|
+
notes = ?,
|
|
588
|
+
updated_at = ?
|
|
589
|
+
WHERE id = ?
|
|
590
|
+
`).run(taskStep.startTime, taskStep.action ?? null, taskStep.notes ?? null, new Date().toISOString(), taskStep.id);
|
|
591
|
+
}
|
|
592
|
+
export function persistTaskStepCompletion(taskStep, db) {
|
|
593
|
+
if (!taskStep.id)
|
|
594
|
+
return;
|
|
595
|
+
if (!taskStep.endTime) {
|
|
596
|
+
taskStep.endTime = Date.now();
|
|
597
|
+
}
|
|
598
|
+
db.prepare(`
|
|
599
|
+
UPDATE task_steps
|
|
600
|
+
SET
|
|
601
|
+
status = ?,
|
|
602
|
+
end_time = ?,
|
|
603
|
+
action = ?,
|
|
604
|
+
result_json = ?,
|
|
605
|
+
notes = ?,
|
|
606
|
+
updated_at = ?
|
|
607
|
+
WHERE id = ?
|
|
608
|
+
`).run(taskStep.status, taskStep.endTime, taskStep.action ?? null, taskStep.result != null ? JSON.stringify(taskStep.result) : null, taskStep.notes ?? null, new Date().toISOString(), taskStep.id);
|
|
609
|
+
}
|
|
@@ -10,22 +10,25 @@ import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
|
|
|
10
10
|
export const analysisPlanGenStep = {
|
|
11
11
|
name: "analysisPlanGen",
|
|
12
12
|
description: "Generates a single analysis reasoning step.",
|
|
13
|
-
requires: ["analysis.intent", "analysis.focus", "analysis.routingDecision"
|
|
13
|
+
requires: ["analysis.intent", "analysis.focus", "analysis.routingDecision"],
|
|
14
14
|
produces: ["analysis.planSuggestion"],
|
|
15
15
|
async run(context) {
|
|
16
16
|
context.analysis || (context.analysis = {});
|
|
17
17
|
context.execution || (context.execution = {});
|
|
18
|
-
//
|
|
18
|
+
// Ensure task exists (required for currentStep)
|
|
19
|
+
if (!context.task) {
|
|
20
|
+
throw new Error("analysisPlanGenStep: context.task must exist — ensure runBoot has been called");
|
|
21
|
+
}
|
|
22
|
+
// 🔁 Always overwrite previous planSuggestion
|
|
19
23
|
delete context.analysis.planSuggestion;
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const hasSemanticAnalysis = !!targetFile && !!context.analysis.fileAnalysis?.[targetFile];
|
|
24
|
-
// Only skip if we truly have nothing to analyze
|
|
25
|
-
if (!targetFile) {
|
|
24
|
+
// Use the DB-backed currentStep
|
|
25
|
+
const currentStep = context.task.currentStep;
|
|
26
|
+
if (!currentStep?.filePath) {
|
|
26
27
|
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
28
|
+
logInputOutput("analysisPlanGen", "output", { steps: [] });
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
31
|
+
const targetFile = currentStep.filePath;
|
|
29
32
|
// Restrict actions to ANALYSIS only
|
|
30
33
|
const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes("analysis"));
|
|
31
34
|
if (!effectiveActions.length) {
|
|
@@ -33,16 +36,8 @@ export const analysisPlanGenStep = {
|
|
|
33
36
|
logInputOutput("analysisPlanGen", "output", { steps: [] });
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
|
-
// Guard: nothing to analyze
|
|
37
|
-
if (!targetFile) {
|
|
38
|
-
context.analysis.planSuggestion = { plan: { steps: [] } };
|
|
39
|
-
logInputOutput("analysisPlanGen", "output", { steps: [] });
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
39
|
const actionsJson = JSON.stringify(effectiveActions, null, 2);
|
|
43
|
-
const intentText = context.analysis.intent?.normalizedQuery ??
|
|
44
|
-
context.initContext?.userQuery ??
|
|
45
|
-
"";
|
|
40
|
+
const intentText = context.analysis.intent?.normalizedQuery ?? context.initContext?.userQuery ?? "";
|
|
46
41
|
const intentCategory = context.analysis.intent?.intentCategory ?? "";
|
|
47
42
|
const relevantFiles = JSON.stringify([targetFile], null, 2);
|
|
48
43
|
const rationaleText = context.analysis.focus?.rationale ?? "";
|
|
@@ -87,29 +82,23 @@ Return a valid JSON object in this format:
|
|
|
87
82
|
}
|
|
88
83
|
`.trim();
|
|
89
84
|
try {
|
|
90
|
-
logInputOutput("analysis prompt", "input", prompt);
|
|
91
85
|
const genInput = { query: intentText, content: prompt };
|
|
92
86
|
const genOutput = await generate(genInput);
|
|
93
87
|
const raw = typeof genOutput.data === "string"
|
|
94
88
|
? genOutput.data
|
|
95
89
|
: JSON.stringify(genOutput.data ?? "{}");
|
|
96
|
-
const cleaned = await cleanupModule.run({
|
|
97
|
-
query: intentText,
|
|
98
|
-
content: raw
|
|
99
|
-
});
|
|
90
|
+
const cleaned = await cleanupModule.run({ query: intentText, content: raw });
|
|
100
91
|
const jsonString = typeof cleaned.content === "string"
|
|
101
92
|
? cleaned.content
|
|
102
93
|
: JSON.stringify(cleaned.content ?? "{}");
|
|
103
94
|
const planObj = JSON.parse(jsonString);
|
|
104
|
-
const steps = planObj.step
|
|
105
|
-
? [planObj.step]
|
|
106
|
-
: [];
|
|
95
|
+
const steps = planObj.step ? [planObj.step] : [];
|
|
107
96
|
const finalSteps = steps.map((step) => {
|
|
108
97
|
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
109
98
|
return {
|
|
110
99
|
...step,
|
|
111
100
|
groups: actionDef?.groups ?? ["analysis"],
|
|
112
|
-
metadata: step.metadata ?? {}
|
|
101
|
+
metadata: step.metadata ?? {},
|
|
113
102
|
};
|
|
114
103
|
});
|
|
115
104
|
context.analysis.planSuggestion = { plan: { steps: finalSteps } };
|
|
@@ -21,7 +21,6 @@ export const evidenceVerifierStep = {
|
|
|
21
21
|
const candidatePaths = [
|
|
22
22
|
...(context.analysis.focus?.selectedFiles ?? []),
|
|
23
23
|
...(context.analysis.focus?.candidateFiles ?? []),
|
|
24
|
-
...(context.workingFiles?.map(f => f.path) ?? []),
|
|
25
24
|
];
|
|
26
25
|
const uniquePaths = Array.from(new Set(candidatePaths));
|
|
27
26
|
if (!uniquePaths.length) {
|
|
@@ -30,24 +29,37 @@ export const evidenceVerifierStep = {
|
|
|
30
29
|
}
|
|
31
30
|
const filesWithEvidence = [];
|
|
32
31
|
// ----------------- Parse query for targets -----------------
|
|
33
|
-
const sentenceRegex = /['"`](.+?)['"`]/g;
|
|
34
32
|
const sentenceTargets = [];
|
|
33
|
+
// 1️⃣ Capture text inside quotes (existing behavior)
|
|
34
|
+
const quoteRegex = /['"`](.+?)['"`]/g;
|
|
35
35
|
let match;
|
|
36
|
-
while ((match =
|
|
36
|
+
while ((match = quoteRegex.exec(query)) !== null) {
|
|
37
37
|
sentenceTargets.push(match[1]);
|
|
38
38
|
}
|
|
39
|
+
// 2️⃣ Capture heuristic unquoted sentences if none found inside quotes
|
|
40
|
+
if (!sentenceTargets.length) {
|
|
41
|
+
const heuristicSentences = query
|
|
42
|
+
.split(/[\.\n]/)
|
|
43
|
+
.map(s => s.trim())
|
|
44
|
+
.filter(s => s.length > 10);
|
|
45
|
+
heuristicSentences.forEach(s => {
|
|
46
|
+
if (!sentenceTargets.includes(s)) {
|
|
47
|
+
sentenceTargets.push(s);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
39
51
|
const filenameTargets = query
|
|
40
52
|
.split(/\s+/)
|
|
41
|
-
.
|
|
53
|
+
.map(word => word.replace(/['",]/g, ''))
|
|
54
|
+
.filter(w => w.match(/\.(ts|js|tsx|md)$/) || w.length > 5);
|
|
55
|
+
const baseNameTargets = filenameTargets.map(t => t.replace(/\.(ts|js|tsx|md)$/, ''));
|
|
42
56
|
const symbolTargets = [];
|
|
43
|
-
const symbolRegex = /\b([
|
|
57
|
+
const symbolRegex = /\b([a-zA-Z_]\w+)(?:\(\))?\b/g;
|
|
44
58
|
let symMatch;
|
|
45
59
|
while ((symMatch = symbolRegex.exec(query)) !== null) {
|
|
46
60
|
symbolTargets.push(symMatch[1]);
|
|
47
61
|
}
|
|
48
62
|
const mode = context.executionControl?.mode ?? "transform";
|
|
49
|
-
const allowAnalysis = context.executionControl?.constraints?.allowAnalysis ?? mode !== "explain";
|
|
50
|
-
const allowWrites = context.executionControl?.constraints?.allowFileWrites ?? mode === "transform";
|
|
51
63
|
// ----------------- Process each file -----------------
|
|
52
64
|
for (const path of uniquePaths) {
|
|
53
65
|
let code = null;
|
|
@@ -86,11 +98,12 @@ export const evidenceVerifierStep = {
|
|
|
86
98
|
});
|
|
87
99
|
});
|
|
88
100
|
// Filename-level evidence
|
|
89
|
-
const
|
|
90
|
-
|
|
101
|
+
const fullFileName = path.split("/").pop() ?? "";
|
|
102
|
+
const baseFileName = fullFileName.replace(/\.(ts|js|tsx|md)$/, "");
|
|
103
|
+
if (filenameTargets.includes(fullFileName) || baseNameTargets.includes(baseFileName)) {
|
|
91
104
|
evidenceItems.push({
|
|
92
|
-
claim: `Filename matches query target: "${
|
|
93
|
-
excerpt:
|
|
105
|
+
claim: `Filename matches query target: "${fullFileName}"`,
|
|
106
|
+
excerpt: `Path: ${path}`,
|
|
94
107
|
span: { startLine: 0, endLine: 0 },
|
|
95
108
|
confidence: 1,
|
|
96
109
|
});
|
|
@@ -104,10 +117,9 @@ export const evidenceVerifierStep = {
|
|
|
104
117
|
intent: "relevant",
|
|
105
118
|
relevanceExplanation: `${evidenceItems.length} evidence item(s) match the query${isFocusFile ? " (focus file already selected)" : ""}`,
|
|
106
119
|
role: "primary",
|
|
107
|
-
// 🔑 KEY FIX: modification is evidence-gated
|
|
108
120
|
action: {
|
|
109
121
|
isRelevant: true,
|
|
110
|
-
shouldModify: hasEvidence
|
|
122
|
+
shouldModify: hasEvidence
|
|
111
123
|
},
|
|
112
124
|
proposedChanges: hasEvidence
|
|
113
125
|
? {
|
|
@@ -74,9 +74,12 @@ Return strictly valid JSON representing one step:
|
|
|
74
74
|
// Map groups & metadata
|
|
75
75
|
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
76
76
|
step.groups = actionDef?.groups ?? ['finalize'];
|
|
77
|
+
const confidence = context.analysis?.routingDecision?.confidence;
|
|
77
78
|
step.metadata = {
|
|
78
79
|
...step.metadata,
|
|
79
|
-
|
|
80
|
+
...(typeof confidence === 'number' && confidence > 0
|
|
81
|
+
? { routingConfidence: confidence }
|
|
82
|
+
: {})
|
|
80
83
|
};
|
|
81
84
|
// Save single step to context
|
|
82
85
|
context.analysis.planSuggestion = { plan: { steps: [step] } };
|
|
@@ -85,12 +88,15 @@ Return strictly valid JSON representing one step:
|
|
|
85
88
|
catch (err) {
|
|
86
89
|
console.warn('⚠️ Failed to generate final step:', err);
|
|
87
90
|
// Fallback: always include finalAnswer
|
|
91
|
+
const confidence = context.analysis?.routingDecision?.confidence;
|
|
88
92
|
const step = {
|
|
89
93
|
id: 'finalize',
|
|
90
94
|
action: 'finalAnswer',
|
|
91
95
|
description: 'Generate final user-facing response summarizing results.',
|
|
92
96
|
metadata: {
|
|
93
|
-
|
|
97
|
+
...(typeof confidence === 'number' && confidence > 0
|
|
98
|
+
? { routingConfidence: confidence }
|
|
99
|
+
: {})
|
|
94
100
|
},
|
|
95
101
|
groups: ['finalize']
|
|
96
102
|
};
|
|
@@ -71,9 +71,13 @@ If no further information is required, return:
|
|
|
71
71
|
try {
|
|
72
72
|
const genInput = { query: intentText, content: prompt };
|
|
73
73
|
const genOutput = await generate(genInput);
|
|
74
|
-
const raw = typeof genOutput.data === 'string'
|
|
74
|
+
const raw = typeof genOutput.data === 'string'
|
|
75
|
+
? genOutput.data
|
|
76
|
+
: JSON.stringify(genOutput.data ?? '{}');
|
|
75
77
|
const cleaned = await cleanupModule.run({ query: intentText, content: raw });
|
|
76
|
-
const jsonString = typeof cleaned.content === 'string'
|
|
78
|
+
const jsonString = typeof cleaned.content === 'string'
|
|
79
|
+
? cleaned.content
|
|
80
|
+
: JSON.stringify(cleaned.content ?? '{}');
|
|
77
81
|
let step = JSON.parse(jsonString);
|
|
78
82
|
// Validate structure
|
|
79
83
|
if (!step || typeof step.action !== 'string') {
|
|
@@ -83,9 +87,12 @@ If no further information is required, return:
|
|
|
83
87
|
if (step) {
|
|
84
88
|
const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
|
|
85
89
|
step.groups = actionDef?.groups ?? ['info'];
|
|
90
|
+
const confidence = analysis?.routingDecision?.confidence;
|
|
86
91
|
step.metadata = {
|
|
87
92
|
...step.metadata,
|
|
88
|
-
|
|
93
|
+
...(typeof confidence === 'number' && confidence > 0
|
|
94
|
+
? { routingConfidence: confidence }
|
|
95
|
+
: {})
|
|
89
96
|
};
|
|
90
97
|
}
|
|
91
98
|
// Save single step to context as an array (empty if null)
|