gitnexus 1.4.8 → 1.4.10

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 (211) hide show
  1. package/README.md +7 -0
  2. package/dist/cli/index-repo.d.ts +15 -0
  3. package/dist/cli/index-repo.js +115 -0
  4. package/dist/cli/index.js +11 -2
  5. package/dist/cli/setup.js +12 -9
  6. package/dist/cli/wiki.d.ts +4 -0
  7. package/dist/cli/wiki.js +174 -53
  8. package/dist/config/supported-languages.d.ts +7 -5
  9. package/dist/config/supported-languages.js +6 -4
  10. package/dist/core/graph/graph.js +9 -1
  11. package/dist/core/graph/types.d.ts +10 -2
  12. package/dist/core/ingestion/call-processor.d.ts +18 -1
  13. package/dist/core/ingestion/call-processor.js +297 -38
  14. package/dist/core/ingestion/call-routing.d.ts +3 -18
  15. package/dist/core/ingestion/call-routing.js +0 -19
  16. package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
  17. package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
  18. package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
  19. package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
  20. package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
  21. package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
  22. package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
  23. package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
  24. package/dist/core/ingestion/cobol-processor.d.ts +54 -0
  25. package/dist/core/ingestion/cobol-processor.js +1186 -0
  26. package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
  27. package/dist/core/ingestion/entry-point-scoring.js +18 -4
  28. package/dist/core/ingestion/export-detection.d.ts +47 -8
  29. package/dist/core/ingestion/export-detection.js +29 -50
  30. package/dist/core/ingestion/field-extractor.d.ts +29 -0
  31. package/dist/core/ingestion/field-extractor.js +25 -0
  32. package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
  33. package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
  34. package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
  35. package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
  36. package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
  37. package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
  38. package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
  39. package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
  40. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
  41. package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
  42. package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
  43. package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
  44. package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
  45. package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
  46. package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
  47. package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
  48. package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
  49. package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
  50. package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
  51. package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
  52. package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
  53. package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
  54. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
  55. package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
  56. package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
  57. package/dist/core/ingestion/field-extractors/generic.js +111 -0
  58. package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
  59. package/dist/core/ingestion/field-extractors/typescript.js +291 -0
  60. package/dist/core/ingestion/field-types.d.ts +59 -0
  61. package/dist/core/ingestion/field-types.js +2 -0
  62. package/dist/core/ingestion/framework-detection.d.ts +87 -0
  63. package/dist/core/ingestion/framework-detection.js +65 -2
  64. package/dist/core/ingestion/heritage-processor.js +15 -17
  65. package/dist/core/ingestion/import-processor.d.ts +9 -10
  66. package/dist/core/ingestion/import-processor.js +59 -14
  67. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
  68. package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
  69. package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
  70. package/dist/core/ingestion/import-resolvers/dart.js +44 -0
  71. package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
  72. package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
  73. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +9 -1
  74. package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.js +56 -0
  75. package/dist/core/ingestion/{resolvers → import-resolvers}/php.d.ts +6 -10
  76. package/dist/core/ingestion/{resolvers → import-resolvers}/php.js +7 -2
  77. package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
  78. package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
  79. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
  80. package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
  81. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
  82. package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
  83. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
  84. package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
  85. package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
  86. package/dist/core/ingestion/import-resolvers/swift.js +23 -0
  87. package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
  88. package/dist/core/ingestion/import-resolvers/types.js +6 -0
  89. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +0 -3
  90. package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +0 -9
  91. package/dist/core/ingestion/language-config.d.ts +4 -1
  92. package/dist/core/ingestion/language-provider.d.ts +121 -0
  93. package/dist/core/ingestion/language-provider.js +24 -0
  94. package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
  95. package/dist/core/ingestion/languages/c-cpp.js +71 -0
  96. package/dist/core/ingestion/languages/cobol.d.ts +1 -0
  97. package/dist/core/ingestion/languages/cobol.js +26 -0
  98. package/dist/core/ingestion/languages/csharp.d.ts +8 -0
  99. package/dist/core/ingestion/languages/csharp.js +49 -0
  100. package/dist/core/ingestion/languages/dart.d.ts +12 -0
  101. package/dist/core/ingestion/languages/dart.js +58 -0
  102. package/dist/core/ingestion/languages/go.d.ts +11 -0
  103. package/dist/core/ingestion/languages/go.js +28 -0
  104. package/dist/core/ingestion/languages/index.d.ts +38 -0
  105. package/dist/core/ingestion/languages/index.js +63 -0
  106. package/dist/core/ingestion/languages/java.d.ts +9 -0
  107. package/dist/core/ingestion/languages/java.js +29 -0
  108. package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
  109. package/dist/core/ingestion/languages/kotlin.js +53 -0
  110. package/dist/core/ingestion/languages/php.d.ts +8 -0
  111. package/dist/core/ingestion/languages/php.js +145 -0
  112. package/dist/core/ingestion/languages/python.d.ts +12 -0
  113. package/dist/core/ingestion/languages/python.js +39 -0
  114. package/dist/core/ingestion/languages/ruby.d.ts +9 -0
  115. package/dist/core/ingestion/languages/ruby.js +44 -0
  116. package/dist/core/ingestion/languages/rust.d.ts +12 -0
  117. package/dist/core/ingestion/languages/rust.js +44 -0
  118. package/dist/core/ingestion/languages/swift.d.ts +12 -0
  119. package/dist/core/ingestion/languages/swift.js +133 -0
  120. package/dist/core/ingestion/languages/typescript.d.ts +10 -0
  121. package/dist/core/ingestion/languages/typescript.js +60 -0
  122. package/dist/core/ingestion/mro-processor.js +14 -15
  123. package/dist/core/ingestion/{named-binding-extraction.d.ts → named-binding-processor.d.ts} +0 -9
  124. package/dist/core/ingestion/named-binding-processor.js +42 -0
  125. package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
  126. package/dist/core/ingestion/named-bindings/csharp.js +37 -0
  127. package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
  128. package/dist/core/ingestion/named-bindings/java.js +29 -0
  129. package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
  130. package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
  131. package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
  132. package/dist/core/ingestion/named-bindings/php.js +61 -0
  133. package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
  134. package/dist/core/ingestion/named-bindings/python.js +49 -0
  135. package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
  136. package/dist/core/ingestion/named-bindings/rust.js +64 -0
  137. package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
  138. package/dist/core/ingestion/named-bindings/types.js +6 -0
  139. package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
  140. package/dist/core/ingestion/named-bindings/typescript.js +58 -0
  141. package/dist/core/ingestion/parsing-processor.d.ts +5 -1
  142. package/dist/core/ingestion/parsing-processor.js +115 -16
  143. package/dist/core/ingestion/pipeline.js +925 -424
  144. package/dist/core/ingestion/resolution-context.js +1 -1
  145. package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
  146. package/dist/core/ingestion/route-extractors/expo.js +36 -0
  147. package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
  148. package/dist/core/ingestion/route-extractors/middleware.js +143 -0
  149. package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
  150. package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
  151. package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
  152. package/dist/core/ingestion/route-extractors/php.js +21 -0
  153. package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
  154. package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
  155. package/dist/core/ingestion/tree-sitter-queries.d.ts +8 -7
  156. package/dist/core/ingestion/tree-sitter-queries.js +231 -9
  157. package/dist/core/ingestion/type-env.d.ts +14 -17
  158. package/dist/core/ingestion/type-env.js +66 -14
  159. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +1 -1
  160. package/dist/core/ingestion/type-extractors/csharp.js +1 -1
  161. package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
  162. package/dist/core/ingestion/type-extractors/dart.js +371 -0
  163. package/dist/core/ingestion/type-extractors/jvm.js +1 -1
  164. package/dist/core/ingestion/type-extractors/shared.d.ts +1 -13
  165. package/dist/core/ingestion/type-extractors/shared.js +9 -102
  166. package/dist/core/ingestion/type-extractors/swift.js +334 -4
  167. package/dist/core/ingestion/type-extractors/types.d.ts +3 -1
  168. package/dist/core/ingestion/{ast-helpers.d.ts → utils/ast-helpers.d.ts} +16 -13
  169. package/dist/core/ingestion/{ast-helpers.js → utils/ast-helpers.js} +111 -32
  170. package/dist/core/ingestion/{call-analysis.js → utils/call-analysis.js} +37 -0
  171. package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
  172. package/dist/core/ingestion/utils/event-loop.js +5 -0
  173. package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
  174. package/dist/core/ingestion/utils/language-detection.js +70 -0
  175. package/dist/core/ingestion/utils/verbose.d.ts +1 -0
  176. package/dist/core/ingestion/utils/verbose.js +7 -0
  177. package/dist/core/ingestion/workers/parse-worker.d.ts +43 -2
  178. package/dist/core/ingestion/workers/parse-worker.js +361 -150
  179. package/dist/core/lbug/csv-generator.js +34 -1
  180. package/dist/core/lbug/lbug-adapter.js +6 -0
  181. package/dist/core/lbug/schema.d.ts +5 -3
  182. package/dist/core/lbug/schema.js +39 -2
  183. package/dist/core/tree-sitter/parser-loader.js +7 -1
  184. package/dist/core/wiki/cursor-client.d.ts +31 -0
  185. package/dist/core/wiki/cursor-client.js +127 -0
  186. package/dist/core/wiki/generator.d.ts +28 -9
  187. package/dist/core/wiki/generator.js +115 -18
  188. package/dist/core/wiki/graph-queries.d.ts +4 -0
  189. package/dist/core/wiki/graph-queries.js +7 -1
  190. package/dist/core/wiki/llm-client.d.ts +2 -0
  191. package/dist/core/wiki/llm-client.js +8 -4
  192. package/dist/core/wiki/prompts.d.ts +3 -3
  193. package/dist/core/wiki/prompts.js +6 -0
  194. package/dist/mcp/core/lbug-adapter.d.ts +5 -0
  195. package/dist/mcp/core/lbug-adapter.js +11 -1
  196. package/dist/mcp/local/local-backend.d.ts +16 -5
  197. package/dist/mcp/local/local-backend.js +711 -74
  198. package/dist/mcp/tools.js +71 -2
  199. package/dist/storage/repo-manager.d.ts +3 -0
  200. package/package.json +14 -14
  201. package/dist/core/ingestion/import-resolution.d.ts +0 -101
  202. package/dist/core/ingestion/import-resolution.js +0 -251
  203. package/dist/core/ingestion/named-binding-extraction.js +0 -373
  204. package/dist/core/ingestion/resolvers/index.d.ts +0 -18
  205. package/dist/core/ingestion/resolvers/index.js +0 -13
  206. package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
  207. package/dist/core/ingestion/type-extractors/index.js +0 -31
  208. package/dist/core/ingestion/utils.d.ts +0 -20
  209. package/dist/core/ingestion/utils.js +0 -242
  210. package/scripts/patch-tree-sitter-swift.cjs +0 -74
  211. /package/dist/core/ingestion/{call-analysis.d.ts → utils/call-analysis.d.ts} +0 -0
