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