scai 0.1.171 → 0.1.172
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 +314 -58
- package/dist/agents/evidenceVerifierStep.js +128 -12
- package/dist/agents/iterationFileSelector.js +18 -45
- package/dist/agents/readinessGateStep.js +37 -6
- package/dist/agents/reasonNextStep.js +11 -1
- package/dist/agents/reasonNextTaskStep.js +8 -0
- package/dist/agents/resolveExecutionModeStep.js +3 -3
- package/dist/agents/routingDecisionStep.js +70 -0
- package/dist/agents/scopeClassificationStep.js +11 -3
- package/dist/agents/selectRelevantSourcesStep.js +1 -2
- package/dist/agents/understandIntentStep.js +77 -6
- package/dist/commands/AskCmd.js +20 -46
- package/dist/commands/factory.js +2 -2
- package/dist/db/fileIndex.js +523 -18
- package/dist/fileRules/stopWords.js +10 -0
- package/dist/index.js +5 -60
- package/dist/lib/generate.js +26 -15
- package/dist/pipeline/modules/fileSearchModule.js +10 -0
- package/dist/utils/buildContextualPrompt.js +18 -11
- package/dist/utils/extractFileReferences.js +13 -0
- package/dist/utils/runQueryWithDaemonControl.js +26 -0
- package/package.json +1 -1
package/dist/agents/MainAgent.js
CHANGED
|
@@ -10,6 +10,7 @@ import { fileCheckStep } from "./fileCheckStep.js";
|
|
|
10
10
|
import { analysisPlanGenStep } from "./analysisPlanGenStep.js";
|
|
11
11
|
import { readinessGateStep } from "./readinessGateStep.js";
|
|
12
12
|
import { scopeClassificationStep } from "./scopeClassificationStep.js";
|
|
13
|
+
import { routingDecisionStep } from "./routingDecisionStep.js";
|
|
13
14
|
import { evidenceVerifierStep } from "./evidenceVerifierStep.js";
|
|
14
15
|
import { validateChangesStep } from './validateChangesStep.js';
|
|
15
16
|
import { reasonNextTaskStep } from './reasonNextTaskStep.js';
|
|
@@ -19,8 +20,14 @@ import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
|
19
20
|
import { iterationFileSelector } from "./iterationFileSelector.js";
|
|
20
21
|
import { finalAnswerModule } from "../pipeline/modules/finalAnswerModule.js";
|
|
21
22
|
import { reasonNextStep } from "./reasonNextStep.js";
|
|
23
|
+
import { buildLightContext } from "../utils/buildContextualPrompt.js";
|
|
24
|
+
import { semanticSearchFiles } from "../db/fileIndex.js";
|
|
25
|
+
import { NUM_TOPFILES, RELATED_FILES_LIMIT } from "../constants.js";
|
|
22
26
|
import { structuralPreloadStep } from "./structuralPreloadStep.js";
|
|
27
|
+
import { extractFileReferences } from "../utils/extractFileReferences.js";
|
|
28
|
+
import { PREFILTER_STOP_WORDS } from "../fileRules/stopWords.js";
|
|
23
29
|
import chalk from "chalk";
|
|
30
|
+
import path from "path";
|
|
24
31
|
/* ───────────────────────── registry ───────────────────────── */
|
|
25
32
|
const MODULE_REGISTRY = Object.fromEntries(Object.entries(builtInModules).map(([name, mod]) => [name, mod]));
|
|
26
33
|
function resolveModuleForAction(action) {
|
|
@@ -49,7 +56,6 @@ export class MainAgent {
|
|
|
49
56
|
*/
|
|
50
57
|
constructor(context, ui) {
|
|
51
58
|
this.runCount = 0;
|
|
52
|
-
this.maxRuns = 2;
|
|
53
59
|
this.context = context;
|
|
54
60
|
this.query = context.initContext?.userQuery ?? "";
|
|
55
61
|
this.ui = ui;
|
|
@@ -60,6 +66,7 @@ export class MainAgent {
|
|
|
60
66
|
this.runCount = 0;
|
|
61
67
|
await this.runBoot();
|
|
62
68
|
await this.runScope();
|
|
69
|
+
await this.runInitialRetrieval();
|
|
63
70
|
await this.runGrounding();
|
|
64
71
|
await this.runWorkLoop();
|
|
65
72
|
await this.runFinalize();
|
|
@@ -72,7 +79,6 @@ export class MainAgent {
|
|
|
72
79
|
async runBoot() {
|
|
73
80
|
var _a;
|
|
74
81
|
await understandIntentStep.run({ context: this.context });
|
|
75
|
-
await resolveExecutionModeStep.run(this.context);
|
|
76
82
|
// Boot the task and get the real DB taskId
|
|
77
83
|
this.taskId = bootTaskForRepo(this.context, getDbForRepo(), (phase, step, ms, desc) => this.logLine(phase, step, ms, desc, { highlight: true }));
|
|
78
84
|
(_a = this.context).task || (_a.task = {
|
|
@@ -90,11 +96,38 @@ export class MainAgent {
|
|
|
90
96
|
/* ───────────── scope ───────────── */
|
|
91
97
|
async runScope() {
|
|
92
98
|
await scopeClassificationStep.run(this.context);
|
|
99
|
+
await resolveExecutionModeStep.run(this.context);
|
|
100
|
+
await routingDecisionStep.run(this.context);
|
|
101
|
+
const routing = this.context.analysis?.routingDecision;
|
|
102
|
+
if (routing) {
|
|
103
|
+
this.logLine("TASK", "Routing decision", undefined, `${routing.decision} | search=${routing.allowSearch} | transform=${routing.allowTransform} | scopeLocked=${routing.scopeLocked}`);
|
|
104
|
+
}
|
|
93
105
|
this.logLine("TASK", "Scope classification complete");
|
|
94
106
|
}
|
|
107
|
+
/* ───────────── initial retrieval ───────────── */
|
|
108
|
+
async runInitialRetrieval() {
|
|
109
|
+
const { rawUserQuery, retrievalQuery } = this.resolveInitialRetrievalQueries();
|
|
110
|
+
const t = this.startTimer();
|
|
111
|
+
try {
|
|
112
|
+
const results = await this.fetchInitialRetrievalResults(retrievalQuery);
|
|
113
|
+
const promptArgs = this.buildInitialRetrievalPromptArgs(results, retrievalQuery);
|
|
114
|
+
const seededContext = await buildLightContext(promptArgs);
|
|
115
|
+
const mergedRelatedCount = this.mergeSeededInitialContext(rawUserQuery, seededContext);
|
|
116
|
+
const prefilter = this.applyDeterministicPreGroundingPrefilter(retrievalQuery);
|
|
117
|
+
this.logLine("ANALYSIS", "initialRetrieval", t(), `${results.length} result(s), ${mergedRelatedCount} candidate file(s), prefilter ${prefilter.before} -> ${prefilter.after}`);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
this.logLine("ANALYSIS", "initialRetrieval", t(), `failed: ${String(err)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/* ───────────── grounding ───────────── */
|
|
95
124
|
async runGrounding() {
|
|
96
125
|
let ready = false;
|
|
97
|
-
|
|
126
|
+
const maxGroundingWaves = this.getGroundingWaveBudget();
|
|
127
|
+
let groundingWave = 0;
|
|
128
|
+
while (groundingWave < maxGroundingWaves) {
|
|
129
|
+
groundingWave++;
|
|
130
|
+
this.logLine("ANALYSIS", "groundingWave", undefined, `wave ${groundingWave}/${maxGroundingWaves}`);
|
|
98
131
|
// ---------------- EVIDENCE PIPELINE ----------------
|
|
99
132
|
// -------- STRUCTURAL PRELOAD --------
|
|
100
133
|
const t0 = this.startTimer();
|
|
@@ -112,7 +145,7 @@ export class MainAgent {
|
|
|
112
145
|
// ---------------- READINESS GATE ----------------
|
|
113
146
|
const t4 = this.startTimer();
|
|
114
147
|
await readinessGateStep.run(this.context);
|
|
115
|
-
this.logLine("
|
|
148
|
+
this.logLine("ANALYSIS", "readinessGate", t4());
|
|
116
149
|
ready = this.context.analysis?.readiness?.decision === "ready";
|
|
117
150
|
if (ready) {
|
|
118
151
|
break;
|
|
@@ -135,82 +168,67 @@ export class MainAgent {
|
|
|
135
168
|
}
|
|
136
169
|
this.logLine("PLAN", "infoPlanGen", t(), undefined, { highlight: false });
|
|
137
170
|
}
|
|
138
|
-
this.runCount++;
|
|
139
171
|
this.logLine("HASINFO", "Not ready — looping back to evidence collection", undefined, undefined, { highlight: false });
|
|
140
172
|
}
|
|
173
|
+
const selectedFiles = this.context.analysis?.focus?.selectedFiles ?? [];
|
|
174
|
+
if (selectedFiles.length > 0) {
|
|
175
|
+
this.context.analysis.readiness.decision = "ready";
|
|
176
|
+
this.logLine("ANALYSIS", "readinessOverrideFromSelectedFiles", undefined, `${selectedFiles.length} selected file(s) available after grounding`);
|
|
177
|
+
}
|
|
141
178
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Resolves grounding wave budget from current routing metadata.
|
|
181
|
+
* Example: scope=repo-wide + decision=needs-info => 4 waves.
|
|
182
|
+
*/
|
|
183
|
+
getGroundingWaveBudget() {
|
|
184
|
+
const scope = this.context.analysis?.scopeType ?? "repo-wide";
|
|
185
|
+
const decision = this.context.analysis?.routingDecision?.decision ?? "has-info";
|
|
186
|
+
const allowSearch = this.context.analysis?.routingDecision?.allowSearch ?? true;
|
|
187
|
+
let budget = 2;
|
|
188
|
+
if (!allowSearch || scope === "none")
|
|
189
|
+
budget = 1;
|
|
190
|
+
else if (scope === "single-file" && decision === "has-info")
|
|
191
|
+
budget = 2;
|
|
192
|
+
else if (scope === "multi-file")
|
|
193
|
+
budget = 3;
|
|
194
|
+
else if (scope === "repo-wide" && decision === "needs-info")
|
|
195
|
+
budget = 4;
|
|
196
|
+
this.logLine("ANALYSIS", "groundingBudget", undefined, `scope=${scope}, decision=${decision}, search=${allowSearch}, maxWaves=${budget}`);
|
|
197
|
+
return budget;
|
|
147
198
|
}
|
|
148
199
|
/* ───────────── work loop ───────────── */
|
|
149
200
|
async runWorkLoop() {
|
|
150
|
-
|
|
151
|
-
const readinessConfidence = this.context.analysis?.readiness?.confidence ?? 0;
|
|
152
|
-
if (readinessDecision !== "ready") {
|
|
153
|
-
// ❌ Graceful fallback instead of throwing
|
|
154
|
-
this.context.task.status = "deferred";
|
|
155
|
-
this.context.task.reason = `Readiness not achieved (decision=${readinessDecision}, confidence=${readinessConfidence})`;
|
|
156
|
-
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
157
|
-
this.logLine("TASK", `Cannot start work loop — agent needs more evidence to safely proceed`, undefined, `Readiness: ${readinessDecision}, Confidence: ${readinessConfidence}`, { highlight: true });
|
|
201
|
+
if (!this.isWorkLoopReady())
|
|
158
202
|
return;
|
|
159
|
-
|
|
160
|
-
if (!this.context.task) {
|
|
161
|
-
throw new Error("runWorkLoop: missing task");
|
|
162
|
-
}
|
|
203
|
+
this.ensureTaskForWorkLoop();
|
|
163
204
|
const MAX_TASK_STEPS = 5;
|
|
164
205
|
let stepCount = 0;
|
|
165
206
|
while (stepCount < MAX_TASK_STEPS &&
|
|
166
207
|
this.context.task.status === "active") {
|
|
167
|
-
await
|
|
168
|
-
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
169
|
-
// 🟡 Pause for user clarification
|
|
208
|
+
const nextAction = await this.resolveNextTaskAction();
|
|
170
209
|
if (nextAction === "request-feedback") {
|
|
171
|
-
this.
|
|
172
|
-
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
210
|
+
this.persistTaskStatus("paused");
|
|
173
211
|
this.logLine("TASK", "Execution paused — awaiting user clarification", undefined, undefined, { highlight: false });
|
|
174
212
|
return;
|
|
175
213
|
}
|
|
176
|
-
// 🟢 Completed task
|
|
177
214
|
if (nextAction === "complete") {
|
|
178
|
-
this.
|
|
179
|
-
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
215
|
+
this.persistTaskStatus("completed");
|
|
180
216
|
this.logLine("TASK", "All selected files processed — task complete", undefined, undefined, { highlight: false });
|
|
181
217
|
return;
|
|
182
218
|
}
|
|
183
219
|
const taskStep = await iterationFileSelector.run(this.context);
|
|
184
220
|
if (!taskStep) {
|
|
185
|
-
this.
|
|
186
|
-
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
221
|
+
this.persistTaskStatus("completed");
|
|
187
222
|
this.logLine("TASK", "No eligible taskStep found — task complete", undefined, undefined, { highlight: false });
|
|
188
223
|
return;
|
|
189
224
|
}
|
|
190
|
-
this.context.task.currentStep = taskStep;
|
|
191
225
|
stepCount++;
|
|
192
|
-
taskStep
|
|
193
|
-
taskStep.stepIndex = stepCount;
|
|
194
|
-
taskStep.status = "pending";
|
|
195
|
-
persistTaskStepInsert(taskStep, getDbForRepo());
|
|
196
|
-
this.logLine("NEW STEP", `Processing taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: true });
|
|
197
|
-
taskStep.startTime = Date.now();
|
|
198
|
-
persistTaskStepStart(taskStep, getDbForRepo());
|
|
226
|
+
this.startTaskStep(taskStep, stepCount);
|
|
199
227
|
// ---------------------------
|
|
200
228
|
// Step-level iterations
|
|
201
229
|
// ---------------------------
|
|
202
230
|
const stepAction = await this.runStepIterations(taskStep);
|
|
203
|
-
|
|
204
|
-
taskStep.status = "completed";
|
|
205
|
-
taskStep.endTime = Date.now();
|
|
206
|
-
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
207
|
-
this.logLine("STEP-DONE", `Completed taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: false });
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
taskStep.status = "pending";
|
|
211
|
-
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
212
|
-
this.logLine("STEP", `Pending taskStep ${stepCount}`, undefined, taskStep.filePath);
|
|
213
|
-
}
|
|
231
|
+
this.finishTaskStep(taskStep, stepCount, stepAction);
|
|
214
232
|
}
|
|
215
233
|
this.logLine("TASK", "Max task step limit reached — stopping work loop", undefined, undefined, { highlight: false });
|
|
216
234
|
}
|
|
@@ -219,16 +237,17 @@ export class MainAgent {
|
|
|
219
237
|
const MAX_ITERATIONS = 5;
|
|
220
238
|
let loopCount = 0;
|
|
221
239
|
const getNextIterationAction = () => {
|
|
222
|
-
const nextAction =
|
|
223
|
-
if (!["continue", "redo-step", "expand-scope", "request-feedback", "complete"].includes(nextAction ?? ""))
|
|
224
|
-
return "
|
|
240
|
+
const nextAction = taskStep.result?.stepReasoning?.nextAction;
|
|
241
|
+
if (!["continue", "redo-step", "expand-scope", "request-feedback", "complete"].includes(nextAction ?? "")) {
|
|
242
|
+
return "continue";
|
|
243
|
+
}
|
|
225
244
|
return nextAction;
|
|
226
245
|
};
|
|
227
246
|
while (loopCount < MAX_ITERATIONS) {
|
|
228
247
|
this.runCount++;
|
|
229
248
|
loopCount++;
|
|
230
|
-
if (
|
|
231
|
-
|
|
249
|
+
if (taskStep.result?.stepReasoning)
|
|
250
|
+
taskStep.result.stepReasoning.nextAction = undefined;
|
|
232
251
|
await this.runWorkIteration(taskStep);
|
|
233
252
|
const nextAction = getNextIterationAction();
|
|
234
253
|
this.logLine("STEP-LOOP", `nextAction = ${nextAction}`);
|
|
@@ -236,6 +255,8 @@ export class MainAgent {
|
|
|
236
255
|
return "complete";
|
|
237
256
|
if (nextAction === "request-feedback")
|
|
238
257
|
return "request-feedback";
|
|
258
|
+
if (nextAction === "redo-step")
|
|
259
|
+
continue;
|
|
239
260
|
}
|
|
240
261
|
return "continue";
|
|
241
262
|
}
|
|
@@ -307,14 +328,189 @@ export class MainAgent {
|
|
|
307
328
|
}
|
|
308
329
|
try {
|
|
309
330
|
this.ui.update(`Running step: ${step.action}`);
|
|
310
|
-
await mod.run({ query: step.description ?? input.query, content: input.data ?? input.content, context: this.context });
|
|
311
|
-
|
|
331
|
+
const output = await mod.run({ query: step.description ?? input.query, content: input.data ?? input.content, context: this.context });
|
|
332
|
+
const errors = Array.isArray(output.data?.errors)
|
|
333
|
+
? output.data.errors.filter((e) => typeof e === "string" && e.trim().length > 0)
|
|
334
|
+
: [];
|
|
335
|
+
if (errors.length > 0) {
|
|
336
|
+
const detail = errors.slice(0, 2).join(" | ");
|
|
337
|
+
this.logLine("EXECUTE", step.action, stop(), `completed with errors: ${detail}`);
|
|
338
|
+
console.error(`[${step.action}] ${errors.join(" | ")}`);
|
|
339
|
+
}
|
|
340
|
+
return output;
|
|
312
341
|
}
|
|
313
342
|
catch (err) {
|
|
314
343
|
this.logLine("EXECUTE", step.action, stop(), "failed");
|
|
315
344
|
throw err;
|
|
316
345
|
}
|
|
317
346
|
}
|
|
347
|
+
/* ───────────── finalize ───────────── */
|
|
348
|
+
async runFinalize() {
|
|
349
|
+
await finalAnswerModule.run({ query: this.query, context: this.context });
|
|
350
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
351
|
+
this.logLine("TASK", "Finalize complete", undefined, undefined, { highlight: false });
|
|
352
|
+
}
|
|
353
|
+
/* ───────────── extracted from runInitialRetrieval ───────────── */
|
|
354
|
+
resolveInitialRetrievalQueries() {
|
|
355
|
+
const rawUserQuery = this.context.initContext?.userQuery ?? this.query;
|
|
356
|
+
const retrievalQuery = this.context.analysis?.intent?.normalizedQuery?.trim() || rawUserQuery;
|
|
357
|
+
return { rawUserQuery, retrievalQuery };
|
|
358
|
+
}
|
|
359
|
+
async fetchInitialRetrievalResults(retrievalQuery) {
|
|
360
|
+
return semanticSearchFiles(retrievalQuery, RELATED_FILES_LIMIT, this.context.analysis?.intent ?? {});
|
|
361
|
+
}
|
|
362
|
+
mapSearchResultToTopFile(result) {
|
|
363
|
+
return {
|
|
364
|
+
id: result.id,
|
|
365
|
+
path: result.path,
|
|
366
|
+
summary: result.summary ?? undefined,
|
|
367
|
+
bm25Score: result.bm25Score,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
mapSearchResultToRelatedFile(result) {
|
|
371
|
+
return {
|
|
372
|
+
id: result.id,
|
|
373
|
+
path: result.path,
|
|
374
|
+
summary: result.summary ?? undefined,
|
|
375
|
+
bm25Score: result.bm25Score,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
buildInitialRetrievalPromptArgs(results, retrievalQuery) {
|
|
379
|
+
const topFiles = results
|
|
380
|
+
.slice(0, NUM_TOPFILES)
|
|
381
|
+
.map(result => this.mapSearchResultToTopFile(result));
|
|
382
|
+
const relatedFiles = results
|
|
383
|
+
.slice(NUM_TOPFILES)
|
|
384
|
+
.map(result => this.mapSearchResultToRelatedFile(result));
|
|
385
|
+
const queryExpansionTerms = results.find(result => Array.isArray(result.queryExpansionTerms))?.queryExpansionTerms;
|
|
386
|
+
return {
|
|
387
|
+
topFiles,
|
|
388
|
+
relatedFiles,
|
|
389
|
+
query: retrievalQuery,
|
|
390
|
+
queryExpansionTerms,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
mergeSeededInitialContext(rawUserQuery, seededContext) {
|
|
394
|
+
const existingInit = this.context.initContext ?? { userQuery: rawUserQuery };
|
|
395
|
+
const seededInit = seededContext.initContext;
|
|
396
|
+
const mergedRelatedFiles = Array.from(new Set([
|
|
397
|
+
...(existingInit.relatedFiles ?? []),
|
|
398
|
+
...(seededInit?.relatedFiles ?? []),
|
|
399
|
+
]));
|
|
400
|
+
const mergedScores = {
|
|
401
|
+
...(existingInit.relatedFileScores ?? {}),
|
|
402
|
+
...(seededInit?.relatedFileScores ?? {}),
|
|
403
|
+
};
|
|
404
|
+
const mergedQueryExpansionTerms = Array.from(new Set([
|
|
405
|
+
...(existingInit.queryExpansionTerms ?? []),
|
|
406
|
+
...(seededInit?.queryExpansionTerms ?? []),
|
|
407
|
+
]));
|
|
408
|
+
this.context.initContext = {
|
|
409
|
+
...existingInit,
|
|
410
|
+
...(seededInit ?? {}),
|
|
411
|
+
userQuery: rawUserQuery,
|
|
412
|
+
relatedFiles: mergedRelatedFiles,
|
|
413
|
+
relatedFileScores: mergedScores,
|
|
414
|
+
queryExpansionTerms: mergedQueryExpansionTerms,
|
|
415
|
+
folderCapsules: (seededInit?.folderCapsules?.length
|
|
416
|
+
? seededInit.folderCapsules
|
|
417
|
+
: existingInit.folderCapsules) ?? [],
|
|
418
|
+
};
|
|
419
|
+
return mergedRelatedFiles.length;
|
|
420
|
+
}
|
|
421
|
+
applyDeterministicPreGroundingPrefilter(retrievalQuery) {
|
|
422
|
+
const init = this.context.initContext;
|
|
423
|
+
if (!init?.relatedFiles?.length)
|
|
424
|
+
return { before: 0, after: 0 };
|
|
425
|
+
const before = init.relatedFiles.length;
|
|
426
|
+
const scored = scoreCandidateFiles(init.relatedFiles, init.relatedFileScores ?? {}, retrievalQuery);
|
|
427
|
+
if (scored.length === 0)
|
|
428
|
+
return { before, after: before };
|
|
429
|
+
const scope = this.context.analysis?.scopeType ?? "repo-wide";
|
|
430
|
+
const baseKeepCount = scope === "single-file" ? 6 : 10;
|
|
431
|
+
const keepCount = Math.min(Math.max(3, baseKeepCount), scored.length);
|
|
432
|
+
const selected = new Set(scored
|
|
433
|
+
.slice(0, keepCount)
|
|
434
|
+
.map(item => item.filePath));
|
|
435
|
+
for (const item of scored) {
|
|
436
|
+
if (item.reasons.includes("exact-filename") || item.reasons.includes("path-anchor")) {
|
|
437
|
+
selected.add(item.filePath);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
init.relatedFiles = scored
|
|
441
|
+
.filter(item => selected.has(item.filePath))
|
|
442
|
+
.map(item => item.filePath);
|
|
443
|
+
init.relatedFileScores = Object.fromEntries(Object.entries(init.relatedFileScores ?? {})
|
|
444
|
+
.filter(([filePath]) => selected.has(filePath)));
|
|
445
|
+
logInputOutput("deterministicPreGroundingPrefilter", "output", {
|
|
446
|
+
retrievalQuery,
|
|
447
|
+
scope,
|
|
448
|
+
before,
|
|
449
|
+
after: init.relatedFiles.length,
|
|
450
|
+
keepCount,
|
|
451
|
+
files: scored.map(item => ({
|
|
452
|
+
file: item.filePath,
|
|
453
|
+
score: Number(item.score.toFixed(2)),
|
|
454
|
+
bm25Raw: item.bm25Raw,
|
|
455
|
+
kept: selected.has(item.filePath),
|
|
456
|
+
reasons: item.reasons,
|
|
457
|
+
})),
|
|
458
|
+
});
|
|
459
|
+
return { before, after: init.relatedFiles.length };
|
|
460
|
+
}
|
|
461
|
+
/* ───────────── extracted from runWorkLoop ───────────── */
|
|
462
|
+
isWorkLoopReady() {
|
|
463
|
+
const readinessDecision = this.context.analysis?.readiness?.decision;
|
|
464
|
+
const readinessConfidence = this.context.analysis?.readiness?.confidence ?? 0;
|
|
465
|
+
if (readinessDecision === "ready")
|
|
466
|
+
return true;
|
|
467
|
+
this.context.task.status = "deferred";
|
|
468
|
+
this.context.task.reason = `Readiness not achieved (decision=${readinessDecision}, confidence=${readinessConfidence})`;
|
|
469
|
+
this.persistTaskDataForRun();
|
|
470
|
+
this.logLine("TASK", "Cannot start work loop — agent needs more evidence to safely proceed", undefined, `Readiness: ${readinessDecision}, Confidence: ${readinessConfidence}`, { highlight: true });
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
ensureTaskForWorkLoop() {
|
|
474
|
+
if (!this.context.task) {
|
|
475
|
+
throw new Error("runWorkLoop: missing task");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async resolveNextTaskAction() {
|
|
479
|
+
await reasonNextTaskStep.run(this.context);
|
|
480
|
+
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
481
|
+
if (nextAction === "request-feedback" || nextAction === "complete")
|
|
482
|
+
return nextAction;
|
|
483
|
+
return "continue";
|
|
484
|
+
}
|
|
485
|
+
persistTaskDataForRun() {
|
|
486
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
487
|
+
}
|
|
488
|
+
persistTaskStatus(status) {
|
|
489
|
+
this.context.task.status = status;
|
|
490
|
+
this.persistTaskDataForRun();
|
|
491
|
+
}
|
|
492
|
+
startTaskStep(taskStep, stepCount) {
|
|
493
|
+
this.context.task.currentStep = taskStep;
|
|
494
|
+
taskStep.taskId = this.taskId;
|
|
495
|
+
taskStep.stepIndex = stepCount;
|
|
496
|
+
taskStep.status = "pending";
|
|
497
|
+
persistTaskStepInsert(taskStep, getDbForRepo());
|
|
498
|
+
this.logLine("NEW STEP", `Processing taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: true });
|
|
499
|
+
taskStep.startTime = Date.now();
|
|
500
|
+
persistTaskStepStart(taskStep, getDbForRepo());
|
|
501
|
+
}
|
|
502
|
+
finishTaskStep(taskStep, stepCount, stepAction) {
|
|
503
|
+
taskStep.endTime = Date.now();
|
|
504
|
+
if (stepAction === "complete") {
|
|
505
|
+
taskStep.status = "completed";
|
|
506
|
+
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
507
|
+
this.logLine("STEP-DONE", `Completed taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: false });
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
taskStep.status = "pending";
|
|
511
|
+
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
512
|
+
this.logLine("STEP", `Pending taskStep ${stepCount}`, undefined, taskStep.filePath);
|
|
513
|
+
}
|
|
318
514
|
/* ───────────── execution gates ───────────── */
|
|
319
515
|
/**
|
|
320
516
|
* Determines whether a phase can be executed based on execution mode and constraints.
|
|
@@ -403,6 +599,66 @@ export class MainAgent {
|
|
|
403
599
|
});
|
|
404
600
|
}
|
|
405
601
|
}
|
|
602
|
+
function scoreCandidateFiles(filePaths, relatedFileScores, retrievalQuery) {
|
|
603
|
+
const explicitRefs = extractFileReferences(retrievalQuery, { lowercase: true });
|
|
604
|
+
const explicitBasenames = new Set(explicitRefs.map(ref => path.basename(ref)));
|
|
605
|
+
const symbolAnchors = extractSymbolAnchors(retrievalQuery);
|
|
606
|
+
const scored = filePaths.map(filePath => {
|
|
607
|
+
const fileLower = filePath.toLowerCase();
|
|
608
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
609
|
+
let score = 0;
|
|
610
|
+
const reasons = [];
|
|
611
|
+
if (explicitBasenames.has(basename)) {
|
|
612
|
+
score += 100;
|
|
613
|
+
reasons.push("exact-filename");
|
|
614
|
+
}
|
|
615
|
+
if (explicitRefs.some(ref => fileLower.includes(ref))) {
|
|
616
|
+
score += 60;
|
|
617
|
+
reasons.push("path-anchor");
|
|
618
|
+
}
|
|
619
|
+
for (const anchor of symbolAnchors) {
|
|
620
|
+
if (basename.includes(anchor)) {
|
|
621
|
+
score += 40;
|
|
622
|
+
reasons.push(`symbol:${anchor}:filename`);
|
|
623
|
+
}
|
|
624
|
+
else if (fileLower.includes(anchor)) {
|
|
625
|
+
score += 20;
|
|
626
|
+
reasons.push(`symbol:${anchor}:path`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const bm25Raw = relatedFileScores[filePath];
|
|
630
|
+
if (typeof bm25Raw === "number" && Number.isFinite(bm25Raw)) {
|
|
631
|
+
const prior = Math.max(0, Math.min(20, -bm25Raw));
|
|
632
|
+
score += prior;
|
|
633
|
+
reasons.push(`bm25-prior:${prior.toFixed(2)}`);
|
|
634
|
+
}
|
|
635
|
+
return { filePath, score, bm25Raw, reasons };
|
|
636
|
+
});
|
|
637
|
+
return scored.sort((a, b) => {
|
|
638
|
+
if (b.score !== a.score)
|
|
639
|
+
return b.score - a.score;
|
|
640
|
+
const aBm25 = typeof a.bm25Raw === "number" ? a.bm25Raw : Number.POSITIVE_INFINITY;
|
|
641
|
+
const bBm25 = typeof b.bm25Raw === "number" ? b.bm25Raw : Number.POSITIVE_INFINITY;
|
|
642
|
+
return aBm25 - bBm25;
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
function extractSymbolAnchors(query) {
|
|
646
|
+
const matches = query.match(/[A-Za-z_][A-Za-z0-9_]{2,}/g) ?? [];
|
|
647
|
+
const out = new Set();
|
|
648
|
+
for (const token of matches) {
|
|
649
|
+
const lowered = token.toLowerCase();
|
|
650
|
+
const looksLikeSymbol = /[A-Z]/.test(token) ||
|
|
651
|
+
token.includes("_") ||
|
|
652
|
+
token.endsWith("Step") ||
|
|
653
|
+
token.endsWith("Module");
|
|
654
|
+
if (!looksLikeSymbol)
|
|
655
|
+
continue;
|
|
656
|
+
if (PREFILTER_STOP_WORDS.has(lowered))
|
|
657
|
+
continue;
|
|
658
|
+
out.add(lowered);
|
|
659
|
+
}
|
|
660
|
+
return Array.from(out);
|
|
661
|
+
}
|
|
406
662
|
// All helper functions (persistTaskData, bootTaskForRepo, persistTaskStep*) remain unchanged
|
|
407
663
|
/* ───────────── FOLDER CAPSULES SUMMARY HELPER ───────────── */
|
|
408
664
|
/**
|