gitnexus 1.6.5-rc.9 → 1.6.5

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 (334) hide show
  1. package/dist/_shared/scope-resolution/finalize-algorithm.js +10 -2
  2. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  3. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +26 -0
  4. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  5. package/dist/_shared/scope-resolution/method-dispatch-index.js +22 -3
  6. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  7. package/dist/_shared/scope-resolution/registries/lookup-core.js +18 -1
  8. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  9. package/dist/_shared/scope-resolution/symbol-definition.d.ts +2 -0
  10. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  11. package/dist/cli/ai-context.d.ts +1 -0
  12. package/dist/cli/ai-context.js +70 -16
  13. package/dist/cli/analyze.d.ts +30 -2
  14. package/dist/cli/analyze.js +45 -5
  15. package/dist/cli/index.js +8 -1
  16. package/dist/cli/setup.js +18 -0
  17. package/dist/cli/wiki.d.ts +2 -0
  18. package/dist/cli/wiki.js +11 -0
  19. package/dist/core/augmentation/engine.js +24 -4
  20. package/dist/core/incremental/shadow-candidates.d.ts +44 -0
  21. package/dist/core/incremental/shadow-candidates.js +74 -0
  22. package/dist/core/incremental/subgraph-extract.d.ts +64 -0
  23. package/dist/core/incremental/subgraph-extract.js +111 -0
  24. package/dist/core/ingestion/call-processor.d.ts +12 -1
  25. package/dist/core/ingestion/call-processor.js +183 -47
  26. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +38 -0
  27. package/dist/core/ingestion/class-extractors/generic.js +8 -0
  28. package/dist/core/ingestion/class-types.d.ts +15 -0
  29. package/dist/core/ingestion/community-processor.js +18 -0
  30. package/dist/core/ingestion/import-resolvers/standard.d.ts +5 -0
  31. package/dist/core/ingestion/import-resolvers/standard.js +31 -1
  32. package/dist/core/ingestion/import-resolvers/utils.js +4 -0
  33. package/dist/core/ingestion/languages/c-cpp.js +20 -2
  34. package/dist/core/ingestion/languages/cpp/adl.d.ts +141 -0
  35. package/dist/core/ingestion/languages/cpp/adl.js +478 -0
  36. package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +18 -0
  37. package/dist/core/ingestion/languages/cpp/arity-metadata.js +170 -0
  38. package/dist/core/ingestion/languages/cpp/arity.d.ts +17 -0
  39. package/dist/core/ingestion/languages/cpp/arity.js +31 -0
  40. package/dist/core/ingestion/languages/cpp/captures.d.ts +2 -0
  41. package/dist/core/ingestion/languages/cpp/captures.js +1131 -0
  42. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +31 -0
  43. package/dist/core/ingestion/languages/cpp/conversion-rank.js +47 -0
  44. package/dist/core/ingestion/languages/cpp/file-local-linkage.d.ts +118 -0
  45. package/dist/core/ingestion/languages/cpp/file-local-linkage.js +266 -0
  46. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +8 -0
  47. package/dist/core/ingestion/languages/cpp/header-scan.js +50 -0
  48. package/dist/core/ingestion/languages/cpp/import-decomposer.d.ts +19 -0
  49. package/dist/core/ingestion/languages/cpp/import-decomposer.js +111 -0
  50. package/dist/core/ingestion/languages/cpp/import-target.d.ts +10 -0
  51. package/dist/core/ingestion/languages/cpp/import-target.js +13 -0
  52. package/dist/core/ingestion/languages/cpp/index.d.ts +11 -0
  53. package/dist/core/ingestion/languages/cpp/index.js +11 -0
  54. package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +67 -0
  55. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +176 -0
  56. package/dist/core/ingestion/languages/cpp/interpret.d.ts +35 -0
  57. package/dist/core/ingestion/languages/cpp/interpret.js +113 -0
  58. package/dist/core/ingestion/languages/cpp/merge-bindings.d.ts +13 -0
  59. package/dist/core/ingestion/languages/cpp/merge-bindings.js +29 -0
  60. package/dist/core/ingestion/languages/cpp/query.d.ts +3 -0
  61. package/dist/core/ingestion/languages/cpp/query.js +508 -0
  62. package/dist/core/ingestion/languages/cpp/range-bindings.d.ts +20 -0
  63. package/dist/core/ingestion/languages/cpp/range-bindings.js +212 -0
  64. package/dist/core/ingestion/languages/cpp/scope-resolver.d.ts +17 -0
  65. package/dist/core/ingestion/languages/cpp/scope-resolver.js +246 -0
  66. package/dist/core/ingestion/languages/cpp/simple-hooks.d.ts +24 -0
  67. package/dist/core/ingestion/languages/cpp/simple-hooks.js +62 -0
  68. package/dist/core/ingestion/languages/cpp/two-phase-lookup.d.ts +76 -0
  69. package/dist/core/ingestion/languages/cpp/two-phase-lookup.js +195 -0
  70. package/dist/core/ingestion/languages/java/arity-metadata.d.ts +18 -0
  71. package/dist/core/ingestion/languages/java/arity-metadata.js +40 -0
  72. package/dist/core/ingestion/languages/java/arity.d.ts +10 -0
  73. package/dist/core/ingestion/languages/java/arity.js +24 -0
  74. package/dist/core/ingestion/languages/java/cache-stats.d.ts +15 -0
  75. package/dist/core/ingestion/languages/java/cache-stats.js +26 -0
  76. package/dist/core/ingestion/languages/java/captures.d.ts +17 -0
  77. package/dist/core/ingestion/languages/java/captures.js +187 -0
  78. package/dist/core/ingestion/languages/java/import-decomposer.d.ts +18 -0
  79. package/dist/core/ingestion/languages/java/import-decomposer.js +85 -0
  80. package/dist/core/ingestion/languages/java/import-target.d.ts +17 -0
  81. package/dist/core/ingestion/languages/java/import-target.js +100 -0
  82. package/dist/core/ingestion/languages/java/index.d.ts +29 -0
  83. package/dist/core/ingestion/languages/java/index.js +29 -0
  84. package/dist/core/ingestion/languages/java/interpret.d.ts +13 -0
  85. package/dist/core/ingestion/languages/java/interpret.js +131 -0
  86. package/dist/core/ingestion/languages/java/merge-bindings.d.ts +12 -0
  87. package/dist/core/ingestion/languages/java/merge-bindings.js +40 -0
  88. package/dist/core/ingestion/languages/java/query.d.ts +30 -0
  89. package/dist/core/ingestion/languages/java/query.js +192 -0
  90. package/dist/core/ingestion/languages/java/receiver-binding.d.ts +11 -0
  91. package/dist/core/ingestion/languages/java/receiver-binding.js +95 -0
  92. package/dist/core/ingestion/languages/java/scope-resolver.d.ts +50 -0
  93. package/dist/core/ingestion/languages/java/scope-resolver.js +74 -0
  94. package/dist/core/ingestion/languages/java/simple-hooks.d.ts +13 -0
  95. package/dist/core/ingestion/languages/java/simple-hooks.js +34 -0
  96. package/dist/core/ingestion/languages/java.js +11 -0
  97. package/dist/core/ingestion/languages/php/arity-metadata.d.ts +28 -0
  98. package/dist/core/ingestion/languages/php/arity-metadata.js +63 -0
  99. package/dist/core/ingestion/languages/php/arity.d.ts +25 -0
  100. package/dist/core/ingestion/languages/php/arity.js +40 -0
  101. package/dist/core/ingestion/languages/php/cache-stats.d.ts +15 -0
  102. package/dist/core/ingestion/languages/php/cache-stats.js +26 -0
  103. package/dist/core/ingestion/languages/php/captures.d.ts +34 -0
  104. package/dist/core/ingestion/languages/php/captures.js +739 -0
  105. package/dist/core/ingestion/languages/php/import-decomposer.d.ts +28 -0
  106. package/dist/core/ingestion/languages/php/import-decomposer.js +265 -0
  107. package/dist/core/ingestion/languages/php/import-target.d.ts +47 -0
  108. package/dist/core/ingestion/languages/php/import-target.js +100 -0
  109. package/dist/core/ingestion/languages/php/index.d.ts +68 -0
  110. package/dist/core/ingestion/languages/php/index.js +72 -0
  111. package/dist/core/ingestion/languages/php/interpret.d.ts +36 -0
  112. package/dist/core/ingestion/languages/php/interpret.js +241 -0
  113. package/dist/core/ingestion/languages/php/merge-bindings.d.ts +19 -0
  114. package/dist/core/ingestion/languages/php/merge-bindings.js +47 -0
  115. package/dist/core/ingestion/languages/php/namespace-siblings.d.ts +51 -0
  116. package/dist/core/ingestion/languages/php/namespace-siblings.js +288 -0
  117. package/dist/core/ingestion/languages/php/query.d.ts +32 -0
  118. package/dist/core/ingestion/languages/php/query.js +326 -0
  119. package/dist/core/ingestion/languages/php/receiver-binding.d.ts +36 -0
  120. package/dist/core/ingestion/languages/php/receiver-binding.js +128 -0
  121. package/dist/core/ingestion/languages/php/scope-resolver.d.ts +23 -0
  122. package/dist/core/ingestion/languages/php/scope-resolver.js +358 -0
  123. package/dist/core/ingestion/languages/php/simple-hooks.d.ts +42 -0
  124. package/dist/core/ingestion/languages/php/simple-hooks.js +111 -0
  125. package/dist/core/ingestion/languages/php.d.ts +0 -7
  126. package/dist/core/ingestion/languages/php.js +15 -0
  127. package/dist/core/ingestion/markdown-processor.js +6 -1
  128. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  129. package/dist/core/ingestion/model/symbol-table.js +3 -0
  130. package/dist/core/ingestion/parsing-processor.d.ts +22 -2
  131. package/dist/core/ingestion/parsing-processor.js +123 -50
  132. package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +54 -2
  133. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +5 -0
  134. package/dist/core/ingestion/pipeline-phases/parse-impl.js +119 -11
  135. package/dist/core/ingestion/pipeline-phases/parse.d.ts +14 -0
  136. package/dist/core/ingestion/pipeline.d.ts +13 -0
  137. package/dist/core/ingestion/registry-primary-flag.js +2 -0
  138. package/dist/core/ingestion/scope-extractor.js +4 -0
  139. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +167 -0
  140. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +3 -0
  141. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  142. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
  143. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +1 -1
  144. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +12 -2
  145. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +16 -0
  146. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +36 -0
  147. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +204 -13
  148. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +1 -1
  149. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +55 -4
  150. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +186 -5
  151. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +10 -1
  152. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +207 -9
  153. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +13 -1
  154. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +6 -0
  155. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +17 -0
  156. package/dist/core/ingestion/scope-resolution/pipeline/run.js +86 -8
  157. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +30 -0
  158. package/dist/core/ingestion/scope-resolution/scope/walkers.js +90 -8
  159. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  160. package/dist/core/ingestion/tree-sitter-queries.js +23 -0
  161. package/dist/core/ingestion/utils/ast-helpers.js +6 -1
  162. package/dist/core/ingestion/utils/template-arguments.d.ts +11 -0
  163. package/dist/core/ingestion/utils/template-arguments.js +65 -0
  164. package/dist/core/ingestion/workers/parse-worker.d.ts +3 -0
  165. package/dist/core/ingestion/workers/parse-worker.js +42 -1
  166. package/dist/core/lbug/lbug-adapter.d.ts +39 -0
  167. package/dist/core/lbug/lbug-adapter.js +348 -48
  168. package/dist/core/run-analyze.d.ts +2 -0
  169. package/dist/core/run-analyze.js +323 -29
  170. package/dist/core/search/hybrid-search.d.ts +3 -0
  171. package/dist/core/search/hybrid-search.js +23 -6
  172. package/dist/core/wiki/generator.js +4 -3
  173. package/dist/core/wiki/html-viewer.js +2 -1
  174. package/dist/core/wiki/llm-client.d.ts +4 -0
  175. package/dist/core/wiki/llm-client.js +3 -3
  176. package/dist/core/wiki/mermaid-sanitizer.d.ts +2 -0
  177. package/dist/core/wiki/mermaid-sanitizer.js +100 -0
  178. package/dist/mcp/local/local-backend.js +20 -7
  179. package/dist/storage/file-hash.d.ts +47 -0
  180. package/dist/storage/file-hash.js +86 -0
  181. package/dist/storage/parse-cache.d.ts +73 -0
  182. package/dist/storage/parse-cache.js +253 -0
  183. package/dist/storage/repo-manager.d.ts +41 -1
  184. package/dist/storage/repo-manager.js +61 -6
  185. package/hooks/claude/gitnexus-hook.cjs +42 -4
  186. package/hooks/claude/hook-db-lock-probe.cjs +238 -0
  187. package/hooks/claude/hook-lock.cjs +119 -0
  188. package/hooks/claude/win-rm-list-json.ps1 +76 -0
  189. package/package.json +1 -1
  190. package/scripts/build.js +6 -2
  191. package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +16 -16
  192. package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +16 -16
  193. package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +16 -16
  194. package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +16 -16
  195. package/web/assets/{agent-BpHQleHE.js → agent-D2kLt6Dl.js} +91 -91
  196. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +1 -0
  197. package/web/assets/architectureDiagram-UL44E2DR-DvIkEGkp.js +36 -0
  198. package/web/assets/blockDiagram-7IZFK4PR-B4niSLvR.js +132 -0
  199. package/web/assets/{c4Diagram-DFAF54RM-DeJV1rCn.js → c4Diagram-Y2BXMSZH-DIzEXalR.js} +6 -6
  200. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +4 -0
  201. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +1 -0
  202. package/web/assets/{chunk-7RZVMHOQ-BdIU-RGO.js → chunk-3SSMPTDK-BzGeeheI.js} +1 -1
  203. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +159 -0
  204. package/web/assets/chunk-5IMINLNL-CnTLsXIV.js +1 -0
  205. package/web/assets/chunk-5VCL7Z4A-w1BCx98Z.js +1 -0
  206. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +1 -0
  207. package/web/assets/chunk-67TQ5CYL-CChC2cAv.js +124 -0
  208. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +10 -0
  209. package/web/assets/chunk-7W6UQGC5-CgFyfZ6k.js +1 -0
  210. package/web/assets/chunk-AQ6EADP3-CCFje6lL.js +1 -0
  211. package/web/assets/{chunk-KSICW3F5-C2tZmXwv.js → chunk-AZZRMDJM-5E9ARdDQ.js} +1 -1
  212. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +1 -0
  213. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +1 -0
  214. package/web/assets/chunk-INKRHTLW-ELpfuk9h.js +70 -0
  215. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +1 -0
  216. package/web/assets/{chunk-AEOMTBSW-D7qjBMHW.js → chunk-JQRUD6KW-Dcq_Qnyy.js} +1 -1
  217. package/web/assets/chunk-KGFNY3KK-W-VR57xb.js +62 -0
  218. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +1 -0
  219. package/web/assets/chunk-KNLZD3CH-BuZBOcso.js +1 -0
  220. package/web/assets/chunk-KRXBNO2N-DH2NPM7I.js +1 -0
  221. package/web/assets/chunk-LCXTWHL2-CqJ0oo1x.js +231 -0
  222. package/web/assets/chunk-LII3EMHJ-3X33tCpU.js +1 -0
  223. package/web/assets/chunk-QA3QBVWF-D5Ha3GgB.js +2 -0
  224. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +1 -0
  225. package/web/assets/chunk-RG4AUYOV-D0M6vdq9.js +206 -0
  226. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +1 -0
  227. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +1 -0
  228. package/web/assets/chunk-T2UQINTJ-kGYG7ppP.js +1 -0
  229. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +1 -0
  230. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +1 -0
  231. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +1 -0
  232. package/web/assets/chunk-UY5QBCOK-Du-c56gs.js +1 -0
  233. package/web/assets/chunk-W44A43WB-lrCpuupA.js +1 -0
  234. package/web/assets/chunk-ZXARS5L4-CSZRiyOf.js +1 -0
  235. package/web/assets/classDiagram-KGZ6W3CR-BB-saqC1.js +1 -0
  236. package/web/assets/classDiagram-v2-72OJOZXJ-Bp2bAlS5.js +1 -0
  237. package/web/assets/cose-bilkent-UX7MHV2Q-BHv2bbtl.js +1 -0
  238. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +4 -0
  239. package/web/assets/diagram-3NCE3AQN-BUxnDPOY.js +43 -0
  240. package/web/assets/diagram-GF46GFSD-BJTyqqvI.js +24 -0
  241. package/web/assets/diagram-HNR7UZ2L-fUWreZzx.js +3 -0
  242. package/web/assets/diagram-QXG6HAR7-DilMHamP.js +24 -0
  243. package/web/assets/diagram-WEQXMOUZ-8KUrarg1.js +10 -0
  244. package/web/assets/erDiagram-L5TCEMPS-Doj9mSiW.js +85 -0
  245. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +1 -0
  246. package/web/assets/{flowDiagram-OTCZ4VVT-aAZw4c_M.js → flowDiagram-H6V6AXG4-BOSR-i3p.js} +12 -12
  247. package/web/assets/ganttDiagram-JCBTUEKG-BHQssTz6.js +292 -0
  248. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +1 -0
  249. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +106 -0
  250. package/web/assets/index-j4-NuleT.js +623 -0
  251. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +1 -0
  252. package/web/assets/infoDiagram-3YFTVSEB-Ce_TvtaV.js +2 -0
  253. package/web/assets/{ishikawaDiagram-YMYX4NHK-BF8AtDaj.js → ishikawaDiagram-BNXS4ZKH-4MXIDCSF.js} +6 -6
  254. package/web/assets/journeyDiagram-M6C3CM3L-DNPYCpvq.js +139 -0
  255. package/web/assets/kanban-definition-75IXJCU3-yYdfm8Kk.js +89 -0
  256. package/web/assets/{katex-GD7MH7QM-CJiOjBBJ.js → katex-K3KEBU37-CrVLz8l7.js} +1 -1
  257. package/web/assets/mindmap-definition-2TDM6QVE-DFNEKM77.js +96 -0
  258. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +1 -0
  259. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +1 -0
  260. package/web/assets/pieDiagram-CU6KROY3-iZAqpvhd.js +30 -0
  261. package/web/assets/quadrantDiagram-VICAPDV7-bAm6Hi_5.js +7 -0
  262. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +1 -0
  263. package/web/assets/requirementDiagram-JXO7QTGE-CtDijhnf.js +84 -0
  264. package/web/assets/sankeyDiagram-URQDO5SZ-LFJz53u5.js +40 -0
  265. package/web/assets/sequenceDiagram-VS2MUI6T-BdjvyCHM.js +162 -0
  266. package/web/assets/stateDiagram-7D4R322I-e5odN9fG.js +1 -0
  267. package/web/assets/stateDiagram-v2-36443NZ5-vPsvjR2D.js +1 -0
  268. package/web/assets/timeline-definition-O6YCAMPW-DPAksNQp.js +120 -0
  269. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +1 -0
  270. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +1 -0
  271. package/web/assets/vennDiagram-MWXL3ELB-Dtlfdt7J.js +34 -0
  272. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +1 -0
  273. package/web/assets/wardleyDiagram-CUQ6CDDI-B23uRMUC.js +78 -0
  274. package/web/assets/{xychartDiagram-ZHJ5623Y-DZ7fP1_3.js → xychartDiagram-N2JHSOCM-DxwHeksW.js} +6 -6
  275. package/web/index.html +13 -2
  276. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-BUzkWgDl.js +0 -1
  277. package/web/assets/architectureDiagram-EMZXCZ2Q-CXbzok1G.js +0 -36
  278. package/web/assets/blockDiagram-IGV67L2C-DA1QJ4ZB.js +0 -132
  279. package/web/assets/chunk-3GS5O3IE-B8hpnia8.js +0 -231
  280. package/web/assets/chunk-3YCYZ6SJ-B-BVu21C.js +0 -1
  281. package/web/assets/chunk-6NTNNK5N-DW-jYSDr.js +0 -1
  282. package/web/assets/chunk-A34GCYZU-BbZl-IKy.js +0 -1
  283. package/web/assets/chunk-DJ7UZH7F-B5tMfF87.js +0 -1
  284. package/web/assets/chunk-DKKBVRCY-Djm-6K_e.js +0 -4
  285. package/web/assets/chunk-DU5LTGQ6-QPoynYcp.js +0 -1
  286. package/web/assets/chunk-FXACKDTF-jieSmbe2.js +0 -159
  287. package/web/assets/chunk-H3VCZNTA-8EMHZ3SV.js +0 -1
  288. package/web/assets/chunk-HN6EAY2L-CgE_agsD.js +0 -1
  289. package/web/assets/chunk-O5ABG6QK-DEZV1O19.js +0 -1
  290. package/web/assets/chunk-PK6DOVAG-BXv0JnsJ.js +0 -206
  291. package/web/assets/chunk-RNJOYNJ4-BiBkF9dN.js +0 -1
  292. package/web/assets/chunk-RWUO3TPN-DTWzAhQ1.js +0 -1
  293. package/web/assets/chunk-TBF5ZNIQ-BSl6s9lT.js +0 -1
  294. package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +0 -1
  295. package/web/assets/chunk-TYMNRAUI-D1NDLIMG.js +0 -1
  296. package/web/assets/chunk-VELTKBKT-C9dVN39o.js +0 -1
  297. package/web/assets/chunk-W7ZLLLMY-Bu-cdLzs.js +0 -1
  298. package/web/assets/chunk-WSB5WSVC-Dor71SOz.js +0 -1
  299. package/web/assets/chunk-XGPFEOL4-CUonLRle.js +0 -1
  300. package/web/assets/classDiagram-PPOCWD7C-Cdd6UdVv.js +0 -1
  301. package/web/assets/classDiagram-v2-23LJLIIU-DNhxY2Ib.js +0 -1
  302. package/web/assets/cose-bilkent-PNC4W37J-DdZq7Qz_.js +0 -1
  303. package/web/assets/dagre-E77IOHMT-Cfj_6e17.js +0 -4
  304. package/web/assets/diagram-H7BISOXX-BNvpsrBv.js +0 -43
  305. package/web/assets/diagram-JC5VWROH-Cn0rtDbJ.js +0 -24
  306. package/web/assets/diagram-LXUTUG65-CHfoVKNm.js +0 -10
  307. package/web/assets/diagram-WEHSV5V5-BLxHksnu.js +0 -24
  308. package/web/assets/erDiagram-GCSMX5X6-DfdN6iqF.js +0 -85
  309. package/web/assets/ganttDiagram-MUNLMDZQ-BEfC_7Or.js +0 -292
  310. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-B6ZeRLP0.js +0 -1
  311. package/web/assets/gitGraphDiagram-3HKGZ4G3-CG0kC8Q5.js +0 -106
  312. package/web/assets/index-3NUCUW2s.js +0 -886
  313. package/web/assets/info-OMHHGYJF-BF2H5H6G-BIOsZy0W.js +0 -1
  314. package/web/assets/infoDiagram-MN7RKWGX-DUoMH-pq.js +0 -2
  315. package/web/assets/journeyDiagram-SO5T7YLQ-BW8oKa3N.js +0 -139
  316. package/web/assets/kanban-definition-LJHFXRCJ-DzbOsR-2.js +0 -89
  317. package/web/assets/mindmap-definition-2EUWGEK5-BIXGQALq.js +0 -96
  318. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-Bs7ysNMU.js +0 -1
  319. package/web/assets/pie-ZZUOXDRM-N23DN5KN-Csi8nnbH.js +0 -1
  320. package/web/assets/pieDiagram-3IATQBI2-QY-u7P6a.js +0 -30
  321. package/web/assets/quadrantDiagram-E256RVCF-Di4aNiU8.js +0 -7
  322. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-DJlyXh2-.js +0 -1
  323. package/web/assets/requirementDiagram-M5DCFWZL-CzUUrhQD.js +0 -84
  324. package/web/assets/sankeyDiagram-L3NBLAOT-D2fbMiIU.js +0 -10
  325. package/web/assets/sequenceDiagram-ZOUHS735-BNdkqguh.js +0 -157
  326. package/web/assets/stateDiagram-MLPALWAM-51_Q2w2L.js +0 -1
  327. package/web/assets/stateDiagram-v2-B5LQ5ZB2-8nHs2aNE.js +0 -1
  328. package/web/assets/timeline-definition-5SPVSISX-DkMiGj4Z.js +0 -120
  329. package/web/assets/treeView-SZITEDCU-5DXDK3XO-Cy6P0_CZ.js +0 -1
  330. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-C_pp1R7A.js +0 -1
  331. package/web/assets/vennDiagram-IE5QUKF5-BCxMmf9q.js +0 -34
  332. package/web/assets/wardley-RL74JXVD-BCRCBASE-CMStJhpn.js +0 -1
  333. package/web/assets/wardleyDiagram-XU3VSMPF-ijlLF77s.js +0 -20
  334. /package/web/assets/{context-builder-CqQNhRj1.js → context-builder-_HS0v2ma.js} +0 -0
