gitnexus 1.6.5-rc.2 → 1.6.5-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 (288) hide show
  1. package/README.md +1 -1
  2. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +26 -0
  3. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  4. package/dist/_shared/scope-resolution/method-dispatch-index.js +22 -3
  5. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  6. package/dist/_shared/scope-resolution/registries/lookup-core.js +18 -1
  7. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  8. package/dist/cli/ai-context.d.ts +1 -0
  9. package/dist/cli/ai-context.js +30 -12
  10. package/dist/cli/analyze.d.ts +18 -0
  11. package/dist/cli/analyze.js +33 -4
  12. package/dist/cli/augment.d.ts +1 -1
  13. package/dist/cli/augment.js +1 -1
  14. package/dist/cli/index.js +6 -1
  15. package/dist/cli/setup.js +6 -0
  16. package/dist/core/augmentation/engine.d.ts +2 -2
  17. package/dist/core/augmentation/engine.js +26 -6
  18. package/dist/core/embeddings/ast-utils.js +2 -1
  19. package/dist/core/embeddings/http-client.js +22 -6
  20. package/dist/core/group/extractors/grpc-extractor.js +2 -1
  21. package/dist/core/group/extractors/http-patterns/python.js +175 -0
  22. package/dist/core/group/extractors/http-route-extractor.js +2 -1
  23. package/dist/core/group/extractors/include-extractor.js +2 -1
  24. package/dist/core/group/extractors/thrift-extractor.js +2 -1
  25. package/dist/core/group/extractors/tree-sitter-scanner.js +2 -1
  26. package/dist/core/incremental/shadow-candidates.d.ts +44 -0
  27. package/dist/core/incremental/shadow-candidates.js +74 -0
  28. package/dist/core/incremental/subgraph-extract.d.ts +64 -0
  29. package/dist/core/incremental/subgraph-extract.js +111 -0
  30. package/dist/core/ingestion/call-processor.js +166 -47
  31. package/dist/core/ingestion/community-processor.js +18 -0
  32. package/dist/core/ingestion/heritage-processor.js +3 -2
  33. package/dist/core/ingestion/import-processor.js +2 -1
  34. package/dist/core/ingestion/import-resolvers/standard.d.ts +5 -0
  35. package/dist/core/ingestion/import-resolvers/standard.js +27 -1
  36. package/dist/core/ingestion/import-resolvers/utils.js +4 -0
  37. package/dist/core/ingestion/languages/c/arity-metadata.d.ts +14 -0
  38. package/dist/core/ingestion/languages/c/arity-metadata.js +94 -0
  39. package/dist/core/ingestion/languages/c/arity.d.ts +6 -0
  40. package/dist/core/ingestion/languages/c/arity.js +18 -0
  41. package/dist/core/ingestion/languages/c/captures.d.ts +2 -0
  42. package/dist/core/ingestion/languages/c/captures.js +105 -0
  43. package/dist/core/ingestion/languages/c/header-scan.d.ts +7 -0
  44. package/dist/core/ingestion/languages/c/header-scan.js +55 -0
  45. package/dist/core/ingestion/languages/c/import-decomposer.d.ts +8 -0
  46. package/dist/core/ingestion/languages/c/import-decomposer.js +50 -0
  47. package/dist/core/ingestion/languages/c/import-target.d.ts +14 -0
  48. package/dist/core/ingestion/languages/c/import-target.js +57 -0
  49. package/dist/core/ingestion/languages/c/index.d.ts +11 -0
  50. package/dist/core/ingestion/languages/c/index.js +11 -0
  51. package/dist/core/ingestion/languages/c/interpret.d.ts +14 -0
  52. package/dist/core/ingestion/languages/c/interpret.js +48 -0
  53. package/dist/core/ingestion/languages/c/merge-bindings.d.ts +7 -0
  54. package/dist/core/ingestion/languages/c/merge-bindings.js +23 -0
  55. package/dist/core/ingestion/languages/c/query.d.ts +3 -0
  56. package/dist/core/ingestion/languages/c/query.js +161 -0
  57. package/dist/core/ingestion/languages/c/scope-resolver.d.ts +13 -0
  58. package/dist/core/ingestion/languages/c/scope-resolver.js +60 -0
  59. package/dist/core/ingestion/languages/c/simple-hooks.d.ts +14 -0
  60. package/dist/core/ingestion/languages/c/simple-hooks.js +19 -0
  61. package/dist/core/ingestion/languages/c/static-linkage.d.ts +13 -0
  62. package/dist/core/ingestion/languages/c/static-linkage.js +57 -0
  63. package/dist/core/ingestion/languages/c-cpp.js +10 -0
  64. package/dist/core/ingestion/languages/csharp/captures.js +2 -1
  65. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +3 -2
  66. package/dist/core/ingestion/languages/go/captures.js +2 -1
  67. package/dist/core/ingestion/languages/go/range-binding.js +2 -1
  68. package/dist/core/ingestion/languages/java/arity-metadata.d.ts +18 -0
  69. package/dist/core/ingestion/languages/java/arity-metadata.js +40 -0
  70. package/dist/core/ingestion/languages/java/arity.d.ts +10 -0
  71. package/dist/core/ingestion/languages/java/arity.js +24 -0
  72. package/dist/core/ingestion/languages/java/cache-stats.d.ts +15 -0
  73. package/dist/core/ingestion/languages/java/cache-stats.js +26 -0
  74. package/dist/core/ingestion/languages/java/captures.d.ts +17 -0
  75. package/dist/core/ingestion/languages/java/captures.js +187 -0
  76. package/dist/core/ingestion/languages/java/import-decomposer.d.ts +18 -0
  77. package/dist/core/ingestion/languages/java/import-decomposer.js +85 -0
  78. package/dist/core/ingestion/languages/java/import-target.d.ts +17 -0
  79. package/dist/core/ingestion/languages/java/import-target.js +100 -0
  80. package/dist/core/ingestion/languages/java/index.d.ts +29 -0
  81. package/dist/core/ingestion/languages/java/index.js +29 -0
  82. package/dist/core/ingestion/languages/java/interpret.d.ts +13 -0
  83. package/dist/core/ingestion/languages/java/interpret.js +131 -0
  84. package/dist/core/ingestion/languages/java/merge-bindings.d.ts +12 -0
  85. package/dist/core/ingestion/languages/java/merge-bindings.js +40 -0
  86. package/dist/core/ingestion/languages/java/query.d.ts +30 -0
  87. package/dist/core/ingestion/languages/java/query.js +192 -0
  88. package/dist/core/ingestion/languages/java/receiver-binding.d.ts +11 -0
  89. package/dist/core/ingestion/languages/java/receiver-binding.js +95 -0
  90. package/dist/core/ingestion/languages/java/scope-resolver.d.ts +50 -0
  91. package/dist/core/ingestion/languages/java/scope-resolver.js +74 -0
  92. package/dist/core/ingestion/languages/java/simple-hooks.d.ts +13 -0
  93. package/dist/core/ingestion/languages/java/simple-hooks.js +34 -0
  94. package/dist/core/ingestion/languages/java.js +11 -0
  95. package/dist/core/ingestion/languages/php/arity-metadata.d.ts +28 -0
  96. package/dist/core/ingestion/languages/php/arity-metadata.js +63 -0
  97. package/dist/core/ingestion/languages/php/arity.d.ts +25 -0
  98. package/dist/core/ingestion/languages/php/arity.js +40 -0
  99. package/dist/core/ingestion/languages/php/cache-stats.d.ts +15 -0
  100. package/dist/core/ingestion/languages/php/cache-stats.js +26 -0
  101. package/dist/core/ingestion/languages/php/captures.d.ts +34 -0
  102. package/dist/core/ingestion/languages/php/captures.js +739 -0
  103. package/dist/core/ingestion/languages/php/import-decomposer.d.ts +28 -0
  104. package/dist/core/ingestion/languages/php/import-decomposer.js +265 -0
  105. package/dist/core/ingestion/languages/php/import-target.d.ts +47 -0
  106. package/dist/core/ingestion/languages/php/import-target.js +100 -0
  107. package/dist/core/ingestion/languages/php/index.d.ts +68 -0
  108. package/dist/core/ingestion/languages/php/index.js +72 -0
  109. package/dist/core/ingestion/languages/php/interpret.d.ts +36 -0
  110. package/dist/core/ingestion/languages/php/interpret.js +241 -0
  111. package/dist/core/ingestion/languages/php/merge-bindings.d.ts +19 -0
  112. package/dist/core/ingestion/languages/php/merge-bindings.js +47 -0
  113. package/dist/core/ingestion/languages/php/namespace-siblings.d.ts +51 -0
  114. package/dist/core/ingestion/languages/php/namespace-siblings.js +288 -0
  115. package/dist/core/ingestion/languages/php/query.d.ts +32 -0
  116. package/dist/core/ingestion/languages/php/query.js +326 -0
  117. package/dist/core/ingestion/languages/php/receiver-binding.d.ts +36 -0
  118. package/dist/core/ingestion/languages/php/receiver-binding.js +128 -0
  119. package/dist/core/ingestion/languages/php/scope-resolver.d.ts +23 -0
  120. package/dist/core/ingestion/languages/php/scope-resolver.js +358 -0
  121. package/dist/core/ingestion/languages/php/simple-hooks.d.ts +42 -0
  122. package/dist/core/ingestion/languages/php/simple-hooks.js +111 -0
  123. package/dist/core/ingestion/languages/php.d.ts +0 -7
  124. package/dist/core/ingestion/languages/php.js +15 -0
  125. package/dist/core/ingestion/languages/python/captures.js +2 -1
  126. package/dist/core/ingestion/languages/typescript/captures.js +2 -1
  127. package/dist/core/ingestion/parsing-processor.d.ts +22 -2
  128. package/dist/core/ingestion/parsing-processor.js +85 -50
  129. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +5 -0
  130. package/dist/core/ingestion/pipeline-phases/parse-impl.js +119 -11
  131. package/dist/core/ingestion/pipeline-phases/parse.d.ts +14 -0
  132. package/dist/core/ingestion/pipeline.d.ts +13 -0
  133. package/dist/core/ingestion/registry-primary-flag.js +2 -0
  134. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +65 -0
  135. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +1 -1
  136. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +12 -2
  137. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +5 -0
  138. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +25 -1
  139. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +79 -7
  140. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +7 -3
  141. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +24 -4
  142. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +21 -2
  143. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +13 -1
  144. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +6 -0
  145. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +17 -0
  146. package/dist/core/ingestion/scope-resolution/pipeline/run.js +31 -7
  147. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  148. package/dist/core/ingestion/tree-sitter-queries.js +10 -0
  149. package/dist/core/ingestion/workers/parse-worker.js +2 -1
  150. package/dist/core/lbug/lbug-adapter.d.ts +26 -0
  151. package/dist/core/lbug/lbug-adapter.js +174 -40
  152. package/dist/core/lbug/pool-adapter.js +19 -2
  153. package/dist/core/run-analyze.d.ts +2 -0
  154. package/dist/core/run-analyze.js +323 -29
  155. package/dist/core/tree-sitter/safe-parse.d.ts +6 -0
  156. package/dist/core/tree-sitter/safe-parse.js +32 -0
  157. package/dist/server/git-clone.js +10 -13
  158. package/dist/storage/file-hash.d.ts +47 -0
  159. package/dist/storage/file-hash.js +86 -0
  160. package/dist/storage/git.d.ts +17 -6
  161. package/dist/storage/git.js +46 -12
  162. package/dist/storage/parse-cache.d.ts +67 -0
  163. package/dist/storage/parse-cache.js +182 -0
  164. package/dist/storage/repo-manager.d.ts +41 -1
  165. package/dist/storage/repo-manager.js +17 -2
  166. package/hooks/claude/gitnexus-hook.cjs +8 -1
  167. package/hooks/claude/hook-lock.cjs +119 -0
  168. package/package.json +1 -1
  169. package/web/assets/{agent-BpHQleHE.js → agent-BG6RCOzz.js} +1 -1
  170. package/web/assets/architecture-7EHR7CIX-6QZW5X65-F-tujre-.js +1 -0
  171. package/web/assets/architectureDiagram-UL44E2DR-2GL7nOvO.js +36 -0
  172. package/web/assets/blockDiagram-7IZFK4PR-5AjX0pPU.js +132 -0
  173. package/web/assets/{c4Diagram-DFAF54RM-DeJV1rCn.js → c4Diagram-Y2BXMSZH-hLOeq7xC.js} +2 -2
  174. package/web/assets/chunk-2T2R6R2M-B_pq_2nf.js +4 -0
  175. package/web/assets/chunk-2UTLFMKG-CpoohCFN.js +1 -0
  176. package/web/assets/{chunk-7RZVMHOQ-BdIU-RGO.js → chunk-3SSMPTDK-CfqpR3Nr.js} +1 -1
  177. package/web/assets/chunk-4R4BOZG6-D3gm7tYj.js +159 -0
  178. package/web/assets/chunk-6764PJDD-WcI965eO.js +1 -0
  179. package/web/assets/chunk-AQ6EADP3-BpMj3rQl.js +1 -0
  180. package/web/assets/{chunk-KSICW3F5-C2tZmXwv.js → chunk-AZZRMDJM-DSoCJlUA.js} +1 -1
  181. package/web/assets/chunk-C62D2QBJ-CvFF2PnB.js +1 -0
  182. package/web/assets/chunk-CEXFNPSA-DdNtJtrF.js +1 -0
  183. package/web/assets/chunk-J5EP6P6S-DtcVaAJI.js +1 -0
  184. package/web/assets/{chunk-AEOMTBSW-D7qjBMHW.js → chunk-JQRUD6KW-DElaNjc0.js} +1 -1
  185. package/web/assets/chunk-KGYTTC2M-D0K5or7e.js +1 -0
  186. package/web/assets/chunk-KRXBNO2N-DpR8iDt8.js +1 -0
  187. package/web/assets/chunk-LCXTWHL2-DdMObOEk.js +231 -0
  188. package/web/assets/chunk-LII3EMHJ-I78zXtzi.js +1 -0
  189. package/web/assets/chunk-RERM46MO-Betu1cSy.js +1 -0
  190. package/web/assets/chunk-RG4AUYOV-BUCJ2JP5.js +206 -0
  191. package/web/assets/chunk-RKZBBQEN-Cutpza0t.js +1 -0
  192. package/web/assets/chunk-RLI5ZMPA-BFUxCvX2.js +1 -0
  193. package/web/assets/chunk-T5OCTHI4-DKwlkuvB.js +1 -0
  194. package/web/assets/chunk-UP6H54XL-0YWa0NDg.js +1 -0
  195. package/web/assets/chunk-UXSXWOXI-CSz0OXpz.js +1 -0
  196. package/web/assets/chunk-W44A43WB-AOmgoT-X.js +1 -0
  197. package/web/assets/{chunk-RWUO3TPN-DTWzAhQ1.js → chunk-ZXARS5L4-DIzgxjMw.js} +1 -1
  198. package/web/assets/classDiagram-KGZ6W3CR-BT-rn7LF.js +1 -0
  199. package/web/assets/classDiagram-v2-72OJOZXJ-uR3nDadY.js +1 -0
  200. package/web/assets/{cose-bilkent-PNC4W37J-DdZq7Qz_.js → cose-bilkent-UX7MHV2Q-DCSCD1TK.js} +1 -1
  201. package/web/assets/dagre-ND4H6XIP-CsciHSij.js +4 -0
  202. package/web/assets/diagram-3NCE3AQN-BKUT5Rpc.js +43 -0
  203. package/web/assets/diagram-GF46GFSD-Dxb4hZSo.js +24 -0
  204. package/web/assets/diagram-HNR7UZ2L-D3qus9Cn.js +3 -0
  205. package/web/assets/diagram-QXG6HAR7-r9BMLGGP.js +24 -0
  206. package/web/assets/diagram-WEQXMOUZ-BGXqs7-m.js +10 -0
  207. package/web/assets/{erDiagram-GCSMX5X6-DfdN6iqF.js → erDiagram-L5TCEMPS-LgSWVrkZ.js} +2 -2
  208. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-By7r3bla.js +1 -0
  209. package/web/assets/flowDiagram-H6V6AXG4-Bj_vV5ky.js +162 -0
  210. package/web/assets/ganttDiagram-JCBTUEKG-B1eVITSh.js +292 -0
  211. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-ClEP6RdE.js +1 -0
  212. package/web/assets/gitGraphDiagram-S2ZK5IYY-BP0R-_FD.js +106 -0
  213. package/web/assets/{index-3NUCUW2s.js → index-oL-ZNYwb.js} +80 -81
  214. package/web/assets/info-J43DQDTF-KCYPFFUO-bBJRjmP5.js +1 -0
  215. package/web/assets/infoDiagram-3YFTVSEB-Dmj4OEO3.js +2 -0
  216. package/web/assets/{ishikawaDiagram-YMYX4NHK-BF8AtDaj.js → ishikawaDiagram-BNXS4ZKH-B3NgQ4lJ.js} +2 -2
  217. package/web/assets/{journeyDiagram-SO5T7YLQ-BW8oKa3N.js → journeyDiagram-M6C3CM3L-DALmL4_N.js} +3 -3
  218. package/web/assets/{kanban-definition-LJHFXRCJ-DzbOsR-2.js → kanban-definition-75IXJCU3-AvVbHpnN.js} +4 -4
  219. package/web/assets/{katex-GD7MH7QM-CJiOjBBJ.js → katex-K3KEBU37-CqF6_ZN-.js} +1 -1
  220. package/web/assets/{mindmap-definition-2EUWGEK5-BIXGQALq.js → mindmap-definition-2TDM6QVE-B5c9VOzs.js} +28 -28
  221. package/web/assets/packet-YPE3B663-LP52Z2RK-1ZCldnu3.js +1 -0
  222. package/web/assets/pie-LRSECV5Y-TCRJHUBD-EhjHC5EE.js +1 -0
  223. package/web/assets/pieDiagram-CU6KROY3-Po2HSSR4.js +30 -0
  224. package/web/assets/quadrantDiagram-VICAPDV7-C47UxSZD.js +7 -0
  225. package/web/assets/radar-GUYGQ44K-RDLRG3WG-CvXjgxlW.js +1 -0
  226. package/web/assets/{requirementDiagram-M5DCFWZL-CzUUrhQD.js → requirementDiagram-JXO7QTGE-CpECN_Hq.js} +6 -6
  227. package/web/assets/sankeyDiagram-URQDO5SZ-B3b7cLrS.js +40 -0
  228. package/web/assets/sequenceDiagram-VS2MUI6T-CRJ95oVP.js +162 -0
  229. package/web/assets/stateDiagram-7D4R322I-C7U9b3ks.js +1 -0
  230. package/web/assets/stateDiagram-v2-36443NZ5-BQajbtov.js +1 -0
  231. package/web/assets/{timeline-definition-5SPVSISX-DkMiGj4Z.js → timeline-definition-O6YCAMPW-rtbk6BBo.js} +4 -4
  232. package/web/assets/treeView-BLDUP644-QA4HXRO3-R2o5gwfP.js +1 -0
  233. package/web/assets/treemap-LRROVOQU-LLAWBHMP-Cesmac_u.js +1 -0
  234. package/web/assets/{vennDiagram-IE5QUKF5-BCxMmf9q.js → vennDiagram-MWXL3ELB-W8Yoaayh.js} +3 -3
  235. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-B9HYoRgA.js +1 -0
  236. package/web/assets/wardleyDiagram-CUQ6CDDI-DIZw5Abj.js +78 -0
  237. package/web/assets/{xychartDiagram-ZHJ5623Y-DZ7fP1_3.js → xychartDiagram-N2JHSOCM-DGRtrj4P.js} +3 -3
  238. package/web/index.html +2 -2
  239. package/web/assets/architecture-YZFGNWBL-S5CXDPWN-BUzkWgDl.js +0 -1
  240. package/web/assets/architectureDiagram-EMZXCZ2Q-CXbzok1G.js +0 -36
  241. package/web/assets/blockDiagram-IGV67L2C-DA1QJ4ZB.js +0 -132
  242. package/web/assets/chunk-3GS5O3IE-B8hpnia8.js +0 -231
  243. package/web/assets/chunk-3YCYZ6SJ-B-BVu21C.js +0 -1
  244. package/web/assets/chunk-6NTNNK5N-DW-jYSDr.js +0 -1
  245. package/web/assets/chunk-A34GCYZU-BbZl-IKy.js +0 -1
  246. package/web/assets/chunk-DJ7UZH7F-B5tMfF87.js +0 -1
  247. package/web/assets/chunk-DKKBVRCY-Djm-6K_e.js +0 -4
  248. package/web/assets/chunk-DU5LTGQ6-QPoynYcp.js +0 -1
  249. package/web/assets/chunk-FXACKDTF-jieSmbe2.js +0 -159
  250. package/web/assets/chunk-H3VCZNTA-8EMHZ3SV.js +0 -1
  251. package/web/assets/chunk-HN6EAY2L-CgE_agsD.js +0 -1
  252. package/web/assets/chunk-O5ABG6QK-DEZV1O19.js +0 -1
  253. package/web/assets/chunk-PK6DOVAG-BXv0JnsJ.js +0 -206
  254. package/web/assets/chunk-RNJOYNJ4-BiBkF9dN.js +0 -1
  255. package/web/assets/chunk-TBF5ZNIQ-BSl6s9lT.js +0 -1
  256. package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +0 -1
  257. package/web/assets/chunk-TYMNRAUI-D1NDLIMG.js +0 -1
  258. package/web/assets/chunk-VELTKBKT-C9dVN39o.js +0 -1
  259. package/web/assets/chunk-W7ZLLLMY-Bu-cdLzs.js +0 -1
  260. package/web/assets/chunk-WSB5WSVC-Dor71SOz.js +0 -1
  261. package/web/assets/chunk-XGPFEOL4-CUonLRle.js +0 -1
  262. package/web/assets/classDiagram-PPOCWD7C-Cdd6UdVv.js +0 -1
  263. package/web/assets/classDiagram-v2-23LJLIIU-DNhxY2Ib.js +0 -1
  264. package/web/assets/dagre-E77IOHMT-Cfj_6e17.js +0 -4
  265. package/web/assets/diagram-H7BISOXX-BNvpsrBv.js +0 -43
  266. package/web/assets/diagram-JC5VWROH-Cn0rtDbJ.js +0 -24
  267. package/web/assets/diagram-LXUTUG65-CHfoVKNm.js +0 -10
  268. package/web/assets/diagram-WEHSV5V5-BLxHksnu.js +0 -24
  269. package/web/assets/flowDiagram-OTCZ4VVT-aAZw4c_M.js +0 -162
  270. package/web/assets/ganttDiagram-MUNLMDZQ-BEfC_7Or.js +0 -292
  271. package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-B6ZeRLP0.js +0 -1
  272. package/web/assets/gitGraphDiagram-3HKGZ4G3-CG0kC8Q5.js +0 -106
  273. package/web/assets/info-OMHHGYJF-BF2H5H6G-BIOsZy0W.js +0 -1
  274. package/web/assets/infoDiagram-MN7RKWGX-DUoMH-pq.js +0 -2
  275. package/web/assets/packet-4T2RLAQJ-EV4IVRXR-Bs7ysNMU.js +0 -1
  276. package/web/assets/pie-ZZUOXDRM-N23DN5KN-Csi8nnbH.js +0 -1
  277. package/web/assets/pieDiagram-3IATQBI2-QY-u7P6a.js +0 -30
  278. package/web/assets/quadrantDiagram-E256RVCF-Di4aNiU8.js +0 -7
  279. package/web/assets/radar-PYXPWWZC-P6TP7ZYP-DJlyXh2-.js +0 -1
  280. package/web/assets/sankeyDiagram-L3NBLAOT-D2fbMiIU.js +0 -10
  281. package/web/assets/sequenceDiagram-ZOUHS735-BNdkqguh.js +0 -157
  282. package/web/assets/stateDiagram-MLPALWAM-51_Q2w2L.js +0 -1
  283. package/web/assets/stateDiagram-v2-B5LQ5ZB2-8nHs2aNE.js +0 -1
  284. package/web/assets/treeView-SZITEDCU-5DXDK3XO-Cy6P0_CZ.js +0 -1
  285. package/web/assets/treemap-W4RFUUIX-WYLRDWKO-C_pp1R7A.js +0 -1
  286. package/web/assets/wardley-RL74JXVD-BCRCBASE-CMStJhpn.js +0 -1
  287. package/web/assets/wardleyDiagram-XU3VSMPF-ijlLF77s.js +0 -20
  288. /package/web/assets/{context-builder-CqQNhRj1.js → context-builder-CG-ydtp4.js} +0 -0
