gitnexus 1.4.0 → 1.4.5

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 (102) hide show
  1. package/README.md +19 -18
  2. package/dist/cli/analyze.js +37 -28
  3. package/dist/cli/augment.js +1 -1
  4. package/dist/cli/eval-server.d.ts +1 -1
  5. package/dist/cli/eval-server.js +1 -1
  6. package/dist/cli/index.js +1 -0
  7. package/dist/cli/mcp.js +1 -1
  8. package/dist/cli/setup.js +25 -13
  9. package/dist/cli/status.js +13 -4
  10. package/dist/cli/tool.d.ts +1 -1
  11. package/dist/cli/tool.js +2 -2
  12. package/dist/cli/wiki.js +2 -2
  13. package/dist/config/ignore-service.d.ts +25 -0
  14. package/dist/config/ignore-service.js +76 -0
  15. package/dist/config/supported-languages.d.ts +1 -0
  16. package/dist/config/supported-languages.js +1 -1
  17. package/dist/core/augmentation/engine.js +94 -67
  18. package/dist/core/embeddings/embedder.d.ts +1 -1
  19. package/dist/core/embeddings/embedder.js +1 -1
  20. package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
  21. package/dist/core/embeddings/embedding-pipeline.js +52 -25
  22. package/dist/core/embeddings/types.d.ts +1 -1
  23. package/dist/core/ingestion/call-processor.d.ts +6 -7
  24. package/dist/core/ingestion/call-processor.js +490 -127
  25. package/dist/core/ingestion/call-routing.d.ts +53 -0
  26. package/dist/core/ingestion/call-routing.js +108 -0
  27. package/dist/core/ingestion/entry-point-scoring.js +13 -2
  28. package/dist/core/ingestion/export-detection.js +1 -0
  29. package/dist/core/ingestion/filesystem-walker.js +4 -3
  30. package/dist/core/ingestion/framework-detection.js +9 -0
  31. package/dist/core/ingestion/heritage-processor.d.ts +3 -4
  32. package/dist/core/ingestion/heritage-processor.js +40 -50
  33. package/dist/core/ingestion/import-processor.d.ts +3 -5
  34. package/dist/core/ingestion/import-processor.js +41 -10
  35. package/dist/core/ingestion/parsing-processor.d.ts +2 -1
  36. package/dist/core/ingestion/parsing-processor.js +41 -4
  37. package/dist/core/ingestion/pipeline.d.ts +5 -1
  38. package/dist/core/ingestion/pipeline.js +174 -121
  39. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  40. package/dist/core/ingestion/resolution-context.js +132 -0
  41. package/dist/core/ingestion/resolvers/index.d.ts +2 -0
  42. package/dist/core/ingestion/resolvers/index.js +2 -0
  43. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  44. package/dist/core/ingestion/resolvers/python.js +52 -0
  45. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  46. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  47. package/dist/core/ingestion/resolvers/standard.js +0 -22
  48. package/dist/core/ingestion/resolvers/utils.js +2 -0
  49. package/dist/core/ingestion/symbol-table.d.ts +3 -0
  50. package/dist/core/ingestion/symbol-table.js +1 -0
  51. package/dist/core/ingestion/tree-sitter-queries.d.ts +3 -2
  52. package/dist/core/ingestion/tree-sitter-queries.js +53 -1
  53. package/dist/core/ingestion/type-env.d.ts +32 -10
  54. package/dist/core/ingestion/type-env.js +520 -47
  55. package/dist/core/ingestion/type-extractors/c-cpp.js +326 -1
  56. package/dist/core/ingestion/type-extractors/csharp.js +282 -2
  57. package/dist/core/ingestion/type-extractors/go.js +333 -2
  58. package/dist/core/ingestion/type-extractors/index.d.ts +3 -2
  59. package/dist/core/ingestion/type-extractors/index.js +3 -1
  60. package/dist/core/ingestion/type-extractors/jvm.js +537 -4
  61. package/dist/core/ingestion/type-extractors/php.js +387 -7
  62. package/dist/core/ingestion/type-extractors/python.js +356 -5
  63. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  64. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  65. package/dist/core/ingestion/type-extractors/rust.js +399 -2
  66. package/dist/core/ingestion/type-extractors/shared.d.ts +116 -1
  67. package/dist/core/ingestion/type-extractors/shared.js +488 -14
  68. package/dist/core/ingestion/type-extractors/swift.js +95 -1
  69. package/dist/core/ingestion/type-extractors/types.d.ts +81 -0
  70. package/dist/core/ingestion/type-extractors/typescript.js +436 -2
  71. package/dist/core/ingestion/utils.d.ts +33 -2
  72. package/dist/core/ingestion/utils.js +399 -27
  73. package/dist/core/ingestion/workers/parse-worker.d.ts +18 -1
  74. package/dist/core/ingestion/workers/parse-worker.js +169 -19
  75. package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
  76. package/dist/core/{kuzu → lbug}/csv-generator.js +1 -1
  77. package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
  78. package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +70 -65
  79. package/dist/core/{kuzu → lbug}/schema.d.ts +1 -1
  80. package/dist/core/{kuzu → lbug}/schema.js +1 -1
  81. package/dist/core/search/bm25-index.d.ts +4 -4
  82. package/dist/core/search/bm25-index.js +10 -10
  83. package/dist/core/search/hybrid-search.d.ts +2 -2
  84. package/dist/core/search/hybrid-search.js +6 -6
  85. package/dist/core/tree-sitter/parser-loader.js +9 -2
  86. package/dist/core/wiki/generator.d.ts +2 -2
  87. package/dist/core/wiki/generator.js +4 -4
  88. package/dist/core/wiki/graph-queries.d.ts +4 -4
  89. package/dist/core/wiki/graph-queries.js +7 -7
  90. package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -7
  91. package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +72 -43
  92. package/dist/mcp/local/local-backend.d.ts +6 -6
  93. package/dist/mcp/local/local-backend.js +25 -18
  94. package/dist/server/api.js +12 -12
  95. package/dist/server/mcp-http.d.ts +1 -1
  96. package/dist/server/mcp-http.js +1 -1
  97. package/dist/storage/repo-manager.d.ts +20 -2
  98. package/dist/storage/repo-manager.js +55 -1
  99. package/dist/types/pipeline.d.ts +1 -1
  100. package/package.json +5 -3
  101. package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
  102. package/dist/core/ingestion/symbol-resolver.js +0 -83