@@ -12,9 +12,10 @@
12
12
  import fs from 'fs/promises';
13
13
  import path from 'path';
14
14
  import { execSync, execFileSync } from 'child_process';
15
- import { initWikiDb, closeWikiDb, getFilesWithExports, getAllFiles, getIntraModuleCallEdges, getInterModuleCallEdges, getProcessesForFiles, getAllProcesses, getInterModuleEdgesForOverview, } from './graph-queries.js';
15
+ import { initWikiDb, closeWikiDb, touchWikiDb, getFilesWithExports, getAllFiles, getIntraModuleCallEdges, getInterModuleCallEdges, getProcessesForFiles, getAllProcesses, getInterModuleEdgesForOverview, } from './graph-queries.js';
16
16
  import { generateHTMLViewer } from './html-viewer.js';
17
17
  import { callLLM, estimateTokens, } from './llm-client.js';
18
+ import { callCursorLLM, resolveCursorConfig, } from './cursor-client.js';
18
19
  import { GROUPING_SYSTEM_PROMPT, GROUPING_USER_PROMPT, MODULE_SYSTEM_PROMPT, MODULE_USER_PROMPT, PARENT_SYSTEM_PROMPT, PARENT_USER_PROMPT, OVERVIEW_SYSTEM_PROMPT, OVERVIEW_USER_PROMPT, fillTemplate, formatFileListForGrouping, formatDirectoryTree, formatCallEdges, formatProcesses, } from './prompts.js';
