gitnexus 1.6.10-rc.2 → 1.6.10-rc.21

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 (219) hide show
  1. package/README.md +56 -12
  2. package/dist/cli/analyze.js +166 -5
  3. package/dist/cli/clean.js +17 -3
  4. package/dist/cli/cli-message.d.ts +1 -1
  5. package/dist/cli/doctor.d.ts +18 -0
  6. package/dist/cli/doctor.js +76 -3
  7. package/dist/cli/embeddings.d.ts +12 -0
  8. package/dist/cli/embeddings.js +49 -0
  9. package/dist/cli/help-i18n.js +4 -0
  10. package/dist/cli/i18n/en.d.ts +10 -5
  11. package/dist/cli/i18n/en.js +10 -5
  12. package/dist/cli/i18n/resources.d.ts +15 -5
  13. package/dist/cli/i18n/zh-CN.d.ts +5 -0
  14. package/dist/cli/i18n/zh-CN.js +10 -5
  15. package/dist/cli/index.js +12 -1
  16. package/dist/core/augmentation/engine.js +4 -3
  17. package/dist/core/embeddings/embedder.js +16 -2
  18. package/dist/core/embeddings/embedding-pipeline.d.ts +20 -0
  19. package/dist/core/embeddings/embedding-pipeline.js +10 -2
  20. package/dist/core/embeddings/http-client.d.ts +28 -1
  21. package/dist/core/embeddings/http-client.js +107 -15
  22. package/dist/core/embeddings/node-module-compat.d.ts +23 -0
  23. package/dist/core/embeddings/node-module-compat.js +23 -0
  24. package/dist/core/embeddings/onnxruntime-common-resolver.js +6 -3
  25. package/dist/core/embeddings/onnxruntime-node-resolver.js +31 -5
  26. package/dist/core/embeddings/runtime-install.d.ts +119 -0
  27. package/dist/core/embeddings/runtime-install.js +372 -0
  28. package/dist/core/embeddings/runtime-support.d.ts +48 -17
  29. package/dist/core/embeddings/runtime-support.js +96 -0
  30. package/dist/core/group/extractors/http-patterns/python.js +246 -108
  31. package/dist/core/group/extractors/manifest-extractor.js +4 -0
  32. package/dist/core/incremental/escalation-gate.d.ts +38 -0
  33. package/dist/core/incremental/escalation-gate.js +48 -0
  34. package/dist/core/incremental/shadow-candidates.d.ts +3 -3
  35. package/dist/core/incremental/shadow-candidates.js +3 -3
  36. package/dist/core/incremental/subgraph-extract.d.ts +2 -2
  37. package/dist/core/incremental/subgraph-extract.js +2 -2
  38. package/dist/core/ingestion/cobol/jcl-processor.js +9 -8
  39. package/dist/core/ingestion/cobol-processor.js +27 -26
  40. package/dist/core/ingestion/community-processor.d.ts +51 -1
  41. package/dist/core/ingestion/community-processor.js +316 -63
  42. package/dist/core/ingestion/emit-references.js +3 -2
  43. package/dist/core/ingestion/languages/cpp/captures.js +157 -74
  44. package/dist/core/ingestion/markdown-processor.js +3 -2
  45. package/dist/core/ingestion/parsing-processor.d.ts +3 -1
  46. package/dist/core/ingestion/parsing-processor.js +4 -0
  47. package/dist/core/ingestion/pipeline-phases/parse-impl.js +42 -0
  48. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +83 -0
  49. package/dist/core/ingestion/route-extractors/constant-resolver.js +131 -0
  50. package/dist/core/ingestion/route-extractors/python-const-resolver.d.ts +75 -0
  51. package/dist/core/ingestion/route-extractors/python-const-resolver.js +299 -0
  52. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  53. package/dist/core/ingestion/tree-sitter-queries.js +12 -1
  54. package/dist/core/ingestion/utils/line-base.d.ts +20 -0
  55. package/dist/core/ingestion/utils/line-base.js +20 -0
  56. package/dist/core/ingestion/utils/symbol-labels.d.ts +21 -0
  57. package/dist/core/ingestion/utils/symbol-labels.js +45 -0
  58. package/dist/core/ingestion/workers/parse-worker.d.ts +34 -0
  59. package/dist/core/ingestion/workers/parse-worker.js +40 -5
  60. package/dist/core/ingestion/workers/result-merge.js +4 -0
  61. package/dist/core/ingestion/workers/worker-pool.d.ts +14 -0
  62. package/dist/core/ingestion/workers/worker-pool.js +85 -5
  63. package/dist/core/lbug/csv-generator.js +8 -2
  64. package/dist/core/lbug/cypher-escape.d.ts +20 -0
  65. package/dist/core/lbug/cypher-escape.js +20 -0
  66. package/dist/core/lbug/extension-load-error.d.ts +67 -0
  67. package/dist/core/lbug/extension-load-error.js +320 -0
  68. package/dist/core/lbug/extension-loader.d.ts +17 -2
  69. package/dist/core/lbug/extension-loader.js +38 -13
  70. package/dist/core/lbug/lbug-adapter.d.ts +147 -1
  71. package/dist/core/lbug/lbug-adapter.js +390 -70
  72. package/dist/core/lbug/lbug-config.d.ts +28 -0
  73. package/dist/core/lbug/lbug-config.js +114 -4
  74. package/dist/core/lbug/native-check.d.ts +22 -0
  75. package/dist/core/lbug/native-check.js +66 -0
  76. package/dist/core/lbug/pool-adapter.js +10 -1
  77. package/dist/core/lbug/sidecar-recovery.d.ts +172 -0
  78. package/dist/core/lbug/sidecar-recovery.js +418 -16
  79. package/dist/core/platform/capabilities.js +27 -1
  80. package/dist/core/run-analyze.js +470 -108
  81. package/dist/core/search/fts-indexes.d.ts +7 -0
  82. package/dist/core/search/fts-indexes.js +37 -0
  83. package/dist/core/tree-sitter/safe-parse.d.ts +8 -3
  84. package/dist/core/tree-sitter/safe-parse.js +21 -7
  85. package/dist/core/wiki/graph-queries.js +6 -5
  86. package/dist/mcp/core/embedder.js +16 -2
  87. package/dist/mcp/local/line-display.d.ts +22 -0
  88. package/dist/mcp/local/line-display.js +3 -0
  89. package/dist/mcp/local/local-backend.d.ts +14 -0
  90. package/dist/mcp/local/local-backend.js +63 -19
  91. package/dist/mcp/local/pdg-impact.d.ts +5 -3
  92. package/dist/mcp/local/pdg-impact.js +5 -2
  93. package/dist/mcp/resources.js +1 -0
  94. package/dist/mcp/tools.js +1 -1
  95. package/dist/server/analyze-launch.d.ts +6 -0
  96. package/dist/server/analyze-launch.js +82 -6
  97. package/dist/server/api.d.ts +30 -0
  98. package/dist/server/api.js +63 -21
  99. package/dist/storage/parse-cache.js +1 -1
  100. package/dist/storage/repo-manager.d.ts +65 -1
  101. package/dist/storage/repo-manager.js +18 -1
  102. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +50 -3
  103. package/hooks/claude/gitnexus-hook.cjs +59 -9
  104. package/package.json +5 -3
  105. package/scripts/cross-platform-tests.ts +43 -0
  106. package/scripts/ensure-fts.ts +32 -0
  107. package/scripts/install-duckdb-extension.mjs +85 -24
  108. package/scripts/run-cross-platform.ts +46 -6
  109. package/scripts/shard-arg.ts +30 -0
  110. package/web/assets/ProcessFlowModal-Dna7GkwO.js +22 -0
  111. package/web/assets/{agent-Dli_x7bU.js → agent-DNYDy-KA.js} +49 -49
  112. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +1 -0
  113. package/web/assets/architectureDiagram-UL44E2DR-BU5ZRITg.js +36 -0
  114. package/web/assets/{blockDiagram-7IZFK4PR-BkWhpzWB.js → blockDiagram-7IZFK4PR-Dut1BpTV.js} +2 -2
  115. package/web/assets/{c4Diagram-Y2BXMSZH-DtBvpzWD.js → c4Diagram-Y2BXMSZH-DOXVs7-C.js} +2 -2
  116. package/web/assets/chunk-2T2R6R2M-pkw2oXHH.js +4 -0
  117. package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +1 -0
  118. package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +321 -0
  119. package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +159 -0
  120. package/web/assets/{chunk-6764PJDD-DpIS7hxD.js → chunk-6764PJDD-C7xxk063.js} +1 -1
  121. package/web/assets/{chunk-67TQ5CYL-4sNPJU1q.js → chunk-67TQ5CYL-zxhDWgyF.js} +3 -3
  122. package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +32 -0
  123. package/web/assets/chunk-7J6CGLKN-AmkN8jJN.js +10 -0
  124. package/web/assets/chunk-7W6UQGC5-pyKGssV3.js +1 -0
  125. package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +1 -0
  126. package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +1 -0
  127. package/web/assets/{chunk-INKRHTLW-Nu4ri9P-.js → chunk-INKRHTLW-D9BM9HWZ.js} +1 -1
  128. package/web/assets/chunk-J5EP6P6S-L19oz55R.js +1 -0
  129. package/web/assets/{chunk-KGFNY3KK-JGMJvKCK.js → chunk-KGFNY3KK-tD2F6wxK.js} +1 -1
  130. package/web/assets/chunk-KGYTTC2M-zm6n242I.js +1 -0
  131. package/web/assets/{chunk-KRXBNO2N-BW5o0KUN.js → chunk-KRXBNO2N-C-UNuKKf.js} +1 -1
  132. package/web/assets/{chunk-LCXTWHL2-CfLLCywY.js → chunk-LCXTWHL2-Ba9JK-6F.js} +2 -2
  133. package/web/assets/{chunk-LII3EMHJ-D-jm-dLa.js → chunk-LII3EMHJ-EGx-eBIu.js} +1 -1
  134. package/web/assets/chunk-LRIF4GLE-C3_PqUBq.js +1 -0
  135. package/web/assets/{chunk-QA3QBVWF-BgRW3SXd.js → chunk-QA3QBVWF-D4cSJBf6.js} +1 -1
  136. package/web/assets/chunk-RERM46MO-B5dAdWpP.js +1 -0
  137. package/web/assets/{chunk-RG4AUYOV-CqWzWsV3.js → chunk-RG4AUYOV-OqBBMwuQ.js} +4 -4
  138. package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +1 -0
  139. package/web/assets/chunk-RLI5ZMPA-CkXZ0y0U.js +1 -0
  140. package/web/assets/{chunk-T2UQINTJ-DSBqkzH7.js → chunk-T2UQINTJ-BJusEiGe.js} +1 -1
  141. package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +1 -0
  142. package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +1 -0
  143. package/web/assets/{chunk-UY5QBCOK-DyF0vyGd.js → chunk-UY5QBCOK-BCpUlARQ.js} +1 -1
  144. package/web/assets/chunk-VU6ZFW4Y-BeXVk45_.js +2 -0
  145. package/web/assets/{chunk-W44A43WB-JqMLIpR0.js → chunk-W44A43WB-BN8EQc_u.js} +1 -1
  146. package/web/assets/{chunk-ZXARS5L4-CxN8oiwI.js → chunk-ZXARS5L4-BI_qR2Nm.js} +1 -1
  147. package/web/assets/classDiagram-KGZ6W3CR-D9zSIg0x.js +1 -0
  148. package/web/assets/classDiagram-v2-72OJOZXJ-D9zSIg0x.js +1 -0
  149. package/web/assets/cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js +1 -0
  150. package/web/assets/dagre-ND4H6XIP-CpcV5kbl.js +4 -0
  151. package/web/assets/diagram-3NCE3AQN-6SMxBRpj.js +43 -0
  152. package/web/assets/diagram-GF46GFSD-BZEdNcRZ.js +24 -0
  153. package/web/assets/{diagram-HNR7UZ2L-DFCfd5LI.js → diagram-HNR7UZ2L-DxKW2a2Y.js} +2 -2
  154. package/web/assets/diagram-QXG6HAR7-HfTHcXX_.js +24 -0
  155. package/web/assets/diagram-WEQXMOUZ-CyijUS8r.js +10 -0
  156. package/web/assets/{erDiagram-L5TCEMPS-8X1y6R4Y.js → erDiagram-L5TCEMPS-D0fvpWEX.js} +3 -3
  157. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +1 -0
  158. package/web/assets/flowDiagram-H6V6AXG4-Bvr9bbhk.js +162 -0
  159. package/web/assets/{ganttDiagram-JCBTUEKG-D3iL2Aet.js → ganttDiagram-JCBTUEKG-TzODrLzv.js} +2 -2
  160. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +1 -0
  161. package/web/assets/gitGraphDiagram-S2ZK5IYY-ONQSwJIY.js +106 -0
  162. package/web/assets/index-B4eB4dNZ.js +575 -0
  163. package/web/assets/index-CX_fADmQ.css +2 -0
  164. package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +1 -0
  165. package/web/assets/infoDiagram-3YFTVSEB-DIPVUIQ6.js +2 -0
  166. package/web/assets/{ishikawaDiagram-BNXS4ZKH-DfYpxnE5.js → ishikawaDiagram-BNXS4ZKH-BLjivjXL.js} +3 -3
  167. package/web/assets/{journeyDiagram-M6C3CM3L-CvArszLo.js → journeyDiagram-M6C3CM3L-Cetgs3NH.js} +3 -3
  168. package/web/assets/{kanban-definition-75IXJCU3-CpfVLLf9.js → kanban-definition-75IXJCU3-DMlCaIpf.js} +5 -5
  169. package/web/assets/{katex-K3KEBU37-CTc5BslQ.js → katex-K3KEBU37-qRQBy-EZ.js} +2 -2
  170. package/web/assets/{mindmap-definition-2TDM6QVE-DypvbOMQ.js → mindmap-definition-2TDM6QVE-B4GbXQBc.js} +3 -3
  171. package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +1 -0
  172. package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +1 -0
  173. package/web/assets/{pieDiagram-CU6KROY3-s27EFWgP.js → pieDiagram-CU6KROY3-D3YC_xyg.js} +2 -2
  174. package/web/assets/{quadrantDiagram-VICAPDV7-yXSZ2lLz.js → quadrantDiagram-VICAPDV7-B1TQnlm0.js} +2 -2
  175. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +1 -0
  176. package/web/assets/{requirementDiagram-JXO7QTGE-CWhqInuf.js → requirementDiagram-JXO7QTGE-f8wczLQf.js} +3 -3
  177. package/web/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  178. package/web/assets/{sankeyDiagram-URQDO5SZ-1qEjlFIB.js → sankeyDiagram-URQDO5SZ-BTxzG5j0.js} +2 -2
  179. package/web/assets/{sequenceDiagram-VS2MUI6T-RyTlZ5cR.js → sequenceDiagram-VS2MUI6T-DuamM-IR.js} +5 -5
  180. package/web/assets/src-BNCXICdA.js +1 -0
  181. package/web/assets/stateDiagram-7D4R322I-Ch2pEUcw.js +1 -0
  182. package/web/assets/stateDiagram-v2-36443NZ5-7ANuoZLL.js +1 -0
  183. package/web/assets/{timeline-definition-O6YCAMPW-DrrqLYAf.js → timeline-definition-O6YCAMPW-07BR8R8G.js} +3 -3
  184. package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +1 -0
  185. package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +1 -0
  186. package/web/assets/{vennDiagram-MWXL3ELB-B0RkWAim.js → vennDiagram-MWXL3ELB-BQX2ossk.js} +4 -4
  187. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +1 -0
  188. package/web/assets/{wardleyDiagram-CUQ6CDDI-boUxiQ5F.js → wardleyDiagram-CUQ6CDDI-CGIJBBiK.js} +3 -3
  189. package/web/assets/{xychartDiagram-N2JHSOCM-BOLm0eNm.js → xychartDiagram-N2JHSOCM-B3HNMuWp.js} +4 -4
  190. package/web/index.html +17 -10
  191. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +0 -1
  192. package/web/assets/architectureDiagram-UL44E2DR-dEkqUZN9.js +0 -36
  193. package/web/assets/chunk-3SSMPTDK-B4_etUhr.js +0 -321
  194. package/web/assets/chunk-7W6UQGC5-KyEG0HQg.js +0 -1
  195. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +0 -161
  196. package/web/assets/classDiagram-KGZ6W3CR-CMne4tG9.js +0 -1
  197. package/web/assets/classDiagram-v2-72OJOZXJ-CMne4tG9.js +0 -1
  198. package/web/assets/cose-bilkent-UX7MHV2Q-D71wNYRJ.js +0 -1
  199. package/web/assets/dagre-ND4H6XIP-Co5rRx9X.js +0 -4
  200. package/web/assets/diagram-3NCE3AQN-DBp4O00j.js +0 -43
  201. package/web/assets/diagram-GF46GFSD-6N_8yS0i.js +0 -24
  202. package/web/assets/diagram-QXG6HAR7-Di093rFg.js +0 -24
  203. package/web/assets/diagram-WEQXMOUZ-DZWi4SUN.js +0 -10
  204. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +0 -1
  205. package/web/assets/flowDiagram-H6V6AXG4-CMpY8Ufn.js +0 -162
  206. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +0 -1
  207. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +0 -106
  208. package/web/assets/index-KctnI3Vq.js +0 -635
  209. package/web/assets/index-_lgn7hs5.css +0 -2
  210. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +0 -1
  211. package/web/assets/infoDiagram-3YFTVSEB-BaLMZkUg.js +0 -2
  212. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +0 -1
  213. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +0 -1
  214. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +0 -1
  215. package/web/assets/stateDiagram-7D4R322I-DTjVfX4A.js +0 -1
  216. package/web/assets/stateDiagram-v2-36443NZ5-xY9_k9_Q.js +0 -1
  217. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +0 -1
  218. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +0 -1
  219. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +0 -1