@@ -10,10 +10,15 @@
10
10
  */
11
11
  import path from 'path';
12
12
  import fs from 'fs/promises';
13
+ import { execFileSync } from 'child_process';
13
14
  import { runPipelineFromRepo } from './ingestion/pipeline.js';
14
- import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, } from './lbug/lbug-adapter.js';
15
+ import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, loadCachedEmbeddings, deleteNodesForFile, deleteAllCommunitiesAndProcesses, queryImporters, } from './lbug/lbug-adapter.js';
15
16
  import { createSearchFTSIndexes } from './search/fts-indexes.js';
16
- import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, } from '../storage/repo-manager.js';
17
+ import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, INCREMENTAL_SCHEMA_VERSION, } from '../storage/repo-manager.js';
18
+ import { computeFileHashes, diffFileHashes } from '../storage/file-hash.js';
19
+ import { extractChangedSubgraph, computeEffectiveWriteSet, } from './incremental/subgraph-extract.js';
20
+ import { shadowCandidatesFor } from './incremental/shadow-candidates.js';
21
+ import { loadParseCache, saveParseCache, pruneCache } from '../storage/parse-cache.js';
17
22
  import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
18
23
  import { generateAIContextFiles } from '../cli/ai-context.js';
19
24
  import { EMBEDDING_TABLE_NAME } from './lbug/schema.js';
