srcly 0.1.19__tar.gz → 0.1.20__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 (312) hide show
  1. {srcly-0.1.19 → srcly-0.1.20}/PKG-INFO +1 -1
  2. {srcly-0.1.19 → srcly-0.1.20}/app/routers/files.py +3 -0
  3. {srcly-0.1.19 → srcly-0.1.20}/app/services/analysis.py +171 -38
  4. {srcly-0.1.19 → srcly-0.1.20}/app/services/python/python_analysis.py +68 -0
  5. {srcly-0.1.19 → srcly-0.1.20}/app/services/typescript/typescript_analysis.py +122 -12
  6. srcly-0.1.20/app/static/assets/index-DAI1ORsS.js +66 -0
  7. srcly-0.1.19/app/static/assets/index-Tx90OM1c.css → srcly-0.1.20/app/static/assets/index-Dpk19KOJ.css +1 -1
  8. {srcly-0.1.19 → srcly-0.1.20}/app/static/index.html +2 -2
  9. {srcly-0.1.19 → srcly-0.1.20}/pyproject.toml +1 -1
  10. srcly-0.1.19/app/static/assets/index-CeLPDK_o.js +0 -66
  11. {srcly-0.1.19 → srcly-0.1.20}/.gitignore +0 -0
  12. {srcly-0.1.19 → srcly-0.1.20}/README.md +0 -0
  13. {srcly-0.1.19 → srcly-0.1.20}/app/.gitignore +0 -0
  14. {srcly-0.1.19 → srcly-0.1.20}/app/__init__.py +0 -0
  15. {srcly-0.1.19 → srcly-0.1.20}/app/config.py +0 -0
  16. {srcly-0.1.19 → srcly-0.1.20}/app/main.py +0 -0
  17. {srcly-0.1.19 → srcly-0.1.20}/app/models.py +0 -0
  18. {srcly-0.1.19 → srcly-0.1.20}/app/routers/analysis.py +0 -0
  19. {srcly-0.1.19 → srcly-0.1.20}/app/run.py +0 -0
  20. {srcly-0.1.19 → srcly-0.1.20}/app/services/analysis_types.py +0 -0
  21. {srcly-0.1.19 → srcly-0.1.20}/app/services/cache.py +0 -0
  22. {srcly-0.1.19 → srcly-0.1.20}/app/services/css/css_analysis.py +0 -0
  23. {srcly-0.1.19 → srcly-0.1.20}/app/services/data_flow_analysis.py +0 -0
  24. {srcly-0.1.19 → srcly-0.1.20}/app/services/ipynb/ipynb_analysis.py +0 -0
  25. {srcly-0.1.19 → srcly-0.1.20}/app/services/markdown/markdown_analysis.py +0 -0
  26. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/abap-BdImnpbu.js +0 -0
  27. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  28. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ada-bCR0ucgS.js +0 -0
  29. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  30. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  31. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  32. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/apache-Pmp26Uib.js +0 -0
  33. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/apex-DhZLUxFE.js +0 -0
  34. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/apl-dKokRX4l.js +0 -0
  35. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  36. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ara-BRHolxvo.js +0 -0
  37. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  38. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  39. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/astro-CbQHKStN.js +0 -0
  40. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  41. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/awk-DMzUqQB5.js +0 -0
  42. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  43. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  44. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bat-BkioyH1T.js +0 -0
  45. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  46. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/berry-D08WgyRC.js +0 -0
  47. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  48. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  49. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/blade-DVc8C-J4.js +0 -0
  50. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  51. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/c-BIGW1oBm.js +0 -0
  52. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  53. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  54. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  55. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  56. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  57. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  58. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/clarity-D53aC0YG.js +0 -0
  59. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/clojure-P80f7IUj.js +0 -0
  60. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  61. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cobol-nwyudZeR.js +0 -0
  62. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  63. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  64. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  65. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  66. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/coq-DkFqJrB1.js +0 -0
  67. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cpp-CofmeUqb.js +0 -0
  68. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  69. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  70. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/css-DPfMkruS.js +0 -0
  71. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/csv-fuZLfV_i.js +0 -0
  72. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cue-D82EKSYY.js +0 -0
  73. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/cypher-COkxafJQ.js +0 -0
  74. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/d-85-TOEBH.js +0 -0
  75. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  76. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dart-CF10PKvl.js +0 -0
  77. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dax-CEL-wOlO.js +0 -0
  78. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  79. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/diff-D97Zzqfu.js +0 -0
  80. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/docker-BcOcwvcX.js +0 -0
  81. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  82. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  83. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  84. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  85. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/edge-BkV0erSs.js +0 -0
  86. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/elixir-CDX3lj18.js +0 -0
  87. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/elm-DbKCFpqz.js +0 -0
  88. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  89. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/erb-BOJIQeun.js +0 -0
  90. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  91. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  92. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  93. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fennel-BYunw83y.js +0 -0
  94. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fish-BvzEVeQv.js +0 -0
  95. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  96. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  97. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  98. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  99. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  100. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  101. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  102. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/genie-D0YGMca9.js +0 -0
  103. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  104. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  105. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  106. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  107. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  108. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  109. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  110. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  111. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  112. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  113. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gleam-BspZqrRM.js +0 -0
  114. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  115. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  116. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/glsl-DplSGwfg.js +0 -0
  117. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  118. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/go-Dn2_MT6a.js +0 -0
  119. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  120. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  121. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  122. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  123. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  124. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  125. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  126. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  127. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hack-CaT9iCJl.js +0 -0
  128. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/haml-B8DHNrY2.js +0 -0
  129. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  130. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  131. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  132. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  133. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hjson-D5-asLiD.js +0 -0
  134. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  135. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/houston-DnULxvSX.js +0 -0
  136. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/html-GMplVEZG.js +0 -0
  137. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  138. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/http-jrhK8wxY.js +0 -0
  139. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hurl-irOxFIW8.js +0 -0
  140. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  141. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/hy-DFXneXwc.js +0 -0
  142. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/imba-DGztddWO.js +0 -0
  143. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ini-BEwlwnbL.js +0 -0
  144. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/java-CylS5w8V.js +0 -0
  145. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  146. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  147. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jison-wvAkD_A8.js +0 -0
  148. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/json-Cp-IABpG.js +0 -0
  149. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/json5-C9tS-k6U.js +0 -0
  150. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  151. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  152. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  153. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  154. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  155. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/julia-C8NyazO9.js +0 -0
  156. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  157. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  158. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  159. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kdl-DV7GczEv.js +0 -0
  160. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  161. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  162. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  163. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/latex-BUKiar2Z.js +0 -0
  164. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/lean-DP1Csr6i.js +0 -0
  165. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/less-B1dDrJ26.js +0 -0
  166. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  167. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/liquid-DYVedYrR.js +0 -0
  168. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/llvm-BtvRca6l.js +0 -0
  169. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/log-2UxHyX5q.js +0 -0
  170. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/logo-BtOb2qkB.js +0 -0
  171. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/lua-BbnMAYS6.js +0 -0
  172. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/luau-CXu1NL6O.js +0 -0
  173. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/make-CHLpvVh8.js +0 -0
  174. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  175. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/marko-CPi9NSCl.js +0 -0
  176. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  177. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  178. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  179. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  180. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  181. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/matlab-D7o27uSR.js +0 -0
  182. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mdc-DUICxH0z.js +0 -0
  183. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  184. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  185. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  186. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/min-light-CTRr51gU.js +0 -0
  187. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  188. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/mojo-1DNp92w6.js +0 -0
  189. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  190. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/move-Bu9oaDYs.js +0 -0
  191. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  192. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  193. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nginx-DknmC5AR.js +0 -0
  194. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  195. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nim-CVrawwO9.js +0 -0
  196. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nix-BbRYJGeE.js +0 -0
  197. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nord-Ddv68eIx.js +0 -0
  198. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/nushell-C-sUppwS.js +0 -0
  199. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  200. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  201. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  202. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  203. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  204. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  205. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/perl-C0TMdlhV.js +0 -0
  206. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/php-CDn_0X-4.js +0 -0
  207. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  208. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  209. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  210. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/po-BTJTHyun.js +0 -0
  211. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  212. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/polar-C0HS_06l.js +0 -0
  213. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/postcss-CXtECtnM.js +0 -0
  214. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  215. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  216. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  217. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  218. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/proto-DyJlTyXw.js +0 -0
  219. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/pug-CGlum2m_.js +0 -0
  220. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/puppet-BMWR74SV.js +0 -0
  221. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/purescript-CklMAg4u.js +0 -0
  222. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/python-B6aJPvgy.js +0 -0
  223. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/qml-3beO22l8.js +0 -0
  224. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  225. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/qss-IeuSbFQv.js +0 -0
  226. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/r-DiinP2Uv.js +0 -0
  227. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/racket-BqYA7rlc.js +0 -0
  228. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/raku-DXvB9xmW.js +0 -0
  229. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/razor-WgofotgN.js +0 -0
  230. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/red-bN70gL4F.js +0 -0
  231. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/reg-C-SQnVFl.js +0 -0
  232. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  233. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rel-C3B-1QV4.js +0 -0
  234. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  235. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  236. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  237. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  238. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  239. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rst-B0xPkSld.js +0 -0
  240. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  241. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/rust-B1yitclQ.js +0 -0
  242. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sas-cz2c8ADy.js +0 -0
  243. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  244. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/scala-C151Ov-r.js +0 -0
  245. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/scheme-C98Dy4si.js +0 -0
  246. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/scss-OYdSNvt2.js +0 -0
  247. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  248. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  249. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  250. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  251. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  252. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  253. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  254. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  255. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  256. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  257. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  258. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/soy-Brmx7dQM.js +0 -0
  259. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  260. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  261. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/sql-BLtJtn59.js +0 -0
  262. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  263. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/stata-BH5u7GGu.js +0 -0
  264. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  265. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  266. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/swift-Dg5xB15N.js +0 -0
  267. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  268. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  269. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  270. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  271. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  272. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  273. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/templ-W15q3VgB.js +0 -0
  274. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/terraform-BETggiCN.js +0 -0
  275. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tex-Cppo0RY3.js +0 -0
  276. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  277. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/toml-vGWfd6FD.js +0 -0
  278. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  279. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  280. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/tsx-COt5Ahok.js +0 -0
  281. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/turtle-BsS91CYL.js +0 -0
  282. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/twig-CO9l9SDP.js +0 -0
  283. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  284. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/typespec-Df68jz8_.js +0 -0
  285. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/typst-DHCkPAjA.js +0 -0
  286. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/v-BcVCzyr7.js +0 -0
  287. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vala-CsfeWuGM.js +0 -0
  288. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vb-D17OF-Vu.js +0 -0
  289. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  290. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vesper-DU1UobuO.js +0 -0
  291. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  292. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/viml-CJc9bBzg.js +0 -0
  293. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  294. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  295. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  296. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vue-CCoi5OLL.js +0 -0
  297. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  298. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  299. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  300. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  301. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  302. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  303. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  304. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  305. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wit-5i3qLPDT.js +0 -0
  306. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  307. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  308. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  309. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/yaml-Buea-lGh.js +0 -0
  310. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  311. {srcly-0.1.19 → srcly-0.1.20}/app/static/assets/zig-VOosw3JB.js +0 -0
  312. {srcly-0.1.19 → srcly-0.1.20}/app/static/vite.svg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: srcly