@@ -14,6 +14,7 @@ import Graph from 'graphology';
14
14
  import { createRequire } from 'node:module';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { dirname, resolve } from 'node:path';
17
+ import { Worker } from 'node:worker_threads';
17
18
  const __filename = fileURLToPath(import.meta.url);
18
19
  const __dirname = dirname(__filename);
19
20
  // Navigate to package root (works from both src/ and dist/)
@@ -37,6 +38,20 @@ function createSeededRng(seed) {
37
38
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
38
39
  };
39
40
  }
41
+ const COMMUNITY_ENGINE_ENV = 'GITNEXUS_COMMUNITY_ENGINE';
42
+ const DEFAULT_COMMUNITY_ENGINE = 'graphology';
43
+ const LEIDEN_TIMEOUT_MS = 60_000;
44
+ const ICEBUG_TIMEOUT_MS = 60_000;
45
+ const MIN_CONFIDENCE_LARGE = 0.5;
46
+ export const resolveCommunityDetectionEngine = (raw = process.env[COMMUNITY_ENGINE_ENV]) => {
47
+ if (raw === undefined || raw.trim() === '')
48
+ return DEFAULT_COMMUNITY_ENGINE;
49
+ const normalized = raw.trim().toLowerCase();
50
+ if (normalized === 'graphology' || normalized === 'icebug' || normalized === 'auto') {
51
+ return normalized;
52
+ }
53
+ return DEFAULT_COMMUNITY_ENGINE;
54
+ };
40
55
  // ============================================================================