@@ -63,22 +68,74 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
63
68
  const repoHasGit = hasGitDir(repoPath);
64
69
  const currentCommit = repoHasGit ? getCurrentCommit(repoPath) : '';
65
70
  const existingMeta = await loadMeta(storagePath);
71
+ // ── Crash recovery: dirty flag forces full rebuild ────────────────
72
+ // If the previous incremental run set incrementalInProgress and didn't
73
+ // clear it, the on-disk index may be in a half-state. Cheapest path
74
+ // back to a known-good index is to wipe + rebuild from scratch.
75
+ if (existingMeta?.incrementalInProgress) {
76
+ log('Previous incremental run did not complete cleanly (incrementalInProgress flag set); ' +
77
+ 'forcing full rebuild to restore a known-good index.');
78
+ options = { ...options, force: true };
79
+ // Reload meta after clearing the flag in-memory; we still want fileHashes
80
+ // for the post-rebuild meta carry-over, but force=true ensures the
81
+ // rebuild path executes.
82
+ }
66
83
  // ── Early-return: already up to date ──────────────────────────────
67
84
  if (existingMeta && !options.force && existingMeta.lastCommit === currentCommit) {
68
85
  // Non-git folders have currentCommit = '' — always rebuild since we can't detect changes
69
86
  if (currentCommit !== '') {
70
- await ensureGitNexusIgnored(repoPath);
71
- return {
72
- // `resolveRepoIdentityRoot` collapses worktree roots to the
73
- // canonical repo basename (#1259) but leaves arbitrary subdirs
74
- // and `--skip-git` paths unchanged (#1232/#1233 intent preserved).
75
- repoName: options.registryName ??
76
- getInferredRepoName(repoPath) ??
77
- path.basename(resolveRepoIdentityRoot(repoPath)),
78
- repoPath,
79
- stats: existingMeta.stats ?? {},
80
- alreadyUpToDate: true,
81
- };
87
+ // For git repos, even if HEAD matches lastCommit, the working tree
88
+ // may have uncommitted changes. Only short-circuit when the working
89
+ // tree is also clean — otherwise fall through to the incremental
90
+ // path which will hash-diff and update only changed files.
91
+ //
92
+ // We exclude paths that GitNexus itself writes during analyze:
93
+ // .gitnexus/ — db / parse cache / meta.json
94
+ // .claude/, .cursor/ — auto-generated agent skill files
95
+ // AGENTS.md, CLAUDE.md — auto-updated stats blocks
96
+ // Counting them as dirty would perpetually defeat the up-to-date
97
+ // fast path because the previous analyze just wrote them
98
+ // (regression vs PR #1233 behavior).
99
+ const dirty = (() => {
100
+ try {
101
+ const out = execFileSync('git', [
102
+ 'status',
103
+ '--porcelain',
104
+ '--',
105
+ '.',
106
+ ':(exclude).gitnexus',
107
+ ':(exclude).gitnexus/**',
108
+ ':(exclude).claude',
109
+ ':(exclude).claude/**',
110
+ ':(exclude).cursor',
111
+ ':(exclude).cursor/**',
112
+ ':(exclude)AGENTS.md',
113
+ ':(exclude)CLAUDE.md',
114
+ ], {
115
+ cwd: repoPath,
116
+ stdio: ['ignore', 'pipe', 'ignore'],
117
+ encoding: 'utf8',
118
+ });
119
+ return out.trim().length > 0;
120
+ }
121
+ catch {
122
+ return true; // conservative on git failure
123
+ }
124
+ })();
125
+ if (!dirty) {
126
+ await ensureGitNexusIgnored(repoPath);
127
+ return {
128
+ // `resolveRepoIdentityRoot` collapses worktree roots to the
129
+ // canonical repo basename (#1259) but leaves arbitrary subdirs
130
+ // and `--skip-git` paths unchanged (#1232/#1233 intent preserved).
131
+ repoName: options.registryName ??
132
+ getInferredRepoName(repoPath) ??
133
+ path.basename(resolveRepoIdentityRoot(repoPath)),
134
+ repoPath,
135
+ stats: existingMeta.stats ?? {},
136
+ alreadyUpToDate: true,
137
+ };
138
+ }
82
139
  }
83
140
  }
