scai 0.1.157 → 0.1.158

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.
@@ -5,16 +5,28 @@ import { generate } from "../../lib/generate.js";
5
5
  const MAX_CODE_CHARS = 6000; // conservative prompt-safe limit
6
6
  export const semanticAnalysisModule = {
7
7
  name: "semanticAnalysis",
8
- description: "Performs semantic analysis for each file provided by the plan steps, storing file-level and combined insights.",
8
+ description: "Performs semantic analysis for a single target file defined by the current execution step.",
9
9
  groups: ["analysis"],
10
10
  run: async (input) => {
11
11
  var _a, _b;
12
12
  const context = input.context;
13
13
  if (!context)
14
14
  throw new Error("[semanticAnalysisStep] No context provided");
15
+ const step = context.currentStep;
16
+ if (!step || step.action !== "semanticAnalysis") {
17
+ const notes = "[semanticAnalysisStep] Invoked without semanticAnalysis step";
18
+ logInputOutput("semanticAnalysisStep", "output", { notes });
19
+ return { query: input.query, data: { notes }, context };
20
+ }
21
+ if (!step.targetFile) {
22
+ const notes = "[semanticAnalysisStep] semanticAnalysis step missing targetFile";
23
+ logInputOutput("semanticAnalysisStep", "output", { notes });
24
+ return { query: input.query, data: { notes }, context };
25
+ }
15
26
  const workingFiles = context.workingFiles ?? [];
16
- if (!workingFiles.length) {
17
- const notes = "[semanticAnalysisStep] No working files loaded; skipping semantic analysis";
27
+ const file = workingFiles.find(f => f.path === step.targetFile);
28
+ if (!file) {
29
+ const notes = `[semanticAnalysisStep] targetFile not found: ${step.targetFile}`;
18
30
  logInputOutput("semanticAnalysisStep", "output", { notes });
19
31
  return { query: input.query, data: { notes }, context };
20
32
  }
@@ -22,56 +34,37 @@ export const semanticAnalysisModule = {
22
34
  (_a = context.analysis).fileAnalysis || (_a.fileAnalysis = {});
23
35
  (_b = context.analysis).combinedAnalysis || (_b.combinedAnalysis = {});
24
36
  const intentCategory = context.analysis.intent?.intentCategory ?? "other";
25
- // Determine which files to analyze based on plan steps
26
- const planSteps = context.analysis.planSuggestion?.plan?.steps ?? [];
27
- const filesToAnalyze = planSteps
28
- .filter(step => step.action === "semanticAnalysis" && step.targetFile)
29
- .map(step => step.targetFile)
30
- .map(path => workingFiles.find(f => f.path === path))
31
- .filter(Boolean);
32
- // ----------------------------
33
- // 1️⃣ Per-file semantic analysis
34
- // ----------------------------
35
- for (const file of filesToAnalyze) {
36
- const filePath = file.path;
37
- const prevAnalysis = context.analysis.fileAnalysis[filePath];
38
- // Use evidence relevance if available
39
- const isRelevant = prevAnalysis?.action?.isRelevant ?? false;
40
- if (!isRelevant)
41
- continue; // skip irrelevant files
42
- const shouldModify = prevAnalysis?.action?.shouldModify ?? determineShouldModify(intentCategory, isRelevant);
43
- // Analyze the file (enriching previous evidence)
44
- const semanticAnalysis = await analyzeFile(file, input.query, context, isRelevant, shouldModify);
45
- context.analysis.fileAnalysis[filePath] = {
46
- ...prevAnalysis, // keep evidence and prior info
47
- ...semanticAnalysis, // add semantic insights
48
- action: { isRelevant, shouldModify },
49
- };
50
- logInputOutput("semanticAnalysisStep - per-file", "output", {
51
- file: filePath,
52
- analysis: context.analysis.fileAnalysis[filePath],
53
- });
54
- }
55
- // ----------------------------
56
- // 2️⃣ Cross-file combined analysis (optional)
57
- // Only consider relevant files
58
- // ----------------------------
37
+ const filePath = file.path;
38
+ const prevAnalysis = context.analysis.fileAnalysis[filePath];
39
+ // Default: allow semantic analysis to determine relevance
40
+ const isRelevant = prevAnalysis?.action?.isRelevant ?? true;
41
+ const shouldModify = prevAnalysis?.action?.shouldModify ??
42
+ determineShouldModify(intentCategory, isRelevant);
43
+ const semanticAnalysis = await analyzeFile(file, input.query, context, isRelevant, shouldModify);
44
+ context.analysis.fileAnalysis[filePath] = {
45
+ ...prevAnalysis,
46
+ ...semanticAnalysis,
47
+ action: { isRelevant, shouldModify },
48
+ };
49
+ logInputOutput("semanticAnalysisStep - per-file", "output", {
50
+ file: filePath,
51
+ analysis: context.analysis.fileAnalysis[filePath],
52
+ });
53
+ // -------------------------------------------------
54
+ // ⚠️ Transitional: opportunistic combined analysis
55
+ // -------------------------------------------------
59
56
  const relevantFiles = Object.entries(context.analysis.fileAnalysis)
60
57
  .filter(([_, analysis]) => analysis.action?.isRelevant)
61
58
  .map(([path]) => path);
62
- let combinedAnalysis = {
63
- sharedPatterns: [],
64
- architectureSummary: "[skipped]",
65
- hotspots: [],
66
- };
67
59
  if (relevantFiles.length > 2) {
68
- const filteredAnalysis = {};
69
- for (const path of relevantFiles)
70
- filteredAnalysis[path] = context.analysis.fileAnalysis[path];
71
- combinedAnalysis = await analyzeCombined(filteredAnalysis, input.query);
60
+ const filtered = {};
61
+ for (const path of relevantFiles) {
62
+ filtered[path] = context.analysis.fileAnalysis[path];
63
+ }
64
+ const combinedAnalysis = await analyzeCombined(filtered, input.query);
65
+ context.analysis.combinedAnalysis = combinedAnalysis;
72
66
  logInputOutput("semanticAnalysisStep - combined", "output", combinedAnalysis);
73
67
  }
74
- context.analysis.combinedAnalysis = combinedAnalysis;
75
68
  return {
76
69
  query: input.query,
77
70
  data: { notes: "Semantic analysis completed" },
@@ -104,9 +97,9 @@ ${contextSnippet}
104
97
  File path: ${file.path}
105
98
 
106
99
  Task:
107
- - Analyze this file thoroughly based on the description in the plan.
108
- - Identify key functions, classes, data structures, and logical roles in the system.
109
- - Provide semantic insights that would help any future transformations or reasoning steps.
100
+ - Analyze this file thoroughly based on its role in the system.
101
+ - Identify key functions, classes, data structures, and responsibilities.
102
+ - Provide semantic insights useful for future reasoning or transformations.
110
103
 
111
104
  Code excerpt:
112
105
  ${slicedCode ?? "[no code]"}
@@ -141,15 +134,18 @@ Return STRICT JSON:
141
134
  data = {};
142
135
  }
143
136
  }
144
- const intent = data.intent === "relevant" || data.intent === "irrelevant" ? data.intent : "irrelevant";
137
+ const intent = data.intent === "relevant" || data.intent === "irrelevant"
138
+ ? data.intent
139
+ : "irrelevant";
145
140
  return {
146
141
  intent,
147
142
  relevance: typeof data.relevance === "string" && data.relevance.trim()
148
143
  ? data.relevance
149
144
  : intent === "relevant"
150
145
  ? "This file appears relevant to the query."
151
- : "This file does not appear relevant to answering the query.",
152
- role: intent === "relevant" && ["primary", "supporting", "contextual"].includes(data.role)
146
+ : "This file does not appear relevant to the query.",
147
+ role: intent === "relevant" &&
148
+ ["primary", "supporting", "contextual"].includes(data.role)
153
149
  ? data.role
154
150
  : undefined,
155
151
  action: { shouldModify },
@@ -157,8 +153,12 @@ Return STRICT JSON:
157
153
  ? {
158
154
  summary: String(data.proposedChanges.summary ?? ""),
159
155
  scope: data.proposedChanges.scope ?? "none",
160
- targets: Array.isArray(data.proposedChanges.targets) ? data.proposedChanges.targets : undefined,
161
- rationale: typeof data.proposedChanges.rationale === "string" ? data.proposedChanges.rationale : undefined,
156
+ targets: Array.isArray(data.proposedChanges.targets)
157
+ ? data.proposedChanges.targets
158
+ : undefined,
159
+ rationale: typeof data.proposedChanges.rationale === "string"
160
+ ? data.proposedChanges.rationale
161
+ : undefined,
162
162
  }
163
163
  : { summary: "No changes required for this file.", scope: "none" },
164
164
  risks: Array.isArray(data.risks) ? data.risks : [],
@@ -214,7 +214,9 @@ Return STRICT JSON:
214
214
  try {
215
215
  const ai = await generate({ query: "cross-file analysis", content: prompt });
216
216
  const cleaned = await cleanupModule.run({ query, content: ai.data });
217
- const data = typeof cleaned.data === "object" && cleaned.data ? cleaned.data : JSON.parse(String(cleaned.content ?? "{}"));
217
+ const data = typeof cleaned.data === "object" && cleaned.data
218
+ ? cleaned.data
219
+ : JSON.parse(String(cleaned.content ?? "{}"));
218
220
  return {
219
221
  sharedPatterns: data.sharedPatterns ?? [],
220
222
  architectureSummary: data.architectureSummary ?? "[unparsed]",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.157",
3
+ "version": "0.1.158",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"