@herbertgao/pi-extensions 2026.10.0 → 2026.10.2
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.
- package/README.md +16 -12
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/index.js +4 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +43 -3
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +142 -59
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
- package/node_modules/@dietrichgebert/ponytail/README.md +52 -242
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +70 -16
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +8 -7
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +46 -23
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +39 -7
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +20 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/package.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +47 -17
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -7
- package/node_modules/@dietrichgebert/ponytail/skills/plugin.json +5 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -27
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +21 -18
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +12 -13
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -52
- package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
- package/node_modules/@herbertgao/pi-agency/README.md +40 -0
- package/node_modules/@herbertgao/pi-agency/package.json +53 -0
- package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +4 -4
- package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
- package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
- package/node_modules/@tifan/pi-recap/package.json +1 -1
- package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
- package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
- package/node_modules/pi-antigravity/README.md +23 -2
- package/node_modules/pi-antigravity/package.json +5 -3
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
- package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
- package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
- package/node_modules/pi-antigravity/src/image/image.ts +86 -47
- package/node_modules/pi-antigravity/src/image/native.ts +69 -0
- package/node_modules/pi-antigravity/src/index.ts +52 -137
- package/node_modules/pi-antigravity/src/search/search.ts +130 -31
- package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
- package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
- package/node_modules/pi-antigravity/src/types/types.ts +1 -1
- package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
- package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
- package/node_modules/pi-lens/CHANGELOG.md +1064 -0
- package/node_modules/pi-lens/README.md +115 -25
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
- package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
- package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
- package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
- package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
- package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
- package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
- package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
- package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
- package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
- package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
- package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
- package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
- package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
- package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
- package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
- package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
- package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
- package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
- package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
- package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
- package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
- package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
- package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
- package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
- package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
- package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
- package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
- package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
- package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
- package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
- package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
- package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
- package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
- package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
- package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
- package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
- package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
- package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
- package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
- package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
- package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
- package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
- package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
- package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
- package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
- package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
- package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
- package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
- package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
- package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
- package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
- package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
- package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
- package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
- package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
- package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
- package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
- package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
- package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
- package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
- package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
- package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
- package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
- package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
- package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
- package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
- package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
- package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
- package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
- package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
- package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
- package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
- package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
- package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
- package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
- package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
- package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
- package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
- package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
- package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
- package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
- package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
- package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
- package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
- package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
- package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
- package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
- package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
- package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
- package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
- package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
- package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
- package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
- package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
- package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
- package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
- package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
- package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
- package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
- package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
- package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
- package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
- package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
- package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
- package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
- package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
- package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
- package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
- package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
- package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
- package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
- package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
- package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
- package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
- package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
- package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
- package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
- package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
- package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
- package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
- package/node_modules/pi-lens/dist/index.js +16456 -8335
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
- package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
- package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
- package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
- package/node_modules/pi-lens/dist/probes/installer.js +135 -0
- package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
- package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
- package/node_modules/pi-lens/docs/agent-guide.md +13 -4
- package/node_modules/pi-lens/docs/agent-tools.md +42 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
- package/node_modules/pi-lens/docs/configuration.md +20 -1
- package/node_modules/pi-lens/docs/environment-variables.md +52 -2
- package/node_modules/pi-lens/docs/features.md +8 -4
- package/node_modules/pi-lens/docs/globalconfig.md +1 -0
- package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
- package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
- package/node_modules/pi-lens/docs/mcp.md +21 -5
- package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
- package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
- package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
- package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
- package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
- package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
- package/node_modules/pi-lens/docs/settings.md +1 -0
- package/node_modules/pi-lens/docs/usage.md +5 -1
- package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
- package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
- package/node_modules/pi-lens/package.json +18 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
- package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
- package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
- package/node_modules/pi-typesafe/README.md +1 -1
- package/node_modules/pi-typesafe/dist/backends.d.ts +15 -5
- package/node_modules/pi-typesafe/dist/backends.js +18 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +4 -3
- package/node_modules/pi-typesafe/dist/client.js +22 -7
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/dist/usage.d.ts +14 -4
- package/node_modules/pi-typesafe/dist/usage.js +147 -42
- package/node_modules/pi-typesafe/package.json +1 -1
- package/node_modules/pi-web-access/CHANGELOG.md +48 -0
- package/node_modules/pi-web-access/README.md +57 -11
- package/node_modules/pi-web-access/brave-rate-limit.ts +8 -2
- package/node_modules/pi-web-access/curator-page.ts +5 -1
- package/node_modules/pi-web-access/curator-server.ts +2 -0
- package/node_modules/pi-web-access/declared-web-links.ts +2 -2
- package/node_modules/pi-web-access/degoog.ts +323 -0
- package/node_modules/pi-web-access/dist/index.js +3483 -2745
- package/node_modules/pi-web-access/dist/mcp-cli.js +27443 -0
- package/node_modules/pi-web-access/exa.ts +5 -1
- package/node_modules/pi-web-access/extract.ts +41 -3
- package/node_modules/pi-web-access/firecrawl.ts +35 -4
- package/node_modules/pi-web-access/gemini-search.ts +34 -11
- package/node_modules/pi-web-access/gemini-web.ts +14 -9
- package/node_modules/pi-web-access/index.ts +153 -1202
- package/node_modules/pi-web-access/keenable.ts +221 -0
- package/node_modules/pi-web-access/mcp-cli.ts +40 -0
- package/node_modules/pi-web-access/mcp-server.ts +81 -0
- package/node_modules/pi-web-access/mcp-tool-definitions.ts +117 -0
- package/node_modules/pi-web-access/openai-search.ts +34 -1
- package/node_modules/pi-web-access/package.json +23 -5
- package/node_modules/pi-web-access/perplexity.ts +64 -56
- package/node_modules/pi-web-access/tool-arguments.ts +20 -0
- package/node_modules/pi-web-access/video-extract.ts +1 -1
- package/node_modules/pi-web-access/web-tool-contract.ts +86 -0
- package/node_modules/pi-web-access/web-tool-core.ts +1502 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -5
- package/package.json +17 -12
- package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -329
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -346
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +0 -21
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +0 -62
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +0 -115
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +0 -27
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +0 -27
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +0 -4
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +0 -4
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
- package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
- package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
- package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
- package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
- package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
- package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
- package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
- package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
- package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
- package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
- package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
- package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
- package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
- package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
- package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
- package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
- package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
- package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
- package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
- package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
- package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
- package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
- package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
- package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
- package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
- package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
- package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
- package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
- package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
- package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
- package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
- package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
- package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
- package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
- package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
- package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
- package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
- package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
- package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
- package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
- package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
- package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
- package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
- package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
- package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
- package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
- package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
- package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
- package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
- package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
- package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
- package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
- package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
- package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
- package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
- package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
- package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
- package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
- package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
- package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
- package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
- package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
- package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
- package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
- package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
- package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
- package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
- package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
- package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
- package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
- package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
- package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
- package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
- package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
- package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
- package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
- package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
- package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
- package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
- package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
- package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
- package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
- package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
- package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
- package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
- package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
- package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
- package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
- package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
- package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
- package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
- package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
- package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
- package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
- package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
- package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
- package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
- package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
- package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
- package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
- package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
- package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
- package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
- package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
- package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
- package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
- package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
- package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
- package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
- package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
- package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
- package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
- package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
- package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
- package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
- package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
- package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
- package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
- package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
- package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
- package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
- package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
- package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
- package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
- package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
- package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
- package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
- package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
- package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
- package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
- package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
- package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
- package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
- package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
- package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
- package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
- package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
- package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
- package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
- package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
- package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
- package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
- package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
- package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
- package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
- package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
- package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
- package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
- package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
- package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
- package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
- package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
- package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
- package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
- package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
- package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
- package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
- package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
- package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
- package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
- package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
- package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
- package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
- package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
- package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
- package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
- package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
- package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
- package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
- package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
- package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
- package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
- package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
- package/node_modules/pi-lens/dist/i18n.js +0 -69
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
- package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
- package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
- package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
- package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
- package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
- package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
- package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
- package/node_modules/pi-lens/dist/tools/shared.js +0 -29
- package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
|
@@ -1,2150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Module Report (#245) — a structured, navigable substitute for raw full-file
|
|
3
|
-
* reads. An agent calls this to understand a module's shape (outline, signatures,
|
|
4
|
-
* who-uses-this) before requesting exact source, so `read` becomes the fallback
|
|
5
|
-
* rather than the default.
|
|
6
|
-
*
|
|
7
|
-
* Language-uniform by construction: the outline is extracted for EVERY supported
|
|
8
|
-
* language through the one tree-sitter symbol extractor (jsts uses the
|
|
9
|
-
* `typescript` query), giving the same fields — name/kind/startLine/endLine/
|
|
10
|
-
* signature/exported — across all 18 SYMBOL_QUERIES languages plus jsts. The
|
|
11
|
-
* review graph supplies cross-file enrichment (who-uses-this, complexity, fanout)
|
|
12
|
-
* merged onto entries by symbol name; that enrichment is additive and varies by
|
|
13
|
-
* language (e.g. complexity exists for jsts), which is honest rather than faked.
|
|
14
|
-
*
|
|
15
|
-
* Single mode (no depth knob — #256). READ-ONLY by contract — it never builds a
|
|
16
|
-
* graph and never calls an LSP server on this path (both repeatedly OOM'd pi when
|
|
17
|
-
* an agent fanned out reports). Every call:
|
|
18
|
-
* 1. tree-sitter extract of THE one file (always; cold-safe structure).
|
|
19
|
-
* 2. language-agnostic inline executable extraction over the same tree-sitter
|
|
20
|
-
* AST (callbacks/closures/lambdas/function literals; no second parse).
|
|
21
|
-
* 3. read the already-built review graph (in-memory, else the persisted disk
|
|
22
|
-
* snapshot) for who-uses-this / flags / imports — never a build. Cold cache
|
|
23
|
-
* → outline only.
|
|
24
|
-
* `semantic.source` reflects who-uses-this provenance: "review-graph" when the
|
|
25
|
-
* cached graph backs it, "unavailable:file-cap" when a size verdict disabled the
|
|
26
|
-
* graph, else "none" (cold). Live-LSP enrichment is re-homed to #236, where LSP
|
|
27
|
-
* writes provenance-tagged edges INTO the graph (once, persisted) for this path
|
|
28
|
-
* to read as "graph-lsp". That logic lives in clients/module-report-lsp.ts.
|
|
29
|
-
*
|
|
30
|
-
* Guard integrity: moduleReport injects NO read records — an outline is not
|
|
31
|
-
* "having seen the body". readSymbol returns the actual body lines so the host
|
|
32
|
-
* can record a read that legitimately satisfies the read-guard for that symbol.
|
|
33
|
-
*/
|
|
34
|
-
import * as fs from "node:fs";
|
|
35
|
-
import * as path from "node:path";
|
|
36
|
-
import { detectFileKind } from "./file-kinds.js";
|
|
37
|
-
import { logLatency } from "./latency-logger.js";
|
|
38
|
-
import { annotateMiddleMan } from "./middle-man-analysis.js";
|
|
39
|
-
import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
|
|
40
|
-
import { loadCallGraph, } from "./call-graph.js";
|
|
41
|
-
import { resolveImportToFiles } from "./review-graph/import-resolvers.js";
|
|
42
|
-
import { buildSymbolId } from "./review-graph/symbol-id.js";
|
|
43
|
-
import { getSharedTreeSitterClient, resolveTreeSitterLanguage, } from "./tree-sitter-shared.js";
|
|
44
|
-
import { symbolExtractionGrammar, TreeSitterSymbolExtractor, } from "./tree-sitter-symbol-extractor.js";
|
|
45
|
-
// NOTE: live-LSP enrichment is NO LONGER called on this read path (#256). Firing
|
|
46
|
-
// LSP per read — speculatively, on the agent's fan-out path — repeatedly OOM'd
|
|
47
|
-
// pi. The enrichment logic is kept as a separate module (clients/module-report-
|
|
48
|
-
// lsp.ts) to be re-homed in #236, where LSP writes provenance-tagged edges INTO
|
|
49
|
-
// the review graph (computed once, persisted) so this path just reads them.
|
|
50
|
-
/** Hard payload bound for the per-symbol who-uses-this section. */
|
|
51
|
-
const MAX_MODULE_REPORT_REFS = 100;
|
|
52
|
-
function normalizeMaxRefsPerSymbol(value) {
|
|
53
|
-
if (value === undefined || !Number.isFinite(value))
|
|
54
|
-
return 10;
|
|
55
|
-
return Math.min(MAX_MODULE_REPORT_REFS, Math.max(1, Math.floor(value)));
|
|
56
|
-
}
|
|
57
|
-
export function tsLangForFile(filePath, kind) {
|
|
58
|
-
// jsts is an extension-split kind with no single grammar: resolve it through
|
|
59
|
-
// the SHARED ext -> grammar authority (language-registry via
|
|
60
|
-
// tree-sitter-shared) so a file is parsed and TreeCache-keyed
|
|
61
|
-
// (`languageId:path`) under exactly one grammar process-wide. #887: this used
|
|
62
|
-
// to hand-roll a local map that sent .js/.mjs/.cjs to the typescript grammar
|
|
63
|
-
// and .jsx to tsx, so every plain-JS file was parsed and cached twice under
|
|
64
|
-
// two grammars and ran TS-grammar symbol queries on JS trees. .svelte/.vue
|
|
65
|
-
// have no grammar wiring and keep the historical kind default.
|
|
66
|
-
if (kind === "jsts") {
|
|
67
|
-
return resolveTreeSitterLanguage(filePath) ?? "typescript";
|
|
68
|
-
}
|
|
69
|
-
// Every other kind goes through the registry-derived resolver, which handles
|
|
70
|
-
// the cxx `.c`/`.h`-vs-cpp split and only answers for grammars that have
|
|
71
|
-
// symbol queries. #2424 deleted the hand-kept kind -> languageId map that
|
|
72
|
-
// used to live here alongside review-graph's copy of the same thing.
|
|
73
|
-
return symbolExtractionGrammar(kind, filePath);
|
|
74
|
-
}
|
|
75
|
-
// Per-language extractor cache — extractors are cheap once their queries are
|
|
76
|
-
// compiled. The shared TreeSitterClient (which memoizes grammar init) is obtained
|
|
77
|
-
// per call from the process-wide singleton.
|
|
78
|
-
const extractorCache = new Map();
|
|
79
|
-
async function getExtractor(languageId) {
|
|
80
|
-
let cached = extractorCache.get(languageId);
|
|
81
|
-
if (!cached) {
|
|
82
|
-
cached = (async () => {
|
|
83
|
-
const client = getSharedTreeSitterClient();
|
|
84
|
-
if (!client)
|
|
85
|
-
return null;
|
|
86
|
-
const extractor = new TreeSitterSymbolExtractor(languageId, client);
|
|
87
|
-
const ok = await extractor.init();
|
|
88
|
-
return ok ? extractor : null;
|
|
89
|
-
})().catch((err) => {
|
|
90
|
-
extractorCache.delete(languageId);
|
|
91
|
-
throw err;
|
|
92
|
-
});
|
|
93
|
-
extractorCache.set(languageId, cached);
|
|
94
|
-
}
|
|
95
|
-
return cached;
|
|
96
|
-
}
|
|
97
|
-
function diagnosticMessage(err) {
|
|
98
|
-
return err instanceof Error ? err.message : String(err);
|
|
99
|
-
}
|
|
100
|
-
async function extractFile(absPath, languageId, content) {
|
|
101
|
-
try {
|
|
102
|
-
const tsClient = getSharedTreeSitterClient();
|
|
103
|
-
if (!tsClient) {
|
|
104
|
-
return {
|
|
105
|
-
symbols: [],
|
|
106
|
-
imports: [],
|
|
107
|
-
callbacks: [],
|
|
108
|
-
error: "tree-sitter runtime unavailable (wasm aborted)",
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const initialized = await tsClient.init();
|
|
112
|
-
if (!initialized) {
|
|
113
|
-
return {
|
|
114
|
-
symbols: [],
|
|
115
|
-
imports: [],
|
|
116
|
-
callbacks: [],
|
|
117
|
-
error: "tree-sitter runtime failed to initialize",
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
const extractor = await getExtractor(languageId);
|
|
121
|
-
const extracted = await tsClient.withParsedTree(absPath, languageId, content, (tree) => {
|
|
122
|
-
const warnings = extractor
|
|
123
|
-
? []
|
|
124
|
-
: [`Symbol extractor not available for ${languageId}`];
|
|
125
|
-
const result = extractor
|
|
126
|
-
? extractor.extract(tree, absPath, content)
|
|
127
|
-
: { symbols: [], imports: [] };
|
|
128
|
-
const owners = result.symbols
|
|
129
|
-
.filter((candidate) => !candidate.local)
|
|
130
|
-
.map((candidate) => ({
|
|
131
|
-
name: candidate.name,
|
|
132
|
-
startLine: candidate.line,
|
|
133
|
-
endLine: candidate.endLine ?? candidate.line,
|
|
134
|
-
}));
|
|
135
|
-
let callbacks = [];
|
|
136
|
-
let callbackError;
|
|
137
|
-
try {
|
|
138
|
-
// SAFETY: `ModuleReportNode` is this module's own structural
|
|
139
|
-
// subset of a tree-sitter node: `type`, `text`, `children`,
|
|
140
|
-
// `parent`, `isNamed`, and the two positions. A real node
|
|
141
|
-
// implements all of it, but the two types are declared in
|
|
142
|
-
// unrelated packages so TypeScript sees no relation. The cast
|
|
143
|
-
// asserts only that structural subset; widen
|
|
144
|
-
// `ModuleReportNode` past what tree-sitter nodes provide and
|
|
145
|
-
// it stops holding.
|
|
146
|
-
callbacks = extractCallbacks(tree.rootNode, owners, languageId, warnings);
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
callbackError = diagnosticMessage(error);
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
symbols: result.symbols,
|
|
153
|
-
imports: result.imports,
|
|
154
|
-
callbacks,
|
|
155
|
-
callbackError,
|
|
156
|
-
warnings,
|
|
157
|
-
};
|
|
158
|
-
});
|
|
159
|
-
if (!extracted.parsed) {
|
|
160
|
-
return {
|
|
161
|
-
symbols: [],
|
|
162
|
-
imports: [],
|
|
163
|
-
callbacks: [],
|
|
164
|
-
error: `tree-sitter failed to parse as ${languageId}`,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
return extracted.value;
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
const message = diagnosticMessage(err);
|
|
171
|
-
logLatency({
|
|
172
|
-
type: "phase",
|
|
173
|
-
phase: "module_report_extract_error",
|
|
174
|
-
filePath: absPath,
|
|
175
|
-
durationMs: 0,
|
|
176
|
-
metadata: { error: message },
|
|
177
|
-
});
|
|
178
|
-
return { symbols: [], imports: [], callbacks: [], error: message };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function readArgsFor(filePath, startLine, endLine) {
|
|
182
|
-
const offset = Math.max(1, startLine);
|
|
183
|
-
const limit = Math.max(1, endLine - startLine + 1);
|
|
184
|
-
return { path: filePath, offset, limit };
|
|
185
|
-
}
|
|
186
|
-
function resolveUsedBy(graph, symbolNodeId, cap, projectRoot) {
|
|
187
|
-
const out = [];
|
|
188
|
-
const seen = new Set();
|
|
189
|
-
for (const edge of graph.edgesByTo.get(symbolNodeId) ?? []) {
|
|
190
|
-
if (edge.kind !== "calls" && edge.kind !== "references")
|
|
191
|
-
continue;
|
|
192
|
-
const from = graph.nodes.get(edge.from);
|
|
193
|
-
const rawFile = from?.filePath ??
|
|
194
|
-
(edge.from.startsWith("file:") ? edge.from.slice("file:".length) : "");
|
|
195
|
-
if (!rawFile)
|
|
196
|
-
continue;
|
|
197
|
-
const file = toDisplayPath(rawFile, projectRoot);
|
|
198
|
-
// refs #655 phase 2: prefer the owner-qualified display name
|
|
199
|
-
// (`UserService.run`) when the caller has one, so two different classes'
|
|
200
|
-
// same-named methods are distinguishable in `usedBy`/`blastRadius` output
|
|
201
|
-
// without cross-referencing line numbers. This exact dotted string is a
|
|
202
|
-
// valid `read_symbol` qualifier (see `ReviewGraphNode.qualifiedName`'s doc
|
|
203
|
-
// comment) — both derive from the same containment notion of "owner".
|
|
204
|
-
const symbol = from?.qualifiedName ?? from?.symbolName ?? "";
|
|
205
|
-
// Caller line: a symbol caller node carries metadata.line; a file-level
|
|
206
|
-
// `references` edge carries the line on the edge metadata.
|
|
207
|
-
const line = (typeof from?.metadata?.line === "number"
|
|
208
|
-
? from.metadata.line
|
|
209
|
-
: undefined) ??
|
|
210
|
-
(typeof edge.metadata?.line === "number"
|
|
211
|
-
? edge.metadata.line
|
|
212
|
-
: 0);
|
|
213
|
-
const key = `${file} ${symbol} ${edge.kind}`;
|
|
214
|
-
if (seen.has(key))
|
|
215
|
-
continue;
|
|
216
|
-
seen.add(key);
|
|
217
|
-
out.push({
|
|
218
|
-
file,
|
|
219
|
-
symbol,
|
|
220
|
-
line,
|
|
221
|
-
relation: edge.kind,
|
|
222
|
-
...(edge.resolution && edge.resolution !== "unresolved"
|
|
223
|
-
? { resolution: edge.resolution }
|
|
224
|
-
: {}),
|
|
225
|
-
});
|
|
226
|
-
if (out.length >= cap)
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
return out;
|
|
230
|
-
}
|
|
231
|
-
// Human-facing path: cwd-relative + forward-slashed when the file sits under the
|
|
232
|
-
// project root, else the absolute (slash-normalized) path. Machine fields (the
|
|
233
|
-
// `read` args) keep the absolute path so the host's Read tool resolves them
|
|
234
|
-
// unambiguously; only display fields (`path`, `usedBy.file`, imports) relativize.
|
|
235
|
-
function toDisplayPath(p, projectRoot) {
|
|
236
|
-
return toProjectRelativePath(p, projectRoot);
|
|
237
|
-
}
|
|
238
|
-
function collectImports(graph, normalizedPath, projectRoot) {
|
|
239
|
-
const fileNodeId = graph.fileNodes.get(normalizedPath);
|
|
240
|
-
const external = new Set();
|
|
241
|
-
const internal = new Set();
|
|
242
|
-
if (fileNodeId) {
|
|
243
|
-
for (const edge of graph.edgesByFrom.get(fileNodeId) ?? []) {
|
|
244
|
-
if (edge.kind !== "imports")
|
|
245
|
-
continue;
|
|
246
|
-
const target = graph.nodes.get(edge.to);
|
|
247
|
-
if (!target)
|
|
248
|
-
continue;
|
|
249
|
-
if (target.kind === "external") {
|
|
250
|
-
external.add(String(target.metadata?.source ?? edge.to));
|
|
251
|
-
}
|
|
252
|
-
else if (target.filePath) {
|
|
253
|
-
internal.add(toDisplayPath(target.filePath, projectRoot));
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
internal.add(String(target.metadata?.source ?? edge.to));
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
external: [...external].sort((a, b) => a.localeCompare(b)),
|
|
262
|
-
internal: [...internal].sort((a, b) => a.localeCompare(b)),
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
// An import source string "looks internal" when its shape names a same-project
|
|
266
|
-
// target the per-language resolver couldn't pin to a file (a not-yet-created
|
|
267
|
-
// file, or a language we don't resolve to disk). Relative paths and Rust's
|
|
268
|
-
// crate-relative prefixes are unambiguously in-project; everything else
|
|
269
|
-
// (bare specifiers, absolute package paths) is treated as external. This is the
|
|
270
|
-
// floor under `resolveImportToFiles` — it never fabricates a file path, only a
|
|
271
|
-
// best-effort internal/external bucket.
|
|
272
|
-
function looksInternal(source, languageId) {
|
|
273
|
-
// C/C++ (#302): a #include is local-vs-system by syntax, not by a leading dot.
|
|
274
|
-
// A system header keeps its angle brackets (<stdio.h>) → external; a quoted
|
|
275
|
-
// local include arrives bare (foo.h, quotes already stripped) → internal, even
|
|
276
|
-
// when the header file isn't on disk.
|
|
277
|
-
if (languageId === "c" || languageId === "cpp") {
|
|
278
|
-
return !source.startsWith("<");
|
|
279
|
-
}
|
|
280
|
-
// A leading "." is relative across every language we extract (JS ./ ../,
|
|
281
|
-
// Python . .foo ..pkg, Ruby/Dart/bash relative paths) and never begins a bare
|
|
282
|
-
// or scoped package specifier (react, @scope/pkg, java.util.List, fmt). Rust's
|
|
283
|
-
// crate-relative prefixes are likewise unambiguously in-project.
|
|
284
|
-
return (source.startsWith(".") ||
|
|
285
|
-
source.startsWith("crate::") ||
|
|
286
|
-
source.startsWith("super::") ||
|
|
287
|
-
source.startsWith("self::"));
|
|
288
|
-
}
|
|
289
|
-
// Cold-cache imports (#301): the warm review graph is the source of truth for
|
|
290
|
-
// imports, but on a cold cache it's absent and `collectImports` returns empty
|
|
291
|
-
// even though the tree-sitter extractor already parsed the import sources. Rebuild
|
|
292
|
-
// the same {external, internal} shape language-uniformly from those sources:
|
|
293
|
-
// resolve each to real in-project files via the warm graph's own resolver
|
|
294
|
-
// (`resolveImportToFiles`) when the language supports it, else fall back to the
|
|
295
|
-
// shape heuristic. Internal entries are cwd-relative display paths (resolved) or
|
|
296
|
-
// the raw source (heuristic), mirroring `collectImports`.
|
|
297
|
-
function coldImports(imports, languageId, absPath, projectRoot) {
|
|
298
|
-
const external = new Set();
|
|
299
|
-
const internal = new Set();
|
|
300
|
-
const warnings = [];
|
|
301
|
-
for (const imp of imports) {
|
|
302
|
-
let files = [];
|
|
303
|
-
try {
|
|
304
|
-
files = resolveImportToFiles(projectRoot, absPath, languageId, imp.source);
|
|
305
|
-
}
|
|
306
|
-
catch (err) {
|
|
307
|
-
const message = diagnosticMessage(err);
|
|
308
|
-
warnings.push(`Failed to resolve import "${imp.source}": ${message}`);
|
|
309
|
-
logLatency({
|
|
310
|
-
type: "phase",
|
|
311
|
-
phase: "module_report_import_resolve_error",
|
|
312
|
-
filePath: absPath,
|
|
313
|
-
durationMs: 0,
|
|
314
|
-
metadata: {
|
|
315
|
-
import: imp.source,
|
|
316
|
-
error: message,
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
if (files.length > 0) {
|
|
321
|
-
for (const f of files)
|
|
322
|
-
internal.add(toDisplayPath(f, projectRoot));
|
|
323
|
-
}
|
|
324
|
-
else if (looksInternal(imp.source, languageId)) {
|
|
325
|
-
internal.add(imp.source);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
external.add(imp.source);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
external: [...external].sort((a, b) => a.localeCompare(b)),
|
|
333
|
-
internal: [...internal].sort((a, b) => a.localeCompare(b)),
|
|
334
|
-
warnings,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function toEntry(sym, normalizedPath, graph, maxRefs, projectRoot, fileKind) {
|
|
338
|
-
const startLine = sym.line;
|
|
339
|
-
const endLine = sym.endLine ?? sym.line;
|
|
340
|
-
// The graph-node `kind` bucket in the ID must match whatever builder.ts
|
|
341
|
-
// actually stamped on the node (refs #655 shared-ID-helper invariant), NOT
|
|
342
|
-
// necessarily this file's own extractor's `sym.kind`. For jsts specifically,
|
|
343
|
-
// builder.ts's addJsTsFile sources symbols from a DIFFERENT extractor
|
|
344
|
-
// (dispatch/facts/function-facts.ts) that has no method/function distinction
|
|
345
|
-
// — every jsts symbol (including class methods) is stamped `"function"`. This
|
|
346
|
-
// extractor (tree-sitter-symbol-extractor, used here for ALL languages'
|
|
347
|
-
// outlines) does distinguish `"method"` from `"function"`, so naively reusing
|
|
348
|
-
// `sym.kind` for jsts would look up the wrong bucket and silently break
|
|
349
|
-
// usedBy/fanout for every TS/JS method. Every other language builds its graph
|
|
350
|
-
// node with THIS SAME extractor, so `sym.kind` matches there.
|
|
351
|
-
const idKind = fileKind === "jsts" ? "function" : sym.kind;
|
|
352
|
-
const symbolNodeId = buildSymbolId(normalizedPath, sym.name, idKind, sym.line);
|
|
353
|
-
const node = graph?.nodes.get(symbolNodeId);
|
|
354
|
-
const metadata = node?.metadata ?? {};
|
|
355
|
-
const complexity = typeof metadata.cyclomaticComplexity === "number"
|
|
356
|
-
? metadata.cyclomaticComplexity
|
|
357
|
-
: undefined;
|
|
358
|
-
const fanout = graph
|
|
359
|
-
? (graph.edgesByFrom.get(symbolNodeId) ?? []).filter((edge) => edge.kind === "calls").length
|
|
360
|
-
: undefined;
|
|
361
|
-
// A private/protected member of an exported class is reachable but NOT part
|
|
362
|
-
// of the public API, so it must not count as `exported` for the api/internal
|
|
363
|
-
// split or read ranking (#258). The extractor's sym.isExported is untouched
|
|
364
|
-
// (the review graph still sees the full surface); this gating is local to
|
|
365
|
-
// the report's presentation.
|
|
366
|
-
const nonPublic = sym.visibility === "private" || sym.visibility === "protected";
|
|
367
|
-
const exported = (sym.isExported || !!node?.exported) && !nonPublic;
|
|
368
|
-
// `flags` carries only non-derivable signals — "exported" is NOT pushed here
|
|
369
|
-
// since it duplicates the `exported` boolean field below (#512).
|
|
370
|
-
const flags = [];
|
|
371
|
-
if (sym.isAsync)
|
|
372
|
-
flags.push("async");
|
|
373
|
-
if (fanout !== undefined && fanout >= 4)
|
|
374
|
-
flags.push("high fanout");
|
|
375
|
-
if (complexity !== undefined && complexity >= 8)
|
|
376
|
-
flags.push("high complexity");
|
|
377
|
-
if (metadata.isBoundaryWrapper)
|
|
378
|
-
flags.push("boundary wrapper");
|
|
379
|
-
const usedBy = graph
|
|
380
|
-
? resolveUsedBy(graph, symbolNodeId, maxRefs, projectRoot)
|
|
381
|
-
: undefined;
|
|
382
|
-
return {
|
|
383
|
-
name: sym.name,
|
|
384
|
-
kind: sym.kind,
|
|
385
|
-
startLine,
|
|
386
|
-
endLine,
|
|
387
|
-
exported,
|
|
388
|
-
...(sym.visibility ? { visibility: sym.visibility } : {}),
|
|
389
|
-
...(sym.decorators?.length ? { decorators: sym.decorators } : {}),
|
|
390
|
-
signature: sym.signature,
|
|
391
|
-
doc: sym.doc,
|
|
392
|
-
fanout: fanout && fanout > 0 ? fanout : undefined,
|
|
393
|
-
complexity,
|
|
394
|
-
// Empty flags array would waste ~3-5 tokens per entry on a 41-symbol
|
|
395
|
-
// outline (~200 tok total). Omit when there's nothing to report.
|
|
396
|
-
...(flags.length > 0 ? { flags } : {}),
|
|
397
|
-
usedBy: usedBy && usedBy.length > 0 ? usedBy : undefined,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
// Nest members under their container by line-range containment (#301), mirroring
|
|
401
|
-
// ast-grep's outline: a class/interface's methods sit in its `members[]`, not at
|
|
402
|
-
// the top level. Each entry attaches to its NEAREST (smallest) strictly-enclosing
|
|
403
|
-
// entry, so arbitrary depth (a method in an inner class in an outer class) nests
|
|
404
|
-
// correctly. Mutates the entries (sets `members`) and returns the TOP-LEVEL ones
|
|
405
|
-
// (no container) for the api/internal split. The flat list stays usable for
|
|
406
|
-
// ranking so hot nested methods still surface in recommendedReads.
|
|
407
|
-
function nestEntries(entries) {
|
|
408
|
-
const span = (e) => e.endLine - e.startLine;
|
|
409
|
-
const containerOf = new Map();
|
|
410
|
-
for (const e of entries) {
|
|
411
|
-
let best;
|
|
412
|
-
for (const c of entries) {
|
|
413
|
-
if (c === e)
|
|
414
|
-
continue;
|
|
415
|
-
// Strict containment: c wraps e AND is strictly larger, so equal-range
|
|
416
|
-
// pairs (a mis-extracted class+ctor on the same lines) never mutually nest.
|
|
417
|
-
const contains = c.startLine <= e.startLine &&
|
|
418
|
-
c.endLine >= e.endLine &&
|
|
419
|
-
span(c) > span(e);
|
|
420
|
-
if (!contains)
|
|
421
|
-
continue;
|
|
422
|
-
if (!best || span(c) < span(best))
|
|
423
|
-
best = c;
|
|
424
|
-
}
|
|
425
|
-
containerOf.set(e, best);
|
|
426
|
-
}
|
|
427
|
-
for (const e of entries) {
|
|
428
|
-
const parent = containerOf.get(e);
|
|
429
|
-
if (!parent)
|
|
430
|
-
continue;
|
|
431
|
-
if (!parent.members)
|
|
432
|
-
parent.members = [];
|
|
433
|
-
parent.members.push(e);
|
|
434
|
-
}
|
|
435
|
-
for (const e of entries) {
|
|
436
|
-
if (e.members)
|
|
437
|
-
e.members.sort((a, b) => a.startLine - b.startLine);
|
|
438
|
-
}
|
|
439
|
-
return entries.filter((e) => !containerOf.get(e));
|
|
440
|
-
}
|
|
441
|
-
function summarizeEntries(entries) {
|
|
442
|
-
return entries.map((entry) => ({
|
|
443
|
-
name: entry.name,
|
|
444
|
-
kind: entry.kind,
|
|
445
|
-
startLine: entry.startLine,
|
|
446
|
-
endLine: entry.endLine,
|
|
447
|
-
exported: entry.exported,
|
|
448
|
-
...(entry.visibility ? { visibility: entry.visibility } : {}),
|
|
449
|
-
...(entry.signature ? { signature: entry.signature } : {}),
|
|
450
|
-
...(entry.doc ? { doc: entry.doc } : {}),
|
|
451
|
-
...(entry.flags ? { flags: entry.flags } : {}),
|
|
452
|
-
...(entry.members
|
|
453
|
-
? {
|
|
454
|
-
members: entry.members.map((member) => ({
|
|
455
|
-
name: member.name,
|
|
456
|
-
kind: member.kind,
|
|
457
|
-
startLine: member.startLine,
|
|
458
|
-
endLine: member.endLine,
|
|
459
|
-
exported: member.exported,
|
|
460
|
-
...(member.visibility ? { visibility: member.visibility } : {}),
|
|
461
|
-
...(member.signature ? { signature: member.signature } : {}),
|
|
462
|
-
...(member.doc ? { doc: member.doc } : {}),
|
|
463
|
-
})),
|
|
464
|
-
}
|
|
465
|
-
: {}),
|
|
466
|
-
}));
|
|
467
|
-
}
|
|
468
|
-
function normalizeFocus(focus) {
|
|
469
|
-
return (focus ?? "")
|
|
470
|
-
.toLowerCase()
|
|
471
|
-
.split(/[^a-z0-9_.]+/)
|
|
472
|
-
.map((part) => part.trim())
|
|
473
|
-
.filter((part) => part.length >= 3)
|
|
474
|
-
.slice(0, 8);
|
|
475
|
-
}
|
|
476
|
-
function focusScore(text, terms) {
|
|
477
|
-
if (terms.length === 0)
|
|
478
|
-
return 0;
|
|
479
|
-
const haystack = text.toLowerCase();
|
|
480
|
-
return terms.reduce((score, term) => score + (haystack.includes(term) ? 6 : 0), 0);
|
|
481
|
-
}
|
|
482
|
-
function rankRecommendedReads(entries, callbacks = [], limit = 5, focus) {
|
|
483
|
-
const focusTerms = normalizeFocus(focus);
|
|
484
|
-
const scoredSymbols = entries.map((entry) => {
|
|
485
|
-
const refs = entry.usedBy?.length ?? 0;
|
|
486
|
-
const focus = focusScore([
|
|
487
|
-
entry.name,
|
|
488
|
-
entry.kind,
|
|
489
|
-
entry.signature ?? "",
|
|
490
|
-
entry.flags?.join(" ") ?? "",
|
|
491
|
-
].join(" "), focusTerms);
|
|
492
|
-
const score = refs * 2 +
|
|
493
|
-
(entry.complexity ?? 0) +
|
|
494
|
-
(entry.exported ? 2 : 0) +
|
|
495
|
-
(entry.flags?.includes("high complexity") ? 3 : 0) +
|
|
496
|
-
focus;
|
|
497
|
-
return { kind: "symbol", entry, score, refs, focus };
|
|
498
|
-
});
|
|
499
|
-
const scoredCallbacks = callbacks.map((callback) => {
|
|
500
|
-
const flags = callback.flags ?? [];
|
|
501
|
-
const focus = focusScore([
|
|
502
|
-
callback.name,
|
|
503
|
-
callback.kind,
|
|
504
|
-
callback.signature ?? "",
|
|
505
|
-
flags.join(" "),
|
|
506
|
-
].join(" "), focusTerms);
|
|
507
|
-
const score = (flags.includes("captures ctx.ui") ? 8 : 0) +
|
|
508
|
-
(flags.includes("captures ctx") ? 5 : 0) +
|
|
509
|
-
(flags.includes("detached timer") ? 4 : 0) +
|
|
510
|
-
(flags.includes("lifecycle") ? 3 : 0) +
|
|
511
|
-
(callback.kind === "object_property_callback" ? 2 : 0) +
|
|
512
|
-
(callback.kind === "assigned_callback" ? 1 : 0) +
|
|
513
|
-
focus;
|
|
514
|
-
return { kind: "callback", callback, score, focus };
|
|
515
|
-
});
|
|
516
|
-
const scored = [...scoredSymbols, ...scoredCallbacks].filter((item) => item.score > 0);
|
|
517
|
-
scored.sort((a, b) => b.score - a.score);
|
|
518
|
-
return scored.slice(0, limit).map((item) => {
|
|
519
|
-
if (item.kind === "symbol") {
|
|
520
|
-
const { entry, refs } = item;
|
|
521
|
-
const reasons = [];
|
|
522
|
-
if (item.focus > 0)
|
|
523
|
-
reasons.push("matches focus");
|
|
524
|
-
if (entry.exported)
|
|
525
|
-
reasons.push("exported");
|
|
526
|
-
if (refs > 0)
|
|
527
|
-
reasons.push(`used by ${refs}`);
|
|
528
|
-
if (entry.complexity !== undefined && entry.complexity >= 8) {
|
|
529
|
-
reasons.push(`complexity ${entry.complexity}`);
|
|
530
|
-
}
|
|
531
|
-
return {
|
|
532
|
-
reason: reasons.join(", ") || "public surface",
|
|
533
|
-
symbol: entry.name,
|
|
534
|
-
startLine: entry.startLine,
|
|
535
|
-
endLine: entry.endLine,
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
const reasons = [];
|
|
539
|
-
if (item.focus > 0)
|
|
540
|
-
reasons.push("matches focus");
|
|
541
|
-
if (item.callback.flags?.length)
|
|
542
|
-
reasons.push(...item.callback.flags);
|
|
543
|
-
if (item.callback.kind === "object_property_callback") {
|
|
544
|
-
reasons.push("callback property");
|
|
545
|
-
}
|
|
546
|
-
if (item.callback.kind === "assigned_callback") {
|
|
547
|
-
reasons.push("assigned callback");
|
|
548
|
-
}
|
|
549
|
-
return {
|
|
550
|
-
reason: reasons.join(", ") || item.callback.kind,
|
|
551
|
-
symbol: item.callback.name,
|
|
552
|
-
startLine: item.callback.startLine,
|
|
553
|
-
endLine: item.callback.endLine,
|
|
554
|
-
};
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
// Cap the blast-radius list so a high-fanout module doesn't blow the token
|
|
558
|
-
// budget; the ranking puts the closest/most-depended-on files first. The read
|
|
559
|
-
// limit is the dependent file's own line count when the graph knows it, else a
|
|
560
|
-
// modest default — these are "go verify" pointers, not full dumps.
|
|
561
|
-
const BLAST_RADIUS_FILE_CAP = 12;
|
|
562
|
-
const BLAST_RADIUS_DEFAULT_READ_LIMIT = 400;
|
|
563
|
-
function blastReadArgs(graph, normalizedFile) {
|
|
564
|
-
const fileNodeId = graph.fileNodes.get(normalizedFile);
|
|
565
|
-
const lineCount = fileNodeId
|
|
566
|
-
? graph.nodes.get(fileNodeId)?.metadata?.lineCount
|
|
567
|
-
: undefined;
|
|
568
|
-
const limit = typeof lineCount === "number" && lineCount > 0
|
|
569
|
-
? lineCount
|
|
570
|
-
: BLAST_RADIUS_DEFAULT_READ_LIMIT;
|
|
571
|
-
// Machine field keeps the absolute (slash-normalized) path so the host's Read
|
|
572
|
-
// resolves it unambiguously — same convention as ModuleSymbolEntry.read.
|
|
573
|
-
return { path: normalizedFile, offset: 1, limit };
|
|
574
|
-
}
|
|
575
|
-
// Cross-file blast radius (#304): the transitive dependents of this module,
|
|
576
|
-
// aggregated from symbol-level impact hits to ranked FILE reads — "if you change
|
|
577
|
-
// this module, read/verify these files". Read-only over the CACHED graph the
|
|
578
|
-
// caller already loaded (never builds; the caller gates on a warm graph), so it
|
|
579
|
-
// shares module_report's #256 no-build contract. Returns undefined when nothing
|
|
580
|
-
// depends on the module (no section to show).
|
|
581
|
-
async function computeBlastRadius(graph, normalizedPath, projectRoot, maxDepth) {
|
|
582
|
-
const { computeTransitiveImpact } = await import("./review-graph/query.js");
|
|
583
|
-
const result = computeTransitiveImpact(graph, normalizedPath, { maxDepth });
|
|
584
|
-
const byFile = new Map();
|
|
585
|
-
for (const hit of result.hits) {
|
|
586
|
-
if (!hit.file)
|
|
587
|
-
continue;
|
|
588
|
-
const key = normalizeMapKey(hit.file);
|
|
589
|
-
if (key === normalizedPath)
|
|
590
|
-
continue; // never list the module itself
|
|
591
|
-
const cur = byFile.get(key) ?? {
|
|
592
|
-
dependents: 0,
|
|
593
|
-
minDepth: Number.POSITIVE_INFINITY,
|
|
594
|
-
relations: new Set(),
|
|
595
|
-
};
|
|
596
|
-
cur.dependents += 1;
|
|
597
|
-
cur.minDepth = Math.min(cur.minDepth, hit.depth);
|
|
598
|
-
cur.relations.add(hit.relation);
|
|
599
|
-
byFile.set(key, cur);
|
|
600
|
-
}
|
|
601
|
-
if (byFile.size === 0)
|
|
602
|
-
return undefined;
|
|
603
|
-
const files = [...byFile.entries()]
|
|
604
|
-
.map(([key, v]) => ({
|
|
605
|
-
file: toDisplayPath(key, projectRoot),
|
|
606
|
-
dependents: v.dependents,
|
|
607
|
-
minDepth: v.minDepth,
|
|
608
|
-
relations: [...v.relations].sort((a, b) => a.localeCompare(b)),
|
|
609
|
-
read: blastReadArgs(graph, key),
|
|
610
|
-
}))
|
|
611
|
-
// Closest hop first, then most-depended-on, then stable by path.
|
|
612
|
-
.sort((a, b) => a.minDepth - b.minDepth ||
|
|
613
|
-
b.dependents - a.dependents ||
|
|
614
|
-
a.file.localeCompare(b.file))
|
|
615
|
-
.slice(0, BLAST_RADIUS_FILE_CAP);
|
|
616
|
-
return {
|
|
617
|
-
truncated: result.truncated,
|
|
618
|
-
maxDepth: result.maxDepthReached,
|
|
619
|
-
files,
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function nodeLine(node) {
|
|
623
|
-
return node.startPosition.row + 1;
|
|
624
|
-
}
|
|
625
|
-
function nodeEndLine(node) {
|
|
626
|
-
return node.endPosition.row + 1;
|
|
627
|
-
}
|
|
628
|
-
function firstLine(text) {
|
|
629
|
-
return text.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
630
|
-
}
|
|
631
|
-
function findNearestSymbolName(entries, startLine, endLine) {
|
|
632
|
-
let best;
|
|
633
|
-
for (const entry of entries) {
|
|
634
|
-
if (entry.startLine > startLine || entry.endLine < endLine)
|
|
635
|
-
continue;
|
|
636
|
-
if (!best ||
|
|
637
|
-
entry.endLine - entry.startLine < best.endLine - best.startLine) {
|
|
638
|
-
best = entry;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return best?.name;
|
|
642
|
-
}
|
|
643
|
-
const INLINE_EXECUTABLE_NODE_KINDS = new Set([
|
|
644
|
-
// JavaScript / TypeScript
|
|
645
|
-
"arrow_function",
|
|
646
|
-
"function_expression",
|
|
647
|
-
// Python
|
|
648
|
-
"lambda",
|
|
649
|
-
// Go
|
|
650
|
-
"func_literal",
|
|
651
|
-
// Rust
|
|
652
|
-
"closure_expression",
|
|
653
|
-
// Swift / Kotlin (trailing/lambda closures)
|
|
654
|
-
"lambda_literal",
|
|
655
|
-
// Other grammars use one of these for lambdas/anonymous functions.
|
|
656
|
-
"lambda_expression",
|
|
657
|
-
"anonymous_function",
|
|
658
|
-
]);
|
|
659
|
-
const ARGUMENT_CONTAINER_NODE_KINDS = new Set([
|
|
660
|
-
"arguments",
|
|
661
|
-
"argument_list",
|
|
662
|
-
"argument_list_expression",
|
|
663
|
-
]);
|
|
664
|
-
// Call-node kinds across grammars: JS/TS/Rust use `call_expression`; Python and
|
|
665
|
-
// Ruby use a bare `call`. Accepting both lets the per-language callback rules see
|
|
666
|
-
// the enclosing call name (e.g. `loop.call_later`) regardless of grammar.
|
|
667
|
-
const CALL_NODE_KINDS = new Set(["call_expression", "call"]);
|
|
668
|
-
function callNameForCallback(node) {
|
|
669
|
-
const parent = node.parent;
|
|
670
|
-
const call = ARGUMENT_CONTAINER_NODE_KINDS.has(parent?.type ?? "")
|
|
671
|
-
? parent?.parent
|
|
672
|
-
: CALL_NODE_KINDS.has(parent?.type ?? "")
|
|
673
|
-
? parent
|
|
674
|
-
: undefined;
|
|
675
|
-
if (!call || !CALL_NODE_KINDS.has(call.type))
|
|
676
|
-
return undefined;
|
|
677
|
-
const callee = call.children.find((child) => !ARGUMENT_CONTAINER_NODE_KINDS.has(child.type));
|
|
678
|
-
return callee?.text;
|
|
679
|
-
}
|
|
680
|
-
function eventNameForCallback(node) {
|
|
681
|
-
const args = node.parent;
|
|
682
|
-
if (args?.type !== "arguments")
|
|
683
|
-
return undefined;
|
|
684
|
-
const first = args.children.find((child) => child.type === "string" && nodeLine(child) <= nodeLine(node));
|
|
685
|
-
return first?.text;
|
|
686
|
-
}
|
|
687
|
-
function propertyNameForCallback(node) {
|
|
688
|
-
const parent = node.parent;
|
|
689
|
-
if (parent?.type !== "pair" && parent?.type !== "key_value_pair") {
|
|
690
|
-
return undefined;
|
|
691
|
-
}
|
|
692
|
-
const key = parent.children.find((child) => child !== node);
|
|
693
|
-
return key?.text;
|
|
694
|
-
}
|
|
695
|
-
function assignedNameForCallback(node) {
|
|
696
|
-
const parent = node.parent;
|
|
697
|
-
if (!parent)
|
|
698
|
-
return undefined;
|
|
699
|
-
if (parent.type === "let_declaration" ||
|
|
700
|
-
parent.type === "variable_declarator") {
|
|
701
|
-
return parent.children.find((child) => child.type === "identifier")?.text;
|
|
702
|
-
}
|
|
703
|
-
if (parent.type === "expression_list") {
|
|
704
|
-
const declaration = parent.parent;
|
|
705
|
-
if (declaration?.type !== "short_var_declaration")
|
|
706
|
-
return undefined;
|
|
707
|
-
const nameList = declaration.children.find((child) => child !== parent);
|
|
708
|
-
return nameList?.children.find((child) => child.type === "identifier")
|
|
709
|
-
?.text;
|
|
710
|
-
}
|
|
711
|
-
if (parent.type === "assignment") {
|
|
712
|
-
return parent.children.find((child) => child.type === "identifier")?.text;
|
|
713
|
-
}
|
|
714
|
-
return undefined;
|
|
715
|
-
}
|
|
716
|
-
/** Walk up to `maxHops` ancestors looking for a node of one of `types`. */
|
|
717
|
-
function ancestorOfType(node, types, maxHops = 6) {
|
|
718
|
-
let current = node.parent;
|
|
719
|
-
let hops = 0;
|
|
720
|
-
while (current && hops < maxHops) {
|
|
721
|
-
if (types.has(current.type))
|
|
722
|
-
return current;
|
|
723
|
-
current = current.parent;
|
|
724
|
-
hops += 1;
|
|
725
|
-
}
|
|
726
|
-
return undefined;
|
|
727
|
-
}
|
|
728
|
-
/** Find the first descendant of `type` within `maxDepth` levels (shallow). */
|
|
729
|
-
function descendantOfType(node, type, maxDepth = 2) {
|
|
730
|
-
if (maxDepth < 0)
|
|
731
|
-
return undefined;
|
|
732
|
-
for (const child of node.children ?? []) {
|
|
733
|
-
if (child.type === type)
|
|
734
|
-
return child;
|
|
735
|
-
const found = descendantOfType(child, type, maxDepth - 1);
|
|
736
|
-
if (found)
|
|
737
|
-
return found;
|
|
738
|
-
}
|
|
739
|
-
return undefined;
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Generic, JS/TS-tuned classification — the historical behavior, now the
|
|
743
|
-
* default rule set for any language without a tuned entry. Kept byte-for-byte
|
|
744
|
-
* equivalent to the previous callbackKind/callbackFlags/shouldIncludeCallback
|
|
745
|
-
* so the refactor is behavior-preserving for every currently-supported language.
|
|
746
|
-
*/
|
|
747
|
-
function classifyGenericCallback(ctx) {
|
|
748
|
-
const { node, callName, propertyName, assignedName } = ctx;
|
|
749
|
-
let kind;
|
|
750
|
-
if (callName === "setTimeout" ||
|
|
751
|
-
callName === "setInterval" ||
|
|
752
|
-
callName === "setImmediate") {
|
|
753
|
-
kind = "timer_callback";
|
|
754
|
-
}
|
|
755
|
-
else if (callName === "pi.on" || callName?.endsWith(".on")) {
|
|
756
|
-
kind = "event_handler";
|
|
757
|
-
}
|
|
758
|
-
else if (callName?.endsWith(".then") ||
|
|
759
|
-
callName?.endsWith(".catch") ||
|
|
760
|
-
callName?.endsWith(".finally")) {
|
|
761
|
-
kind = "promise_callback";
|
|
762
|
-
}
|
|
763
|
-
else if (propertyName) {
|
|
764
|
-
kind = "object_property_callback";
|
|
765
|
-
}
|
|
766
|
-
else if (assignedName) {
|
|
767
|
-
kind = "assigned_callback";
|
|
768
|
-
}
|
|
769
|
-
else {
|
|
770
|
-
kind = "callback";
|
|
771
|
-
}
|
|
772
|
-
const flags = [];
|
|
773
|
-
if (node.text.trimStart().startsWith("async"))
|
|
774
|
-
flags.push("async");
|
|
775
|
-
if (/\bctx\s*\.\s*ui\b/.test(node.text))
|
|
776
|
-
flags.push("captures ctx.ui");
|
|
777
|
-
else if (/\bctx\b/.test(node.text))
|
|
778
|
-
flags.push("captures ctx");
|
|
779
|
-
if (kind === "timer_callback")
|
|
780
|
-
flags.push("detached timer");
|
|
781
|
-
if (kind === "event_handler")
|
|
782
|
-
flags.push("lifecycle");
|
|
783
|
-
const include = kind !== "callback" ||
|
|
784
|
-
!!propertyName ||
|
|
785
|
-
!!assignedName ||
|
|
786
|
-
flags.some((flag) => flag.startsWith("captures "));
|
|
787
|
-
return { kind, ...(flags.length > 0 ? { flags } : {}), include };
|
|
788
|
-
}
|
|
789
|
-
const jstsCallbackRules = {
|
|
790
|
-
classify: (ctx) => classifyGenericCallback(ctx),
|
|
791
|
-
};
|
|
792
|
-
// Go: goroutines (`go func() {…}()`) and deferred closures (`defer func() {…}()`)
|
|
793
|
-
// are the high-signal lifecycle constructs the generic rules DROP (they land as
|
|
794
|
-
// a bare "callback"). Detect them structurally via the enclosing go_statement /
|
|
795
|
-
// defer_statement — unambiguous node kinds, no call-name heuristics. Anything
|
|
796
|
-
// else (assigned closures etc.) delegates to the generic rules unchanged.
|
|
797
|
-
const GO_GOROUTINE_KINDS = new Set(["go_statement"]);
|
|
798
|
-
const GO_DEFER_KINDS = new Set(["defer_statement"]);
|
|
799
|
-
const goCallbackRules = {
|
|
800
|
-
classify(ctx, owner) {
|
|
801
|
-
if (ancestorOfType(ctx.node, GO_GOROUTINE_KINDS, 3)) {
|
|
802
|
-
return {
|
|
803
|
-
kind: "goroutine",
|
|
804
|
-
flags: ["goroutine"],
|
|
805
|
-
include: true,
|
|
806
|
-
nameBase: owner ? `${owner}.goroutine` : "goroutine",
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
if (ancestorOfType(ctx.node, GO_DEFER_KINDS, 3)) {
|
|
810
|
-
return {
|
|
811
|
-
kind: "deferred_callback",
|
|
812
|
-
flags: ["deferred"],
|
|
813
|
-
include: true,
|
|
814
|
-
nameBase: owner ? `${owner}.defer` : "defer",
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
return classifyGenericCallback(ctx);
|
|
818
|
-
},
|
|
819
|
-
};
|
|
820
|
-
/** Append a flag without duplicating it; tolerates an undefined start list. */
|
|
821
|
-
function withFlag(flags, flag) {
|
|
822
|
-
const next = flags ? [...flags] : [];
|
|
823
|
-
if (!next.includes(flag))
|
|
824
|
-
next.push(flag);
|
|
825
|
-
return next;
|
|
826
|
-
}
|
|
827
|
-
// Python: lambdas handed to schedulers/futures are the lifecycle-sensitive
|
|
828
|
-
// inline executables the generic rules drop (a bare-arg lambda lands as
|
|
829
|
-
// "callback"). Classify by the enclosing call name — now visible via the `call`
|
|
830
|
-
// node kind. Python has no `async` lambdas, so no async boundary to add here.
|
|
831
|
-
const pythonCallbackRules = {
|
|
832
|
-
classify(ctx) {
|
|
833
|
-
const base = classifyGenericCallback(ctx);
|
|
834
|
-
const callName = ctx.callName ?? "";
|
|
835
|
-
if (/(?:^|\.)(?:call_later|call_soon|call_at)$/.test(callName) ||
|
|
836
|
-
/(?:^|\.)Timer$/.test(callName)) {
|
|
837
|
-
return {
|
|
838
|
-
kind: "timer_callback",
|
|
839
|
-
flags: withFlag(base.flags, "detached timer"),
|
|
840
|
-
include: true,
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
if (callName.endsWith(".add_done_callback")) {
|
|
844
|
-
return {
|
|
845
|
-
kind: "future_callback",
|
|
846
|
-
flags: withFlag(base.flags, "future completion"),
|
|
847
|
-
include: true,
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
return base;
|
|
851
|
-
},
|
|
852
|
-
};
|
|
853
|
-
// Rust: closures handed to thread/task spawns, and `move` closures (capture by
|
|
854
|
-
// value — the classic detached-state shape), are high-signal. `move` is
|
|
855
|
-
// structurally certain: the closure text begins with `move` / `async move`.
|
|
856
|
-
const rustCallbackRules = {
|
|
857
|
-
classify(ctx) {
|
|
858
|
-
const base = classifyGenericCallback(ctx);
|
|
859
|
-
const isMove = /^\s*(?:async\s+)?move\b/.test(ctx.node.text);
|
|
860
|
-
const flags = isMove ? withFlag(base.flags, "move") : base.flags;
|
|
861
|
-
const callName = ctx.callName ?? "";
|
|
862
|
-
if (/(?:^|::|\.)spawn$/.test(callName)) {
|
|
863
|
-
return {
|
|
864
|
-
kind: "task",
|
|
865
|
-
flags: withFlag(flags, "spawned"),
|
|
866
|
-
include: true,
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
return {
|
|
870
|
-
...base,
|
|
871
|
-
...(flags ? { flags } : {}),
|
|
872
|
-
include: base.include || isMove,
|
|
873
|
-
};
|
|
874
|
-
},
|
|
875
|
-
};
|
|
876
|
-
// Swift: the canonical Swift lifecycle bug is a closure that captures `self`
|
|
877
|
-
// strongly across an async boundary (retain cycle). The capture list is fully
|
|
878
|
-
// structural — `capture_list → capture_list_item → ownership_modifier`
|
|
879
|
-
// (weak/unowned) — so weak-vs-strong self capture is detectable with zero
|
|
880
|
-
// guessing. A strong self capture is the high-signal one we surface.
|
|
881
|
-
const swiftCallbackRules = {
|
|
882
|
-
classify(ctx) {
|
|
883
|
-
const base = classifyGenericCallback(ctx);
|
|
884
|
-
const node = ctx.node;
|
|
885
|
-
const captureList = descendantOfType(node, "capture_list", 2);
|
|
886
|
-
let weakSelf = false;
|
|
887
|
-
if (captureList) {
|
|
888
|
-
for (const item of captureList.children ?? []) {
|
|
889
|
-
if (item.type !== "capture_list_item")
|
|
890
|
-
continue;
|
|
891
|
-
const own = item.children?.find((c) => c.type === "ownership_modifier");
|
|
892
|
-
if (own && /\bself\b/.test(item.text))
|
|
893
|
-
weakSelf = true;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
const refsSelf = /\bself\b/.test(node.text);
|
|
897
|
-
let flags = base.flags;
|
|
898
|
-
let include = base.include;
|
|
899
|
-
if (weakSelf) {
|
|
900
|
-
flags = withFlag(flags, "weak self");
|
|
901
|
-
include = true;
|
|
902
|
-
}
|
|
903
|
-
else if (refsSelf) {
|
|
904
|
-
flags = withFlag(flags, "captures self");
|
|
905
|
-
include = true;
|
|
906
|
-
}
|
|
907
|
-
return { ...base, ...(flags ? { flags } : {}), include };
|
|
908
|
-
},
|
|
909
|
-
};
|
|
910
|
-
// C++: a lambda with a by-reference default capture (`[&]`) can dangle once the
|
|
911
|
-
// enclosing scope returns — the classic async/thread bug. Capture mode is
|
|
912
|
-
// structural (`lambda_capture_specifier → lambda_default_capture`). Also flag
|
|
913
|
-
// std::thread / std::async launches.
|
|
914
|
-
const cppCallbackRules = {
|
|
915
|
-
classify(ctx) {
|
|
916
|
-
const base = classifyGenericCallback(ctx);
|
|
917
|
-
const capture = descendantOfType(ctx.node, "lambda_capture_specifier", 2);
|
|
918
|
-
let flags = base.flags;
|
|
919
|
-
let byRef = false;
|
|
920
|
-
if (capture) {
|
|
921
|
-
const def = capture.children?.find((c) => c.type === "lambda_default_capture");
|
|
922
|
-
if (def?.text.includes("&")) {
|
|
923
|
-
flags = withFlag(flags, "captures by reference");
|
|
924
|
-
byRef = true;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
const callName = ctx.callName ?? "";
|
|
928
|
-
if (/(?:^|::)(?:thread|async)$/.test(callName)) {
|
|
929
|
-
return {
|
|
930
|
-
kind: "task",
|
|
931
|
-
flags: withFlag(flags, "spawned"),
|
|
932
|
-
include: true,
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
return {
|
|
936
|
-
...base,
|
|
937
|
-
...(flags ? { flags } : {}),
|
|
938
|
-
include: base.include || byRef,
|
|
939
|
-
};
|
|
940
|
-
},
|
|
941
|
-
};
|
|
942
|
-
/** Trailing identifier of a (possibly dotted) callee, e.g. `scope.launch` → `launch`. */
|
|
943
|
-
function lastCalleeSegment(text) {
|
|
944
|
-
const m = String(text ?? "")
|
|
945
|
-
.trim()
|
|
946
|
-
.match(/([A-Za-z_$][\w$]*)\s*$/);
|
|
947
|
-
return m ? m[1] : "";
|
|
948
|
-
}
|
|
949
|
-
// Kotlin: coroutine builders (`launch`/`async`/`withContext`/`runBlocking`/…)
|
|
950
|
-
// are the dominant lifecycle/leak source. The trailing lambda sits under
|
|
951
|
-
// `call_expression → call_suffix → annotated_lambda`, so resolve the builder
|
|
952
|
-
// name from the enclosing call's callee.
|
|
953
|
-
const KOTLIN_COROUTINE_BUILDERS = new Set([
|
|
954
|
-
"launch",
|
|
955
|
-
"async",
|
|
956
|
-
"withContext",
|
|
957
|
-
"runBlocking",
|
|
958
|
-
"coroutineScope",
|
|
959
|
-
"supervisorScope",
|
|
960
|
-
]);
|
|
961
|
-
const kotlinCallbackRules = {
|
|
962
|
-
classify(ctx) {
|
|
963
|
-
const base = classifyGenericCallback(ctx);
|
|
964
|
-
const call = ancestorOfType(ctx.node, new Set(["call_expression"]), 4);
|
|
965
|
-
const callee = call?.children?.find((c) => c.type === "navigation_expression" || c.type === "simple_identifier");
|
|
966
|
-
const name = lastCalleeSegment(callee?.text);
|
|
967
|
-
if (KOTLIN_COROUTINE_BUILDERS.has(name)) {
|
|
968
|
-
return {
|
|
969
|
-
kind: "coroutine",
|
|
970
|
-
flags: withFlag(base.flags, "coroutine"),
|
|
971
|
-
include: true,
|
|
972
|
-
nameBase: name,
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
return base;
|
|
976
|
-
},
|
|
977
|
-
};
|
|
978
|
-
// Java: lambdas handed to `new Thread(...)`, executor `submit`/`execute`/
|
|
979
|
-
// `schedule`, or UI/event listeners. Resolve the constructor type or the
|
|
980
|
-
// method name from the enclosing invocation.
|
|
981
|
-
const JAVA_TASK_METHODS = /^(?:submit|execute|schedule|scheduleAtFixedRate|scheduleWithFixedDelay|invokeLater|invokeAndWait)$/;
|
|
982
|
-
const JAVA_LISTENER_METHODS = /^(?:add\w*Listener|set\w*Listener|subscribe|addCallback|then\w*)$/;
|
|
983
|
-
const javaCallbackRules = {
|
|
984
|
-
classify(ctx) {
|
|
985
|
-
const base = classifyGenericCallback(ctx);
|
|
986
|
-
const obj = ancestorOfType(ctx.node, new Set(["object_creation_expression"]), 3);
|
|
987
|
-
const created = obj?.children?.find((c) => c.type === "type_identifier");
|
|
988
|
-
if (created && created.text.endsWith("Thread")) {
|
|
989
|
-
return {
|
|
990
|
-
kind: "task",
|
|
991
|
-
flags: withFlag(base.flags, "thread"),
|
|
992
|
-
include: true,
|
|
993
|
-
nameBase: `new ${created.text}`,
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
const inv = ancestorOfType(ctx.node, new Set(["method_invocation"]), 3);
|
|
997
|
-
if (inv) {
|
|
998
|
-
const named = (inv.children ?? []).filter((c) => c.isNamed);
|
|
999
|
-
const argIdx = named.findIndex((c) => c.type === "argument_list");
|
|
1000
|
-
const nameNode = argIdx > 0 ? named[argIdx - 1] : undefined;
|
|
1001
|
-
const name = nameNode?.type === "identifier" ? nameNode.text : "";
|
|
1002
|
-
if (JAVA_TASK_METHODS.test(name)) {
|
|
1003
|
-
return {
|
|
1004
|
-
kind: "task",
|
|
1005
|
-
flags: withFlag(base.flags, "submitted"),
|
|
1006
|
-
include: true,
|
|
1007
|
-
nameBase: name,
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
if (JAVA_LISTENER_METHODS.test(name)) {
|
|
1011
|
-
return {
|
|
1012
|
-
kind: "event_handler",
|
|
1013
|
-
flags: withFlag(base.flags, "listener"),
|
|
1014
|
-
include: true,
|
|
1015
|
-
nameBase: name,
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
return base;
|
|
1020
|
-
},
|
|
1021
|
-
};
|
|
1022
|
-
// C#: event subscriptions (`x.Click += (s,e) => …`), `Task.Run`/`StartNew`
|
|
1023
|
-
// launches, and `async` lambdas. The event case is a lambda whose parent is a
|
|
1024
|
-
// `+=` assignment.
|
|
1025
|
-
const csharpCallbackRules = {
|
|
1026
|
-
classify(ctx) {
|
|
1027
|
-
const base = classifyGenericCallback(ctx);
|
|
1028
|
-
const parent = ctx.node.parent;
|
|
1029
|
-
if (parent?.type === "assignment_expression" && /\+=/.test(parent.text)) {
|
|
1030
|
-
return {
|
|
1031
|
-
kind: "event_handler",
|
|
1032
|
-
flags: withFlag(base.flags, "event +="),
|
|
1033
|
-
include: true,
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
const inv = ancestorOfType(ctx.node, new Set(["invocation_expression"]), 4);
|
|
1037
|
-
if (inv) {
|
|
1038
|
-
const callee = inv.children?.find((c) => c.type === "member_access_expression" || c.type === "identifier");
|
|
1039
|
-
const name = lastCalleeSegment(callee?.text);
|
|
1040
|
-
if (/^(?:Run|StartNew|Start)$/.test(name)) {
|
|
1041
|
-
return {
|
|
1042
|
-
kind: "task",
|
|
1043
|
-
flags: withFlag(base.flags, "task"),
|
|
1044
|
-
include: true,
|
|
1045
|
-
nameBase: name,
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
if (/^\s*async\b/.test(ctx.node.text)) {
|
|
1050
|
-
return {
|
|
1051
|
-
...base,
|
|
1052
|
-
flags: withFlag(base.flags, "async"),
|
|
1053
|
-
include: true,
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
return base;
|
|
1057
|
-
},
|
|
1058
|
-
};
|
|
1059
|
-
const CALLBACK_RULES = {
|
|
1060
|
-
typescript: jstsCallbackRules,
|
|
1061
|
-
tsx: jstsCallbackRules,
|
|
1062
|
-
javascript: jstsCallbackRules,
|
|
1063
|
-
go: goCallbackRules,
|
|
1064
|
-
python: pythonCallbackRules,
|
|
1065
|
-
rust: rustCallbackRules,
|
|
1066
|
-
swift: swiftCallbackRules,
|
|
1067
|
-
cpp: cppCallbackRules,
|
|
1068
|
-
kotlin: kotlinCallbackRules,
|
|
1069
|
-
java: javaCallbackRules,
|
|
1070
|
-
csharp: csharpCallbackRules,
|
|
1071
|
-
};
|
|
1072
|
-
function callbackRulesFor(languageId) {
|
|
1073
|
-
return ((languageId ? CALLBACK_RULES[languageId] : undefined) ?? jstsCallbackRules);
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Whether a language has a TUNED callback rule set (explicit CALLBACK_RULES
|
|
1077
|
-
* entry) vs. falling back to the generic JS/TS-shaped heuristics. Drives the
|
|
1078
|
-
* report's `callbackSupport` honesty signal.
|
|
1079
|
-
*/
|
|
1080
|
-
function callbackSupportFor(languageId) {
|
|
1081
|
-
return languageId && CALLBACK_RULES[languageId] ? "tuned" : "generic";
|
|
1082
|
-
}
|
|
1083
|
-
function extractCallbacks(root, entries, languageId, warnings) {
|
|
1084
|
-
if (!root)
|
|
1085
|
-
return [];
|
|
1086
|
-
const rules = callbackRulesFor(languageId);
|
|
1087
|
-
const callbacks = [];
|
|
1088
|
-
const maxDepth = 1000;
|
|
1089
|
-
let depthTruncated = false;
|
|
1090
|
-
const visit = (node, depth = 0) => {
|
|
1091
|
-
if (depth > maxDepth) {
|
|
1092
|
-
depthTruncated = true;
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
if (INLINE_EXECUTABLE_NODE_KINDS.has(node.type)) {
|
|
1096
|
-
const startLine = nodeLine(node);
|
|
1097
|
-
const endLine = nodeEndLine(node);
|
|
1098
|
-
const callName = callNameForCallback(node);
|
|
1099
|
-
const eventName = eventNameForCallback(node);
|
|
1100
|
-
const propertyName = propertyNameForCallback(node);
|
|
1101
|
-
const assignedName = assignedNameForCallback(node);
|
|
1102
|
-
const owner = findNearestSymbolName(entries, startLine, endLine);
|
|
1103
|
-
const cls = rules.classify({ node, callName, eventName, propertyName, assignedName }, owner);
|
|
1104
|
-
if (cls.include) {
|
|
1105
|
-
const base = cls.nameBase ??
|
|
1106
|
-
(propertyName
|
|
1107
|
-
? owner
|
|
1108
|
-
? `${owner}.${propertyName}`
|
|
1109
|
-
: propertyName
|
|
1110
|
-
: assignedName
|
|
1111
|
-
? owner
|
|
1112
|
-
? `${owner}.${assignedName}`
|
|
1113
|
-
: assignedName
|
|
1114
|
-
: eventName && callName
|
|
1115
|
-
? `${callName}(${eventName})`
|
|
1116
|
-
: (callName ?? "callback"));
|
|
1117
|
-
callbacks.push({
|
|
1118
|
-
name: `${base}@${startLine}`,
|
|
1119
|
-
kind: cls.kind,
|
|
1120
|
-
rawKind: node.type,
|
|
1121
|
-
startLine,
|
|
1122
|
-
endLine,
|
|
1123
|
-
signature: firstLine(node.text),
|
|
1124
|
-
...(owner ? { parentChain: [owner] } : {}),
|
|
1125
|
-
...(cls.flags ? { flags: cls.flags } : {}),
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
for (const child of node.children ?? [])
|
|
1130
|
-
visit(child, depth + 1);
|
|
1131
|
-
};
|
|
1132
|
-
visit(root);
|
|
1133
|
-
if (depthTruncated) {
|
|
1134
|
-
warnings?.push(`Callback extraction stopped at AST depth ${maxDepth}`);
|
|
1135
|
-
}
|
|
1136
|
-
const callbackCap = 25;
|
|
1137
|
-
if (callbacks.length > callbackCap) {
|
|
1138
|
-
warnings?.push(`Callback list truncated to ${callbackCap} of ${callbacks.length} entries`);
|
|
1139
|
-
}
|
|
1140
|
-
return callbacks.slice(0, callbackCap);
|
|
1141
|
-
}
|
|
1142
|
-
const CALL_GRAPH_DEFAULT_ENTRY_CAP = 20;
|
|
1143
|
-
const CALL_GRAPH_MAX_ENTRY_CAP = 100;
|
|
1144
|
-
function unavailableCallGraph(reason) {
|
|
1145
|
-
return {
|
|
1146
|
-
available: false,
|
|
1147
|
-
reason,
|
|
1148
|
-
callers: [],
|
|
1149
|
-
callees: [],
|
|
1150
|
-
truncated: false,
|
|
1151
|
-
coverage: { status: "unavailable", complete: false },
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
function callGraphCoverage(coverage) {
|
|
1155
|
-
const languages = coverage.languages;
|
|
1156
|
-
const complete = coverage.complete &&
|
|
1157
|
-
Object.values(languages ?? {}).every((status) => status === "complete");
|
|
1158
|
-
return {
|
|
1159
|
-
status: complete ? "complete" : "partial",
|
|
1160
|
-
complete,
|
|
1161
|
-
totalEvidence: coverage.totalEvidence,
|
|
1162
|
-
callsEvidence: coverage.callsEvidence,
|
|
1163
|
-
referencesEvidence: coverage.referencesEvidence,
|
|
1164
|
-
eligibleEvidence: coverage.eligibleEvidence,
|
|
1165
|
-
resolvedEvidence: coverage.resolvedEvidence,
|
|
1166
|
-
unresolvedEvidence: coverage.unresolvedEvidence,
|
|
1167
|
-
typeOnlyEvidence: coverage.typeOnlyEvidence,
|
|
1168
|
-
unsupportedEvidence: coverage.unsupportedEvidence,
|
|
1169
|
-
sameFileEvidence: coverage.sameFileEvidence,
|
|
1170
|
-
duplicateEvidence: coverage.duplicateEvidence,
|
|
1171
|
-
...(languages ? { languages } : {}),
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
function callGraphRelation(edge, kind, projectRoot) {
|
|
1175
|
-
const caller = kind === "caller";
|
|
1176
|
-
return {
|
|
1177
|
-
symbolId: caller ? edge.callerKey : edge.calleeKey,
|
|
1178
|
-
targetSymbolId: caller ? edge.calleeKey : edge.callerKey,
|
|
1179
|
-
file: toDisplayPath(caller ? edge.callerFile : edge.calleeFile, projectRoot),
|
|
1180
|
-
...(caller
|
|
1181
|
-
? {
|
|
1182
|
-
symbol: edge.callerSymbol,
|
|
1183
|
-
kind: edge.callerKind,
|
|
1184
|
-
line: edge.callerLine,
|
|
1185
|
-
}
|
|
1186
|
-
: {
|
|
1187
|
-
symbol: edge.calleeSymbol,
|
|
1188
|
-
kind: edge.calleeKind,
|
|
1189
|
-
line: edge.calleeLine,
|
|
1190
|
-
}),
|
|
1191
|
-
...(edge.evidenceKind ? { evidenceKind: edge.evidenceKind } : {}),
|
|
1192
|
-
...(edge.resolution ? { resolution: edge.resolution } : {}),
|
|
1193
|
-
...(edge.evidenceCount && edge.evidenceCount > 1
|
|
1194
|
-
? { evidenceCount: edge.evidenceCount }
|
|
1195
|
-
: {}),
|
|
1196
|
-
...(edge.weight !== 1 ? { weight: edge.weight } : {}),
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Read the separately persisted FunctionCallGraph projection. This accessor is
|
|
1201
|
-
* deliberately conservative: the module-report path never walks/builds the
|
|
1202
|
-
* review graph, and a changed source file invalidates the projection rather
|
|
1203
|
-
* than being reported as a clean zero-call result.
|
|
1204
|
-
*/
|
|
1205
|
-
function readCallGraph(projectRoot, normalizedPath, maxEntries, graphFileCap, identity, reviewGraph) {
|
|
1206
|
-
if (graphFileCap !== undefined)
|
|
1207
|
-
return unavailableCallGraph("file-cap");
|
|
1208
|
-
// The canonical review graph is the only freshness authority. This path is
|
|
1209
|
-
// read-only: do not walk source files or compare an independent mtime map.
|
|
1210
|
-
if (!reviewGraph)
|
|
1211
|
-
return unavailableCallGraph("review-graph-missing");
|
|
1212
|
-
if (reviewGraph.persistCoverage?.partial ||
|
|
1213
|
-
reviewGraph.persistCoverage?.inProgress) {
|
|
1214
|
-
return unavailableCallGraph("partial");
|
|
1215
|
-
}
|
|
1216
|
-
if (!identity)
|
|
1217
|
-
return unavailableCallGraph("identity-missing");
|
|
1218
|
-
if (!reviewGraph.fileNodes.has(normalizedPath))
|
|
1219
|
-
return unavailableCallGraph("stale");
|
|
1220
|
-
const cached = loadCallGraph(projectRoot, identity);
|
|
1221
|
-
if (!cached)
|
|
1222
|
-
return unavailableCallGraph("stale");
|
|
1223
|
-
const callers = [];
|
|
1224
|
-
const callees = [];
|
|
1225
|
-
for (const edge of cached.graph.edges) {
|
|
1226
|
-
if (normalizeMapKey(edge.calleeFile) === normalizedPath) {
|
|
1227
|
-
callers.push(callGraphRelation(edge, "caller", projectRoot));
|
|
1228
|
-
}
|
|
1229
|
-
if (normalizeMapKey(edge.callerFile) === normalizedPath) {
|
|
1230
|
-
callees.push(callGraphRelation(edge, "callee", projectRoot));
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
const stable = (left, right) => left.targetSymbolId.localeCompare(right.targetSymbolId) ||
|
|
1234
|
-
(left.line ?? 0) - (right.line ?? 0) ||
|
|
1235
|
-
left.symbolId.localeCompare(right.symbolId);
|
|
1236
|
-
callers.sort(stable);
|
|
1237
|
-
callees.sort(stable);
|
|
1238
|
-
return {
|
|
1239
|
-
available: true,
|
|
1240
|
-
callers: callers.slice(0, maxEntries),
|
|
1241
|
-
callees: callees.slice(0, maxEntries),
|
|
1242
|
-
truncated: callers.length > maxEntries || callees.length > maxEntries,
|
|
1243
|
-
coverage: callGraphCoverage(cached.graph.coverage ?? {
|
|
1244
|
-
totalEvidence: cached.graph.totalRefs,
|
|
1245
|
-
callsEvidence: cached.graph.totalRefs,
|
|
1246
|
-
referencesEvidence: 0,
|
|
1247
|
-
eligibleEvidence: cached.graph.totalRefs,
|
|
1248
|
-
resolvedEvidence: cached.graph.totalRefs,
|
|
1249
|
-
unresolvedEvidence: cached.graph.unresolvedRefs,
|
|
1250
|
-
typeOnlyEvidence: 0,
|
|
1251
|
-
unsupportedEvidence: 0,
|
|
1252
|
-
sameFileEvidence: 0,
|
|
1253
|
-
duplicateEvidence: 0,
|
|
1254
|
-
complete: false,
|
|
1255
|
-
}),
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
function unavailableReport(displayPath, error) {
|
|
1259
|
-
return {
|
|
1260
|
-
available: false,
|
|
1261
|
-
staleness: "unavailable",
|
|
1262
|
-
path: displayPath,
|
|
1263
|
-
...(error ? { error } : {}),
|
|
1264
|
-
summary: { imports: 0, exports: 0, symbols: 0 },
|
|
1265
|
-
imports: { external: [], internal: [] },
|
|
1266
|
-
api: [],
|
|
1267
|
-
internal: [],
|
|
1268
|
-
callbacks: [],
|
|
1269
|
-
recommendedReads: [],
|
|
1270
|
-
semantic: { source: "none", references: false, implementations: false },
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
/**
|
|
1274
|
-
* Build a structured report for a single module. Read-only, single mode (#256):
|
|
1275
|
-
* tree-sitter extract + 3-tier-cached review graph (who-uses-this, imports,
|
|
1276
|
-
* complexity/fanout) + callback extraction. This path never calls LSP; LSP-derived
|
|
1277
|
-
* relationships must be written into the cached graph ahead of time (#236) and are
|
|
1278
|
-
* then read here as graph data. Each tier degrades independently, so a cold graph
|
|
1279
|
-
* never aborts the report — it just narrows what's populated.
|
|
1280
|
-
*/
|
|
1281
|
-
export async function moduleReport(file, cwd, options) {
|
|
1282
|
-
const startedAt = Date.now();
|
|
1283
|
-
const maxRefs = normalizeMaxRefsPerSymbol(options?.maxRefsPerSymbol);
|
|
1284
|
-
const maxCallGraphEntries = Math.min(CALL_GRAPH_MAX_ENTRY_CAP, Math.max(1, Math.floor(options?.maxCallGraphEntries ?? CALL_GRAPH_DEFAULT_ENTRY_CAP)));
|
|
1285
|
-
const absPath = path.resolve(cwd, file);
|
|
1286
|
-
const normalizedPath = normalizeMapKey(absPath);
|
|
1287
|
-
let content;
|
|
1288
|
-
try {
|
|
1289
|
-
content = fs.readFileSync(absPath, "utf-8");
|
|
1290
|
-
}
|
|
1291
|
-
catch {
|
|
1292
|
-
return unavailableReport(toDisplayPath(absPath, cwd));
|
|
1293
|
-
}
|
|
1294
|
-
const kind = detectFileKind(absPath);
|
|
1295
|
-
const languageId = tsLangForFile(absPath, kind);
|
|
1296
|
-
const lineCount = content.split(/\r?\n/).length;
|
|
1297
|
-
const { symbols: extracted, imports: extractedImports, callbacks, callbackError, error: extractionError, warnings: extractionWarnings, } = languageId
|
|
1298
|
-
? await extractFile(absPath, languageId, content)
|
|
1299
|
-
: {
|
|
1300
|
-
symbols: [],
|
|
1301
|
-
imports: [],
|
|
1302
|
-
callbacks: [],
|
|
1303
|
-
callbackError: undefined,
|
|
1304
|
-
error: undefined,
|
|
1305
|
-
warnings: undefined,
|
|
1306
|
-
};
|
|
1307
|
-
if (extractionError) {
|
|
1308
|
-
return unavailableReport(toDisplayPath(absPath, cwd), extractionError);
|
|
1309
|
-
}
|
|
1310
|
-
// READ-ONLY: consume the already-built review graph, never build one here. A
|
|
1311
|
-
// synchronous full build re-runs every fact provider (TS-compiler ASTs for
|
|
1312
|
-
// jsts) and two racing builds OOM'd pi (#256). Cold cache → outline-only.
|
|
1313
|
-
let graph;
|
|
1314
|
-
let graphFileCap;
|
|
1315
|
-
let callGraphIdentity;
|
|
1316
|
-
// #1961 review F4: the blind read now serves a snapshot stamped at a
|
|
1317
|
-
// different commit. project_report says so in its trust notes; this report
|
|
1318
|
-
// reads through the SAME accessor, so without this it would present the
|
|
1319
|
-
// other branch's importers and call edges as current, under a builtAt that
|
|
1320
|
-
// looks fresh. Computed per call, never cached.
|
|
1321
|
-
let revisionDriftNote;
|
|
1322
|
-
try {
|
|
1323
|
-
const { getCachedReviewGraph, getReviewGraphCacheIdentity, getReviewGraphSizeSkipVerdict, getReviewGraphRevisionDrift, formatReviewGraphRevisionDriftNote, } = await import("./review-graph/builder.js");
|
|
1324
|
-
graph = getCachedReviewGraph(cwd);
|
|
1325
|
-
graphFileCap = getReviewGraphSizeSkipVerdict(cwd)?.maxFileCount;
|
|
1326
|
-
const drift = graph ? getReviewGraphRevisionDrift(cwd) : undefined;
|
|
1327
|
-
if (drift)
|
|
1328
|
-
revisionDriftNote = formatReviewGraphRevisionDriftNote(drift);
|
|
1329
|
-
callGraphIdentity = graph
|
|
1330
|
-
? (() => {
|
|
1331
|
-
const identity = getReviewGraphCacheIdentity(cwd, graph);
|
|
1332
|
-
return identity
|
|
1333
|
-
? {
|
|
1334
|
-
reviewGraphVersion: identity.version,
|
|
1335
|
-
reviewGraphSignature: identity.signature,
|
|
1336
|
-
}
|
|
1337
|
-
: undefined;
|
|
1338
|
-
})()
|
|
1339
|
-
: undefined;
|
|
1340
|
-
}
|
|
1341
|
-
catch {
|
|
1342
|
-
graph = undefined;
|
|
1343
|
-
callGraphIdentity = undefined;
|
|
1344
|
-
}
|
|
1345
|
-
// Drop function-local declarations (a nested const/arrow/function) from the
|
|
1346
|
-
// outline — they're implementation detail of a parent symbol, not navigable
|
|
1347
|
-
// module structure (#259). Presentation-only: the review graph keeps them.
|
|
1348
|
-
const outlineSymbols = extracted.filter((sym) => !sym.local);
|
|
1349
|
-
// Flat entries first — ranking and cold-import resolution both read the full
|
|
1350
|
-
// list. `entries` is mutated by nestEntries (members attached); `topLevel` is
|
|
1351
|
-
// the api/internal split surface.
|
|
1352
|
-
const entries = outlineSymbols.map((sym) => toEntry(sym, normalizedPath, graph, maxRefs, cwd, kind));
|
|
1353
|
-
const topLevel = nestEntries(entries);
|
|
1354
|
-
// Middle-man / delegate-only class detection (#325): a whole-class judgment
|
|
1355
|
-
// over the now-nested members[], so it must run AFTER nestEntries. Mutates
|
|
1356
|
-
// `entries` in place (topLevel/api/internal hold the same object references).
|
|
1357
|
-
annotateMiddleMan(entries, content, languageId);
|
|
1358
|
-
const api = topLevel.filter((entry) => entry.exported);
|
|
1359
|
-
const internal = topLevel.filter((entry) => !entry.exported);
|
|
1360
|
-
const warnings = [...(extractionWarnings ?? [])];
|
|
1361
|
-
// Every graph-derived section below (who-uses-this, blast radius, call
|
|
1362
|
-
// edges) came from the served snapshot, so the caveat belongs with them.
|
|
1363
|
-
if (revisionDriftNote)
|
|
1364
|
-
warnings.push(revisionDriftNote);
|
|
1365
|
-
if (callbackError) {
|
|
1366
|
-
warnings.push(`Failed to extract callbacks: ${callbackError}`);
|
|
1367
|
-
logLatency({
|
|
1368
|
-
type: "phase",
|
|
1369
|
-
phase: "module_report_callback_extract_error",
|
|
1370
|
-
filePath: absPath,
|
|
1371
|
-
durationMs: 0,
|
|
1372
|
-
metadata: { error: callbackError },
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
if (graphFileCap !== undefined) {
|
|
1376
|
-
warnings.push(`who-uses-this is unavailable: review graph disabled because the project ` +
|
|
1377
|
-
`has more than ${graphFileCap} files (cap ${graphFileCap}) — raise ` +
|
|
1378
|
-
"maxProjectFiles in .pi-lens.json or set " +
|
|
1379
|
-
"PI_LENS_REVIEW_GRAPH_MAX_FILES; for CI/cron, run npx pi-lens " +
|
|
1380
|
-
"build-graph after configuring the cap");
|
|
1381
|
-
}
|
|
1382
|
-
// Imports: the warm review graph is source-of-truth; on a cold cache (or a
|
|
1383
|
-
// graph without this file's node) fall back to the language-uniform tree-sitter
|
|
1384
|
-
// resolution (#301) so a cold report no longer shows zero imports.
|
|
1385
|
-
const warmImports = graph
|
|
1386
|
-
? collectImports(graph, normalizedPath, cwd)
|
|
1387
|
-
: { external: [], internal: [] };
|
|
1388
|
-
const coldImportResult = warmImports.external.length + warmImports.internal.length > 0 || !languageId
|
|
1389
|
-
? undefined
|
|
1390
|
-
: coldImports(extractedImports, languageId, absPath, cwd);
|
|
1391
|
-
if (coldImportResult?.warnings.length)
|
|
1392
|
-
warnings.push(...coldImportResult.warnings);
|
|
1393
|
-
const imports = coldImportResult
|
|
1394
|
-
? {
|
|
1395
|
-
external: coldImportResult.external,
|
|
1396
|
-
internal: coldImportResult.internal,
|
|
1397
|
-
}
|
|
1398
|
-
: warmImports;
|
|
1399
|
-
const hasGraphNode = graph?.fileNodes.has(normalizedPath) ?? false;
|
|
1400
|
-
// #511: distinguish two very different reasons `usedBy`/`semantic` degrade to
|
|
1401
|
-
// "none". A graph that doesn't exist at all (`!graph`) is an honest, expected
|
|
1402
|
-
// cold start — the edit pipeline hasn't warmed this workspace yet. But a graph
|
|
1403
|
-
// that DOES exist and just doesn't have a node for THIS file (e.g. the file was
|
|
1404
|
-
// added/renamed after the graph was last persisted) is silently
|
|
1405
|
-
// indistinguishable from "no who-uses-this data exists" unless we say so. Make
|
|
1406
|
-
// the second case actionable: a rebuild (pilens_rebuild) would populate it.
|
|
1407
|
-
if (graph && !hasGraphNode) {
|
|
1408
|
-
warnings.push("who-uses-this is unavailable for this file: the cached review graph " +
|
|
1409
|
-
"exists but has no node for it (likely added/changed after the graph " +
|
|
1410
|
-
"was last built). Run pilens_rebuild to refresh it.");
|
|
1411
|
-
}
|
|
1412
|
-
// Cross-file blast radius (#304): opt-in, read-only over the CACHED graph. Only
|
|
1413
|
-
// computed when requested AND the file is in a warm graph — a cold cache omits
|
|
1414
|
-
// the section entirely (never builds, same #256 contract as the rest of this
|
|
1415
|
-
// path). Aggregated to file reads; undefined when nothing depends on the module.
|
|
1416
|
-
const blastRadius = options?.blastRadius && graph && hasGraphNode
|
|
1417
|
-
? await computeBlastRadius(graph, normalizedPath, cwd, Math.max(1, options.blastRadiusDepth ?? 3))
|
|
1418
|
-
: undefined;
|
|
1419
|
-
const callGraph = options?.callGraph
|
|
1420
|
-
? readCallGraph(cwd, normalizedPath, maxCallGraphEntries, graphFileCap, callGraphIdentity, graph)
|
|
1421
|
-
: undefined;
|
|
1422
|
-
const view = options?.view ?? "default";
|
|
1423
|
-
const summaryView = view === "summary";
|
|
1424
|
-
// "compact" computes the same full data as "default" — it's a rendering
|
|
1425
|
-
// instruction for the caller (renderCompactModuleReport), not a data tier —
|
|
1426
|
-
// so it only needs to echo back on the report; it never gates section content
|
|
1427
|
-
// the way summaryView does.
|
|
1428
|
-
const compactView = view === "compact";
|
|
1429
|
-
let importsProvenance = "none";
|
|
1430
|
-
if (coldImportResult) {
|
|
1431
|
-
importsProvenance = "syntax";
|
|
1432
|
-
}
|
|
1433
|
-
else if (graph) {
|
|
1434
|
-
importsProvenance = "cached-review-graph";
|
|
1435
|
-
}
|
|
1436
|
-
const unavailableGraphProvenance = graphFileCap !== undefined ? "unavailable:file-cap" : "none";
|
|
1437
|
-
const blastRadiusProvenance = blastRadius
|
|
1438
|
-
? "cached-review-graph"
|
|
1439
|
-
: unavailableGraphProvenance;
|
|
1440
|
-
const callGraphProvenance = callGraph?.available
|
|
1441
|
-
? "cached-call-graph"
|
|
1442
|
-
: callGraph?.reason === "file-cap"
|
|
1443
|
-
? "unavailable:file-cap"
|
|
1444
|
-
: "none";
|
|
1445
|
-
const report = {
|
|
1446
|
-
available: entries.length > 0 || hasGraphNode,
|
|
1447
|
-
staleness: entries.length === 0 && !hasGraphNode ? "unavailable" : "fresh",
|
|
1448
|
-
path: toDisplayPath(absPath, cwd),
|
|
1449
|
-
language: kind ?? undefined,
|
|
1450
|
-
lineCount,
|
|
1451
|
-
summary: {
|
|
1452
|
-
imports: imports.external.length + imports.internal.length,
|
|
1453
|
-
exports: api.length,
|
|
1454
|
-
symbols: entries.length,
|
|
1455
|
-
},
|
|
1456
|
-
imports,
|
|
1457
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
1458
|
-
api: summaryView ? summarizeEntries(api) : api,
|
|
1459
|
-
internal: summaryView ? summarizeEntries(internal) : internal,
|
|
1460
|
-
callbacks: summaryView ? [] : callbacks,
|
|
1461
|
-
callbackSupport: callbackSupportFor(languageId),
|
|
1462
|
-
recommendedReads: rankRecommendedReads(entries, callbacks, 5, options?.focus),
|
|
1463
|
-
...(summaryView ? { view: "summary" } : {}),
|
|
1464
|
-
...(compactView ? { view: "compact" } : {}),
|
|
1465
|
-
...(blastRadius && !summaryView ? { blastRadius } : {}),
|
|
1466
|
-
...(callGraph ? { callGraph } : {}),
|
|
1467
|
-
...(graph ? { graphBuiltAt: graph.builtAt } : {}),
|
|
1468
|
-
provenance: {
|
|
1469
|
-
symbols: languageId ? "syntax" : "none",
|
|
1470
|
-
imports: importsProvenance,
|
|
1471
|
-
usedBy: hasGraphNode ? "cached-review-graph" : unavailableGraphProvenance,
|
|
1472
|
-
callbacks: languageId && !summaryView ? "heuristic-tree-sitter" : "none",
|
|
1473
|
-
...(options?.blastRadius ? { blastRadius: blastRadiusProvenance } : {}),
|
|
1474
|
-
...(options?.callGraph ? { callGraph: callGraphProvenance } : {}),
|
|
1475
|
-
},
|
|
1476
|
-
semantic: {
|
|
1477
|
-
// Provenance of who-uses-this / references. The AST review graph is the
|
|
1478
|
-
// only source on this read path; "graph-lsp" is reserved for #236 (LSP
|
|
1479
|
-
// writes provenance edges INTO the graph). Cold cache → "none".
|
|
1480
|
-
source: hasGraphNode ? "review-graph" : unavailableGraphProvenance,
|
|
1481
|
-
references: hasGraphNode,
|
|
1482
|
-
implementations: false,
|
|
1483
|
-
},
|
|
1484
|
-
};
|
|
1485
|
-
// Observability (#256): record graph source (cached vs cold) so a future
|
|
1486
|
-
// regression is attributable per call. This path is read-only by contract —
|
|
1487
|
-
// "graph: cached|cold", never a build, never an LSP call.
|
|
1488
|
-
logLatency({
|
|
1489
|
-
type: "phase",
|
|
1490
|
-
phase: "module_report",
|
|
1491
|
-
filePath: absPath,
|
|
1492
|
-
durationMs: Date.now() - startedAt,
|
|
1493
|
-
metadata: {
|
|
1494
|
-
graph: graph ? "cached" : "cold",
|
|
1495
|
-
symbols: entries.length,
|
|
1496
|
-
exported: api.length,
|
|
1497
|
-
},
|
|
1498
|
-
});
|
|
1499
|
-
return report;
|
|
1500
|
-
}
|
|
1501
|
-
// --- Compact (line-oriented text) rendering (#512 slice 4) ------------------
|
|
1502
|
-
//
|
|
1503
|
-
// An opt-in `view: "compact"` alternative to the JSON report: one line per
|
|
1504
|
-
// symbol/callback instead of a repeated-keys JSON object, at roughly a quarter
|
|
1505
|
-
// of the token cost for the same information. Purely a rendering step over the
|
|
1506
|
-
// already-built ModuleReport — it changes no data, only presentation, so a
|
|
1507
|
-
// caller that wants JSON just skips this function. Default view stays JSON
|
|
1508
|
-
// (this is opt-in for dogfooding, not a default flip).
|
|
1509
|
-
function padRange(startLine, endLine, width) {
|
|
1510
|
-
return `${startLine}-${endLine}`.padEnd(width);
|
|
1511
|
-
}
|
|
1512
|
-
const KIND_ABBREV = {
|
|
1513
|
-
function: "fn",
|
|
1514
|
-
method: "fn",
|
|
1515
|
-
class: "class",
|
|
1516
|
-
interface: "iface",
|
|
1517
|
-
type: "type",
|
|
1518
|
-
variable: "var",
|
|
1519
|
-
property: "prop",
|
|
1520
|
-
};
|
|
1521
|
-
function compactKind(kind) {
|
|
1522
|
-
return KIND_ABBREV[kind] ?? kind;
|
|
1523
|
-
}
|
|
1524
|
-
function compactUsedBySuffix(usedBy) {
|
|
1525
|
-
if (!usedBy || usedBy.length === 0)
|
|
1526
|
-
return "";
|
|
1527
|
-
const counts = new Map();
|
|
1528
|
-
for (const u of usedBy)
|
|
1529
|
-
counts.set(u.file, (counts.get(u.file) ?? 0) + 1);
|
|
1530
|
-
const parts = [...counts.entries()].map(([file, n]) => n > 1 ? `${file}×${n}` : file);
|
|
1531
|
-
return ` used-by: ${parts.join(", ")}`;
|
|
1532
|
-
}
|
|
1533
|
-
function compactEntryLine(entry, width) {
|
|
1534
|
-
const range = padRange(entry.startLine, entry.endLine, width);
|
|
1535
|
-
const kind = compactKind(entry.kind).padEnd(6);
|
|
1536
|
-
const sig = entry.signature ? `${entry.name}${entry.signature}` : entry.name;
|
|
1537
|
-
const flagsSuffix = entry.flags && entry.flags.length > 0
|
|
1538
|
-
? ` [${entry.flags.join(", ")}]`
|
|
1539
|
-
: "";
|
|
1540
|
-
const docSuffix = entry.doc ? ` — ${entry.doc}` : "";
|
|
1541
|
-
const usedBySuffix = compactUsedBySuffix(entry.usedBy);
|
|
1542
|
-
return ` ${range}${kind}${sig}${flagsSuffix}${usedBySuffix}${docSuffix}`;
|
|
1543
|
-
}
|
|
1544
|
-
function compactMemberLines(entry, width, indent) {
|
|
1545
|
-
if (!entry.members || entry.members.length === 0)
|
|
1546
|
-
return [];
|
|
1547
|
-
return entry.members.map((m) => `${indent}${compactEntryLine(m, width).slice(2)}`);
|
|
1548
|
-
}
|
|
1549
|
-
function compactCallbackLine(callback, width) {
|
|
1550
|
-
const range = padRange(callback.startLine, callback.endLine, width);
|
|
1551
|
-
const kind = callback.kind.padEnd(20);
|
|
1552
|
-
const flagsSuffix = callback.flags && callback.flags.length > 0
|
|
1553
|
-
? ` [${callback.flags.join(", ")}]`
|
|
1554
|
-
: "";
|
|
1555
|
-
const ownerSuffix = callback.parentChain?.length
|
|
1556
|
-
? ` (in ${callback.parentChain.join(".")})`
|
|
1557
|
-
: "";
|
|
1558
|
-
return ` ${range}${kind}${callback.name}${flagsSuffix}${ownerSuffix}`;
|
|
1559
|
-
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Render a ModuleReport as the line-oriented compact text view (#512 slice 4):
|
|
1562
|
-
* one line per symbol/member/callback instead of a JSON object per entry.
|
|
1563
|
-
* Example:
|
|
1564
|
-
* ```
|
|
1565
|
-
* clients/agent-nudge.ts jsts 266L — 8 symbols, 5 exported | imports: bus-publish, latency-logger
|
|
1566
|
-
* API:
|
|
1567
|
-
* 77-81 fn _resetAgentNudgeForTests() — Test-only: clear accumulator state.
|
|
1568
|
-
* INTERNAL:
|
|
1569
|
-
* 95-104 fn isValidPayload(data: unknown)
|
|
1570
|
-
* CALLBACKS:
|
|
1571
|
-
* 164-172 event_handler events.on@164 [lifecycle] (in wireAgentNudgeSubscriber)
|
|
1572
|
-
* ```
|
|
1573
|
-
* Purely presentational over an already-built report — call `moduleReport`
|
|
1574
|
-
* first (with `view: "compact"` or any other view) and pass its result here.
|
|
1575
|
-
*/
|
|
1576
|
-
export function renderCompactModuleReport(report) {
|
|
1577
|
-
if (!report.available) {
|
|
1578
|
-
return `${report.path} — unavailable${report.error ? `: ${report.error}` : ""}`;
|
|
1579
|
-
}
|
|
1580
|
-
const allEntries = [...report.api, ...report.internal];
|
|
1581
|
-
const allRanges = allEntries.flatMap((e) => [e, ...(e.members ?? [])]);
|
|
1582
|
-
const width = Math.max(5, ...allRanges.map((e) => `${e.startLine}-${e.endLine}`.length), ...report.callbacks.map((c) => `${c.startLine}-${c.endLine}`.length)) + 2;
|
|
1583
|
-
const importsList = [...report.imports.internal, ...report.imports.external];
|
|
1584
|
-
const importsSuffix = importsList.length > 0
|
|
1585
|
-
? ` | imports: ${importsList
|
|
1586
|
-
.slice(0, 6)
|
|
1587
|
-
.map((i) => baseNameNoExt(i))
|
|
1588
|
-
.join(", ")}${importsList.length > 6 ? ", …" : ""}`
|
|
1589
|
-
: "";
|
|
1590
|
-
const lines = [
|
|
1591
|
-
`${report.path} ${report.language ?? "?"} ${report.lineCount ?? "?"}L — ` +
|
|
1592
|
-
`${report.summary.symbols} symbols, ${report.summary.exports} exported${importsSuffix}`,
|
|
1593
|
-
];
|
|
1594
|
-
for (const warning of report.warnings ?? []) {
|
|
1595
|
-
lines.push(`WARNING: ${warning}`);
|
|
1596
|
-
}
|
|
1597
|
-
if (report.api.length > 0) {
|
|
1598
|
-
lines.push("API:");
|
|
1599
|
-
for (const entry of report.api) {
|
|
1600
|
-
lines.push(compactEntryLine(entry, width));
|
|
1601
|
-
lines.push(...compactMemberLines(entry, width, " "));
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
if (report.internal.length > 0) {
|
|
1605
|
-
lines.push("INTERNAL:");
|
|
1606
|
-
for (const entry of report.internal) {
|
|
1607
|
-
lines.push(compactEntryLine(entry, width));
|
|
1608
|
-
lines.push(...compactMemberLines(entry, width, " "));
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
if (report.callbacks.length > 0) {
|
|
1612
|
-
lines.push("CALLBACKS:");
|
|
1613
|
-
for (const callback of report.callbacks) {
|
|
1614
|
-
lines.push(compactCallbackLine(callback, width));
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
if (report.callGraph) {
|
|
1618
|
-
const callGraph = report.callGraph;
|
|
1619
|
-
const suffix = callGraph.truncated ? " (truncated)" : "";
|
|
1620
|
-
lines.push("CALL GRAPH:");
|
|
1621
|
-
if (!callGraph.available) {
|
|
1622
|
-
lines.push(` unavailable (${callGraph.reason ?? "unknown"})`);
|
|
1623
|
-
}
|
|
1624
|
-
else {
|
|
1625
|
-
lines.push(` callers: ${callGraph.callers.length} · callees: ${callGraph.callees.length}` +
|
|
1626
|
-
` · coverage: ${callGraph.coverage.status}${suffix}`);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
if (report.recommendedReads.length > 0) {
|
|
1630
|
-
lines.push("RECOMMENDED:");
|
|
1631
|
-
for (const r of report.recommendedReads) {
|
|
1632
|
-
lines.push(` ${padRange(r.startLine, r.endLine, width)}${r.symbol ?? ""} — ${r.reason}`);
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
return lines.join("\n");
|
|
1636
|
-
}
|
|
1637
|
-
function baseNameNoExt(p) {
|
|
1638
|
-
const base = p.split(/[\\/]/).pop() ?? p;
|
|
1639
|
-
return base.replace(/\.[^./\\]+$/, "");
|
|
1640
|
-
}
|
|
1641
|
-
// Duplicate-name disambiguation (#523 item 4). `candidates` is every symbol
|
|
1642
|
-
// already known to share the requested name; when `kind` is given it narrows
|
|
1643
|
-
// first, else the historical "first match" (source order) wins — unchanged
|
|
1644
|
-
// behavior for the common (unambiguous) case. `ambiguous` is populated only
|
|
1645
|
-
// when the CHOSEN pool still has more than one entry, so a kind that uniquely
|
|
1646
|
-
// resolves the collision reports no ambiguity.
|
|
1647
|
-
function selectMatch(candidates, kind) {
|
|
1648
|
-
if (candidates.length === 0)
|
|
1649
|
-
return {};
|
|
1650
|
-
const filtered = kind
|
|
1651
|
-
? candidates.filter((c) => c.kind === kind)
|
|
1652
|
-
: candidates;
|
|
1653
|
-
const pool = filtered.length > 0 ? filtered : candidates;
|
|
1654
|
-
const match = pool[0];
|
|
1655
|
-
if (pool.length <= 1)
|
|
1656
|
-
return { match };
|
|
1657
|
-
return {
|
|
1658
|
-
match,
|
|
1659
|
-
ambiguous: {
|
|
1660
|
-
count: pool.length,
|
|
1661
|
-
kinds: [...new Set(pool.map((c) => c.kind))],
|
|
1662
|
-
},
|
|
1663
|
-
};
|
|
1664
|
-
}
|
|
1665
|
-
// `Class.method` qualification (#523 item 3). Members are located by line-range
|
|
1666
|
-
// containment within the named parent — the same shape module-report's
|
|
1667
|
-
// `nestEntries` uses for the outline, computed directly here over the flat
|
|
1668
|
-
// extractor list (readSymbol never builds the nested outline). Returns
|
|
1669
|
-
// undefined (NOT a miss) when the qualifier doesn't resolve to a known parent —
|
|
1670
|
-
// the caller then falls through to the plain unqualified lookup using the full
|
|
1671
|
-
// dotted string, which naturally misses and feeds the did-you-mean path below
|
|
1672
|
-
// rather than crashing.
|
|
1673
|
-
function resolveQualifiedMatch(symbols, qualifiedName, kind) {
|
|
1674
|
-
const dotIdx = qualifiedName.lastIndexOf(".");
|
|
1675
|
-
if (dotIdx <= 0 || dotIdx === qualifiedName.length - 1)
|
|
1676
|
-
return undefined;
|
|
1677
|
-
const parentName = qualifiedName.slice(0, dotIdx);
|
|
1678
|
-
const memberName = qualifiedName.slice(dotIdx + 1);
|
|
1679
|
-
const parent = symbols.find((candidate) => candidate.name === parentName);
|
|
1680
|
-
if (!parent)
|
|
1681
|
-
return undefined;
|
|
1682
|
-
const parentStart = parent.line;
|
|
1683
|
-
const parentEnd = parent.endLine ?? parent.line;
|
|
1684
|
-
const members = symbols.filter((candidate) => candidate !== parent &&
|
|
1685
|
-
candidate.name === memberName &&
|
|
1686
|
-
candidate.line >= parentStart &&
|
|
1687
|
-
(candidate.endLine ?? candidate.line) <= parentEnd);
|
|
1688
|
-
if (members.length === 0)
|
|
1689
|
-
return undefined;
|
|
1690
|
-
return selectMatch(members, kind);
|
|
1691
|
-
}
|
|
1692
|
-
// Did-you-mean on miss (#523 item 2). A small, focused Levenshtein distance —
|
|
1693
|
-
// NOT a reuse of read-guard-tool-lines.ts's `findSimilarLines`/`tokenSimilarity`.
|
|
1694
|
-
// That function does Jaccard similarity over whitespace-tokenized LINE CONTENT
|
|
1695
|
-
// (built for relocated-block suggestions across a window of file lines); a
|
|
1696
|
-
// single identifier is one token to it, so "isAgentNudgeEnable" vs
|
|
1697
|
-
// "isAgentNudgeEnabled" scores 0 (disjoint token sets) despite being a
|
|
1698
|
-
// one-character typo. Symbol-name matching needs character-level edit distance
|
|
1699
|
-
// instead, so this is a small dedicated implementation rather than a forced
|
|
1700
|
-
// reuse. No existing levenshtein/editDistance utility was found elsewhere in
|
|
1701
|
-
// the codebase (checked clients/read-guard-tool-lines.ts and clients/dispatch/
|
|
1702
|
-
// dispatcher.ts, the only other "similarity" hits).
|
|
1703
|
-
function levenshteinDistance(a, b) {
|
|
1704
|
-
const al = a.length;
|
|
1705
|
-
const bl = b.length;
|
|
1706
|
-
if (al === 0)
|
|
1707
|
-
return bl;
|
|
1708
|
-
if (bl === 0)
|
|
1709
|
-
return al;
|
|
1710
|
-
let prev = [];
|
|
1711
|
-
prev.length = bl + 1;
|
|
1712
|
-
let curr = [];
|
|
1713
|
-
curr.length = bl + 1;
|
|
1714
|
-
for (let j = 0; j <= bl; j++)
|
|
1715
|
-
prev[j] = j;
|
|
1716
|
-
for (let i = 1; i <= al; i++) {
|
|
1717
|
-
curr[0] = i;
|
|
1718
|
-
for (let j = 1; j <= bl; j++) {
|
|
1719
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1720
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
1721
|
-
}
|
|
1722
|
-
[prev, curr] = [curr, prev];
|
|
1723
|
-
}
|
|
1724
|
-
return prev[bl];
|
|
1725
|
-
}
|
|
1726
|
-
// Normalized similarity in [0, 1]; 1 = identical (case-insensitive).
|
|
1727
|
-
function nameSimilarity(a, b) {
|
|
1728
|
-
const maxLen = Math.max(a.length, b.length);
|
|
1729
|
-
if (maxLen === 0)
|
|
1730
|
-
return 1;
|
|
1731
|
-
return 1 - levenshteinDistance(a.toLowerCase(), b.toLowerCase()) / maxLen;
|
|
1732
|
-
}
|
|
1733
|
-
// Threshold chosen so a plausible typo/near-miss (one-two edits on a
|
|
1734
|
-
// medium-length identifier, e.g. "isAgentNudgeEnable" -> "isAgentNudgeEnabled",
|
|
1735
|
-
// score ~0.95) clears it while a wildly-wrong name (near-zero character
|
|
1736
|
-
// overlap) doesn't — avoids suggesting misleading names on a genuine miss.
|
|
1737
|
-
const SIMILAR_NAME_MIN_SCORE = 0.45;
|
|
1738
|
-
const SIMILAR_NAME_MAX_SUGGESTIONS = 3;
|
|
1739
|
-
function suggestSimilarNames(candidates, target) {
|
|
1740
|
-
const unique = [...new Set(candidates)].filter((name) => name !== target);
|
|
1741
|
-
return unique
|
|
1742
|
-
.map((name) => ({ name, score: nameSimilarity(target, name) }))
|
|
1743
|
-
.filter((entry) => entry.score >= SIMILAR_NAME_MIN_SCORE)
|
|
1744
|
-
.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name))
|
|
1745
|
-
.slice(0, SIMILAR_NAME_MAX_SUGGESTIONS)
|
|
1746
|
-
.map((entry) => entry.name);
|
|
1747
|
-
}
|
|
1748
|
-
/**
|
|
1749
|
-
* Return the verbatim body of a single symbol. Unlike moduleReport (which shows
|
|
1750
|
-
* shape, not content), this delivers the actual source lines — so the host can
|
|
1751
|
-
* record it as a read that legitimately satisfies the read-guard's coverage for
|
|
1752
|
-
* that symbol's range.
|
|
1753
|
-
*/
|
|
1754
|
-
export async function readSymbol(file, symbolName, cwd, options = {}) {
|
|
1755
|
-
const { kind: symbolKindFilter } = options;
|
|
1756
|
-
const startedAt = Date.now();
|
|
1757
|
-
const absPath = path.resolve(cwd, file);
|
|
1758
|
-
// Pure tree-sitter on one file — no graph, no LSP. Log for correlation with
|
|
1759
|
-
// module_report frequency/timing (#256), one event per outcome.
|
|
1760
|
-
const log = (found) => {
|
|
1761
|
-
logLatency({
|
|
1762
|
-
type: "phase",
|
|
1763
|
-
phase: "read_symbol",
|
|
1764
|
-
filePath: absPath,
|
|
1765
|
-
durationMs: Date.now() - startedAt,
|
|
1766
|
-
metadata: { symbol: symbolName, found },
|
|
1767
|
-
});
|
|
1768
|
-
};
|
|
1769
|
-
let content;
|
|
1770
|
-
try {
|
|
1771
|
-
content = fs.readFileSync(absPath, "utf-8");
|
|
1772
|
-
}
|
|
1773
|
-
catch {
|
|
1774
|
-
log(false);
|
|
1775
|
-
return { found: false, path: absPath, name: symbolName };
|
|
1776
|
-
}
|
|
1777
|
-
const kind = detectFileKind(absPath);
|
|
1778
|
-
const languageId = tsLangForFile(absPath, kind);
|
|
1779
|
-
if (!languageId) {
|
|
1780
|
-
log(false);
|
|
1781
|
-
return { found: false, path: absPath, name: symbolName };
|
|
1782
|
-
}
|
|
1783
|
-
const { symbols, callbacks: allCallbacks, callbackError, error: extractionError, warnings: extractionWarnings, } = await extractFile(absPath, languageId, content);
|
|
1784
|
-
if (extractionError) {
|
|
1785
|
-
log(false);
|
|
1786
|
-
return {
|
|
1787
|
-
found: false,
|
|
1788
|
-
path: absPath,
|
|
1789
|
-
name: symbolName,
|
|
1790
|
-
error: extractionError,
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
const lines = content.split(/\r?\n/);
|
|
1794
|
-
// #523 item 3: a dotted name tries the qualified (Class.method) lookup
|
|
1795
|
-
// first; a hit there wins outright. Otherwise fall through to the plain
|
|
1796
|
-
// unqualified lookup using the FULL requested name (so an unresolved
|
|
1797
|
-
// qualifier or a non-dotted name both flow through the same miss/did-you-
|
|
1798
|
-
// mean path below rather than a separate branch).
|
|
1799
|
-
const qualified = resolveQualifiedMatch(symbols, symbolName, symbolKindFilter);
|
|
1800
|
-
const unqualifiedMatches = symbols.filter((candidate) => candidate.name === symbolName);
|
|
1801
|
-
const selection = qualified ?? selectMatch(unqualifiedMatches, symbolKindFilter);
|
|
1802
|
-
if (selection.match) {
|
|
1803
|
-
const sym = selection.match;
|
|
1804
|
-
// #523 item 1: extend the returned range (and thus the read-guard
|
|
1805
|
-
// coverage recorded for it — see tools/module-report.ts's
|
|
1806
|
-
// recordReadCoverage) to include an attached doc comment, when one
|
|
1807
|
-
// exists. `docStartLine` is the SAME position-based, blank-line-gap-aware
|
|
1808
|
-
// attachment computation the outline's `doc` summary already uses (#517's
|
|
1809
|
-
// extractDocCommentInfo) — no re-derivation here, just reusing the line
|
|
1810
|
-
// it already computed.
|
|
1811
|
-
const startLine = sym.docStartLine ?? sym.line;
|
|
1812
|
-
const endLine = sym.endLine ?? sym.line;
|
|
1813
|
-
const source = lines.slice(startLine - 1, endLine).join("\n");
|
|
1814
|
-
log(true);
|
|
1815
|
-
return {
|
|
1816
|
-
found: true,
|
|
1817
|
-
path: absPath,
|
|
1818
|
-
name: sym.name,
|
|
1819
|
-
kind: sym.kind,
|
|
1820
|
-
startLine,
|
|
1821
|
-
endLine,
|
|
1822
|
-
signature: sym.signature,
|
|
1823
|
-
source,
|
|
1824
|
-
...(selection.ambiguous ? { ambiguous: selection.ambiguous } : {}),
|
|
1825
|
-
};
|
|
1826
|
-
}
|
|
1827
|
-
if (callbackError) {
|
|
1828
|
-
const message = `Callback extraction failed: ${callbackError}`;
|
|
1829
|
-
logLatency({
|
|
1830
|
-
type: "phase",
|
|
1831
|
-
phase: "read_symbol_callback_extract_error",
|
|
1832
|
-
filePath: absPath,
|
|
1833
|
-
durationMs: Date.now() - startedAt,
|
|
1834
|
-
metadata: { symbol: symbolName, error: message },
|
|
1835
|
-
});
|
|
1836
|
-
log(false);
|
|
1837
|
-
return { found: false, path: absPath, name: symbolName, error: message };
|
|
1838
|
-
}
|
|
1839
|
-
const callbackWarnings = [...(extractionWarnings ?? [])];
|
|
1840
|
-
const callback = allCallbacks.find((candidate) => candidate.name === symbolName);
|
|
1841
|
-
if (!callback) {
|
|
1842
|
-
log(false);
|
|
1843
|
-
// #523 item 2: embed the ~3 nearest symbol/callback names directly in the
|
|
1844
|
-
// miss response so the caller can self-correct without a module_report
|
|
1845
|
-
// round-trip. Non-local symbols only — locals aren't reachable by name
|
|
1846
|
-
// from outside their enclosing scope, so suggesting one would send the
|
|
1847
|
-
// caller to a dead end.
|
|
1848
|
-
const corpus = [
|
|
1849
|
-
...symbols.filter((candidate) => !candidate.local).map((c) => c.name),
|
|
1850
|
-
...allCallbacks.map((c) => c.name),
|
|
1851
|
-
];
|
|
1852
|
-
const suggestions = suggestSimilarNames(corpus, symbolName);
|
|
1853
|
-
return {
|
|
1854
|
-
found: false,
|
|
1855
|
-
path: absPath,
|
|
1856
|
-
name: symbolName,
|
|
1857
|
-
...(callbackWarnings.length > 0 ? { warnings: callbackWarnings } : {}),
|
|
1858
|
-
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
1859
|
-
};
|
|
1860
|
-
}
|
|
1861
|
-
const source = lines
|
|
1862
|
-
.slice(callback.startLine - 1, callback.endLine)
|
|
1863
|
-
.join("\n");
|
|
1864
|
-
log(true);
|
|
1865
|
-
return {
|
|
1866
|
-
found: true,
|
|
1867
|
-
path: absPath,
|
|
1868
|
-
name: callback.name,
|
|
1869
|
-
kind: callback.kind,
|
|
1870
|
-
startLine: callback.startLine,
|
|
1871
|
-
endLine: callback.endLine,
|
|
1872
|
-
signature: callback.signature,
|
|
1873
|
-
source,
|
|
1874
|
-
};
|
|
1875
|
-
}
|
|
1876
|
-
function symbolMatchesKind(kind, filters) {
|
|
1877
|
-
if (filters.size === 0)
|
|
1878
|
-
return true;
|
|
1879
|
-
const normalized = kind.toLowerCase();
|
|
1880
|
-
if (filters.has(normalized))
|
|
1881
|
-
return true;
|
|
1882
|
-
if (filters.has("function") && normalized.includes("function"))
|
|
1883
|
-
return true;
|
|
1884
|
-
if (filters.has("method") && normalized.includes("method"))
|
|
1885
|
-
return true;
|
|
1886
|
-
if (filters.has("type") &&
|
|
1887
|
-
["class", "interface", "struct", "enum", "trait", "type"].includes(normalized)) {
|
|
1888
|
-
return true;
|
|
1889
|
-
}
|
|
1890
|
-
return false;
|
|
1891
|
-
}
|
|
1892
|
-
function callbackMatchesKind(kind, filters) {
|
|
1893
|
-
if (filters.size === 0)
|
|
1894
|
-
return true;
|
|
1895
|
-
const normalized = kind.toLowerCase();
|
|
1896
|
-
return (filters.has(normalized) ||
|
|
1897
|
-
filters.has("callback") ||
|
|
1898
|
-
filters.has("closure") ||
|
|
1899
|
-
filters.has("lambda") ||
|
|
1900
|
-
(normalized.includes("callback") && filters.has("function")));
|
|
1901
|
-
}
|
|
1902
|
-
function symbolParentChain(symbol, symbols) {
|
|
1903
|
-
const chain = symbols
|
|
1904
|
-
.filter((candidate) => {
|
|
1905
|
-
if (candidate === symbol)
|
|
1906
|
-
return false;
|
|
1907
|
-
const start = candidate.line;
|
|
1908
|
-
const end = candidate.endLine ?? candidate.line;
|
|
1909
|
-
const symEnd = symbol.endLine ?? symbol.line;
|
|
1910
|
-
return (start <= symbol.line &&
|
|
1911
|
-
end >= symEnd &&
|
|
1912
|
-
end - start > symEnd - symbol.line);
|
|
1913
|
-
})
|
|
1914
|
-
.sort((a, b) => a.line - b.line ||
|
|
1915
|
-
(b.endLine ?? b.line) - b.line - ((a.endLine ?? a.line) - a.line))
|
|
1916
|
-
.map((candidate) => candidate.name);
|
|
1917
|
-
return chain.length > 0 ? chain : undefined;
|
|
1918
|
-
}
|
|
1919
|
-
function clampSliceRange(targetLine, startLine, endLine, limit) {
|
|
1920
|
-
const boundedLimit = Math.max(1, Math.min(endLine - startLine + 1, limit));
|
|
1921
|
-
let sliceStart = targetLine - Math.floor(boundedLimit / 2);
|
|
1922
|
-
sliceStart = Math.max(startLine, Math.min(sliceStart, endLine - boundedLimit + 1));
|
|
1923
|
-
return { startLine: sliceStart, endLine: sliceStart + boundedLimit - 1 };
|
|
1924
|
-
}
|
|
1925
|
-
function enclosingOutline(selected, symbols, callbacks, filters, filePath) {
|
|
1926
|
-
const items = [];
|
|
1927
|
-
for (const sym of symbols) {
|
|
1928
|
-
const startLine = sym.line;
|
|
1929
|
-
const endLine = sym.endLine ?? sym.line;
|
|
1930
|
-
if (startLine === selected.startLine && endLine === selected.endLine)
|
|
1931
|
-
continue;
|
|
1932
|
-
if (startLine < selected.startLine || endLine > selected.endLine)
|
|
1933
|
-
continue;
|
|
1934
|
-
if (!symbolMatchesKind(sym.kind, filters))
|
|
1935
|
-
continue;
|
|
1936
|
-
items.push({
|
|
1937
|
-
name: sym.name,
|
|
1938
|
-
kind: sym.kind,
|
|
1939
|
-
startLine,
|
|
1940
|
-
endLine,
|
|
1941
|
-
signature: sym.signature,
|
|
1942
|
-
parentChain: symbolParentChain(sym, symbols),
|
|
1943
|
-
read: readArgsFor(filePath, startLine, endLine),
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
for (const callback of callbacks) {
|
|
1947
|
-
if (callback.startLine < selected.startLine ||
|
|
1948
|
-
callback.endLine > selected.endLine)
|
|
1949
|
-
continue;
|
|
1950
|
-
if (!callbackMatchesKind(callback.kind, filters))
|
|
1951
|
-
continue;
|
|
1952
|
-
items.push({
|
|
1953
|
-
name: callback.name,
|
|
1954
|
-
kind: callback.kind,
|
|
1955
|
-
startLine: callback.startLine,
|
|
1956
|
-
endLine: callback.endLine,
|
|
1957
|
-
signature: callback.signature,
|
|
1958
|
-
parentChain: callback.parentChain,
|
|
1959
|
-
read: readArgsFor(filePath, callback.startLine, callback.endLine),
|
|
1960
|
-
});
|
|
1961
|
-
}
|
|
1962
|
-
return items
|
|
1963
|
-
.sort((a, b) => a.startLine - b.startLine ||
|
|
1964
|
-
a.endLine - a.startLine - (b.endLine - b.startLine))
|
|
1965
|
-
.slice(0, 25);
|
|
1966
|
-
}
|
|
1967
|
-
/**
|
|
1968
|
-
* Read the smallest useful symbol/callback enclosing a source line. This is the
|
|
1969
|
-
* search/diagnostic → exact-body bridge: single-file tree-sitter only, no graph,
|
|
1970
|
-
* no LSP, and the returned range is concrete read-guard coverage.
|
|
1971
|
-
*/
|
|
1972
|
-
export async function readEnclosing(file, line, cwd, options) {
|
|
1973
|
-
const startedAt = Date.now();
|
|
1974
|
-
const absPath = path.resolve(cwd, file);
|
|
1975
|
-
const targetLine = Math.max(1, Math.floor(line));
|
|
1976
|
-
const log = (found) => {
|
|
1977
|
-
logLatency({
|
|
1978
|
-
type: "phase",
|
|
1979
|
-
phase: "read_enclosing",
|
|
1980
|
-
filePath: absPath,
|
|
1981
|
-
durationMs: Date.now() - startedAt,
|
|
1982
|
-
metadata: { line: targetLine, found },
|
|
1983
|
-
});
|
|
1984
|
-
};
|
|
1985
|
-
let content;
|
|
1986
|
-
try {
|
|
1987
|
-
content = fs.readFileSync(absPath, "utf-8");
|
|
1988
|
-
}
|
|
1989
|
-
catch {
|
|
1990
|
-
log(false);
|
|
1991
|
-
return { found: false, path: absPath, line: targetLine };
|
|
1992
|
-
}
|
|
1993
|
-
const kind = detectFileKind(absPath);
|
|
1994
|
-
const languageId = tsLangForFile(absPath, kind);
|
|
1995
|
-
if (!languageId) {
|
|
1996
|
-
log(false);
|
|
1997
|
-
return { found: false, path: absPath, line: targetLine };
|
|
1998
|
-
}
|
|
1999
|
-
const { symbols, callbacks, callbackError, error: extractionError, warnings: extractionWarnings, } = await extractFile(absPath, languageId, content);
|
|
2000
|
-
if (extractionError) {
|
|
2001
|
-
log(false);
|
|
2002
|
-
return {
|
|
2003
|
-
found: false,
|
|
2004
|
-
path: absPath,
|
|
2005
|
-
line: targetLine,
|
|
2006
|
-
error: extractionError,
|
|
2007
|
-
};
|
|
2008
|
-
}
|
|
2009
|
-
const filters = new Set((options?.kinds ?? []).map((value) => value.toLowerCase()));
|
|
2010
|
-
const warnings = [...(extractionWarnings ?? [])];
|
|
2011
|
-
if (callbackError) {
|
|
2012
|
-
const message = `Callback extraction failed: ${callbackError}`;
|
|
2013
|
-
warnings.push(message);
|
|
2014
|
-
logLatency({
|
|
2015
|
-
type: "phase",
|
|
2016
|
-
phase: "read_enclosing_callback_extract_error",
|
|
2017
|
-
filePath: absPath,
|
|
2018
|
-
durationMs: Date.now() - startedAt,
|
|
2019
|
-
metadata: { line: targetLine, error: message },
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
const candidates = [];
|
|
2023
|
-
for (const sym of symbols.filter((candidate) => !candidate.local)) {
|
|
2024
|
-
const startLine = sym.line;
|
|
2025
|
-
const endLine = sym.endLine ?? sym.line;
|
|
2026
|
-
if (targetLine < startLine || targetLine > endLine)
|
|
2027
|
-
continue;
|
|
2028
|
-
if (!symbolMatchesKind(sym.kind, filters))
|
|
2029
|
-
continue;
|
|
2030
|
-
candidates.push({
|
|
2031
|
-
name: sym.name,
|
|
2032
|
-
kind: sym.kind,
|
|
2033
|
-
startLine,
|
|
2034
|
-
endLine,
|
|
2035
|
-
signature: sym.signature,
|
|
2036
|
-
parentChain: symbolParentChain(sym, symbols),
|
|
2037
|
-
priority: 1,
|
|
2038
|
-
});
|
|
2039
|
-
}
|
|
2040
|
-
for (const callback of callbacks) {
|
|
2041
|
-
if (targetLine < callback.startLine || targetLine > callback.endLine)
|
|
2042
|
-
continue;
|
|
2043
|
-
if (!callbackMatchesKind(callback.kind, filters))
|
|
2044
|
-
continue;
|
|
2045
|
-
candidates.push({
|
|
2046
|
-
name: callback.name,
|
|
2047
|
-
kind: callback.kind,
|
|
2048
|
-
startLine: callback.startLine,
|
|
2049
|
-
endLine: callback.endLine,
|
|
2050
|
-
signature: callback.signature,
|
|
2051
|
-
parentChain: callback.parentChain,
|
|
2052
|
-
priority: 0,
|
|
2053
|
-
});
|
|
2054
|
-
}
|
|
2055
|
-
candidates.sort((a, b) => {
|
|
2056
|
-
const span = a.endLine - a.startLine - (b.endLine - b.startLine);
|
|
2057
|
-
return span || a.priority - b.priority || a.startLine - b.startLine;
|
|
2058
|
-
});
|
|
2059
|
-
const selected = candidates[0];
|
|
2060
|
-
if (!selected) {
|
|
2061
|
-
log(false);
|
|
2062
|
-
return {
|
|
2063
|
-
found: false,
|
|
2064
|
-
path: absPath,
|
|
2065
|
-
line: targetLine,
|
|
2066
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
const lines = content.split(/\r?\n/);
|
|
2070
|
-
const limit = selected.endLine - selected.startLine + 1;
|
|
2071
|
-
if (options?.maxLines && limit > options.maxLines) {
|
|
2072
|
-
const oversize = options.onOversize ?? "error";
|
|
2073
|
-
const base = {
|
|
2074
|
-
path: absPath,
|
|
2075
|
-
line: targetLine,
|
|
2076
|
-
name: selected.name,
|
|
2077
|
-
kind: selected.kind,
|
|
2078
|
-
startLine: selected.startLine,
|
|
2079
|
-
endLine: selected.endLine,
|
|
2080
|
-
signature: selected.signature,
|
|
2081
|
-
parentChain: selected.parentChain,
|
|
2082
|
-
enclosingStartLine: selected.startLine,
|
|
2083
|
-
enclosingEndLine: selected.endLine,
|
|
2084
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
2085
|
-
};
|
|
2086
|
-
if (oversize === "slice") {
|
|
2087
|
-
const sliceLimit = Math.max(1, Math.floor(options.aroundLine ?? options.maxLines ?? 80));
|
|
2088
|
-
const slice = clampSliceRange(targetLine, selected.startLine, selected.endLine, sliceLimit);
|
|
2089
|
-
const source = lines.slice(slice.startLine - 1, slice.endLine).join("\n");
|
|
2090
|
-
log(true);
|
|
2091
|
-
return {
|
|
2092
|
-
...base,
|
|
2093
|
-
found: true,
|
|
2094
|
-
startLine: slice.startLine,
|
|
2095
|
-
endLine: slice.endLine,
|
|
2096
|
-
partial: true,
|
|
2097
|
-
selection: {
|
|
2098
|
-
strategy: "oversize-slice",
|
|
2099
|
-
source: "tree-sitter",
|
|
2100
|
-
confidence: "medium",
|
|
2101
|
-
},
|
|
2102
|
-
source,
|
|
2103
|
-
};
|
|
2104
|
-
}
|
|
2105
|
-
if (oversize === "outline") {
|
|
2106
|
-
log(false);
|
|
2107
|
-
return {
|
|
2108
|
-
...base,
|
|
2109
|
-
found: false,
|
|
2110
|
-
selection: {
|
|
2111
|
-
strategy: "oversize-outline",
|
|
2112
|
-
source: "tree-sitter",
|
|
2113
|
-
confidence: "medium",
|
|
2114
|
-
},
|
|
2115
|
-
outline: enclosingOutline(selected, symbols, callbacks, filters, absPath),
|
|
2116
|
-
error: `Enclosing ${selected.kind} spans ${limit} lines, above maxLines ${options.maxLines}`,
|
|
2117
|
-
};
|
|
2118
|
-
}
|
|
2119
|
-
log(false);
|
|
2120
|
-
return {
|
|
2121
|
-
...base,
|
|
2122
|
-
found: false,
|
|
2123
|
-
error: `Enclosing ${selected.kind} spans ${limit} lines, above maxLines ${options.maxLines}`,
|
|
2124
|
-
};
|
|
2125
|
-
}
|
|
2126
|
-
const source = lines
|
|
2127
|
-
.slice(selected.startLine - 1, selected.endLine)
|
|
2128
|
-
.join("\n");
|
|
2129
|
-
log(true);
|
|
2130
|
-
return {
|
|
2131
|
-
found: true,
|
|
2132
|
-
path: absPath,
|
|
2133
|
-
line: targetLine,
|
|
2134
|
-
name: selected.name,
|
|
2135
|
-
kind: selected.kind,
|
|
2136
|
-
startLine: selected.startLine,
|
|
2137
|
-
endLine: selected.endLine,
|
|
2138
|
-
enclosingStartLine: selected.startLine,
|
|
2139
|
-
enclosingEndLine: selected.endLine,
|
|
2140
|
-
signature: selected.signature,
|
|
2141
|
-
parentChain: selected.parentChain,
|
|
2142
|
-
selection: {
|
|
2143
|
-
strategy: "range-containment",
|
|
2144
|
-
source: "tree-sitter",
|
|
2145
|
-
confidence: "high",
|
|
2146
|
-
},
|
|
2147
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
2148
|
-
source,
|
|
2149
|
-
};
|
|
2150
|
-
}
|