84
141
  // ── Cache embeddings from existing index before rebuild ────────────
@@ -114,6 +171,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
114
171
  `Pass --embeddings to also generate embeddings for new/changed nodes, ` +
115
172
  `or --drop-embeddings to wipe them.`);
116
173
  }
174
+ // We *always* load the embedding cache when one is requested (regardless
175
+ // of the predicted `willTryIncremental`). The post-pipeline branch may
176
+ // disagree with the prediction (e.g. when the pipeline produces zero
177
+ // File nodes, `isIncremental` flips false and the full-rebuild path
178
+ // wipes the DB) — loading unconditionally is cheap insurance against
179
+ // silently dropping embeddings on a mispredicted run. The re-insert
180
+ // step gates itself on the actual `isIncremental` value to avoid
181
+ // PK-conflicts when the incremental writeback path keeps the rows.
117
182
  if (shouldLoadCache && existingMeta) {
118
183
  try {
119
184
  progress('embeddings', 0, 'Caching embeddings...');
@@ -140,23 +205,77 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
140
205
  }
141
206
  }
142
207
  }
208
+ // ── Load incremental parse cache ──────────────────────────────────
209
+ // Content-addressed: safe to reuse across `--force` runs (chunks whose
210
+ // file contents haven't changed produce identical worker output).
211
+ // Loaded into a single ParseCache object that the pipeline mutates
212
+ // in-place (cache hits leave entries unchanged; misses add new ones).
213
+ const parseCache = await loadParseCache(storagePath);
143
214
  // ── Phase 1: Full Pipeline (0–60%) ────────────────────────────────
