scai 0.1.173 → 0.1.175
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
CHANGED
|
@@ -198,6 +198,7 @@ export class MainAgent {
|
|
|
198
198
|
if (!this.isWorkLoopReady())
|
|
199
199
|
return;
|
|
200
200
|
this.ensureTaskForWorkLoop();
|
|
201
|
+
this.recalibrateRoutingAfterVerify();
|
|
201
202
|
// Research gate is evaluated after runResearch() in run().
|
|
202
203
|
}
|
|
203
204
|
/* ───────────── research ───────────── */
|
|
@@ -207,8 +208,10 @@ export class MainAgent {
|
|
|
207
208
|
*/
|
|
208
209
|
async runResearch() {
|
|
209
210
|
var _a, _b;
|
|
210
|
-
if (!this.canExecuteRoute("research"))
|
|
211
|
+
if (!this.canExecuteRoute("research")) {
|
|
212
|
+
this.logLine("RESEARCH", "taskStepSeed", undefined, "skipped (route disallows research)");
|
|
211
213
|
return;
|
|
214
|
+
}
|
|
212
215
|
if (!this.context.task)
|
|
213
216
|
return;
|
|
214
217
|
(_a = this.context.task).taskSteps || (_a.taskSteps = []);
|
|
@@ -305,9 +308,20 @@ export class MainAgent {
|
|
|
305
308
|
.map(step => step.filePath));
|
|
306
309
|
const selectedFiles = this.context.analysis.focus?.selectedFiles ?? [];
|
|
307
310
|
const touchedFromResearch = this.context.analysis.researchArtifacts?.touchedFiles ?? [];
|
|
308
|
-
const
|
|
309
|
-
|
|
311
|
+
const route = this.context.analysis.routingDecision;
|
|
312
|
+
const useFocusedSelectedPlanOnly = this.context.analysis.readiness?.decision === "ready" &&
|
|
313
|
+
(route?.decision === "has-info") &&
|
|
314
|
+
(route?.scopeLocked ?? false) &&
|
|
315
|
+
(route?.allowSearch === false) &&
|
|
316
|
+
selectedFiles.length > 0;
|
|
317
|
+
const verifyMinConfidence = this.getVerifyConfidenceThresholdForPlan();
|
|
318
|
+
const verifyEntries = Object.entries(this.context.analysis.verify?.byFile ?? {});
|
|
319
|
+
const verifyRelevantFiles = verifyEntries
|
|
320
|
+
.filter(([_, verify]) => verify?.isRelevant &&
|
|
321
|
+
(verify.fileConfidence ?? 0) >= verifyMinConfidence)
|
|
310
322
|
.map(([filePath]) => filePath);
|
|
323
|
+
const verifySkippedLowConfidenceCount = verifyEntries.filter(([_, verify]) => !!verify?.isRelevant &&
|
|
324
|
+
(verify.fileConfidence ?? 0) < verifyMinConfidence).length;
|
|
311
325
|
const rankPath = (filePath) => {
|
|
312
326
|
const inSelected = selectedFiles.includes(filePath);
|
|
313
327
|
const inResearchTouched = touchedFromResearch.includes(filePath);
|
|
@@ -322,14 +336,18 @@ export class MainAgent {
|
|
|
322
336
|
return 3;
|
|
323
337
|
return 4;
|
|
324
338
|
};
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
339
|
+
const plannedPathsSource = useFocusedSelectedPlanOnly
|
|
340
|
+
? Array.from(new Set(selectedFiles))
|
|
341
|
+
: Array.from(new Set([
|
|
342
|
+
...selectedFiles,
|
|
343
|
+
...touchedFromResearch,
|
|
344
|
+
...verifyRelevantFiles,
|
|
345
|
+
]));
|
|
346
|
+
const plannedPaths = plannedPathsSource
|
|
330
347
|
.filter(filePath => !!filePath && !filePath.startsWith("__research__/") && fs.existsSync(filePath))
|
|
331
348
|
.sort((a, b) => rankPath(a) - rankPath(b))
|
|
332
349
|
.slice(0, 16);
|
|
350
|
+
this.ensureWorkingFilesLoaded(plannedPaths, "Planned for execution");
|
|
333
351
|
let seededCount = 0;
|
|
334
352
|
const seeded = [];
|
|
335
353
|
for (const filePath of plannedPaths) {
|
|
@@ -363,10 +381,71 @@ export class MainAgent {
|
|
|
363
381
|
selectedFileCount: selectedFiles.length,
|
|
364
382
|
researchTouchedCount: touchedFromResearch.length,
|
|
365
383
|
verifyRelevantCount: verifyRelevantFiles.length,
|
|
384
|
+
focusedSelectedOnly: useFocusedSelectedPlanOnly,
|
|
385
|
+
verifyMinConfidence,
|
|
386
|
+
verifySkippedLowConfidenceCount,
|
|
366
387
|
seeded,
|
|
367
388
|
});
|
|
368
389
|
this.logLine("PLAN", "taskStepSeed", undefined, `${seededCount} execution step(s) planned`);
|
|
369
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Sets minimum verify confidence before a file can be plan-seeded from verify-only signal.
|
|
393
|
+
* Example: single-file lanes require higher confidence than repo-wide lanes.
|
|
394
|
+
*/
|
|
395
|
+
getVerifyConfidenceThresholdForPlan() {
|
|
396
|
+
const scope = this.context.analysis?.scopeType ?? "repo-wide";
|
|
397
|
+
if (scope === "single-file")
|
|
398
|
+
return 0.45;
|
|
399
|
+
if (scope === "multi-file")
|
|
400
|
+
return 0.35;
|
|
401
|
+
return 0.3;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Re-routes after verify when evidence converges on selected files with high confidence.
|
|
405
|
+
* Example: selected files strongly verified => disable expansion/research and lock focused execution.
|
|
406
|
+
*/
|
|
407
|
+
recalibrateRoutingAfterVerify() {
|
|
408
|
+
var _a;
|
|
409
|
+
(_a = this.context).analysis || (_a.analysis = {});
|
|
410
|
+
const routing = this.context.analysis.routingDecision;
|
|
411
|
+
if (!routing)
|
|
412
|
+
return;
|
|
413
|
+
const intentCategory = (this.context.analysis.intent?.intentCategory ?? "").toLowerCase();
|
|
414
|
+
const isEditLikeIntent = intentCategory === "codingtask" ||
|
|
415
|
+
intentCategory === "refactortask" ||
|
|
416
|
+
intentCategory === "request" ||
|
|
417
|
+
intentCategory === "docsandcomments";
|
|
418
|
+
const canWrite = this.context.executionControl?.constraints?.allowFileWrites ?? false;
|
|
419
|
+
if (!isEditLikeIntent || !canWrite) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const selectedFiles = this.context.analysis.focus?.selectedFiles ?? [];
|
|
423
|
+
if (selectedFiles.length === 0)
|
|
424
|
+
return;
|
|
425
|
+
const readinessConfidence = this.context.analysis.readiness?.confidence ?? 0;
|
|
426
|
+
const intentConfidence = this.context.analysis.intent?.confidence ?? 0;
|
|
427
|
+
const minFileConfidence = 0.28;
|
|
428
|
+
const strongSelected = selectedFiles.filter(filePath => {
|
|
429
|
+
const verify = this.context.analysis?.verify?.byFile?.[filePath];
|
|
430
|
+
return verify?.isRelevant === true && (verify.fileConfidence ?? 0) >= minFileConfidence;
|
|
431
|
+
});
|
|
432
|
+
const convergedSingle = selectedFiles.length === 1 &&
|
|
433
|
+
strongSelected.length === 1 &&
|
|
434
|
+
readinessConfidence >= 0.9 &&
|
|
435
|
+
intentConfidence >= 0.8;
|
|
436
|
+
const convergedMulti = selectedFiles.length >= 2 &&
|
|
437
|
+
strongSelected.length >= 2 &&
|
|
438
|
+
readinessConfidence >= 0.9 &&
|
|
439
|
+
intentConfidence >= 0.75;
|
|
440
|
+
if (!convergedSingle && !convergedMulti)
|
|
441
|
+
return;
|
|
442
|
+
routing.decision = "has-info";
|
|
443
|
+
routing.allowSearch = false;
|
|
444
|
+
routing.allowResearch = false;
|
|
445
|
+
routing.scopeLocked = true;
|
|
446
|
+
routing.rationale = `${routing.rationale}; postVerify=focused-selection(${strongSelected.length})`;
|
|
447
|
+
this.logLine("TASK", "Routing recalibrated", undefined, `focused=${selectedFiles.length} selected, strong=${strongSelected.length}`);
|
|
448
|
+
}
|
|
370
449
|
/* ───────────── work loop ───────────── */
|
|
371
450
|
async runWorkLoop() {
|
|
372
451
|
if (this.context.task.status !== "active")
|
|
@@ -1224,6 +1303,7 @@ export class MainAgent {
|
|
|
1224
1303
|
this.persistTaskDataForRun();
|
|
1225
1304
|
}
|
|
1226
1305
|
startTaskStep(taskStep, stepCount) {
|
|
1306
|
+
this.ensureWorkingFilesLoaded([taskStep.filePath], "Current task step");
|
|
1227
1307
|
this.context.task.currentStep = taskStep;
|
|
1228
1308
|
taskStep.taskId = this.taskId;
|
|
1229
1309
|
taskStep.stepIndex = stepCount;
|
|
@@ -1364,6 +1444,41 @@ export class MainAgent {
|
|
|
1364
1444
|
console.log(`[USER OUTPUT] ${message}`);
|
|
1365
1445
|
});
|
|
1366
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Ensures workingFiles has file capsules with code for the given paths.
|
|
1449
|
+
* Example: planned verify-relevant files are hydrated before transform starts.
|
|
1450
|
+
*/
|
|
1451
|
+
ensureWorkingFilesLoaded(paths, reason) {
|
|
1452
|
+
var _a;
|
|
1453
|
+
(_a = this.context).workingFiles || (_a.workingFiles = []);
|
|
1454
|
+
const indexByPath = new Map(this.context.workingFiles.map(file => [file.path, file]));
|
|
1455
|
+
for (const filePath of paths) {
|
|
1456
|
+
if (!filePath || filePath.startsWith("__research__/"))
|
|
1457
|
+
continue;
|
|
1458
|
+
if (!fs.existsSync(filePath))
|
|
1459
|
+
continue;
|
|
1460
|
+
const existing = indexByPath.get(filePath);
|
|
1461
|
+
if (existing && typeof existing.code === "string" && existing.code.length > 0) {
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
let code;
|
|
1465
|
+
try {
|
|
1466
|
+
code = fs.readFileSync(filePath, "utf-8");
|
|
1467
|
+
}
|
|
1468
|
+
catch {
|
|
1469
|
+
code = undefined;
|
|
1470
|
+
}
|
|
1471
|
+
if (existing) {
|
|
1472
|
+
existing.code = code;
|
|
1473
|
+
existing.selectionReason || (existing.selectionReason = reason);
|
|
1474
|
+
}
|
|
1475
|
+
else {
|
|
1476
|
+
const capsule = { path: filePath, code, selectionReason: reason };
|
|
1477
|
+
this.context.workingFiles.push(capsule);
|
|
1478
|
+
indexByPath.set(filePath, capsule);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1367
1482
|
}
|
|
1368
1483
|
function scoreCandidateFiles(filePaths, relatedFileScores, retrievalQuery) {
|
|
1369
1484
|
const explicitRefs = extractFileReferences(retrievalQuery, { lowercase: true });
|
|
@@ -39,9 +39,12 @@ export const reasonNextTaskStep = {
|
|
|
39
39
|
...(context.analysis.focus?.selectedFiles ?? []),
|
|
40
40
|
...(context.workingFiles?.map(f => f.path).filter(Boolean) ?? []),
|
|
41
41
|
]));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// Use union (not fallback switch) so docs-only or planning-skipped lanes
|
|
43
|
+
// still process all selected files instead of only ad-hoc seeded taskSteps.
|
|
44
|
+
const intendedFiles = Array.from(new Set([
|
|
45
|
+
...plannedExecutionFiles,
|
|
46
|
+
...fallbackIntendedFiles,
|
|
47
|
+
]));
|
|
45
48
|
// ---------------------------
|
|
46
49
|
// 2️⃣ Transformed and analyzed files
|
|
47
50
|
// ---------------------------
|
|
@@ -48,6 +48,19 @@ export const resolveExecutionModeStep = {
|
|
|
48
48
|
docsOnly: true
|
|
49
49
|
};
|
|
50
50
|
break;
|
|
51
|
+
// ───── Generic requests: promote to transform when edit intent is explicit ─────
|
|
52
|
+
case "request":
|
|
53
|
+
if (hasExplicitWriteIntent(normalizedQuery)) {
|
|
54
|
+
mode = "transform";
|
|
55
|
+
rationale = "Request contains explicit edit intent.";
|
|
56
|
+
constraints = {
|
|
57
|
+
allowAnalysis: true,
|
|
58
|
+
allowPlanning: true,
|
|
59
|
+
allowFileWrites: true,
|
|
60
|
+
docsOnly: false
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
51
64
|
// ───── Everything else is analysis ─────
|
|
52
65
|
case "debugging":
|
|
53
66
|
case "planning":
|
|
@@ -88,5 +101,5 @@ function isQuestionLikeQuery(query) {
|
|
|
88
101
|
}
|
|
89
102
|
function hasExplicitWriteIntent(query) {
|
|
90
103
|
const q = query.toLowerCase();
|
|
91
|
-
return /\b(add|update|edit|modify|refactor|rewrite|implement|fix|create|remove|delete|replace|rename|write|patch|change)\b/.test(q);
|
|
104
|
+
return /\b(add|update|edit|modify|refactor|rewrite|implement|fix|create|remove|delete|replace|rename|write|patch|change|increment|bump|set)\b/.test(q);
|
|
92
105
|
}
|
|
@@ -23,6 +23,9 @@ export const routingDecisionStep = {
|
|
|
23
23
|
const hasExplicitTargets = (context.analysis.intent?.targetFiles?.length ?? 0) > 0;
|
|
24
24
|
const ambiguousIntent = intentConfidence < 0.45;
|
|
25
25
|
const isRefactorLike = intentCategory === "refactorTask" || intentCategory === "codingTask";
|
|
26
|
+
const isAnalysisLike = intentCategory === "question" ||
|
|
27
|
+
intentCategory === "analysis" ||
|
|
28
|
+
intentCategory === "explanation";
|
|
26
29
|
let decision = "has-info";
|
|
27
30
|
let allowSearch = true;
|
|
28
31
|
let scopeLocked = false;
|
|
@@ -50,8 +53,8 @@ export const routingDecisionStep = {
|
|
|
50
53
|
? "single-file-focused"
|
|
51
54
|
: "bounded-analysis";
|
|
52
55
|
const allowResearch = scope !== "none" &&
|
|
53
|
-
(
|
|
54
|
-
(
|
|
56
|
+
((isAnalysisLike && isResearchScope && (decision === "needs-info" || complexitySignals >= 2)) ||
|
|
57
|
+
(isRefactorLike && complexitySignals >= 1));
|
|
55
58
|
const confidence = Math.max(0, Math.min(1, 0.55 + intentConfidence * 0.35 - (ambiguousIntent ? 0.2 : 0)));
|
|
56
59
|
const routingDecision = {
|
|
57
60
|
decision,
|
|
@@ -3,7 +3,7 @@ import chalk from "chalk";
|
|
|
3
3
|
import { RUN_LOG_PATH } from "../constants.js";
|
|
4
4
|
// ---------------- Test Queries ----------------
|
|
5
5
|
export const testQueries = [
|
|
6
|
-
"Add concise comments to semanticAnalysisModule.ts and
|
|
6
|
+
"Add concise comments to semanticAnalysisModule.ts and finalAnswerModule.ts describing phase boundaries.",
|
|
7
7
|
"Refactor MainAgent runVerify flow to reduce nesting while preserving behavior.",
|
|
8
8
|
"Explain how resolveExecutionModeStep, routingDecisionStep, and canExecuteRoute interact.",
|
|
9
9
|
"Add stronger validation and safer fallback behavior in contextReviewStep.ts.",
|