srcly 0.1.29__tar.gz → 0.1.30__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {srcly-0.1.29 → srcly-0.1.30}/.gitignore +2 -1
- {srcly-0.1.29 → srcly-0.1.30}/PKG-INFO +1 -1
- srcly-0.1.30/app/run.py +361 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/analysis.py +11 -10
- {srcly-0.1.29 → srcly-0.1.30}/app/services/focus_overlay.py +51 -14
- srcly-0.1.30/app/services/reporting.py +653 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/typescript/typescript_analysis.py +1 -1
- srcly-0.1.30/app/static/assets/index-Bn6hDYiR.js +66 -0
- srcly-0.1.30/app/static/assets/index-DK1czI50.css +1 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/index.html +2 -2
- {srcly-0.1.29 → srcly-0.1.30}/pyproject.toml +1 -1
- srcly-0.1.29/app/run.py +0 -125
- srcly-0.1.29/app/static/assets/index-LMOq9q5x.css +0 -1
- srcly-0.1.29/app/static/assets/index-VGOFgxN6.js +0 -66
- {srcly-0.1.29 → srcly-0.1.30}/README.md +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/.gitignore +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/__init__.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/config.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/main.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/models.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/routers/analysis.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/routers/files.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/analysis_types.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/cache.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/css/css_analysis.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/data_flow_analysis.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/ipynb/ipynb_analysis.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/markdown/markdown_analysis.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/services/python/python_analysis.py +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/abap-BdImnpbu.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ada-bCR0ucgS.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/apache-Pmp26Uib.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/apex-DhZLUxFE.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/apl-dKokRX4l.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/applescript-Co6uUVPk.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ara-BRHolxvo.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/asm-D_Q5rh1f.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/astro-CbQHKStN.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/awk-DMzUqQB5.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bat-BkioyH1T.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/beancount-k_qm7-4y.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/berry-D08WgyRC.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bibtex-CHM0blh-.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bicep-Bmn6On1c.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/blade-DVc8C-J4.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/bsl-BO_Y6i37.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/c-BIGW1oBm.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cairo-KRGpt6FW.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/clarity-D53aC0YG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/clojure-P80f7IUj.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cmake-D1j8_8rp.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cobol-nwyudZeR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/coffee-Ch7k5sss.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/coq-DkFqJrB1.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cpp-CofmeUqb.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/crystal-tKQVLTB8.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/csharp-CX12Zw3r.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/css-DPfMkruS.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/csv-fuZLfV_i.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cue-D82EKSYY.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/cypher-COkxafJQ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/d-85-TOEBH.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dart-CF10PKvl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dax-CEL-wOlO.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/diff-D97Zzqfu.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/docker-BcOcwvcX.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dotenv-Da5cRb03.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dracula-BzJJZx-M.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/edge-BkV0erSs.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/elixir-CDX3lj18.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/elm-DbKCFpqz.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/erb-BOJIQeun.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/erlang-DsQrWhSR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fennel-BYunw83y.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fish-BvzEVeQv.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fluent-C4IJs8-o.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gdshader-DkwncUOv.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/genie-D0YGMca9.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-DHJKELXO.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-light-DAi9KRSo.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gleam-BspZqrRM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/glsl-DplSGwfg.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/go-Dn2_MT6a.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/graphql-ChdNCCLP.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/groovy-gcz8RCvz.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hack-CaT9iCJl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/haml-B8DHNrY2.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/handlebars-BL8al0AC.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/haskell-Df6bDoY_.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/haxe-CzTSHFRz.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hcl-BWvSN4gD.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hjson-D5-asLiD.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/houston-DnULxvSX.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/html-GMplVEZG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/http-jrhK8wxY.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hurl-irOxFIW8.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/hy-DFXneXwc.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/imba-DGztddWO.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ini-BEwlwnbL.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/java-CylS5w8V.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/javascript-wDzz0qaB.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jison-wvAkD_A8.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/json-Cp-IABpG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/json5-C9tS-k6U.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsonc-Des-eS-w.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jssm-C2t-YnRu.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/jsx-g9-lgVsj.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/julia-C8NyazO9.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kdl-DV7GczEv.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/kusto-BvAqAH-y.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/laserwave-DUszq2jm.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/latex-BUKiar2Z.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/lean-DP1Csr6i.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/less-B1dDrJ26.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/liquid-DYVedYrR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/llvm-BtvRca6l.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/log-2UxHyX5q.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/logo-BtOb2qkB.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/lua-BbnMAYS6.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/luau-CXu1NL6O.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/make-CHLpvVh8.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/markdown-Cvjx9yec.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/marko-CPi9NSCl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/matlab-D7o27uSR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mdc-DUICxH0z.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/min-dark-CafNBF8u.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/min-light-CTRr51gU.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/mojo-1DNp92w6.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/monokai-D4h5O-jR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/move-Bu9oaDYs.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/narrat-DRg8JJMk.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nextflow-CUEJCptM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nginx-DknmC5AR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/night-owl-C39BiMTA.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nim-CVrawwO9.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nix-BbRYJGeE.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nord-Ddv68eIx.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/nushell-C-sUppwS.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/one-light-PoHY5YXO.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/pascal-D93ZcfNL.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/perl-C0TMdlhV.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/php-CDn_0X-4.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/pkl-u5AG7uiY.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/plastic-3e1v2bzS.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/plsql-ChMvpjG-.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/po-BTJTHyun.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/polar-C0HS_06l.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/postcss-CXtECtnM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/powershell-Dpen1YoG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/prisma-Dd19v3D-.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/prolog-CbFg5uaA.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/proto-DyJlTyXw.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/pug-CGlum2m_.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/puppet-BMWR74SV.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/purescript-CklMAg4u.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/python-B6aJPvgy.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/qml-3beO22l8.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/qss-IeuSbFQv.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/r-DiinP2Uv.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/racket-BqYA7rlc.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/raku-DXvB9xmW.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/razor-WgofotgN.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/red-bN70gL4F.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/reg-C-SQnVFl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rel-C3B-1QV4.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/riscv-BM1_JUlF.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rst-B0xPkSld.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ruby-BvKwtOVI.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/rust-B1yitclQ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sas-cz2c8ADy.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/scala-C151Ov-r.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/scheme-C98Dy4si.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/scss-OYdSNvt2.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/shellscript-Yzrsuije.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/shellsession-BADoaaVG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/solidity-BbcW6ACK.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/soy-Brmx7dQM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sparql-rVzFXLq3.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/splunk-BtCnVYZw.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/sql-BLtJtn59.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/stata-BH5u7GGu.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/swift-Dg5xB15N.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/systemd-4A_iFExJ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/talonscript-CkByrt1z.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tasl-QIJgUcNo.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tcl-dwOrl1Do.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/templ-W15q3VgB.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/terraform-BETggiCN.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tex-Cppo0RY3.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tokyo-night-hegEt444.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/toml-vGWfd6FD.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tsv-B_m7g4N7.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/tsx-COt5Ahok.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/turtle-BsS91CYL.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/twig-CO9l9SDP.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/typespec-Df68jz8_.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/typst-DHCkPAjA.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/v-BcVCzyr7.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vala-CsfeWuGM.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vb-D17OF-Vu.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/verilog-BQ8w6xss.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vesper-DU1UobuO.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/viml-CJc9bBzg.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vue-CCoi5OLL.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/vyper-CDx5xZoG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wasm-MzD3tlZU.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wenyan-BV7otONQ.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wit-5i3qLPDT.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/xml-sdJ4AIDG.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/xsl-CtQFsRM5.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/yaml-Buea-lGh.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/assets/zig-VOosw3JB.js +0 -0
- {srcly-0.1.29 → srcly-0.1.30}/app/static/vite.svg +0 -0
srcly-0.1.30/app/run.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import socket
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import webbrowser
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import uvicorn
|
|
11
|
+
|
|
12
|
+
from app.services import reporting
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _open_browser_later(url: str, delay: float = 1.0) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Open the default web browser after a short delay.
|
|
18
|
+
|
|
19
|
+
This lets the server start first so the page is reachable.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def _worker() -> None:
|
|
23
|
+
time.sleep(delay)
|
|
24
|
+
try:
|
|
25
|
+
webbrowser.open(url)
|
|
26
|
+
except Exception:
|
|
27
|
+
# Don't crash the CLI if opening the browser fails (e.g. headless env)
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
thread = threading.Thread(target=_worker, daemon=True)
|
|
31
|
+
thread.start()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _find_repo_root(start_path: str) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Walk upwards from ``start_path`` to find a Git repository root.
|
|
37
|
+
|
|
38
|
+
Returns the first directory that contains a `.git` directory or file.
|
|
39
|
+
If none is found, the original ``start_path`` is returned.
|
|
40
|
+
"""
|
|
41
|
+
current = os.path.abspath(start_path)
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
git_path = os.path.join(current, ".git")
|
|
45
|
+
if os.path.exists(git_path):
|
|
46
|
+
return current
|
|
47
|
+
|
|
48
|
+
parent = os.path.dirname(current)
|
|
49
|
+
if parent == current:
|
|
50
|
+
# Reached filesystem root; fall back to the original start path.
|
|
51
|
+
return os.path.abspath(start_path)
|
|
52
|
+
|
|
53
|
+
current = parent
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main(argv: list[str] | None = None) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Entry point for the CLI.
|
|
59
|
+
|
|
60
|
+
- With no path argument, uses the enclosing Git repo root as the codebase root.
|
|
61
|
+
- If a path is provided (including "."), uses that as the codebase root.
|
|
62
|
+
- Starts the FastAPI server.
|
|
63
|
+
- Opens the default browser to the app URL.
|
|
64
|
+
"""
|
|
65
|
+
argv = list(argv) if argv is not None else sys.argv[1:]
|
|
66
|
+
if argv and argv[0] in {"scan", "report", "hotspots", "explain"}:
|
|
67
|
+
_run_headless(argv)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
parser = argparse.ArgumentParser(
|
|
71
|
+
prog="srcly",
|
|
72
|
+
description=(
|
|
73
|
+
"Interactive codebase treemap and metrics viewer. "
|
|
74
|
+
"By default, analyzes the enclosing Git repository root."
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"path",
|
|
79
|
+
nargs="?",
|
|
80
|
+
help=(
|
|
81
|
+
"Path to the codebase to analyze. "
|
|
82
|
+
"If omitted, the enclosing Git repo root is used. "
|
|
83
|
+
'Use "." explicitly to analyze the current directory.'
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--host",
|
|
88
|
+
default="127.0.0.1",
|
|
89
|
+
help="Host interface to bind the server to (default: 127.0.0.1).",
|
|
90
|
+
)
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"--port",
|
|
93
|
+
type=int,
|
|
94
|
+
default=None,
|
|
95
|
+
help="Port to run the server on (default: random free port).",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
args = parser.parse_args(argv)
|
|
99
|
+
|
|
100
|
+
if args.path is None:
|
|
101
|
+
target_path = _find_repo_root(os.getcwd())
|
|
102
|
+
else:
|
|
103
|
+
target_path = os.path.abspath(args.path)
|
|
104
|
+
|
|
105
|
+
if not os.path.exists(target_path):
|
|
106
|
+
raise SystemExit(f"Path does not exist: {target_path}")
|
|
107
|
+
|
|
108
|
+
# Change working directory so the API defaults to this path.
|
|
109
|
+
os.chdir(target_path)
|
|
110
|
+
print(f"📂 Analyzing codebase at: {target_path}")
|
|
111
|
+
|
|
112
|
+
port = args.port
|
|
113
|
+
if port is None:
|
|
114
|
+
# Find a random free port
|
|
115
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
116
|
+
s.bind(("", 0))
|
|
117
|
+
port = s.getsockname()[1]
|
|
118
|
+
|
|
119
|
+
url = f"http://{args.host}:{port}"
|
|
120
|
+
print(f"🚀 Starting server at {url}")
|
|
121
|
+
print(" Press Ctrl+C to stop.")
|
|
122
|
+
|
|
123
|
+
_open_browser_later(url)
|
|
124
|
+
|
|
125
|
+
uvicorn.run(
|
|
126
|
+
"app.main:app",
|
|
127
|
+
host=args.host,
|
|
128
|
+
port=port,
|
|
129
|
+
reload=False,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _resolve_target_path(path: str | None) -> Path:
|
|
134
|
+
if path is None:
|
|
135
|
+
target_path = Path(_find_repo_root(os.getcwd()))
|
|
136
|
+
else:
|
|
137
|
+
target_path = Path(path).resolve()
|
|
138
|
+
|
|
139
|
+
if not target_path.exists():
|
|
140
|
+
raise SystemExit(f"Path does not exist: {target_path}")
|
|
141
|
+
return target_path
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _run_headless(argv: list[str]) -> None:
|
|
145
|
+
parser = argparse.ArgumentParser(
|
|
146
|
+
prog="srcly",
|
|
147
|
+
description="Headless Srcly analysis commands for agents and CI.",
|
|
148
|
+
)
|
|
149
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
150
|
+
|
|
151
|
+
scan_parser = subparsers.add_parser("scan", help="Write the raw analysis tree as JSON.")
|
|
152
|
+
scan_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
|
|
153
|
+
scan_parser.add_argument("--out", default="codebase_mri.json", help="Output JSON file.")
|
|
154
|
+
scan_parser.add_argument("--refresh", action="store_true", help="Ignore cache when cache support is available.")
|
|
155
|
+
scan_parser.add_argument("--format", choices=["json", "jsonl"], default="json", help="Output format. JSONL is reserved.")
|
|
156
|
+
scan_parser.add_argument(
|
|
157
|
+
"--include-source",
|
|
158
|
+
choices=["false", "snippets", "none"],
|
|
159
|
+
default="none",
|
|
160
|
+
help="Reserved source inclusion mode. Full source is not emitted.",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
report_parser = subparsers.add_parser("report", help="Write agent-ready report artifacts.")
|
|
164
|
+
report_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
|
|
165
|
+
report_parser.add_argument("--out", default=".srcly", help="Output artifact directory.")
|
|
166
|
+
report_parser.add_argument(
|
|
167
|
+
"--stdout",
|
|
168
|
+
action="store_true",
|
|
169
|
+
help="Emit one compact bundled JSON report to stdout instead of writing artifact files.",
|
|
170
|
+
)
|
|
171
|
+
report_parser.add_argument("--refresh", action="store_true", help="Ignore cache when cache support is available.")
|
|
172
|
+
report_parser.add_argument("--limit", type=int, default=50, help="Maximum ranked findings to emit.")
|
|
173
|
+
report_parser.add_argument(
|
|
174
|
+
"--profile",
|
|
175
|
+
choices=["general", "frontend", "backend", "typescript", "python", "docs"],
|
|
176
|
+
default="general",
|
|
177
|
+
help="Scoring profile.",
|
|
178
|
+
)
|
|
179
|
+
report_parser.add_argument(
|
|
180
|
+
"--format",
|
|
181
|
+
choices=["markdown", "json", "both"],
|
|
182
|
+
default="both",
|
|
183
|
+
help="Artifact formats to write.",
|
|
184
|
+
)
|
|
185
|
+
report_parser.add_argument(
|
|
186
|
+
"--fail-on",
|
|
187
|
+
choices=["high", "medium", "none"],
|
|
188
|
+
default="none",
|
|
189
|
+
help="Exit non-zero if findings at this priority or higher are present.",
|
|
190
|
+
)
|
|
191
|
+
report_parser.add_argument("--tree-depth", type=int, default=3, help="Maximum depth in tree.summary.json.")
|
|
192
|
+
report_parser.add_argument("--tree-top", type=int, default=6, help="Maximum children per tree level.")
|
|
193
|
+
report_parser.add_argument("--include-tree", action="store_true", help="Also write/include the full raw tree.json artifact.")
|
|
194
|
+
report_parser.add_argument(
|
|
195
|
+
"--include-dependencies",
|
|
196
|
+
action="store_true",
|
|
197
|
+
help="Also write/include the TS/TSX dependency graph artifact.",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
hotspots_parser = subparsers.add_parser("hotspots", help="Print ranked hotspots.")
|
|
201
|
+
hotspots_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
|
|
202
|
+
hotspots_parser.add_argument("--metric", action="append", default=None, help="Metric to score. May be repeated.")
|
|
203
|
+
hotspots_parser.add_argument("--limit", type=int, default=25, help="Maximum hotspots to print.")
|
|
204
|
+
hotspots_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
|
|
205
|
+
|
|
206
|
+
explain_parser = subparsers.add_parser("explain", help="Print a focused report for one file or subtree.")
|
|
207
|
+
explain_parser.add_argument("path", nargs="?", help="Path to analyze. Defaults to the enclosing Git repo root.")
|
|
208
|
+
explain_parser.add_argument("--file", required=True, help="File path to explain.")
|
|
209
|
+
explain_parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
|
|
210
|
+
|
|
211
|
+
args = parser.parse_args(argv)
|
|
212
|
+
|
|
213
|
+
if args.command == "scan":
|
|
214
|
+
if args.format != "json":
|
|
215
|
+
raise SystemExit("--format jsonl is reserved and not implemented yet.")
|
|
216
|
+
if args.include_source not in {"none", "false"}:
|
|
217
|
+
raise SystemExit("--include-source snippets is reserved and not implemented yet.")
|
|
218
|
+
target_path = _resolve_target_path(args.path)
|
|
219
|
+
out_path = Path("-") if args.out == "-" else Path(args.out).resolve()
|
|
220
|
+
reporting.write_scan(target_path, out_path, refresh=args.refresh)
|
|
221
|
+
if str(args.out) != "-":
|
|
222
|
+
print(f"Wrote Srcly tree JSON to {out_path}")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
if args.command == "report":
|
|
226
|
+
target_path = _resolve_target_path(args.path)
|
|
227
|
+
if args.stdout:
|
|
228
|
+
payload = reporting.build_report_payload(
|
|
229
|
+
target_path,
|
|
230
|
+
profile=args.profile,
|
|
231
|
+
limit=args.limit,
|
|
232
|
+
refresh=args.refresh,
|
|
233
|
+
tree_depth=args.tree_depth,
|
|
234
|
+
tree_top=args.tree_top,
|
|
235
|
+
include_tree=args.include_tree,
|
|
236
|
+
include_dependencies=args.include_dependencies,
|
|
237
|
+
)
|
|
238
|
+
print(reporting._json_dumps(payload))
|
|
239
|
+
else:
|
|
240
|
+
out_dir = Path(args.out).resolve()
|
|
241
|
+
output_format = "both" if args.fail_on != "none" and args.format == "markdown" else args.format
|
|
242
|
+
written = reporting.write_report(
|
|
243
|
+
target_path,
|
|
244
|
+
out_dir,
|
|
245
|
+
profile=args.profile,
|
|
246
|
+
limit=args.limit,
|
|
247
|
+
output_format=output_format,
|
|
248
|
+
refresh=args.refresh,
|
|
249
|
+
tree_depth=args.tree_depth,
|
|
250
|
+
tree_top=args.tree_top,
|
|
251
|
+
include_tree=args.include_tree,
|
|
252
|
+
include_dependencies=args.include_dependencies,
|
|
253
|
+
)
|
|
254
|
+
print(f"Wrote Srcly report artifacts to {out_dir}")
|
|
255
|
+
for path in written.values():
|
|
256
|
+
print(f"- {path}")
|
|
257
|
+
if args.fail_on != "none":
|
|
258
|
+
if args.stdout:
|
|
259
|
+
_enforce_fail_on_payload(payload["findings"], args.fail_on)
|
|
260
|
+
else:
|
|
261
|
+
_enforce_fail_on(out_dir / "findings.json", args.fail_on)
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
if args.command == "hotspots":
|
|
265
|
+
target_path = _resolve_target_path(args.path)
|
|
266
|
+
tree = reporting.scan_tree(target_path)
|
|
267
|
+
profile = _profile_from_metrics(args.metric)
|
|
268
|
+
ranked = reporting.rank_nodes(tree, profile=profile)[: args.limit]
|
|
269
|
+
if args.format == "json":
|
|
270
|
+
print(reporting._json_dumps({"rankings": ranked}))
|
|
271
|
+
else:
|
|
272
|
+
for item in ranked:
|
|
273
|
+
location = item["path"]
|
|
274
|
+
if item.get("start_line") and item.get("end_line"):
|
|
275
|
+
location = f"{location}:{item['start_line']}-{item['end_line']}"
|
|
276
|
+
print(f"{item['rank']}. score={item['score']} {location} - {item['reason']}")
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
if args.command == "explain":
|
|
280
|
+
target_path = _resolve_target_path(args.path)
|
|
281
|
+
tree = reporting.scan_tree(target_path)
|
|
282
|
+
payload = _explain_file(tree, Path(args.file).resolve(), target_path)
|
|
283
|
+
if args.format == "json":
|
|
284
|
+
print(reporting._json_dumps(payload))
|
|
285
|
+
else:
|
|
286
|
+
print(_render_explain_markdown(payload))
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _profile_from_metrics(metrics: list[str] | None) -> str:
|
|
291
|
+
if not metrics:
|
|
292
|
+
return "general"
|
|
293
|
+
weights = {metric: 1.0 for metric in metrics}
|
|
294
|
+
reporting.PROFILE_WEIGHTS["_custom"] = weights
|
|
295
|
+
return "_custom"
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _enforce_fail_on(findings_path: Path, fail_on: str) -> None:
|
|
299
|
+
import json
|
|
300
|
+
|
|
301
|
+
data = json.loads(findings_path.read_text(encoding="utf-8"))
|
|
302
|
+
_enforce_fail_on_payload(data, fail_on)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _enforce_fail_on_payload(data: dict, fail_on: str) -> None:
|
|
306
|
+
priorities = {"high": 2, "medium": 1, "low": 0}
|
|
307
|
+
threshold = priorities[fail_on]
|
|
308
|
+
offenders = [
|
|
309
|
+
finding
|
|
310
|
+
for finding in data.get("findings", [])
|
|
311
|
+
if priorities.get(finding.get("priority", "low"), 0) >= threshold
|
|
312
|
+
]
|
|
313
|
+
if offenders:
|
|
314
|
+
raise SystemExit(f"Srcly found {len(offenders)} findings at priority {fail_on} or higher.")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _explain_file(tree, file_path: Path, root_path: Path) -> dict:
|
|
318
|
+
target = None
|
|
319
|
+
for node in reporting.iter_nodes(tree):
|
|
320
|
+
try:
|
|
321
|
+
node_path = Path(node.path).resolve()
|
|
322
|
+
except Exception:
|
|
323
|
+
continue
|
|
324
|
+
if node_path == file_path:
|
|
325
|
+
target = node
|
|
326
|
+
break
|
|
327
|
+
|
|
328
|
+
if target is None:
|
|
329
|
+
raise SystemExit(f"File not found in analysis tree: {file_path}")
|
|
330
|
+
|
|
331
|
+
ranked = reporting.rank_nodes(target)
|
|
332
|
+
return {
|
|
333
|
+
"path": str(file_path),
|
|
334
|
+
"summary": reporting.summarize_tree(target, ranked, root_path=root_path, max_depth=3, top_children=20),
|
|
335
|
+
"hotspots": ranked[:10],
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _render_explain_markdown(payload: dict) -> str:
|
|
340
|
+
lines = [
|
|
341
|
+
f"# Srcly Explain: {payload['path']}",
|
|
342
|
+
"",
|
|
343
|
+
"## Tree",
|
|
344
|
+
"",
|
|
345
|
+
"```text",
|
|
346
|
+
*reporting.render_tree_lines(payload["summary"]),
|
|
347
|
+
"```",
|
|
348
|
+
"",
|
|
349
|
+
"## Hotspots",
|
|
350
|
+
"",
|
|
351
|
+
]
|
|
352
|
+
for item in payload["hotspots"]:
|
|
353
|
+
location = item["path"]
|
|
354
|
+
if item.get("start_line") and item.get("end_line"):
|
|
355
|
+
location = f"{location}:{item['start_line']}-{item['end_line']}"
|
|
356
|
+
lines.append(f"- score={item['score']} `{location}`: {item['reason']}")
|
|
357
|
+
return "\n".join(lines) + "\n"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
if __name__ == "__main__":
|
|
361
|
+
main()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import lizard
|
|
3
3
|
import multiprocessing
|
|
4
|
+
import sys
|
|
4
5
|
import time
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -369,6 +370,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
369
370
|
total_type_interface_count = 0
|
|
370
371
|
total_export_count = 0
|
|
371
372
|
total_md_data_url_count = 0
|
|
373
|
+
total_python_import_count = 0
|
|
372
374
|
|
|
373
375
|
# For average function length, we need total function loc and total functions (already have total_funcs)
|
|
374
376
|
# But we need to sum function locs from children.
|
|
@@ -390,11 +392,7 @@ def aggregate_metrics(node: Node) -> Metrics:
|
|
|
390
392
|
total_type_interface_count += child_metrics.ts_type_interface_count
|
|
391
393
|
total_export_count += child_metrics.ts_export_count
|
|
392
394
|
total_md_data_url_count += child_metrics.md_data_url_count
|
|
393
|
-
|
|
394
|
-
# Metric model has it, but child_metrics is a Metrics object so it should have it
|
|
395
|
-
total_python_import_count = getattr(child_metrics, 'python_import_count', 0)
|
|
396
|
-
# Should we add a local var for this? Yes
|
|
397
|
-
pass
|
|
395
|
+
total_python_import_count += child_metrics.python_import_count
|
|
398
396
|
|
|
399
397
|
|
|
400
398
|
# Reconstruct total function loc from average * count
|
|
@@ -510,7 +508,7 @@ def _run_file_analyses_with_hard_timeouts(
|
|
|
510
508
|
|
|
511
509
|
# Log every file *before* it is processed so we can identify the
|
|
512
510
|
# last-started file if analysis hangs or crashes.
|
|
513
|
-
print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", flush=True)
|
|
511
|
+
print(f"➡️ [{next_index}/{total_count}] Starting analysis: {file_path}", file=sys.stderr, flush=True)
|
|
514
512
|
|
|
515
513
|
recv_conn, send_conn = ctx.Pipe(duplex=False)
|
|
516
514
|
proc = ctx.Process(
|
|
@@ -544,6 +542,7 @@ def _run_file_analyses_with_hard_timeouts(
|
|
|
544
542
|
completed_count += 1
|
|
545
543
|
print(
|
|
546
544
|
f"❌ [{completed_count}/{total_count}] Timeout analyzing {file_path} after {elapsed:.2f}s (terminated)",
|
|
545
|
+
file=sys.stderr,
|
|
547
546
|
flush=True,
|
|
548
547
|
)
|
|
549
548
|
try:
|
|
@@ -582,11 +581,12 @@ def _run_file_analyses_with_hard_timeouts(
|
|
|
582
581
|
if isinstance(result, dict) and "error" in result:
|
|
583
582
|
print(
|
|
584
583
|
f"❌ [{completed_count}/{total_count}] Error analyzing {file_path}: {result.get('error')}",
|
|
584
|
+
file=sys.stderr,
|
|
585
585
|
flush=True,
|
|
586
586
|
)
|
|
587
587
|
else:
|
|
588
588
|
if _should_log_file_progress(completed_count, total_count):
|
|
589
|
-
print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", flush=True)
|
|
589
|
+
print(f"✅ [{completed_count}/{total_count}] Analyzed {file_path}", file=sys.stderr, flush=True)
|
|
590
590
|
results.append(result)
|
|
591
591
|
|
|
592
592
|
active = still_active
|
|
@@ -599,7 +599,7 @@ def _run_file_analyses_with_hard_timeouts(
|
|
|
599
599
|
|
|
600
600
|
|
|
601
601
|
def scan_codebase(root_path: Path) -> Node:
|
|
602
|
-
print(f"🔍 Scanning: {root_path}", flush=True)
|
|
602
|
+
print(f"🔍 Scanning: {root_path}", file=sys.stderr, flush=True)
|
|
603
603
|
|
|
604
604
|
# Load .gitignore spec (repo-wide, with nested .gitignore support)
|
|
605
605
|
ignore_root, gitignore_spec = _load_gitignore_spec(root_path)
|
|
@@ -614,7 +614,7 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
614
614
|
# We must modify dirs in-place to prune traversal
|
|
615
615
|
pruned_dirs: list[str] = []
|
|
616
616
|
for d in dirs:
|
|
617
|
-
if d in IGNORE_DIRS:
|
|
617
|
+
if d in IGNORE_DIRS or d.startswith(".srcly"):
|
|
618
618
|
continue
|
|
619
619
|
dir_path = root_dir_path / d
|
|
620
620
|
if _is_gitignored(dir_path, ignore_root, gitignore_spec):
|
|
@@ -643,6 +643,7 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
643
643
|
|
|
644
644
|
print(
|
|
645
645
|
f"📂 Analyzing {len(files_to_scan)} source files... (workers={MAX_ANALYSIS_WORKERS}, timeout={PER_FILE_ANALYSIS_TIMEOUT_SECONDS}s)",
|
|
646
|
+
file=sys.stderr,
|
|
646
647
|
flush=True,
|
|
647
648
|
)
|
|
648
649
|
|
|
@@ -661,7 +662,7 @@ def scan_codebase(root_path: Path) -> Node:
|
|
|
661
662
|
# ``filename`` attribute), skip it instead of crashing the whole scan.
|
|
662
663
|
filename = getattr(file_info, "filename", None)
|
|
663
664
|
if not filename:
|
|
664
|
-
print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", flush=True)
|
|
665
|
+
print(f"⚠️ Skipping unexpected analysis result without filename: {file_info!r}", file=sys.stderr, flush=True)
|
|
665
666
|
continue
|
|
666
667
|
|
|
667
668
|
path_obj = Path(filename)
|
|
@@ -76,6 +76,7 @@ class _Usage:
|
|
|
76
76
|
start_col: int # 0-based
|
|
77
77
|
end_col: int # exclusive
|
|
78
78
|
resolved: _Def | None
|
|
79
|
+
containing_scope_id: str # Closest scope boundary that contains this usage (includes JSX scopes)
|
|
79
80
|
containing_fn_scope_id: str # Added to track where this usage occurs
|
|
80
81
|
|
|
81
82
|
|
|
@@ -536,6 +537,39 @@ class FocusAnalyzer:
|
|
|
536
537
|
if attr_name:
|
|
537
538
|
return attr_name.text.decode("utf-8", errors="ignore")
|
|
538
539
|
|
|
540
|
+
# TSX/JSX scope naming: name scopes after their JSX tag so they can be explored as a tree.
|
|
541
|
+
if self.is_tsx and node.type in {"jsx_element", "jsx_self_closing_element", "jsx_fragment"}:
|
|
542
|
+
if node.type == "jsx_fragment":
|
|
543
|
+
return "<>"
|
|
544
|
+
|
|
545
|
+
opening = node.child_by_field_name("opening_element")
|
|
546
|
+
if opening is None:
|
|
547
|
+
for c in node.children:
|
|
548
|
+
if c.type in {"jsx_opening_element", "jsx_self_closing_element"}:
|
|
549
|
+
opening = c
|
|
550
|
+
break
|
|
551
|
+
|
|
552
|
+
name_node = opening.child_by_field_name("name") if opening is not None else None
|
|
553
|
+
if name_node is None and opening is not None:
|
|
554
|
+
# Fallback: first plausible identifier-ish node under the opening element.
|
|
555
|
+
for c in opening.children:
|
|
556
|
+
if c.type in {
|
|
557
|
+
"jsx_identifier",
|
|
558
|
+
"identifier",
|
|
559
|
+
"property_identifier",
|
|
560
|
+
"member_expression",
|
|
561
|
+
"jsx_member_expression",
|
|
562
|
+
"nested_identifier",
|
|
563
|
+
}:
|
|
564
|
+
name_node = c
|
|
565
|
+
break
|
|
566
|
+
|
|
567
|
+
if name_node is not None:
|
|
568
|
+
raw = name_node.text.decode("utf-8", errors="ignore").strip()
|
|
569
|
+
if raw:
|
|
570
|
+
return f"<{raw}>"
|
|
571
|
+
return "<jsx>"
|
|
572
|
+
|
|
539
573
|
return None
|
|
540
574
|
|
|
541
575
|
|
|
@@ -553,6 +587,12 @@ class FocusAnalyzer:
|
|
|
553
587
|
return n.type == "catch_clause"
|
|
554
588
|
|
|
555
589
|
def _is_scope_boundary(self, n: Node) -> bool:
|
|
590
|
+
# TSX/JSX: every JSX element/fragment becomes its own nested "scope" layer.
|
|
591
|
+
# This is intentionally verbose so the client can expand/collapse JSX structure
|
|
592
|
+
# to guide refactors.
|
|
593
|
+
if self.is_tsx and n.type in {"jsx_element", "jsx_self_closing_element", "jsx_fragment"}:
|
|
594
|
+
return True
|
|
595
|
+
|
|
556
596
|
if n.type in {"block", "statement_block"}:
|
|
557
597
|
parent = n.parent
|
|
558
598
|
if parent and (self._is_function_node(parent) or self._is_catch_clause(parent)):
|
|
@@ -563,6 +603,8 @@ class FocusAnalyzer:
|
|
|
563
603
|
return self._is_function_node(n)
|
|
564
604
|
|
|
565
605
|
def _scope_type(self, n: Node) -> str:
|
|
606
|
+
if self.is_tsx and n.type in {"jsx_element", "jsx_self_closing_element", "jsx_fragment"}:
|
|
607
|
+
return "jsx"
|
|
566
608
|
if self._is_function_node(n):
|
|
567
609
|
return "function"
|
|
568
610
|
if self._is_catch_clause(n):
|
|
@@ -928,6 +970,7 @@ class FocusAnalyzer:
|
|
|
928
970
|
start_col=n.start_point.column,
|
|
929
971
|
end_col=n.end_point.column,
|
|
930
972
|
resolved=resolved,
|
|
973
|
+
containing_scope_id=next_scope.id if next_scope else self.global_scope.id,
|
|
931
974
|
containing_fn_scope_id=usage_fn_scope.id if usage_fn_scope else self.global_scope.id,
|
|
932
975
|
)
|
|
933
976
|
)
|
|
@@ -1124,26 +1167,20 @@ def compute_scope_graph(
|
|
|
1124
1167
|
isCaptured=False
|
|
1125
1168
|
))
|
|
1126
1169
|
|
|
1127
|
-
captured_map: Dict[
|
|
1128
|
-
|
|
1129
|
-
descendant_ids = {s.id}
|
|
1130
|
-
queue = [s.id]
|
|
1131
|
-
while queue:
|
|
1132
|
-
curr_id = queue.pop(0)
|
|
1133
|
-
curr_s = analyzer.scopes[curr_id]
|
|
1134
|
-
descendant_ids.add(curr_id)
|
|
1135
|
-
queue.extend(curr_s.children_ids)
|
|
1136
|
-
|
|
1170
|
+
captured_map: Dict[tuple, _Def] = {}
|
|
1171
|
+
|
|
1137
1172
|
ancestor_ids = set(analyzer._ancestor_chain(s.id))
|
|
1138
1173
|
if s.id in ancestor_ids:
|
|
1139
1174
|
ancestor_ids.remove(s.id)
|
|
1140
1175
|
|
|
1141
1176
|
for u in analyzer.usages:
|
|
1142
|
-
|
|
1177
|
+
# Attribute captured variables to the closest scope boundary.
|
|
1178
|
+
# This includes JSX scopes so each TSX element can show its own captures.
|
|
1179
|
+
if u.containing_scope_id == s.id:
|
|
1143
1180
|
d = u.resolved
|
|
1144
1181
|
if d and d.scope_id in ancestor_ids and d.name not in _BUILTINS:
|
|
1145
1182
|
if d.kind != "import" and d.scope_type != "global":
|
|
1146
|
-
captured_map[d.name] = d
|
|
1183
|
+
captured_map[(d.name, d.def_line)] = d
|
|
1147
1184
|
|
|
1148
1185
|
captured_nodes: List[SymbolNode] = []
|
|
1149
1186
|
for d in captured_map.values():
|
|
@@ -1163,8 +1200,8 @@ def compute_scope_graph(
|
|
|
1163
1200
|
if node:
|
|
1164
1201
|
child_nodes.append(node)
|
|
1165
1202
|
|
|
1166
|
-
# Prune empty nodes
|
|
1167
|
-
if not declared_nodes and not captured_nodes and not child_nodes:
|
|
1203
|
+
# Prune empty nodes (but never prune JSX scopes; we want the full JSX tree).
|
|
1204
|
+
if s.type != "jsx" and not declared_nodes and not captured_nodes and not child_nodes:
|
|
1168
1205
|
# But keep the root of our request?
|
|
1169
1206
|
# The function `compute_scope_graph` returns `ScopeGraph(root=...)`.
|
|
1170
1207
|
# If the top-level scope itself is empty, we must return *something* or handle it.
|