srcly 0.1.14__tar.gz → 0.1.16__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 (311) hide show
  1. {srcly-0.1.14 → srcly-0.1.16}/PKG-INFO +4 -1
  2. {srcly-0.1.14 → srcly-0.1.16}/app/models.py +2 -0
  3. {srcly-0.1.14 → srcly-0.1.16}/app/routers/analysis.py +8 -1
  4. {srcly-0.1.14 → srcly-0.1.16}/app/services/analysis.py +91 -6
  5. srcly-0.1.16/app/services/css_analysis.py +232 -0
  6. srcly-0.1.16/app/services/ipynb_analysis.py +89 -0
  7. srcly-0.1.16/app/services/markdown_analysis.py +414 -0
  8. {srcly-0.1.14 → srcly-0.1.16}/app/services/tree_sitter_analysis.py +6 -0
  9. srcly-0.1.16/app/static/assets/index-CX6VqYPz.js +37 -0
  10. srcly-0.1.16/app/static/assets/index-DXVBnNom.css +1 -0
  11. {srcly-0.1.14 → srcly-0.1.16}/app/static/index.html +2 -2
  12. {srcly-0.1.14 → srcly-0.1.16}/pyproject.toml +4 -1
  13. srcly-0.1.14/app/static/assets/index-CTNf0zED.css +0 -1
  14. srcly-0.1.14/app/static/assets/index-DzyR2Ke2.js +0 -37
  15. {srcly-0.1.14 → srcly-0.1.16}/.gitignore +0 -0
  16. {srcly-0.1.14 → srcly-0.1.16}/README.md +0 -0
  17. {srcly-0.1.14 → srcly-0.1.16}/app/.gitignore +0 -0
  18. {srcly-0.1.14 → srcly-0.1.16}/app/__init__.py +0 -0
  19. {srcly-0.1.14 → srcly-0.1.16}/app/config.py +0 -0
  20. {srcly-0.1.14 → srcly-0.1.16}/app/main.py +0 -0
  21. {srcly-0.1.14 → srcly-0.1.16}/app/routers/files.py +0 -0
  22. {srcly-0.1.14 → srcly-0.1.16}/app/run.py +0 -0
  23. {srcly-0.1.14 → srcly-0.1.16}/app/services/cache.py +0 -0
  24. {srcly-0.1.14 → srcly-0.1.16}/app/services/data_flow_analysis.py +0 -0
  25. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/abap-BdImnpbu.js +0 -0
  26. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  27. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ada-bCR0ucgS.js +0 -0
  28. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  29. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  30. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  31. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/apache-Pmp26Uib.js +0 -0
  32. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/apex-DhZLUxFE.js +0 -0
  33. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/apl-dKokRX4l.js +0 -0
  34. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  35. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ara-BRHolxvo.js +0 -0
  36. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  37. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  38. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/astro-CbQHKStN.js +0 -0
  39. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  40. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/awk-DMzUqQB5.js +0 -0
  41. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  42. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  43. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bat-BkioyH1T.js +0 -0
  44. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  45. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/berry-D08WgyRC.js +0 -0
  46. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  47. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  48. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/blade-DVc8C-J4.js +0 -0
  49. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  50. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/c-BIGW1oBm.js +0 -0
  51. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  52. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  53. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  54. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  55. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  56. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  57. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/clarity-D53aC0YG.js +0 -0
  58. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/clojure-P80f7IUj.js +0 -0
  59. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  60. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cobol-nwyudZeR.js +0 -0
  61. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  62. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  63. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  64. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  65. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/coq-DkFqJrB1.js +0 -0
  66. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cpp-CofmeUqb.js +0 -0
  67. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  68. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  69. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/css-DPfMkruS.js +0 -0
  70. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/csv-fuZLfV_i.js +0 -0
  71. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cue-D82EKSYY.js +0 -0
  72. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/cypher-COkxafJQ.js +0 -0
  73. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/d-85-TOEBH.js +0 -0
  74. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  75. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dart-CF10PKvl.js +0 -0
  76. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dax-CEL-wOlO.js +0 -0
  77. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  78. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/diff-D97Zzqfu.js +0 -0
  79. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/docker-BcOcwvcX.js +0 -0
  80. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  81. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  82. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  83. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  84. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/edge-BkV0erSs.js +0 -0
  85. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/elixir-CDX3lj18.js +0 -0
  86. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/elm-DbKCFpqz.js +0 -0
  87. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  88. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/erb-BOJIQeun.js +0 -0
  89. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  90. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  91. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  92. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fennel-BYunw83y.js +0 -0
  93. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fish-BvzEVeQv.js +0 -0
  94. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  95. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  96. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  97. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  98. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  99. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  100. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  101. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/genie-D0YGMca9.js +0 -0
  102. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  103. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  104. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  105. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  106. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  107. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  108. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  109. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  110. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  111. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  112. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gleam-BspZqrRM.js +0 -0
  113. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  114. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  115. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/glsl-DplSGwfg.js +0 -0
  116. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  117. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/go-Dn2_MT6a.js +0 -0
  118. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  119. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  120. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  121. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  122. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  123. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  124. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  125. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  126. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hack-CaT9iCJl.js +0 -0
  127. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/haml-B8DHNrY2.js +0 -0
  128. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  129. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  130. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  131. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  132. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hjson-D5-asLiD.js +0 -0
  133. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  134. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/houston-DnULxvSX.js +0 -0
  135. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/html-GMplVEZG.js +0 -0
  136. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  137. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/http-jrhK8wxY.js +0 -0
  138. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hurl-irOxFIW8.js +0 -0
  139. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  140. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/hy-DFXneXwc.js +0 -0
  141. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/imba-DGztddWO.js +0 -0
  142. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ini-BEwlwnbL.js +0 -0
  143. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/java-CylS5w8V.js +0 -0
  144. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  145. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  146. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jison-wvAkD_A8.js +0 -0
  147. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/json-Cp-IABpG.js +0 -0
  148. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/json5-C9tS-k6U.js +0 -0
  149. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  150. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  151. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  152. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  153. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  154. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/julia-C8NyazO9.js +0 -0
  155. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  156. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  157. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  158. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kdl-DV7GczEv.js +0 -0
  159. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  160. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  161. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  162. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/latex-BUKiar2Z.js +0 -0
  163. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/lean-DP1Csr6i.js +0 -0
  164. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/less-B1dDrJ26.js +0 -0
  165. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  166. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/liquid-DYVedYrR.js +0 -0
  167. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/llvm-BtvRca6l.js +0 -0
  168. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/log-2UxHyX5q.js +0 -0
  169. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/logo-BtOb2qkB.js +0 -0
  170. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/lua-BbnMAYS6.js +0 -0
  171. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/luau-CXu1NL6O.js +0 -0
  172. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/make-CHLpvVh8.js +0 -0
  173. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  174. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/marko-CPi9NSCl.js +0 -0
  175. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  176. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  177. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  178. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  179. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  180. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/matlab-D7o27uSR.js +0 -0
  181. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mdc-DUICxH0z.js +0 -0
  182. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  183. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  184. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  185. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/min-light-CTRr51gU.js +0 -0
  186. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  187. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/mojo-1DNp92w6.js +0 -0
  188. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  189. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/move-Bu9oaDYs.js +0 -0
  190. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  191. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  192. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nginx-DknmC5AR.js +0 -0
  193. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  194. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nim-CVrawwO9.js +0 -0
  195. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nix-BbRYJGeE.js +0 -0
  196. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nord-Ddv68eIx.js +0 -0
  197. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/nushell-C-sUppwS.js +0 -0
  198. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  199. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  200. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  201. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  202. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  203. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  204. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/perl-C0TMdlhV.js +0 -0
  205. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/php-CDn_0X-4.js +0 -0
  206. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  207. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  208. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  209. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/po-BTJTHyun.js +0 -0
  210. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  211. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/polar-C0HS_06l.js +0 -0
  212. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/postcss-CXtECtnM.js +0 -0
  213. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  214. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  215. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  216. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  217. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/proto-DyJlTyXw.js +0 -0
  218. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/pug-CGlum2m_.js +0 -0
  219. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/puppet-BMWR74SV.js +0 -0
  220. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/purescript-CklMAg4u.js +0 -0
  221. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/python-B6aJPvgy.js +0 -0
  222. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/qml-3beO22l8.js +0 -0
  223. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  224. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/qss-IeuSbFQv.js +0 -0
  225. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/r-DiinP2Uv.js +0 -0
  226. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/racket-BqYA7rlc.js +0 -0
  227. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/raku-DXvB9xmW.js +0 -0
  228. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/razor-WgofotgN.js +0 -0
  229. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/red-bN70gL4F.js +0 -0
  230. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/reg-C-SQnVFl.js +0 -0
  231. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  232. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rel-C3B-1QV4.js +0 -0
  233. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  234. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  235. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  236. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  237. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  238. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rst-B0xPkSld.js +0 -0
  239. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  240. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/rust-B1yitclQ.js +0 -0
  241. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sas-cz2c8ADy.js +0 -0
  242. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  243. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/scala-C151Ov-r.js +0 -0
  244. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/scheme-C98Dy4si.js +0 -0
  245. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/scss-OYdSNvt2.js +0 -0
  246. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  247. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  248. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  249. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  250. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  251. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  252. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  253. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  254. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  255. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  256. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  257. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/soy-Brmx7dQM.js +0 -0
  258. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  259. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  260. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/sql-BLtJtn59.js +0 -0
  261. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  262. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/stata-BH5u7GGu.js +0 -0
  263. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  264. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  265. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/swift-Dg5xB15N.js +0 -0
  266. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  267. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  268. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  269. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  270. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  271. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  272. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/templ-W15q3VgB.js +0 -0
  273. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/terraform-BETggiCN.js +0 -0
  274. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tex-Cppo0RY3.js +0 -0
  275. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  276. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/toml-vGWfd6FD.js +0 -0
  277. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  278. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  279. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/tsx-COt5Ahok.js +0 -0
  280. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/turtle-BsS91CYL.js +0 -0
  281. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/twig-CO9l9SDP.js +0 -0
  282. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  283. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/typespec-Df68jz8_.js +0 -0
  284. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/typst-DHCkPAjA.js +0 -0
  285. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/v-BcVCzyr7.js +0 -0
  286. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vala-CsfeWuGM.js +0 -0
  287. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vb-D17OF-Vu.js +0 -0
  288. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  289. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vesper-DU1UobuO.js +0 -0
  290. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  291. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/viml-CJc9bBzg.js +0 -0
  292. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  293. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  294. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  295. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vue-CCoi5OLL.js +0 -0
  296. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  297. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  298. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  299. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  300. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  301. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  302. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  303. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  304. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wit-5i3qLPDT.js +0 -0
  305. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  306. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  307. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  308. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/yaml-Buea-lGh.js +0 -0
  309. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  310. {srcly-0.1.14 → srcly-0.1.16}/app/static/assets/zig-VOosw3JB.js +0 -0
  311. {srcly-0.1.14 → srcly-0.1.16}/app/static/vite.svg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: srcly