144
215
  const pipelineResult = await runPipelineFromRepo(repoPath, (p) => {
145
216
  const phaseLabel = PHASE_LABELS[p.phase] || p.phase;
146
217
  const scaled = Math.round(p.percent * 0.6);
147
- const message = p.detail ? `${p.message || phaseLabel} (${p.detail})` : p.message || phaseLabel;
218
+ const message = p.detail
219
+ ? `${p.message || phaseLabel} (${p.detail})`
220
+ : p.message || phaseLabel;
148
221
  progress(p.phase, scaled, message);
149
- });
222
+ }, { parseCache });
150
223
  // ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────
151
224
  progress('lbug', 60, 'Loading into LadybugDB...');
152
- await closeLbug();
153
- const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
154
- for (const f of lbugFiles) {
155
- try {
156
- await fs.rm(f, { recursive: true, force: true });
225
+ // Compute current per-file content hashes from the pipeline's File nodes.
226
+ // Used both to drive the incremental DB writeback (when eligible) and to
227
+ // populate meta.json.fileHashes for the next run.
228
+ const allFilePaths = [];
229
+ pipelineResult.graph.forEachNode((n) => {
230
+ if (n.label === 'File') {
231
+ const fp = n.properties?.filePath;
232
+ if (fp)
233
+ allFilePaths.push(fp);
157
234
  }
158
- catch {
159
- /* swallow */
235
+ });
236
+ const newFileHashes = await computeFileHashes(repoPath, allFilePaths);
237
+ // Decide incremental vs full at THIS point (post-pipeline, pre-DB).
238
+ // All eligibility conditions are checked here against the actual
239
+ // pipeline output — no separate pre-pipeline prediction to desync from
240
+ // (Bugbot review on PR #1479: a prediction that flipped post-pipeline
241
+ // could skip the embedding cache load and then take the full-rebuild
242
+ // path, silently losing embeddings).
243
+ const isIncremental = !options.force &&
244
+ !!existingMeta &&
245
+ existingMeta.schemaVersion === INCREMENTAL_SCHEMA_VERSION &&
246
+ !!existingMeta.fileHashes &&
247
+ Object.keys(existingMeta.fileHashes).length > 0 &&
248
+ repoHasGit &&
249
+ allFilePaths.length > 0;
250
+ const hashDiff = isIncremental
251
+ ? diffFileHashes(newFileHashes, existingMeta.fileHashes)
252
+ : undefined;
253
+ if (isIncremental && hashDiff) {
254
+ log(`Incremental: changed=${hashDiff.changed.length}, ` +
255
+ `added=${hashDiff.added.length}, ` +
256
+ `deleted=${hashDiff.deleted.length} ` +
257
+ `(skipping wipe + ${allFilePaths.length - hashDiff.toWrite.length} unchanged file rows preserved)`);
258
+ // Set the dirty flag BEFORE any destructive DB mutation. Cleared on
259
+ // success at the meta-save step.
260
+ await saveMeta(storagePath, {
261
+ ...existingMeta,
262
+ incrementalInProgress: {
263
+ startedAt: Date.now(),
264
+ toWriteCount: hashDiff.toWrite.length,
265
+ },
266
+ });
267
+ }
268
+ else {
269
+ // Full rebuild path: wipe DB files first.
270
+ await closeLbug();
271
+ const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
272
+ for (const f of lbugFiles) {
273
+ try {
274
+ await fs.rm(f, { recursive: true, force: true });
275
+ }
276
+ catch {
277
+ /* swallow */
278
+ }
160
279
  }
161
280
  }
162
281
  await initLbug(lbugPath);
@@ -165,16 +284,158 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
165
284
  // is called even if an error occurs — the module-level singleton DB handle
166
285
  // must be released to avoid blocking subsequent invocations.
167
286
  let lbugMsgCount = 0;
168
- await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
169
- lbugMsgCount++;
170
- const pct = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
171
- progress('lbug', pct, msg);
172
- });
287
+ if (isIncremental && hashDiff) {
288
+ // ── Incremental DB writeback ───────────────────────────────────
289
+ // 0. Expand the writable set with transitive importers of
290
+ // changed/deleted files (bounded BFS).
291
+ //
292
+ // Reason (Bugbot/Claude review on PR #1479): when a barrel /
293
+ // re-export file C changes, cross-file resolution may update
294
+ // CALLS edges between two unchanged files A and B (A imports
295
+ // from C, C re-exports something from B). Those refined edges
296
+ // live in `ctx.graph` but would be excluded from the subgraph
297
+ // if neither endpoint is in the changed set. To catch this,
298
+ // files that imported (directly OR transitively, through
299
+ // other unchanged intermediaries) any changed file get pulled
300
+ // into the writable set so their rows are deleted + rewritten
301
+ // against the refined edges.
302
+ //
303
+ // BFS bound: MAX_IMPORTER_BFS_DEPTH. Practically sized to
304
+ // catch nested barrel chains (e.g. `index.ts → submodule/index.ts
305
+ // → submodule/impl.ts`) without ballooning into a near-full-
306
+ // rebuild on monorepos with deep re-export pyramids. Beyond
307
+ // this depth, the "incremental ≡ full-rebuild" invariant is
308
+ // self-acknowledged as best-effort; `--force` remains the
309
+ // escape hatch documented in GUARDRAILS.md.
310
+ //
311
+ // `queryImporters` reads `IMPORTS` from the pre-pipeline DB
312
+ // state, so the result is "files that USED TO import the
313
+ // target" — exactly the set whose previously-stored edges may
314
+ // no longer match what cross-file resolution produces this run.
315
+ const MAX_IMPORTER_BFS_DEPTH = 4;
316
+ const writableFiles = new Set(hashDiff.toWrite);
317
+ const directlyChangedCount = writableFiles.size;
318
+ // Shadow-seed: for ADDED files, queryImporters returns 0 (the new
319
+ // file has no IMPORTS rows in the pre-pipeline DB yet). But pre-
320
+ // existing unchanged files may have IMPORTS edges whose module-
321
+ // resolution claim the newcomer can steal under standard JS/TS
322
+ // resolution (Bugbot review on PR #1479). For each added file we
323
+ // derive the shadow candidates and, if the candidate was a known
324
+ // file in the prior meta, seed it into the BFS frontier so its
325
+ // importers — surfaced via queryImporters — get their CALLS edges
326
+ // re-resolved against the new file. See shadow-candidates.ts for
327
+ // the full pattern catalogue.
328
+ const priorFileSet = new Set(existingMeta?.fileHashes ? Object.keys(existingMeta.fileHashes) : []);
329
+ const shadowSeed = [];
330
+ for (const added of hashDiff.added) {
331
+ for (const cand of shadowCandidatesFor(added)) {
332
+ if (priorFileSet.has(cand) && !writableFiles.has(cand)) {
333
+ shadowSeed.push(cand);
334
+ }
335
+ }
336
+ }
337
+ {
338
+ let frontier = [...hashDiff.toWrite, ...hashDiff.deleted, ...shadowSeed];
339
+ for (let depth = 0; depth < MAX_IMPORTER_BFS_DEPTH && frontier.length > 0; depth++) {
340
+ const nextFrontier = [];
341
+ for (const f of frontier) {
342
+ try {
343
+ const importers = await queryImporters(f);
344
+ for (const i of importers) {
345
+ if (!writableFiles.has(i)) {
346
+ writableFiles.add(i);
347
+ nextFrontier.push(i);
348
+ }
349
+ }
350
+ }
351
+ catch {
352
+ /* per-file importer query failure → skip; correctness degrades on
353
+ that branch, but DB stays writable. */
354
+ }
355
+ }
356
+ frontier = nextFrontier;
357
+ }
358
+ }
359
+ const importerExpansion = writableFiles.size - directlyChangedCount;
360
+ if (importerExpansion > 0) {
361
+ log(`Incremental: +${importerExpansion} importer(s) added to writable set ` +
362
+ `(BFS depth ≤ ${MAX_IMPORTER_BFS_DEPTH}` +
363
+ (shadowSeed.length > 0 ? `, ${shadowSeed.length} shadow-seed(s)` : '') +
364
+ `)`);
365
+ }
366
+ // 1. Compute the EFFECTIVE write-set (Finding 1). Two layers,
367
+ // composed:
368
+ // (a) `writableFiles` — toWrite ∪ transitive importers of
369
+ // changed/deleted files (the bounded BFS above, reading
370
+ // IMPORTS from the pre-pipeline DB).
371
+ // (b) `computeEffectiveWriteSet` — walks the NEW graph's
372
+ // edges and pulls in any unchanged-side file that sits
373
+ // on a writable-boundary-crossing edge (catches refined
374
+ // cross-file CALLS edges that the pre-run DB couldn't
375
+ // predict, e.g. a barrel re-export shifting `foo` from
376
+ // B to D).
377
+ // The composed set is the input to BOTH deleteNodesForFile
378
+ // and extractChangedSubgraph — asymmetry between the two would
379
+ // leave stale rows or PK-conflict at COPY time.
380
+ const effectiveWriteSet = computeEffectiveWriteSet(pipelineResult.graph, writableFiles);
381
+ // Deduped: deleted entries may already appear via importer-BFS
382
+ // expansion (queryImporters can return a now-deleted path), which
383
+ // would otherwise call deleteNodesForFile twice for the same file
384
+ // (Bugbot LOW finding on PR #1479).
385
+ const filesToDelete = [...new Set([...effectiveWriteSet, ...hashDiff.deleted])];
386
+ for (let i = 0; i < filesToDelete.length; i++) {
387
+ const f = filesToDelete[i];
388
+ try {
389
+ await deleteNodesForFile(f);
390
+ }
391
+ catch {
392
+ /* file may not have rows (e.g. an unparseable file) — fine */
393
+ }
394
+ if (i % 20 === 0) {
395
+ progress('lbug', 62, `Removing rows for changed files (${i}/${filesToDelete.length})...`);
396
+ }
397
+ }
398
+ // 2. Drop graph-wide nodes (Community, Process). They'll be re-inserted
399
+ // from the fresh pipeline output below. Required for the
400
+ // "Leiden runs on the FULL graph" correctness invariant.
401
+ await deleteAllCommunitiesAndProcesses();
402
+ // 3. Extract the changed subgraph from the FULL ctx.graph and write
403
+ // only that. Unchanged-file rows in the DB stay untouched. Pass
404
+ // the SAME effectiveWriteSet so the subgraph and the deletes
405
+ // cover identical files (asymmetry would silently corrupt).
406
+ const subgraph = extractChangedSubgraph(pipelineResult.graph, effectiveWriteSet);
407
+ await loadGraphToLbug(subgraph, pipelineResult.repoPath, storagePath, (msg) => {
408
+ lbugMsgCount++;
409
+ const pct = Math.min(84, 65 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 19));
410
+ progress('lbug', pct, msg);
411
+ });
412
+ }
413
+ else {
414
+ // ── Full rebuild ───────────────────────────────────────────────
415
+ await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
416
+ lbugMsgCount++;
417
+ const pct = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
418
+ progress('lbug', pct, msg);
419
+ });
420
+ }
173
421
  // ── Phase 3: FTS (85–90%) ─────────────────────────────────────────
