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
package/README.md CHANGED
@@ -163,6 +163,16 @@ GitNexus builds a complete knowledge graph of your codebase through a multi-phas
163
163
 
164
164
  The result is a **LadybugDB graph database** stored locally in `.gitnexus/` with full-text search and semantic embeddings.
165
165
 
166
+ ### Experimental community detection engine
167
+
168
+ Community detection uses the bundled Graphology Leiden implementation by default. To test the #2337 Icebug migration path without changing default analyze behavior, set:
169
+
170
+ ```bash
171
+ GITNEXUS_COMMUNITY_ENGINE=icebug npx gitnexus analyze
172
+ ```
173
+
174
+ Supported values are `graphology`, `icebug`, and `auto`. The Icebug path is an experimental probe: GitNexus does not bundle an Icebug native package yet, and if a separately resolvable module is unavailable or its API does not match the expected `Graph.fromCSR` / `ParallelLeidenView` shape, analyze falls back to Graphology and reports the fallback in progress output. Today `auto` is behaviorally identical to `icebug`: both try Icebug and fall back to Graphology, while `graphology` skips the Icebug probe entirely.
175
+
166
176
  ## MCP Tools
167
177
 
168
178
  Your AI agent gets **17 tools** (15 per-repo + 2 group) automatically:
@@ -220,6 +230,7 @@ gitnexus analyze [path] # Index a repository (or update stale index)
220
230
  gitnexus analyze --repair-fts # Fast path: rebuild/verify only FTS indexes on existing index data
221
231
  gitnexus analyze --force # Full rebuild: re-parse + graph rebuild + FTS rebuild
222
232
  gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
233
+ gitnexus embeddings install # Fetch the optional local embedding stack on demand (--cuda, --force)
223
234
  gitnexus analyze --skills # Generate repo-specific skill files from detected communities
224
235
  gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
225
236
  gitnexus analyze --skip-skills # Skip installing .claude/skills/gitnexus/ skill files
@@ -420,18 +431,40 @@ If `npm install -g gitnexus` fails on native modules:
420
431
  npm install -g gitnexus
421
432
  ```
422
433
 
434
+ ### Installation fails behind an HTTP proxy (`onnxruntime-node` postinstall)
435
+
436
+ `onnxruntime-node`'s postinstall downloads optional CUDA GPU binaries from `api.nuget.org` — outside the npm registry, so registry mirrors don't cover it, and its proxy layer (`global-agent`) ignores the standard `HTTP_PROXY`/`HTTPS_PROXY` variables and rejects 302 redirects ([#2370](https://github.com/abhigyanpatwari/GitNexus/issues/2370)).
437
+
438
+ Since the packages are optional dependencies, a failed download no longer breaks `npm install -g gitnexus` — npm skips the embedding stack and everything else works. The stack then **self-heals on demand**: the first `gitnexus analyze --embeddings` (or an explicit `gitnexus embeddings install`) fetches it through your configured npm registry — mirrors and proxies apply, no NuGet download involved — into `~/.gitnexus/embedding-runtime`.
439
+
440
+ ```bash
441
+ # heal a proxy-degraded install manually (CPU embeddings; registry-only)
442
+ gitnexus embeddings install
443
+
444
+ # reinstall into the prefix even when the stack already resolves
445
+ gitnexus embeddings install --force
446
+
447
+ # CUDA GPU hosts: also fetch GPU binaries (NuGet; set the proxy global-agent reads)
448
+ GLOBAL_AGENT_HTTPS_PROXY=<proxy-url> gitnexus embeddings install --cuda
449
+ ```
450
+
451
+ The prefix defaults to `~/.gitnexus/embedding-runtime`; set `GITNEXUS_EMBEDDING_RUNTIME_DIR` to install it elsewhere (e.g. a writable path in a container).
452
+
453
+ > **Node requirement for the on-demand prefix:** the self-heal loads the prefixed packages via `module.registerHooks`, available on Node **≥ 22.15** (on the 22.x line) or **≥ 23.5** (on the 23.x line). On an older Node the packages install but can't be loaded from the prefix — reinstall them into the install itself instead (works on every supported Node): `ONNXRUNTIME_NODE_INSTALL=skip npm install -g gitnexus` (Windows: `set ONNXRUNTIME_NODE_INSTALL=skip && npm install -g gitnexus`). Skipping only the CUDA download keeps full CPU embeddings (CPU embeddings don't need it). Check the result any time with `gitnexus doctor` (Embeddings → Support line).
454
+
423
455
  ### Analyze warns about unavailable FTS or VECTOR extensions
424
456
 
425
- GitNexus uses optional DuckDB extensions for BM25 and vector search. The `gitnexus serve` and MCP read paths only ever try to `LOAD` the extensions — they never block on a network install. The `analyze` command, by default, attempts one bounded out-of-process `INSTALL` if `LOAD` fails and proceeds even when that install times out, so the index is always written to disk; BM25/vector search degrade gracefully until the extensions become available.
457
+ GitNexus uses optional DuckDB extensions for BM25 and vector search. The `gitnexus serve` and MCP read paths only ever try to `LOAD` the extensions — they never block on a network install. The `analyze` command, by default, attempts one bounded out-of-process install if `LOAD` fails (a plain `INSTALL` to download a missing extension, escalating to `FORCE INSTALL` only when the `LOAD` error shows the existing file is broken or truncated, so a permanent non-file failure does not re-download on every run) and proceeds even when that install times out, so the index is always written to disk; BM25/vector search degrade gracefully until the extensions become available.
426
458
 
427
459
  Configure the behavior with these environment variables:
428
460
 
429
461
  | Variable | Values | Default | Effect |
430
462
  | -------------------------------------------- | ---------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
431
- | `GITNEXUS_LBUG_EXTENSION_INSTALL` | `auto`, `load-only`, `never` | `auto` | `auto` runs one bounded INSTALL if LOAD fails. `load-only` only uses already-installed extensions (recommended for offline / firewalled environments). `never` skips optional extensions entirely. |
432
- | `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` | positive integer | `15000` | Wall-clock budget for the out-of-process `INSTALL` child before it is killed. |
463
+ | `GITNEXUS_LBUG_EXTENSION_INSTALL` | `auto`, `load-only`, `never` | `auto` | `auto` runs one bounded install if LOAD fails — a plain `INSTALL`, escalating to `FORCE INSTALL` only when the LOAD error shows the present extension file is broken. `load-only` only uses already-installed extensions (recommended for offline / firewalled environments). `never` skips optional extensions entirely. |
464
+ | `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` | positive integer | `15000` | Wall-clock budget for the out-of-process extension-install child before it is killed. |
433
465
  | `GITNEXUS_FTS_STEMMER` | supported LadybugDB stemmer | `porter` | Stemmer used when rebuilding BM25/FTS indexes. Use `none` for CJK-heavy repositories, or a language stemmer such as `german`, `french`, or `spanish` when that better matches repository comments and identifiers. Re-run `gitnexus analyze --repair-fts` after changing it. |
434
466
  | `GITNEXUS_FTS_CJK_SEGMENTATION` | `none`, `bigram` | `none` | `bigram` inserts overlapping character-bigram boundaries into Chinese/Japanese Han-ideograph spans in `content`/`description` before FTS indexing, so LadybugDB's space-only tokenizer can see sub-phrase word boundaries. Scoped to CJK Unified Ideographs only — Japanese Hiragana/Katakana and Korean Hangul are not currently segmented. Unlike `GITNEXUS_FTS_STEMMER`, this rewrites stored text — enabling it on an already-indexed repo requires a full `gitnexus analyze --force`; neither `--repair-fts` nor a plain incremental `analyze` applies it to previously-indexed files. Set the same value wherever `analyze` and search-serving processes (CLI query, MCP server, web server) run. |
467
+ | `GITNEXUS_COMMUNITY_ENGINE` | `graphology`, `icebug`, `auto` | `graphology` | Community-detection engine used during analyze. `graphology` uses the bundled default path. `icebug` and `auto` currently behave identically: both try the experimental Icebug CSR path and fall back to Graphology if the optional native module is unavailable or incompatible. |
435
468
  | `GITNEXUS_WAL_CHECKPOINT_THRESHOLD` | integer `>= -1` | `67108864` (64 MiB) | LadybugDB WAL auto-checkpoint threshold during analyze (bytes). Auto-checkpoint remains enabled; `-1` keeps Ladybug's stock ~16 MiB. Larger thresholds reduce checkpoint frequency but increase the WAL size at rotation time — choose a smaller value on disk-constrained environments. |
436
469
 
437
470
  ```bash