3
- Version: 0.1.19
3
+ Version: 0.1.20
4
4
  Summary: Interactive codebase treemap and metrics viewer.
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: fastapi>=0.121.3
@@ -24,6 +24,9 @@ async def get_file_content(path: str = Query(..., description="Absolute path to
24
24
  # For this local tool, we'll allow reading any file as requested, but maybe warn?
25
25
 
26
26
  try:
27
+ # Log every file read request before processing so hangs/crashes are attributable.
28
+ print(f"📄 Reading file content: {file_path}", flush=True)
29
+
27
30
  if file_path.suffix == ".ipynb":
28
31
  return get_ipynb_analyzer().get_virtual_content(str(file_path))
29
32
 
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import lizard
3
- import concurrent.futures
3
+ import multiprocessing
4
+ import time
4
5
  from pathlib import Path
5
6
 
6
7
  from app.models import Node, Metrics
@@ -14,6 +15,20 @@ from pathspec import PathSpec
14
15
  # Maximum time allowed for analyzing a single file in a worker process.
15
16
  PER_FILE_ANALYSIS_TIMEOUT_SECONDS: float = 10.0
16
17
 
18
+ # Max parallel workers for per-file analysis. Each file is analyzed in its own
19
+ # subprocess so we can enforce a *hard* timeout and terminate hung analysis.
20
+ def _default_max_workers() -> int:
21
+ """
22
+ Pick a reasonable default worker count close to available CPU cores.
23
+
24
+ We keep 1 core of headroom so the main process / OS remains responsive.
25
+ """
26
+ cpu = os.cpu_count() or 4
27
+ return max(1, cpu - 1)
28
+
29
+
30
+ MAX_ANALYSIS_WORKERS: int = _default_max_workers()
31
+
17
32
  _ts_analyzer = None
18
33
  _md_analyzer = None
19
34
  _ipynb_analyzer = None
@@ -209,13 +224,9 @@ def attach_file_metrics(node: Node, file_info) -> None:
209
224
  node.children.append(func_node)
210
225
  func_sum_loc += func.nloc
211
226
 
212
- # CRITICAL FIX: Add a virtual node for the "Glue Code" (Imports, Exports, Global Vars)
213
- remainder = total_loc - func_sum_loc
214
- if remainder > 0:
215
- misc_node = create_node("(misc/imports)", "code_fragment", f"{node.path}::__misc__")
216
- misc_node.metrics.loc = remainder
217
- misc_node.metrics.complexity = 0 # Glue code is usually simple
218
- node.children.append(misc_node)
227
+ # NOTE: We no longer create a synthetic child node for module-level "glue".
228
+ # Any top-level/module lines should be represented as the (body) of the
229
+ # file/module scope (handled by the newer scope-body attribution logic).
219
230
 
220
231
 
221
232
  def _translate_gitignore_pattern(raw_line: str, base_rel: str) -> str | None:
@@ -390,7 +401,7 @@ def aggregate_metrics(node: Node) -> Metrics:
390
401
  total_function_loc += (child_metrics.average_function_length * child_metrics.function_count)
391
402
 
392
403
  # For Folders: Sum of children
393
- # For Files: We trust the attach_file_metrics logic (which includes __misc__)
404
+ # For Files: We trust the attach_file_metrics logic.
394
405
  if node.type == "folder":
395
406
  node.metrics.loc = total_loc
396
407
  node.metrics.complexity = max_complexity
@@ -446,6 +457,147 @@ def analyze_single_file(file_path: str):
446
457
  # Return error info instead of crashing
447
458
  return {"error": str(e), "filename": file_path}
448
459
 
460
+
461
+ def _analyze_file_in_subprocess(file_path: str, send_conn) -> None:
462
+ """
463
+ Child-process entry point. Runs analysis and sends the result back over a pipe.
464
+ Must be top-level for multiprocessing pickling.
465
+ """
466
+ try:
467
+ result = analyze_single_file(file_path)
468
+ except Exception as e:
469
+ result = {"error": str(e), "filename": file_path}
470
+ try:
471
+ send_conn.send(result)
472
+ except Exception:
473
+ # If sending fails, there's nothing useful we can do here.
474
+ pass
475
+ finally:
476
+ try:
477
+ send_conn.close()
478
+ except Exception:
479
+ pass
480
+
481
+
482
+ def _run_file_analyses_with_hard_timeouts(
483
+ files_to_scan: list[str],
484
+ timeout_seconds: float,
485
+ max_workers: int,
486
+ ) -> list:
487
+ """
488
+ Analyze files with a *hard* per-file timeout by running each file in its own
489
+ subprocess. This avoids the common pitfall where a ProcessPoolExecutor can
490
+ hang forever if a worker gets stuck, because individual tasks cannot be
491
+ force-killed reliably.
492
+ """
493
+ if not files_to_scan:
494
+ return []
495
+
496
+ ctx = multiprocessing.get_context("spawn")
497
+ total_count = len(files_to_scan)
498
+ completed_count = 0
499
+
500
+ # Active entries: (process, file_path, start_time, recv_conn)
501
+ active: list[tuple[multiprocessing.Process, str, float, object]] = []
502
+ results: list = []
503
+
504
+ next_index = 0
505
+ while next_index < total_count or active:
506
+ # Fill up worker slots.
507
+ while next_index < total_count and len(active) < max_workers:
508
+ file_path = files_to_scan[next_index]
509
+ next_index += 1
510
+
511
+ # Log every file *before* it is processed so we can identify the
512
+ # last-started file if analysis hangs or crashes.
513
+ print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", flush=True)
514
+
515
+ recv_conn, send_conn = ctx.Pipe(duplex=False)
516
+ proc = ctx.Process(
517
+ target=_analyze_file_in_subprocess,
518
+ args=(file_path, send_conn),
519
+ daemon=True,
520
+ )
521
+ start_time = time.time()
522
+ proc.start()
523
+ # Close the child end in the parent process to avoid leaks.
524
+ try:
525
+ send_conn.close()
526
+ except Exception:
527
+ pass
528
+
529
+ active.append((proc, file_path, start_time, recv_conn))
530
+
531
+ # Check running workers for completion or timeout.
532
+ still_active: list[tuple[multiprocessing.Process, str, float, object]] = []
533
+ now = time.time()
534
+
535
+ for proc, file_path, start_time, recv_conn in active:
536
+ elapsed = now - start_time
537
+
538
+ if proc.is_alive() and elapsed <= timeout_seconds:
539
+ still_active.append((proc, file_path, start_time, recv_conn))
540
+ continue
541
+
542
+ # Either finished, or timed out.
543
+ if proc.is_alive() and elapsed > timeout_seconds:
544
+ completed_count += 1
545
+ print(
546
+ f"❌ [{completed_count}/{total_count}] Timeout analyzing {file_path} after {elapsed:.2f}s (terminated)",
547
+ flush=True,
548
+ )
549
+ try:
550
+ proc.terminate()
551
+ except Exception:
552
+ pass
553
+ try:
554
+ proc.join(timeout=1.0)
555
+ except Exception:
556
+ pass
557
+ try:
558
+ recv_conn.close()
559
+ except Exception:
560
+ pass
561
+ continue
562
+
563
+ # Process finished; collect result if possible.
564
+ try:
565
+ proc.join(timeout=0.0)
566
+ except Exception:
567
+ pass
568
+
569
+ completed_count += 1
570
+ result = None
571
+ try:
572
+ # If the child crashed before sending anything, recv may raise EOFError.
573
+ result = recv_conn.recv()
574
+ except Exception as exc:
575
+ result = {"error": str(exc), "filename": file_path}
576
+ finally:
577
+ try:
578
+ recv_conn.close()
579
+ except Exception:
580
+ pass
581
+
582
+ 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
+ )
587
+ else:
588
+ if _should_log_file_progress(completed_count, total_count):
589
+ print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", flush=True)
590
+ results.append(result)
591
+
592
+ active = still_active
593
+
594
+ # Avoid busy-looping when workers are running.
595
+ if active and (next_index < total_count or active):
596
+ time.sleep(0.02)
597
+
598
+ return results
599
+
600
+
449
601
  def scan_codebase(root_path: Path) -> Node:
450
602
  print(f"🔍 Scanning: {root_path}", flush=True)
451
603
 
@@ -489,35 +641,16 @@ def scan_codebase(root_path: Path) -> Node:
489
641
  if current_ignored_count > 0:
490
642
  ignored_counts[str(root_dir_path)] = current_ignored_count
491
643
 
492
- print(f"📂 Analyzing {len(files_to_scan)} source files...", flush=True)
493
-
494
- analysis_results = []
495
-
496
- # Use ProcessPoolExecutor for better control and per-file error handling
497
- # lizard.analyze_files was opaque and crashing the whole pool on one error
498
- with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
499
- future_to_file = {executor.submit(analyze_single_file, f): f for f in files_to_scan}
500
-
501
- completed_count = 0
502
- total_count = len(files_to_scan)
503
-
504
- for future in concurrent.futures.as_completed(future_to_file):
505
- file = future_to_file[future]
506
- completed_count += 1
507
- try:
508
- # Add a timeout to prevent hanging on single files
509
- result = future.result(timeout=PER_FILE_ANALYSIS_TIMEOUT_SECONDS)
510
- if isinstance(result, dict) and "error" in result:
511
- print(f"❌ [{completed_count}/{total_count}] Error analyzing {file}: {result['error']}", flush=True)
512
- else:
513
- # Success
514
- if _should_log_file_progress(completed_count, total_count):
515
- print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
516
- analysis_results.append(result)
517
- except concurrent.futures.TimeoutError:
518
- print(f"❌ [{completed_count}/{total_count}] Timeout analyzing {file} (skipped)", flush=True)
519
- except Exception as exc:
520
- print(f"❌ [{completed_count}/{total_count}] Exception analyzing {file}: {exc}", flush=True)
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
+ )
648
+
649
+ analysis_results = _run_file_analyses_with_hard_timeouts(
650
+ files_to_scan=files_to_scan,
651
+ timeout_seconds=PER_FILE_ANALYSIS_TIMEOUT_SECONDS,
652
+ max_workers=MAX_ANALYSIS_WORKERS,
653
+ )
521
654
 
