@veewo/gitnexus 1.3.11 → 1.4.6-rc
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 +37 -80
- package/dist/benchmark/agent-context/tool-runner.js +2 -2
- package/dist/benchmark/neonspark-candidates.js +3 -3
- package/dist/benchmark/tool-runner.js +2 -2
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +16 -12
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +68 -48
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +8 -1
- package/dist/cli/eval-server.js +30 -13
- package/dist/cli/index.js +28 -82
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +3 -1
- package/dist/cli/setup.js +87 -48
- package/dist/cli/setup.test.js +18 -13
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +50 -16
- package/dist/cli/wiki.js +8 -4
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +4 -1
- package/dist/config/supported-languages.js +3 -2
- 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/graph/types.d.ts +7 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +8 -6
- package/dist/core/ingestion/call-processor.js +468 -206
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +116 -23
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +19 -4
- package/dist/core/ingestion/framework-detection.js +182 -6
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +199 -579
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +4 -1
- package/dist/core/ingestion/parsing-processor.js +107 -109
- package/dist/core/ingestion/pipeline.d.ts +6 -3
- package/dist/core/ingestion/pipeline.js +208 -114
- package/dist/core/ingestion/process-processor.js +8 -2
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -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/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/symbol-table.d.ts +21 -1
- package/dist/core/ingestion/symbol-table.js +40 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +13 -10
- package/dist/core/ingestion/tree-sitter-queries.js +297 -7
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +611 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +406 -0
- 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.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +449 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
- package/dist/core/ingestion/type-extractors/shared.js +703 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +103 -0
- package/dist/core/ingestion/utils.js +1085 -4
- package/dist/core/ingestion/workers/parse-worker.d.ts +51 -4
- package/dist/core/ingestion/workers/parse-worker.js +634 -222
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +12 -10
- package/dist/core/{kuzu → lbug}/csv-generator.js +82 -101
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +20 -25
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +150 -122
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +23 -22
- package/dist/core/lbug/schema.test.d.ts +1 -0
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +12 -11
- 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.d.ts +1 -0
- package/dist/core/tree-sitter/parser-loader.js +19 -0
- package/dist/core/wiki/generator.d.ts +2 -2
- package/dist/core/wiki/generator.js +6 -6
- package/dist/core/wiki/graph-queries.d.ts +4 -4
- package/dist/core/wiki/graph-queries.js +7 -7
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +11 -10
- package/dist/mcp/core/lbug-adapter.js +327 -0
- package/dist/mcp/local/local-backend.d.ts +21 -16
- package/dist/mcp/local/local-backend.js +306 -706
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +6 -1
- package/dist/mcp/local/unity-parity-seed-loader.js +119 -9
- package/dist/mcp/local/unity-parity-seed-loader.test.js +95 -7
- package/dist/mcp/resources.js +2 -2
- package/dist/mcp/server.js +28 -13
- package/dist/mcp/staleness.js +2 -2
- package/dist/mcp/tools.js +12 -3
- 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/git.js +4 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +74 -4
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +149 -46
- package/hooks/claude/pre-tool-use.sh +2 -1
- package/hooks/claude/session-start.sh +0 -0
- package/package.json +20 -4
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/skills/gitnexus-cli.md +8 -8
- package/skills/gitnexus-debugging.md +1 -1
- package/skills/gitnexus-exploring.md +1 -1
- package/skills/gitnexus-guide.md +1 -1
- package/skills/gitnexus-impact-analysis.md +1 -1
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/dist/cli/claude-hooks.d.ts +0 -22
- package/dist/cli/claude-hooks.js +0 -97
- package/dist/mcp/core/kuzu-adapter.js +0 -231
- /package/dist/core/{kuzu/csv-generator.test.d.ts → ingestion/type-extractors/types.js} +0 -0
- /package/dist/core/{kuzu/relationship-pair-buckets.test.d.ts → lbug/csv-generator.test.d.ts} +0 -0
- /package/dist/core/{kuzu → lbug}/csv-generator.test.js +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.d.ts +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.js +0 -0
- /package/dist/core/{kuzu/schema.test.d.ts → lbug/relationship-pair-buckets.test.d.ts} +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.test.js +0 -0
- /package/dist/core/{kuzu → lbug}/schema.test.js +0 -0
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.
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
* POST /shutdown — Graceful shutdown.
|
|
25
25
|
*/
|
|
26
26
|
import http from 'http';
|
|
27
|
+
import { writeSync } from 'node:fs';
|
|
27
28
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
28
29
|
// ─── Text Formatters ──────────────────────────────────────────────────
|
|
29
30
|
// Convert structured JSON results into compact, LLM-friendly text.
|
|
30
31
|
// Design: minimize tokens, maximize actionability.
|
|
31
|
-
function formatQueryResult(result) {
|
|
32
|
+
export function formatQueryResult(result) {
|
|
32
33
|
if (result.error)
|
|
33
34
|
return `Error: ${result.error}`;
|
|
34
35
|
const lines = [];
|
|
@@ -63,7 +64,7 @@ function formatQueryResult(result) {
|
|
|
63
64
|
}
|
|
64
65
|
return lines.join('\n').trim();
|
|
65
66
|
}
|
|
66
|
-
function formatContextResult(result) {
|
|
67
|
+
export function formatContextResult(result) {
|
|
67
68
|
if (result.error)
|
|
68
69
|
return `Error: ${result.error}`;
|
|
69
70
|
if (result.status === 'ambiguous') {
|
|
@@ -120,9 +121,11 @@ function formatContextResult(result) {
|
|
|
120
121
|
}
|
|
121
122
|
return lines.join('\n').trim();
|
|
122
123
|
}
|
|
123
|
-
function formatImpactResult(result) {
|
|
124
|
-
if (result.error)
|
|
125
|
-
|
|
124
|
+
export function formatImpactResult(result) {
|
|
125
|
+
if (result.error) {
|
|
126
|
+
const suggestion = result.suggestion ? `\nSuggestion: ${result.suggestion}` : '';
|
|
127
|
+
return `Error: ${result.error}${suggestion}`;
|
|
128
|
+
}
|
|
126
129
|
const target = result.target;
|
|
127
130
|
const direction = result.direction;
|
|
128
131
|
const byDepth = result.byDepth || {};
|
|
@@ -132,7 +135,11 @@ function formatImpactResult(result) {
|
|
|
132
135
|
}
|
|
133
136
|
const lines = [];
|
|
134
137
|
const dirLabel = direction === 'upstream' ? 'depends on this (will break if changed)' : 'this depends on';
|
|
135
|
-
lines.push(`Blast radius for ${target?.kind || ''} ${target?.name} (${direction}): ${total} symbol(s) ${dirLabel}
|
|
138
|
+
lines.push(`Blast radius for ${target?.kind || ''} ${target?.name} (${direction}): ${total} symbol(s) ${dirLabel}`);
|
|
139
|
+
if (result.partial) {
|
|
140
|
+
lines.push('⚠️ Partial results — graph traversal was interrupted. Deeper impacts may exist.');
|
|
141
|
+
}
|
|
142
|
+
lines.push('');
|
|
136
143
|
const depthLabels = {
|
|
137
144
|
1: 'WILL BREAK (direct)',
|
|
138
145
|
2: 'LIKELY AFFECTED (indirect)',
|
|
@@ -154,7 +161,7 @@ function formatImpactResult(result) {
|
|
|
154
161
|
}
|
|
155
162
|
return lines.join('\n').trim();
|
|
156
163
|
}
|
|
157
|
-
function formatCypherResult(result) {
|
|
164
|
+
export function formatCypherResult(result) {
|
|
158
165
|
if (result.error)
|
|
159
166
|
return `Error: ${result.error}`;
|
|
160
167
|
if (Array.isArray(result)) {
|
|
@@ -174,7 +181,7 @@ function formatCypherResult(result) {
|
|
|
174
181
|
}
|
|
175
182
|
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
176
183
|
}
|
|
177
|
-
function formatDetectChangesResult(result) {
|
|
184
|
+
export function formatDetectChangesResult(result) {
|
|
178
185
|
if (result.error)
|
|
179
186
|
return `Error: ${result.error}`;
|
|
180
187
|
const summary = result.summary || {};
|
|
@@ -205,7 +212,7 @@ function formatDetectChangesResult(result) {
|
|
|
205
212
|
}
|
|
206
213
|
return lines.join('\n').trim();
|
|
207
214
|
}
|
|
208
|
-
function formatListReposResult(result) {
|
|
215
|
+
export function formatListReposResult(result) {
|
|
209
216
|
if (!Array.isArray(result) || result.length === 0) {
|
|
210
217
|
return 'No indexed repositories.';
|
|
211
218
|
}
|
|
@@ -349,10 +356,11 @@ export async function evalServerCommand(options) {
|
|
|
349
356
|
console.error(` Auto-shutdown after ${idleTimeoutSec}s idle`);
|
|
350
357
|
}
|
|
351
358
|
try {
|
|
352
|
-
process.stdout
|
|
359
|
+
// Use fd 1 directly — LadybugDB captures process.stdout (#324)
|
|
360
|
+
writeSync(1, `GITNEXUS_EVAL_SERVER_READY:${port}\n`);
|
|
353
361
|
}
|
|
354
362
|
catch {
|
|
355
|
-
// stdout may not be available
|
|
363
|
+
// stdout may not be available (e.g., broken pipe)
|
|
356
364
|
}
|
|
357
365
|
});
|
|
358
366
|
resetIdleTimer();
|
|
@@ -365,10 +373,19 @@ export async function evalServerCommand(options) {
|
|
|
365
373
|
process.on('SIGINT', shutdown);
|
|
366
374
|
process.on('SIGTERM', shutdown);
|
|
367
375
|
}
|
|
376
|
+
export const MAX_BODY_SIZE = 1024 * 1024; // 1MB
|
|
368
377
|
function readBody(req) {
|
|
369
378
|
return new Promise((resolve, reject) => {
|
|
370
379
|
const chunks = [];
|
|
371
|
-
|
|
380
|
+
let totalSize = 0;
|
|
381
|
+
req.on('data', (chunk) => {
|
|
382
|
+
totalSize += chunk.length;
|
|
383
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
384
|
+
req.destroy(new Error('Request body too large (max 1MB)'));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
chunks.push(chunk);
|
|
388
|
+
});
|
|
372
389
|
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
373
390
|
req.on('error', reject);
|
|
374
391
|
});
|
package/dist/cli/index.js
CHANGED
|
@@ -1,74 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
if (!process.env.NODE_OPTIONS?.includes('--max-old-space-size')) {
|
|
5
|
-
const execArgv = process.execArgv.join(' ');
|
|
6
|
-
if (!execArgv.includes('--max-old-space-size')) {
|
|
7
|
-
// Re-spawn with a larger heap (8 GB)
|
|
8
|
-
const { execFileSync } = await import('node:child_process');
|
|
9
|
-
try {
|
|
10
|
-
execFileSync(process.execPath, ['--max-old-space-size=8192', ...process.argv.slice(1)], {
|
|
11
|
-
stdio: 'inherit',
|
|
12
|
-
env: { ...process.env, NODE_OPTIONS: `${process.env.NODE_OPTIONS || ''} --max-old-space-size=8192`.trim() },
|
|
13
|
-
});
|
|
14
|
-
process.exit(0);
|
|
15
|
-
}
|
|
16
|
-
catch (e) {
|
|
17
|
-
const resolved = resolveChildProcessExit(e, 1);
|
|
18
|
-
if (resolved.bySignal && resolved.signal) {
|
|
19
|
-
process.stderr.write(`gitnexus: child process terminated by signal ${resolved.signal}\n`);
|
|
20
|
-
}
|
|
21
|
-
process.exit(resolved.code);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
2
|
+
// Heap re-spawn removed — only analyze.ts needs the 8GB heap (via its own ensureHeap()).
|
|
3
|
+
// Removing it from here improves MCP server startup time significantly.
|
|
25
4
|
import { Command } from 'commander';
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
import { serveCommand } from './serve.js';
|
|
31
|
-
import { listCommand } from './list.js';
|
|
32
|
-
import { statusCommand } from './status.js';
|
|
33
|
-
import { mcpCommand } from './mcp.js';
|
|
34
|
-
import { cleanCommand } from './clean.js';
|
|
35
|
-
import { setupCommand } from './setup.js';
|
|
36
|
-
import { augmentCommand } from './augment.js';
|
|
37
|
-
import { wikiCommand } from './wiki.js';
|
|
38
|
-
import { queryCommand, contextCommand, impactCommand, cypherCommand } from './tool.js';
|
|
39
|
-
import { evalServerCommand } from './eval-server.js';
|
|
40
|
-
import { benchmarkUnityCommand } from './benchmark-unity.js';
|
|
41
|
-
import { benchmarkAgentContextCommand } from './benchmark-agent-context.js';
|
|
42
|
-
import { unityBindingsCommand } from './unity-bindings.js';
|
|
43
|
-
import { benchmarkU2E2ECommand } from './benchmark-u2-e2e.js';
|
|
44
|
-
import { resolveChildProcessExit } from './exit-code.js';
|
|
45
|
-
function resolveCliVersion() {
|
|
46
|
-
try {
|
|
47
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
48
|
-
const packageJsonPath = path.resolve(path.dirname(currentFile), '..', '..', 'package.json');
|
|
49
|
-
const raw = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
50
|
-
const parsed = JSON.parse(raw);
|
|
51
|
-
if (typeof parsed.version === 'string' && parsed.version.length > 0) {
|
|
52
|
-
return parsed.version;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// fall through to default
|
|
57
|
-
}
|
|
58
|
-
return '0.0.0';
|
|
59
|
-
}
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { createLazyAction } from './lazy-action.js';
|
|
7
|
+
const _require = createRequire(import.meta.url);
|
|
8
|
+
const pkg = _require('../../package.json');
|
|
60
9
|
const program = new Command();
|
|
61
10
|
const collectValues = (value, previous) => [...previous, value];
|
|
62
11
|
program
|
|
63
12
|
.name('gitnexus')
|
|
64
13
|
.description('GitNexus local CLI and MCP server')
|
|
65
|
-
.version(
|
|
14
|
+
.version(pkg.version);
|
|
66
15
|
program
|
|
67
16
|
.command('setup')
|
|
68
17
|
.description('One-time setup: configure MCP for a selected coding agent (claude/opencode/codex)')
|
|
69
18
|
.option('--scope <scope>', 'Install target: global (default) or project')
|
|
70
19
|
.option('--agent <agent>', 'Target coding agent: claude, opencode, or codex')
|
|
71
|
-
.action(setupCommand);
|
|
20
|
+
.action(createLazyAction(() => import('./setup.js'), 'setupCommand'));
|
|
72
21
|
program
|
|
73
22
|
.command('analyze [path]')
|
|
74
23
|
.description('Index a repository (full analysis)')
|
|
@@ -77,33 +26,36 @@ program
|
|
|
77
26
|
.option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
|
|
78
27
|
.option('--extensions <list>', 'Comma-separated file extensions to include (e.g. .cs,.ts)')
|
|
79
28
|
.option('--repo-alias <name>', 'Override indexed repository name with a stable alias')
|
|
29
|
+
.option('--skills', 'Generate repo-specific skill files from detected communities')
|
|
30
|
+
.option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
|
|
80
31
|
.option('--scope-manifest <path>', 'Manifest file with scope rules (supports comments and * wildcard; recommended: .gitnexus/sync-manifest.txt)')
|
|
81
32
|
.option('--scope-prefix <pathPrefix>', 'Add a scope path prefix rule (repeatable)', collectValues, [])
|
|
82
|
-
.
|
|
33
|
+
.addHelpText('after', '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)')
|
|
34
|
+
.action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
|
|
83
35
|
program
|
|
84
36
|
.command('serve')
|
|
85
37
|
.description('Start local HTTP server for web UI connection')
|
|
86
38
|
.option('-p, --port <port>', 'Port number', '4747')
|
|
87
39
|
.option('--host <host>', 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)')
|
|
88
|
-
.action(serveCommand);
|
|
40
|
+
.action(createLazyAction(() => import('./serve.js'), 'serveCommand'));
|
|
89
41
|
program
|
|
90
42
|
.command('mcp')
|
|
91
43
|
.description('Start MCP server (stdio) — serves all indexed repos')
|
|
92
|
-
.action(mcpCommand);
|
|
44
|
+
.action(createLazyAction(() => import('./mcp.js'), 'mcpCommand'));
|
|
93
45
|
program
|
|
94
46
|
.command('list')
|
|
95
47
|
.description('List all indexed repositories')
|
|
96
|
-
.action(listCommand);
|
|
48
|
+
.action(createLazyAction(() => import('./list.js'), 'listCommand'));
|
|
97
49
|
program
|
|
98
50
|
.command('status')
|
|
99
51
|
.description('Show index status for current repo')
|
|
100
|
-
.action(statusCommand);
|
|
52
|
+
.action(createLazyAction(() => import('./status.js'), 'statusCommand'));
|
|
101
53
|
program
|
|
102
54
|
.command('clean')
|
|
103
55
|
.description('Delete GitNexus index for current repo')
|
|
104
56
|
.option('-f, --force', 'Skip confirmation prompt')
|
|
105
57
|
.option('--all', 'Clean all indexed repos')
|
|
106
|
-
.action(cleanCommand);
|
|
58
|
+
.action(createLazyAction(() => import('./clean.js'), 'cleanCommand'));
|
|
107
59
|
program
|
|
108
60
|
.command('wiki [path]')
|
|
109
61
|
.description('Generate repository wiki from knowledge graph')
|
|
@@ -113,11 +65,11 @@ program
|
|
|
113
65
|
.option('--api-key <key>', 'LLM API key (saved to ~/.gitnexus/config.json)')
|
|
114
66
|
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
115
67
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
116
|
-
.action(wikiCommand);
|
|
68
|
+
.action(createLazyAction(() => import('./wiki.js'), 'wikiCommand'));
|
|
117
69
|
program
|
|
118
70
|
.command('augment <pattern>')
|
|
119
71
|
.description('Augment a search pattern with knowledge graph context (used by hooks)')
|
|
120
|
-
.action(augmentCommand);
|
|
72
|
+
.action(createLazyAction(() => import('./augment.js'), 'augmentCommand'));
|
|
121
73
|
// ─── Direct Tool Commands (no MCP overhead) ────────────────────────
|
|
122
74
|
// These invoke LocalBackend directly for use in eval, scripts, and CI.
|
|
123
75
|
program
|
|
@@ -130,7 +82,7 @@ program
|
|
|
130
82
|
.option('--content', 'Include full symbol source code')
|
|
131
83
|
.option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
|
|
132
84
|
.option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
|
|
133
|
-
.action(queryCommand);
|
|
85
|
+
.action(createLazyAction(() => import('./tool.js'), 'queryCommand'));
|
|
134
86
|
program
|
|
135
87
|
.command('context [name]')
|
|
136
88
|
.description('360-degree view of a code symbol: callers, callees, processes')
|
|
@@ -140,15 +92,13 @@ program
|
|
|
140
92
|
.option('--content', 'Include full symbol source code')
|
|
141
93
|
.option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
|
|
142
94
|
.option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
|
|
143
|
-
.action(contextCommand);
|
|
95
|
+
.action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
|
|
144
96
|
program
|
|
145
97
|
.command('unity-bindings <symbol>')
|
|
146
98
|
.description('Experimental: inspect Unity resource bindings for a C# symbol')
|
|
147
99
|
.option('--target-path <path>', 'Unity project root (default: cwd)')
|
|
148
100
|
.option('--json', 'Output JSON')
|
|
149
|
-
.action(
|
|
150
|
-
await unityBindingsCommand(symbol, options);
|
|
151
|
-
});
|
|
101
|
+
.action(createLazyAction(() => import('./unity-bindings.js'), 'unityBindingsCommand'));
|
|
152
102
|
program
|
|
153
103
|
.command('impact <target>')
|
|
154
104
|
.description('Blast radius analysis: what breaks if you change a symbol')
|
|
@@ -159,19 +109,19 @@ program
|
|
|
159
109
|
.option('--depth <n>', 'Max relationship depth (default: 3)')
|
|
160
110
|
.option('--min-confidence <n>', 'Minimum edge confidence 0-1 (default: 0.3)')
|
|
161
111
|
.option('--include-tests', 'Include test files in results')
|
|
162
|
-
.action(impactCommand);
|
|
112
|
+
.action(createLazyAction(() => import('./tool.js'), 'impactCommand'));
|
|
163
113
|
program
|
|
164
114
|
.command('cypher <query>')
|
|
165
115
|
.description('Execute raw Cypher query against the knowledge graph')
|
|
166
116
|
.option('-r, --repo <name>', 'Target repository')
|
|
167
|
-
.action(cypherCommand);
|
|
117
|
+
.action(createLazyAction(() => import('./tool.js'), 'cypherCommand'));
|
|
168
118
|
// ─── Eval Server (persistent daemon for SWE-bench) ─────────────────
|
|
169
119
|
program
|
|
170
120
|
.command('eval-server')
|
|
171
121
|
.description('Start lightweight HTTP server for fast tool calls during evaluation')
|
|
172
122
|
.option('-p, --port <port>', 'Port number', '4848')
|
|
173
123
|
.option('--idle-timeout <seconds>', 'Auto-shutdown after N seconds idle (0 = disabled)', '0')
|
|
174
|
-
.action(evalServerCommand);
|
|
124
|
+
.action(createLazyAction(() => import('./eval-server.js'), 'evalServerCommand'));
|
|
175
125
|
program
|
|
176
126
|
.command('benchmark-unity <dataset>')
|
|
177
127
|
.description('Run Unity accuracy baseline and hard-gated regression checks')
|
|
@@ -184,7 +134,7 @@ program
|
|
|
184
134
|
.option('--scope-manifest <path>', 'Analyze scope manifest file')
|
|
185
135
|
.option('--scope-prefix <pathPrefix>', 'Analyze scope path prefix (repeatable)', collectValues, [])
|
|
186
136
|
.option('--skip-analyze', 'Skip analyze stage and evaluate current index only')
|
|
187
|
-
.action(benchmarkUnityCommand);
|
|
137
|
+
.action(createLazyAction(() => import('./benchmark-unity.js'), 'benchmarkUnityCommand'));
|
|
188
138
|
program
|
|
189
139
|
.command('benchmark-agent-context <dataset>')
|
|
190
140
|
.description('Run scenario-based agent refactor context benchmark')
|
|
@@ -197,15 +147,11 @@ program
|
|
|
197
147
|
.option('--scope-manifest <path>', 'Analyze scope manifest file')
|
|
198
148
|
.option('--scope-prefix <pathPrefix>', 'Analyze scope path prefix (repeatable)', collectValues, [])
|
|
199
149
|
.option('--skip-analyze', 'Skip analyze stage and evaluate current index only')
|
|
200
|
-
.action(
|
|
201
|
-
await benchmarkAgentContextCommand(dataset, options);
|
|
202
|
-
});
|
|
150
|
+
.action(createLazyAction(() => import('./benchmark-agent-context.js'), 'benchmarkAgentContextCommand'));
|
|
203
151
|
program
|
|
204
152
|
.command('benchmark-u2-e2e')
|
|
205
153
|
.description('Run fail-fast full neonspark U2 E2E benchmark and emit evidence reports')
|
|
206
154
|
.option('--config <path>', 'Path to E2E config JSON')
|
|
207
155
|
.option('--report-dir <path>', 'Output directory for reports')
|
|
208
|
-
.action(
|
|
209
|
-
await benchmarkU2E2ECommand(options);
|
|
210
|
-
});
|
|
156
|
+
.action(createLazyAction(() => import('./benchmark-u2-e2e.js'), 'benchmarkU2E2ECommand'));
|
|
211
157
|
program.parse(process.argv);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a lazy-loaded CLI action that defers module import until invocation.
|
|
3
|
+
* The generic constraints ensure the export name is a valid key of the module
|
|
4
|
+
* at compile time — catching typos when used with concrete module imports.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createLazyAction<TModule extends Record<string, unknown>, TKey extends string & keyof TModule>(loader: () => Promise<TModule>, exportName: TKey): (...args: unknown[]) => Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a lazy-loaded CLI action that defers module import until invocation.
|
|
3
|
+
* The generic constraints ensure the export name is a valid key of the module
|
|
4
|
+
* at compile time — catching typos when used with concrete module imports.
|
|
5
|
+
*/
|
|
6
|
+
function isCallable(value) {
|
|
7
|
+
return typeof value === 'function';
|
|
8
|
+
}
|
|
9
|
+
export function createLazyAction(loader, exportName) {
|
|
10
|
+
return async (...args) => {
|
|
11
|
+
const module = await loader();
|
|
12
|
+
const action = module[exportName];
|
|
13
|
+
if (!isCallable(action)) {
|
|
14
|
+
throw new Error(`Lazy action export not found: ${exportName}`);
|
|
15
|
+
}
|
|
16
|
+
await action(...args);
|
|
17
|
+
};
|
|
18
|
+
}
|
package/dist/cli/mcp.js
CHANGED
|
@@ -12,9 +12,11 @@ export const mcpCommand = async () => {
|
|
|
12
12
|
process.env.GITNEXUS_UNITY_PARITY_WARMUP = '1';
|
|
13
13
|
}
|
|
14
14
|
// Prevent unhandled errors from crashing the MCP server process.
|
|
15
|
-
//
|
|
15
|
+
// LadybugDB lock conflicts and transient errors should degrade gracefully.
|
|
16
16
|
process.on('uncaughtException', (err) => {
|
|
17
17
|
console.error(`GitNexus MCP: uncaught exception — ${err.message}`);
|
|
18
|
+
// Process is in an undefined state after uncaughtException — exit after flushing
|
|
19
|
+
setTimeout(() => process.exit(1), 100);
|
|
18
20
|
});
|
|
19
21
|
process.on('unhandledRejection', (reason) => {
|
|
20
22
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
package/dist/cli/setup.js
CHANGED
|
@@ -14,10 +14,12 @@ import { promisify } from 'node:util';
|
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
15
|
import { getGlobalDir, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
|
|
16
16
|
import { getGitRoot } from '../storage/git.js';
|
|
17
|
+
import { glob } from 'glob';
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = path.dirname(__filename);
|
|
19
20
|
const execFileAsync = promisify(execFile);
|
|
20
|
-
const FALLBACK_MCP_PACKAGE = 'gitnexus@latest';
|
|
21
|
+
const FALLBACK_MCP_PACKAGE = '@veewo/gitnexus@latest';
|
|
22
|
+
const LEGACY_CURSOR_AGENT = 'cursor';
|
|
21
23
|
function resolveSetupScope(rawScope) {
|
|
22
24
|
if (!rawScope || rawScope.trim() === '')
|
|
23
25
|
return 'global';
|
|
@@ -27,16 +29,28 @@ function resolveSetupScope(rawScope) {
|
|
|
27
29
|
}
|
|
28
30
|
function resolveSetupAgent(rawAgent) {
|
|
29
31
|
if (!rawAgent || rawAgent.trim() === '') {
|
|
30
|
-
|
|
32
|
+
return 'claude';
|
|
31
33
|
}
|
|
32
34
|
if (rawAgent === 'claude' || rawAgent === 'opencode' || rawAgent === 'codex') {
|
|
33
35
|
return rawAgent;
|
|
34
36
|
}
|
|
35
37
|
throw new Error(`Invalid --agent value "${rawAgent}". Use "claude", "opencode", or "codex".`);
|
|
36
38
|
}
|
|
39
|
+
async function installLegacyCursorSkills(result) {
|
|
40
|
+
const skillsDir = path.join(os.homedir(), '.cursor', 'skills');
|
|
41
|
+
try {
|
|
42
|
+
const installed = await installSkillsTo(skillsDir);
|
|
43
|
+
if (installed.length > 0) {
|
|
44
|
+
result.configured.push(`Cursor skills (${installed.length} skills → ~/.cursor/skills/)`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
result.errors.push(`Cursor skills: ${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
37
51
|
/**
|
|
38
52
|
* Resolve the package spec used by MCP commands.
|
|
39
|
-
* Defaults to gitnexus@latest when package metadata is unavailable.
|
|
53
|
+
* Defaults to @veewo/gitnexus@latest when package metadata is unavailable.
|
|
40
54
|
*/
|
|
41
55
|
function resolveMcpPackageSpec() {
|
|
42
56
|
try {
|
|
@@ -262,36 +276,39 @@ async function installClaudeCodeHooks(result) {
|
|
|
262
276
|
const src = path.join(pluginHooksPath, 'gitnexus-hook.cjs');
|
|
263
277
|
const dest = path.join(destHooksDir, 'gitnexus-hook.cjs');
|
|
264
278
|
try {
|
|
265
|
-
|
|
279
|
+
let content = await fs.readFile(src, 'utf-8');
|
|
280
|
+
// Inject resolved CLI path so the copied hook can find the CLI
|
|
281
|
+
// even when it's no longer inside the npm package tree
|
|
282
|
+
const resolvedCli = path.join(__dirname, '..', 'cli', 'index.js');
|
|
283
|
+
const normalizedCli = path.resolve(resolvedCli).replace(/\\/g, '/');
|
|
284
|
+
const jsonCli = JSON.stringify(normalizedCli);
|
|
285
|
+
content = content.replace("let cliPath = path.resolve(__dirname, '..', '..', 'dist', 'cli', 'index.js');", `let cliPath = ${jsonCli};`);
|
|
266
286
|
await fs.writeFile(dest, content, 'utf-8');
|
|
267
287
|
}
|
|
268
288
|
catch {
|
|
269
289
|
// Script not found in source — skip
|
|
270
290
|
}
|
|
271
|
-
const
|
|
291
|
+
const hookPath = path.join(destHooksDir, 'gitnexus-hook.cjs').replace(/\\/g, '/');
|
|
292
|
+
const hookCmd = `node "${hookPath.replace(/"/g, '\\"')}"`;
|
|
272
293
|
// Merge hook config into ~/.claude/settings.json
|
|
273
294
|
const existing = await readJsonFile(settingsPath) || {};
|
|
274
295
|
if (!existing.hooks)
|
|
275
296
|
existing.hooks = {};
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
type: 'command',
|
|
287
|
-
command: hookCmd,
|
|
288
|
-
timeout: 8000,
|
|
289
|
-
statusMessage: 'Enriching with GitNexus graph context...',
|
|
290
|
-
}],
|
|
291
|
-
});
|
|
297
|
+
function ensureHookEntry(eventName, matcher, timeout, statusMessage) {
|
|
298
|
+
if (!existing.hooks[eventName])
|
|
299
|
+
existing.hooks[eventName] = [];
|
|
300
|
+
const hasHook = existing.hooks[eventName].some((h) => h.hooks?.some(hh => hh.command?.includes('gitnexus-hook')));
|
|
301
|
+
if (!hasHook) {
|
|
302
|
+
existing.hooks[eventName].push({
|
|
303
|
+
matcher,
|
|
304
|
+
hooks: [{ type: 'command', command: hookCmd, timeout, statusMessage }],
|
|
305
|
+
});
|
|
306
|
+
}
|
|
292
307
|
}
|
|
308
|
+
ensureHookEntry('PreToolUse', 'Grep|Glob|Bash', 10, 'Enriching with GitNexus graph context...');
|
|
309
|
+
ensureHookEntry('PostToolUse', 'Bash', 10, 'Checking GitNexus index freshness...');
|
|
293
310
|
await writeJsonFile(settingsPath, existing);
|
|
294
|
-
result.configured.push('Claude Code hooks (PreToolUse)');
|
|
311
|
+
result.configured.push('Claude Code hooks (PreToolUse, PostToolUse)');
|
|
295
312
|
}
|
|
296
313
|
catch (err) {
|
|
297
314
|
result.errors.push(`Claude Code hooks: ${err.message}`);
|
|
@@ -383,7 +400,6 @@ async function saveSetupScope(scope, result) {
|
|
|
383
400
|
}
|
|
384
401
|
}
|
|
385
402
|
// ─── Skill Installation ───────────────────────────────────────────
|
|
386
|
-
const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impact-analysis', 'gitnexus-refactoring', 'gitnexus-guide', 'gitnexus-cli'];
|
|
387
403
|
/**
|
|
388
404
|
* Install GitNexus skills to a target directory.
|
|
389
405
|
* Each skill is installed as {targetDir}/{skillName}/SKILL.md.
|
|
@@ -395,23 +411,36 @@ const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impac
|
|
|
395
411
|
async function installSkillsTo(targetDir) {
|
|
396
412
|
const installed = [];
|
|
397
413
|
const skillsRoot = path.join(__dirname, '..', '..', 'skills');
|
|
398
|
-
|
|
414
|
+
let flatFiles = [];
|
|
415
|
+
let dirSkillFiles = [];
|
|
416
|
+
try {
|
|
417
|
+
[flatFiles, dirSkillFiles] = await Promise.all([
|
|
418
|
+
glob('*.md', { cwd: skillsRoot }),
|
|
419
|
+
glob('*/SKILL.md', { cwd: skillsRoot }),
|
|
420
|
+
]);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
const skillSources = new Map();
|
|
426
|
+
for (const relPath of dirSkillFiles) {
|
|
427
|
+
skillSources.set(path.dirname(relPath), { isDirectory: true });
|
|
428
|
+
}
|
|
429
|
+
for (const relPath of flatFiles) {
|
|
430
|
+
const skillName = path.basename(relPath, '.md');
|
|
431
|
+
if (!skillSources.has(skillName)) {
|
|
432
|
+
skillSources.set(skillName, { isDirectory: false });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
for (const [skillName, source] of skillSources) {
|
|
399
436
|
const skillDir = path.join(targetDir, skillName);
|
|
400
437
|
try {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
let isDirectory = false;
|
|
404
|
-
try {
|
|
405
|
-
const stat = await fs.stat(dirSource);
|
|
406
|
-
isDirectory = stat.isDirectory();
|
|
407
|
-
}
|
|
408
|
-
catch { /* not a directory */ }
|
|
409
|
-
if (isDirectory) {
|
|
438
|
+
if (source.isDirectory) {
|
|
439
|
+
const dirSource = path.join(skillsRoot, skillName);
|
|
410
440
|
await copyDirRecursive(dirSource, skillDir);
|
|
411
441
|
installed.push(skillName);
|
|
412
442
|
}
|
|
413
443
|
else {
|
|
414
|
-
// Fall back to flat file (skills/{name}.md)
|
|
415
444
|
const flatSource = path.join(skillsRoot, `${skillName}.md`);
|
|
416
445
|
const content = await fs.readFile(flatSource, 'utf-8');
|
|
417
446
|
await fs.mkdir(skillDir, { recursive: true });
|
|
@@ -450,6 +479,7 @@ export const setupCommand = async (options = {}) => {
|
|
|
450
479
|
console.log('');
|
|
451
480
|
let scope;
|
|
452
481
|
let agent;
|
|
482
|
+
const legacyCursorMode = !options.agent || options.agent.trim() === '';
|
|
453
483
|
try {
|
|
454
484
|
scope = resolveSetupScope(options.scope);
|
|
455
485
|
agent = resolveSetupAgent(options.agent);
|
|
@@ -468,20 +498,29 @@ export const setupCommand = async (options = {}) => {
|
|
|
468
498
|
errors: [],
|
|
469
499
|
};
|
|
470
500
|
if (scope === 'global') {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
await
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
else if (agent === 'opencode') {
|
|
478
|
-
await setupOpenCode(result);
|
|
501
|
+
if (legacyCursorMode) {
|
|
502
|
+
await setupCursor(result);
|
|
503
|
+
await installLegacyCursorSkills(result);
|
|
504
|
+
await saveSetupScope(scope, result);
|
|
505
|
+
agent = LEGACY_CURSOR_AGENT;
|
|
479
506
|
}
|
|
480
|
-
else
|
|
481
|
-
|
|
507
|
+
else {
|
|
508
|
+
// Configure only the selected agent MCP
|
|
509
|
+
if (agent === 'claude') {
|
|
510
|
+
await setupClaudeCode(result);
|
|
511
|
+
// Claude-only hooks should only be installed when Claude is selected.
|
|
512
|
+
await installClaudeCodeHooks(result);
|
|
513
|
+
}
|
|
514
|
+
else if (agent === 'opencode') {
|
|
515
|
+
await setupOpenCode(result);
|
|
516
|
+
}
|
|
517
|
+
else if (agent === 'codex') {
|
|
518
|
+
await setupCodex(result);
|
|
519
|
+
}
|
|
520
|
+
// Install shared global skills once
|
|
521
|
+
await installGlobalAgentSkills(result);
|
|
522
|
+
await saveSetupScope(scope, result);
|
|
482
523
|
}
|
|
483
|
-
// Install shared global skills once
|
|
484
|
-
await installGlobalAgentSkills(result);
|
|
485
524
|
}
|
|
486
525
|
else {
|
|
487
526
|
const repoRoot = getGitRoot(process.cwd());
|
|
@@ -500,8 +539,8 @@ export const setupCommand = async (options = {}) => {
|
|
|
500
539
|
await setupProjectOpenCode(repoRoot, result);
|
|
501
540
|
}
|
|
502
541
|
await installProjectAgentSkills(repoRoot, result);
|
|
542
|
+
await saveSetupScope(scope, result);
|
|
503
543
|
}
|
|
504
|
-
await saveSetupScope(scope, result);
|
|
505
544
|
// Print results
|
|
506
545
|
if (result.configured.length > 0) {
|
|
507
546
|
console.log(' Configured:');
|
|
@@ -526,7 +565,7 @@ export const setupCommand = async (options = {}) => {
|
|
|
526
565
|
console.log('');
|
|
527
566
|
console.log(' Summary:');
|
|
528
567
|
console.log(` Scope: ${scope}`);
|
|
529
|
-
console.log(` Agent: ${agent}`);
|
|
568
|
+
console.log(` Agent: ${legacyCursorMode ? LEGACY_CURSOR_AGENT : agent}`);
|
|
530
569
|
console.log(` MCP configured for: ${result.configured.filter(c => !c.includes('skills')).join(', ') || 'none'}`);
|
|
531
570
|
console.log(` Skills installed to: ${result.configured.filter(c => c.includes('skills')).length > 0 ? result.configured.filter(c => c.includes('skills')).join(', ') : 'none'}`);
|
|
532
571
|
console.log('');
|
package/dist/cli/setup.test.js
CHANGED
|
@@ -16,21 +16,26 @@ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
16
16
|
async function runSetup(args, env, cwd = packageRoot) {
|
|
17
17
|
return execFileAsync(process.execPath, [cliPath, 'setup', ...args], { cwd, env });
|
|
18
18
|
}
|
|
19
|
-
test('setup
|
|
19
|
+
test('setup without --agent uses legacy Cursor install path', async () => {
|
|
20
20
|
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
21
21
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
await fs.mkdir(path.join(fakeHome, '.cursor'), { recursive: true });
|
|
23
|
+
await runSetup([], {
|
|
24
|
+
...process.env,
|
|
25
|
+
HOME: fakeHome,
|
|
26
|
+
USERPROFILE: fakeHome,
|
|
27
|
+
});
|
|
28
|
+
const cursorMcpPath = path.join(fakeHome, '.cursor', 'mcp.json');
|
|
29
|
+
const cursorSkillPath = path.join(fakeHome, '.cursor', 'skills', 'gitnexus-cli', 'SKILL.md');
|
|
30
|
+
const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
|
|
31
|
+
const cursorMcpRaw = await fs.readFile(cursorMcpPath, 'utf-8');
|
|
32
|
+
const cursorMcp = JSON.parse(cursorMcpRaw);
|
|
33
|
+
assert.equal(cursorMcp.mcpServers?.gitnexus?.command, 'npx');
|
|
34
|
+
assert.deepEqual(cursorMcp.mcpServers?.gitnexus?.args, ['-y', expectedMcpPackage, 'mcp']);
|
|
35
|
+
await fs.access(cursorSkillPath);
|
|
36
|
+
const configRaw = await fs.readFile(configPath, 'utf-8');
|
|
37
|
+
const config = JSON.parse(configRaw);
|
|
38
|
+
assert.equal(config.setupScope, 'global');
|
|
34
39
|
}
|
|
35
40
|
finally {
|
|
36
41
|
await fs.rm(fakeHome, { recursive: true, force: true });
|