gitnexus 1.3.9 → 1.3.10
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 +194 -194
- package/dist/cli/ai-context.js +87 -87
- package/dist/cli/index.js +15 -25
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/core/augmentation/engine.js +20 -20
- package/dist/core/embeddings/embedding-pipeline.js +26 -26
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/pipeline.js +8 -0
- package/dist/core/ingestion/tree-sitter-queries.js +484 -484
- package/dist/core/kuzu/kuzu-adapter.js +9 -9
- package/dist/core/kuzu/schema.js +287 -287
- package/dist/core/search/bm25-index.js +5 -5
- package/dist/core/search/hybrid-search.js +3 -3
- package/dist/core/wiki/graph-queries.js +52 -52
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/prompts.js +82 -82
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/local/local-backend.js +128 -128
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +18 -18
- package/dist/mcp/tools.js +86 -86
- package/hooks/claude/gitnexus-hook.cjs +155 -155
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +96 -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
|
@@ -335,9 +335,9 @@ export class LocalBackend {
|
|
|
335
335
|
// Find processes this symbol participates in
|
|
336
336
|
let processRows = [];
|
|
337
337
|
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
|
|
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
|
|
341
341
|
`, { nodeId: sym.nodeId });
|
|
342
342
|
}
|
|
343
343
|
catch (e) {
|
|
@@ -347,10 +347,10 @@ export class LocalBackend {
|
|
|
347
347
|
let cohesion = 0;
|
|
348
348
|
let module;
|
|
349
349
|
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
|
|
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
|
|
354
354
|
`, { nodeId: sym.nodeId });
|
|
355
355
|
if (cohesionRows.length > 0) {
|
|
356
356
|
cohesion = (cohesionRows[0].cohesion ?? cohesionRows[0][0]) || 0;
|
|
@@ -364,9 +364,9 @@ export class LocalBackend {
|
|
|
364
364
|
let content;
|
|
365
365
|
if (includeContent) {
|
|
366
366
|
try {
|
|
367
|
-
const contentRows = await executeParameterized(repo.id, `
|
|
368
|
-
MATCH (n {id: $nodeId})
|
|
369
|
-
RETURN n.content AS content
|
|
367
|
+
const contentRows = await executeParameterized(repo.id, `
|
|
368
|
+
MATCH (n {id: $nodeId})
|
|
369
|
+
RETURN n.content AS content
|
|
370
370
|
`, { nodeId: sym.nodeId });
|
|
371
371
|
if (contentRows.length > 0) {
|
|
372
372
|
content = contentRows[0].content ?? contentRows[0][0];
|
|
@@ -474,11 +474,11 @@ export class LocalBackend {
|
|
|
474
474
|
for (const bm25Result of bm25Results) {
|
|
475
475
|
const fullPath = bm25Result.filePath;
|
|
476
476
|
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
|
|
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
|
|
482
482
|
`, { filePath: fullPath });
|
|
483
483
|
if (symbols.length > 0) {
|
|
484
484
|
for (const sym of symbols) {
|
|
@@ -528,14 +528,14 @@ export class LocalBackend {
|
|
|
528
528
|
const queryVec = await embedQuery(query);
|
|
529
529
|
const dims = getEmbeddingDims();
|
|
530
530
|
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
|
|
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
|
|
539
539
|
`;
|
|
540
540
|
const embResults = await executeQuery(repo.id, vectorQuery);
|
|
541
541
|
if (embResults.length === 0)
|
|
@@ -675,11 +675,11 @@ export class LocalBackend {
|
|
|
675
675
|
try {
|
|
676
676
|
// Fetch more raw communities than the display limit so aggregation has enough data
|
|
677
677
|
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}
|
|
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}
|
|
683
683
|
`);
|
|
684
684
|
const rawClusters = clusters.map((c) => ({
|
|
685
685
|
id: c.id || c[0],
|
|
@@ -696,11 +696,11 @@ export class LocalBackend {
|
|
|
696
696
|
}
|
|
697
697
|
if (params.showProcesses !== false) {
|
|
698
698
|
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}
|
|
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}
|
|
704
704
|
`);
|
|
705
705
|
result.processes = processes.map((p) => ({
|
|
706
706
|
id: p.id || p[0],
|
|
@@ -730,10 +730,10 @@ export class LocalBackend {
|
|
|
730
730
|
// Step 1: Find the symbol
|
|
731
731
|
let symbols;
|
|
732
732
|
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
|
|
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
|
|
737
737
|
`, { uid });
|
|
738
738
|
}
|
|
739
739
|
else {
|
|
@@ -752,10 +752,10 @@ export class LocalBackend {
|
|
|
752
752
|
whereClause = `WHERE n.name = $symName`;
|
|
753
753
|
queryParams = { symName: name };
|
|
754
754
|
}
|
|
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
|
|
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
|
|
759
759
|
`, queryParams);
|
|
760
760
|
}
|
|
761
761
|
if (symbols.length === 0) {
|
|
@@ -779,25 +779,25 @@ export class LocalBackend {
|
|
|
779
779
|
const sym = symbols[0];
|
|
780
780
|
const symId = sym.id || sym[0];
|
|
781
781
|
// 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
|
|
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
|
|
787
787
|
`, { symId });
|
|
788
788
|
// 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
|
|
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
|
|
794
794
|
`, { symId });
|
|
795
795
|
// Process participation
|
|
796
796
|
let processRows = [];
|
|
797
797
|
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
|
|
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
|
|
801
801
|
`, { symId });
|
|
802
802
|
}
|
|
803
803
|
catch (e) {
|
|
@@ -852,10 +852,10 @@ export class LocalBackend {
|
|
|
852
852
|
return this.context(repo, { name });
|
|
853
853
|
}
|
|
854
854
|
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
|
|
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
|
|
859
859
|
`, { clusterName: name });
|
|
860
860
|
if (clusters.length === 0)
|
|
861
861
|
return { error: `Cluster '${name}' not found` };
|
|
@@ -869,11 +869,11 @@ export class LocalBackend {
|
|
|
869
869
|
totalSymbols += s;
|
|
870
870
|
weightedCohesion += (c.cohesion || 0) * s;
|
|
871
871
|
}
|
|
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
|
|
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
|
|
877
877
|
`, { clusterName: name });
|
|
878
878
|
return {
|
|
879
879
|
cluster: {
|
|
@@ -890,20 +890,20 @@ export class LocalBackend {
|
|
|
890
890
|
};
|
|
891
891
|
}
|
|
892
892
|
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
|
|
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
|
|
898
898
|
`, { processName: name });
|
|
899
899
|
if (processes.length === 0)
|
|
900
900
|
return { error: `Process '${name}' not found` };
|
|
901
901
|
const proc = processes[0];
|
|
902
902
|
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
|
|
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
|
|
907
907
|
`, { procId });
|
|
908
908
|
return {
|
|
909
909
|
process: {
|
|
@@ -964,10 +964,10 @@ export class LocalBackend {
|
|
|
964
964
|
for (const file of changedFiles) {
|
|
965
965
|
const normalizedFile = file.replace(/\\/g, '/');
|
|
966
966
|
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
|
|
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
|
|
971
971
|
`, { filePath: normalizedFile });
|
|
972
972
|
for (const sym of symbols) {
|
|
973
973
|
changedSymbols.push({
|
|
@@ -987,9 +987,9 @@ export class LocalBackend {
|
|
|
987
987
|
const affectedProcesses = new Map();
|
|
988
988
|
for (const sym of changedSymbols) {
|
|
989
989
|
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
|
|
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
|
|
993
993
|
`, { nodeId: sym.id });
|
|
994
994
|
for (const proc of procs) {
|
|
995
995
|
const pid = proc.pid || proc[0];
|
|
@@ -1193,11 +1193,11 @@ export class LocalBackend {
|
|
|
1193
1193
|
const minConfidence = params.minConfidence ?? 0;
|
|
1194
1194
|
const relTypeFilter = relationTypes.map(t => `'${t}'`).join(', ');
|
|
1195
1195
|
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
|
|
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
|
|
1201
1201
|
`, { targetName: target });
|
|
1202
1202
|
if (targets.length === 0)
|
|
1203
1203
|
return { error: `Target '${target}' not found` };
|
|
@@ -1255,24 +1255,24 @@ export class LocalBackend {
|
|
|
1255
1255
|
const d1Ids = (grouped[1] || []).map((i) => `'${i.id.replace(/'/g, "''")}'`).join(', ');
|
|
1256
1256
|
// Affected processes: which execution flows are broken and at which step
|
|
1257
1257
|
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
|
|
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
|
|
1264
1264
|
`).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
|
|
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
|
|
1271
1271
|
`).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
|
|
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
|
|
1276
1276
|
`).catch(() => []) : Promise.resolve([]),
|
|
1277
1277
|
]);
|
|
1278
1278
|
affectedProcesses = processRows.map((r) => ({
|
|
@@ -1334,11 +1334,11 @@ export class LocalBackend {
|
|
|
1334
1334
|
await this.ensureInitialized(repo.id);
|
|
1335
1335
|
try {
|
|
1336
1336
|
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}
|
|
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}
|
|
1342
1342
|
`);
|
|
1343
1343
|
const rawClusters = clusters.map((c) => ({
|
|
1344
1344
|
id: c.id || c[0],
|
|
@@ -1361,11 +1361,11 @@ export class LocalBackend {
|
|
|
1361
1361
|
const repo = await this.resolveRepo(repoName);
|
|
1362
1362
|
await this.ensureInitialized(repo.id);
|
|
1363
1363
|
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}
|
|
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}
|
|
1369
1369
|
`);
|
|
1370
1370
|
return {
|
|
1371
1371
|
processes: processes.map((p) => ({
|
|
@@ -1388,10 +1388,10 @@ export class LocalBackend {
|
|
|
1388
1388
|
async queryClusterDetail(name, repoName) {
|
|
1389
1389
|
const repo = await this.resolveRepo(repoName);
|
|
1390
1390
|
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
|
|
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
|
|
1395
1395
|
`, { clusterName: name });
|
|
1396
1396
|
if (clusters.length === 0)
|
|
1397
1397
|
return { error: `Cluster '${name}' not found` };
|
|
@@ -1405,11 +1405,11 @@ export class LocalBackend {
|
|
|
1405
1405
|
totalSymbols += s;
|
|
1406
1406
|
weightedCohesion += (c.cohesion || 0) * s;
|
|
1407
1407
|
}
|
|
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
|
|
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
|
|
1413
1413
|
`, { clusterName: name });
|
|
1414
1414
|
return {
|
|
1415
1415
|
cluster: {
|
|
@@ -1432,20 +1432,20 @@ export class LocalBackend {
|
|
|
1432
1432
|
async queryProcessDetail(name, repoName) {
|
|
1433
1433
|
const repo = await this.resolveRepo(repoName);
|
|
1434
1434
|
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
|
|
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
|
|
1440
1440
|
`, { processName: name });
|
|
1441
1441
|
if (processes.length === 0)
|
|
1442
1442
|
return { error: `Process '${name}' not found` };
|
|
1443
1443
|
const proc = processes[0];
|
|
1444
1444
|
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
|
|
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
|
|
1449
1449
|
`, { procId });
|
|
1450
1450
|
return {
|
|
1451
1451
|
process: {
|
package/dist/mcp/resources.js
CHANGED
|
@@ -256,48 +256,48 @@ async function getProcessesResource(backend, repoName) {
|
|
|
256
256
|
* Schema resource — graph structure for Cypher queries
|
|
257
257
|
*/
|
|
258
258
|
function getSchemaResource() {
|
|
259
|
-
return `# GitNexus Graph Schema
|
|
260
|
-
|
|
261
|
-
nodes:
|
|
262
|
-
- File: Source code files
|
|
263
|
-
- Folder: Directory containers
|
|
264
|
-
- Function: Functions and arrow functions
|
|
265
|
-
- Class: Class definitions
|
|
266
|
-
- Interface: Interface/type definitions
|
|
267
|
-
- Method: Class methods
|
|
268
|
-
- CodeElement: Catch-all for other code elements
|
|
269
|
-
- Community: Auto-detected functional area (Leiden algorithm)
|
|
270
|
-
- Process: Execution flow trace
|
|
271
|
-
|
|
272
|
-
additional_node_types: "Multi-language: Struct, Enum, Macro, Typedef, Union, Namespace, Trait, Impl, TypeAlias, Const, Static, Property, Record, Delegate, Annotation, Constructor, Template, Module (use backticks in queries: \`Struct\`, \`Enum\`, etc.)"
|
|
273
|
-
|
|
274
|
-
relationships:
|
|
275
|
-
- CONTAINS: File/Folder contains child
|
|
276
|
-
- DEFINES: File defines a symbol
|
|
277
|
-
- CALLS: Function/method invocation
|
|
278
|
-
- IMPORTS: Module imports
|
|
279
|
-
- EXTENDS: Class inheritance
|
|
280
|
-
- IMPLEMENTS: Interface implementation
|
|
281
|
-
- MEMBER_OF: Symbol belongs to community
|
|
282
|
-
- STEP_IN_PROCESS: Symbol is step N in process
|
|
283
|
-
|
|
284
|
-
relationship_table: "All relationships use a single CodeRelation table with a 'type' property. Properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)"
|
|
285
|
-
|
|
286
|
-
example_queries:
|
|
287
|
-
find_callers: |
|
|
288
|
-
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
|
|
289
|
-
RETURN caller.name, caller.filePath
|
|
290
|
-
|
|
291
|
-
find_community_members: |
|
|
292
|
-
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
293
|
-
WHERE c.heuristicLabel = "Auth"
|
|
294
|
-
RETURN s.name, labels(s)[0] AS type
|
|
295
|
-
|
|
296
|
-
trace_process: |
|
|
297
|
-
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
298
|
-
WHERE p.heuristicLabel = "LoginFlow"
|
|
299
|
-
RETURN s.name, r.step
|
|
300
|
-
ORDER BY r.step
|
|
259
|
+
return `# GitNexus Graph Schema
|
|
260
|
+
|
|
261
|
+
nodes:
|
|
262
|
+
- File: Source code files
|
|
263
|
+
- Folder: Directory containers
|
|
264
|
+
- Function: Functions and arrow functions
|
|
265
|
+
- Class: Class definitions
|
|
266
|
+
- Interface: Interface/type definitions
|
|
267
|
+
- Method: Class methods
|
|
268
|
+
- CodeElement: Catch-all for other code elements
|
|
269
|
+
- Community: Auto-detected functional area (Leiden algorithm)
|
|
270
|
+
- Process: Execution flow trace
|
|
271
|
+
|
|
272
|
+
additional_node_types: "Multi-language: Struct, Enum, Macro, Typedef, Union, Namespace, Trait, Impl, TypeAlias, Const, Static, Property, Record, Delegate, Annotation, Constructor, Template, Module (use backticks in queries: \`Struct\`, \`Enum\`, etc.)"
|
|
273
|
+
|
|
274
|
+
relationships:
|
|
275
|
+
- CONTAINS: File/Folder contains child
|
|
276
|
+
- DEFINES: File defines a symbol
|
|
277
|
+
- CALLS: Function/method invocation
|
|
278
|
+
- IMPORTS: Module imports
|
|
279
|
+
- EXTENDS: Class inheritance
|
|
280
|
+
- IMPLEMENTS: Interface implementation
|
|
281
|
+
- MEMBER_OF: Symbol belongs to community
|
|
282
|
+
- STEP_IN_PROCESS: Symbol is step N in process
|
|
283
|
+
|
|
284
|
+
relationship_table: "All relationships use a single CodeRelation table with a 'type' property. Properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)"
|
|
285
|
+
|
|
286
|
+
example_queries:
|
|
287
|
+
find_callers: |
|
|
288
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
|
|
289
|
+
RETURN caller.name, caller.filePath
|
|
290
|
+
|
|
291
|
+
find_community_members: |
|
|
292
|
+
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
293
|
+
WHERE c.heuristicLabel = "Auth"
|
|
294
|
+
RETURN s.name, labels(s)[0] AS type
|
|
295
|
+
|
|
296
|
+
trace_process: |
|
|
297
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
298
|
+
WHERE p.heuristicLabel = "LoginFlow"
|
|
299
|
+
RETURN s.name, r.step
|
|
300
|
+
ORDER BY r.step
|
|
301
301
|
`;
|
|
302
302
|
}
|
|
303
303
|
/**
|
package/dist/mcp/server.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { createRequire } from 'module';
|
|
14
14
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
15
|
-
import {
|
|
15
|
+
import { CompatibleStdioServerTransport } from './compatible-stdio-transport.js';
|
|
16
16
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
17
17
|
import { GITNEXUS_TOOLS } from './tools.js';
|
|
18
18
|
import { getResourceDefinitions, getResourceTemplates, readResource } from './resources.js';
|
|
@@ -192,14 +192,14 @@ export function createMCPServer(backend) {
|
|
|
192
192
|
role: 'user',
|
|
193
193
|
content: {
|
|
194
194
|
type: 'text',
|
|
195
|
-
text: `Analyze the impact of my current code changes before committing.
|
|
196
|
-
|
|
197
|
-
Follow these steps:
|
|
198
|
-
1. Run \`detect_changes(${JSON.stringify({ scope, ...(baseRef ? { base_ref: baseRef } : {}) })})\` to find what changed and affected processes
|
|
199
|
-
2. For each changed symbol in critical processes, run \`context({name: "<symbol>"})\` to see its full reference graph
|
|
200
|
-
3. For any high-risk items (many callers or cross-process), run \`impact({target: "<symbol>", direction: "upstream"})\` for blast radius
|
|
201
|
-
4. Summarize: changes, affected processes, risk level, and recommended actions
|
|
202
|
-
|
|
195
|
+
text: `Analyze the impact of my current code changes before committing.
|
|
196
|
+
|
|
197
|
+
Follow these steps:
|
|
198
|
+
1. Run \`detect_changes(${JSON.stringify({ scope, ...(baseRef ? { base_ref: baseRef } : {}) })})\` to find what changed and affected processes
|
|
199
|
+
2. For each changed symbol in critical processes, run \`context({name: "<symbol>"})\` to see its full reference graph
|
|
200
|
+
3. For any high-risk items (many callers or cross-process), run \`impact({target: "<symbol>", direction: "upstream"})\` for blast radius
|
|
201
|
+
4. Summarize: changes, affected processes, risk level, and recommended actions
|
|
202
|
+
|
|
203
203
|
Present the analysis as a clear risk report.`,
|
|
204
204
|
},
|
|
205
205
|
},
|
|
@@ -214,14 +214,14 @@ Present the analysis as a clear risk report.`,
|
|
|
214
214
|
role: 'user',
|
|
215
215
|
content: {
|
|
216
216
|
type: 'text',
|
|
217
|
-
text: `Generate architecture documentation for this codebase using the knowledge graph.
|
|
218
|
-
|
|
219
|
-
Follow these steps:
|
|
220
|
-
1. READ \`gitnexus://repo/${repo || '{name}'}/context\` for codebase stats
|
|
221
|
-
2. READ \`gitnexus://repo/${repo || '{name}'}/clusters\` to see all functional areas
|
|
222
|
-
3. READ \`gitnexus://repo/${repo || '{name}'}/processes\` to see all execution flows
|
|
223
|
-
4. For the top 5 most important processes, READ \`gitnexus://repo/${repo || '{name}'}/process/{name}\` for step-by-step traces
|
|
224
|
-
5. Generate a mermaid architecture diagram showing the major areas and their connections
|
|
217
|
+
text: `Generate architecture documentation for this codebase using the knowledge graph.
|
|
218
|
+
|
|
219
|
+
Follow these steps:
|
|
220
|
+
1. READ \`gitnexus://repo/${repo || '{name}'}/context\` for codebase stats
|
|
221
|
+
2. READ \`gitnexus://repo/${repo || '{name}'}/clusters\` to see all functional areas
|
|
222
|
+
3. READ \`gitnexus://repo/${repo || '{name}'}/processes\` to see all execution flows
|
|
223
|
+
4. For the top 5 most important processes, READ \`gitnexus://repo/${repo || '{name}'}/process/{name}\` for step-by-step traces
|
|
224
|
+
5. Generate a mermaid architecture diagram showing the major areas and their connections
|
|
225
225
|
6. Write an ARCHITECTURE.md file with: overview, functional areas, key execution flows, and the mermaid diagram`,
|
|
226
226
|
},
|
|
227
227
|
},
|
|
@@ -238,7 +238,7 @@ Follow these steps:
|
|
|
238
238
|
export async function startMCPServer(backend) {
|
|
239
239
|
const server = createMCPServer(backend);
|
|
240
240
|
// Connect to stdio transport
|
|
241
|
-
const transport = new
|
|
241
|
+
const transport = new CompatibleStdioServerTransport();
|
|
242
242
|
await server.connect(transport);
|
|
243
243
|
// Graceful shutdown helper
|
|
244
244
|
let shuttingDown = false;
|