@@ -502,6 +535,8 @@ Three env vars expose the pool's resilience layers (respawn budget, cumulative-t
502
535
  | `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT` | `3` | Max replacement spawns per slot before the slot is dropped from the active rotation. |
503
536
  | `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs` | Total retry wall-time budget per job before quarantining. Bounds exponentially-growing retry waits. |
504
537
  | `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)` | Per-slot consecutive deaths before the pool's circuit breaker trips. After tripping, dispatches require a fresh pool. |
538
+ | `GITNEXUS_WORKER_SHUTDOWN_DRAIN_MS` | `30000` | Max wait at pool shutdown for a retired worker still inside native code — terminated at its next JS-safe point instead of mid-native-call, which would abort the process (`Napi::Error`, #2432). |
539
+ | `GITNEXUS_CPP_CAPTURE_BUDGET_MS` | `20000` | Per-file wall-clock budget for C++ capture extraction; on breach the file keeps partial captures with a warning (#2432). `0` expires immediately. |
505
540
 
506
541
  ### Graph cleanup tuning
507
542
 
@@ -513,17 +548,26 @@ After scope resolution, analyze prunes inert block-local value symbols (a functi
513
548
 
514
549
  Programmatic callers can pass `keepLocalValueSymbols: true` in `PipelineOptions` instead of setting the env var.
515
550
 
516
- ### Hook augmentation/notifications are silently skipped
551
+ ### Hook augmentation and skip diagnostics
517
552
 
518
- The Claude Code / Antigravity hooks intentionally stay **silent** on normal skip
553
+ The Claude Code / Antigravity hooks keep their **stderr** silent on normal skip
519
554
  paths so strict hook runners (e.g. Codex `PreToolUse`) never see unexpected
520
- output. A search may not be augmented — or a stale-index reminder may not appear
521
- on stderr — when the GitNexus MCP server owns the repo DB, when the DB-lock probe
522
- times out and fails closed, or when the index is already current.
523
-
524
- To see why a hook skipped, set `GITNEXUS_DEBUG=1` and re-run the action the hook
525
- writes the reason (e.g. `[GitNexus] augment skipped: MCP server owns DB`) and the
526
- stale-index hint to its stderr:
555
+ diagnostic output.
556
+
557
+ When a GitNexus process holds the repo DB write lock (the common case the MCP
558
+ server is running, or the DB-lock probe timed out and failed closed), the local
559
+ CLI `augment` can't run (LadybugDB is single-writer). Rather than drop the
560
+ augmentation, the hook hands the agent a short, conditional MCP-query hint on
561
+ stdout (the sanctioned `additionalContext` channel) — _"if the GitNexus MCP tools
562
+ are live in this session, call `query` …"_ — so an agent that has the tools can
563
+ still fetch graph-ranked context. The hint is throttled to at most once per repo
564
+ per window (`GITNEXUS_MCP_HINT_THROTTLE_MS`, default 10 min; `0` disables), so an
565
+ owner-locked session isn't nudged on every search. A stale-index reminder, or an
566
+ already-current index, stays silent.
567
+
568
+ To see why a hook skipped the CLI augment, set `GITNEXUS_DEBUG=1` and re-run the
569
+ action — the hook writes the reason (e.g. `[GitNexus] augment skipped: MCP server
570
+ owns DB`) and the stale-index hint to its stderr:
527
571
 
528
572
  ```bash
529
573
  GITNEXUS_DEBUG=1 <your command> # surfaces hook skip/diagnostic reasons on stderr
@@ -12,13 +12,14 @@ import os from 'os';
12
12
  import { spawn } from 'child_process';
13
13
  import v8 from 'v8';
14
14
  import cliProgress from 'cli-progress';
15
- import { isLbugReady } from '../core/lbug/lbug-adapter.js';
15
+ import { isLbugReady, LbugWipeError } from '../core/lbug/lbug-adapter.js';
16
16
  import { boundedCheckpointBeforeExit } from '../core/lbug/shutdown-helpers.js';
17
- import { isLbugCheckpointIoError, isWalCorruptionError, parseWalCheckpointThreshold, WAL_RECOVERY_SUGGESTION, } from '../core/lbug/lbug-config.js';
17
+ import { getOsPageSize, isLbugCheckpointIoError, isLbugPageSizeFrameError, isPageSizeAwareLadybug, isWalCorruptionError, parseWalCheckpointThreshold, WAL_RECOVERY_SUGGESTION, } from '../core/lbug/lbug-config.js';
18
18
  import { getStoragePaths, getGlobalRegistryPath, RegistryNameCollisionError, AnalysisNotFinalizedError, assertAnalysisFinalized, } from '../storage/repo-manager.js';
19
19
  import { getGitRoot, hasGitDir, getDefaultBranch } from '../storage/git.js';
20
20
  import { loadAnalyzeConfig, mergeAnalyzeOptions, resolveDefaultBranch, validateBranchName, GitNexusRcError, } from './analyze-config.js';
21
21
  import { runFullAnalysis } from '../core/run-analyze.js';
22
+ import { getRuntimeFingerprint } from '../core/platform/capabilities.js';
22
23
  import { getMaxFileSizeBannerMessage } from '../core/ingestion/utils/max-file-size.js';
23
24
  import { warnMissingOptionalGrammars, getOptionalGrammarExtensions } from './optional-grammars.js';
24
25
  import { glob } from 'glob';
@@ -27,8 +28,9 @@ import { cliError } from './cli-message.js';
27
28
  import { EMBEDDING_DIMS_ERROR, normalizeEmbeddingDims } from './embedding-dims.js';
28
29
  import { formatElapsed } from './format-elapsed.js';
29
30
  import { isHfDownloadFailure } from '../core/embeddings/hf-env.js';
30
- import { safeUrl } from '../core/embeddings/http-client.js';
31
- import { isLocalEmbeddingRuntimeBlockerMessage } from '../core/embeddings/runtime-support.js';
31
+ import { isHttpEmbeddingDimsError, isHttpEmbeddingError, isHttpMode, safeUrl, } from '../core/embeddings/http-client.js';
32
+ import { isLocalEmbeddingRuntimeBlockerMessage, isMissingLocalEmbeddingStackMessage, localEmbeddingPrefixUnloadableMessage, localEmbeddingStackMissingMessage, } from '../core/embeddings/runtime-support.js';
33
+ import { ANALYZE_EMBEDDING_INSTALL_TIMEOUT_MS, getEmbeddingInstallTimeoutMs, getEmbeddingRuntimeDir, installEmbeddingRuntime, isPrefixRuntimeLoadable, resolveEmbeddingRuntime, } from '../core/embeddings/runtime-install.js';
32
34
  import { warnIfNpm11NpxRisk } from './resolve-invocation.js';
33
35
  // Capture stderr.write at module load BEFORE anything (LadybugDB native
34
36
  // init, progress bar, console redirection) can monkey-patch it. The
@@ -797,6 +799,52 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
797
799
  console.log(' Note: custom HTTP embeddings require BOTH --embedding-base-url and --embedding-model ' +
798
800
  '(or the matching env vars). Falling back to local ONNX embeddings.\n');
799
801
  }
802
+ // On-demand embedding runtime (#2370): when the optional stack was pruned at
803
+ // install time (proxy-blocked NuGet download in onnxruntime-node's
804
+ // postinstall), heal it here instead of failing later in the pipeline. The
805
+ // install goes through the user's npm registry config (mirrors/proxies
806
+ // apply) with --ignore-scripts, so no NuGet download is attempted. Runs
807
+ // before bar.start() like the sibling validations above.
808
+ if (embeddingsEnabled && !isHttpMode()) {
809
+ const resolved = resolveEmbeddingRuntime();
810
+ // Resolved-but-unloadable (a populated prefix on a Node with no
811
+ // module.registerHooks), or nothing installed on such a Node: fail fast with
812
+ // capability guidance instead of dying mid-pipeline over an unusable prefix
813
+ // or downloading a runtime the loader can't reach. A package-sourced stack
814
+ // never needs the hook, so it is excluded. --embeddings was explicitly
815
+ // requested and this failure is deterministic, so fail fast rather than
816
+ // silently degrading to BM25 (distinct from a transient install timeout).
817
+ if (!isPrefixRuntimeLoadable() && (resolved === null || resolved.source === 'runtime-prefix')) {
818
+ cliError(` ${localEmbeddingPrefixUnloadableMessage().replace(/\n/g, '\n ')}\n`, {
819
+ recoveryHint: 'local-embedding-stack-missing',
820
+ });
821
+ process.exitCode = 1;
822
+ return;
823
+ }
824
+ // On-demand embedding runtime (#2370): when the optional stack was pruned at
825
+ // install time (proxy-blocked NuGet download in onnxruntime-node's
826
+ // postinstall), heal it here instead of failing later in the pipeline. The
827
+ // install goes through the user's npm registry config (mirrors/proxies
828
+ // apply) with --ignore-scripts, so no NuGet download is attempted.
829
+ if (resolved === null) {
830
+ console.log(` Local embedding runtime is not installed (optional packages were skipped at install time).\n` +
831
+ ` Downloading it now from your npm registry into ${getEmbeddingRuntimeDir()} …\n` +
832
+ ` (one-time; rerun manually anytime with \`gitnexus embeddings install\`)\n`);
833
+ try {
834
+ // Short deadline (env override still wins): analyze is interactive, so a
835
+ // blackholed proxy must not stall the whole index run for the 10-minute
836
+ // default — fail over to the guidance below instead.
837
+ await installEmbeddingRuntime({}, getEmbeddingInstallTimeoutMs(ANALYZE_EMBEDDING_INSTALL_TIMEOUT_MS));
838
+ console.log(' Embedding runtime installed.\n');
839
+ }
840
+ catch (err) {
841
+ cliError(` Could not install the embedding runtime: ${err instanceof Error ? err.message : String(err)}\n\n` +
842
+ ` ${localEmbeddingStackMissingMessage().replace(/\n/g, '\n ')}\n`, { recoveryHint: 'local-embedding-stack-missing' });
843
+ process.exitCode = 1;
844
+ return;
845
+ }
846
+ }
847
+ }
800
848
  if (options.repairFts && options.force) {
801
849
  cliError(' Cannot combine `--repair-fts` with `--force`. ' +
802
850
  'Use `--repair-fts` for fast FTS-only repair, or `--force` for a full rebuild.\n');
@@ -1190,6 +1238,63 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
1190
1238
  process.exitCode = 1;
1191
1239
  return;
1192
1240
  }
1241
+ // DB-family wipe failure (#2409, tri-review 4669518496 P2-4): the rebuild
1242
+ // could not verify the LadybugDB file family was removed — usually another
1243
+ // process (MCP server, serve worker, antivirus) holding the index open.
1244
+ // Keyed on the error *type* (repo norm from #2385), never message text.
1245
+ // The message itself is fully self-contained (survivor paths + stop-MCP /
1246
+ // AV-exclusion / re-run guidance) because the serve worker forwards only
1247
+ // `err.message` over IPC — this branch just renders it without the
1248
+ // raw-stack fallback below.
1249
+ if (err instanceof LbugWipeError) {
1250
+ cliError(` ${msg.replace(/\n/g, '\n ')}\n`, {
1251
+ recoveryHint: 'lbug-wipe-failed',
1252
+ });
1253
+ process.exitCode = 1;
1254
+ return;
1255
+ }
1256
+ // Buffer-manager frame-release failure on non-4K-page kernels (#1231).
1257
+ // LadybugDB <= 0.17.x assumed 4 KiB OS pages when releasing evicted
1258
+ // frames; Raspberry Pi 5 (16 KiB kernel pages) and other arm64 systems
1259
+ // crash mid-COPY with a raw native message. 0.18.0 detects the page size
1260
+ // at runtime, so the actionable fix depends on which side of that
1261
+ // boundary the installed @ladybugdb/core is.
1262
+ if (isLbugPageSizeFrameError(err)) {
1263
+ const pageSize = getOsPageSize();
1264
+ const ladybug = getRuntimeFingerprint().ladybugdb;
1265
+ const pageLine = pageSize !== undefined && pageSize !== 4096
1266
+ ? ` Detected OS page size: ${pageSize} bytes (non-4K — e.g. Raspberry Pi 5 16K kernel, Asahi Linux).\n`
1267
+ : '';
1268
+ // The upgrade variant must not assert version facts about an unknown
1269
+ // version — mirror the doctor-side wording rule (#2424 review R2).
1270
+ const upgradeIntro = ladybug === undefined
1271
+ ? ` The installed @ladybugdb/core version is unknown — it may predate the\n` +
1272
+ ` runtime OS-page-size detection added in 0.18.0.\n`
1273
+ : ` The installed @ladybugdb/core (${ladybug}) assumes 4 KiB pages in its buffer\n` +
1274
+ ` manager.\n`;
1275
+ const guidance = isPageSizeAwareLadybug(ladybug)
1276
+ ? ` The installed @ladybugdb/core (${ladybug}) already detects the OS page size at runtime,\n` +
1277
+ ` so this configuration was expected to work. Please report it:\n` +
1278
+ ` https://github.com/abhigyanpatwari/GitNexus/issues/1231\n` +
1279
+ ` and include: gitnexus --version, node --version, getconf PAGE_SIZE, uname -a,\n` +
1280
+ ` and the full error message above.\n`
1281
+ : upgradeIntro +
1282
+ ` Upgrade GitNexus to a release that bundles @ladybugdb/core >= 0.18.0\n` +
1283
+ ` (gitnexus >= 1.6.9), which detects the OS page size at runtime:\n` +
1284
+ ` npm install -g gitnexus@latest\n` +
1285
+ ` Last-resort workaround on Raspberry Pi 5: boot the 4 KiB-page kernel\n` +
1286
+ ` (config.txt: kernel=kernel8.img), at the cost of Pi 5 optimizations.\n`;
1287
+ // Embed the raw native text (indented, no stack) so "the full error
1288
+ // message above" is fulfillable — same idiom as the LbugWipeError
1289
+ // branch. The errno suffix and the 0.18.0 guard's frame/granule numbers
1290
+ // are the discriminating triage content (#2424 review P2).
1291
+ cliError(` LadybugDB's buffer manager failed to release frame memory.\n` +
1292
+ ` ${msg.replace(/\n/g, '\n ')}\n` +
1293
+ pageLine +
1294
+ guidance, { recoveryHint: 'lbug-page-size', pageSize, ladybugVersion: ladybug });
1295
+ process.exitCode = 1;
1296
+ return;
1297
+ }
1193
1298
  // Local embedding runtime unsupported on this platform (macOS Intel ships no
1194
1299
  // darwin/x64 ONNX native binding, #1515). The guard threw before importing
1195
1300
  // transformers.js, so this is a clean, actionable GitNexus message. Checked
@@ -1203,10 +1308,66 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
1203
1308
  process.exitCode = 1;
1204
1309
  return;
1205
1310
  }