41
56
  // COMMUNITY COLORS (for visualization)
42
57
  // ============================================================================
@@ -66,59 +81,28 @@ export const getCommunityColor = (communityIndex) => {
66
81
  * This runs AFTER all relationships (CALLS, IMPORTS, etc.) have been built.
67
82
  * It uses primarily CALLS edges to cluster code that works together.
68
83
  */
69
- export const processCommunities = async (knowledgeGraph, onProgress) => {
84
+ export const processCommunities = async (knowledgeGraph, onProgress, options = {}) => {
70
85
  onProgress?.('Building graph for community detection...', 0);
71
- // Pre-check total symbol count to determine large-graph mode before building
72
- let symbolCount = 0;
73
- knowledgeGraph.forEachNode((node) => {
74
- if (node.label === 'Function' ||
75
- node.label === 'Class' ||
76
- node.label === 'Method' ||
77
- node.label === 'Interface') {
78
- symbolCount++;
79
- }
80
- });
81
- const isLarge = symbolCount > 10_000;
82
- const graph = buildGraphologyGraph(knowledgeGraph, isLarge);
86
+ const engineRequested = options.engine ?? resolveCommunityDetectionEngine();
87
+ const projection = buildCommunityProjection(knowledgeGraph);
88
+ const graph = buildGraphologyGraph(projection);
83
89
  if (graph.order === 0) {
84
90
  return {
85
91
  communities: [],
86
92
  memberships: [],
87
- stats: { totalCommunities: 0, modularity: 0, nodesProcessed: 0 },
93
+ stats: {
94
+ totalCommunities: 0,
95
+ modularity: 0,
96
+ nodesProcessed: 0,
97
+ engine: DEFAULT_COMMUNITY_ENGINE,
98
+ engineRequested,
99
+ },
88
100
  };
89
101
  }
90
102
  const nodeCount = graph.order;
91
103
  const edgeCount = graph.size;
92
- onProgress?.(`Running Leiden on ${nodeCount} nodes, ${edgeCount} edges${isLarge ? ` (filtered from ${symbolCount} symbols)` : ''}...`, 30);
93
- // Large graphs: higher resolution + capped iterations (matching Python leidenalg default of 2).
94
- // The first 2 iterations capture ~95%+ of modularity; additional iterations have diminishing returns.
95
- // Timeout: abort after 60s for pathological graph structures.
96
- const LEIDEN_TIMEOUT_MS = 60_000;
97
- let details;
98
- try {
99
- details = await Promise.race([
100
- Promise.resolve(leiden.detailed(graph, {
101
- resolution: isLarge ? 2.0 : 1.0,
102
- maxIterations: isLarge ? 3 : 0,
103
- rng: createSeededRng(LEIDEN_SEED),
104
- })),
105
- new Promise((_, reject) => setTimeout(() => reject(new Error('Leiden timeout')), LEIDEN_TIMEOUT_MS)),
106
- ]);
107
- }
108
- catch (e) {
109
- if (e.message === 'Leiden timeout') {
110
- onProgress?.('Community detection timed out, using fallback...', 60);
111
- // Fallback: assign all nodes to community 0
112
- const communities = {};
113
- graph.forEachNode((node) => {
114
- communities[node] = 0;
115
- });
116
- details = { communities, count: 1, modularity: 0 };
117
- }
118
- else {
119
- throw e;
120
- }
121
- }
104
+ onProgress?.(`Running Leiden on ${nodeCount} nodes, ${edgeCount} edges${projection.isLarge ? ` (filtered from ${projection.symbolCount} symbols)` : ''}...`, 30);
105
+ const details = await runCommunityEngine(graph, projection, engineRequested, options, onProgress);
122
106
  onProgress?.(`Found ${details.count} communities...`, 60);
123
107
  // Step 3: Create community nodes with heuristic labels
124
108
  const communityNodes = createCommunityNodes(details.communities, details.count, graph, knowledgeGraph);
@@ -139,27 +123,32 @@ export const processCommunities = async (knowledgeGraph, onProgress) => {
139
123
  totalCommunities: details.count,
140
124
  modularity: details.modularity,
141
125
  nodesProcessed: graph.order,
126
+ engine: details.engine,
127
+ engineRequested: details.engineRequested,
128
+ fallbackReason: details.fallbackReason,
142
129
  },
143
130
  };
144
131
  };
145
132
  // ============================================================================
146
- // HELPER: Build graphology graph from knowledge graph
133
+ // HELPER: Build community projection from knowledge graph
147
134
  // ============================================================================
148
135
  /**
149
- * Build a graphology graph containing only symbol nodes and clustering edges.
136
+ * Build a community projection containing only symbol nodes and clustering edges.
150
137
  * For large graphs (>10K symbols), filter out low-confidence fuzzy-global edges
151
138
  * and degree-1 nodes that add noise and massively increase Leiden runtime.
152
139
  */
153
- const MIN_CONFIDENCE_LARGE = 0.5;
154
- const buildGraphologyGraph = (knowledgeGraph, isLarge) => {
155
- const GraphCtor = Graph;
156
- const graph = new GraphCtor({ type: 'undirected', allowSelfLoops: false });
157
- const symbolTypes = new Set(['Function', 'Class', 'Method', 'Interface']);
158
- const clusteringRelTypes = new Set(['CALLS', 'EXTENDS', 'IMPLEMENTS']);
140
+ export const buildCommunityProjection = (knowledgeGraph) => {
141
+ let symbolCount = 0;
142
+ knowledgeGraph.forEachNode((node) => {
143
+ if (isCommunitySymbol(node)) {
144
+ symbolCount++;
145
+ }
146
+ });
147
+ const isLarge = symbolCount > 10_000;
159
148
  const connectedNodes = new Set();
160
149
  const nodeDegree = new Map();
161
150
  knowledgeGraph.forEachRelationship((rel) => {
162
- if (!clusteringRelTypes.has(rel.type) || rel.sourceId === rel.targetId)
151
+ if (!isClusteringRelationship(rel.type) || rel.sourceId === rel.targetId)
163
152
  return;
164
153
  if (isLarge && rel.confidence < MIN_CONFIDENCE_LARGE)
165
154
  return;
@@ -168,34 +157,298 @@ const buildGraphologyGraph = (knowledgeGraph, isLarge) => {
168
157
  nodeDegree.set(rel.sourceId, (nodeDegree.get(rel.sourceId) || 0) + 1);
169
158
  nodeDegree.set(rel.targetId, (nodeDegree.get(rel.targetId) || 0) + 1);
170
159
  });
160
+ const nodes = [];
161
+ const nodeIndexById = new Map();
171
162
  knowledgeGraph.forEachNode((node) => {
172
- if (!symbolTypes.has(node.label) || !connectedNodes.has(node.id))
163
+ if (!isCommunitySymbol(node) || !connectedNodes.has(node.id))
173
164
  return;
174
165
  // For large graphs, skip degree-1 nodes — they just become singletons or
175
166
  // get absorbed into their single neighbor's community, but cost iteration time.
176
167
  if (isLarge && (nodeDegree.get(node.id) || 0) < 2)
177
168
  return;
178
- graph.addNode(node.id, {
169
+ nodeIndexById.set(node.id, nodes.length);
170
+ nodes.push({
171
+ id: node.id,
179
172
  name: node.properties.name,
180
173
  filePath: node.properties.filePath,
181
174
  type: node.label,
182
175
  });
183
176
  });
177
+ const seenEdges = new Set();
178
+ const edges = [];
184
179
  knowledgeGraph.forEachRelationship((rel) => {
185
- if (!clusteringRelTypes.has(rel.type))
180
+ if (!isClusteringRelationship(rel.type) || rel.sourceId === rel.targetId)
186
181
  return;
187
182
  if (isLarge && rel.confidence < MIN_CONFIDENCE_LARGE)
188
183
  return;
189
- if (graph.hasNode(rel.sourceId) &&
190
- graph.hasNode(rel.targetId) &&
191
- rel.sourceId !== rel.targetId) {
192
- if (!graph.hasEdge(rel.sourceId, rel.targetId)) {
193
- graph.addEdge(rel.sourceId, rel.targetId);
194
- }
195
- }
184
+ const sourceIndex = nodeIndexById.get(rel.sourceId);
185
+ const targetIndex = nodeIndexById.get(rel.targetId);
186
+ if (sourceIndex === undefined || targetIndex === undefined || sourceIndex === targetIndex)
187
+ return;
188
+ const [a, b] = sourceIndex < targetIndex ? [sourceIndex, targetIndex] : [targetIndex, sourceIndex];
189
+ const edgeKey = `${a}:${b}`;
190
+ if (seenEdges.has(edgeKey))
191
+ return;
192
+ seenEdges.add(edgeKey);
193
+ edges.push([a, b]);
196
194
  });
195
+ return { nodes, edges, symbolCount, isLarge };
196
+ };
197
+ export const buildCommunityCsr = (projection) => {
198
+ const adjacency = Array.from({ length: projection.nodes.length }, () => new Set());
199
+ for (const [sourceIndex, targetIndex] of projection.edges) {
200
+ adjacency[sourceIndex].add(targetIndex);
201
+ adjacency[targetIndex].add(sourceIndex);
202
+ }
203
+ const edgeTraversalCount = adjacency.reduce((count, neighbors) => count + neighbors.size, 0);
204
+ const indptr = new BigUint64Array(projection.nodes.length + 1);
205
+ const indices = new BigUint64Array(edgeTraversalCount);
206
+ let cursor = 0;
207
+ for (let nodeIndex = 0; nodeIndex < adjacency.length; nodeIndex++) {
208
+ indptr[nodeIndex] = BigInt(cursor);
209
+ const neighbors = [...adjacency[nodeIndex]].sort((a, b) => a - b);
210
+ for (const neighbor of neighbors) {
211
+ indices[cursor++] = BigInt(neighbor);
212
+ }
213
+ }
214
+ indptr[projection.nodes.length] = BigInt(cursor);
215
+ return { indptr, indices };
216
+ };
217
+ export const buildGraphologyGraph = (projection) => {
218
+ const GraphCtor = Graph;
219
+ const graph = new GraphCtor({ type: 'undirected', allowSelfLoops: false });
220
+ for (const node of projection.nodes) {
221
+ graph.addNode(node.id, {
222
+ name: node.name,
223
+ filePath: node.filePath,
224
+ type: node.type,
225
+ });
226
+ }
227
+ for (const [sourceIndex, targetIndex] of projection.edges) {
228
+ graph.addEdge(projection.nodes[sourceIndex].id, projection.nodes[targetIndex].id);
229
+ }
197
230
  return graph;
198
231
  };
232
+ const isCommunitySymbol = (node) => node.label === 'Function' ||
233
+ node.label === 'Class' ||
234
+ node.label === 'Method' ||
235
+ node.label === 'Interface';
236
+ const isClusteringRelationship = (type) => type === 'CALLS' || type === 'EXTENDS' || type === 'IMPLEMENTS';
237
+ const runCommunityEngine = async (graph, projection, engineRequested, options, onProgress) => {
238
+ if (engineRequested === 'graphology') {
239
+ return runGraphologyLeiden(graph, projection.isLarge, engineRequested);
240
+ }
241
+ try {
242
+ return await runIcebugLeiden(projection, engineRequested, options);
243
+ }
244
+ catch (error) {
245
+ const fallbackReason = error instanceof Error ? error.message : String(error);
246
+ onProgress?.(`Icebug community engine unavailable, falling back to Graphology: ${fallbackReason}`, 35);
247
+ const fallback = await runGraphologyLeiden(graph, projection.isLarge, engineRequested);
248
+ return { ...fallback, fallbackReason };
249
+ }
250
+ };
251
+ const runGraphologyLeiden = async (graph, isLarge, engineRequested) => {
252
+ try {
253
+ const details = await Promise.race([
254
+ Promise.resolve(leiden.detailed(graph, {
255
+ resolution: isLarge ? 2.0 : 1.0,
256
+ maxIterations: isLarge ? 3 : 0,
257
+ rng: createSeededRng(LEIDEN_SEED),
258
+ })),
259
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Leiden timeout')), LEIDEN_TIMEOUT_MS)),
260
+ ]);
261
+ return { ...details, engine: 'graphology', engineRequested };
262
+ }
263
+ catch (e) {
264
+ if (e.message !== 'Leiden timeout') {
265
+ throw e;
266
+ }
267
+ // Fallback: assign all nodes to community 0
268
+ const communities = {};
269
+ graph.forEachNode((node) => {
270
+ communities[node] = 0;
271
+ });
272
+ return {
273
+ communities,
274
+ count: 1,
275
+ modularity: 0,
276
+ engine: 'graphology',
277
+ engineRequested,
278
+ fallbackReason: 'Graphology Leiden timeout',
279
+ };
280
+ }
281
+ };
282
+ const runIcebugLeiden = async (projection, engineRequested, options) => {
283
+ const csr = buildCommunityCsr(projection);
284
+ const nativeResult = await runIcebugWorker(projection.nodes.length, csr, options);
285
+ const partition = nativeResult.partition;
286
+ if (!Number.isFinite(nativeResult.modularity)) {
287
+ throw new Error('optional icebug modularity was not finite');
288
+ }
289
+ if (partition.length !== projection.nodes.length ||
290
+ partition.some((community) => !Number.isSafeInteger(community))) {
291
+ throw new Error(`optional icebug partition was malformed for ${projection.nodes.length} projected nodes`);
292
+ }
293
+ const communities = normalizePartition(projection, partition);
294
+ return {
295
+ communities,
296
+ count: new Set(Object.values(communities)).size,
297
+ modularity: nativeResult.modularity,
298
+ engine: 'icebug',
299
+ engineRequested,
300
+ };
301
+ };
302
+ const runIcebugWorker = (nodeCount, csr, options) => {
303
+ const threads = options.icebug?.threads ?? 1;
304
+ if (!Number.isSafeInteger(threads) || threads !== 1) {
305
+ throw new Error('optional icebug engine currently requires deterministic threads=1');
306
+ }
307
+ if (options.icebug?.randomize === true) {
308
+ throw new Error('optional icebug engine currently requires randomize=false');
309
+ }
310
+ const worker = new Worker(ICEBUG_WORKER_SOURCE, {
311
+ eval: true,
312
+ workerData: {
313
+ nodeCount,
314
+ indices: csr.indices,
315
+ indptr: csr.indptr,
316
+ threads,
317
+ seed: options.icebug?.seed ?? LEIDEN_SEED,
318
+ iterations: options.icebug?.iterations ?? 4,
319
+ gamma: options.icebug?.gamma ?? 1.0,
320
+ randomize: options.icebug?.randomize ?? false,
321
+ },
322
+ });
323
+ return new Promise((resolve, reject) => {
324
+ let settled = false;
325
+ const timeout = setTimeout(() => {
326
+ settled = true;
327
+ void worker.terminate();
328
+ reject(new Error(`optional icebug community engine timed out after ${ICEBUG_TIMEOUT_MS}ms`));
329
+ }, ICEBUG_TIMEOUT_MS);
330
+ worker.once('message', (message) => {
331
+ settled = true;
332
+ clearTimeout(timeout);
333
+ void worker.terminate();
334
+ if (message.ok === true) {
335
+ resolve(message);
336
+ }
337
+ else {
338
+ reject(new Error(message.error));
339
+ }
340
+ });
341
+ worker.once('error', (error) => {
342
+ settled = true;
343
+ clearTimeout(timeout);
344
+ void worker.terminate();
345
+ reject(error);
346
+ });
347
+ worker.once('exit', (code) => {
348
+ if (settled)
349
+ return;
350
+ clearTimeout(timeout);
351
+ if (code === 0) {
352
+ reject(new Error('optional icebug worker exited before returning a partition'));
353
+ return;
354
+ }
355
+ reject(new Error(`optional icebug worker exited with code ${code}`));
356
+ });
357
+ });
358
+ };
359
+ const ICEBUG_WORKER_SOURCE = `
360
+ const { parentPort, workerData } = require('node:worker_threads');
361
+
362
+ const isNumericArrayLike = (value) =>
363
+ typeof value === 'object' &&
364
+ value !== null &&
365
+ 'length' in value &&
366
+ typeof value.length === 'number';
367
+
368
+ const readPartition = (runner) => {
369
+ const candidates = [
370
+ typeof runner.getPartition === 'function' ? runner.getPartition() : runner.partition,
371
+ typeof runner.getCommunities === 'function' ? runner.getCommunities() : undefined,
372
+ typeof runner.getMembership === 'function' ? runner.getMembership() : undefined,
373
+ typeof runner.getMemberships === 'function' ? runner.getMemberships() : undefined,
374
+ ];
375
+
376
+ for (const candidate of candidates) {
377
+ if (isNumericArrayLike(candidate)) {
378
+ return Array.from(candidate, Number);
379
+ }
380
+ }
381
+
382
+ throw new Error('optional icebug ParallelLeidenView did not expose a partition array');
383
+ };
384
+
385
+ const readModularity = (runner) => {
386
+ if (typeof runner.getModularity === 'function') return runner.getModularity();
387
+ if (typeof runner.modularity === 'function') return runner.modularity();
388
+ if (typeof runner.modularity === 'number') return runner.modularity;
389
+ return 0;
390
+ };
391
+
392
+ (async () => {
393
+ const imported = await import('icebug');
394
+ const icebug = imported.default ?? imported;
395
+ const fromCSR = icebug.Graph?.fromCSR;
396
+ const ParallelLeidenView = icebug.community?.ParallelLeidenView;
397
+ if (!fromCSR || !ParallelLeidenView) {
398
+ throw new Error('optional icebug module does not expose Graph.fromCSR/ParallelLeidenView');
399
+ }
400
+
401
+ if (typeof icebug.setNumberOfThreads !== 'function' || typeof icebug.setSeed !== 'function') {
402
+ throw new Error('optional icebug module does not expose deterministic thread/seed controls');
403
+ }
404
+ icebug.setNumberOfThreads(workerData.threads);
405
+ icebug.setSeed(workerData.seed, false);
406
+
407
+ const nativeGraph = fromCSR(workerData.nodeCount, false, workerData.indices, workerData.indptr);
408
+ let runner;
409
+ try {
410
+ runner = new ParallelLeidenView(nativeGraph, {
411
+ iterations: workerData.iterations,
412
+ gamma: workerData.gamma,
413
+ randomize: workerData.randomize,
414
+ });
415
+ } catch {
416
+ runner = new ParallelLeidenView(
417
+ nativeGraph,
418
+ workerData.iterations,
419
+ workerData.gamma,
420
+ workerData.randomize,
421
+ );
422
+ }
423
+
424
+ if (typeof runner.run !== 'function') {
425
+ throw new Error('optional icebug ParallelLeidenView does not expose run()');
426
+ }
427
+
428
+ runner.run();
429
+ parentPort.postMessage({
430
+ ok: true,
431
+ partition: readPartition(runner),
432
+ modularity: readModularity(runner),
433
+ });
434
+ })().catch((error) => {
435
+ parentPort.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) });
436
+ });
437
+ `;
438
+ const normalizePartition = (projection, partition) => {
439
+ const remap = new Map();
440
+ const communities = {};
441
+ for (let index = 0; index < projection.nodes.length; index++) {
442
+ const rawCommunity = String(partition[index]);
443
+ let communityId = remap.get(rawCommunity);
444
+ if (communityId === undefined) {
445
+ communityId = remap.size;
446
+ remap.set(rawCommunity, communityId);
447
+ }
448
+ communities[projection.nodes[index].id] = communityId;
449
+ }
450
+ return communities;
451
+ };
199
452
  // ============================================================================
200
453
  // HELPER: Create community nodes with heuristic labels
201
454
  // ============================================================================
@@ -44,6 +44,7 @@
44
44
  * ancestor scope, and if THAT produces nothing either the edge is
45
45
  * skipped (with a count returned in `EmitStats.skippedNoCaller`).
46
46
  */
47
+ import { toZeroBasedLine } from './utils/line-base.js';
47
48
  /**
48
49
  * Drain `referenceIndex.bySourceScope` into graph edges.
49
50
  *
@@ -95,8 +96,8 @@ export function emitScopeGraph(input) {
95
96
  properties: {
96
97
  name: scope.kind,
97
98
  filePath: scope.filePath,
98
- startLine: scope.range.startLine,
99
- endLine: scope.range.endLine,
99
+ startLine: toZeroBasedLine(scope.range.startLine),
100
+ endLine: toZeroBasedLine(scope.range.endLine),
100
101
  description: `Scope: ${scope.kind}`,
101
102
  },
102
103
  });