3
- Version: 0.1.14
3
+ Version: 0.1.16
4
4
  Summary: Interactive codebase treemap and metrics viewer.
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: fastapi>=0.121.3
@@ -9,6 +9,9 @@ Requires-Dist: lizard>=1.19.0
9
9
  Requires-Dist: pathspec>=0.12.1
10
10
  Requires-Dist: pydantic>=2.12.4
11
11
  Requires-Dist: pytest>=9.0.1
12
+ Requires-Dist: tree-sitter-css==0.25.0
13
+ Requires-Dist: tree-sitter-markdown<0.6.0,>=0.5.1
14
+ Requires-Dist: tree-sitter-scss==1.0.0
12
15
  Requires-Dist: tree-sitter-typescript>=0.23.2
13
16
  Requires-Dist: tree-sitter>=0.25.2
14
17
  Requires-Dist: uvicorn>=0.38.0
@@ -34,6 +34,8 @@ class Metrics(BaseModel):
34
34
  tsx_duplicated_string_count: int = 0
35
35
  ts_type_interface_count: int = 0
36
36
  ts_export_count: int = 0
37
+ # Markdown-specific metrics
38
+ md_data_url_count: int = 0
37
39
 
38
40
  class Node(BaseModel):
39
41
  name: str
@@ -570,7 +570,14 @@ async def get_analysis_context():
570
570
  load, for both the current working directory and the repository root.
