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,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP API Server
|
|
3
|
+
*
|
|
4
|
+
* REST API for browser-based clients to query the local .gitnexus/ index.
|
|
5
|
+
*/
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import cors from 'cors';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import { findRepo } from '../storage/repo-manager.js';
|
|
11
|
+
import { initKuzu, executeQuery } from '../core/kuzu/kuzu-adapter.js';
|
|
12
|
+
import { NODE_TABLES } from '../core/kuzu/schema.js';
|
|
13
|
+
import { searchFTSFromKuzu } from '../core/search/bm25-index.js';
|
|
14
|
+
import { hybridSearch } from '../core/search/hybrid-search.js';
|
|
15
|
+
import { semanticSearch } from '../core/embeddings/embedding-pipeline.js';
|
|
16
|
+
import { isEmbedderReady } from '../core/embeddings/embedder.js';
|
|
17
|
+
const buildGraph = async () => {
|
|
18
|
+
const nodes = [];
|
|
19
|
+
for (const table of NODE_TABLES) {
|
|
20
|
+
try {
|
|
21
|
+
let query = '';
|
|
22
|
+
if (table === 'File') {
|
|
23
|
+
query = `MATCH (n:File) RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.content AS content`;
|
|
24
|
+
}
|
|
25
|
+
else if (table === 'Folder') {
|
|
26
|
+
query = `MATCH (n:Folder) RETURN n.id AS id, n.name AS name, n.filePath AS filePath`;
|
|
27
|
+
}
|
|
28
|
+
else if (table === 'Community') {
|
|
29
|
+
query = `MATCH (n:Community) RETURN n.id AS id, n.label AS label, n.heuristicLabel AS heuristicLabel, n.cohesion AS cohesion, n.symbolCount AS symbolCount`;
|
|
30
|
+
}
|
|
31
|
+
else if (table === 'Process') {
|
|
32
|
+
query = `MATCH (n:Process) RETURN n.id AS id, n.label AS label, n.heuristicLabel AS heuristicLabel, n.processType AS processType, n.stepCount AS stepCount, n.communities AS communities, n.entryPointId AS entryPointId, n.terminalId AS terminalId`;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
query = `MATCH (n:${table}) RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.content AS content`;
|
|
36
|
+
}
|
|
37
|
+
const rows = await executeQuery(query);
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
nodes.push({
|
|
40
|
+
id: row.id ?? row[0],
|
|
41
|
+
label: table,
|
|
42
|
+
properties: {
|
|
43
|
+
name: row.name ?? row.label ?? row[1],
|
|
44
|
+
filePath: row.filePath ?? row[2],
|
|
45
|
+
startLine: row.startLine,
|
|
46
|
+
endLine: row.endLine,
|
|
47
|
+
content: row.content,
|
|
48
|
+
heuristicLabel: row.heuristicLabel,
|
|
49
|
+
cohesion: row.cohesion,
|
|
50
|
+
symbolCount: row.symbolCount,
|
|
51
|
+
processType: row.processType,
|
|
52
|
+
stepCount: row.stepCount,
|
|
53
|
+
communities: row.communities,
|
|
54
|
+
entryPointId: row.entryPointId,
|
|
55
|
+
terminalId: row.terminalId,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore empty tables
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const relationships = [];
|
|
65
|
+
const relRows = await executeQuery(`MATCH (a)-[r:CodeRelation]->(b) RETURN a.id AS sourceId, b.id AS targetId, r.type AS type, r.confidence AS confidence, r.reason AS reason, r.step AS step`);
|
|
66
|
+
for (const row of relRows) {
|
|
67
|
+
relationships.push({
|
|
68
|
+
id: `${row.sourceId}_${row.type}_${row.targetId}`,
|
|
69
|
+
type: row.type,
|
|
70
|
+
sourceId: row.sourceId,
|
|
71
|
+
targetId: row.targetId,
|
|
72
|
+
confidence: row.confidence,
|
|
73
|
+
reason: row.reason,
|
|
74
|
+
step: row.step,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return { nodes, relationships };
|
|
78
|
+
};
|
|
79
|
+
export const createServer = async (port) => {
|
|
80
|
+
const app = express();
|
|
81
|
+
app.use(cors());
|
|
82
|
+
app.use(express.json({ limit: '10mb' }));
|
|
83
|
+
// Get repo info
|
|
84
|
+
app.get('/api/repo', async (_req, res) => {
|
|
85
|
+
const repo = await findRepo(process.cwd());
|
|
86
|
+
if (!repo) {
|
|
87
|
+
res.status(404).json({ error: 'Repository not indexed. Run: gitnexus analyze' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
res.json({
|
|
91
|
+
repoPath: repo.repoPath,
|
|
92
|
+
indexedAt: repo.meta.indexedAt,
|
|
93
|
+
stats: repo.meta.stats || {},
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
// Get full graph
|
|
97
|
+
app.get('/api/graph', async (_req, res) => {
|
|
98
|
+
const repo = await findRepo(process.cwd());
|
|
99
|
+
if (!repo) {
|
|
100
|
+
res.status(404).json({ error: 'Repository not indexed' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await initKuzu(repo.kuzuPath);
|
|
104
|
+
const graph = await buildGraph();
|
|
105
|
+
res.json(graph);
|
|
106
|
+
});
|
|
107
|
+
// Execute Cypher query
|
|
108
|
+
app.post('/api/query', async (req, res) => {
|
|
109
|
+
const repo = await findRepo(process.cwd());
|
|
110
|
+
if (!repo) {
|
|
111
|
+
res.status(404).json({ error: 'Repository not indexed' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await initKuzu(repo.kuzuPath);
|
|
115
|
+
const result = await executeQuery(req.body.cypher);
|
|
116
|
+
res.json({ result });
|
|
117
|
+
});
|
|
118
|
+
// Search
|
|
119
|
+
app.post('/api/search', async (req, res) => {
|
|
120
|
+
const repo = await findRepo(process.cwd());
|
|
121
|
+
if (!repo) {
|
|
122
|
+
res.status(404).json({ error: 'Repository not indexed' });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await initKuzu(repo.kuzuPath);
|
|
126
|
+
const query = req.body.query ?? '';
|
|
127
|
+
const limit = req.body.limit ?? 10;
|
|
128
|
+
if (isEmbedderReady()) {
|
|
129
|
+
const results = await hybridSearch(query, limit, executeQuery, semanticSearch);
|
|
130
|
+
res.json({ results });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// FTS-only fallback when embeddings aren't loaded
|
|
134
|
+
const results = await searchFTSFromKuzu(query, limit);
|
|
135
|
+
res.json({ results });
|
|
136
|
+
});
|
|
137
|
+
// Read file
|
|
138
|
+
app.get('/api/file', async (req, res) => {
|
|
139
|
+
const repo = await findRepo(process.cwd());
|
|
140
|
+
if (!repo) {
|
|
141
|
+
res.status(404).json({ error: 'Repository not indexed' });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const filePath = req.query.path;
|
|
145
|
+
if (!filePath) {
|
|
146
|
+
res.status(400).json({ error: 'Missing path' });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const fullPath = path.join(repo.repoPath, filePath);
|
|
150
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
151
|
+
res.json({ content });
|
|
152
|
+
});
|
|
153
|
+
app.listen(port, () => {
|
|
154
|
+
console.log(`GitNexus server running on http://localhost:${port}`);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const isGitRepo: (repoPath: string) => boolean;
|
|
2
|
+
export declare const getCurrentCommit: (repoPath: string) => string;
|
|
3
|
+
export declare const getStatusPorcelain: (repoPath: string) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Find the git repository root from any path inside the repo
|
|
6
|
+
*/
|
|
7
|
+
export declare const getGitRoot: (fromPath: string) => string | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
export const isGitRepo = (repoPath) => {
|
|
3
|
+
try {
|
|
4
|
+
execSync('git rev-parse --is-inside-work-tree', { cwd: repoPath, stdio: 'ignore' });
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export const getCurrentCommit = (repoPath) => {
|
|
12
|
+
try {
|
|
13
|
+
return execSync('git rev-parse HEAD', { cwd: repoPath }).toString().trim();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const getStatusPorcelain = (repoPath) => {
|
|
20
|
+
try {
|
|
21
|
+
return execSync('git status --porcelain', { cwd: repoPath }).toString();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Find the git repository root from any path inside the repo
|
|
29
|
+
*/
|
|
30
|
+
export const getGitRoot = (fromPath) => {
|
|
31
|
+
try {
|
|
32
|
+
return execSync('git rev-parse --show-toplevel', { cwd: fromPath })
|
|
33
|
+
.toString()
|
|
34
|
+
.trim();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages GitNexus index storage in .gitnexus/ at repo root.
|
|
5
|
+
*/
|
|
6
|
+
export interface RepoMeta {
|
|
7
|
+
repoPath: string;
|
|
8
|
+
lastCommit: string;
|
|
9
|
+
indexedAt: string;
|
|
10
|
+
stats?: {
|
|
11
|
+
files?: number;
|
|
12
|
+
nodes?: number;
|
|
13
|
+
edges?: number;
|
|
14
|
+
communities?: number;
|
|
15
|
+
processes?: number;
|
|
16
|
+
embeddings?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface IndexedRepo {
|
|
20
|
+
repoPath: string;
|
|
21
|
+
storagePath: string;
|
|
22
|
+
kuzuPath: string;
|
|
23
|
+
metaPath: string;
|
|
24
|
+
meta: RepoMeta;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the .gitnexus storage path for a repository
|
|
28
|
+
*/
|
|
29
|
+
export declare const getStoragePath: (repoPath: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Get paths to key storage files
|
|
32
|
+
*/
|
|
33
|
+
export declare const getStoragePaths: (repoPath: string) => {
|
|
34
|
+
storagePath: string;
|
|
35
|
+
kuzuPath: string;
|
|
36
|
+
metaPath: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Load metadata from an indexed repo
|
|
40
|
+
*/
|
|
41
|
+
export declare const loadMeta: (storagePath: string) => Promise<RepoMeta | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Save metadata to storage
|
|
44
|
+
*/
|
|
45
|
+
export declare const saveMeta: (storagePath: string, meta: RepoMeta) => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Check if a path has a GitNexus index
|
|
48
|
+
*/
|
|
49
|
+
export declare const hasIndex: (repoPath: string) => Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Load an indexed repo from a path
|
|
52
|
+
*/
|
|
53
|
+
export declare const loadRepo: (repoPath: string) => Promise<IndexedRepo | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Find .gitnexus by walking up from a starting path
|
|
56
|
+
*/
|
|
57
|
+
export declare const findRepo: (startPath: string) => Promise<IndexedRepo | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Add .gitnexus to .gitignore if not already present
|
|
60
|
+
*/
|
|
61
|
+
export declare const addToGitignore: (repoPath: string) => Promise<void>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages GitNexus index storage in .gitnexus/ at repo root.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
const GITNEXUS_DIR = '.gitnexus';
|
|
9
|
+
/**
|
|
10
|
+
* Get the .gitnexus storage path for a repository
|
|
11
|
+
*/
|
|
12
|
+
export const getStoragePath = (repoPath) => {
|
|
13
|
+
return path.join(path.resolve(repoPath), GITNEXUS_DIR);
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Get paths to key storage files
|
|
17
|
+
*/
|
|
18
|
+
export const getStoragePaths = (repoPath) => {
|
|
19
|
+
const storagePath = getStoragePath(repoPath);
|
|
20
|
+
return {
|
|
21
|
+
storagePath,
|
|
22
|
+
kuzuPath: path.join(storagePath, 'kuzu'),
|
|
23
|
+
metaPath: path.join(storagePath, 'meta.json'),
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Load metadata from an indexed repo
|
|
28
|
+
*/
|
|
29
|
+
export const loadMeta = async (storagePath) => {
|
|
30
|
+
try {
|
|
31
|
+
const metaPath = path.join(storagePath, 'meta.json');
|
|
32
|
+
const raw = await fs.readFile(metaPath, 'utf-8');
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Save metadata to storage
|
|
41
|
+
*/
|
|
42
|
+
export const saveMeta = async (storagePath, meta) => {
|
|
43
|
+
await fs.mkdir(storagePath, { recursive: true });
|
|
44
|
+
const metaPath = path.join(storagePath, 'meta.json');
|
|
45
|
+
await fs.writeFile(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Check if a path has a GitNexus index
|
|
49
|
+
*/
|
|
50
|
+
export const hasIndex = async (repoPath) => {
|
|
51
|
+
const { metaPath } = getStoragePaths(repoPath);
|
|
52
|
+
try {
|
|
53
|
+
await fs.access(metaPath);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Load an indexed repo from a path
|
|
62
|
+
*/
|
|
63
|
+
export const loadRepo = async (repoPath) => {
|
|
64
|
+
const paths = getStoragePaths(repoPath);
|
|
65
|
+
const meta = await loadMeta(paths.storagePath);
|
|
66
|
+
if (!meta)
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
repoPath: path.resolve(repoPath),
|
|
70
|
+
...paths,
|
|
71
|
+
meta,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Find .gitnexus by walking up from a starting path
|
|
76
|
+
*/
|
|
77
|
+
export const findRepo = async (startPath) => {
|
|
78
|
+
let current = path.resolve(startPath);
|
|
79
|
+
const root = path.parse(current).root;
|
|
80
|
+
while (current !== root) {
|
|
81
|
+
const repo = await loadRepo(current);
|
|
82
|
+
if (repo)
|
|
83
|
+
return repo;
|
|
84
|
+
current = path.dirname(current);
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Add .gitnexus to .gitignore if not already present
|
|
90
|
+
*/
|
|
91
|
+
export const addToGitignore = async (repoPath) => {
|
|
92
|
+
const gitignorePath = path.join(repoPath, '.gitignore');
|
|
93
|
+
try {
|
|
94
|
+
const content = await fs.readFile(gitignorePath, 'utf-8');
|
|
95
|
+
if (content.includes(GITNEXUS_DIR))
|
|
96
|
+
return;
|
|
97
|
+
const newContent = content.endsWith('\n')
|
|
98
|
+
? `${content}${GITNEXUS_DIR}\n`
|
|
99
|
+
: `${content}\n${GITNEXUS_DIR}\n`;
|
|
100
|
+
await fs.writeFile(gitignorePath, newContent, 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// .gitignore doesn't exist, create it
|
|
104
|
+
await fs.writeFile(gitignorePath, `${GITNEXUS_DIR}\n`, 'utf-8');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { GraphNode, GraphRelationship, KnowledgeGraph } from '../core/graph/types.js';
|
|
2
|
+
import { CommunityDetectionResult } from '../core/ingestion/community-processor.js';
|
|
3
|
+
import { ProcessDetectionResult } from '../core/ingestion/process-processor.js';
|
|
4
|
+
export type PipelinePhase = 'idle' | 'extracting' | 'structure' | 'parsing' | 'imports' | 'calls' | 'heritage' | 'communities' | 'processes' | 'enriching' | 'complete' | 'error';
|
|
5
|
+
export interface PipelineProgress {
|
|
6
|
+
phase: PipelinePhase;
|
|
7
|
+
percent: number;
|
|
8
|
+
message: string;
|
|
9
|
+
detail?: string;
|
|
10
|
+
stats?: {
|
|
11
|
+
filesProcessed: number;
|
|
12
|
+
totalFiles: number;
|
|
13
|
+
nodesCreated: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface PipelineResult {
|
|
17
|
+
graph: KnowledgeGraph;
|
|
18
|
+
fileContents: Map<string, string>;
|
|
19
|
+
communityResult?: CommunityDetectionResult;
|
|
20
|
+
processResult?: ProcessDetectionResult;
|
|
21
|
+
}
|
|
22
|
+
export interface SerializablePipelineResult {
|
|
23
|
+
nodes: GraphNode[];
|
|
24
|
+
relationships: GraphRelationship[];
|
|
25
|
+
fileContents: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
export declare const serializePipelineResult: (result: PipelineResult) => SerializablePipelineResult;
|
|
28
|
+
export declare const deserializePipelineResult: (serialized: SerializablePipelineResult, createGraph: () => KnowledgeGraph) => PipelineResult;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Helper to convert PipelineResult to serializable format
|
|
2
|
+
export const serializePipelineResult = (result) => ({
|
|
3
|
+
nodes: result.graph.nodes,
|
|
4
|
+
relationships: result.graph.relationships,
|
|
5
|
+
fileContents: Object.fromEntries(result.fileContents),
|
|
6
|
+
});
|
|
7
|
+
// Helper to reconstruct from serializable format (used in main thread)
|
|
8
|
+
export const deserializePipelineResult = (serialized, createGraph) => {
|
|
9
|
+
const graph = createGraph();
|
|
10
|
+
serialized.nodes.forEach(node => graph.addNode(node));
|
|
11
|
+
serialized.relationships.forEach(rel => graph.addRelationship(rel));
|
|
12
|
+
return {
|
|
13
|
+
graph,
|
|
14
|
+
fileContents: new Map(Object.entries(serialized.fileContents)),
|
|
15
|
+
};
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitnexus",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
|
+
"author": "Abhigyan Patwari",
|
|
6
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
7
|
+
"homepage": "https://github.com/abhigyanpatwari/GitNexus#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/abhigyanpatwari/GitNexus.git",
|
|
11
|
+
"directory": "gitnexus"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/abhigyanpatwari/GitNexus/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"code-intelligence",
|
|
20
|
+
"knowledge-graph",
|
|
21
|
+
"cursor",
|
|
22
|
+
"claude",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"gitnexus",
|
|
25
|
+
"static-analysis",
|
|
26
|
+
"codebase-indexing"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"gitnexus": "dist/cli/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"skills",
|
|
35
|
+
"vendor"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"dev": "tsx watch src/cli/index.ts",
|
|
40
|
+
"prepare": "npm run build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@huggingface/transformers": "^3.0.0",
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
|
+
"commander": "^12.0.0",
|
|
46
|
+
"cors": "^2.8.5",
|
|
47
|
+
"express": "^4.19.2",
|
|
48
|
+
"glob": "^11.0.0",
|
|
49
|
+
"graphology": "^0.25.4",
|
|
50
|
+
"graphology-indices": "^0.17.0",
|
|
51
|
+
"graphology-utils": "^2.3.0",
|
|
52
|
+
"kuzu": "^0.11.3",
|
|
53
|
+
"lru-cache": "^11.0.0",
|
|
54
|
+
"mnemonist": "^0.39.0",
|
|
55
|
+
"ora": "^8.0.0",
|
|
56
|
+
"pandemonium": "^2.4.0",
|
|
57
|
+
"tree-sitter": "^0.21.0",
|
|
58
|
+
"tree-sitter-c": "^0.21.0",
|
|
59
|
+
"tree-sitter-c-sharp": "^0.21.0",
|
|
60
|
+
"tree-sitter-cpp": "^0.22.0",
|
|
61
|
+
"tree-sitter-go": "^0.21.0",
|
|
62
|
+
"tree-sitter-java": "^0.21.0",
|
|
63
|
+
"tree-sitter-javascript": "^0.21.0",
|
|
64
|
+
"tree-sitter-python": "^0.21.0",
|
|
65
|
+
"tree-sitter-rust": "^0.21.0",
|
|
66
|
+
"tree-sitter-typescript": "^0.21.0",
|
|
67
|
+
"typescript": "^5.4.5",
|
|
68
|
+
"uuid": "^13.0.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@types/cors": "^2.8.17",
|
|
72
|
+
"@types/express": "^4.17.21",
|
|
73
|
+
"@types/node": "^20.0.0",
|
|
74
|
+
"@types/uuid": "^10.0.0",
|
|
75
|
+
"tsx": "^4.0.0"
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=18.0.0"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gitnexus-debugging
|
|
3
|
+
description: Trace bugs through call chains using knowledge graph
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Debugging with GitNexus
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
```
|
|
10
|
+
0. If "Index is stale" → gitnexus_analyze({})
|
|
11
|
+
1. gitnexus_search({query}) → Find code related to error
|
|
12
|
+
2. gitnexus_explore({name, type: "symbol"}) → Get callers and callees
|
|
13
|
+
3. READ gitnexus://process/{name} → Trace execution flow
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
- "Why is this function failing?"
|
|
18
|
+
- "Trace where this error comes from"
|
|
19
|
+
- "Who calls this method?"
|
|
20
|
+
- "Debug the payment issue"
|
|
21
|
+
|
|
22
|
+
## Workflow Checklist
|
|
23
|
+
```
|
|
24
|
+
Bug Investigation:
|
|
25
|
+
- [ ] Understand the symptom (error message, behavior)
|
|
26
|
+
- [ ] gitnexus_search to find related code
|
|
27
|
+
- [ ] Identify the suspect function
|
|
28
|
+
- [ ] gitnexus_explore to see callers/callees
|
|
29
|
+
- [ ] READ gitnexus://process/{name} if suspect is in a process
|
|
30
|
+
- [ ] READ gitnexus://schema for Cypher query help
|
|
31
|
+
- [ ] gitnexus_cypher for custom traces
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Resource Reference
|
|
35
|
+
|
|
36
|
+
### gitnexus://schema
|
|
37
|
+
Graph schema for writing Cypher queries:
|
|
38
|
+
```yaml
|
|
39
|
+
nodes: [Function, Class, Method, File, Community, Process]
|
|
40
|
+
relationships: [CALLS, IMPORTS, EXTENDS, IMPLEMENTS, MEMBER_OF, STEP_IN_PROCESS]
|
|
41
|
+
example_queries:
|
|
42
|
+
find_callers: |
|
|
43
|
+
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "X"})
|
|
44
|
+
RETURN caller.name
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### gitnexus://process/{name}
|
|
48
|
+
Trace execution flow to find where bug might occur:
|
|
49
|
+
```yaml
|
|
50
|
+
name: CheckoutFlow
|
|
51
|
+
trace:
|
|
52
|
+
1: handleCheckout
|
|
53
|
+
2: validateCart
|
|
54
|
+
3: processPayment ← bug here?
|
|
55
|
+
4: sendConfirmation
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Tool Reference
|
|
59
|
+
|
|
60
|
+
### gitnexus_search
|
|
61
|
+
Find code related to error or symptom:
|
|
62
|
+
```
|
|
63
|
+
gitnexus_search({query: "payment validation error", depth: "full"})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### gitnexus_explore
|
|
67
|
+
Get symbol context:
|
|
68
|
+
```
|
|
69
|
+
gitnexus_explore({name: "validatePayment", type: "symbol"})
|
|
70
|
+
→ Callers: processCheckout, webhookHandler
|
|
71
|
+
→ Callees: verifyCard, fetchRates
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### gitnexus_cypher
|
|
75
|
+
Custom graph queries for tracing:
|
|
76
|
+
```cypher
|
|
77
|
+
// Trace call chain (2 hops)
|
|
78
|
+
MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"})
|
|
79
|
+
RETURN [n IN nodes(path) | n.name] AS chain
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Example: "Payment endpoint returns 500 intermittently"
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
1. gitnexus_search({query: "payment error handling"})
|
|
86
|
+
→ validatePayment, handlePaymentError, PaymentException
|
|
87
|
+
|
|
88
|
+
2. gitnexus_explore({name: "validatePayment", type: "symbol"})
|
|
89
|
+
→ Callees: verifyCard, fetchRates (external API!)
|
|
90
|
+
|
|
91
|
+
3. READ gitnexus://process/CheckoutFlow
|
|
92
|
+
→ Step 3: validatePayment → calls external API
|
|
93
|
+
|
|
94
|
+
4. Root cause: fetchRates calls external API without proper timeout
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Debugging Patterns
|
|
98
|
+
|
|
99
|
+
| Symptom | Approach |
|
|
100
|
+
|---------|----------|
|
|
101
|
+
| Error message | Search for error text, trace throw sites |
|
|
102
|
+
| Wrong return value | Trace data flow through callees |
|
|
103
|
+
| Intermittent failure | Look for external calls, timeouts |
|
|
104
|
+
| Performance issue | Find hot paths via callers count |
|