package/README.md CHANGED
@@ -96,7 +96,7 @@ GitNexus builds a complete knowledge graph of your codebase through a multi-phas
96
96
  5. **Processes** — Traces execution flows from entry points through call chains
97
97
  6. **Search** — Builds hybrid search indexes for fast retrieval
98
98
 
99
- The result is a **KuzuDB graph database** stored locally in `.gitnexus/` with full-text search and semantic embeddings.
99
+ The result is a **LadybugDB graph database** stored locally in `.gitnexus/` with full-text search and semantic embeddings.
100
100
 
101
101
  ## MCP Tools
102
102
 
@@ -157,26 +157,27 @@ GitNexus supports indexing multiple repositories. Each `gitnexus analyze` regist
157
157
 
158
158
  ## Supported Languages
159
159
 
160
- TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift
160
+ TypeScript, JavaScript, Python, Java, C, C++, C#, Go, Rust, PHP, Kotlin, Swift, Ruby
161
161
 
162
162
  ### Language Feature Matrix
163
163
 
164
- | Language | Imports | Types | Exports | Named Bindings | Config | Frameworks | Entry Points | Heritage |
165
- |----------|---------|-------|---------|----------------|--------|------------|-------------|----------|
166
- | TypeScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
167
- | JavaScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
168
- | Python | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
169
- | C# | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
170
- | Java | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
171
- | Kotlin | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
172
- | Go | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ |
173
- | Rust | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ |
174
- | PHP | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
175
- | Swift | — | ✓ | ✓ | — | ✓ | | ✓ | ✓ |
176
- | C | — | ✓ | ✓ | | | ✓ | ✓ | ✓ |
177
- | C++ | — | | ✓ | — | | ✓ | ✓ | ✓ |
178
-
179
- **Imports** — cross-file import resolution · **Types** — type annotation extraction · **Exports** — public/exported symbol detection · **Named Bindings** — `import { X }` tracking · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics · **Heritage** — class inheritance / interface implementation
164
+ | Language | Imports | Named Bindings | Exports | Heritage | Type Annotations | Constructor Inference | Config | Frameworks | Entry Points |
165
+ |----------|---------|----------------|---------|----------|-----------------|---------------------|--------|------------|-------------|
166
+ | TypeScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
167
+ | JavaScript | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ |
168
+ | Python | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
169
+ | Java | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
170
+ | Kotlin | ✓ | ✓ | ✓ | ✓ | | ✓ | — | ✓ | ✓ |
171
+ | C# | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ |
172
+ | Go | ✓ | — | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ |
173
+ | Rust | ✓ | ✓ | ✓ | ✓ | | ✓ | — | ✓ | ✓ |
174
+ | PHP | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ | |
175
+ | Ruby | ✓ | — | ✓ | ✓ | — | ✓ | | ✓ | ✓ |
176
+ | Swift | — | — | ✓ | ✓ | | | ✓ | ✓ | ✓ |
177
+ | C | — | | ✓ | — | | ✓ | — | ✓ | ✓ |
178
+ | C++ | — | — | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ |
179
+
180
+ **Imports** — cross-file import resolution · **Named Bindings** — `import { X as Y }` / re-export tracking · **Exports** — public/exported symbol detection · **Heritage** — class inheritance, interfaces, mixins · **Type Annotations** — explicit type extraction for receiver resolution · **Constructor Inference** — infer receiver type from constructor calls (`self`/`this` resolution included for all languages) · **Config** — language toolchain config parsing (tsconfig, go.mod, etc.) · **Frameworks** — AST-based framework pattern detection · **Entry Points** — entry point scoring heuristics
180
181
 
