scai 0.1.116 → 0.1.118

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.
Files changed (96) hide show
  1. package/dist/agents/MainAgent.js +255 -0
  2. package/dist/agents/contextReviewStep.js +104 -0
  3. package/dist/agents/finalPlanGenStep.js +123 -0
  4. package/dist/agents/infoPlanGenStep.js +126 -0
  5. package/dist/agents/planGeneratorStep.js +118 -0
  6. package/dist/agents/planResolverStep.js +95 -0
  7. package/dist/agents/planTargetFilesStep.js +48 -0
  8. package/dist/agents/preFileSearchCheckStep.js +95 -0
  9. package/dist/agents/selectRelevantSourcesStep.js +100 -0
  10. package/dist/agents/semanticAnalysisStep.js +144 -0
  11. package/dist/agents/structuralAnalysisStep.js +46 -0
  12. package/dist/agents/transformPlanGenStep.js +107 -0
  13. package/dist/agents/understandIntentStep.js +72 -0
  14. package/dist/agents/validationAnalysisStep.js +87 -0
  15. package/dist/commands/AskCmd.js +47 -116
  16. package/dist/commands/ChangeLogUpdateCmd.js +11 -5
  17. package/dist/commands/CommitSuggesterCmd.js +50 -75
  18. package/dist/commands/DaemonCmd.js +119 -29
  19. package/dist/commands/IndexCmd.js +41 -24
  20. package/dist/commands/InspectCmd.js +0 -1
  21. package/dist/commands/ReadlineSingleton.js +18 -0
  22. package/dist/commands/ResetDbCmd.js +20 -21
  23. package/dist/commands/ReviewCmd.js +89 -54
  24. package/dist/commands/SummaryCmd.js +12 -18
  25. package/dist/commands/WorkflowCmd.js +41 -0
  26. package/dist/commands/factory.js +254 -0
  27. package/dist/config.js +67 -15
  28. package/dist/constants.js +20 -4
  29. package/dist/context.js +10 -11
  30. package/dist/daemon/daemonQueues.js +63 -0
  31. package/dist/daemon/daemonWorker.js +40 -63
  32. package/dist/daemon/generateSummaries.js +58 -0
  33. package/dist/daemon/runFolderCapsuleBatch.js +247 -0
  34. package/dist/daemon/runIndexingBatch.js +147 -0
  35. package/dist/daemon/runKgBatch.js +104 -0
  36. package/dist/db/fileIndex.js +168 -63
  37. package/dist/db/functionExtractors/extractFromJava.js +210 -6
  38. package/dist/db/functionExtractors/extractFromJs.js +186 -198
  39. package/dist/db/functionExtractors/extractFromTs.js +181 -192
  40. package/dist/db/functionExtractors/index.js +7 -5
  41. package/dist/db/schema.js +55 -20
  42. package/dist/db/sqlTemplates.js +50 -19
  43. package/dist/fileRules/builtins.js +31 -0
  44. package/dist/fileRules/codeAllowedExtensions.js +4 -0
  45. package/dist/fileRules/fileExceptions.js +0 -13
  46. package/dist/fileRules/ignoredExtensions.js +10 -0
  47. package/dist/index.js +128 -325
  48. package/dist/lib/generate.js +37 -14
  49. package/dist/lib/generateFolderCapsules.js +109 -0
  50. package/dist/lib/spinner.js +12 -5
  51. package/dist/modelSetup.js +35 -6
  52. package/dist/pipeline/modules/changeLogModule.js +16 -19
  53. package/dist/pipeline/modules/chunkManagerModule.js +24 -0
  54. package/dist/pipeline/modules/cleanupModule.js +96 -91
  55. package/dist/pipeline/modules/codeTransformModule.js +208 -0
  56. package/dist/pipeline/modules/commentModule.js +20 -11
  57. package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
  58. package/dist/pipeline/modules/contextReviewModule.js +52 -0
  59. package/dist/pipeline/modules/fileReaderModule.js +72 -0
  60. package/dist/pipeline/modules/fileSearchModule.js +136 -0
  61. package/dist/pipeline/modules/finalAnswerModule.js +53 -0
  62. package/dist/pipeline/modules/gatherInfoModule.js +176 -0
  63. package/dist/pipeline/modules/generateTestsModule.js +63 -54
  64. package/dist/pipeline/modules/kgModule.js +26 -11
  65. package/dist/pipeline/modules/preserveCodeModule.js +91 -49
  66. package/dist/pipeline/modules/refactorModule.js +19 -7
  67. package/dist/pipeline/modules/repairTestsModule.js +44 -36
  68. package/dist/pipeline/modules/reviewModule.js +23 -13
  69. package/dist/pipeline/modules/summaryModule.js +27 -35
  70. package/dist/pipeline/modules/writeFileModule.js +86 -0
  71. package/dist/pipeline/registry/moduleRegistry.js +38 -93
  72. package/dist/pipeline/runModulePipeline.js +22 -19
  73. package/dist/scripts/dbcheck.js +156 -91
  74. package/dist/utils/buildContextualPrompt.js +245 -164
  75. package/dist/utils/debugContext.js +24 -0
  76. package/dist/utils/fileTree.js +16 -6
  77. package/dist/utils/loadRelevantFolderCapsules.js +64 -0
  78. package/dist/utils/log.js +2 -0
  79. package/dist/utils/normalizeData.js +23 -0
  80. package/dist/utils/planActions.js +60 -0
  81. package/dist/utils/promptBuilderHelper.js +67 -0
  82. package/dist/utils/promptLogHelper.js +52 -0
  83. package/dist/utils/sanitizeQuery.js +20 -8
  84. package/dist/utils/sharedUtils.js +8 -0
  85. package/dist/utils/sleep.js +3 -0
  86. package/dist/utils/splitCodeIntoChunk.js +65 -32
  87. package/dist/utils/vscode.js +49 -0
  88. package/dist/workflow/workflowResolver.js +14 -0
  89. package/dist/workflow/workflowRunner.js +103 -0
  90. package/package.json +6 -5
  91. package/dist/agent/agentManager.js +0 -39
  92. package/dist/agent/workflowManager.js +0 -95
  93. package/dist/commands/ModulePipelineCmd.js +0 -31
  94. package/dist/daemon/daemonBatch.js +0 -186
  95. package/dist/fileRules/scoreFiles.js +0 -71
  96. package/dist/lib/generateEmbedding.js +0 -22
