@duytransipher/gitnexus 1.4.6-sipher.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +73 -0
- package/README.md +261 -0
- package/dist/cli/ai-context.d.ts +23 -0
- package/dist/cli/ai-context.js +265 -0
- package/dist/cli/analyze.d.ts +12 -0
- package/dist/cli/analyze.js +345 -0
- package/dist/cli/augment.d.ts +13 -0
- package/dist/cli/augment.js +33 -0
- package/dist/cli/clean.d.ts +10 -0
- package/dist/cli/clean.js +60 -0
- package/dist/cli/eval-server.d.ts +37 -0
- package/dist/cli/eval-server.js +389 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +137 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +30 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +36 -0
- package/dist/cli/serve.d.ts +4 -0
- package/dist/cli/serve.js +6 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +367 -0
- package/dist/cli/sipher-patched.d.ts +2 -0
- package/dist/cli/sipher-patched.js +77 -0
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +36 -0
- package/dist/cli/tool.d.ts +60 -0
- package/dist/cli/tool.js +180 -0
- package/dist/cli/wiki.d.ts +15 -0
- package/dist/cli/wiki.js +365 -0
- package/dist/config/ignore-service.d.ts +26 -0
- package/dist/config/ignore-service.js +284 -0
- package/dist/config/supported-languages.d.ts +15 -0
- package/dist/config/supported-languages.js +16 -0
- package/dist/core/augmentation/engine.d.ts +26 -0
- package/dist/core/augmentation/engine.js +240 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +251 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
- package/dist/core/embeddings/embedding-pipeline.js +356 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +66 -0
- package/dist/core/graph/types.d.ts +66 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +35 -0
- package/dist/core/ingestion/call-processor.d.ts +23 -0
- package/dist/core/ingestion/call-processor.js +793 -0
- package/dist/core/ingestion/call-routing.d.ts +68 -0
- package/dist/core/ingestion/call-routing.js +129 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +312 -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 +40 -0
- package/dist/core/ingestion/entry-point-scoring.js +353 -0
- 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.d.ts +28 -0
- package/dist/core/ingestion/filesystem-walker.js +81 -0
- package/dist/core/ingestion/framework-detection.d.ts +54 -0
- package/dist/core/ingestion/framework-detection.js +411 -0
- package/dist/core/ingestion/heritage-processor.d.ts +28 -0
- package/dist/core/ingestion/heritage-processor.js +251 -0
- package/dist/core/ingestion/import-processor.d.ts +34 -0
- package/dist/core/ingestion/import-processor.js +398 -0
- 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 +19 -0
- package/dist/core/ingestion/parsing-processor.js +315 -0
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +401 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +315 -0
- 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/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +63 -0
- package/dist/core/ingestion/symbol-table.js +85 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
- package/dist/core/ingestion/tree-sitter-queries.js +888 -0
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +613 -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 +455 -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 +456 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
- package/dist/core/ingestion/type-extractors/shared.js +810 -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/types.js +1 -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 +138 -0
- package/dist/core/ingestion/utils.js +1290 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
- package/dist/core/ingestion/workers/parse-worker.js +1126 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
- package/dist/core/ingestion/workers/worker-pool.js +128 -0
- package/dist/core/lbug/csv-generator.d.ts +33 -0
- package/dist/core/lbug/csv-generator.js +366 -0
- package/dist/core/lbug/lbug-adapter.d.ts +103 -0
- package/dist/core/lbug/lbug-adapter.js +769 -0
- package/dist/core/lbug/schema.d.ts +53 -0
- package/dist/core/lbug/schema.js +430 -0
- package/dist/core/search/bm25-index.d.ts +23 -0
- package/dist/core/search/bm25-index.js +96 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
- package/dist/core/tree-sitter/parser-loader.js +63 -0
- package/dist/core/wiki/generator.d.ts +120 -0
- package/dist/core/wiki/generator.js +939 -0
- package/dist/core/wiki/graph-queries.d.ts +80 -0
- package/dist/core/wiki/graph-queries.js +238 -0
- package/dist/core/wiki/html-viewer.d.ts +10 -0
- package/dist/core/wiki/html-viewer.js +297 -0
- package/dist/core/wiki/llm-client.d.ts +43 -0
- package/dist/core/wiki/llm-client.js +186 -0
- package/dist/core/wiki/prompts.d.ts +53 -0
- package/dist/core/wiki/prompts.js +174 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +108 -0
- package/dist/mcp/core/lbug-adapter.d.ts +57 -0
- package/dist/mcp/core/lbug-adapter.js +455 -0
- package/dist/mcp/local/local-backend.d.ts +181 -0
- package/dist/mcp/local/local-backend.js +1722 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +411 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +296 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +292 -0
- package/dist/server/api.d.ts +10 -0
- package/dist/server/api.js +344 -0
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +138 -0
- package/dist/storage/repo-manager.js +299 -0
- package/dist/types/pipeline.d.ts +32 -0
- package/dist/types/pipeline.js +18 -0
- package/dist/unreal/bridge.d.ts +4 -0
- package/dist/unreal/bridge.js +113 -0
- package/dist/unreal/config.d.ts +6 -0
- package/dist/unreal/config.js +55 -0
- package/dist/unreal/types.d.ts +105 -0
- package/dist/unreal/types.js +1 -0
- package/hooks/claude/gitnexus-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/package.json +100 -0
- package/scripts/ensure-cli-executable.cjs +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/scripts/setup-unreal-gitnexus.ps1 +191 -0
- package/skills/gitnexus-cli.md +82 -0
- package/skills/gitnexus-debugging.md +89 -0
- package/skills/gitnexus-exploring.md +78 -0
- package/skills/gitnexus-guide.md +64 -0
- package/skills/gitnexus-impact-analysis.md +97 -0
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +121 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval Server — Lightweight HTTP server for SWE-bench evaluation
|
|
3
|
+
*
|
|
4
|
+
* Keeps LadybugDB warm in memory so tool calls from the agent are near-instant.
|
|
5
|
+
* Designed to run inside Docker containers during SWE-bench evaluation.
|
|
6
|
+
*
|
|
7
|
+
* KEY DESIGN: Returns LLM-friendly text, not raw JSON.
|
|
8
|
+
* Raw JSON wastes tokens and is hard for models to parse. The text formatter
|
|
9
|
+
* converts structured results into compact, readable output that models
|
|
10
|
+
* can immediately act on. Next-step hints guide the agent through a
|
|
11
|
+
* productive tool-chaining workflow (query → context → impact → fix).
|
|
12
|
+
*
|
|
13
|
+
* Architecture:
|
|
14
|
+
* Agent bash cmd → curl localhost:PORT/tool/query → eval-server → LocalBackend → format → text
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* gitnexus eval-server # default port 4848
|
|
18
|
+
* gitnexus eval-server --port 4848 # explicit port
|
|
19
|
+
* gitnexus eval-server --idle-timeout 300 # auto-shutdown after 300s idle
|
|
20
|
+
*
|
|
21
|
+
* API:
|
|
22
|
+
* POST /tool/:name — Call a tool. Body is JSON arguments. Returns formatted text.
|
|
23
|
+
* GET /health — Health check. Returns {"status":"ok","repos":[...]}
|
|
24
|
+
* POST /shutdown — Graceful shutdown.
|
|
25
|
+
*/
|
|
26
|
+
import http from 'http';
|
|
27
|
+
import { writeSync } from 'node:fs';
|
|
28
|
+
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
29
|
+
// ─── Text Formatters ──────────────────────────────────────────────────
|
|
30
|
+
// Convert structured JSON results into compact, LLM-friendly text.
|
|
31
|
+
// Design: minimize tokens, maximize actionability.
|
|
32
|
+
export function formatQueryResult(result) {
|
|
33
|
+
if (result.error)
|
|
34
|
+
return `Error: ${result.error}`;
|
|
35
|
+
const lines = [];
|
|
36
|
+
const processes = result.processes || [];
|
|
37
|
+
const symbols = result.process_symbols || [];
|
|
38
|
+
const defs = result.definitions || [];
|
|
39
|
+
if (processes.length === 0 && defs.length === 0) {
|
|
40
|
+
return 'No matching execution flows found. Try a different search term or use grep.';
|
|
41
|
+
}
|
|
42
|
+
lines.push(`Found ${processes.length} execution flow(s):\n`);
|
|
43
|
+
for (let i = 0; i < processes.length; i++) {
|
|
44
|
+
const p = processes[i];
|
|
45
|
+
lines.push(`${i + 1}. ${p.summary} (${p.step_count} steps, ${p.symbol_count} symbols)`);
|
|
46
|
+
// Show symbols belonging to this process
|
|
47
|
+
const procSymbols = symbols.filter((s) => s.process_id === p.id);
|
|
48
|
+
for (const s of procSymbols.slice(0, 6)) {
|
|
49
|
+
const loc = s.startLine ? `:${s.startLine}` : '';
|
|
50
|
+
lines.push(` ${s.type} ${s.name} → ${s.filePath}${loc}`);
|
|
51
|
+
}
|
|
52
|
+
if (procSymbols.length > 6) {
|
|
53
|
+
lines.push(` ... and ${procSymbols.length - 6} more`);
|
|
54
|
+
}
|
|
55
|
+
lines.push('');
|
|
56
|
+
}
|
|
57
|
+
if (defs.length > 0) {
|
|
58
|
+
lines.push(`Standalone definitions:`);
|
|
59
|
+
for (const d of defs.slice(0, 8)) {
|
|
60
|
+
lines.push(` ${d.type || 'Symbol'} ${d.name} → ${d.filePath || '?'}`);
|
|
61
|
+
}
|
|
62
|
+
if (defs.length > 8)
|
|
63
|
+
lines.push(` ... and ${defs.length - 8} more`);
|
|
64
|
+
}
|
|
65
|
+
return lines.join('\n').trim();
|
|
66
|
+
}
|
|
67
|
+
export function formatContextResult(result) {
|
|
68
|
+
if (result.error)
|
|
69
|
+
return `Error: ${result.error}`;
|
|
70
|
+
if (result.status === 'ambiguous') {
|
|
71
|
+
const lines = [`Multiple symbols named '${result.candidates?.[0]?.name || '?'}'. Disambiguate with file path:\n`];
|
|
72
|
+
for (const c of result.candidates || []) {
|
|
73
|
+
lines.push(` ${c.kind} ${c.name} → ${c.filePath}:${c.line || '?'} (uid: ${c.uid})`);
|
|
74
|
+
}
|
|
75
|
+
lines.push(`\nRe-run: gitnexus-context "${result.candidates?.[0]?.name}" "<file_path>"`);
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
const sym = result.symbol;
|
|
79
|
+
if (!sym)
|
|
80
|
+
return 'Symbol not found.';
|
|
81
|
+
const lines = [];
|
|
82
|
+
const loc = sym.startLine ? `:${sym.startLine}-${sym.endLine}` : '';
|
|
83
|
+
lines.push(`${sym.kind} ${sym.name} → ${sym.filePath}${loc}`);
|
|
84
|
+
lines.push('');
|
|
85
|
+
// Incoming refs (who calls/imports/extends this)
|
|
86
|
+
const incoming = result.incoming || {};
|
|
87
|
+
const incomingCount = Object.values(incoming).reduce((sum, arr) => sum + arr.length, 0);
|
|
88
|
+
if (incomingCount > 0) {
|
|
89
|
+
lines.push(`Called/imported by (${incomingCount}):`);
|
|
90
|
+
for (const [relType, refs] of Object.entries(incoming)) {
|
|
91
|
+
for (const ref of refs.slice(0, 10)) {
|
|
92
|
+
lines.push(` ← [${relType}] ${ref.kind} ${ref.name} → ${ref.filePath}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
lines.push('');
|
|
96
|
+
}
|
|
97
|
+
// Outgoing refs (what this calls/imports)
|
|
98
|
+
const outgoing = result.outgoing || {};
|
|
99
|
+
const outgoingCount = Object.values(outgoing).reduce((sum, arr) => sum + arr.length, 0);
|
|
100
|
+
if (outgoingCount > 0) {
|
|
101
|
+
lines.push(`Calls/imports (${outgoingCount}):`);
|
|
102
|
+
for (const [relType, refs] of Object.entries(outgoing)) {
|
|
103
|
+
for (const ref of refs.slice(0, 10)) {
|
|
104
|
+
lines.push(` → [${relType}] ${ref.kind} ${ref.name} → ${ref.filePath}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
// Processes
|
|
110
|
+
const procs = result.processes || [];
|
|
111
|
+
if (procs.length > 0) {
|
|
112
|
+
lines.push(`Participates in ${procs.length} execution flow(s):`);
|
|
113
|
+
for (const p of procs) {
|
|
114
|
+
lines.push(` • ${p.name} (step ${p.step_index}/${p.step_count})`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (sym.content) {
|
|
118
|
+
lines.push('');
|
|
119
|
+
lines.push(`Source:`);
|
|
120
|
+
lines.push(sym.content);
|
|
121
|
+
}
|
|
122
|
+
return lines.join('\n').trim();
|
|
123
|
+
}
|
|
124
|
+
export function formatImpactResult(result) {
|
|
125
|
+
if (result.error) {
|
|
126
|
+
const suggestion = result.suggestion ? `\nSuggestion: ${result.suggestion}` : '';
|
|
127
|
+
return `Error: ${result.error}${suggestion}`;
|
|
128
|
+
}
|
|
129
|
+
const target = result.target;
|
|
130
|
+
const direction = result.direction;
|
|
131
|
+
const byDepth = result.byDepth || {};
|
|
132
|
+
const total = result.impactedCount || 0;
|
|
133
|
+
if (total === 0) {
|
|
134
|
+
return `${target?.name || '?'}: No ${direction} dependencies found. This symbol appears isolated.`;
|
|
135
|
+
}
|
|
136
|
+
const lines = [];
|
|
137
|
+
const dirLabel = direction === 'upstream' ? 'depends on this (will break if changed)' : 'this depends on';
|
|
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('');
|
|
143
|
+
const depthLabels = {
|
|
144
|
+
1: 'WILL BREAK (direct)',
|
|
145
|
+
2: 'LIKELY AFFECTED (indirect)',
|
|
146
|
+
3: 'MAY NEED TESTING (transitive)',
|
|
147
|
+
};
|
|
148
|
+
for (const depth of [1, 2, 3]) {
|
|
149
|
+
const items = byDepth[depth];
|
|
150
|
+
if (!items || items.length === 0)
|
|
151
|
+
continue;
|
|
152
|
+
lines.push(`d=${depth}: ${depthLabels[depth] || ''} (${items.length})`);
|
|
153
|
+
for (const item of items.slice(0, 12)) {
|
|
154
|
+
const conf = item.confidence < 1 ? ` (conf: ${item.confidence})` : '';
|
|
155
|
+
lines.push(` ${item.type} ${item.name} → ${item.filePath} [${item.relationType}]${conf}`);
|
|
156
|
+
}
|
|
157
|
+
if (items.length > 12) {
|
|
158
|
+
lines.push(` ... and ${items.length - 12} more`);
|
|
159
|
+
}
|
|
160
|
+
lines.push('');
|
|
161
|
+
}
|
|
162
|
+
return lines.join('\n').trim();
|
|
163
|
+
}
|
|
164
|
+
export function formatCypherResult(result) {
|
|
165
|
+
if (result.error)
|
|
166
|
+
return `Error: ${result.error}`;
|
|
167
|
+
if (Array.isArray(result)) {
|
|
168
|
+
if (result.length === 0)
|
|
169
|
+
return 'Query returned 0 rows.';
|
|
170
|
+
// Format as simple table
|
|
171
|
+
const keys = Object.keys(result[0]);
|
|
172
|
+
const lines = [`${result.length} row(s):\n`];
|
|
173
|
+
for (const row of result.slice(0, 30)) {
|
|
174
|
+
const parts = keys.map(k => `${k}: ${row[k]}`);
|
|
175
|
+
lines.push(` ${parts.join(' | ')}`);
|
|
176
|
+
}
|
|
177
|
+
if (result.length > 30) {
|
|
178
|
+
lines.push(` ... ${result.length - 30} more rows`);
|
|
179
|
+
}
|
|
180
|
+
return lines.join('\n');
|
|
181
|
+
}
|
|
182
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
183
|
+
}
|
|
184
|
+
export function formatDetectChangesResult(result) {
|
|
185
|
+
if (result.error)
|
|
186
|
+
return `Error: ${result.error}`;
|
|
187
|
+
const summary = result.summary || {};
|
|
188
|
+
const lines = [];
|
|
189
|
+
if (summary.changed_count === 0) {
|
|
190
|
+
return 'No changes detected.';
|
|
191
|
+
}
|
|
192
|
+
lines.push(`Changes: ${summary.changed_files || 0} files, ${summary.changed_count || 0} symbols`);
|
|
193
|
+
lines.push(`Affected processes: ${summary.affected_count || 0}`);
|
|
194
|
+
lines.push(`Risk level: ${summary.risk_level || 'unknown'}\n`);
|
|
195
|
+
const changed = result.changed_symbols || [];
|
|
196
|
+
if (changed.length > 0) {
|
|
197
|
+
lines.push(`Changed symbols:`);
|
|
198
|
+
for (const s of changed.slice(0, 15)) {
|
|
199
|
+
lines.push(` ${s.type} ${s.name} → ${s.filePath}`);
|
|
200
|
+
}
|
|
201
|
+
if (changed.length > 15)
|
|
202
|
+
lines.push(` ... and ${changed.length - 15} more`);
|
|
203
|
+
lines.push('');
|
|
204
|
+
}
|
|
205
|
+
const affected = result.affected_processes || [];
|
|
206
|
+
if (affected.length > 0) {
|
|
207
|
+
lines.push(`Affected execution flows:`);
|
|
208
|
+
for (const p of affected.slice(0, 10)) {
|
|
209
|
+
const steps = (p.changed_steps || []).map((s) => s.symbol).join(', ');
|
|
210
|
+
lines.push(` • ${p.name} (${p.step_count} steps) — changed: ${steps}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return lines.join('\n').trim();
|
|
214
|
+
}
|
|
215
|
+
export function formatListReposResult(result) {
|
|
216
|
+
if (!Array.isArray(result) || result.length === 0) {
|
|
217
|
+
return 'No indexed repositories.';
|
|
218
|
+
}
|
|
219
|
+
const lines = ['Indexed repositories:\n'];
|
|
220
|
+
for (const r of result) {
|
|
221
|
+
const stats = r.stats || {};
|
|
222
|
+
lines.push(` ${r.name} — ${stats.nodes || '?'} symbols, ${stats.edges || '?'} relationships, ${stats.processes || '?'} flows`);
|
|
223
|
+
lines.push(` Path: ${r.path}`);
|
|
224
|
+
lines.push(` Indexed: ${r.indexedAt}`);
|
|
225
|
+
}
|
|
226
|
+
return lines.join('\n');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Format a tool result as compact, LLM-friendly text.
|
|
230
|
+
*/
|
|
231
|
+
function formatToolResult(toolName, result) {
|
|
232
|
+
switch (toolName) {
|
|
233
|
+
case 'query': return formatQueryResult(result);
|
|
234
|
+
case 'context': return formatContextResult(result);
|
|
235
|
+
case 'impact': return formatImpactResult(result);
|
|
236
|
+
case 'cypher': return formatCypherResult(result);
|
|
237
|
+
case 'detect_changes': return formatDetectChangesResult(result);
|
|
238
|
+
case 'list_repos': return formatListReposResult(result);
|
|
239
|
+
default: return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// ─── Next-Step Hints ──────────────────────────────────────────────────
|
|
243
|
+
// Guide the agent to the logical next tool call.
|
|
244
|
+
// Critical for tool chaining: query → context → impact → fix.
|
|
245
|
+
function getNextStepHint(toolName) {
|
|
246
|
+
switch (toolName) {
|
|
247
|
+
case 'query':
|
|
248
|
+
return '\n---\nNext: Pick a symbol above and run gitnexus-context "<name>" to see all its callers, callees, and execution flows.';
|
|
249
|
+
case 'context':
|
|
250
|
+
return '\n---\nNext: To check what breaks if you change this, run gitnexus-impact "<name>" upstream';
|
|
251
|
+
case 'impact':
|
|
252
|
+
return '\n---\nNext: Review d=1 items first (WILL BREAK). Read the source with cat to understand the code, then make your fix.';
|
|
253
|
+
case 'cypher':
|
|
254
|
+
return '\n---\nNext: To explore a result symbol in depth, run gitnexus-context "<name>"';
|
|
255
|
+
case 'detect_changes':
|
|
256
|
+
return '\n---\nNext: Run gitnexus-context "<symbol>" on high-risk changed symbols to check their callers.';
|
|
257
|
+
default:
|
|
258
|
+
return '';
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ─── Server ───────────────────────────────────────────────────────────
|
|
262
|
+
export async function evalServerCommand(options) {
|
|
263
|
+
const port = parseInt(options?.port || '4848');
|
|
264
|
+
const idleTimeoutSec = parseInt(options?.idleTimeout || '0');
|
|
265
|
+
const backend = new LocalBackend();
|
|
266
|
+
const ok = await backend.init();
|
|
267
|
+
if (!ok) {
|
|
268
|
+
console.error('GitNexus eval-server: No indexed repositories found. Run: gitnexus analyze');
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
const repos = await backend.listRepos();
|
|
272
|
+
console.error(`GitNexus eval-server: ${repos.length} repo(s) loaded: ${repos.map(r => r.name).join(', ')}`);
|
|
273
|
+
let idleTimer = null;
|
|
274
|
+
function resetIdleTimer() {
|
|
275
|
+
if (idleTimeoutSec <= 0)
|
|
276
|
+
return;
|
|
277
|
+
if (idleTimer)
|
|
278
|
+
clearTimeout(idleTimer);
|
|
279
|
+
idleTimer = setTimeout(async () => {
|
|
280
|
+
console.error('GitNexus eval-server: Idle timeout reached, shutting down');
|
|
281
|
+
await backend.disconnect();
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}, idleTimeoutSec * 1000);
|
|
284
|
+
}
|
|
285
|
+
const server = http.createServer(async (req, res) => {
|
|
286
|
+
resetIdleTimer();
|
|
287
|
+
try {
|
|
288
|
+
// Health check
|
|
289
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
290
|
+
res.setHeader('Content-Type', 'application/json');
|
|
291
|
+
res.writeHead(200);
|
|
292
|
+
res.end(JSON.stringify({ status: 'ok', repos: repos.map(r => r.name) }));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Shutdown
|
|
296
|
+
if (req.method === 'POST' && req.url === '/shutdown') {
|
|
297
|
+
res.setHeader('Content-Type', 'application/json');
|
|
298
|
+
res.writeHead(200);
|
|
299
|
+
res.end(JSON.stringify({ status: 'shutting_down' }));
|
|
300
|
+
setTimeout(async () => {
|
|
301
|
+
await backend.disconnect();
|
|
302
|
+
server.close();
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}, 100);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Tool calls: POST /tool/:name
|
|
308
|
+
const toolMatch = req.url?.match(/^\/tool\/(\w+)$/);
|
|
309
|
+
if (req.method === 'POST' && toolMatch) {
|
|
310
|
+
const toolName = toolMatch[1];
|
|
311
|
+
const body = await readBody(req);
|
|
312
|
+
let args = {};
|
|
313
|
+
if (body.trim()) {
|
|
314
|
+
try {
|
|
315
|
+
args = JSON.parse(body);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
319
|
+
res.writeHead(400);
|
|
320
|
+
res.end('Error: Invalid JSON body');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Call tool, format result as text, append next-step hint
|
|
325
|
+
const result = await backend.callTool(toolName, args);
|
|
326
|
+
const formatted = formatToolResult(toolName, result);
|
|
327
|
+
const hint = getNextStepHint(toolName);
|
|
328
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
329
|
+
res.writeHead(200);
|
|
330
|
+
res.end(formatted + hint);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// 404
|
|
334
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
335
|
+
res.writeHead(404);
|
|
336
|
+
res.end('Not found. Use POST /tool/:name or GET /health');
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
340
|
+
res.writeHead(500);
|
|
341
|
+
res.end(`Error: ${err.message || 'Internal error'}`);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
server.listen(port, '127.0.0.1', () => {
|
|
345
|
+
console.error(`GitNexus eval-server: listening on http://127.0.0.1:${port}`);
|
|
346
|
+
console.error(` POST /tool/query — search execution flows`);
|
|
347
|
+
console.error(` POST /tool/context — 360-degree symbol view`);
|
|
348
|
+
console.error(` POST /tool/impact — blast radius analysis`);
|
|
349
|
+
console.error(` POST /tool/cypher — raw Cypher query`);
|
|
350
|
+
console.error(` GET /health — health check`);
|
|
351
|
+
console.error(` POST /shutdown — graceful shutdown`);
|
|
352
|
+
if (idleTimeoutSec > 0) {
|
|
353
|
+
console.error(` Auto-shutdown after ${idleTimeoutSec}s idle`);
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
// Use fd 1 directly — LadybugDB captures process.stdout (#324)
|
|
357
|
+
writeSync(1, `GITNEXUS_EVAL_SERVER_READY:${port}\n`);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// stdout may not be available (e.g., broken pipe)
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
resetIdleTimer();
|
|
364
|
+
const shutdown = async () => {
|
|
365
|
+
console.error('GitNexus eval-server: shutting down...');
|
|
366
|
+
await backend.disconnect();
|
|
367
|
+
server.close();
|
|
368
|
+
process.exit(0);
|
|
369
|
+
};
|
|
370
|
+
process.on('SIGINT', shutdown);
|
|
371
|
+
process.on('SIGTERM', shutdown);
|
|
372
|
+
}
|
|
373
|
+
export const MAX_BODY_SIZE = 1024 * 1024; // 1MB
|
|
374
|
+
function readBody(req) {
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
const chunks = [];
|
|
377
|
+
let totalSize = 0;
|
|
378
|
+
req.on('data', (chunk) => {
|
|
379
|
+
totalSize += chunk.length;
|
|
380
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
381
|
+
req.destroy(new Error('Request body too large (max 1MB)'));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
chunks.push(chunk);
|
|
385
|
+
});
|
|
386
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
387
|
+
req.on('error', reject);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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.
|
|
4
|
+
import { Command } from 'commander';
|
|
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');
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name('gitnexus')
|
|
12
|
+
.description('GitNexus local CLI and MCP server')
|
|
13
|
+
.version(pkg.version);
|
|
14
|
+
program
|
|
15
|
+
.command('setup')
|
|
16
|
+
.description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode')
|
|
17
|
+
.action(createLazyAction(() => import('./setup.js'), 'setupCommand'));
|
|
18
|
+
program
|
|
19
|
+
.command('analyze [path]')
|
|
20
|
+
.description('Index a repository (full analysis)')
|
|
21
|
+
.option('-f, --force', 'Force full re-index even if up to date')
|
|
22
|
+
.option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
|
|
23
|
+
.option('--skills', 'Generate repo-specific skill files from detected communities')
|
|
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)')
|
|
26
|
+
.action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
|
|
27
|
+
program
|
|
28
|
+
.command('serve')
|
|
29
|
+
.description('Start local HTTP server for web UI connection')
|
|
30
|
+
.option('-p, --port <port>', 'Port number', '4747')
|
|
31
|
+
.option('--host <host>', 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)')
|
|
32
|
+
.action(createLazyAction(() => import('./serve.js'), 'serveCommand'));
|
|
33
|
+
program
|
|
34
|
+
.command('mcp')
|
|
35
|
+
.description('Start MCP server (stdio) — serves all indexed repos')
|
|
36
|
+
.action(createLazyAction(() => import('./mcp.js'), 'mcpCommand'));
|
|
37
|
+
program
|
|
38
|
+
.command('list')
|
|
39
|
+
.description('List all indexed repositories')
|
|
40
|
+
.action(createLazyAction(() => import('./list.js'), 'listCommand'));
|
|
41
|
+
program
|
|
42
|
+
.command('status')
|
|
43
|
+
.description('Show index status for current repo')
|
|
44
|
+
.action(createLazyAction(() => import('./status.js'), 'statusCommand'));
|
|
45
|
+
program
|
|
46
|
+
.command('clean')
|
|
47
|
+
.description('Delete GitNexus index for current repo')
|
|
48
|
+
.option('-f, --force', 'Skip confirmation prompt')
|
|
49
|
+
.option('--all', 'Clean all indexed repos')
|
|
50
|
+
.action(createLazyAction(() => import('./clean.js'), 'cleanCommand'));
|
|
51
|
+
program
|
|
52
|
+
.command('wiki [path]')
|
|
53
|
+
.description('Generate repository wiki from knowledge graph')
|
|
54
|
+
.option('-f, --force', 'Force full regeneration even if up to date')
|
|
55
|
+
.option('--model <model>', 'LLM model name (default: minimax/minimax-m2.5)')
|
|
56
|
+
.option('--base-url <url>', 'LLM API base URL (default: OpenAI)')
|
|
57
|
+
.option('--api-key <key>', 'LLM API key (saved to ~/.gitnexus/config.json)')
|
|
58
|
+
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
59
|
+
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
60
|
+
.action(createLazyAction(() => import('./wiki.js'), 'wikiCommand'));
|
|
61
|
+
program
|
|
62
|
+
.command('sipher-patched [path]')
|
|
63
|
+
.description('Validate S2 repo shape and Sipher gateway env for wiki generation')
|
|
64
|
+
.action(createLazyAction(() => import('./sipher-patched.js'), 'sipherPatchedCommand'));
|
|
65
|
+
program
|
|
66
|
+
.command('augment <pattern>')
|
|
67
|
+
.description('Augment a search pattern with knowledge graph context (used by hooks)')
|
|
68
|
+
.action(createLazyAction(() => import('./augment.js'), 'augmentCommand'));
|
|
69
|
+
// ─── Direct Tool Commands (no MCP overhead) ────────────────────────
|
|
70
|
+
// These invoke LocalBackend directly for use in eval, scripts, and CI.
|
|
71
|
+
program
|
|
72
|
+
.command('query <search_query>')
|
|
73
|
+
.description('Search the knowledge graph for execution flows related to a concept')
|
|
74
|
+
.option('-r, --repo <name>', 'Target repository (omit if only one indexed)')
|
|
75
|
+
.option('-c, --context <text>', 'Task context to improve ranking')
|
|
76
|
+
.option('-g, --goal <text>', 'What you want to find')
|
|
77
|
+
.option('-l, --limit <n>', 'Max processes to return (default: 5)')
|
|
78
|
+
.option('--content', 'Include full symbol source code')
|
|
79
|
+
.action(createLazyAction(() => import('./tool.js'), 'queryCommand'));
|
|
80
|
+
program
|
|
81
|
+
.command('context [name]')
|
|
82
|
+
.description('360-degree view of a code symbol: callers, callees, processes')
|
|
83
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
84
|
+
.option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
|
|
85
|
+
.option('-f, --file <path>', 'File path to disambiguate common names')
|
|
86
|
+
.option('--content', 'Include full symbol source code')
|
|
87
|
+
.action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
|
|
88
|
+
program
|
|
89
|
+
.command('impact <target>')
|
|
90
|
+
.description('Blast radius analysis: what breaks if you change a symbol')
|
|
91
|
+
.option('-d, --direction <dir>', 'upstream (dependants) or downstream (dependencies)', 'upstream')
|
|
92
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
93
|
+
.option('--depth <n>', 'Max relationship depth (default: 3)')
|
|
94
|
+
.option('--include-tests', 'Include test files in results')
|
|
95
|
+
.action(createLazyAction(() => import('./tool.js'), 'impactCommand'));
|
|
96
|
+
program
|
|
97
|
+
.command('cypher <query>')
|
|
98
|
+
.description('Execute raw Cypher query against the knowledge graph')
|
|
99
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
100
|
+
.action(createLazyAction(() => import('./tool.js'), 'cypherCommand'));
|
|
101
|
+
program
|
|
102
|
+
.command('unreal-sync')
|
|
103
|
+
.description('Refresh the Unreal Blueprint asset manifest for the current indexed repo')
|
|
104
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
105
|
+
.action(createLazyAction(() => import('./tool.js'), 'syncUnrealAssetManifestCommand'));
|
|
106
|
+
program
|
|
107
|
+
.command('unreal-find-refs [functionName]')
|
|
108
|
+
.description('Find confirmed Blueprint references to a native C++ function via the Unreal analyzer')
|
|
109
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
110
|
+
.option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
|
|
111
|
+
.option('-c, --class-name <name>', 'Owning native class name')
|
|
112
|
+
.option('-f, --file <path>', 'Source file path to disambiguate the function')
|
|
113
|
+
.option('--refresh-manifest', 'Refresh the Unreal asset manifest before searching')
|
|
114
|
+
.option('--max-candidates <n>', 'Cap the Blueprint candidate set passed to the Unreal analyzer')
|
|
115
|
+
.action(createLazyAction(() => import('./tool.js'), 'findNativeBlueprintReferencesCommand'));
|
|
116
|
+
program
|
|
117
|
+
.command('unreal-expand-chain <assetPath> <chainAnchorId>')
|
|
118
|
+
.description('Expand a Blueprint chain from a confirmed Unreal reference anchor')
|
|
119
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
120
|
+
.option('-d, --direction <dir>', 'upstream or downstream', 'downstream')
|
|
121
|
+
.option('--depth <n>', 'Maximum Blueprint traversal depth')
|
|
122
|
+
.action(createLazyAction(() => import('./tool.js'), 'expandBlueprintChainCommand'));
|
|
123
|
+
program
|
|
124
|
+
.command('unreal-derived-blueprints <className>')
|
|
125
|
+
.description('List Blueprint assets derived from a native C++ class via the Unreal manifest')
|
|
126
|
+
.option('-r, --repo <name>', 'Target repository')
|
|
127
|
+
.option('--refresh-manifest', 'Refresh the Unreal asset manifest before searching')
|
|
128
|
+
.option('--max-results <n>', 'Maximum Blueprint assets to return')
|
|
129
|
+
.action(createLazyAction(() => import('./tool.js'), 'findBlueprintsDerivedFromNativeClassCommand'));
|
|
130
|
+
// ─── Eval Server (persistent daemon for SWE-bench) ─────────────────
|
|
131
|
+
program
|
|
132
|
+
.command('eval-server')
|
|
133
|
+
.description('Start lightweight HTTP server for fast tool calls during evaluation')
|
|
134
|
+
.option('-p, --port <port>', 'Port number', '4848')
|
|
135
|
+
.option('--idle-timeout <seconds>', 'Auto-shutdown after N seconds idle (0 = disabled)', '0')
|
|
136
|
+
.action(createLazyAction(() => import('./eval-server.js'), 'evalServerCommand'));
|
|
137
|
+
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/list.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Command
|
|
3
|
+
*
|
|
4
|
+
* Shows all indexed repositories from the global registry.
|
|
5
|
+
*/
|
|
6
|
+
import { listRegisteredRepos } from '../storage/repo-manager.js';
|
|
7
|
+
export const listCommand = async () => {
|
|
8
|
+
const entries = await listRegisteredRepos({ validate: true });
|
|
9
|
+
if (entries.length === 0) {
|
|
10
|
+
console.log('No indexed repositories found.');
|
|
11
|
+
console.log('Run `gitnexus analyze` in a git repo to index it.');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log(`\n Indexed Repositories (${entries.length})\n`);
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const indexedDate = new Date(entry.indexedAt).toLocaleString();
|
|
17
|
+
const stats = entry.stats || {};
|
|
18
|
+
const commitShort = entry.lastCommit?.slice(0, 7) || 'unknown';
|
|
19
|
+
console.log(` ${entry.name}`);
|
|
20
|
+
console.log(` Path: ${entry.path}`);
|
|
21
|
+
console.log(` Indexed: ${indexedDate}`);
|
|
22
|
+
console.log(` Commit: ${commitShort}`);
|
|
23
|
+
console.log(` Stats: ${stats.files ?? 0} files, ${stats.nodes ?? 0} symbols, ${stats.edges ?? 0} edges`);
|
|
24
|
+
if (stats.communities)
|
|
25
|
+
console.log(` Clusters: ${stats.communities}`);
|
|
26
|
+
if (stats.processes)
|
|
27
|
+
console.log(` Processes: ${stats.processes}`);
|
|
28
|
+
console.log('');
|
|
29
|
+
}
|
|
30
|
+
};
|
package/dist/cli/mcp.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Command
|
|
3
|
+
*
|
|
4
|
+
* Starts the MCP server in standalone mode.
|
|
5
|
+
* Loads all indexed repos from the global registry.
|
|
6
|
+
* No longer depends on cwd — works from any directory.
|
|
7
|
+
*/
|
|
8
|
+
import { startMCPServer } from '../mcp/server.js';
|
|
9
|
+
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
10
|
+
export const mcpCommand = async () => {
|
|
11
|
+
// Prevent unhandled errors from crashing the MCP server process.
|
|
12
|
+
// LadybugDB lock conflicts and transient errors should degrade gracefully.
|
|
13
|
+
process.on('uncaughtException', (err) => {
|
|
14
|
+
console.error(`GitNexus MCP: uncaught exception — ${err.message}`);
|
|
15
|
+
// Process is in an undefined state after uncaughtException — exit after flushing
|
|
16
|
+
setTimeout(() => process.exit(1), 100);
|
|
17
|
+
});
|
|
18
|
+
process.on('unhandledRejection', (reason) => {
|
|
19
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
20
|
+
console.error(`GitNexus MCP: unhandled rejection — ${msg}`);
|
|
21
|
+
});
|
|
22
|
+
// Initialize multi-repo backend from registry.
|
|
23
|
+
// The server starts even with 0 repos — tools call refreshRepos() lazily,
|
|
24
|
+
// so repos indexed after the server starts are discovered automatically.
|
|
25
|
+
const backend = new LocalBackend();
|
|
26
|
+
await backend.init();
|
|
27
|
+
const repos = await backend.listRepos();
|
|
28
|
+
if (repos.length === 0) {
|
|
29
|
+
console.error('GitNexus: No indexed repos yet. Run `gitnexus analyze` in a git repo — the server will pick it up automatically.');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.error(`GitNexus: MCP server starting with ${repos.length} repo(s): ${repos.map(r => r.name).join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
// Start MCP server (serves all repos, discovers new ones lazily)
|
|
35
|
+
await startMCPServer(backend);
|
|
36
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup Command
|
|
3
|
+
*
|
|
4
|
+
* One-time global MCP configuration writer.
|
|
5
|
+
* Detects installed AI editors and writes the appropriate MCP config
|
|
6
|
+
* so the GitNexus MCP server is available in all projects.
|
|
7
|
+
*/
|
|
8
|
+
export declare const setupCommand: () => Promise<void>;
|