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.
- package/README.md +19 -18
- package/dist/cli/analyze.js +37 -28
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +1 -1
- package/dist/cli/eval-server.js +1 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/setup.js +25 -13
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +1 -1
- package/dist/cli/tool.js +2 -2
- package/dist/cli/wiki.js +2 -2
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +1 -0
- package/dist/config/supported-languages.js +1 -1
- package/dist/core/augmentation/engine.js +94 -67
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +52 -25
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/ingestion/call-processor.d.ts +6 -7
- package/dist/core/ingestion/call-processor.js +490 -127
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/entry-point-scoring.js +13 -2
- package/dist/core/ingestion/export-detection.js +1 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.js +9 -0
- package/dist/core/ingestion/heritage-processor.d.ts +3 -4
- package/dist/core/ingestion/heritage-processor.js +40 -50
- package/dist/core/ingestion/import-processor.d.ts +3 -5
- package/dist/core/ingestion/import-processor.js +41 -10
- package/dist/core/ingestion/parsing-processor.d.ts +2 -1
- package/dist/core/ingestion/parsing-processor.js +41 -4
- package/dist/core/ingestion/pipeline.d.ts +5 -1
- package/dist/core/ingestion/pipeline.js +174 -121
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/index.d.ts +2 -0
- package/dist/core/ingestion/resolvers/index.js +2 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/standard.js +0 -22
- package/dist/core/ingestion/resolvers/utils.js +2 -0
- package/dist/core/ingestion/symbol-table.d.ts +3 -0
- package/dist/core/ingestion/symbol-table.js +1 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +3 -2
- package/dist/core/ingestion/tree-sitter-queries.js +53 -1
- package/dist/core/ingestion/type-env.d.ts +32 -10
- package/dist/core/ingestion/type-env.js +520 -47
- package/dist/core/ingestion/type-extractors/c-cpp.js +326 -1
- package/dist/core/ingestion/type-extractors/csharp.js +282 -2
- package/dist/core/ingestion/type-extractors/go.js +333 -2
- package/dist/core/ingestion/type-extractors/index.d.ts +3 -2
- package/dist/core/ingestion/type-extractors/index.js +3 -1
- package/dist/core/ingestion/type-extractors/jvm.js +537 -4
- package/dist/core/ingestion/type-extractors/php.js +387 -7
- package/dist/core/ingestion/type-extractors/python.js +356 -5
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.js +399 -2
- package/dist/core/ingestion/type-extractors/shared.d.ts +116 -1
- package/dist/core/ingestion/type-extractors/shared.js +488 -14
- package/dist/core/ingestion/type-extractors/swift.js +95 -1
- package/dist/core/ingestion/type-extractors/types.d.ts +81 -0
- package/dist/core/ingestion/type-extractors/typescript.js +436 -2
- package/dist/core/ingestion/utils.d.ts +33 -2
- package/dist/core/ingestion/utils.js +399 -27
- package/dist/core/ingestion/workers/parse-worker.d.ts +18 -1
- package/dist/core/ingestion/workers/parse-worker.js +169 -19
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/csv-generator.js +1 -1
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +70 -65
- package/dist/core/{kuzu → lbug}/schema.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/schema.js +1 -1
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +10 -10
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +6 -6
- package/dist/core/tree-sitter/parser-loader.js +9 -2
- package/dist/core/wiki/generator.d.ts +2 -2
- package/dist/core/wiki/generator.js +4 -4
- package/dist/core/wiki/graph-queries.d.ts +4 -4
- package/dist/core/wiki/graph-queries.js +7 -7
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -7
- package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +72 -43
- package/dist/mcp/local/local-backend.d.ts +6 -6
- package/dist/mcp/local/local-backend.js +25 -18
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +55 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/package.json +5 -3
- package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
- 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 **
|
|
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 |
|
|
165
|
-
|
|
166
|
-
| TypeScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
167
|
-
| JavaScript | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
168
|
-
| Python | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
| Go | ✓ | ✓ | ✓ |
|
|
173
|
-
| Rust | ✓ | ✓ | ✓ | ✓ |
|
|
174
|
-
| PHP | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
| C
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
package/dist/cli/analyze.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
166
|
+
await initLbug(lbugPath);
|
|
158
167
|
const cached = await loadCachedEmbeddings();
|
|
159
168
|
cachedEmbeddingNodeIds = cached.embeddingNodeIds;
|
|
160
169
|
cachedEmbeddings = cached.embeddings;
|
|
161
|
-
await
|
|
170
|
+
await closeLbug();
|
|
162
171
|
}
|
|
163
172
|
catch {
|
|
164
173
|
try {
|
|
165
|
-
await
|
|
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:
|
|
177
|
-
updateBar(60, 'Loading into
|
|
178
|
-
await
|
|
179
|
-
const
|
|
180
|
-
for (const f of
|
|
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
|
|
187
|
-
await
|
|
188
|
-
let
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const progress = Math.min(84, 60 + Math.round((
|
|
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
|
|
195
|
-
const
|
|
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
|
|
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
|
|
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(`
|
|
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 (
|
|
319
|
-
const totalFallback =
|
|
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 ${
|
|
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
|
-
//
|
|
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);
|
package/dist/cli/augment.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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/eval-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Eval Server — Lightweight HTTP server for SWE-bench evaluation
|
|
3
3
|
*
|
|
4
|
-
* Keeps
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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 });
|
package/dist/cli/status.js
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
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);
|
package/dist/cli/tool.d.ts
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
+
};
|
|
@@ -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 = {}));
|