scai 0.1.150 → 0.1.152

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.
@@ -3,31 +3,31 @@ import fs from "fs";
3
3
  import { generate } from "../lib/generate.js";
4
4
  import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
5
5
  import { logInputOutput } from "../utils/promptLogHelper.js";
6
- import { MODULE_STEP_INDENTATION } from "../constants.js";
7
6
  export const collectAnalysisEvidenceStep = {
8
7
  name: "collectAnalysisEvidence",
9
8
  description: "Evidence-first, deterministic analysis over candidate source files. Builds fileAnalysis only.",
10
9
  groups: ["analysis"],
11
10
  run: async (input) => {
12
- var _a, _b, _c;
11
+ var _a;
13
12
  const query = input.query ?? "";
14
13
  const context = input.context;
15
14
  if (!context?.analysis) {
16
15
  throw new Error("[collectAnalysisEvidence] context.analysis is required.");
17
16
  }
18
- (_a = context.analysis).fileAnalysis ?? (_a.fileAnalysis = {});
19
- (_b = context.analysis).requiresClarification ?? (_b.requiresClarification = false);
17
+ context.analysis.fileAnalysis = context.analysis.fileAnalysis ?? {};
20
18
  const intentCategory = context.analysis.intent?.intentCategory ?? "other";
21
19
  // ───────────── Guard ─────────────
22
20
  if (!["refactorTask", "codingTask", "writing"].includes(intentCategory)) {
23
21
  console.log(`[collectAnalysisEvidence] Skipping file scan because intentCategory=${intentCategory}`);
24
22
  return {
25
23
  query,
26
- data: { fileAnalysis: context.analysis.fileAnalysis },
24
+ data: {
25
+ fileAnalysis: context.analysis.fileAnalysis
26
+ }
27
27
  };
28
28
  }
29
29
  // --------------------------------------------------
30
- // Collect candidate paths
30
+ // Collect candidate paths (old selectRelevantSources behavior)
31
31
  // --------------------------------------------------
32
32
  const candidatePaths = [
33
33
  ...(context.analysis.focus?.relevantFiles ?? []),
@@ -41,20 +41,6 @@ export const collectAnalysisEvidenceStep = {
41
41
  }
42
42
  const filesWithEvidence = [];
43
43
  // --------------------------------------------------
44
- // Pre-extract query signals
45
- // --------------------------------------------------
46
- const quotedRegex = /['"`](.+?)['"`]/g;
47
- const quotedTargets = [];
48
- let m;
49
- while ((m = quotedRegex.exec(query)) !== null) {
50
- quotedTargets.push(m[1]);
51
- }
52
- const identifierRegex = /\b([A-Za-z_][A-Za-z0-9_]*)\b/g;
53
- const queryIdentifiers = new Set();
54
- while ((m = identifierRegex.exec(query)) !== null) {
55
- queryIdentifiers.add(m[1]);
56
- }
57
- // --------------------------------------------------
58
44
  // Deterministic evidence collection
59
45
  // --------------------------------------------------
60
46
  for (const path of uniquePaths) {
@@ -67,107 +53,75 @@ export const collectAnalysisEvidenceStep = {
67
53
  continue;
68
54
  }
69
55
  const lines = code.split("\n");
70
- const evidence = [];
71
- let literalHits = 0;
72
- let identifierHits = 0;
73
- let structuralHits = 0;
56
+ const evidenceItems = [];
57
+ const sentenceMatches = [];
58
+ // Heuristic: quoted strings in query
59
+ const sentenceRegex = /['"`](.+?)['"`]/g;
60
+ const targetSentences = [];
61
+ let match;
62
+ while ((match = sentenceRegex.exec(query)) !== null) {
63
+ targetSentences.push(match[1]);
64
+ }
65
+ // Line-by-line scan
74
66
  lines.forEach((line, index) => {
75
- const lineNo = index + 1;
76
- // ───── Literal evidence ─────
77
- for (const target of quotedTargets) {
67
+ for (const target of targetSentences) {
78
68
  if (line.includes(target)) {
79
- literalHits++;
80
- evidence.push({
81
- type: "literal",
82
- claim: `Exact quoted text found: "${target}"`,
69
+ evidenceItems.push({
70
+ claim: `Line contains the target sentence: "${target}"`,
83
71
  excerpt: line,
84
- span: { startLine: lineNo, endLine: lineNo },
72
+ span: { startLine: index + 1, endLine: index + 1 },
85
73
  confidence: 1,
86
74
  });
75
+ sentenceMatches.push(line);
87
76
  }
88
77
  }
89
- // ───── Identifier evidence ─────
90
- for (const ident of queryIdentifiers) {
91
- if (ident.length > 2 &&
92
- line.includes(ident) &&
93
- !quotedTargets.includes(ident)) {
94
- identifierHits++;
95
- evidence.push({
96
- type: "identifier",
97
- claim: `Identifier "${ident}" appears in file`,
98
- excerpt: line,
99
- span: { startLine: lineNo, endLine: lineNo },
100
- confidence: 0.6,
101
- });
102
- }
103
- }
104
- // ───── Anchored structural evidence ─────
105
- if (line.match(/^#{1,6}\s+/) || // markdown headers
106
- line.match(/^(export\s+)?(function|class)\s+/) ||
107
- line.match(/^module\.exports\b/)) {
108
- structuralHits++;
109
- evidence.push({
110
- type: "anchoredStructural",
111
- claim: "Top-level structural anchor detected",
112
- excerpt: line,
113
- span: { startLine: lineNo, endLine: lineNo },
114
- confidence: 0.4,
115
- });
116
- }
117
78
  });
118
- const isRelevant = evidence.length > 0;
79
+ const isRelevant = evidenceItems.length > 0;
119
80
  const shouldModify = determineShouldModify(intentCategory, isRelevant);
120
- const scope = determineScope(evidence.length, intentCategory, isRelevant);
121
- if (isRelevant)
81
+ const scope = determineScope(evidenceItems.length, intentCategory, isRelevant);
82
+ if (isRelevant) {
122
83
  filesWithEvidence.push(path);
123
- // ───────────── Clarification heuristic ─────────────
124
- const requiresClarification = literalHits === 0 &&
125
- identifierHits === 0 &&
126
- structuralHits > 0 &&
127
- ["refactorTask", "codingTask"].includes(intentCategory);
128
- if (requiresClarification) {
129
- context.analysis.requiresClarification = true;
130
84
  }
131
85
  const fileAnalysis = {
132
86
  intent: isRelevant ? "relevant" : "irrelevant",
133
87
  relevance: isRelevant
134
- ? `Evidence found (literal=${literalHits}, identifier=${identifierHits}, structural=${structuralHits}).`
135
- : "No evidence found in file.",
88
+ ? `${evidenceItems.length} candidate item(s) relevant to the query.`
89
+ : "No matching lines found in the file.",
136
90
  role: "primary",
137
91
  action: {
138
92
  isRelevant,
139
- shouldModify,
93
+ shouldModify
140
94
  },
141
95
  proposedChanges: {
142
96
  summary: isRelevant
143
- ? "File contains evidence relevant to the query."
97
+ ? "File contains elements that should be modified according to the query."
144
98
  : "No changes required.",
145
99
  scope,
146
- rationale: requiresClarification
147
- ? "Query is broad and only structural anchors were found. User clarification recommended."
100
+ targets: sentenceMatches,
101
+ rationale: isRelevant
102
+ ? "Detected sentence(s) in file matching the query."
148
103
  : undefined,
149
104
  },
150
105
  risks: [],
151
- evidence,
152
- requiresClarification,
106
+ evidence: evidenceItems,
153
107
  };
154
- // ───── Optional LLM rationale (non-authoritative) ─────
155
- if (isRelevant && !requiresClarification) {
108
+ // Optional LLM rationale (non-authoritative)
109
+ if (isRelevant) {
156
110
  try {
157
111
  const prompt = `
158
- Provide a concise rationale (1–2 sentences) explaining why this file is relevant.
112
+ Provide a concise rationale (1–2 sentences) explaining why the identified lines in this file are relevant to the user's query.
159
113
 
160
114
  Query:
161
115
  "${query}"
162
116
 
163
117
  File:
164
118
  ${path}
119
+
120
+ Lines:
121
+ ${sentenceMatches.join("\n")}
165
122
  `.trim();
166
123
  const response = await generate({ content: prompt, query });
167
- const cleaned = await cleanupModule.run({
168
- query,
169
- content: response.data,
170
- });
124
+ const cleaned = await cleanupModule.run({ query, content: response.data });
171
125
  if (typeof cleaned.data === "string") {
172
126
  fileAnalysis.proposedChanges.rationale = cleaned.data;
173
127
  }
@@ -176,25 +130,34 @@ ${path}
176
130
  console.warn(`[collectAnalysisEvidence] Rationale enrichment failed for ${path}`);
177
131
  }
178
132
  }
133
+ // Persist analysis only
179
134
  context.analysis.fileAnalysis[path] = {
180
135
  ...context.analysis.fileAnalysis[path],
181
136
  ...fileAnalysis,
182
137
  };
183
138
  }
184
- // ───────────── Focus update ─────────────
139
+ // ───────────── Conditional relevance update ─────────────
140
+ // Only update focus.relevantFiles if at least one file has evidence
185
141
  if (filesWithEvidence.length > 0) {
186
- (_c = context.analysis).focus ?? (_c.focus = { relevantFiles: [] });
142
+ (_a = context.analysis).focus ?? (_a.focus = { relevantFiles: [] });
187
143
  context.analysis.focus.relevantFiles = filesWithEvidence;
144
+ // Mark others as irrelevant
145
+ for (const path of uniquePaths) {
146
+ if (!filesWithEvidence.includes(path)) {
147
+ context.analysis.fileAnalysis[path] = {
148
+ ...context.analysis.fileAnalysis[path],
149
+ intent: "irrelevant",
150
+ action: { ...context.analysis.fileAnalysis[path]?.action, isRelevant: false }
151
+ };
152
+ }
153
+ }
188
154
  }
189
155
  const output = {
190
156
  query,
191
- data: { fileAnalysis: context.analysis.fileAnalysis },
157
+ data: {
158
+ fileAnalysis: context.analysis.fileAnalysis,
159
+ },
192
160
  };
193
- // ───────────── Clarification note (agent-visible) ─────────────
194
- if (context.analysis.requiresClarification) {
195
- console.warn(`${MODULE_STEP_INDENTATION}[collectAnalysisEvidence] Clarification required: query is underspecified. ` +
196
- `Only structural anchors were found; no literal or identifier evidence.`);
197
- }
198
161
  logInputOutput("collectAnalysisEvidence", "output", output.data);
199
162
  return output;
200
163
  },
@@ -208,9 +171,19 @@ function determineShouldModify(intentCategory, isRelevant) {
208
171
  function determineScope(evidenceCount, intentCategory, isRelevant) {
209
172
  if (!isRelevant)
210
173
  return "none";
174
+ let scope = "minor";
211
175
  if (evidenceCount <= 2)
212
- return "minor";
213
- if (evidenceCount <= 5)
214
- return "moderate";
215
- return "major";
176
+ scope = "minor";
177
+ else if (evidenceCount <= 5)
178
+ scope = "moderate";
179
+ else
180
+ scope = "major";
181
+ // Intent bump
182
+ if (["refactorTask", "codingTask"].includes(intentCategory)) {
183
+ if (scope === "minor" && evidenceCount > 1)
184
+ scope = "moderate";
185
+ if (scope === "moderate" && evidenceCount > 4)
186
+ scope = "major";
187
+ }
188
+ return scope;
216
189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.150",
3
+ "version": "0.1.152",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"