1311
+ // The optional embedding stack (@huggingface/transformers → onnxruntime-node)
1312
+ // was pruned at install time — usually a proxy-blocked NuGet download during
1313
+ // onnxruntime-node's postinstall (#2370). Checked before the generic
1314
+ // module-not-found "installation may be corrupt" hint below, which would
1315
+ // otherwise misdiagnose a deliberate optional-dependency skip.
1316
+ if (isMissingLocalEmbeddingStackMessage(msg)) {
1317
+ cliError(` ${msg.replace(/\n/g, '\n ')}\n`, {
1318
+ recoveryHint: 'local-embedding-stack-missing',
1319
+ });
1320
+ process.exitCode = 1;
1321
+ return;
1322
+ }
1323
+ // Malformed GITNEXUS_EMBEDDING_DIMS env var (#2385). readConfig() throws a
1324
+ // plain Error (a config mistake, not an endpoint failure), surfacing here from
1325
+ // httpEmbed()->readConfig() inside the analysis run. Show a clean config
1326
+ // message rather than a raw stack dump. The --embedding-dims CLI flag is
1327
+ // validated up front (EMBEDDING_DIMS_ERROR); this covers the env-var path.
1328
+ // Checked before the endpoint/HF branches: it is a plain Error, so
1329
+ // isHttpEmbeddingError() is false and the HF network heuristic must not claim it.
1330
+ if (isHttpEmbeddingDimsError(msg)) {
1331
+ cliError(` ${msg.replace(/\n/g, '\n ')}\n`, {
1332
+ recoveryHint: 'embedding-dims-invalid',
1333
+ });
1334
+ process.exitCode = 1;
1335
+ return;
1336
+ }
1337
+ // Custom HTTP embedding endpoint failure (#2385). When a `--embedding-base-url`
1338
+ // is configured, HTTP mode never downloads a model — so a failure talking to
1339
+ // that endpoint must NOT show the huggingface-download guidance. Keyed on the
1340
+ // error *type* (HttpEmbeddingError), not its message text, so it stays correct
1341
+ // regardless of locale or wording. Checked before the HF branch, whose network
1342
+ // heuristic (`fetch failed` / `ECONNREFUSED`) would otherwise also match a
1343
+ // wrapped endpoint-connection error. The header is deliberately neutral: this
1344
+ // type covers both never-reached failures (connection/timeout/DNS) and
1345
+ // reached-but-failed ones (4xx/5xx, dimension/shape mismatch), so it must not
1346
+ // assert "unreachable". The thrown `msg` carries the specific reason (and the
1347
+ // masked URL where one applies), so it is surfaced verbatim.
1348
+ if (isHttpEmbeddingError(err)) {
1349
+ cliError(` The custom embedding endpoint request failed.\n` +
1350
+ ` ${msg.replace(/\n/g, '\n ')}\n` +
1351
+ ` Suggestions:\n` +
1352
+ ` 1. Verify the endpoint URL is reachable and running ` +
1353
+ `(--embedding-base-url / GITNEXUS_EMBEDDING_URL: host, port, /v1 path).\n` +
1354
+ ` 2. Confirm the model name and embedding dimensions match what the endpoint serves.\n` +
1355
+ ` 3. Re-run without --embeddings to index without vectors.\n`, { recoveryHint: 'http-embedding-endpoint-error' });
1356
+ process.exitCode = 1;
1357
+ return;
1358
+ }
1359
+ // isHttpMode() is a pure presence probe (URL+MODEL) that never throws — a
1360
+ // malformed GITNEXUS_EMBEDDING_DIMS is handled by the dims branch above — so
1361
+ // no defensive try/catch is needed here (#2385).
1362
+ const inHttpMode = isHttpMode();
1206
1363
  // HF download failure — show clean guidance without the raw stack trace.
