srcly 0.1.27__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.27 → srcly-0.1.30}/.gitignore +2 -1
  2. {srcly-0.1.27 → srcly-0.1.30}/PKG-INFO +1 -1
  3. {srcly-0.1.27 → srcly-0.1.30}/app/models.py +31 -0
  4. {srcly-0.1.27 → srcly-0.1.30}/app/routers/analysis.py +25 -1
  5. srcly-0.1.30/app/run.py +361 -0
  6. {srcly-0.1.27 → srcly-0.1.30}/app/services/analysis.py +11 -10
  7. {srcly-0.1.27 → srcly-0.1.30}/app/services/focus_overlay.py +354 -206
  8. srcly-0.1.30/app/services/reporting.py +653 -0
  9. {srcly-0.1.27 → srcly-0.1.30}/app/services/typescript/typescript_analysis.py +1 -1
  10. srcly-0.1.30/app/static/assets/index-Bn6hDYiR.js +66 -0
  11. srcly-0.1.30/app/static/assets/index-DK1czI50.css +1 -0
  12. {srcly-0.1.27 → srcly-0.1.30}/app/static/index.html +2 -2
  13. {srcly-0.1.27 → srcly-0.1.30}/pyproject.toml +1 -1
  14. srcly-0.1.27/app/run.py +0 -125
  15. srcly-0.1.27/app/static/assets/index-BD7BxeQJ.js +0 -66
  16. srcly-0.1.27/app/static/assets/index-BDq2TMkJ.css +0 -1
  17. {srcly-0.1.27 → srcly-0.1.30}/README.md +0 -0
  18. {srcly-0.1.27 → srcly-0.1.30}/app/.gitignore +0 -0
  19. {srcly-0.1.27 → srcly-0.1.30}/app/__init__.py +0 -0
  20. {srcly-0.1.27 → srcly-0.1.30}/app/config.py +0 -0
  21. {srcly-0.1.27 → srcly-0.1.30}/app/main.py +0 -0
  22. {srcly-0.1.27 → srcly-0.1.30}/app/routers/files.py +0 -0
  23. {srcly-0.1.27 → srcly-0.1.30}/app/services/analysis_types.py +0 -0
  24. {srcly-0.1.27 → srcly-0.1.30}/app/services/cache.py +0 -0
  25. {srcly-0.1.27 → srcly-0.1.30}/app/services/css/css_analysis.py +0 -0
  26. {srcly-0.1.27 → srcly-0.1.30}/app/services/data_flow_analysis.py +0 -0
  27. {srcly-0.1.27 → srcly-0.1.30}/app/services/ipynb/ipynb_analysis.py +0 -0
  28. {srcly-0.1.27 → srcly-0.1.30}/app/services/markdown/markdown_analysis.py +0 -0
  29. {srcly-0.1.27 → srcly-0.1.30}/app/services/python/python_analysis.py +0 -0
  30. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/abap-BdImnpbu.js +0 -0
  31. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  32. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ada-bCR0ucgS.js +0 -0
  33. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  34. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  35. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  36. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/apache-Pmp26Uib.js +0 -0
  37. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/apex-DhZLUxFE.js +0 -0
  38. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/apl-dKokRX4l.js +0 -0
  39. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  40. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ara-BRHolxvo.js +0 -0
  41. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  42. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  43. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/astro-CbQHKStN.js +0 -0
  44. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  45. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/awk-DMzUqQB5.js +0 -0
  46. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  47. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  48. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/bat-BkioyH1T.js +0 -0
  49. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  50. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/berry-D08WgyRC.js +0 -0
  51. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  52. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  53. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/blade-DVc8C-J4.js +0 -0
  54. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  55. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/c-BIGW1oBm.js +0 -0
  56. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  57. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  58. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  59. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  60. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  61. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  62. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/clarity-D53aC0YG.js +0 -0
  63. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/clojure-P80f7IUj.js +0 -0
  64. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  65. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cobol-nwyudZeR.js +0 -0
  66. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  67. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  68. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  69. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  70. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/coq-DkFqJrB1.js +0 -0
  71. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cpp-CofmeUqb.js +0 -0
  72. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  73. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  74. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/css-DPfMkruS.js +0 -0
  75. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/csv-fuZLfV_i.js +0 -0
  76. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cue-D82EKSYY.js +0 -0
  77. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/cypher-COkxafJQ.js +0 -0
  78. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/d-85-TOEBH.js +0 -0
  79. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  80. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dart-CF10PKvl.js +0 -0
  81. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dax-CEL-wOlO.js +0 -0
  82. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  83. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/diff-D97Zzqfu.js +0 -0
  84. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/docker-BcOcwvcX.js +0 -0
  85. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  86. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  87. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  88. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  89. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/edge-BkV0erSs.js +0 -0
  90. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/elixir-CDX3lj18.js +0 -0
  91. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/elm-DbKCFpqz.js +0 -0
  92. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  93. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/erb-BOJIQeun.js +0 -0
  94. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  95. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  96. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  97. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/fennel-BYunw83y.js +0 -0
  98. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/fish-BvzEVeQv.js +0 -0
  99. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  100. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  101. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  102. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  103. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  104. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  105. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  106. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/genie-D0YGMca9.js +0 -0
  107. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  108. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  109. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  110. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  111. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  112. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  113. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  114. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  115. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  116. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  117. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gleam-BspZqrRM.js +0 -0
  118. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  119. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  120. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/glsl-DplSGwfg.js +0 -0
  121. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  122. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/go-Dn2_MT6a.js +0 -0
  123. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  124. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  125. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  126. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  127. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  128. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  129. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  130. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  131. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hack-CaT9iCJl.js +0 -0
  132. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/haml-B8DHNrY2.js +0 -0
  133. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  134. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  135. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  136. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  137. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hjson-D5-asLiD.js +0 -0
  138. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  139. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/houston-DnULxvSX.js +0 -0
  140. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/html-GMplVEZG.js +0 -0
  141. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  142. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/http-jrhK8wxY.js +0 -0
  143. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hurl-irOxFIW8.js +0 -0
  144. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  145. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/hy-DFXneXwc.js +0 -0
  146. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/imba-DGztddWO.js +0 -0
  147. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ini-BEwlwnbL.js +0 -0
  148. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/java-CylS5w8V.js +0 -0
  149. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  150. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  151. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jison-wvAkD_A8.js +0 -0
  152. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/json-Cp-IABpG.js +0 -0
  153. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/json5-C9tS-k6U.js +0 -0
  154. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  155. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  156. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  157. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  158. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  159. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/julia-C8NyazO9.js +0 -0
  160. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  161. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  162. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  163. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/kdl-DV7GczEv.js +0 -0
  164. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  165. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  166. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  167. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/latex-BUKiar2Z.js +0 -0
  168. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/lean-DP1Csr6i.js +0 -0
  169. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/less-B1dDrJ26.js +0 -0
  170. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  171. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/liquid-DYVedYrR.js +0 -0
  172. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/llvm-BtvRca6l.js +0 -0
  173. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/log-2UxHyX5q.js +0 -0
  174. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/logo-BtOb2qkB.js +0 -0
  175. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/lua-BbnMAYS6.js +0 -0
  176. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/luau-CXu1NL6O.js +0 -0
  177. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/make-CHLpvVh8.js +0 -0
  178. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  179. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/marko-CPi9NSCl.js +0 -0
  180. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  181. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  182. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  183. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  184. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  185. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/matlab-D7o27uSR.js +0 -0
  186. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/mdc-DUICxH0z.js +0 -0
  187. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  188. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  189. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  190. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/min-light-CTRr51gU.js +0 -0
  191. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  192. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/mojo-1DNp92w6.js +0 -0
  193. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  194. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/move-Bu9oaDYs.js +0 -0
  195. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  196. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  197. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/nginx-DknmC5AR.js +0 -0
  198. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  199. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/nim-CVrawwO9.js +0 -0
  200. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/nix-BbRYJGeE.js +0 -0
  201. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/nord-Ddv68eIx.js +0 -0
  202. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/nushell-C-sUppwS.js +0 -0
  203. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  204. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  205. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  206. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  207. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  208. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  209. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/perl-C0TMdlhV.js +0 -0
  210. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/php-CDn_0X-4.js +0 -0
  211. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  212. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  213. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  214. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/po-BTJTHyun.js +0 -0
  215. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  216. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/polar-C0HS_06l.js +0 -0
  217. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/postcss-CXtECtnM.js +0 -0
  218. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  219. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  220. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  221. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  222. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/proto-DyJlTyXw.js +0 -0
  223. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/pug-CGlum2m_.js +0 -0
  224. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/puppet-BMWR74SV.js +0 -0
  225. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/purescript-CklMAg4u.js +0 -0
  226. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/python-B6aJPvgy.js +0 -0
  227. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/qml-3beO22l8.js +0 -0
  228. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  229. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/qss-IeuSbFQv.js +0 -0
  230. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/r-DiinP2Uv.js +0 -0
  231. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/racket-BqYA7rlc.js +0 -0
  232. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/raku-DXvB9xmW.js +0 -0
  233. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/razor-WgofotgN.js +0 -0
  234. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/red-bN70gL4F.js +0 -0
  235. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/reg-C-SQnVFl.js +0 -0
  236. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  237. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rel-C3B-1QV4.js +0 -0
  238. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  239. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  240. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  241. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  242. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  243. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rst-B0xPkSld.js +0 -0
  244. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  245. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/rust-B1yitclQ.js +0 -0
  246. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/sas-cz2c8ADy.js +0 -0
  247. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  248. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/scala-C151Ov-r.js +0 -0
  249. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/scheme-C98Dy4si.js +0 -0
  250. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/scss-OYdSNvt2.js +0 -0
  251. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  252. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  253. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  254. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  255. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  256. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  257. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  258. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  259. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  260. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  261. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  262. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/soy-Brmx7dQM.js +0 -0
  263. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  264. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  265. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/sql-BLtJtn59.js +0 -0
  266. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  267. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/stata-BH5u7GGu.js +0 -0
  268. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  269. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  270. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/swift-Dg5xB15N.js +0 -0
  271. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  272. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  273. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  274. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  275. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  276. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  277. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/templ-W15q3VgB.js +0 -0
  278. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/terraform-BETggiCN.js +0 -0
  279. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/tex-Cppo0RY3.js +0 -0
  280. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  281. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/toml-vGWfd6FD.js +0 -0
  282. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  283. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  284. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/tsx-COt5Ahok.js +0 -0
  285. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/turtle-BsS91CYL.js +0 -0
  286. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/twig-CO9l9SDP.js +0 -0
  287. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  288. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/typespec-Df68jz8_.js +0 -0
  289. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/typst-DHCkPAjA.js +0 -0
  290. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/v-BcVCzyr7.js +0 -0
  291. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vala-CsfeWuGM.js +0 -0
  292. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vb-D17OF-Vu.js +0 -0
  293. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  294. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vesper-DU1UobuO.js +0 -0
  295. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  296. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/viml-CJc9bBzg.js +0 -0
  297. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  298. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  299. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  300. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vue-CCoi5OLL.js +0 -0
  301. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  302. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  303. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  304. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  305. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  306. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  307. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  308. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  309. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wit-5i3qLPDT.js +0 -0
  310. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  311. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  312. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  313. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/yaml-Buea-lGh.js +0 -0
  314. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  315. {srcly-0.1.27 → srcly-0.1.30}/app/static/assets/zig-VOosw3JB.js +0 -0
  316. {srcly-0.1.27 → 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.27
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
@@ -99,3 +99,34 @@ class OverlayToken(BaseModel):
99
99
 
100
100
  class FocusOverlayResponse(BaseModel):
101
101
  tokens: List[OverlayToken] = Field(default_factory=list)
102
+
103
+
104
+ # --- Scope Graph Models ---
105
+
106
+ class ScopeGraphRequest(BaseModel):
107
+ path: str
108
+ focusStartLine: int
109
+ focusEndLine: int
110
+
111
+ class SymbolNode(BaseModel):
112
+ id: str # e.g. "var:expanded:L12:C5" or just a unique ID
113
+ name: str
114
+ kind: str # "var", "func", "param", "class", "jsx_comp", etc.
115
+ declLine: int
116
+ isCaptured: bool = False
117
+ isDeclaredHere: bool = False
118
+
119
+ class ScopeNode(BaseModel):
120
+ id: str
121
+ kind: str # "function", "jsx", "block", "root"
122
+ name: str | None = None
123
+ startLine: int
124
+ endLine: int
125
+ children: List["ScopeNode"] = Field(default_factory=list)
126
+ declared: List[SymbolNode] = Field(default_factory=list)
127
+ captured: List[SymbolNode] = Field(default_factory=list)
128
+
129
+ class ScopeGraph(BaseModel):
130
+ root: ScopeNode
131
+ # We might want edges later, but for MVP, nested scopes + lists are enough.
132
+
@@ -8,7 +8,7 @@ from app.models import Node, DependencyGraph, DependencyNode, DependencyEdge
8
8
  from app.services import analysis, cache
9
9
  from app.services.typescript import typescript_analysis
10
10
  from app.config import IGNORE_DIRS
11
- from app.models import FocusOverlayRequest, FocusOverlayResponse
11
+ from app.models import FocusOverlayRequest, FocusOverlayResponse, ScopeGraphRequest, ScopeGraph
12
12
 
13
13
  router = APIRouter(prefix="/api/analysis", tags=["analysis"])
14
14
 
@@ -648,3 +648,27 @@ async def get_focus_overlay(req: FocusOverlayRequest):
648
648
  except Exception as e:
649
649
  print(f"Error computing focus overlay for {req.path}: {e}")
650
650
  raise HTTPException(status_code=500, detail=str(e))
651
+
652
+
653
+ @router.post("/focus/scope-graph", response_model=ScopeGraph)
654
+ async def get_scope_graph(req: ScopeGraphRequest):
655
+ """
656
+ Return the nested scope graph for a focused region.
657
+ """
658
+ target_path = Path(req.path)
659
+ if not target_path.exists() or not target_path.is_file():
660
+ raise HTTPException(status_code=404, detail="File not found")
661
+
662
+ from app.services.focus_overlay import compute_scope_graph
663
+
664
+ try:
665
+ return compute_scope_graph(
666
+ file_path=str(target_path),
667
+ focus_start_line=req.focusStartLine,
668
+ focus_end_line=req.focusEndLine,
669
+ )
670
+ except HTTPException:
671
+ raise
672
+ except Exception as e:
673
+ print(f"Error computing scope graph for {req.path}: {e}")
674
+ raise HTTPException(status_code=500, detail=str(e))
@@ -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)