gitnexus 1.2.7 → 1.2.9
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 +9 -1
- package/dist/cli/analyze.d.ts +1 -1
- package/dist/cli/analyze.js +59 -15
- package/dist/cli/index.js +1 -1
- package/dist/cli/setup.js +8 -1
- package/dist/cli/view.d.ts +13 -0
- package/dist/cli/view.js +59 -0
- package/dist/core/embeddings/embedder.js +1 -0
- package/dist/core/graph/html-graph-viewer.d.ts +15 -0
- package/dist/core/graph/html-graph-viewer.js +542 -0
- package/dist/core/graph/html-graph-viewer.test.d.ts +1 -0
- package/dist/core/graph/html-graph-viewer.test.js +67 -0
- package/dist/core/ingestion/tree-sitter-queries.js +282 -282
- package/dist/mcp/core/embedder.js +8 -4
- package/dist/mcp/local/local-backend.d.ts +6 -0
- package/dist/mcp/local/local-backend.js +113 -6
- package/dist/mcp/tools.js +12 -3
- package/dist/server/api.d.ts +4 -2
- package/dist/server/api.js +253 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,12 @@ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up
|
|
|
39
39
|
|
|
40
40
|
> **Claude Code** gets the deepest integration: MCP tools + agent skills + PreToolUse hooks that automatically enrich grep/glob/bash calls with knowledge graph context.
|
|
41
41
|
|
|
42
|
+
### Community Integrations
|
|
43
|
+
|
|
44
|
+
| Agent | Install | Source |
|
|
45
|
+
|-------|---------|--------|
|
|
46
|
+
| [pi](https://pi.dev) | `pi install npm:pi-gitnexus` | [pi-gitnexus](https://github.com/tintinweb/pi-gitnexus) |
|
|
47
|
+
|
|
42
48
|
## MCP Setup (manual)
|
|
43
49
|
|
|
44
50
|
If you prefer to configure manually instead of using `gitnexus setup`:
|
|
@@ -135,7 +141,7 @@ gitnexus analyze [path] # Index a repository (or update stale index)
|
|
|
135
141
|
gitnexus analyze --force # Force full re-index
|
|
136
142
|
gitnexus analyze --skip-embeddings # Skip embedding generation (faster)
|
|
137
143
|
gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
|
|
138
|
-
gitnexus serve # Start HTTP server for web UI
|
|
144
|
+
gitnexus serve # Start local HTTP server (multi-repo) for web UI
|
|
139
145
|
gitnexus list # List all indexed repositories
|
|
140
146
|
gitnexus status # Show index status for current repo
|
|
141
147
|
gitnexus clean # Delete index for current repo
|
|
@@ -179,6 +185,8 @@ Installed automatically by both `gitnexus analyze` (per-repo) and `gitnexus setu
|
|
|
179
185
|
|
|
180
186
|
GitNexus also has a browser-based UI at [gitnexus.vercel.app](https://gitnexus.vercel.app) — 100% client-side, your code never leaves the browser.
|
|
181
187
|
|
|
188
|
+
**Local Backend Mode:** Run `gitnexus serve` and open the web UI locally — it auto-detects the server and shows all your indexed repos, with full AI chat support. No need to re-upload or re-index. The agent's tools (Cypher queries, search, code navigation) route through the backend HTTP API automatically.
|
|
189
|
+
|
|
182
190
|
## License
|
|
183
191
|
|
|
184
192
|
[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/)
|
package/dist/cli/analyze.d.ts
CHANGED
package/dist/cli/analyze.js
CHANGED
|
@@ -8,7 +8,7 @@ import cliProgress from 'cli-progress';
|
|
|
8
8
|
import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
|
|
9
9
|
import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReusedStatement, closeKuzu, createFTSIndex, loadCachedEmbeddings } from '../core/kuzu/kuzu-adapter.js';
|
|
10
10
|
import { runEmbeddingPipeline } from '../core/embeddings/embedding-pipeline.js';
|
|
11
|
-
|
|
11
|
+
// disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
|
|
12
12
|
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
|
|
13
13
|
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
14
14
|
import { generateAIContextFiles } from './ai-context.js';
|
|
@@ -70,11 +70,29 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
70
70
|
stopOnComplete: false,
|
|
71
71
|
}, cliProgress.Presets.shades_grey);
|
|
72
72
|
bar.start(100, 0, { phase: 'Initializing...' });
|
|
73
|
+
// Route all console output through bar.log() so the bar doesn't stamp itself
|
|
74
|
+
// multiple times when other code writes to stdout/stderr mid-render.
|
|
75
|
+
const origLog = console.log.bind(console);
|
|
76
|
+
const origWarn = console.warn.bind(console);
|
|
77
|
+
const origError = console.error.bind(console);
|
|
78
|
+
const barLog = (...args) => origLog(args.map(a => (typeof a === 'string' ? a : String(a))).join(' '));
|
|
79
|
+
console.log = barLog;
|
|
80
|
+
console.warn = barLog;
|
|
81
|
+
console.error = barLog;
|
|
82
|
+
// Show elapsed seconds for phases that run longer than 3s
|
|
83
|
+
let lastPhaseLabel = 'Initializing...';
|
|
84
|
+
let phaseStart = Date.now();
|
|
85
|
+
const elapsedTimer = setInterval(() => {
|
|
86
|
+
const elapsed = Math.round((Date.now() - phaseStart) / 1000);
|
|
87
|
+
if (elapsed >= 3) {
|
|
88
|
+
bar.update({ phase: `${lastPhaseLabel} (${elapsed}s)` });
|
|
89
|
+
}
|
|
90
|
+
}, 1000);
|
|
73
91
|
const t0Global = Date.now();
|
|
74
92
|
// ── Cache embeddings from existing index before rebuild ────────────
|
|
75
93
|
let cachedEmbeddingNodeIds = new Set();
|
|
76
94
|
let cachedEmbeddings = [];
|
|
77
|
-
if (existingMeta && !options?.force) {
|
|
95
|
+
if (options?.embeddings && existingMeta && !options?.force) {
|
|
78
96
|
try {
|
|
79
97
|
bar.update(0, { phase: 'Caching embeddings...' });
|
|
80
98
|
await initKuzu(kuzuPath);
|
|
@@ -94,10 +112,16 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
94
112
|
const pipelineResult = await runPipelineFromRepo(repoPath, (progress) => {
|
|
95
113
|
const phaseLabel = PHASE_LABELS[progress.phase] || progress.phase;
|
|
96
114
|
const scaled = Math.round(progress.percent * 0.6);
|
|
115
|
+
if (phaseLabel !== lastPhaseLabel) {
|
|
116
|
+
lastPhaseLabel = phaseLabel;
|
|
117
|
+
phaseStart = Date.now();
|
|
118
|
+
}
|
|
97
119
|
bar.update(scaled, { phase: phaseLabel });
|
|
98
120
|
});
|
|
99
121
|
// ── Phase 2: KuzuDB (60–85%) ──────────────────────────────────────
|
|
100
|
-
|
|
122
|
+
lastPhaseLabel = 'Loading into KuzuDB...';
|
|
123
|
+
phaseStart = Date.now();
|
|
124
|
+
bar.update(60, { phase: lastPhaseLabel });
|
|
101
125
|
await closeKuzu();
|
|
102
126
|
const kuzuFiles = [kuzuPath, `${kuzuPath}.wal`, `${kuzuPath}.lock`];
|
|
103
127
|
for (const f of kuzuFiles) {
|
|
@@ -117,7 +141,9 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
117
141
|
const kuzuTime = ((Date.now() - t0Kuzu) / 1000).toFixed(1);
|
|
118
142
|
const kuzuWarnings = kuzuResult.warnings;
|
|
119
143
|
// ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
|
|
120
|
-
|
|
144
|
+
lastPhaseLabel = 'Creating search indexes...';
|
|
145
|
+
phaseStart = Date.now();
|
|
146
|
+
bar.update(85, { phase: lastPhaseLabel });
|
|
121
147
|
const t0Fts = Date.now();
|
|
122
148
|
try {
|
|
123
149
|
await createFTSIndex('File', 'file_fts', ['name', 'content']);
|
|
@@ -146,22 +172,28 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
146
172
|
// ── Phase 4: Embeddings (90–98%) ──────────────────────────────────
|
|
147
173
|
const stats = await getKuzuStats();
|
|
148
174
|
let embeddingTime = '0.0';
|
|
149
|
-
let embeddingSkipped =
|
|
150
|
-
let embeddingSkipReason = '';
|
|
151
|
-
if (options?.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
let embeddingSkipped = true;
|
|
176
|
+
let embeddingSkipReason = 'off (use --embeddings to enable)';
|
|
177
|
+
if (options?.embeddings) {
|
|
178
|
+
if (stats.nodes > EMBEDDING_NODE_LIMIT) {
|
|
179
|
+
embeddingSkipReason = `skipped (${stats.nodes.toLocaleString()} nodes > ${EMBEDDING_NODE_LIMIT.toLocaleString()} limit)`;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
embeddingSkipped = false;
|
|
183
|
+
}
|
|
158
184
|
}
|
|
159
185
|
if (!embeddingSkipped) {
|
|
160
|
-
|
|
186
|
+
lastPhaseLabel = 'Loading embedding model...';
|
|
187
|
+
phaseStart = Date.now();
|
|
188
|
+
bar.update(90, { phase: lastPhaseLabel });
|
|
161
189
|
const t0Emb = Date.now();
|
|
162
190
|
await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
|
|
163
191
|
const scaled = 90 + Math.round((progress.percent / 100) * 8);
|
|
164
192
|
const label = progress.phase === 'loading-model' ? 'Loading embedding model...' : `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
|
|
193
|
+
if (label !== lastPhaseLabel) {
|
|
194
|
+
lastPhaseLabel = label;
|
|
195
|
+
phaseStart = Date.now();
|
|
196
|
+
}
|
|
165
197
|
bar.update(scaled, { phase: label });
|
|
166
198
|
}, {}, cachedEmbeddingNodeIds.size > 0 ? cachedEmbeddingNodeIds : undefined);
|
|
167
199
|
embeddingTime = ((Date.now() - t0Emb) / 1000).toFixed(1);
|
|
@@ -203,8 +235,14 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
203
235
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
204
236
|
});
|
|
205
237
|
await closeKuzu();
|
|
206
|
-
|
|
238
|
+
// Note: we intentionally do NOT call disposeEmbedder() here.
|
|
239
|
+
// ONNX Runtime's native cleanup segfaults on macOS and some Linux configs.
|
|
240
|
+
// Since the process exits immediately after, Node.js reclaims everything.
|
|
207
241
|
const totalTime = ((Date.now() - t0Global) / 1000).toFixed(1);
|
|
242
|
+
clearInterval(elapsedTimer);
|
|
243
|
+
console.log = origLog;
|
|
244
|
+
console.warn = origWarn;
|
|
245
|
+
console.error = origError;
|
|
208
246
|
bar.update(100, { phase: 'Done' });
|
|
209
247
|
bar.stop();
|
|
210
248
|
// ── Summary ───────────────────────────────────────────────────────
|
|
@@ -233,4 +271,10 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
233
271
|
console.log('\n Tip: Run `gitnexus setup` to configure MCP for your editor.');
|
|
234
272
|
}
|
|
235
273
|
console.log('');
|
|
274
|
+
// ONNX Runtime registers native atexit hooks that segfault during process
|
|
275
|
+
// shutdown on macOS (#38) and some Linux configs (#40). Force-exit to
|
|
276
|
+
// bypass them when embeddings were loaded.
|
|
277
|
+
if (!embeddingSkipped) {
|
|
278
|
+
process.exit(0);
|
|
279
|
+
}
|
|
236
280
|
};
|
package/dist/cli/index.js
CHANGED
|
@@ -24,7 +24,7 @@ program
|
|
|
24
24
|
.command('analyze [path]')
|
|
25
25
|
.description('Index a repository (full analysis)')
|
|
26
26
|
.option('-f, --force', 'Force full re-index even if up to date')
|
|
27
|
-
.option('--
|
|
27
|
+
.option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
|
|
28
28
|
.action(analyzeCommand);
|
|
29
29
|
program
|
|
30
30
|
.command('serve')
|
package/dist/cli/setup.js
CHANGED
|
@@ -13,9 +13,16 @@ import { getGlobalDir } from '../storage/repo-manager.js';
|
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
15
|
/**
|
|
16
|
-
* The MCP server entry for all editors
|
|
16
|
+
* The MCP server entry for all editors.
|
|
17
|
+
* On Windows, npx must be invoked via cmd /c since it's a .cmd script.
|
|
17
18
|
*/
|
|
18
19
|
function getMcpEntry() {
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
return {
|
|
22
|
+
command: 'cmd',
|
|
23
|
+
args: ['/c', 'npx', '-y', 'gitnexus@latest', 'mcp'],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
19
26
|
return {
|
|
20
27
|
command: 'npx',
|
|
21
28
|
args: ['-y', 'gitnexus@latest', 'mcp'],
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Command
|
|
3
|
+
*
|
|
4
|
+
* Generates a self-contained graph.html from the KuzuDB index and
|
|
5
|
+
* opens it in the default browser.
|
|
6
|
+
*
|
|
7
|
+
* Usage: gitnexus view [path] [--no-open]
|
|
8
|
+
*/
|
|
9
|
+
export interface ViewCommandOptions {
|
|
10
|
+
noOpen?: boolean;
|
|
11
|
+
output?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const viewCommand: (inputPath?: string, options?: ViewCommandOptions) => Promise<void>;
|
package/dist/cli/view.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Command
|
|
3
|
+
*
|
|
4
|
+
* Generates a self-contained graph.html from the KuzuDB index and
|
|
5
|
+
* opens it in the default browser.
|
|
6
|
+
*
|
|
7
|
+
* Usage: gitnexus view [path] [--no-open]
|
|
8
|
+
*/
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import { exec } from 'child_process';
|
|
12
|
+
import { findRepo } from '../storage/repo-manager.js';
|
|
13
|
+
import { initKuzu } from '../core/kuzu/kuzu-adapter.js';
|
|
14
|
+
import { buildGraph } from '../server/api.js';
|
|
15
|
+
import { generateHTMLGraphViewer } from '../core/graph/html-graph-viewer.js';
|
|
16
|
+
import { getCurrentCommit } from '../storage/git.js';
|
|
17
|
+
function openInBrowser(filePath) {
|
|
18
|
+
const url = `file://${filePath}`;
|
|
19
|
+
let cmd;
|
|
20
|
+
if (process.platform === 'darwin') {
|
|
21
|
+
cmd = `open "${url}"`;
|
|
22
|
+
}
|
|
23
|
+
else if (process.platform === 'win32') {
|
|
24
|
+
cmd = `start "" "${url}"`;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
cmd = `xdg-open "${url}"`;
|
|
28
|
+
}
|
|
29
|
+
exec(cmd, (err) => {
|
|
30
|
+
if (err)
|
|
31
|
+
console.error('Failed to open browser:', err.message);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export const viewCommand = async (inputPath, options) => {
|
|
35
|
+
console.log('⚠ Experimental: gitnexus view is under active development.\n');
|
|
36
|
+
const repoPath = inputPath ? path.resolve(inputPath) : process.cwd();
|
|
37
|
+
const repo = await findRepo(repoPath);
|
|
38
|
+
if (!repo) {
|
|
39
|
+
console.error('No index found. Run: gitnexus analyze');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const currentCommit = getCurrentCommit(repo.repoPath);
|
|
43
|
+
if (currentCommit !== repo.meta.lastCommit) {
|
|
44
|
+
console.warn('Index is stale — showing last indexed state. Run: gitnexus analyze\n');
|
|
45
|
+
}
|
|
46
|
+
await initKuzu(repo.kuzuPath);
|
|
47
|
+
const { nodes, relationships } = await buildGraph();
|
|
48
|
+
const projectName = path.basename(repo.repoPath);
|
|
49
|
+
const outputPath = options?.output
|
|
50
|
+
? path.resolve(options.output)
|
|
51
|
+
: path.join(repo.storagePath, 'graph.html');
|
|
52
|
+
const html = generateHTMLGraphViewer(nodes, relationships, projectName);
|
|
53
|
+
await fs.writeFile(outputPath, html, 'utf-8');
|
|
54
|
+
console.log(`Graph written to: ${outputPath}`);
|
|
55
|
+
console.log(`Nodes: ${nodes.length} Edges: ${relationships.length}`);
|
|
56
|
+
if (!options?.noOpen) {
|
|
57
|
+
openInBrowser(outputPath);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Graph Viewer Generator
|
|
3
|
+
*
|
|
4
|
+
* Produces a self-contained graph.html that renders the knowledge graph
|
|
5
|
+
* using Sigma.js v2 + graphology (both from CDN).
|
|
6
|
+
*
|
|
7
|
+
* Critical: node `content` fields are stripped before embedding to prevent
|
|
8
|
+
* </script> injection from source code breaking the HTML parser.
|
|
9
|
+
*/
|
|
10
|
+
import { GraphNode, GraphRelationship } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Generate a self-contained HTML file that renders the knowledge graph.
|
|
13
|
+
* Strips large/unsafe fields from nodes before embedding.
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateHTMLGraphViewer(nodes: GraphNode[], relationships: GraphRelationship[], projectName: string): string;
|