scai 0.1.117 → 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 (95) 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 +173 -214
  39. package/dist/db/functionExtractors/extractFromTs.js +159 -160
  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 -14
  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 +0 -10
  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 +143 -228
  74. package/dist/utils/buildContextualPrompt.js +245 -172
  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/sleep.js +3 -0
  85. package/dist/utils/splitCodeIntoChunk.js +65 -32
  86. package/dist/utils/vscode.js +49 -0
  87. package/dist/workflow/workflowResolver.js +14 -0
  88. package/dist/workflow/workflowRunner.js +103 -0
  89. package/package.json +6 -5
  90. package/dist/agent/agentManager.js +0 -39
  91. package/dist/agent/workflowManager.js +0 -95
  92. package/dist/commands/ModulePipelineCmd.js +0 -31
  93. package/dist/daemon/daemonBatch.js +0 -186
  94. package/dist/fileRules/scoreFiles.js +0 -71
  95. package/dist/lib/generateEmbedding.js +0 -22
@@ -1,78 +1,183 @@
1
- import fs from 'fs';
1
+ // indexCmd.ts
2
+ import fg from 'fast-glob';
2
3
  import path from 'path';
3
- import { generateEmbedding } from '../lib/generateEmbedding.js';
4
+ import lockfile from 'proper-lockfile';
5
+ import { initSchema } from '../db/schema.js';
6
+ import { getDbForRepo, getDbPathForRepo } from '../db/client.js';
7
+ import { upsertFileTemplate } from '../db/sqlTemplates.js';
8
+ import { detectFileType } from '../fileRules/detectFileType.js';
9
+ import { classifyFile } from '../fileRules/classifyFile.js';
10
+ import { IGNORED_FOLDER_GLOBS } from '../fileRules/ignoredPaths.js';
11
+ import { Config } from '../config.js';
12
+ import { log } from '../utils/log.js';
13
+ import { startDaemon } from '../commands/DaemonCmd.js';
4
14
  import { sanitizeQueryForFts } from '../utils/sanitizeQuery.js';
