srcly 0.1.32__tar.gz → 0.1.34__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 (364) hide show
  1. {srcly-0.1.32 → srcly-0.1.34}/PKG-INFO +2 -2
  2. {srcly-0.1.32 → srcly-0.1.34}/app/config.py +3 -0
  3. {srcly-0.1.32 → srcly-0.1.34}/app/main.py +52 -1
  4. {srcly-0.1.32 → srcly-0.1.34}/app/models.py +0 -56
  5. {srcly-0.1.32 → srcly-0.1.34}/app/routers/analysis.py +17 -84
  6. srcly-0.1.34/app/routers/scans.py +121 -0
  7. {srcly-0.1.32 → srcly-0.1.34}/app/run.py +17 -1
  8. {srcly-0.1.32 → srcly-0.1.34}/app/services/analysis.py +149 -164
  9. srcly-0.1.34/app/services/scan_jobs.py +217 -0
  10. srcly-0.1.34/app/services/scan_progress.py +39 -0
  11. srcly-0.1.34/app/services/scan_workers.py +306 -0
  12. srcly-0.1.34/app/static/assets/CodeModal-BBkwbpez.js +29 -0
  13. srcly-0.1.34/app/static/assets/DependencyGraph-9gIcsNut.js +1 -0
  14. srcly-0.1.32/app/static/assets/shellscript-Yzrsuije.js → srcly-0.1.34/app/static/assets/bash-DgJyDXW6.js +1 -1
  15. srcly-0.1.32/app/static/assets/css-DPfMkruS.js → srcly-0.1.34/app/static/assets/css-PZBBrkI0.js +1 -1
  16. srcly-0.1.34/app/static/assets/dependencyForce.worker-WVMkyynK.js +1 -0
  17. srcly-0.1.34/app/static/assets/elk-worker.min-DGCccKuH.js +6386 -0
  18. srcly-0.1.34/app/static/assets/github-light-Rcb2q3L5.js +1 -0
  19. srcly-0.1.34/app/static/assets/highlight.worker-C64dOQ1-.js +14 -0
  20. srcly-0.1.32/app/static/assets/html-GMplVEZG.js → srcly-0.1.34/app/static/assets/html-DOf0XnR2.js +1 -1
  21. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  22. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  23. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  24. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  25. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-600-normal-CTOM6hUh.woff2 +0 -0
  26. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-600-normal-fLZuRloM.woff +0 -0
  27. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  28. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  29. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  30. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  31. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-600-normal-9HEixskS.woff +0 -0
  32. srcly-0.1.34/app/static/assets/ibm-plex-mono-cyrillic-ext-600-normal-V-xxqcpd.woff2 +0 -0
  33. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  34. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  35. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  36. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  37. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  38. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  39. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  40. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  41. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  42. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  43. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-600-normal-D38SheWl.woff2 +0 -0
  44. srcly-0.1.34/app/static/assets/ibm-plex-mono-latin-ext-600-normal-DmB0ttJJ.woff +0 -0
  45. srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  46. srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  47. srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  48. srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  49. srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-600-normal-D2EvbN8M.woff2 +0 -0
  50. srcly-0.1.34/app/static/assets/ibm-plex-mono-vietnamese-600-normal-iLQfcSjf.woff +0 -0
  51. srcly-0.1.34/app/static/assets/index-64jBfyHS.css +1 -0
  52. srcly-0.1.34/app/static/assets/index-CPpALji7.js +2 -0
  53. srcly-0.1.34/app/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  54. srcly-0.1.34/app/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  55. srcly-0.1.34/app/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  56. srcly-0.1.34/app/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  57. srcly-0.1.34/app/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  58. srcly-0.1.34/app/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  59. srcly-0.1.34/app/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  60. srcly-0.1.32/app/static/assets/javascript-wDzz0qaB.js → srcly-0.1.34/app/static/assets/javascript-WyGyM1wV.js +1 -1
  61. srcly-0.1.32/app/static/assets/json-Cp-IABpG.js → srcly-0.1.34/app/static/assets/json-B8JryHcM.js +1 -1
  62. srcly-0.1.32/app/static/assets/jsx-g9-lgVsj.js → srcly-0.1.34/app/static/assets/jsx-8cLv2Six.js +1 -1
  63. srcly-0.1.32/app/static/assets/markdown-Cvjx9yec.js → srcly-0.1.34/app/static/assets/markdown-DX-TNHnc.js +1 -1
  64. srcly-0.1.32/app/static/assets/python-B6aJPvgy.js → srcly-0.1.34/app/static/assets/python-B6Ihcp-E.js +1 -1
  65. srcly-0.1.32/app/static/assets/toml-vGWfd6FD.js → srcly-0.1.34/app/static/assets/toml-Ms459DBw.js +1 -1
  66. srcly-0.1.32/app/static/assets/tsx-COt5Ahok.js → srcly-0.1.34/app/static/assets/tsx-D6116Ycy.js +1 -1
  67. srcly-0.1.32/app/static/assets/typescript-BPQ3VLAy.js → srcly-0.1.34/app/static/assets/typescript-BqvgUolh.js +1 -1
  68. srcly-0.1.32/app/static/assets/yaml-Buea-lGh.js → srcly-0.1.34/app/static/assets/yaml-Bopit2L3.js +1 -1
  69. srcly-0.1.34/app/static/favicon.svg +9 -0
  70. srcly-0.1.34/app/static/index.html +18 -0
  71. {srcly-0.1.32 → srcly-0.1.34}/pyproject.toml +1 -1
  72. srcly-0.1.32/app/services/data_flow_analysis.py +0 -875
  73. srcly-0.1.32/app/services/focus_overlay.py +0 -1239
  74. srcly-0.1.32/app/static/assets/abap-BdImnpbu.js +0 -1
  75. srcly-0.1.32/app/static/assets/actionscript-3-CfeIJUat.js +0 -1
  76. srcly-0.1.32/app/static/assets/ada-bCR0ucgS.js +0 -1
  77. srcly-0.1.32/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -1
  78. srcly-0.1.32/app/static/assets/angular-html-CU67Zn6k.js +0 -1
  79. srcly-0.1.32/app/static/assets/angular-ts-BwZT4LLn.js +0 -1
  80. srcly-0.1.32/app/static/assets/apache-Pmp26Uib.js +0 -1
  81. srcly-0.1.32/app/static/assets/apex-DhZLUxFE.js +0 -1
  82. srcly-0.1.32/app/static/assets/apl-dKokRX4l.js +0 -1
  83. srcly-0.1.32/app/static/assets/applescript-Co6uUVPk.js +0 -1
  84. srcly-0.1.32/app/static/assets/ara-BRHolxvo.js +0 -1
  85. srcly-0.1.32/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -1
  86. srcly-0.1.32/app/static/assets/asm-D_Q5rh1f.js +0 -1
  87. srcly-0.1.32/app/static/assets/astro-CbQHKStN.js +0 -1
  88. srcly-0.1.32/app/static/assets/aurora-x-D-2ljcwZ.js +0 -1
  89. srcly-0.1.32/app/static/assets/awk-DMzUqQB5.js +0 -1
  90. srcly-0.1.32/app/static/assets/ayu-dark-Cv9koXgw.js +0 -1
  91. srcly-0.1.32/app/static/assets/ballerina-BFfxhgS-.js +0 -1
  92. srcly-0.1.32/app/static/assets/bat-BkioyH1T.js +0 -1
  93. srcly-0.1.32/app/static/assets/beancount-k_qm7-4y.js +0 -1
  94. srcly-0.1.32/app/static/assets/berry-D08WgyRC.js +0 -1
  95. srcly-0.1.32/app/static/assets/bibtex-CHM0blh-.js +0 -1
  96. srcly-0.1.32/app/static/assets/bicep-Bmn6On1c.js +0 -1
  97. srcly-0.1.32/app/static/assets/blade-DVc8C-J4.js +0 -1
  98. srcly-0.1.32/app/static/assets/bsl-BO_Y6i37.js +0 -1
  99. srcly-0.1.32/app/static/assets/c-BIGW1oBm.js +0 -1
  100. srcly-0.1.32/app/static/assets/cadence-Bv_4Rxtq.js +0 -1
  101. srcly-0.1.32/app/static/assets/cairo-KRGpt6FW.js +0 -1
  102. srcly-0.1.32/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -1
  103. srcly-0.1.32/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -1
  104. srcly-0.1.32/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -1
  105. srcly-0.1.32/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -1
  106. srcly-0.1.32/app/static/assets/clarity-D53aC0YG.js +0 -1
  107. srcly-0.1.32/app/static/assets/clojure-P80f7IUj.js +0 -1
  108. srcly-0.1.32/app/static/assets/cmake-D1j8_8rp.js +0 -1
  109. srcly-0.1.32/app/static/assets/cobol-nwyudZeR.js +0 -1
  110. srcly-0.1.32/app/static/assets/codeowners-Bp6g37R7.js +0 -1
  111. srcly-0.1.32/app/static/assets/codeql-DsOJ9woJ.js +0 -1
  112. srcly-0.1.32/app/static/assets/coffee-Ch7k5sss.js +0 -1
  113. srcly-0.1.32/app/static/assets/common-lisp-Cg-RD9OK.js +0 -1
  114. srcly-0.1.32/app/static/assets/coq-DkFqJrB1.js +0 -1
  115. srcly-0.1.32/app/static/assets/cpp-CofmeUqb.js +0 -1
  116. srcly-0.1.32/app/static/assets/crystal-tKQVLTB8.js +0 -1
  117. srcly-0.1.32/app/static/assets/csharp-CX12Zw3r.js +0 -1
  118. srcly-0.1.32/app/static/assets/csv-fuZLfV_i.js +0 -1
  119. srcly-0.1.32/app/static/assets/cue-D82EKSYY.js +0 -1
  120. srcly-0.1.32/app/static/assets/cypher-COkxafJQ.js +0 -1
  121. srcly-0.1.32/app/static/assets/d-85-TOEBH.js +0 -1
  122. srcly-0.1.32/app/static/assets/dark-plus-eOWES_5F.js +0 -1
  123. srcly-0.1.32/app/static/assets/dart-CF10PKvl.js +0 -1
  124. srcly-0.1.32/app/static/assets/dax-CEL-wOlO.js +0 -1
  125. srcly-0.1.32/app/static/assets/desktop-BmXAJ9_W.js +0 -1
  126. srcly-0.1.32/app/static/assets/diff-D97Zzqfu.js +0 -1
  127. srcly-0.1.32/app/static/assets/docker-BcOcwvcX.js +0 -1
  128. srcly-0.1.32/app/static/assets/dotenv-Da5cRb03.js +0 -1
  129. srcly-0.1.32/app/static/assets/dracula-BzJJZx-M.js +0 -1
  130. srcly-0.1.32/app/static/assets/dracula-soft-BXkSAIEj.js +0 -1
  131. srcly-0.1.32/app/static/assets/dream-maker-BtqSS_iP.js +0 -1
  132. srcly-0.1.32/app/static/assets/edge-BkV0erSs.js +0 -1
  133. srcly-0.1.32/app/static/assets/elixir-CDX3lj18.js +0 -1
  134. srcly-0.1.32/app/static/assets/elm-DbKCFpqz.js +0 -1
  135. srcly-0.1.32/app/static/assets/emacs-lisp-C9XAeP06.js +0 -1
  136. srcly-0.1.32/app/static/assets/erb-BOJIQeun.js +0 -1
  137. srcly-0.1.32/app/static/assets/erlang-DsQrWhSR.js +0 -1
  138. srcly-0.1.32/app/static/assets/everforest-dark-BgDCqdQA.js +0 -1
  139. srcly-0.1.32/app/static/assets/everforest-light-C8M2exoo.js +0 -1
  140. srcly-0.1.32/app/static/assets/fennel-BYunw83y.js +0 -1
  141. srcly-0.1.32/app/static/assets/fish-BvzEVeQv.js +0 -1
  142. srcly-0.1.32/app/static/assets/fluent-C4IJs8-o.js +0 -1
  143. srcly-0.1.32/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -1
  144. srcly-0.1.32/app/static/assets/fortran-free-form-D22FLkUw.js +0 -1
  145. srcly-0.1.32/app/static/assets/fsharp-CXgrBDvD.js +0 -1
  146. srcly-0.1.32/app/static/assets/gdresource-B7Tvp0Sc.js +0 -1
  147. srcly-0.1.32/app/static/assets/gdscript-DTMYz4Jt.js +0 -1
  148. srcly-0.1.32/app/static/assets/gdshader-DkwncUOv.js +0 -1
  149. srcly-0.1.32/app/static/assets/genie-D0YGMca9.js +0 -1
  150. srcly-0.1.32/app/static/assets/gherkin-DyxjwDmM.js +0 -1
  151. srcly-0.1.32/app/static/assets/git-commit-F4YmCXRG.js +0 -1
  152. srcly-0.1.32/app/static/assets/git-rebase-r7XF79zn.js +0 -1
  153. srcly-0.1.32/app/static/assets/github-dark-DHJKELXO.js +0 -1
  154. srcly-0.1.32/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -1
  155. srcly-0.1.32/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  156. srcly-0.1.32/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  157. srcly-0.1.32/app/static/assets/github-light-DAi9KRSo.js +0 -1
  158. srcly-0.1.32/app/static/assets/github-light-default-D7oLnXFd.js +0 -1
  159. srcly-0.1.32/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  160. srcly-0.1.32/app/static/assets/gleam-BspZqrRM.js +0 -1
  161. srcly-0.1.32/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -1
  162. srcly-0.1.32/app/static/assets/glimmer-ts-U6CK756n.js +0 -1
  163. srcly-0.1.32/app/static/assets/glsl-DplSGwfg.js +0 -1
  164. srcly-0.1.32/app/static/assets/gnuplot-DdkO51Og.js +0 -1
  165. srcly-0.1.32/app/static/assets/go-Dn2_MT6a.js +0 -1
  166. srcly-0.1.32/app/static/assets/graphql-ChdNCCLP.js +0 -1
  167. srcly-0.1.32/app/static/assets/groovy-gcz8RCvz.js +0 -1
  168. srcly-0.1.32/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
  169. srcly-0.1.32/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
  170. srcly-0.1.32/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
  171. srcly-0.1.32/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
  172. srcly-0.1.32/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
  173. srcly-0.1.32/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
  174. srcly-0.1.32/app/static/assets/hack-CaT9iCJl.js +0 -1
  175. srcly-0.1.32/app/static/assets/haml-B8DHNrY2.js +0 -1
  176. srcly-0.1.32/app/static/assets/handlebars-BL8al0AC.js +0 -1
  177. srcly-0.1.32/app/static/assets/haskell-Df6bDoY_.js +0 -1
  178. srcly-0.1.32/app/static/assets/haxe-CzTSHFRz.js +0 -1
  179. srcly-0.1.32/app/static/assets/hcl-BWvSN4gD.js +0 -1
  180. srcly-0.1.32/app/static/assets/hjson-D5-asLiD.js +0 -1
  181. srcly-0.1.32/app/static/assets/hlsl-D3lLCCz7.js +0 -1
  182. srcly-0.1.32/app/static/assets/houston-DnULxvSX.js +0 -1
  183. srcly-0.1.32/app/static/assets/html-derivative-BFtXZ54Q.js +0 -1
  184. srcly-0.1.32/app/static/assets/http-jrhK8wxY.js +0 -1
  185. srcly-0.1.32/app/static/assets/hurl-irOxFIW8.js +0 -1
  186. srcly-0.1.32/app/static/assets/hxml-Bvhsp5Yf.js +0 -1
  187. srcly-0.1.32/app/static/assets/hy-DFXneXwc.js +0 -1
  188. srcly-0.1.32/app/static/assets/imba-DGztddWO.js +0 -1
  189. srcly-0.1.32/app/static/assets/index--Q3ju03c.js +0 -66
  190. srcly-0.1.32/app/static/assets/index-DOSG0br7.css +0 -1
  191. srcly-0.1.32/app/static/assets/ini-BEwlwnbL.js +0 -1
  192. srcly-0.1.32/app/static/assets/java-CylS5w8V.js +0 -1
  193. srcly-0.1.32/app/static/assets/jinja-4LBKfQ-Z.js +0 -1
  194. srcly-0.1.32/app/static/assets/jison-wvAkD_A8.js +0 -1
  195. srcly-0.1.32/app/static/assets/json5-C9tS-k6U.js +0 -1
  196. srcly-0.1.32/app/static/assets/jsonc-Des-eS-w.js +0 -1
  197. srcly-0.1.32/app/static/assets/jsonl-DcaNXYhu.js +0 -1
  198. srcly-0.1.32/app/static/assets/jsonnet-DFQXde-d.js +0 -1
  199. srcly-0.1.32/app/static/assets/jssm-C2t-YnRu.js +0 -1
  200. srcly-0.1.32/app/static/assets/julia-C8NyazO9.js +0 -1
  201. srcly-0.1.32/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
  202. srcly-0.1.32/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
  203. srcly-0.1.32/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -1
  204. srcly-0.1.32/app/static/assets/kdl-DV7GczEv.js +0 -1
  205. srcly-0.1.32/app/static/assets/kotlin-BdnUsdx6.js +0 -1
  206. srcly-0.1.32/app/static/assets/kusto-BvAqAH-y.js +0 -1
  207. srcly-0.1.32/app/static/assets/laserwave-DUszq2jm.js +0 -1
  208. srcly-0.1.32/app/static/assets/latex-BUKiar2Z.js +0 -1
  209. srcly-0.1.32/app/static/assets/lean-DP1Csr6i.js +0 -1
  210. srcly-0.1.32/app/static/assets/less-B1dDrJ26.js +0 -1
  211. srcly-0.1.32/app/static/assets/light-plus-B7mTdjB0.js +0 -1
  212. srcly-0.1.32/app/static/assets/liquid-DYVedYrR.js +0 -1
  213. srcly-0.1.32/app/static/assets/llvm-BtvRca6l.js +0 -1
  214. srcly-0.1.32/app/static/assets/log-2UxHyX5q.js +0 -1
  215. srcly-0.1.32/app/static/assets/logo-BtOb2qkB.js +0 -1
  216. srcly-0.1.32/app/static/assets/lua-BbnMAYS6.js +0 -1
  217. srcly-0.1.32/app/static/assets/luau-CXu1NL6O.js +0 -1
  218. srcly-0.1.32/app/static/assets/make-CHLpvVh8.js +0 -1
  219. srcly-0.1.32/app/static/assets/marko-CPi9NSCl.js +0 -1
  220. srcly-0.1.32/app/static/assets/material-theme-D5KoaKCx.js +0 -1
  221. srcly-0.1.32/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -1
  222. srcly-0.1.32/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  223. srcly-0.1.32/app/static/assets/material-theme-ocean-CyktbL80.js +0 -1
  224. srcly-0.1.32/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  225. srcly-0.1.32/app/static/assets/matlab-D7o27uSR.js +0 -1
  226. srcly-0.1.32/app/static/assets/mdc-DUICxH0z.js +0 -1
  227. srcly-0.1.32/app/static/assets/mdx-Cmh6b_Ma.js +0 -1
  228. srcly-0.1.32/app/static/assets/mermaid-DKYwYmdq.js +0 -1
  229. srcly-0.1.32/app/static/assets/min-dark-CafNBF8u.js +0 -1
  230. srcly-0.1.32/app/static/assets/min-light-CTRr51gU.js +0 -1
  231. srcly-0.1.32/app/static/assets/mipsasm-CKIfxQSi.js +0 -1
  232. srcly-0.1.32/app/static/assets/mojo-1DNp92w6.js +0 -1
  233. srcly-0.1.32/app/static/assets/monokai-D4h5O-jR.js +0 -1
  234. srcly-0.1.32/app/static/assets/move-Bu9oaDYs.js +0 -1
  235. srcly-0.1.32/app/static/assets/narrat-DRg8JJMk.js +0 -1
  236. srcly-0.1.32/app/static/assets/nextflow-CUEJCptM.js +0 -1
  237. srcly-0.1.32/app/static/assets/nginx-DknmC5AR.js +0 -1
  238. srcly-0.1.32/app/static/assets/night-owl-C39BiMTA.js +0 -1
  239. srcly-0.1.32/app/static/assets/nim-CVrawwO9.js +0 -1
  240. srcly-0.1.32/app/static/assets/nix-BbRYJGeE.js +0 -1
  241. srcly-0.1.32/app/static/assets/nord-Ddv68eIx.js +0 -1
  242. srcly-0.1.32/app/static/assets/nushell-C-sUppwS.js +0 -1
  243. srcly-0.1.32/app/static/assets/objective-c-DXmwc3jG.js +0 -1
  244. srcly-0.1.32/app/static/assets/objective-cpp-CLxacb5B.js +0 -1
  245. srcly-0.1.32/app/static/assets/ocaml-C0hk2d4L.js +0 -1
  246. srcly-0.1.32/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -1
  247. srcly-0.1.32/app/static/assets/one-light-PoHY5YXO.js +0 -1
  248. srcly-0.1.32/app/static/assets/pascal-D93ZcfNL.js +0 -1
  249. srcly-0.1.32/app/static/assets/perl-C0TMdlhV.js +0 -1
  250. srcly-0.1.32/app/static/assets/php-CDn_0X-4.js +0 -1
  251. srcly-0.1.32/app/static/assets/pkl-u5AG7uiY.js +0 -1
  252. srcly-0.1.32/app/static/assets/plastic-3e1v2bzS.js +0 -1
  253. srcly-0.1.32/app/static/assets/plsql-ChMvpjG-.js +0 -1
  254. srcly-0.1.32/app/static/assets/po-BTJTHyun.js +0 -1
  255. srcly-0.1.32/app/static/assets/poimandres-CS3Unz2-.js +0 -1
  256. srcly-0.1.32/app/static/assets/polar-C0HS_06l.js +0 -1
  257. srcly-0.1.32/app/static/assets/postcss-CXtECtnM.js +0 -1
  258. srcly-0.1.32/app/static/assets/powerquery-CEu0bR-o.js +0 -1
  259. srcly-0.1.32/app/static/assets/powershell-Dpen1YoG.js +0 -1
  260. srcly-0.1.32/app/static/assets/prisma-Dd19v3D-.js +0 -1
  261. srcly-0.1.32/app/static/assets/prolog-CbFg5uaA.js +0 -1
  262. srcly-0.1.32/app/static/assets/proto-DyJlTyXw.js +0 -1
  263. srcly-0.1.32/app/static/assets/pug-CGlum2m_.js +0 -1
  264. srcly-0.1.32/app/static/assets/puppet-BMWR74SV.js +0 -1
  265. srcly-0.1.32/app/static/assets/purescript-CklMAg4u.js +0 -1
  266. srcly-0.1.32/app/static/assets/qml-3beO22l8.js +0 -1
  267. srcly-0.1.32/app/static/assets/qmldir-C8lEn-DE.js +0 -1
  268. srcly-0.1.32/app/static/assets/qss-IeuSbFQv.js +0 -1
  269. srcly-0.1.32/app/static/assets/r-DiinP2Uv.js +0 -1
  270. srcly-0.1.32/app/static/assets/racket-BqYA7rlc.js +0 -1
  271. srcly-0.1.32/app/static/assets/raku-DXvB9xmW.js +0 -1
  272. srcly-0.1.32/app/static/assets/razor-WgofotgN.js +0 -1
  273. srcly-0.1.32/app/static/assets/red-bN70gL4F.js +0 -1
  274. srcly-0.1.32/app/static/assets/reg-C-SQnVFl.js +0 -1
  275. srcly-0.1.32/app/static/assets/regexp-CDVJQ6XC.js +0 -1
  276. srcly-0.1.32/app/static/assets/rel-C3B-1QV4.js +0 -1
  277. srcly-0.1.32/app/static/assets/riscv-BM1_JUlF.js +0 -1
  278. srcly-0.1.32/app/static/assets/rose-pine-BHrmToEH.js +0 -1
  279. srcly-0.1.32/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -1
  280. srcly-0.1.32/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -1
  281. srcly-0.1.32/app/static/assets/rosmsg-BJDFO7_C.js +0 -1
  282. srcly-0.1.32/app/static/assets/rst-B0xPkSld.js +0 -1
  283. srcly-0.1.32/app/static/assets/ruby-BvKwtOVI.js +0 -1
  284. srcly-0.1.32/app/static/assets/rust-B1yitclQ.js +0 -1
  285. srcly-0.1.32/app/static/assets/sas-cz2c8ADy.js +0 -1
  286. srcly-0.1.32/app/static/assets/sass-Cj5Yp3dK.js +0 -1
  287. srcly-0.1.32/app/static/assets/scala-C151Ov-r.js +0 -1
  288. srcly-0.1.32/app/static/assets/scheme-C98Dy4si.js +0 -1
  289. srcly-0.1.32/app/static/assets/scss-OYdSNvt2.js +0 -1
  290. srcly-0.1.32/app/static/assets/sdbl-DVxCFoDh.js +0 -1
  291. srcly-0.1.32/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -1
  292. srcly-0.1.32/app/static/assets/shellsession-BADoaaVG.js +0 -1
  293. srcly-0.1.32/app/static/assets/slack-dark-BthQWCQV.js +0 -1
  294. srcly-0.1.32/app/static/assets/slack-ochin-DqwNpetd.js +0 -1
  295. srcly-0.1.32/app/static/assets/smalltalk-BERRCDM3.js +0 -1
  296. srcly-0.1.32/app/static/assets/snazzy-light-Bw305WKR.js +0 -1
  297. srcly-0.1.32/app/static/assets/solarized-dark-DXbdFlpD.js +0 -1
  298. srcly-0.1.32/app/static/assets/solarized-light-L9t79GZl.js +0 -1
  299. srcly-0.1.32/app/static/assets/solidity-BbcW6ACK.js +0 -1
  300. srcly-0.1.32/app/static/assets/soy-Brmx7dQM.js +0 -1
  301. srcly-0.1.32/app/static/assets/sparql-rVzFXLq3.js +0 -1
  302. srcly-0.1.32/app/static/assets/splunk-BtCnVYZw.js +0 -1
  303. srcly-0.1.32/app/static/assets/sql-BLtJtn59.js +0 -1
  304. srcly-0.1.32/app/static/assets/ssh-config-_ykCGR6B.js +0 -1
  305. srcly-0.1.32/app/static/assets/stata-BH5u7GGu.js +0 -1
  306. srcly-0.1.32/app/static/assets/stylus-BEDo0Tqx.js +0 -1
  307. srcly-0.1.32/app/static/assets/svelte-3Dk4HxPD.js +0 -1
  308. srcly-0.1.32/app/static/assets/swift-Dg5xB15N.js +0 -1
  309. srcly-0.1.32/app/static/assets/synthwave-84-CbfX1IO0.js +0 -1
  310. srcly-0.1.32/app/static/assets/system-verilog-CnnmHF94.js +0 -1
  311. srcly-0.1.32/app/static/assets/systemd-4A_iFExJ.js +0 -1
  312. srcly-0.1.32/app/static/assets/talonscript-CkByrt1z.js +0 -1
  313. srcly-0.1.32/app/static/assets/tasl-QIJgUcNo.js +0 -1
  314. srcly-0.1.32/app/static/assets/tcl-dwOrl1Do.js +0 -1
  315. srcly-0.1.32/app/static/assets/templ-W15q3VgB.js +0 -1
  316. srcly-0.1.32/app/static/assets/terraform-BETggiCN.js +0 -1
  317. srcly-0.1.32/app/static/assets/tex-Cppo0RY3.js +0 -1
  318. srcly-0.1.32/app/static/assets/tokyo-night-hegEt444.js +0 -1
  319. srcly-0.1.32/app/static/assets/ts-tags-zn1MmPIZ.js +0 -1
  320. srcly-0.1.32/app/static/assets/tsv-B_m7g4N7.js +0 -1
  321. srcly-0.1.32/app/static/assets/turtle-BsS91CYL.js +0 -1
  322. srcly-0.1.32/app/static/assets/twig-CO9l9SDP.js +0 -1
  323. srcly-0.1.32/app/static/assets/typespec-Df68jz8_.js +0 -1
  324. srcly-0.1.32/app/static/assets/typst-DHCkPAjA.js +0 -1
  325. srcly-0.1.32/app/static/assets/v-BcVCzyr7.js +0 -1
  326. srcly-0.1.32/app/static/assets/vala-CsfeWuGM.js +0 -1
  327. srcly-0.1.32/app/static/assets/vb-D17OF-Vu.js +0 -1
  328. srcly-0.1.32/app/static/assets/verilog-BQ8w6xss.js +0 -1
  329. srcly-0.1.32/app/static/assets/vesper-DU1UobuO.js +0 -1
  330. srcly-0.1.32/app/static/assets/vhdl-CeAyd5Ju.js +0 -1
  331. srcly-0.1.32/app/static/assets/viml-CJc9bBzg.js +0 -1
  332. srcly-0.1.32/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -1
  333. srcly-0.1.32/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -1
  334. srcly-0.1.32/app/static/assets/vitesse-light-CVO1_9PV.js +0 -1
  335. srcly-0.1.32/app/static/assets/vue-CCoi5OLL.js +0 -1
  336. srcly-0.1.32/app/static/assets/vue-html-DAAvJJDi.js +0 -1
  337. srcly-0.1.32/app/static/assets/vue-vine-_Ih-lPRR.js +0 -1
  338. srcly-0.1.32/app/static/assets/vyper-CDx5xZoG.js +0 -1
  339. srcly-0.1.32/app/static/assets/wasm-MzD3tlZU.js +0 -1
  340. srcly-0.1.32/app/static/assets/wenyan-BV7otONQ.js +0 -1
  341. srcly-0.1.32/app/static/assets/wgsl-Dx-B1_4e.js +0 -1
  342. srcly-0.1.32/app/static/assets/wikitext-BhOHFoWU.js +0 -1
  343. srcly-0.1.32/app/static/assets/wit-5i3qLPDT.js +0 -1
  344. srcly-0.1.32/app/static/assets/wolfram-lXgVvXCa.js +0 -1
  345. srcly-0.1.32/app/static/assets/xml-sdJ4AIDG.js +0 -1
  346. srcly-0.1.32/app/static/assets/xsl-CtQFsRM5.js +0 -1
  347. srcly-0.1.32/app/static/assets/zenscript-DVFEvuxE.js +0 -1
  348. srcly-0.1.32/app/static/assets/zig-VOosw3JB.js +0 -1
  349. srcly-0.1.32/app/static/index.html +0 -17
  350. srcly-0.1.32/app/static/vite.svg +0 -1
  351. {srcly-0.1.32 → srcly-0.1.34}/.gitignore +0 -0
  352. {srcly-0.1.32 → srcly-0.1.34}/README.md +0 -0
  353. {srcly-0.1.32 → srcly-0.1.34}/app/.gitignore +0 -0
  354. {srcly-0.1.32 → srcly-0.1.34}/app/__init__.py +0 -0
  355. {srcly-0.1.32 → srcly-0.1.34}/app/routers/files.py +0 -0
  356. {srcly-0.1.32 → srcly-0.1.34}/app/services/analysis_types.py +0 -0
  357. {srcly-0.1.32 → srcly-0.1.34}/app/services/cache.py +0 -0
  358. {srcly-0.1.32 → srcly-0.1.34}/app/services/css/css_analysis.py +0 -0
  359. {srcly-0.1.32 → srcly-0.1.34}/app/services/ipynb/ipynb_analysis.py +0 -0
  360. {srcly-0.1.32 → srcly-0.1.34}/app/services/markdown/markdown_analysis.py +0 -0
  361. {srcly-0.1.32 → srcly-0.1.34}/app/services/python/python_analysis.py +0 -0
  362. {srcly-0.1.32 → srcly-0.1.34}/app/services/reporting.py +0 -0
  363. {srcly-0.1.32 → srcly-0.1.34}/app/services/typescript/typescript_analysis.py +0 -0
  364. {srcly-0.1.32 → srcly-0.1.34}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: srcly
