srcly 0.1.29__tar.gz → 0.1.30__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 (316) hide show
  1. {srcly-0.1.29 → srcly-0.1.30}/.gitignore +2 -1
  2. {srcly-0.1.29 → srcly-0.1.30}/PKG-INFO +1 -1
  3. srcly-0.1.30/app/run.py +361 -0
  4. {srcly-0.1.29 → srcly-0.1.30}/app/services/analysis.py +11 -10
  5. {srcly-0.1.29 → srcly-0.1.30}/app/services/focus_overlay.py +51 -14
  6. srcly-0.1.30/app/services/reporting.py +653 -0
  7. {srcly-0.1.29 → srcly-0.1.30}/app/services/typescript/typescript_analysis.py +1 -1
  8. srcly-0.1.30/app/static/assets/index-Bn6hDYiR.js +66 -0
  9. srcly-0.1.30/app/static/assets/index-DK1czI50.css +1 -0
  10. {srcly-0.1.29 → srcly-0.1.30}/app/static/index.html +2 -2
  11. {srcly-0.1.29 → srcly-0.1.30}/pyproject.toml +1 -1
  12. srcly-0.1.29/app/run.py +0 -125
  13. srcly-0.1.29/app/static/assets/index-LMOq9q5x.css +0 -1
  14. srcly-0.1.29/app/static/assets/index-VGOFgxN6.js +0 -66
  15. {srcly-0.1.29 → srcly-0.1.30}/README.md +0 -0
  16. {srcly-0.1.29 → srcly-0.1.30}/app/.gitignore +0 -0
  17. {srcly-0.1.29 → srcly-0.1.30}/app/__init__.py +0 -0
  18. {srcly-0.1.29 → srcly-0.1.30}/app/config.py +0 -0
  19. {srcly-0.1.29 → srcly-0.1.30}/app/main.py +0 -0
  20. {srcly-0.1.29 → srcly-0.1.30}/app/models.py +0 -0
  21. {srcly-0.1.29 → srcly-0.1.30}/app/routers/analysis.py +0 -0
  22. {srcly-0.1.29 → srcly-0.1.30}/app/routers/files.py +0 -0
  23. {srcly-0.1.29 → srcly-0.1.30}/app/services/analysis_types.py +0 -0
  24. {srcly-0.1.29 → srcly-0.1.30}/app/services/cache.py +0 -0
  25. {srcly-0.1.29 → srcly-0.1.30}/app/services/css/css_analysis.py +0 -0
  26. {srcly-0.1.29 → srcly-0.1.30}/app/services/data_flow_analysis.py +0 -0
  27. {srcly-0.1.29 → srcly-0.1.30}/app/services/ipynb/ipynb_analysis.py +0 -0
  28. {srcly-0.1.29 → srcly-0.1.30}/app/services/markdown/markdown_analysis.py +0 -0
  29. {srcly-0.1.29 → srcly-0.1.30}/app/services/python/python_analysis.py +0 -0
  30. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/abap-BdImnpbu.js +0 -0
  31. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  32. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ada-bCR0ucgS.js +0 -0
  33. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  34. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  35. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  36. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/apache-Pmp26Uib.js +0 -0
  37. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/apex-DhZLUxFE.js +0 -0
  38. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/apl-dKokRX4l.js +0 -0
  39. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  40. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ara-BRHolxvo.js +0 -0
  41. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  42. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  43. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/astro-CbQHKStN.js +0 -0
  44. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  45. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/awk-DMzUqQB5.js +0 -0
  46. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  47. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  48. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bat-BkioyH1T.js +0 -0
  49. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  50. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/berry-D08WgyRC.js +0 -0
  51. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  52. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  53. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/blade-DVc8C-J4.js +0 -0
  54. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  55. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/c-BIGW1oBm.js +0 -0
  56. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  57. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  58. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  59. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  60. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  61. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  62. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/clarity-D53aC0YG.js +0 -0
  63. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/clojure-P80f7IUj.js +0 -0
  64. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  65. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cobol-nwyudZeR.js +0 -0
  66. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  67. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  68. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  69. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  70. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/coq-DkFqJrB1.js +0 -0
  71. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cpp-CofmeUqb.js +0 -0
  72. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  73. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  74. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/css-DPfMkruS.js +0 -0
  75. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/csv-fuZLfV_i.js +0 -0
  76. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cue-D82EKSYY.js +0 -0
  77. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cypher-COkxafJQ.js +0 -0
  78. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/d-85-TOEBH.js +0 -0
  79. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  80. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dart-CF10PKvl.js +0 -0
  81. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dax-CEL-wOlO.js +0 -0
  82. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  83. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/diff-D97Zzqfu.js +0 -0
  84. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/docker-BcOcwvcX.js +0 -0
  85. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  86. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  87. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  88. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  89. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/edge-BkV0erSs.js +0 -0
  90. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/elixir-CDX3lj18.js +0 -0
  91. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/elm-DbKCFpqz.js +0 -0
  92. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  93. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/erb-BOJIQeun.js +0 -0
  94. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  95. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  96. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  97. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fennel-BYunw83y.js +0 -0
  98. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fish-BvzEVeQv.js +0 -0
  99. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  100. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  101. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  102. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  103. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  104. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  105. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  106. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/genie-D0YGMca9.js +0 -0
  107. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  108. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  109. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  110. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  111. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  112. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  113. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  114. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  115. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  116. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  117. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gleam-BspZqrRM.js +0 -0
  118. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  119. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  120. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/glsl-DplSGwfg.js +0 -0
  121. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  122. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/go-Dn2_MT6a.js +0 -0
  123. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  124. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  125. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  126. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  127. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  128. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  129. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  130. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  131. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hack-CaT9iCJl.js +0 -0
  132. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/haml-B8DHNrY2.js +0 -0
  133. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  134. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  135. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  136. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  137. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hjson-D5-asLiD.js +0 -0
  138. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  139. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/houston-DnULxvSX.js +0 -0
  140. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/html-GMplVEZG.js +0 -0
  141. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  142. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/http-jrhK8wxY.js +0 -0
  143. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hurl-irOxFIW8.js +0 -0
  144. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  145. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hy-DFXneXwc.js +0 -0
  146. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/imba-DGztddWO.js +0 -0
  147. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ini-BEwlwnbL.js +0 -0
  148. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/java-CylS5w8V.js +0 -0
  149. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  150. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  151. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jison-wvAkD_A8.js +0 -0
  152. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/json-Cp-IABpG.js +0 -0
  153. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/json5-C9tS-k6U.js +0 -0
  154. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  155. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  156. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  157. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  158. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  159. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/julia-C8NyazO9.js +0 -0
  160. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  161. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  162. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  163. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kdl-DV7GczEv.js +0 -0
  164. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  165. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  166. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  167. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/latex-BUKiar2Z.js +0 -0
  168. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/lean-DP1Csr6i.js +0 -0
  169. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/less-B1dDrJ26.js +0 -0
  170. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  171. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/liquid-DYVedYrR.js +0 -0
  172. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/llvm-BtvRca6l.js +0 -0
  173. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/log-2UxHyX5q.js +0 -0
  174. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/logo-BtOb2qkB.js +0 -0
  175. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/lua-BbnMAYS6.js +0 -0
  176. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/luau-CXu1NL6O.js +0 -0
  177. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/make-CHLpvVh8.js +0 -0
  178. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  179. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/marko-CPi9NSCl.js +0 -0
  180. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  181. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  182. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  183. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  184. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  185. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/matlab-D7o27uSR.js +0 -0
  186. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mdc-DUICxH0z.js +0 -0
  187. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  188. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  189. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  190. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/min-light-CTRr51gU.js +0 -0
  191. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  192. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mojo-1DNp92w6.js +0 -0
  193. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  194. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/move-Bu9oaDYs.js +0 -0
  195. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  196. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  197. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nginx-DknmC5AR.js +0 -0
  198. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  199. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nim-CVrawwO9.js +0 -0
  200. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nix-BbRYJGeE.js +0 -0
  201. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nord-Ddv68eIx.js +0 -0
  202. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nushell-C-sUppwS.js +0 -0
  203. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  204. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  205. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  206. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  207. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  208. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  209. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/perl-C0TMdlhV.js +0 -0
  210. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/php-CDn_0X-4.js +0 -0
  211. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  212. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  213. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  214. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/po-BTJTHyun.js +0 -0
  215. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  216. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/polar-C0HS_06l.js +0 -0
  217. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/postcss-CXtECtnM.js +0 -0
  218. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  219. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  220. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  221. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  222. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/proto-DyJlTyXw.js +0 -0
  223. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/pug-CGlum2m_.js +0 -0
  224. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/puppet-BMWR74SV.js +0 -0
  225. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/purescript-CklMAg4u.js +0 -0
  226. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/python-B6aJPvgy.js +0 -0
  227. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/qml-3beO22l8.js +0 -0
  228. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  229. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/qss-IeuSbFQv.js +0 -0
  230. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/r-DiinP2Uv.js +0 -0
  231. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/racket-BqYA7rlc.js +0 -0
  232. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/raku-DXvB9xmW.js +0 -0
  233. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/razor-WgofotgN.js +0 -0
  234. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/red-bN70gL4F.js +0 -0
  235. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/reg-C-SQnVFl.js +0 -0
  236. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  237. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rel-C3B-1QV4.js +0 -0
  238. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  239. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  240. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  241. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  242. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  243. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rst-B0xPkSld.js +0 -0
  244. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  245. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rust-B1yitclQ.js +0 -0
  246. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sas-cz2c8ADy.js +0 -0
  247. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  248. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/scala-C151Ov-r.js +0 -0
  249. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/scheme-C98Dy4si.js +0 -0
  250. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/scss-OYdSNvt2.js +0 -0
  251. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  252. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  253. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  254. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  255. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  256. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  257. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  258. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  259. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  260. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  261. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  262. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/soy-Brmx7dQM.js +0 -0
  263. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  264. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  265. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sql-BLtJtn59.js +0 -0
  266. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  267. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/stata-BH5u7GGu.js +0 -0
  268. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  269. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  270. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/swift-Dg5xB15N.js +0 -0
  271. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  272. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  273. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  274. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  275. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  276. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  277. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/templ-W15q3VgB.js +0 -0
  278. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/terraform-BETggiCN.js +0 -0
  279. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tex-Cppo0RY3.js +0 -0
  280. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  281. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/toml-vGWfd6FD.js +0 -0
  282. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  283. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  284. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tsx-COt5Ahok.js +0 -0
  285. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/turtle-BsS91CYL.js +0 -0
  286. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/twig-CO9l9SDP.js +0 -0
  287. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  288. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/typespec-Df68jz8_.js +0 -0
  289. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/typst-DHCkPAjA.js +0 -0
  290. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/v-BcVCzyr7.js +0 -0
  291. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vala-CsfeWuGM.js +0 -0
  292. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vb-D17OF-Vu.js +0 -0
  293. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  294. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vesper-DU1UobuO.js +0 -0
  295. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  296. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/viml-CJc9bBzg.js +0 -0
  297. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  298. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  299. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  300. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vue-CCoi5OLL.js +0 -0
  301. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  302. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  303. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  304. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  305. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  306. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  307. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  308. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  309. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wit-5i3qLPDT.js +0 -0
  310. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  311. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  312. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  313. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/yaml-Buea-lGh.js +0 -0
  314. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  315. {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/zig-VOosw3JB.js +0 -0
  316. {srcly-0.1.29 → srcly-0.1.30}/app/static/vite.svg +0 -0
@@ -5,6 +5,7 @@ node_modules/
5
5
 
6
6
  # Data files
7
7
  codebase_mri.json
8
+ .srcly*/
8
9
 
9
10
  # Python
10
11
  __pycache__/
@@ -66,4 +67,4 @@ venv.bak/
66
67
  .python-version
67
68
  # uv.lock is intentionally not ignored as this is an application
68
69
 
69
- .tmp
70
+ .tmp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: srcly
3
- Version: 0.1.29
3
+ Version: 0.1.30
4
4
  Summary: Interactive codebase treemap and metrics viewer.
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: fastapi>=0.121.3
@@ -0,0 +1,361 @@
1
+ import argparse
2
+ import os
3
+ import socket
4
+ import sys
5
+ import threading
6
+ import time
7
+ import webbrowser
8
+ from pathlib import Path
9
+
10
+ import uvicorn
11
+
12
+ from app.services import reporting
13
+
14
+
15
+ def _open_browser_later(url: str, delay: float = 1.0) -> None:
16
+ """
17
+ Open the default web browser after a short delay.
18
+
19
+ This lets the server start first so the page is reachable.
20
+ """
21
+
22
+ def _worker() -> None:
23
+ time.sleep(delay)
24
+ try:
25
+ webbrowser.open(url)
26
+ except Exception:
27
+ # Don't crash the CLI if opening the browser fails (e.g. headless env)
28
+ pass
29
+
30
+ thread = threading.Thread(target=_worker, daemon=True)
31
+ thread.start()
32
+
33
+
34
+ def _find_repo_root(start_path: str) -> str:
35
+ """
36
+ Walk upwards from ``start_path`` to find a Git repository root.
37
+
38
+ Returns the first directory that contains a `.git` directory or file.
39
+ If none is found, the original ``start_path`` is returned.
40
+ """
41
+ current = os.path.abspath(start_path)
42
+
43
+ while True:
44
+ git_path = os.path.join(current, ".git")
45
+ if os.path.exists(git_path):
46
+ return current
47
+
48
+ parent = os.path.dirname(current)
49
+ if parent == current:
50
+ # Reached filesystem root; fall back to the original start path.
51
+ return os.path.abspath(start_path)
52
+
53
+ current = parent
54
+
55
+
56
+ def main(argv: list[str] | None = None) -> None:
57
+ """
58
+ Entry point for the CLI.
59
+
60
+ - With no path argument, uses the enclosing Git repo root as the codebase root.
61
+ - If a path is provided (including "."), uses that as the codebase root.
62
+ - Starts the FastAPI server.
63
+ - Opens the default browser to the app URL.
64
+ """
65
+ argv = list(argv) if argv is not None else sys.argv[1:]
66
+ if argv and argv[0] in {"scan", "report", "hotspots", "explain"}:
67
+ _run_headless(argv)
68
+ return
69
+
70
+ parser = argparse.ArgumentParser(
71
+ prog="srcly",
72
+ description=(
73
+ "Interactive codebase treemap and metrics viewer. "
74
+ "By default, analyzes the enclosing Git repository root."
75
+ ),
76
+ )
77
+ parser.add_argument(
78
+ "path",
79
+ nargs="?",
80
+ help=(
81
+ "Path to the codebase to analyze. "
82
+ "If omitted, the enclosing Git repo root is used. "
83
+ 'Use "." explicitly to analyze the current directory.'
84
+ ),
85
+ )
86
+ parser.add_argument(
87
+ "--host",
88
+ default="127.0.0.1",
89
+ help="Host interface to bind the server to (default: 127.0.0.1).",
90
+ )
91
+ parser.add_argument(
92
+ "--port",
93
+ type=int,
94
+ default=None,
95
+ help="Port to run the server on (default: random free port).",
96
+ )
97
+
98
+ args = parser.parse_args(argv)
99
+
100
+ if args.path is None:
101
+ target_path = _find_repo_root(os.getcwd())
102
+ else:
103
+ target_path = os.path.abspath(args.path)
104
+
105
+ if not os.path.exists(target_path):
106
+ raise SystemExit(f"Path does not exist: {target_path}")
107
+
108
+ # Change working directory so the API defaults to this path.
109
+ os.chdir(target_path)
110
+ print(f"📂 Analyzing codebase at: {target_path}")
111
+
112
+ port = args.port
113
+ if port is None:
114
+ # Find a random free port
115
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
116
+ s.bind(("", 0))
117
+ port = s.getsockname()[1]
118
+
119
+ url = f"http://{args.host}:{port}"
120
+ print(f"🚀 Starting server at {url}")
121
+ print(" Press Ctrl+C to stop.")
122
+
123
+ _open_browser_later(url)
124
+
125
+ uvicorn.run(
126
+ "app.main:app",
127
+ host=args.host,
128
+ port=port,
129
+ reload=False,
130
+ )
131
+
132
+
133
+ def _resolve_target_path(path: str | None) -> Path:
134
+ if path is None:
135
+ target_path = Path(_find_repo_root(os.getcwd()))
136
+ else:
137
+ target_path = Path(path).resolve()
138
+
139
+ if not target_path.exists():
140
+ raise SystemExit(f"Path does not exist: {target_path}")
141
+ return target_path
142
+
143
+
144
+ def _run_headless(argv: list[str]) -> None:
145
+ parser = argparse.ArgumentParser(
146
+ prog="srcly",
147
+ description="Headless Srcly analysis commands for agents and CI.",
148
+ )
149
+ subparsers = parser.add_subparsers(dest="command", required=True)
150
+
151
+ scan_parser = subparsers.add_parser("scan", help="Write the raw analysis tree as JSON.")
152
+ scan_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
153
+ scan_parser.add_argument("--out", default="codebase_mri.json", help="Output JSON file.")
154
+ scan_parser.add_argument("--refresh", action="store_true", help="Ignore cache when cache support is available.")
155
+ scan_parser.add_argument("--format", choices=["json", "jsonl"], default="json", help="Output format. JSONL is reserved.")
156
+ scan_parser.add_argument(
157
+ "--include-source",
158
+ choices=["false", "snippets", "none"],
159
+ default="none",
160
+ help="Reserved source inclusion mode. Full source is not emitted.",
161
+ )
162
+
163
+ report_parser = subparsers.add_parser("report", help="Write agent-ready report artifacts.")
164
+ report_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
165
+ report_parser.add_argument("--out", default=".srcly", help="Output artifact directory.")
166
+ report_parser.add_argument(
167
+ "--stdout",
168
+ action="store_true",
169
+ help="Emit one compact bundled JSON report to stdout instead of writing artifact files.",
170
+ )
171
+ report_parser.add_argument("--refresh", action="store_true", help="Ignore cache when cache support is available.")
172
+ report_parser.add_argument("--limit", type=int, default=50, help="Maximum ranked findings to emit.")
173
+ report_parser.add_argument(
174
+ "--profile",
175
+ choices=["general", "frontend", "backend", "typescript", "python", "docs"],
176
+ default="general",
177
+ help="Scoring profile.",
178
+ )
179
+ report_parser.add_argument(
180
+ "--format",
181
+ choices=["markdown", "json", "both"],
182
+ default="both",
183
+ help="Artifact formats to write.",
184
+ )
185
+ report_parser.add_argument(
186
+ "--fail-on",
187
+ choices=["high", "medium", "none"],
188
+ default="none",
189
+ help="Exit non-zero if findings at this priority or higher are present.",
190
+ )
191
+ report_parser.add_argument("--tree-depth", type=int, default=3, help="Maximum depth in tree.summary.json.")
192
+ report_parser.add_argument("--tree-top", type=int, default=6, help="Maximum children per tree level.")
193
+ report_parser.add_argument("--include-tree", action="store_true", help="Also write/include the full raw tree.json artifact.")
194
+ report_parser.add_argument(
195
+ "--include-dependencies",
196
+ action="store_true",
197
+ help="Also write/include the TS/TSX dependency graph artifact.",
198
+ )
199
+
200
+ hotspots_parser = subparsers.add_parser("hotspots", help="Print ranked hotspots.")
201
+ hotspots_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
202
+ hotspots_parser.add_argument("--metric", action="append", default=None, help="Metric to score. May be repeated.")
203
+ hotspots_parser.add_argument("--limit", type=int, default=25, help="Maximum hotspots to print.")
204
+ hotspots_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
205
+
206
+ explain_parser = subparsers.add_parser("explain", help="Print a focused report for one file or subtree.")
207
+ explain_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
208
+ explain_parser.add_argument("--file", required=True, help="File path to explain.")
209
+ explain_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
210
+
211
+ args = parser.parse_args(argv)
212
+
213
+ if args.command == "scan":
214
+ if args.format != "json":
215
+ raise SystemExit("--format jsonl is reserved and not implemented yet.")
216
+ if args.include_source not in {"none", "false"}:
217
+ raise SystemExit("--include-source snippets is reserved and not implemented yet.")
218
+ target_path = _resolve_target_path(args.path)
219
+ out_path = Path("-") if args.out == "-" else Path(args.out).resolve()
220
+ reporting.write_scan(target_path, out_path, refresh=args.refresh)
221
+ if str(args.out) != "-":
222
+ print(f"Wrote Srcly tree JSON to {out_path}")
223
+ return
224
+
225
+ if args.command == "report":
226
+ target_path = _resolve_target_path(args.path)
227
+ if args.stdout:
228
+ payload = reporting.build_report_payload(
229
+ target_path,
230
+ profile=args.profile,
231
+ limit=args.limit,
232
+ refresh=args.refresh,
233
+ tree_depth=args.tree_depth,
234
+ tree_top=args.tree_top,
235
+ include_tree=args.include_tree,
236
+ include_dependencies=args.include_dependencies,
237
+ )
238
+ print(reporting._json_dumps(payload))
239
+ else:
240
+ out_dir = Path(args.out).resolve()
241
+ output_format = "both" if args.fail_on != "none" and args.format == "markdown" else args.format
242
+ written = reporting.write_report(
243
+ target_path,
244
+ out_dir,
245
+ profile=args.profile,
246
+ limit=args.limit,
247
+ output_format=output_format,
248
+ refresh=args.refresh,
249
+ tree_depth=args.tree_depth,
250
+ tree_top=args.tree_top,
251
+ include_tree=args.include_tree,
252
+ include_dependencies=args.include_dependencies,
253
+ )
254
+ print(f"Wrote Srcly report artifacts to {out_dir}")
255
+ for path in written.values():
256
+ print(f"- {path}")
257
+ if args.fail_on != "none":
258
+ if args.stdout:
259
+ _enforce_fail_on_payload(payload["findings"], args.fail_on)
260
+ else:
261
+ _enforce_fail_on(out_dir / "findings.json", args.fail_on)
262
+ return
263
+
264
+ if args.command == "hotspots":
265
+ target_path = _resolve_target_path(args.path)
266
+ tree = reporting.scan_tree(target_path)
267
+ profile = _profile_from_metrics(args.metric)
268
+ ranked = reporting.rank_nodes(tree, profile=profile)[: args.limit]
269
+ if args.format == "json":
270
+ print(reporting._json_dumps({"rankings": ranked}))
271
+ else:
272
+ for item in ranked:
273
+ location = item["path"]
274
+ if item.get("start_line") and item.get("end_line"):
275
+ location = f"{location}:{item['start_line']}-{item['end_line']}"
276
+ print(f"{item['rank']}. score={item['score']} {location} - {item['reason']}")
277
+ return
278
+
279
+ if args.command == "explain":
280
+ target_path = _resolve_target_path(args.path)
281
+ tree = reporting.scan_tree(target_path)
282
+ payload = _explain_file(tree, Path(args.file).resolve(), target_path)
283
+ if args.format == "json":
284
+ print(reporting._json_dumps(payload))
285
+ else:
286
+ print(_render_explain_markdown(payload))
287
+ return
288
+
289
+
290
+ def _profile_from_metrics(metrics: list[str] | None) -> str:
291
+ if not metrics:
292
+ return "general"
293
+ weights = {metric: 1.0 for metric in metrics}
294
+ reporting.PROFILE_WEIGHTS["_custom"] = weights
295
+ return "_custom"
296
+
297
+
298
+ def _enforce_fail_on(findings_path: Path, fail_on: str) -> None:
299
+ import json
300
+
301
+ data = json.loads(findings_path.read_text(encoding="utf-8"))
302
+ _enforce_fail_on_payload(data, fail_on)
303
+
304
+
305
+ def _enforce_fail_on_payload(data: dict, fail_on: str) -> None:
306
+ priorities = {"high": 2, "medium": 1, "low": 0}
307
+ threshold = priorities[fail_on]
308
+ offenders = [
309
+ finding
310
+ for finding in data.get("findings", [])
311
+ if priorities.get(finding.get("priority", "low"), 0) >= threshold
312
+ ]
313
+ if offenders:
314
+ raise SystemExit(f"Srcly found {len(offenders)} findings at priority {fail_on} or higher.")
315
+
316
+
317
+ def _explain_file(tree, file_path: Path, root_path: Path) -> dict:
318
+ target = None
319
+ for node in reporting.iter_nodes(tree):
320
+ try:
321
+ node_path = Path(node.path).resolve()
322
+ except Exception:
323
+ continue
324
+ if node_path == file_path:
325
+ target = node
326
+ break
327
+
328
+ if target is None:
329
+ raise SystemExit(f"File not found in analysis tree: {file_path}")
330
+
331
+ ranked = reporting.rank_nodes(target)
332
+ return {
333
+ "path": str(file_path),
334
+ "summary": reporting.summarize_tree(target, ranked, root_path=root_path, max_depth=3, top_children=20),
335
+ "hotspots": ranked[:10],
336
+ }
337
+
338
+
339
+ def _render_explain_markdown(payload: dict) -> str:
340
+ lines = [
341
+ f"# Srcly Explain: {payload['path']}",
342
+ "",
343
+ "## Tree",
344
+ "",
345
+ "```text",
346
+ *reporting.render_tree_lines(payload["summary"]),
347
+ "```",
348
+ "",
349
+ "## Hotspots",
350
+ "",
351
+ ]
352
+ for item in payload["hotspots"]:
353
+ location = item["path"]
354
+ if item.get("start_line") and item.get("end_line"):
355
+ location = f"{location}:{item['start_line']}-{item['end_line']}"
356
+ lines.append(f"- score={item['score']} `{location}`: {item['reason']}")
357
+ return "\n".join(lines) + "\n"
358
+
359
+
360
+ if __name__ == "__main__":
361
+ main()
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import lizard
3
3
  import multiprocessing
4
+ import sys
4
5
  import time
5
6
  from pathlib import Path
6
7
 
@@ -369,6 +370,7 @@ def aggregate_metrics(node: Node) -> Metrics:
369
370
  total_type_interface_count = 0
370
371
  total_export_count = 0
371
372
  total_md_data_url_count = 0
373
+ total_python_import_count = 0
372
374
 
373
375
  # For average function length, we need total function loc and total functions (already have total_funcs)
374
376
  # But we need to sum function locs from children.
@@ -390,11 +392,7 @@ def aggregate_metrics(node: Node) -> Metrics:
390
392
  total_type_interface_count += child_metrics.ts_type_interface_count
391
393
  total_export_count += child_metrics.ts_export_count
392
394
  total_md_data_url_count += child_metrics.md_data_url_count
393
- if hasattr(child_metrics, 'python_import_count'):
394
- # Metric model has it, but child_metrics is a Metrics object so it should have it
395
- total_python_import_count = getattr(child_metrics, 'python_import_count', 0)
396
- # Should we add a local var for this? Yes
397
- pass
395
+ total_python_import_count += child_metrics.python_import_count
398
396
 
399
397
 
400
398
  # Reconstruct total function loc from average * count
@@ -510,7 +508,7 @@ def _run_file_analyses_with_hard_timeouts(
510
508
 
511
509
  # Log every file *before* it is processed so we can identify the
512
510
  # last-started file if analysis hangs or crashes.
513
- print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", flush=True)
511
+ print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", file=sys.stderr, flush=True)
514
512
 
515
513
  recv_conn, send_conn = ctx.Pipe(duplex=False)
516
514
  proc = ctx.Process(
@@ -544,6 +542,7 @@ def _run_file_analyses_with_hard_timeouts(
544
542
  completed_count += 1
545
543
  print(
546
544
  f"❌ [{completed_count}/{total_count}] Timeout analyzing {file_path} after {elapsed:.2f}s (terminated)",
545
+ file=sys.stderr,
547
546
  flush=True,
548
547
  )
549
548
  try:
@@ -582,11 +581,12 @@ def _run_file_analyses_with_hard_timeouts(
582
581
  if isinstance(result, dict) and "error" in result:
583
582
  print(
584
583
  f"❌ [{completed_count}/{total_count}] Error analyzing {file_path}: {result.get('error')}",
584
+ file=sys.stderr,
585
585
  flush=True,
586
586
  )
587
587
  else:
588
588
  if _should_log_file_progress(completed_count, total_count):
589
- print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", flush=True)
589
+ print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", file=sys.stderr, flush=True)
590
590
  results.append(result)
591
591
 
592
592
  active = still_active
@@ -599,7 +599,7 @@ def _run_file_analyses_with_hard_timeouts(
599
599
 
600
600
 
601
601
  def scan_codebase(root_path: Path) -> Node:
602
- print(f"🔍 Scanning: {root_path}", flush=True)
602
+ print(f"🔍 Scanning: {root_path}", file=sys.stderr, flush=True)
603
603
 
604
604
  # Load .gitignore spec (repo-wide, with nested .gitignore support)
605
605
  ignore_root, gitignore_spec = _load_gitignore_spec(root_path)
@@ -614,7 +614,7 @@ def scan_codebase(root_path: Path) -> Node:
614
614
  # We must modify dirs in-place to prune traversal
615
615
  pruned_dirs: list[str] = []
616
616
  for d in dirs:
617
- if d in IGNORE_DIRS:
617
+ if d in IGNORE_DIRS or d.startswith(".srcly"):
618
618
  continue
619
619
  dir_path = root_dir_path / d
620
620
  if _is_gitignored(dir_path, ignore_root, gitignore_spec):
@@ -643,6 +643,7 @@ def scan_codebase(root_path: Path) -> Node:
643
643
 
644
644
  print(
645
645
  f"📂 Analyzing {len(files_to_scan)} source files... (workers={MAX_ANALYSIS_WORKERS}, timeout={PER_FILE_ANALYSIS_TIMEOUT_SECONDS}s)",
646
+ file=sys.stderr,
646
647
  flush=True,
647
648
  )
648
649
 
@@ -661,7 +662,7 @@ def scan_codebase(root_path: Path) -> Node:
661
662
  # ``filename`` attribute), skip it instead of crashing the whole scan.
662
663
  filename = getattr(file_info, "filename", None)
663
664
  if not filename:
664
- print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", flush=True)
665
+ print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", file=sys.stderr, flush=True)
665
666
  continue
666
667
 
667
668
  path_obj = Path(filename)
@@ -76,6 +76,7 @@ class _Usage:
76
76
  start_col: int # 0-based
77
77
  end_col: int # exclusive
78
78
  resolved: _Def | None
79
+ containing_scope_id: str # Closest scope boundary that contains this usage (includes JSX scopes)
79
80
  containing_fn_scope_id: str # Added to track where this usage occurs
80
81
 
81
82
 
@@ -536,6 +537,39 @@ class FocusAnalyzer:
536
537
  if attr_name:
537
538
  return attr_name.text.decode("utf-8", errors="ignore")
538
539
 
540
+ # TSX/JSX scope naming: name scopes after their JSX tag so they can be explored as a tree.
541
+ if self.is_tsx and node.type in {"jsx_element", "jsx_self_closing_element", "jsx_fragment"}:
542
+ if node.type == "jsx_fragment":
543
+ return "<>"
544
+
545
+ opening = node.child_by_field_name("opening_element")
546
+ if opening is None:
547
+ for c in node.children:
548
+ if c.type in {"jsx_opening_element", "jsx_self_closing_element"}:
549
+ opening = c
550
+ break
551
+
552
+ name_node = opening.child_by_field_name("name") if opening is not None else None
553
+ if name_node is None and opening is not None:
554
+ # Fallback: first plausible identifier-ish node under the opening element.
555
+ for c in opening.children:
556
+ if c.type in {
557
+ "jsx_identifier",
558
+ "identifier",
559
+ "property_identifier",
560
+ "member_expression",
561
+ "jsx_member_expression",
562
+ "nested_identifier",
563
+ }:
564
+ name_node = c
565
+ break
566
+
567
+ if name_node is not None:
568
+ raw = name_node.text.decode("utf-8", errors="ignore").strip()
569
+ if raw:
570
+ return f"<{raw}>"
571
+ return "<jsx>"
572
+
539
573
  return None
540
574
 
541
575
 
@@ -553,6 +587,12 @@ class FocusAnalyzer:
553
587
  return n.type == "catch_clause"
554
588
 
555
589
  def _is_scope_boundary(self, n: Node) -> bool:
590
+ # TSX/JSX: every JSX element/fragment becomes its own nested "scope" layer.
591
+ # This is intentionally verbose so the client can expand/collapse JSX structure
592
+ # to guide refactors.
593
+ if self.is_tsx and n.type in {"jsx_element", "jsx_self_closing_element", "jsx_fragment"}:
594
+ return True
595
+
556
596
  if n.type in {"block", "statement_block"}:
557
597
  parent = n.parent
558
598
  if parent and (self._is_function_node(parent) or self._is_catch_clause(parent)):
@@ -563,6 +603,8 @@ class FocusAnalyzer:
563
603
  return self._is_function_node(n)
564
604
 
565
605
  def _scope_type(self, n: Node) -> str:
606
+ if self.is_tsx and n.type in {"jsx_element", "jsx_self_closing_element", "jsx_fragment"}:
607
+ return "jsx"
566
608
  if self._is_function_node(n):
567
609
  return "function"
568
610
  if self._is_catch_clause(n):
@@ -928,6 +970,7 @@ class FocusAnalyzer:
928
970
  start_col=n.start_point.column,
929
971
  end_col=n.end_point.column,
930
972
  resolved=resolved,
973
+ containing_scope_id=next_scope.id if next_scope else self.global_scope.id,
931
974
  containing_fn_scope_id=usage_fn_scope.id if usage_fn_scope else self.global_scope.id,
932
975
  )
933
976
  )
@@ -1124,26 +1167,20 @@ def compute_scope_graph(
1124
1167
  isCaptured=False
1125
1168
  ))
1126
1169
 
1127
- captured_map: Dict[str, _Def] = {}
1128
-
1129
- descendant_ids = {s.id}
1130
- queue = [s.id]
1131
- while queue:
1132
- curr_id = queue.pop(0)
1133
- curr_s = analyzer.scopes[curr_id]
1134
- descendant_ids.add(curr_id)
1135
- queue.extend(curr_s.children_ids)
1136
-
1170
+ captured_map: Dict[tuple, _Def] = {}
1171
+
1137
1172
  ancestor_ids = set(analyzer._ancestor_chain(s.id))
1138
1173
  if s.id in ancestor_ids:
1139
1174
  ancestor_ids.remove(s.id)
1140
1175
 
1141
1176
  for u in analyzer.usages:
1142
- if u.containing_fn_scope_id in descendant_ids:
1177
+ # Attribute captured variables to the closest scope boundary.
1178
+ # This includes JSX scopes so each TSX element can show its own captures.
1179
+ if u.containing_scope_id == s.id:
1143
1180
  d = u.resolved
1144
1181
  if d and d.scope_id in ancestor_ids and d.name not in _BUILTINS:
1145
1182
  if d.kind != "import" and d.scope_type != "global":
1146
- captured_map[d.name] = d
1183
+ captured_map[(d.name, d.def_line)] = d
1147
1184
 
1148
1185
  captured_nodes: List[SymbolNode] = []
1149
1186
  for d in captured_map.values():
@@ -1163,8 +1200,8 @@ def compute_scope_graph(
1163
1200
  if node:
1164
1201
  child_nodes.append(node)
1165
1202
 
1166
- # Prune empty nodes
1167
- if not declared_nodes and not captured_nodes and not child_nodes:
1203
+ # Prune empty nodes (but never prune JSX scopes; we want the full JSX tree).
1204
+ if s.type != "jsx" and not declared_nodes and not captured_nodes and not child_nodes:
1168
1205
  # But keep the root of our request?
1169
1206
  # The function `compute_scope_graph` returns `ScopeGraph(root=...)`.
1170
1207
  # If the top-level scope itself is empty, we must return *something* or handle it.