srcly 0.1.11__tar.gz → 0.1.14__tar.gz

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 (309) hide show
  1. {srcly-0.1.11 → srcly-0.1.14}/PKG-INFO +2 -1
  2. {srcly-0.1.11 → srcly-0.1.14}/app/models.py +19 -0
  3. srcly-0.1.14/app/routers/analysis.py +608 -0
  4. {srcly-0.1.11 → srcly-0.1.14}/app/services/analysis.py +58 -2
  5. srcly-0.1.14/app/services/data_flow_analysis.py +875 -0
  6. {srcly-0.1.11 → srcly-0.1.14}/app/services/tree_sitter_analysis.py +583 -2
  7. srcly-0.1.14/app/static/assets/index-CTNf0zED.css +1 -0
  8. srcly-0.1.14/app/static/assets/index-DzyR2Ke2.js +37 -0
  9. {srcly-0.1.11 → srcly-0.1.14}/app/static/index.html +2 -2
  10. {srcly-0.1.11 → srcly-0.1.14}/pyproject.toml +2 -1
  11. srcly-0.1.11/app/routers/analysis.py +0 -99
  12. srcly-0.1.11/app/static/assets/index-BjRxOY6r.css +0 -1
  13. srcly-0.1.11/app/static/assets/index-poSkqVZY.js +0 -14
  14. {srcly-0.1.11 → srcly-0.1.14}/.gitignore +0 -0
  15. {srcly-0.1.11 → srcly-0.1.14}/README.md +0 -0
  16. {srcly-0.1.11 → srcly-0.1.14}/app/.gitignore +0 -0
  17. {srcly-0.1.11 → srcly-0.1.14}/app/__init__.py +0 -0
  18. {srcly-0.1.11 → srcly-0.1.14}/app/config.py +0 -0
  19. {srcly-0.1.11 → srcly-0.1.14}/app/main.py +0 -0
  20. {srcly-0.1.11 → srcly-0.1.14}/app/routers/files.py +0 -0
  21. {srcly-0.1.11 → srcly-0.1.14}/app/run.py +0 -0
  22. {srcly-0.1.11 → srcly-0.1.14}/app/services/cache.py +0 -0
  23. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/abap-BdImnpbu.js +0 -0
  24. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  25. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ada-bCR0ucgS.js +0 -0
  26. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  27. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  28. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  29. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/apache-Pmp26Uib.js +0 -0
  30. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/apex-DhZLUxFE.js +0 -0
  31. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/apl-dKokRX4l.js +0 -0
  32. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  33. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ara-BRHolxvo.js +0 -0
  34. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  35. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  36. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/astro-CbQHKStN.js +0 -0
  37. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  38. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/awk-DMzUqQB5.js +0 -0
  39. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  40. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  41. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bat-BkioyH1T.js +0 -0
  42. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  43. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/berry-D08WgyRC.js +0 -0
  44. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  45. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  46. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/blade-DVc8C-J4.js +0 -0
  47. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  48. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/c-BIGW1oBm.js +0 -0
  49. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  50. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  51. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  52. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  53. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  54. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  55. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/clarity-D53aC0YG.js +0 -0
  56. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/clojure-P80f7IUj.js +0 -0
  57. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  58. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cobol-nwyudZeR.js +0 -0
  59. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  60. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  61. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  62. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  63. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/coq-DkFqJrB1.js +0 -0
  64. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cpp-CofmeUqb.js +0 -0
  65. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  66. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  67. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/css-DPfMkruS.js +0 -0
  68. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/csv-fuZLfV_i.js +0 -0
  69. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cue-D82EKSYY.js +0 -0
  70. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/cypher-COkxafJQ.js +0 -0
  71. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/d-85-TOEBH.js +0 -0
  72. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  73. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dart-CF10PKvl.js +0 -0
  74. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dax-CEL-wOlO.js +0 -0
  75. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  76. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/diff-D97Zzqfu.js +0 -0
  77. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/docker-BcOcwvcX.js +0 -0
  78. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  79. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  80. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  81. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  82. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/edge-BkV0erSs.js +0 -0
  83. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/elixir-CDX3lj18.js +0 -0
  84. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/elm-DbKCFpqz.js +0 -0
  85. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  86. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/erb-BOJIQeun.js +0 -0
  87. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  88. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  89. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  90. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fennel-BYunw83y.js +0 -0
  91. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fish-BvzEVeQv.js +0 -0
  92. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  93. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  94. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  95. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  96. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  97. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  98. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  99. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/genie-D0YGMca9.js +0 -0
  100. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  101. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  102. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  103. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  104. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  105. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  106. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  107. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  108. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  109. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  110. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gleam-BspZqrRM.js +0 -0
  111. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  112. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  113. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/glsl-DplSGwfg.js +0 -0
  114. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  115. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/go-Dn2_MT6a.js +0 -0
  116. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  117. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  118. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  119. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  120. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  121. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  122. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  123. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  124. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hack-CaT9iCJl.js +0 -0
  125. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/haml-B8DHNrY2.js +0 -0
  126. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  127. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  128. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  129. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  130. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hjson-D5-asLiD.js +0 -0
  131. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  132. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/houston-DnULxvSX.js +0 -0
  133. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/html-GMplVEZG.js +0 -0
  134. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  135. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/http-jrhK8wxY.js +0 -0
  136. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hurl-irOxFIW8.js +0 -0
  137. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  138. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/hy-DFXneXwc.js +0 -0
  139. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/imba-DGztddWO.js +0 -0
  140. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ini-BEwlwnbL.js +0 -0
  141. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/java-CylS5w8V.js +0 -0
  142. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  143. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  144. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jison-wvAkD_A8.js +0 -0
  145. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/json-Cp-IABpG.js +0 -0
  146. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/json5-C9tS-k6U.js +0 -0
  147. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  148. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  149. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  150. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  151. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  152. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/julia-C8NyazO9.js +0 -0
  153. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  154. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  155. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  156. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kdl-DV7GczEv.js +0 -0
  157. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  158. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  159. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  160. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/latex-BUKiar2Z.js +0 -0
  161. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/lean-DP1Csr6i.js +0 -0
  162. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/less-B1dDrJ26.js +0 -0
  163. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  164. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/liquid-DYVedYrR.js +0 -0
  165. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/llvm-BtvRca6l.js +0 -0
  166. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/log-2UxHyX5q.js +0 -0
  167. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/logo-BtOb2qkB.js +0 -0
  168. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/lua-BbnMAYS6.js +0 -0
  169. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/luau-CXu1NL6O.js +0 -0
  170. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/make-CHLpvVh8.js +0 -0
  171. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  172. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/marko-CPi9NSCl.js +0 -0
  173. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  174. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  175. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  176. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  177. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  178. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/matlab-D7o27uSR.js +0 -0
  179. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mdc-DUICxH0z.js +0 -0
  180. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  181. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  182. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  183. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/min-light-CTRr51gU.js +0 -0
  184. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  185. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/mojo-1DNp92w6.js +0 -0
  186. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  187. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/move-Bu9oaDYs.js +0 -0
  188. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  189. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  190. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nginx-DknmC5AR.js +0 -0
  191. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  192. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nim-CVrawwO9.js +0 -0
  193. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nix-BbRYJGeE.js +0 -0
  194. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nord-Ddv68eIx.js +0 -0
  195. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/nushell-C-sUppwS.js +0 -0
  196. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  197. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  198. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  199. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  200. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  201. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  202. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/perl-C0TMdlhV.js +0 -0
  203. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/php-CDn_0X-4.js +0 -0
  204. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  205. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  206. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  207. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/po-BTJTHyun.js +0 -0
  208. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  209. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/polar-C0HS_06l.js +0 -0
  210. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/postcss-CXtECtnM.js +0 -0
  211. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  212. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  213. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  214. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  215. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/proto-DyJlTyXw.js +0 -0
  216. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/pug-CGlum2m_.js +0 -0
  217. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/puppet-BMWR74SV.js +0 -0
  218. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/purescript-CklMAg4u.js +0 -0
  219. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/python-B6aJPvgy.js +0 -0
  220. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/qml-3beO22l8.js +0 -0
  221. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  222. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/qss-IeuSbFQv.js +0 -0
  223. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/r-DiinP2Uv.js +0 -0
  224. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/racket-BqYA7rlc.js +0 -0
  225. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/raku-DXvB9xmW.js +0 -0
  226. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/razor-WgofotgN.js +0 -0
  227. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/red-bN70gL4F.js +0 -0
  228. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/reg-C-SQnVFl.js +0 -0
  229. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  230. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rel-C3B-1QV4.js +0 -0
  231. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  232. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  233. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  234. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  235. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  236. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rst-B0xPkSld.js +0 -0
  237. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  238. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/rust-B1yitclQ.js +0 -0
  239. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sas-cz2c8ADy.js +0 -0
  240. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  241. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/scala-C151Ov-r.js +0 -0
  242. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/scheme-C98Dy4si.js +0 -0
  243. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/scss-OYdSNvt2.js +0 -0
  244. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  245. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  246. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  247. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  248. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  249. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  250. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  251. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  252. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  253. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  254. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  255. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/soy-Brmx7dQM.js +0 -0
  256. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  257. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  258. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/sql-BLtJtn59.js +0 -0
  259. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  260. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/stata-BH5u7GGu.js +0 -0
  261. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  262. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  263. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/swift-Dg5xB15N.js +0 -0
  264. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  265. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  266. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  267. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  268. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  269. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  270. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/templ-W15q3VgB.js +0 -0
  271. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/terraform-BETggiCN.js +0 -0
  272. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tex-Cppo0RY3.js +0 -0
  273. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  274. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/toml-vGWfd6FD.js +0 -0
  275. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  276. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  277. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/tsx-COt5Ahok.js +0 -0
  278. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/turtle-BsS91CYL.js +0 -0
  279. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/twig-CO9l9SDP.js +0 -0
  280. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  281. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/typespec-Df68jz8_.js +0 -0
  282. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/typst-DHCkPAjA.js +0 -0
  283. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/v-BcVCzyr7.js +0 -0
  284. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vala-CsfeWuGM.js +0 -0
  285. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vb-D17OF-Vu.js +0 -0
  286. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  287. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vesper-DU1UobuO.js +0 -0
  288. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  289. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/viml-CJc9bBzg.js +0 -0
  290. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  291. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  292. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  293. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vue-CCoi5OLL.js +0 -0
  294. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  295. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  296. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  297. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  298. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  299. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  300. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  301. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  302. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wit-5i3qLPDT.js +0 -0
  303. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  304. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  305. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  306. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/yaml-Buea-lGh.js +0 -0
  307. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  308. {srcly-0.1.11 → srcly-0.1.14}/app/static/assets/zig-VOosw3JB.js +0 -0
  309. {srcly-0.1.11 → srcly-0.1.14}/app/static/vite.svg +0 -0
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: srcly
3
- Version: 0.1.11
3
+ Version: 0.1.14
4
4
  Summary: Interactive codebase treemap and metrics viewer.
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: fastapi>=0.121.3
7
+ Requires-Dist: httpx>=0.28.1
7
8
  Requires-Dist: lizard>=1.19.0