@@ -1,267 +1,255 @@
1
1
  import { parse } from 'acorn';
2
2
  import { ancestor as walkAncestor } from 'acorn-walk';
3
- import { generateEmbedding } from '../../lib/generateEmbedding.js';
4
3
  import path from 'path';
4
+ import chalk from 'chalk';
5
5
  import { log } from '../../utils/log.js';
6
6
  import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, insertFunctionTemplate, insertGraphClassTemplate, insertEdgeTemplate, insertGraphEntityTagTemplate, insertGraphTagTemplate, selectGraphTagIdTemplate, } from '../sqlTemplates.js';
7
7
  import { getDbForRepo } from '../client.js';
8
8
  import { kgModule } from '../../pipeline/modules/kgModule.js';
9
- function getFunctionName(node, parent, fileName) {
9
+ import { getUniqueId } from '../../utils/sharedUtils.js';
10
+ import { BUILTINS } from '../../fileRules/builtins.js';
11
+ /** Determine function name from AST node and its parent */
12
+ function getFunctionName(node, parent) {
10
13
  if (node.id?.name)
11
14
  return node.id.name;
12
15
  if (parent?.type === 'VariableDeclarator' && parent.id?.name)
13
- return parent.id.name;
16
+ return parent.id?.name;
14
17
  if (parent?.type === 'Property' && parent.key?.name)
15
- return parent.key.name;
18
+ return parent.key?.name;
16
19
  if (parent?.type === 'AssignmentExpression' && parent.left?.name)
17
- return parent.left.name;
20
+ return parent.left?.name;
18
21
  if (parent?.type === 'MethodDefinition' && parent.key?.name)
19
- return parent.key.name;
22
+ return parent.key?.name;
20
23
  return '<anon>';
21
24
  }
22
25
  export async function extractFromJS(filePath, content, fileId) {
23
26
  const db = getDbForRepo();
24
27
  const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
28
+ if (!fileId || typeof fileId !== 'number') {
29
+ console.error(`❌ extractFromJS: invalid or missing fileId for ${filePath}`);
30
+ return false;
31
+ }
32
+ if (!content || typeof content !== 'string' || !content.trim()) {
33
+ console.error(`❌ extractFromJS: empty or invalid file content for ${filePath}`);
34
+ db.prepare(markFileAsFailedTemplate).run({ id: fileId });
35
+ return false;
36
+ }
25
37
  try {
26
38
  const ast = parse(content, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
27
39
  const functions = [];
28
40
  const classes = [];
29
41
  const imports = [];
30
42
  const exports = [];
31
- // --- Traverse AST ---
43
+ // --- Function info ---
44
+ const handleFunctionNode = (node, ancestors) => {
45
+ const parent = ancestors[ancestors.length - 2];
46
+ const name = getFunctionName(node, parent);
47
+ const funcContent = content.slice(node.start, node.end);
48
+ const unique_id = getUniqueId(name, filePath, node.loc?.start.line ?? -1, node.start, funcContent);
49
+ functions.push({
50
+ name,
51
+ start_line: node.loc?.start.line ?? -1,
52
+ end_line: node.loc?.end.line ?? -1,
53
+ content: funcContent,
54
+ unique_id,
55
+ });
56
+ };
57
+ // --- Class info ---
58
+ const handleClassNode = (node) => {
59
+ const className = node.id?.name || `${path.basename(filePath)}:<anon-class>`;
60
+ const classContent = content.slice(node.start, node.end);
61
+ const unique_id = node.id?.name
62
+ ? `${className}@${normalizedPath}`
63
+ : getUniqueId(className, filePath, node.loc?.start.line ?? -1, node.start, classContent);
64
+ classes.push({
65
+ name: className,
66
+ start_line: node.loc?.start.line ?? -1,
67
+ end_line: node.loc?.end.line ?? -1,
68
+ content: classContent,
69
+ superClass: node.superClass?.name ?? null,
70
+ unique_id,
71
+ });
72
+ };
73
+ // --- AST traversal ---
32
74
  walkAncestor(ast, {
33
- ImportDeclaration(node) {
34
- if (node.source?.value)
35
- imports.push(node.source.value);
36
- },
37
- ExportNamedDeclaration(node) {
38
- if (node.source?.value)
39
- exports.push(node.source.value);
40
- },
41
- ExportAllDeclaration(node) {
42
- if (node.source?.value)
43
- exports.push(node.source.value);
44
- },
45
- FunctionDeclaration(node, ancestors) {
46
- const parent = ancestors[ancestors.length - 2];
47
- const name = getFunctionName(node, parent, path.basename(filePath));
48
- const uniqueId = name !== '<anon>'
49
- ? `${name}@${normalizedPath}`
50
- : `${path.basename(filePath)}:<anon>@${normalizedPath}:${node.loc?.start.line}`;
51
- functions.push({
52
- name,
53
- start_line: node.loc?.start.line ?? -1,
54
- end_line: node.loc?.end.line ?? -1,
55
- content: content.slice(node.start, node.end),
56
- uniqueId,
57
- });
58
- },
59
- FunctionExpression(node, ancestors) {
60
- const parent = ancestors[ancestors.length - 2];
61
- const name = getFunctionName(node, parent, path.basename(filePath));
62
- const uniqueId = name !== '<anon>'
63
- ? `${name}@${normalizedPath}`
64
- : `${path.basename(filePath)}:<anon>@${normalizedPath}:${node.loc?.start.line}`;
65
- functions.push({
66
- name,
67
- start_line: node.loc?.start.line ?? -1,
68
- end_line: node.loc?.end.line ?? -1,
69
- content: content.slice(node.start, node.end),
70
- uniqueId,
71
- });
72
- },
73
- ArrowFunctionExpression(node, ancestors) {
74
- const parent = ancestors[ancestors.length - 2];
75
- const name = getFunctionName(node, parent, path.basename(filePath));
76
- const uniqueId = name !== '<anon>'
77
- ? `${name}@${normalizedPath}`
78
- : `${path.basename(filePath)}:<anon>@${normalizedPath}:${node.loc?.start.line}`;
79
- functions.push({
80
- name,
81
- start_line: node.loc?.start.line ?? -1,
82
- end_line: node.loc?.end.line ?? -1,
83
- content: content.slice(node.start, node.end),
84
- uniqueId,
85
- });
86
- },
87
- ClassDeclaration(node) {
88
- const className = node.id?.name || '<anon-class>';
89
- const uniqueId = className !== '<anon-class>'
90
- ? `${className}@${normalizedPath}`
91
- : `${path.basename(filePath)}:<anon-class>@${normalizedPath}:${node.loc?.start.line}`;
92
- classes.push({
93
- name: className,
94
- start_line: node.loc?.start.line ?? -1,
95
- end_line: node.loc?.end.line ?? -1,
96
- content: content.slice(node.start, node.end),
97
- superClass: node.superClass?.name ?? null,
98
- uniqueId,
99
- });
100
- },
101
- ClassExpression(node) {
102
- const className = node.id?.name || '<anon-class>';
103
- const uniqueId = className !== '<anon-class>'
104
- ? `${className}@${normalizedPath}`
105
- : `${path.basename(filePath)}:<anon-class>@${normalizedPath}:${node.loc?.start.line}`;
106
- classes.push({
107
- name: className,
108
- start_line: node.loc?.start.line ?? -1,
109
- end_line: node.loc?.end.line ?? -1,
110
- content: content.slice(node.start, node.end),
111
- superClass: node.superClass?.name ?? null,
112
- uniqueId,
113
- });
114
- },
75
+ ImportDeclaration(node) { if (node.source?.value)
76
+ imports.push(node.source.value); },
77
+ ExportNamedDeclaration(node) { if (node.source?.value)
78
+ exports.push(node.source.value); },
79
+ ExportAllDeclaration(node) { if (node.source?.value)
80
+ exports.push(node.source.value); },
81
+ FunctionDeclaration: handleFunctionNode,
82
+ FunctionExpression: handleFunctionNode,
83
+ ArrowFunctionExpression: handleFunctionNode,
84
+ ClassDeclaration: handleClassNode,
85
+ ClassExpression: handleClassNode,
115
86
  });
116
87
  if (functions.length === 0 && classes.length === 0) {
117
- log(`⚠️ No functions/classes found in: ${filePath}`);
118
- db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
88
+ log(`⚠️ No functions/classes found in JS file: ${filePath}`);
89
+ try {
90
+ db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
91
+ }
92
+ catch { }
119
93
  return false;
120
94
  }
121
- log(`🔍 Found ${functions.length} functions and ${classes.length} classes in ${filePath}`);
122
95
  // --- KG tagging ---
123
96
  try {
124
97
  const kgInput = { fileId, filepath: filePath, summary: undefined };
125
98
  const kgResult = await kgModule.run(kgInput, content);
126
- if (kgResult.entities?.length > 0) {
127
- const insertTag = db.prepare(insertGraphTagTemplate);
128
- const getTagId = db.prepare(selectGraphTagIdTemplate);
129
- const insertEntityTag = db.prepare(insertGraphEntityTagTemplate);
130
- for (const entity of kgResult.entities) {
131
- if (!entity.type || !Array.isArray(entity.tags) || entity.tags.length === 0)
99
+ if (!kgResult.entities?.length) {
100
+ console.log(chalk.yellow(`⚠️ [KG] Garbage or empty result for ${filePath}`));
101
+ db.prepare(markFileAsFailedTemplate).run({ id: fileId });
102
+ return false;
103
+ }
104
+ const insertTagStmt = db.prepare(insertGraphTagTemplate);
105
+ const getTagIdStmt = db.prepare(selectGraphTagIdTemplate);
106
+ const insertEntityTagStmt = db.prepare(insertGraphEntityTagTemplate);
107
+ const persistTag = (tag) => {
108
+ try {
109
+ insertTagStmt.run({ name: tag });
110
+ return getTagIdStmt.get({ name: tag })?.id;
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ };
116
+ const persistEntityTags = (entity) => {
117
+ if (!entity.type || !Array.isArray(entity.tags) || !entity.tags.length)
118
+ return;
119
+ for (const tag of entity.tags) {
120
+ if (!tag || typeof tag !== 'string')
132
121
  continue;
133
- for (const tag of entity.tags) {
134
- if (!tag || typeof tag !== 'string')
135
- continue;
136
- try {
137
- insertTag.run({ name: tag });
138
- const tagRow = getTagId.get({ name: tag });
139
- if (!tagRow)
140
- continue;
141
- const matchedUniqueId = functions.find(f => f.name === entity.name)?.uniqueId ||
142
- classes.find(c => c.name === entity.name)?.uniqueId ||
143
- `${entity.name}@${filePath}`;
144
- insertEntityTag.run({
145
- entity_type: entity.type,
146
- entity_unique_id: matchedUniqueId,
147
- tag_id: tagRow.id,
148
- });
149
- }
150
- catch (err) {
151
- console.error('❌ Failed to persist entity/tag', { entity, tag, error: err });
152
- }
122
+ const tagId = persistTag(tag);
123
+ if (!tagId)
124
+ continue;
125
+ const matchedUniqueId = functions.find(f => f.name === entity.name)?.unique_id ||
126
+ classes.find(c => c.name === entity.name)?.unique_id ||
127
+ `${entity.name}@${filePath}`;
128
+ try {
129
+ insertEntityTagStmt.run({
130
+ entity_type: entity.type,
131
+ entity_unique_id: matchedUniqueId,
132
+ tag_id: tagId,
133
+ });
153
134
  }
135
+ catch { /* ignore */ }
154
136
  }
155
- log(`🏷 Persisted LLM-generated tags for ${filePath}`);
156
- }
137
+ };
138
+ kgResult.entities.forEach(persistEntityTags);
157
139
  }
158
140
  catch (kgErr) {
159
- console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
141
+ console.log(chalk.yellow(`⚠️ [KG] Garbage or failed parse for ${filePath}: ${kgErr instanceof Error ? kgErr.message : kgErr}`));
142
+ db.prepare(markFileAsFailedTemplate).run({ id: fileId });
143
+ return false;
160
144
  }
161
- // --- Insert functions + edges ---
145
+ const seenEdges = new Set();
146
+ const jsBuiltins = BUILTINS.ts;
147
+ // --- Insert functions ---
162
148
  for (const fn of functions) {
163
- try {
164
- const embedding = await generateEmbedding(fn.content);
165
- db.prepare(insertFunctionTemplate).run({
166
- file_id: fileId,
167
- name: fn.name,
168
- start_line: fn.start_line,
169
- end_line: fn.end_line,
170
- content: fn.content,
171
- embedding: JSON.stringify(embedding),
172
- lang: 'js',
173
- unique_id: fn.uniqueId,
174
- });
149
+ db.prepare(insertFunctionTemplate).run({
150
+ file_id: fileId,
151
+ name: fn.name,
152
+ start_line: fn.start_line,
153
+ end_line: fn.end_line,
154
+ content: fn.content,
155
+ embedding: null,
156
+ lang: 'js',
157
+ unique_id: fn.unique_id,
158
+ });
159
+ const edgeKey = `file->${fn.unique_id}`;
160
+ if (!seenEdges.has(edgeKey)) {
175
161
  db.prepare(insertEdgeTemplate).run({
176
162
  source_type: 'file',
177
163
  source_unique_id: normalizedPath,
178
164
  target_type: 'function',
179
- target_unique_id: fn.uniqueId,
165
+ target_unique_id: fn.unique_id,
180
166
  relation: 'contains',
181
167
  });
168
+ seenEdges.add(edgeKey);
169
+ }
170
+ // --- JS call edges ---
171
+ try {
182
172
  const fnAst = parse(fn.content, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
183
173
  walkAncestor(fnAst, {
184
174
  CallExpression(node) {
185
- const calleeName = node.callee?.name || 'unresolved';
175
+ let calleeName;
176
+ if (node.callee?.name)
177
+ calleeName = node.callee.name;
178
+ else if (node.callee?.property?.name)
179
+ calleeName = node.callee.property.name;
180
+ if (!calleeName || jsBuiltins.has(calleeName))
181
+ return;
186
182
  const targetUniqueId = `${calleeName}@${normalizedPath}`;
187
- db.prepare(insertEdgeTemplate).run({
188
- source_type: 'function',
189
- source_unique_id: fn.uniqueId,
190
- target_type: 'function',
191
- target_unique_id: targetUniqueId,
192
- relation: 'calls',
193
- });
183
+ const callEdgeKey = `${fn.unique_id}->${targetUniqueId}`;
184
+ if (!seenEdges.has(callEdgeKey)) {
185
+ try {
186
+ db.prepare(insertEdgeTemplate).run({
187
+ source_type: 'function',
188
+ source_unique_id: fn.unique_id,
189
+ target_type: 'function',
190
+ target_unique_id: targetUniqueId,
191
+ relation: 'calls',
192
+ });
193
+ seenEdges.add(callEdgeKey);
194
+ }
195
+ catch { /* ignore */ }
196
+ }
194
197
  },
195
198
  });
196
- log(`📌 Indexed JS function: ${fn.name}`);
197
199
  }
198
- catch (err) {
199
- console.error('❌ Failed to insert function or call edges', { fn, error: err });
200
+ catch (parseErr) {
201
+ console.log(chalk.yellow(`⚠️ [Extract] Failed to parse function ${fn.name} in ${filePath}: ${parseErr.message}`));
200
202
  }
201
203
  }
202
- // --- Insert classes + edges ---
204
+ // --- Insert classes ---
203
205
  for (const cls of classes) {
204
- try {
205
- const embedding = await generateEmbedding(cls.content);
206
- db.prepare(insertGraphClassTemplate).run({
207
- file_id: fileId,
208
- name: cls.name,
209
- start_line: cls.start_line,
210
- end_line: cls.end_line,
211
- content: cls.content,
212
- embedding: JSON.stringify(embedding),
213
- lang: 'js',
214
- unique_id: cls.uniqueId,
215
- });
216
- db.prepare(insertEdgeTemplate).run({
217
- source_type: 'file',
218
- source_unique_id: normalizedPath,
219
- target_type: 'class',
220
- target_unique_id: cls.uniqueId,
221
- relation: 'contains',
222
- });
223
- if (cls.superClass) {
224
- db.prepare(insertEdgeTemplate).run({
225
- source_type: 'class',
226
- source_unique_id: cls.uniqueId,
227
- target_type: `unresolved:${cls.superClass}`,
228
- relation: 'inherits',
229
- });
230
- log(`🔗 Class ${cls.name} extends ${cls.superClass}`);
231
- }
232
- log(`🏷 Indexed JS class: ${cls.name}`);
233
- }
234
- catch (err) {
235
- console.error('❌ Failed to insert class or edges', { cls, error: err });
236
- }
237
- }
238
- // --- Imports / Exports edges ---
239
- for (const imp of imports) {
240
- db.prepare(insertEdgeTemplate).run({
241
- source_type: 'file',
242
- source_unique_id: normalizedPath,
243
- target_type: 'file',
244
- target_unique_id: `file@${imp}`,
245
- relation: 'imports',
206
+ db.prepare(insertGraphClassTemplate).run({
207
+ file_id: fileId,
208
+ name: cls.name,
209
+ start_line: cls.start_line,
210
+ end_line: cls.end_line,
211
+ content: cls.content,
212
+ embedding: null,
213
+ lang: 'js',
214
+ unique_id: cls.unique_id,
246
215
  });
247
- }
248
- for (const exp of exports) {
249
216
  db.prepare(insertEdgeTemplate).run({
250
217
  source_type: 'file',
251
218
  source_unique_id: normalizedPath,
252
- target_type: 'file',
253
- target_unique_id: `file@${exp}`,
254
- relation: 'exports',
219
+ target_type: 'class',
220
+ target_unique_id: cls.unique_id,
221
+ relation: 'contains',
255
222
  });
223
+ if (cls.superClass) {
224
+ db.prepare(insertEdgeTemplate).run({
225
+ source_type: 'class',
226
+ source_unique_id: cls.unique_id,
227
+ target_type: `unresolved:${cls.superClass}`,
228
+ relation: 'inherits',
229
+ });
230
+ }
256
231
  }
257
- log(`📊 Extraction summary for ${filePath}: ${functions.length} functions, ${classes.length} classes, ${imports.length} imports, ${exports.length} exports`);
232
+ // --- Imports/Exports edges ---
233
+ const handleEdges = (items, relation) => {
234
+ for (const item of items) {
235
+ const resolved = item.startsWith('.') ? path.resolve(path.dirname(filePath), item) : item;
236
+ const normalizedTarget = path.normalize(resolved).replace(/\\/g, '/');
237
+ db.prepare(insertEdgeTemplate).run({
238
+ source_type: 'file',
239
+ source_unique_id: normalizedPath,
240
+ target_type: 'file',
241
+ target_unique_id: normalizedTarget,
242
+ relation,
243
+ });
244
+ }
245
+ };
246
+ handleEdges(imports, 'imports');
247
+ handleEdges(exports, 'exports');
258
248
  db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
259
- log(`✅ Marked JS functions/classes as extracted for ${filePath}`);
260
249
  return true;
261
250
  }
262
251
  catch (err) {
263
- log(`❌ Failed to extract from: ${filePath}`);
264
- log(` ↳ ${err.message}`);
252
+ console.log(chalk.yellow(`⚠️ [Extract] Failed for ${filePath}: ${err.message}`));
265
253
  db.prepare(markFileAsFailedTemplate).run({ id: fileId });
266
254
  return false;
267
255
  }