174
422
  progress('fts', 85, 'Creating search indexes...');
175
423
  await createSearchFTSIndexes();
176
424
  progress('fts', 90, 'Search indexes ready');
177
425
  // ── Phase 3.5: Re-insert cached embeddings ────────────────────────
426
+ // Runs on BOTH the full-rebuild path and the incremental path:
427
+ // - Full rebuild: DB was wiped, every cached row needs to come back.
428
+ // - Incremental: changed-file rows were just deleted by
429
+ // deleteNodesForFile (which cascades to their
430
+ // embedding rows) — so their cached vectors need
431
+ // to come back too. Unchanged-file rows still
432
+ // exist; re-inserting their cached vectors would
433
+ // PK-conflict, but the per-batch try/catch below
434
+ // silently ignores those (matches the existing
435
+ // "some may fail if node was removed, that's
436
+ // fine" semantics). Bugbot review on PR #1479
437
+ // flagged that gating this on `!isIncremental`
438
+ // silently lost changed-file embeddings.
178
439
  if (cachedEmbeddings.length > 0) {
179
440
  const cachedDims = cachedEmbeddings[0].embedding.length;
180
441
  const { EMBEDDING_DIMS } = await import('./lbug/schema.js');
@@ -286,6 +547,11 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
286
547
  const runtimeCapabilities = getRuntimeCapabilities();
287
548
  const effectiveSemanticMode = semanticMode ??
288
549
  (runtimeCapabilities.semanticMode === 'vector-index' ? 'vector-index' : 'exact-scan');
550
+ // Convert the post-run file-hash map to the on-disk Record<string,string>
551
+ // shape consumed by RepoMeta.fileHashes.
552
+ const newFileHashesRecord = {};
553
+ for (const [k, v] of newFileHashes)
554
+ newFileHashesRecord[k] = v;
289
555
  const meta = {
290
556
  repoPath,
291
557
  lastCommit: currentCommit,
@@ -315,8 +581,32 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
315
581
  reason: runtimeCapabilities.reason,
316
582
  },
317
583
  },
