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.
@@ -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 t = startTimer();
135
+ const t1 = startTimer();
125
136
  await understandIntentStep.run({ context: this.context });
126
- logLine("BOOT", "understandIntent", t());
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
- /* ================= INFORMATION ACQUISITION ================= */
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
- const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
160
- let stepIO = { query: this.query };
161
- for (const step of infoPlan.steps.filter(s => s.groups?.includes("info"))) {
162
- stepIO = await this.executeStep(step, stepIO);
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
- const t = startTimer();
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
- const finalPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
226
- for (const step of finalPlan.steps.filter(s => s.groups?.includes("finalize"))) {
227
- stepIO = await this.executeStep(step, stepIO);
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
- // ───────────── LOG FINAL CONTEXT ─────────────
233
- console.log("\n[DEBUG] Final MainAgent context:");
234
- console.dir(this.context, { depth: null, colors: true });
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
- if (!context.initContext?.folderCapsules?.length)
308
+ export function logFolderCapsulesSummary(context) {
309
+ const capsules = context.initContext?.folderCapsules;
310
+ if (!capsules?.length)
247
311
  return;
248
- const capsulesSummary = context.initContext.folderCapsules.map(fc => ({
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
- context.analysis.folderCapsulesSummary = capsulesSummary;
258
- const humanReadable = capsulesSummary.map(fc => `- ${fc.path}: ${fc.fileCount} files, depth ${fc.depth}, confidence ${fc.confidence}`).join("\n");
259
- context.analysis.folderCapsulesHuman = humanReadable;
260
- logLine("BOOT", "folderCapsulesSummary", undefined, `📂 ${capsulesSummary.length} capsules summarized`);
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
- if (!context.analysis)
7
- context.analysis = {};
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
- context.analysis.focus = context.analysis.focus || { relevantFiles: [] };
16
- context.analysis.focus.relevantFiles = extractedFiles.filter(f => knownFiles.has(f));
17
- context.analysis.focus.missingFiles = extractedFiles.filter(f => !knownFiles.has(f));
18
- context.analysis.focus.rationale = `Pre-check: ${context.analysis.focus.relevantFiles.length} files already available, ${context.analysis.focus.missingFiles.length} missing.`;
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 cleaned = await cleanupModule.run({
52
+ const ai = await generate({
55
53
  query: context.initContext?.userQuery ?? '',
56
- content: ai.data,
54
+ content: prompt
57
55
  });
58
- let parsed;
59
- if (typeof cleaned.data === "object" && cleaned.data !== null) {
60
- parsed = cleaned.data;
56
+ let cleaned;
57
+ try {
58
+ cleaned = await cleanupModule.run({
59
+ query: context.initContext?.userQuery ?? '',
60
+ content: ai.data,
61
+ });
61
62
  }
62
- else if (typeof cleaned.content === "string") {
63
- parsed = JSON.parse(cleaned.content);
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
- else {
66
- throw new Error("Cleanup output is neither object nor string");
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
- context.analysis.focus.relevantFiles =
69
- parsed.relevantFiles ?? context.analysis.focus.relevantFiles;
70
- context.analysis.focus.missingFiles =
71
- parsed.missingFiles ?? context.analysis.focus.missingFiles;
72
- context.analysis.focus.rationale =
73
- parsed.rationale ?? context.analysis.focus.rationale;
74
- if (parsed.understanding) {
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] Failed to parse AI output, using defaults", err);
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, multi-line code, or anything complex,
124
- to open your configured editor (from $EDITOR) where you can freely paste,
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: '\nWrite your query:\n',
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?)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.140",
3
+ "version": "0.1.142",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"