gitnexus 1.4.7 → 1.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/README.md +29 -1
  2. package/dist/cli/ai-context.d.ts +1 -1
  3. package/dist/cli/ai-context.js +1 -1
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +54 -21
  6. package/dist/cli/index-repo.d.ts +15 -0
  7. package/dist/cli/index-repo.js +115 -0
  8. package/dist/cli/index.js +13 -3
  9. package/dist/cli/setup.js +90 -10
  10. package/dist/cli/wiki.d.ts +4 -0
  11. package/dist/cli/wiki.js +174 -53
  12. package/dist/config/supported-languages.d.ts +33 -1
  13. package/dist/config/supported-languages.js +32 -0
  14. package/dist/core/embeddings/embedder.d.ts +6 -1
  15. package/dist/core/embeddings/embedder.js +65 -5
  16. package/dist/core/embeddings/embedding-pipeline.js +11 -9
  17. package/dist/core/embeddings/http-client.d.ts +31 -0
  18. package/dist/core/embeddings/http-client.js +179 -0
  19. package/dist/core/embeddings/index.d.ts +1 -0
  20. package/dist/core/embeddings/index.js +1 -0
  21. package/dist/core/embeddings/types.d.ts +1 -1
  22. package/dist/core/graph/graph.js +9 -1
  23. package/dist/core/graph/types.d.ts +11 -2
  24. package/dist/core/ingestion/call-processor.d.ts +66 -2
  25. package/dist/core/ingestion/call-processor.js +650 -30
  26. package/dist/core/ingestion/call-routing.d.ts +9 -18
  27. package/dist/core/ingestion/call-routing.js +0 -19
  28. package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
  29. package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
  30. package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
  31. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
  32. package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
  33. package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
  34. package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
  35. package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
  36. package/dist/core/ingestion/cobol-processor.d.ts +54 -0
  37. package/dist/core/ingestion/cobol-processor.js +1186 -0
  38. package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
  39. package/dist/core/ingestion/entry-point-scoring.js +52 -28
  40. package/dist/core/ingestion/export-detection.d.ts +47 -8
  41. package/dist/core/ingestion/export-detection.js +29 -50
  42. package/dist/core/ingestion/field-extractor.d.ts +29 -0
  43. package/dist/core/ingestion/field-extractor.js +25 -0
  44. package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
  45. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
  46. package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
  47. package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
  48. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
  49. package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
  50. package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
  51. package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
  52. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
  53. package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
  54. package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
  55. package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
  56. package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
  57. package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
  58. package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
  59. package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
  60. package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
  61. package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
  62. package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
  63. package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
  64. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
  65. package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
  66. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
  67. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
  68. package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
  69. package/dist/core/ingestion/field-extractors/generic.js +111 -0
  70. package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
  71. package/dist/core/ingestion/field-extractors/typescript.js +291 -0
  72. package/dist/core/ingestion/field-types.d.ts +59 -0
  73. package/dist/core/ingestion/field-types.js +2 -0
  74. package/dist/core/ingestion/framework-detection.d.ts +97 -2
  75. package/dist/core/ingestion/framework-detection.js +114 -14
  76. package/dist/core/ingestion/heritage-processor.js +62 -66
  77. package/dist/core/ingestion/import-processor.d.ts +9 -10
  78. package/dist/core/ingestion/import-processor.js +150 -196
  79. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
  80. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
  81. package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
  82. package/dist/core/ingestion/import-resolvers/dart.js +44 -0
  83. package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
  84. package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
  85. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +10 -1
  86. package/dist/core/ingestion/import-resolvers/jvm.js +159 -0
  87. package/dist/core/ingestion/import-resolvers/php.d.ts +25 -0
  88. package/dist/core/ingestion/import-resolvers/php.js +80 -0
  89. package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
  90. package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
  91. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
  92. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
  93. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
  94. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
  95. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
  96. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
  97. package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
  98. package/dist/core/ingestion/import-resolvers/swift.js +23 -0
  99. package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
  100. package/dist/core/ingestion/import-resolvers/types.js +6 -0
  101. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +2 -0
  102. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +7 -0
  103. package/dist/core/ingestion/language-config.d.ts +6 -0
  104. package/dist/core/ingestion/language-config.js +13 -0
  105. package/dist/core/ingestion/language-provider.d.ts +121 -0
  106. package/dist/core/ingestion/language-provider.js +24 -0
  107. package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
  108. package/dist/core/ingestion/languages/c-cpp.js +71 -0
  109. package/dist/core/ingestion/languages/cobol.d.ts +1 -0
  110. package/dist/core/ingestion/languages/cobol.js +26 -0
  111. package/dist/core/ingestion/languages/csharp.d.ts +8 -0
  112. package/dist/core/ingestion/languages/csharp.js +49 -0
  113. package/dist/core/ingestion/languages/dart.d.ts +12 -0
  114. package/dist/core/ingestion/languages/dart.js +58 -0
  115. package/dist/core/ingestion/languages/go.d.ts +11 -0
  116. package/dist/core/ingestion/languages/go.js +28 -0
  117. package/dist/core/ingestion/languages/index.d.ts +38 -0
  118. package/dist/core/ingestion/languages/index.js +63 -0
  119. package/dist/core/ingestion/languages/java.d.ts +9 -0
  120. package/dist/core/ingestion/languages/java.js +29 -0
  121. package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
  122. package/dist/core/ingestion/languages/kotlin.js +53 -0
  123. package/dist/core/ingestion/languages/php.d.ts +8 -0
  124. package/dist/core/ingestion/languages/php.js +145 -0
  125. package/dist/core/ingestion/languages/python.d.ts +12 -0
  126. package/dist/core/ingestion/languages/python.js +39 -0
  127. package/dist/core/ingestion/languages/ruby.d.ts +9 -0
  128. package/dist/core/ingestion/languages/ruby.js +44 -0
  129. package/dist/core/ingestion/languages/rust.d.ts +12 -0
  130. package/dist/core/ingestion/languages/rust.js +44 -0
  131. package/dist/core/ingestion/languages/swift.d.ts +12 -0
  132. package/dist/core/ingestion/languages/swift.js +133 -0
  133. package/dist/core/ingestion/languages/typescript.d.ts +10 -0
  134. package/dist/core/ingestion/languages/typescript.js +60 -0
  135. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  136. package/dist/core/ingestion/markdown-processor.js +124 -0
  137. package/dist/core/ingestion/mro-processor.js +22 -18
  138. package/dist/core/ingestion/named-binding-processor.d.ts +18 -0
  139. package/dist/core/ingestion/named-binding-processor.js +42 -0
  140. package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
  141. package/dist/core/ingestion/named-bindings/csharp.js +37 -0
  142. package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
  143. package/dist/core/ingestion/named-bindings/java.js +29 -0
  144. package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
  145. package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
  146. package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
  147. package/dist/core/ingestion/named-bindings/php.js +61 -0
  148. package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
  149. package/dist/core/ingestion/named-bindings/python.js +49 -0
  150. package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
  151. package/dist/core/ingestion/named-bindings/rust.js +64 -0
  152. package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
  153. package/dist/core/ingestion/named-bindings/types.js +6 -0
  154. package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
  155. package/dist/core/ingestion/named-bindings/typescript.js +58 -0
  156. package/dist/core/ingestion/parsing-processor.d.ts +6 -2
  157. package/dist/core/ingestion/parsing-processor.js +125 -85
  158. package/dist/core/ingestion/pipeline.d.ts +10 -0
  159. package/dist/core/ingestion/pipeline.js +1235 -317
  160. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  161. package/dist/core/ingestion/resolution-context.js +8 -5
  162. package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
  163. package/dist/core/ingestion/route-extractors/expo.js +36 -0
  164. package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
  165. package/dist/core/ingestion/route-extractors/middleware.js +143 -0
  166. package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
  167. package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
  168. package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
  169. package/dist/core/ingestion/route-extractors/php.js +21 -0
  170. package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
  171. package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
  172. package/dist/core/ingestion/symbol-table.d.ts +16 -0
  173. package/dist/core/ingestion/symbol-table.js +20 -6
  174. package/dist/core/ingestion/tree-sitter-queries.d.ts +10 -9
  175. package/dist/core/ingestion/tree-sitter-queries.js +274 -11
  176. package/dist/core/ingestion/type-env.d.ts +42 -18
  177. package/dist/core/ingestion/type-env.js +481 -106
  178. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  179. package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
  180. package/dist/core/ingestion/type-extractors/csharp.js +149 -16
  181. package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
  182. package/dist/core/ingestion/type-extractors/dart.js +371 -0
  183. package/dist/core/ingestion/type-extractors/jvm.js +169 -66
  184. package/dist/core/ingestion/type-extractors/rust.js +35 -1
  185. package/dist/core/ingestion/type-extractors/shared.d.ts +1 -15
  186. package/dist/core/ingestion/type-extractors/shared.js +14 -112
  187. package/dist/core/ingestion/type-extractors/swift.js +338 -7
  188. package/dist/core/ingestion/type-extractors/types.d.ts +40 -8
  189. package/dist/core/ingestion/type-extractors/typescript.js +141 -9
  190. package/dist/core/ingestion/utils/ast-helpers.d.ts +83 -0
  191. package/dist/core/ingestion/utils/ast-helpers.js +817 -0
  192. package/dist/core/ingestion/utils/call-analysis.d.ts +73 -0
  193. package/dist/core/ingestion/utils/call-analysis.js +527 -0
  194. package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
  195. package/dist/core/ingestion/utils/event-loop.js +5 -0
  196. package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
  197. package/dist/core/ingestion/utils/language-detection.js +70 -0
  198. package/dist/core/ingestion/utils/verbose.d.ts +1 -0
  199. package/dist/core/ingestion/utils/verbose.js +7 -0
  200. package/dist/core/ingestion/workers/parse-worker.d.ts +55 -5
  201. package/dist/core/ingestion/workers/parse-worker.js +415 -225
  202. package/dist/core/lbug/csv-generator.js +51 -1
  203. package/dist/core/lbug/lbug-adapter.d.ts +10 -0
  204. package/dist/core/lbug/lbug-adapter.js +75 -4
  205. package/dist/core/lbug/schema.d.ts +8 -4
  206. package/dist/core/lbug/schema.js +65 -4
  207. package/dist/core/tree-sitter/parser-loader.js +7 -1
  208. package/dist/core/wiki/cursor-client.d.ts +31 -0
  209. package/dist/core/wiki/cursor-client.js +127 -0
  210. package/dist/core/wiki/generator.d.ts +28 -9
  211. package/dist/core/wiki/generator.js +115 -18
  212. package/dist/core/wiki/graph-queries.d.ts +4 -0
  213. package/dist/core/wiki/graph-queries.js +7 -1
  214. package/dist/core/wiki/llm-client.d.ts +2 -0
  215. package/dist/core/wiki/llm-client.js +8 -4
  216. package/dist/core/wiki/prompts.d.ts +3 -3
  217. package/dist/core/wiki/prompts.js +6 -0
  218. package/dist/mcp/core/embedder.js +11 -3
  219. package/dist/mcp/core/lbug-adapter.d.ts +5 -0
  220. package/dist/mcp/core/lbug-adapter.js +23 -2
  221. package/dist/mcp/local/local-backend.d.ts +38 -5
  222. package/dist/mcp/local/local-backend.js +804 -63
  223. package/dist/mcp/resources.js +2 -0
  224. package/dist/mcp/tools.js +73 -4
  225. package/dist/server/api.d.ts +19 -1
  226. package/dist/server/api.js +66 -6
  227. package/dist/storage/git.d.ts +12 -0
  228. package/dist/storage/git.js +21 -0
  229. package/dist/storage/repo-manager.d.ts +3 -0
  230. package/package.json +25 -16
  231. package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
  232. package/dist/core/ingestion/named-binding-extraction.js +0 -363
  233. package/dist/core/ingestion/resolvers/index.d.ts +0 -18
  234. package/dist/core/ingestion/resolvers/index.js +0 -13
  235. package/dist/core/ingestion/resolvers/jvm.js +0 -87
  236. package/dist/core/ingestion/resolvers/php.d.ts +0 -15
  237. package/dist/core/ingestion/resolvers/php.js +0 -35
  238. package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
  239. package/dist/core/ingestion/type-extractors/index.js +0 -31
  240. package/dist/core/ingestion/utils.d.ts +0 -138
  241. package/dist/core/ingestion/utils.js +0 -1290
  242. package/scripts/patch-tree-sitter-swift.cjs +0 -74
