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.
@@ -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 { reasonNextIterationStep } from './reasonNextIterationStep.js';
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
- /* ===== BOOT ===== */
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
- /* ===== PRECHECK ===== */
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
- /* ===== SCOPE ===== */
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
- // ---------------- FILE SELECTION ----------------
152
- const tSelect = this.startTimer();
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
- let stepCounter = 1; // 🔹 initialize step counter
185
- for (const step of transformPlan.steps) {
263
+ const firstStep = transformPlan.steps[0];
264
+ if (firstStep) {
186
265
  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
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 reasonNextIterationStep.run(this.context);
209
- this.logLine("REASONING", "reasonNextIterationStep", tReason());
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
- allowed = !docsOnly && (constraints?.allowPlanning ?? (mode === "analyze" || mode === "transform"));
346
+ // Read-only phases are always allowed
347
+ allowed = !docsOnly;
290
348
  break;
291
349
  case "transform":
292
350
  case "write":
293
- allowed = constraints?.allowFileWrites ?? (mode === "transform");
294
- break;
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}, mode=${mode}, docsOnly=${docsOnly}, allowed=${allowed}`);
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 queries: only explain makes sense
317
- allowed = phase === "explain";
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
- // Repo-wide explain must not short-circuit via analysis
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", "analysis.currentTargetFile"],
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
- // 🔁 Always overwrite previous planSuggestion for analysis phase
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
- const targetFile = context.analysis.currentTargetFile;
21
- const routingDecision = context.analysis.routingDecision?.decision;
22
- // Determine whether semantic analysis has already been performed
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 = sentenceRegex.exec(query)) !== null) {
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
- .filter(w => w.match(/\.(ts|js|tsx|md)$/));
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([A-Z]\w+|[a-z]\w+)\(\)/g;
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 fileName = path.split("/").pop() ?? "";
90
- if (filenameTargets.includes(fileName)) {
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: "${fileName}"`,
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 && allowAnalysis && allowWrites,
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
- routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
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
- routingConfidence: context.analysis?.routingDecision?.confidence ?? 0
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' ? genOutput.data : JSON.stringify(genOutput.data ?? '{}');
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' ? cleaned.content : JSON.stringify(cleaned.content ?? '{}');
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
- routingConfidence: analysis?.routingDecision?.confidence ?? 0
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)