571
571
  """
572
572
  current_root = ROOT_PATH
573
- repo_root = current_root.parent
573
+ # Use the same repository root detection logic as the analysis layer so that
574
+ # the client sees a consistent "repo root" regardless of how the server was
575
+ # started (uvx, direct CLI, dev mode, etc.).
576
+ #
577
+ # This mirrors the behaviour of the CLI helper that walks upwards from the
578
+ # current directory to the enclosing `.git` root, falling back to the
579
+ # starting directory when no repository is found.
580
+ repo_root = analysis.find_repo_root(current_root)
574
581
 
575
582
  current_file_count, current_folder_count = _estimate_counts(current_root)
576
583
 
@@ -6,9 +6,19 @@ from pathlib import Path
6
6
  from app.models import Node, Metrics
7
7
  from app.config import IGNORE_DIRS, IGNORE_FILES, IGNORE_EXTENSIONS
8
8
  from app.services.tree_sitter_analysis import TreeSitterAnalyzer
9
+ from app.services.markdown_analysis import MarkdownTreeSitterAnalyzer
10
+ from app.services.ipynb_analysis import NotebookAnalyzer
11
+ from app.services.css_analysis import CssTreeSitterAnalyzer
9
12
  from pathspec import PathSpec
10
13
 
14
+ # Maximum time allowed for analyzing a single file in a worker process.
15
+ PER_FILE_ANALYSIS_TIMEOUT_SECONDS: float = 10.0
16
+
11
17
  _ts_analyzer = None
18
+ _md_analyzer = None
19
+ _ipynb_analyzer = None
20
+ _css_analyzer = None
21
+
12
22
 
13
23
  def get_ts_analyzer():
14
24
  global _ts_analyzer
@@ -16,6 +26,27 @@ def get_ts_analyzer():
16
26
  _ts_analyzer = TreeSitterAnalyzer()
17
27
  return _ts_analyzer
18
28
 
29
+
30
+ def get_md_analyzer():
31
+ global _md_analyzer
32
+ if _md_analyzer is None:
33
+ _md_analyzer = MarkdownTreeSitterAnalyzer()
34
+ return _md_analyzer
35
+
36
+
37
+ def get_ipynb_analyzer():
38
+ global _ipynb_analyzer
39
+ if _ipynb_analyzer is None:
40
+ _ipynb_analyzer = NotebookAnalyzer()
41
+ return _ipynb_analyzer
42
+
43
+
44
+ def get_css_analyzer():
45
+ global _css_analyzer
46
+ if _css_analyzer is None:
47
+ _css_analyzer = CssTreeSitterAnalyzer()
48
+ return _css_analyzer
49
+
19
50
  def find_repo_root(start_path: Path) -> Path:
20
51
  current = start_path.resolve()
21
52
  for parent in [current, *current.parents]:
@@ -54,6 +85,9 @@ def attach_file_metrics(node: Node, file_info) -> None:
54
85
  node.metrics.tsx_duplicated_string_count = file_info.tsx_duplicated_string_count
55
86
  node.metrics.ts_type_interface_count = file_info.ts_type_interface_count
56
87
  node.metrics.ts_export_count = file_info.ts_export_count
88
+ # Markdown-specific metrics
89
+ if hasattr(file_info, "md_data_url_count"):
90
+ node.metrics.md_data_url_count = file_info.md_data_url_count
57
91
 
58
92
  # New metrics
59
93
  if hasattr(file_info, 'comment_lines'):
@@ -125,6 +159,7 @@ def attach_file_metrics(node: Node, file_info) -> None:
125
159
  func_node.metrics.comment_lines = getattr(func, 'comment_lines', 0)
126
160
  func_node.metrics.todo_count = getattr(func, 'todo_count', 0)
127
161
  func_node.metrics.ts_type_interface_count = getattr(func, 'ts_type_interface_count', 0)
162
+ func_node.metrics.md_data_url_count = getattr(func, 'md_data_url_count', 0)
128
163
 
129
164
  # Density for function
130
165
  comment_lines = getattr(func, 'comment_lines', 0)
@@ -266,6 +301,36 @@ def _is_gitignored(path: Path, ignore_root: Path, spec: PathSpec | None) -> bool
266
301
  rel_str = rel.as_posix()
267
302
  return spec.match_file(rel_str)
268
303
 
304
+
305
+ def _should_log_file_progress(completed_count: int, total_count: int) -> bool:
306
+ """
307
+ Decide whether to emit a per-file progress log.
308
+
309
+ Rules:
310
+ - If there are fewer than 10 files, log every file.
311
+ - Otherwise:
312
+ - Log the first 10 files.
313
+ - Log at 15 and 20.
314
+ - Log every 25th file after that (25, 50, 75, ...).
315
+ - Always log the final file when done.
316
+ """
317
+ if total_count < 10:
318
+ return True
319
+
320
+ if completed_count <= 10:
321
+ return True
322
+
323
+ if completed_count in (15, 20):
324
+ return True
325
+
326
+ if completed_count % 25 == 0:
327
+ return True
328
+
329
+ if completed_count == total_count:
330
+ return True
331
+
332
+ return False
333
+
269
334
  def aggregate_metrics(node: Node) -> Metrics:
270
335
  if not node.children: return node.metrics
271
336
 
@@ -281,6 +346,7 @@ def aggregate_metrics(node: Node) -> Metrics:
281
346
  total_classes_count = 0
282
347
  total_type_interface_count = 0
283
348
  total_export_count = 0
349
+ total_md_data_url_count = 0
284
350
 
285
351
  # For average function length, we need total function loc and total functions (already have total_funcs)
286
352
  # But we need to sum function locs from children.
@@ -301,6 +367,7 @@ def aggregate_metrics(node: Node) -> Metrics:
301
367
  total_classes_count += child_metrics.classes_count
302
368
  total_type_interface_count += child_metrics.ts_type_interface_count
303
369
  total_export_count += child_metrics.ts_export_count
370
+ total_md_data_url_count += child_metrics.md_data_url_count
304
371
 
305
372
  # Reconstruct total function loc from average * count
306
373
  total_function_loc += (child_metrics.average_function_length * child_metrics.function_count)
@@ -327,6 +394,7 @@ def aggregate_metrics(node: Node) -> Metrics:
327
394
  node.metrics.average_function_length = total_function_loc / total_funcs if total_funcs > 0 else 0.0
328
395
  node.metrics.ts_type_interface_count = total_type_interface_count
329
396
  node.metrics.ts_export_count = total_export_count
397
+ node.metrics.md_data_url_count = total_md_data_url_count
330
398
 
331
399
  return node.metrics
332
400
 
@@ -336,11 +404,19 @@ def analyze_single_file(file_path: str):
336
404
  Must be top-level for multiprocessing pickling.
337
405
  """