522
655
  tree_root = create_node("root", "folder", str(root_path))
523
656
  node_map = {str(root_path): tree_root}
@@ -22,6 +22,9 @@ class PythonTreeSitterAnalyzer:
22
22
 
23
23
  # Extract functions, classes, and other scopes
24
24
  functions = self._extract_scopes(tree.root_node)
25
+ import_scope = self._compute_import_scope(tree.root_node, content)
26
+ if import_scope is not None:
27
+ functions.insert(0, import_scope)
25
28
 
26
29
  avg_complexity = 0.0
27
30
  if functions:
@@ -71,6 +74,71 @@ class PythonTreeSitterAnalyzer:
71
74
  md_data_url_count=0
72
75
  )
73
76
 
77
+ def _compute_import_scope(self, root_node: Node, content: bytes) -> FunctionMetrics | None:
78
+ """
79
+ Create a synthetic top-level scope representing import statements.
80
+
81
+ - nloc: total LOC occupied by *all* import statements in the file
82
+ - start/end_line: span of the largest contiguous block of imports
83
+ """
84
+ import_spans: List[tuple[int, int]] = []
85
+ lines = content.splitlines()
86
+
87
+ def only_blank_lines_between(end_line: int, start_line: int) -> bool:
88
+ if start_line <= end_line + 1:
89
+ return True
90
+ for line_no in range(end_line + 1, start_line):
91
+ idx = line_no - 1
92
+ if 0 <= idx < len(lines):
93
+ if lines[idx].strip():
94
+ return False
95
+ return True
96
+
97
+ def traverse(n: Node) -> None:
98
+ if n.type in {"import_statement", "import_from_statement"}:
99
+ start_line = n.start_point.row + 1
100
+ end_line = n.end_point.row + 1
101
+ import_spans.append((start_line, end_line))
102
+ for child in n.children:
103
+ traverse(child)
104
+
105
+ traverse(root_node)
106
+
107
+ if not import_spans:
108
+ return None
109
+
110
+ import_spans.sort(key=lambda s: (s[0], s[1]))
111
+ total_import_loc = sum(e - s + 1 for s, e in import_spans)
112
+
113
+ blocks: List[tuple[int, int, int]] = [] # (block_start, block_end, block_loc_sum)
114
+ cur_s, cur_e = import_spans[0]
115
+ cur_loc = cur_e - cur_s + 1
116
+ for s, e in import_spans[1:]:
117
+ # Allow blank lines between imports to still count as one contiguous block.
118
+ if only_blank_lines_between(cur_e, s):
119
+ cur_e = max(cur_e, e)
120
+ cur_loc += (e - s + 1)
121
+ else:
122
+ blocks.append((cur_s, cur_e, cur_loc))
123
+ cur_s, cur_e = s, e
124
+ cur_loc = e - s + 1
125
+ blocks.append((cur_s, cur_e, cur_loc))
126
+
127
+ block_s, block_e, _ = max(blocks, key=lambda b: (b[2], -b[0]))
128
+
129
+ return FunctionMetrics(
130
+ name="(imports)",
131
+ cyclomatic_complexity=0,
132
+ nloc=total_import_loc,
133
+ start_line=block_s,
134
+ end_line=block_e,
135
+ parameter_count=0,
136
+ max_nesting_depth=0,
137
+ comment_lines=0,
138
+ todo_count=0,
139
+ origin_type="imports",
140
+ )
141
+
74
142
  def _extract_scopes(self, root_node: Node) -> List[FunctionMetrics]:
75
143
  # We want to capture:
76
144
  # - Functions (def)
@@ -27,6 +27,11 @@ class TreeSitterAnalyzer:
27
27
  nloc = len([l for l in lines if l.strip()])
28
28
 
29
29
  functions = self._extract_functions(tree.root_node, content)
30
+ import_scope = self._compute_import_scope(tree.root_node, content)
31
+ if import_scope is not None:
32
+ # Make imports visible as a top-level "scope" in treemap/scopes views.
33
+ functions.insert(0, import_scope)
34
+
30
35
  # Post-process TSX scopes so that each function containing TSX gets a
31
36
  # single virtual root "<fragment>" node that groups all JSX container
32
37
  # scopes beneath it. This keeps TSX scopes together without changing
@@ -97,6 +102,79 @@ class TreeSitterAnalyzer:
97
102
  ts_export_count=ts_export_count,
98
103
  )
99
104
 
105
+ def _compute_import_scope(self, root_node: Node, content: bytes) -> FunctionMetrics | None:
106
+ """
107
+ Create a synthetic top-level scope representing import statements.
108
+
109
+ - nloc: total LOC occupied by *all* import statements in the file
110
+ - start/end_line: span of the largest contiguous block of imports
111
+ """
112
+ import_spans: List[tuple[int, int]] = []
113
+ lines = content.splitlines()
114
+
115
+ def only_blank_lines_between(end_line: int, start_line: int) -> bool:
116
+ """
117
+ Returns True if every line strictly between end_line and start_line
118
+ is blank/whitespace-only. Lines are 1-based.
119
+ """
120
+ if start_line <= end_line + 1:
121
+ return True
122
+ # Between (end_line+1) and (start_line-1), inclusive.
123
+ for line_no in range(end_line + 1, start_line):
124
+ idx = line_no - 1
125
+ if 0 <= idx < len(lines):
126
+ if lines[idx].strip():
127
+ return False
128
+ return True
129
+
130
+ def traverse(n: Node) -> None:
131
+ if n.type == "import_statement":
132
+ start_line = n.start_point.row + 1
133
+ end_line = n.end_point.row + 1
134
+ import_spans.append((start_line, end_line))
135
+ for child in n.children:
136
+ traverse(child)
137
+
138
+ traverse(root_node)
139
+
140
+ if not import_spans:
141
+ return None
142
+
143
+ import_spans.sort(key=lambda s: (s[0], s[1]))
144
+ total_import_loc = sum(e - s + 1 for s, e in import_spans)
145
+
146
+ # Merge adjacent import statements into contiguous blocks.
147
+ blocks: List[tuple[int, int, int]] = [] # (block_start, block_end, block_loc_sum)
148
+ cur_s, cur_e = import_spans[0]
149
+ cur_loc = cur_e - cur_s + 1
150
+ for s, e in import_spans[1:]:
151
+ # Allow blank lines between imports to still count as one contiguous block.
152
+ if only_blank_lines_between(cur_e, s):
153
+ cur_e = max(cur_e, e)
154
+ cur_loc += (e - s + 1)
155
+ else:
156
+ blocks.append((cur_s, cur_e, cur_loc))
157
+ cur_s, cur_e = s, e
158
+ cur_loc = e - s + 1
159
+ blocks.append((cur_s, cur_e, cur_loc))
160
+
161
+ # Pick the largest block (by LOC, then by earliest start line for stability).
162
+ block_s, block_e, _ = max(blocks, key=lambda b: (b[2], -b[0]))
163
+
164
+ return FunctionMetrics(
165
+ name="(imports)",
166
+ cyclomatic_complexity=0,
167
+ nloc=total_import_loc,
168
+ start_line=block_s,
169
+ end_line=block_e,
170
+ parameter_count=0,
171
+ max_nesting_depth=0,
172
+ comment_lines=0,
173
+ todo_count=0,
174
+ ts_type_interface_count=0,
175
+ origin_type="imports",
176
+ )
177
+
100
178
  def _extract_functions(self, root_node: Node, content: bytes) -> List[FunctionMetrics]:
101
179
  function_types = {
102
180
  'function_declaration',
@@ -213,7 +291,41 @@ class TreeSitterAnalyzer:
213
291
  return f"{name_node.text.decode('utf-8')} (type)"
214
292
  return "type"
215
293
  elif node.type == 'object':
216
- # Try to find the name from the parent assignment or property
294
+ # TSX / JSX: object literal used inside a JSX attribute expression, e.g.
295
+ # <TapestryNode class={classy(..., { wireframe: () => ... })} />
296
+ # Prefer naming after the owning attribute: "class (obj)".
297
+ current = node
298
+ hops = 0
299
+ function_boundary_types = {
300
+ 'function_declaration',
301
+ 'method_definition',
302
+ 'arrow_function',
303
+ 'function_expression',
304
+ 'generator_function',
305
+ 'generator_function_declaration',
306
+ }
307
+ while current is not None and hops < 12:
308
+ if current.type == 'jsx_attribute':
309
+ name_node = current.child_by_field_name('name')
310
+ if name_node is None:
311
+ for c in current.children:
312
+ if c.type in {"property_identifier", "identifier", "jsx_identifier"}:
313
+ name_node = c
314
+ break
315
+ if name_node:
316
+ return f"{name_node.text.decode('utf-8')} (obj)"
317
+ break
318
+
319
+ # Don't cross into a nested function/container; if the object literal
320
+ # is inside a callback, it should be named by its local context instead.
321
+ if current is not node and current.type in function_boundary_types:
322
+ break
323
+ if current.type in {'program', 'statement_block'}:
324
+ break
325
+ current = current.parent
326
+ hops += 1
327
+
328
+ # Try to find the name from the parent assignment or property
217
329
  parent = node.parent
218
330
  if parent:
219
331
  # const obj = { ... }
@@ -410,21 +522,19 @@ class TreeSitterAnalyzer:
410
522
 
411
523
  def _expression_defines_function(self, expr_node: Node) -> bool:
412
524
  # Check if the expression contains an inline function definition
413
- # It might be wrapped in parenthesized_expression
525
+ # It might be wrapped in parenthesized_expression, and it may be nested
526
+ # inside other expressions (e.g. call arguments, object literals) like:
527
+ # class={classy(..., { wireframe: () => ... })}
414
528
 
415
- def has_func(n: Node):
416
- if n.type in {'arrow_function', 'function_expression'}:
529
+ def has_func(n: Node) -> bool:
530
+ if n.type in {"arrow_function", "function_expression"}:
417
531
  return True
418
- if n.type == 'parenthesized_expression':
419
- for c in n.children:
420
- if has_func(c):
421
- return True
532
+ for c in n.children:
533
+ if has_func(c):
534
+ return True
422
535
  return False
423
536
 
424
- for child in expr_node.children:
425
- if has_func(child):
426
- return True
427
- return False
537
+ return has_func(expr_node)
428
538
 
429
539
  def _calculate_complexity(self, node: Node) -> int:
430
540
  complexity = 1