@@ -0,0 +1,100 @@
1
+ const MERMAID_FENCE_RE = /```mermaid\s*\n([\s\S]*?)```/g;
2
+ const NODE_LABEL_RE = /(\[[^\]\n]*(?:\\n)[^\]\n]*\]|\{[^}\n]*(?:\\n)[^}\n]*\}|\([^)\n]*(?:\\n)[^)\n]*\))/g;
3
+ const EDGE_LABEL_RE = /\|([^|\n]+)\|/g;
4
+ const UNSAFE_EDGE_LABEL_RE = /[()[\]{}<>]/;
5
+ const UNSAFE_NODE_ID_RE = /[^A-Za-z0-9_-]/;
6
+ const NODE_ID_RE = /^[A-Za-z0-9_.:/()-]+$/;
7
+ const LINE_PREFIX_RE = /^(\s*(?:(?:[-A-Za-z0-9_]+)\s*:\s*)?)(.*)$/;
8
+ const EDGE_RE = /(\s*(?:[ox])?(?:--+|==+|\.\.+)(?:[>|ox])?\|[^|\n]*\|(?:[>|ox])?|\s*(?:[ox])?(?:--+|==+|\.\.+)(?:[>|ox])?|\s*<--+>?\s*)/g;
9
+ export function sanitizeMermaidMarkdown(markdown) {
10
+ return markdown.replace(MERMAID_FENCE_RE, (_match, diagram) => {
11
+ return '```mermaid\n' + sanitizeMermaidDiagram(diagram) + '```';
12
+ });
13
+ }
14
+ export function sanitizeMermaidDiagram(diagram) {
15
+ const aliases = new Map();
16
+ let nextAlias = 1;
17
+ const aliasFor = (id) => {
18
+ const existing = aliases.get(id);
19
+ if (existing)
20
+ return existing;
21
+ const base = id.replace(/[^A-Za-z0-9_-]/g, '_').replace(/^_+|_+$/g, '') || 'node';
22
+ let alias = base;
23
+ while ([...aliases.values()].includes(alias)) {
24
+ nextAlias += 1;
25
+ alias = `${base}_${nextAlias}`;
26
+ }
27
+ aliases.set(id, alias);
28
+ return alias;
29
+ };
30
+ return diagram
31
+ .split('\n')
32
+ .map((line) => sanitizeMermaidLine(line, aliasFor))
33
+ .join('\n');
34
+ }
35
+ function sanitizeMermaidLine(line, aliasFor) {
36
+ let sanitized = replaceLiteralLineBreaksInLabels(line);
37
+ sanitized = quoteUnsafeEdgeLabels(sanitized);
38
+ const prefixMatch = sanitized.match(LINE_PREFIX_RE);
39
+ if (!prefixMatch)
40
+ return sanitized;
41
+ const prefix = prefixMatch[1];
42
+ const body = prefixMatch[2];
43
+ if (isDirectiveLine(body))
44
+ return sanitized;
45
+ const parts = body.split(EDGE_RE);
46
+ if (parts.length === 1)
47
+ return sanitized;
48
+ for (let i = 0; i < parts.length; i += 2) {
49
+ parts[i] = sanitizeNodeReference(parts[i], aliasFor);
50
+ }
51
+ return prefix + parts.join('');
52
+ }
53
+ function replaceLiteralLineBreaksInLabels(line) {
54
+ return line.replace(NODE_LABEL_RE, (label) => label.replace(/\\n/g, '<br/>'));
55
+ }
56
+ function quoteUnsafeEdgeLabels(line) {
57
+ return line.replace(EDGE_LABEL_RE, (match, label) => {
58
+ const trimmed = label.trim();
59
+ if (!UNSAFE_EDGE_LABEL_RE.test(trimmed))
60
+ return match;
61
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
62
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
63
+ return match;
64
+ }
65
+ return `|"${escapeMermaidLabel(trimmed)}"|`;
66
+ });
67
+ }
68
+ function sanitizeNodeReference(segment, aliasFor) {
69
+ const match = segment.match(/^(\s*)([A-Za-z0-9_.:/()-]+)(.*?)(\s*)$/);
70
+ if (!match)
71
+ return segment;
72
+ const [, leading, id, suffix, trailing] = match;
73
+ if (!NODE_ID_RE.test(id) || !UNSAFE_NODE_ID_RE.test(id))
74
+ return segment;
75
+ const hasInlineLabel = suffix.trim().startsWith('[') || suffix.trim().startsWith('(') || suffix.trim().startsWith('{');
76
+ if (hasInlineLabel)
77
+ return `${leading}${aliasFor(id)}${suffix}${trailing}`;
78
+ return `${leading}${aliasFor(id)}["${escapeMermaidLabel(id)}"]${suffix}${trailing}`;
79
+ }
80
+ function escapeMermaidLabel(label) {
81
+ return label.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
82
+ }
83
+ function isDirectiveLine(line) {
84
+ const trimmed = line.trim();
85
+ return (trimmed === '' ||
86
+ trimmed.startsWith('%%') ||
87
+ trimmed.startsWith('graph ') ||
88
+ trimmed.startsWith('flowchart ') ||
89
+ trimmed.startsWith('sequenceDiagram') ||
90
+ trimmed.startsWith('classDiagram') ||
91
+ trimmed.startsWith('stateDiagram') ||
92
+ trimmed.startsWith('erDiagram') ||
93
+ trimmed.startsWith('journey') ||
94
+ trimmed.startsWith('gantt') ||
95
+ trimmed.startsWith('pie ') ||
96
+ trimmed.startsWith('mindmap') ||
97
+ trimmed.startsWith('timeline') ||
98
+ trimmed.startsWith('subgraph ') ||
99
+ trimmed === 'end');
100
+ }
@@ -634,8 +634,10 @@ export class LocalBackend {
634
634
  timer.time('bm25', this.bm25Search(repo, searchQuery, searchLimit)),
635
635
  timer.time('vector', this.semanticSearch(repo, searchQuery, searchLimit)),
636
636
  ]);
