scai 0.1.158 → 0.1.160
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 +226 -259
- package/dist/agents/analysisPlanGenStep.js +45 -38
- package/dist/agents/collaboratorStep.js +45 -0
- package/dist/agents/contextReviewStep.js +3 -1
- package/dist/agents/evidenceVerifierStep.js +41 -26
- package/dist/agents/finalPlanGenStep.js +49 -75
- package/dist/agents/infoPlanGenStep.js +47 -89
- package/dist/agents/integrateFeedbackStep.js +66 -0
- package/dist/agents/iterationFileSelector.js +68 -0
- package/dist/agents/readinessGateStep.js +8 -4
- package/dist/agents/reasonNextIterationStep.js +104 -0
- package/dist/agents/transformPlanGenStep.js +76 -66
- package/dist/agents/validateChangesStep.js +106 -0
- package/dist/agents/writeFileStep.js +57 -11
- package/dist/config.js +1 -1
- package/dist/index.js +2 -2
- package/dist/pipeline/modules/cleanupModule.js +36 -0
- package/dist/pipeline/modules/codeTransformModule.js +66 -77
- package/dist/pipeline/modules/explainModule.js +47 -35
- package/dist/pipeline/modules/finalAnswerModule.js +30 -13
- package/dist/pipeline/modules/semanticAnalysisModule.js +72 -54
- package/package.json +1 -1
package/dist/agents/MainAgent.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { builtInModules } from "../pipeline/registry/moduleRegistry.js";
|
|
2
2
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
3
|
-
import {
|
|
3
|
+
import { infoPlanGenStep } from "./infoPlanGenStep.js";
|
|
4
4
|
import { understandIntentStep } from "./understandIntentStep.js";
|
|
5
5
|
import { contextReviewStep } from "./contextReviewStep.js";
|
|
6
|
-
import { planTargetFilesStep } from "./planTargetFilesStep.js";
|
|
7
|
-
import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
8
6
|
import { transformPlanGenStep } from "./transformPlanGenStep.js";
|
|
9
|
-
import { finalPlanGenStep } from "./finalPlanGenStep.js";
|
|
10
7
|
import { getDbForRepo } from "../db/client.js";
|
|
11
8
|
import { writeFileStep } from "./writeFileStep.js";
|
|
12
9
|
import { resolveExecutionModeStep } from "./resolveExecutionModeStep.js";
|
|
@@ -14,8 +11,14 @@ import { fileCheckStep } from "./fileCheckStep.js";
|
|
|
14
11
|
import { analysisPlanGenStep } from "./analysisPlanGenStep.js";
|
|
15
12
|
import { readinessGateStep } from "./readinessGateStep.js";
|
|
16
13
|
import { scopeClassificationStep } from "./scopeClassificationStep.js";
|
|
17
|
-
import { routingDecisionStep } from "./routingDecisionStep.js";
|
|
18
14
|
import { evidenceVerifierStep } from "./evidenceVerifierStep.js";
|
|
15
|
+
import { validateChangesStep } from './validateChangesStep.js';
|
|
16
|
+
import { reasonNextIterationStep } from './reasonNextIterationStep.js';
|
|
17
|
+
import { collaboratorStep } from './collaboratorStep.js';
|
|
18
|
+
import { integrateFeedbackStep } from './integrateFeedbackStep.js';
|
|
19
|
+
import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
20
|
+
import { iterationFileSelector } from "./iterationFileSelector.js";
|
|
21
|
+
import { finalAnswerModule } from "../pipeline/modules/finalAnswerModule.js";
|
|
19
22
|
/* ───────────────────────── registry ───────────────────────── */
|
|
20
23
|
/**
|
|
21
24
|
* Registry mapping action names to their corresponding Module implementations.
|
|
@@ -39,7 +42,7 @@ function resolveModuleForAction(action) {
|
|
|
39
42
|
* The agent follows a multi-phase execution model:
|
|
40
43
|
* 1. Boot: Determine intent and execution mode
|
|
41
44
|
* 2. Precheck: File existence validation
|
|
42
|
-
* 3. Scope
|
|
45
|
+
* 3. Scope: Determine where to act and what actions are allowed
|
|
43
46
|
* 4. Grounding & Readiness Loop: Acquire evidence and verify readiness
|
|
44
47
|
* 5. Analysis: Perform in-depth analysis
|
|
45
48
|
* 6. Transform: Generate and execute transformations
|
|
@@ -59,53 +62,175 @@ export class MainAgent {
|
|
|
59
62
|
this.query = context.initContext?.userQuery ?? "";
|
|
60
63
|
this.ui = ui;
|
|
61
64
|
}
|
|
62
|
-
/* ─────────────
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
65
|
+
/* ───────────── main run ───────────── */
|
|
66
|
+
async run() {
|
|
67
|
+
this.runCount = 0;
|
|
68
|
+
/* ===== BOOT ===== */
|
|
69
|
+
await understandIntentStep.run({ context: this.context });
|
|
70
|
+
await resolveExecutionModeStep.run(this.context);
|
|
71
|
+
this.taskId = bootTaskForRepo(this.context, getDbForRepo(), this.logLine.bind(this));
|
|
72
|
+
/* ===== PRECHECK ===== */
|
|
73
|
+
await fileCheckStep(this.context);
|
|
74
|
+
/* ===== SCOPE ===== */
|
|
75
|
+
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));
|
|
84
104
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
/* ───────────── grounding / info acquisition loop ───────────── */
|
|
106
|
+
async runGrounding() {
|
|
107
|
+
let ready = false;
|
|
108
|
+
while (!ready && this.runCount < this.maxRuns) {
|
|
109
|
+
// ---------------- INFORMATION ACQUISITION ----------------
|
|
110
|
+
if (this.canExecutePhase("planning") &&
|
|
111
|
+
this.canExecuteScope("planning")) {
|
|
112
|
+
const t = this.startTimer();
|
|
113
|
+
// Generate info plan step(s) in context
|
|
114
|
+
await infoPlanGenStep.run(this.context);
|
|
115
|
+
const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
116
|
+
for (const step of infoPlan.steps) {
|
|
117
|
+
const stepIO = { query: this.query };
|
|
118
|
+
await this.executeStep(step, stepIO);
|
|
119
|
+
// Re-check any new files discovered
|
|
120
|
+
const t2 = this.startTimer();
|
|
121
|
+
await fileCheckStep(this.context);
|
|
122
|
+
this.logLine("PRECHECK", "postFileSearch", t2());
|
|
123
|
+
}
|
|
124
|
+
this.logLine("PLAN", "infoPlanGen", t());
|
|
125
|
+
}
|
|
126
|
+
// ---------------- DETERMINISTIC EVIDENCE VERIFICATION ----------------
|
|
127
|
+
const t1 = this.startTimer();
|
|
128
|
+
await evidenceVerifierStep.run({ query: this.query, context: this.context });
|
|
129
|
+
this.logLine("ANALYSIS", "collectAnalysisEvidence", t1(), undefined);
|
|
130
|
+
// Select grounded candidate files
|
|
131
|
+
const t2 = this.startTimer();
|
|
132
|
+
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
133
|
+
this.logLine("ANALYSIS", "selectRelevantSources", t2(), undefined);
|
|
134
|
+
// ---------------- READINESS GATE ----------------
|
|
135
|
+
const t3 = this.startTimer();
|
|
136
|
+
await readinessGateStep.run(this.context);
|
|
137
|
+
this.logLine("HASINFO", "readinessGate", t3(), undefined);
|
|
138
|
+
ready = this.context.analysis?.readiness?.decision === "ready";
|
|
139
|
+
if (!ready) {
|
|
140
|
+
this.runCount++;
|
|
141
|
+
// Previously resetInitContextForLoop removed; optionally clear temporary data
|
|
142
|
+
if (this.context.analysis) {
|
|
143
|
+
this.context.analysis.planSuggestion = undefined;
|
|
144
|
+
}
|
|
145
|
+
this.logLine("HASINFO", "readinessGate", undefined, "Not ready, looping back to information acquisition");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
99
148
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
149
|
+
/* ───────────── work iteration ───────────── */
|
|
150
|
+
async runWorkIteration() {
|
|
151
|
+
// ---------------- FILE SELECTION ----------------
|
|
152
|
+
const tSelect = this.startTimer();
|
|
153
|
+
await iterationFileSelector.run(this.context); // <-- sets context.analysis.currentTargetFile
|
|
154
|
+
this.logLine("LOOP", "iterationFileSelector", tSelect());
|
|
155
|
+
// ---------------- ANALYSIS ----------------
|
|
156
|
+
if (this.canExecutePhase("analysis") && this.canExecuteScope("analysis")) {
|
|
157
|
+
const tAnalysis = this.startTimer();
|
|
158
|
+
await analysisPlanGenStep.run(this.context);
|
|
159
|
+
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
|
+
const analysisPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
169
|
+
for (const step of analysisPlan.steps) {
|
|
170
|
+
const tStep = this.startTimer();
|
|
171
|
+
await this.executeStep(step, { query: this.query });
|
|
172
|
+
this.logLine("STEP", step.action || "unnamedStep", tStep());
|
|
173
|
+
}
|
|
174
|
+
if (this.context.analysis) {
|
|
175
|
+
this.context.analysis.planSuggestion = undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ---------------- TRANSFORM ----------------
|
|
179
|
+
if (this.canExecutePhase("transform") && this.canExecuteScope("transform")) {
|
|
180
|
+
const tTransform = this.startTimer();
|
|
181
|
+
await transformPlanGenStep.run(this.context);
|
|
182
|
+
this.logLine("PLAN", "transformPlanGen", tTransform());
|
|
183
|
+
const transformPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
184
|
+
let stepCounter = 1; // 🔹 initialize step counter
|
|
185
|
+
for (const step of transformPlan.steps) {
|
|
186
|
+
const tStep = this.startTimer();
|
|
187
|
+
await this.executeStep(step, { query: this.query });
|
|
188
|
+
// 🔹 include step number in log
|
|
189
|
+
this.logLine("STEP", `#${stepCounter} - ${step.action || "unnamedStep"}`, tStep());
|
|
190
|
+
stepCounter++; // 🔹 increment counter
|
|
191
|
+
}
|
|
192
|
+
if (this.context.analysis) {
|
|
193
|
+
this.context.analysis.planSuggestion = undefined;
|
|
194
|
+
}
|
|
195
|
+
// ---------------- WRITE ----------------
|
|
196
|
+
if (this.canExecutePhase("write") && this.canExecuteScope("write")) {
|
|
197
|
+
const tWrite = this.startTimer();
|
|
198
|
+
await writeFileStep.run({ query: this.query, context: this.context });
|
|
199
|
+
this.logLine("WRITE", "writeFileStep", tWrite());
|
|
200
|
+
}
|
|
201
|
+
// ---------------- VALIDATION ----------------
|
|
202
|
+
const tValidate = this.startTimer();
|
|
203
|
+
await validateChangesStep.run(this.context);
|
|
204
|
+
this.logLine("VALIDATION", "validateChangesStep", tValidate());
|
|
205
|
+
}
|
|
206
|
+
// ---------------- REASONING ----------------
|
|
207
|
+
const tReason = this.startTimer();
|
|
208
|
+
await reasonNextIterationStep.run(this.context);
|
|
209
|
+
this.logLine("REASONING", "reasonNextIterationStep", tReason());
|
|
210
|
+
// ---------------- COLLABORATOR FEEDBACK ----------------
|
|
211
|
+
const tCollab = this.startTimer();
|
|
212
|
+
await collaboratorStep.run(this.context);
|
|
213
|
+
this.logLine("FEEDBACK", "collaboratorStep", tCollab());
|
|
214
|
+
// ---------------- INTEGRATE FEEDBACK ----------------
|
|
215
|
+
const tIntegrate = this.startTimer();
|
|
216
|
+
await integrateFeedbackStep.run(this.context);
|
|
217
|
+
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
|
+
}
|
|
109
234
|
}
|
|
110
235
|
/* ───────────── step executor ───────────── */
|
|
111
236
|
/**
|
|
@@ -118,7 +243,9 @@ export class MainAgent {
|
|
|
118
243
|
*/
|
|
119
244
|
async executeStep(step, input) {
|
|
120
245
|
const stop = this.startTimer();
|
|
246
|
+
// Set current step in context
|
|
121
247
|
this.context.currentStep = step;
|
|
248
|
+
// Resolve module for this action
|
|
122
249
|
const mod = resolveModuleForAction(step.action);
|
|
123
250
|
if (!mod) {
|
|
124
251
|
this.logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
|
|
@@ -126,15 +253,15 @@ export class MainAgent {
|
|
|
126
253
|
}
|
|
127
254
|
try {
|
|
128
255
|
this.ui.update(`Running step: ${step.action}`);
|
|
129
|
-
|
|
256
|
+
// Execute the module
|
|
257
|
+
await mod.run({
|
|
130
258
|
query: step.description ?? input.query,
|
|
131
259
|
content: input.data ?? input.content,
|
|
132
260
|
context: this.context
|
|
133
261
|
});
|
|
134
|
-
if (!output)
|
|
135
|
-
throw new Error(`Module "${mod.name}" returned empty output`);
|
|
136
262
|
this.logLine("EXECUTE", step.action, stop());
|
|
137
|
-
|
|
263
|
+
// Return ModuleIO (can remain minimal since output is untyped)
|
|
264
|
+
return { query: step.description ?? input.query, data: {} };
|
|
138
265
|
}
|
|
139
266
|
catch (err) {
|
|
140
267
|
this.logLine("EXECUTE", step.action, stop(), "failed");
|
|
@@ -205,211 +332,56 @@ export class MainAgent {
|
|
|
205
332
|
this.logLine("EXEC", "canExecuteScope", undefined, `phase=${phase}, scope=${scope}, allowed=${allowed}`);
|
|
206
333
|
return allowed;
|
|
207
334
|
}
|
|
208
|
-
/*
|
|
335
|
+
/* ----------------------------------- */
|
|
336
|
+
/* ------------- helpers ------------- */
|
|
337
|
+
/* ----------------------------------- */
|
|
338
|
+
/* ───────────── timers ───────────── */
|
|
209
339
|
/**
|
|
210
|
-
*
|
|
340
|
+
* Creates a timer function that measures execution time.
|
|
211
341
|
*
|
|
212
|
-
* @returns A
|
|
342
|
+
* @returns A function that returns the elapsed time in milliseconds.
|
|
213
343
|
*/
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.logLine("RUN", `start #${this.runCount}`);
|
|
218
|
-
logInputOutput("GlobalContext (structured)", "input", this.context);
|
|
219
|
-
/* ================= BOOT ================= */
|
|
220
|
-
// AXIS 1: Capability — determine WHAT actions are allowed
|
|
221
|
-
// executionMode: "explain" | "analyze" | "transform"
|
|
222
|
-
// Controls: files may be written, code analyzed, or run text-only
|
|
223
|
-
// This step does NOT determine WHERE to act or whether enough evidence exists
|
|
224
|
-
{
|
|
225
|
-
const t1 = this.startTimer();
|
|
226
|
-
await understandIntentStep.run({ context: this.context }); // Classify user intent
|
|
227
|
-
this.logLine("BOOT", "understandIntent", t1());
|
|
228
|
-
const t2 = this.startTimer();
|
|
229
|
-
await resolveExecutionModeStep.run(this.context); // Set executionMode
|
|
230
|
-
this.logLine("BOOT", "resolveExecutionMode", t2(), `mode = ${this.context.executionControl?.mode}`);
|
|
231
|
-
const db = getDbForRepo();
|
|
232
|
-
this.taskId = bootTaskForRepo(this.context, db, this.logLine.bind(this)); // Persist task
|
|
233
|
-
}
|
|
234
|
-
/* ================= PRECHECK (INITIAL) ================= */
|
|
235
|
-
// Quick file existence check (pre-grounding)
|
|
236
|
-
// Does NOT infer relevance or scope beyond directly referenced files
|
|
237
|
-
{
|
|
238
|
-
const t = this.startTimer();
|
|
239
|
-
await fileCheckStep(this.context);
|
|
240
|
-
this.logLine("PRECHECK", "preFileSearch", t());
|
|
241
|
-
}
|
|
242
|
-
/* ───────────────────────── SCOPE & ROUTING GATE ───────────────────────── */
|
|
243
|
-
// AXIS 2: Scope — determine WHERE the agent may act
|
|
244
|
-
// scopeType: "none" | "single-file" | "multi-file" | "repo-wide"
|
|
245
|
-
// Responsible for deciding which files, repos, or modules the agent should consider
|
|
246
|
-
// Does NOT validate anchors (Axis 3) or decide allowed actions (Axis 1)
|
|
247
|
-
// ----------------- SCOPE CLASSIFICATION -----------------
|
|
248
|
-
// Responsibility:
|
|
249
|
-
// - Determine problem coverage: none / single-file / multi-file / repo-wide
|
|
250
|
-
// - Purely repo/file coverage decision
|
|
251
|
-
// - Output: context.analysis.scopeType
|
|
252
|
-
// Does NOT decide allowed actions, validate evidence, or affect executionMode
|
|
253
|
-
{
|
|
254
|
-
const t1 = this.startTimer();
|
|
255
|
-
await scopeClassificationStep.run(this.context);
|
|
256
|
-
this.logLine("SCOPE", "scopeClassification", t1());
|
|
257
|
-
}
|
|
258
|
-
// ----------------- ROUTING DECISION -----------------
|
|
259
|
-
// Responsibility:
|
|
260
|
-
// - Determine allowed actions within scope
|
|
261
|
-
// * Can search be performed?
|
|
262
|
-
// * Are early exits (explain) allowed?
|
|
263
|
-
// - Output: context.analysis.routingDecision
|
|
264
|
-
// Does NOT check anchors, determine readiness, or modify scope
|
|
265
|
-
{
|
|
266
|
-
const t2 = this.startTimer();
|
|
267
|
-
await routingDecisionStep.run(this.context);
|
|
268
|
-
this.logLine("SCOPE", "routingDecision", t2());
|
|
269
|
-
}
|
|
270
|
-
/* ================= GROUNDING & READINESS LOOP ================= */
|
|
271
|
-
// AXIS 3: Certainty — do we have enough evidence to safely proceed?
|
|
272
|
-
// confidence: "high" | "medium" | "low"
|
|
273
|
-
// Controls whether we loop back to acquire more info
|
|
274
|
-
// Does NOT change executionMode (Axis 1) or scopeType (Axis 2)
|
|
275
|
-
let ready = false;
|
|
276
|
-
while (!ready && this.runCount < this.maxRuns) {
|
|
277
|
-
const routing = this.context.analysis?.routingDecision;
|
|
278
|
-
// ---------------- INFORMATION ACQUISITION ----------------
|
|
279
|
-
// Plan & execute info steps; may discover new files
|
|
280
|
-
// Does NOT guarantee readiness — anchors still need verification
|
|
281
|
-
if (this.canExecutePhase("planning") &&
|
|
282
|
-
this.canExecuteScope("planning") &&
|
|
283
|
-
routing?.allowInfoSteps !== false) {
|
|
284
|
-
const t = this.startTimer();
|
|
285
|
-
await infoPlanGen.run(this.context); // Generate info-gathering plan
|
|
286
|
-
this.logLine("PLAN", "infoPlanGen", t());
|
|
287
|
-
let stepIO = { query: this.query };
|
|
288
|
-
const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
289
|
-
for (const step of infoPlan.steps.filter(s => s.groups?.includes("info"))) {
|
|
290
|
-
// Guard: skip fileSearch if routingDecision forbids repo search
|
|
291
|
-
if (step.action === "fileSearch" && !routing?.allowSearch) {
|
|
292
|
-
this.logLine("PLAN", "infoStepSkipped", undefined, `Step "${step.description}" skipped: file search not allowed by routingDecision`);
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
stepIO = await this.executeStep(step, stepIO);
|
|
296
|
-
}
|
|
297
|
-
// Re-run precheck for newly discovered files
|
|
298
|
-
if (infoPlan.steps.length > 0) {
|
|
299
|
-
const t2 = this.startTimer();
|
|
300
|
-
await fileCheckStep(this.context);
|
|
301
|
-
this.logLine("PRECHECK", "postFileSearch", t2());
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
// ---------------- DETERMINISTIC EVIDENCE VERIFICATION ----------------
|
|
305
|
-
// Verify anchors exist (filenames, paths, literals)
|
|
306
|
-
// Does NOT execute full analysis or transformations
|
|
307
|
-
const t1 = this.startTimer();
|
|
308
|
-
await evidenceVerifierStep.run({ query: this.query, context: this.context });
|
|
309
|
-
this.logLine("ANALYSIS", "collectAnalysisEvidence", t1(), undefined);
|
|
310
|
-
// Select grounded candidate files
|
|
311
|
-
// Does NOT update executionMode or scope classification
|
|
312
|
-
const t2 = this.startTimer();
|
|
313
|
-
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
314
|
-
this.logLine("ANALYSIS", "selectRelevantSources", t2(), undefined);
|
|
315
|
-
// ---------------- READINESS GATE ----------------
|
|
316
|
-
// Decide if we have enough info to proceed
|
|
317
|
-
// Does NOT modify info plan or transform steps
|
|
318
|
-
const t3 = this.startTimer();
|
|
319
|
-
await readinessGateStep.run(this.context);
|
|
320
|
-
this.logLine("HASINFO", "readinessGate", t3(), undefined);
|
|
321
|
-
ready = this.context.analysis?.readiness?.decision === "ready";
|
|
322
|
-
if (!ready) {
|
|
323
|
-
this.runCount++;
|
|
324
|
-
this.resetInitContextForLoop();
|
|
325
|
-
this.logLine("HASINFO", "readinessGate", undefined, "Not ready, looping back to information acquisition");
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/* ================= ANALYSIS (DEEP) ================= */
|
|
329
|
-
if (this.canExecutePhase("analysis") &&
|
|
330
|
-
this.canExecuteScope("analysis")) {
|
|
331
|
-
{
|
|
332
|
-
const t = this.startTimer();
|
|
333
|
-
await analysisPlanGenStep.run(this.context);
|
|
334
|
-
this.logLine("PLAN", "analysisPlanGen", t());
|
|
335
|
-
}
|
|
336
|
-
let stepIO = { query: this.query };
|
|
337
|
-
const analysisPlan = this.context.analysis?.planSuggestion?.plan?.steps ?? [];
|
|
338
|
-
for (const step of analysisPlan.filter(s => s.groups?.includes("analysis"))) {
|
|
339
|
-
stepIO = await this.executeStep(step, stepIO);
|
|
340
|
-
}
|
|
341
|
-
{
|
|
342
|
-
const t = this.startTimer();
|
|
343
|
-
await planTargetFilesStep.run({ query: this.query, context: this.context });
|
|
344
|
-
this.logLine("ANALYSIS", "planTargetFiles", t());
|
|
345
|
-
}
|
|
346
|
-
const review = await contextReviewStep(this.context);
|
|
347
|
-
if (review.decision === "has" && this.runCount < this.maxRuns) {
|
|
348
|
-
this.runCount++;
|
|
349
|
-
this.resetInitContextForLoop();
|
|
350
|
-
return this.run();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
/* ================= EXPLAIN (EVIDENCE EXIT) ================= */
|
|
354
|
-
// Early exit if mode=explain and routing allows it
|
|
355
|
-
// Does NOT perform analysis, transform, or write steps
|
|
356
|
-
if (this.canExecutePhase("explain") &&
|
|
357
|
-
this.canExecuteScope("explain") &&
|
|
358
|
-
this.context.analysis?.routingDecision?.decision === "has-info") {
|
|
359
|
-
const explainMod = resolveModuleForAction("explain");
|
|
360
|
-
if (!explainMod)
|
|
361
|
-
throw new Error("Explain module not found");
|
|
362
|
-
return await explainMod.run({
|
|
363
|
-
query: this.query,
|
|
364
|
-
context: this.context
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
// Remaining phases (ANALYSIS, TRANSFORM, FINALIZE, PERSIST) execute after loop exits
|
|
368
|
-
/* ================= TRANSFORM ================= */
|
|
369
|
-
if (this.canExecutePhase("transform") &&
|
|
370
|
-
this.canExecuteScope("transform")) {
|
|
371
|
-
{
|
|
372
|
-
const t = this.startTimer();
|
|
373
|
-
await transformPlanGenStep.run(this.context);
|
|
374
|
-
this.logLine("PLAN", "transformPlanGen", t());
|
|
375
|
-
}
|
|
376
|
-
let stepIO = { query: this.query };
|
|
377
|
-
const transformSteps = this.context.analysis?.planSuggestion?.plan?.steps
|
|
378
|
-
?.filter(s => s.groups?.includes("transform")) ?? [];
|
|
379
|
-
for (const step of transformSteps) {
|
|
380
|
-
stepIO = await this.executeStep(step, stepIO);
|
|
381
|
-
}
|
|
382
|
-
if (this.canExecutePhase("write") &&
|
|
383
|
-
this.canExecuteScope("write")) {
|
|
384
|
-
const t = this.startTimer();
|
|
385
|
-
await writeFileStep.run({ query: this.query, context: this.context });
|
|
386
|
-
this.logLine("EXECUTE", "writeFile", t());
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/* ================= FINALIZE ================= */
|
|
390
|
-
{
|
|
391
|
-
const t = this.startTimer();
|
|
392
|
-
await finalPlanGenStep.run(this.context);
|
|
393
|
-
this.logLine("PLAN", "finalPlanGen", t());
|
|
394
|
-
let stepIO = { query: this.query };
|
|
395
|
-
const finalPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
396
|
-
for (const step of finalPlan.steps.filter(s => s.groups?.includes("finalize"))) {
|
|
397
|
-
stepIO = await this.executeStep(step, stepIO);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
const db = getDbForRepo();
|
|
401
|
-
persistTaskData(this.context, this.taskId, db, this.logLine.bind(this));
|
|
402
|
-
this.logLine("RUN", "complete", stopRun());
|
|
403
|
-
return { query: this.query, data: {} };
|
|
344
|
+
startTimer() {
|
|
345
|
+
const start = Date.now();
|
|
346
|
+
return () => Date.now() - start;
|
|
404
347
|
}
|
|
348
|
+
/* ───────────── spinner helpers ───────────── */
|
|
405
349
|
/**
|
|
406
|
-
*
|
|
350
|
+
* Executes a function while pausing the UI spinner.
|
|
407
351
|
*
|
|
408
|
-
*
|
|
352
|
+
* @param fn - The function to execute while spinner is paused.
|
|
409
353
|
*/
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
354
|
+
withSpinnerPaused(fn) {
|
|
355
|
+
this.ui.pause(() => {
|
|
356
|
+
// Ensure spinner line is fully gone
|
|
357
|
+
process.stdout.write('\r\x1b[K');
|
|
358
|
+
fn();
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Logs a line with timing information.
|
|
363
|
+
*
|
|
364
|
+
* @param phase - The phase of execution.
|
|
365
|
+
* @param step - The step being executed.
|
|
366
|
+
* @param ms - The execution time in milliseconds.
|
|
367
|
+
* @param desc - Optional description of the step.
|
|
368
|
+
*/
|
|
369
|
+
logLine(phase, step, ms, desc) {
|
|
370
|
+
this.withSpinnerPaused(() => {
|
|
371
|
+
const suffix = desc ? ` — ${desc}` : "";
|
|
372
|
+
const timing = typeof ms === "number" ? ` (${ms}ms)` : "";
|
|
373
|
+
console.log(`[AGENT] ${phase} :: ${step}${suffix}${timing}`);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Logs a message to the user output.
|
|
378
|
+
*
|
|
379
|
+
* @param message - The message to log.
|
|
380
|
+
*/
|
|
381
|
+
userOutput(message) {
|
|
382
|
+
this.withSpinnerPaused(() => {
|
|
383
|
+
console.log(`[USER OUTPUT] ${message}`);
|
|
384
|
+
});
|
|
413
385
|
}
|
|
414
386
|
}
|
|
415
387
|
/* ───────────── FOLDER CAPSULES SUMMARY HELPER ───────────── */
|
|
@@ -440,7 +412,7 @@ export function logFolderCapsulesSummary(context) {
|
|
|
440
412
|
body += summaryText;
|
|
441
413
|
if (key) {
|
|
442
414
|
const keyHint = `Key: ${key.path.split("/").pop()} — ${truncate(key.reason)}`;
|
|
443
|
-
body += summaryText ? `
|
|
415
|
+
body += summaryText ? `[${keyHint}]` : keyHint;
|
|
444
416
|
}
|
|
445
417
|
}
|
|
446
418
|
return body ? `${header}\n${body}` : header;
|
|
@@ -480,11 +452,6 @@ export function persistTaskData(context, taskId, db, logLine) {
|
|
|
480
452
|
if (context.analysis?.focus?.candidateFiles?.length) {
|
|
481
453
|
fieldsToUpdate.missing_files_json = JSON.stringify(context.analysis.focus.candidateFiles);
|
|
482
454
|
}
|
|
483
|
-
// ❌ focus_rationale intentionally removed
|
|
484
|
-
// Persist routing decision (meta / audit)
|
|
485
|
-
if (context.analysis?.routingDecision) {
|
|
486
|
-
fieldsToUpdate.routing_decision_json = JSON.stringify(context.analysis.routingDecision);
|
|
487
|
-
}
|
|
488
455
|
// ✅ Persist final answer → tasks.summary
|
|
489
456
|
if (context.analysis?.finalAnswer) {
|
|
490
457
|
fieldsToUpdate.summary = context.analysis.finalAnswer;
|