3
- Version: 0.1.32
3
+ Version: 0.1.34
4
4
  Summary: Interactive codebase treemap and metrics viewer.
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: fastapi>=0.121.3
@@ -19,6 +19,9 @@ IGNORE_DIRS: Set[str] = {
19
19
  }
20
20
 
21
21
  IGNORE_FILES: Set[str] = {
22
+ # In git worktrees and submodules `.git` is a pointer file, not a directory.
23
+ ".git",
24
+ ".DS_Store",
22
25
  "package-lock.json",
23
26
  "yarn.lock",
24
27
  "pnpm-lock.yaml",
@@ -1,16 +1,66 @@
1
+ from contextlib import asynccontextmanager
1
2
  from fastapi import FastAPI
2
3
  from fastapi.middleware.cors import CORSMiddleware
3
4
  from fastapi.staticfiles import StaticFiles
4
5
  from importlib import resources
5
6
  from pathlib import Path
6
7
  import os
8
+ import signal
9
+ import threading
10
+
11
+ from app.routers import analysis, files, scans
12
+ from app.services.scan_workers import cancel_all_scans
13
+
14
+ _SHUTDOWN_SIGNALS = (signal.SIGINT, signal.SIGTERM)
15
+
16
+
17
+ def _install_scan_cancelling_signal_handlers() -> dict:
18
+ """
19
+ Chain a "cancel running scans" step in front of the server's own
20
+ SIGINT/SIGTERM handlers.
21
+
22
+ Uvicorn handles Ctrl+C by waiting for in-flight requests to finish. A scan
23
+ can run for minutes, so without this the first Ctrl+C appears to do
24
+ nothing. Cancelling scans first lets their requests end promptly and the
25
+ normal graceful shutdown proceed. Works for `srcly` and plain `uvicorn`.
26
+ """
27
+ if threading.current_thread() is not threading.main_thread():
28
+ return {}
29
+
30
+ previous: dict = {}
31
+ for sig in _SHUTDOWN_SIGNALS:
32
+ prior = signal.getsignal(sig)
33
+
34
+ def handler(signum, frame, _prior=prior):
35
+ cancel_all_scans()
36
+ if callable(_prior):
37
+ _prior(signum, frame)
38
+ else:
39
+ # SIG_DFL / SIG_IGN: restore it and re-deliver the signal.
40
+ signal.signal(signum, _prior)
41
+ signal.raise_signal(signum)
42
+
43
+ previous[sig] = prior
44
+ signal.signal(sig, handler)
45
+ return previous
46
+
47
+
48
+ @asynccontextmanager
49
+ async def lifespan(_app: FastAPI):
50
+ previous = _install_scan_cancelling_signal_handlers()
51
+ try:
52
+ yield
53
+ finally:
54
+ cancel_all_scans()
55
+ for sig, prior in previous.items():
56
+ signal.signal(sig, prior)
7
57
 
8
- from app.routers import analysis, files
9
58
 
10
59
  app = FastAPI(
11
60
  title="Srcly Server",
12
61
  description="API for static code analysis and file serving.",
13
62
  version="1.0.0",
63
+ lifespan=lifespan,
14
64
  )
15
65
 
16
66
  # Configure CORS
@@ -25,6 +75,7 @@ app.add_middleware(
25
75
  # Include Routers
26
76
  app.include_router(analysis.router)
27
77
  app.include_router(files.router)
78
+ app.include_router(scans.router)
28
79
 
29
80
 
30
81
  def _find_static_dir() -> str | None:
@@ -74,59 +74,3 @@ class DependencyGraph(BaseModel):
74
74
  nodes: List[DependencyNode]
75
75
  edges: List[DependencyEdge]
76
76
 
77
-
78
- class FocusOverlayRequest(BaseModel):
79
- path: str
80
- sliceStartLine: int
81
- sliceEndLine: int
82
- focusStartLine: int | None = None
83
- focusEndLine: int | None = None
84
-
85
-
86
- class OverlayToken(BaseModel):
87
- fileLine: int
88
- startCol: int
89
- endCol: int
90
- category: str
91
- symbolId: str
92
- tooltip: str
93
- definitionSnippet: str | None = None
94
- definitionLine: int | None = None
95
- scopeSnippet: str | None = None
96
- scopeLine: int | None = None
97
- scopeEndLine: int | None = None
98
-
99
-
100
- class FocusOverlayResponse(BaseModel):
101
- tokens: List[OverlayToken] = Field(default_factory=list)
102
-
103
-
104
- # --- Scope Graph Models ---
105
-
106
- class ScopeGraphRequest(BaseModel):
107
- path: str
108
- focusStartLine: int
109
- focusEndLine: int
110
-
111
- class SymbolNode(BaseModel):
112
- id: str # e.g. "var:expanded:L12:C5" or just a unique ID
113
- name: str
114
- kind: str # "var", "func", "param", "class", "jsx_comp", etc.
115
- declLine: int
116
- isCaptured: bool = False
117
- isDeclaredHere: bool = False
118
-
119
- class ScopeNode(BaseModel):
120
- id: str
121
- kind: str # "function", "jsx", "block", "root"
122
- name: str | None = None
123
- startLine: int
124
- endLine: int
125
- children: List["ScopeNode"] = Field(default_factory=list)
126
- declared: List[SymbolNode] = Field(default_factory=list)
127
- captured: List[SymbolNode] = Field(default_factory=list)
128
-
129
- class ScopeGraph(BaseModel):
130
- root: ScopeNode
131
- # We might want edges later, but for MVP, nested scopes + lists are enough.
132
-
@@ -6,9 +6,9 @@ from typing import Dict, List, Optional, Tuple
6
6
 
7
7
  from app.models import Node, DependencyGraph, DependencyNode, DependencyEdge
8
8
  from app.services import analysis, cache
9
+ from app.services.scan_workers import ScanCancelled
9
10
  from app.services.typescript import typescript_analysis
10
11
  from app.config import IGNORE_DIRS
11
- from app.models import FocusOverlayRequest, FocusOverlayResponse, ScopeGraphRequest, ScopeGraph
12
12
 
13
13
  router = APIRouter(prefix="/api/analysis", tags=["analysis"])
14
14
 
@@ -303,8 +303,18 @@ def _resolve_internal_file(spec_path: Path, file_to_id: Dict[Path, str]) -> Opti
303
303
 
304
304
  return None
305
305
 
306
+ def _scan_or_503(target_path: Path) -> Node:
307
+ try:
308
+ return analysis.scan_codebase(target_path)
309
+ except ScanCancelled:
310
+ raise HTTPException(status_code=503, detail="Scan cancelled (server shutting down)")
311
+
312
+
313
+ # Scan endpoints are plain `def` on purpose: FastAPI runs them in a worker
314
+ # thread, so a long scan never blocks the event loop (which would also stop
315
+ # uvicorn from reacting to Ctrl+C).
306
316
  @router.get("", response_model=Node)
307
- async def get_analysis(path: str = None):
317
+ def get_analysis(path: str = None):
308
318
  """
309
319
  Get the static analysis of the codebase.
310
320
  Returns cached result if available, otherwise triggers a scan.
@@ -321,13 +331,12 @@ async def get_analysis(path: str = None):
321
331
  if cached_tree:
322
332
  return cached_tree
323
333
 
324
- # If no cache, run scan synchronously (for now, could be async/background)
325
- tree = analysis.scan_codebase(target_path)
334
+ tree = _scan_or_503(target_path)
326
335
  cache.save_analysis(target_path, tree)
327
336
  return tree
328
337
 
329
338
  @router.get("/dependencies", response_model=DependencyGraph)
330
- async def get_dependencies(path: str = None):
339
+ def get_dependencies(path: str = None):
331
340
  """
332
341
  Build a dependency graph for the specified path.
333
342
  """
@@ -527,11 +536,11 @@ async def get_dependencies(path: str = None):
527
536
  return DependencyGraph(nodes=nodes, edges=edges)
528
537
 
529
538
  @router.post("/refresh", response_model=Node)
530
- async def refresh_analysis():
539
+ def refresh_analysis():
531
540
  """
532
541
  Force a re-scan of the codebase.
533
542
  """
534
- tree = analysis.scan_codebase(ROOT_PATH)
543
+ tree = _scan_or_503(ROOT_PATH)
535
544
  cache.save_analysis(ROOT_PATH, tree)
536
545
  return tree
537
546
 
@@ -564,7 +573,7 @@ def _estimate_counts(root: Path) -> tuple[int, int]:
564
573
 
565
574
 
566
575
  @router.get("/context")
567
- async def get_analysis_context():
576
+ def get_analysis_context():
568
577
  """
569
578
  Return basic information about the current analysis root directory.
570
579
 
@@ -596,79 +605,3 @@ async def get_analysis_context():
596
605
  "repo_file_count": repo_file_count,
597
606
  "repo_folder_count": repo_folder_count,
598
607
  }
599
-
600
- @router.get("/data-flow")
601
- async def get_data_flow(path: str):
602
- """
603
- Analyze data flow for a specific file.
604
- """
605
- target_path = Path(path)
606
- if not target_path.exists() or not target_path.is_file():
607
- raise HTTPException(status_code=404, detail="File not found")
608
-
609
- from app.services.data_flow_analysis import DataFlowAnalyzer
610
- analyzer = DataFlowAnalyzer()
611
-
612
- try:
613
- graph = analyzer.analyze_file(str(target_path))
614
- return graph
615
- except Exception as e:
616
- print(f"Error analyzing data flow for {path}: {e}")
617
- raise HTTPException(status_code=500, detail=str(e))
618
-
619
-
620
- @router.post("/focus/overlay", response_model=FocusOverlayResponse)
621
- async def get_focus_overlay(req: FocusOverlayRequest):
622
- """
623
- Return a minimal overlay model for a single file and a focus range.
624
-
625
- The client uses this to decorate Shiki-rendered HTML with subtle identifier
626
- background highlights + provenance tooltips.
627
- """
628
- target_path = Path(req.path)
629
- if not target_path.exists() or not target_path.is_file():
630
- raise HTTPException(status_code=404, detail="File not found")
631
-
632
- # If no focus is provided, return an empty overlay.
633
- if req.focusStartLine is None or req.focusEndLine is None:
634
- return FocusOverlayResponse(tokens=[])
635
-
636
- from app.services.focus_overlay import compute_focus_overlay
637
-
638
- try:
639
- return compute_focus_overlay(
640
- file_path=str(target_path),
641
- slice_start_line=req.sliceStartLine,
642
- slice_end_line=req.sliceEndLine,
643
- focus_start_line=req.focusStartLine,
644
- focus_end_line=req.focusEndLine,
645
- )
646
- except HTTPException:
647
- raise
648
- except Exception as e:
649
- print(f"Error computing focus overlay for {req.path}: {e}")
650
- raise HTTPException(status_code=500, detail=str(e))
651
-
652
-
653
- @router.post("/focus/scope-graph", response_model=ScopeGraph)
654
- async def get_scope_graph(req: ScopeGraphRequest):
655
- """
656
- Return the nested scope graph for a focused region.
657
- """
658
- target_path = Path(req.path)
659
- if not target_path.exists() or not target_path.is_file():
660
- raise HTTPException(status_code=404, detail="File not found")
661
-
662
- from app.services.focus_overlay import compute_scope_graph
663
-
664
- try:
665
- return compute_scope_graph(
666
- file_path=str(target_path),
667
- focus_start_line=req.focusStartLine,
668
- focus_end_line=req.focusEndLine,
669
- )
670
- except HTTPException:
671
- raise
672
- except Exception as e:
673
- print(f"Error computing scope graph for {req.path}: {e}")
674
- raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,121 @@
1
+ """
2
+ Scan jobs with live progress over Server-Sent Events.
3
+
4
+ POST /api/scans start (or join) a scan -> snapshot
5
+ GET /api/scans/{id} current snapshot (polling fallback)
6
+ GET /api/scans/{id}/events SSE stream of snapshots
7
+ GET /api/scans/{id}/result analysis tree once complete
8
+ DELETE /api/scans/{id} cancel -> snapshot
9
+
10
+ SSE rather than WebSockets: progress only flows server -> client, cancel is
11
+ a plain HTTP call, and EventSource gives reconnects for free. Each stream
12
+ sends the full snapshot (not deltas), so a reconnecting client is always
13
+ consistent without replay. Snapshots are checked every ``STREAM_TICK_SECONDS``
14
+ and sent when the job's version changed (which throttles bursts), or every
15
+ ``REFRESH_SECONDS`` regardless so elapsed and in-flight timers keep moving
16
+ while a slow file holds things up. The refresh doubles as a keep-alive.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import json
23
+ import time
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+ from fastapi import APIRouter, HTTPException, Request
28
+ from fastapi.responses import StreamingResponse
29
+ from pydantic import BaseModel
30
+
31
+ from app.models import Node
32
+ from app.routers import analysis as analysis_router
33
+ from app.services.scan_jobs import TERMINAL_PHASES, ScanJob, manager
34
+
35
+ router = APIRouter(prefix="/api/scans", tags=["scans"])
36
+
37
+ STREAM_TICK_SECONDS = 0.1
38
+ REFRESH_SECONDS = 1.0
39
+ CLIENT_RETRY_MS = 2000
40
+
41
+
42
+ class StartScanRequest(BaseModel):
43
+ path: Optional[str] = None
44
+
45
+
46
+ def _job_or_404(job_id: str) -> ScanJob:
47
+ job = manager.get(job_id)
48
+ if job is None:
49
+ raise HTTPException(status_code=404, detail="Scan not found")
50
+ return job
51
+
52
+
53
+ def _sse(event: str, data: dict, event_id: Optional[int] = None) -> str:
54
+ lines = [f"event: {event}"]
55
+ if event_id is not None:
56
+ lines.append(f"id: {event_id}")
57
+ lines.append(f"data: {json.dumps(data, separators=(',', ':'))}")
58
+ return "\n".join(lines) + "\n\n"
59
+
60
+
61
+ @router.post("", status_code=202)
62
+ def start_scan(req: StartScanRequest) -> dict:
63
+ target = Path(req.path) if req.path else analysis_router.ROOT_PATH
64
+ if not target.exists():
65
+ raise HTTPException(status_code=404, detail=f"Path not found: {target}")
66
+ return manager.start(target).snapshot()
67
+
68
+
69
+ @router.get("/{job_id}")
70
+ def get_scan(job_id: str) -> dict:
71
+ return _job_or_404(job_id).snapshot()
72
+
73
+
74
+ @router.delete("/{job_id}")
75
+ def cancel_scan(job_id: str) -> dict:
76
+ job = _job_or_404(job_id)
77
+ job.cancel()
78
+ return job.snapshot()
79
+
80
+
81
+ @router.get("/{job_id}/result", response_model=Node)
82
+ def get_scan_result(job_id: str):
83
+ job = _job_or_404(job_id)
84
+ if job.phase != "complete" or job.result is None:
85
+ raise HTTPException(status_code=409, detail=f"Scan is {job.phase}, not complete")
86
+ return job.result
87
+
88
+
89
+ @router.get("/{job_id}/events")
90
+ async def stream_scan(job_id: str, request: Request) -> StreamingResponse:
91
+ job = _job_or_404(job_id)
92
+
93
+ async def events():
94
+ yield f"retry: {CLIENT_RETRY_MS}\n\n"
95
+ sent_version = -1
96
+ last_write = time.monotonic()
97
+ while True:
98
+ snapshot = job.snapshot()
99
+ stale = time.monotonic() - last_write >= REFRESH_SECONDS
100
+ if snapshot["version"] != sent_version or stale:
101
+ sent_version = snapshot["version"]
102
+ last_write = time.monotonic()
103
+ if snapshot["phase"] in TERMINAL_PHASES:
104
+ yield _sse(snapshot["phase"], snapshot, sent_version)
105
+ return
106
+ yield _sse("progress", snapshot, sent_version)
107
+
108
+ if await request.is_disconnected():
109
+ return
110
+ await asyncio.sleep(STREAM_TICK_SECONDS)
111
+
112
+ return StreamingResponse(
113
+ events(),
114
+ media_type="text/event-stream",
115
+ headers={
116
+ "Cache-Control": "no-cache",
117
+ "Connection": "keep-alive",
118
+ # Disable proxy buffering (nginx and friends) so events flush immediately.
119
+ "X-Accel-Buffering": "no",
120
+ },
121
+ )
@@ -10,6 +10,12 @@ from pathlib import Path
10
10
  import uvicorn
11
11
 
12
12
  from app.services import reporting
13
+ from app.services.scan_workers import ScanCancelled
14
+
15
+ # Seconds uvicorn waits for open connections after Ctrl+C before closing them.
16
+ GRACEFUL_SHUTDOWN_SECONDS = 3
17
+ # Conventional exit status for a process stopped by SIGINT.
18
+ EXIT_INTERRUPTED = 130
13
19
 
14
20
 
15
21
  def _open_browser_later(url: str, delay: float = 1.0) -> None:
@@ -54,8 +60,17 @@ def _find_repo_root(start_path: str) -> str:
54
60
 
55
61
 
56
62
  def main(argv: list[str] | None = None) -> None:
63
+ """CLI entry point. Ctrl+C exits promptly with status 130 and no traceback."""
64
+ try:
65
+ _main(argv)
66
+ except (KeyboardInterrupt, ScanCancelled):
67
+ print("\nStopped.", file=sys.stderr, flush=True)
68
+ raise SystemExit(EXIT_INTERRUPTED)
69
+
70
+
71
+ def _main(argv: list[str] | None = None) -> None:
57
72
  """
58
- Entry point for the CLI.
73
+ Parse arguments and dispatch.
59
74
 
60
75
  - With no path argument, uses the enclosing Git repo root as the codebase root.
61
76
  - If a path is provided (including "."), uses that as the codebase root.
@@ -127,6 +142,7 @@ def main(argv: list[str] | None = None) -> None:
127
142
  host=args.host,
128
143
  port=port,
129
144
  reload=False,
145
+ timeout_graceful_shutdown=GRACEFUL_SHUTDOWN_SECONDS,
130
146
  )
131
147
 
132
148