srcly 0.1.29__tar.gz → 0.1.31__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.31}/.gitignore +2 -1
  2. {srcly-0.1.29 → srcly-0.1.31}/PKG-INFO +1 -1
  3. srcly-0.1.31/app/run.py +412 -0
  4. {srcly-0.1.29 → srcly-0.1.31}/app/services/analysis.py +33 -24
  5. {srcly-0.1.29 → srcly-0.1.31}/app/services/focus_overlay.py +51 -14
  6. srcly-0.1.31/app/services/reporting.py +804 -0
  7. {srcly-0.1.29 → srcly-0.1.31}/app/services/typescript/typescript_analysis.py +1 -1
  8. srcly-0.1.31/app/static/assets/index--Q3ju03c.js +66 -0
  9. srcly-0.1.31/app/static/assets/index-DOSG0br7.css +1 -0
  10. {srcly-0.1.29 → srcly-0.1.31}/app/static/index.html +2 -2
  11. {srcly-0.1.29 → srcly-0.1.31}/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.31}/README.md +0 -0
  16. {srcly-0.1.29 → srcly-0.1.31}/app/.gitignore +0 -0
  17. {srcly-0.1.29 → srcly-0.1.31}/app/__init__.py +0 -0
  18. {srcly-0.1.29 → srcly-0.1.31}/app/config.py +0 -0
  19. {srcly-0.1.29 → srcly-0.1.31}/app/main.py +0 -0
  20. {srcly-0.1.29 → srcly-0.1.31}/app/models.py +0 -0
  21. {srcly-0.1.29 → srcly-0.1.31}/app/routers/analysis.py +0 -0
  22. {srcly-0.1.29 → srcly-0.1.31}/app/routers/files.py +0 -0
  23. {srcly-0.1.29 → srcly-0.1.31}/app/services/analysis_types.py +0 -0
  24. {srcly-0.1.29 → srcly-0.1.31}/app/services/cache.py +0 -0
  25. {srcly-0.1.29 → srcly-0.1.31}/app/services/css/css_analysis.py +0 -0
  26. {srcly-0.1.29 → srcly-0.1.31}/app/services/data_flow_analysis.py +0 -0
  27. {srcly-0.1.29 → srcly-0.1.31}/app/services/ipynb/ipynb_analysis.py +0 -0
  28. {srcly-0.1.29 → srcly-0.1.31}/app/services/markdown/markdown_analysis.py +0 -0
  29. {srcly-0.1.29 → srcly-0.1.31}/app/services/python/python_analysis.py +0 -0
  30. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/abap-BdImnpbu.js +0 -0
  31. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  32. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ada-bCR0ucgS.js +0 -0
  33. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  34. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  35. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  36. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/apache-Pmp26Uib.js +0 -0
  37. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/apex-DhZLUxFE.js +0 -0
  38. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/apl-dKokRX4l.js +0 -0
  39. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  40. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ara-BRHolxvo.js +0 -0
  41. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  42. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  43. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/astro-CbQHKStN.js +0 -0
  44. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  45. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/awk-DMzUqQB5.js +0 -0
  46. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  47. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  48. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/bat-BkioyH1T.js +0 -0
  49. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  50. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/berry-D08WgyRC.js +0 -0
  51. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  52. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  53. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/blade-DVc8C-J4.js +0 -0
  54. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  55. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/c-BIGW1oBm.js +0 -0
  56. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  57. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  58. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  59. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  60. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  61. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  62. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/clarity-D53aC0YG.js +0 -0
  63. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/clojure-P80f7IUj.js +0 -0
  64. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  65. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cobol-nwyudZeR.js +0 -0
  66. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  67. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  68. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  69. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  70. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/coq-DkFqJrB1.js +0 -0
  71. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cpp-CofmeUqb.js +0 -0
  72. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  73. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  74. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/css-DPfMkruS.js +0 -0
  75. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/csv-fuZLfV_i.js +0 -0
  76. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cue-D82EKSYY.js +0 -0
  77. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/cypher-COkxafJQ.js +0 -0
  78. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/d-85-TOEBH.js +0 -0
  79. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  80. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dart-CF10PKvl.js +0 -0
  81. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dax-CEL-wOlO.js +0 -0
  82. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  83. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/diff-D97Zzqfu.js +0 -0
  84. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/docker-BcOcwvcX.js +0 -0
  85. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  86. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  87. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  88. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  89. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/edge-BkV0erSs.js +0 -0
  90. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/elixir-CDX3lj18.js +0 -0
  91. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/elm-DbKCFpqz.js +0 -0
  92. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  93. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/erb-BOJIQeun.js +0 -0
  94. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  95. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  96. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  97. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/fennel-BYunw83y.js +0 -0
  98. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/fish-BvzEVeQv.js +0 -0
  99. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  100. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  101. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  102. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  103. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  104. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  105. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  106. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/genie-D0YGMca9.js +0 -0
  107. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  108. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  109. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  110. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  111. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  112. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  113. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  114. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  115. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  116. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  117. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gleam-BspZqrRM.js +0 -0
  118. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  119. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  120. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/glsl-DplSGwfg.js +0 -0
  121. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  122. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/go-Dn2_MT6a.js +0 -0
  123. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  124. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  125. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  126. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  127. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  128. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  129. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  130. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  131. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hack-CaT9iCJl.js +0 -0
  132. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/haml-B8DHNrY2.js +0 -0
  133. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  134. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  135. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  136. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  137. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hjson-D5-asLiD.js +0 -0
  138. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  139. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/houston-DnULxvSX.js +0 -0
  140. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/html-GMplVEZG.js +0 -0
  141. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  142. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/http-jrhK8wxY.js +0 -0
  143. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hurl-irOxFIW8.js +0 -0
  144. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  145. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/hy-DFXneXwc.js +0 -0
  146. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/imba-DGztddWO.js +0 -0
  147. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ini-BEwlwnbL.js +0 -0
  148. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/java-CylS5w8V.js +0 -0
  149. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  150. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  151. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jison-wvAkD_A8.js +0 -0
  152. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/json-Cp-IABpG.js +0 -0
  153. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/json5-C9tS-k6U.js +0 -0
  154. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  155. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  156. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  157. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  158. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  159. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/julia-C8NyazO9.js +0 -0
  160. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  161. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  162. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  163. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/kdl-DV7GczEv.js +0 -0
  164. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  165. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  166. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  167. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/latex-BUKiar2Z.js +0 -0
  168. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/lean-DP1Csr6i.js +0 -0
  169. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/less-B1dDrJ26.js +0 -0
  170. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  171. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/liquid-DYVedYrR.js +0 -0
  172. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/llvm-BtvRca6l.js +0 -0
  173. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/log-2UxHyX5q.js +0 -0
  174. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/logo-BtOb2qkB.js +0 -0
  175. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/lua-BbnMAYS6.js +0 -0
  176. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/luau-CXu1NL6O.js +0 -0
  177. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/make-CHLpvVh8.js +0 -0
  178. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  179. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/marko-CPi9NSCl.js +0 -0
  180. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  181. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  182. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  183. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  184. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  185. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/matlab-D7o27uSR.js +0 -0
  186. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/mdc-DUICxH0z.js +0 -0
  187. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  188. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  189. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  190. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/min-light-CTRr51gU.js +0 -0
  191. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  192. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/mojo-1DNp92w6.js +0 -0
  193. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  194. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/move-Bu9oaDYs.js +0 -0
  195. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  196. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  197. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/nginx-DknmC5AR.js +0 -0
  198. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  199. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/nim-CVrawwO9.js +0 -0
  200. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/nix-BbRYJGeE.js +0 -0
  201. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/nord-Ddv68eIx.js +0 -0
  202. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/nushell-C-sUppwS.js +0 -0
  203. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  204. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  205. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  206. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  207. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  208. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  209. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/perl-C0TMdlhV.js +0 -0
  210. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/php-CDn_0X-4.js +0 -0
  211. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  212. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  213. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  214. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/po-BTJTHyun.js +0 -0
  215. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  216. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/polar-C0HS_06l.js +0 -0
  217. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/postcss-CXtECtnM.js +0 -0
  218. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  219. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  220. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  221. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  222. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/proto-DyJlTyXw.js +0 -0
  223. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/pug-CGlum2m_.js +0 -0
  224. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/puppet-BMWR74SV.js +0 -0
  225. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/purescript-CklMAg4u.js +0 -0
  226. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/python-B6aJPvgy.js +0 -0
  227. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/qml-3beO22l8.js +0 -0
  228. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  229. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/qss-IeuSbFQv.js +0 -0
  230. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/r-DiinP2Uv.js +0 -0
  231. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/racket-BqYA7rlc.js +0 -0
  232. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/raku-DXvB9xmW.js +0 -0
  233. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/razor-WgofotgN.js +0 -0
  234. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/red-bN70gL4F.js +0 -0
  235. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/reg-C-SQnVFl.js +0 -0
  236. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  237. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rel-C3B-1QV4.js +0 -0
  238. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  239. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  240. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  241. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  242. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  243. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rst-B0xPkSld.js +0 -0
  244. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  245. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/rust-B1yitclQ.js +0 -0
  246. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/sas-cz2c8ADy.js +0 -0
  247. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  248. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/scala-C151Ov-r.js +0 -0
  249. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/scheme-C98Dy4si.js +0 -0
  250. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/scss-OYdSNvt2.js +0 -0
  251. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  252. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  253. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  254. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  255. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  256. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  257. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  258. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  259. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  260. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  261. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  262. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/soy-Brmx7dQM.js +0 -0
  263. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  264. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  265. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/sql-BLtJtn59.js +0 -0
  266. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  267. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/stata-BH5u7GGu.js +0 -0
  268. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  269. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  270. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/swift-Dg5xB15N.js +0 -0
  271. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  272. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  273. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  274. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  275. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  276. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  277. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/templ-W15q3VgB.js +0 -0
  278. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/terraform-BETggiCN.js +0 -0
  279. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/tex-Cppo0RY3.js +0 -0
  280. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  281. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/toml-vGWfd6FD.js +0 -0
  282. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  283. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  284. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/tsx-COt5Ahok.js +0 -0
  285. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/turtle-BsS91CYL.js +0 -0
  286. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/twig-CO9l9SDP.js +0 -0
  287. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  288. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/typespec-Df68jz8_.js +0 -0
  289. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/typst-DHCkPAjA.js +0 -0
  290. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/v-BcVCzyr7.js +0 -0
  291. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vala-CsfeWuGM.js +0 -0
  292. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vb-D17OF-Vu.js +0 -0
  293. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  294. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vesper-DU1UobuO.js +0 -0
  295. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  296. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/viml-CJc9bBzg.js +0 -0
  297. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  298. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  299. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  300. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vue-CCoi5OLL.js +0 -0
  301. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  302. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  303. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  304. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  305. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  306. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  307. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  308. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  309. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wit-5i3qLPDT.js +0 -0
  310. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  311. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  312. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  313. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/yaml-Buea-lGh.js +0 -0
  314. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  315. {srcly-0.1.29 → srcly-0.1.31}/app/static/assets/zig-VOosw3JB.js +0 -0
  316. {srcly-0.1.29 → srcly-0.1.31}/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.31
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,412 @@
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
+ "--quiet",
175
+ action="store_true",
176
+ help="Suppress per-file scan progress and print only the report summary.",
177
+ )
178
+ report_parser.add_argument(
179
+ "--agent-compact",
180
+ action="store_true",
181
+ help="Expand compact agent artifacts from the default top 10 findings to the top 20.",
182
+ )
183
+ report_parser.add_argument(
184
+ "--focus",
185
+ action="append",
186
+ default=None,
187
+ help="Path prefix to promote in report ranking. May be repeated.",
188
+ )
189
+ report_parser.add_argument(
190
+ "--deprioritize",
191
+ action="append",
192
+ default=None,
193
+ help="Path prefix to lower in report ranking. May be repeated. Defaults include .agents and docs/idea.",
194
+ )
195
+ report_parser.add_argument(
196
+ "--no-default-deprioritize",
197
+ action="store_true",
198
+ help="Disable default report deprioritization for agent docs, vendored, and generated-like paths.",
199
+ )
200
+ report_parser.add_argument(
201
+ "--profile",
202
+ choices=["general", "frontend", "backend", "typescript", "python", "docs"],
203
+ default="general",
204
+ help="Scoring profile.",
205
+ )
206
+ report_parser.add_argument(
207
+ "--format",
208
+ choices=["markdown", "json", "both"],
209
+ default="both",
210
+ help="Artifact formats to write.",
211
+ )
212
+ report_parser.add_argument(
213
+ "--fail-on",
214
+ choices=["high", "medium", "none"],
215
+ default="none",
216
+ help="Exit non-zero if findings at this priority or higher are present.",
217
+ )
218
+ report_parser.add_argument("--tree-depth", type=int, default=3, help="Maximum depth in tree.summary.json.")
219
+ report_parser.add_argument("--tree-top", type=int, default=6, help="Maximum children per tree level.")
220
+ report_parser.add_argument("--include-tree", action="store_true", help="Also write/include the full raw tree.json artifact.")
221
+ report_parser.add_argument(
222
+ "--include-dependencies",
223
+ action="store_true",
224
+ help="Also write/include the TS/TSX dependency graph artifact.",
225
+ )
226
+
227
+ hotspots_parser = subparsers.add_parser("hotspots", help="Print ranked hotspots.")
228
+ hotspots_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
229
+ hotspots_parser.add_argument("--metric", action="append", default=None, help="Metric to score. May be repeated.")
230
+ hotspots_parser.add_argument("--limit", type=int, default=25, help="Maximum hotspots to print.")
231
+ hotspots_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
232
+
233
+ explain_parser = subparsers.add_parser("explain", help="Print a focused report for one file or subtree.")
234
+ explain_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
235
+ explain_parser.add_argument("--file", required=True, help="File path to explain.")
236
+ explain_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
237
+
238
+ args = parser.parse_args(argv)
239
+
240
+ if args.command == "scan":
241
+ if args.format != "json":
242
+ raise SystemExit("--format jsonl is reserved and not implemented yet.")
243
+ if args.include_source not in {"none", "false"}:
244
+ raise SystemExit("--include-source snippets is reserved and not implemented yet.")
245
+ target_path = _resolve_target_path(args.path)
246
+ out_path = Path("-") if args.out == "-" else Path(args.out).resolve()
247
+ reporting.write_scan(target_path, out_path, refresh=args.refresh)
248
+ if str(args.out) != "-":
249
+ print(f"Wrote Srcly tree JSON to {out_path}")
250
+ return
251
+
252
+ if args.command == "report":
253
+ target_path = _resolve_target_path(args.path)
254
+ if args.stdout:
255
+ payload = reporting.build_report_payload(
256
+ target_path,
257
+ profile=args.profile,
258
+ limit=args.limit,
259
+ refresh=args.refresh,
260
+ tree_depth=args.tree_depth,
261
+ tree_top=args.tree_top,
262
+ include_tree=args.include_tree,
263
+ include_dependencies=args.include_dependencies,
264
+ focus_paths=args.focus,
265
+ deprioritize_paths=_deprioritize_paths(args),
266
+ agent_compact=args.agent_compact,
267
+ verbose=not args.quiet,
268
+ output_format=args.format,
269
+ )
270
+ print(reporting._json_dumps(payload))
271
+ else:
272
+ out_dir = Path(args.out).resolve()
273
+ output_format = "both" if args.fail_on != "none" and args.format == "markdown" else args.format
274
+ written = reporting.write_report(
275
+ target_path,
276
+ out_dir,
277
+ profile=args.profile,
278
+ limit=args.limit,
279
+ output_format=output_format,
280
+ refresh=args.refresh,
281
+ tree_depth=args.tree_depth,
282
+ tree_top=args.tree_top,
283
+ include_tree=args.include_tree,
284
+ include_dependencies=args.include_dependencies,
285
+ focus_paths=args.focus,
286
+ deprioritize_paths=_deprioritize_paths(args),
287
+ agent_compact=args.agent_compact,
288
+ verbose=not args.quiet,
289
+ )
290
+ if args.quiet:
291
+ print(_format_quiet_report_summary(out_dir, written))
292
+ else:
293
+ print(f"Wrote Srcly report artifacts to {out_dir}")
294
+ for path in written.values():
295
+ print(f"- {path}")
296
+ if args.fail_on != "none":
297
+ if args.stdout:
298
+ _enforce_fail_on_payload(payload["findings"], args.fail_on)
299
+ else:
300
+ _enforce_fail_on(out_dir / "findings.json", args.fail_on)
301
+ return
302
+
303
+ if args.command == "hotspots":
304
+ target_path = _resolve_target_path(args.path)
305
+ tree = reporting.scan_tree(target_path)
306
+ profile = _profile_from_metrics(args.metric)
307
+ ranked = reporting.rank_nodes(tree, profile=profile)[: args.limit]
308
+ if args.format == "json":
309
+ print(reporting._json_dumps({"rankings": ranked}))
310
+ else:
311
+ for item in ranked:
312
+ location = item["path"]
313
+ if item.get("start_line") and item.get("end_line"):
314
+ location = f"{location}:{item['start_line']}-{item['end_line']}"
315
+ print(f"{item['rank']}. score={item['score']} {location} - {item['reason']}")
316
+ return
317
+
318
+ if args.command == "explain":
319
+ target_path = _resolve_target_path(args.path)
320
+ tree = reporting.scan_tree(target_path)
321
+ payload = _explain_file(tree, Path(args.file).resolve(), target_path)
322
+ if args.format == "json":
323
+ print(reporting._json_dumps(payload))
324
+ else:
325
+ print(_render_explain_markdown(payload))
326
+ return
327
+
328
+
329
+ def _profile_from_metrics(metrics: list[str] | None) -> str:
330
+ if not metrics:
331
+ return "general"
332
+ weights = {metric: 1.0 for metric in metrics}
333
+ reporting.PROFILE_WEIGHTS["_custom"] = weights
334
+ return "_custom"
335
+
336
+
337
+ def _deprioritize_paths(args) -> tuple[str, ...]:
338
+ if args.no_default_deprioritize:
339
+ return tuple(args.deprioritize or ())
340
+ return tuple(reporting.DEFAULT_DEPRIORITIZED_PATHS) + tuple(args.deprioritize or ())
341
+
342
+
343
+ def _format_quiet_report_summary(out_dir: Path, written: dict[str, Path]) -> str:
344
+ names = ", ".join(path.name for path in written.values())
345
+ read_first = out_dir / "report.md"
346
+ return f"Wrote {len(written)} Srcly artifacts to {out_dir}: {names}\nRead first: {read_first}"
347
+
348
+
349
+ def _enforce_fail_on(findings_path: Path, fail_on: str) -> None:
350
+ import json
351
+
352
+ data = json.loads(findings_path.read_text(encoding="utf-8"))
353
+ _enforce_fail_on_payload(data, fail_on)
354
+
355
+
356
+ def _enforce_fail_on_payload(data: dict, fail_on: str) -> None:
357
+ priorities = {"high": 2, "medium": 1, "low": 0}
358
+ threshold = priorities[fail_on]
359
+ offenders = [
360
+ finding
361
+ for finding in data.get("findings", [])
362
+ if priorities.get(finding.get("priority", "low"), 0) >= threshold
363
+ ]
364
+ if offenders:
365
+ raise SystemExit(f"Srcly found {len(offenders)} findings at priority {fail_on} or higher.")
366
+
367
+
368
+ def _explain_file(tree, file_path: Path, root_path: Path) -> dict:
369
+ target = None
370
+ for node in reporting.iter_nodes(tree):
371
+ try:
372
+ node_path = Path(node.path).resolve()
373
+ except Exception:
374
+ continue
375
+ if node_path == file_path:
376
+ target = node
377
+ break
378
+
379
+ if target is None:
380
+ raise SystemExit(f"File not found in analysis tree: {file_path}")
381
+
382
+ ranked = reporting.rank_nodes(target)
383
+ return {
384
+ "path": str(file_path),
385
+ "summary": reporting.summarize_tree(target, ranked, root_path=root_path, max_depth=3, top_children=20),
386
+ "hotspots": ranked[:10],
387
+ }
388
+
389
+
390
+ def _render_explain_markdown(payload: dict) -> str:
391
+ lines = [
392
+ f"# Srcly Explain: {payload['path']}",
393
+ "",
394
+ "## Tree",
395
+ "",
396
+ "```text",
397
+ *reporting.render_tree_lines(payload["summary"]),
398
+ "```",
399
+ "",
400
+ "## Hotspots",
401
+ "",
402
+ ]
403
+ for item in payload["hotspots"]:
404
+ location = item["path"]
405
+ if item.get("start_line") and item.get("end_line"):
406
+ location = f"{location}:{item['start_line']}-{item['end_line']}"
407
+ lines.append(f"- score={item['score']} `{location}`: {item['reason']}")
408
+ return "\n".join(lines) + "\n"
409
+
410
+
411
+ if __name__ == "__main__":
412
+ 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
@@ -483,6 +481,8 @@ def _run_file_analyses_with_hard_timeouts(
483
481
  files_to_scan: list[str],
484
482
  timeout_seconds: float,
485
483
  max_workers: int,
484
+ *,
485
+ verbose: bool = True,
486
486
  ) -> list:
487
487
  """
488
488
  Analyze files with a *hard* per-file timeout by running each file in its own
@@ -510,7 +510,8 @@ def _run_file_analyses_with_hard_timeouts(
510
510
 
511
511
  # Log every file *before* it is processed so we can identify the
512
512
  # last-started file if analysis hangs or crashes.
513
- print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", flush=True)
513
+ if verbose:
514
+ print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", file=sys.stderr, flush=True)
514
515
 
515
516
  recv_conn, send_conn = ctx.Pipe(duplex=False)
516
517
  proc = ctx.Process(
@@ -542,10 +543,12 @@ def _run_file_analyses_with_hard_timeouts(
542
543
  # Either finished, or timed out.
543
544
  if proc.is_alive() and elapsed > timeout_seconds:
544
545
  completed_count += 1
545
- print(
546
- f"❌ [{completed_count}/{total_count}] Timeout analyzing {file_path} after {elapsed:.2f}s (terminated)",
547
- flush=True,
548
- )
546
+ if verbose:
547
+ print(
548
+ f"❌ [{completed_count}/{total_count}] Timeout analyzing {file_path} after {elapsed:.2f}s (terminated)",
549
+ file=sys.stderr,
550
+ flush=True,
551
+ )
549
552
  try:
550
553
  proc.terminate()
551
554
  except Exception:
@@ -580,13 +583,15 @@ def _run_file_analyses_with_hard_timeouts(
580
583
  pass
581
584
 
582
585
  if isinstance(result, dict) and "error" in result:
583
- print(
584
- f"❌ [{completed_count}/{total_count}] Error analyzing {file_path}: {result.get('error')}",
585
- flush=True,
586
- )
586
+ if verbose:
587
+ print(
588
+ f"❌ [{completed_count}/{total_count}] Error analyzing {file_path}: {result.get('error')}",
589
+ file=sys.stderr,
590
+ flush=True,
591
+ )
587
592
  else:
588
- if _should_log_file_progress(completed_count, total_count):
589
- print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", flush=True)
593
+ if verbose and _should_log_file_progress(completed_count, total_count):
594
+ print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", file=sys.stderr, flush=True)
590
595
  results.append(result)
591
596
 
592
597
  active = still_active
@@ -598,8 +603,9 @@ def _run_file_analyses_with_hard_timeouts(
598
603
  return results
599
604
 
600
605
 
601
- def scan_codebase(root_path: Path) -> Node:
602
- print(f"🔍 Scanning: {root_path}", flush=True)
606
+ def scan_codebase(root_path: Path, *, verbose: bool = True) -> Node:
607
+ if verbose:
608
+ print(f"🔍 Scanning: {root_path}", file=sys.stderr, flush=True)
603
609
 
604
610
  # Load .gitignore spec (repo-wide, with nested .gitignore support)
605
611
  ignore_root, gitignore_spec = _load_gitignore_spec(root_path)
@@ -614,7 +620,7 @@ def scan_codebase(root_path: Path) -> Node:
614
620
  # We must modify dirs in-place to prune traversal
615
621
  pruned_dirs: list[str] = []
616
622
  for d in dirs:
617
- if d in IGNORE_DIRS:
623
+ if d in IGNORE_DIRS or d.startswith(".srcly"):
618
624
  continue
619
625
  dir_path = root_dir_path / d
620
626
  if _is_gitignored(dir_path, ignore_root, gitignore_spec):
@@ -641,15 +647,18 @@ def scan_codebase(root_path: Path) -> Node:
641
647
  if current_ignored_count > 0:
642
648
  ignored_counts[str(root_dir_path)] = current_ignored_count
643
649
 
644
- print(
645
- f"📂 Analyzing {len(files_to_scan)} source files... (workers={MAX_ANALYSIS_WORKERS}, timeout={PER_FILE_ANALYSIS_TIMEOUT_SECONDS}s)",
646
- flush=True,
647
- )
650
+ if verbose:
651
+ print(
652
+ f"📂 Analyzing {len(files_to_scan)} source files... (workers={MAX_ANALYSIS_WORKERS}, timeout={PER_FILE_ANALYSIS_TIMEOUT_SECONDS}s)",
653
+ file=sys.stderr,
654
+ flush=True,
655
+ )
648
656
 
649
657
  analysis_results = _run_file_analyses_with_hard_timeouts(
650
658
  files_to_scan=files_to_scan,
651
659
  timeout_seconds=PER_FILE_ANALYSIS_TIMEOUT_SECONDS,
652
660
  max_workers=MAX_ANALYSIS_WORKERS,
661
+ verbose=verbose,
653
662
  )
654
663
 
655
664
  tree_root = create_node("root", "folder", str(root_path))
@@ -661,7 +670,7 @@ def scan_codebase(root_path: Path) -> Node:
661
670
  # ``filename`` attribute), skip it instead of crashing the whole scan.
662
671
  filename = getattr(file_info, "filename", None)
663
672
  if not filename:
664
- print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", flush=True)
673
+ print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", file=sys.stderr, flush=True)
665
674
  continue
666
675
 
667
676
  path_obj = Path(filename)