scai 0.1.167 → 0.1.168

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.
@@ -55,12 +55,17 @@ export class MainAgent {
55
55
  }
56
56
  /* ───────────── main run ───────────── */
57
57
  async run() {
58
- this.runCount = 0;
59
- await this.runBoot();
60
- await this.runScope();
61
- await this.runGrounding();
62
- await this.runWorkLoop();
63
- await this.runFinalize();
58
+ try {
59
+ this.runCount = 0;
60
+ await this.runBoot();
61
+ await this.runScope();
62
+ await this.runGrounding();
63
+ await this.runWorkLoop();
64
+ await this.runFinalize();
65
+ }
66
+ finally {
67
+ this.ui.stop(); // ← guaranteed cleanup
68
+ }
64
69
  }
65
70
  /* ───────────── boot ───────────── */
66
71
  async runBoot() {
@@ -86,31 +91,16 @@ export class MainAgent {
86
91
  await scopeClassificationStep.run(this.context);
87
92
  this.logLine("TASK", "Scope classification complete");
88
93
  }
89
- /* ───────────── grounding / info acquisition loop ───────────── */
90
94
  async runGrounding() {
91
95
  let ready = false;
92
- while (!ready && this.runCount < this.maxRuns) {
93
- // ---------------- INFORMATION ACQUISITION ----------------
94
- if (this.canExecutePhase("planning") &&
95
- this.canExecuteScope("planning")) {
96
- const t = this.startTimer();
97
- await infoPlanGenStep.run(this.context);
98
- const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
99
- for (const step of infoPlan.steps) {
100
- const stepIO = { query: this.query };
101
- await this.executeStep(step, stepIO);
102
- }
103
- this.logLine("PLAN", "infoPlanGen", t(), undefined, { highlight: false });
104
- }
105
- // ---------------- DETERMINISTIC EVIDENCE VERIFICATION ----------------
96
+ while (this.runCount < this.maxRuns) {
97
+ // ---------------- EVIDENCE PIPELINE ----------------
106
98
  const t1 = this.startTimer();
107
99
  await evidenceVerifierStep.run({ query: this.query, context: this.context });
108
100
  this.logLine("ANALYSIS", "collectAnalysisEvidence", t1());
109
- /* ───────────── precheck ───────────── */
110
101
  const t2 = this.startTimer();
111
102
  await fileCheckStep(this.context);
112
103
  this.logLine("ANALYSIS", "fileCheckStep", t2());
113
- // Select grounded candidate files
114
104
  const t3 = this.startTimer();
115
105
  await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
116
106
  this.logLine("ANALYSIS", "selectRelevantSources", t3());
@@ -119,12 +109,29 @@ export class MainAgent {
119
109
  await readinessGateStep.run(this.context);
120
110
  this.logLine("HASINFO", "readinessGate", t4());
121
111
  ready = this.context.analysis?.readiness?.decision === "ready";
122
- if (!ready) {
123
- this.runCount++;
124
- if (this.context.analysis)
125
- this.context.analysis.planSuggestion = undefined;
126
- this.logLine("HASINFO", "Not ready — looping back to information acquisition", undefined, undefined, { highlight: false });
112
+ if (ready) {
113
+ break;
127
114
  }
115
+ // ---------------- INFORMATION ACQUISITION ----------------
116
+ if (this.canExecutePhase("planning") &&
117
+ this.canExecuteScope("planning")) {
118
+ const t = this.startTimer();
119
+ await infoPlanGenStep.run(this.context);
120
+ const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
121
+ // If we are about to execute a new info acquisition wave,
122
+ // wipe previous search results.
123
+ if (infoPlan.steps.length > 0 && this.context.initContext && this.context.analysis?.focus) {
124
+ this.context.initContext.relatedFiles = [];
125
+ this.context.analysis.focus.candidateFiles = [];
126
+ }
127
+ for (const step of infoPlan.steps) {
128
+ const stepIO = { query: this.query };
129
+ await this.executeStep(step, stepIO);
130
+ }
131
+ this.logLine("PLAN", "infoPlanGen", t(), undefined, { highlight: false });
132
+ }
133
+ this.runCount++;
134
+ this.logLine("HASINFO", "Not ready — looping back to evidence collection", undefined, undefined, { highlight: false });
128
135
  }
129
136
  }
130
137
  /* ───────────── finalize ───────────── */
@@ -135,17 +142,43 @@ export class MainAgent {
135
142
  }
136
143
  /* ───────────── work loop ───────────── */
137
144
  async runWorkLoop() {
145
+ const readinessDecision = this.context.analysis?.readiness?.decision;
146
+ const readinessConfidence = this.context.analysis?.readiness?.confidence ?? 0;
147
+ if (readinessDecision !== "ready") {
148
+ // ❌ Graceful fallback instead of throwing
149
+ this.context.task.status = "deferred";
150
+ this.context.task.reason = `Readiness not achieved (decision=${readinessDecision}, confidence=${readinessConfidence})`;
151
+ persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
152
+ this.logLine("TASK", `Cannot start work loop — agent needs more evidence to safely proceed`, undefined, `Readiness: ${readinessDecision}, Confidence: ${readinessConfidence}`, { highlight: true });
153
+ return;
154
+ }
155
+ if (!this.context.task) {
156
+ throw new Error("runWorkLoop: missing task");
157
+ }
138
158
  const MAX_TASK_STEPS = 5;
139
159
  let stepCount = 0;
140
- while (stepCount < MAX_TASK_STEPS) {
160
+ while (stepCount < MAX_TASK_STEPS &&
161
+ this.context.task.status === "active") {
141
162
  await reasonNextTaskStep.run(this.context);
142
163
  const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
164
+ // 🟡 Pause for user clarification
165
+ if (nextAction === "request-feedback") {
166
+ this.context.task.status = "paused";
167
+ persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
168
+ this.logLine("TASK", "Execution paused — awaiting user clarification", undefined, undefined, { highlight: false });
169
+ return;
170
+ }
171
+ // 🟢 Completed task
143
172
  if (nextAction === "complete") {
173
+ this.context.task.status = "completed";
174
+ persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
144
175
  this.logLine("TASK", "All selected files processed — task complete", undefined, undefined, { highlight: false });
145
176
  return;
146
177
  }
147
178
  const taskStep = await iterationFileSelector.run(this.context);
148
179
  if (!taskStep) {
180
+ this.context.task.status = "completed";
181
+ persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
149
182
  this.logLine("TASK", "No eligible taskStep found — task complete", undefined, undefined, { highlight: false });
150
183
  return;
151
184
  }
@@ -155,11 +188,11 @@ export class MainAgent {
155
188
  taskStep.stepIndex = stepCount;
156
189
  taskStep.status = "pending";
157
190
  persistTaskStepInsert(taskStep, getDbForRepo());
158
- this.logLine("NEW STEP", `Processing taskStep ${stepCount}/${MAX_TASK_STEPS}`, undefined, taskStep.filePath, { highlight: true });
191
+ this.logLine("NEW STEP", `Processing taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: true });
159
192
  taskStep.startTime = Date.now();
160
193
  persistTaskStepStart(taskStep, getDbForRepo());
161
194
  // ---------------------------
162
- // Step-level iterations: reason only about this file
195
+ // Step-level iterations
163
196
  // ---------------------------
164
197
  const stepAction = await this.runStepIterations(taskStep);
165
198
  if (stepAction === "complete") {
@@ -229,7 +229,19 @@ export const evidenceVerifierStep = {
229
229
  query,
230
230
  data: { fileAnalysis: context.analysis.fileAnalysis },
231
231
  };
232
- logInputOutput("evidenceVerifier", "output", output.data);
232
+ // Build compact log summary
233
+ const logSummary = Object.entries(context.analysis.fileAnalysis).map(([path, analysis]) => {
234
+ const evidenceCount = analysis.evidence?.length ?? 0;
235
+ const confidenceMatch = analysis.relevanceExplanation?.match(/\[confidence:(\d+\.\d+)\]/);
236
+ const confidence = confidenceMatch?.[1] ?? "0.00";
237
+ return {
238
+ file: path,
239
+ confidence,
240
+ evidenceCount,
241
+ isRelevant: analysis.action?.isRelevant ?? false,
242
+ };
243
+ });
244
+ logInputOutput("evidenceVerifier", "output", logSummary);
233
245
  return output;
234
246
  },
235
247
  };
@@ -5,40 +5,32 @@ import { logInputOutput } from '../utils/promptLogHelper.js';
5
5
  import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
6
6
  /**
7
7
  * INFO PLAN GENERATOR
8
- * Generates a single information-gathering step.
8
+ * Generates a single information-acquisition step.
9
+ *
10
+ * NOTE:
11
+ * - This step only creates a plan; execution happens in the agent loop.
12
+ * - The agent controls when this step runs.
9
13
  */
10
14
  export const infoPlanGenStep = {
11
15
  name: 'infoPlanGen',
12
16
  description: 'Generates one information-acquisition step.',
13
- requires: ['userQuery', 'analysis.intent', 'analysis.focus'],
17
+ requires: ['userQuery', 'analysis.intent'],
14
18
  produces: ['analysis.planSuggestion'],
15
19
  async run(context) {
16
20
  context.analysis || (context.analysis = {});
17
21
  // Clear any previous plan
18
22
  delete context.analysis.planSuggestion;
19
- const missingFiles = Array.isArray(context.analysis.focus?.candidateFiles)
20
- ? context.analysis.focus.candidateFiles
21
- : [];
22
23
  const analysis = context.analysis;
23
- const needsDiscovery = analysis?.routingDecision?.decision === "needs-info" ||
24
- analysis?.scopeType === "repo-wide";
25
- // Nothing to do? Save empty steps array
26
- if (!needsDiscovery && missingFiles.length === 0) {
27
- context.analysis.planSuggestion = { plan: { steps: [] } };
28
- logInputOutput("infoPlanGen", "output", []);
29
- return;
30
- }
31
- // Restrict actions to INFO only
24
+ const intentText = analysis.intent?.normalizedQuery ?? '';
25
+ const intentCategory = analysis.intent?.intentCategory ?? '';
26
+ // Only info-type actions
32
27
  const effectiveActions = PLAN_ACTIONS.filter(a => a.groups?.includes('info'));
33
- // No actions available? Save empty steps array
34
28
  if (!effectiveActions.length) {
35
29
  context.analysis.planSuggestion = { plan: { steps: [] } };
36
30
  logInputOutput('infoPlanGen', 'output', []);
37
31
  return;
38
32
  }
39
33
  const actionsJson = JSON.stringify(effectiveActions, null, 2);
40
- const intentText = analysis.intent?.normalizedQuery ?? '';
41
- const intentCategory = analysis.intent?.intentCategory ?? '';
42
34
  const prompt = `
43
35
  You are an autonomous coding agent.
44
36
 
@@ -53,16 +45,10 @@ ${intentCategory}
53
45
  Allowed actions (info only):
54
46
  ${actionsJson}
55
47
 
56
- Existing relevant files:
57
- ${JSON.stringify(analysis.focus?.selectedFiles ?? [], null, 2)}
58
-
59
- Missing files / candidates:
60
- ${JSON.stringify(missingFiles, null, 2)}
61
-
62
48
  Rules:
63
49
  - Only produce a single info step.
64
50
  - Step must include: "action", "description", "subQuery" (array), "metadata".
65
- - Do NOT invent new files or actions.
51
+ - Do NOT invent new actions or files.
66
52
  - Return strictly valid JSON representing one step.
67
53
 
68
54
  If no further information is required, return:
@@ -78,12 +64,14 @@ If no further information is required, return:
78
64
  const jsonString = typeof cleaned.content === 'string'
79
65
  ? cleaned.content
80
66
  : JSON.stringify(cleaned.content ?? '{}');
81
- let step = JSON.parse(jsonString);
82
- // Validate structure
67
+ // Unwrap the step from LLM output
68
+ const parsed = JSON.parse(jsonString);
69
+ let step = parsed?.step ?? null;
70
+ // Validate minimal structure
83
71
  if (!step || typeof step.action !== 'string') {
84
72
  step = null;
85
73
  }
86
- // Map groups & metadata
74
+ // Attach groups & routing confidence if available
87
75
  if (step) {
88
76
  const actionDef = PLAN_ACTIONS.find(a => a.action === step.action);
89
77
  step.groups = actionDef?.groups ?? ['info'];
@@ -95,10 +83,9 @@ If no further information is required, return:
95
83
  : {})
96
84
  };
97
85
  }
98
- // Save single step to context as an array (empty if null)
99
- const steps = step ? [step] : [];
100
- context.analysis.planSuggestion = { plan: { steps } };
101
- logInputOutput('infoPlanGen', 'output', steps);
86
+ // Save as array in planSuggestion
87
+ context.analysis.planSuggestion = { plan: { steps: step ? [step] : [] } };
88
+ logInputOutput('infoPlanGen', 'output', context.analysis.planSuggestion.plan?.steps ?? []);
102
89
  }
103
90
  catch (err) {
104
91
  console.warn('⚠️ Failed to generate info step:', err);
@@ -131,6 +131,5 @@ function logOutput(context) {
131
131
  logInputOutput('readinessGateStep', 'output', {
132
132
  readiness: context.analysis?.readiness,
133
133
  focus: context.analysis?.focus,
134
- fileAnalysis: context.analysis?.fileAnalysis,
135
134
  });
136
135
  }
@@ -1,6 +1,3 @@
1
- // File: src/agents/reasonNextTaskStep.ts
2
- import { generate } from "../lib/generate.js";
3
- import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
4
1
  import { logInputOutput } from "../utils/promptLogHelper.js";
5
2
  /**
6
3
  * REASON NEXT TASK STEP
@@ -132,10 +129,11 @@ export const reasonNextTaskStep = {
132
129
  rationale = "All selected files have been analyzed, transformed, and validated successfully.";
133
130
  confidence = 0.98;
134
131
  }
135
- // ---------------------------
132
+ /* // ---------------------------
136
133
  // 6.5️⃣ Optional: Reason over known risks
137
134
  // ---------------------------
138
135
  const knownRisks = context.analysis.understanding?.risks ?? [];
136
+
139
137
  if (knownRisks.length > 0) {
140
138
  // Optionally call the LLM with constrained instructions
141
139
  const riskPrompt = `
@@ -146,34 +144,39 @@ Task:
146
144
  - Decide whether it is reasonable to ask the user for clarification before proceeding.
147
145
  - Return STRICT JSON: { askUser: true|false, rationale: string }
148
146
  `;
147
+
149
148
  try {
150
149
  const aiResponse = await generate({
151
150
  query: context.initContext?.userQuery ?? "",
152
151
  content: riskPrompt
153
152
  });
153
+
154
154
  const cleaned = await cleanupModule.run({
155
155
  query: context.initContext?.userQuery ?? "",
156
156
  content: aiResponse.data ?? ""
157
157
  });
158
+
158
159
  const parsed = cleaned.data;
160
+
159
161
  // type guard
160
- if (parsed &&
162
+ if (
163
+ parsed &&
161
164
  typeof parsed === "object" &&
162
165
  "askUser" in parsed &&
163
166
  "rationale" in parsed &&
164
- typeof parsed.rationale === "string") {
165
- if (parsed.askUser) {
167
+ typeof (parsed as { rationale?: unknown }).rationale === "string"
168
+ ) {
169
+ if ((parsed as { askUser: boolean }).askUser) {
166
170
  nextAction = "request-feedback";
167
- rationale += `\nUser clarification recommended due to known risks: ${parsed.rationale}`;
171
+ rationale += `\nUser clarification recommended due to known risks: ${(parsed as { rationale: string }).rationale}`;
168
172
  confidence = Math.min(confidence, 0.8); // slightly lower because human needed
169
173
  }
170
174
  }
171
- }
172
- catch (err) {
175
+ } catch (err) {
173
176
  console.warn("[reasonNextTaskStep] Risk reasoning failed", err);
174
177
  // fallback: ignore, keep deterministic nextAction
175
178
  }
176
- }
179
+ } */
177
180
  // ---------------------------
178
181
  // 7️⃣ Ensure a TaskStep exists for nextFile
179
182
  // ---------------------------
@@ -4,70 +4,81 @@ import { logInputOutput } from "../utils/promptLogHelper.js";
4
4
  export const scopeClassificationStep = {
5
5
  run: async (context) => {
6
6
  const query = context.initContext?.userQuery?.trim() ?? "";
7
- const repoFiles = context.initContext?.relatedFiles?.join(", ") ?? "";
8
- // ------------------------ LLM PROMPT ------------------------
7
+ context.analysis ?? (context.analysis = {});
8
+ // ------------------------------------------------------------
9
+ // 1️⃣ Prepare deterministic hints for the LLM
10
+ // ------------------------------------------------------------
11
+ const lower = query.toLowerCase();
12
+ const hints = [];
13
+ // Systemic/repo-wide hints
14
+ if (/\b(codebase|entire repo|whole repo|entire project|whole project|application|system)\b/i.test(lower)) {
15
+ hints.push("The query mentions the entire codebase or system; likely repo-wide scope.");
16
+ }
17
+ // Debugging or error hints
18
+ if (/\b(debug|issue|bug|memory leak|performance|error|crash|failure)\b/i.test(lower)) {
19
+ hints.push("The query mentions debugging, errors, or memory/performance issues; consider broad impact.");
20
+ }
21
+ // Explicit artifact references
22
+ if (/\b[\w-]+\.(ts|js|tsx|jsx|py|java|go|rs|cpp|c|cs)\b/i.test(lower) ||
23
+ /\b(class|function|method|module)\s+\w+/i.test(lower)) {
24
+ hints.push("The query references a specific file, class, function, or module; possibly single-file scope.");
25
+ }
26
+ // ------------------------------------------------------------
27
+ // 2️⃣ LLM classification
28
+ // ------------------------------------------------------------
9
29
  const prompt = `
10
- You are an assistant that classifies the "scope" of a user's request in a code repository.
30
+ You classify the scope of a user's request in a software repository.
11
31
 
12
- Return STRICT JSON using this schema:
32
+ Return STRICT JSON:
13
33
  {
14
34
  "scopeType": "none" | "single-file" | "multi-file" | "repo-wide"
15
35
  }
16
36
 
17
- Rules:
18
- - "none": request does not require touching any repository files.
19
- - "single-file": request likely affects only one file.
20
- - "multi-file": request affects several files or modules.
21
- - "repo-wide": request affects many files or the whole repo.
37
+ Definitions:
38
+ - "none": purely conceptual, no repository interaction required.
39
+ - "single-file": clearly limited to one specific file or artifact.
40
+ - "multi-file": involves several related files/modules.
41
+ - "repo-wide": broad/systemic across the repository.
22
42
 
23
- User query: "${query}"
24
- Candidate files: [${repoFiles}]
25
- `.trim();
43
+ User query:
44
+ "${query}"
45
+
46
+ Hints for classification:
47
+ ${hints.length ? "- " + hints.join("\n- ") : "None"}
48
+ `.trim();
26
49
  let llmScope = null;
27
50
  try {
28
- // Step 1: ask LLM
29
51
  const genInput = { query, content: prompt };
30
52
  const genOutput = await generate(genInput);
31
53
  const raw = typeof genOutput.data === "string"
32
54
  ? genOutput.data
33
55
  : JSON.stringify(genOutput.data ?? "{}");
34
- // Step 2: cleanup LLM output (remove extra text, comments, etc.)
35
56
  const cleaned = await cleanupModule.run({ query, content: raw });
36
57
  const jsonString = typeof cleaned.content === "string"
37
58
  ? cleaned.content
38
59
  : JSON.stringify(cleaned.content ?? "{}");
39
60
  const parsed = JSON.parse(jsonString);
40
- if (parsed && ["none", "single-file", "multi-file", "repo-wide"].includes(parsed.scopeType)) {
61
+ if (parsed &&
62
+ ["none", "single-file", "multi-file", "repo-wide"].includes(parsed.scopeType)) {
41
63
  llmScope = parsed.scopeType;
42
64
  }
43
65
  }
44
66
  catch (err) {
45
- console.warn("LLM scope classification failed:", err);
46
- }
47
- // ------------------------ FALLBACK HEURISTICS ------------------------
48
- let fallbackScope = "repo-wide";
49
- if (!query || /\b(explain|what|who|define|describe|overview|summary)\b/i.test(query)) {
50
- fallbackScope = "none";
67
+ console.warn("⚠️ LLM scope classification failed, falling back:", err);
51
68
  }
52
- else if (/\b(file|module|class|function|cleanupModule\.ts|MainAgent\.ts|example\.ts)\b/i.test(query)) {
53
- fallbackScope = "single-file";
69
+ // ------------------------------------------------------------
70
+ // 3️⃣ Safe fallback if LLM fails
71
+ // ------------------------------------------------------------
72
+ if (!llmScope) {
73
+ llmScope = "repo-wide";
54
74
  }
55
- else if (/\b(across|multiple files|several files|refactor|update many|shared modules)\b/i.test(query)) {
56
- fallbackScope = "multi-file";
57
- }
58
- // ------------------------ FINAL DECISION ------------------------
59
- const finalScope = llmScope ?? fallbackScope;
60
- // Save in context
61
- context.analysis ?? (context.analysis = {});
62
- context.analysis.scopeType = finalScope;
63
- const scopeReasoning = llmScope ? "llm" : "heuristic-fallback";
64
- // Log both for monitoring
75
+ context.analysis.scopeType = llmScope;
65
76
  logInputOutput("scopeClassificationStep", "output", {
66
77
  llmScope,
67
- fallbackScope: fallbackScope,
68
- finalScope,
69
- reasoning: scopeReasoning,
78
+ fallbackScope: "repo-wide",
79
+ finalScope: llmScope,
80
+ reasoning: "llm-always",
70
81
  });
71
- return { scopeType: finalScope };
72
- }
82
+ return { scopeType: llmScope };
83
+ },
73
84
  };
@@ -65,16 +65,17 @@ export async function runAskCommand(query) {
65
65
  const ui = {
66
66
  update: text => spinner.update(text),
67
67
  pause: fn => {
68
- spinner.stop();
68
+ spinner.stop(); // pause spinner
69
69
  try {
70
70
  fn();
71
71
  }
72
72
  finally {
73
- spinner.start();
73
+ spinner.start(); // resume spinner
74
74
  }
75
75
  },
76
76
  succeed: msg => spinner.succeed(msg),
77
- fail: msg => spinner.fail(msg)
77
+ fail: msg => spinner.fail(msg),
78
+ stop: () => spinner.stop() // ✅ proper stop implementation
78
79
  };
79
80
  const agent = new MainAgent(context, ui);
80
81
  await agent.run();
@@ -19,12 +19,18 @@ export async function runTasksCommand() {
19
19
  console.log(`${chalk.cyan(`[${t.id}]`)} ${chalk.yellow(t.status.padEnd(9))} ${oneLineTruncate(t.initial_query)}`);
20
20
  }
21
21
  await new Promise((resolve) => {
22
- rl.question("\nSelect task id (enter to quit): ", answer => {
23
- if (!answer.trim() || answer.trim().toLowerCase() === "q") {
22
+ rl.question("\nSelect task id (q to quit): ", answer => {
23
+ const trimmed = answer.trim();
24
+ if (trimmed.toLowerCase() === "q") {
24
25
  resolve();
25
26
  return;
26
27
  }
27
- const id = Number(answer);
28
+ if (!trimmed) {
29
+ console.log(chalk.red("Please enter a task id or 'q' to quit."));
30
+ resolve();
31
+ return;
32
+ }
33
+ const id = Number(trimmed);
28
34
  if (!Number.isInteger(id)) {
29
35
  console.log(chalk.red("Invalid task id."));
30
36
  resolve();
@@ -130,21 +130,33 @@ export async function semanticSearchFiles(originalQuery, _query, topK = 5) {
130
130
  return [];
131
131
  }
132
132
  /* -------------------------------------------------- */
133
- /* LLM → FTS QUERY GENERATION (TAG-BASED) */
133
+ /* LLM → FTS QUERY GENERATION (TAG-BASED) */
134
134
  /* -------------------------------------------------- */
135
135
  async function generatePrimaryFtsQuery(userQuery) {
136
136
  const prompt = `
137
- Generate a SQLite FTS query for searching a source code repository.
137
+ You are generating a SQLite FTS query for searching a source code repository.
138
+
139
+ The user query may refer to:
140
+ - High-level intent
141
+ - Domain terminology
142
+ - Specific filenames, file types, or configuration files
138
143
 
139
144
  Input:
140
145
  "${userQuery}"
141
146
 
147
+ Task:
148
+ 1. Extract high-level intent terms
149
+ 2. Expand to related domain-specific terminology
150
+ 3. Expand to likely filenames, config files, or structural artifacts if relevant
151
+ 4. Combine ALL useful terms into ONE OR-joined FTS query
152
+
142
153
  Rules:
143
- - Output ONLY the query terms
154
+ - Output ONLY the OR-joined terms
155
+ - Max 12 total terms
144
156
  - Use OR between terms
145
- - Max 10 terms
157
+ - Include filenames when relevant
146
158
  - No explanations
147
- - No sentences
159
+ - No natural language sentences
148
160
 
149
161
  Wrap the result in <FILE_CONTENT> tags.
150
162
 
@@ -164,7 +176,7 @@ term1 OR term2 OR term3
164
176
  }
165
177
  async function generateFallbackFtsQueries(userQuery, failedQuery) {
166
178
  const prompt = `
167
- You are generating fallback SQLite FTS queries for a source code search.
179
+ You are generating fallback SQLite FTS queries for a source code repository search.
168
180
 
169
181
  Original user query:
170
182
  "${userQuery}"
@@ -173,18 +185,27 @@ Primary FTS query returned ZERO results:
173
185
  "${failedQuery}"
174
186
 
175
187
  Task:
176
- - Generate 23 independent FTS queries (MAX 3)
177
- - Each query must be a single OR-joined expression
178
- - Max 10 terms per query
179
- - Focus on filenames, symbols, module names
188
+ Generate 35 independent FTS queries (MAX 5).
189
+
190
+ For each query:
191
+ 1. Think at a different abstraction level (intent-level, domain-level, structural-level).
192
+ 2. Include filenames, file types, modules, config files, or symbols when relevant.
193
+ 3. Use a single OR-joined expression.
194
+ 4. Max 10 terms per query.
195
+
196
+ Rules:
180
197
  - Avoid natural language sentences
181
- - Avoid explanations or commentary
198
+ - No explanations
199
+ - No commentary
200
+ - Each line must be one complete OR expression
182
201
 
183
202
  Output format (STRICT):
184
203
  <FILE_CONTENT>
185
204
  query1
186
205
  query2
187
206
  query3
207
+ query4
208
+ query5
188
209
  </FILE_CONTENT>
189
210
  `.trim();
190
211
  try {
@@ -195,7 +216,7 @@ query3
195
216
  .split(/\r?\n/)
196
217
  .map(q => sanitizeQueryForFts(q.trim()))
197
218
  .filter(Boolean)
198
- .slice(0, 3);
219
+ .slice(0, 5);
199
220
  if (!subQueries.length) {
200
221
  throw new Error("No fallback subqueries generated");
201
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.167",
3
+ "version": "0.1.168",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"
@@ -1,69 +0,0 @@
1
- import { logInputOutput } from "../utils/promptLogHelper.js";
2
- export const routingDecisionStep = {
3
- run: async (context) => {
4
- context.analysis ?? (context.analysis = {});
5
- const scope = context.analysis.scopeType ?? "repo-wide";
6
- // Default routing decision
7
- let routing = {
8
- decision: "has-info",
9
- allowSearch: false,
10
- allowInfoSteps: false,
11
- allowTransform: false,
12
- allowExplain: false,
13
- scopeLocked: false,
14
- rationale: ""
15
- };
16
- switch (scope) {
17
- case "none":
18
- routing = {
19
- decision: "has-info",
20
- allowSearch: false,
21
- allowInfoSteps: false,
22
- allowTransform: false,
23
- allowExplain: true,
24
- scopeLocked: true,
25
- rationale: "Query is not repo-related, early explain exit allowed"
26
- };
27
- break;
28
- case "single-file":
29
- routing = {
30
- decision: "has-info",
31
- allowSearch: true,
32
- allowInfoSteps: true,
33
- allowTransform: true,
34
- allowExplain: false,
35
- scopeLocked: true,
36
- rationale: "Query targets a single file; safe to execute info and transform steps"
37
- };
38
- break;
39
- case "multi-file":
40
- routing = {
41
- decision: "needs-info",
42
- allowSearch: true,
43
- allowInfoSteps: true,
44
- allowTransform: true,
45
- allowExplain: false,
46
- scopeLocked: false,
47
- rationale: "Query spans multiple files; controlled search and info acquisition needed"
48
- };
49
- break;
50
- case "repo-wide":
51
- routing = {
52
- decision: "needs-info",
53
- allowSearch: true,
54
- allowInfoSteps: true,
55
- allowTransform: true,
56
- allowExplain: false,
57
- scopeLocked: false,
58
- rationale: "Query is repo-wide; full search and sampling required"
59
- };
60
- break;
61
- }
62
- context.analysis.routingDecision = routing;
63
- logInputOutput("routingDecisionStep", "output", {
64
- routingDecision: routing,
65
- scope,
66
- });
67
- return routing;
68
- }
69
- };