181
182
  ## Agent Skills
182
183
 
@@ -8,12 +8,12 @@ import { execFileSync } from 'child_process';
8
8
  import v8 from 'v8';
9
9
  import cliProgress from 'cli-progress';
10
10
  import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
11
- import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReusedStatement, closeKuzu, createFTSIndex, loadCachedEmbeddings } from '../core/kuzu/kuzu-adapter.js';
11
+ import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, createFTSIndex, loadCachedEmbeddings } from '../core/lbug/lbug-adapter.js';
12
12
  // Embedding imports are lazy (dynamic import) so onnxruntime-node is never
13
13
  // loaded when embeddings are not requested. This avoids crashes on Node
14
14
  // versions whose ABI is not yet supported by the native binary (#89).
15
15
  // disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
16
- import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
16
+ import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, cleanupOldKuzuFiles } from '../storage/repo-manager.js';
17
17
  import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
18
18
  import { generateAIContextFiles } from './ai-context.js';
19
19
  import { generateSkillFiles } from './skill-gen.js';
@@ -51,7 +51,7 @@ const PHASE_LABELS = {
51
51
  communities: 'Detecting communities',
52
52
  processes: 'Detecting processes',
53
53
  complete: 'Pipeline complete',
54
- kuzu: 'Loading into KuzuDB',
54
+ lbug: 'Loading into LadybugDB',
55
55
  fts: 'Creating search indexes',
56
56
  embeddings: 'Generating embeddings',
57
57
  done: 'Done',
@@ -81,13 +81,22 @@ export const analyzeCommand = async (inputPath, options) => {
81
81
  process.exitCode = 1;
82
82
  return;
83
83
  }
84
- const { storagePath, kuzuPath } = getStoragePaths(repoPath);
84
+ const { storagePath, lbugPath } = getStoragePaths(repoPath);
85
+ // Clean up stale KuzuDB files from before the LadybugDB migration.
86
+ // If kuzu existed but lbug doesn't, we're doing a migration re-index — say so.
87
+ const kuzuResult = await cleanupOldKuzuFiles(storagePath);
88
+ if (kuzuResult.found && kuzuResult.needsReindex) {
89
+ console.log(' Migrating from KuzuDB to LadybugDB — rebuilding index...\n');
90
+ }
85
91
  const currentCommit = getCurrentCommit(repoPath);
86
92
  const existingMeta = await loadMeta(storagePath);
87
93
  if (existingMeta && !options?.force && !options?.skills && existingMeta.lastCommit === currentCommit) {
88
94
  console.log(' Already up to date\n');
89
95
  return;
90
96
  }
97
+ if (process.env.GITNEXUS_NO_GITIGNORE) {
98
+ console.log(' GITNEXUS_NO_GITIGNORE is set — skipping .gitignore (still reading .gitnexusignore)\n');
99
+ }
91
100
  // Single progress bar for entire pipeline
92
101
  const bar = new cliProgress.SingleBar({
93
102
  format: ' {bar} {percentage}% | {phase}',
@@ -108,7 +117,7 @@ export const analyzeCommand = async (inputPath, options) => {
108
117
  aborted = true;
109
118
  bar.stop();
110
119
  console.log('\n Interrupted — cleaning up...');
111
- closeKuzu().catch(() => { }).finally(() => process.exit(130));
120
+ closeLbug().catch(() => { }).finally(() => process.exit(130));
112
121
  };
113
122
  process.on('SIGINT', sigintHandler);
114
123
  // Route all console output through bar.log() so the bar doesn't stamp itself
@@ -154,15 +163,15 @@ export const analyzeCommand = async (inputPath, options) => {
154
163
  if (options?.embeddings && existingMeta && !options?.force) {
155
164
  try {
156
165
  updateBar(0, 'Caching embeddings...');
157
- await initKuzu(kuzuPath);
166
+ await initLbug(lbugPath);
158
167
  const cached = await loadCachedEmbeddings();
159
168
  cachedEmbeddingNodeIds = cached.embeddingNodeIds;
160
169
  cachedEmbeddings = cached.embeddings;
161
- await closeKuzu();
170
+ await closeLbug();
162
171
  }
163
172
  catch {
164
173
  try {
165
- await closeKuzu();
174
+ await closeLbug();
166
175
  }
167
176
  catch { }
168
177
  }
@@ -173,26 +182,26 @@ export const analyzeCommand = async (inputPath, options) => {
173
182
  const scaled = Math.round(progress.percent * 0.6);
174
183
  updateBar(scaled, phaseLabel);
175
184
  });
176
- // ── Phase 2: KuzuDB (60–85%) ──────────────────────────────────────
177
- updateBar(60, 'Loading into KuzuDB...');
178
- await closeKuzu();
179
- const kuzuFiles = [kuzuPath, `${kuzuPath}.wal`, `${kuzuPath}.lock`];
180
- for (const f of kuzuFiles) {
185
+ // ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────────
186
+ updateBar(60, 'Loading into LadybugDB...');
187
+ await closeLbug();
188
+ const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
189
+ for (const f of lbugFiles) {
181
190
  try {
182
191
  await fs.rm(f, { recursive: true, force: true });
183
192
  }
184
193
  catch { }
185
194
  }
186
- const t0Kuzu = Date.now();
187
- await initKuzu(kuzuPath);
188
- let kuzuMsgCount = 0;
189
- const kuzuResult = await loadGraphToKuzu(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
190
- kuzuMsgCount++;
191
- const progress = Math.min(84, 60 + Math.round((kuzuMsgCount / (kuzuMsgCount + 10)) * 24));
195
+ const t0Lbug = Date.now();
196
+ await initLbug(lbugPath);
197
+ let lbugMsgCount = 0;
198
+ const lbugResult = await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
199
+ lbugMsgCount++;
200
+ const progress = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
192
201
  updateBar(progress, msg);
193
202
  });
194
- const kuzuTime = ((Date.now() - t0Kuzu) / 1000).toFixed(1);
195
- const kuzuWarnings = kuzuResult.warnings;
203
+ const lbugTime = ((Date.now() - t0Lbug) / 1000).toFixed(1);
204
+ const lbugWarnings = lbugResult.warnings;
196
205
  // ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
197
206
  updateBar(85, 'Creating search indexes...');
198
207
  const t0Fts = Date.now();
@@ -221,7 +230,7 @@ export const analyzeCommand = async (inputPath, options) => {
221
230
  }
222
231
  }
223
232
  // ── Phase 4: Embeddings (90–98%) ──────────────────────────────────
224
- const stats = await getKuzuStats();
233
+ const stats = await getLbugStats();
225
234
  let embeddingTime = '0.0';
226
235
  let embeddingSkipped = true;
227
236
  let embeddingSkipReason = 'off (use --embeddings to enable)';
@@ -293,7 +302,7 @@ export const analyzeCommand = async (inputPath, options) => {
293
302
  clusters: aggregatedClusterCount,
294
303
  processes: pipelineResult.processResult?.stats.totalProcesses,
295
304
  }, generatedSkills);
296
- await closeKuzu();
305
+ await closeLbug();
297
306
  // Note: we intentionally do NOT call disposeEmbedder() here.
298
307
  // ONNX Runtime's native cleanup segfaults on macOS and some Linux configs.
299
308
  // Since the process exits immediately after, Node.js reclaims everything.
@@ -309,18 +318,18 @@ export const analyzeCommand = async (inputPath, options) => {
309
318
  const embeddingsCached = cachedEmbeddings.length > 0;
310
319
  console.log(`\n Repository indexed successfully (${totalTime}s)${embeddingsCached ? ` [${cachedEmbeddings.length} embeddings cached]` : ''}\n`);
311
320
  console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineResult.processResult?.stats.totalProcesses || 0} flows`);
312
- console.log(` KuzuDB ${kuzuTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
321
+ console.log(` LadybugDB ${lbugTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
313
322
  console.log(` ${repoPath}`);
314
323
  if (aiContext.files.length > 0) {
315
324
  console.log(` Context: ${aiContext.files.join(', ')}`);
316
325
  }
317
326
  // Show a quiet summary if some edge types needed fallback insertion
318
- if (kuzuWarnings.length > 0) {
319
- const totalFallback = kuzuWarnings.reduce((sum, w) => {
327
+ if (lbugWarnings.length > 0) {
328
+ const totalFallback = lbugWarnings.reduce((sum, w) => {
320
329
  const m = w.match(/\((\d+) edges\)/);
321
330
  return sum + (m ? parseInt(m[1]) : 0);
322
331
  }, 0);
323
- console.log(` Note: ${totalFallback} edges across ${kuzuWarnings.length} types inserted via fallback (schema will be updated in next release)`);
332
+ console.log(` Note: ${totalFallback} edges across ${lbugWarnings.length} types inserted via fallback (schema will be updated in next release)`);
324
333
  }
325
334
  try {
326
335
  await fs.access(getGlobalRegistryPath());
@@ -329,7 +338,7 @@ export const analyzeCommand = async (inputPath, options) => {
329
338
  console.log('\n Tip: Run `gitnexus setup` to configure MCP for your editor.');
330
339
  }
331
340
  console.log('');
332
- // KuzuDB's native module holds open handles that prevent Node from exiting.
341
+ // LadybugDB's native module holds open handles that prevent Node from exiting.
333
342
  // ONNX Runtime also registers native atexit hooks that segfault on some
334
343
  // platforms (#38, #40). Force-exit to ensure clean termination.
335
344
  process.exit(0);
@@ -19,7 +19,7 @@ export async function augmentCommand(pattern) {
19
19
  const result = await augment(pattern, process.cwd());
20
20
  if (result) {
21
21
  // IMPORTANT: Write to stderr, NOT stdout.
22
- // KuzuDB's native module captures stdout fd at OS level during init,
22
+ // LadybugDB's native module captures stdout fd at OS level during init,
23
23
  // which makes stdout permanently broken in subprocess contexts.
24
24
  // stderr is never captured, so it works reliably everywhere.
25
25
  // The hook reads from the subprocess's stderr.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Eval Server — Lightweight HTTP server for SWE-bench evaluation
3
3
  *
4
- * Keeps KuzuDB warm in memory so tool calls from the agent are near-instant.
4
+ * Keeps LadybugDB warm in memory so tool calls from the agent are near-instant.
5
5
  * Designed to run inside Docker containers during SWE-bench evaluation.
6
6
  *
7
7
  * KEY DESIGN: Returns LLM-friendly text, not raw JSON.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Eval Server — Lightweight HTTP server for SWE-bench evaluation
3
3
  *
4
- * Keeps KuzuDB warm in memory so tool calls from the agent are near-instant.
4
+ * Keeps LadybugDB warm in memory so tool calls from the agent are near-instant.
5
5
  * Designed to run inside Docker containers during SWE-bench evaluation.
6
6
  *
7
7
  * KEY DESIGN: Returns LLM-friendly text, not raw JSON.
package/dist/cli/index.js CHANGED
@@ -22,6 +22,7 @@ program
22
22
  .option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
23
23
  .option('--skills', 'Generate repo-specific skill files from detected communities')
24
24
  .option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
25
+ .addHelpText('after', '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)')
25
26
  .action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
26
27
  program
27
28
  .command('serve')
package/dist/cli/mcp.js CHANGED
@@ -9,7 +9,7 @@ import { startMCPServer } from '../mcp/server.js';
9
9
  import { LocalBackend } from '../mcp/local/local-backend.js';
10
10
  export const mcpCommand = async () => {
11
11
  // Prevent unhandled errors from crashing the MCP server process.
12
- // KuzuDB lock conflicts and transient errors should degrade gracefully.
12
+ // LadybugDB lock conflicts and transient errors should degrade gracefully.
13
13
  process.on('uncaughtException', (err) => {
14
14
  console.error(`GitNexus MCP: uncaught exception — ${err.message}`);
15
15
  // Process is in an undefined state after uncaughtException — exit after flushing
package/dist/cli/setup.js CHANGED
@@ -9,6 +9,7 @@ import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import os from 'os';
11
11
  import { fileURLToPath } from 'url';
12
+ import { glob } from 'glob';
12
13
  import { getGlobalDir } from '../storage/repo-manager.js';
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
@@ -201,7 +202,6 @@ async function setupOpenCode(result) {
201
202
  }
202
203
  }
203
204
  // ─── Skill Installation ───────────────────────────────────────────
204
- const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impact-analysis', 'gitnexus-refactoring', 'gitnexus-guide', 'gitnexus-cli'];
205
205
  /**
206
206
  * Install GitNexus skills to a target directory.
207
207
  * Each skill is installed as {targetDir}/gitnexus-{skillName}/SKILL.md
@@ -214,24 +214,36 @@ const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impac
214
214
  async function installSkillsTo(targetDir) {
215
215
  const installed = [];
216
216
  const skillsRoot = path.join(__dirname, '..', '..', 'skills');
217
- for (const skillName of SKILL_NAMES) {
217
+ let flatFiles = [];
218
+ let dirSkillFiles = [];
219
+ try {
220
+ [flatFiles, dirSkillFiles] = await Promise.all([
221
+ glob('*.md', { cwd: skillsRoot }),
222
+ glob('*/SKILL.md', { cwd: skillsRoot }),
223
+ ]);
224
+ }
225
+ catch {
226
+ return [];
227
+ }
228
+ const skillSources = new Map();
229
+ for (const relPath of dirSkillFiles) {
230
+ skillSources.set(path.dirname(relPath), { isDirectory: true });
231
+ }
232
+ for (const relPath of flatFiles) {
233
+ const skillName = path.basename(relPath, '.md');
234
+ if (!skillSources.has(skillName)) {
235
+ skillSources.set(skillName, { isDirectory: false });
236
+ }
237
+ }
238
+ for (const [skillName, source] of skillSources) {
218
239
  const skillDir = path.join(targetDir, skillName);
219
240
  try {
220
- // Try directory-based skill first (skills/{name}/SKILL.md)
221
- const dirSource = path.join(skillsRoot, skillName);
222
- const dirSkillFile = path.join(dirSource, 'SKILL.md');
223
- let isDirectory = false;
224
- try {
225
- const stat = await fs.stat(dirSource);
226
- isDirectory = stat.isDirectory();
227
- }
228
- catch { /* not a directory */ }
229
- if (isDirectory) {
241
+ if (source.isDirectory) {
242
+ const dirSource = path.join(skillsRoot, skillName);
230
243
  await copyDirRecursive(dirSource, skillDir);
231
244
  installed.push(skillName);
232
245
  }
233
246
  else {
234
- // Fall back to flat file (skills/{name}.md)
235
247
  const flatSource = path.join(skillsRoot, `${skillName}.md`);
236
248
  const content = await fs.readFile(flatSource, 'utf-8');
237
249
  await fs.mkdir(skillDir, { recursive: true });
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Shows the indexing status of the current repository.
5
5
  */
6
- import { findRepo } from '../storage/repo-manager.js';
7
- import { getCurrentCommit, isGitRepo } from '../storage/git.js';
6
+ import { findRepo, getStoragePaths, hasKuzuIndex } from '../storage/repo-manager.js';
7
+ import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
8
8
  export const statusCommand = async () => {
9
9
  const cwd = process.cwd();
10
10
  if (!isGitRepo(cwd)) {
@@ -13,8 +13,17 @@ export const statusCommand = async () => {
13
13
  }
14
14
  const repo = await findRepo(cwd);
15
15
  if (!repo) {
16
- console.log('Repository not indexed.');
17
- console.log('Run: gitnexus analyze');
16
+ // Check if there's a stale KuzuDB index that needs migration
17
+ const repoRoot = getGitRoot(cwd) ?? cwd;
18
+ const { storagePath } = getStoragePaths(repoRoot);
19
+ if (await hasKuzuIndex(storagePath)) {
20
+ console.log('Repository has a stale KuzuDB index from a previous version.');
21
+ console.log('Run: gitnexus analyze (rebuilds the index with LadybugDB)');
22
+ }
23
+ else {
24
+ console.log('Repository not indexed.');
25
+ console.log('Run: gitnexus analyze');
26
+ }
18
27
  return;
19
28
  }
20
29
  const currentCommit = getCurrentCommit(repo.repoPath);
@@ -10,7 +10,7 @@
10
10
  * gitnexus impact --target "AuthService" --direction upstream
11
11
  * gitnexus cypher "MATCH (n:Function) RETURN n.name LIMIT 10"
12
12
  *
13
- * Note: Output goes to stderr because KuzuDB's native module captures stdout
13
+ * Note: Output goes to stderr because LadybugDB's native module captures stdout
14
14
  * at the OS level during init. This is consistent with augment.ts.
15
15
  */
16
16
  export declare function queryCommand(queryText: string, options?: {
package/dist/cli/tool.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * gitnexus impact --target "AuthService" --direction upstream
11
11
  * gitnexus cypher "MATCH (n:Function) RETURN n.name LIMIT 10"
12
12
  *
13
- * Note: Output goes to stderr because KuzuDB's native module captures stdout
13
+ * Note: Output goes to stderr because LadybugDB's native module captures stdout
14
14
  * at the OS level during init. This is consistent with augment.ts.
15
15
  */
16
16
  import { LocalBackend } from '../mcp/local/local-backend.js';
@@ -28,7 +28,7 @@ async function getBackend() {
28
28
  }
29
29
  function output(data) {
30
30
  const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
31
- // stderr because KuzuDB captures stdout at OS level
31
+ // stderr because LadybugDB captures stdout at OS level
32
32
  process.stderr.write(text + '\n');
33
33
  }
34
34
  export async function queryCommand(queryText, options) {
package/dist/cli/wiki.js CHANGED
@@ -86,7 +86,7 @@ export const wikiCommand = async (inputPath, options) => {
86
86
  return;
87
87
  }
88
88
  // ── Check for existing index ────────────────────────────────────────
89
- const { storagePath, kuzuPath } = getStoragePaths(repoPath);
89
+ const { storagePath, lbugPath } = getStoragePaths(repoPath);
90
90
  const meta = await loadMeta(storagePath);
91
91
  if (!meta) {
92
92
  console.log(' Error: No GitNexus index found.');
@@ -217,7 +217,7 @@ export const wikiCommand = async (inputPath, options) => {
217
217
  baseUrl: options?.baseUrl,
218
218
  concurrency: options?.concurrency ? parseInt(options.concurrency, 10) : undefined,
219
219
  };
220
- const generator = new WikiGenerator(repoPath, storagePath, kuzuPath, llmConfig, wikiOptions, (phase, percent, detail) => {
220
+ const generator = new WikiGenerator(repoPath, storagePath, lbugPath, llmConfig, wikiOptions, (phase, percent, detail) => {
221
221
  const label = detail || phase;
222
222
  if (label !== lastPhase) {
223
223
  lastPhase = label;
@@ -1 +1,26 @@
1
+ import { type Ignore } from 'ignore';
2
+ import type { Path } from 'path-scurry';
1
3
  export declare const shouldIgnorePath: (filePath: string) => boolean;
4
+ /** Check if a directory name is in the hardcoded ignore list */
5
+ export declare const isHardcodedIgnoredDirectory: (name: string) => boolean;
6
+ /**
7
+ * Load .gitignore and .gitnexusignore rules from the repo root.
8
+ * Returns an `ignore` instance with all patterns, or null if no files found.
9
+ */
10
+ export interface IgnoreOptions {
11
+ /** Skip .gitignore parsing, only read .gitnexusignore. Defaults to GITNEXUS_NO_GITIGNORE env var. */
12
+ noGitignore?: boolean;
13
+ }
14
+ export declare const loadIgnoreRules: (repoPath: string, options?: IgnoreOptions) => Promise<Ignore | null>;
15
+ /**
16
+ * Create a glob-compatible ignore filter combining:
17
+ * - .gitignore / .gitnexusignore patterns (via `ignore` package)
18
+ * - Hardcoded DEFAULT_IGNORE_LIST, IGNORED_EXTENSIONS, IGNORED_FILES
19
+ *
20
+ * Returns an IgnoreLike object for glob's `ignore` option,
21
+ * enabling directory-level pruning during traversal.
22
+ */
23
+ export declare const createIgnoreFilter: (repoPath: string, options?: IgnoreOptions) => Promise<{
24
+ ignored(p: Path): boolean;
25
+ childrenIgnored(p: Path): boolean;
26
+ }>;
@@ -1,3 +1,6 @@
1
+ import ignore from 'ignore';
2
+ import fs from 'fs/promises';
3
+ import nodePath from 'path';
1
4
  const DEFAULT_IGNORE_LIST = new Set([
2
5
  // Version Control
3
6
  '.git',
@@ -161,6 +164,10 @@ const IGNORED_FILES = new Set([
161
164
  '.env.test',
162
165
  '.env.example',
163
166
  ]);
167
+ // NOTE: Negation patterns in .gitnexusignore (e.g. `!vendor/`) cannot override
168
+ // entries in DEFAULT_IGNORE_LIST — this is intentional. The hardcoded list protects
169
+ // against indexing directories that are almost never source code (node_modules, .git, etc.).
170
+ // Users who need to include such directories should remove them from the hardcoded list.
164
171
  export const shouldIgnorePath = (filePath) => {
165
172
  const normalizedPath = filePath.replace(/\\/g, '/');
166
173
  const parts = normalizedPath.split('/');
@@ -206,3 +213,72 @@ export const shouldIgnorePath = (filePath) => {
206
213
  }
207
214
  return false;
208
215
  };
216
+ /** Check if a directory name is in the hardcoded ignore list */
217
+ export const isHardcodedIgnoredDirectory = (name) => {
218
+ return DEFAULT_IGNORE_LIST.has(name);
219
+ };
220
+ export const loadIgnoreRules = async (repoPath, options) => {
221
+ const ig = ignore();
222
+ let hasRules = false;
223
+ // Allow users to bypass .gitignore parsing (e.g. when .gitignore accidentally excludes source files)
224
+ const skipGitignore = options?.noGitignore ?? !!process.env.GITNEXUS_NO_GITIGNORE;
225
+ const filenames = skipGitignore
226
+ ? ['.gitnexusignore']
227
+ : ['.gitignore', '.gitnexusignore'];
228
+ for (const filename of filenames) {
229
+ try {
230
+ const content = await fs.readFile(nodePath.join(repoPath, filename), 'utf-8');
231
+ ig.add(content);
232
+ hasRules = true;
233
+ }
234
+ catch (err) {
235
+ const code = err.code;
236
+ if (code !== 'ENOENT') {
237
+ console.warn(` Warning: could not read ${filename}: ${err.message}`);
238
+ }
239
+ }
240
+ }
241
+ return hasRules ? ig : null;
242
+ };
243
+ /**
244
+ * Create a glob-compatible ignore filter combining:
245
+ * - .gitignore / .gitnexusignore patterns (via `ignore` package)
246
+ * - Hardcoded DEFAULT_IGNORE_LIST, IGNORED_EXTENSIONS, IGNORED_FILES
247
+ *
248
+ * Returns an IgnoreLike object for glob's `ignore` option,
249
+ * enabling directory-level pruning during traversal.
250
+ */
251
+ export const createIgnoreFilter = async (repoPath, options) => {
252
+ const ig = await loadIgnoreRules(repoPath, options);
253
+ return {
254
+ ignored(p) {
255
+ // path-scurry's Path.relative() returns POSIX paths on all platforms,
256
+ // which is what the `ignore` package expects. No explicit normalization needed.
257
+ const rel = p.relative();
258
+ if (!rel)
259
+ return false;
260
+ // Check .gitignore / .gitnexusignore patterns
261
+ if (ig && ig.ignores(rel))
262
+ return true;
263
+ // Fall back to hardcoded rules
264
+ return shouldIgnorePath(rel);
265
+ },
266
+ childrenIgnored(p) {
267
+ // Fast path: check directory name against hardcoded list.
268
+ // Note: dot-directories (.git, .vscode, etc.) are primarily excluded by
269
+ // glob's `dot: false` option in filesystem-walker.ts. This check is
270
+ // defense-in-depth — do not remove `dot: false` assuming this covers it.
271
+ if (DEFAULT_IGNORE_LIST.has(p.name))
272
+ return true;
273
+ // Check against .gitignore / .gitnexusignore patterns.
274
+ // Test both bare path and path with trailing slash to handle
275
+ // bare-name patterns (e.g. `local`) and dir-only patterns (e.g. `local/`).
276
+ if (ig) {
277
+ const rel = p.relative();
278
+ if (rel && (ig.ignores(rel) || ig.ignores(rel + '/')))
279
+ return true;
280
+ }
281
+ return false;
282
+ },
283
+ };
284
+ };
@@ -7,6 +7,7 @@ export declare enum SupportedLanguages {
7
7
  CPlusPlus = "cpp",
8
8
  CSharp = "csharp",
9
9
  Go = "go",
10
+ Ruby = "ruby",
10
11
  Rust = "rust",
11
12
  PHP = "php",
12
13
  Kotlin = "kotlin",
@@ -8,9 +8,9 @@ export var SupportedLanguages;
8
8
  SupportedLanguages["CPlusPlus"] = "cpp";
9
9
  SupportedLanguages["CSharp"] = "csharp";
10
10
  SupportedLanguages["Go"] = "go";
11
+ SupportedLanguages["Ruby"] = "ruby";
11
12
  SupportedLanguages["Rust"] = "rust";
12
13
  SupportedLanguages["PHP"] = "php";
13
14
  SupportedLanguages["Kotlin"] = "kotlin";
14
- // Ruby = 'ruby',
15
15
  SupportedLanguages["Swift"] = "swift";
16
16
  })(SupportedLanguages || (SupportedLanguages = {}));