gitnexus 1.1.9 → 1.2.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.
Files changed (55) hide show
  1. package/README.md +50 -59
  2. package/dist/cli/analyze.js +114 -32
  3. package/dist/cli/eval-server.d.ts +30 -0
  4. package/dist/cli/eval-server.js +372 -0
  5. package/dist/cli/index.js +51 -1
  6. package/dist/cli/mcp.js +9 -0
  7. package/dist/cli/setup.js +44 -7
  8. package/dist/cli/tool.d.ts +37 -0
  9. package/dist/cli/tool.js +91 -0
  10. package/dist/cli/wiki.d.ts +13 -0
  11. package/dist/cli/wiki.js +199 -0
  12. package/dist/core/embeddings/embedder.d.ts +2 -2
  13. package/dist/core/embeddings/embedder.js +10 -10
  14. package/dist/core/embeddings/embedding-pipeline.d.ts +2 -1
  15. package/dist/core/embeddings/embedding-pipeline.js +12 -4
  16. package/dist/core/embeddings/types.d.ts +2 -2
  17. package/dist/core/ingestion/call-processor.d.ts +7 -0
  18. package/dist/core/ingestion/call-processor.js +61 -23
  19. package/dist/core/ingestion/community-processor.js +34 -26
  20. package/dist/core/ingestion/filesystem-walker.js +15 -10
  21. package/dist/core/ingestion/heritage-processor.d.ts +6 -0
  22. package/dist/core/ingestion/heritage-processor.js +68 -5
  23. package/dist/core/ingestion/import-processor.d.ts +22 -0
  24. package/dist/core/ingestion/import-processor.js +214 -19
  25. package/dist/core/ingestion/parsing-processor.d.ts +8 -1
  26. package/dist/core/ingestion/parsing-processor.js +66 -25
  27. package/dist/core/ingestion/pipeline.js +103 -39
  28. package/dist/core/ingestion/workers/parse-worker.d.ts +58 -0
  29. package/dist/core/ingestion/workers/parse-worker.js +451 -0
  30. package/dist/core/ingestion/workers/worker-pool.d.ts +22 -0
  31. package/dist/core/ingestion/workers/worker-pool.js +65 -0
  32. package/dist/core/kuzu/kuzu-adapter.d.ts +15 -1
  33. package/dist/core/kuzu/kuzu-adapter.js +177 -67
  34. package/dist/core/kuzu/schema.d.ts +1 -1
  35. package/dist/core/kuzu/schema.js +3 -0
  36. package/dist/core/wiki/generator.d.ts +96 -0
  37. package/dist/core/wiki/generator.js +674 -0
  38. package/dist/core/wiki/graph-queries.d.ts +80 -0
  39. package/dist/core/wiki/graph-queries.js +238 -0
  40. package/dist/core/wiki/html-viewer.d.ts +10 -0
  41. package/dist/core/wiki/html-viewer.js +297 -0
  42. package/dist/core/wiki/llm-client.d.ts +36 -0
  43. package/dist/core/wiki/llm-client.js +111 -0
  44. package/dist/core/wiki/prompts.d.ts +53 -0
  45. package/dist/core/wiki/prompts.js +174 -0
  46. package/dist/mcp/core/embedder.js +4 -2
  47. package/dist/mcp/core/kuzu-adapter.d.ts +2 -1
  48. package/dist/mcp/core/kuzu-adapter.js +35 -15
  49. package/dist/mcp/local/local-backend.js +9 -2
  50. package/dist/mcp/server.js +1 -1
  51. package/dist/storage/git.d.ts +0 -1
  52. package/dist/storage/git.js +1 -8
  53. package/dist/storage/repo-manager.d.ts +17 -0
  54. package/dist/storage/repo-manager.js +26 -0
  55. package/package.json +1 -1
package/README.md CHANGED
@@ -18,31 +18,37 @@ AI coding tools don't understand your codebase structure. They edit a function w
18
18
  ## Quick Start
19
19
 
20
20
  ```bash
21
- # Install
22
- npm install -g gitnexus
21
+ # Index your repo (run from repo root)
22
+ npx gitnexus analyze
23
+ ```
23
24
 
24
- # One-time: configure MCP for your editors
25
- gitnexus setup
25
+ That's it. This indexes the codebase, installs agent skills, registers Claude Code hooks, and creates `AGENTS.md` / `CLAUDE.md` context files — all in one command.
26
26
 