19
20
  import { shouldIgnorePath } from '../../config/ignore-service.js';
20
21
  // ─── Constants ────────────────────────────────────────────────────────
@@ -51,17 +52,55 @@ export class WikiGenerator {
51
52
  lastPercent = 0;
52
53
  /**
53
54
  * Create streaming options that report LLM progress to the progress bar.
54
- * Uses the last known percent so streaming doesn't reset the bar backwards.
55
+ *
56
+ * Progress calculation:
57
+ * - If fixedPercent is provided, we show incremental progress within that phase
58
+ * based on token generation (e.g., grouping at 15% → 15-28%)
59
+ * - If fixedPercent is NOT provided, we only update the label with token count
60
+ * but keep the current percentage (avoids fluctuation during module generation)
61
+ *
62
+ * Also touches the DB connection periodically to prevent idle timeout.
55
63
  */
56
- streamOpts(label, fixedPercent) {
64
+ streamOpts(label, fixedPercent, percentRange = 10) {
65
+ const hasFixedStart = fixedPercent !== undefined;
66
+ const startPercent = fixedPercent ?? this.lastPercent;
67
+ const expectedTokens = 2000;
68
+ let lastTouch = Date.now();
57
69
  return {
58
70
  onChunk: (chars) => {
59
71
  const tokens = Math.round(chars / 4);
60
- const pct = fixedPercent ?? this.lastPercent;
61
- this.onProgress('stream', pct, `${label} (${tokens} tok)`);
72
+ if (hasFixedStart) {
73
+ // For fixed phases (like grouping), show incremental progress
74
+ const progress = Math.min(1, tokens / expectedTokens);
75
+ const pct = Math.round(startPercent + (progress * percentRange));
76
+ this.onProgress('stream', pct, `${label} (${tokens} tok)`);
77
+ }
78
+ else {
79
+ // For module generation, only update the label, keep current percent
80
+ this.onProgress('stream', this.lastPercent, `${label} (${tokens} tok)`);
81
+ }
82
+ // Touch DB every 60s to prevent idle timeout during long LLM calls
83
+ const now = Date.now();
84
+ if (now - lastTouch > 60_000) {
85
+ touchWikiDb();
86
+ lastTouch = now;
87
+ }
62
88
  },
63
89
  };
64
90
  }
91
+ /**
92
+ * Route LLM call to the appropriate provider (OpenAI-compatible or Cursor CLI).
93
+ */
94
+ async invokeLLM(prompt, systemPrompt, options) {
95
+ if (this.llmConfig.provider === 'cursor') {
96
+ const cursorConfig = resolveCursorConfig({
97
+ model: this.llmConfig.model,
98
+ workingDirectory: this.repoPath,
99
+ });
100
+ return callCursorLLM(prompt, cursorConfig, systemPrompt, options);
101
+ }
102
+ return callLLM(prompt, this.llmConfig, systemPrompt, options);
103
+ }
65
104
  /**
66
105
  * Main entry point. Runs the full pipeline or incremental update.
67
106
  */
@@ -144,6 +183,13 @@ export class WikiGenerator {
144
183
  // Phase 1: Build module tree
145
184
  const moduleTree = await this.buildModuleTree(enrichedFiles);
146
185
  pagesGenerated = 0;
186
+ // If reviewOnly mode, save tree and stop for user to review/edit
187
+ if (this.options.reviewOnly) {
188
+ await this.saveModuleTree(moduleTree);
189
+ this.onProgress('review', 30, 'Module tree ready for review');
190
+ const reviewResult = { pagesGenerated: 0, mode: 'full', failedModules: [], moduleTree };
191
+ return reviewResult;
192
+ }
147
193
  // Phase 2: Generate module pages (parallel with concurrency limit)
148
194
  const totalModules = this.countModules(moduleTree);
149
195
  let modulesProcessed = 0;
@@ -213,6 +259,19 @@ export class WikiGenerator {
213
259
  }
214
260
  // ─── Phase 1: Build Module Tree ────────────────────────────────────
215
261
  async buildModuleTree(files) {
262
+ // First, check for user-edited module_tree.json (from --review workflow)
263
+ const editablePath = path.join(this.wikiDir, 'module_tree.json');
264
+ try {
265
+ const edited = await fs.readFile(editablePath, 'utf-8');
266
+ const parsed = JSON.parse(edited);
267
+ if (Array.isArray(parsed) && parsed.length > 0) {
268
+ this.onProgress('grouping', 25, 'Using edited module tree');
269
+ return parsed;
270
+ }
271
+ }
272
+ catch {
273
+ // No edited tree, check for original snapshot
274
+ }
216
275
  // Check for existing immutable snapshot (resumability)
217
276
  const snapshotPath = path.join(this.wikiDir, 'first_module_tree.json');
218
277
  try {
@@ -233,7 +292,7 @@ export class WikiGenerator {
233
292
  FILE_LIST: fileList,
234
293
  DIRECTORY_TREE: dirTree,
235
294
  });
236
- const response = await callLLM(prompt, this.llmConfig, GROUPING_SYSTEM_PROMPT, this.streamOpts('Grouping files', 15));
295
+ const response = await this.invokeLLM(prompt, GROUPING_SYSTEM_PROMPT, this.streamOpts('Grouping files', 15, 13));
237
296
  const grouping = this.parseGroupingResponse(response.content, files);
238
297
  // Convert to tree nodes
239
298
  const tree = [];
@@ -243,8 +302,14 @@ export class WikiGenerator {
243
302
  // Token budget check — split if too large
244
303
  const totalTokens = await this.estimateModuleTokens(modulePaths);
245
304
  if (totalTokens > this.maxTokensPerModule && modulePaths.length > 3) {
246
- node.children = this.splitBySubdirectory(moduleName, modulePaths);
247
- node.files = []; // Parent doesn't own files directly when split
305
+ const children = this.splitBySubdirectory(moduleName, modulePaths);
306
+ // Only create hierarchy if we actually got multiple children
307
+ // If splitting results in 1 child, keep files flat (avoid redundant nesting)
308
+ if (children.length > 1) {
309
+ node.children = children;
310
+ node.files = []; // Parent doesn't own files directly when split
311
+ }
312
+ // If only 1 child, keep original flat structure (files stay in node.files)
248
313
  }
249
314
  tree.push(node);
250
315
  }
@@ -322,12 +387,13 @@ export class WikiGenerator {
322
387
  }
323
388
  /**
324
389
  * Split a large module into sub-modules by subdirectory.
390
+ * Uses the full subDir path for naming to avoid slug collisions
391
+ * (e.g., "synapse-screen/src" vs "synapse-core/src").
325
392
  */
326
393
  splitBySubdirectory(moduleName, files) {
327
394
  const subGroups = new Map();
328
395
  for (const fp of files) {
329
396
  const parts = fp.replace(/\\/g, '/').split('/');
330
- // Use the deepest common-ish directory
331
397
  const subDir = parts.length > 2 ? parts.slice(0, 2).join('/') : parts[0];
332
398
  let group = subGroups.get(subDir);
333
399
  if (!group) {
@@ -336,11 +402,17 @@ export class WikiGenerator {
336
402
  }
337
403
  group.push(fp);
338
404
  }
339
- return Array.from(subGroups.entries()).map(([subDir, subFiles]) => ({
340
- name: `${moduleName} ${path.basename(subDir)}`,
341
- slug: this.slugify(`${moduleName}-${path.basename(subDir)}`),
342
- files: subFiles,
343
- }));
405
+ // Check if basenames are unique; if not, use the full subDir path
406
+ const basenames = Array.from(subGroups.keys()).map(s => path.basename(s));
407
+ const hasCollisions = new Set(basenames).size < basenames.length;
408
+ return Array.from(subGroups.entries()).map(([subDir, subFiles]) => {
409
+ const label = hasCollisions ? subDir.replace(/\//g, '-') : path.basename(subDir);
410
+ return {
411
+ name: `${moduleName} — ${label}`,
412
+ slug: this.slugify(`${moduleName}-${label}`),
413
+ files: subFiles,
414
+ };
415
+ });
344
416
  }
345
417
  // ─── Phase 2: Generate Module Pages ─────────────────────────────────
346
418
  /**
@@ -370,7 +442,7 @@ export class WikiGenerator {
370
442
  INCOMING_CALLS: formatCallEdges(interCalls.incoming),
371
443
  PROCESSES: formatProcesses(processes),
372
444
  });
373
- const response = await callLLM(prompt, this.llmConfig, MODULE_SYSTEM_PROMPT, this.streamOpts(node.name));
445
+ const response = await this.invokeLLM(prompt, MODULE_SYSTEM_PROMPT, this.streamOpts(node.name));
374
446
  // Write page with front matter
375
447
  const pageContent = `# ${node.name}\n\n${response.content}`;
376
448
  await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
@@ -406,7 +478,7 @@ export class WikiGenerator {
406
478
  CROSS_MODULE_CALLS: formatCallEdges(crossCalls),
407
479
  CROSS_PROCESSES: formatProcesses(processes),
408
480
  });
409
- const response = await callLLM(prompt, this.llmConfig, PARENT_SYSTEM_PROMPT, this.streamOpts(node.name));
481
+ const response = await this.invokeLLM(prompt, PARENT_SYSTEM_PROMPT, this.streamOpts(node.name));
410
482
  const pageContent = `# ${node.name}\n\n${response.content}`;
411
483
  await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
412
484
  }
@@ -442,7 +514,7 @@ export class WikiGenerator {
442
514
  MODULE_EDGES: edgesText,
443
515
  TOP_PROCESSES: formatProcesses(topProcesses),
444
516
  });
445
- const response = await callLLM(prompt, this.llmConfig, OVERVIEW_SYSTEM_PROMPT, this.streamOpts('Generating overview', 88));
517
+ const response = await this.invokeLLM(prompt, OVERVIEW_SYSTEM_PROMPT, this.streamOpts('Generating overview', 88));
446
518
  const pageContent = `# ${path.basename(this.repoPath)} — Wiki\n\n${response.content}`;
447
519
  await fs.writeFile(path.join(this.wikiDir, 'overview.md'), pageContent, 'utf-8');
448
520
  }
@@ -451,6 +523,13 @@ export class WikiGenerator {
451
523
  this.onProgress('incremental', 5, 'Detecting changes...');
452
524
  // Get changed files since last generation
453
525
  const changedFiles = this.getChangedFiles(existingMeta.fromCommit, currentCommit);
526
+ // If null, commits are on divergent branches (e.g., wiki generated on feature branch,
527
+ // now running on main). Fall back to full generation.
528
+ if (changedFiles === null) {
529
+ this.onProgress('incremental', 10, 'Branch diverged, running full generation...');
530
+ const fullResult = await this.fullGeneration(currentCommit);
531
+ return { ...fullResult, mode: 'incremental' };
532
+ }
454
533
  if (changedFiles.length === 0) {
455
534
  // No file changes but commit differs (e.g. merge commit)
456
535
  await this.saveWikiMeta({
@@ -559,13 +638,31 @@ export class WikiGenerator {
559
638
  return '';
560
639
  }
561
640
  }
641
+ /**
642
+ * Check if fromCommit is an ancestor of toCommit (reachable in git history).
643
+ * Returns false if commits are on divergent branches or fromCommit doesn't exist.
644
+ */
645
+ isCommitReachable(fromCommit, toCommit) {
646
+ try {
647
+ execFileSync('git', ['merge-base', '--is-ancestor', fromCommit, toCommit], { cwd: this.repoPath, stdio: 'ignore' });
648
+ return true;
649
+ }
650
+ catch {
651
+ return false;
652
+ }
653
+ }
562
654
  getChangedFiles(fromCommit, toCommit) {
655
+ // First check if fromCommit is reachable from toCommit
656
+ // This handles the case where wiki was generated on a different branch
657
+ if (!this.isCommitReachable(fromCommit, toCommit)) {
658
+ return null; // Signal that we can't compute diff (divergent branches)
659
+ }
563
660
  try {
564
661
  const output = execFileSync('git', ['diff', `${fromCommit}..${toCommit}`, '--name-only'], { cwd: this.repoPath }).toString().trim();
565
662
  return output ? output.split('\n').filter(Boolean) : [];
566
663
  }
567
664
  catch {
568
- return [];
665
+ return null; // Treat git errors as needing full regen
569
666
  }
570
667
  }
571
668
  async readSourceFiles(filePaths) {
@@ -4,6 +4,10 @@
4
4
  * Encapsulated Cypher queries against the GitNexus knowledge graph.
5
5
  * Uses the MCP-style pooled lbug-adapter for connection management.
6
6
  */
7
+ /**
8
+ * Touch the wiki DB connection to prevent idle timeout during long LLM calls.
9
+ */
10
+ export declare function touchWikiDb(): void;
7
11
  export interface FileWithExports {
8
12
  filePath: string;
9
13
  symbols: Array<{
@@ -4,8 +4,14 @@
4
4
  * Encapsulated Cypher queries against the GitNexus knowledge graph.
5
5
  * Uses the MCP-style pooled lbug-adapter for connection management.
6
6
  */
7
- import { initLbug, executeQuery, closeLbug } from '../../mcp/core/lbug-adapter.js';
7
+ import { initLbug, executeQuery, closeLbug, touchRepo } from '../../mcp/core/lbug-adapter.js';
8
8
  const REPO_ID = '__wiki__';
9
+ /**
10
+ * Touch the wiki DB connection to prevent idle timeout during long LLM calls.
11
+ */
12
+ export function touchWikiDb() {
13
+ touchRepo(REPO_ID);
14
+ }
9
15
  /**
10
16
  * Initialize the LadybugDB connection for wiki generation.
11
17
  */
@@ -6,12 +6,14 @@
6
6
  *
7
7
  * Config priority: CLI flags > env vars > defaults
8
8
  */
9
+ export type LLMProvider = 'openai' | 'cursor';
9
10
  export interface LLMConfig {
10
11
  apiKey: string;
11
12
  baseUrl: string;
12
13
  model: string;
13
14
  maxTokens: number;
14
15
  temperature: number;
16
+ provider?: LLMProvider;
15
17
  }
16
18
  export interface LLMResponse {
17
19
  content: string;
@@ -15,23 +15,27 @@
15
15
  export async function resolveLLMConfig(overrides) {
16
16
  const { loadCLIConfig } = await import('../../storage/repo-manager.js');
17
17
  const savedConfig = await loadCLIConfig();
18
+ const provider = overrides?.provider || savedConfig.provider || 'openai';
18
19
  const apiKey = overrides?.apiKey
19
20
  || process.env.GITNEXUS_API_KEY
20
21
  || process.env.OPENAI_API_KEY
21
22
  || savedConfig.apiKey
22
23
  || '';
24
+ // For cursor provider, only use model if explicitly provided (default is 'auto' handled by CLI)
25
+ // For openai provider, use model with fallback to default
26
+ const model = provider === 'cursor'
27
+ ? (overrides?.model || savedConfig.cursorModel || '')
28
+ : (overrides?.model || process.env.GITNEXUS_MODEL || savedConfig.model || 'minimax/minimax-m2.5');
23
29
  return {
24
30
  apiKey,
25
31
  baseUrl: overrides?.baseUrl
26
32
  || process.env.GITNEXUS_LLM_BASE_URL
27
33
  || savedConfig.baseUrl
28
34
  || 'https://openrouter.ai/api/v1',
29
- model: overrides?.model
30
- || process.env.GITNEXUS_MODEL
31
- || savedConfig.model
32
- || 'minimax/minimax-m2.5',
35
+ model,
33
36
  maxTokens: overrides?.maxTokens ?? 16_384,
34
37
  temperature: overrides?.temperature ?? 0,
38
+ provider,
35
39
  };
36
40
  }
37
41
  /**
@@ -6,11 +6,11 @@
6
6
  */
7
7
  export declare const GROUPING_SYSTEM_PROMPT = "You are a documentation architect. Given a list of source files with their exported symbols, group them into logical documentation modules.\n\nRules:\n- Each module should represent a cohesive feature, layer, or domain\n- Every file must appear in exactly one module\n- Module names should be human-readable (e.g. \"Authentication\", \"Database Layer\", \"API Routes\")\n- Aim for 5-15 modules for a typical project. Fewer for small projects, more for large ones\n- Group by functionality, not by file type or directory structure alone\n- Do NOT create modules for tests, configs, or non-source files";
8
8
  export declare const GROUPING_USER_PROMPT = "Group these source files into documentation modules.\n\n**Files and their exports:**\n{{FILE_LIST}}\n\n**Directory structure:**\n{{DIRECTORY_TREE}}\n\nRespond with ONLY a JSON object mapping module names to file path arrays. No markdown, no explanation.\nExample format:\n{\n \"Authentication\": [\"src/auth/login.ts\", \"src/auth/session.ts\"],\n \"Database\": [\"src/db/connection.ts\", \"src/db/models.ts\"]\n}";
9
- export declare const MODULE_SYSTEM_PROMPT = "You are a technical documentation writer. Write clear, developer-focused documentation for a code module.\n\nRules:\n- Reference actual function names, class names, and code patterns \u2014 do NOT invent APIs\n- Use the call graph and execution flow data for accuracy, but do NOT mechanically list every edge\n- Include Mermaid diagrams only when they genuinely help understanding. Keep them small (5-10 nodes max)\n- Structure the document however makes sense for this module \u2014 there is no mandatory format\n- Write for a developer who needs to understand and contribute to this code";
9
+ export declare const MODULE_SYSTEM_PROMPT = "You are a technical documentation writer. Write clear, developer-focused documentation for a code module.\n\nRules:\n- Output ONLY the documentation content \u2014 no meta-commentary like \"I've written...\", \"Here's the documentation...\", \"The documentation covers...\", or similar\n- Start directly with the module heading and content\n- Reference actual function names, class names, and code patterns \u2014 do NOT invent APIs\n- Use the call graph and execution flow data for accuracy, but do NOT mechanically list every edge\n- Include Mermaid diagrams only when they genuinely help understanding. Keep them small (5-10 nodes max)\n- Structure the document however makes sense for this module \u2014 there is no mandatory format\n- Write for a developer who needs to understand and contribute to this code";
10
10
  export declare const MODULE_USER_PROMPT = "Write documentation for the **{{MODULE_NAME}}** module.\n\n## Source Code\n\n{{SOURCE_CODE}}\n\n## Call Graph & Execution Flows (reference for accuracy)\n\nInternal calls: {{INTRA_CALLS}}\nOutgoing calls: {{OUTGOING_CALLS}}\nIncoming calls: {{INCOMING_CALLS}}\nExecution flows: {{PROCESSES}}\n\n---\n\nWrite comprehensive documentation for this module. Cover its purpose, how it works, its key components, and how it connects to the rest of the codebase. Use whatever structure best fits this module \u2014 you decide the sections and headings. Include a Mermaid diagram only if it genuinely clarifies the architecture.";
11
- export declare const PARENT_SYSTEM_PROMPT = "You are a technical documentation writer. Write a summary page for a module that contains sub-modules. Synthesize the children's documentation \u2014 do not re-read source code.\n\nRules:\n- Reference actual components from the child modules\n- Focus on how the sub-modules work together, not repeating their individual docs\n- Keep it concise \u2014 the reader can click through to child pages for detail\n- Include a Mermaid diagram only if it genuinely clarifies how the sub-modules relate";
11
+ export declare const PARENT_SYSTEM_PROMPT = "You are a technical documentation writer. Write a summary page for a module that contains sub-modules. Synthesize the children's documentation \u2014 do not re-read source code.\n\nRules:\n- Output ONLY the documentation content \u2014 no meta-commentary like \"I've written...\", \"Here's the documentation...\", \"The documentation covers...\", or similar\n- Start directly with the module heading and content\n- Reference actual components from the child modules\n- Focus on how the sub-modules work together, not repeating their individual docs\n- Keep it concise \u2014 the reader can click through to child pages for detail\n- Include a Mermaid diagram only if it genuinely clarifies how the sub-modules relate";
12
12
  export declare const PARENT_USER_PROMPT = "Write documentation for the **{{MODULE_NAME}}** module, which contains these sub-modules:\n\n{{CHILDREN_DOCS}}\n\nCross-module calls: {{CROSS_MODULE_CALLS}}\nShared execution flows: {{CROSS_PROCESSES}}\n\n---\n\nWrite a concise overview of this module group. Explain its purpose, how the sub-modules fit together, and the key workflows that span them. Link to sub-module pages (e.g. `[Sub-module Name](sub-module-slug.md)`) rather than repeating their content. Use whatever structure fits best.";
13
- export declare const OVERVIEW_SYSTEM_PROMPT = "You are a technical documentation writer. Write the top-level overview page for a repository wiki. This is the first page a new developer sees.\n\nRules:\n- Be clear and welcoming \u2014 this is the entry point to the entire codebase\n- Reference actual module names so readers can navigate to their docs\n- Include a high-level Mermaid architecture diagram showing only the most important modules and their relationships (max 10 nodes). A new dev should grasp it in 10 seconds\n- Do NOT create module index tables or list every module with descriptions \u2014 just link to module pages naturally within the text\n- Use the inter-module edges and execution flow data for accuracy, but do NOT dump them raw";
13
+ export declare const OVERVIEW_SYSTEM_PROMPT = "You are a technical documentation writer. Write the top-level overview page for a repository wiki. This is the first page a new developer sees.\n\nRules:\n- Output ONLY the documentation content \u2014 no meta-commentary like \"I've written...\", \"Here's the documentation...\", \"The page has been rewritten...\", or similar\n- Start directly with the project heading and content\n- Be clear and welcoming \u2014 this is the entry point to the entire codebase\n- Reference actual module names so readers can navigate to their docs\n- Include a high-level Mermaid architecture diagram showing only the most important modules and their relationships (max 10 nodes). A new dev should grasp it in 10 seconds\n- Do NOT create module index tables or list every module with descriptions \u2014 just link to module pages naturally within the text\n- Use the inter-module edges and execution flow data for accuracy, but do NOT dump them raw";
14
14
  export declare const OVERVIEW_USER_PROMPT = "Write the overview page for this repository's wiki.\n\n## Project Info\n\n{{PROJECT_INFO}}\n\n## Module Summaries\n\n{{MODULE_SUMMARIES}}\n\n## Reference Data (for accuracy \u2014 do not reproduce verbatim)\n\nInter-module call edges: {{MODULE_EDGES}}\nKey system flows: {{TOP_PROCESSES}}\n\n---\n\nWrite a clear overview of this project: what it does, how it's architected, and the key end-to-end flows. Include a simple Mermaid architecture diagram (max 10 nodes, big-picture only). Link to module pages (e.g. `[Module Name](module-slug.md)`) naturally in the text rather than listing them in a table. If project config was provided, include brief setup instructions. Structure the page however reads best.";
15
15
  /**
16
16
  * Replace {{PLACEHOLDER}} tokens in a template string.
@@ -32,6 +32,8 @@ Example format:
32
32
  export const MODULE_SYSTEM_PROMPT = `You are a technical documentation writer. Write clear, developer-focused documentation for a code module.
33
33
 
34
34
  Rules:
35
+ - Output ONLY the documentation content — no meta-commentary like "I've written...", "Here's the documentation...", "The documentation covers...", or similar
36
+ - Start directly with the module heading and content
35
37
  - Reference actual function names, class names, and code patterns — do NOT invent APIs
36
38
  - Use the call graph and execution flow data for accuracy, but do NOT mechanically list every edge
37
39
  - Include Mermaid diagrams only when they genuinely help understanding. Keep them small (5-10 nodes max)
@@ -57,6 +59,8 @@ Write comprehensive documentation for this module. Cover its purpose, how it wor
57
59
  export const PARENT_SYSTEM_PROMPT = `You are a technical documentation writer. Write a summary page for a module that contains sub-modules. Synthesize the children's documentation — do not re-read source code.
58
60
 
59
61
  Rules:
62
+ - Output ONLY the documentation content — no meta-commentary like "I've written...", "Here's the documentation...", "The documentation covers...", or similar
63
+ - Start directly with the module heading and content
60
64
  - Reference actual components from the child modules
61
65
  - Focus on how the sub-modules work together, not repeating their individual docs
62
66
  - Keep it concise — the reader can click through to child pages for detail
@@ -75,6 +79,8 @@ Write a concise overview of this module group. Explain its purpose, how the sub-
75
79
  export const OVERVIEW_SYSTEM_PROMPT = `You are a technical documentation writer. Write the top-level overview page for a repository wiki. This is the first page a new developer sees.
76
80
 
77
81
  Rules:
82
+ - Output ONLY the documentation content — no meta-commentary like "I've written...", "Here's the documentation...", "The page has been rewritten...", or similar
83
+ - Start directly with the project heading and content
78
84
  - Be clear and welcoming — this is the entry point to the entire codebase
79
85
  - Reference actual module names so readers can navigate to their docs
80
86
  - Include a high-level Mermaid architecture diagram showing only the most important modules and their relationships (max 10 nodes). A new dev should grasp it in 10 seconds
@@ -15,6 +15,11 @@
15
15
  import lbug from '@ladybugdb/core';
16
16
  /** Saved real stdout.write — used to silence LadybugDB native output without race conditions */
17
17
  export declare const realStdoutWrite: any;
18
+ /**
19
+ * Touch a repo to reset its idle timeout.
20
+ * Call this during long-running operations to prevent the connection from being closed.
21
+ */
22
+ export declare const touchRepo: (repoId: string) => void;
18
23
  /**
19
24
  * Initialize (or reuse) a Database + connection pool for a specific repo.
20
25
  * Retries on lock errors (e.g., when `gitnexus analyze` is running).
@@ -46,6 +46,16 @@ function ensureIdleTimer() {
46
46
  idleTimer.unref();
47
47
  }
48
48
  }
49
+ /**
50
+ * Touch a repo to reset its idle timeout.
51
+ * Call this during long-running operations to prevent the connection from being closed.
52
+ */
53
+ export const touchRepo = (repoId) => {
54
+ const entry = pool.get(repoId);
55
+ if (entry) {
56
+ entry.lastUsed = Date.now();
57
+ }
58
+ };
49
59
  /**
50
60
  * Evict the least-recently-used repo if pool is at capacity
51
61
  */
@@ -459,7 +469,7 @@ export const closeLbug = async (repoId) => {
459
469
  */
460
470
  export const isLbugReady = (repoId) => pool.has(repoId);
461
471
  /** Regex to detect write operations in user-supplied Cypher queries */
462
- export const CYPHER_WRITE_RE = /\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH)\b/i;
472
+ export const CYPHER_WRITE_RE = /(?<!:)\b(CREATE|DELETE|SET|MERGE|REMOVE|DROP|ALTER|COPY|DETACH|FOREACH)\b/i;
463
473
  /** Check if a Cypher query contains write operations */
464
474
  export function isWriteQuery(query) {
465
475
  return CYPHER_WRITE_RE.test(query);
@@ -5,6 +5,8 @@
5
5
  * Supports multiple indexed repositories via a global registry.
6
6
  * LadybugDB connections are opened lazily per repo on first query.
7
7
  */
8
+ import { isWriteQuery } from '../core/lbug-adapter.js';
9
+ export { isWriteQuery };
8
10
  import { type RegistryEntry } from '../../storage/repo-manager.js';
9
11
  /**
10
12
  * Quick test-file detection for filtering impact results.
@@ -35,10 +37,6 @@ export declare const VALID_RELATION_TYPES: Set<string>;
35
37
  * (unknown type) – conservative fallback → 0.5
36
38
  */
37
39
  export declare const IMPACT_RELATION_CONFIDENCE: Readonly<Record<string, number>>;
38
- /** Regex to detect write operations in user-supplied Cypher queries */
39
- export declare const CYPHER_WRITE_RE: RegExp;
40
- /** Check if a Cypher query contains write operations */
41
- export declare function isWriteQuery(query: string): boolean;
42
40
  export interface CodebaseContext {
43
41
  projectName: string;
44
42
  stats: {
@@ -167,6 +165,20 @@ export declare class LocalBackend {
167
165
  private rename;
168
166
  private impact;
169
167
  private _impactImpl;
168
+ /**
169
+ * Fetch Route nodes with their consumers in a single query.
170
+ * Shared by routeMap and shapeCheck to avoid N+1 query patterns.
171
+ */
172
+ private fetchRoutesWithConsumers;
173
+ /**
174
+ * Batch-fetch execution flows linked to a set of Route or Tool nodes.
175
+ * Single query instead of N+1.
176
+ */
177
+ private fetchLinkedFlowsBatch;
178
+ private routeMap;
179
+ private shapeCheck;
180
+ private toolMap;
181
+ private apiImpact;
170
182
  /**
171
183
  * Query clusters (communities) directly from graph.
172
184
  * Used by getClustersResource — avoids legacy overview() dispatch.
@@ -193,4 +205,3 @@ export declare class LocalBackend {
193
205
  queryProcessDetail(name: string, repoName?: string): Promise<any>;
194
206
  disconnect(): Promise<void>;
195
207
  }
196
- export {};