@armanckeser/downstream 0.0.0-stage → 0.1.0

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 (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -2
  3. package/SKILL.md +150 -0
  4. package/bin/downstream.mjs +19 -0
  5. package/dist/cli/downstream.js +543 -0
  6. package/dist/domain/agent.js +3 -0
  7. package/dist/domain/approval.js +190 -0
  8. package/dist/domain/delivery.js +21 -0
  9. package/dist/domain/home.js +1 -0
  10. package/dist/domain/inbox.js +245 -0
  11. package/dist/domain/model.js +180 -0
  12. package/dist/domain/next-move.js +34 -0
  13. package/dist/domain/pr-detail.js +45 -0
  14. package/dist/server/actions.js +564 -0
  15. package/dist/server/agent-host.js +230 -0
  16. package/dist/server/analyze/git.js +161 -0
  17. package/dist/server/analyze/graph.js +403 -0
  18. package/dist/server/analyze/py-graph.js +141 -0
  19. package/dist/server/analyze/py.js +346 -0
  20. package/dist/server/analyze/ts-decls.js +299 -0
  21. package/dist/server/analyze/ts-project.js +212 -0
  22. package/dist/server/gh.js +51 -0
  23. package/dist/server/github.js +264 -0
  24. package/dist/server/home-store.js +127 -0
  25. package/dist/server/home.js +325 -0
  26. package/dist/server/install.js +12 -0
  27. package/dist/server/main.js +228 -0
  28. package/dist/server/prompts.js +32 -0
  29. package/dist/server/publish.js +136 -0
  30. package/dist/server/repos.js +164 -0
  31. package/dist/server/stale.js +50 -0
  32. package/dist/server/store.js +269 -0
  33. package/dist/server/workspace.js +251 -0
  34. package/package.json +63 -4
  35. package/web/dist/assets/abap-CLvhMVsD.js +1 -0
  36. package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  37. package/web/dist/assets/ada-X3Y2mlq2.js +1 -0
  38. package/web/dist/assets/ahk-DwADkxyd.js +1 -0
  39. package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  40. package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  41. package/web/dist/assets/angular-html-iITFzleb.js +1 -0
  42. package/web/dist/assets/angular-ts-qNGaWUZr.js +1 -0
  43. package/web/dist/assets/apache-U0d_L8uA.js +1 -0
  44. package/web/dist/assets/apex-KFGFyKwG.js +1 -0
  45. package/web/dist/assets/apl-DAcxbtpZ.js +1 -0
  46. package/web/dist/assets/applescript-CCn79oCD.js +1 -0
  47. package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  48. package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  49. package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  50. package/web/dist/assets/astro-CxPQu5s8.js +1 -0
  51. package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  52. package/web/dist/assets/awk-BWXHIvNe.js +1 -0
  53. package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  54. package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  55. package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  56. package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  57. package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  58. package/web/dist/assets/beancount-C2IN4Vk8.js +1 -0
  59. package/web/dist/assets/berry-DKpUyyne.js +1 -0
  60. package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  61. package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  62. package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  63. package/web/dist/assets/blade-DJrnOqL4.js +1 -0
  64. package/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  65. package/web/dist/assets/c-DJLzn8qa.js +1 -0
  66. package/web/dist/assets/c3-D0apG41V.js +1 -0
  67. package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  68. package/web/dist/assets/cairo-DLTphjLi.js +1 -0
  69. package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  70. package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  71. package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  72. package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  73. package/web/dist/assets/chapel-BHYX99F3.js +1 -0
  74. package/web/dist/assets/clarity-CtfBBspg.js +1 -0
  75. package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  76. package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  77. package/web/dist/assets/cobol-C_LDam8n.js +1 -0
  78. package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  79. package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  80. package/web/dist/assets/coffee-CcuifkxZ.js +1 -0
  81. package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  82. package/web/dist/assets/coq-CGaSfhwR.js +1 -0
  83. package/web/dist/assets/cpp-E7p80IG9.js +1 -0
  84. package/web/dist/assets/crystal-iYHODIir.js +1 -0
  85. package/web/dist/assets/csharp-dmMxLVLW.js +1 -0
  86. package/web/dist/assets/css-Bk6njqO4.js +1 -0
  87. package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  88. package/web/dist/assets/cue-CE9AQfxI.js +1 -0
  89. package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  90. package/web/dist/assets/d-qD-0Kul2.js +1 -0
  91. package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  92. package/web/dist/assets/dart-CHZY9Xhz.js +1 -0
  93. package/web/dist/assets/dax-BkyTk9wS.js +1 -0
  94. package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  95. package/web/dist/assets/diff-woXpYk--.js +1 -0
  96. package/web/dist/assets/docker-IyjqRm3v.js +1 -0
  97. package/web/dist/assets/dotenv-CuH5my32.js +1 -0
  98. package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  99. package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  100. package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  101. package/web/dist/assets/edge-Ct7_SkpN.js +1 -0
  102. package/web/dist/assets/elixir-YTzSQL2h.js +1 -0
  103. package/web/dist/assets/elm-bNkwlHr8.js +1 -0
  104. package/web/dist/assets/emacs-lisp-DR2vtWaj.js +1 -0
  105. package/web/dist/assets/erb-uFBspDdG.js +1 -0
  106. package/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  107. package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  108. package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  109. package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  110. package/web/dist/assets/fish-BJitypiv.js +1 -0
  111. package/web/dist/assets/fluent-C03EYrpw.js +1 -0
  112. package/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  113. package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  114. package/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  115. package/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  116. package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  117. package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  118. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  119. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  120. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  121. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  122. package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  123. package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  124. package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  125. package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  126. package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  127. package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  128. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  129. package/web/dist/assets/genie-CV2tkWYe.js +1 -0
  130. package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  131. package/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  132. package/web/dist/assets/git-rebase-CSzaMu4k.js +1 -0
  133. package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  134. package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  135. package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  136. package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  137. package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  138. package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  139. package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  140. package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  141. package/web/dist/assets/glimmer-js-WrpQIag3.js +1 -0
  142. package/web/dist/assets/glimmer-ts-BkR7s81M.js +1 -0
  143. package/web/dist/assets/glsl-r6ahr8UN.js +1 -0
  144. package/web/dist/assets/gn-CAXYAgV-.js +1 -0
  145. package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  146. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  147. package/web/dist/assets/graphql-vcFzEvUM.js +1 -0
  148. package/web/dist/assets/groovy-CacY0gHj.js +1 -0
  149. package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  150. package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  151. package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  152. package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  153. package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  154. package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  155. package/web/dist/assets/hack-DV1TStQR.js +1 -0
  156. package/web/dist/assets/haml-BzEpEFnM.js +1 -0
  157. package/web/dist/assets/handlebars-Bw2ai2tn.js +1 -0
  158. package/web/dist/assets/haskell-D8IpX4py.js +1 -0
  159. package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  160. package/web/dist/assets/hcl-Dh228itO.js +1 -0
  161. package/web/dist/assets/hjson-CxZEssPk.js +1 -0
  162. package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  163. package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  164. package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  165. package/web/dist/assets/houston-CsvMBhTu.js +1 -0
  166. package/web/dist/assets/html-CVWJgEI4.js +1 -0
  167. package/web/dist/assets/html-derivative-kTFEcoss.js +1 -0
  168. package/web/dist/assets/http-DXC6PGgt.js +1 -0
  169. package/web/dist/assets/hurl-Bpd8P_ef.js +1 -0
  170. package/web/dist/assets/hxml-Dcm88srW.js +1 -0
  171. package/web/dist/assets/hy-CZbG8q4J.js +1 -0
  172. package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  173. package/web/dist/assets/index-DpYMsPxu.js +1673 -0
  174. package/web/dist/assets/index-Zh-Bd2u6.css +2 -0
  175. package/web/dist/assets/ini-B5eOa1yu.js +1 -0
  176. package/web/dist/assets/java-Bsmh9nXE.js +1 -0
  177. package/web/dist/assets/javascript-CeyaFN_P.js +1 -0
  178. package/web/dist/assets/jinja-BuqTDofy.js +1 -0
  179. package/web/dist/assets/jison-BYfmmaBZ.js +1 -0
  180. package/web/dist/assets/json-Bhl6cDM_.js +1 -0
  181. package/web/dist/assets/json5-BR5RXkoi.js +1 -0
  182. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  183. package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  184. package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  185. package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  186. package/web/dist/assets/jsx-BSUHjwDX.js +1 -0
  187. package/web/dist/assets/julia-CWrNF0wf.js +1 -0
  188. package/web/dist/assets/just-wLw7gWsD.js +1 -0
  189. package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  190. package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  191. package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  192. package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  193. package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  194. package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  195. package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  196. package/web/dist/assets/latex-DyWaeH0P.js +1 -0
  197. package/web/dist/assets/lean-CewbzKMR.js +1 -0
  198. package/web/dist/assets/less-DVTAwKKz.js +1 -0
  199. package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  200. package/web/dist/assets/liquid-DeCc5Rg2.js +1 -0
  201. package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  202. package/web/dist/assets/log-BNLmms1o.js +1 -0
  203. package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  204. package/web/dist/assets/lua-Ba-NltBp.js +1 -0
  205. package/web/dist/assets/luau-VGWVLoqX.js +1 -0
  206. package/web/dist/assets/make-Dixweg8N.js +1 -0
  207. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  208. package/web/dist/assets/marko-DGb2HMVj.js +1 -0
  209. package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  210. package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  211. package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  212. package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  213. package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  214. package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  215. package/web/dist/assets/mdc-CL7bhRQ-.js +1 -0
  216. package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  217. package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  218. package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  219. package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  220. package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  221. package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  222. package/web/dist/assets/monokai-BscAjiQZ.js +1 -0
  223. package/web/dist/assets/moonbit-D9W784E_.js +1 -0
  224. package/web/dist/assets/move-BYnGhZoM.js +1 -0
  225. package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  226. package/web/dist/assets/nextflow-CSgcYZcx.js +1 -0
  227. package/web/dist/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
  228. package/web/dist/assets/nginx-CbyuJibN.js +1 -0
  229. package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  230. package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  231. package/web/dist/assets/nim-C7LemgDR.js +1 -0
  232. package/web/dist/assets/nix-IvuFDN5E.js +1 -0
  233. package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  234. package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  235. package/web/dist/assets/nushell--0fT3PI1.js +1 -0
  236. package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  237. package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  238. package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  239. package/web/dist/assets/odin-B1RWQWA5.js +1 -0
  240. package/web/dist/assets/one-dark-pro-C6YyR8OD.js +1 -0
  241. package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  242. package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  243. package/web/dist/assets/org-wrbLazmP.js +1 -0
  244. package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  245. package/web/dist/assets/perl-DarHXyEu.js +1 -0
  246. package/web/dist/assets/php--54M0-pj.js +1 -0
  247. package/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  248. package/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  249. package/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  250. package/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  251. package/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  252. package/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  253. package/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  254. package/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  255. package/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  256. package/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  257. package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  258. package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  259. package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  260. package/web/dist/assets/po-BiJDBrnU.js +1 -0
  261. package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  262. package/web/dist/assets/polar-C7UOKdEL.js +1 -0
  263. package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  264. package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  265. package/web/dist/assets/powershell-QtSnmXcu.js +1 -0
  266. package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  267. package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  268. package/web/dist/assets/proto-DB4EqR-F.js +1 -0
  269. package/web/dist/assets/pug-CFrC1rDC.js +1 -0
  270. package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  271. package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  272. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  273. package/web/dist/assets/qml-Go4B46qX.js +1 -0
  274. package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  275. package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  276. package/web/dist/assets/r-Bpu17F3f.js +1 -0
  277. package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  278. package/web/dist/assets/raku-B3gFvitq.js +1 -0
  279. package/web/dist/assets/razor-CeZTEIIg.js +1 -0
  280. package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  281. package/web/dist/assets/red-CYlAq2b0.js +1 -0
  282. package/web/dist/assets/reg-CRGYupPL.js +1 -0
  283. package/web/dist/assets/regexp-D4K6CiTG.js +1 -0
  284. package/web/dist/assets/rel-BtDbiS_P.js +1 -0
  285. package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  286. package/web/dist/assets/ron-VUp2lXgN.js +1 -0
  287. package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  288. package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  289. package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  290. package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  291. package/web/dist/assets/rst-Dy2Bl1Fg.js +1 -0
  292. package/web/dist/assets/ruby-CNqr2Wuz.js +1 -0
  293. package/web/dist/assets/rust-DpiIf5JO.js +1 -0
  294. package/web/dist/assets/sas-B6MsvXFb.js +1 -0
  295. package/web/dist/assets/sass-DXrisJhu.js +1 -0
  296. package/web/dist/assets/scala-BiFt_7mu.js +1 -0
  297. package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  298. package/web/dist/assets/scss-CHuFWGi8.js +1 -0
  299. package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  300. package/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  301. package/web/dist/assets/shellscript-EEdMjEbs.js +1 -0
  302. package/web/dist/assets/shellsession-U_I5Q2cJ.js +1 -0
  303. package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  304. package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  305. package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  306. package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  307. package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  308. package/web/dist/assets/solarized-dark-b97czJOk.js +1 -0
  309. package/web/dist/assets/solarized-light-DFPxpIYQ.js +1 -0
  310. package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  311. package/web/dist/assets/soy-BnCBZkG3.js +1 -0
  312. package/web/dist/assets/sparql-D_iOobhT.js +1 -0
  313. package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  314. package/web/dist/assets/sql-5dStcFSw.js +1 -0
  315. package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  316. package/web/dist/assets/stata-B5iSQdtS.js +1 -0
  317. package/web/dist/assets/stylus-B6D30XZt.js +1 -0
  318. package/web/dist/assets/surrealql-D4K5msa4.js +1 -0
  319. package/web/dist/assets/svelte-Dn-_7BgE.js +1 -0
  320. package/web/dist/assets/swift-CyEgAFGc.js +1 -0
  321. package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  322. package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  323. package/web/dist/assets/systemd-BxMlprV5.js +1 -0
  324. package/web/dist/assets/talonscript-CohzipZa.js +1 -0
  325. package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  326. package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  327. package/web/dist/assets/templ-BTe8yLW8.js +1 -0
  328. package/web/dist/assets/terraform-DswuEJGm.js +1 -0
  329. package/web/dist/assets/tex-BBG77enc.js +1 -0
  330. package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  331. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  332. package/web/dist/assets/ts-tags-BzJXXnMD.js +1 -0
  333. package/web/dist/assets/tsv-sltzmVWM.js +1 -0
  334. package/web/dist/assets/tsx-Cj2inrAP.js +1 -0
  335. package/web/dist/assets/turtle-ByJddavk.js +1 -0
  336. package/web/dist/assets/twig-BH3HU2q4.js +1 -0
  337. package/web/dist/assets/typescript-DpDozqSz.js +1 -0
  338. package/web/dist/assets/typespec-BGhR2SqG.js +1 -0
  339. package/web/dist/assets/typst-GHzvrUad.js +1 -0
  340. package/web/dist/assets/v-DBj4K9d_.js +1 -0
  341. package/web/dist/assets/vala-zf12oZj6.js +1 -0
  342. package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  343. package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  344. package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  345. package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  346. package/web/dist/assets/viml-DvXPmvsu.js +1 -0
  347. package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  348. package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  349. package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  350. package/web/dist/assets/vue-CqkncxvB.js +1 -0
  351. package/web/dist/assets/vue-html-DrD64fqb.js +1 -0
  352. package/web/dist/assets/vue-vine-BJy37wv2.js +1 -0
  353. package/web/dist/assets/vyper-CgoNMtux.js +1 -0
  354. package/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  355. package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  356. package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  357. package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  358. package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  359. package/web/dist/assets/wit-DdvCle-K.js +1 -0
  360. package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  361. package/web/dist/assets/xml-CtXDvb3g.js +1 -0
  362. package/web/dist/assets/xsl-9J2XPEqv.js +1 -0
  363. package/web/dist/assets/yaml-CKsrpaXo.js +1 -0
  364. package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  365. package/web/dist/assets/zig-CMLA9XwU.js +1 -0
  366. package/web/dist/favicon.svg +1 -0
  367. package/web/dist/index.html +15 -0
@@ -0,0 +1,403 @@
1
+ // Turns a diff into a flow graph: the changed symbols, what they call, who
2
+ // calls them, and which of those relationships are new. The agent edits the
3
+ // result; this only has to be a good first draft.
4
+ import ts from "typescript";
5
+ import path from "node:path";
6
+ import { countInRange, inRanges } from "./git.js";
7
+ import { calleeNames, enclosing, extractDecls, isTsLike, parse } from "./ts-decls.js";
8
+ import { TsProjects } from "./ts-project.js";
9
+ import { isPy, PyIndex } from "./py.js";
10
+ import { pyEdges, pySymbols } from "./py-graph.js";
11
+ const MAX_CALLERS = 12;
12
+ const symbolId = (file, key) => `${file}#${key}`;
13
+ const TEST_FILE = /(^|\/)(tests?|__tests__|spec)\/|(^|\/)test_[^/]*\.py$|_test\.(py|go)$|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)conftest\.py$/;
14
+ export const isTestFile = (file) => TEST_FILE.test(file);
15
+ function toSymbol(file, d, status, changedLines = 0) {
16
+ return {
17
+ test: isTestFile(file),
18
+ id: symbolId(file, d.key),
19
+ name: d.name,
20
+ container: d.container,
21
+ kind: d.kind,
22
+ file,
23
+ range: status === "removed" ? null : d.range,
24
+ oldFile: null,
25
+ oldRange: null,
26
+ status,
27
+ signature: d.signature,
28
+ exported: d.exported,
29
+ entry: false,
30
+ summary: null,
31
+ changedLines,
32
+ };
33
+ }
34
+ /** Lines changed in a declaration that do not belong to one of its members. */
35
+ function ownChanges(d, ranges) {
36
+ let n = 0;
37
+ for (let line = d.range.start; line <= d.range.end; line++) {
38
+ if (inRanges(line, ranges) && !inRanges(line, d.memberRanges))
39
+ n++;
40
+ }
41
+ return n;
42
+ }
43
+ export function buildGraph(input) {
44
+ const lap = timer();
45
+ const symbols = new Map();
46
+ const edges = new Map();
47
+ const projects = input.projects ?? new TsProjects(input.root, input.newSrc, input.files.filter((f) => f.status !== "deleted").map((f) => f.path));
48
+ const addEdge = (from, to, kind, line, change) => {
49
+ if (from === to)
50
+ return;
51
+ const id = `${from}->${to}:${kind}`;
52
+ const prev = edges.get(id);
53
+ if (prev) {
54
+ if (change === "added" && prev.change === "same")
55
+ prev.change = "added";
56
+ return;
57
+ }
58
+ edges.set(id, { id, from, to, kind, change, line, label: null });
59
+ };
60
+ // 1. Symbols per file.
61
+ const changedDecls = [];
62
+ const pyChanged = [];
63
+ for (const f of input.files) {
64
+ const hunks = input.hunks.get(f.path) ?? { added: [], deleted: [] };
65
+ if (isPy(f.path)) {
66
+ pyChanged.push(...pySymbols(input, f, hunks, toSymbol, symbols));
67
+ continue;
68
+ }
69
+ if (!isTsLike(f.path)) {
70
+ symbols.set(...moduleSymbol(f, hunks, input));
71
+ continue;
72
+ }
73
+ const newText = f.status === "deleted" ? null : input.newSrc.read(f.path);
74
+ const oldText = f.oldPath && input.oldSrc ? input.oldSrc.read(f.oldPath) : null;
75
+ const newDecls = newText !== null ? extractDecls(parse(f.path, newText)) : [];
76
+ const oldDecls = oldText !== null ? extractDecls(parse(f.oldPath, oldText)) : [];
77
+ const oldByKey = new Map(oldDecls.map((d) => [d.key, d]));
78
+ const newKeys = new Set(newDecls.map((d) => d.key));
79
+ for (const d of newDecls) {
80
+ const od = oldByKey.get(d.key);
81
+ if (input.mode === "teach") {
82
+ if (d.kind === "const" && !d.exported)
83
+ continue;
84
+ const s = toSymbol(f.path, d, "context");
85
+ symbols.set(s.id, s);
86
+ changedDecls.push({ file: f.path, decl: d, symbol: s });
87
+ continue;
88
+ }
89
+ const hasMembers = d.memberRanges.length > 0;
90
+ const added = hasMembers ? ownChanges(d, hunks.added) : countInRange(d.range, hunks.added);
91
+ const deleted = od ? (hasMembers ? ownChanges(od, hunks.deleted) : countInRange(od.range, hunks.deleted)) : 0;
92
+ const status = !od && (added > 0 || f.status === "added") ? "added" : added + deleted > 0 ? "modified" : null;
93
+ if (!status)
94
+ continue;
95
+ const s = toSymbol(f.path, d, status, added + deleted);
96
+ if (od) {
97
+ s.oldFile = f.oldPath;
98
+ s.oldRange = od.range;
99
+ }
100
+ symbols.set(s.id, s);
101
+ changedDecls.push({ file: f.path, decl: d, symbol: s });
102
+ }
103
+ if (input.mode === "diff") {
104
+ for (const od of oldDecls) {
105
+ if (newKeys.has(od.key))
106
+ continue;
107
+ const s = toSymbol(f.path, od, "removed", od.range.end - od.range.start + 1);
108
+ s.oldFile = f.oldPath;
109
+ s.oldRange = od.range;
110
+ symbols.set(s.id, s);
111
+ }
112
+ }
113
+ }
114
+ lap("symbols");
115
+ // 2. Outgoing edges through the checker; context symbols for what changed code calls.
116
+ const ensureContext = (project, rel, decl) => {
117
+ const id = symbolId(rel, decl.key);
118
+ if (!symbols.has(id))
119
+ symbols.set(id, toSymbol(rel, decl, "context"));
120
+ return id;
121
+ };
122
+ const resolve = (project, node) => {
123
+ const checker = project.program().getTypeChecker();
124
+ let sym = checker.getSymbolAtLocation(node);
125
+ if (sym && sym.flags & ts.SymbolFlags.Alias)
126
+ sym = checker.getAliasedSymbol(sym);
127
+ let target = sym?.valueDeclaration ?? sym?.declarations?.[0];
128
+ // Services return `{ createGrant, patchGrant }`; follow the shorthand to the function itself.
129
+ if (target && ts.isShorthandPropertyAssignment(target)) {
130
+ target = checker.getShorthandAssignmentValueSymbol(target)?.valueDeclaration ?? target;
131
+ }
132
+ else if (target && ts.isPropertyAssignment(target) && ts.isIdentifier(target.initializer)) {
133
+ target = checker.getSymbolAtLocation(target.initializer)?.valueDeclaration ?? target;
134
+ }
135
+ if (!target)
136
+ return null;
137
+ const sf = target.getSourceFile();
138
+ const rel = project.rel(sf.fileName);
139
+ if (rel === null || project.program().isSourceFileDefaultLibrary(sf))
140
+ return null;
141
+ const entry = project.declsFor(rel);
142
+ if (!entry)
143
+ return null;
144
+ const decl = enclosing(entry.decls, entry.sf, target.getStart(entry.sf));
145
+ return decl ? { rel, decl } : null;
146
+ };
147
+ for (const { file, decl, symbol } of changedDecls) {
148
+ const project = projects.for(file);
149
+ const entry = project?.declsFor(file);
150
+ if (!project || !entry)
151
+ continue;
152
+ const live = entry.decls.find((d) => d.key === decl.key);
153
+ if (!live)
154
+ continue;
155
+ const addedLines = input.hunks.get(file)?.added ?? [];
156
+ const lineOf = (n) => entry.sf.getLineAndCharacterOfPosition(n.getStart(entry.sf)).line + 1;
157
+ // An unchanged call into unchanged code is noise in a diff; hover still explains it.
158
+ const relevant = (targetId, line, kind) => {
159
+ if (input.mode === "teach" || kind === "extends" || kind === "implements")
160
+ return true;
161
+ const known = symbols.get(targetId);
162
+ if (known && known.status !== "context")
163
+ return true;
164
+ return inRanges(line, addedLines) || inRanges(line - 1, addedLines) || inRanges(line + 1, addedLines);
165
+ };
166
+ const visit = (n) => {
167
+ // Members are their own symbols; don't attribute their calls to the class.
168
+ if (n !== live.node && live.memberRanges.length && (ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) || ts.isPropertyDeclaration(n)))
169
+ return;
170
+ let target = null;
171
+ let kind = "calls";
172
+ if (ts.isCallExpression(n) || ts.isNewExpression(n)) {
173
+ target = ts.isPropertyAccessExpression(n.expression) ? n.expression.name : n.expression;
174
+ for (const arg of n.arguments ?? []) {
175
+ if (ts.isIdentifier(arg) || ts.isPropertyAccessExpression(arg)) {
176
+ const hit = resolve(project, ts.isPropertyAccessExpression(arg) ? arg.name : arg);
177
+ if (hit && relevant(symbolId(hit.rel, hit.decl.key), lineOf(arg), "calls") && hit.decl.kind !== "const" && hit.decl.kind !== "type" && hit.decl.kind !== "interface") {
178
+ addEdge(symbol.id, ensureContext(project, hit.rel, hit.decl), "calls", lineOf(arg), input.mode === "diff" && inRanges(lineOf(arg), addedLines) ? "added" : "same");
179
+ }
180
+ }
181
+ }
182
+ }
183
+ else if (ts.isJsxOpeningElement(n) || ts.isJsxSelfClosingElement(n)) {
184
+ target = n.tagName;
185
+ kind = "renders";
186
+ }
187
+ else if (ts.isTypeReferenceNode(n)) {
188
+ target = n.typeName;
189
+ kind = "uses";
190
+ }
191
+ else if (ts.isExpressionWithTypeArguments(n) && ts.isHeritageClause(n.parent)) {
192
+ target = n.expression;
193
+ kind = n.parent.token === ts.SyntaxKind.ExtendsKeyword ? "extends" : "implements";
194
+ }
195
+ if (target) {
196
+ const hit = resolve(project, target);
197
+ if (hit) {
198
+ const targetId = symbolId(hit.rel, hit.decl.key);
199
+ // Type uses only matter when the type is part of the change.
200
+ const known = symbols.get(targetId);
201
+ const interesting = kind !== "uses" || (known !== undefined && known.status !== "context");
202
+ const line = lineOf(n);
203
+ if (interesting && targetId !== symbol.id && relevant(targetId, line, kind)) {
204
+ addEdge(symbol.id, ensureContext(project, hit.rel, hit.decl), kind, line, input.mode === "diff" && inRanges(line, addedLines) ? "added" : "same");
205
+ }
206
+ }
207
+ }
208
+ ts.forEachChild(n, visit);
209
+ };
210
+ ts.forEachChild(live.node, visit);
211
+ }
212
+ lap("outgoing");
213
+ // 3. Callers: who reaches the changed code from outside it.
214
+ if (input.mode === "diff") {
215
+ for (const { file, decl, symbol } of changedDecls) {
216
+ if (symbol.kind === "route" || symbol.kind === "class")
217
+ continue;
218
+ const project = projects.for(file);
219
+ const entry = project?.declsFor(file);
220
+ const live = entry?.decls.find((d) => d.key === decl.key);
221
+ if (!project || !entry || !live?.nameNode)
222
+ continue;
223
+ // Where to ask for references: the declaration, plus (for a lifted service function) the
224
+ // `return { patchGrant }` shorthand that callers actually reach through `store.patchGrant(...)`.
225
+ const positions = [live.nameNode.getStart(entry.sf)];
226
+ if (live.container) {
227
+ const holder = entry.decls.find((d) => d.key === live.container);
228
+ const visit = (n) => {
229
+ if (ts.isShorthandPropertyAssignment(n) && n.name.text === live.name)
230
+ positions.push(n.name.getStart(entry.sf));
231
+ ts.forEachChild(n, visit);
232
+ };
233
+ if (holder)
234
+ visit(holder.node);
235
+ }
236
+ const refs = [];
237
+ for (const at of positions) {
238
+ try {
239
+ refs.push(...(project.service.findReferences(project.abs(file), at) ?? []));
240
+ }
241
+ catch {
242
+ /* a reference search that throws just finds nothing */
243
+ }
244
+ }
245
+ let count = 0;
246
+ for (const group of refs) {
247
+ for (const ref of group.references) {
248
+ if (ref.isDefinition || count >= MAX_CALLERS)
249
+ continue;
250
+ const rel = project.rel(ref.fileName);
251
+ if (rel === null)
252
+ continue;
253
+ const callerEntry = project.declsFor(rel);
254
+ if (!callerEntry)
255
+ continue;
256
+ const pos = ref.textSpan.start;
257
+ const caller = enclosing(callerEntry.decls, callerEntry.sf, pos);
258
+ if (!caller)
259
+ continue;
260
+ const callerId = symbolId(rel, caller.key);
261
+ if (callerId === symbol.id)
262
+ continue;
263
+ const kind = refKind(callerEntry.sf, pos);
264
+ if (!kind)
265
+ continue;
266
+ if (kind === "uses" && symbol.kind !== "interface" && symbol.kind !== "type")
267
+ continue;
268
+ const line = callerEntry.sf.getLineAndCharacterOfPosition(pos).line + 1;
269
+ const callerAdded = input.hunks.get(rel)?.added ?? [];
270
+ if (!symbols.has(callerId))
271
+ symbols.set(callerId, toSymbol(rel, caller, "context"));
272
+ addEdge(callerId, symbol.id, kind, line, inRanges(line, callerAdded) ? "added" : "same");
273
+ count++;
274
+ }
275
+ }
276
+ }
277
+ // 4. Calls that disappeared: compare call names across sides.
278
+ for (const s of symbols.values()) {
279
+ if ((s.status !== "modified" && s.status !== "removed") || !s.oldRange || !input.oldSrc || !isTsLike(s.file))
280
+ continue;
281
+ const oldText = input.oldSrc.read(s.oldFile ?? s.file);
282
+ if (!oldText)
283
+ continue;
284
+ const oldSf = parse(s.oldFile ?? s.file, oldText);
285
+ const oldDecl = extractDecls(oldSf).find((d) => symbolId(s.file, d.key) === s.id);
286
+ if (!oldDecl)
287
+ continue;
288
+ const before = calleeNames(oldSf, oldDecl.node);
289
+ let after = new Map();
290
+ if (s.status === "modified" && s.range) {
291
+ const newText = input.newSrc.read(s.file);
292
+ const newSf = newText ? parse(s.file, newText) : null;
293
+ const newDecl = newSf ? extractDecls(newSf).find((d) => symbolId(s.file, d.key) === s.id) : null;
294
+ if (newSf && newDecl)
295
+ after = calleeNames(newSf, newDecl.node);
296
+ }
297
+ for (const [name, line] of before) {
298
+ if (after.has(name))
299
+ continue;
300
+ const target = [...symbols.values()].find((t) => t.name === name && t.id !== s.id && (t.file === s.file || t.status !== "context"));
301
+ if (target)
302
+ addEdge(s.id, target.id, "calls", line, "removed");
303
+ }
304
+ }
305
+ }
306
+ lap("callers+removed");
307
+ if (pyChanged.length) {
308
+ const index = input.pyIndex ?? new PyIndex(input.newSrc.files(), (f) => input.newSrc.read(f));
309
+ pyEdges(input, index, pyChanged, symbols, toSymbol, addEdge);
310
+ lap("python");
311
+ }
312
+ // 5. Doors: changed symbols nothing else in the change calls.
313
+ const all = [...symbols.values()];
314
+ const changed = new Set(all.filter((s) => s.status !== "context").map((s) => s.id));
315
+ for (const s of all) {
316
+ if (s.test || s.status === "removed" || s.kind === "type" || s.kind === "interface" || s.kind === "module" || s.kind === "const")
317
+ continue;
318
+ const scope = input.mode === "teach" ? true : changed.has(s.id);
319
+ if (!scope)
320
+ continue;
321
+ const incoming = [...edges.values()].filter((e) => e.to === s.id && e.kind !== "uses" && e.change !== "removed" && !symbols.get(e.from)?.test && (input.mode === "teach" || changed.has(e.from)));
322
+ s.entry = incoming.length === 0 && s.name !== "constructor" && (input.mode === "diff" || s.exported || s.kind === "route");
323
+ }
324
+ return { symbols: all, edges: [...edges.values()] };
325
+ }
326
+ function refKind(sf, pos) {
327
+ let node = findToken(sf, pos);
328
+ if (!node)
329
+ return null;
330
+ // Walk up through property access so obj.method() counts as a call of method.
331
+ while (node.parent && ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
332
+ node = node.parent;
333
+ const p = node.parent;
334
+ if (!p)
335
+ return null;
336
+ if (ts.isImportSpecifier(p) || ts.isExportSpecifier(p) || ts.isImportClause(p) || ts.isNamespaceImport(p))
337
+ return null;
338
+ if ((ts.isCallExpression(p) || ts.isNewExpression(p)) && p.expression === node)
339
+ return "calls";
340
+ if ((ts.isCallExpression(p) || ts.isNewExpression(p)) && p.arguments?.includes(node))
341
+ return "calls";
342
+ if (ts.isJsxOpeningElement(p) || ts.isJsxSelfClosingElement(p) || ts.isJsxClosingElement(p))
343
+ return ts.isJsxClosingElement(p) ? null : "renders";
344
+ if (ts.isTypeReferenceNode(p) || ts.isExpressionWithTypeArguments(p))
345
+ return "uses";
346
+ if (ts.isTaggedTemplateExpression(p))
347
+ return "calls";
348
+ return null;
349
+ }
350
+ function findToken(sf, pos) {
351
+ let found = null;
352
+ const visit = (n) => {
353
+ if (pos < n.getStart(sf) || pos >= n.getEnd())
354
+ return;
355
+ found = n;
356
+ ts.forEachChild(n, visit);
357
+ };
358
+ visit(sf);
359
+ return found;
360
+ }
361
+ /** Non-TS files get one symbol covering their changed region; the agent can split it. */
362
+ function moduleSymbol(f, hunks, input) {
363
+ const text = f.status === "deleted" ? null : input.newSrc.read(f.path);
364
+ const total = text?.split("\n").length ?? 0;
365
+ const span = (ranges, max) => {
366
+ if (!ranges.length)
367
+ return null;
368
+ const start = Math.max(1, Math.min(...ranges.map((r) => r.start)) - 3);
369
+ const end = Math.min(max || Infinity, Math.max(...ranges.map((r) => r.end)) + 3);
370
+ return { start, end: Math.max(start, end) };
371
+ };
372
+ const status = f.status === "added" ? "added" : f.status === "deleted" ? "removed" : input.mode === "teach" ? "context" : "modified";
373
+ const range = status === "removed" ? null : input.mode === "teach" ? { start: 1, end: Math.max(1, total) } : span(hunks.added, total) ?? { start: 1, end: Math.min(Math.max(total, 1), 40) };
374
+ const id = symbolId(f.path, "module");
375
+ return [
376
+ id,
377
+ {
378
+ id,
379
+ name: path.posix.basename(f.path),
380
+ container: null,
381
+ kind: "module",
382
+ file: f.path,
383
+ range,
384
+ oldFile: f.oldPath,
385
+ oldRange: span(hunks.deleted, Infinity),
386
+ status,
387
+ signature: null,
388
+ exported: false,
389
+ entry: false,
390
+ summary: null,
391
+ changedLines: f.additions + f.deletions,
392
+ test: isTestFile(f.path),
393
+ },
394
+ ];
395
+ }
396
+ function timer() {
397
+ let t = performance.now();
398
+ return (label) => {
399
+ if (process.env.DS_DEBUG)
400
+ console.error(`[graph] ${label}: ${Math.round(performance.now() - t)}ms`);
401
+ t = performance.now();
402
+ };
403
+ }
@@ -0,0 +1,141 @@
1
+ import { countInRange, inRanges } from "./git.js";
2
+ import { enclosingPy, parsePy, PyIndex } from "./py.js";
3
+ const MAX_CALLERS = 12;
4
+ const id = (file, key) => `${file}#${key}`;
5
+ function ownChanges(d, ranges) {
6
+ let n = 0;
7
+ for (let line = d.range.start; line <= d.range.end; line++)
8
+ if (inRanges(line, ranges) && !inRanges(line, d.memberRanges))
9
+ n++;
10
+ return n;
11
+ }
12
+ /** Statuses for one Python file's declarations, both sides. */
13
+ export function pySymbols(input, f, hunks, toSymbol, symbols) {
14
+ const newText = f.status === "deleted" ? null : input.newSrc.read(f.path);
15
+ const oldText = f.oldPath && input.oldSrc ? input.oldSrc.read(f.oldPath) : null;
16
+ const newDecls = newText !== null ? parsePy(f.path, newText).decls : [];
17
+ const oldDecls = oldText !== null ? parsePy(f.oldPath, oldText).decls : [];
18
+ const oldByKey = new Map(oldDecls.map((d) => [d.key, d]));
19
+ const newKeys = new Set(newDecls.map((d) => d.key));
20
+ const changed = [];
21
+ for (const d of newDecls) {
22
+ if (input.mode === "teach") {
23
+ if (!d.exported && d.kind === "function" && !d.container)
24
+ continue;
25
+ const s = toSymbol(f.path, d, "context");
26
+ symbols.set(s.id, s);
27
+ changed.push({ file: f.path, decl: d, symbol: s });
28
+ continue;
29
+ }
30
+ const od = oldByKey.get(d.key);
31
+ const own = d.memberRanges.length > 0;
32
+ const added = own ? ownChanges(d, hunks.added) : countInRange(d.range, hunks.added);
33
+ const deleted = od ? (od.memberRanges.length ? ownChanges(od, hunks.deleted) : countInRange(od.range, hunks.deleted)) : 0;
34
+ const status = !od && (added > 0 || f.status === "added") ? "added" : added + deleted > 0 ? "modified" : null;
35
+ if (!status)
36
+ continue;
37
+ const s = toSymbol(f.path, d, status, added + deleted);
38
+ if (od) {
39
+ s.oldFile = f.oldPath;
40
+ s.oldRange = od.range;
41
+ }
42
+ symbols.set(s.id, s);
43
+ changed.push({ file: f.path, decl: d, symbol: s });
44
+ }
45
+ if (input.mode === "diff") {
46
+ for (const od of oldDecls) {
47
+ if (newKeys.has(od.key))
48
+ continue;
49
+ const s = toSymbol(f.path, od, "removed", od.range.end - od.range.start + 1);
50
+ s.oldFile = f.oldPath;
51
+ s.oldRange = od.range;
52
+ symbols.set(s.id, s);
53
+ }
54
+ }
55
+ return changed;
56
+ }
57
+ /** Outgoing calls, callers, and calls that disappeared, for the changed Python symbols. */
58
+ export function pyEdges(input, index, changed, symbols, toSymbol, addEdge) {
59
+ const ensure = (file, d) => {
60
+ const sid = id(file, d.key);
61
+ if (!symbols.has(sid))
62
+ symbols.set(sid, toSymbol(file, d, "context"));
63
+ return sid;
64
+ };
65
+ // What changed code calls.
66
+ for (const { file, decl, symbol } of changed) {
67
+ const pf = index.get(file);
68
+ if (!pf)
69
+ continue;
70
+ const added = input.hunks.get(file)?.added ?? [];
71
+ for (const c of pf.calls) {
72
+ if (c.line < decl.range.start || c.line > decl.range.end || inRanges(c.line, decl.memberRanges))
73
+ continue;
74
+ const hit = index.resolve(file, c.chain, c.line);
75
+ if (!hit)
76
+ continue;
77
+ const targetId = id(hit.file, hit.decl.key);
78
+ if (targetId === symbol.id)
79
+ continue;
80
+ const known = symbols.get(targetId);
81
+ const near = inRanges(c.line, added) || inRanges(c.line - 1, added) || inRanges(c.line + 1, added);
82
+ if (input.mode === "diff" && !(known && known.status !== "context") && !near)
83
+ continue;
84
+ addEdge(symbol.id, ensure(hit.file, hit.decl), "calls", c.line, input.mode === "diff" && inRanges(c.line, added) ? "added" : "same");
85
+ }
86
+ }
87
+ if (input.mode !== "diff")
88
+ return;
89
+ // Who calls the changed code, from anywhere in the repo.
90
+ for (const { file, decl, symbol } of changed) {
91
+ if (symbol.kind === "class")
92
+ continue;
93
+ const callName = decl.name === "__init__" && decl.container ? decl.container.split(".").at(-1) : decl.name;
94
+ let count = 0;
95
+ for (const other of index.all()) {
96
+ if (count >= MAX_CALLERS)
97
+ break;
98
+ const text = input.newSrc.read(other);
99
+ if (!text || !text.includes(callName))
100
+ continue;
101
+ const pf = index.get(other);
102
+ if (!pf)
103
+ continue;
104
+ for (const c of pf.calls) {
105
+ if (c.chain.split(".").at(-1) !== callName)
106
+ continue;
107
+ const hit = index.resolve(other, c.chain, c.line);
108
+ if (!hit || id(hit.file, hit.decl.key) !== symbol.id)
109
+ continue;
110
+ const caller = enclosingPy(pf.decls, c.line);
111
+ if (!caller)
112
+ continue;
113
+ const callerId = id(other, caller.key);
114
+ if (callerId === symbol.id)
115
+ continue;
116
+ const added = input.hunks.get(other)?.added ?? [];
117
+ addEdge(ensure(other, caller), symbol.id, "calls", c.line, inRanges(c.line, added) ? "added" : "same");
118
+ if (++count >= MAX_CALLERS)
119
+ break;
120
+ }
121
+ }
122
+ }
123
+ // Calls that disappeared.
124
+ for (const s of symbols.values()) {
125
+ if ((s.status !== "modified" && s.status !== "removed") || !s.oldRange || !input.oldSrc || !(s.file.endsWith(".py") || s.file.endsWith(".pyi")))
126
+ continue;
127
+ const oldText = input.oldSrc.read(s.oldFile ?? s.file);
128
+ if (!oldText)
129
+ continue;
130
+ const before = PyIndex.calleeNames(parsePy(s.file, oldText), s.oldRange);
131
+ const pf = s.range ? index.get(s.file) : null;
132
+ const after = pf && s.range ? PyIndex.calleeNames(pf, s.range) : new Map();
133
+ for (const [name, line] of before) {
134
+ if (after.has(name))
135
+ continue;
136
+ const target = [...symbols.values()].find((t) => t.name === name && t.id !== s.id && (t.file === s.file || t.status !== "context"));
137
+ if (target)
138
+ addEdge(s.id, target.id, "calls", line, "removed");
139
+ }
140
+ }
141
+ }