27
- # Index your repository (run from repo root)
28
- gitnexus analyze
27
+ To configure MCP for your editor, run `npx gitnexus setup` once — or set it up manually below.
29
28
 
30
- # Done! Open your editor MCP connects automatically.
31
- ```
29
+ `gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once.
32
30
 
33
- Or without installing globally:
31
+ ### Editor Support
34
32
 
35
- ```bash
36
- npx gitnexus setup # one-time
37
- npx gitnexus analyze # per repo
38
- ```
33
+ | Editor | MCP | Skills | Hooks (auto-augment) | Support |
34
+ |--------|-----|--------|---------------------|---------|
35
+ | **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
36
+ | **Cursor** | Yes | Yes | — | MCP + Skills |
37
+ | **Windsurf** | Yes | — | — | MCP |
38
+ | **OpenCode** | Yes | Yes | — | MCP + Skills |
39
39
 
40
- The `setup` command auto-detects Cursor, Claude Code, and OpenCode, then writes the correct global MCP config. You only run it once.
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
42
  ## MCP Setup (manual)
43
43
 
44
44
  If you prefer to configure manually instead of using `gitnexus setup`:
45
45
 
46
+ ### Claude Code (full support — MCP + skills + hooks)
47
+
48
+ ```bash
49
+ claude mcp add gitnexus -- npx -y gitnexus@latest mcp
50
+ ```
51
+
46
52
  ### Cursor / Windsurf
47
53
 
48
54
  Add to `~/.cursor/mcp.json` (global — works for all projects):
@@ -58,12 +64,6 @@ Add to `~/.cursor/mcp.json` (global — works for all projects):
58
64
  }
59
65
  ```
60
66
 
61
- ### Claude Code
62
-
63
- ```bash
64
- claude mcp add gitnexus -- npx -y gitnexus@latest mcp
65
- ```
66
-
67
67
  ### OpenCode
68
68
 
69
69
  Add to `~/.config/opencode/config.json`:
@@ -100,68 +100,59 @@ Your AI agent gets these tools automatically:
100
100
  | Tool | What It Does | `repo` Param |
101
101
  |------|-------------|--------------|
102
102
  | `list_repos` | Discover all indexed repositories | — |
103
- | `search` | Hybrid search (BM25 + semantic) with cluster context | Optional |
104
- | `overview` | List all clusters and processes | Optional |
105
- | `explore` | Deep dive on a symbol, cluster, or process | Optional |
106
- | `impact` | Blast radius analysis | Optional |
103
+ | `query` | Process-grouped hybrid search (BM25 + semantic + RRF) | Optional |
104
+ | `context` | 360-degree symbol view categorized refs, process participation | Optional |
105
+ | `impact` | Blast radius analysis with depth grouping and confidence | Optional |
106
+ | `detect_changes` | Git-diff impact maps changed lines to affected processes | Optional |
107
+ | `rename` | Multi-file coordinated rename with graph + text search | Optional |
107
108
  | `cypher` | Raw Cypher graph queries | Optional |
108
- | `analyze` | Index or re-index a repository | Optional |
109
109
 
110
- > With one indexed repo, the `repo` param is optional. With multiple, specify which: `search({query: "auth", repo: "my-app"})`.
110
+ > With one indexed repo, the `repo` param is optional. With multiple, specify which: `query({query: "auth", repo: "my-app"})`.
111
111
 
112
112
  ## MCP Resources
113
113
 
114
114
  | Resource | Purpose |
115
115
  |----------|---------|
116
116
  | `gitnexus://repos` | List all indexed repositories (read first) |
117
- | `gitnexus://repo/{name}/context` | Codebase stats and overview |
118
- | `gitnexus://repo/{name}/clusters` | All functional clusters |
117
+ | `gitnexus://repo/{name}/context` | Codebase stats, staleness check, and available tools |
118
+ | `gitnexus://repo/{name}/clusters` | All functional clusters with cohesion scores |
119
119
  | `gitnexus://repo/{name}/cluster/{name}` | Cluster members and details |
120
120
  | `gitnexus://repo/{name}/processes` | All execution flows |
121
- | `gitnexus://repo/{name}/process/{name}` | Full process trace |
121
+ | `gitnexus://repo/{name}/process/{name}` | Full process trace with steps |
122
122
  | `gitnexus://repo/{name}/schema` | Graph schema for Cypher queries |
123
123
 