338
406
  try:
339
- if file_path.endswith('.ts') or file_path.endswith('.tsx'):
407
+ if file_path.endswith(".ts") or file_path.endswith(".tsx"):
340
408
  analyzer = get_ts_analyzer()
341
409
  return analyzer.analyze_file(file_path)
342
- else:
343
- return lizard.analyze_file(file_path)
410
+ if file_path.endswith(".css") or file_path.endswith(".scss"):
411
+ analyzer = get_css_analyzer()
412
+ return analyzer.analyze_file(file_path)
413
+ if file_path.endswith(".md") or file_path.endswith(".markdown"):
414
+ analyzer = get_md_analyzer()
415
+ return analyzer.analyze_file(file_path)
416
+ if file_path.endswith(".ipynb"):
417
+ analyzer = get_ipynb_analyzer()
418
+ return analyzer.analyze_file(file_path)
419
+ return lizard.analyze_file(file_path)
344
420
  except Exception as e:
345
421
  # Return error info instead of crashing
346
422
  return {"error": str(e), "filename": file_path}
@@ -405,12 +481,13 @@ def scan_codebase(root_path: Path) -> Node:
405
481
  completed_count += 1
406
482
  try:
407
483
  # Add a timeout to prevent hanging on single files
408
- result = future.result(timeout=5)
484
+ result = future.result(timeout=PER_FILE_ANALYSIS_TIMEOUT_SECONDS)
409
485
  if isinstance(result, dict) and "error" in result:
410
486
  print(f"❌ [{completed_count}/{total_count}] Error analyzing {file}: {result['error']}", flush=True)
411
487
  else:
412
488
  # Success
413
- print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
489
+ if _should_log_file_progress(completed_count, total_count):
490
+ print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
414
491
  analysis_results.append(result)
415
492
  except concurrent.futures.TimeoutError:
416
493
  print(f"❌ [{completed_count}/{total_count}] Timeout analyzing {file} (skipped)", flush=True)
@@ -421,7 +498,15 @@ def scan_codebase(root_path: Path) -> Node:
421
498
  node_map = {str(root_path): tree_root}
422
499
 
423
500
  for file_info in analysis_results:
424
- path_obj = Path(file_info.filename)
501
+ # Be defensive: if we ever get back an unexpected object from the
502
+ # worker (e.g. a dict without "error", or something without a
503
+ # ``filename`` attribute), skip it instead of crashing the whole scan.
504
+ filename = getattr(file_info, "filename", None)
505
+ if not filename:
506
+ print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", flush=True)
507
+ continue
508
+
509
+ path_obj = Path(filename)
425
510
  try: rel_path = path_obj.relative_to(root_path)
426
511
  except ValueError: continue
427
512
 
@@ -0,0 +1,232 @@
1
+ import re
2
+ from typing import List
3
+
4
+ import tree_sitter_css as tscss
5
+ import tree_sitter_scss as tsscss
6
+ from tree_sitter import Language, Node, Parser
7
+
8
+ from app.services.tree_sitter_analysis import FileMetrics, FunctionMetrics
9
+
10
+
11
+ CSS_LANGUAGE = Language(tscss.language())
12
+ SCSS_LANGUAGE = Language(tsscss.language())
13
+
14
+
15
+ class CssTreeSitterAnalyzer:
16
+ """
17
+ Analyze CSS and SCSS files using tree-sitter and emit a hierarchy of
18
+ "scopes" compatible with the existing FunctionMetrics / FileMetrics
19
+ pipeline.
20
+
21
+ Goals:
22
+ - For CSS: treat each rule / at-rule block as a scope.
23
+ - For SCSS: in addition to CSS rules, create scopes for:
24
+ - nested rules (selectors inside selectors / at-rules)
25
+ - mixin definitions
26
+ - function definitions
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ self.css_parser = Parser(CSS_LANGUAGE)
31
+ self.scss_parser = Parser(SCSS_LANGUAGE)
32
+
33
+ def analyze_file(self, file_path: str) -> FileMetrics:
34
+ with open(file_path, "rb") as f:
35
+ content = f.read()
36
+
37
+ is_scss = file_path.endswith(".scss")
38
+ parser = self.scss_parser if is_scss else self.css_parser
39
+ tree = parser.parse(content)
40
+
41
+ # Simple non-empty line count for LOC
42
+ lines = content.splitlines()
43
+ nloc = len([l for l in lines if l.strip()])
44
+
45
+ scopes = self._extract_scopes(tree.root_node, content, is_scss=is_scss)
46
+
47
+ average_complexity = (
48
+ sum(f.cyclomatic_complexity for f in scopes) / len(scopes)
49
+ if scopes
50
+ else 0.0
51
+ )
52
+
53
+ total_scope_loc = sum(f.nloc for f in scopes)
54
+ average_scope_length = total_scope_loc / len(scopes) if scopes else 0.0
55
+
56
+ # For now we do not compute any CSS/SCSS-specific metrics; everything
57
+ # other than LOC and the scope hierarchy stays at 0 so the rest of the
58
+ # pipeline can aggregate safely.
59
+ return FileMetrics(
60
+ nloc=nloc,
61
+ average_cyclomatic_complexity=average_complexity,
62
+ function_list=scopes,
63
+ filename=file_path,
64
+ comment_lines=0,
65
+ comment_density=0.0,
66
+ max_nesting_depth=0,
67
+ average_function_length=average_scope_length,
68
+ parameter_count=0,
69
+ todo_count=0,
70
+ classes_count=0,
71
+ tsx_nesting_depth=0,
72
+ tsx_render_branching_count=0,
73
+ tsx_react_use_effect_count=0,
74
+ tsx_anonymous_handler_count=0,
75
+ tsx_prop_count=0,
76
+ ts_any_usage_count=0,
77
+ ts_ignore_count=0,
78
+ ts_import_coupling_count=0,
79
+ tsx_hardcoded_string_volume=0,
80
+ tsx_duplicated_string_count=0,
81
+ ts_type_interface_count=0,
82
+ ts_export_count=0,
83
+ md_data_url_count=0,
84
+ )
85
+
86
+ # ---- Scope extraction ----------------------------------------------------
87
+
88
+ def _extract_scopes(
89
+ self, root: Node, content: bytes, is_scss: bool
90
+ ) -> List[FunctionMetrics]:
91
+ """
92
+ Walk the CSS / SCSS syntax tree and build a hierarchy of scopes.
93
+
94
+ We treat any node that looks like a "rule" / "block" container (rule
95
+ sets, at-rules, etc.) as a scope. For SCSS we also treat mixin and
96
+ function definitions as scopes.
97
+ """
98
+
99
+ def walk(node: Node) -> List[FunctionMetrics]:
100
+ results: List[FunctionMetrics] = []
101
+ for child in node.children:
102
+ if self._node_is_scope(child, is_scss=is_scss):
103
+ scope = self._build_scope_metrics(child, content, is_scss=is_scss)
104
+ scope.children = walk(child)
105
+ results.append(scope)
106
+ else:
107
+ results.extend(walk(child))
108
+ return results
109
+
110
+ return walk(root)
111
+
112
+ def _node_is_scope(self, node: Node, is_scss: bool) -> bool:
113
+ """
114
+ Heuristic: identify rule / at-rule / mixin / function nodes.
115
+
116
+ The exact node type names differ slightly between CSS and SCSS
117
+ grammars, so we rely on a mix of explicit names and substring checks.
118
+ """
119
+
120
+ t = node.type
121
+
122
+ # Common CSS rule / at-rule containers.
123
+ rule_like_types = {
124
+ "rule_set",
125
+ "ruleset",
126
+ "style_rule",
127
+ "qualified_rule",
128
+ "media_rule",
129
+ "supports_rule",
130
+ "keyframes_rule",
131
+ "font_face_rule",
132
+ "page_rule",
133
+ }
134
+ if t in rule_like_types:
135
+ return True
136
+
137
+ # Generic heuristic: anything with "rule" in the name (but not the
138
+ # root stylesheet) is probably a rule-like container.
139
+ if "rule" in t and t not in {"stylesheet"}:
140
+ return True
141
+
142
+ if is_scss:
143
+ # SCSS-specific constructs for functions / mixins and control flow
144
+ # that introduce nested blocks.
145
+ scss_scope_types = {
146
+ "mixin_declaration",
147
+ "mixin_definition",
148
+ "function_declaration",
149
+ "function_definition",
150
+ "if_statement",
151
+ "each_statement",
152
+ "for_statement",
153
+ "while_statement",
154
+ }
155
+ if t in scss_scope_types:
156
+ return True
157
+
158
+ if any(
159
+ key in t
160
+ for key in (
161
+ "mixin",
162
+ "function",
163
+ )
164
+ ):
165
+ return True
166
+
167
+ return False
168
+
169
+ def _build_scope_metrics(
170
+ self, node: Node, content: bytes, is_scss: bool
171
+ ) -> FunctionMetrics:
172
+ start_line = node.start_point.row + 1
173
+ end_line = node.end_point.row + 1
174
+ nloc = max(0, end_line - start_line + 1)
175
+
176
+ name = self._get_scope_name(node, content)
177
+ origin_type = "scss_scope" if is_scss else "css_scope"
178
+
179
+ return FunctionMetrics(
180
+ name=name,
181
+ cyclomatic_complexity=0,
182
+ nloc=nloc,
183
+ start_line=start_line,
184
+ end_line=end_line,
185
+ parameter_count=0,
186
+ max_nesting_depth=0,
187
+ comment_lines=0,
188
+ todo_count=0,
189
+ ts_type_interface_count=0,
190
+ md_data_url_count=0,
191
+ origin_type=origin_type,
192
+ is_jsx_container=False,
193
+ )
194
+
195
+ # ---- Helpers -------------------------------------------------------------
196
+
197
+ def _get_scope_name(self, node: Node, content: bytes, max_len: int = 80) -> str:
198
+ """
199
+ Derive a human-friendly name for a CSS / SCSS scope by taking the
200
+ first line of the node text up to the opening '{', if present.
201
+ """
202
+
203
+ # Use byte offsets so we don't depend on child layout details.
204
+ src = content[node.start_byte : node.end_byte].decode(
205
+ "utf-8", errors="ignore"
206
+ )
207
+
208
+ # Take the first non-empty line.
209
+ first_line = ""
210
+ for line in src.splitlines():
211
+ stripped = line.strip()
212
+ if stripped:
213
+ first_line = stripped
214
+ break
215
+
216
+ if not first_line:
217
+ first_line = node.type
218
+
219
+ # Trim everything after '{' so selectors / rule headers look clean.
220
+ brace_index = first_line.find("{")
221
+ if brace_index != -1:
222
+ first_line = first_line[:brace_index].rstrip()
223
+
224
+ # Collapse internal whitespace to keep long selectors readable.
225
+ first_line = re.sub(r"\s+", " ", first_line).strip()
226
+
227
+ if len(first_line) > max_len:
228
+ first_line = first_line[: max_len - 1] + "…"
229
+
230
+ return first_line or node.type
231
+
232
+
@@ -0,0 +1,89 @@
1
+ import json
2
+ from typing import List
3
+
4
+ from app.services.tree_sitter_analysis import FileMetrics, FunctionMetrics
5
+
6
+
7
+ class NotebookAnalyzer:
8
+ """
9
+ Lightweight analysis pipeline for Jupyter notebooks.
10
+
11
+ The goal is to:
12
+ - Treat each cell as a "scope" so it can appear as a child in the treemap.
13
+ - Count LOC **only** from the `source` field of each cell (code/markdown/raw).
14
+ We explicitly ignore any `outputs`/execution metadata so that large output-
15
+ heavy notebooks don't dominate the treemap.
16
+ """
17
+
18
+ def analyze_file(self, file_path: str) -> FileMetrics:
19
+ with open(file_path, "r", encoding="utf-8") as f:
20
+ data = json.load(f)
21
+
22
+ cells = data.get("cells") or []
23
+
24
+ function_list: List[FunctionMetrics] = []
25
+ current_line = 1
26
+ total_nloc = 0
27
+
28
+ for idx, cell in enumerate(cells):
29
+ cell_type = cell.get("cell_type", "cell")
30
+ source = cell.get("source", "")
31
+
32
+ # `source` can be a list of lines or a single string.
33
+ if isinstance(source, list):
34
+ text = "".join(source)
35
+ else:
36
+ text = str(source)
37
+
38
+ lines = text.splitlines()
39
+ # Count only non-empty lines of actual content.
40
+ non_empty_lines = [ln for ln in lines if ln.strip()]
41
+ loc = len(non_empty_lines)
42
+
43
+ if loc <= 0:
44
+ # Skip zero-LOC cells entirely for treemap sizing purposes.
45
+ continue
46
+
47
+ start_line = current_line
48
+ end_line = current_line + loc - 1
49
+ current_line = end_line + 1
50
+ total_nloc += loc
51
+
52
+ name = f"[{cell_type}] cell {idx + 1}"
53
+
54
+ cell_metrics = FunctionMetrics(
55
+ name=name,
56
+ cyclomatic_complexity=0,
57
+ nloc=loc,
58
+ start_line=start_line,
59
+ end_line=end_line,
60
+ )
61
+ function_list.append(cell_metrics)
62
+
63
+ # Aggregate simple file-level metrics.
64
+ average_complexity = (
65
+ sum(f.cyclomatic_complexity for f in function_list) / len(function_list)
66
+ if function_list
67
+ else 0.0
68
+ )
69
+
70
+ average_function_length = (
71
+ total_nloc / len(function_list) if function_list else 0.0
72
+ )
73
+
74
+ return FileMetrics(
75
+ nloc=total_nloc,
76
+ average_cyclomatic_complexity=average_complexity,
77
+ function_list=function_list,
78
+ filename=file_path,
79
+ comment_lines=0,
80
+ comment_density=0.0,
81
+ max_nesting_depth=0,
82
+ average_function_length=average_function_length,
83
+ parameter_count=0,
84
+ todo_count=0,
85
+ classes_count=0,
86
+ # TS/TSX-specific metrics remain at their defaults (all zeros) for notebooks.
87
+ )
88
+
89
+