584
+ // Incremental-indexing fields. Populated for git repos so the next
585
+ // analyze run can take the incremental DB-writeback path. Setting
586
+ // incrementalInProgress to undefined explicitly clears any prior
587
+ // dirty flag (full and incremental success paths converge here).
588
+ schemaVersion: hasGitDir(repoPath) ? INCREMENTAL_SCHEMA_VERSION : undefined,
589
+ fileHashes: hasGitDir(repoPath) ? newFileHashesRecord : undefined,
590
+ incrementalInProgress: undefined,
318
591
  };
319
592
  await saveMeta(storagePath, meta);
593
+ // Persist the incremental parse cache for the next run. Wraps in
594
+ // try/catch so a cache-write failure never breaks an otherwise
595
+ // successful indexing run. Prune stale chunk-hash entries first so
596
+ // the cache file size stays bounded across runs (chunks whose
597
+ // composition no longer matches anything in the current scan are
598
+ // dead weight; the parse phase populates `usedKeys` as it processes
599
+ // chunks).
600
+ try {
601
+ const pruned = pruneCache(parseCache, parseCache.usedKeys);
602
+ if (pruned > 0) {
603
+ log(`Parse cache: pruned ${pruned} stale chunk entries`);
604
+ }
605
+ await saveParseCache(storagePath, parseCache);
606
+ }
607
+ catch (e) {
608
+ log(`Warning: could not save parse cache (${e.message}); continuing.`);
609
+ }
320
610
  // Forward the --name alias and the registry-collision bypass bit.