124
+ ## MCP Prompts
125
+
126
+ | Prompt | What It Does |
127
+ |--------|-------------|
128
+ | `detect_impact` | Pre-commit change analysis — scope, affected processes, risk level |
129
+ | `generate_map` | Architecture documentation from the knowledge graph with mermaid diagrams |
130
+
124
131
  ## CLI Commands
125
132
 
126
133
  ```bash
127
- gitnexus setup # Configure MCP for your editors (one-time)
128
- gitnexus analyze [path] # Index a repository (or update stale index)
129
- gitnexus analyze --force # Force full re-index
130
- gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
131
- gitnexus serve # Start HTTP server for web UI
132
- gitnexus list # List all indexed repositories
133
- gitnexus status # Show index status for current repo
134
- gitnexus clean # Delete index for current repo
135
- gitnexus clean --all # Delete all indexes
134
+ gitnexus setup # Configure MCP for your editors (one-time)
135
+ gitnexus analyze [path] # Index a repository (or update stale index)
136
+ gitnexus analyze --force # Force full re-index
137
+ gitnexus analyze --skip-embeddings # Skip embedding generation (faster)
138
+ gitnexus mcp # Start MCP server (stdio) serves all indexed repos
139
+ gitnexus serve # Start HTTP server for web UI
140
+ gitnexus list # List all indexed repositories
141
+ gitnexus status # Show index status for current repo
142
+ gitnexus clean # Delete index for current repo
143
+ gitnexus clean --all --force # Delete all indexes
144
+ gitnexus wiki [path] # Generate LLM-powered docs from knowledge graph
145
+ gitnexus wiki --model <model> # Wiki with custom LLM model (default: gpt-4o-mini)
136
146
  ```
137
147
 
138
148
  ## Multi-Repo Support
139
149
 
140
- GitNexus supports indexing multiple repositories. Each `gitnexus analyze` registers the repo in a global registry (`~/.gitnexus/registry.json`). The MCP server serves all indexed repos automatically with lazy KuzuDB connections.
150
+ GitNexus supports indexing multiple repositories. Each `gitnexus analyze` registers the repo in a global registry (`~/.gitnexus/registry.json`). The MCP server serves all indexed repos automatically with lazy KuzuDB connections (max 5 concurrent, evicted after 5 minutes idle).
141
151
 
142
152
  ## Supported Languages
143
153
 
144
154
  TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust
145
155
 
146
- ## How Impact Analysis Works
147
-
148
- ```
149
- gitnexus_impact({target: "UserService", direction: "upstream", repo: "my-app"})
150
-
151
- TARGET: Class UserService (src/services/user.ts)
152
-
153
- UPSTREAM (what depends on this):
154
- Depth 1 (direct callers):
155
- handleLogin [CALLS 90%] → src/api/auth.ts:45
156
- handleRegister [CALLS 90%] → src/api/auth.ts:78
157
- Depth 2:
158
- authRouter [IMPORTS] → src/routes/auth.ts
159
-
160
- 8 files affected, 3 clusters touched
161
- ```
162
-
163
- Options: `maxDepth`, `minConfidence`, `relationTypes`, `includeTests`
164
-
165
156
  ## Agent Skills
166
157
 
167
158
  GitNexus ships with skill files that teach AI agents how to use the tools effectively:
@@ -171,7 +162,7 @@ GitNexus ships with skill files that teach AI agents how to use the tools effect
171
162
  - **Impact Analysis** — Analyze blast radius before changes
172
163
  - **Refactoring** — Plan safe refactors using dependency mapping
173
164
 
174
- These are installed automatically to `.claude/skills/` when you run `gitnexus analyze`.
165
+ Installed automatically by both `gitnexus analyze` (per-repo) and `gitnexus setup` (global).
175
166
 
176
167
  ## Requirements
177
168
 
@@ -6,14 +6,16 @@
6
6
  import path from 'path';
7
7
  import cliProgress from 'cli-progress';
8
8
  import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
9
- import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReusedStatement, closeKuzu, createFTSIndex } from '../core/kuzu/kuzu-adapter.js';
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
  import { disposeEmbedder } from '../core/embeddings/embedder.js';
12
- import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, getGlobalDir } from '../storage/repo-manager.js';
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';
15
15
  import fs from 'fs/promises';
16
16
  import { registerClaudeHook } from './claude-hooks.js';