637
- const bm25Results = bm25SearchResult.results;
638
- const ftsUsed = bm25SearchResult.ftsUsed;
637
+ // Guard against undefined results (#1489) — when FTS is entirely
638
+ // unavailable the search helper may return an unexpected shape.
639
+ const bm25Results = bm25SearchResult?.results ?? [];
640
+ const ftsUsed = bm25SearchResult?.ftsUsed ?? false;
639
641
  // Merge via reciprocal rank fusion
640
642
  timer.start('merge');
641
643
  const scoreMap = new Map();
@@ -651,8 +653,9 @@ export class LocalBackend {
651
653
  scoreMap.set(key, { score: rrfScore, data: result });
652
654
  }
653
655
  }
654
- for (let i = 0; i < semanticResults.length; i++) {
655
- const result = semanticResults[i];
656
+ const safeSemanticResults = semanticResults ?? [];
657
+ for (let i = 0; i < safeSemanticResults.length; i++) {
658
+ const result = safeSemanticResults[i];
656
659
  const key = result.nodeId || result.filePath;
657
660
  const rrfScore = 1 / (60 + i);
658
661
  const existing = scoreMap.get(key);
@@ -826,7 +829,15 @@ export class LocalBackend {
826
829
  * BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
827
830
  */
828
831
  async bm25Search(repo, query, limit) {
829
- const { searchFTSFromLbug } = await import('../../core/search/bm25-index.js');
832
+ let searchFTSFromLbug;
833
+ try {
834
+ ({ searchFTSFromLbug } = await import('../../core/search/bm25-index.js'));
835
+ }
836
+ catch (err) {
837
+ // Module import can fail in sandboxed MCP contexts (#1489)
838
+ logger.warn({ err: err?.message }, 'GitNexus: bm25-index.js import failed — falling back to semantic-only');
839
+ return { results: [], ftsUsed: false };
840
+ }
830
841
  let ftsResponse;
831
842
  try {
832
843
  ftsResponse = await searchFTSFromLbug(query, limit, repo.id);
@@ -835,8 +846,10 @@ export class LocalBackend {
835
846
  logger.error({ err: err.message }, 'GitNexus: BM25/FTS search failed (FTS indexes may not exist) -');
836
847
  return { results: [], ftsUsed: false };
837
848
  }
838
- const bm25Results = ftsResponse.results;
839
- const ftsUsed = ftsResponse.ftsAvailable;
849
+ // Guard against unexpected response shape (#1489) — ftsResponse.results
850
+ // could be undefined when the FTS extension is unavailable in the MCP process.
851
+ const bm25Results = ftsResponse?.results ?? [];
852
+ const ftsUsed = ftsResponse?.ftsAvailable ?? false;
840
853
  const results = [];
841
854
  for (const bm25Result of bm25Results) {
842
855
  const fullPath = bm25Result.filePath;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Per-file content hashing for incremental DB writeback.
3
+ *
4
+ * On every analyze run we compute SHA-256 of every file's content and
5
+ * store the map in meta.json. The next run compares disk against the
6
+ * stored map and produces:
7
+ * - `changed` — content differs (re-emit DB rows for this file)
8
+ * - `added` — file is new on disk (insert DB rows)
9
+ * - `deleted` — file was in last meta but no longer on disk (drop rows)
10
+ *
11
+ * The pipeline still parses every file (correctness invariant: cross-file
12
+ * resolution needs full data). What this enables is a SELECTIVE DB
13
+ * writeback: instead of wipe-and-reload of the whole graph (~50s of CSV
14
+ * COPY on a 25K-node repo), we only delete-and-rewrite rows for the
15
+ * changed/added/deleted set.
16
+ *
17
+ * See docs/superpowers/specs/2026-05-10-incremental-indexing-design.md
18
+ * (Option B revision).
19
+ */
20
+ /**
21
+ * Compute SHA-256 of a single file. Returns null when the file can't be
22
+ * read — caller treats that as "no signature, assume changed".
23
+ */
24
+ export declare const computeFileHash: (absPath: string) => Promise<string | null>;
25
+ /**
26
+ * Compute SHA-256 hashes for many files in parallel batches. Files that
27
+ * fail to read are omitted from the result map.
28
+ */
29
+ export declare const computeFileHashes: (repoPath: string, relPaths: readonly string[]) => Promise<Map<string, string>>;
30
+ /** Result of comparing the current on-disk hashes against stored ones. */
31
+ export interface FileHashDiff {
32
+ /** Files whose content hash differs from stored. */
33
+ changed: string[];
34
+ /** Files in the current scan that weren't in the stored map. */
35
+ added: string[];
36
+ /** Files in the stored map that aren't in the current scan. */
37
+ deleted: string[];
38
+ /** All files whose DB rows must be replaced (changed ∪ added). */
39
+ toWrite: string[];
40
+ }
41
+ /**
42
+ * Diff a current hash map against a previously stored one.
43
+ *
44
+ * Sorted output so two runs produce identical diff arrays for the same
45
+ * changes — useful for stable logging / equivalence checks.
46
+ */
47
+ export declare const diffFileHashes: (current: ReadonlyMap<string, string>, stored: Readonly<Record<string, string>> | undefined) => FileHashDiff;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Per-file content hashing for incremental DB writeback.
3
+ *
4
+ * On every analyze run we compute SHA-256 of every file's content and
5
+ * store the map in meta.json. The next run compares disk against the
6
+ * stored map and produces:
7
+ * - `changed` — content differs (re-emit DB rows for this file)
8
+ * - `added` — file is new on disk (insert DB rows)
9
+ * - `deleted` — file was in last meta but no longer on disk (drop rows)
10
+ *
11
+ * The pipeline still parses every file (correctness invariant: cross-file
12
+ * resolution needs full data). What this enables is a SELECTIVE DB
13
+ * writeback: instead of wipe-and-reload of the whole graph (~50s of CSV
14
+ * COPY on a 25K-node repo), we only delete-and-rewrite rows for the
15
+ * changed/added/deleted set.
16
+ *
17
+ * See docs/superpowers/specs/2026-05-10-incremental-indexing-design.md
18
+ * (Option B revision).
19
+ */
20
+ import { createHash } from 'crypto';
21
+ import fs from 'fs/promises';
22
+ import path from 'path';
23
+ /**
24
+ * Compute SHA-256 of a single file. Returns null when the file can't be
25
+ * read — caller treats that as "no signature, assume changed".
26
+ */
27
+ export const computeFileHash = async (absPath) => {
28
+ try {
29
+ const buf = await fs.readFile(absPath);
30
+ return createHash('sha256').update(buf).digest('hex');
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ };
36
+ /**
37
+ * Compute SHA-256 hashes for many files in parallel batches. Files that
38
+ * fail to read are omitted from the result map.
39
+ */
40
+ export const computeFileHashes = async (repoPath, relPaths) => {
41
+ const out = new Map();
42
+ const BATCH = 100;
43
+ for (let i = 0; i < relPaths.length; i += BATCH) {
44
+ const batch = relPaths.slice(i, i + BATCH);
45
+ const results = await Promise.all(batch.map(async (rel) => {
46
+ const h = await computeFileHash(path.join(repoPath, rel));
47
+ return h ? [rel, h] : null;
48
+ }));
49
+ for (const r of results)
50
+ if (r)
51
+ out.set(r[0], r[1]);
52
+ }
53
+ return out;
54
+ };
55
+ /**
56
+ * Diff a current hash map against a previously stored one.
57
+ *
58
+ * Sorted output so two runs produce identical diff arrays for the same
59
+ * changes — useful for stable logging / equivalence checks.
60
+ */
61
+ export const diffFileHashes = (current, stored) => {
62
+ const storedMap = new Map(stored ? Object.entries(stored) : []);
63
+ const changed = [];
64
+ const added = [];
65
+ for (const [p, h] of current) {
66
+ const prev = storedMap.get(p);
67
+ if (prev === undefined)
68
+ added.push(p);
69
+ else if (prev !== h)
70
+ changed.push(p);
71
+ }
72
+ const deleted = [];
73
+ for (const p of storedMap.keys()) {
74
+ if (!current.has(p))
75
+ deleted.push(p);
76
+ }
77
+ changed.sort();
78
+ added.sort();
79
+ deleted.sort();
80
+ return {
81
+ changed,
82
+ added,
83
+ deleted,
84
+ toWrite: [...changed, ...added].sort(),
85
+ };
86
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Chunk-level content-addressed parse cache.
3
+ *
4
+ * The pipeline always parses every file (correctness invariant: cross-file
5
+ * resolution and downstream phases need full graph data). What this cache
6
+ * does is skip the tree-sitter worker dispatch when a chunk's contents
7
+ * haven't changed since the last run.
8
+ *
9
+ * Granularity: chunk-level. The parse phase chunks files into ~20MB byte
10
+ * budgets. The cache key is `sha256(joined(filePath:contentHash for each
11
+ * file in the chunk, sorted))`. A change to a single file invalidates only
12
+ * that file's chunk — typically 1 of ~50 chunks on a 1000-file repo.
13
+ *
14
+ * Why not per-file:
15
+ * - Workers process sub-batches and emit aggregated `ParseWorkerResult`s.
16
+ * Splitting back to per-file would require reworking the worker contract.
17
+ * - Chunk-level invalidation gives a useful speedup floor (98% on a single
18
+ * 1-of-50 invalidated chunk) without touching the worker.
19
+ *
20
+ * Survives `--force` because it's content-addressed: the same bytes always
21
+ * produce the same key. `--force` only matters for the LadybugDB writeback;
22
+ * the cache itself is always safe to reuse.
23
+ */
24
+ import type { ParseWorkerResult } from '../core/ingestion/workers/parse-worker.js';
25
+ export declare const PARSE_CACHE_VERSION: string;
26
+ /** Runtime view: keyed Map for fast lookup; mutated in place during a run. */
27
+ export interface ParseCache {
28
+ version: string;
29
+ entries: Map<string, ParseWorkerResult[]>;
30
+ /**
31
+ * Hashes referenced (hit OR miss-and-stored) by the current run.
32
+ * The parse phase populates this as it processes chunks; the orchestrator
33
+ * uses it as input to `pruneCache` before saving so entries that no
34
+ * longer correspond to any chunk in the current scan are discarded.
35
+ * Transient — never serialized to disk.
36
+ */
37
+ usedKeys: Set<string>;
38
+ }
39
+ /** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
40
+ export declare const fileContentHash: (content: Buffer | string) => string;
41
+ /**
42
+ * Compute the canonical cache key for a chunk's contents.
43
+ *
44
+ * `entries` is the list of (filePath, file content hash) for every file
45
+ * in the chunk. We sort by filePath before hashing so chunks composed of
46
+ * the same files in different order produce the same key.
47
+ */
48
+ export declare const computeChunkHash: (entries: Array<{
49
+ filePath: string;
50
+ contentHash: string;
51
+ }>) => string;
52
+ /**
53
+ * Load the parse cache. Returns an empty cache on any failure (missing
54
+ * file, corrupt JSON, version mismatch). Never throws on a normal load.
55
+ */
56
+ export declare const loadParseCache: (storagePath: string) => Promise<ParseCache>;
57
+ /**
58
+ * Persist the cache to disk using a temp directory + rename.
59
+ *
60
+ * Writes shards under `${cacheDir}.tmp`, then removes the old `cacheDir` and
61
+ * renames the temp directory into place. There is a crash window after
62
+ * `rm(cacheDir)` and before `rename(tmpDir, cacheDir)` where no cache exists;
63
+ * that is acceptable — `loadParseCache` yields empty and the next run
64
+ * reparses. This is not a single atomic swap of the whole tree, but avoids
65
+ * leaving a half-written shard set visible to readers.
66
+ */
67
+ export declare const saveParseCache: (storagePath: string, cache: ParseCache) => Promise<void>;
68
+ /**
69
+ * Drop entries whose hashes are not in `usedHashes`. Called at the end
70
+ * of a run so chunks that no longer correspond to any current chunk
71
+ * don't keep their stale entries forever.
72
+ */
73
+ export declare const pruneCache: (cache: ParseCache, usedHashes: ReadonlySet<string>) => number;
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Chunk-level content-addressed parse cache.
3
+ *
4
+ * The pipeline always parses every file (correctness invariant: cross-file
5
+ * resolution and downstream phases need full graph data). What this cache
6
+ * does is skip the tree-sitter worker dispatch when a chunk's contents
7
+ * haven't changed since the last run.
8
+ *
9
+ * Granularity: chunk-level. The parse phase chunks files into ~20MB byte
10
+ * budgets. The cache key is `sha256(joined(filePath:contentHash for each
11
+ * file in the chunk, sorted))`. A change to a single file invalidates only
12
+ * that file's chunk — typically 1 of ~50 chunks on a 1000-file repo.
13
+ *
14
+ * Why not per-file:
15
+ * - Workers process sub-batches and emit aggregated `ParseWorkerResult`s.
16
+ * Splitting back to per-file would require reworking the worker contract.
17
+ * - Chunk-level invalidation gives a useful speedup floor (98% on a single
18
+ * 1-of-50 invalidated chunk) without touching the worker.
19
+ *
20
+ * Survives `--force` because it's content-addressed: the same bytes always
21
+ * produce the same key. `--force` only matters for the LadybugDB writeback;
22
+ * the cache itself is always safe to reuse.
23
+ */
24
+ import { createHash } from 'crypto';
25
+ import { createRequire } from 'module';
26
+ import fs from 'fs/promises';
27
+ import path from 'path';
28
+ import { fileURLToPath } from 'url';
29
+ /**
30
+ * Cache version composed of:
31
+ * - A schema bump knob (`SCHEMA_BUMP`) for hand-controlled invalidation
32
+ * when ParseWorkerResult shape or upstream parse semantics change.
33
+ * - The current `gitnexus` npm package version, read at module load.
34
+ * Any release that ships an updated tree-sitter grammar or revised
35
+ * extractor logic implies a version bump in package.json, which
36
+ * automatically invalidates the on-disk cache. Without this, a user
37
+ * running `npm i -g gitnexus@latest` after a parser-affecting
38
+ * release would silently replay pre-upgrade ParseWorkerResults
39
+ * against the new graph schema (Bugbot/Claude review on #1479).
40
+ *
41
+ * On version mismatch, `loadParseCache` returns an empty cache and the
42
+ * next save overwrites the on-disk file with the new version baked in.
43
+ */
44
+ const SCHEMA_BUMP = 1;
45
+ const GITNEXUS_PKG_VERSION = (() => {
46
+ try {
47
+ // package.json sits at gitnexus/package.json — two levels up from
48
+ // gitnexus/src/storage/parse-cache.ts (or its dist/ equivalent).
49
+ const here = path.dirname(fileURLToPath(import.meta.url));
50
+ const candidates = [
51
+ path.join(here, '..', '..', 'package.json'), // src/storage → gitnexus/
52
+ path.join(here, '..', '..', '..', 'package.json'), // dist/storage → gitnexus/
53
+ ];
54
+ const requireCJS = createRequire(import.meta.url);
55
+ for (const c of candidates) {
56
+ try {
57
+ const pkg = requireCJS(c);
58
+ if (typeof pkg?.version === 'string')
59
+ return pkg.version;
60
+ }
61
+ catch {
62
+ /* try next candidate */
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ /* fall through to fallback */
68
+ }
69
+ return '0.0.0-unknown';
70
+ })();
71
+ export const PARSE_CACHE_VERSION = `${SCHEMA_BUMP}+${GITNEXUS_PKG_VERSION}`;
72
+ const LEGACY_CACHE_FILENAME = 'parse-cache.json';
73
+ const CACHE_DIRNAME = 'parse-cache';
74
+ const CACHE_INDEX_FILENAME = 'index.json';
75
+ /** Keys on disk always come from `computeChunkHash` — 64-char lowercase hex. */
76
+ const CHUNK_CACHE_KEY_HEX_RE = /^[a-f0-9]{64}$/;
77
+ const isValidChunkCacheKey = (chunkHash) => CHUNK_CACHE_KEY_HEX_RE.test(chunkHash);
78
+ /** SHA-256 hex of a single string or buffer. */
79
+ const sha256Hex = (input) => createHash('sha256')
80
+ .update(typeof input === 'string' ? Buffer.from(input) : input)
81
+ .digest('hex');
82
+ /** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
83
+ export const fileContentHash = (content) => sha256Hex(content);
84
+ /**
85
+ * Compute the canonical cache key for a chunk's contents.
86
+ *
87
+ * `entries` is the list of (filePath, file content hash) for every file
88
+ * in the chunk. We sort by filePath before hashing so chunks composed of
89
+ * the same files in different order produce the same key.
90
+ */
91
+ export const computeChunkHash = (entries) => {
92
+ const sorted = [...entries].sort((a, b) => (a.filePath < b.filePath ? -1 : 1));
93
+ const joined = sorted.map((e) => `${e.filePath}:${e.contentHash}`).join('\n');
94
+ return sha256Hex(joined);
95
+ };
96
+ /**
97
+ * JSON replacer that round-trips Map/Set instances through plain JSON.
98
+ *
99
+ * `ParseWorkerResult.parsedFiles[*].scopes[*].typeBindings` is a
100
+ * `ReadonlyMap<string, TypeRef>`; without this transform it serializes
101
+ * to `{}` and downstream code that iterates / `.get()`s on it crashes
102
+ * with "is not iterable". Applied symmetrically by `mapReviver` on
103
+ * load so the in-memory shape stays Map-typed.
104
+ */
105
+ const MAP_TAG = '__$mapEntries$__';
106
+ const SET_TAG = '__$setValues$__';
107
+ const mapReplacer = (_key, value) => {
108
+ if (value instanceof Map)
109
+ return { [MAP_TAG]: Array.from(value.entries()) };
110
+ if (value instanceof Set)
111
+ return { [SET_TAG]: Array.from(value.values()) };
112
+ return value;
113
+ };
114
+ const mapReviver = (_key, value) => {
115
+ if (value && typeof value === 'object') {
116
+ const v = value;
117
+ if (Array.isArray(v[MAP_TAG]))
118
+ return new Map(v[MAP_TAG]);
119
+ if (Array.isArray(v[SET_TAG]))
120
+ return new Set(v[SET_TAG]);
121
+ }
122
+ return value;
123
+ };
124
+ const getLegacyCachePath = (storagePath) => path.join(storagePath, LEGACY_CACHE_FILENAME);
125
+ const getCacheDirPath = (storagePath) => path.join(storagePath, CACHE_DIRNAME);
126
+ const getCacheIndexPath = (storagePath) => path.join(getCacheDirPath(storagePath), CACHE_INDEX_FILENAME);
127
+ const getCacheChunkPath = (storagePath, chunkHash) => path.join(getCacheDirPath(storagePath), `${chunkHash}.json`);
128
+ const loadLegacyParseCache = async (storagePath) => {
129
+ const cachePath = getLegacyCachePath(storagePath);
130
+ try {
131
+ const raw = await fs.readFile(cachePath, 'utf-8');
132
+ const data = JSON.parse(raw, mapReviver);
133
+ if (typeof data !== 'object' ||
134
+ data === null ||
135
+ data.version !== PARSE_CACHE_VERSION ||
136
+ typeof data.entries !== 'object' ||
137
+ data.entries === null) {
138
+ return emptyCache();
139
+ }
140
+ const entries = new Map();
141
+ for (const [k, v] of Object.entries(data.entries)) {
142
+ if (Array.isArray(v))
143
+ entries.set(k, v);
144
+ }
145
+ return { version: PARSE_CACHE_VERSION, entries, usedKeys: new Set() };
146
+ }
147
+ catch {
148
+ return emptyCache();
149
+ }
150
+ };
151
+ const loadShardedParseCache = async (storagePath) => {
152
+ const indexPath = getCacheIndexPath(storagePath);
153
+ try {
154
+ const raw = await fs.readFile(indexPath, 'utf-8');
155
+ const data = JSON.parse(raw);
156
+ if (typeof data !== 'object' ||
157
+ data === null ||
158
+ data.version !== PARSE_CACHE_VERSION ||
159
+ !Array.isArray(data.keys)) {
160
+ return emptyCache();
161
+ }
162
+ const entries = new Map();
163
+ for (const chunkHash of data.keys) {
164
+ if (typeof chunkHash !== 'string' || !isValidChunkCacheKey(chunkHash))
165
+ continue;
166
+ try {
167
+ const chunkRaw = await fs.readFile(getCacheChunkPath(storagePath, chunkHash), 'utf-8');
168
+ const chunkData = JSON.parse(chunkRaw, mapReviver);
169
+ if (Array.isArray(chunkData))
170
+ entries.set(chunkHash, chunkData);
171
+ }
172
+ catch {
173
+ /* skip corrupt or missing shard */
174
+ }
175
+ }
176
+ return { version: PARSE_CACHE_VERSION, entries, usedKeys: new Set() };
177
+ }
178
+ catch {
179
+ return null;
180
+ }
181
+ };
182
+ /**
183
+ * Load the parse cache. Returns an empty cache on any failure (missing
184
+ * file, corrupt JSON, version mismatch). Never throws on a normal load.
185
+ */
186
+ export const loadParseCache = async (storagePath) => {
187
+ const sharded = await loadShardedParseCache(storagePath);
188
+ if (sharded)
189
+ return sharded;
190
+ return loadLegacyParseCache(storagePath);
191
+ };
192
+ /**
193
+ * Persist the cache to disk using a temp directory + rename.
194
+ *
195
+ * Writes shards under `${cacheDir}.tmp`, then removes the old `cacheDir` and
196
+ * renames the temp directory into place. There is a crash window after
197
+ * `rm(cacheDir)` and before `rename(tmpDir, cacheDir)` where no cache exists;
198
+ * that is acceptable — `loadParseCache` yields empty and the next run
199
+ * reparses. This is not a single atomic swap of the whole tree, but avoids
200
+ * leaving a half-written shard set visible to readers.
201
+ */
202
+ export const saveParseCache = async (storagePath, cache) => {
203
+ await fs.mkdir(storagePath, { recursive: true });
204
+ const cacheDir = getCacheDirPath(storagePath);
205
+ const tmpDir = `${cacheDir}.tmp`;
206
+ await fs.rm(tmpDir, { recursive: true, force: true });
207
+ await fs.mkdir(tmpDir, { recursive: true });
208
+ const keys = [];
209
+ for (const [chunkHash, chunkResults] of cache.entries) {
210
+ if (!isValidChunkCacheKey(chunkHash))
211
+ continue;
212
+ let payload;
213
+ try {
214
+ payload = JSON.stringify(chunkResults, mapReplacer);
215
+ }
216
+ catch {
217
+ // Extremely dense chunks could theoretically exceed string limits; skip
218
+ // rather than failing the entire save (orchestrator catches save errors).
219
+ continue;
220
+ }
221
+ keys.push(chunkHash);
222
+ const chunkPath = path.join(tmpDir, `${chunkHash}.json`);
223
+ await fs.writeFile(chunkPath, payload, 'utf-8');
224
+ }
225
+ const index = {
226
+ version: cache.version,
227
+ keys,
228
+ };
229
+ await fs.writeFile(path.join(tmpDir, CACHE_INDEX_FILENAME), JSON.stringify(index), 'utf-8');
230
+ await fs.rm(cacheDir, { recursive: true, force: true });
231
+ await fs.rename(tmpDir, cacheDir);
232
+ await fs.rm(getLegacyCachePath(storagePath), { force: true });
233
+ };
234
+ /**
235
+ * Drop entries whose hashes are not in `usedHashes`. Called at the end
236
+ * of a run so chunks that no longer correspond to any current chunk
237
+ * don't keep their stale entries forever.
238
+ */
239
+ export const pruneCache = (cache, usedHashes) => {
240
+ let removed = 0;
241
+ for (const k of cache.entries.keys()) {
242
+ if (!usedHashes.has(k)) {
243
+ cache.entries.delete(k);
244
+ removed++;
245
+ }
246
+ }
247
+ return removed;
248
+ };
249
+ const emptyCache = () => ({
250
+ version: PARSE_CACHE_VERSION,
251
+ entries: new Map(),
252
+ usedKeys: new Set(),
253
+ });
@@ -56,7 +56,38 @@ export interface RepoMeta {
56
56
  processes?: number;
57
57
  embeddings?: number;
58
58
  };
59
+ /**
60
+ * Bumped whenever incremental-indexing invariants change in an
61
+ * incompatible way (delete-and-rewrite logic, subgraph extraction,
62
+ * graph-wide node handling). On mismatch, runFullAnalysis forces a
63
+ * full rebuild rather than risk an inconsistent incremental update.
64
+ */
65
+ schemaVersion?: number;
66
+ /**
67
+ * SHA-256 of every file's content at the time of the last successful
68
+ * indexing run. The next run computes current hashes and diffs against
69
+ * this map to determine which files' DB rows must be replaced.
70
+ * Map keys are repo-relative paths.
71
+ */
72
+ fileHashes?: Record<string, string>;
73
+ /**
74
+ * Crash-recovery dirty flag. Written to meta.json BEFORE any
75
+ * destructive DB mutation in an incremental run; cleared on success
76
+ * by overwriting meta.json. If a run crashes between, the next run
77
+ * sees the flag and forces a full rebuild — the cheapest path back
78
+ * to a known-good index.
79
+ */
80
+ incrementalInProgress?: {
81
+ /** When the incremental run started (epoch ms). */
82
+ startedAt: number;
83
+ /** Number of files in the writable set, for diagnostic logs. */
84
+ toWriteCount: number;
85
+ };
59
86
  }
87
+ /**
88
+ * Bumped whenever incremental-indexing invariants change incompatibly.
89
+ */
90
+ export declare const INCREMENTAL_SCHEMA_VERSION = 1;
60
91
  export interface IndexedRepo {
61
92
  repoPath: string;
62
93
  storagePath: string;
@@ -112,7 +143,16 @@ export declare const cleanupOldKuzuFiles: (storagePath: string) => Promise<{
112
143
  */
113
144
  export declare const loadMeta: (storagePath: string) => Promise<RepoMeta | null>;
114
145
  /**
115
- * Save metadata to storage
146
+ * Save metadata to storage.
147
+ *
148
+ * Atomic via tmp-file + rename (matches `saveParseCache`'s pattern). The
149
+ * `incrementalInProgress` dirty flag travels through this file — a crash
150
+ * mid-write would leave a corrupt `meta.json` that the next run's
151
+ * `loadMeta` would silently treat as "no prior index", losing the dirty
152
+ * flag and skipping the recovery full-rebuild. Write-and-rename rules
153
+ * that out: the rename is atomic on POSIX and on Windows (`fs.rename`
154
+ * on `node:fs/promises` uses `MoveFileEx(REPLACE_EXISTING)`), so either
155
+ * the old or the new file is observed at every moment.
116
156
  */
117
157
  export declare const saveMeta: (storagePath: string, meta: RepoMeta) => Promise<void>;
118
158
  /**