321
611
  // `allowDuplicateName` is its own concern — independent from the
322
612
  // pipeline `force` above. The CLI maps it from
@@ -350,7 +640,11 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
350
640
  communities: pipelineResult.communityResult?.stats.totalCommunities,
351
641
  clusters: aggregatedClusterCount,
352
642
  processes: pipelineResult.processResult?.stats.totalProcesses,
353
- }, undefined, { skipAgentsMd: options.skipAgentsMd, noStats: options.noStats });
643
+ }, undefined, {
644
+ skipAgentsMd: options.skipAgentsMd,
645
+ skipSkills: options.skipSkills,
646
+ noStats: options.noStats,
647
+ });
354
648
  }
355
649
  catch {
356
650
  // Best-effort — don't fail the entire analysis for context file issues
@@ -0,0 +1,6 @@
1
+ import type Parser from 'tree-sitter';
2
+ /**
3
+ * Parse `sourceText` safely on every platform. See {@link SAFE_PARSE_CHUNK_CHARS}
4
+ * for the underlying tree-sitter binding bug this works around.
5
+ */
6
+ export declare function parseSourceSafe(parser: Parser, sourceText: string, oldTree?: Parser.Tree, options?: Parser.Options): Parser.Tree;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * tree-sitter 0.21.x's Node native binding crashes (SIGSEGV) on Windows when
3
+ * `parser.parse(string, …)` is handed a JS string longer than 32 767 chars.
4
+ * The crash happens inside the binding's V8 string-to-buffer conversion and
5
+ * cannot be intercepted from JavaScript. The callback (`Parser.Input`) overload
6
+ * pulls source in fixed-size chunks via repeated callback invocations and
7
+ * bypasses that conversion path entirely.
8
+ *
9
+ * Chunk size is comfortably below the boundary; any value < 32 767 works.
10
+ */
11
+ const SAFE_PARSE_CHUNK_CHARS = 16 * 1024;
12
+ /**
13
+ * Files at or below this length skip the callback machinery and use the
14
+ * direct string overload — the bug only manifests above the int16 boundary,
15
+ * so small inputs save the cost of N callback invocations per parse.
16
+ */
17
+ const DIRECT_PARSE_LIMIT_CHARS = 16 * 1024;
18
+ /**
19
+ * Parse `sourceText` safely on every platform. See {@link SAFE_PARSE_CHUNK_CHARS}
20
+ * for the underlying tree-sitter binding bug this works around.
21
+ */
22
+ export function parseSourceSafe(parser, sourceText, oldTree, options) {
23
+ if (sourceText.length <= DIRECT_PARSE_LIMIT_CHARS) {
24
+ return parser.parse(sourceText, oldTree, options);
25
+ }
26
+ const input = (index) => {
27
+ if (index >= sourceText.length)
28
+ return null;
29
+ return sourceText.slice(index, index + SAFE_PARSE_CHUNK_CHARS);
30
+ };
31
+ return parser.parse(input, oldTree, options);
32
+ }
@@ -10,6 +10,7 @@ import os from 'os';
10
10
  import fs from 'fs/promises';
11
11
  import { isIP } from 'net';
12
12
  import { logger } from '../core/logger.js';
13
+ import { parseRepoNameFromUrl } from '../storage/git.js';
13
14
  /** Root directory for all cloned repositories. Targets must resolve inside this. */
14
15
  const CLONE_ROOT = path.resolve(path.join(os.homedir(), '.gitnexus', 'repos'));
15
16
  // A valid git repository name is filesystem-safe: alphanumerics plus `. _ -`.
@@ -25,19 +26,15 @@ const REPO_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
25
26
  * clone root via path traversal.
26
27
  */
27
28
  export function extractRepoName(url) {
28
- // Strip trailing slashes without a regex to avoid polynomial-ReDoS on
29
- // pathological inputs like `https://x.com/y` + '/'.repeat(1e6). CodeQL's
30
- // js/polynomial-redos flagged `/\/+$/` here.
31
- let end = url.length;
32
- while (end > 0 && url.charCodeAt(end - 1) === 47 /* '/' */)
33
- end--;
34
- const cleaned = url.slice(0, end);
35
- const lastSegment = cleaned.split(/[/:]/).pop() || '';
36
- const stripped = lastSegment.endsWith('.git') ? lastSegment.slice(0, -4) : lastSegment;
37
- if (!stripped || stripped === '.' || stripped === '..' || !REPO_NAME_PATTERN.test(stripped)) {
29
+ const name = parseRepoNameFromUrl(url);
30
+ if (!name ||
31
+ name === '.' ||
32
+ name === '..' ||
33
+ name === 'unknown' ||
34
+ !REPO_NAME_PATTERN.test(name)) {
38
35
  throw new Error('Could not extract a valid repository name from URL');
39
36
  }
40
- return stripped;
37
+ return name;
41
38
  }
42
39
  /** Get the clone target directory for a repo name. */
43
40
  export function getCloneDir(repoName) {
@@ -347,8 +344,8 @@ export async function cloneOrPull(url, targetDir, onProgress) {
347
344
  throw new Error(`Clone target must be a subdirectory of ${CLONE_ROOT}`);
348
345
  }
349
346
  // Always validate the requested URL — the prior shape only ran this in
350
- // the clone branch, leaving the pull branch as an SSRF / blocked-host
351
- // bypass when an existing clone shared the basename of an attacker URL.
347
+ // the code path where the repo was cloned. Now it runs unconditionally,
348
+ // preventing SSRF / blocked-host bypasses even when targetDir already exists.
352
349
  validateGitUrl(url);
353
350
  const exists = await fs.access(path.join(safeTarget, '.git')).then(() => true, () => false);
354
351
  if (exists) {
@@ -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
+ };