5
- import * as sqlTemplates from './sqlTemplates.js';
6
- import { CANDIDATE_LIMIT } from '../constants.js';
7
- import { getDbForRepo } from './client.js';
8
- import { scoreFiles } from '../fileRules/scoreFiles.js';
9
- import chalk from 'chalk';
10
- export function indexFile(filePath, summary, type) {
11
- const stats = fs.statSync(filePath);
12
- const lastModified = stats.mtime.toISOString();
13
- const indexedAt = new Date().toISOString();
14
- const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
15
- const fileName = path.basename(normalizedPath);
16
- const db = getDbForRepo();
17
- db.prepare(sqlTemplates.upsertFileTemplate).run({
18
- path: normalizedPath,
19
- filename: fileName,
20
- summary,
21
- type,
22
- lastModified,
23
- indexedAt,
24
- embedding: null
25
- });
26
- db.prepare(sqlTemplates.upsertFileFtsTemplate).run({
27
- path: normalizedPath,
28
- filename: fileName,
29
- summary,
15
+ import * as sqlTemplates from '../db/sqlTemplates.js';
16
+ import { RELATED_FILES_LIMIT } from '../constants.js';
17
+ import { generate } from '../lib/generate.js';
18
+ import { cleanupModule } from '../pipeline/modules/cleanupModule.js';
19
+ async function lockDb() {
20
+ try {
21
+ return await lockfile.lock(getDbPathForRepo());
22
+ }
23
+ catch (err) {
24
+ log('❌ Failed to acquire DB lock: ' + err);
25
+ throw err;
26
+ }
27
+ }
28
+ export async function runIndexCommand() {
29
+ try {
30
+ initSchema();
31
+ }
32
+ catch (err) {
33
+ console.error('❌ Failed to initialize schema:', err);
34
+ process.exit(1);
35
+ }
36
+ const indexDir = Config.getIndexDir() || process.cwd();
37
+ Config.setIndexDir(indexDir);
38
+ log(`📂 Scanning files in: ${indexDir}`);
39
+ const files = await fg('**/*.*', {
40
+ cwd: indexDir,
41
+ ignore: IGNORED_FOLDER_GLOBS,
42
+ absolute: true,
30
43
  });
31
- console.log(`📄 Indexed: ${normalizedPath}`);
44
+ const db = getDbForRepo();
45
+ const release = await lockDb();
46
+ const countByExt = {};
47
+ let count = 0;
48
+ try {
49
+ for (const file of files) {
50
+ const classification = classifyFile(file);
51
+ if (classification !== 'valid') {
52
+ log(`⏭️ Skipping (${classification}): ${file}`);
53
+ continue;
54
+ }
55
+ try {
56
+ const type = detectFileType(file);
57
+ const normalizedPath = path.normalize(file).replace(/\\/g, '/');
58
+ const filename = path.basename(normalizedPath);
59
+ // --------------------------------------------------
60
+ // Enqueue file for daemon processing
61
+ // --------------------------------------------------
62
+ db.prepare(upsertFileTemplate).run({
63
+ path: normalizedPath,
64
+ filename,
65
+ summary: null,
66
+ type,
67
+ lastModified: null,
68
+ indexedAt: null,
69
+ });
70
+ const ext = path.extname(file);
71
+ countByExt[ext] = (countByExt[ext] || 0) + 1;
72
+ count++;
73
+ }
74
+ catch (err) {
75
+ log(`⚠️ Skipped in indexCmd ${file}: ${err instanceof Error ? err.message : err}`);
76
+ }
77
+ }
78
+ }
79
+ finally {
80
+ await release();
81
+ }
82
+ log('📊 Discovered files by extension:', JSON.stringify(countByExt, null, 2));
83
+ log(`✅ Done. Enqueued ${count} files for indexing.`);
84
+ // Kick the daemon — it now owns all processing
85
+ startDaemon();
32
86
  }
87
+ // --------------------------------------------------
88
+ // QUERY API (read-only, used by CLI / search)
89
+ // --------------------------------------------------
33
90
  export function queryFiles(safeQuery, limit = 10) {
34
- console.log(`Executing search query: ${safeQuery}`);
35
91
  const db = getDbForRepo();
36
- return db.prepare(sqlTemplates.queryFilesTemplate).all(safeQuery, limit);
92
+ return db
93
+ .prepare(sqlTemplates.queryFilesTemplate)
94
+ .all(safeQuery, limit);
37
95
  }
96
+ // --------------------------------------------------
97
+ // searchFiles with semantic fallback
98
+ // --------------------------------------------------
38
99
  export async function searchFiles(query, topK = 5) {
39
- console.log(chalk.yellow(`🧠 Searching for query: "${query}"`));
40
- const embedding = await generateEmbedding(query);
41
- if (!embedding) {
42
- console.log('⚠️ Failed to generate embedding for query');
100
+ const db = getDbForRepo();
101
+ // -----------------------------
102
+ // Primary FTS search
103
+ // -----------------------------
104
+ const safeQuery = sanitizeQueryForFts(query);
105
+ const primaryResults = db
106
+ .prepare(sqlTemplates.searchFilesTemplate)
107
+ .all(safeQuery, RELATED_FILES_LIMIT);
108
+ if (primaryResults.length > 0) {
109
+ return mapFtsResults(primaryResults, topK);
110
+ }
111
+ // -----------------------------
112
+ // Fallback: model-assisted expansion
113
+ // -----------------------------
114
+ const expandedTerms = await expandQueryWithModel(query);
115
+ if (expandedTerms.length === 0)
43
116
  return [];
117
+ log(`🔁 [searchFiles] Fallback used for "${query}". Expanded terms: ${expandedTerms.join(", ")}`);
118
+ const seen = new Map();
119
+ for (const term of expandedTerms) {
120
+ const safeTerm = sanitizeQueryForFts(term);
121
+ const rows = db
122
+ .prepare(sqlTemplates.searchFilesTemplate)
123
+ .all(safeTerm, RELATED_FILES_LIMIT);
124
+ for (const row of rows) {
125
+ if (!seen.has(row.id)) {
126
+ seen.set(row.id, row);
127
+ }
128
+ }
44
129
  }
45
- const safeQuery = sanitizeQueryForFts(query);
46
- console.log(`Executing search query in FTS5: ${safeQuery}`);
47
- const db = getDbForRepo();
48
- const ftsResults = db.prepare(sqlTemplates.searchFilesTemplate).all(safeQuery, CANDIDATE_LIMIT);
49
- console.log(`FTS search returned ${ftsResults.length} results`);
50
- if (ftsResults.length === 0)
130
+ if (seen.size === 0)
51
131
  return [];
52
- const scored = scoreFiles(query, embedding, ftsResults);
53
- return scored.slice(0, topK);
132
+ const merged = Array.from(seen.values()).sort((a, b) => a.bm25Score - b.bm25Score // lower = more relevant
133
+ );
134
+ return mapFtsResults(merged, topK);
54
135
  }
55
- export function getFunctionsForFiles(fileIds) {
56
- if (!fileIds.length)
57
- return {};
58
- const placeholders = fileIds.map(() => '?').join(',');
59
- const db = getDbForRepo();
60
- const stmt = db.prepare(`
61
- SELECT f.file_id, f.name, f.start_line, f.end_line, f.content
62
- FROM functions f
63
- WHERE f.file_id IN (${placeholders})
64
- `);
65
- const rows = stmt.all(...fileIds);
66
- const grouped = {};
67
- for (const row of rows) {
68
- if (!grouped[row.file_id])
69
- grouped[row.file_id] = [];
70
- grouped[row.file_id].push({
71
- name: row.name,
72
- start_line: row.start_line,
73
- end_line: row.end_line,
74
- content: row.content,
136
+ // --------------------------------------------------
137
+ // Helpers
138
+ // --------------------------------------------------
139
+ function mapFtsResults(rows, topK) {
140
+ return rows.slice(0, topK).map(r => ({
141
+ id: r.id,
142
+ path: r.path,
143
+ filename: r.filename,
144
+ summary: r.summary,
145
+ type: r.type,
146
+ lastModified: r.lastModified,
147
+ bm25Score: r.bm25Score,
148
+ }));
149
+ }
150
+ async function expandQueryWithModel(query) {
151
+ const prompt = `
152
+ You are assisting a code search system.
153
+
154
+ Given a natural-language question about a codebase, return a JSON array
155
+ of 3–8 concrete search terms that are likely to appear literally in source code.
156
+
157
+ Rules:
158
+ - Return ONLY a JSON array of strings
159
+ - No explanations
160
+ - Prefer filenames, function names, symbols, library names
161
+
162
+ Question:
163
+ "${query}"
164
+ `.trim();
165
+ try {
166
+ const response = await generate({
167
+ content: prompt,
168
+ query: "",
169
+ });
170
+ const cleaned = await cleanupModule.run({
171
+ query,
172
+ content: response.data,
75
173
  });
174
+ const terms = Array.isArray(cleaned.data)
175
+ ? cleaned.data.filter((t) => typeof t === "string")
176
+ : [];
177
+ return terms;
178
+ }
179
+ catch (err) {
180
+ log(`⚠️ [searchFiles] Failed to expand query "${query}": ${String(err)}`);
181
+ return [];
76
182
  }
77
- return grouped;
78
183
  }
@@ -1,9 +1,213 @@
1
+ import { parse } from 'java-parser';
2
+ import path from 'path';
3
+ import { log } from '../../utils/log.js';
1
4
  import { getDbForRepo } from '../client.js';
2
- import { markFileAsSkippedTemplate } from '../sqlTemplates.js';
3
- export async function extractFromJava(filePath, _content, fileId) {
4
- console.warn(`⛔️ Java extraction not implemented: ${filePath}`);
5
- // Mark the file as skipped with the relevant status update
5
+ import { markFileAsSkippedTemplate, markFileAsExtractedTemplate, markFileAsFailedTemplate, insertFunctionTemplate, insertGraphClassTemplate, insertEdgeTemplate, insertGraphEntityTagTemplate, insertGraphTagTemplate, selectGraphTagIdTemplate, } from '../sqlTemplates.js';
6
+ import { kgModule } from '../../pipeline/modules/kgModule.js';
7
+ import { BUILTINS } from '../../fileRules/builtins.js';
8
+ import { getUniqueId } from '../../utils/sharedUtils.js';
9
+ export async function extractFromJava(filePath, content, fileId) {
6
10
  const db = getDbForRepo();
7
- db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
8
- return false;
11
+ const normalizedPath = path.normalize(filePath).replace(/\\/g, '/');
12
+ try {
13
+ const cst = parse(content);
14
+ log(`🧩 First-level CST node types:`, cst.children ? Object.keys(cst.children) : []);
15
+ const functions = [];
16
+ const classes = [];
17
+ // --- Traverse CST to collect functions and classes ---
18
+ function traverse(node, parentClassName) {
19
+ if (!node)
20
+ return;
21
+ // Classes
22
+ if (node.node === 'normalClassDeclaration') {
23
+ const name = node.children?.Identifier?.[0]?.image ?? `${path.basename(filePath)}:<anon-class>`;
24
+ const superClass = node.children?.superclass?.[0]?.children?.Identifier?.[0]?.image ?? null;
25
+ const startLine = node.location?.startLine ?? 0;
26
+ const endLine = node.location?.endLine ?? 0;
27
+ const code = content.split('\n').slice(startLine - 1, endLine).join('\n');
28
+ const unique_id = getUniqueId(name, filePath, startLine, node.location?.startOffset ?? 0, code);
29
+ classes.push({ name, start_line: startLine, end_line: endLine, content: code, superClass, unique_id });
30
+ const classBody = node.children?.classBody?.[0]?.children;
31
+ if (classBody)
32
+ classBody.forEach((child) => traverse(child, name));
33
+ return;
34
+ }
35
+ // Methods/Constructors
36
+ if (node.node === 'methodDeclaration' || node.node === 'constructorDeclaration') {
37
+ const name = node.children?.Identifier?.[0]?.image ?? '<anon>';
38
+ const startLine = node.location?.startLine ?? 0;
39
+ const endLine = node.location?.endLine ?? 0;
40
+ const code = content.split('\n').slice(startLine - 1, endLine).join('\n');
41
+ const unique_id = getUniqueId(name, filePath, startLine, node.location?.startOffset ?? 0, code);
42
+ functions.push({ name, start_line: startLine, end_line: endLine, content: code, unique_id });
43
+ return;
44
+ }
45
+ // Traverse children
46
+ if (node.children) {
47
+ Object.values(node.children).forEach((child) => {
48
+ if (Array.isArray(child))
49
+ child.forEach(c => traverse(c, parentClassName));
50
+ else
51
+ traverse(child, parentClassName);
52
+ });
53
+ }
54
+ }
55
+ traverse(cst);
56
+ if (functions.length === 0 && classes.length === 0) {
57
+ log(`⚠️ No functions/classes found in Java file: ${filePath}`);
58
+ try {
59
+ db.prepare(markFileAsSkippedTemplate).run({ id: fileId });
60
+ }
61
+ catch { }
62
+ return false;
63
+ }
64
+ log(`🔍 Found ${functions.length} methods and ${classes.length} classes in ${filePath}`);
65
+ // --- Knowledge Graph tagging ---
66
+ try {
67
+ const kgInput = { fileId, filepath: filePath, summary: undefined };
68
+ const kgResult = await kgModule.run(kgInput, content);
69
+ if (kgResult.entities?.length) {
70
+ const insertTagStmt = db.prepare(insertGraphTagTemplate);
71
+ const getTagIdStmt = db.prepare(selectGraphTagIdTemplate);
72
+ const insertEntityTagStmt = db.prepare(insertGraphEntityTagTemplate);
73
+ const persistTag = (tag) => {
74
+ try {
75
+ insertTagStmt.run({ name: tag });
76
+ const tagRow = getTagIdStmt.get({ name: tag });
77
+ return tagRow?.id;
78
+ }
79
+ catch {
80
+ return undefined;
81
+ }
82
+ };
83
+ const persistEntityTags = (entity) => {
84
+ if (!entity.type || !Array.isArray(entity.tags) || !entity.tags.length)
85
+ return;
86
+ for (const tag of entity.tags) {
87
+ if (!tag)
88
+ continue;
89
+ const tagId = persistTag(tag);
90
+ if (!tagId)
91
+ continue;
92
+ const matchedUniqueId = functions.find(f => f.name === entity.name)?.unique_id ||
93
+ classes.find(c => c.name === entity.name)?.unique_id ||
94
+ `${entity.name}@${filePath}`;
95
+ try {
96
+ insertEntityTagStmt.run({ entity_type: entity.type, entity_unique_id: matchedUniqueId, tag_id: tagId });
97
+ }
98
+ catch { }
99
+ }
100
+ };
101
+ kgResult.entities.forEach(persistEntityTags);
102
+ }
103
+ }
104
+ catch (kgErr) {
105
+ console.warn(`⚠️ KG tagging failed for ${filePath}:`, kgErr instanceof Error ? kgErr.message : kgErr);
106
+ }
107
+ const seenEdges = new Set();
108
+ const javaBuiltins = BUILTINS.java;
109
+ // --- Insert functions with call edges ---
110
+ for (const fn of functions) {
111
+ db.prepare(insertFunctionTemplate).run({
112
+ file_id: fileId,
113
+ name: fn.name ?? '<anon>',
114
+ start_line: fn.start_line ?? -1,
115
+ end_line: fn.end_line ?? -1,
116
+ content: fn.content ?? '',
117
+ embedding: null,
118
+ lang: 'java',
119
+ unique_id: fn.unique_id,
120
+ });
121
+ const containsEdgeKey = `file->${fn.unique_id}`;
122
+ if (!seenEdges.has(containsEdgeKey)) {
123
+ db.prepare(insertEdgeTemplate).run({
124
+ source_type: 'file',
125
+ source_unique_id: normalizedPath,
126
+ target_type: 'function',
127
+ target_unique_id: fn.unique_id,
128
+ relation: 'contains',
129
+ });
130
+ seenEdges.add(containsEdgeKey);
131
+ }
132
+ // --- Function call edges ---
133
+ const callRegex = /([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
134
+ const callSet = new Set();
135
+ let match;
136
+ while ((match = callRegex.exec(fn.content)) !== null) {
137
+ const callee = match[1];
138
+ if (!callee || javaBuiltins.has(callee) || callSet.has(callee))
139
+ continue;
140
+ callSet.add(callee);
141
+ const targetUniqueId = `${callee}@${normalizedPath}`;
142
+ db.prepare(insertEdgeTemplate).run({
143
+ source_type: 'function',
144
+ source_unique_id: fn.unique_id,
145
+ target_type: 'function',
146
+ target_unique_id: targetUniqueId,
147
+ relation: 'calls',
148
+ });
149
+ }
150
+ }
151
+ // --- Insert classes with inheritance edges ---
152
+ for (const cls of classes) {
153
+ db.prepare(insertGraphClassTemplate).run({
154
+ file_id: fileId,
155
+ name: cls.name,
156
+ start_line: cls.start_line ?? -1,
157
+ end_line: cls.end_line ?? -1,
158
+ content: cls.content ?? '',
159
+ embedding: null,
160
+ lang: 'java',
161
+ unique_id: cls.unique_id,
162
+ });
163
+ const containsEdgeKey = `file->${cls.unique_id}`;
164
+ if (!seenEdges.has(containsEdgeKey)) {
165
+ db.prepare(insertEdgeTemplate).run({
166
+ source_type: 'file',
167
+ source_unique_id: normalizedPath,
168
+ target_type: 'class',
169
+ target_unique_id: cls.unique_id,
170
+ relation: 'contains',
171
+ });
172
+ seenEdges.add(containsEdgeKey);
173
+ }
174
+ if (cls.superClass) {
175
+ db.prepare(insertEdgeTemplate).run({
176
+ source_type: 'class',
177
+ source_unique_id: cls.unique_id,
178
+ target_type: 'class',
179
+ target_unique_id: `unresolved:${cls.superClass}`,
180
+ relation: 'inherits',
181
+ });
182
+ }
183
+ }
184
+ // --- Java imports as edges ---
185
+ const importRegex = /^\s*import\s+([A-Za-z0-9_.]+);/gm;
186
+ let impMatch;
187
+ while ((impMatch = importRegex.exec(content)) !== null) {
188
+ const importedClass = impMatch[1];
189
+ if (!importedClass)
190
+ continue;
191
+ const targetUniqueId = `imported:${importedClass}`;
192
+ db.prepare(insertEdgeTemplate).run({
193
+ source_type: 'file',
194
+ source_unique_id: normalizedPath,
195
+ target_type: 'class',
196
+ target_unique_id: targetUniqueId,
197
+ relation: 'imports',
198
+ });
199
+ }
200
+ db.prepare(markFileAsExtractedTemplate).run({ id: fileId });
201
+ log(`📊 Extraction summary for ${filePath}: ${functions.length} methods, ${classes.length} classes`);
202
+ return true;
203
+ }
204
+ catch (err) {
205
+ log(`❌ Failed to extract from Java file: ${filePath}`);
206
+ log(` ↳ ${err.message}`);
207
+ try {
208
+ db.prepare(markFileAsFailedTemplate).run({ id: fileId });
209
+ }
210
+ catch { }
211
+ return false;
212
+ }
9
213
  }