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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Graph Queries for Wiki Generation
|
|
3
3
|
*
|
|
4
4
|
* Encapsulated Cypher queries against the GitNexus knowledge graph.
|
|
5
|
-
* Uses the MCP-style pooled
|
|
5
|
+
* Uses the MCP-style pooled lbug-adapter for connection management.
|
|
6
6
|
*/
|
|
7
7
|
export interface FileWithExports {
|
|
8
8
|
filePath: string;
|
|
@@ -29,36 +29,12 @@ export interface ProcessInfo {
|
|
|
29
29
|
type: string;
|
|
30
30
|
}>;
|
|
31
31
|
}
|
|
32
|
-
export interface CommunityFileGroup {
|
|
33
|
-
communityId: string;
|
|
34
|
-
label: string;
|
|
35
|
-
keywords: string[];
|
|
36
|
-
description: string;
|
|
37
|
-
cohesion: number;
|
|
38
|
-
symbolCount: number;
|
|
39
|
-
files: string[];
|
|
40
|
-
secondaryFiles: string[];
|
|
41
|
-
}
|
|
42
|
-
export interface InterCommunityEdge {
|
|
43
|
-
fromLabel: string;
|
|
44
|
-
toLabel: string;
|
|
45
|
-
callCount: number;
|
|
46
|
-
sampleCalls: Array<{
|
|
47
|
-
fromName: string;
|
|
48
|
-
toName: string;
|
|
49
|
-
}>;
|
|
50
|
-
}
|
|
51
|
-
export interface CrossCommunityProcess {
|
|
52
|
-
label: string;
|
|
53
|
-
communities: string[];
|
|
54
|
-
stepCount: number;
|
|
55
|
-
}
|
|
56
32
|
/**
|
|
57
|
-
* Initialize the
|
|
33
|
+
* Initialize the LadybugDB connection for wiki generation.
|
|
58
34
|
*/
|
|
59
|
-
export declare function initWikiDb(
|
|
35
|
+
export declare function initWikiDb(lbugPath: string): Promise<void>;
|
|
60
36
|
/**
|
|
61
|
-
* Close the
|
|
37
|
+
* Close the LadybugDB connection.
|
|
62
38
|
*/
|
|
63
39
|
export declare function closeWikiDb(): Promise<void>;
|
|
64
40
|
/**
|
|
@@ -84,11 +60,6 @@ export declare function getInterModuleCallEdges(filePaths: string[]): Promise<{
|
|
|
84
60
|
outgoing: CallEdge[];
|
|
85
61
|
incoming: CallEdge[];
|
|
86
62
|
}>;
|
|
87
|
-
/**
|
|
88
|
-
* Get files that are call-graph neighbors of the given files but not in the set.
|
|
89
|
-
* Used to assign new files to existing modules during incremental updates.
|
|
90
|
-
*/
|
|
91
|
-
export declare function getCallGraphNeighborFiles(filePaths: string[]): Promise<string[]>;
|
|
92
63
|
/**
|
|
93
64
|
* Get processes (execution flows) that pass through a set of files.
|
|
94
65
|
* Returns top N by step count.
|
|
@@ -107,16 +78,3 @@ export declare function getInterModuleEdgesForOverview(moduleFiles: Record<strin
|
|
|
107
78
|
to: string;
|
|
108
79
|
count: number;
|
|
109
80
|
}>>;
|
|
110
|
-
/**
|
|
111
|
-
* Get community-to-file mapping from Leiden-detected clusters.
|
|
112
|
-
* Each file is assigned to its majority community; secondary memberships tracked separately.
|
|
113
|
-
*/
|
|
114
|
-
export declare function getCommunityFileMapping(): Promise<CommunityFileGroup[]>;
|
|
115
|
-
/**
|
|
116
|
-
* Get call edges between communities for inter-module coupling analysis.
|
|
117
|
-
*/
|
|
118
|
-
export declare function getInterCommunityCallEdges(): Promise<InterCommunityEdge[]>;
|
|
119
|
-
/**
|
|
120
|
-
* Get cross-community execution flows.
|
|
121
|
-
*/
|
|
122
|
-
export declare function getCrossCommunityProcesses(): Promise<CrossCommunityProcess[]>;
|
|
@@ -2,31 +2,31 @@
|
|
|
2
2
|
* Graph Queries for Wiki Generation
|
|
3
3
|
*
|
|
4
4
|
* Encapsulated Cypher queries against the GitNexus knowledge graph.
|
|
5
|
-
* Uses the MCP-style pooled
|
|
5
|
+
* Uses the MCP-style pooled lbug-adapter for connection management.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { initLbug, executeQuery, closeLbug } from '../../mcp/core/lbug-adapter.js';
|
|
8
8
|
const REPO_ID = '__wiki__';
|
|
9
9
|
/**
|
|
10
|
-
* Initialize the
|
|
10
|
+
* Initialize the LadybugDB connection for wiki generation.
|
|
11
11
|
*/
|
|
12
|
-
export async function initWikiDb(
|
|
13
|
-
await
|
|
12
|
+
export async function initWikiDb(lbugPath) {
|
|
13
|
+
await initLbug(REPO_ID, lbugPath);
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Close the
|
|
16
|
+
* Close the LadybugDB connection.
|
|
17
17
|
*/
|
|
18
18
|
export async function closeWikiDb() {
|
|
19
|
-
await
|
|
19
|
+
await closeLbug(REPO_ID);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Get all source files with their exported symbol names and types.
|
|
23
23
|
*/
|
|
24
24
|
export async function getFilesWithExports() {
|
|
25
|
-
const rows = await executeQuery(REPO_ID, `
|
|
26
|
-
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
|
|
27
|
-
WHERE n.isExported = true
|
|
28
|
-
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
|
29
|
-
ORDER BY f.filePath
|
|
25
|
+
const rows = await executeQuery(REPO_ID, `
|
|
26
|
+
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
|
|
27
|
+
WHERE n.isExported = true
|
|
28
|
+
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
|
29
|
+
ORDER BY f.filePath
|
|
30
30
|
`);
|
|
31
31
|
const fileMap = new Map();
|
|
32
32
|
for (const row of rows) {
|
|
@@ -46,10 +46,10 @@ export async function getFilesWithExports() {
|
|
|
46
46
|
* Get all files tracked in the graph (including those with no exports).
|
|
47
47
|
*/
|
|
48
48
|
export async function getAllFiles() {
|
|
49
|
-
const rows = await executeQuery(REPO_ID, `
|
|
50
|
-
MATCH (f:File)
|
|
51
|
-
RETURN f.filePath AS filePath
|
|
52
|
-
ORDER BY f.filePath
|
|
49
|
+
const rows = await executeQuery(REPO_ID, `
|
|
50
|
+
MATCH (f:File)
|
|
51
|
+
RETURN f.filePath AS filePath
|
|
52
|
+
ORDER BY f.filePath
|
|
53
53
|
`);
|
|
54
54
|
return rows.map(r => r.filePath || r[0]);
|
|
55
55
|
}
|
|
@@ -57,11 +57,11 @@ export async function getAllFiles() {
|
|
|
57
57
|
* Get inter-file call edges (calls between different files).
|
|
58
58
|
*/
|
|
59
59
|
export async function getInterFileCallEdges() {
|
|
60
|
-
const rows = await executeQuery(REPO_ID, `
|
|
61
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
62
|
-
WHERE a.filePath <> b.filePath
|
|
63
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
64
|
-
b.filePath AS toFile, b.name AS toName
|
|
60
|
+
const rows = await executeQuery(REPO_ID, `
|
|
61
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
62
|
+
WHERE a.filePath <> b.filePath
|
|
63
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
64
|
+
b.filePath AS toFile, b.name AS toName
|
|
65
65
|
`);
|
|
66
66
|
return rows.map(r => ({
|
|
67
67
|
fromFile: r.fromFile || r[0],
|
|
@@ -77,11 +77,11 @@ export async function getIntraModuleCallEdges(filePaths) {
|
|
|
77
77
|
if (filePaths.length === 0)
|
|
78
78
|
return [];
|
|
79
79
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
80
|
-
const rows = await executeQuery(REPO_ID, `
|
|
81
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
82
|
-
WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
83
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
84
|
-
b.filePath AS toFile, b.name AS toName
|
|
80
|
+
const rows = await executeQuery(REPO_ID, `
|
|
81
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
82
|
+
WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
83
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
84
|
+
b.filePath AS toFile, b.name AS toName
|
|
85
85
|
`);
|
|
86
86
|
return rows.map(r => ({
|
|
87
87
|
fromFile: r.fromFile || r[0],
|
|
@@ -97,19 +97,19 @@ export async function getInterModuleCallEdges(filePaths) {
|
|
|
97
97
|
if (filePaths.length === 0)
|
|
98
98
|
return { outgoing: [], incoming: [] };
|
|
99
99
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
100
|
-
const outRows = await executeQuery(REPO_ID, `
|
|
101
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
102
|
-
WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
|
|
103
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
104
|
-
b.filePath AS toFile, b.name AS toName
|
|
105
|
-
LIMIT 30
|
|
100
|
+
const outRows = await executeQuery(REPO_ID, `
|
|
101
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
102
|
+
WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
|
|
103
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
104
|
+
b.filePath AS toFile, b.name AS toName
|
|
105
|
+
LIMIT 30
|
|
106
106
|
`);
|
|
107
|
-
const inRows = await executeQuery(REPO_ID, `
|
|
108
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
109
|
-
WHERE NOT a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
110
|
-
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
111
|
-
b.filePath AS toFile, b.name AS toName
|
|
112
|
-
LIMIT 30
|
|
107
|
+
const inRows = await executeQuery(REPO_ID, `
|
|
108
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
109
|
+
WHERE NOT a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
110
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
111
|
+
b.filePath AS toFile, b.name AS toName
|
|
112
|
+
LIMIT 30
|
|
113
113
|
`);
|
|
114
114
|
return {
|
|
115
115
|
outgoing: outRows.map(r => ({
|
|
@@ -126,50 +126,6 @@ export async function getInterModuleCallEdges(filePaths) {
|
|
|
126
126
|
})),
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Get files that are call-graph neighbors of the given files but not in the set.
|
|
131
|
-
* Used to assign new files to existing modules during incremental updates.
|
|
132
|
-
*/
|
|
133
|
-
export async function getCallGraphNeighborFiles(filePaths) {
|
|
134
|
-
if (filePaths.length === 0)
|
|
135
|
-
return [];
|
|
136
|
-
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
137
|
-
const rows = await executeQuery(REPO_ID, `
|
|
138
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]-(b)
|
|
139
|
-
WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
|
|
140
|
-
RETURN DISTINCT b.filePath AS neighborFile
|
|
141
|
-
`);
|
|
142
|
-
return rows.map(r => r.neighborFile || r[0]);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Shared helper: group step rows by process ID.
|
|
146
|
-
*/
|
|
147
|
-
function groupStepsByProcess(procRows, stepRows) {
|
|
148
|
-
const stepsByProc = new Map();
|
|
149
|
-
for (const s of stepRows) {
|
|
150
|
-
const procId = s.processId || s[4];
|
|
151
|
-
if (!stepsByProc.has(procId))
|
|
152
|
-
stepsByProc.set(procId, []);
|
|
153
|
-
stepsByProc.get(procId).push({
|
|
154
|
-
step: s.step || s[3] || 0,
|
|
155
|
-
name: s.name || s[0],
|
|
156
|
-
filePath: s.filePath || s[1],
|
|
157
|
-
type: s.type || s[2],
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
return procRows.map(row => {
|
|
161
|
-
const procId = row.id || row[0];
|
|
162
|
-
const steps = stepsByProc.get(procId) || [];
|
|
163
|
-
steps.sort((a, b) => a.step - b.step);
|
|
164
|
-
return {
|
|
165
|
-
id: procId,
|
|
166
|
-
label: row.label || row[1] || procId,
|
|
167
|
-
type: row.type || row[2] || 'unknown',
|
|
168
|
-
stepCount: row.stepCount || row[3] || 0,
|
|
169
|
-
steps,
|
|
170
|
-
};
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
129
|
/**
|
|
174
130
|
* Get processes (execution flows) that pass through a set of files.
|
|
175
131
|
* Returns top N by step count.
|
|
@@ -178,57 +134,78 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
178
134
|
if (filePaths.length === 0)
|
|
179
135
|
return [];
|
|
180
136
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
181
|
-
//
|
|
182
|
-
const procRows = await executeQuery(REPO_ID, `
|
|
183
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
184
|
-
WHERE s.filePath IN [${fileList}]
|
|
185
|
-
RETURN DISTINCT p.id AS id, p.heuristicLabel AS label,
|
|
186
|
-
p.processType AS type, p.stepCount AS stepCount
|
|
187
|
-
ORDER BY stepCount DESC
|
|
188
|
-
LIMIT ${limit}
|
|
137
|
+
// Find processes that have steps in the given files
|
|
138
|
+
const procRows = await executeQuery(REPO_ID, `
|
|
139
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
140
|
+
WHERE s.filePath IN [${fileList}]
|
|
141
|
+
RETURN DISTINCT p.id AS id, p.heuristicLabel AS label,
|
|
142
|
+
p.processType AS type, p.stepCount AS stepCount
|
|
143
|
+
ORDER BY stepCount DESC
|
|
144
|
+
LIMIT ${limit}
|
|
189
145
|
`);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
146
|
+
const processes = [];
|
|
147
|
+
for (const row of procRows) {
|
|
148
|
+
const procId = row.id || row[0];
|
|
149
|
+
const label = row.label || row[1] || procId;
|
|
150
|
+
const type = row.type || row[2] || 'unknown';
|
|
151
|
+
const stepCount = row.stepCount || row[3] || 0;
|
|
152
|
+
// Get the full step trace for this process
|
|
153
|
+
const stepRows = await executeQuery(REPO_ID, `
|
|
154
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
155
|
+
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
156
|
+
ORDER BY r.step
|
|
157
|
+
`);
|
|
158
|
+
processes.push({
|
|
159
|
+
id: procId,
|
|
160
|
+
label,
|
|
161
|
+
type,
|
|
162
|
+
stepCount,
|
|
163
|
+
steps: stepRows.map(s => ({
|
|
164
|
+
step: s.step || s[3] || 0,
|
|
165
|
+
name: s.name || s[0],
|
|
166
|
+
filePath: s.filePath || s[1],
|
|
167
|
+
type: s.type || s[2],
|
|
168
|
+
})),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return processes;
|
|
205
172
|
}
|
|
206
173
|
/**
|
|
207
174
|
* Get all processes in the graph (for overview page).
|
|
208
175
|
*/
|
|
209
176
|
export async function getAllProcesses(limit = 20) {
|
|
210
|
-
const procRows = await executeQuery(REPO_ID, `
|
|
211
|
-
MATCH (p:Process)
|
|
212
|
-
RETURN p.id AS id, p.heuristicLabel AS label,
|
|
213
|
-
p.processType AS type, p.stepCount AS stepCount
|
|
214
|
-
ORDER BY stepCount DESC
|
|
215
|
-
LIMIT ${limit}
|
|
216
|
-
`);
|
|
217
|
-
if (procRows.length === 0)
|
|
218
|
-
return [];
|
|
219
|
-
// Batch fetch all steps for all matched processes
|
|
220
|
-
const procIdList = procRows.map(r => {
|
|
221
|
-
const id = r.id || r[0];
|
|
222
|
-
return `'${id.replace(/'/g, "''")}'`;
|
|
223
|
-
}).join(', ');
|
|
224
|
-
const stepRows = await executeQuery(REPO_ID, `
|
|
225
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
226
|
-
WHERE p.id IN [${procIdList}]
|
|
227
|
-
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type,
|
|
228
|
-
r.step AS step, p.id AS processId
|
|
229
|
-
ORDER BY p.id, r.step
|
|
177
|
+
const procRows = await executeQuery(REPO_ID, `
|
|
178
|
+
MATCH (p:Process)
|
|
179
|
+
RETURN p.id AS id, p.heuristicLabel AS label,
|
|
180
|
+
p.processType AS type, p.stepCount AS stepCount
|
|
181
|
+
ORDER BY stepCount DESC
|
|
182
|
+
LIMIT ${limit}
|
|
230
183
|
`);
|
|
231
|
-
|
|
184
|
+
const processes = [];
|
|
185
|
+
for (const row of procRows) {
|
|
186
|
+
const procId = row.id || row[0];
|
|
187
|
+
const label = row.label || row[1] || procId;
|
|
188
|
+
const type = row.type || row[2] || 'unknown';
|
|
189
|
+
const stepCount = row.stepCount || row[3] || 0;
|
|
190
|
+
const stepRows = await executeQuery(REPO_ID, `
|
|
191
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
192
|
+
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
193
|
+
ORDER BY r.step
|
|
194
|
+
`);
|
|
195
|
+
processes.push({
|
|
196
|
+
id: procId,
|
|
197
|
+
label,
|
|
198
|
+
type,
|
|
199
|
+
stepCount,
|
|
200
|
+
steps: stepRows.map(s => ({
|
|
201
|
+
step: s.step || s[3] || 0,
|
|
202
|
+
name: s.name || s[0],
|
|
203
|
+
filePath: s.filePath || s[1],
|
|
204
|
+
type: s.type || s[2],
|
|
205
|
+
})),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return processes;
|
|
232
209
|
}
|
|
233
210
|
/**
|
|
234
211
|
* Get inter-module edges for overview architecture diagram.
|
|
@@ -259,159 +236,3 @@ export async function getInterModuleEdgesForOverview(moduleFiles) {
|
|
|
259
236
|
})
|
|
260
237
|
.sort((a, b) => b.count - a.count);
|
|
261
238
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Get community-to-file mapping from Leiden-detected clusters.
|
|
264
|
-
* Each file is assigned to its majority community; secondary memberships tracked separately.
|
|
265
|
-
*/
|
|
266
|
-
export async function getCommunityFileMapping() {
|
|
267
|
-
// Query 1: Fetch all Community nodes
|
|
268
|
-
const communityRows = await executeQuery(REPO_ID, `
|
|
269
|
-
MATCH (c:Community)
|
|
270
|
-
RETURN c.id AS id, c.heuristicLabel AS label, c.keywords AS keywords,
|
|
271
|
-
c.description AS description, c.cohesion AS cohesion,
|
|
272
|
-
c.symbolCount AS symbolCount
|
|
273
|
-
ORDER BY c.symbolCount DESC
|
|
274
|
-
`);
|
|
275
|
-
if (communityRows.length === 0)
|
|
276
|
-
return [];
|
|
277
|
-
// Query 2: Fetch all MEMBER_OF edges with file paths
|
|
278
|
-
const memberRows = await executeQuery(REPO_ID, `
|
|
279
|
-
MATCH (n)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
280
|
-
WHERE n.filePath IS NOT NULL
|
|
281
|
-
RETURN n.filePath AS filePath, c.id AS communityId
|
|
282
|
-
`);
|
|
283
|
-
// Build file -> community count map
|
|
284
|
-
const fileCommunityCount = new Map();
|
|
285
|
-
for (const row of memberRows) {
|
|
286
|
-
const fp = row.filePath || row[0];
|
|
287
|
-
const cId = row.communityId || row[1];
|
|
288
|
-
if (!fileCommunityCount.has(fp))
|
|
289
|
-
fileCommunityCount.set(fp, new Map());
|
|
290
|
-
const counts = fileCommunityCount.get(fp);
|
|
291
|
-
counts.set(cId, (counts.get(cId) || 0) + 1);
|
|
292
|
-
}
|
|
293
|
-
// Assign each file to majority community
|
|
294
|
-
const communityFiles = new Map();
|
|
295
|
-
for (const row of communityRows) {
|
|
296
|
-
const cId = row.id || row[0];
|
|
297
|
-
communityFiles.set(cId, { primary: [], secondary: [] });
|
|
298
|
-
}
|
|
299
|
-
for (const [fp, counts] of fileCommunityCount) {
|
|
300
|
-
let maxCount = 0;
|
|
301
|
-
let maxCommunity = '';
|
|
302
|
-
for (const [cId, count] of counts) {
|
|
303
|
-
if (count > maxCount) {
|
|
304
|
-
maxCount = count;
|
|
305
|
-
maxCommunity = cId;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (maxCommunity && communityFiles.has(maxCommunity)) {
|
|
309
|
-
communityFiles.get(maxCommunity).primary.push(fp);
|
|
310
|
-
}
|
|
311
|
-
// Track secondary memberships
|
|
312
|
-
for (const [cId] of counts) {
|
|
313
|
-
if (cId !== maxCommunity && communityFiles.has(cId)) {
|
|
314
|
-
communityFiles.get(cId).secondary.push(fp);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
// Build result, merge tiny communities into "Other"
|
|
319
|
-
const results = [];
|
|
320
|
-
const otherFiles = [];
|
|
321
|
-
const otherSecondary = [];
|
|
322
|
-
for (const row of communityRows) {
|
|
323
|
-
const cId = row.id || row[0];
|
|
324
|
-
const entry = communityFiles.get(cId);
|
|
325
|
-
if (!entry)
|
|
326
|
-
continue;
|
|
327
|
-
if (entry.primary.length < 2) {
|
|
328
|
-
otherFiles.push(...entry.primary);
|
|
329
|
-
otherSecondary.push(...entry.secondary);
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
const keywords = row.keywords || row[2];
|
|
333
|
-
results.push({
|
|
334
|
-
communityId: cId,
|
|
335
|
-
label: row.label || row[1] || cId,
|
|
336
|
-
keywords: Array.isArray(keywords) ? keywords : typeof keywords === 'string' ? keywords.split(',').map((k) => k.trim()) : [],
|
|
337
|
-
description: row.description || row[3] || '',
|
|
338
|
-
cohesion: row.cohesion || row[4] || 0,
|
|
339
|
-
symbolCount: row.symbolCount || row[5] || 0,
|
|
340
|
-
files: entry.primary,
|
|
341
|
-
secondaryFiles: entry.secondary,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
// Add "Other" bucket if non-empty
|
|
345
|
-
if (otherFiles.length > 0) {
|
|
346
|
-
results.push({
|
|
347
|
-
communityId: '__other__',
|
|
348
|
-
label: 'Other small clusters',
|
|
349
|
-
keywords: [],
|
|
350
|
-
description: 'Files from small communities merged together',
|
|
351
|
-
cohesion: 0,
|
|
352
|
-
symbolCount: 0,
|
|
353
|
-
files: otherFiles,
|
|
354
|
-
secondaryFiles: otherSecondary,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
// Cap at 30 communities
|
|
358
|
-
return results.slice(0, 30);
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Get call edges between communities for inter-module coupling analysis.
|
|
362
|
-
*/
|
|
363
|
-
export async function getInterCommunityCallEdges() {
|
|
364
|
-
const rows = await executeQuery(REPO_ID, `
|
|
365
|
-
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b),
|
|
366
|
-
(a)-[:CodeRelation {type: 'MEMBER_OF'}]->(ca:Community),
|
|
367
|
-
(b)-[:CodeRelation {type: 'MEMBER_OF'}]->(cb:Community)
|
|
368
|
-
WHERE ca.id <> cb.id
|
|
369
|
-
RETURN ca.heuristicLabel AS fromLabel, cb.heuristicLabel AS toLabel,
|
|
370
|
-
a.name AS fromName, b.name AS toName
|
|
371
|
-
`);
|
|
372
|
-
// Aggregate in JS
|
|
373
|
-
const edgeMap = new Map();
|
|
374
|
-
for (const row of rows) {
|
|
375
|
-
const from = row.fromLabel || row[0];
|
|
376
|
-
const to = row.toLabel || row[1];
|
|
377
|
-
const key = `${from}|||${to}`;
|
|
378
|
-
if (!edgeMap.has(key))
|
|
379
|
-
edgeMap.set(key, { count: 0, samples: [] });
|
|
380
|
-
const entry = edgeMap.get(key);
|
|
381
|
-
entry.count++;
|
|
382
|
-
if (entry.samples.length < 3) {
|
|
383
|
-
entry.samples.push({
|
|
384
|
-
fromName: row.fromName || row[2],
|
|
385
|
-
toName: row.toName || row[3],
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
const results = [];
|
|
390
|
-
for (const [key, val] of edgeMap) {
|
|
391
|
-
const [fromLabel, toLabel] = key.split('|||');
|
|
392
|
-
results.push({ fromLabel, toLabel, callCount: val.count, sampleCalls: val.samples });
|
|
393
|
-
}
|
|
394
|
-
return results
|
|
395
|
-
.sort((a, b) => b.callCount - a.callCount)
|
|
396
|
-
.slice(0, 40);
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Get cross-community execution flows.
|
|
400
|
-
*/
|
|
401
|
-
export async function getCrossCommunityProcesses() {
|
|
402
|
-
const rows = await executeQuery(REPO_ID, `
|
|
403
|
-
MATCH (p:Process)
|
|
404
|
-
WHERE p.processType = 'cross_community'
|
|
405
|
-
RETURN p.heuristicLabel AS label, p.communities AS communities, p.stepCount AS stepCount
|
|
406
|
-
ORDER BY p.stepCount DESC
|
|
407
|
-
LIMIT 15
|
|
408
|
-
`);
|
|
409
|
-
return rows.map(r => {
|
|
410
|
-
const communities = r.communities || r[1];
|
|
411
|
-
return {
|
|
412
|
-
label: r.label || r[0] || 'Unknown',
|
|
413
|
-
communities: Array.isArray(communities) ? communities : typeof communities === 'string' ? communities.split(',').map((c) => c.trim()) : [],
|
|
414
|
-
stepCount: r.stepCount || r[2] || 0,
|
|
415
|
-
};
|
|
416
|
-
});
|
|
417
|
-
}
|