memorix 1.8.6 → 1.8.7
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/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/cli/index.js +2208 -1160
- package/dist/dashboard/static/app.js +51 -3
- package/dist/dashboard/static/style.css +88 -0
- package/dist/index.js +1726 -736
- package/dist/maintenance-runner.js +855 -218
- package/dist/media-audio-runner.js +856 -219
- package/dist/media-video-runner.js +854 -217
- package/dist/memcode-runtime/CHANGELOG.md +33 -0
- package/dist/sdk.js +1716 -726
- package/dist/vector-backfill-runner.js +853 -216
- package/docs/1.8.7-CODEGRAPH.md +166 -0
- package/docs/API_REFERENCE.md +23 -2
- package/docs/ARCHITECTURE.md +13 -0
- package/docs/README.md +4 -1
- package/package.json +3 -2
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/codegraph.ts +25 -11
- package/src/cli/commands/doctor.ts +11 -2
- package/src/cli/commands/serve-http.ts +27 -1
- package/src/codegraph/auto-context.ts +19 -10
- package/src/codegraph/context-pack.ts +20 -6
- package/src/codegraph/dashboard.ts +82 -0
- package/src/codegraph/external-provider.ts +10 -1
- package/src/codegraph/freshness.ts +1 -1
- package/src/codegraph/lite-provider.ts +81 -0
- package/src/codegraph/project-context.ts +18 -5
- package/src/codegraph/scip-provider.ts +181 -0
- package/src/codegraph/semantic-context.ts +139 -0
- package/src/codegraph/semantic-provider.ts +404 -0
- package/src/codegraph/store.ts +253 -18
- package/src/codegraph/types.ts +25 -3
- package/src/dashboard/server.ts +6 -0
- package/src/dashboard/static/style.css +88 -0
- package/src/knowledge/workset.ts +46 -44
- package/src/server/mcp-binding-store.ts +17 -0
- package/src/store/sqlite-db.ts +13 -0
package/src/codegraph/store.ts
CHANGED
|
@@ -34,6 +34,8 @@ function rowToFile(row: any): CodeFile {
|
|
|
34
34
|
...(row.sizeBytes != null ? { sizeBytes: row.sizeBytes } : {}),
|
|
35
35
|
indexedAt: row.indexedAt,
|
|
36
36
|
...(row.gitCommit ? { gitCommit: row.gitCommit } : {}),
|
|
37
|
+
...(row.source ? { source: row.source } : {}),
|
|
38
|
+
...(row.parserError ? { parserError: row.parserError } : {}),
|
|
37
39
|
...(row.snapshotId ? { snapshotId: row.snapshotId } : {}),
|
|
38
40
|
...(row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}),
|
|
39
41
|
};
|
|
@@ -54,6 +56,7 @@ function rowToSymbol(row: any): CodeSymbol {
|
|
|
54
56
|
...(row.contentHash ? { contentHash: row.contentHash } : {}),
|
|
55
57
|
indexedAt: row.indexedAt,
|
|
56
58
|
stale: !!row.stale,
|
|
59
|
+
...(row.source ? { source: row.source } : {}),
|
|
57
60
|
...(row.snapshotId ? { snapshotId: row.snapshotId } : {}),
|
|
58
61
|
...(row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}),
|
|
59
62
|
} as CodeSymbol;
|
|
@@ -70,6 +73,7 @@ function rowToEdge(row: any): CodeEdge {
|
|
|
70
73
|
type: row.type,
|
|
71
74
|
confidence: row.confidence,
|
|
72
75
|
...(row.evidence ? { evidence: row.evidence } : {}),
|
|
76
|
+
...(row.source ? { source: row.source } : {}),
|
|
73
77
|
indexedAt: row.indexedAt,
|
|
74
78
|
...(row.snapshotId ? { snapshotId: row.snapshotId } : {}),
|
|
75
79
|
...(row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}),
|
|
@@ -209,9 +213,9 @@ export class CodeGraphStore {
|
|
|
209
213
|
if (files.length === 0) return;
|
|
210
214
|
const stmt = this.db.prepare(`
|
|
211
215
|
INSERT OR REPLACE INTO code_files
|
|
212
|
-
(id, projectId, path, language, contentHash, mtimeMs, sizeBytes, indexedAt, gitCommit)
|
|
216
|
+
(id, projectId, path, language, contentHash, mtimeMs, sizeBytes, indexedAt, gitCommit, source, parserError)
|
|
213
217
|
VALUES
|
|
214
|
-
(@id, @projectId, @path, @language, @contentHash, @mtimeMs, @sizeBytes, @indexedAt, @gitCommit)
|
|
218
|
+
(@id, @projectId, @path, @language, @contentHash, @mtimeMs, @sizeBytes, @indexedAt, @gitCommit, @source, @parserError)
|
|
215
219
|
`);
|
|
216
220
|
const tx = this.db.transaction((items: CodeFile[]) => {
|
|
217
221
|
for (const file of items) {
|
|
@@ -225,6 +229,8 @@ export class CodeGraphStore {
|
|
|
225
229
|
sizeBytes: file.sizeBytes ?? null,
|
|
226
230
|
indexedAt: file.indexedAt,
|
|
227
231
|
gitCommit: file.gitCommit ?? null,
|
|
232
|
+
source: file.source ?? null,
|
|
233
|
+
parserError: file.parserError ?? null,
|
|
228
234
|
});
|
|
229
235
|
}
|
|
230
236
|
});
|
|
@@ -235,9 +241,9 @@ export class CodeGraphStore {
|
|
|
235
241
|
if (symbols.length === 0) return;
|
|
236
242
|
const stmt = this.db.prepare(`
|
|
237
243
|
INSERT OR REPLACE INTO code_symbols
|
|
238
|
-
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale)
|
|
244
|
+
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale, source)
|
|
239
245
|
VALUES
|
|
240
|
-
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale)
|
|
246
|
+
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale, @source)
|
|
241
247
|
`);
|
|
242
248
|
const tx = this.db.transaction((items: CodeSymbol[]) => {
|
|
243
249
|
for (const symbol of items) {
|
|
@@ -255,6 +261,7 @@ export class CodeGraphStore {
|
|
|
255
261
|
contentHash: symbol.contentHash ?? null,
|
|
256
262
|
indexedAt: symbol.indexedAt,
|
|
257
263
|
stale: symbol.stale ? 1 : 0,
|
|
264
|
+
source: symbol.source ?? null,
|
|
258
265
|
});
|
|
259
266
|
}
|
|
260
267
|
});
|
|
@@ -265,9 +272,9 @@ export class CodeGraphStore {
|
|
|
265
272
|
if (edges.length === 0) return;
|
|
266
273
|
const stmt = this.db.prepare(`
|
|
267
274
|
INSERT OR REPLACE INTO code_edges
|
|
268
|
-
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt)
|
|
275
|
+
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt, source)
|
|
269
276
|
VALUES
|
|
270
|
-
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt)
|
|
277
|
+
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt, @source)
|
|
271
278
|
`);
|
|
272
279
|
const tx = this.db.transaction((items: CodeEdge[]) => {
|
|
273
280
|
for (const edge of items) {
|
|
@@ -282,6 +289,7 @@ export class CodeGraphStore {
|
|
|
282
289
|
confidence: edge.confidence,
|
|
283
290
|
evidence: edge.evidence ?? null,
|
|
284
291
|
indexedAt: edge.indexedAt,
|
|
292
|
+
source: edge.source ?? null,
|
|
285
293
|
});
|
|
286
294
|
}
|
|
287
295
|
});
|
|
@@ -297,21 +305,21 @@ export class CodeGraphStore {
|
|
|
297
305
|
const deleteFiles = this.db.prepare(`DELETE FROM code_files WHERE projectId = ?`);
|
|
298
306
|
const insertFile = this.db.prepare(`
|
|
299
307
|
INSERT OR REPLACE INTO code_files
|
|
300
|
-
(id, projectId, path, language, contentHash, mtimeMs, sizeBytes, indexedAt, gitCommit)
|
|
308
|
+
(id, projectId, path, language, contentHash, mtimeMs, sizeBytes, indexedAt, gitCommit, source, parserError)
|
|
301
309
|
VALUES
|
|
302
|
-
(@id, @projectId, @path, @language, @contentHash, @mtimeMs, @sizeBytes, @indexedAt, @gitCommit)
|
|
310
|
+
(@id, @projectId, @path, @language, @contentHash, @mtimeMs, @sizeBytes, @indexedAt, @gitCommit, @source, @parserError)
|
|
303
311
|
`);
|
|
304
312
|
const insertSymbol = this.db.prepare(`
|
|
305
313
|
INSERT OR REPLACE INTO code_symbols
|
|
306
|
-
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale)
|
|
314
|
+
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale, source)
|
|
307
315
|
VALUES
|
|
308
|
-
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale)
|
|
316
|
+
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale, @source)
|
|
309
317
|
`);
|
|
310
318
|
const insertEdge = this.db.prepare(`
|
|
311
319
|
INSERT OR REPLACE INTO code_edges
|
|
312
|
-
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt)
|
|
320
|
+
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt, source)
|
|
313
321
|
VALUES
|
|
314
|
-
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt)
|
|
322
|
+
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt, @source)
|
|
315
323
|
`);
|
|
316
324
|
|
|
317
325
|
const tx = this.db.transaction(() => {
|
|
@@ -330,6 +338,8 @@ export class CodeGraphStore {
|
|
|
330
338
|
sizeBytes: file.sizeBytes ?? null,
|
|
331
339
|
indexedAt: file.indexedAt,
|
|
332
340
|
gitCommit: file.gitCommit ?? null,
|
|
341
|
+
source: file.source ?? null,
|
|
342
|
+
parserError: file.parserError ?? null,
|
|
333
343
|
});
|
|
334
344
|
}
|
|
335
345
|
|
|
@@ -348,6 +358,7 @@ export class CodeGraphStore {
|
|
|
348
358
|
contentHash: symbol.contentHash ?? null,
|
|
349
359
|
indexedAt: symbol.indexedAt,
|
|
350
360
|
stale: 0,
|
|
361
|
+
source: symbol.source ?? null,
|
|
351
362
|
});
|
|
352
363
|
}
|
|
353
364
|
|
|
@@ -363,6 +374,7 @@ export class CodeGraphStore {
|
|
|
363
374
|
confidence: edge.confidence,
|
|
364
375
|
evidence: edge.evidence ?? null,
|
|
365
376
|
indexedAt: edge.indexedAt,
|
|
377
|
+
source: edge.source ?? null,
|
|
366
378
|
});
|
|
367
379
|
}
|
|
368
380
|
});
|
|
@@ -417,21 +429,21 @@ export class CodeGraphStore {
|
|
|
417
429
|
const deleteFile = this.db.prepare(`DELETE FROM code_files WHERE projectId = ? AND id = ?`);
|
|
418
430
|
const upsertFile = this.db.prepare(`
|
|
419
431
|
INSERT OR REPLACE INTO code_files
|
|
420
|
-
(id, projectId, path, language, contentHash, mtimeMs, sizeBytes, indexedAt, gitCommit)
|
|
432
|
+
(id, projectId, path, language, contentHash, mtimeMs, sizeBytes, indexedAt, gitCommit, source, parserError)
|
|
421
433
|
VALUES
|
|
422
|
-
(@id, @projectId, @path, @language, @contentHash, @mtimeMs, @sizeBytes, @indexedAt, @gitCommit)
|
|
434
|
+
(@id, @projectId, @path, @language, @contentHash, @mtimeMs, @sizeBytes, @indexedAt, @gitCommit, @source, @parserError)
|
|
423
435
|
`);
|
|
424
436
|
const upsertSymbol = this.db.prepare(`
|
|
425
437
|
INSERT OR REPLACE INTO code_symbols
|
|
426
|
-
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale)
|
|
438
|
+
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale, source)
|
|
427
439
|
VALUES
|
|
428
|
-
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale)
|
|
440
|
+
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale, @source)
|
|
429
441
|
`);
|
|
430
442
|
const upsertEdge = this.db.prepare(`
|
|
431
443
|
INSERT OR REPLACE INTO code_edges
|
|
432
|
-
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt)
|
|
444
|
+
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt, source)
|
|
433
445
|
VALUES
|
|
434
|
-
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt)
|
|
446
|
+
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt, @source)
|
|
435
447
|
`);
|
|
436
448
|
const writeFile = (file: CodeFile) => upsertFile.run({
|
|
437
449
|
id: file.id,
|
|
@@ -443,6 +455,8 @@ export class CodeGraphStore {
|
|
|
443
455
|
sizeBytes: file.sizeBytes ?? null,
|
|
444
456
|
indexedAt: file.indexedAt,
|
|
445
457
|
gitCommit: file.gitCommit ?? null,
|
|
458
|
+
source: file.source ?? null,
|
|
459
|
+
parserError: file.parserError ?? null,
|
|
446
460
|
});
|
|
447
461
|
|
|
448
462
|
const tx = this.db.transaction(() => {
|
|
@@ -480,6 +494,7 @@ export class CodeGraphStore {
|
|
|
480
494
|
contentHash: symbol.contentHash ?? null,
|
|
481
495
|
indexedAt: symbol.indexedAt,
|
|
482
496
|
stale: symbol.stale ? 1 : 0,
|
|
497
|
+
source: symbol.source ?? null,
|
|
483
498
|
});
|
|
484
499
|
}
|
|
485
500
|
|
|
@@ -495,6 +510,7 @@ export class CodeGraphStore {
|
|
|
495
510
|
confidence: edge.confidence,
|
|
496
511
|
evidence: edge.evidence ?? null,
|
|
497
512
|
indexedAt: edge.indexedAt,
|
|
513
|
+
source: edge.source ?? null,
|
|
498
514
|
});
|
|
499
515
|
}
|
|
500
516
|
}
|
|
@@ -655,6 +671,219 @@ export class CodeGraphStore {
|
|
|
655
671
|
return this.db.prepare(`SELECT * FROM code_edges WHERE projectId = ? ORDER BY type, id`).all(projectId).map(rowToEdge);
|
|
656
672
|
}
|
|
657
673
|
|
|
674
|
+
/** Return direct importers before a refresh clears their old target IDs. */
|
|
675
|
+
listImportDependentPaths(projectId: string, paths: string[], candidatePaths: string[] = []): string[] {
|
|
676
|
+
const normalized = [...new Set(paths.map(normalizeCodePath).filter(Boolean))];
|
|
677
|
+
if (normalized.length === 0) return [];
|
|
678
|
+
const files = this.listFiles(projectId);
|
|
679
|
+
const fileIds = files
|
|
680
|
+
.filter(file => normalized.includes(file.path))
|
|
681
|
+
.map(file => file.id);
|
|
682
|
+
const fileById = new Map(files.map(file => [file.id, file]));
|
|
683
|
+
const fileByPath = new Map(files.map(file => [file.path, file]));
|
|
684
|
+
for (const path of candidatePaths.map(normalizeCodePath).filter(Boolean)) {
|
|
685
|
+
if (!fileByPath.has(path)) {
|
|
686
|
+
fileByPath.set(path, {
|
|
687
|
+
id: `candidate:${path}`,
|
|
688
|
+
projectId,
|
|
689
|
+
path,
|
|
690
|
+
contentHash: '',
|
|
691
|
+
indexedAt: '',
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const rows = this.db.prepare(`
|
|
696
|
+
SELECT DISTINCT fromFileId, evidence
|
|
697
|
+
FROM code_edges
|
|
698
|
+
WHERE projectId = ? AND type = 'imports'
|
|
699
|
+
`).all(projectId) as Array<{ fromFileId?: string; evidence?: string }>;
|
|
700
|
+
const wanted = new Set(normalized);
|
|
701
|
+
const dependents = new Set<string>();
|
|
702
|
+
const queue = [...normalized];
|
|
703
|
+
while (queue.length > 0) {
|
|
704
|
+
const targetPath = queue.shift()!;
|
|
705
|
+
for (const row of rows) {
|
|
706
|
+
const source = row.fromFileId ? fileById.get(row.fromFileId) : undefined;
|
|
707
|
+
if (!source || source.path === targetPath || dependents.has(source.path)) continue;
|
|
708
|
+
const target = resolveRelativeImportTarget(source.path, row.evidence, fileByPath);
|
|
709
|
+
if (!target || !wanted.has(target.path)) continue;
|
|
710
|
+
dependents.add(source.path);
|
|
711
|
+
if (!wanted.has(source.path)) {
|
|
712
|
+
wanted.add(source.path);
|
|
713
|
+
queue.push(source.path);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (fileIds.length > 0) {
|
|
718
|
+
const placeholders = fileIds.map(() => '?').join(', ');
|
|
719
|
+
const exactRows = this.db.prepare(`
|
|
720
|
+
SELECT DISTINCT fromFileId
|
|
721
|
+
FROM code_edges
|
|
722
|
+
WHERE projectId = ? AND type = 'imports' AND toFileId IN (${placeholders})
|
|
723
|
+
`).all(projectId, ...fileIds) as Array<{ fromFileId?: string }>;
|
|
724
|
+
for (const row of exactRows) {
|
|
725
|
+
const source = row.fromFileId ? fileById.get(row.fromFileId) : undefined;
|
|
726
|
+
if (source) dependents.add(source.path);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return [...dependents].sort();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Replace only the semantic slice touched by a change, in one transaction. */
|
|
733
|
+
replaceSemanticSlice(
|
|
734
|
+
projectId: string,
|
|
735
|
+
affectedPaths: string[],
|
|
736
|
+
input: { symbols: CodeSymbol[]; edges: CodeEdge[]; diagnostics?: Array<{ path: string; message: string }> },
|
|
737
|
+
): void {
|
|
738
|
+
const normalizedPaths = [...new Set(affectedPaths.map(normalizeCodePath).filter(Boolean))];
|
|
739
|
+
if (normalizedPaths.length === 0) return;
|
|
740
|
+
const fileRows = this.listFiles(projectId).filter(file => normalizedPaths.includes(file.path));
|
|
741
|
+
const fileIds = fileRows.map(file => file.id);
|
|
742
|
+
const placeholders = fileIds.map(() => '?').join(', ');
|
|
743
|
+
if (fileIds.length === 0) return;
|
|
744
|
+
const staleRefs = this.db.prepare(`
|
|
745
|
+
UPDATE observation_code_refs SET status = 'stale', updatedAt = ?
|
|
746
|
+
WHERE projectId = ? AND (fileId IN (${placeholders}) OR symbolId IN (
|
|
747
|
+
SELECT id FROM code_symbols WHERE projectId = ? AND fileId IN (${placeholders})
|
|
748
|
+
))
|
|
749
|
+
`);
|
|
750
|
+
const deleteEdges = this.db.prepare(`
|
|
751
|
+
DELETE FROM code_edges
|
|
752
|
+
WHERE projectId = ? AND (source IS NULL OR source IN ('lite-regex', 'typescript-compiler'))
|
|
753
|
+
AND (fromFileId IN (${placeholders}) OR toFileId IN (${placeholders})
|
|
754
|
+
OR fromSymbolId IN (SELECT id FROM code_symbols WHERE projectId = ? AND fileId IN (${placeholders}))
|
|
755
|
+
OR toSymbolId IN (SELECT id FROM code_symbols WHERE projectId = ? AND fileId IN (${placeholders})))
|
|
756
|
+
`);
|
|
757
|
+
const deleteSymbols = this.db.prepare(`DELETE FROM code_symbols WHERE projectId = ? AND fileId IN (${placeholders})`);
|
|
758
|
+
const updateFile = this.db.prepare('UPDATE code_files SET source = ?, parserError = ? WHERE projectId = ? AND id = ?');
|
|
759
|
+
const insertSymbol = this.db.prepare(`
|
|
760
|
+
INSERT OR REPLACE INTO code_symbols
|
|
761
|
+
(id, projectId, fileId, path, name, qualifiedName, kind, startLine, endLine, signature, contentHash, indexedAt, stale, source)
|
|
762
|
+
VALUES
|
|
763
|
+
(@id, @projectId, @fileId, @path, @name, @qualifiedName, @kind, @startLine, @endLine, @signature, @contentHash, @indexedAt, @stale, @source)
|
|
764
|
+
`);
|
|
765
|
+
const insertEdge = this.db.prepare(`
|
|
766
|
+
INSERT OR REPLACE INTO code_edges
|
|
767
|
+
(id, projectId, fromSymbolId, toSymbolId, fromFileId, toFileId, type, confidence, evidence, indexedAt, source)
|
|
768
|
+
VALUES
|
|
769
|
+
(@id, @projectId, @fromSymbolId, @toSymbolId, @fromFileId, @toFileId, @type, @confidence, @evidence, @indexedAt, @source)
|
|
770
|
+
`);
|
|
771
|
+
const diagnosticsByPath = new Map((input.diagnostics ?? []).map(item => [normalizeCodePath(item.path), item.message]));
|
|
772
|
+
const symbolFileIds = new Set(fileIds);
|
|
773
|
+
const tx = this.db.transaction(() => {
|
|
774
|
+
const now = new Date().toISOString();
|
|
775
|
+
staleRefs.run(now, projectId, ...fileIds, projectId, ...fileIds);
|
|
776
|
+
deleteEdges.run(projectId, ...fileIds, ...fileIds, projectId, ...fileIds, projectId, ...fileIds);
|
|
777
|
+
deleteSymbols.run(projectId, ...fileIds);
|
|
778
|
+
for (const file of fileRows) {
|
|
779
|
+
const diagnostic = diagnosticsByPath.get(file.path);
|
|
780
|
+
updateFile.run(diagnostic ? 'typescript-compiler' : 'typescript-compiler', diagnostic ?? null, projectId, file.id);
|
|
781
|
+
}
|
|
782
|
+
for (const symbol of input.symbols.filter(symbol => symbolFileIds.has(symbol.fileId))) {
|
|
783
|
+
insertSymbol.run({
|
|
784
|
+
id: symbol.id,
|
|
785
|
+
projectId: symbol.projectId,
|
|
786
|
+
fileId: symbol.fileId,
|
|
787
|
+
path: normalizeCodePath(symbol.path),
|
|
788
|
+
name: symbol.name,
|
|
789
|
+
qualifiedName: symbol.qualifiedName,
|
|
790
|
+
kind: symbol.kind,
|
|
791
|
+
startLine: symbol.startLine ?? null,
|
|
792
|
+
endLine: symbol.endLine ?? null,
|
|
793
|
+
signature: symbol.signature ?? null,
|
|
794
|
+
contentHash: symbol.contentHash ?? null,
|
|
795
|
+
indexedAt: symbol.indexedAt,
|
|
796
|
+
stale: symbol.stale ? 1 : 0,
|
|
797
|
+
source: symbol.source ?? 'typescript-compiler',
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
for (const edge of input.edges) {
|
|
801
|
+
insertEdge.run({
|
|
802
|
+
id: edge.id,
|
|
803
|
+
projectId: edge.projectId,
|
|
804
|
+
fromSymbolId: edge.fromSymbolId ?? null,
|
|
805
|
+
toSymbolId: edge.toSymbolId ?? null,
|
|
806
|
+
fromFileId: edge.fromFileId ?? null,
|
|
807
|
+
toFileId: edge.toFileId ?? null,
|
|
808
|
+
type: edge.type,
|
|
809
|
+
confidence: edge.confidence,
|
|
810
|
+
evidence: edge.evidence ?? null,
|
|
811
|
+
indexedAt: edge.indexedAt,
|
|
812
|
+
source: edge.source ?? 'typescript-compiler',
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
tx();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/** Record a semantic refresh failure without presenting the old facts as current. */
|
|
820
|
+
markSemanticSliceFailed(projectId: string, affectedPaths: string[], message: string): void {
|
|
821
|
+
const normalizedPaths = [...new Set(affectedPaths.map(normalizeCodePath).filter(Boolean))];
|
|
822
|
+
const fileRows = this.listFiles(projectId).filter(file => normalizedPaths.includes(file.path));
|
|
823
|
+
const fileIds = fileRows.map(file => file.id);
|
|
824
|
+
const filePlaceholders = fileIds.map(() => '?').join(', ');
|
|
825
|
+
const staleRefs = fileIds.length > 0
|
|
826
|
+
? this.db.prepare(`
|
|
827
|
+
UPDATE observation_code_refs SET status = 'stale', updatedAt = ?
|
|
828
|
+
WHERE projectId = ? AND (fileId IN (${filePlaceholders}) OR symbolId IN (
|
|
829
|
+
SELECT id FROM code_symbols WHERE projectId = ? AND fileId IN (${filePlaceholders})
|
|
830
|
+
))
|
|
831
|
+
`)
|
|
832
|
+
: undefined;
|
|
833
|
+
const staleSymbols = fileIds.length > 0
|
|
834
|
+
? this.db.prepare(`
|
|
835
|
+
UPDATE code_symbols SET stale = 1
|
|
836
|
+
WHERE projectId = ? AND source = 'typescript-compiler' AND fileId IN (${filePlaceholders})
|
|
837
|
+
`)
|
|
838
|
+
: undefined;
|
|
839
|
+
const updateFiles = fileIds.length > 0
|
|
840
|
+
? this.db.prepare(`
|
|
841
|
+
UPDATE code_files SET parserError = ?
|
|
842
|
+
WHERE projectId = ? AND id IN (${filePlaceholders})
|
|
843
|
+
`)
|
|
844
|
+
: undefined;
|
|
845
|
+
const affectedEdgeClause = fileIds.length > 0
|
|
846
|
+
? `
|
|
847
|
+
OR fromFileId IN (${filePlaceholders})
|
|
848
|
+
OR toFileId IN (${filePlaceholders})
|
|
849
|
+
OR fromSymbolId IN (SELECT id FROM code_symbols WHERE projectId = ? AND fileId IN (${filePlaceholders}))
|
|
850
|
+
OR toSymbolId IN (SELECT id FROM code_symbols WHERE projectId = ? AND fileId IN (${filePlaceholders}))
|
|
851
|
+
`
|
|
852
|
+
: '';
|
|
853
|
+
const deleteEdges = this.db.prepare(`
|
|
854
|
+
DELETE FROM code_edges
|
|
855
|
+
WHERE projectId = ? AND source = 'typescript-compiler'
|
|
856
|
+
AND (
|
|
857
|
+
(fromSymbolId IS NOT NULL AND NOT EXISTS (
|
|
858
|
+
SELECT 1 FROM code_symbols WHERE projectId = ? AND id = fromSymbolId
|
|
859
|
+
))
|
|
860
|
+
OR (toSymbolId IS NOT NULL AND NOT EXISTS (
|
|
861
|
+
SELECT 1 FROM code_symbols WHERE projectId = ? AND id = toSymbolId
|
|
862
|
+
))
|
|
863
|
+
${affectedEdgeClause}
|
|
864
|
+
)
|
|
865
|
+
`);
|
|
866
|
+
const tx = this.db.transaction(() => {
|
|
867
|
+
const failure = `semantic indexing failed: ${message.slice(0, 500)}`;
|
|
868
|
+
if (updateFiles) updateFiles.run(failure, projectId, ...fileIds);
|
|
869
|
+
if (staleRefs) staleRefs.run(new Date().toISOString(), projectId, ...fileIds, projectId, ...fileIds);
|
|
870
|
+
if (staleSymbols) staleSymbols.run(projectId, ...fileIds);
|
|
871
|
+
const edgeParams: unknown[] = [projectId, projectId, projectId];
|
|
872
|
+
if (fileIds.length > 0) {
|
|
873
|
+
edgeParams.push(
|
|
874
|
+
...fileIds,
|
|
875
|
+
...fileIds,
|
|
876
|
+
projectId,
|
|
877
|
+
...fileIds,
|
|
878
|
+
projectId,
|
|
879
|
+
...fileIds,
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
deleteEdges.run(...edgeParams);
|
|
883
|
+
});
|
|
884
|
+
tx();
|
|
885
|
+
}
|
|
886
|
+
|
|
658
887
|
listObservationRefs(projectId: string, observationId: number): ObservationCodeRef[] {
|
|
659
888
|
return this.db.prepare(`
|
|
660
889
|
SELECT * FROM observation_code_refs
|
|
@@ -869,6 +1098,9 @@ export class CodeGraphStore {
|
|
|
869
1098
|
const symbols = this.db.prepare(`SELECT COUNT(*) AS count FROM code_symbols WHERE projectId = ? AND stale = 0`).get(projectId).count;
|
|
870
1099
|
const edges = this.db.prepare(`SELECT COUNT(*) AS count FROM code_edges WHERE projectId = ?`).get(projectId).count;
|
|
871
1100
|
const refs = this.db.prepare(`SELECT COUNT(*) AS count FROM observation_code_refs WHERE projectId = ?`).get(projectId).count;
|
|
1101
|
+
const semanticSymbols = this.db.prepare(`SELECT COUNT(*) AS count FROM code_symbols WHERE projectId = ? AND stale = 0 AND source = 'typescript-compiler'`).get(projectId).count;
|
|
1102
|
+
const semanticEdges = this.db.prepare(`SELECT COUNT(*) AS count FROM code_edges WHERE projectId = ? AND source = 'typescript-compiler'`).get(projectId).count;
|
|
1103
|
+
const parserErrors = this.db.prepare(`SELECT COUNT(*) AS count FROM code_files WHERE projectId = ? AND parserError IS NOT NULL`).get(projectId).count;
|
|
872
1104
|
const latest = this.db.prepare(`SELECT MAX(indexedAt) AS indexedAt FROM code_files WHERE projectId = ?`).get(projectId);
|
|
873
1105
|
const latestSnapshot = this.latestSnapshot(projectId);
|
|
874
1106
|
return {
|
|
@@ -877,6 +1109,9 @@ export class CodeGraphStore {
|
|
|
877
1109
|
symbols,
|
|
878
1110
|
edges,
|
|
879
1111
|
refs,
|
|
1112
|
+
semanticSymbols,
|
|
1113
|
+
semanticEdges,
|
|
1114
|
+
parserErrors,
|
|
880
1115
|
...(latest?.indexedAt ? { indexedAt: latest.indexedAt } : {}),
|
|
881
1116
|
...(latestSnapshot ? { latestSnapshot } : {}),
|
|
882
1117
|
};
|
package/src/codegraph/types.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export type CodeGraphProviderKind = 'external' | 'lite';
|
|
2
|
+
export type CodeGraphEvidenceProvider = 'lite' | 'semantic' | 'external';
|
|
3
|
+
|
|
4
|
+
/** The component that produced a persisted code fact. */
|
|
5
|
+
export type CodeEvidenceSource = 'lite-regex' | 'typescript-compiler' | 'scip' | 'external';
|
|
2
6
|
|
|
3
7
|
export type CodeRefStatus = 'current' | 'suspect' | 'stale' | 'unbound';
|
|
4
8
|
|
|
@@ -10,6 +14,7 @@ export type CodeSymbolKind =
|
|
|
10
14
|
| 'type'
|
|
11
15
|
| 'component'
|
|
12
16
|
| 'constant'
|
|
17
|
+
| 'property'
|
|
13
18
|
| 'route'
|
|
14
19
|
| 'unknown';
|
|
15
20
|
|
|
@@ -19,6 +24,8 @@ export type CodeEdgeType =
|
|
|
19
24
|
| 'calls'
|
|
20
25
|
| 'defines'
|
|
21
26
|
| 'tests'
|
|
27
|
+
| 'extends'
|
|
28
|
+
| 'implements'
|
|
22
29
|
| 'routes_to'
|
|
23
30
|
| 'references';
|
|
24
31
|
|
|
@@ -32,6 +39,8 @@ export interface CodeFile {
|
|
|
32
39
|
sizeBytes?: number;
|
|
33
40
|
indexedAt: string;
|
|
34
41
|
gitCommit?: string;
|
|
42
|
+
source?: CodeEvidenceSource;
|
|
43
|
+
parserError?: string;
|
|
35
44
|
snapshotId?: string;
|
|
36
45
|
sourceEpoch?: number;
|
|
37
46
|
}
|
|
@@ -50,6 +59,7 @@ export interface CodeSymbol {
|
|
|
50
59
|
contentHash?: string;
|
|
51
60
|
indexedAt: string;
|
|
52
61
|
stale?: boolean;
|
|
62
|
+
source?: CodeEvidenceSource;
|
|
53
63
|
snapshotId?: string;
|
|
54
64
|
sourceEpoch?: number;
|
|
55
65
|
}
|
|
@@ -64,6 +74,7 @@ export interface CodeEdge {
|
|
|
64
74
|
type: CodeEdgeType;
|
|
65
75
|
confidence: number;
|
|
66
76
|
evidence?: string;
|
|
77
|
+
source?: CodeEvidenceSource;
|
|
67
78
|
indexedAt: string;
|
|
68
79
|
snapshotId?: string;
|
|
69
80
|
sourceEpoch?: number;
|
|
@@ -162,6 +173,9 @@ export interface CodeGraphStatus {
|
|
|
162
173
|
symbols: number;
|
|
163
174
|
edges: number;
|
|
164
175
|
refs: number;
|
|
176
|
+
semanticSymbols: number;
|
|
177
|
+
semanticEdges: number;
|
|
178
|
+
parserErrors: number;
|
|
165
179
|
indexedAt?: string;
|
|
166
180
|
latestSnapshot?: CodeStateSnapshot;
|
|
167
181
|
}
|
|
@@ -195,7 +209,7 @@ export interface ExternalCodeGraphHealth {
|
|
|
195
209
|
|
|
196
210
|
export interface CodeGraphProviderQuality {
|
|
197
211
|
/** Provider that contributed task-scoped code evidence to this response. */
|
|
198
|
-
selected:
|
|
212
|
+
selected: CodeGraphEvidenceProvider;
|
|
199
213
|
/** Lite is heuristic; external outlines are only semantic after validation. */
|
|
200
214
|
selectedQuality: 'heuristic' | 'semantic';
|
|
201
215
|
mode: CodeGraphExternalMode;
|
|
@@ -212,6 +226,12 @@ export interface CodeGraphProviderQuality {
|
|
|
212
226
|
supportedLanguages: string[];
|
|
213
227
|
};
|
|
214
228
|
external: ExternalCodeGraphHealth;
|
|
229
|
+
semantic: {
|
|
230
|
+
state: 'not-indexed' | 'ready' | 'partial';
|
|
231
|
+
symbols: number;
|
|
232
|
+
edges: number;
|
|
233
|
+
parserErrors: number;
|
|
234
|
+
};
|
|
215
235
|
}
|
|
216
236
|
|
|
217
237
|
export interface ExternalCodeGraphSymbol {
|
|
@@ -233,8 +253,8 @@ export interface ExternalCodeGraphRelation {
|
|
|
233
253
|
}
|
|
234
254
|
|
|
235
255
|
/** A bounded, non-source-code semantic outline returned for one task. */
|
|
236
|
-
export interface
|
|
237
|
-
provider: 'external';
|
|
256
|
+
export interface CodeGraphOutline {
|
|
257
|
+
provider: 'semantic' | 'external';
|
|
238
258
|
entryPoints: ExternalCodeGraphSymbol[];
|
|
239
259
|
relations: ExternalCodeGraphRelation[];
|
|
240
260
|
relatedFiles: string[];
|
|
@@ -244,3 +264,5 @@ export interface ExternalCodeGraphOutline {
|
|
|
244
264
|
files: number;
|
|
245
265
|
};
|
|
246
266
|
}
|
|
267
|
+
|
|
268
|
+
export type ExternalCodeGraphOutline = CodeGraphOutline & { provider: 'external' };
|
package/src/dashboard/server.ts
CHANGED
|
@@ -321,6 +321,12 @@ async function handleApi(
|
|
|
321
321
|
break;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
case '/codegraph': {
|
|
325
|
+
const { getCodeGraphDashboardStatus } = await import('../codegraph/dashboard.js');
|
|
326
|
+
sendJson(res, await getCodeGraphDashboardStatus(effectiveDataDir, effectiveProjectId));
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
|
|
324
330
|
case '/observations': {
|
|
325
331
|
const observations = filterDashboardObservations(
|
|
326
332
|
await getObservationStore().loadByProject(effectiveProjectId, { status: 'active' }),
|
|
@@ -1133,12 +1133,100 @@ body {
|
|
|
1133
1133
|
padding: 16px 20px 8px;
|
|
1134
1134
|
}
|
|
1135
1135
|
|
|
1136
|
+
#codegraph-health-panel {
|
|
1137
|
+
flex-shrink: 0;
|
|
1138
|
+
padding: 0 20px 10px;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.codegraph-health {
|
|
1142
|
+
display: flex;
|
|
1143
|
+
align-items: center;
|
|
1144
|
+
justify-content: space-between;
|
|
1145
|
+
gap: 16px;
|
|
1146
|
+
min-width: 0;
|
|
1147
|
+
padding: 9px 12px;
|
|
1148
|
+
border: 1px solid var(--border-subtle);
|
|
1149
|
+
border-left: 3px solid var(--accent-green);
|
|
1150
|
+
border-radius: var(--radius-sm);
|
|
1151
|
+
background: var(--bg-surface);
|
|
1152
|
+
color: var(--text-secondary);
|
|
1153
|
+
font-size: 11px;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.codegraph-health--partial {
|
|
1157
|
+
border-left-color: var(--accent-amber);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
.codegraph-health--not-indexed,
|
|
1161
|
+
.codegraph-health--unknown {
|
|
1162
|
+
border-left-color: var(--text-muted);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.codegraph-health-title,
|
|
1166
|
+
.codegraph-health-metrics {
|
|
1167
|
+
display: flex;
|
|
1168
|
+
align-items: center;
|
|
1169
|
+
gap: 9px;
|
|
1170
|
+
min-width: 0;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.codegraph-health-title strong {
|
|
1174
|
+
color: var(--text-primary);
|
|
1175
|
+
white-space: nowrap;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.codegraph-health-title > span:not(.codegraph-health-dot) {
|
|
1179
|
+
overflow: hidden;
|
|
1180
|
+
text-overflow: ellipsis;
|
|
1181
|
+
white-space: nowrap;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.codegraph-health-dot {
|
|
1185
|
+
width: 7px;
|
|
1186
|
+
height: 7px;
|
|
1187
|
+
flex: 0 0 7px;
|
|
1188
|
+
border-radius: 50%;
|
|
1189
|
+
background: var(--accent-green);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.codegraph-health--partial .codegraph-health-dot {
|
|
1193
|
+
background: var(--accent-amber);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.codegraph-health--not-indexed .codegraph-health-dot,
|
|
1197
|
+
.codegraph-health--unknown .codegraph-health-dot {
|
|
1198
|
+
background: var(--text-muted);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.codegraph-health-metrics {
|
|
1202
|
+
flex-wrap: wrap;
|
|
1203
|
+
justify-content: flex-end;
|
|
1204
|
+
color: var(--text-muted);
|
|
1205
|
+
font-family: var(--font-mono);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.codegraph-health-error {
|
|
1209
|
+
color: var(--accent-amber);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1136
1212
|
.kg-page-shell .graph-layout {
|
|
1137
1213
|
flex: 1;
|
|
1138
1214
|
min-height: 0;
|
|
1215
|
+
height: auto;
|
|
1139
1216
|
}
|
|
1140
1217
|
|
|
1141
1218
|
@media (max-width: 768px) {
|
|
1219
|
+
#codegraph-health-panel {
|
|
1220
|
+
padding: 0 12px 10px;
|
|
1221
|
+
}
|
|
1222
|
+
.codegraph-health {
|
|
1223
|
+
align-items: flex-start;
|
|
1224
|
+
flex-direction: column;
|
|
1225
|
+
gap: 6px;
|
|
1226
|
+
}
|
|
1227
|
+
.codegraph-health-metrics {
|
|
1228
|
+
justify-content: flex-start;
|
|
1229
|
+
}
|
|
1142
1230
|
.kg-page-shell .graph-layout {
|
|
1143
1231
|
flex-direction: column;
|
|
1144
1232
|
height: auto;
|