17
+ /** Threshold: auto-skip embeddings for repos with more nodes than this */
18
+ const EMBEDDING_NODE_LIMIT = 50_000;
17
19
  const PHASE_LABELS = {
18
20
  extracting: 'Scanning files',
19
21
  structure: 'Building structure',
@@ -23,7 +25,11 @@ const PHASE_LABELS = {
23
25
  heritage: 'Extracting inheritance',
24
26
  communities: 'Detecting communities',
25
27
  processes: 'Detecting processes',
26
- complete: 'Complete',
28
+ complete: 'Pipeline complete',
29
+ kuzu: 'Loading into KuzuDB',
30
+ fts: 'Creating search indexes',
31
+ embeddings: 'Generating embeddings',
32
+ done: 'Done',
27
33
  };
28
34
  export const analyzeCommand = async (inputPath, options) => {
29
35
  console.log('\n GitNexus Analyzer\n');
@@ -34,14 +40,14 @@ export const analyzeCommand = async (inputPath, options) => {
34
40
  else {
35
41
  const gitRoot = getGitRoot(process.cwd());
36
42
  if (!gitRoot) {
37
- console.log(' Not inside a git repository\n');
43
+ console.log(' Not inside a git repository\n');
38
44
  process.exitCode = 1;
39
45
  return;
40
46
  }
41
47
  repoPath = gitRoot;
42
48
  }
43
49
  if (!isGitRepo(repoPath)) {
44
- console.log(' Not a git repository\n');
50
+ console.log(' Not a git repository\n');
45
51
  process.exitCode = 1;
46
52
  return;
47
53
  }
@@ -49,35 +55,70 @@ export const analyzeCommand = async (inputPath, options) => {
49
55
  const currentCommit = getCurrentCommit(repoPath);
50
56
  const existingMeta = await loadMeta(storagePath);
51
57
  if (existingMeta && !options?.force && existingMeta.lastCommit === currentCommit) {
52
- console.log(' Repository already up to date\n');
58
+ console.log(' Already up to date\n');
53
59
  return;
54
60
  }
55
- const multibar = new cliProgress.MultiBar({
61
+ // Single progress bar for entire pipeline
62
+ const bar = new cliProgress.SingleBar({
56
63
  format: ' {bar} {percentage}% | {phase}',
57
- barCompleteChar: '',
58
- barIncompleteChar: '',
64
+ barCompleteChar: '\u2588',
65
+ barIncompleteChar: '\u2591',
59
66
  hideCursor: true,
60
67
  barGlue: '',
61
68
  autopadding: true,
69
+ clearOnComplete: false,
70
+ stopOnComplete: false,
62
71
  }, cliProgress.Presets.shades_grey);
63
- const progressBar = multibar.create(100, 0, { phase: 'Initializing...' });
72
+ bar.start(100, 0, { phase: 'Initializing...' });
73
+ const t0Global = Date.now();
74
+ // ── Cache embeddings from existing index before rebuild ────────────
75
+ let cachedEmbeddingNodeIds = new Set();
76
+ let cachedEmbeddings = [];
77
+ if (existingMeta && !options?.force) {
78
+ try {
79
+ bar.update(0, { phase: 'Caching embeddings...' });
80
+ await initKuzu(kuzuPath);
81
+ const cached = await loadCachedEmbeddings();
82
+ cachedEmbeddingNodeIds = cached.embeddingNodeIds;
83
+ cachedEmbeddings = cached.embeddings;
84
+ await closeKuzu();
85
+ }
86
+ catch {
87
+ try {
88
+ await closeKuzu();
89
+ }
90
+ catch { }
91
+ }
92
+ }
93
+ // ── Phase 1: Full Pipeline (0–60%) ─────────────────────────────────
64
94
  const pipelineResult = await runPipelineFromRepo(repoPath, (progress) => {
65
95
  const phaseLabel = PHASE_LABELS[progress.phase] || progress.phase;
66
- progressBar.update(progress.percent, { phase: phaseLabel });
96
+ const scaled = Math.round(progress.percent * 0.6);
97
+ bar.update(scaled, { phase: phaseLabel });
67
98
  });
68
- progressBar.update(100, { phase: 'Loading graph into KuzuDB...' });
99
+ // ── Phase 2: KuzuDB (60–85%) ──────────────────────────────────────
100
+ bar.update(60, { phase: 'Loading into KuzuDB...' });
69
101
  await closeKuzu();
70
- const fsClean = await import('fs/promises');
71
102
  const kuzuFiles = [kuzuPath, `${kuzuPath}.wal`, `${kuzuPath}.lock`];
72
103
  for (const f of kuzuFiles) {
73
104
  try {
74
- await fsClean.rm(f, { recursive: true, force: true });
105
+ await fs.rm(f, { recursive: true, force: true });
75
106
  }
76
- catch { /* may not exist */ }
107
+ catch { }
77
108
  }
109
+ const t0Kuzu = Date.now();
78
110
  await initKuzu(kuzuPath);
79
- await loadGraphToKuzu(pipelineResult.graph, pipelineResult.fileContents, storagePath);
80
- progressBar.update(100, { phase: 'Creating search indexes...' });
111
+ let kuzuMsgCount = 0;
112
+ const kuzuResult = await loadGraphToKuzu(pipelineResult.graph, pipelineResult.fileContents, storagePath, (msg) => {
113
+ kuzuMsgCount++;
114
+ const progress = Math.min(84, 60 + Math.round((kuzuMsgCount / (kuzuMsgCount + 10)) * 24));
115
+ bar.update(progress, { phase: msg });
116
+ });
117
+ const kuzuTime = ((Date.now() - t0Kuzu) / 1000).toFixed(1);
118
+ const kuzuWarnings = kuzuResult.warnings;
119
+ // ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
120
+ bar.update(85, { phase: 'Creating search indexes...' });
121
+ const t0Fts = Date.now();
81
122
  try {
82
123
  await createFTSIndex('File', 'file_fts', ['name', 'content']);
83
124
  await createFTSIndex('Function', 'function_fts', ['name', 'content']);
@@ -86,15 +127,47 @@ export const analyzeCommand = async (inputPath, options) => {
86
127
  await createFTSIndex('Interface', 'interface_fts', ['name', 'content']);
87
128
  }
88
129
  catch (e) {
89
- console.error(' Note: Some FTS indexes may not have been created:', e.message);
130
+ // Non-fatal FTS is best-effort
90
131
  }
91
- if (!options?.skipEmbeddings) {
92
- progressBar.update(100, { phase: 'Generating embeddings...' });
93
- await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
94
- progressBar.update(progress.percent, { phase: `Embeddings ${progress.percent}%` });
95
- });
132
+ const ftsTime = ((Date.now() - t0Fts) / 1000).toFixed(1);
133
+ // ── Phase 3.5: Re-insert cached embeddings ────────────────────────
134
+ if (cachedEmbeddings.length > 0) {
135
+ bar.update(88, { phase: `Restoring ${cachedEmbeddings.length} cached embeddings...` });
136
+ const EMBED_BATCH = 200;
137
+ for (let i = 0; i < cachedEmbeddings.length; i += EMBED_BATCH) {
138
+ const batch = cachedEmbeddings.slice(i, i + EMBED_BATCH);
139
+ const paramsList = batch.map(e => ({ nodeId: e.nodeId, embedding: e.embedding }));
140
+ try {
141
+ await executeWithReusedStatement(`CREATE (e:CodeEmbedding {nodeId: $nodeId, embedding: $embedding})`, paramsList);
142
+ }
143
+ catch { /* some may fail if node was removed, that's fine */ }
144
+ }
96
145
  }
146
+ // ── Phase 4: Embeddings (90–98%) ──────────────────────────────────
97
147
  const stats = await getKuzuStats();
148
+ let embeddingTime = '0.0';
149
+ let embeddingSkipped = false;
150
+ let embeddingSkipReason = '';
151
+ if (options?.skipEmbeddings) {
152
+ embeddingSkipped = true;
153
+ embeddingSkipReason = 'skipped (--skip-embeddings)';
154
+ }
155
+ else if (stats.nodes > EMBEDDING_NODE_LIMIT) {
156
+ embeddingSkipped = true;
157
+ embeddingSkipReason = `skipped (${stats.nodes.toLocaleString()} nodes > ${EMBEDDING_NODE_LIMIT.toLocaleString()} limit)`;
158
+ }
159
+ if (!embeddingSkipped) {
160
+ bar.update(90, { phase: 'Loading embedding model...' });
161
+ const t0Emb = Date.now();
162
+ await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
163
+ const scaled = 90 + Math.round((progress.percent / 100) * 8);
164
+ const label = progress.phase === 'loading-model' ? 'Loading embedding model...' : `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
165
+ bar.update(scaled, { phase: label });
166
+ }, {}, cachedEmbeddingNodeIds.size > 0 ? cachedEmbeddingNodeIds : undefined);
167
+ embeddingTime = ((Date.now() - t0Emb) / 1000).toFixed(1);
168
+ }
169
+ // ── Phase 5: Finalize (98–100%) ───────────────────────────────────
170
+ bar.update(98, { phase: 'Saving metadata...' });
98
171
  const meta = {
99
172
  repoPath,
100
173
  lastCommit: currentCommit,
@@ -110,7 +183,6 @@ export const analyzeCommand = async (inputPath, options) => {
110
183
  await saveMeta(storagePath, meta);
111
184
  await registerRepo(repoPath, meta);
112
185
  await addToGitignore(repoPath);
113
- // Auto-register Claude Code hook (idempotent)
114
186
  const hookResult = await registerClaudeHook();
115
187
  const projectName = path.basename(repoPath);
116
188
  let aggregatedClusterCount = 0;
@@ -132,17 +204,27 @@ export const analyzeCommand = async (inputPath, options) => {
132
204
  });
133
205
  await closeKuzu();
134
206
  await disposeEmbedder();
135
- multibar.stop();
136
- console.log('\n ✓ Repository indexed successfully\n');
137
- console.log(` Path: ${repoPath}`);
138
- console.log(` Storage: ${storagePath}`);
139
- console.log(` Registry: ${getGlobalDir()}`);
140
- console.log(` Stats: ${stats.nodes} nodes, ${stats.edges} edges, ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters, ${pipelineResult.processResult?.stats.totalProcesses || 0} processes`);
207
+ const totalTime = ((Date.now() - t0Global) / 1000).toFixed(1);
208
+ bar.update(100, { phase: 'Done' });
209
+ bar.stop();
210
+ // ── Summary ───────────────────────────────────────────────────────
211
+ const embeddingsCached = cachedEmbeddings.length > 0;
212
+ console.log(`\n Repository indexed successfully (${totalTime}s)${embeddingsCached ? ` [${cachedEmbeddings.length} embeddings cached]` : ''}\n`);
213
+ console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineResult.processResult?.stats.totalProcesses || 0} flows`);
214
+ console.log(` KuzuDB ${kuzuTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
215
+ console.log(` ${repoPath}`);
141
216
  if (aiContext.files.length > 0) {
142
- console.log(` Context: ${aiContext.files.join(', ')}`);
217
+ console.log(` Context: ${aiContext.files.join(', ')}`);
143
218
  }
144
219
  if (hookResult.registered) {
145
- console.log(` Hooks: ${hookResult.message}`);
220
+ console.log(` Hooks: ${hookResult.message}`);
221
+ }
222
+ // Show warnings (missing schema pairs, etc.) after the clean output
223
+ if (kuzuWarnings.length > 0) {
224
+ console.log(`\n Warnings (${kuzuWarnings.length}):`);
225
+ for (const w of kuzuWarnings) {
226
+ console.log(` ${w}`);
227
+ }
146
228
  }
147
229
  try {
148
230
  await fs.access(getGlobalRegistryPath());
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Eval Server — Lightweight HTTP server for SWE-bench evaluation
3
+ *
4
+ * Keeps KuzuDB warm in memory so tool calls from the agent are near-instant.
5
+ * Designed to run inside Docker containers during SWE-bench evaluation.
6
+ *
7
+ * KEY DESIGN: Returns LLM-friendly text, not raw JSON.
8
+ * Raw JSON wastes tokens and is hard for models to parse. The text formatter
9
+ * converts structured results into compact, readable output that models
10
+ * can immediately act on. Next-step hints guide the agent through a
11
+ * productive tool-chaining workflow (query → context → impact → fix).
12
+ *
13
+ * Architecture:
14
+ * Agent bash cmd → curl localhost:PORT/tool/query → eval-server → LocalBackend → format → text
15
+ *
16
+ * Usage:
17
+ * gitnexus eval-server # default port 4848
18
+ * gitnexus eval-server --port 4848 # explicit port
19
+ * gitnexus eval-server --idle-timeout 300 # auto-shutdown after 300s idle
20
+ *
21
+ * API:
22
+ * POST /tool/:name — Call a tool. Body is JSON arguments. Returns formatted text.
23
+ * GET /health — Health check. Returns {"status":"ok","repos":[...]}
24
+ * POST /shutdown — Graceful shutdown.
25
+ */
26
+ export interface EvalServerOptions {
27
+ port?: string;
28
+ idleTimeout?: string;
29
+ }
30
+ export declare function evalServerCommand(options?: EvalServerOptions): Promise<void>;