gitnexus 1.0.0
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 +181 -0
- package/dist/cli/ai-context.d.ts +21 -0
- package/dist/cli/ai-context.js +219 -0
- package/dist/cli/analyze.d.ts +10 -0
- package/dist/cli/analyze.js +118 -0
- package/dist/cli/clean.d.ts +8 -0
- package/dist/cli/clean.js +29 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +7 -0
- package/dist/cli/mcp.js +85 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.js +5 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +27 -0
- package/dist/config/ignore-service.d.ts +1 -0
- package/dist/config/ignore-service.js +208 -0
- package/dist/config/supported-languages.d.ts +11 -0
- package/dist/config/supported-languages.js +15 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +205 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +50 -0
- package/dist/core/embeddings/embedding-pipeline.js +321 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +61 -0
- package/dist/core/graph/types.d.ts +50 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +8 -0
- package/dist/core/ingestion/call-processor.js +269 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +269 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +39 -0
- package/dist/core/ingestion/entry-point-scoring.js +235 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +5 -0
- package/dist/core/ingestion/filesystem-walker.js +26 -0
- package/dist/core/ingestion/framework-detection.d.ts +38 -0
- package/dist/core/ingestion/framework-detection.js +183 -0
- package/dist/core/ingestion/heritage-processor.d.ts +14 -0
- package/dist/core/ingestion/heritage-processor.js +134 -0
- package/dist/core/ingestion/import-processor.d.ts +8 -0
- package/dist/core/ingestion/import-processor.js +490 -0
- package/dist/core/ingestion/parsing-processor.d.ts +8 -0
- package/dist/core/ingestion/parsing-processor.js +249 -0
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +228 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +278 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +33 -0
- package/dist/core/ingestion/symbol-table.js +38 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -0
- package/dist/core/ingestion/tree-sitter-queries.js +319 -0
- package/dist/core/ingestion/utils.d.ts +10 -0
- package/dist/core/ingestion/utils.js +44 -0
- package/dist/core/kuzu/csv-generator.d.ts +22 -0
- package/dist/core/kuzu/csv-generator.js +272 -0
- package/dist/core/kuzu/kuzu-adapter.d.ts +81 -0
- package/dist/core/kuzu/kuzu-adapter.js +568 -0
- package/dist/core/kuzu/schema.d.ts +53 -0
- package/dist/core/kuzu/schema.js +380 -0
- package/dist/core/search/bm25-index.d.ts +22 -0
- package/dist/core/search/bm25-index.js +52 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +4 -0
- package/dist/core/tree-sitter/parser-loader.js +42 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +93 -0
- package/dist/mcp/core/kuzu-adapter.d.ts +23 -0
- package/dist/mcp/core/kuzu-adapter.js +62 -0
- package/dist/mcp/local/local-backend.d.ts +73 -0
- package/dist/mcp/local/local-backend.js +752 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +279 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.js +130 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +160 -0
- package/dist/server/api.d.ts +6 -0
- package/dist/server/api.js +156 -0
- package/dist/storage/git.d.ts +7 -0
- package/dist/storage/git.js +39 -0
- package/dist/storage/repo-manager.d.ts +61 -0
- package/dist/storage/repo-manager.js +106 -0
- package/dist/types/pipeline.d.ts +28 -0
- package/dist/types/pipeline.js +16 -0
- package/package.json +80 -0
- package/skills/debugging.md +104 -0
- package/skills/exploring.md +112 -0
- package/skills/impact-analysis.md +114 -0
- package/skills/refactoring.md +119 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources
|
|
3
|
+
*
|
|
4
|
+
* Provides structured on-demand data to AI agents.
|
|
5
|
+
* Resources complement tools by offering lightweight, cacheable data.
|
|
6
|
+
*/
|
|
7
|
+
import type { LocalBackend } from './local/local-backend.js';
|
|
8
|
+
export interface ResourceDefinition {
|
|
9
|
+
uri: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
mimeType: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ResourceTemplate {
|
|
15
|
+
uriTemplate: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Static resources available when codebase is indexed
|
|
22
|
+
*/
|
|
23
|
+
export declare function getResourceDefinitions(projectName: string): ResourceDefinition[];
|
|
24
|
+
/**
|
|
25
|
+
* Dynamic resource templates
|
|
26
|
+
*/
|
|
27
|
+
export declare function getResourceTemplates(): ResourceTemplate[];
|
|
28
|
+
/**
|
|
29
|
+
* Read a resource and return its content
|
|
30
|
+
*/
|
|
31
|
+
export declare function readResource(uri: string, backend: LocalBackend): Promise<string>;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources
|
|
3
|
+
*
|
|
4
|
+
* Provides structured on-demand data to AI agents.
|
|
5
|
+
* Resources complement tools by offering lightweight, cacheable data.
|
|
6
|
+
*/
|
|
7
|
+
import { checkStaleness } from './staleness.js';
|
|
8
|
+
/**
|
|
9
|
+
* Static resources available when codebase is indexed
|
|
10
|
+
*/
|
|
11
|
+
export function getResourceDefinitions(projectName) {
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
uri: 'gitnexus://context',
|
|
15
|
+
name: `${projectName} Overview`,
|
|
16
|
+
description: 'Codebase stats, hotspots, and available tools',
|
|
17
|
+
mimeType: 'text/yaml',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
uri: 'gitnexus://clusters',
|
|
21
|
+
name: 'All Clusters',
|
|
22
|
+
description: 'List of all functional clusters with stats',
|
|
23
|
+
mimeType: 'text/yaml',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
uri: 'gitnexus://processes',
|
|
27
|
+
name: 'All Processes',
|
|
28
|
+
description: 'List of all execution flows with types',
|
|
29
|
+
mimeType: 'text/yaml',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
uri: 'gitnexus://schema',
|
|
33
|
+
name: 'Graph Schema',
|
|
34
|
+
description: 'Node types and relationships for Cypher queries',
|
|
35
|
+
mimeType: 'text/yaml',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Dynamic resource templates
|
|
41
|
+
*/
|
|
42
|
+
export function getResourceTemplates() {
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
uriTemplate: 'gitnexus://cluster/{name}',
|
|
46
|
+
name: 'Cluster Detail',
|
|
47
|
+
description: 'Deep dive into a specific cluster',
|
|
48
|
+
mimeType: 'text/yaml',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
uriTemplate: 'gitnexus://process/{name}',
|
|
52
|
+
name: 'Process Trace',
|
|
53
|
+
description: 'Step-by-step execution trace',
|
|
54
|
+
mimeType: 'text/yaml',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read a resource and return its content
|
|
60
|
+
*/
|
|
61
|
+
export async function readResource(uri, backend) {
|
|
62
|
+
// Static resources
|
|
63
|
+
if (uri === 'gitnexus://context') {
|
|
64
|
+
return getContextResource(backend);
|
|
65
|
+
}
|
|
66
|
+
if (uri === 'gitnexus://clusters') {
|
|
67
|
+
return getClustersResource(backend);
|
|
68
|
+
}
|
|
69
|
+
if (uri === 'gitnexus://processes') {
|
|
70
|
+
return getProcessesResource(backend);
|
|
71
|
+
}
|
|
72
|
+
if (uri === 'gitnexus://schema') {
|
|
73
|
+
return getSchemaResource();
|
|
74
|
+
}
|
|
75
|
+
// Dynamic resources
|
|
76
|
+
if (uri.startsWith('gitnexus://cluster/')) {
|
|
77
|
+
const name = uri.replace('gitnexus://cluster/', '');
|
|
78
|
+
return getClusterDetailResource(name, backend);
|
|
79
|
+
}
|
|
80
|
+
if (uri.startsWith('gitnexus://process/')) {
|
|
81
|
+
const name = uri.replace('gitnexus://process/', '');
|
|
82
|
+
return getProcessDetailResource(name, backend);
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Context resource - codebase overview
|
|
88
|
+
*/
|
|
89
|
+
async function getContextResource(backend) {
|
|
90
|
+
const context = backend.context;
|
|
91
|
+
if (!context) {
|
|
92
|
+
return 'error: No codebase loaded. Run: gitnexus analyze';
|
|
93
|
+
}
|
|
94
|
+
// Check staleness
|
|
95
|
+
const repoPath = backend.repoPath;
|
|
96
|
+
const lastCommit = backend.meta?.lastCommit || 'HEAD';
|
|
97
|
+
const staleness = repoPath ? checkStaleness(repoPath, lastCommit) : { isStale: false, commitsBehind: 0 };
|
|
98
|
+
const lines = [
|
|
99
|
+
`project: ${context.projectName}`,
|
|
100
|
+
];
|
|
101
|
+
// Add staleness warning if index is behind
|
|
102
|
+
if (staleness.isStale && staleness.hint) {
|
|
103
|
+
lines.push('');
|
|
104
|
+
lines.push(`staleness: "${staleness.hint}"`);
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push('stats:');
|
|
108
|
+
lines.push(` files: ${context.stats.fileCount}`);
|
|
109
|
+
lines.push(` symbols: ${context.stats.functionCount}`);
|
|
110
|
+
lines.push(` clusters: ${context.stats.communityCount}`);
|
|
111
|
+
lines.push(` processes: ${context.stats.processCount}`);
|
|
112
|
+
lines.push('');
|
|
113
|
+
lines.push('tools_available:');
|
|
114
|
+
lines.push(' - search: Hybrid semantic + keyword search');
|
|
115
|
+
lines.push(' - explore: Deep dive on symbol/cluster/process');
|
|
116
|
+
lines.push(' - impact: Blast radius analysis');
|
|
117
|
+
lines.push(' - overview: List all clusters and processes');
|
|
118
|
+
lines.push(' - cypher: Raw graph queries');
|
|
119
|
+
lines.push(' - analyze: Re-index to update stale data');
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push('resources_available:');
|
|
122
|
+
lines.push(' - gitnexus://clusters: All clusters');
|
|
123
|
+
lines.push(' - gitnexus://processes: All processes');
|
|
124
|
+
lines.push(' - gitnexus://cluster/{name}: Cluster details');
|
|
125
|
+
lines.push(' - gitnexus://process/{name}: Process trace');
|
|
126
|
+
return lines.join('\n');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clusters resource - list all clusters
|
|
130
|
+
*/
|
|
131
|
+
async function getClustersResource(backend) {
|
|
132
|
+
try {
|
|
133
|
+
const result = await backend.callTool('overview', { showClusters: true, showProcesses: false, limit: 50 });
|
|
134
|
+
if (!result.clusters || result.clusters.length === 0) {
|
|
135
|
+
return 'clusters: []\n# No clusters detected. Run: gitnexus analyze';
|
|
136
|
+
}
|
|
137
|
+
const lines = ['clusters:'];
|
|
138
|
+
for (const cluster of result.clusters) {
|
|
139
|
+
const label = cluster.heuristicLabel || cluster.label || cluster.id;
|
|
140
|
+
lines.push(` - name: "${label}"`);
|
|
141
|
+
lines.push(` symbols: ${cluster.symbolCount || 0}`);
|
|
142
|
+
if (cluster.cohesion) {
|
|
143
|
+
lines.push(` cohesion: ${(cluster.cohesion * 100).toFixed(0)}%`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
return `error: ${err.message}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Processes resource - list all processes
|
|
154
|
+
*/
|
|
155
|
+
async function getProcessesResource(backend) {
|
|
156
|
+
try {
|
|
157
|
+
const result = await backend.callTool('overview', { showClusters: false, showProcesses: true, limit: 50 });
|
|
158
|
+
if (!result.processes || result.processes.length === 0) {
|
|
159
|
+
return 'processes: []\n# No processes detected. Run: gitnexus analyze';
|
|
160
|
+
}
|
|
161
|
+
const lines = ['processes:'];
|
|
162
|
+
for (const proc of result.processes) {
|
|
163
|
+
const label = proc.heuristicLabel || proc.label || proc.id;
|
|
164
|
+
lines.push(` - name: "${label}"`);
|
|
165
|
+
lines.push(` type: ${proc.processType || 'unknown'}`);
|
|
166
|
+
lines.push(` steps: ${proc.stepCount || 0}`);
|
|
167
|
+
}
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
return `error: ${err.message}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Schema resource - graph structure for Cypher queries
|
|
176
|
+
*/
|
|
177
|
+
function getSchemaResource() {
|
|
178
|
+
return `# GitNexus Graph Schema
|
|
179
|
+
|
|
180
|
+
nodes:
|
|
181
|
+
- File: Source code files
|
|
182
|
+
- Function: Functions and arrow functions
|
|
183
|
+
- Class: Class definitions
|
|
184
|
+
- Interface: Interface/type definitions
|
|
185
|
+
- Method: Class methods
|
|
186
|
+
- Community: Functional cluster (Leiden algorithm)
|
|
187
|
+
- Process: Execution flow trace
|
|
188
|
+
|
|
189
|
+
relationships:
|
|
190
|
+
- CALLS: Function/method invocation
|
|
191
|
+
- IMPORTS: Module imports
|
|
192
|
+
- EXTENDS: Class inheritance
|
|
193
|
+
- IMPLEMENTS: Interface implementation
|
|
194
|
+
- DEFINES: File defines symbol
|
|
195
|
+
- MEMBER_OF: Symbol belongs to community
|
|
196
|
+
- STEP_IN_PROCESS: Symbol is step N in process
|
|
197
|
+
|
|
198
|
+
example_queries:
|
|
199
|
+
find_callers: |
|
|
200
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
|
|
201
|
+
RETURN caller.name, caller.filePath
|
|
202
|
+
|
|
203
|
+
find_community_members: |
|
|
204
|
+
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
205
|
+
WHERE c.heuristicLabel = "Auth"
|
|
206
|
+
RETURN s.name, labels(s)[0] AS type
|
|
207
|
+
|
|
208
|
+
trace_process: |
|
|
209
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
210
|
+
WHERE p.heuristicLabel = "LoginFlow"
|
|
211
|
+
RETURN s.name, r.step
|
|
212
|
+
ORDER BY r.step
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Cluster detail resource
|
|
217
|
+
*/
|
|
218
|
+
async function getClusterDetailResource(name, backend) {
|
|
219
|
+
try {
|
|
220
|
+
const result = await backend.callTool('explore', { name, type: 'cluster' });
|
|
221
|
+
if (result.error) {
|
|
222
|
+
return `error: ${result.error}`;
|
|
223
|
+
}
|
|
224
|
+
const cluster = result.cluster;
|
|
225
|
+
const members = result.members || [];
|
|
226
|
+
const lines = [
|
|
227
|
+
`name: "${cluster.heuristicLabel || cluster.label || cluster.id}"`,
|
|
228
|
+
`symbols: ${cluster.symbolCount || members.length}`,
|
|
229
|
+
];
|
|
230
|
+
if (cluster.cohesion) {
|
|
231
|
+
lines.push(`cohesion: ${(cluster.cohesion * 100).toFixed(0)}%`);
|
|
232
|
+
}
|
|
233
|
+
if (members.length > 0) {
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push('members:');
|
|
236
|
+
for (const member of members.slice(0, 20)) {
|
|
237
|
+
lines.push(` - name: ${member.name}`);
|
|
238
|
+
lines.push(` type: ${member.type}`);
|
|
239
|
+
lines.push(` file: ${member.filePath}`);
|
|
240
|
+
}
|
|
241
|
+
if (members.length > 20) {
|
|
242
|
+
lines.push(` # ... and ${members.length - 20} more`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return lines.join('\n');
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
return `error: ${err.message}`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Process detail resource
|
|
253
|
+
*/
|
|
254
|
+
async function getProcessDetailResource(name, backend) {
|
|
255
|
+
try {
|
|
256
|
+
const result = await backend.callTool('explore', { name, type: 'process' });
|
|
257
|
+
if (result.error) {
|
|
258
|
+
return `error: ${result.error}`;
|
|
259
|
+
}
|
|
260
|
+
const proc = result.process;
|
|
261
|
+
const steps = result.steps || [];
|
|
262
|
+
const lines = [
|
|
263
|
+
`name: "${proc.heuristicLabel || proc.label || proc.id}"`,
|
|
264
|
+
`type: ${proc.processType || 'unknown'}`,
|
|
265
|
+
`step_count: ${proc.stepCount || steps.length}`,
|
|
266
|
+
];
|
|
267
|
+
if (steps.length > 0) {
|
|
268
|
+
lines.push('');
|
|
269
|
+
lines.push('trace:');
|
|
270
|
+
for (const step of steps) {
|
|
271
|
+
lines.push(` ${step.step}: ${step.name} (${step.filePath})`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return lines.join('\n');
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
return `error: ${err.message}`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Model Context Protocol server that runs on stdio.
|
|
5
|
+
* External AI tools (Cursor, Claude) spawn this process and
|
|
6
|
+
* communicate via stdin/stdout using the MCP protocol.
|
|
7
|
+
*
|
|
8
|
+
* Tools: context, search, cypher, overview, explore, impact, analyze
|
|
9
|
+
* Resources: context, clusters, processes, schema, cluster/{name}, process/{name}
|
|
10
|
+
*/
|
|
11
|
+
import type { LocalBackend } from './local/local-backend.js';
|
|
12
|
+
export declare function startMCPServer(backend: LocalBackend): Promise<void>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Model Context Protocol server that runs on stdio.
|
|
5
|
+
* External AI tools (Cursor, Claude) spawn this process and
|
|
6
|
+
* communicate via stdin/stdout using the MCP protocol.
|
|
7
|
+
*
|
|
8
|
+
* Tools: context, search, cypher, overview, explore, impact, analyze
|
|
9
|
+
* Resources: context, clusters, processes, schema, cluster/{name}, process/{name}
|
|
10
|
+
*/
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import { GITNEXUS_TOOLS } from './tools.js';
|
|
15
|
+
import { getResourceDefinitions, getResourceTemplates, readResource } from './resources.js';
|
|
16
|
+
export async function startMCPServer(backend) {
|
|
17
|
+
const server = new Server({
|
|
18
|
+
name: 'gitnexus',
|
|
19
|
+
version: '0.2.0',
|
|
20
|
+
}, {
|
|
21
|
+
capabilities: {
|
|
22
|
+
tools: {},
|
|
23
|
+
resources: {},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
// Handle list resources request
|
|
27
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
28
|
+
const context = backend.context;
|
|
29
|
+
if (!context) {
|
|
30
|
+
return { resources: [] };
|
|
31
|
+
}
|
|
32
|
+
const resources = getResourceDefinitions(context.projectName);
|
|
33
|
+
return {
|
|
34
|
+
resources: resources.map(r => ({
|
|
35
|
+
uri: r.uri,
|
|
36
|
+
name: r.name,
|
|
37
|
+
description: r.description,
|
|
38
|
+
mimeType: r.mimeType,
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
// Handle list resource templates request (for dynamic resources)
|
|
43
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
44
|
+
const templates = getResourceTemplates();
|
|
45
|
+
return {
|
|
46
|
+
resourceTemplates: templates.map(t => ({
|
|
47
|
+
uriTemplate: t.uriTemplate,
|
|
48
|
+
name: t.name,
|
|
49
|
+
description: t.description,
|
|
50
|
+
mimeType: t.mimeType,
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
// Handle read resource request
|
|
55
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
56
|
+
const { uri } = request.params;
|
|
57
|
+
try {
|
|
58
|
+
const content = await readResource(uri, backend);
|
|
59
|
+
return {
|
|
60
|
+
contents: [
|
|
61
|
+
{
|
|
62
|
+
uri,
|
|
63
|
+
mimeType: 'text/yaml',
|
|
64
|
+
text: content,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
contents: [
|
|
72
|
+
{
|
|
73
|
+
uri,
|
|
74
|
+
mimeType: 'text/plain',
|
|
75
|
+
text: `Error: ${err.message}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// Handle list tools request
|
|
82
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
83
|
+
tools: GITNEXUS_TOOLS.map((tool) => ({
|
|
84
|
+
name: tool.name,
|
|
85
|
+
description: tool.description,
|
|
86
|
+
inputSchema: tool.inputSchema,
|
|
87
|
+
})),
|
|
88
|
+
}));
|
|
89
|
+
// Handle tool calls
|
|
90
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
91
|
+
const { name, arguments: args } = request.params;
|
|
92
|
+
try {
|
|
93
|
+
const result = await backend.callTool(name, args);
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: `Error: ${message}`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
isError: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// Connect to stdio transport
|
|
117
|
+
const transport = new StdioServerTransport();
|
|
118
|
+
await server.connect(transport);
|
|
119
|
+
// Handle graceful shutdown
|
|
120
|
+
process.on('SIGINT', async () => {
|
|
121
|
+
await backend.disconnect();
|
|
122
|
+
await server.close();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
});
|
|
125
|
+
process.on('SIGTERM', async () => {
|
|
126
|
+
await backend.disconnect();
|
|
127
|
+
await server.close();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staleness Check
|
|
3
|
+
*
|
|
4
|
+
* Checks if the GitNexus index is behind the current git HEAD.
|
|
5
|
+
* Returns a hint for the LLM to call analyze if stale.
|
|
6
|
+
*/
|
|
7
|
+
export interface StalenessInfo {
|
|
8
|
+
isStale: boolean;
|
|
9
|
+
commitsBehind: number;
|
|
10
|
+
hint?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Check how many commits the index is behind HEAD
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkStaleness(repoPath: string, lastCommit: string): StalenessInfo;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staleness Check
|
|
3
|
+
*
|
|
4
|
+
* Checks if the GitNexus index is behind the current git HEAD.
|
|
5
|
+
* Returns a hint for the LLM to call analyze if stale.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
/**
|
|
9
|
+
* Check how many commits the index is behind HEAD
|
|
10
|
+
*/
|
|
11
|
+
export function checkStaleness(repoPath, lastCommit) {
|
|
12
|
+
try {
|
|
13
|
+
// Get count of commits between lastCommit and HEAD
|
|
14
|
+
const result = execSync(`git rev-list --count ${lastCommit}..HEAD`, { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
15
|
+
const commitsBehind = parseInt(result, 10) || 0;
|
|
16
|
+
if (commitsBehind > 0) {
|
|
17
|
+
return {
|
|
18
|
+
isStale: true,
|
|
19
|
+
commitsBehind,
|
|
20
|
+
hint: `⚠️ Index is ${commitsBehind} commit${commitsBehind > 1 ? 's' : ''} behind HEAD. Run analyze tool to update.`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { isStale: false, commitsBehind: 0 };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// If git command fails, assume not stale (fail open)
|
|
27
|
+
return { isStale: false, commitsBehind: 0 };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Definitions
|
|
3
|
+
*
|
|
4
|
+
* Defines the tools that GitNexus exposes to external AI agents.
|
|
5
|
+
* Only includes tools that provide unique value over native IDE capabilities.
|
|
6
|
+
*/
|
|
7
|
+
export interface ToolDefinition {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object';
|
|
12
|
+
properties: Record<string, {
|
|
13
|
+
type: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
default?: any;
|
|
16
|
+
items?: {
|
|
17
|
+
type: string;
|
|
18
|
+
};
|
|
19
|
+
enum?: string[];
|
|
20
|
+
}>;
|
|
21
|
+
required: string[];
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare const GITNEXUS_TOOLS: ToolDefinition[];
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Definitions
|
|
3
|
+
*
|
|
4
|
+
* Defines the tools that GitNexus exposes to external AI agents.
|
|
5
|
+
* Only includes tools that provide unique value over native IDE capabilities.
|
|
6
|
+
*/
|
|
7
|
+
export const GITNEXUS_TOOLS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'analyze',
|
|
10
|
+
description: `Index or re-index the current repository. Runs the full pipeline in-process.
|
|
11
|
+
|
|
12
|
+
Creates .gitnexus/ in repo root with:
|
|
13
|
+
- Knowledge graph (functions, classes, calls, imports)
|
|
14
|
+
- Full-text search indexes
|
|
15
|
+
- Community detection (Leiden)
|
|
16
|
+
- Process tracing
|
|
17
|
+
- Embeddings for semantic search
|
|
18
|
+
|
|
19
|
+
Run this when:
|
|
20
|
+
- First time using GitNexus on a repo
|
|
21
|
+
- After major code changes
|
|
22
|
+
- When staleness warning appears in gitnexus://context
|
|
23
|
+
- When 'not indexed' error appears
|
|
24
|
+
|
|
25
|
+
Note: This may take 30-120 seconds for large repos.`,
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
path: { type: 'string', description: 'Repo path (default: current directory)' },
|
|
30
|
+
force: { type: 'boolean', description: 'Re-index even if exists', default: false },
|
|
31
|
+
skipEmbeddings: { type: 'boolean', description: 'Skip embedding generation (faster)', default: false },
|
|
32
|
+
},
|
|
33
|
+
required: [],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'search',
|
|
38
|
+
description: `Hybrid search (keyword + semantic) across the codebase.
|
|
39
|
+
Returns code nodes with cluster context and optional graph connections.
|
|
40
|
+
|
|
41
|
+
BETTER THAN IDE search because:
|
|
42
|
+
- Cluster context (which functional area each result belongs to)
|
|
43
|
+
- Relationship data (callers/callees with depth=full)
|
|
44
|
+
- Hybrid ranking (BM25 + semantic via Reciprocal Rank Fusion)
|
|
45
|
+
|
|
46
|
+
RETURNS: Array of {name, type, filePath, cluster?, connections[]?, fusedScore, searchSource}`,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
query: { type: 'string', description: 'Natural language or keyword search query' },
|
|
51
|
+
limit: { type: 'number', description: 'Max results to return', default: 10 },
|
|
52
|
+
depth: { type: 'string', description: 'Result detail: "definitions" (symbols only) or "full" (with relationships)', enum: ['definitions', 'full'], default: 'definitions' },
|
|
53
|
+
},
|
|
54
|
+
required: ['query'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'cypher',
|
|
59
|
+
description: `Execute Cypher query against the code knowledge graph.
|
|
60
|
+
|
|
61
|
+
SCHEMA:
|
|
62
|
+
- Nodes: File, Folder, Function, Class, Interface, Method, Community, Process
|
|
63
|
+
- Edges via CodeRelation.type: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, CONTAINS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
|
|
64
|
+
|
|
65
|
+
EXAMPLES:
|
|
66
|
+
• Find callers of a function:
|
|
67
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b:Function {name: "validateUser"}) RETURN a.name, a.filePath
|
|
68
|
+
|
|
69
|
+
• Find all functions in a community:
|
|
70
|
+
MATCH (f:Function)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community {label: "Auth"}) RETURN f.name
|
|
71
|
+
|
|
72
|
+
• Find steps in a process:
|
|
73
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {label: "UserLogin"}) RETURN s.name, r.step ORDER BY r.step
|
|
74
|
+
|
|
75
|
+
TIPS:
|
|
76
|
+
- All relationships use CodeRelation table with 'type' property
|
|
77
|
+
- Community = functional cluster detected by Leiden algorithm
|
|
78
|
+
- Process = execution flow trace from entry point to terminal`,
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
query: { type: 'string', description: 'Cypher query to execute' },
|
|
83
|
+
},
|
|
84
|
+
required: ['query'],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'explore',
|
|
89
|
+
description: `Deep dive on a symbol, cluster, or process.
|
|
90
|
+
|
|
91
|
+
TYPE: symbol | cluster | process
|
|
92
|
+
|
|
93
|
+
For SYMBOL: Shows cluster membership, process participation, callers/callees
|
|
94
|
+
For CLUSTER: Shows members, cohesion score, processes touching it
|
|
95
|
+
For PROCESS: Shows step-by-step trace, clusters traversed, entry/terminal points
|
|
96
|
+
|
|
97
|
+
Use after search to understand context of a specific node.`,
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
name: { type: 'string', description: 'Name of symbol, cluster, or process to explore' },
|
|
102
|
+
type: { type: 'string', description: 'Type: symbol, cluster, or process' },
|
|
103
|
+
},
|
|
104
|
+
required: ['name', 'type'],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'overview',
|
|
109
|
+
description: `Get codebase map showing all clusters and processes.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
- All communities (clusters) with member counts and cohesion scores
|
|
113
|
+
- All processes with step counts and types (intra/cross-community)
|
|
114
|
+
- High-level architectural view
|
|
115
|
+
|
|
116
|
+
Use to understand overall codebase structure before diving deep.`,
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
showProcesses: { type: 'boolean', description: 'Include process list', default: true },
|
|
121
|
+
showClusters: { type: 'boolean', description: 'Include cluster list', default: true },
|
|
122
|
+
limit: { type: 'number', description: 'Max items per category', default: 20 },
|
|
123
|
+
},
|
|
124
|
+
required: [],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'impact',
|
|
129
|
+
description: `Analyze the impact of changing a code element.
|
|
130
|
+
Returns all nodes affected by modifying the target, with distance, edge type, and confidence.
|
|
131
|
+
|
|
132
|
+
USE BEFORE making changes to understand ripple effects.
|
|
133
|
+
|
|
134
|
+
Output includes:
|
|
135
|
+
- Affected processes (with step positions)
|
|
136
|
+
- Affected clusters (direct/indirect)
|
|
137
|
+
- Risk assessment (critical/high/medium/low)
|
|
138
|
+
- Callers/dependents grouped by depth
|
|
139
|
+
|
|
140
|
+
EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS
|
|
141
|
+
Confidence: 100% = certain, <80% = fuzzy match
|
|
142
|
+
|
|
143
|
+
Depth groups:
|
|
144
|
+
- d=1: WILL BREAK (direct callers/importers)
|
|
145
|
+
- d=2: LIKELY AFFECTED (indirect)
|
|
146
|
+
- d=3: MAY NEED TESTING (transitive)`,
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
target: { type: 'string', description: 'Name of function, class, or file to analyze' },
|
|
151
|
+
direction: { type: 'string', description: 'upstream (what depends on this) or downstream (what this depends on)' },
|
|
152
|
+
maxDepth: { type: 'number', description: 'Max relationship depth (default: 3)', default: 3 },
|
|
153
|
+
relationTypes: { type: 'array', items: { type: 'string' }, description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS (default: usage-based)' },
|
|
154
|
+
includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
|
|
155
|
+
minConfidence: { type: 'number', description: 'Minimum confidence 0-1 (default: 0.7)' },
|
|
156
|
+
},
|
|
157
|
+
required: ['target', 'direction'],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
];
|