gitnexus 1.4.1 → 1.4.6
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 +215 -194
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +117 -90
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +57 -30
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +1 -1
- package/dist/cli/eval-server.js +14 -6
- package/dist/cli/index.js +18 -25
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/setup.js +42 -32
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +48 -13
- package/dist/cli/wiki.js +2 -2
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +1 -0
- package/dist/config/supported-languages.js +1 -1
- package/dist/core/augmentation/engine.js +99 -72
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +74 -47
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +5 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +5 -7
- package/dist/core/ingestion/call-processor.js +430 -283
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +94 -24
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +5 -1
- package/dist/core/ingestion/framework-detection.js +48 -8
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +202 -696
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +3 -11
- package/dist/core/ingestion/parsing-processor.js +85 -181
- package/dist/core/ingestion/pipeline.d.ts +5 -1
- package/dist/core/ingestion/pipeline.js +192 -116
- package/dist/core/ingestion/process-processor.js +2 -1
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/symbol-table.d.ts +21 -1
- package/dist/core/ingestion/symbol-table.js +40 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -11
- package/dist/core/ingestion/tree-sitter-queries.js +642 -485
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +611 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +406 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +449 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
- package/dist/core/ingestion/type-extractors/shared.js +703 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +98 -0
- package/dist/core/ingestion/utils.js +1064 -9
- package/dist/core/ingestion/workers/parse-worker.d.ts +38 -4
- package/dist/core/ingestion/workers/parse-worker.js +251 -359
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/csv-generator.js +20 -4
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +82 -82
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +304 -289
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +17 -16
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +9 -9
- package/dist/core/tree-sitter/parser-loader.js +9 -2
- package/dist/core/wiki/generator.d.ts +4 -52
- package/dist/core/wiki/generator.js +53 -552
- package/dist/core/wiki/graph-queries.d.ts +4 -46
- package/dist/core/wiki/graph-queries.js +103 -282
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/llm-client.js +11 -73
- package/dist/core/wiki/prompts.d.ts +8 -52
- package/dist/core/wiki/prompts.js +86 -200
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -9
- package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +77 -79
- package/dist/mcp/local/local-backend.d.ts +7 -6
- package/dist/mcp/local/local-backend.js +176 -147
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +18 -19
- package/dist/mcp/tools.js +103 -104
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +55 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +238 -155
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +99 -96
- package/scripts/patch-tree-sitter-swift.cjs +74 -74
- package/skills/gitnexus-cli.md +82 -82
- package/skills/gitnexus-debugging.md +89 -89
- package/skills/gitnexus-exploring.md +78 -78
- package/skills/gitnexus-guide.md +64 -64
- package/skills/gitnexus-impact-analysis.md +97 -97
- package/skills/gitnexus-pr-review.md +163 -163
- package/skills/gitnexus-refactoring.md +121 -121
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
- package/dist/core/wiki/diagrams.d.ts +0 -27
- package/dist/core/wiki/diagrams.js +0 -163
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides tool implementations using local .gitnexus/ indexes.
|
|
5
5
|
* Supports multiple indexed repositories via a global registry.
|
|
6
|
-
*
|
|
6
|
+
* LadybugDB connections are opened lazily per repo on first query.
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
|
-
import {
|
|
10
|
+
import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady } from '../core/lbug-adapter.js';
|
|
11
11
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
12
12
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
13
13
|
// git utilities available if needed
|
|
14
14
|
// import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
|
|
15
|
-
import { listRegisteredRepos, } from '../../storage/repo-manager.js';
|
|
15
|
+
import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
|
|
16
16
|
// AI context generation is CLI-only (gitnexus analyze)
|
|
17
17
|
// import { generateAIContextFiles } from '../../cli/ai-context.js';
|
|
18
18
|
/**
|
|
@@ -26,9 +26,10 @@ export function isTestFilePath(filePath) {
|
|
|
26
26
|
p.includes('/test/') || p.includes('/tests/') ||
|
|
27
27
|
p.includes('/testing/') || p.includes('/fixtures/') ||
|
|
28
28
|
p.endsWith('_test.go') || p.endsWith('_test.py') ||
|
|
29
|
+
p.endsWith('_spec.rb') || p.endsWith('_test.rb') || p.includes('/spec/') ||
|
|
29
30
|
p.includes('/test_') || p.includes('/conftest.'));
|
|
30
31
|
}
|
|
31
|
-
/** Valid
|
|
32
|
+
/** Valid LadybugDB node labels for safe Cypher query construction */
|
|
32
33
|
export const VALID_NODE_LABELS = new Set([
|
|
33
34
|
'File', 'Folder', 'Function', 'Class', 'Interface', 'Method', 'CodeElement',
|
|
34
35
|
'Community', 'Process', 'Struct', 'Enum', 'Macro', 'Typedef', 'Union',
|
|
@@ -36,7 +37,7 @@ export const VALID_NODE_LABELS = new Set([
|
|
|
36
37
|
'Record', 'Delegate', 'Annotation', 'Constructor', 'Template', 'Module',
|
|
37
38
|
]);
|
|
38
39
|
/** Valid relation types for impact analysis filtering */
|
|
39
|
-
export const VALID_RELATION_TYPES = new Set(['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS']);
|
|
40
|
+
export const VALID_RELATION_TYPES = new Set(['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'OVERRIDES']);
|
|
40
41
|
/** Regex to detect write operations in user-supplied Cypher queries */
|
|
41
42
|
export const CYPHER_WRITE_RE = /\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH)\b/i;
|
|
42
43
|
/** Check if a Cypher query contains write operations */
|
|
@@ -64,7 +65,7 @@ export class LocalBackend {
|
|
|
64
65
|
/**
|
|
65
66
|
* Re-read the global registry and update the in-memory repo map.
|
|
66
67
|
* New repos are added, existing repos are updated, removed repos are pruned.
|
|
67
|
-
*
|
|
68
|
+
* LadybugDB connections for removed repos are NOT closed (they idle-timeout naturally).
|
|
68
69
|
*/
|
|
69
70
|
async refreshRepos() {
|
|
70
71
|
const entries = await listRegisteredRepos({ validate: true });
|
|
@@ -73,19 +74,25 @@ export class LocalBackend {
|
|
|
73
74
|
const id = this.repoId(entry.name, entry.path);
|
|
74
75
|
freshIds.add(id);
|
|
75
76
|
const storagePath = entry.storagePath;
|
|
76
|
-
const
|
|
77
|
+
const lbugPath = path.join(storagePath, 'lbug');
|
|
78
|
+
// Clean up any leftover KuzuDB files from before the LadybugDB migration.
|
|
79
|
+
// If kuzu exists but lbug doesn't, warn so the user knows to re-analyze.
|
|
80
|
+
const kuzu = await cleanupOldKuzuFiles(storagePath);
|
|
81
|
+
if (kuzu.found && kuzu.needsReindex) {
|
|
82
|
+
console.error(`GitNexus: "${entry.name}" has a stale KuzuDB index. Run: gitnexus analyze ${entry.path}`);
|
|
83
|
+
}
|
|
77
84
|
const handle = {
|
|
78
85
|
id,
|
|
79
86
|
name: entry.name,
|
|
80
87
|
repoPath: entry.path,
|
|
81
88
|
storagePath,
|
|
82
|
-
|
|
89
|
+
lbugPath,
|
|
83
90
|
indexedAt: entry.indexedAt,
|
|
84
91
|
lastCommit: entry.lastCommit,
|
|
85
92
|
stats: entry.stats,
|
|
86
93
|
};
|
|
87
94
|
this.repos.set(id, handle);
|
|
88
|
-
// Build lightweight context (no
|
|
95
|
+
// Build lightweight context (no LadybugDB needed)
|
|
89
96
|
const s = entry.stats || {};
|
|
90
97
|
this.contextCache.set(id, {
|
|
91
98
|
projectName: entry.name,
|
|
@@ -186,16 +193,16 @@ export class LocalBackend {
|
|
|
186
193
|
}
|
|
187
194
|
return null; // Multiple repos, no param — ambiguous
|
|
188
195
|
}
|
|
189
|
-
// ─── Lazy
|
|
196
|
+
// ─── Lazy LadybugDB Init ────────────────────────────────────────────
|
|
190
197
|
async ensureInitialized(repoId) {
|
|
191
198
|
// Always check the actual pool — the idle timer may have evicted the connection
|
|
192
|
-
if (this.initializedRepos.has(repoId) &&
|
|
199
|
+
if (this.initializedRepos.has(repoId) && isLbugReady(repoId))
|
|
193
200
|
return;
|
|
194
201
|
const handle = this.repos.get(repoId);
|
|
195
202
|
if (!handle)
|
|
196
203
|
throw new Error(`Unknown repo: ${repoId}`);
|
|
197
204
|
try {
|
|
198
|
-
await
|
|
205
|
+
await initLbug(repoId, handle.lbugPath);
|
|
199
206
|
this.initializedRepos.add(repoId);
|
|
200
207
|
}
|
|
201
208
|
catch (err) {
|
|
@@ -335,9 +342,9 @@ export class LocalBackend {
|
|
|
335
342
|
// Find processes this symbol participates in
|
|
336
343
|
let processRows = [];
|
|
337
344
|
try {
|
|
338
|
-
processRows = await executeParameterized(repo.id, `
|
|
339
|
-
MATCH (n {id: $nodeId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
340
|
-
RETURN p.id AS pid, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
|
|
345
|
+
processRows = await executeParameterized(repo.id, `
|
|
346
|
+
MATCH (n {id: $nodeId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
347
|
+
RETURN p.id AS pid, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
|
|
341
348
|
`, { nodeId: sym.nodeId });
|
|
342
349
|
}
|
|
343
350
|
catch (e) {
|
|
@@ -347,10 +354,10 @@ export class LocalBackend {
|
|
|
347
354
|
let cohesion = 0;
|
|
348
355
|
let module;
|
|
349
356
|
try {
|
|
350
|
-
const cohesionRows = await executeParameterized(repo.id, `
|
|
351
|
-
MATCH (n {id: $nodeId})-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
352
|
-
RETURN c.cohesion AS cohesion, c.heuristicLabel AS module
|
|
353
|
-
LIMIT 1
|
|
357
|
+
const cohesionRows = await executeParameterized(repo.id, `
|
|
358
|
+
MATCH (n {id: $nodeId})-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
359
|
+
RETURN c.cohesion AS cohesion, c.heuristicLabel AS module
|
|
360
|
+
LIMIT 1
|
|
354
361
|
`, { nodeId: sym.nodeId });
|
|
355
362
|
if (cohesionRows.length > 0) {
|
|
356
363
|
cohesion = (cohesionRows[0].cohesion ?? cohesionRows[0][0]) || 0;
|
|
@@ -364,9 +371,9 @@ export class LocalBackend {
|
|
|
364
371
|
let content;
|
|
365
372
|
if (includeContent) {
|
|
366
373
|
try {
|
|
367
|
-
const contentRows = await executeParameterized(repo.id, `
|
|
368
|
-
MATCH (n {id: $nodeId})
|
|
369
|
-
RETURN n.content AS content
|
|
374
|
+
const contentRows = await executeParameterized(repo.id, `
|
|
375
|
+
MATCH (n {id: $nodeId})
|
|
376
|
+
RETURN n.content AS content
|
|
370
377
|
`, { nodeId: sym.nodeId });
|
|
371
378
|
if (contentRows.length > 0) {
|
|
372
379
|
content = contentRows[0].content ?? contentRows[0][0];
|
|
@@ -458,13 +465,13 @@ export class LocalBackend {
|
|
|
458
465
|
};
|
|
459
466
|
}
|
|
460
467
|
/**
|
|
461
|
-
* BM25 keyword search helper - uses
|
|
468
|
+
* BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
|
|
462
469
|
*/
|
|
463
470
|
async bm25Search(repo, query, limit) {
|
|
464
|
-
const {
|
|
471
|
+
const { searchFTSFromLbug } = await import('../../core/search/bm25-index.js');
|
|
465
472
|
let bm25Results;
|
|
466
473
|
try {
|
|
467
|
-
bm25Results = await
|
|
474
|
+
bm25Results = await searchFTSFromLbug(query, limit, repo.id);
|
|
468
475
|
}
|
|
469
476
|
catch (err) {
|
|
470
477
|
console.error('GitNexus: BM25/FTS search failed (FTS indexes may not exist) -', err.message);
|
|
@@ -474,11 +481,11 @@ export class LocalBackend {
|
|
|
474
481
|
for (const bm25Result of bm25Results) {
|
|
475
482
|
const fullPath = bm25Result.filePath;
|
|
476
483
|
try {
|
|
477
|
-
const symbols = await executeParameterized(repo.id, `
|
|
478
|
-
MATCH (n)
|
|
479
|
-
WHERE n.filePath = $filePath
|
|
480
|
-
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
|
|
481
|
-
LIMIT 3
|
|
484
|
+
const symbols = await executeParameterized(repo.id, `
|
|
485
|
+
MATCH (n)
|
|
486
|
+
WHERE n.filePath = $filePath
|
|
487
|
+
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
|
|
488
|
+
LIMIT 3
|
|
482
489
|
`, { filePath: fullPath });
|
|
483
490
|
if (symbols.length > 0) {
|
|
484
491
|
for (const sym of symbols) {
|
|
@@ -528,14 +535,14 @@ export class LocalBackend {
|
|
|
528
535
|
const queryVec = await embedQuery(query);
|
|
529
536
|
const dims = getEmbeddingDims();
|
|
530
537
|
const queryVecStr = `[${queryVec.join(',')}]`;
|
|
531
|
-
const vectorQuery = `
|
|
532
|
-
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx',
|
|
533
|
-
CAST(${queryVecStr} AS FLOAT[${dims}]), ${limit})
|
|
534
|
-
YIELD node AS emb, distance
|
|
535
|
-
WITH emb, distance
|
|
536
|
-
WHERE distance < 0.6
|
|
537
|
-
RETURN emb.nodeId AS nodeId, distance
|
|
538
|
-
ORDER BY distance
|
|
538
|
+
const vectorQuery = `
|
|
539
|
+
CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx',
|
|
540
|
+
CAST(${queryVecStr} AS FLOAT[${dims}]), ${limit})
|
|
541
|
+
YIELD node AS emb, distance
|
|
542
|
+
WITH emb, distance
|
|
543
|
+
WHERE distance < 0.6
|
|
544
|
+
RETURN emb.nodeId AS nodeId, distance
|
|
545
|
+
ORDER BY distance
|
|
539
546
|
`;
|
|
540
547
|
const embResults = await executeQuery(repo.id, vectorQuery);
|
|
541
548
|
if (embResults.length === 0)
|
|
@@ -582,8 +589,8 @@ export class LocalBackend {
|
|
|
582
589
|
}
|
|
583
590
|
async cypher(repo, params) {
|
|
584
591
|
await this.ensureInitialized(repo.id);
|
|
585
|
-
if (!
|
|
586
|
-
return { error: '
|
|
592
|
+
if (!isLbugReady(repo.id)) {
|
|
593
|
+
return { error: 'LadybugDB not ready. Index may be corrupted.' };
|
|
587
594
|
}
|
|
588
595
|
// Block write operations (defense-in-depth — DB is already read-only)
|
|
589
596
|
if (CYPHER_WRITE_RE.test(params.query)) {
|
|
@@ -628,7 +635,7 @@ export class LocalBackend {
|
|
|
628
635
|
/**
|
|
629
636
|
* Aggregate same-named clusters: group by heuristicLabel, sum symbols,
|
|
630
637
|
* weighted-average cohesion, filter out tiny clusters (<5 symbols).
|
|
631
|
-
* Raw communities stay intact in
|
|
638
|
+
* Raw communities stay intact in LadybugDB for Cypher queries.
|
|
632
639
|
*/
|
|
633
640
|
aggregateClusters(clusters) {
|
|
634
641
|
const groups = new Map();
|
|
@@ -675,11 +682,11 @@ export class LocalBackend {
|
|
|
675
682
|
try {
|
|
676
683
|
// Fetch more raw communities than the display limit so aggregation has enough data
|
|
677
684
|
const rawLimit = Math.max(limit * 5, 200);
|
|
678
|
-
const clusters = await executeQuery(repo.id, `
|
|
679
|
-
MATCH (c:Community)
|
|
680
|
-
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
681
|
-
ORDER BY c.symbolCount DESC
|
|
682
|
-
LIMIT ${rawLimit}
|
|
685
|
+
const clusters = await executeQuery(repo.id, `
|
|
686
|
+
MATCH (c:Community)
|
|
687
|
+
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
688
|
+
ORDER BY c.symbolCount DESC
|
|
689
|
+
LIMIT ${rawLimit}
|
|
683
690
|
`);
|
|
684
691
|
const rawClusters = clusters.map((c) => ({
|
|
685
692
|
id: c.id || c[0],
|
|
@@ -696,11 +703,11 @@ export class LocalBackend {
|
|
|
696
703
|
}
|
|
697
704
|
if (params.showProcesses !== false) {
|
|
698
705
|
try {
|
|
699
|
-
const processes = await executeQuery(repo.id, `
|
|
700
|
-
MATCH (p:Process)
|
|
701
|
-
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
702
|
-
ORDER BY p.stepCount DESC
|
|
703
|
-
LIMIT ${limit}
|
|
706
|
+
const processes = await executeQuery(repo.id, `
|
|
707
|
+
MATCH (p:Process)
|
|
708
|
+
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
709
|
+
ORDER BY p.stepCount DESC
|
|
710
|
+
LIMIT ${limit}
|
|
704
711
|
`);
|
|
705
712
|
result.processes = processes.map((p) => ({
|
|
706
713
|
id: p.id || p[0],
|
|
@@ -730,10 +737,10 @@ export class LocalBackend {
|
|
|
730
737
|
// Step 1: Find the symbol
|
|
731
738
|
let symbols;
|
|
732
739
|
if (uid) {
|
|
733
|
-
symbols = await executeParameterized(repo.id, `
|
|
734
|
-
MATCH (n {id: $uid})
|
|
735
|
-
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine${include_content ? ', n.content AS content' : ''}
|
|
736
|
-
LIMIT 1
|
|
740
|
+
symbols = await executeParameterized(repo.id, `
|
|
741
|
+
MATCH (n {id: $uid})
|
|
742
|
+
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine${include_content ? ', n.content AS content' : ''}
|
|
743
|
+
LIMIT 1
|
|
737
744
|
`, { uid });
|
|
738
745
|
}
|
|
739
746
|
else {
|
|
@@ -752,10 +759,10 @@ export class LocalBackend {
|
|
|
752
759
|
whereClause = `WHERE n.name = $symName`;
|
|
753
760
|
queryParams = { symName: name };
|
|
754
761
|
}
|
|
755
|
-
symbols = await executeParameterized(repo.id, `
|
|
756
|
-
MATCH (n) ${whereClause}
|
|
757
|
-
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine${include_content ? ', n.content AS content' : ''}
|
|
758
|
-
LIMIT 10
|
|
762
|
+
symbols = await executeParameterized(repo.id, `
|
|
763
|
+
MATCH (n) ${whereClause}
|
|
764
|
+
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine${include_content ? ', n.content AS content' : ''}
|
|
765
|
+
LIMIT 10
|
|
759
766
|
`, queryParams);
|
|
760
767
|
}
|
|
761
768
|
if (symbols.length === 0) {
|
|
@@ -779,25 +786,25 @@ export class LocalBackend {
|
|
|
779
786
|
const sym = symbols[0];
|
|
780
787
|
const symId = sym.id || sym[0];
|
|
781
788
|
// Categorized incoming refs
|
|
782
|
-
const incomingRows = await executeParameterized(repo.id, `
|
|
783
|
-
MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
|
|
784
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS']
|
|
785
|
-
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
786
|
-
LIMIT 30
|
|
789
|
+
const incomingRows = await executeParameterized(repo.id, `
|
|
790
|
+
MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
|
|
791
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS']
|
|
792
|
+
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
793
|
+
LIMIT 30
|
|
787
794
|
`, { symId });
|
|
788
795
|
// Categorized outgoing refs
|
|
789
|
-
const outgoingRows = await executeParameterized(repo.id, `
|
|
790
|
-
MATCH (n {id: $symId})-[r:CodeRelation]->(target)
|
|
791
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS']
|
|
792
|
-
RETURN r.type AS relType, target.id AS uid, target.name AS name, target.filePath AS filePath, labels(target)[0] AS kind
|
|
793
|
-
LIMIT 30
|
|
796
|
+
const outgoingRows = await executeParameterized(repo.id, `
|
|
797
|
+
MATCH (n {id: $symId})-[r:CodeRelation]->(target)
|
|
798
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS']
|
|
799
|
+
RETURN r.type AS relType, target.id AS uid, target.name AS name, target.filePath AS filePath, labels(target)[0] AS kind
|
|
800
|
+
LIMIT 30
|
|
794
801
|
`, { symId });
|
|
795
802
|
// Process participation
|
|
796
803
|
let processRows = [];
|
|
797
804
|
try {
|
|
798
|
-
processRows = await executeParameterized(repo.id, `
|
|
799
|
-
MATCH (n {id: $symId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
800
|
-
RETURN p.id AS pid, p.heuristicLabel AS label, r.step AS step, p.stepCount AS stepCount
|
|
805
|
+
processRows = await executeParameterized(repo.id, `
|
|
806
|
+
MATCH (n {id: $symId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
807
|
+
RETURN p.id AS pid, p.heuristicLabel AS label, r.step AS step, p.stepCount AS stepCount
|
|
801
808
|
`, { symId });
|
|
802
809
|
}
|
|
803
810
|
catch (e) {
|
|
@@ -852,10 +859,10 @@ export class LocalBackend {
|
|
|
852
859
|
return this.context(repo, { name });
|
|
853
860
|
}
|
|
854
861
|
if (type === 'cluster') {
|
|
855
|
-
const clusters = await executeParameterized(repo.id, `
|
|
856
|
-
MATCH (c:Community)
|
|
857
|
-
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
858
|
-
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
862
|
+
const clusters = await executeParameterized(repo.id, `
|
|
863
|
+
MATCH (c:Community)
|
|
864
|
+
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
865
|
+
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
859
866
|
`, { clusterName: name });
|
|
860
867
|
if (clusters.length === 0)
|
|
861
868
|
return { error: `Cluster '${name}' not found` };
|
|
@@ -869,11 +876,11 @@ export class LocalBackend {
|
|
|
869
876
|
totalSymbols += s;
|
|
870
877
|
weightedCohesion += (c.cohesion || 0) * s;
|
|
871
878
|
}
|
|
872
|
-
const members = await executeParameterized(repo.id, `
|
|
873
|
-
MATCH (n)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
874
|
-
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
875
|
-
RETURN DISTINCT n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
876
|
-
LIMIT 30
|
|
879
|
+
const members = await executeParameterized(repo.id, `
|
|
880
|
+
MATCH (n)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
881
|
+
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
882
|
+
RETURN DISTINCT n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
883
|
+
LIMIT 30
|
|
877
884
|
`, { clusterName: name });
|
|
878
885
|
return {
|
|
879
886
|
cluster: {
|
|
@@ -890,20 +897,20 @@ export class LocalBackend {
|
|
|
890
897
|
};
|
|
891
898
|
}
|
|
892
899
|
if (type === 'process') {
|
|
893
|
-
const processes = await executeParameterized(repo.id, `
|
|
894
|
-
MATCH (p:Process)
|
|
895
|
-
WHERE p.label = $processName OR p.heuristicLabel = $processName
|
|
896
|
-
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
897
|
-
LIMIT 1
|
|
900
|
+
const processes = await executeParameterized(repo.id, `
|
|
901
|
+
MATCH (p:Process)
|
|
902
|
+
WHERE p.label = $processName OR p.heuristicLabel = $processName
|
|
903
|
+
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
904
|
+
LIMIT 1
|
|
898
905
|
`, { processName: name });
|
|
899
906
|
if (processes.length === 0)
|
|
900
907
|
return { error: `Process '${name}' not found` };
|
|
901
908
|
const proc = processes[0];
|
|
902
909
|
const procId = proc.id || proc[0];
|
|
903
|
-
const steps = await executeParameterized(repo.id, `
|
|
904
|
-
MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p {id: $procId})
|
|
905
|
-
RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step
|
|
906
|
-
ORDER BY r.step
|
|
910
|
+
const steps = await executeParameterized(repo.id, `
|
|
911
|
+
MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p {id: $procId})
|
|
912
|
+
RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step
|
|
913
|
+
ORDER BY r.step
|
|
907
914
|
`, { procId });
|
|
908
915
|
return {
|
|
909
916
|
process: {
|
|
@@ -964,10 +971,10 @@ export class LocalBackend {
|
|
|
964
971
|
for (const file of changedFiles) {
|
|
965
972
|
const normalizedFile = file.replace(/\\/g, '/');
|
|
966
973
|
try {
|
|
967
|
-
const symbols = await executeParameterized(repo.id, `
|
|
968
|
-
MATCH (n) WHERE n.filePath CONTAINS $filePath
|
|
969
|
-
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
970
|
-
LIMIT 20
|
|
974
|
+
const symbols = await executeParameterized(repo.id, `
|
|
975
|
+
MATCH (n) WHERE n.filePath CONTAINS $filePath
|
|
976
|
+
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
977
|
+
LIMIT 20
|
|
971
978
|
`, { filePath: normalizedFile });
|
|
972
979
|
for (const sym of symbols) {
|
|
973
980
|
changedSymbols.push({
|
|
@@ -987,9 +994,9 @@ export class LocalBackend {
|
|
|
987
994
|
const affectedProcesses = new Map();
|
|
988
995
|
for (const sym of changedSymbols) {
|
|
989
996
|
try {
|
|
990
|
-
const procs = await executeParameterized(repo.id, `
|
|
991
|
-
MATCH (n {id: $nodeId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
992
|
-
RETURN p.id AS pid, p.heuristicLabel AS label, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
|
|
997
|
+
const procs = await executeParameterized(repo.id, `
|
|
998
|
+
MATCH (n {id: $nodeId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
999
|
+
RETURN p.id AS pid, p.heuristicLabel AS label, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
|
|
993
1000
|
`, { nodeId: sym.id });
|
|
994
1001
|
for (const proc of procs) {
|
|
995
1002
|
const pid = proc.pid || proc[0];
|
|
@@ -1182,6 +1189,22 @@ export class LocalBackend {
|
|
|
1182
1189
|
};
|
|
1183
1190
|
}
|
|
1184
1191
|
async impact(repo, params) {
|
|
1192
|
+
try {
|
|
1193
|
+
return await this._impactImpl(repo, params);
|
|
1194
|
+
}
|
|
1195
|
+
catch (err) {
|
|
1196
|
+
// Return structured error instead of crashing (#321)
|
|
1197
|
+
return {
|
|
1198
|
+
error: (err instanceof Error ? err.message : String(err)) || 'Impact analysis failed',
|
|
1199
|
+
target: { name: params.target },
|
|
1200
|
+
direction: params.direction,
|
|
1201
|
+
impactedCount: 0,
|
|
1202
|
+
risk: 'UNKNOWN',
|
|
1203
|
+
suggestion: 'The graph query failed — try gitnexus context <symbol> as a fallback',
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
async _impactImpl(repo, params) {
|
|
1185
1208
|
await this.ensureInitialized(repo.id);
|
|
1186
1209
|
const { target, direction } = params;
|
|
1187
1210
|
const maxDepth = params.maxDepth || 3;
|
|
@@ -1193,11 +1216,11 @@ export class LocalBackend {
|
|
|
1193
1216
|
const minConfidence = params.minConfidence ?? 0;
|
|
1194
1217
|
const relTypeFilter = relationTypes.map(t => `'${t}'`).join(', ');
|
|
1195
1218
|
const confidenceFilter = minConfidence > 0 ? ` AND r.confidence >= ${minConfidence}` : '';
|
|
1196
|
-
const targets = await executeParameterized(repo.id, `
|
|
1197
|
-
MATCH (n)
|
|
1198
|
-
WHERE n.name = $targetName
|
|
1199
|
-
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
1200
|
-
LIMIT 1
|
|
1219
|
+
const targets = await executeParameterized(repo.id, `
|
|
1220
|
+
MATCH (n)
|
|
1221
|
+
WHERE n.name = $targetName
|
|
1222
|
+
RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
1223
|
+
LIMIT 1
|
|
1201
1224
|
`, { targetName: target });
|
|
1202
1225
|
if (targets.length === 0)
|
|
1203
1226
|
return { error: `Target '${target}' not found` };
|
|
@@ -1206,6 +1229,7 @@ export class LocalBackend {
|
|
|
1206
1229
|
const impacted = [];
|
|
1207
1230
|
const visited = new Set([symId]);
|
|
1208
1231
|
let frontier = [symId];
|
|
1232
|
+
let traversalComplete = true;
|
|
1209
1233
|
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
1210
1234
|
const nextFrontier = [];
|
|
1211
1235
|
// Batch frontier nodes into a single Cypher query per depth level
|
|
@@ -1237,6 +1261,10 @@ export class LocalBackend {
|
|
|
1237
1261
|
}
|
|
1238
1262
|
catch (e) {
|
|
1239
1263
|
logQueryError('impact:depth-traversal', e);
|
|
1264
|
+
// Break out of depth loop on query failure but return partial results
|
|
1265
|
+
// collected so far, rather than silently swallowing the error (#321)
|
|
1266
|
+
traversalComplete = false;
|
|
1267
|
+
break;
|
|
1240
1268
|
}
|
|
1241
1269
|
frontier = nextFrontier;
|
|
1242
1270
|
}
|
|
@@ -1255,24 +1283,24 @@ export class LocalBackend {
|
|
|
1255
1283
|
const d1Ids = (grouped[1] || []).map((i) => `'${i.id.replace(/'/g, "''")}'`).join(', ');
|
|
1256
1284
|
// Affected processes: which execution flows are broken and at which step
|
|
1257
1285
|
const [processRows, moduleRows, directModuleRows] = await Promise.all([
|
|
1258
|
-
executeQuery(repo.id, `
|
|
1259
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
1260
|
-
WHERE s.id IN [${allIds}]
|
|
1261
|
-
RETURN p.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits, MIN(r.step) AS minStep, p.stepCount AS stepCount
|
|
1262
|
-
ORDER BY hits DESC
|
|
1263
|
-
LIMIT 20
|
|
1286
|
+
executeQuery(repo.id, `
|
|
1287
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
1288
|
+
WHERE s.id IN [${allIds}]
|
|
1289
|
+
RETURN p.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits, MIN(r.step) AS minStep, p.stepCount AS stepCount
|
|
1290
|
+
ORDER BY hits DESC
|
|
1291
|
+
LIMIT 20
|
|
1264
1292
|
`).catch(() => []),
|
|
1265
|
-
executeQuery(repo.id, `
|
|
1266
|
-
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
1267
|
-
WHERE s.id IN [${allIds}]
|
|
1268
|
-
RETURN c.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits
|
|
1269
|
-
ORDER BY hits DESC
|
|
1270
|
-
LIMIT 20
|
|
1293
|
+
executeQuery(repo.id, `
|
|
1294
|
+
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
1295
|
+
WHERE s.id IN [${allIds}]
|
|
1296
|
+
RETURN c.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits
|
|
1297
|
+
ORDER BY hits DESC
|
|
1298
|
+
LIMIT 20
|
|
1271
1299
|
`).catch(() => []),
|
|
1272
|
-
d1Ids ? executeQuery(repo.id, `
|
|
1273
|
-
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
1274
|
-
WHERE s.id IN [${d1Ids}]
|
|
1275
|
-
RETURN DISTINCT c.heuristicLabel AS name
|
|
1300
|
+
d1Ids ? executeQuery(repo.id, `
|
|
1301
|
+
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
1302
|
+
WHERE s.id IN [${d1Ids}]
|
|
1303
|
+
RETURN DISTINCT c.heuristicLabel AS name
|
|
1276
1304
|
`).catch(() => []) : Promise.resolve([]),
|
|
1277
1305
|
]);
|
|
1278
1306
|
affectedProcesses = processRows.map((r) => ({
|
|
@@ -1314,6 +1342,7 @@ export class LocalBackend {
|
|
|
1314
1342
|
direction,
|
|
1315
1343
|
impactedCount: impacted.length,
|
|
1316
1344
|
risk,
|
|
1345
|
+
...(!traversalComplete && { partial: true }),
|
|
1317
1346
|
summary: {
|
|
1318
1347
|
direct: directCount,
|
|
1319
1348
|
processes_affected: processCount,
|
|
@@ -1334,11 +1363,11 @@ export class LocalBackend {
|
|
|
1334
1363
|
await this.ensureInitialized(repo.id);
|
|
1335
1364
|
try {
|
|
1336
1365
|
const rawLimit = Math.max(limit * 5, 200);
|
|
1337
|
-
const clusters = await executeQuery(repo.id, `
|
|
1338
|
-
MATCH (c:Community)
|
|
1339
|
-
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
1340
|
-
ORDER BY c.symbolCount DESC
|
|
1341
|
-
LIMIT ${rawLimit}
|
|
1366
|
+
const clusters = await executeQuery(repo.id, `
|
|
1367
|
+
MATCH (c:Community)
|
|
1368
|
+
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
1369
|
+
ORDER BY c.symbolCount DESC
|
|
1370
|
+
LIMIT ${rawLimit}
|
|
1342
1371
|
`);
|
|
1343
1372
|
const rawClusters = clusters.map((c) => ({
|
|
1344
1373
|
id: c.id || c[0],
|
|
@@ -1361,11 +1390,11 @@ export class LocalBackend {
|
|
|
1361
1390
|
const repo = await this.resolveRepo(repoName);
|
|
1362
1391
|
await this.ensureInitialized(repo.id);
|
|
1363
1392
|
try {
|
|
1364
|
-
const processes = await executeQuery(repo.id, `
|
|
1365
|
-
MATCH (p:Process)
|
|
1366
|
-
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
1367
|
-
ORDER BY p.stepCount DESC
|
|
1368
|
-
LIMIT ${limit}
|
|
1393
|
+
const processes = await executeQuery(repo.id, `
|
|
1394
|
+
MATCH (p:Process)
|
|
1395
|
+
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
1396
|
+
ORDER BY p.stepCount DESC
|
|
1397
|
+
LIMIT ${limit}
|
|
1369
1398
|
`);
|
|
1370
1399
|
return {
|
|
1371
1400
|
processes: processes.map((p) => ({
|
|
@@ -1388,10 +1417,10 @@ export class LocalBackend {
|
|
|
1388
1417
|
async queryClusterDetail(name, repoName) {
|
|
1389
1418
|
const repo = await this.resolveRepo(repoName);
|
|
1390
1419
|
await this.ensureInitialized(repo.id);
|
|
1391
|
-
const clusters = await executeParameterized(repo.id, `
|
|
1392
|
-
MATCH (c:Community)
|
|
1393
|
-
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
1394
|
-
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
1420
|
+
const clusters = await executeParameterized(repo.id, `
|
|
1421
|
+
MATCH (c:Community)
|
|
1422
|
+
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
1423
|
+
RETURN c.id AS id, c.label AS label, c.heuristicLabel AS heuristicLabel, c.cohesion AS cohesion, c.symbolCount AS symbolCount
|
|
1395
1424
|
`, { clusterName: name });
|
|
1396
1425
|
if (clusters.length === 0)
|
|
1397
1426
|
return { error: `Cluster '${name}' not found` };
|
|
@@ -1405,11 +1434,11 @@ export class LocalBackend {
|
|
|
1405
1434
|
totalSymbols += s;
|
|
1406
1435
|
weightedCohesion += (c.cohesion || 0) * s;
|
|
1407
1436
|
}
|
|
1408
|
-
const members = await executeParameterized(repo.id, `
|
|
1409
|
-
MATCH (n)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
1410
|
-
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
1411
|
-
RETURN DISTINCT n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
1412
|
-
LIMIT 30
|
|
1437
|
+
const members = await executeParameterized(repo.id, `
|
|
1438
|
+
MATCH (n)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
1439
|
+
WHERE c.label = $clusterName OR c.heuristicLabel = $clusterName
|
|
1440
|
+
RETURN DISTINCT n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
|
|
1441
|
+
LIMIT 30
|
|
1413
1442
|
`, { clusterName: name });
|
|
1414
1443
|
return {
|
|
1415
1444
|
cluster: {
|
|
@@ -1432,20 +1461,20 @@ export class LocalBackend {
|
|
|
1432
1461
|
async queryProcessDetail(name, repoName) {
|
|
1433
1462
|
const repo = await this.resolveRepo(repoName);
|
|
1434
1463
|
await this.ensureInitialized(repo.id);
|
|
1435
|
-
const processes = await executeParameterized(repo.id, `
|
|
1436
|
-
MATCH (p:Process)
|
|
1437
|
-
WHERE p.label = $processName OR p.heuristicLabel = $processName
|
|
1438
|
-
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
1439
|
-
LIMIT 1
|
|
1464
|
+
const processes = await executeParameterized(repo.id, `
|
|
1465
|
+
MATCH (p:Process)
|
|
1466
|
+
WHERE p.label = $processName OR p.heuristicLabel = $processName
|
|
1467
|
+
RETURN p.id AS id, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount
|
|
1468
|
+
LIMIT 1
|
|
1440
1469
|
`, { processName: name });
|
|
1441
1470
|
if (processes.length === 0)
|
|
1442
1471
|
return { error: `Process '${name}' not found` };
|
|
1443
1472
|
const proc = processes[0];
|
|
1444
1473
|
const procId = proc.id || proc[0];
|
|
1445
|
-
const steps = await executeParameterized(repo.id, `
|
|
1446
|
-
MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p {id: $procId})
|
|
1447
|
-
RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step
|
|
1448
|
-
ORDER BY r.step
|
|
1474
|
+
const steps = await executeParameterized(repo.id, `
|
|
1475
|
+
MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p {id: $procId})
|
|
1476
|
+
RETURN n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, r.step AS step
|
|
1477
|
+
ORDER BY r.step
|
|
1449
1478
|
`, { procId });
|
|
1450
1479
|
return {
|
|
1451
1480
|
process: {
|
|
@@ -1458,7 +1487,7 @@ export class LocalBackend {
|
|
|
1458
1487
|
};
|
|
1459
1488
|
}
|
|
1460
1489
|
async disconnect() {
|
|
1461
|
-
await
|
|
1490
|
+
await closeLbug(); // close all connections
|
|
1462
1491
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
1463
1492
|
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs,
|
|
1464
1493
|
// and importing the embedder module on Node v24+ crashes if onnxruntime
|