scai 0.1.140 → 0.1.142
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 +152 -76
- package/dist/agents/preFileSearchCheckStep.js +44 -29
- package/dist/agents/resolveExecutionModeStep.js +47 -0
- package/dist/index.js +4 -5
- package/dist/pipeline/modules/explainModule.js +68 -0
- package/dist/pipeline/registry/moduleRegistry.js +2 -0
- package/package.json +1 -1
package/dist/agents/MainAgent.js
CHANGED
|
@@ -7,7 +7,6 @@ import { structuralAnalysisStep } from "./structuralAnalysisStep.js";
|
|
|
7
7
|
import { contextReviewStep } from "./contextReviewStep.js";
|
|
8
8
|
import { planTargetFilesStep } from "./planTargetFilesStep.js";
|
|
9
9
|
import { validationAnalysisStep } from "./validationAnalysisStep.js";
|
|
10
|
-
import { preFileSearchCheckStep } from "./preFileSearchCheckStep.js";
|
|
11
10
|
import { semanticAnalysisStep } from "./semanticAnalysisStep.js";
|
|
12
11
|
import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
13
12
|
import { transformPlanGenStep } from "./transformPlanGenStep.js";
|
|
@@ -15,6 +14,8 @@ import { finalPlanGenStep } from "./finalPlanGenStep.js";
|
|
|
15
14
|
import { Spinner } from "../lib/spinner.js";
|
|
16
15
|
import { getDbForRepo } from "../db/client.js";
|
|
17
16
|
import { writeFileStep } from "./writeFileStep.js";
|
|
17
|
+
import { resolveExecutionModeStep } from "./resolveExecutionModeStep.js";
|
|
18
|
+
import { preFileSearchCheckStep } from "./preFileSearchCheckStep.js";
|
|
18
19
|
/* ───────────────────────── helpers ───────────────────────── */
|
|
19
20
|
let activeSpinner = null;
|
|
20
21
|
/**
|
|
@@ -85,11 +86,7 @@ export class MainAgent {
|
|
|
85
86
|
const mod = resolveModuleForAction(step.action);
|
|
86
87
|
if (!mod) {
|
|
87
88
|
logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
|
|
88
|
-
return {
|
|
89
|
-
query: input.query,
|
|
90
|
-
content: input.content,
|
|
91
|
-
data: { skipped: true }
|
|
92
|
-
};
|
|
89
|
+
return { query: input.query, content: input.content, data: { skipped: true } };
|
|
93
90
|
}
|
|
94
91
|
try {
|
|
95
92
|
this.spinner.update(`Running step: ${step.action}`);
|
|
@@ -98,22 +95,36 @@ export class MainAgent {
|
|
|
98
95
|
content: input.data ?? input.content,
|
|
99
96
|
context: this.context
|
|
100
97
|
});
|
|
101
|
-
if (!output)
|
|
98
|
+
if (!output)
|
|
102
99
|
throw new Error(`Module "${mod.name}" returned empty output`);
|
|
103
|
-
}
|
|
104
100
|
logLine("EXECUTE", step.action, stop());
|
|
105
|
-
return {
|
|
106
|
-
query: step.description ?? input.query,
|
|
107
|
-
data: output.data
|
|
108
|
-
};
|
|
101
|
+
return { query: step.description ?? input.query, data: output.data };
|
|
109
102
|
}
|
|
110
103
|
catch (err) {
|
|
111
104
|
logLine("EXECUTE", step.action, stop(), "failed");
|
|
112
105
|
throw err;
|
|
113
106
|
}
|
|
114
107
|
}
|
|
108
|
+
/* ───────────────────────── execution gates ───────────────────────── */
|
|
109
|
+
canExecutePhase(phase) {
|
|
110
|
+
const mode = this.context.executionControl?.mode ?? "transform";
|
|
111
|
+
switch (phase) {
|
|
112
|
+
case "analysis":
|
|
113
|
+
return mode !== "explain"; // only analyze if not explain
|
|
114
|
+
case "planning":
|
|
115
|
+
return mode === "transform"; // planning only in transform
|
|
116
|
+
case "transform":
|
|
117
|
+
case "write":
|
|
118
|
+
return mode === "transform"; // write only in transform
|
|
119
|
+
case "explain":
|
|
120
|
+
return mode === "explain"; // only explain mode runs explain
|
|
121
|
+
default:
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
115
125
|
/* ───────────── main run ───────────── */
|
|
116
126
|
async run() {
|
|
127
|
+
var _a, _b;
|
|
117
128
|
this.runCount++;
|
|
118
129
|
const stopRun = startTimer();
|
|
119
130
|
logLine("RUN", `start #${this.runCount}`);
|
|
@@ -121,9 +132,54 @@ export class MainAgent {
|
|
|
121
132
|
this.spinner.start();
|
|
122
133
|
/* ================= BOOT ================= */
|
|
123
134
|
{
|
|
124
|
-
const
|
|
135
|
+
const t1 = startTimer();
|
|
125
136
|
await understandIntentStep.run({ context: this.context });
|
|
126
|
-
logLine("BOOT", "understandIntent",
|
|
137
|
+
logLine("BOOT", "understandIntent", t1());
|
|
138
|
+
const t2 = startTimer();
|
|
139
|
+
await resolveExecutionModeStep.run(this.context);
|
|
140
|
+
logLine("BOOT", "resolveExecutionMode", t2(), `mode=${this.context.executionControl?.mode}`);
|
|
141
|
+
// Ensure executionControl exists first
|
|
142
|
+
(_a = this.context).executionControl ?? (_a.executionControl = {
|
|
143
|
+
mode: "transform", // default mode if missing
|
|
144
|
+
rationale: "Default execution mode",
|
|
145
|
+
confidence: 1,
|
|
146
|
+
constraints: {
|
|
147
|
+
allowAnalysis: true,
|
|
148
|
+
allowPlanning: true,
|
|
149
|
+
allowFileWrites: false
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// Ensure constraints are set for consistency
|
|
153
|
+
switch (this.context.executionControl?.mode) {
|
|
154
|
+
case "explain":
|
|
155
|
+
this.context.executionControl.constraints = {
|
|
156
|
+
allowAnalysis: false,
|
|
157
|
+
allowPlanning: false,
|
|
158
|
+
allowFileWrites: false
|
|
159
|
+
};
|
|
160
|
+
break;
|
|
161
|
+
case "analyze":
|
|
162
|
+
this.context.executionControl.constraints = {
|
|
163
|
+
allowAnalysis: true,
|
|
164
|
+
allowPlanning: true,
|
|
165
|
+
allowFileWrites: false
|
|
166
|
+
};
|
|
167
|
+
break;
|
|
168
|
+
case "transform":
|
|
169
|
+
this.context.executionControl.constraints = {
|
|
170
|
+
allowAnalysis: true,
|
|
171
|
+
allowPlanning: true,
|
|
172
|
+
allowFileWrites: true
|
|
173
|
+
};
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
(_b = this.context.executionControl).constraints ?? (_b.constraints = {
|
|
177
|
+
allowAnalysis: true,
|
|
178
|
+
allowPlanning: true,
|
|
179
|
+
allowFileWrites: false
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// Task tracking
|
|
127
183
|
const db = getDbForRepo();
|
|
128
184
|
const now = new Date().toISOString();
|
|
129
185
|
const userQuery = this.context.initContext?.userQuery ?? "unknown query";
|
|
@@ -133,36 +189,48 @@ export class MainAgent {
|
|
|
133
189
|
`).run(userQuery, now, now);
|
|
134
190
|
this.taskId = result.lastInsertRowid;
|
|
135
191
|
logLine("TASK", `created task id=${this.taskId}"`);
|
|
136
|
-
logFolderCapsulesSummary(this.context);
|
|
137
192
|
}
|
|
193
|
+
/* ================= INFORMATION ACQUISITION ================= */
|
|
194
|
+
let t = startTimer();
|
|
195
|
+
await preFileSearchCheckStep(this.context);
|
|
196
|
+
logLine("PRECHECK", "preFileSearch", t());
|
|
197
|
+
// -------------------- EXPLAIN MODE HANDLING --------------------
|
|
198
|
+
if (this.canExecutePhase("explain")) {
|
|
199
|
+
const explainMod = resolveModuleForAction("explain");
|
|
200
|
+
if (!explainMod)
|
|
201
|
+
throw new Error("Explain module not found");
|
|
202
|
+
const explainOutput = await explainMod.run({
|
|
203
|
+
query: this.query,
|
|
204
|
+
context: this.context
|
|
205
|
+
});
|
|
206
|
+
logLine("MODE", "explain", undefined, "returning AI-generated explanation after preFileSearchCheck");
|
|
207
|
+
this.spinner.stop();
|
|
208
|
+
return explainOutput;
|
|
209
|
+
}
|
|
210
|
+
// -------------------- SUMMARIZE FOLDERCAPSULES FOR PLANNING --------------------
|
|
211
|
+
logFolderCapsulesSummary(this.context);
|
|
138
212
|
/* ================= FAST-PATH CHECK ================= */
|
|
139
213
|
{
|
|
140
214
|
await planResolverStep.run(this.context);
|
|
141
215
|
const routing = this.context.analysis?.routingDecision;
|
|
142
216
|
if (routing?.decision === "final-answer" && routing.answer) {
|
|
143
217
|
logLine("ROUTING", "fastPathHit", undefined, "returning final answer early");
|
|
144
|
-
return {
|
|
145
|
-
query: this.query,
|
|
146
|
-
data: { finalAnswer: routing.answer, source: "planResolver" }
|
|
147
|
-
};
|
|
218
|
+
return { query: this.query, data: { finalAnswer: routing.answer, source: "planResolver" } };
|
|
148
219
|
}
|
|
149
220
|
}
|
|
150
|
-
|
|
151
|
-
{
|
|
152
|
-
let t = startTimer();
|
|
153
|
-
await preFileSearchCheckStep(this.context);
|
|
154
|
-
logLine("PRECHECK", "preFileSearch", t());
|
|
221
|
+
// -------------------- PLANNING --------------------
|
|
222
|
+
if (this.canExecutePhase("planning")) {
|
|
155
223
|
t = startTimer();
|
|
156
224
|
await infoPlanGen.run(this.context);
|
|
157
225
|
logLine("PLAN", "infoPlanGen", t());
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
226
|
+
const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
227
|
+
let stepIO = { query: this.query };
|
|
228
|
+
for (const step of infoPlan.steps.filter(s => s.groups?.includes("info"))) {
|
|
229
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
230
|
+
}
|
|
163
231
|
}
|
|
164
232
|
/* ================= ANALYSIS PHASE ================= */
|
|
165
|
-
{
|
|
233
|
+
if (this.canExecutePhase("analysis")) {
|
|
166
234
|
let t = startTimer();
|
|
167
235
|
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
168
236
|
logLine("ANALYSIS", "selectRelevantSources", t());
|
|
@@ -192,70 +260,78 @@ export class MainAgent {
|
|
|
192
260
|
}
|
|
193
261
|
}
|
|
194
262
|
/* ================= TRANSFORM PHASE ================= */
|
|
195
|
-
{
|
|
196
|
-
|
|
263
|
+
if (this.canExecutePhase("transform")) {
|
|
264
|
+
let t = startTimer();
|
|
197
265
|
await transformPlanGenStep.run(this.context);
|
|
198
266
|
logLine("PLAN", "transformPlanGen", t());
|
|
267
|
+
const transformSteps = (this.context.analysis?.planSuggestion?.plan?.steps ?? [])
|
|
268
|
+
.filter(s => s.groups?.includes("transform"));
|
|
269
|
+
let stepIO = { query: this.query };
|
|
270
|
+
for (const step of transformSteps) {
|
|
271
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
272
|
+
}
|
|
273
|
+
/* ================= WRITE FILES ================= */
|
|
274
|
+
if (this.canExecutePhase("write")) {
|
|
275
|
+
const tWrite = startTimer();
|
|
276
|
+
stepIO = await writeFileStep.run({
|
|
277
|
+
query: this.query,
|
|
278
|
+
context: this.context,
|
|
279
|
+
data: stepIO.data
|
|
280
|
+
});
|
|
281
|
+
logLine("EXECUTE", "writeFile", tWrite());
|
|
282
|
+
}
|
|
199
283
|
}
|
|
200
|
-
const transformSteps = [
|
|
201
|
-
...(this.context.analysis?.planSuggestion?.plan?.steps ?? [])
|
|
202
|
-
].filter(s => s.groups?.includes("transform"));
|
|
203
|
-
for (const step of transformSteps) {
|
|
204
|
-
stepIO = await this.executeStep(step, stepIO);
|
|
205
|
-
}
|
|
206
|
-
/* ================= WRITE FILES (SIDE-EFFECT) ================= */
|
|
207
|
-
const t = startTimer();
|
|
208
|
-
stepIO = await writeFileStep.run({
|
|
209
|
-
query: this.query,
|
|
210
|
-
context: this.context,
|
|
211
|
-
data: stepIO.data
|
|
212
|
-
});
|
|
213
|
-
logLine("EXECUTE", "writeFile", t());
|
|
214
|
-
/* // ── OPTIONAL DEBUGGING ──
|
|
215
|
-
debugContext(this.context, {
|
|
216
|
-
step: "After writefile ",
|
|
217
|
-
note: "Does transform exist?"
|
|
218
|
-
}); */
|
|
219
284
|
/* ================= FINALIZE PHASE ================= */
|
|
220
285
|
{
|
|
221
286
|
const t = startTimer();
|
|
222
287
|
await finalPlanGenStep.run(this.context);
|
|
223
288
|
logLine("PLAN", "finalPlanGen", t());
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
289
|
+
const finalPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
290
|
+
let stepIO = { query: this.query };
|
|
291
|
+
for (const step of finalPlan.steps.filter(s => s.groups?.includes("finalize"))) {
|
|
292
|
+
stepIO = await this.executeStep(step, stepIO);
|
|
293
|
+
}
|
|
228
294
|
}
|
|
229
295
|
this.spinner.stop();
|
|
230
296
|
userOutput("All input/output logs can be found at ~/.scai/input_output.log");
|
|
231
297
|
logLine("RUN", "complete", stopRun());
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return stepIO;
|
|
298
|
+
/* console.log("\n[DEBUG] Final MainAgent context:");
|
|
299
|
+
console.dir(this.context, { depth: null, colors: true }); */
|
|
300
|
+
return { query: this.query, data: {} };
|
|
236
301
|
}
|
|
237
|
-
/* ───────────── helpers ───────────── */
|
|
238
302
|
resetInitContextForLoop() {
|
|
239
|
-
if (this.context.initContext)
|
|
303
|
+
if (this.context.initContext)
|
|
240
304
|
this.context.initContext.relatedFiles = [];
|
|
241
|
-
}
|
|
242
305
|
}
|
|
243
306
|
}
|
|
244
307
|
/* ───────────── FOLDER CAPSULES SUMMARY HELPER ───────────── */
|
|
245
|
-
function logFolderCapsulesSummary(context) {
|
|
246
|
-
|
|
308
|
+
export function logFolderCapsulesSummary(context) {
|
|
309
|
+
const capsules = context.initContext?.folderCapsules;
|
|
310
|
+
if (!capsules?.length)
|
|
247
311
|
return;
|
|
248
|
-
|
|
249
|
-
path: fc.path,
|
|
250
|
-
fileCount: fc.stats?.fileCount ?? 0,
|
|
251
|
-
depth: fc.depth ?? 0,
|
|
252
|
-
confidence: fc.confidence ?? 0,
|
|
253
|
-
roles: fc.roles ?? [],
|
|
254
|
-
concerns: fc.concerns ?? []
|
|
255
|
-
}));
|
|
312
|
+
// Ensure analysis exists
|
|
256
313
|
context.analysis ?? (context.analysis = {});
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
314
|
+
// Helper: truncate text to max length
|
|
315
|
+
const truncate = (text, max = 120) => text.length > max ? text.slice(0, max - 1) + "…" : text;
|
|
316
|
+
// Build human-readable summary (1–2 lines per folder)
|
|
317
|
+
const humanReadable = capsules.map(fc => {
|
|
318
|
+
const conf = Math.round((fc.confidence ?? 0) * 100) / 100; // round to 2 decimals
|
|
319
|
+
const header = `- ${fc.path} (${fc.stats?.fileCount ?? 0} files, conf ${conf})`;
|
|
320
|
+
// Line 2: summary + optional first key file reason
|
|
321
|
+
const summaryText = fc.summary ? truncate(fc.summary.trim()) : "";
|
|
322
|
+
const key = fc.keyFiles?.[0];
|
|
323
|
+
let body = "";
|
|
324
|
+
if (summaryText || key) {
|
|
325
|
+
body += " ";
|
|
326
|
+
if (summaryText)
|
|
327
|
+
body += summaryText;
|
|
328
|
+
if (key) {
|
|
329
|
+
const keyHint = `Key: ${key.path.split("/").pop()} — ${truncate(key.reason)}`;
|
|
330
|
+
body += summaryText ? ` [${keyHint}]` : keyHint;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return body ? `${header}\n${body}` : header;
|
|
334
|
+
}).join("\n\n"); // extra newline for readability
|
|
335
|
+
logInputOutput('FolderCapsule summarized', 'output', context.analysis.folderCapsulesHuman = humanReadable);
|
|
336
|
+
logLine("BOOT", "folderCapsulesSummary", undefined, `📂 ${capsules.length} folders summarized`);
|
|
261
337
|
}
|
|
@@ -3,19 +3,21 @@ import { generate } from "../lib/generate.js";
|
|
|
3
3
|
import { logInputOutput } from "../utils/promptLogHelper.js";
|
|
4
4
|
import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
|
|
5
5
|
export async function preFileSearchCheckStep(context) {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
var _a;
|
|
7
|
+
context.analysis ?? (context.analysis = {});
|
|
8
|
+
(_a = context.analysis).focus ?? (_a.focus = { relevantFiles: [], missingFiles: [], rationale: "" });
|
|
8
9
|
const intent = context.analysis.intent;
|
|
9
10
|
const planSuggestion = context.analysis.planSuggestion?.text ?? "";
|
|
10
11
|
// Step 1: gather known files from initContext only
|
|
11
12
|
const knownFiles = new Set(context.initContext?.relatedFiles ?? []);
|
|
12
13
|
// Step 2: extract file names from normalizedQuery or planSuggestion
|
|
13
14
|
const extractedFiles = extractFilesFromAnalysis(context.analysis);
|
|
14
|
-
// Step 3: populate focus
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
context.analysis.focus.
|
|
18
|
-
context.analysis.focus.
|
|
15
|
+
// Step 3: populate focus with safe defaults
|
|
16
|
+
const relevantFiles = extractedFiles.filter(f => knownFiles.has(f));
|
|
17
|
+
const missingFiles = extractedFiles.filter(f => !knownFiles.has(f));
|
|
18
|
+
context.analysis.focus.relevantFiles = relevantFiles;
|
|
19
|
+
context.analysis.focus.missingFiles = missingFiles;
|
|
20
|
+
context.analysis.focus.rationale = `Pre-check: ${relevantFiles.length} files already available, ${missingFiles.length} missing.`;
|
|
19
21
|
// Optional: LLM call for semantic understanding (assumptions, constraints, risks)
|
|
20
22
|
const prompt = `
|
|
21
23
|
You are an AI meta-agent assisting with context verification. The user intent and plan suggestion are below.
|
|
@@ -46,32 +48,45 @@ Task:
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
`.trim();
|
|
49
|
-
const ai = await generate({
|
|
50
|
-
query: context.initContext?.userQuery ?? '',
|
|
51
|
-
content: prompt
|
|
52
|
-
});
|
|
53
51
|
try {
|
|
54
|
-
const
|
|
52
|
+
const ai = await generate({
|
|
55
53
|
query: context.initContext?.userQuery ?? '',
|
|
56
|
-
content:
|
|
54
|
+
content: prompt
|
|
57
55
|
});
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
let cleaned;
|
|
57
|
+
try {
|
|
58
|
+
cleaned = await cleanupModule.run({
|
|
59
|
+
query: context.initContext?.userQuery ?? '',
|
|
60
|
+
content: ai.data,
|
|
61
|
+
});
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
catch (cleanupErr) {
|
|
64
|
+
console.warn("[preFileSearchCheckStep] cleanupModule failed, using raw AI output", cleanupErr);
|
|
65
|
+
cleaned = { data: ai.data, content: ai.data };
|
|
64
66
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
let parsed = {};
|
|
68
|
+
try {
|
|
69
|
+
if (typeof cleaned.data === "object" && cleaned.data !== null) {
|
|
70
|
+
parsed = cleaned.data;
|
|
71
|
+
}
|
|
72
|
+
else if (typeof cleaned.content === "string") {
|
|
73
|
+
parsed = JSON.parse(cleaned.content);
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
context.analysis.focus.
|
|
73
|
-
parsed.
|
|
74
|
-
|
|
76
|
+
catch (parseErr) {
|
|
77
|
+
console.warn("[preFileSearchCheckStep] Failed to parse cleanup output, using defaults", parseErr);
|
|
78
|
+
}
|
|
79
|
+
// Merge parsed output safely
|
|
80
|
+
context.analysis.focus.relevantFiles = Array.isArray(parsed.relevantFiles)
|
|
81
|
+
? parsed.relevantFiles
|
|
82
|
+
: context.analysis.focus.relevantFiles;
|
|
83
|
+
context.analysis.focus.missingFiles = Array.isArray(parsed.missingFiles)
|
|
84
|
+
? parsed.missingFiles
|
|
85
|
+
: context.analysis.focus.missingFiles;
|
|
86
|
+
context.analysis.focus.rationale = typeof parsed.rationale === "string"
|
|
87
|
+
? parsed.rationale
|
|
88
|
+
: context.analysis.focus.rationale;
|
|
89
|
+
if (parsed.understanding && typeof parsed.understanding === "object") {
|
|
75
90
|
context.analysis.understanding = {
|
|
76
91
|
...context.analysis.understanding,
|
|
77
92
|
...parsed.understanding,
|
|
@@ -80,9 +95,9 @@ Task:
|
|
|
80
95
|
logInputOutput("preFileSearchCheckStep", "output", parsed);
|
|
81
96
|
}
|
|
82
97
|
catch (err) {
|
|
83
|
-
console.warn("[preFileSearchCheckStep]
|
|
98
|
+
console.warn("[preFileSearchCheckStep] AI pre-check failed, using defaults", err);
|
|
84
99
|
}
|
|
85
|
-
// Simple regex-based extractor
|
|
100
|
+
// Simple regex-based extractor (always returns array)
|
|
86
101
|
function extractFilesFromAnalysis(analysis) {
|
|
87
102
|
const sources = [
|
|
88
103
|
analysis?.intent?.normalizedQuery ?? "",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const resolveExecutionModeStep = {
|
|
2
|
+
name: "resolveExecutionMode",
|
|
3
|
+
description: "Derive a hard execution mode from the interpreted user intent. " +
|
|
4
|
+
"This decision is authoritative and must not be changed later.",
|
|
5
|
+
run: async (context) => {
|
|
6
|
+
const intent = context.analysis?.intent;
|
|
7
|
+
if (!intent) {
|
|
8
|
+
throw new Error("resolveExecutionMode: missing analysis.intent");
|
|
9
|
+
}
|
|
10
|
+
let mode;
|
|
11
|
+
let rationale = "";
|
|
12
|
+
switch (intent.intentCategory) {
|
|
13
|
+
case "codingTask":
|
|
14
|
+
case "refactorTask":
|
|
15
|
+
mode = "transform";
|
|
16
|
+
rationale = "User intent implies code modification.";
|
|
17
|
+
break;
|
|
18
|
+
case "debugging":
|
|
19
|
+
case "planning":
|
|
20
|
+
mode = "analyze";
|
|
21
|
+
rationale = "User intent implies investigation or reasoning.";
|
|
22
|
+
break;
|
|
23
|
+
case "question":
|
|
24
|
+
mode = "analyze"; // <-- run analysis for factual/code questions
|
|
25
|
+
rationale = "User intent requests factual/code analysis.";
|
|
26
|
+
break;
|
|
27
|
+
case "explanation":
|
|
28
|
+
case "writing":
|
|
29
|
+
mode = "explain";
|
|
30
|
+
rationale = "User intent requests text explanation only.";
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
mode = "explain";
|
|
34
|
+
rationale = "Defaulted to explanation due to unclear intent.";
|
|
35
|
+
}
|
|
36
|
+
context.executionControl = {
|
|
37
|
+
mode,
|
|
38
|
+
rationale,
|
|
39
|
+
confidence: intent.confidence,
|
|
40
|
+
constraints: {
|
|
41
|
+
allowAnalysis: mode !== "explain",
|
|
42
|
+
allowPlanning: mode === "transform",
|
|
43
|
+
allowFileWrites: mode === "transform"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -118,18 +118,17 @@ function editInEditorAsync(initialContent = '') {
|
|
|
118
118
|
async function startShell() {
|
|
119
119
|
console.log(chalk.yellow("Welcome to SCAI shell!") + "\n" +
|
|
120
120
|
chalk.blueBright(`- Type your query directly for short commands or questions.
|
|
121
|
+
- Use !command to run terminal commands.
|
|
121
122
|
- Use /command for built-in or custom commands.
|
|
122
123
|
- /help
|
|
123
|
-
- /edit for pasting long queries
|
|
124
|
-
|
|
125
|
-
edit, and save your input before executing.
|
|
126
|
-
- Use !command to run terminal commands.`));
|
|
124
|
+
- /edit for pasting long queries & multi-line input
|
|
125
|
+
- /git commit\n`));
|
|
127
126
|
while (true) {
|
|
128
127
|
try {
|
|
129
128
|
const { line } = (await prompt({
|
|
130
129
|
type: 'input',
|
|
131
130
|
name: 'line',
|
|
132
|
-
message: '
|
|
131
|
+
message: 'Write your query:',
|
|
133
132
|
validate: (input) => !!input.trim() || 'Please enter something',
|
|
134
133
|
}));
|
|
135
134
|
const trimmed = line.trim();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// File: src/pipeline/modules/explainModule.ts
|
|
2
|
+
import { logInputOutput } from "../../utils/promptLogHelper.js";
|
|
3
|
+
import { generate } from "../../lib/generate.js";
|
|
4
|
+
export const explainModule = {
|
|
5
|
+
name: "explainModule",
|
|
6
|
+
description: "Generates a textual explanation for explain mode based on query and pre-file check context",
|
|
7
|
+
groups: ["finalize"],
|
|
8
|
+
run: async (input) => {
|
|
9
|
+
const query = input.query;
|
|
10
|
+
const context = input.context;
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error("[explainModule] No context provided");
|
|
13
|
+
}
|
|
14
|
+
// ---------------------------
|
|
15
|
+
// Gather preFileSearchCheck output
|
|
16
|
+
// ---------------------------
|
|
17
|
+
const focus = context.analysis?.focus ?? {};
|
|
18
|
+
const intent = context.analysis?.intent ?? {};
|
|
19
|
+
const rationale = context.analysis?.focus?.rationale ?? "No rationale available";
|
|
20
|
+
// ---------------------------
|
|
21
|
+
// Build prompt for the model
|
|
22
|
+
// ---------------------------
|
|
23
|
+
const prompt = `
|
|
24
|
+
You are an AI assistant tasked with explaining the requested work.
|
|
25
|
+
|
|
26
|
+
User query:
|
|
27
|
+
${query}
|
|
28
|
+
|
|
29
|
+
Intent (user goal):
|
|
30
|
+
${JSON.stringify(intent, null, 2)}
|
|
31
|
+
|
|
32
|
+
Relevant files:
|
|
33
|
+
${JSON.stringify(focus.relevantFiles ?? [], null, 2)}
|
|
34
|
+
|
|
35
|
+
Rationale for selected files:
|
|
36
|
+
${rationale}
|
|
37
|
+
|
|
38
|
+
INSTRUCTIONS:
|
|
39
|
+
- Provide a concise explanation answering the user query based primarily on the query itself.
|
|
40
|
+
- Use the intent and file rationale to support your explanation.
|
|
41
|
+
- Do NOT suggest code changes.
|
|
42
|
+
- Do NOT include unrelated folder summaries or analysis not yet performed.
|
|
43
|
+
- Keep answer clear, factual, and direct.
|
|
44
|
+
`.trim();
|
|
45
|
+
// ---------------------------
|
|
46
|
+
// Generate explanation
|
|
47
|
+
// ---------------------------
|
|
48
|
+
const aiResponse = await generate({
|
|
49
|
+
query,
|
|
50
|
+
content: prompt,
|
|
51
|
+
});
|
|
52
|
+
const explanationText = typeof aiResponse.data === "string" ? aiResponse.data : JSON.stringify(aiResponse.data, null, 2);
|
|
53
|
+
// ---------------------------
|
|
54
|
+
// Show explanation to user
|
|
55
|
+
// ---------------------------
|
|
56
|
+
console.log(`\n[EXPLAIN OUTPUT]\n${explanationText}\n`);
|
|
57
|
+
// ---------------------------
|
|
58
|
+
// Keep input/output logging
|
|
59
|
+
// ---------------------------
|
|
60
|
+
logInputOutput("explainModule", "output", aiResponse.data);
|
|
61
|
+
const output = {
|
|
62
|
+
query,
|
|
63
|
+
content: explanationText,
|
|
64
|
+
data: aiResponse.data,
|
|
65
|
+
};
|
|
66
|
+
return output;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -5,11 +5,13 @@ import { cleanupModule } from "../modules/cleanupModule.js";
|
|
|
5
5
|
import { summaryModule } from "../modules/summaryModule.js";
|
|
6
6
|
import { addCommentsModule } from "../modules/commentModule.js";
|
|
7
7
|
import { codeTransformModule } from "../modules/codeTransformModule.js";
|
|
8
|
+
import { explainModule } from "../modules/explainModule.js";
|
|
8
9
|
/**
|
|
9
10
|
* Active built-in modules — all use ModuleIO for input/output.
|
|
10
11
|
*/
|
|
11
12
|
export const builtInModules = {
|
|
12
13
|
fileSearch: fileSearchModule,
|
|
14
|
+
explain: explainModule,
|
|
13
15
|
summary: summaryModule,
|
|
14
16
|
contextReview: contextReviewModule,
|
|
15
17
|
// analysis (as agent options?)
|