package/dist/cli/wiki.js CHANGED
@@ -12,6 +12,7 @@ import { getGitRoot, isGitRepo } from '../storage/git.js';
12
12
  import { getStoragePaths, loadMeta, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
13
13
  import { WikiGenerator } from '../core/wiki/generator.js';
14
14
  import { resolveLLMConfig } from '../core/wiki/llm-client.js';
15
+ import { detectCursorCLI } from '../core/wiki/cursor-client.js';
15
16
  /**
16
17
  * Prompt the user for input via stdin.
17
18
  */
@@ -65,6 +66,10 @@ function prompt(question, hide = false) {
65
66
  });
66
67
  }
67
68
  export const wikiCommand = async (inputPath, options) => {
69
+ // Set verbose mode globally for cursor-client to pick up
70
+ if (options?.verbose) {
71
+ process.env.GITNEXUS_VERBOSE = '1';
72
+ }
68
73
  console.log('\n GitNexus Wiki Generator\n');
69
74
  // ── Resolve repo path ───────────────────────────────────────────────
70
75
  let repoPath;
@@ -96,94 +101,130 @@ export const wikiCommand = async (inputPath, options) => {
96
101
  }
97
102
  // ── Resolve LLM config (with interactive fallback) ─────────────────
98
103
  // Save any CLI overrides immediately
99
- if (options?.apiKey || options?.model || options?.baseUrl) {
104
+ if (options?.apiKey || options?.model || options?.baseUrl || options?.provider) {
100
105
  const existing = await loadCLIConfig();
101
106
  const updates = {};
102
107
  if (options.apiKey)
103
108
  updates.apiKey = options.apiKey;
104
- if (options.model)
105
- updates.model = options.model;
106
109
  if (options.baseUrl)
107
110
  updates.baseUrl = options.baseUrl;
111
+ if (options.provider)
112
+ updates.provider = options.provider;
113
+ // Save model to appropriate field based on provider
114
+ if (options.model) {
115
+ if (options.provider === 'cursor') {
116
+ updates.cursorModel = options.model;
117
+ }
118
+ else {
119
+ updates.model = options.model;
120
+ }
121
+ }
108
122
  await saveCLIConfig({ ...existing, ...updates });
109
123
  console.log(' Config saved to ~/.gitnexus/config.json\n');
110
124
  }
111
125
  const savedConfig = await loadCLIConfig();
112
- const hasSavedConfig = !!(savedConfig.apiKey && savedConfig.baseUrl);
113
- const hasCLIOverrides = !!(options?.apiKey || options?.model || options?.baseUrl);
126
+ const hasSavedConfig = !!(savedConfig.provider === 'cursor' || (savedConfig.apiKey && savedConfig.baseUrl));
127
+ const hasCLIOverrides = !!(options?.apiKey || options?.model || options?.baseUrl || options?.provider);
114
128
  let llmConfig = await resolveLLMConfig({
115
129
  model: options?.model,
116
130
  baseUrl: options?.baseUrl,
117
131
  apiKey: options?.apiKey,
132
+ provider: options?.provider,
118
133
  });
119
134
  // Run interactive setup if no saved config and no CLI flags provided
120
135
  // (even if env vars exist — let user explicitly choose their provider)
121
136
  if (!hasSavedConfig && !hasCLIOverrides) {
122
137
  if (!process.stdin.isTTY) {
123
- if (!llmConfig.apiKey) {
138
+ // Non-interactive mode — need either API key or Cursor CLI
139
+ if (!llmConfig.apiKey && llmConfig.provider !== 'cursor') {
124
140
  console.log(' Error: No LLM API key found.');
125
141
  console.log(' Set OPENAI_API_KEY or GITNEXUS_API_KEY environment variable,');
126
- console.log(' or pass --api-key <key>.\n');
142
+ console.log(' or pass --api-key <key>, or use --provider cursor.\n');
127
143
  process.exitCode = 1;
128
144
  return;
129
145
  }
130
- // Non-interactive with env var — just use it
146
+ // Non-interactive with env var or cursor — just use it
131
147
  }
132
148
  else {
133
149
  console.log(' No LLM configured. Let\'s set it up.\n');
134
- console.log(' Supports OpenAI, OpenRouter, or any OpenAI-compatible API.\n');
150
+ console.log(' Supports OpenAI, OpenRouter, any OpenAI-compatible API, or Cursor CLI.\n');
151
+ // Check if Cursor CLI is available
152
+ const hasCursor = detectCursorCLI();
135
153
  // Provider selection
136
154
  console.log(' [1] OpenAI (api.openai.com)');
137
155
  console.log(' [2] OpenRouter (openrouter.ai)');
138
- console.log(' [3] Custom endpoint\n');
139
- const choice = await prompt(' Select provider (1/2/3): ');
156
+ console.log(' [3] Custom endpoint');
157
+ if (hasCursor) {
158
+ console.log(' [4] Cursor CLI (local, uses your Cursor subscription)');
159
+ }
160
+ console.log('');
161
+ const maxChoice = hasCursor ? '4' : '3';
162
+ const choice = await prompt(` Select provider (1/${maxChoice}): `);
140
163
  let baseUrl;
141
164
  let defaultModel;
142
- if (choice === '2') {
143
- baseUrl = 'https://openrouter.ai/api/v1';
144
- defaultModel = 'minimax/minimax-m2.5';
145
- }
146
- else if (choice === '3') {
147
- baseUrl = await prompt(' Base URL (e.g. http://localhost:11434/v1): ');
148
- if (!baseUrl) {
149
- console.log('\n No URL provided. Aborting.\n');
150
- process.exitCode = 1;
151
- return;
152
- }
153
- defaultModel = 'gpt-4o-mini';
165
+ let provider = 'openai';
166
+ let key = '';
167
+ if (choice === '4' && hasCursor) {
168
+ // Cursor CLI selected - model defaults to 'auto' (Cursor's default)
169
+ provider = 'cursor';
170
+ baseUrl = '';
171
+ const modelInput = await prompt(' Model (leave empty for auto): ');
172
+ const model = modelInput || '';
173
+ // Save config for Cursor
174
+ const cursorConfig = { provider: 'cursor' };
175
+ if (model)
176
+ cursorConfig.cursorModel = model;
177
+ await saveCLIConfig(cursorConfig);
178
+ console.log(' Config saved to ~/.gitnexus/config.json\n');
179
+ llmConfig = { ...llmConfig, provider: 'cursor', model, apiKey: '', baseUrl: '' };
154
180
  }
155
181
  else {
156
- baseUrl = 'https://api.openai.com/v1';
157
- defaultModel = 'gpt-4o-mini';
158
- }
159
- // Model
160
- const modelInput = await prompt(` Model (default: ${defaultModel}): `);
161
- const model = modelInput || defaultModel;
162
- // API key pre-fill hint if env var exists
163
- const envKey = process.env.GITNEXUS_API_KEY || process.env.OPENAI_API_KEY || '';
164
- let key;
165
- if (envKey) {
166
- const masked = envKey.slice(0, 6) + '...' + envKey.slice(-4);
167
- const useEnv = await prompt(` Use existing env key (${masked})? (Y/n): `);
168
- if (!useEnv || useEnv.toLowerCase() === 'y' || useEnv.toLowerCase() === 'yes') {
169
- key = envKey;
182
+ // OpenAI-compatible provider
183
+ if (choice === '2') {
184
+ baseUrl = 'https://openrouter.ai/api/v1';
185
+ defaultModel = 'minimax/minimax-m2.5';
186
+ }
187
+ else if (choice === '3') {
188
+ baseUrl = await prompt(' Base URL (e.g. http://localhost:11434/v1): ');
189
+ if (!baseUrl) {
190
+ console.log('\n No URL provided. Aborting.\n');
191
+ process.exitCode = 1;
192
+ return;
193
+ }
194
+ defaultModel = 'gpt-4o-mini';
195
+ }
196
+ else {
197
+ baseUrl = 'https://api.openai.com/v1';
198
+ defaultModel = 'gpt-4o-mini';
199
+ }
200
+ // Model
201
+ const modelInput = await prompt(` Model (default: ${defaultModel}): `);
202
+ const model = modelInput || defaultModel;
203
+ // API key — pre-fill hint if env var exists
204
+ const envKey = process.env.GITNEXUS_API_KEY || process.env.OPENAI_API_KEY || '';
205
+ if (envKey) {
206
+ const masked = envKey.slice(0, 6) + '...' + envKey.slice(-4);
207
+ const useEnv = await prompt(` Use existing env key (${masked})? (Y/n): `);
208
+ if (!useEnv || useEnv.toLowerCase() === 'y' || useEnv.toLowerCase() === 'yes') {
209
+ key = envKey;
210
+ }
211
+ else {
212
+ key = await prompt(' API key: ', true);
213
+ }
170
214
  }
171
215
  else {
172
216
  key = await prompt(' API key: ', true);
173
217
  }
218
+ if (!key) {
219
+ console.log('\n No key provided. Aborting.\n');
220
+ process.exitCode = 1;
221
+ return;
222
+ }
223
+ // Save
224
+ await saveCLIConfig({ apiKey: key, baseUrl, model, provider: 'openai' });
225
+ console.log(' Config saved to ~/.gitnexus/config.json\n');
226
+ llmConfig = { ...llmConfig, apiKey: key, baseUrl, model, provider: 'openai' };
174
227
  }
175
- else {
176
- key = await prompt(' API key: ', true);
177
- }
178
- if (!key) {
179
- console.log('\n No key provided. Aborting.\n');
180
- process.exitCode = 1;
181
- return;
182
- }
183
- // Save
184
- await saveCLIConfig({ apiKey: key, baseUrl, model });
185
- console.log(' Config saved to ~/.gitnexus/config.json\n');
186
- llmConfig = { ...llmConfig, apiKey: key, baseUrl, model };
187
228
  }
188
229
  }
189
230
  // ── Setup progress bar with elapsed timer ──────────────────────────
@@ -213,9 +254,8 @@ export const wikiCommand = async (inputPath, options) => {
213
254
  // ── Run generator ───────────────────────────────────────────────────
214
255
  const wikiOptions = {
215
256
  force: options?.force,
216
- model: options?.model,
217
- baseUrl: options?.baseUrl,
218
257
  concurrency: options?.concurrency ? parseInt(options.concurrency, 10) : undefined,
258
+ reviewOnly: options?.review,
219
259
  };
220
260
  const generator = new WikiGenerator(repoPath, storagePath, lbugPath, llmConfig, wikiOptions, (phase, percent, detail) => {
221
261
  const label = detail || phase;
@@ -228,11 +268,92 @@ export const wikiCommand = async (inputPath, options) => {
228
268
  try {
229
269
  const result = await generator.run();
230
270
  clearInterval(elapsedTimer);
231
- bar.update(100, { phase: 'Done' });
232
271
  bar.stop();
233
272
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
234
273
  const wikiDir = path.join(storagePath, 'wiki');
235
274
  const viewerPath = path.join(wikiDir, 'index.html');
275
+ const treeFile = path.join(wikiDir, 'module_tree.json');
276
+ // Review mode: show module tree and ask for confirmation
277
+ if (options?.review && result.moduleTree) {
278
+ console.log(`\n Module structure ready for review (${elapsed}s)\n`);
279
+ console.log(' Modules to generate:\n');
280
+ const printTree = (nodes, indent = 0) => {
281
+ for (const node of nodes) {
282
+ const prefix = ' '.repeat(indent + 2);
283
+ const fileCount = node.files?.length || 0;
284
+ const childCount = node.children?.length || 0;
285
+ const suffix = fileCount > 0 ? ` (${fileCount} files)` : childCount > 0 ? ` (${childCount} children)` : '';
286
+ console.log(`${prefix}- ${node.name}${suffix}`);
287
+ if (node.children && node.children.length > 0) {
288
+ printTree(node.children, indent + 1);
289
+ }
290
+ }
291
+ };
292
+ printTree(result.moduleTree);
293
+ console.log(`\n Tree saved to: ${treeFile}`);
294
+ console.log(' You can edit this file to remove/rename modules.\n');
295
+ // Ask for confirmation (auto-continue in non-interactive environments)
296
+ if (!process.stdin.isTTY) {
297
+ console.log(' Non-interactive mode — auto-continuing with generation.\n');
298
+ }
299
+ const answer = process.stdin.isTTY
300
+ ? await prompt(' Continue with generation? (Y/n/edit): ')
301
+ : 'y';
302
+ const choice = answer.trim().toLowerCase();
303
+ if (choice === 'n' || choice === 'no') {
304
+ console.log('\n Generation cancelled. Run `gitnexus wiki` later to generate.\n');
305
+ return;
306
+ }
307
+ if (choice === 'edit' || choice === 'e') {
308
+ // Open editor for the user
309
+ const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
310
+ console.log(`\n Opening ${treeFile} in ${editor}...`);
311
+ console.log(' Save and close the editor when done.\n');
312
+ try {
313
+ execSync(`${editor} "${treeFile}"`, { stdio: 'inherit' });
314
+ }
315
+ catch {
316
+ console.log(` Could not open editor. Please edit manually:\n ${treeFile}\n`);
317
+ console.log(' Then run `gitnexus wiki` to continue.\n');
318
+ return;
319
+ }
320
+ }
321
+ // Continue with generation using the (possibly edited) tree
322
+ console.log('\n Continuing with wiki generation...\n');
323
+ bar.start(100, 30, { phase: 'Generating pages...' });
324
+ // Re-run generator without reviewOnly flag
325
+ const continueOptions = {
326
+ ...wikiOptions,
327
+ reviewOnly: false,
328
+ };
329
+ const continueGenerator = new WikiGenerator(repoPath, storagePath, lbugPath, llmConfig, continueOptions, (phase, percent, detail) => {
330
+ const label = detail || phase;
331
+ if (label !== lastPhase) {
332
+ lastPhase = label;
333
+ phaseStart = Date.now();
334
+ }
335
+ bar.update(percent, { phase: label });
336
+ });
337
+ const continueResult = await continueGenerator.run();
338
+ bar.update(100, { phase: 'Done' });
339
+ bar.stop();
340
+ const totalElapsed = ((Date.now() - t0) / 1000).toFixed(1);
341
+ console.log(`\n Wiki generated successfully (${totalElapsed}s)\n`);
342
+ console.log(` Mode: ${continueResult.mode}`);
343
+ console.log(` Pages: ${continueResult.pagesGenerated}`);
344
+ console.log(` Output: ${wikiDir}`);
345
+ console.log(` Viewer: ${viewerPath}`);
346
+ if (continueResult.failedModules && continueResult.failedModules.length > 0) {
347
+ console.log(`\n Failed modules (${continueResult.failedModules.length}):`);
348
+ for (const mod of continueResult.failedModules) {
349
+ console.log(` - ${mod}`);
350
+ }
351
+ }
352
+ console.log('');
353
+ await maybePublishGist(viewerPath, options?.gist);
354
+ return;
355
+ }
356
+ bar.update(100, { phase: 'Done' });
236
357
  if (result.mode === 'up-to-date' && !options?.force) {
237
358
  console.log('\n Wiki is already up to date.');
238
359
  console.log(` Viewer: ${viewerPath}\n`);
@@ -280,7 +401,7 @@ export const wikiCommand = async (inputPath, options) => {
280
401
  }
281
402
  else {
282
403
  console.log(`\n Error: ${err.message}\n`);
283
- if (process.env.DEBUG) {
404
+ if (process.env.GITNEXUS_VERBOSE) {
284
405
  console.error(err);
285
406
  }
286
407
  }
@@ -1,3 +1,32 @@
1
+ /**
2
+ * HOW TO ADD A NEW LANGUAGE:
3
+ *
4
+ * 1. Add the enum member below (e.g., Scala = 'scala')
5
+ * 2. Run `tsc --noEmit` — compiler errors guide you to every dispatch table
6
+ * 3. Use this checklist for each file:
7
+ *
8
+ * FILE | WHAT TO ADD | DEFAULT (simple languages)
9
+ * ----------------------------------|------------------------------------------|---------------------------
10
+ * tree-sitter-queries.ts | Query string + LANGUAGE_QUERIES entry | (required)
11
+ * export-detection.ts | ExportChecker function + table entry | (required)
12
+ * import-resolvers/<lang>.ts | Exported resolve<Lang>Import function | resolveStandard(...)
13
+ * call-routing.ts | CallRouter function (or noRouting) | noRouting
14
+ * entry-point-scoring.ts | ENTRY_POINT_PATTERNS entry | []
15
+ * framework-detection.ts | AST_FRAMEWORK_PATTERNS entry | []
16
+ * type-extractors/<lang>.ts | New file + index.ts import | (required)
17
+ * resolvers/<lang>.ts | Resolver file (if non-standard) | (only if resolveStandard insufficient)
18
+ * named-bindings/<lang>.ts | Extractor (if named imports) | (only if language has named imports)
19
+ *
20
+ * 4. Also check these files for language-specific if-checks (no compile-time guard):
21
+ * - mro-processor.ts (MRO strategy selection)
22
+ * - heritage-processor.ts (extends/implements handling)
23
+ * - parse-worker.ts (AST edge cases)
24
+ * - parsing-processor.ts (node label normalization)
25
+ *
26
+ * 5. Add tree-sitter-<lang> to package.json dependencies
27
+ * 6. Add file extension mapping in utils.ts getLanguageFromFilename()
28
+ * 7. Run full test suite
29
+ */
1
30
  export declare enum SupportedLanguages {
2
31
  JavaScript = "javascript",
3
32
  TypeScript = "typescript",
@@ -11,5 +40,8 @@ export declare enum SupportedLanguages {
11
40
  Rust = "rust",
12
41
  PHP = "php",
13
42
  Kotlin = "kotlin",
14
- Swift = "swift"
43
+ Swift = "swift",
44
+ Dart = "dart",
45
+ /** Standalone regex processor — no tree-sitter, no LanguageProvider. */
46
+ Cobol = "cobol"
15
47
  }
@@ -1,3 +1,32 @@
1
+ /**
2
+ * HOW TO ADD A NEW LANGUAGE:
3
+ *
4
+ * 1. Add the enum member below (e.g., Scala = 'scala')
5
+ * 2. Run `tsc --noEmit` — compiler errors guide you to every dispatch table
6
+ * 3. Use this checklist for each file:
7
+ *
8
+ * FILE | WHAT TO ADD | DEFAULT (simple languages)
9
+ * ----------------------------------|------------------------------------------|---------------------------
10
+ * tree-sitter-queries.ts | Query string + LANGUAGE_QUERIES entry | (required)
11
+ * export-detection.ts | ExportChecker function + table entry | (required)
12
+ * import-resolvers/<lang>.ts | Exported resolve<Lang>Import function | resolveStandard(...)
13
+ * call-routing.ts | CallRouter function (or noRouting) | noRouting
14
+ * entry-point-scoring.ts | ENTRY_POINT_PATTERNS entry | []
15
+ * framework-detection.ts | AST_FRAMEWORK_PATTERNS entry | []
16
+ * type-extractors/<lang>.ts | New file + index.ts import | (required)
17
+ * resolvers/<lang>.ts | Resolver file (if non-standard) | (only if resolveStandard insufficient)
18
+ * named-bindings/<lang>.ts | Extractor (if named imports) | (only if language has named imports)
19
+ *
20
+ * 4. Also check these files for language-specific if-checks (no compile-time guard):
21
+ * - mro-processor.ts (MRO strategy selection)
22
+ * - heritage-processor.ts (extends/implements handling)
23
+ * - parse-worker.ts (AST edge cases)
24
+ * - parsing-processor.ts (node label normalization)
25
+ *
26
+ * 5. Add tree-sitter-<lang> to package.json dependencies
27
+ * 6. Add file extension mapping in utils.ts getLanguageFromFilename()
28
+ * 7. Run full test suite
29
+ */
1
30
  export var SupportedLanguages;
2
31
  (function (SupportedLanguages) {
3
32
  SupportedLanguages["JavaScript"] = "javascript";
@@ -13,4 +42,7 @@ export var SupportedLanguages;
13
42
  SupportedLanguages["PHP"] = "php";
14
43
  SupportedLanguages["Kotlin"] = "kotlin";
15
44
  SupportedLanguages["Swift"] = "swift";
45
+ SupportedLanguages["Dart"] = "dart";
46
+ /** Standalone regex processor — no tree-sitter, no LanguageProvider. */
47
+ SupportedLanguages["Cobol"] = "cobol";
16
48
  })(SupportedLanguages || (SupportedLanguages = {}));
@@ -30,6 +30,11 @@ export declare const initEmbedder: (onProgress?: ModelProgressCallback, config?:
30
30
  * Check if the embedder is initialized and ready
31
31
  */
32
32
  export declare const isEmbedderReady: () => boolean;
33
+ /**
34
+ * Get the effective embedding dimensions.
35
+ * In HTTP mode, uses GITNEXUS_EMBEDDING_DIMS if set, otherwise the default.
36
+ */
37
+ export declare const getEmbeddingDimensions: () => number;
33
38
  /**
34
39
  * Get the embedder instance (throws if not initialized)
35
40
  */
@@ -38,7 +43,7 @@ export declare const getEmbedder: () => FeatureExtractionPipeline;
38
43
  * Embed a single text string
39
44
  *
40
45
  * @param text - Text to embed
41
- * @returns Float32Array of embedding vector (384 dimensions)
46
+ * @returns Float32Array of embedding vector
42
47
  */
43
48
  export declare const embedText: (text: string) => Promise<Float32Array>;
44
49
  /**
@@ -15,17 +15,53 @@ if (!process.env.ORT_LOG_LEVEL) {
15
15
  import { pipeline, env } from '@huggingface/transformers';
16
16
  import { existsSync } from 'fs';
17
17
  import { execFileSync } from 'child_process';
18
- import { join } from 'path';
18
+ import { join, dirname } from 'path';
19
+ import { createRequire } from 'module';
19
20
  import { DEFAULT_EMBEDDING_CONFIG } from './types.js';
21
+ import { isHttpMode, getHttpDimensions, httpEmbed } from './http-client.js';
22
+ /**
23
+ * Check whether the onnxruntime-node package that @huggingface/transformers
24
+ * will actually load at runtime ships the CUDA execution provider.
25
+ *
26
+ * Critical: we resolve from transformers' own module scope, NOT from ours.
27
+ * npm may install two copies — a top-level 1.24.x (our dep) and a nested
28
+ * 1.21.0 (transformers' pinned dep). The guard must inspect whichever copy
29
+ * transformers.js will dlopen, otherwise the check is meaningless.
30
+ */
31
+ function hasOrtCudaProvider() {
32
+ try {
33
+ const require = createRequire(import.meta.url);
34
+ // Resolve from @huggingface/transformers' scope so we find the same
35
+ // onnxruntime-node binary that transformers.js will use at runtime
36
+ const transformersDir = dirname(require.resolve('@huggingface/transformers/package.json'));
37
+ const ortRequire = createRequire(join(transformersDir, 'package.json'));
38
+ const ortPath = dirname(ortRequire.resolve('onnxruntime-node/package.json'));
39
+ // ORT 1.24.x only ships CUDA binaries for linux/x64 (downloaded from NuGet
40
+ // at postinstall). arm64 will correctly return false here until ORT adds support.
41
+ const arch = process.arch;
42
+ return existsSync(join(ortPath, 'bin', 'napi-v6', 'linux', arch, 'libonnxruntime_providers_cuda.so'));
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
20
48
  /**
21
49
  * Check whether CUDA libraries are actually available on this system.
22
50
  * ONNX Runtime's native layer crashes (uncatchable) if we attempt CUDA
23
51
  * without the required shared libraries, so we probe first.
24
52
  *
25
- * Checks the dynamic linker cache (ldconfig) which covers all architectures
26
- * and install paths, then falls back to CUDA_PATH / LD_LIBRARY_PATH env vars.
53
+ * Checks both:
54
+ * 1. That system CUDA libraries (libcublasLt) are present
55
+ * 2. That onnxruntime-node ships the CUDA execution provider binary
56
+ *
57
+ * Both conditions must be true — system CUDA libs alone are not enough
58
+ * if onnxruntime-node is a CPU-only build (versions < 1.24.0).
27
59
  */
28
60
  function isCudaAvailable() {
61
+ // First, verify onnxruntime-node has the CUDA provider binary.
62
+ // Without this, requesting CUDA causes an uncatchable native crash.
63
+ if (!hasOrtCudaProvider())
64
+ return false;
29
65
  // Primary: query the dynamic linker cache — covers all architectures,
30
66
  // distro layouts, and custom install paths registered with ldconfig
31
67
  try {
@@ -70,6 +106,10 @@ export const getCurrentDevice = () => currentDevice;
70
106
  * @returns Promise resolving to the embedder pipeline
71
107
  */
72
108
  export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
109
+ if (isHttpMode()) {
110
+ throw new Error('initEmbedder() should not be called in HTTP mode. ' +
111
+ 'Use embedText()/embedBatch() which handle HTTP transparently.');
112
+ }
73
113
  // Return existing instance if available
74
114
  if (embedderInstance) {
75
115
  return embedderInstance;
@@ -169,12 +209,25 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
169
209
  * Check if the embedder is initialized and ready
170
210
  */
171
211
  export const isEmbedderReady = () => {
172
- return embedderInstance !== null;
212
+ return isHttpMode() || embedderInstance !== null;
213
+ };
214
+ /**
215
+ * Get the effective embedding dimensions.
216
+ * In HTTP mode, uses GITNEXUS_EMBEDDING_DIMS if set, otherwise the default.
217
+ */
218
+ export const getEmbeddingDimensions = () => {
219
+ if (isHttpMode()) {
220
+ return getHttpDimensions() ?? DEFAULT_EMBEDDING_CONFIG.dimensions;
221
+ }
222
+ return DEFAULT_EMBEDDING_CONFIG.dimensions;
173
223
  };
174
224
  /**
175
225
  * Get the embedder instance (throws if not initialized)
176
226
  */
177
227
  export const getEmbedder = () => {
228
+ if (isHttpMode()) {
229
+ throw new Error('getEmbedder() is not available in HTTP embedding mode. Use embedText()/embedBatch() instead.');
230
+ }
178
231
  if (!embedderInstance) {
179
232
  throw new Error('Embedder not initialized. Call initEmbedder() first.');
180
233
  }
@@ -184,9 +237,13 @@ export const getEmbedder = () => {
184
237
  * Embed a single text string
185
238
  *
186
239
  * @param text - Text to embed
187
- * @returns Float32Array of embedding vector (384 dimensions)
240
+ * @returns Float32Array of embedding vector
188
241
  */
189
242
  export const embedText = async (text) => {
243
+ if (isHttpMode()) {
244
+ const [vec] = await httpEmbed([text]);
245
+ return vec;
246
+ }
190
247
  const embedder = getEmbedder();
191
248
  const result = await embedder(text, {
192
249
  pooling: 'mean',
@@ -206,6 +263,9 @@ export const embedBatch = async (texts) => {
206
263
  if (texts.length === 0) {
207
264
  return [];
208
265
  }
266
+ if (isHttpMode()) {
267
+ return httpEmbed(texts);
268
+ }
209
269
  const embedder = getEmbedder();
210
270
  // Process batch
211
271
  const result = await embedder(texts, {
@@ -121,14 +121,16 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
121
121
  percent: 0,
122
122
  modelDownloadPercent: 0,
123
123
  });
124
- await initEmbedder((modelProgress) => {
125
- const downloadPercent = modelProgress.progress ?? 0;
126
- onProgress({
127
- phase: 'loading-model',
128
- percent: Math.round(downloadPercent * 0.2),
129
- modelDownloadPercent: downloadPercent,
130
- });
131
- }, finalConfig);
124
+ if (!isEmbedderReady()) {
125
+ await initEmbedder((modelProgress) => {
126
+ const downloadPercent = modelProgress.progress ?? 0;
127
+ onProgress({
128
+ phase: 'loading-model',
129
+ percent: Math.round(downloadPercent * 0.2),
130
+ modelDownloadPercent: downloadPercent,
131
+ });
132
+ }, finalConfig);
133
+ }
132
134
  onProgress({
133
135
  phase: 'loading-model',
134
136
  percent: 20,
@@ -255,7 +257,7 @@ export const semanticSearch = async (executeQuery, query, k = 10, maxDistance =
255
257
  // Query the vector index on CodeEmbedding to get nodeIds and distances
256
258
  const vectorQuery = `
257
259
  CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx',
258
- CAST(${queryVecStr} AS FLOAT[384]), ${k})
260
+ CAST(${queryVecStr} AS FLOAT[${queryVec.length}]), ${k})
259
261
  YIELD node AS emb, distance
260
262
  WITH emb, distance
261
263
  WHERE distance < ${maxDistance}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * HTTP Embedding Client
3
+ *
4
+ * Shared fetch+retry logic for OpenAI-compatible /v1/embeddings endpoints.
5
+ * Imported by both the core embedder (batch) and MCP embedder (query).
6
+ */
7
+ /**
8
+ * Check whether HTTP embedding mode is active (env vars are set).
9
+ */
10
+ export declare const isHttpMode: () => boolean;
11
+ /**
12
+ * Return the configured embedding dimensions for HTTP mode, or undefined
13
+ * if HTTP mode is not active or no explicit dimensions are set.
14
+ */
15
+ export declare const getHttpDimensions: () => number | undefined;
16
+ /**
17
+ * Embed texts via the HTTP backend, splitting into batches.
18
+ * Reads config from env vars on every call.
19
+ *
20
+ * @param texts - Array of texts to embed
21
+ * @returns Array of Float32Array embedding vectors
22
+ */
23
+ export declare const httpEmbed: (texts: string[]) => Promise<Float32Array[]>;
24
+ /**
25
+ * Embed a single query text via the HTTP backend.
26
+ * Convenience for MCP search where only one vector is needed.
27
+ *
28
+ * @param text - Query text to embed
29
+ * @returns Embedding vector as number array
30
+ */
31
+ export declare const httpEmbedQuery: (text: string) => Promise<number[]>;