@jafreck/lore 0.2.3 → 0.2.5
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/README.md +22 -13
- package/dist/cli.js +130 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer/call-graph.d.ts +24 -7
- package/dist/indexer/call-graph.d.ts.map +1 -1
- package/dist/indexer/call-graph.js +154 -22
- package/dist/indexer/call-graph.js.map +1 -1
- package/dist/indexer/config-parser.js +6 -1
- package/dist/indexer/config-parser.js.map +1 -1
- package/dist/indexer/coverage.js +3 -1
- package/dist/indexer/coverage.js.map +1 -1
- package/dist/indexer/db.d.ts.map +1 -1
- package/dist/indexer/db.js +43 -0
- package/dist/indexer/db.js.map +1 -1
- package/dist/indexer/docs.d.ts.map +1 -1
- package/dist/indexer/docs.js +4 -0
- package/dist/indexer/docs.js.map +1 -1
- package/dist/indexer/embedder.d.ts.map +1 -1
- package/dist/indexer/embedder.js +5 -0
- package/dist/indexer/embedder.js.map +1 -1
- package/dist/indexer/extractors/bash.d.ts.map +1 -1
- package/dist/indexer/extractors/bash.js +29 -1
- package/dist/indexer/extractors/bash.js.map +1 -1
- package/dist/indexer/extractors/c.d.ts +5 -2
- package/dist/indexer/extractors/c.d.ts.map +1 -1
- package/dist/indexer/extractors/c.js +277 -8
- package/dist/indexer/extractors/c.js.map +1 -1
- package/dist/indexer/extractors/cpp.d.ts +5 -2
- package/dist/indexer/extractors/cpp.d.ts.map +1 -1
- package/dist/indexer/extractors/cpp.js +349 -3
- package/dist/indexer/extractors/cpp.js.map +1 -1
- package/dist/indexer/extractors/csharp.d.ts.map +1 -1
- package/dist/indexer/extractors/csharp.js +166 -1
- package/dist/indexer/extractors/csharp.js.map +1 -1
- package/dist/indexer/extractors/dart.d.ts.map +1 -1
- package/dist/indexer/extractors/dart.js +134 -1
- package/dist/indexer/extractors/dart.js.map +1 -1
- package/dist/indexer/extractors/elixir.d.ts.map +1 -1
- package/dist/indexer/extractors/elixir.js +45 -0
- package/dist/indexer/extractors/elixir.js.map +1 -1
- package/dist/indexer/extractors/elm.d.ts.map +1 -1
- package/dist/indexer/extractors/elm.js +33 -0
- package/dist/indexer/extractors/elm.js.map +1 -1
- package/dist/indexer/extractors/go.d.ts.map +1 -1
- package/dist/indexer/extractors/go.js +206 -1
- package/dist/indexer/extractors/go.js.map +1 -1
- package/dist/indexer/extractors/haskell.d.ts.map +1 -1
- package/dist/indexer/extractors/haskell.js +29 -0
- package/dist/indexer/extractors/haskell.js.map +1 -1
- package/dist/indexer/extractors/java.d.ts.map +1 -1
- package/dist/indexer/extractors/java.js +170 -1
- package/dist/indexer/extractors/java.js.map +1 -1
- package/dist/indexer/extractors/javascript.d.ts.map +1 -1
- package/dist/indexer/extractors/javascript.js +24 -2
- package/dist/indexer/extractors/javascript.js.map +1 -1
- package/dist/indexer/extractors/julia.d.ts.map +1 -1
- package/dist/indexer/extractors/julia.js +23 -1
- package/dist/indexer/extractors/julia.js.map +1 -1
- package/dist/indexer/extractors/kotlin.d.ts.map +1 -1
- package/dist/indexer/extractors/kotlin.js +122 -1
- package/dist/indexer/extractors/kotlin.js.map +1 -1
- package/dist/indexer/extractors/lua.d.ts.map +1 -1
- package/dist/indexer/extractors/lua.js +19 -1
- package/dist/indexer/extractors/lua.js.map +1 -1
- package/dist/indexer/extractors/objc.d.ts.map +1 -1
- package/dist/indexer/extractors/objc.js +171 -1
- package/dist/indexer/extractors/objc.js.map +1 -1
- package/dist/indexer/extractors/ocaml.d.ts.map +1 -1
- package/dist/indexer/extractors/ocaml.js +29 -0
- package/dist/indexer/extractors/ocaml.js.map +1 -1
- package/dist/indexer/extractors/php.d.ts.map +1 -1
- package/dist/indexer/extractors/php.js +133 -1
- package/dist/indexer/extractors/php.js.map +1 -1
- package/dist/indexer/extractors/python.d.ts.map +1 -1
- package/dist/indexer/extractors/python.js +24 -3
- package/dist/indexer/extractors/python.js.map +1 -1
- package/dist/indexer/extractors/ruby.d.ts.map +1 -1
- package/dist/indexer/extractors/ruby.js +23 -1
- package/dist/indexer/extractors/ruby.js.map +1 -1
- package/dist/indexer/extractors/rust.d.ts.map +1 -1
- package/dist/indexer/extractors/rust.js +139 -2
- package/dist/indexer/extractors/rust.js.map +1 -1
- package/dist/indexer/extractors/scala.d.ts.map +1 -1
- package/dist/indexer/extractors/scala.js +24 -1
- package/dist/indexer/extractors/scala.js.map +1 -1
- package/dist/indexer/extractors/swift.d.ts.map +1 -1
- package/dist/indexer/extractors/swift.js +129 -1
- package/dist/indexer/extractors/swift.js.map +1 -1
- package/dist/indexer/extractors/types.d.ts +78 -2
- package/dist/indexer/extractors/types.d.ts.map +1 -1
- package/dist/indexer/extractors/types.js +167 -8
- package/dist/indexer/extractors/types.js.map +1 -1
- package/dist/indexer/extractors/typescript.d.ts.map +1 -1
- package/dist/indexer/extractors/typescript.js +217 -1
- package/dist/indexer/extractors/typescript.js.map +1 -1
- package/dist/indexer/extractors/zig.d.ts.map +1 -1
- package/dist/indexer/extractors/zig.js +30 -0
- package/dist/indexer/extractors/zig.js.map +1 -1
- package/dist/indexer/git-history.d.ts.map +1 -1
- package/dist/indexer/git-history.js +4 -8
- package/dist/indexer/git-history.js.map +1 -1
- package/dist/indexer/git-hooks.js +1 -1
- package/dist/indexer/git-hooks.js.map +1 -1
- package/dist/indexer/index.d.ts +24 -1
- package/dist/indexer/index.d.ts.map +1 -1
- package/dist/indexer/index.js +245 -66
- package/dist/indexer/index.js.map +1 -1
- package/dist/indexer/lsp/client.d.ts.map +1 -1
- package/dist/indexer/lsp/client.js +40 -15
- package/dist/indexer/lsp/client.js.map +1 -1
- package/dist/indexer/lsp/config.d.ts.map +1 -1
- package/dist/indexer/lsp/config.js +18 -4
- package/dist/indexer/lsp/config.js.map +1 -1
- package/dist/indexer/poller.d.ts +8 -0
- package/dist/indexer/poller.d.ts.map +1 -1
- package/dist/indexer/poller.js +3 -1
- package/dist/indexer/poller.js.map +1 -1
- package/dist/indexer/resolver.d.ts.map +1 -1
- package/dist/indexer/resolver.js +8 -4
- package/dist/indexer/resolver.js.map +1 -1
- package/dist/indexer/watcher.d.ts +8 -0
- package/dist/indexer/watcher.d.ts.map +1 -1
- package/dist/indexer/watcher.js +18 -1
- package/dist/indexer/watcher.js.map +1 -1
- package/dist/logger.d.ts +104 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +170 -0
- package/dist/logger.js.map +1 -0
- package/dist/lore-server/db.d.ts.map +1 -1
- package/dist/lore-server/db.js +28 -24
- package/dist/lore-server/db.js.map +1 -1
- package/dist/lore-server/server.d.ts +3 -0
- package/dist/lore-server/server.d.ts.map +1 -1
- package/dist/lore-server/server.js +62 -102
- package/dist/lore-server/server.js.map +1 -1
- package/dist/lore-server/tools/graph.d.ts +4 -3
- package/dist/lore-server/tools/graph.d.ts.map +1 -1
- package/dist/lore-server/tools/graph.js +48 -10
- package/dist/lore-server/tools/graph.js.map +1 -1
- package/dist/lore-server/tools/notes.d.ts.map +1 -1
- package/dist/lore-server/tools/notes.js +6 -2
- package/dist/lore-server/tools/notes.js.map +1 -1
- package/dist/lore-server/tools/search.d.ts.map +1 -1
- package/dist/lore-server/tools/search.js +46 -14
- package/dist/lore-server/tools/search.js.map +1 -1
- package/package.json +2 -2
package/dist/indexer/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { inferSeededDocNoteKey, buildDocNoteScope } from './docs.js';
|
|
|
19
19
|
import { ingestGitHistory } from './git-history.js';
|
|
20
20
|
import { ParserPool } from './parser.js';
|
|
21
21
|
import { ImportResolver } from './resolver.js';
|
|
22
|
-
import {
|
|
22
|
+
import { resolveSymbolEdges, normalizeTypeName } from './call-graph.js';
|
|
23
23
|
import { isPublicDeclarationSurfaceSymbol, } from './extractors/types.js';
|
|
24
24
|
import { CExtractor } from './extractors/c.js';
|
|
25
25
|
import { RustExtractor } from './extractors/rust.js';
|
|
@@ -49,6 +49,7 @@ import { DEFAULT_EMBEDDING_MODEL, buildStructuralEmbeddingText } from './embedde
|
|
|
49
49
|
import { ingestCoverageReport } from './coverage.js';
|
|
50
50
|
import { refreshTestMappings } from './test-mapper.js';
|
|
51
51
|
import { LspEnrichmentCoordinator } from './lsp/enrichment.js';
|
|
52
|
+
import { getLogger } from '../logger.js';
|
|
52
53
|
// ─── Extractor registry ───────────────────────────────────────────────────────
|
|
53
54
|
const EXTRACTORS = {
|
|
54
55
|
c: new CExtractor(),
|
|
@@ -127,13 +128,17 @@ export class IndexBuilder {
|
|
|
127
128
|
* the database.
|
|
128
129
|
*/
|
|
129
130
|
async build() {
|
|
131
|
+
const log = getLogger();
|
|
132
|
+
const buildStart = performance.now();
|
|
130
133
|
const db = openDb(this.dbPath);
|
|
131
134
|
const branch = this.resolveBranch();
|
|
132
135
|
const lspCoordinator = this.createLspEnrichmentCoordinator();
|
|
136
|
+
log.indexing('build started', { dbPath: this.dbPath, branch, rootDir: this.walkerConfig.rootDir });
|
|
133
137
|
try {
|
|
134
138
|
this.saveDocsAutoNotesSetting(db);
|
|
135
139
|
const files = await walkFiles(this.walkerConfig);
|
|
136
140
|
const docs = await walkDocumentationFiles(this.walkerConfig);
|
|
141
|
+
log.indexing('walk complete', { fileCount: files.length, docCount: docs.length });
|
|
137
142
|
if (lspCoordinator) {
|
|
138
143
|
const languages = new Set(files.map((file) => file.language));
|
|
139
144
|
if (this.indexDependencies)
|
|
@@ -141,6 +146,9 @@ export class IndexBuilder {
|
|
|
141
146
|
await lspCoordinator.start(languages);
|
|
142
147
|
}
|
|
143
148
|
const resumeAt = this.loadBuildCheckpoint(db, branch, files.length);
|
|
149
|
+
if (resumeAt > 0) {
|
|
150
|
+
log.indexing('resuming from checkpoint', { resumeAt, totalFiles: files.length });
|
|
151
|
+
}
|
|
144
152
|
db.transaction(() => {
|
|
145
153
|
for (let i = resumeAt; i < files.length; i++) {
|
|
146
154
|
const file = files[i];
|
|
@@ -158,24 +166,64 @@ export class IndexBuilder {
|
|
|
158
166
|
this.removeStaleDocumentation(db, branch, seenDocPaths);
|
|
159
167
|
})();
|
|
160
168
|
this.saveBuildCheckpoint(db, branch, files.length, files.length);
|
|
169
|
+
log.indexing('files processed, resolving imports');
|
|
161
170
|
this.resolveImports(db, branch);
|
|
162
171
|
await this.indexDependencyDeclarations(db, lspCoordinator);
|
|
163
|
-
await this.
|
|
172
|
+
await this.enrichProjectRefs(db, branch, files, lspCoordinator);
|
|
164
173
|
refreshTestMappings(db, branch);
|
|
165
|
-
|
|
174
|
+
resolveSymbolEdges(db);
|
|
166
175
|
this.saveLastKnownHead(db);
|
|
167
176
|
if (this.embedder) {
|
|
177
|
+
log.indexing('embedding started', { model: this.embeddingModel });
|
|
168
178
|
await this.embedder.init();
|
|
169
179
|
await this.embedStructural(db);
|
|
170
180
|
await this.embedDocumentation(db);
|
|
181
|
+
log.indexing('embedding complete');
|
|
171
182
|
}
|
|
172
183
|
if (this.history) {
|
|
184
|
+
log.indexing('git history ingestion started');
|
|
173
185
|
const historyOptions = typeof this.history === 'object' ? this.history : undefined;
|
|
174
186
|
await ingestGitHistory(db, this.walkerConfig.rootDir, historyOptions);
|
|
175
187
|
if (this.embedder) {
|
|
176
188
|
await this.embedCommitMessages(db);
|
|
177
189
|
}
|
|
190
|
+
log.indexing('git history ingestion complete');
|
|
191
|
+
}
|
|
192
|
+
// Gather final DB stats for the build summary
|
|
193
|
+
let totalSymbols = 0;
|
|
194
|
+
try {
|
|
195
|
+
totalSymbols = db.prepare('SELECT COUNT(*) AS cnt FROM symbols').get().cnt;
|
|
196
|
+
}
|
|
197
|
+
catch { /* table may not exist */ }
|
|
198
|
+
let totalEdges = 0;
|
|
199
|
+
try {
|
|
200
|
+
totalEdges = db.prepare('SELECT COUNT(*) AS cnt FROM symbol_refs').get().cnt;
|
|
201
|
+
}
|
|
202
|
+
catch { /* table may not exist */ }
|
|
203
|
+
let totalDocs = 0;
|
|
204
|
+
try {
|
|
205
|
+
totalDocs = db.prepare('SELECT COUNT(*) AS cnt FROM docs').get().cnt;
|
|
178
206
|
}
|
|
207
|
+
catch { /* table may not exist */ }
|
|
208
|
+
let commitCount;
|
|
209
|
+
try {
|
|
210
|
+
commitCount = db.prepare('SELECT COUNT(*) AS cnt FROM commits').get().cnt;
|
|
211
|
+
}
|
|
212
|
+
catch { /* commits table may not exist */ }
|
|
213
|
+
const dbSizeBytes = fs.existsSync(this.dbPath) ? fs.statSync(this.dbPath).size : undefined;
|
|
214
|
+
const indexDurationMs = Math.round(performance.now() - buildStart);
|
|
215
|
+
log.startup('indexing complete', {
|
|
216
|
+
dbPath: this.dbPath,
|
|
217
|
+
dbSizeBytes,
|
|
218
|
+
embeddingModel: this.embeddingModel,
|
|
219
|
+
embeddingReady: !!this.embedder,
|
|
220
|
+
totalFiles: files.length,
|
|
221
|
+
totalSymbols,
|
|
222
|
+
totalDocs,
|
|
223
|
+
totalEdges,
|
|
224
|
+
commitCount,
|
|
225
|
+
indexDurationMs,
|
|
226
|
+
});
|
|
179
227
|
}
|
|
180
228
|
finally {
|
|
181
229
|
if (lspCoordinator) {
|
|
@@ -195,6 +243,12 @@ export class IndexBuilder {
|
|
|
195
243
|
const branch = this.resolveBranch();
|
|
196
244
|
const lspCoordinator = this.createLspEnrichmentCoordinator();
|
|
197
245
|
const enrichedFiles = [];
|
|
246
|
+
/** Symbol IDs whose embeddings should be removed (from deleted/re-processed files). */
|
|
247
|
+
const staleSymbolIds = [];
|
|
248
|
+
/** Paths of changed source files — used to look up new file IDs for scoped embedding. */
|
|
249
|
+
const changedSourcePaths = [];
|
|
250
|
+
/** Paths of changed doc files — used to look up new doc IDs for scoped embedding. */
|
|
251
|
+
const changedDocPaths = [];
|
|
198
252
|
try {
|
|
199
253
|
this.saveDocsAutoNotesSetting(db);
|
|
200
254
|
const docs = await walkDocumentationFiles(this.walkerConfig);
|
|
@@ -218,6 +272,10 @@ export class IndexBuilder {
|
|
|
218
272
|
if (!fs.existsSync(filePath)) {
|
|
219
273
|
const row = db.prepare('SELECT id FROM files WHERE path = ? AND branch = ?').get(filePath, branch);
|
|
220
274
|
if (row) {
|
|
275
|
+
// Collect symbol IDs for embedding cleanup before cascade-delete removes them.
|
|
276
|
+
const symRows = db.prepare('SELECT id FROM symbols WHERE file_id = ?').all(row.id);
|
|
277
|
+
for (const s of symRows)
|
|
278
|
+
staleSymbolIds.push(s.id);
|
|
221
279
|
// Null out any resolved_id references pointing to this file
|
|
222
280
|
db.prepare('UPDATE file_imports SET resolved_id = NULL WHERE resolved_id = ?').run(row.id);
|
|
223
281
|
db.prepare('DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_id = ?)').run(row.id);
|
|
@@ -229,10 +287,18 @@ export class IndexBuilder {
|
|
|
229
287
|
const language = detectLanguageForPath(filePath, this.walkerConfig);
|
|
230
288
|
if (language) {
|
|
231
289
|
enrichedFiles.push({ path: filePath, language });
|
|
290
|
+
changedSourcePaths.push(filePath);
|
|
232
291
|
// Null out resolved_id references pointing to this file before deletion
|
|
233
292
|
const existingRow = db.prepare('SELECT id FROM files WHERE path = ? AND branch = ?').get(filePath, branch);
|
|
234
293
|
if (existingRow) {
|
|
294
|
+
// Collect symbol IDs for embedding cleanup before cascade-delete removes them.
|
|
295
|
+
const symRows = db.prepare('SELECT id FROM symbols WHERE file_id = ?').all(existingRow.id);
|
|
296
|
+
for (const s of symRows)
|
|
297
|
+
staleSymbolIds.push(s.id);
|
|
235
298
|
db.prepare('UPDATE file_imports SET resolved_id = NULL WHERE resolved_id = ?').run(existingRow.id);
|
|
299
|
+
db.prepare('UPDATE symbol_refs SET callee_id = NULL WHERE callee_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(existingRow.id);
|
|
300
|
+
db.prepare('UPDATE type_refs SET type_id = NULL WHERE type_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(existingRow.id);
|
|
301
|
+
db.prepare('UPDATE symbol_relationships SET target_symbol_id = NULL WHERE target_symbol_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(existingRow.id);
|
|
236
302
|
db.prepare('DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_id = ?)').run(existingRow.id);
|
|
237
303
|
}
|
|
238
304
|
// Delete existing rows for this file (cascade handles symbols/imports)
|
|
@@ -243,6 +309,7 @@ export class IndexBuilder {
|
|
|
243
309
|
if (changedDoc) {
|
|
244
310
|
this.processDocumentationFile(db, changedDoc, branch);
|
|
245
311
|
this.upsertSeededDocumentationNote(db, changedDoc, branch);
|
|
312
|
+
changedDocPaths.push(filePath);
|
|
246
313
|
}
|
|
247
314
|
else {
|
|
248
315
|
this.deleteDocumentationByPath(db, filePath, branch);
|
|
@@ -251,7 +318,7 @@ export class IndexBuilder {
|
|
|
251
318
|
})();
|
|
252
319
|
this.resolveImports(db, branch);
|
|
253
320
|
await this.indexDependencyDeclarations(db, lspCoordinator);
|
|
254
|
-
await this.
|
|
321
|
+
await this.enrichProjectRefs(db, branch, enrichedFiles, lspCoordinator);
|
|
255
322
|
refreshTestMappings(db, branch);
|
|
256
323
|
if (this.history) {
|
|
257
324
|
const historyOptions = typeof this.history === 'object' ? this.history : undefined;
|
|
@@ -259,13 +326,29 @@ export class IndexBuilder {
|
|
|
259
326
|
}
|
|
260
327
|
if (this.embedder) {
|
|
261
328
|
await this.embedder.init();
|
|
262
|
-
|
|
263
|
-
|
|
329
|
+
// Clean up orphaned symbol embeddings for symbols that were deleted/replaced.
|
|
330
|
+
this.deleteSymbolEmbeddings(db, staleSymbolIds);
|
|
331
|
+
// Resolve the new file IDs for the changed source files.
|
|
332
|
+
const changedFileIds = [];
|
|
333
|
+
for (const p of changedSourcePaths) {
|
|
334
|
+
const row = db.prepare('SELECT id FROM files WHERE path = ? AND branch = ?').get(p, branch);
|
|
335
|
+
if (row)
|
|
336
|
+
changedFileIds.push(row.id);
|
|
337
|
+
}
|
|
338
|
+
// Resolve the new doc IDs for the changed documentation files.
|
|
339
|
+
const changedDocIds = [];
|
|
340
|
+
for (const p of changedDocPaths) {
|
|
341
|
+
const row = db.prepare('SELECT id FROM docs WHERE path = ? AND branch = ?').get(p, branch);
|
|
342
|
+
if (row)
|
|
343
|
+
changedDocIds.push(row.id);
|
|
344
|
+
}
|
|
345
|
+
await this.embedStructural(db, changedFileIds);
|
|
346
|
+
await this.embedDocumentation(db, changedDocIds);
|
|
264
347
|
if (this.history) {
|
|
265
348
|
await this.embedCommitMessages(db);
|
|
266
349
|
}
|
|
267
350
|
}
|
|
268
|
-
|
|
351
|
+
resolveSymbolEdges(db);
|
|
269
352
|
this.saveLastKnownHead(db);
|
|
270
353
|
}
|
|
271
354
|
finally {
|
|
@@ -342,10 +425,18 @@ export class IndexBuilder {
|
|
|
342
425
|
fileId = existing.id;
|
|
343
426
|
// Remove stale symbols / imports / external deps (also clean up FTS5 index)
|
|
344
427
|
db.prepare(`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_id = ?)`).run(fileId);
|
|
428
|
+
db.prepare('DELETE FROM symbol_relationships WHERE file_id = ?').run(fileId);
|
|
429
|
+
db.prepare('DELETE FROM type_refs WHERE file_id = ?').run(fileId);
|
|
430
|
+
// NULL out cross-file FK references that point to symbols in this file
|
|
431
|
+
db.prepare('UPDATE symbol_refs SET callee_id = NULL WHERE callee_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(fileId);
|
|
432
|
+
db.prepare('UPDATE type_refs SET type_id = NULL WHERE type_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(fileId);
|
|
433
|
+
db.prepare('UPDATE symbol_relationships SET target_symbol_id = NULL WHERE target_symbol_id IN (SELECT id FROM symbols WHERE file_id = ?)').run(fileId);
|
|
345
434
|
db.prepare('DELETE FROM symbols WHERE file_id = ?').run(fileId);
|
|
346
435
|
db.prepare('DELETE FROM file_imports WHERE file_id = ?').run(fileId);
|
|
347
436
|
db.prepare('DELETE FROM external_deps WHERE file_id = ?').run(fileId);
|
|
348
437
|
db.prepare('DELETE FROM api_routes WHERE file_id = ?').run(fileId);
|
|
438
|
+
// Delete stale annotations so cascade-independent re-index doesn't accumulate duplicates.
|
|
439
|
+
db.prepare('DELETE FROM annotations WHERE file_id = ?').run(fileId);
|
|
349
440
|
}
|
|
350
441
|
else {
|
|
351
442
|
const info = db
|
|
@@ -388,14 +479,28 @@ export class IndexBuilder {
|
|
|
388
479
|
insertImport.run(fileId, imp.source);
|
|
389
480
|
}
|
|
390
481
|
// Insert call refs (callee_id resolved in call-graph phase)
|
|
391
|
-
const insertCallRef = db.prepare(`INSERT INTO symbol_refs (caller_id, callee_name, call_line)
|
|
392
|
-
VALUES (?, ?, ?)`);
|
|
482
|
+
const insertCallRef = db.prepare(`INSERT INTO symbol_refs (caller_id, callee_name, call_line, call_character, call_kind)
|
|
483
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
393
484
|
for (const ref of result.callRefs) {
|
|
394
485
|
const callerId = symbolIdMap.get(ref.callerSymbol);
|
|
395
486
|
if (callerId !== undefined) {
|
|
396
|
-
insertCallRef.run(callerId, ref.calleeRaw, ref.line);
|
|
487
|
+
insertCallRef.run(callerId, ref.calleeRaw, ref.line, ref.character ?? null, ref.callKind ?? 'direct');
|
|
397
488
|
}
|
|
398
489
|
}
|
|
490
|
+
// Insert relationships (target_symbol_id resolved in resolveSymbolEdges phase)
|
|
491
|
+
const insertRelationship = db.prepare(`INSERT INTO symbol_relationships (file_id, source_symbol_id, target_symbol_name, relationship_type, line, character)
|
|
492
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
493
|
+
for (const rel of result.relationships) {
|
|
494
|
+
const sourceId = symbolIdMap.get(rel.fromSymbol) ?? null;
|
|
495
|
+
insertRelationship.run(fileId, sourceId, rel.toSymbol, rel.kind, rel.line, rel.character ?? null);
|
|
496
|
+
}
|
|
497
|
+
// Insert type refs (type_id resolved in resolveSymbolEdges phase)
|
|
498
|
+
const insertTypeRef = db.prepare(`INSERT INTO type_refs (file_id, symbol_id, type_name, type_name_bare, ref_kind, ref_line, ref_character)
|
|
499
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
500
|
+
for (const ref of result.typeRefs) {
|
|
501
|
+
const symId = symbolIdMap.get(ref.enclosingSymbol) ?? null;
|
|
502
|
+
insertTypeRef.run(fileId, symId, ref.typeRaw, normalizeTypeName(ref.typeRaw), ref.refKind, ref.line, ref.character ?? null);
|
|
503
|
+
}
|
|
399
504
|
}
|
|
400
505
|
processDocumentationFile(db, doc, branch) {
|
|
401
506
|
const existing = db.prepare('SELECT id, content_hash FROM docs WHERE path = ? AND branch = ?').get(doc.path, branch);
|
|
@@ -486,6 +591,18 @@ export class IndexBuilder {
|
|
|
486
591
|
return;
|
|
487
592
|
db.prepare(`DELETE FROM doc_section_embeddings WHERE rowid IN (${sectionIds.map(() => '?').join(', ')})`).run(...sectionIds);
|
|
488
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Remove orphaned rows from the `symbol_embeddings` vec0 table for symbols
|
|
596
|
+
* that have been deleted (e.g. file re-processed or removed).
|
|
597
|
+
*/
|
|
598
|
+
deleteSymbolEmbeddings(db, symbolIds) {
|
|
599
|
+
if (symbolIds.length === 0)
|
|
600
|
+
return;
|
|
601
|
+
const hasEmbeddingsTable = db.prepare("SELECT 1 AS present FROM sqlite_master WHERE type IN ('table', 'virtual table') AND name = 'symbol_embeddings'").get();
|
|
602
|
+
if (!hasEmbeddingsTable)
|
|
603
|
+
return;
|
|
604
|
+
db.prepare(`DELETE FROM symbol_embeddings WHERE rowid IN (${symbolIds.map(() => '?').join(', ')})`).run(...symbolIds);
|
|
605
|
+
}
|
|
489
606
|
/**
|
|
490
607
|
* Second pass: resolve raw_import strings to file IDs in the
|
|
491
608
|
* `file_imports.resolved_id` column. Also populates `external_deps` for
|
|
@@ -582,7 +699,7 @@ export class IndexBuilder {
|
|
|
582
699
|
}
|
|
583
700
|
return new LspEnrichmentCoordinator(this.lspSettings, this.walkerConfig.rootDir);
|
|
584
701
|
}
|
|
585
|
-
async
|
|
702
|
+
async enrichProjectRefs(db, branch, files, lspCoordinator) {
|
|
586
703
|
if (!lspCoordinator || files.length === 0)
|
|
587
704
|
return;
|
|
588
705
|
const selectSymbols = db.prepare(`SELECT s.id, s.name, s.signature, s.start_line
|
|
@@ -590,11 +707,21 @@ export class IndexBuilder {
|
|
|
590
707
|
JOIN files f ON f.id = s.file_id
|
|
591
708
|
WHERE f.path = ? AND f.branch = ?
|
|
592
709
|
ORDER BY s.id`);
|
|
593
|
-
const selectCallRefs = db.prepare(`SELECT sr.id, sr.call_line
|
|
710
|
+
const selectCallRefs = db.prepare(`SELECT sr.id, sr.call_line, sr.call_character
|
|
594
711
|
FROM symbol_refs sr
|
|
595
712
|
JOIN symbols s ON s.id = sr.caller_id
|
|
596
713
|
JOIN files f ON f.id = s.file_id
|
|
597
714
|
WHERE f.path = ? AND f.branch = ?
|
|
715
|
+
ORDER BY sr.id`);
|
|
716
|
+
const selectTypeRefs = db.prepare(`SELECT tr.id, tr.ref_line, tr.ref_character
|
|
717
|
+
FROM type_refs tr
|
|
718
|
+
JOIN files f ON f.id = tr.file_id
|
|
719
|
+
WHERE f.path = ? AND f.branch = ?
|
|
720
|
+
ORDER BY tr.id`);
|
|
721
|
+
const selectRelationships = db.prepare(`SELECT sr.id, sr.line, sr.character
|
|
722
|
+
FROM symbol_relationships sr
|
|
723
|
+
JOIN files f ON f.id = sr.file_id
|
|
724
|
+
WHERE f.path = ? AND f.branch = ? AND sr.line IS NOT NULL
|
|
598
725
|
ORDER BY sr.id`);
|
|
599
726
|
const updateSymbol = db.prepare(`UPDATE symbols
|
|
600
727
|
SET resolved_type_signature = ?, resolved_return_type = ?, definition_uri = ?, definition_path = ?
|
|
@@ -602,6 +729,12 @@ export class IndexBuilder {
|
|
|
602
729
|
const updateSymbolFts = db.prepare('UPDATE symbols_fts SET signature = ? WHERE rowid = ?');
|
|
603
730
|
const updateCallRef = db.prepare(`UPDATE symbol_refs
|
|
604
731
|
SET resolved_type_signature = ?, resolved_return_type = ?, definition_uri = ?, definition_path = ?
|
|
732
|
+
WHERE id = ?`);
|
|
733
|
+
const updateTypeRef = db.prepare(`UPDATE type_refs
|
|
734
|
+
SET resolved_type_signature = ?, definition_uri = ?, definition_path = ?
|
|
735
|
+
WHERE id = ?`);
|
|
736
|
+
const updateRelationship = db.prepare(`UPDATE symbol_relationships
|
|
737
|
+
SET definition_uri = ?, definition_path = ?
|
|
605
738
|
WHERE id = ?`);
|
|
606
739
|
for (const file of files) {
|
|
607
740
|
if (!file || !fs.existsSync(file.path))
|
|
@@ -613,46 +746,55 @@ export class IndexBuilder {
|
|
|
613
746
|
catch {
|
|
614
747
|
continue;
|
|
615
748
|
}
|
|
749
|
+
const tagged = [];
|
|
616
750
|
const symbols = selectSymbols.all(file.path, branch);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
filePath: file.path,
|
|
620
|
-
language: file.language,
|
|
621
|
-
source,
|
|
622
|
-
targets: symbols.map((symbol) => ({ line: symbol.start_line, character: 0 })),
|
|
623
|
-
});
|
|
624
|
-
for (let i = 0; i < symbols.length; i++) {
|
|
625
|
-
const symbol = symbols[i];
|
|
626
|
-
if (!symbol)
|
|
627
|
-
continue;
|
|
628
|
-
const metadata = symbolMetadata[i];
|
|
629
|
-
if (!metadata)
|
|
630
|
-
continue;
|
|
631
|
-
updateSymbol.run(metadata.resolvedTypeSignature, metadata.resolvedReturnType, metadata.definitionUri, metadata.definitionPath, symbol.id);
|
|
632
|
-
updateSymbolFts.run(buildStructuralEmbeddingText({
|
|
633
|
-
name: symbol.name,
|
|
634
|
-
signature: symbol.signature,
|
|
635
|
-
resolvedTypeSignature: metadata.resolvedTypeSignature,
|
|
636
|
-
resolvedReturnType: metadata.resolvedReturnType,
|
|
637
|
-
}), symbol.id);
|
|
638
|
-
}
|
|
751
|
+
for (const s of symbols) {
|
|
752
|
+
tagged.push({ table: 'symbol', rowId: s.id, line: s.start_line, character: 0, name: s.name, signature: s.signature });
|
|
639
753
|
}
|
|
640
754
|
const callRefs = selectCallRefs.all(file.path, branch);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
755
|
+
for (const cr of callRefs) {
|
|
756
|
+
tagged.push({ table: 'callRef', rowId: cr.id, line: cr.call_line, character: cr.call_character ?? 0 });
|
|
757
|
+
}
|
|
758
|
+
const typeRefs = selectTypeRefs.all(file.path, branch);
|
|
759
|
+
for (const tr of typeRefs) {
|
|
760
|
+
tagged.push({ table: 'typeRef', rowId: tr.id, line: tr.ref_line, character: tr.ref_character ?? 0 });
|
|
761
|
+
}
|
|
762
|
+
const relationships = selectRelationships.all(file.path, branch);
|
|
763
|
+
for (const r of relationships) {
|
|
764
|
+
tagged.push({ table: 'relationship', rowId: r.id, line: r.line, character: r.character ?? 0 });
|
|
765
|
+
}
|
|
766
|
+
if (tagged.length === 0)
|
|
767
|
+
continue;
|
|
768
|
+
const metadata = await lspCoordinator.enrich({
|
|
769
|
+
filePath: file.path,
|
|
770
|
+
language: file.language,
|
|
771
|
+
source,
|
|
772
|
+
targets: tagged.map(t => ({ line: t.line, character: t.character })),
|
|
773
|
+
});
|
|
774
|
+
for (let i = 0; i < tagged.length; i++) {
|
|
775
|
+
const tag = tagged[i];
|
|
776
|
+
const m = metadata[i];
|
|
777
|
+
if (!m)
|
|
778
|
+
continue;
|
|
779
|
+
switch (tag.table) {
|
|
780
|
+
case 'symbol':
|
|
781
|
+
updateSymbol.run(m.resolvedTypeSignature, m.resolvedReturnType, m.definitionUri, m.definitionPath, tag.rowId);
|
|
782
|
+
updateSymbolFts.run(buildStructuralEmbeddingText({
|
|
783
|
+
name: tag.name,
|
|
784
|
+
signature: tag.signature ?? null,
|
|
785
|
+
resolvedTypeSignature: m.resolvedTypeSignature,
|
|
786
|
+
resolvedReturnType: m.resolvedReturnType,
|
|
787
|
+
}), tag.rowId);
|
|
788
|
+
break;
|
|
789
|
+
case 'callRef':
|
|
790
|
+
updateCallRef.run(m.resolvedTypeSignature, m.resolvedReturnType, m.definitionUri, m.definitionPath, tag.rowId);
|
|
791
|
+
break;
|
|
792
|
+
case 'typeRef':
|
|
793
|
+
updateTypeRef.run(m.resolvedTypeSignature, m.definitionUri, m.definitionPath, tag.rowId);
|
|
794
|
+
break;
|
|
795
|
+
case 'relationship':
|
|
796
|
+
updateRelationship.run(m.definitionUri, m.definitionPath, tag.rowId);
|
|
797
|
+
break;
|
|
656
798
|
}
|
|
657
799
|
}
|
|
658
800
|
}
|
|
@@ -785,20 +927,31 @@ export class IndexBuilder {
|
|
|
785
927
|
*
|
|
786
928
|
* Also stores the embedding model name and dims in `lore_meta` and
|
|
787
929
|
* creates the vec0 tables if they don't exist yet.
|
|
930
|
+
*
|
|
931
|
+
* @param fileIds When provided, only embed symbols belonging to these file
|
|
932
|
+
* IDs (incremental mode). When omitted, embeds all symbols
|
|
933
|
+
* (full-build mode).
|
|
788
934
|
*/
|
|
789
|
-
async embedStructural(db) {
|
|
935
|
+
async embedStructural(db, fileIds) {
|
|
790
936
|
const embedder = this.embedder;
|
|
791
937
|
setLoreMeta(db, 'embedding_model', embedder.modelName);
|
|
792
938
|
setLoreMeta(db, 'embedding_dims', String(embedder.dims));
|
|
793
939
|
createVec0Tables(db, embedder.dims);
|
|
794
|
-
//
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
940
|
+
// Build the query — scoped to specific files when doing an incremental update.
|
|
941
|
+
const baseQuery = `SELECT id, name, signature, resolved_type_signature, resolved_return_type
|
|
942
|
+
FROM symbols
|
|
943
|
+
WHERE (signature IS NOT NULL
|
|
944
|
+
OR resolved_type_signature IS NOT NULL
|
|
945
|
+
OR resolved_return_type IS NOT NULL)`;
|
|
946
|
+
let symbols;
|
|
947
|
+
if (fileIds && fileIds.length > 0) {
|
|
948
|
+
symbols = db
|
|
949
|
+
.prepare(`${baseQuery} AND file_id IN (${fileIds.map(() => '?').join(', ')})`)
|
|
950
|
+
.all(...fileIds);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
symbols = db.prepare(baseQuery).all();
|
|
954
|
+
}
|
|
802
955
|
const insertEmbed = db.prepare('INSERT OR REPLACE INTO symbol_embeddings(rowid, embedding) VALUES (CAST(? AS INTEGER), json(?))');
|
|
803
956
|
for (let i = 0; i < symbols.length; i += EMBED_BATCH_SIZE) {
|
|
804
957
|
const batch = symbols.slice(i, i + EMBED_BATCH_SIZE);
|
|
@@ -818,16 +971,33 @@ export class IndexBuilder {
|
|
|
818
971
|
})();
|
|
819
972
|
}
|
|
820
973
|
}
|
|
821
|
-
|
|
974
|
+
/**
|
|
975
|
+
* Embed documentation sections in batches and persist results to
|
|
976
|
+
* the `doc_section_embeddings` vec0 virtual table.
|
|
977
|
+
*
|
|
978
|
+
* @param docIds When provided, only embed sections belonging to these
|
|
979
|
+
* doc IDs (incremental mode). When omitted, embeds all
|
|
980
|
+
* sections (full-build mode).
|
|
981
|
+
*/
|
|
982
|
+
async embedDocumentation(db, docIds) {
|
|
822
983
|
const embedder = this.embedder;
|
|
823
984
|
db.exec(`
|
|
824
985
|
CREATE VIRTUAL TABLE IF NOT EXISTS doc_section_embeddings USING vec0(
|
|
825
986
|
embedding FLOAT[${embedder.dims}]
|
|
826
987
|
);
|
|
827
988
|
`);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
989
|
+
let sections;
|
|
990
|
+
if (docIds && docIds.length > 0) {
|
|
991
|
+
sections = db.prepare(`SELECT id, title, content
|
|
992
|
+
FROM doc_sections
|
|
993
|
+
WHERE doc_id IN (${docIds.map(() => '?').join(', ')})
|
|
994
|
+
ORDER BY id`).all(...docIds);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
sections = db.prepare(`SELECT id, title, content
|
|
998
|
+
FROM doc_sections
|
|
999
|
+
ORDER BY id`).all();
|
|
1000
|
+
}
|
|
831
1001
|
if (sections.length === 0)
|
|
832
1002
|
return;
|
|
833
1003
|
const insertEmbed = db.prepare('INSERT OR REPLACE INTO doc_section_embeddings(rowid, embedding) VALUES (CAST(? AS INTEGER), json(?))');
|
|
@@ -845,12 +1015,21 @@ export class IndexBuilder {
|
|
|
845
1015
|
})();
|
|
846
1016
|
}
|
|
847
1017
|
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Embed commit messages that haven't been embedded yet.
|
|
1020
|
+
*
|
|
1021
|
+
* Uses a `LEFT JOIN` against `commit_embeddings` to skip commits whose
|
|
1022
|
+
* embeddings already exist, so only newly-ingested commits are processed.
|
|
1023
|
+
*/
|
|
848
1024
|
async embedCommitMessages(db) {
|
|
849
1025
|
const embedder = this.embedder;
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1026
|
+
// Only embed commits that don't already have an embedding row.
|
|
1027
|
+
const commits = db.prepare(`SELECT c.rowid, c.message
|
|
1028
|
+
FROM commits c
|
|
1029
|
+
LEFT JOIN commit_embeddings ce ON ce.rowid = c.rowid
|
|
1030
|
+
WHERE length(trim(c.message)) > 0
|
|
1031
|
+
AND ce.rowid IS NULL
|
|
1032
|
+
ORDER BY c.rowid`).all();
|
|
854
1033
|
if (commits.length === 0)
|
|
855
1034
|
return;
|
|
856
1035
|
const insertEmbed = db.prepare('INSERT OR REPLACE INTO commit_embeddings(rowid, embedding) VALUES (CAST(? AS INTEGER), json(?))');
|