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.
- package/dist/agents/MainAgent.js +255 -0
- package/dist/agents/contextReviewStep.js +104 -0
- package/dist/agents/finalPlanGenStep.js +123 -0
- package/dist/agents/infoPlanGenStep.js +126 -0
- package/dist/agents/planGeneratorStep.js +118 -0
- package/dist/agents/planResolverStep.js +95 -0
- package/dist/agents/planTargetFilesStep.js +48 -0
- package/dist/agents/preFileSearchCheckStep.js +95 -0
- package/dist/agents/selectRelevantSourcesStep.js +100 -0
- package/dist/agents/semanticAnalysisStep.js +144 -0
- package/dist/agents/structuralAnalysisStep.js +46 -0
- package/dist/agents/transformPlanGenStep.js +107 -0
- package/dist/agents/understandIntentStep.js +72 -0
- package/dist/agents/validationAnalysisStep.js +87 -0
- package/dist/commands/AskCmd.js +47 -116
- package/dist/commands/ChangeLogUpdateCmd.js +11 -5
- package/dist/commands/CommitSuggesterCmd.js +50 -75
- package/dist/commands/DaemonCmd.js +119 -29
- package/dist/commands/IndexCmd.js +41 -24
- package/dist/commands/InspectCmd.js +0 -1
- package/dist/commands/ReadlineSingleton.js +18 -0
- package/dist/commands/ResetDbCmd.js +20 -21
- package/dist/commands/ReviewCmd.js +89 -54
- package/dist/commands/SummaryCmd.js +12 -18
- package/dist/commands/WorkflowCmd.js +41 -0
- package/dist/commands/factory.js +254 -0
- package/dist/config.js +67 -15
- package/dist/constants.js +20 -4
- package/dist/context.js +10 -11
- package/dist/daemon/daemonQueues.js +63 -0
- package/dist/daemon/daemonWorker.js +40 -63
- package/dist/daemon/generateSummaries.js +58 -0
- package/dist/daemon/runFolderCapsuleBatch.js +247 -0
- package/dist/daemon/runIndexingBatch.js +147 -0
- package/dist/daemon/runKgBatch.js +104 -0
- package/dist/db/fileIndex.js +168 -63
- package/dist/db/functionExtractors/extractFromJava.js +210 -6
- package/dist/db/functionExtractors/extractFromJs.js +186 -198
- package/dist/db/functionExtractors/extractFromTs.js +181 -192
- package/dist/db/functionExtractors/index.js +7 -5
- package/dist/db/schema.js +55 -20
- package/dist/db/sqlTemplates.js +50 -19
- package/dist/fileRules/builtins.js +31 -0
- package/dist/fileRules/codeAllowedExtensions.js +4 -0
- package/dist/fileRules/fileExceptions.js +0 -13
- package/dist/fileRules/ignoredExtensions.js +10 -0
- package/dist/index.js +128 -325
- package/dist/lib/generate.js +37 -14
- package/dist/lib/generateFolderCapsules.js +109 -0
- package/dist/lib/spinner.js +12 -5
- package/dist/modelSetup.js +35 -6
- package/dist/pipeline/modules/changeLogModule.js +16 -19
- package/dist/pipeline/modules/chunkManagerModule.js +24 -0
- package/dist/pipeline/modules/cleanupModule.js +96 -91
- package/dist/pipeline/modules/codeTransformModule.js +208 -0
- package/dist/pipeline/modules/commentModule.js +20 -11
- package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
- package/dist/pipeline/modules/contextReviewModule.js +52 -0
- package/dist/pipeline/modules/fileReaderModule.js +72 -0
- package/dist/pipeline/modules/fileSearchModule.js +136 -0
- package/dist/pipeline/modules/finalAnswerModule.js +53 -0
- package/dist/pipeline/modules/gatherInfoModule.js +176 -0
- package/dist/pipeline/modules/generateTestsModule.js +63 -54
- package/dist/pipeline/modules/kgModule.js +26 -11
- package/dist/pipeline/modules/preserveCodeModule.js +91 -49
- package/dist/pipeline/modules/refactorModule.js +19 -7
- package/dist/pipeline/modules/repairTestsModule.js +44 -36
- package/dist/pipeline/modules/reviewModule.js +23 -13
- package/dist/pipeline/modules/summaryModule.js +27 -35
- package/dist/pipeline/modules/writeFileModule.js +86 -0
- package/dist/pipeline/registry/moduleRegistry.js +38 -93
- package/dist/pipeline/runModulePipeline.js +22 -19
- package/dist/scripts/dbcheck.js +156 -91
- package/dist/utils/buildContextualPrompt.js +245 -164
- package/dist/utils/debugContext.js +24 -0
- package/dist/utils/fileTree.js +16 -6
- package/dist/utils/loadRelevantFolderCapsules.js +64 -0
- package/dist/utils/log.js +2 -0
- package/dist/utils/normalizeData.js +23 -0
- package/dist/utils/planActions.js +60 -0
- package/dist/utils/promptBuilderHelper.js +67 -0
- package/dist/utils/promptLogHelper.js +52 -0
- package/dist/utils/sanitizeQuery.js +20 -8
- package/dist/utils/sharedUtils.js +8 -0
- package/dist/utils/sleep.js +3 -0
- package/dist/utils/splitCodeIntoChunk.js +65 -32
- package/dist/utils/vscode.js +49 -0
- package/dist/workflow/workflowResolver.js +14 -0
- package/dist/workflow/workflowRunner.js +103 -0
- package/package.json +6 -5
- package/dist/agent/agentManager.js +0 -39
- package/dist/agent/workflowManager.js +0 -95
- package/dist/commands/ModulePipelineCmd.js +0 -31
- package/dist/daemon/daemonBatch.js +0 -186
- package/dist/fileRules/scoreFiles.js +0 -71
- package/dist/lib/generateEmbedding.js +0 -22
package/dist/db/fileIndex.js
CHANGED
|
@@ -1,78 +1,183 @@
|
|
|
1
|
-
|
|
1
|
+
// indexCmd.ts
|
|
2
|
+
import fg from 'fast-glob';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
import
|
|
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 '
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
}
|