1207
1364
  // Checked before writeFatalToStderr so the user sees one focused message
1208
1365
  // rather than a stack-trace dump followed by a second remediation block.
1209
- if (isHfDownloadFailure(msg) || msg.includes('Failed to download embedding model')) {
1366
+ // Gated on !inHttpMode: with a custom endpoint configured no model download
1367
+ // is ever attempted, so a network error there is the endpoint's, handled by
1368
+ // the HttpEmbeddingError branch above — never HF's (#2385).
1369
+ if ((isHfDownloadFailure(msg) || msg.includes('Failed to download embedding model')) &&
1370
+ !inHttpMode) {
1210
1371
  cliError(` The embedding model could not be downloaded.\n` +
1211
1372
  ` huggingface.co may be unreachable from your network\n` +
1212
1373
  ` (e.g. behind a corporate proxy or a regional firewall).\n` +
package/dist/cli/clean.js CHANGED
@@ -8,7 +8,7 @@ import fs from 'fs/promises';
8
8
  import path from 'path';
9
9
  import { logger } from '../core/logger.js';
10
10
  import { findRepo, unregisterRepo, listRegisteredRepos, assertSafeStoragePath, getStoragePaths, removeBranchIndex, UnsafeStoragePathError, } from '../storage/repo-manager.js';
11
- import { cleanQuarantinedMissingShadowWals, inspectLbugSidecars, listQuarantinedMissingShadowWals, } from '../core/lbug/sidecar-recovery.js';
11
+ import { cleanParkedLbugSidecars, inspectLbugSidecars, listParkedLbugSidecars, } from '../core/lbug/sidecar-recovery.js';
12
12
  import { t } from './i18n/index.js';
13
13
  export const cleanCommand = async (options) => {
14
14
  // --branch <name>: remove a single non-primary branch's index (#2106 R7).
@@ -62,7 +62,13 @@ export const cleanCommand = async (options) => {
62
62
  }
63
63
  const lbugPath = path.join(repo.storagePath, 'lbug');
64
64
  const state = await inspectLbugSidecars(lbugPath);
65
- const quarantined = await listQuarantinedMissingShadowWals(lbugPath);
65
+ // Single roster authority (this shipping review, FIX 5): the aggregate
66
+ // covers both parked-sidecar families — the timestamped missing-shadow
67
+ // WAL quarantines AND the fixed-name `.dirty-recovery` parks (`.next`
68
+ // residues included) left by a dirty-flag recovery rebuild (#2409). The
69
+ // previous inline concatenations here were how the `.next` residue
70
+ // stayed invisible to this surface.
71
+ const quarantined = await listParkedLbugSidecars(lbugPath);
66
72
  console.log(t('clean.lbugSidecars.state', { state: state.kind }));
67
73
  if (quarantined.length === 0) {
68
74
  console.log(t('clean.lbugSidecars.none'));
@@ -76,8 +82,16 @@ export const cleanCommand = async (options) => {
76
82
  console.log(`\n${t('common.runForceConfirm')}`);
77
83
  return;
78
84
  }
79
- const deleted = await cleanQuarantinedMissingShadowWals(lbugPath);
85
+ const { deleted, failed } = await cleanParkedLbugSidecars(lbugPath);
80
86
  console.log(t('clean.lbugSidecars.deleted', { count: deleted.length }));
87
+ // A locked parked file no longer crashes the clean mid-command (FIX 5)
88
+ // — the rest were deleted above; report what remains and why.
89
+ if (failed.length > 0) {
90
+ console.log(t('clean.lbugSidecars.failed', { count: failed.length }));
91
+ for (const file of failed) {
92
+ console.log(` - ${file}`);
93
+ }
94
+ }
81
95
  return;
82
96
  }
83
97
  // --all flag: clean all indexed repos
@@ -12,7 +12,7 @@ import { type CliMessageKey, type CliMessageVars } from './i18n/index.js';
12
12
  * Consumers can import this type to narrow log-record `recoveryHint`
13
13
  * fields without restating the literal list.
14
14
  */
15
- export type RecoveryHint = 'wal-corruption' | 'wal-checkpoint-threshold' | 'heap-oom-respawn' | 'native-worker-abort' | 'hf-endpoint-unreachable' | 'local-embedding-unsupported' | 'large-repo' | 'npm-resolution' | 'module-not-found' | 'gitnexusrc-invalid' | 'default-branch-invalid';
15
+ export type RecoveryHint = 'wal-corruption' | 'wal-checkpoint-threshold' | 'lbug-wipe-failed' | 'lbug-page-size' | 'heap-oom-respawn' | 'native-worker-abort' | 'hf-endpoint-unreachable' | 'http-embedding-endpoint-error' | 'embedding-dims-invalid' | 'local-embedding-unsupported' | 'local-embedding-stack-missing' | 'large-repo' | 'npm-resolution' | 'module-not-found' | 'gitnexusrc-invalid' | 'default-branch-invalid';
16
16
  /**
17
17
  * Common shape for the optional structured-field bag passed to
18
18
  * `cliError`/`cliWarn`/`cliInfo`. Typed so the `recoveryHint` slot is
@@ -1,3 +1,4 @@
1
+ import { type EmbeddingRuntimeResolution } from '../core/embeddings/runtime-install.js';
1
2
  export declare function displayWidth(value: string): number;
2
3
  export declare function padDisplayEnd(value: string, columns: number): string;
3
4
  /**
@@ -14,8 +15,25 @@ export declare function localEmbeddingDoctorStatus(opts: {
14
15
  httpMode: boolean;
15
16
  platform?: NodeJS.Platform;
16
17
  arch?: NodeJS.Architecture;
18
+ /** Injectable for tests; defaults to probing the real install. */
19
+ resolution?: EmbeddingRuntimeResolution | null;
20
+ /** Injectable for tests; defaults to this Node's registerHooks capability. */
21
+ prefixLoadable?: boolean;
17
22
  }): {
18
23
  status: string;
19
24
  detail: string | null;
20
25
  };
26
+ /**
27
+ * Page-size lines for the `doctor` Runtime section (#1231). Pure so the
28
+ * warning gate can be unit-tested without running the whole command (the
29
+ * `localEmbeddingDoctorStatus` precedent above) — but takes the probed
30
+ * values as plain params rather than injectable probes, because `undefined`
31
+ * is a *meaningful* pageSize state here (probe unavailable / win32) and
32
+ * would collide with a "not provided → use default" DI convention.
33
+ *
34
+ * Returns 0 lines (page size unknown), 1 line (page size), or 2 lines
35
+ * (page size + non-4K warning when the installed @ladybugdb/core does not
36
+ * detect the OS page size at runtime).
37
+ */
38
+ export declare function pageSizeDoctorLines(pageSize: number | undefined, ladybugVersion: string | undefined): string[];
21
39
  export declare const doctorCommand: () => Promise<void>;
@@ -1,9 +1,12 @@
1
1
  import { getRuntimeCapabilities, getRuntimeFingerprint } from '../core/platform/capabilities.js';
2
2
  import { resolveEmbeddingConfig } from '../core/embeddings/config.js';
3
3
  import { isHttpMode } from '../core/embeddings/http-client.js';
4
- import { getLocalEmbeddingRuntimeBlocker } from '../core/embeddings/runtime-support.js';
4
+ import { getLocalEmbeddingRuntimeBlocker, localEmbeddingPrefixUnloadableMessage, localEmbeddingStackMissingMessage, } from '../core/embeddings/runtime-support.js';
5
+ import { isPrefixRuntimeLoadable, resolveEmbeddingRuntime, } from '../core/embeddings/runtime-install.js';
5
6
  import { cudaRedirectDoctorStatus } from '../core/embeddings/onnxruntime-node-resolver.js';
6
- import { checkLbugNative } from '../core/lbug/native-check.js';
7
+ import { checkLbugNative, probeFtsExtensionLoad } from '../core/lbug/native-check.js';
8
+ import { getOsPageSize, isPageSizeAwareLadybug } from '../core/lbug/lbug-config.js';
9
+ import { diagnoseExtensionLoad } from '../core/lbug/extension-load-error.js';
7
10
  import { getExtensionInstallPolicy } from '../core/lbug/extension-loader.js';
8
11
  import { t } from './i18n/index.js';
9
12
  function isCombiningMark(codePoint) {
@@ -64,8 +67,54 @@ export function localEmbeddingDoctorStatus(opts) {
64
67
  if (blocker) {
65
68
  return { status: `✗ local embeddings unavailable on ${platform}/${arch}`, detail: blocker };
66
69
  }
70
+ // The stack is an optionalDependency — npm prunes it when onnxruntime-node's
71
+ // postinstall can't download its CUDA binaries (proxy/firewall, #2370).
72
+ const resolution = opts.resolution !== undefined ? opts.resolution : resolveEmbeddingRuntime();
73
+ if (resolution === null) {
74
+ return {
75
+ status: '✗ optional embedding stack not installed',
76
+ detail: localEmbeddingStackMissingMessage(),
77
+ };
78
+ }
79
+ // A prefix-sourced stack needs module.registerHooks to load; on Node < 22.15 /
80
+ // < 23.5 it is present but unreachable (#2372). Report loadability, not bare
81
+ // presence, so the diagnostic stops claiming a ✓ the loader can't honour.
82
+ const prefixLoadable = opts.prefixLoadable ?? isPrefixRuntimeLoadable();
83
+ if (resolution.source === 'runtime-prefix' && !prefixLoadable) {
84
+ return {
85
+ status: '✗ embedding stack installed in the prefix but not loadable on this Node',
86
+ detail: localEmbeddingPrefixUnloadableMessage(),
87
+ };
88
+ }
67
89
  return { status: '✓ local embeddings supported', detail: null };
68
90
  }
91
+ /**
92
+ * Page-size lines for the `doctor` Runtime section (#1231). Pure so the
93
+ * warning gate can be unit-tested without running the whole command (the
94
+ * `localEmbeddingDoctorStatus` precedent above) — but takes the probed
95
+ * values as plain params rather than injectable probes, because `undefined`
96
+ * is a *meaningful* pageSize state here (probe unavailable / win32) and
97
+ * would collide with a "not provided → use default" DI convention.
98
+ *
99
+ * Returns 0 lines (page size unknown), 1 line (page size), or 2 lines
100
+ * (page size + non-4K warning when the installed @ladybugdb/core does not
101
+ * detect the OS page size at runtime).
102
+ */
103
+ export function pageSizeDoctorLines(pageSize, ladybugVersion) {
104
+ if (pageSize === undefined)
105
+ return [];
106
+ const lines = [` ${padDisplayEnd('page size', 10)}${pageSize}`];
107
+ if (pageSize > 4096 && !isPageSizeAwareLadybug(ladybugVersion)) {
108
+ // Don't assert "< 0.18.0" as fact when the version is unresolvable
109
+ // (#2424 review R2) — name the unknown state instead.
110
+ const versionClause = ladybugVersion === undefined
111
+ ? 'an unknown @ladybugdb/core version (may predate 0.18.0)'
112
+ : `@ladybugdb/core < 0.18.0`;
113
+ lines.push(` ${padDisplayEnd('', 10)}⚠ non-4K page size with ${versionClause} — ` +
114
+ `'gitnexus analyze' may fail during COPY (#1231). Upgrade gitnexus (npm install -g gitnexus@latest).`);
115
+ }
116
+ return lines;
117
+ }
69
118
  export const doctorCommand = async () => {
70
119
  const fingerprint = getRuntimeFingerprint();
71
120
  const capabilities = getRuntimeCapabilities();
@@ -76,6 +125,13 @@ export const doctorCommand = async () => {
76
125
  console.log(` ${label('doctor.labels.node', 10)}${fingerprint.node}`);
77
126
  console.log(` ${label('doctor.labels.gitnexus', 10)}${fingerprint.gitnexus}`);
78
127
  console.log(` ${label('doctor.labels.ladybugdb', 10)}${fingerprint.ladybugdb ?? 'unknown'}`);
128
+ // OS page size next to the LadybugDB version because the two interact:
129
+ // @ladybugdb/core < 0.18.0 assumed 4 KiB pages in its buffer manager and
130
+ // crashes mid-COPY on 16 KiB/64 KiB-page kernels (#1231). Literal label
131
+ // (like the 'native' line below) to avoid adding i18n keys.
132
+ for (const line of pageSizeDoctorLines(getOsPageSize(), fingerprint.ladybugdb)) {
133
+ console.log(line);
134
+ }
79
135
  const nativeCheck = checkLbugNative();
80
136
  if (nativeCheck.ok) {
81
137
  console.log(` ${padDisplayEnd('native', 10)}✓ lbugjs.node loaded`);
@@ -88,7 +144,24 @@ export const doctorCommand = async () => {
88
144
  console.log('');
89
145
  console.log(t('doctor.capabilities'));
90
146
  console.log(` ${label('doctor.labels.graphStore', 18)}${capabilities.graph}`);
91
- console.log(` ${label('doctor.labels.fullTextSearch', 18)}${capabilities.fts}`);
147
+ // Live LOAD probe, not the static platform capability — the static value
148
+ // said "available" while analyze failed to load the extension (#2374).
149
+ const ftsProbe = nativeCheck.ok
150
+ ? await probeFtsExtensionLoad()
151
+ : { loaded: false, reason: 'LadybugDB native module (lbugjs.node) failed to load' };
152
+ console.log(` ${label('doctor.labels.fullTextSearch', 18)}${ftsProbe.loaded ? 'available' : 'unavailable'}`);
153
+ if (!ftsProbe.loaded && ftsProbe.reason) {
154
+ console.log(` ${padDisplayEnd('', 18)}${ftsProbe.reason}`);
155
+ // Add an actionable remedy for recognized failure classes (#2374). The
156
+ // Windows missing-dependency case is the point of this: the raw error 126
157
+ // ("specified module could not be found") is opaque, so name the fix (VC++
158
+ // redist, then OpenSSL) instead of leaving the user to reinstall in vain.
159
+ // `unknown`'s remedy is "run doctor", which would be circular here.
160
+ const { kind, remedy } = diagnoseExtensionLoad(ftsProbe.reason);
161
+ if (kind !== 'unknown') {
162
+ console.log(` ${padDisplayEnd('', 18)}${remedy}`);
163
+ }
164
+ }
92
165
  console.log(` ${label('doctor.labels.vectorIndex', 18)}${capabilities.vector}`);
93
166
  console.log(` ${label('doctor.labels.semanticMode', 18)}${capabilities.semanticMode}`);
94
167
  // Surface the optional-extension install policy so offline users can see
@@ -0,0 +1,12 @@
1
+ export interface EmbeddingsInstallOptions {
2
+ cuda?: boolean;
3
+ force?: boolean;
4
+ }
5
+ /**
6
+ * `gitnexus embeddings install [--cuda] [--force]` — fetch the optional local
7
+ * embedding stack on demand (#2370). Goes through the user's npm registry
8
+ * config (mirrors/proxies apply); with --cuda it additionally runs
9
+ * onnxruntime-node's postinstall to download the CUDA GPU binaries from NuGet
10
+ * (set GLOBAL_AGENT_HTTPS_PROXY behind a proxy).
11
+ */
12
+ export declare const embeddingsInstallCommand: (options?: EmbeddingsInstallOptions) => Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { cliError, cliInfo, cliWarn } from './cli-message.js';
2
+ import { getEmbeddingRuntimeDir, getEmbeddingStackSpecs, installEmbeddingRuntime, isPrefixRuntimeLoadable, resolveEmbeddingRuntime, } from '../core/embeddings/runtime-install.js';
3
+ import { localEmbeddingPrefixUnloadableMessage } from '../core/embeddings/runtime-support.js';
4
+ /**
5
+ * `gitnexus embeddings install [--cuda] [--force]` — fetch the optional local
6
+ * embedding stack on demand (#2370). Goes through the user's npm registry
7
+ * config (mirrors/proxies apply); with --cuda it additionally runs
8
+ * onnxruntime-node's postinstall to download the CUDA GPU binaries from NuGet
9
+ * (set GLOBAL_AGENT_HTTPS_PROXY behind a proxy).
10
+ */
11
+ export const embeddingsInstallCommand = async (options = {}) => {
12
+ const resolved = resolveEmbeddingRuntime();
13
+ if (resolved?.source === 'package' && !options.force) {
14
+ cliInfo('The embedding stack is already installed with gitnexus itself — nothing to do.\n' +
15
+ '(Use --force to install a copy into the runtime prefix anyway.)');
16
+ return;
17
+ }
18
+ const specs = Object.entries(getEmbeddingStackSpecs())
19
+ .map(([name, spec]) => `${name}@${spec}`)
20
+ .join(', ');
21
+ cliInfo(`Installing ${specs} into ${getEmbeddingRuntimeDir()} …`);
22
+ cliInfo(options.cuda
23
+ ? 'CUDA mode: onnxruntime-node will download GPU binaries from NuGet ' +
24
+ '(set GLOBAL_AGENT_HTTPS_PROXY=<proxy-url> behind a proxy).'
25
+ : 'CPU mode: install scripts are skipped — only your npm registry is contacted.');
26
+ try {
27
+ await installEmbeddingRuntime({ cuda: options.cuda, onOutput: (line) => cliInfo(` ${line}`) });
28
+ }
29
+ catch (err) {
30
+ cliError(`${err instanceof Error ? err.message : String(err)}\n`, {
31
+ recoveryHint: 'local-embedding-stack-missing',
32
+ });
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ const postInstall = resolveEmbeddingRuntime();
37
+ if (postInstall === null) {
38
+ cliInfo('✗ Install completed but the stack still does not resolve — check the output above.');
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ if (postInstall.source === 'runtime-prefix' && !isPrefixRuntimeLoadable()) {
43
+ // The packages are in the prefix, but this Node has no module.registerHooks
44
+ // to load them — don't claim readiness the loader can't honour.
45
+ cliWarn(`${localEmbeddingPrefixUnloadableMessage()}\n`);
46
+ return;
47
+ }
48
+ cliInfo('✓ Embedding runtime installed. `gitnexus analyze --embeddings` is ready.');
49
+ };
@@ -17,6 +17,8 @@ const COMMAND_DESCRIPTION_KEYS = {
17
17
  list: 'help.command.list.description',
18
18
  status: 'help.command.status.description',
19
19
  doctor: 'help.command.doctor.description',
20
+ embeddings: 'help.command.embeddings.description',
21
+ 'embeddings install': 'help.command.embeddings.install.description',
20
22
  clean: 'help.command.clean.description',
21
23
  remove: 'help.command.remove.description',
22
24
  wiki: 'help.command.wiki.description',
@@ -143,6 +145,8 @@ const OPTION_DESCRIPTION_KEYS = {
143
145
  'eval-server|-p, --port <port>': 'help.option.port',
144
146
  'eval-server|--host <host>': 'help.option.evalServer.host',
145
147
  'eval-server|--idle-timeout <seconds>': 'help.option.evalServer.idleTimeout',
148
+ 'embeddings install|--cuda': 'help.option.embeddings.install.cuda',
149
+ 'embeddings install|--force': 'help.option.embeddings.install.force',
146
150
  'group create|--force': 'help.option.group.create.force',
147
151
  'group sync|--skip-embeddings': 'help.option.group.sync.skipEmbeddings',
148
152
  'group sync|--exact-only': 'help.option.group.sync.exactOnly',