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
@@ -0,0 +1,372 @@
1
+ /**
2
+ * On-demand install of the optional local embedding stack (#2370).
3
+ *
4
+ * `@huggingface/transformers` and `onnxruntime-node` are optionalDependencies:
5
+ * npm prunes them (instead of failing the whole install) when
6
+ * `onnxruntime-node`'s postinstall cannot download its CUDA binaries from
7
+ * api.nuget.org — common behind HTTP proxies and regional firewalls, where
8
+ * that download ignores standard proxy env vars and 302 redirects.
9
+ *
10
+ * This module heals such an install without a reinstall: it fetches the stack
11
+ * into a user-level runtime prefix (`~/.gitnexus/embedding-runtime`) straight
12
+ * from the user's configured npm registry — honouring their mirror and proxy
13
+ * settings, the part of their network setup that demonstrably works — with
14
+ * `--ignore-scripts`, so no NuGet download is attempted at all. The CPU ONNX
15
+ * binding ships inside the npm tarball; only CUDA GPU acceleration needs the
16
+ * postinstall, and `installEmbeddingRuntime({ cuda: true })` opts into it.
17
+ *
18
+ * Resolution is package-first: a normally-installed stack always wins, and the
19
+ * runtime prefix is only consulted when the bare specifier does not resolve.
20
+ */
21
+ import { createRequire } from 'node:module';
22
+ import { spawn, execFileSync } from 'node:child_process';
23
+ import { pathToFileURL } from 'node:url';
24
+ import { homedir } from 'node:os';
25
+ import { join, resolve } from 'node:path';
26
+ import { logger } from '../logger.js';
27
+ import { getRegisterHooks } from './node-module-compat.js';
28
+ const DEFAULT_EMBEDDING_INSTALL_TIMEOUT_MS = 10 * 60 * 1000;
29
+ /** Shorter deadline for analyze's auto-install (interactive; must not stall the index run). */
30
+ export const ANALYZE_EMBEDDING_INSTALL_TIMEOUT_MS = 2 * 60 * 1000;
31
+ /**
32
+ * Deadline for the on-demand npm install. An explicit
33
+ * `GITNEXUS_EMBEDDING_INSTALL_TIMEOUT_MS` always wins (so a user on a slow
34
+ * mirror can raise it); otherwise `defaultMs` applies. The default is generous
35
+ * (the ONNX stack is a large registry fetch), but latency-sensitive callers
36
+ * (analyze's auto-install) pass a shorter `defaultMs` so a blackholed proxy
37
+ * can't stall the whole run for the full ten minutes. Mirrors
38
+ * `getExtensionInstallTimeoutMs`.
39
+ */
40
+ export const getEmbeddingInstallTimeoutMs = (defaultMs = DEFAULT_EMBEDDING_INSTALL_TIMEOUT_MS) => {
41
+ const raw = process.env.GITNEXUS_EMBEDDING_INSTALL_TIMEOUT_MS;
42
+ const parsed = raw ? Number(raw) : NaN;
43
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultMs;
44
+ };
45
+ /**
46
+ * SIGKILL the npm child and its whole tree. npm spawns a node grandchild, and a
47
+ * plain SIGTERM to the direct child lets the grandchild escape (pr-2169), so on
48
+ * Windows use `taskkill /T /F` (mirrors `killChildTree` in local-cli-client.ts).
49
+ */
50
+ const killNpmChild = (child) => {
51
+ if (process.platform === 'win32' && child.pid !== undefined) {
52
+ try {
53
+ execFileSync('taskkill', ['/T', '/F', '/PID', String(child.pid)], {
54
+ stdio: 'ignore',
55
+ windowsHide: true,
56
+ });
57
+ return;
58
+ }
59
+ catch {
60
+ // Already exited — fall through to child.kill().
61
+ }
62
+ }
63
+ child.kill('SIGKILL');
64
+ };
65
+ const require = createRequire(import.meta.url);
66
+ /** The stack the runtime prefix provides; resolution fallback covers all three. */
67
+ const EMBEDDING_STACK_PACKAGES = [
68
+ '@huggingface/transformers',
69
+ 'onnxruntime-node',
70
+ 'onnxruntime-common',
71
+ ];
72
+ /**
73
+ * User-level prefix the on-demand stack installs into. The env override is
74
+ * `path.resolve`d once here (the single chokepoint) so a relative or empty
75
+ * value can't poison the probes downstream — `createRequire` throws
76
+ * `ERR_INVALID_ARG_VALUE` on a relative anchor, which otherwise made every
77
+ * resolution report "not installed" and reinstall on every run. An empty or
78
+ * whitespace-only value falls through to the default.
79
+ */
80
+ export const getEmbeddingRuntimeDir = () => {
81
+ const override = process.env.GITNEXUS_EMBEDDING_RUNTIME_DIR?.trim();
82
+ return override ? resolve(override) : join(homedir(), '.gitnexus', 'embedding-runtime');
83
+ };
84
+ /**
85
+ * The version specs to install — read from gitnexus' own package.json
86
+ * `optionalDependencies` so the on-demand install can never drift from what a
87
+ * normal install would have provided. (The manifest ships in the tarball even
88
+ * when npm pruned the packages themselves.)
89
+ */
90
+ export const getEmbeddingStackSpecs = () => {
91
+ const manifest = require('../../../package.json');
92
+ const optional = manifest.optionalDependencies ?? {};
93
+ return Object.fromEntries(['@huggingface/transformers', 'onnxruntime-node']
94
+ .filter((name) => optional[name] !== undefined)
95
+ .map((name) => [name, optional[name]]));
96
+ };
97
+ /**
98
+ * Whether a runtime-prefix-sourced stack can actually be loaded on this Node
99
+ * (#2372). The prefix mechanism re-anchors bare specifiers via
100
+ * `module.registerHooks`, absent before Node 22.15 / 23.5 — so on 22.0–22.14 and
101
+ * 23.0–23.4 a populated prefix exists but the ESM loader can never reach it. A
102
+ * package-sourced stack never needs the hook and is unaffected. CLI code
103
+ * consumes this predicate (never the compat module directly) to keep messaging
104
+ * truthful instead of promising a prefix runtime the loader can't use.
105
+ */
106
+ export const isPrefixRuntimeLoadable = () => typeof getRegisterHooks() === 'function';
107
+ /** Resolution anchored inside the runtime prefix (`<dir>/node_modules`). */
108
+ const prefixRequire = () => createRequire(join(getEmbeddingRuntimeDir(), 'noop.js'));
109
+ /**
110
+ * True when BOTH load-bearing stack packages resolve from `req`. Probing
111
+ * `@huggingface/transformers` alone is not enough: an interrupted or partial
112
+ * prefix install (transformers extracted, `onnxruntime-node` not yet) would
113
+ * otherwise read as "installed", suppress the self-heal, and fail later at model
114
+ * load. `onnxruntime-common` stays un-probed — it is a regular dependency the
115
+ * #307 resolver owns, never pruned.
116
+ */
117
+ const stackResolvesFrom = (req) => {
118
+ try {
119
+ req.resolve('@huggingface/transformers');
120
+ req.resolve('onnxruntime-node');
121
+ return true;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ };
127
+ /**
128
+ * Where the embedding stack resolves from, or `null` when it is not (fully)
129
+ * installed. Resolution only — nothing is imported, so this never loads native
130
+ * code and is safe on every platform.
131
+ */
132
+ export const resolveEmbeddingRuntime = () => {
133
+ if (stackResolvesFrom(require))
134
+ return { source: 'package' };
135
+ if (stackResolvesFrom(prefixRequire()))
136
+ return { source: 'runtime-prefix' };
137
+ return null;
138
+ };
139
+ let hookAttempted = false;
140
+ // While set, the resolve hook passes straight through. It guards the
141
+ // `resolveEmbeddingRuntime()` probe inside the onnxruntime-common gate below:
142
+ // today `require.resolve` bypasses these sync hooks, so the probe can't re-enter
143
+ // the chain — but the latch makes that acyclicity STRUCTURAL rather than relying
144
+ // on that (undocumented, version-specific — verified on Node 22.16) behaviour.
145
+ let hookReentrant = false;
146
+ /** Whether the stack itself resolved from the runtime prefix — re-entrancy-guarded. */
147
+ const stackIsPrefixSourced = () => {
148
+ hookReentrant = true;
149
+ try {
150
+ return resolveEmbeddingRuntime()?.source === 'runtime-prefix';
151
+ }
152
+ finally {
153
+ hookReentrant = false;
154
+ }
155
+ };
156
+ /**
157
+ * Idempotently register the resolution fallback that redirects the embedding
158
+ * stack's bare specifiers to the runtime prefix when normal resolution fails.
159
+ * Mirrors the onnxruntime-common fallback hook (#307): try the default
160
+ * resolution first so a real, package-manager-installed copy always wins, and
161
+ * only re-anchor at the prefix on ERR_MODULE_NOT_FOUND.
162
+ *
163
+ * Must be registered BEFORE the CUDA-13 redirect hook
164
+ * (`ensureOnnxRuntimeNodeMatchesSystem`) — `registerHooks` runs the most
165
+ * recently registered hook first, so registering this one earliest makes it
166
+ * the last-resort fallback in the chain.
167
+ */
168
+ export const ensureEmbeddingStackResolvable = () => {
169
+ if (hookAttempted)
170
+ return;
171
+ hookAttempted = true;
172
+ try {
173
+ // Node < 22.15 / < 23.5 (engines floor is >= 22.0.0): no synchronous hooks
174
+ // API. Degrade gracefully — normally-installed stacks still resolve; only
175
+ // the runtime-prefix fallback is unavailable. Reachable now that the import
176
+ // is a namespace access (see node-module-compat.ts) rather than a static
177
+ // named import that would fail at link time.
178
+ const registerHooks = getRegisterHooks();
179
+ if (typeof registerHooks !== 'function')
180
+ return;
181
+ registerHooks({
182
+ resolve(specifier, context, nextResolve) {
183
+ if (hookReentrant || !EMBEDDING_STACK_PACKAGES.includes(specifier)) {
184
+ return nextResolve(specifier, context);
185
+ }
186
+ try {
187
+ return nextResolve(specifier, context);
188
+ }
189
+ catch (err) {
190
+ const code = err?.code;
191
+ // ESM-only allowlist: never add the CJS `MODULE_NOT_FOUND` — that is
192
+ // what keeps the source probe below (which uses `require.resolve`)
193
+ // from feeding its own miss back into the chain.
194
+ if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
195
+ throw err;
196
+ }
197
+ // onnxruntime-common is version-paired by the #307 resolver, which sits
198
+ // ABOVE this last-resort fallback. Only steal its phantom-import case
199
+ // when the stack itself came from the prefix — otherwise a leftover
200
+ // user-global prefix would hijack #307 for a package-sourced stack and
201
+ // pair a package onnxruntime-node with a version-drifted prefix common.
202
+ if (specifier === 'onnxruntime-common' && !stackIsPrefixSourced()) {
203
+ throw err;
204
+ }
205
+ // Re-anchor at the runtime prefix so Node applies the package's own
206
+ // exports conditions (ESM/CJS) exactly as a normal install would. The
207
+ // anchor is read here (not at registration) so it stays coherent with
208
+ // the current GITNEXUS_EMBEDDING_RUNTIME_DIR.
209
+ const prefixAnchor = pathToFileURL(join(getEmbeddingRuntimeDir(), 'noop.js')).href;
210
+ return nextResolve(specifier, { ...context, parentURL: prefixAnchor });
211
+ }
212
+ },
213
+ });
214
+ logger.debug({ prefix: getEmbeddingRuntimeDir() }, 'Installed embedding-runtime resolution fallback (#2370)');
215
+ }
216
+ catch (err) {
217
+ logger.debug({ err: err instanceof Error ? err.message : String(err) }, 'embedding-runtime resolution fallback not installed');
218
+ }
219
+ };
220
+ /** Pure command builder, exported for tests. */
221
+ export const buildEmbeddingInstallCommand = (opts = {}) => {
222
+ const specs = getEmbeddingStackSpecs();
223
+ const args = [
224
+ 'install',
225
+ '--prefix',
226
+ getEmbeddingRuntimeDir(),
227
+ '--no-fund',
228
+ '--no-audit',
229
+ '--loglevel',
230
+ 'error',
231
+ ...(opts.cuda ? [] : ['--ignore-scripts']),
232
+ ...Object.entries(specs).map(([name, spec]) => `${name}@${spec}`),
233
+ ];
234
+ const env = { ...process.env };
235
+ if (opts.cuda) {
236
+ // --cuda opts into the NuGet CUDA download. A user who exported
237
+ // ONNXRUNTIME_NODE_INSTALL=skip per our proxy docs must not have it silently
238
+ // suppress that download and then be told the install succeeded.
239
+ delete env.ONNXRUNTIME_NODE_INSTALL;
240
+ }
241
+ else {
242
+ env.ONNXRUNTIME_NODE_INSTALL = 'skip';
243
+ }
244
+ return { args, env };
245
+ };
246
+ /** cmd.exe metacharacters that force quoting (plus whitespace), per Colascione. */
247
+ const WIN32_NEEDS_QUOTING = /[\s&|<>^()%!]/;
248
+ /**
249
+ * Quote a single argument for the Windows `cmd.exe` shell (#2372). npm is a
250
+ * `.cmd` shim, so the spawn must go through a shell (EINVAL otherwise since
251
+ * CVE-2024-27980), and Node does NOT escape args under `shell: true` — a spaced
252
+ * `--prefix` path splits, and cmd eats the `^` in `@pkg@^1.0.0` semver ranges.
253
+ *
254
+ * Rules (validated against Node source, MS cmd/CRT docs, BatBadBut, Rust std):
255
+ * reject NUL/CR/LF and embedded `"` (both unrepresentable/unsafe at the cmd
256
+ * layer, and `"` is illegal in Windows paths and npm specs); wrap in double
257
+ * quotes when empty or containing whitespace/metacharacters; double the trailing
258
+ * backslash run so the added closing quote is not itself escaped (`C:\` →
259
+ * `"C:\\"`). `^` is literal inside cmd double quotes across all three parse
260
+ * layers (cmd `/c` → npm.cmd's `%*` re-parse → node CRT argv). Two documented
261
+ * ceilings quoting can't close: a defined `%VAR%` expands once at the first cmd
262
+ * parse, and `!` expands only under registry-enabled delayed expansion — both
263
+ * are the env-var owner's trust, out of the malicious-repo threat model.
264
+ */
265
+ export const quoteWin32Arg = (arg) => {
266
+ if (/[\0\r\n]/.test(arg)) {
267
+ throw new Error(`argument contains NUL/CR/LF, unsafe for the Windows shell: ${JSON.stringify(arg)}`);
268
+ }
269
+ if (arg.includes('"')) {
270
+ throw new Error(`argument contains a double quote, unsafe for the Windows shell: ${JSON.stringify(arg)}`);
271
+ }
272
+ if (arg !== '' && !WIN32_NEEDS_QUOTING.test(arg))
273
+ return arg;
274
+ const trailingBackslashes = /\\*$/.exec(arg)?.[0].length ?? 0;
275
+ return `"${arg}${'\\'.repeat(trailingBackslashes)}"`;
276
+ };
277
+ /**
278
+ * Compose a full `cmd.exe` command line: the command stays unquoted (so
279
+ * PATH/PATHEXT resolves a bare name or `.cmd` shim), args are individually
280
+ * quoted. Passing this as spawn's first (only) string argument — no args array
281
+ * — yields a byte-identical `cmd.exe /d /s /c "…"` line while avoiding DEP0190
282
+ * (the runtime deprecation warning Node >=24 emits for
283
+ * `spawn(file, args, {shell:true})`). Exported generically so the real-cmd.exe
284
+ * round-trip test drives the exact same composition the npm spawn uses.
285
+ */
286
+ export const composeWin32Command = (command, args) => [command, ...args.map(quoteWin32Arg)].join(' ');
287
+ /** {@link composeWin32Command} for the on-demand npm install (`npm` stays unquoted). */
288
+ export const composeWin32NpmCommand = (args) => composeWin32Command('npm', args);
289
+ /**
290
+ * Install (or update) the embedding stack into the runtime prefix via the
291
+ * user's npm — registry, mirror, and proxy configuration all apply. Rejects
292
+ * with npm's tail output on failure or timeout.
293
+ *
294
+ * The child is bounded by `timeoutMs` (default {@link getEmbeddingInstallTimeoutMs})
295
+ * and SIGKILLed — with its grandchildren — if it overruns, so a blackholed
296
+ * proxy (the exact #2370 environment) can't hang the caller forever. It is also
297
+ * killed if the parent exits mid-install, so a leftover npm can't keep writing
298
+ * into the shared prefix.
299
+ */
300
+ export const installEmbeddingRuntime = async (opts = {}, timeoutMs = getEmbeddingInstallTimeoutMs()) => {
301
+ const { args, env } = buildEmbeddingInstallCommand(opts);
302
+ await new Promise((resolve, reject) => {
303
+ // Windows `npm` is a `.cmd` shim, so the spawn must go through a shell.
304
+ // Compose the quoted command line ourselves and pass it as spawn's single
305
+ // string arg (no args array) so cmd.exe receives correctly-quoted paths/
306
+ // specs and Node >=24 doesn't warn (DEP0190). POSIX uses the array form.
307
+ // cwd: homedir() so npm reads its config from the user's home, never the
308
+ // analyzed repo's cwd — a project-local .npmrc there can't redirect the
309
+ // registry into the prefix we then load in-process (legacy npm; refuted on
310
+ // npm 10, but this closes the class regardless of npm version).
311
+ const child = process.platform === 'win32'
312
+ ? spawn(composeWin32NpmCommand(args), {
313
+ env,
314
+ cwd: homedir(),
315
+ windowsHide: true,
316
+ shell: true,
317
+ stdio: ['ignore', 'pipe', 'pipe'],
318
+ })
319
+ : spawn('npm', args, {
320
+ env,
321
+ cwd: homedir(),
322
+ windowsHide: true,
323
+ stdio: ['ignore', 'pipe', 'pipe'],
324
+ });
325
+ let tail = '';
326
+ const onChunk = (chunk) => {
327
+ const text = chunk.toString();
328
+ tail = (tail + text).slice(-2000);
329
+ if (opts.onOutput)
330
+ text.split('\n').filter(Boolean).forEach(opts.onOutput);
331
+ };
332
+ child.stdout?.on('data', onChunk);
333
+ child.stderr?.on('data', onChunk);
334
+ let settled = false;
335
+ // Kill a still-running npm if the parent exits (analyze's SIGINT handler, or
336
+ // a crash) so it can't keep writing into the shared prefix. Removed on settle.
337
+ const onParentExit = () => killNpmChild(child);
338
+ process.on('exit', onParentExit);
339
+ const cleanup = () => {
340
+ process.removeListener('exit', onParentExit);
341
+ };
342
+ const timer = setTimeout(() => {
343
+ if (settled)
344
+ return;
345
+ settled = true;
346
+ cleanup();
347
+ killNpmChild(child);
348
+ reject(new Error(`npm install of the embedding runtime timed out after ${timeoutMs}ms ` +
349
+ `(override with GITNEXUS_EMBEDDING_INSTALL_TIMEOUT_MS) — check your proxy/registry:\n${tail}`));
350
+ }, timeoutMs);
351
+ child.on('error', (err) => {
352
+ if (settled)
353
+ return;
354
+ settled = true;
355
+ clearTimeout(timer);
356
+ cleanup();
357
+ reject(err);
358
+ });
359
+ child.on('close', (exitCode, signal) => {
360
+ if (settled)
361
+ return;
362
+ settled = true;
363
+ clearTimeout(timer);
364
+ cleanup();
365
+ if (exitCode === 0)
366
+ resolve();
367
+ else
368
+ reject(new Error(`npm install of the embedding runtime failed ` +
369
+ `(${signal ? `killed with ${signal}` : `exit ${exitCode}`}):\n${tail}`));
370
+ });
371
+ });
372
+ };
@@ -1,20 +1,3 @@
1
- /**
2
- * Local embedding runtime support guard.
3
- *
4
- * The bundled local embedding stack (`@huggingface/transformers` →
5
- * `onnxruntime-node`) only ships native ONNX Runtime bindings for a subset of
6
- * platform/arch pairs. On macOS Intel (`darwin`/`x64`), `onnxruntime-node`
7
- * ships no `bin/napi-v6/darwin/x64/onnxruntime_binding.node`, so *importing*
8
- * transformers.js throws a raw `Cannot find module ...onnxruntime_binding.node`
9
- * before any device/backend selection can run (#1515). `ONNX_WEB_BACKEND=wasm`
10
- * cannot rescue this — the failure is at native-module import time, not backend
11
- * selection (#1516).
12
- *
13
- * This module is intentionally free of any native or transformers.js import (at
14
- * module scope or inside its functions) so it can be consulted *before* the
15
- * dynamic import that would crash. HTTP embedding mode never touches the native
16
- * runtime, so callers in HTTP mode must skip this guard.
17
- */
18
1
  export interface LocalEmbeddingRuntimeOptions {
19
2
  platform?: NodeJS.Platform;
20
3
  arch?: NodeJS.Architecture;
@@ -39,3 +22,51 @@ export declare const getLocalEmbeddingRuntimeBlocker: (options?: LocalEmbeddingR
39
22
  * full wording.
40
23
  */
41
24
  export declare const isLocalEmbeddingRuntimeBlockerMessage: (message: string) => boolean;
25
+ /**
26
+ * The full guidance shown when the optional local embedding stack
27
+ * (`@huggingface/transformers` → `onnxruntime-node`) is missing at runtime.
28
+ *
29
+ * Both packages are `optionalDependencies` (#2370): `onnxruntime-node`'s
30
+ * postinstall downloads CUDA support binaries from api.nuget.org, which fails
31
+ * behind HTTP proxies and regional firewalls (its `global-agent` proxy layer
32
+ * ignores the standard HTTP_PROXY/HTTPS_PROXY vars and rejects 302 redirects).
33
+ * npm then skips the optional subtree instead of failing the whole install —
34
+ * every GitNexus feature except local embeddings keeps working.
35
+ */
36
+ export declare const localEmbeddingStackMissingMessage: () => string;
37
+ /**
38
+ * Guidance when the runtime-prefix stack cannot be used because this Node lacks
39
+ * `module.registerHooks` (added in 22.15 / 23.5) — whether the prefix is already
40
+ * populated or not, this Node's ESM loader can never reach a prefix-installed
41
+ * copy (#2372). A normally-installed (package) stack never needs the hook and
42
+ * never hits this. State-neutral lead (it applies both when the prefix is
43
+ * populated and when nothing is installed) plus capability-first wording — a
44
+ * bare ">= 22.15" is untruthful for a 23.0–23.4 user whose version is
45
+ * numerically greater yet still lacks the API.
46
+ */
47
+ export declare const localEmbeddingPrefixUnloadableMessage: () => string;
48
+ /**
49
+ * When `err` is a module-not-found failure for the optional local embedding
50
+ * stack, return the actionable {@link localEmbeddingStackMissingMessage};
51
+ * otherwise `null` so genuine load errors surface unchanged.
52
+ *
53
+ * Matches on the error `code` (ERR_MODULE_NOT_FOUND for ESM `import()`,
54
+ * MODULE_NOT_FOUND for CJS require) plus the missing specifier in the message,
55
+ * so an unrelated module-not-found inside transformers.js is not misreported
56
+ * as a pruned install.
57
+ */
58
+ export declare const getMissingLocalEmbeddingStackMessage: (err: unknown) => string | null;
59
+ /**
60
+ * True when `message` is the missing-optional-stack message produced by
61
+ * {@link localEmbeddingStackMissingMessage}. CLI counterpart of
62
+ * {@link isLocalEmbeddingRuntimeBlockerMessage}.
63
+ */
64
+ export declare const isMissingLocalEmbeddingStackMessage: (message: string) => boolean;
65
+ /**
66
+ * True when the optional local embedding stack resolves from this install —
67
+ * either the normally-installed packages or the on-demand runtime prefix.
68
+ * Resolution only — nothing is imported, so this is safe on every platform
69
+ * (including macOS Intel, where *loading* onnxruntime-node would crash).
70
+ * Used by `doctor` to surface a pruned optional install (#2370) up front.
71
+ */
72
+ export declare const isLocalEmbeddingStackInstalled: () => boolean;
@@ -14,7 +14,10 @@
14
14
  * module scope or inside its functions) so it can be consulted *before* the
15
15
  * dynamic import that would crash. HTTP embedding mode never touches the native
16
16
  * runtime, so callers in HTTP mode must skip this guard.
17
+ * (The runtime-install import below only resolves paths — it never loads the
18
+ * embedding stack.)
17
19
  */
20
+ import { resolveEmbeddingRuntime } from './runtime-install.js';
18
21
  /**
19
22
  * Stable lead line of the macOS-Intel blocker message. Also used to recognise
20
23
  * the thrown error in the CLI error handler without coupling to the full
@@ -63,3 +66,96 @@ export const getLocalEmbeddingRuntimeBlocker = (options = {}) => {
63
66
  * full wording.
64
67
  */
65
68
  export const isLocalEmbeddingRuntimeBlockerMessage = (message) => message.includes(LOCAL_EMBEDDING_BLOCKER_LEAD);
69
+ /**
70
+ * Stable lead line of the missing-optional-stack message. Mirrors
71
+ * {@link LOCAL_EMBEDDING_BLOCKER_LEAD}: the CLI error handler matches on this
72
+ * line (see {@link isMissingLocalEmbeddingStackMessage}).
73
+ */
74
+ const LOCAL_EMBEDDING_STACK_MISSING_LEAD = 'Local semantic embeddings are unavailable: the optional embedding stack is not installed.';
75
+ /**
76
+ * The full guidance shown when the optional local embedding stack
77
+ * (`@huggingface/transformers` → `onnxruntime-node`) is missing at runtime.
78
+ *
79
+ * Both packages are `optionalDependencies` (#2370): `onnxruntime-node`'s
80
+ * postinstall downloads CUDA support binaries from api.nuget.org, which fails
81
+ * behind HTTP proxies and regional firewalls (its `global-agent` proxy layer
82
+ * ignores the standard HTTP_PROXY/HTTPS_PROXY vars and rejects 302 redirects).
83
+ * npm then skips the optional subtree instead of failing the whole install —
84
+ * every GitNexus feature except local embeddings keeps working.
85
+ */
86
+ export const localEmbeddingStackMissingMessage = () => [
87
+ LOCAL_EMBEDDING_STACK_MISSING_LEAD,
88
+ 'npm skipped the optional packages @huggingface/transformers / onnxruntime-node',
89
+ "during install — usually because onnxruntime-node's postinstall could not",
90
+ 'download its CUDA support binaries from api.nuget.org (common behind HTTP',
91
+ 'proxies and regional firewalls, #2370). Everything except local embeddings',
92
+ 'still works.',
93
+ '',
94
+ 'To enable local embeddings:',
95
+ ' - Run `gitnexus embeddings install` — fetches the stack on demand through',
96
+ ' your npm registry config (mirrors and proxies apply; no NuGet download).',
97
+ ' `gitnexus analyze --embeddings` does this automatically.',
98
+ ' Add --cuda on CUDA GPU hosts (behind a proxy, also set',
99
+ ' GLOBAL_AGENT_HTTPS_PROXY=<proxy-url> for the NuGet download).',
100
+ ' - Or reinstall with the CUDA download skipped (CPU embeddings need no CUDA):',
101
+ ' ONNXRUNTIME_NODE_INSTALL=skip npm install -g gitnexus',
102
+ ' (Windows: set ONNXRUNTIME_NODE_INSTALL=skip && npm install -g gitnexus)',
103
+ ' - Or point GITNEXUS_EMBEDDING_URL (with GITNEXUS_EMBEDDING_MODEL) at an',
104
+ ' OpenAI-compatible /v1/embeddings endpoint to embed over HTTP.',
105
+ ].join('\n');
106
+ /** Stable lead line of the prefix-unloadable message (mirrors the leads above). */
107
+ const LOCAL_EMBEDDING_PREFIX_UNLOADABLE_LEAD = 'The on-demand embedding runtime cannot be loaded on this Node build.';
108
+ /**
109
+ * Guidance when the runtime-prefix stack cannot be used because this Node lacks
110
+ * `module.registerHooks` (added in 22.15 / 23.5) — whether the prefix is already
111
+ * populated or not, this Node's ESM loader can never reach a prefix-installed
112
+ * copy (#2372). A normally-installed (package) stack never needs the hook and
113
+ * never hits this. State-neutral lead (it applies both when the prefix is
114
+ * populated and when nothing is installed) plus capability-first wording — a
115
+ * bare ">= 22.15" is untruthful for a 23.0–23.4 user whose version is
116
+ * numerically greater yet still lacks the API.
117
+ */
118
+ export const localEmbeddingPrefixUnloadableMessage = () => [
119
+ LOCAL_EMBEDDING_PREFIX_UNLOADABLE_LEAD,
120
+ 'The runtime prefix loads via module.registerHooks, which needs Node',
121
+ '>= 22.15 (on the 22.x line) or >= 23.5 (on the 23.x line). Either:',
122
+ ' - Upgrade Node to a build that has module.registerHooks, or',
123
+ ' - Reinstall the packages normally (works on every supported Node):',
124
+ ' ONNXRUNTIME_NODE_INSTALL=skip npm install -g gitnexus',
125
+ ' (Windows: set ONNXRUNTIME_NODE_INSTALL=skip && npm install -g gitnexus)',
126
+ ].join('\n');
127
+ /** Module specifiers whose absence means the optional embedding stack was pruned. */
128
+ const EMBEDDING_STACK_SPECIFIERS = ['@huggingface/transformers', 'onnxruntime-node'];
129
+ /**
130
+ * When `err` is a module-not-found failure for the optional local embedding
131
+ * stack, return the actionable {@link localEmbeddingStackMissingMessage};
132
+ * otherwise `null` so genuine load errors surface unchanged.
133
+ *
134
+ * Matches on the error `code` (ERR_MODULE_NOT_FOUND for ESM `import()`,
135
+ * MODULE_NOT_FOUND for CJS require) plus the missing specifier in the message,
136
+ * so an unrelated module-not-found inside transformers.js is not misreported
137
+ * as a pruned install.
138
+ */
139
+ export const getMissingLocalEmbeddingStackMessage = (err) => {
140
+ if (!(err instanceof Error))
141
+ return null;
142
+ const code = err.code;
143
+ if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND')
144
+ return null;
145
+ const namesStack = EMBEDDING_STACK_SPECIFIERS.some((s) => err.message.includes(`'${s}'`));
146
+ return namesStack ? localEmbeddingStackMissingMessage() : null;
147
+ };
148
+ /**
149
+ * True when `message` is the missing-optional-stack message produced by
150
+ * {@link localEmbeddingStackMissingMessage}. CLI counterpart of
151
+ * {@link isLocalEmbeddingRuntimeBlockerMessage}.
152
+ */
153
+ export const isMissingLocalEmbeddingStackMessage = (message) => message.includes(LOCAL_EMBEDDING_STACK_MISSING_LEAD);
154
+ /**
155
+ * True when the optional local embedding stack resolves from this install —
156
+ * either the normally-installed packages or the on-demand runtime prefix.
157
+ * Resolution only — nothing is imported, so this is safe on every platform
158
+ * (including macOS Intel, where *loading* onnxruntime-node would crash).
159
+ * Used by `doctor` to surface a pruned optional install (#2370) up front.
160
+ */
161
+ export const isLocalEmbeddingStackInstalled = () => resolveEmbeddingRuntime() !== null;