8
9
  Requires-Dist: pathspec>=0.12.1
9
10
  Requires-Dist: pydantic>=2.12.4
@@ -48,3 +48,22 @@ class Node(BaseModel):
48
48
  model_config = {
49
49
  "populate_by_name": True
50
50
  }
51
+
52
+ class DependencyNode(BaseModel):
53
+ id: str
54
+ label: str
55
+ # "file", "external", "export"
56
+ type: str = "file"
57
+ # For nested/compound graph layouts (e.g. exports inside file nodes)
58
+ parent: str | None = None
59
+ metrics: Metrics | None = None
60
+
61
+ class DependencyEdge(BaseModel):
62
+ id: str
63
+ source: str
64
+ target: str
65
+ label: str | None = None
66
+
67
+ class DependencyGraph(BaseModel):
68
+ nodes: List[DependencyNode]
69
+ edges: List[DependencyEdge]
@@ -0,0 +1,608 @@
1
+ from fastapi import APIRouter, HTTPException
2
+ from pathlib import Path
3
+ import json
4
+ import os
5
+ from typing import Dict, List, Optional, Tuple
6
+
7
+ from app.models import Node, DependencyGraph, DependencyNode, DependencyEdge
8
+ from app.services import analysis, cache, tree_sitter_analysis
9
+ from app.config import IGNORE_DIRS
10
+
11
+ router = APIRouter(prefix="/api/analysis", tags=["analysis"])
12
+
13
+ # TODO: Make this configurable via env var or request
14
+ ROOT_PATH = Path.cwd()
15
+
16
+ TSCONFIG_CANDIDATE_NAMES: Tuple[str, ...] = (
17
+ "tsconfig.json",
18
+ "tsconfig.app.json",
19
+ "tsconfig.base.json",
20
+ )
21
+
22
+
23
+ def _strip_json_comments(text: str) -> str:
24
+ """
25
+ Best-effort removal of `//` and `/* ... */` comments from a JSON-like file.
26
+
27
+ TypeScript config files (`tsconfig.json`, `tsconfig.app.json`, etc.) are
28
+ JSON-with-comments. The standard `json` module cannot parse them directly,
29
+ so we strip comments while preserving string literals as far as is
30
+ reasonably practical.
31
+
32
+ This is not a full JSONC parser but is sufficient for typical tsconfig
33
+ usage: it understands string literals (single or double quoted), escape
34
+ sequences, line comments and block comments.
35
+ """
36
+ result: list[str] = []
37
+ i = 0
38
+ n = len(text)
39
+ in_string = False
40
+ string_quote = ""
41
+ in_line_comment = False
42
+ in_block_comment = False
43
+
44
+ while i < n:
45
+ ch = text[i]
46
+ nxt = text[i + 1] if i + 1 < n else ""
47
+
48
+ if in_line_comment:
49
+ # End of line comment at newline; keep the newline itself.
50
+ if ch == "\n":
51
+ in_line_comment = False
52
+ result.append(ch)
53
+ i += 1
54
+ continue
55
+
56
+ if in_block_comment:
57
+ # End of block comment at */
58
+ if ch == "*" and nxt == "/":
59
+ in_block_comment = False
60
+ i += 2
61
+ else:
62
+ i += 1
63
+ continue
64
+
65
+ if in_string:
66
+ result.append(ch)
67
+ if ch == "\\":
68
+ # Preserve escaped character and skip over it.
69
+ if i + 1 < n:
70
+ result.append(text[i + 1])
71
+ i += 2
72
+ else:
73
+ i += 1
74
+ elif ch == string_quote:
75
+ in_string = False
76
+ i += 1
77
+ else:
78
+ i += 1
79
+ continue
80
+
81
+ # Not currently in string or comment.
82
+ if ch in ("'", '"'):
83
+ in_string = True
84
+ string_quote = ch
85
+ result.append(ch)
86
+ i += 1
87
+ continue
88
+
89
+ if ch == "/" and nxt == "/":
90
+ in_line_comment = True
91
+ i += 2
92
+ continue
93
+
94
+ if ch == "/" and nxt == "*":
95
+ in_block_comment = True
96
+ i += 2
97
+ continue
98
+
99
+ result.append(ch)
100
+ i += 1
101
+
102
+ return "".join(result)
103
+
104
+
105
+ def _find_candidate_tsconfig_files(start: Path) -> List[Path]:
106
+ """
107
+ Walk up from the given path and collect any nearby tsconfig-style files.
108
+
109
+ We keep this deliberately simple for now:
110
+ - Look for a small set of common tsconfig filenames.
111
+ - Stop at the filesystem root.
112
+ - Return candidates ordered from nearest directory upwards.
113
+ """
114
+ current = start if start.is_dir() else start.parent
115
+ seen: set[Path] = set()
116
+ candidates: List[Path] = []
117
+
118
+ for parent in [current, *current.parents]:
119
+ for name in TSCONFIG_CANDIDATE_NAMES:
120
+ candidate = parent / name
121
+ if candidate.exists() and candidate not in seen:
122
+ seen.add(candidate)
123
+ candidates.append(candidate)
124
+
125
+ return candidates
126
+
127
+
128
+ def _load_tsconfig_paths(tsconfig_path: Path) -> Tuple[Path, Dict[str, List[str]]]:
129
+ """
130
+ Load `compilerOptions.baseUrl` and `compilerOptions.paths` from a tsconfig.
131
+
132
+ Returns a tuple of:
133
+ - base_dir: the directory from which path mappings should be resolved
134
+ - paths: the raw `paths` mapping (alias -> list of patterns)
135
+
136
+ If anything goes wrong, we fall back to treating the tsconfig's parent
137
+ directory as the base and an empty paths mapping.
138
+ """
139
+ try:
140
+ with tsconfig_path.open("r", encoding="utf-8") as f:
141
+ raw = f.read()
142
+ # tsconfig files are JSON-with-comments; strip comments before parsing.
143
+ cleaned = _strip_json_comments(raw)
144
+ data = json.loads(cleaned)
145
+ except Exception:
146
+ # On any failure, fall back to treating the tsconfig directory as the
147
+ # base and ignoring custom path mappings. This preserves previous
148
+ # behaviour while allowing us to handle commented configs when parsing
149
+ # succeeds.
150
+ return tsconfig_path.parent, {}
151
+
152
+ compiler = data.get("compilerOptions") or {}
153
+ base_url = compiler.get("baseUrl")
154
+
155
+ if isinstance(base_url, str) and base_url.strip():
156
+ base_dir = (tsconfig_path.parent / base_url).resolve()
157
+ else:
158
+ base_dir = tsconfig_path.parent.resolve()
159
+
160
+ raw_paths = compiler.get("paths") or {}
161
+ paths: Dict[str, List[str]] = {}
162
+
163
+ if isinstance(raw_paths, dict):
164
+ for key, value in raw_paths.items():
165
+ if isinstance(key, str):
166
+ # Normalise to list[str]
167
+ if isinstance(value, list):
168
+ paths[key] = [v for v in value if isinstance(v, str)]
169
+ elif isinstance(value, str):
170
+ paths[key] = [value]
171
+
172
+ return base_dir, paths
173
+
174
+
175
+ def _apply_tsconfig_paths(
176
+ import_path: str,
177
+ base_dir: Path,
178
+ paths: Dict[str, List[str]],
179
+ ) -> List[Path]:
180
+ """
181
+ Apply TypeScript path aliases to an import path to get candidate filesystem paths.
182
+
183
+ We handle the most common/simple cases:
184
+ - Exact alias: \"@core\": [\"src/core/index.ts\"]
185
+ - Single trailing wildcard: \"@utils/*\": [\"src/utils/*\"]
186
+
187
+ More complex patterns (multiple '*' or wildcards in the middle) are ignored
188
+ for now to keep behaviour predictable.
189
+ """
190
+ if not paths:
191
+ return []
192
+
193
+ candidates: List[Path] = []
194
+
195
+ for pattern, target_patterns in paths.items():
196
+ if "*" in pattern:
197
+ # Only support a single trailing '*' pattern like '@alias/*'
198
+ star_index = pattern.find("*")
199
+ prefix = pattern[:star_index]
200
+ suffix = pattern[star_index + 1 :]
201
+
202
+ if not import_path.startswith(prefix) or not import_path.endswith(suffix):
203
+ continue
204
+
205
+ wildcard_value = import_path[len(prefix) : len(import_path) - len(suffix)]
206
+
207
+ for target_pattern in target_patterns:
208
+ if "*" not in target_pattern:
209
+ # Simple case: just append the wildcard value if present.
210
+ target = target_pattern
211
+ if wildcard_value:
212
+ # Avoid duplicate slashes
213
+ if not target.endswith("/") and not wildcard_value.startswith("/"):
214
+ target = f"{target}/{wildcard_value}"
215
+ else:
216
+ target = f"{target}{wildcard_value}"
217
+ else:
218
+ # Replace the first '*' with the wildcard value.
219
+ target_star_index = target_pattern.find("*")
220
+ target_prefix = target_pattern[:target_star_index]
221
+ target_suffix = target_pattern[target_star_index + 1 :]
222
+ target = f"{target_prefix}{wildcard_value}{target_suffix}"
223
+
224
+ candidates.append((base_dir / target).resolve())
225
+ else:
226
+ # Exact match alias, e.g. '@core': ['src/core/index.ts']
227
+ if import_path != pattern:
228
+ continue
229
+ for target_pattern in target_patterns:
230
+ candidates.append((base_dir / target_pattern).resolve())
231
+
232
+ return candidates
233
+
234
+
235
+ def _resolve_internal_file(spec_path: Path, file_to_id: Dict[Path, str]) -> Optional[Path]:
236
+ """
237
+ Given a spec_path that may or may not include an extension, try to resolve it
238
+ to one of the known files in file_to_id using common TS/TSX conventions.
239
+ """
240
+ resolved = spec_path.resolve()
241
+ if resolved in file_to_id:
242
+ return resolved
243
+
244
+ # If the path has an extension that is clearly a non-code asset (CSS,
245
+ # images, JSON etc.), do not try to map it onto a TS/TSX module.
246
+ # This prevents things like `import "./index.css"` from incorrectly
247
+ # resolving to `index.tsx`.
248
+ code_exts = {".js", ".jsx", ".ts", ".tsx", ".d.ts"}
249
+ asset_exts = {
250
+ ".css",
251
+ ".scss",
252
+ ".sass",
253
+ ".less",
254
+ ".styl",
255
+ ".json",
256
+ ".png",
257
+ ".jpg",
258
+ ".jpeg",
259
+ ".gif",
260
+ ".svg",
261
+ ".ico",
262
+ ".webp",
263
+ ".bmp",
264
+ ".avif",
265
+ }
266
+
267
+ suffix = resolved.suffix
268
+
269
+ if suffix and suffix not in code_exts and suffix in asset_exts:
270
+ return None
271
+
272
+ # Try common TypeScript/TSX extensions based on the stem first, which
273
+ # covers imports like "./foo" or "./foo.js" -> "./foo.ts(x)".
274
+ stem = resolved.with_suffix("")
275
+ for ext in (".ts", ".tsx", ".d.ts"):
276
+ candidate = stem.with_suffix(ext)
277
+ if candidate in file_to_id:
278
+ return candidate
279
+
280
+ # Special case: imports that include an extra "qualifier" segment in the
281
+ # filename such as "../data/docs.service". In many TS codebases this
282
+ # resolves to "docs.service.ts" or "docs.service.tsx". Tree-sitter gives
283
+ # us the literal specifier "docs.service" which Path.suffix reports as
284
+ # ".service", so the simple stem-based logic above would look for
285
+ # "docs.ts" instead of "docs.service.ts".
286
+ #
287
+ # For any non-asset suffix that isn't a recognised JS/TS extension,
288
+ # also try appending TS/TSX extensions to the *full* filename.
289
+ if suffix and suffix not in code_exts and suffix not in asset_exts:
290
+ base_name = resolved.name # e.g. "docs.service"
291
+ for ext in (".ts", ".tsx", ".d.ts"):
292
+ candidate = (resolved.parent / f"{base_name}{ext}").resolve()
293
+ if candidate in file_to_id:
294
+ return candidate
295
+
296
+ # Try index files in the target directory
297
+ for index_name in ("index.ts", "index.tsx"):
298
+ candidate = (resolved / index_name).resolve()
299
+ if candidate in file_to_id:
300
+ return candidate
301
+
302
+ return None
303
+
304
+ @router.get("", response_model=Node)
305
+ async def get_analysis(path: str = None):
306
+ """
307
+ Get the static analysis of the codebase.
308
+ Returns cached result if available, otherwise triggers a scan.
309
+ """
310
+ target_path = Path(path) if path else ROOT_PATH
311
+
312
+ if not target_path.exists():
313
+ # Fallback or error? Let's error if explicit path is invalid
314
+ if path:
315
+ raise HTTPException(status_code=404, detail="Path not found")
316
+ target_path = ROOT_PATH
317
+
318
+ cached_tree = cache.load_analysis(target_path)
319
+ if cached_tree:
320
+ return cached_tree
321
+
322
+ # If no cache, run scan synchronously (for now, could be async/background)
323
+ tree = analysis.scan_codebase(target_path)
324
+ cache.save_analysis(target_path, tree)
325
+ return tree
326
+
327
+ @router.get("/dependencies", response_model=DependencyGraph)
328
+ async def get_dependencies(path: str = None):
329
+ """
330
+ Build a dependency graph for the specified path.
331
+ """
332
+ target_path = Path(path) if path else ROOT_PATH
333
+ if not target_path.exists():
334
+ raise HTTPException(status_code=404, detail="Path not found")
335
+
336
+ analyzer = tree_sitter_analysis.TreeSitterAnalyzer()
337
+ nodes = []
338
+ edges = []
339
+
340
+ # Map file path to node ID
341
+ file_to_id = {}
342
+ # Map node ID to file path
343
+ id_to_file = {}
344
+
345
+ # 1. Scan files and create nodes
346
+ # We only care about TS/TSX files for now
347
+ files_to_process = []
348
+ if target_path.is_file():
349
+ if target_path.suffix in {'.ts', '.tsx'}:
350
+ files_to_process.append(target_path)
351
+ else:
352
+ for root, dirs, files in os.walk(target_path):
353
+ # Filter ignored dirs
354
+ dirs[:] = [d for d in dirs if d not in IGNORE_DIRS and not d.startswith(".")]
355
+ for file in files:
356
+ if file.endswith(('.ts', '.tsx')):
357
+ files_to_process.append(Path(root) / file)
358
+
359
+ for file_path in files_to_process:
360
+ file_path = file_path.resolve()
361
+ node_id = str(file_path) # Use absolute path as ID for simplicity
362
+
363
+ # If the file is an index.* file or uses a dynamic route-style name
364
+ # like [id].tsx, include its parent folder name to make the node
365
+ # label more informative (e.g. "components/index.tsx" or
366
+ # "posts/[id].tsx").
367
+ file_name = file_path.name
368
+ is_index = file_path.stem == "index"
369
+ has_brackets = ("[" in file_name) and ("]" in file_name)
370
+
371
+ if (is_index or has_brackets) and file_path.parent is not None:
372
+ label = f"{file_path.parent.name}/{file_name}"
373
+ else:
374
+ label = file_name
375
+
376
+ nodes.append(DependencyNode(id=node_id, label=label, type="file"))
377
+ file_to_id[file_path] = node_id
378
+ id_to_file[node_id] = file_path
379
+
380
+ # 2. Find candidate tsconfig files and load the first one (nearest first).
381
+ tsconfig_candidates = _find_candidate_tsconfig_files(target_path)
382
+ ts_base_dir: Optional[Path] = None
383
+ ts_paths: Dict[str, List[str]] = {}
384
+ if tsconfig_candidates:
385
+ ts_base_dir, ts_paths = _load_tsconfig_paths(tsconfig_candidates[0])
386
+
387
+ # 3. Extract imports and create edges
388
+
389
+
390
+ # Refactored 2-pass approach
391
+
392
+ # Pass 1: Analyze all files to get exports and build nodes
393
+ file_exports = {} # file_id -> { export_name: export_node_id }
394
+
395
+ # We need to store imports for pass 2
396
+ file_imports = {} # file_id -> list of imports
397
+
398
+ for file_path in files_to_process:
399
+ file_path = file_path.resolve()
400
+ file_id = file_to_id[file_path]
401
+
402
+ try:
403
+ imports, exports = analyzer.extract_imports_exports(str(file_path))
404
+ file_imports[file_id] = imports
405
+
406
+ current_file_exports = {}
407
+ for exp in exports:
408
+ exp_name = exp["name"]
409
+ exp_id = f"{file_id}::{exp_name}"
410
+
411
+ nodes.append(DependencyNode(
412
+ id=exp_id,
413
+ label=exp_name,
414
+ type="export",
415
+ parent=file_id
416
+ ))
417
+ current_file_exports[exp_name] = exp_id
418
+
419
+ file_exports[file_id] = current_file_exports
420
+
421
+ except Exception as e:
422
+ print(f"Error analyzing {file_path}: {e}")
423
+ continue
424
+
425
+ # Pass 2: Build edges
426
+ for file_path in files_to_process:
427
+ file_path = file_path.resolve()
428
+ source_id = file_to_id[file_path] # This is the IMPORTING file
429
+
430
+ imports = file_imports.get(source_id, [])
431
+
432
+ for imp in imports:
433
+ import_path = imp["source"]
434
+ symbols = imp["symbols"]
435
+
436
+ target_file: Optional[Path] = None
437
+
438
+ # Resolve target
439
+ if import_path.startswith("."):
440
+ resolved = file_path.parent / import_path
441
+ target_file = _resolve_internal_file(resolved, file_to_id)
442
+ else:
443
+ if ts_base_dir is not None and ts_paths:
444
+ alias_candidates = _apply_tsconfig_paths(import_path, ts_base_dir, ts_paths)
445
+ for candidate in alias_candidates:
446
+ target_file = _resolve_internal_file(candidate, file_to_id)
447
+ if target_file is not None:
448
+ break
449
+
450
+ if target_file:
451
+ target_id = file_to_id[target_file] # This is the EXPORTING file
452
+
453
+ # If we have symbols, try to link from specific export nodes
454
+ linked_symbols = False
455
+ if symbols:
456
+ target_exports = file_exports.get(target_id, {})
457
+ for sym in symbols:
458
+ # Handle default import
459
+ if sym == "default":
460
+ # Look for a default export
461
+ if "default" in target_exports:
462
+ export_node_id = target_exports["default"]
463
+ edges.append(DependencyEdge(
464
+ id=f"{export_node_id}-{source_id}",
465
+ source=export_node_id,
466
+ target=source_id,
467
+ label="default"
468
+ ))
469
+ linked_symbols = True
470
+ elif sym in target_exports:
471
+ export_node_id = target_exports[sym]
472
+ edges.append(DependencyEdge(
473
+ id=f"{export_node_id}-{source_id}",
474
+ source=export_node_id,
475
+ target=source_id,
476
+ label=None
477
+ ))
478
+ linked_symbols = True
479
+
480
+ # Fallback: if we didn't extract any symbols for this import, connect all
481
+ # exports from the target file to the importing file.
482
+ #
483
+ # IMPORTANT: we deliberately do *not* fall back when we have a non-empty
484
+ # symbol list but failed to match some of them. In that case we prefer
485
+ # to show only the edges for the symbols we could confidently resolve,
486
+ # instead of pretending that every export from the target is used.
487
+ if not symbols and target_id in file_exports:
488
+ for export_name, export_node_id in file_exports[target_id].items():
489
+ edges.append(
490
+ DependencyEdge(
491
+ id=f"{export_node_id}-{source_id}",
492
+ source=export_node_id,
493
+ target=source_id,
494
+ label=None,
495
+ )
496
+ )
497
+
498
+ # Always include a standard file-to-file edge so the client can show
499
+ # high-level dependencies even when export-level edges are hidden.
500
+ edges.append(DependencyEdge(
501
+ id=f"{source_id}-{target_id}", # Note: Standard direction A -> B (A depends on B)
502
+ # Wait, standard direction is Source (Importer) -> Target (Imported).
503
+ # My new edges are Export (Imported) -> Importer.
504
+ # This is reverse direction.
505
+ source=source_id,
506
+ target=target_id,
507
+ label=None
508
+ ))
509
+
510
+ else:
511
+ # External
512
+ if not import_path.startswith("."):
513
+ ext_id = f"ext:{import_path}"
514
+ if ext_id not in id_to_file:
515
+ nodes.append(DependencyNode(id=ext_id, label=import_path, type="external"))
516
+ id_to_file[ext_id] = "external"
517
+
518
+ edges.append(DependencyEdge(
519
+ id=f"{source_id}-{ext_id}",
520
+ source=source_id,
521
+ target=ext_id,
522
+ label=None
523
+ ))
524
+
525
+ return DependencyGraph(nodes=nodes, edges=edges)
526
+
527
+ @router.post("/refresh", response_model=Node)
528
+ async def refresh_analysis():
529
+ """
530
+ Force a re-scan of the codebase.
531
+ """
532
+ tree = analysis.scan_codebase(ROOT_PATH)
533
+ cache.save_analysis(ROOT_PATH, tree)
534
+ return tree
535
+
536
+
537
+ def _estimate_counts(root: Path) -> tuple[int, int]:
538
+ """
539
+ Return an estimated (file_count, folder_count) for a given root directory.
540
+
541
+ This applies the same ignore rules used during a full analysis to keep the
542
+ estimate reasonably fast while still informative.
543
+ """
544
+ file_count = 0
545
+ folder_count = 0
546
+
547
+ try:
548
+ for _, dirnames, filenames in os.walk(root):
549
+ # Apply ignore rules for directories to keep the estimate reasonable
550
+ dirnames[:] = [
551
+ d for d in dirnames
552
+ if d not in IGNORE_DIRS and not d.startswith(".")
553
+ ]
554
+ folder_count += len(dirnames)
555
+ file_count += len(filenames)
556
+ except Exception:
557
+ # If anything goes wrong, fall back to zeros but still report the path.
558
+ file_count = 0
559
+ folder_count = 0
560
+
561
+ return file_count, folder_count
562
+
563
+
564
+ @router.get("/context")
565
+ async def get_analysis_context():
566
+ """
567
+ Return basic information about the current analysis root directory.
568
+
569
+ This is used by the client to offer one-click analysis options on first
570
+ load, for both the current working directory and the repository root.
571
+ """
572
+ current_root = ROOT_PATH
573
+ repo_root = current_root.parent
574
+
575
+ current_file_count, current_folder_count = _estimate_counts(current_root)
576
+
577
+ repo_file_count = 0
578
+ repo_folder_count = 0
579
+ if repo_root.exists():
580
+ repo_file_count, repo_folder_count = _estimate_counts(repo_root)
581
+
582
+ return {
583
+ "root_path": str(current_root),
584
+ "file_count": current_file_count,
585
+ "folder_count": current_folder_count,
586
+ "repo_root_path": str(repo_root),
587
+ "repo_file_count": repo_file_count,
588
+ "repo_folder_count": repo_folder_count,
589
+ }
590
+
591
+ @router.get("/data-flow")
592
+ async def get_data_flow(path: str):
593
+ """
594
+ Analyze data flow for a specific file.
595
+ """
596
+ target_path = Path(path)
597
+ if not target_path.exists() or not target_path.is_file():
598
+ raise HTTPException(status_code=404, detail="File not found")
599
+
600
+ from app.services.data_flow_analysis import DataFlowAnalyzer
601
+ analyzer = DataFlowAnalyzer()
602
+
603
+ try:
604
+ graph = analyzer.analyze_file(str(target_path))
605
+ return graph
606
+ except Exception as e:
607
+ print(f"Error analyzing data flow for {path}: {e}")
608
+ raise HTTPException(status_code=500, detail=str(e))