@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,1745 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lsp_diagnostics tool definition
|
|
3
|
-
*
|
|
4
|
-
* Proactive LSP diagnostics check — single files or directories.
|
|
5
|
-
* Adopted from code-yeongyu/pi-lsp-client design.
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from "node:fs";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import { getProjectIgnoreMatcher, isExcludedDirName, } from "../clients/file-utils.js";
|
|
10
|
-
import { getLSPService, groupFilesByPrimaryServer, runPerServerGroups, } from "../clients/lsp/index.js";
|
|
11
|
-
import { buildScopeKey, createWorkspaceDiagnosticsCacheContext, } from "../clients/lsp/workspace-diagnostics-cache.js";
|
|
12
|
-
import { getServersForFileWithConfig, primaryServerId, } from "../clients/lsp/config.js";
|
|
13
|
-
import { mapWithConcurrency } from "../clients/map-with-concurrency.js";
|
|
14
|
-
import { combineAbortSignals, withDeadline, } from "../clients/deadline-utils.js";
|
|
15
|
-
import { applyAuxiliarySuppressions, retagAuxiliaryDiagnostics, } from "../clients/dispatch/auxiliary-lsp.js";
|
|
16
|
-
import { applyLspFindingPolicy, loadProjectRulePolicyMap, } from "../clients/dispatch/finding-policy.js";
|
|
17
|
-
import { hasStrictDispositionMarks } from "../clients/diagnostic-dispositions.js";
|
|
18
|
-
import { recordDegradationOnce } from "../clients/degradation-ledger.js";
|
|
19
|
-
import { logLatency } from "../clients/latency-logger.js";
|
|
20
|
-
import { detectFileRole } from "../clients/file-role.js";
|
|
21
|
-
import { hashDiagnosticContent, touchCompletedConfirmationPolicy, touchCoverageGap, } from "../clients/lsp/diagnostic-binding.js";
|
|
22
|
-
import { classifyCascadeWaitTier } from "../clients/lsp/wait-policy/index.js";
|
|
23
|
-
import { attemptTsserverSyncDiagnostics } from "../clients/lsp/tsserver-sync.js";
|
|
24
|
-
import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
|
|
25
|
-
import { demoteInferredProjectDiagnostics } from "../clients/lsp/inferred-project.js";
|
|
26
|
-
import { normalizeMapKey } from "../clients/path-utils.js";
|
|
27
|
-
import { countRetainedSuppressedRows, isBlocking, reconcileScanDiagnostics, } from "../clients/widget-state.js";
|
|
28
|
-
import { makeProgressReporter } from "./scan-progress.js";
|
|
29
|
-
import { isWarmAttached, tryWarmAttachedDiagnostics, } from "../clients/warm-attach.js";
|
|
30
|
-
import { extensionsForLanguage, SCAN_LANGUAGE_PRIORITY, } from "../clients/language-registry.js";
|
|
31
|
-
import { exceedsLspSyncLimits } from "../clients/lsp/content-limits.js";
|
|
32
|
-
const MAX_FILES = 100;
|
|
33
|
-
export const MAX_BATCH_FILES = 100;
|
|
34
|
-
const MAX_DIAGNOSTICS = 200;
|
|
35
|
-
const DEFAULT_BATCH_CONCURRENCY = 8;
|
|
36
|
-
const MAX_BATCH_CONCURRENCY = 16;
|
|
37
|
-
const DEFAULT_BATCH_FILE_DEADLINE_MS = 15_000;
|
|
38
|
-
// LSP severities: 1=Error, 2=Warning, 3=Information, 4=Hint
|
|
39
|
-
export const SEVERITY_NAMES = {
|
|
40
|
-
1: "error",
|
|
41
|
-
2: "warning",
|
|
42
|
-
3: "information",
|
|
43
|
-
4: "hint",
|
|
44
|
-
};
|
|
45
|
-
export const LSP_SEVERITY_FILTERS = [
|
|
46
|
-
...Object.values(SEVERITY_NAMES),
|
|
47
|
-
"all",
|
|
48
|
-
];
|
|
49
|
-
function assertNeverBatchFileOutcome(value) {
|
|
50
|
-
throw new Error(`Unhandled batch file outcome: ${String(value)}`);
|
|
51
|
-
}
|
|
52
|
-
/** Render the bounded per-file outcomes shared by batch, directory, and fold surfaces. */
|
|
53
|
-
export function renderBatchOutcomeLines(outcomes, formatFile = (file) => file) {
|
|
54
|
-
const lines = [];
|
|
55
|
-
for (const outcome of outcomes) {
|
|
56
|
-
switch (outcome.outcome) {
|
|
57
|
-
case "too_large":
|
|
58
|
-
lines.push(`${formatFile(outcome.file)}: ${outcome.reason ?? "file too large for LSP diagnostics"}`);
|
|
59
|
-
break;
|
|
60
|
-
case "clean":
|
|
61
|
-
case "findings":
|
|
62
|
-
case "unsupported":
|
|
63
|
-
case "unavailable":
|
|
64
|
-
case "failed":
|
|
65
|
-
case "inconclusive":
|
|
66
|
-
break;
|
|
67
|
-
default:
|
|
68
|
-
assertNeverBatchFileOutcome(outcome.outcome);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return lines;
|
|
72
|
-
}
|
|
73
|
-
function batchFileDeadlineMs() {
|
|
74
|
-
const raw = Number(process.env.PI_LENS_LSP_BATCH_FILE_MS);
|
|
75
|
-
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_BATCH_FILE_DEADLINE_MS;
|
|
76
|
-
}
|
|
77
|
-
// #646: `primaryServerId` moved to clients/lsp/config.ts so this tool and
|
|
78
|
-
// tools/lens-diagnostics.ts's mode=full sweep share the exact same
|
|
79
|
-
// primary-vs-auxiliary classification instead of each keeping its own copy.
|
|
80
|
-
function lspUnavailableMessage(filePath, health) {
|
|
81
|
-
if (!health || !String(health.health ?? "").startsWith("no_clients")) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
const candidates = health.candidateServerIds?.length
|
|
85
|
-
? ` candidates=${health.candidateServerIds.join(",")}`
|
|
86
|
-
: "";
|
|
87
|
-
const reason = (health.serverCountAttempted ?? 0) === 0
|
|
88
|
-
? "no LSP server configured"
|
|
89
|
-
: "no LSP client is currently ready";
|
|
90
|
-
const stale = (health.mergedCount ?? 0) > 0
|
|
91
|
-
? " Showing stale last-known diagnostics below."
|
|
92
|
-
: " No diagnostics were collected.";
|
|
93
|
-
return `LSP unavailable for ${filePath}: ${reason}; ready=${health.serverCountReady ?? 0}/${health.serverCountAttempted ?? 0}.${candidates}.${stale}`;
|
|
94
|
-
}
|
|
95
|
-
function boundedPositiveInt(value, fallback, min, max) {
|
|
96
|
-
const parsed = typeof value === "number" ? Math.floor(value) : Number.NaN;
|
|
97
|
-
if (!Number.isFinite(parsed))
|
|
98
|
-
return fallback;
|
|
99
|
-
return Math.max(min, Math.min(max, parsed));
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* #631: fan `mapper` out across `items` (a batch/directory file list) while
|
|
103
|
-
* respecting per-LSP-server affinity — previously this was a flat,
|
|
104
|
-
* server-oblivious bounded-concurrency pool (up to `concurrency` files
|
|
105
|
-
* in flight at once, regardless of which server they belonged to). That let
|
|
106
|
-
* a single-language batch (the common case) fire many concurrent touches at
|
|
107
|
-
* the SAME shared, single-threaded LSP server — exactly the pattern #387
|
|
108
|
-
* found doesn't parallelize (it queues server-side and cascades per-file
|
|
109
|
-
* timeouts by queue position) and that `runWorkspaceDiagnostics` (the engine
|
|
110
|
-
* behind `lens_diagnostics mode=full`) has been protected against since #387.
|
|
111
|
-
*
|
|
112
|
-
* Groups `items` by primary server via `groupFilesByPrimaryServer` (the same
|
|
113
|
-
* grouping key `runWorkspaceDiagnostics` uses) and schedules them with
|
|
114
|
-
* `runPerServerGroups` (both extracted from `clients/lsp/index.ts` so this
|
|
115
|
-
* tool shares the real primitive instead of a second hand-copied
|
|
116
|
-
* implementation): at most one in-flight `mapper` call per server group,
|
|
117
|
-
* parallelized across distinct groups up to `concurrency`. A single-language
|
|
118
|
-
* batch collapses to one group and runs effectively serially regardless of
|
|
119
|
-
* `concurrency` — the CORRECT, intended #387 behavior, not a regression.
|
|
120
|
-
*
|
|
121
|
-
* Result order matches `items`' original order (not completion order),
|
|
122
|
-
* matching the old flat pool's positional-assignment behavior — callers may
|
|
123
|
-
* depend on `results[i]` corresponding to `items[i]`.
|
|
124
|
-
*
|
|
125
|
-
* #667: before a group's own per-file loop starts, calls the shared
|
|
126
|
-
* `LSPService.ensureWarmForSweep` warm-check/ensure-warm step (the same one
|
|
127
|
-
* `runWorkspaceDiagnostics` uses for `lens_diagnostics mode=full`) against
|
|
128
|
-
* the group's first file — a no-op when that group's primary server already
|
|
129
|
-
* demonstrated readiness earlier this session, one bounded warm-up round
|
|
130
|
-
* trip otherwise. Fixes the first-few-files-eat-cold-start-timeouts pattern
|
|
131
|
-
* for THIS tool's batch/directory sweep the same way #667 fixed it for the
|
|
132
|
-
* workspace-diagnostics sweep.
|
|
133
|
-
*/
|
|
134
|
-
/**
|
|
135
|
-
* Project-ignore predicate rooted at `root`, fail-open. Lets a directory scan
|
|
136
|
-
* honor the user's `.pi-lens.json` / `.gitignore` patterns — not just the
|
|
137
|
-
* canonical dir-name list — so `lsp_diagnostics` stays consistent with the
|
|
138
|
-
* workspace-diagnostics walk and every other scan surface (#243/#297/#298). A
|
|
139
|
-
* config-probe error never blocks a scan (matches the walkers' behaviour).
|
|
140
|
-
*/
|
|
141
|
-
function projectIgnorePredicate(root) {
|
|
142
|
-
try {
|
|
143
|
-
const matcher = getProjectIgnoreMatcher(root);
|
|
144
|
-
return (fullPath, isDir) => matcher.isIgnored(fullPath, isDir);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return () => false;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* #1137: async so a directory read never blocks the event loop (and pi's TUI).
|
|
152
|
-
*
|
|
153
|
-
* This walk was fully synchronous with NO yielding of any kind, bounded only by
|
|
154
|
-
* `maxFiles` *kept* — an ignored-heavy or cloud-backed (OneDrive/network) tree
|
|
155
|
-
* could traverse unboundedly many entries, and a single stalled `readdirSync`
|
|
156
|
-
* held the loop for the whole stall. It is also called once PER FAMILY in
|
|
157
|
-
* `runDirectoryDiagnostics`'s `SCAN_LANGUAGE_PRIORITY` loop (#2434, grouped by
|
|
158
|
-
* family since #2458 fix-round F1), so a directory-mode `lsp_diagnostics`
|
|
159
|
-
* could pay that cost several times over.
|
|
160
|
-
*
|
|
161
|
-
* The traversal is deliberately still **depth-first with immediate descent**
|
|
162
|
-
* (not the shared stack-based `walkTreeStackAsync`): the `maxFiles` cap makes
|
|
163
|
-
* traversal ORDER observable — a stack walk would keep a different subset of
|
|
164
|
-
* files on an over-large tree. Only scheduling changes here; the returned list
|
|
165
|
-
* is identical. Awaiting each directory read is itself the yield point.
|
|
166
|
-
*/
|
|
167
|
-
async function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
|
|
168
|
-
const files = [];
|
|
169
|
-
async function walk(current) {
|
|
170
|
-
if (files.length >= maxFiles)
|
|
171
|
-
return;
|
|
172
|
-
let entries;
|
|
173
|
-
try {
|
|
174
|
-
entries = await fs.promises.readdir(current, { withFileTypes: true });
|
|
175
|
-
}
|
|
176
|
-
catch {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
for (const entry of entries) {
|
|
180
|
-
if (files.length >= maxFiles)
|
|
181
|
-
return;
|
|
182
|
-
if (entry.isSymbolicLink())
|
|
183
|
-
continue;
|
|
184
|
-
const full = path.join(current, entry.name);
|
|
185
|
-
if (entry.isDirectory()) {
|
|
186
|
-
if (!isExcludedDirName(entry.name) && !isIgnored(full, true))
|
|
187
|
-
await walk(full);
|
|
188
|
-
}
|
|
189
|
-
else if (entry.isFile() && extensions.includes(path.extname(full))) {
|
|
190
|
-
if (isIgnored(full, false))
|
|
191
|
-
continue;
|
|
192
|
-
files.push(full);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
await walk(dir);
|
|
197
|
-
return files;
|
|
198
|
-
}
|
|
199
|
-
export function createLspDiagnosticsTool(
|
|
200
|
-
// #571/#1198: same shared write-ordering token source `lens_diagnostics`
|
|
201
|
-
// mode=full uses (index.ts injects `() => runtime.nextWriteIndex()`). A
|
|
202
|
-
// confirmed result reconciled into the footer reserves its token when the
|
|
203
|
-
// per-file check starts, so settlement order cannot make an older result look
|
|
204
|
-
// newer. Optional/undefined in tests.
|
|
205
|
-
nextWriteIndex,
|
|
206
|
-
// #1561: #571 wired this tool's confirmed result into the widget footer and
|
|
207
|
-
// stopped there. The inline-blocker map is a SECOND agent-facing store fed by
|
|
208
|
-
// the same dispatch verdict, and nothing retired it — so pi-lens kept
|
|
209
|
-
// injecting "Unresolved from this turn" for three more turns after answering
|
|
210
|
-
// "confirmed clean" for the same file. index.ts injects
|
|
211
|
-
// `runtime.retireInlineBlockerOnConfirmedClean`. Optional/undefined in tests.
|
|
212
|
-
onConfirmedNoBlockers, getService = getLSPService) {
|
|
213
|
-
return {
|
|
214
|
-
async execute(_toolCallId, params, _signal, onUpdate, ctx) {
|
|
215
|
-
// Escape aborts the turn via ctx.signal; honor both it and the tool-call
|
|
216
|
-
// signal so a batch/directory scan cancels rather than grinding on.
|
|
217
|
-
const signal = combineAbortSignals(_signal, ctx.signal);
|
|
218
|
-
// Stream a throttled progress bar for batch/directory scans (opaque for
|
|
219
|
-
// seconds-to-minutes otherwise).
|
|
220
|
-
const onProgress = makeProgressReporter(onUpdate, "Scanning LSP diagnostics");
|
|
221
|
-
const typedParams = params;
|
|
222
|
-
const severity = (typedParams.severity ?? "all");
|
|
223
|
-
const cwd = ctx.cwd ?? process.cwd();
|
|
224
|
-
const concurrency = boundedPositiveInt(typedParams.concurrency, DEFAULT_BATCH_CONCURRENCY, 1, MAX_BATCH_CONCURRENCY);
|
|
225
|
-
const waitMs = typeof typedParams.waitMs === "number" && typedParams.waitMs >= 0
|
|
226
|
-
? Math.floor(typedParams.waitMs)
|
|
227
|
-
: undefined;
|
|
228
|
-
const serverScope = typedParams.serverScope === "primary" ? "primary" : "all";
|
|
229
|
-
const lspService = getService();
|
|
230
|
-
if (!lspService) {
|
|
231
|
-
return {
|
|
232
|
-
content: [
|
|
233
|
-
{ type: "text", text: "LSP service not available." },
|
|
234
|
-
],
|
|
235
|
-
isError: true,
|
|
236
|
-
details: {},
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
if (Array.isArray(typedParams.paths) && typedParams.paths.length > 0) {
|
|
240
|
-
if (typedParams.paths.length > MAX_BATCH_FILES) {
|
|
241
|
-
return {
|
|
242
|
-
content: [
|
|
243
|
-
{
|
|
244
|
-
type: "text",
|
|
245
|
-
text: `paths accepts at most ${MAX_BATCH_FILES} files; received ${typedParams.paths.length}.`,
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
isError: true,
|
|
249
|
-
details: { mode: "batch", filesChecked: 0 },
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
const rawPaths = typedParams.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
253
|
-
if (rawPaths.length !== typedParams.paths.length) {
|
|
254
|
-
return {
|
|
255
|
-
content: [
|
|
256
|
-
{
|
|
257
|
-
type: "text",
|
|
258
|
-
text: "paths must contain non-empty file paths.",
|
|
259
|
-
},
|
|
260
|
-
],
|
|
261
|
-
isError: true,
|
|
262
|
-
details: { mode: "batch", filesChecked: 0 },
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
// Preserve input order (including duplicate entries); normalize each
|
|
266
|
-
// path before grouping so Windows separators and dot segments cannot
|
|
267
|
-
// change cache/group identity. Explicit lists never enter the walker.
|
|
268
|
-
// #3160/#3182: `path.resolve(cwd, entry)` folds dot segments (and
|
|
269
|
-
// separators) structurally — it ignores `cwd` whenever `entry` is
|
|
270
|
-
// already absolute, so the old `path.isAbsolute` ternary is
|
|
271
|
-
// redundant. `normalizeMapKey` then adopts on-disk CASING where the
|
|
272
|
-
// path exists — an agent-typed, possibly mis-cased `paths` entry
|
|
273
|
-
// must key `reconcileScanDiagnostics`'s widget-state write the same
|
|
274
|
-
// way a canonical writer (clients/pipeline.ts's ctx.filePath) or the
|
|
275
|
-
// #3160-fixed lens_diagnostic_mark reader would. `path.resolve`
|
|
276
|
-
// stays required for the relative→absolute step: `normalizeMapKey`
|
|
277
|
-
// folds dot segments itself since #3184, but never resolves against
|
|
278
|
-
// `cwd` (a cwd fold there broke every monorepo, #2490).
|
|
279
|
-
const absPaths = rawPaths.map((entry) => normalizeMapKey(path.resolve(cwd, entry)));
|
|
280
|
-
return runBatchFileDiagnostics(absPaths, severity, lspService, {
|
|
281
|
-
concurrency,
|
|
282
|
-
waitMs,
|
|
283
|
-
signal,
|
|
284
|
-
onProgress,
|
|
285
|
-
nextWriteIndex,
|
|
286
|
-
serverScope,
|
|
287
|
-
cwd,
|
|
288
|
-
onConfirmedNoBlockers,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
const rawPath = typedParams.path;
|
|
292
|
-
if (!rawPath || rawPath.trim().length === 0) {
|
|
293
|
-
return {
|
|
294
|
-
content: [
|
|
295
|
-
{
|
|
296
|
-
type: "text",
|
|
297
|
-
text: "path or paths is required.",
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
isError: true,
|
|
301
|
-
details: {},
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
// #3184: the single-`path` sibling of the `paths` batch above, and
|
|
305
|
-
// keyed the same way — `runFileDiagnostics` hands this straight to
|
|
306
|
-
// `reconcileScanDiagnostics`, whose `normalizeEphemeralMapKey` key
|
|
307
|
-
// derivation folds neither dot segments nor casing. Same expression as
|
|
308
|
-
// :444 so both modes of this tool write under ONE key per file.
|
|
309
|
-
const absPath = normalizeMapKey(path.resolve(cwd, rawPath));
|
|
310
|
-
let stat;
|
|
311
|
-
try {
|
|
312
|
-
stat = fs.statSync(absPath);
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return {
|
|
316
|
-
content: [
|
|
317
|
-
{ type: "text", text: `Path not found: ${absPath}` },
|
|
318
|
-
],
|
|
319
|
-
isError: true,
|
|
320
|
-
details: {},
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
if (stat.isDirectory()) {
|
|
324
|
-
return runDirectoryDiagnostics(absPath, severity, lspService, {
|
|
325
|
-
concurrency,
|
|
326
|
-
waitMs,
|
|
327
|
-
signal,
|
|
328
|
-
onProgress,
|
|
329
|
-
nextWriteIndex,
|
|
330
|
-
serverScope,
|
|
331
|
-
cwd,
|
|
332
|
-
onConfirmedNoBlockers,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
return runFileDiagnostics(absPath, severity, lspService, waitMs, nextWriteIndex, serverScope, cwd, onConfirmedNoBlockers);
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
async function collectDiagnosticsForFile(absPath, lspService,
|
|
340
|
-
/**
|
|
341
|
-
* #3041: project root the per-rule `ignores` globs (#965) are resolved
|
|
342
|
-
* against. ast-grep's LSP publishes per-document diagnostics without
|
|
343
|
-
* applying them, so this standalone `source=lsp` query has to — the same
|
|
344
|
-
* carve-out the NAPI runner applies on the same file. Positioned before the
|
|
345
|
-
* optional parameters so it is structurally REQUIRED: an optional
|
|
346
|
-
* `string | undefined` would silently disable the carve-out for whichever
|
|
347
|
-
* caller forgot to pass it.
|
|
348
|
-
*/
|
|
349
|
-
scanRoot, waitMs, serverScope = "all") {
|
|
350
|
-
let timedOut = false;
|
|
351
|
-
let content;
|
|
352
|
-
// `touchFile` is the authoritative collection boundary for every scope: it
|
|
353
|
-
// preserves per-touch timeout, content-binding, and silent-clean confirmation
|
|
354
|
-
// metadata while returning diagnostics from only the requested clients. The
|
|
355
|
-
// getDiagnostics fallback below remains for the two states a REAL service
|
|
356
|
-
// still reaches: the file read threw (no content to touch with), or the
|
|
357
|
-
// touch resolved no clients and returned `undefined` (clients/lsp/index.ts
|
|
358
|
-
// `touchFile`'s `no_clients`/`destroyed` returns). #2598 deleted the third
|
|
359
|
-
// reason — "an older/mock service without touchFile" — because the real
|
|
360
|
-
// `LSPService` has always defined the method unconditionally, so that arm
|
|
361
|
-
// was reachable only from a partial test double (AGENTS.md shape 7).
|
|
362
|
-
// #1179: the result is an explicit wrapper so side-channel fields survive
|
|
363
|
-
// array copies; confirmation was added when Marksman's lower-level clean verdict
|
|
364
|
-
// proved that a successful empty collection also needs explicit provenance.
|
|
365
|
-
let touched;
|
|
366
|
-
try {
|
|
367
|
-
content = fs.readFileSync(absPath, "utf-8");
|
|
368
|
-
const contentLimit = exceedsLspSyncLimits(content);
|
|
369
|
-
if (contentLimit.tooLarge) {
|
|
370
|
-
const reason = `file too large for LSP diagnostics (${contentLimit.reason})`;
|
|
371
|
-
recordDegradationOnce({
|
|
372
|
-
kind: "lsp-diagnostics-file-too-large",
|
|
373
|
-
subject: absPath,
|
|
374
|
-
reason,
|
|
375
|
-
});
|
|
376
|
-
return {
|
|
377
|
-
diagnostics: [],
|
|
378
|
-
timedOut: false,
|
|
379
|
-
confirmedByTouch: false,
|
|
380
|
-
unconfirmedServerIds: [],
|
|
381
|
-
diagnosticsUnsupportedServerIds: [],
|
|
382
|
-
content,
|
|
383
|
-
tooLargeReason: reason,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
if (isWarmAttached()) {
|
|
387
|
-
// The local sweep's default service wait is bounded to the same
|
|
388
|
-
// 15-second per-file envelope used by the workspace sweep. An explicit
|
|
389
|
-
// waitMs remains the tighter caller-selected cap.
|
|
390
|
-
const attachedBudgetMs = waitMs ?? 15_000;
|
|
391
|
-
const attached = await tryWarmAttachedDiagnostics(absPath, content, attachedBudgetMs, "sweep");
|
|
392
|
-
if (attached?.available) {
|
|
393
|
-
const scopedDiagnostics = serverScope === "primary"
|
|
394
|
-
? attached.response.diagnostics.filter((item) => item.serverId === primaryServerId(absPath))
|
|
395
|
-
: attached.response.diagnostics;
|
|
396
|
-
const filtered = applyAuxiliarySuppressions(scopedDiagnostics, content, {
|
|
397
|
-
fileRole: detectFileRole(absPath, content),
|
|
398
|
-
filePath: absPath,
|
|
399
|
-
scanRoot,
|
|
400
|
-
});
|
|
401
|
-
return {
|
|
402
|
-
diagnostics: filtered,
|
|
403
|
-
timedOut: false,
|
|
404
|
-
// #1253: the incumbent's touch carries the same confirmation
|
|
405
|
-
// provenance a local touch does (an `available: true` answer is
|
|
406
|
-
// already gated on `fresh && !inconclusive`, but that is NOT
|
|
407
|
-
// evidence a silent-on-clean server was confirmed — only the
|
|
408
|
-
// explicit flag is). The incumbent always touches with
|
|
409
|
-
// `clientScope: "with-auxiliary"`, so this is an AGGREGATE
|
|
410
|
-
// confirmation: it carries the same caveat all-scope local
|
|
411
|
-
// touches do, and classic TypeScript still needs the primary-only
|
|
412
|
-
// tsserver sync check below rather than this verdict.
|
|
413
|
-
// #1470: `"partial"` counts, for the same reason the local touch
|
|
414
|
-
// branch below accepts it — the incumbent's primary confirmed; only
|
|
415
|
-
// a named auxiliary did not.
|
|
416
|
-
confirmedByTouch: attached.response.confirmation !== undefined &&
|
|
417
|
-
primaryServerId(absPath) !== "typescript",
|
|
418
|
-
// #1470: the incumbent's narrowed verdict, carried as an explicit DTO
|
|
419
|
-
// field across the socket. An older incumbent omits it → empty → the
|
|
420
|
-
// pre-#1470 handling, unchanged.
|
|
421
|
-
unconfirmedServerIds: attached.response.unconfirmedServerIds ?? [],
|
|
422
|
-
diagnosticsUnsupportedServerIds: [],
|
|
423
|
-
content,
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
touched = await lspService.touchFile(absPath, content, {
|
|
428
|
-
diagnostics: "document",
|
|
429
|
-
collectDiagnostics: true,
|
|
430
|
-
maxClientWaitMs: waitMs,
|
|
431
|
-
source: "lsp_diagnostics",
|
|
432
|
-
clientScope: serverScope,
|
|
433
|
-
// #3405: the caller asked whether THIS file is clean right now, and the
|
|
434
|
-
// content above was just read from disk — so it is the file's saved
|
|
435
|
-
// state. Without the save a save-triggered server answers zero
|
|
436
|
-
// diagnostics for every file in the query, which this path would report
|
|
437
|
-
// as clean.
|
|
438
|
-
saved: true,
|
|
439
|
-
});
|
|
440
|
-
timedOut = touched?.inconclusive === true;
|
|
441
|
-
}
|
|
442
|
-
catch {
|
|
443
|
-
// Non-fatal: getDiagnostics may still have stale/health information.
|
|
444
|
-
}
|
|
445
|
-
// Only fall through to the unscoped getDiagnostics() read when the touch
|
|
446
|
-
// produced nothing to read — it threw, the file read threw before it ran, or
|
|
447
|
-
// it resolved no clients at all. When touched IS defined it's already the
|
|
448
|
-
// answer — reusing it is what makes serverScope:"primary" actually skip
|
|
449
|
-
// auxiliary scanners and drops the common case back to a single LSP round
|
|
450
|
-
// trip instead of two.
|
|
451
|
-
const diagnostics = touched !== undefined
|
|
452
|
-
? touched.diags
|
|
453
|
-
: await lspService.getDiagnostics(absPath, waitMs !== undefined ? "document" : "full");
|
|
454
|
-
// #586: honor each auxiliary profile's native inline-suppression comment
|
|
455
|
-
// (e.g. opengrep's `// nosemgrep`, #441) the same way the per-edit dispatch
|
|
456
|
-
// runner does — previously this standalone query path ignored it entirely.
|
|
457
|
-
// #692: also honor a profile's `skipTestFiles` gate (e.g. ast-grep, #687) —
|
|
458
|
-
// this standalone-query path had no test-file gating of its own, so an
|
|
459
|
-
// `lsp_diagnostics` check on a test file surfaced ast-grep findings the
|
|
460
|
-
// per-edit dispatch runner would have suppressed. `content` is only unset
|
|
461
|
-
// if the read itself failed above; fail-open (no filtering) rather than
|
|
462
|
-
// lose diagnostics over an unrelated read error.
|
|
463
|
-
const filtered = content !== undefined
|
|
464
|
-
? applyAuxiliarySuppressions(diagnostics, content, {
|
|
465
|
-
fileRole: detectFileRole(absPath, content),
|
|
466
|
-
filePath: absPath,
|
|
467
|
-
scanRoot,
|
|
468
|
-
})
|
|
469
|
-
: diagnostics;
|
|
470
|
-
// #1095: surface the touch's content binding (only a touch that resolved
|
|
471
|
-
// clients carries one; the getDiagnostics fallback leaves it undefined →
|
|
472
|
-
// "unknown", no demotion).
|
|
473
|
-
const binding = touched?.binding;
|
|
474
|
-
// #1470: `"partial"` counts here. `confirmedByTouch` feeds
|
|
475
|
-
// `canTrustTouchConfirmation`, which asks about the PRIMARY's own verdict —
|
|
476
|
-
// and a partial touch is one whose primary confirmed while an auxiliary was
|
|
477
|
-
// cut off. Excluding it would render "Primary LSP: unconfirmed" for a primary
|
|
478
|
-
// that did confirm. The coverage gap is carried separately, below.
|
|
479
|
-
const confirmedByTouch = touchCompletedConfirmationPolicy(touched);
|
|
480
|
-
return {
|
|
481
|
-
diagnostics: filtered,
|
|
482
|
-
timedOut,
|
|
483
|
-
skipReason: touched?.skipReason,
|
|
484
|
-
confirmedByTouch,
|
|
485
|
-
// #1470: only a touch that resolved clients contributes a coverage gap;
|
|
486
|
-
// the getDiagnostics fallback never reports one, which is honest — that
|
|
487
|
-
// path claims no confirmation at all.
|
|
488
|
-
unconfirmedServerIds: touchCoverageGap(touched),
|
|
489
|
-
diagnosticsUnsupportedServerIds: touched?.diagnosticsUnsupportedServerIds ?? [],
|
|
490
|
-
content,
|
|
491
|
-
binding,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
function diagnosticsToFileDiags(file, diagnostics) {
|
|
495
|
-
return diagnostics.map((d) => ({
|
|
496
|
-
file,
|
|
497
|
-
line: d.range?.start?.line,
|
|
498
|
-
character: d.range?.start?.character,
|
|
499
|
-
severity: d.severity,
|
|
500
|
-
message: d.message,
|
|
501
|
-
source: d.source,
|
|
502
|
-
serverId: d.serverId,
|
|
503
|
-
code: d.code,
|
|
504
|
-
}));
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* #533: classify an EMPTY diagnostic result as "clean" (the server actually
|
|
508
|
-
* confirmed no issues) or "unconfirmed" (came from a push-only,
|
|
509
|
-
* silent-on-clean server — classic typescript-language-server — that
|
|
510
|
-
* publishes nothing on a clean→clean transition, so an empty result here is
|
|
511
|
-
* indistinguishable from "still analyzing" or "never asked"). Reuses the same
|
|
512
|
-
* capability-snapshot classifier the #458 cascade lane already trusts
|
|
513
|
-
* (`classifyCascadeWaitTier`) so this tool's notion of "silent tier-3" stays
|
|
514
|
-
* in lockstep with the rest of the LSP layer instead of drifting via a second
|
|
515
|
-
* copy of the server-strategy table. Fail-safe: any error or missing snapshot
|
|
516
|
-
* (server not alive, capability probe failure) reads as "clean" — the same
|
|
517
|
-
* default this tool has always had — rather than manufacturing a new failure
|
|
518
|
-
* mode from a best-effort classification.
|
|
519
|
-
*/
|
|
520
|
-
async function classifyEmptyResult(file, lspService) {
|
|
521
|
-
try {
|
|
522
|
-
const snapshots = await lspService.getCapabilitySnapshots(file);
|
|
523
|
-
const tier = classifyCascadeWaitTier(lspService, file, snapshots);
|
|
524
|
-
return tier === "tier3-silent" ? "unconfirmed" : "clean";
|
|
525
|
-
}
|
|
526
|
-
catch {
|
|
527
|
-
return "clean";
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// --- #611/#707: tier-3 silent escape hatch (typescript.tsserverRequest sync
|
|
531
|
-
// commands) — implementation extracted to clients/lsp/tsserver-sync.ts and
|
|
532
|
-
// re-used from there by the per-edit dispatch path (#707). ---
|
|
533
|
-
/**
|
|
534
|
-
* #611: resolve an EMPTY diagnostic result for a Tier-3 silent server (see
|
|
535
|
-
* `classifyCascadeWaitTier` — today only classic typescript-language-server,
|
|
536
|
-
* native-ts7 is explicitly excluded there) with a definitive answer instead of
|
|
537
|
-
* defaulting straight to "unconfirmed". `confirmed: true` with an empty
|
|
538
|
-
* `diagnostics` array is a genuinely confirmed clean result; `confirmed: true`
|
|
539
|
-
* with a non-empty array means the sync command surfaced real diagnostics the
|
|
540
|
-
* server had computed but never published (silentOnClean) — these must be
|
|
541
|
-
* surfaced to the caller, not discarded. `confirmed: false` is the existing
|
|
542
|
-
* "unconfirmed" fallback (command unavailable, error, or the file isn't part
|
|
543
|
-
* of any project). Fail-safe: any error in the tier classification itself
|
|
544
|
-
* (missing snapshot, server not alive) reads as `confirmed: true` with no
|
|
545
|
-
* diagnostics — the same "clean" default this tool has always had.
|
|
546
|
-
*/
|
|
547
|
-
async function resolveEmptyResult(file, lspService) {
|
|
548
|
-
try {
|
|
549
|
-
const snapshots = await lspService.getCapabilitySnapshots(file);
|
|
550
|
-
const tier = classifyCascadeWaitTier(lspService, file, snapshots);
|
|
551
|
-
if (tier !== "tier3-silent") {
|
|
552
|
-
return { confirmed: true, diagnostics: [] };
|
|
553
|
-
}
|
|
554
|
-
const syncDiagnostics = await attemptTsserverSyncDiagnostics(file, lspService);
|
|
555
|
-
if (syncDiagnostics === undefined) {
|
|
556
|
-
return { confirmed: false, diagnostics: [] };
|
|
557
|
-
}
|
|
558
|
-
return { confirmed: true, diagnostics: syncDiagnostics };
|
|
559
|
-
}
|
|
560
|
-
catch {
|
|
561
|
-
return { confirmed: true, diagnostics: [] };
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* A primary-scope touch is authoritative because `touchFile` runs that
|
|
566
|
-
* primary's own confirmation path (including TypeScript's sync fallback). For
|
|
567
|
-
* all-scope TypeScript touches, the aggregate silent-clean gate can settle
|
|
568
|
-
* without that primary-only sync request, so preserve `resolveEmptyResult`'s
|
|
569
|
-
* existing tsserver fallback instead of accepting the aggregate verdict.
|
|
570
|
-
*/
|
|
571
|
-
function canTrustTouchConfirmation(file, serverScope, confirmedByTouch) {
|
|
572
|
-
return (confirmedByTouch &&
|
|
573
|
-
(serverScope === "primary" || primaryServerId(file) !== "typescript"));
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* #1561 F1: what this check is entitled to speak for.
|
|
577
|
-
*
|
|
578
|
-
* Inline blockers are raised by the whole dispatch — eslint, biome-check,
|
|
579
|
-
* actionlint, ast-grep security rules — while this tool only ever consults
|
|
580
|
-
* language servers. Retiring one therefore needs an explicit coverage claim,
|
|
581
|
-
* and the honest claim is exactly the servers this check consulted and got an
|
|
582
|
-
* answer from. `serverScope: "primary"` consults only the primary, so its claim
|
|
583
|
-
* is `"lsp"` alone; an all-scope check adds each auxiliary server by id, which
|
|
584
|
-
* is the same vocabulary `retagAuxiliaryDiagnostics` tags their findings with.
|
|
585
|
-
*
|
|
586
|
-
* A server named in `unconfirmedServerIds` is dropped: it produced no evidence
|
|
587
|
-
* for this content, so claiming coverage for it would be the #1470/#1493 lie one
|
|
588
|
-
* layer up. (Today an aux gap also demotes the whole verdict to "unconfirmed",
|
|
589
|
-
* so the hook does not fire at all — this stays correct if that ever narrows.)
|
|
590
|
-
*/
|
|
591
|
-
function coveredSourcesForCheck(file, serverScope, unconfirmedServerIds) {
|
|
592
|
-
const unconfirmed = new Set(unconfirmedServerIds);
|
|
593
|
-
const covered = new Set();
|
|
594
|
-
let servers = [];
|
|
595
|
-
try {
|
|
596
|
-
servers = (getServersForFileWithConfig(file) ?? []);
|
|
597
|
-
}
|
|
598
|
-
catch {
|
|
599
|
-
// A registry we cannot read is not a coverage claim. Returning an empty
|
|
600
|
-
// set makes every retire fail closed, which is the safe direction for a
|
|
601
|
-
// commit gate.
|
|
602
|
-
return [];
|
|
603
|
-
}
|
|
604
|
-
for (const server of servers) {
|
|
605
|
-
const auxiliary = server.role === "auxiliary";
|
|
606
|
-
if (auxiliary && serverScope === "primary")
|
|
607
|
-
continue;
|
|
608
|
-
if (unconfirmed.has(server.id))
|
|
609
|
-
continue;
|
|
610
|
-
// The dispatch runner tags the primary language server's findings `lsp`;
|
|
611
|
-
// auxiliaries keep their own tool id.
|
|
612
|
-
covered.add(auxiliary ? server.id : "lsp");
|
|
613
|
-
}
|
|
614
|
-
return [...covered];
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* #1645 review F3: `lsp_diagnostics` writes into the SAME widget store as
|
|
618
|
-
* `lens_diagnostics mode=full`. Two tools writing one store must apply ONE rule
|
|
619
|
-
* for "is this file's diagnostics demoted" — otherwise an orphan file blocks or
|
|
620
|
-
* does not depending purely on which tool ran last, which is the #1633-V1
|
|
621
|
-
* lesson. Every path in this file that reaches `reconcileWidgetFromLspResult`
|
|
622
|
-
* or the rendered output routes its raw diagnostics through here first, so the
|
|
623
|
-
* store, the counts, and the text all agree.
|
|
624
|
-
*
|
|
625
|
-
* Applied AFTER the confirmation verdict is computed, never before: confirmation
|
|
626
|
-
* asks "did the server answer for this content", which the demotion must not
|
|
627
|
-
* influence.
|
|
628
|
-
*/
|
|
629
|
-
function demoteInferredForFile(file, diagnostics, cwd, lspService) {
|
|
630
|
-
return demoteInferredProjectDiagnostics(diagnostics, {
|
|
631
|
-
filePath: file,
|
|
632
|
-
cwd,
|
|
633
|
-
service: lspService,
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* #3088: the probe lane's stored-disposition / rule-policy / inline-suppression
|
|
638
|
-
* gate — the same `clients/dispatch/finding-policy.ts` stack `mode=full` and
|
|
639
|
-
* the per-edit dispatcher apply, called once per file at the position
|
|
640
|
-
* `demoteInferredForFile` already established: AFTER the confirmation verdict
|
|
641
|
-
* (which asks "did the server answer for this content" and must not be
|
|
642
|
-
* influenced by a policy drop) and BEFORE the rendered output, the severity
|
|
643
|
-
* filter and `reconcileWidgetFromLspResult`. One filtered set for all three, so
|
|
644
|
-
* the text, the counts and the widget footer cannot disagree — and so the
|
|
645
|
-
* footer reconcile can no longer re-arm a finding the mark-time
|
|
646
|
-
* `reconcileWidgetDisposition` demoted.
|
|
647
|
-
*
|
|
648
|
-
* Fails OPEN: a filter that throws returns the unfiltered set plus one bounded
|
|
649
|
-
* degradation record. A finding that stays visible is recoverable; one silently
|
|
650
|
-
* hidden by a broken filter is the harm this lane exists to prevent
|
|
651
|
-
* (AGENTS.md shape 48).
|
|
652
|
-
*/
|
|
653
|
-
function applyProbeFindingPolicy(file, diagnostics, cwd, content,
|
|
654
|
-
/**
|
|
655
|
-
* Round 2 F1: the caller's severity threshold. The count reported to the
|
|
656
|
-
* agent must be scoped to the SAME threshold the rendered result is, or a
|
|
657
|
-
* `severity: "error"` probe of a repo carrying warning-level marks prints
|
|
658
|
-
* "suppressed by disposition: N … Not a clean verdict" on a result that IS
|
|
659
|
-
* clean at that threshold — the skill's own folder-check recipe would emit
|
|
660
|
-
* the banner on every check. The threshold is applied HERE, in the tool that
|
|
661
|
-
* owns both the render and the record, rather than in the three call sites
|
|
662
|
-
* (three copies of one subtraction) or in
|
|
663
|
-
* `clients/dispatch/finding-policy.ts`, which must stay threshold-free: its
|
|
664
|
-
* other caller (`mode=full`) has no severity axis at all.
|
|
665
|
-
*/
|
|
666
|
-
severity) {
|
|
667
|
-
const started = Date.now();
|
|
668
|
-
try {
|
|
669
|
-
const result = applyLspFindingPolicy(diagnostics, {
|
|
670
|
-
cwd,
|
|
671
|
-
filePath: file,
|
|
672
|
-
content,
|
|
673
|
-
policyMap: loadProjectRulePolicyMap(cwd),
|
|
674
|
-
fileRole: detectFileRole(file, content),
|
|
675
|
-
});
|
|
676
|
-
const inScopeBefore = applySeverityFilter(diagnostics, severity).length;
|
|
677
|
-
const suppressed = inScopeBefore - applySeverityFilter(result.kept, severity).length;
|
|
678
|
-
if (suppressed > 0) {
|
|
679
|
-
// One record per FILE that actually dropped something the caller would
|
|
680
|
-
// otherwise have SEEN, never one per finding (AGENTS.md "bounded
|
|
681
|
-
// observability"). `total` is the in-scope population for the same
|
|
682
|
-
// reason the count is, so the record and the rendered line agree.
|
|
683
|
-
//
|
|
684
|
-
// #3158 round 2 F4: `retainedSuppressed` is the widget store's
|
|
685
|
-
// SUCCESS-path number — how many suppressed rows the store is carrying
|
|
686
|
-
// INTO this scan, written by the scans before it. Until this, retention
|
|
687
|
-
// was observable only when its per-file cap truncated
|
|
688
|
-
// (`widget-suppressed-retention-capped`), so a healthy footer chip had no
|
|
689
|
-
// record behind it at all. Folded into the record this lane already emits
|
|
690
|
-
// rather than added as a second one: same cardinality (one per filtered
|
|
691
|
-
// file per scan, never one per row), no new sink, and the two numbers are
|
|
692
|
-
// read together — `suppressed` is what THIS scan dropped,
|
|
693
|
-
// `retainedSuppressed` is what the footer chip is still counting.
|
|
694
|
-
logLatency({
|
|
695
|
-
type: "phase",
|
|
696
|
-
toolName: "lsp_diagnostics",
|
|
697
|
-
filePath: file,
|
|
698
|
-
phase: "lsp_probe_disposition_filter",
|
|
699
|
-
durationMs: Date.now() - started,
|
|
700
|
-
metadata: {
|
|
701
|
-
suppressed,
|
|
702
|
-
total: inScopeBefore,
|
|
703
|
-
retainedSuppressed: countRetainedSuppressedRows(file),
|
|
704
|
-
},
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
return { kept: result.kept, inlineKept: result.inlineKept, suppressed };
|
|
708
|
-
}
|
|
709
|
-
catch (err) {
|
|
710
|
-
recordDegradationOnce({
|
|
711
|
-
kind: "lsp-probe-finding-policy",
|
|
712
|
-
subject: cwd,
|
|
713
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
714
|
-
});
|
|
715
|
-
return { kept: diagnostics, inlineKept: diagnostics, suppressed: 0 };
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* #3088 AC5/AC7: the content a cache REPLAY needs to evaluate STRICT
|
|
720
|
-
* (`false-positive`) disposition anchors, which hash the finding's own line.
|
|
721
|
-
* A cache hit means the file is unchanged since the entry was recorded, so this
|
|
722
|
-
* is the same content the recording touch read — but it is a disk read the
|
|
723
|
-
* replay path exists to avoid, so it is paid ONLY when the store actually holds
|
|
724
|
-
* a strict mark. With no marks (the overwhelmingly common case) the probe path
|
|
725
|
-
* adds zero I/O and the weak-anchored marks apply without content at all.
|
|
726
|
-
*/
|
|
727
|
-
function replayContentForStrictMarks(file, cwd) {
|
|
728
|
-
if (!hasStrictDispositionMarks(cwd))
|
|
729
|
-
return undefined;
|
|
730
|
-
try {
|
|
731
|
-
return fs.readFileSync(file, "utf-8");
|
|
732
|
-
}
|
|
733
|
-
catch {
|
|
734
|
-
// Fail open — an unreadable file degrades to the weak-anchored filter
|
|
735
|
-
// rather than hiding or dropping anything.
|
|
736
|
-
return undefined;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
function reconcileWidgetFromLspResult(file, rawDiags, confirmation, writeIndex, cwd, content,
|
|
740
|
-
// #1095: true when the result's content binding demonstrably mismatches disk
|
|
741
|
-
// (boundToCurrentDisk === false). Such a result — even a NON-EMPTY one, which
|
|
742
|
-
// the pre-#1095 "non-empty is definitionally confirmed" doctrine would have
|
|
743
|
-
// written straight through — must NOT re-cement the footer with a stale view
|
|
744
|
-
// (#1092's window-1 transcript: 17 live-looking diagnostics while tsc exits
|
|
745
|
-
// 0). "unknown"/true bindings leave the doctrine unchanged (fallback preserved
|
|
746
|
-
// for servers that never bind a version).
|
|
747
|
-
boundMismatch = false,
|
|
748
|
-
// #1093: when these `rawDiags` were actually OBSERVED. Fresh touches observe
|
|
749
|
-
// now (`undefined` → `Date.now()`); a cache HIT replays diagnostics scanned
|
|
750
|
-
// earlier, so its caller passes the cache entry's `scannedAt` here so the
|
|
751
|
-
// footer's `touchedAt` isn't re-armed to now() and the mtime-staleness gate
|
|
752
|
-
// stays live (the #1092 re-arming defect).
|
|
753
|
-
observedAt) {
|
|
754
|
-
const confirmed = (rawDiags.length > 0 || confirmation !== "unconfirmed") && !boundMismatch;
|
|
755
|
-
if (!confirmed)
|
|
756
|
-
return { confirmed: false, blocking: true };
|
|
757
|
-
try {
|
|
758
|
-
// #692: provenance label ONLY — must never affect `rule`/identity (see
|
|
759
|
-
// `ConvertLspDiagnosticsOptions.scanOrigin`'s doc comment).
|
|
760
|
-
const diagnostics = convertLspDiagnostics(rawDiags, file, {
|
|
761
|
-
scanOrigin: "lsp_diagnostics",
|
|
762
|
-
});
|
|
763
|
-
const retagged = retagAuxiliaryDiagnostics(diagnostics, rawDiags, content ?? "", { cwd, fileRole: detectFileRole(file, content) });
|
|
764
|
-
const reconciled = reconcileScanDiagnostics(file, retagged, true, writeIndex, observedAt);
|
|
765
|
-
if (!reconciled)
|
|
766
|
-
return { confirmed: false, blocking: true };
|
|
767
|
-
// #1561: the blocking tally comes from the SAME `isBlocking` predicate the
|
|
768
|
-
// footer counts with — no second severity rule for the blocker-retire
|
|
769
|
-
// decision to drift away from.
|
|
770
|
-
return { confirmed: true, blocking: retagged.some(isBlocking) };
|
|
771
|
-
}
|
|
772
|
-
catch {
|
|
773
|
-
// Never let a footer-reconciliation hiccup fail the diagnostics check.
|
|
774
|
-
// A verdict we could not compute is NOT evidence of clean: report it as
|
|
775
|
-
// still-blocking so no caller retires a blocker off a failed reconcile.
|
|
776
|
-
return { confirmed: false, blocking: true };
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
async function collectFileDiagnosticResult(file, severity, lspService, waitMs, nextWriteIndex, serverScope = "all",
|
|
780
|
-
// #671: shared workspace-diagnostics cache (see `createWorkspaceDiagnostics
|
|
781
|
-
// CacheContext`'s doc) — optional so single-file callers of this function
|
|
782
|
-
// (there are none today, but keep it non-breaking) can omit it and simply
|
|
783
|
-
// always touch. Only `collectBatchDiagnostics` (the batch/directory sweep)
|
|
784
|
-
// passes these.
|
|
785
|
-
cacheCtx, scopeKey,
|
|
786
|
-
// #692: threaded through so `reconcileWidgetFromLspResult` can compute
|
|
787
|
-
// `allowBlocking(cwd)` for a scan-reconciled aux finding — defaults to
|
|
788
|
-
// `process.cwd()` for any call site that predates this (there are none
|
|
789
|
-
// today besides the two below, both of which now pass it explicitly).
|
|
790
|
-
cwd = process.cwd(),
|
|
791
|
-
// #1561: see `createLspDiagnosticsTool`'s parameter doc. Called only for a
|
|
792
|
-
// FRESH observation — never on the cache-hit replay below, whose `rawDiags`
|
|
793
|
-
// are an OLD observation (#1093) and therefore no evidence about now.
|
|
794
|
-
onConfirmedNoBlockers) {
|
|
795
|
-
// Reserve the ordering token when this diagnostic operation starts, not when
|
|
796
|
-
// its LSP promise settles. A slower old result must not receive a newer token
|
|
797
|
-
// merely because it completed later (#1198).
|
|
798
|
-
const writeIndex = nextWriteIndex?.();
|
|
799
|
-
let stat;
|
|
800
|
-
try {
|
|
801
|
-
stat = fs.statSync(file);
|
|
802
|
-
if (!stat.isFile()) {
|
|
803
|
-
return { file, diagnostics: [], error: `${file}: not a file` };
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
catch {
|
|
807
|
-
return { file, diagnostics: [], error: `${file}: path not found` };
|
|
808
|
-
}
|
|
809
|
-
if (cacheCtx && scopeKey !== undefined) {
|
|
810
|
-
const cached = cacheCtx.lookup(file, scopeKey);
|
|
811
|
-
// #1095: a cached entry whose content binding demonstrably mismatches disk
|
|
812
|
-
// (bytes changed without the mtime bump the freshness gate would catch) is
|
|
813
|
-
// NOT served — fall through to a fresh touch instead of replaying a stale
|
|
814
|
-
// cached result. "unknown"/true bindings serve as before.
|
|
815
|
-
if (cached && cached.binding.boundToCurrentDisk !== false) {
|
|
816
|
-
const confirmation = cached.diagnostics.length === 0 ? "clean" : undefined;
|
|
817
|
-
// #1645 review F3: a cache REPLAY writes the widget store exactly like a
|
|
818
|
-
// fresh touch does, so it gets the same demotion. Without this, replaying
|
|
819
|
-
// yesterday's cached blocker would re-cement an orphan file as blocking
|
|
820
|
-
// after mode=full had demoted it.
|
|
821
|
-
const demotedCached = await demoteInferredForFile(file, cached.diagnostics, cwd, lspService);
|
|
822
|
-
// #3088 AC5: a replay is served to the agent exactly like a fresh
|
|
823
|
-
// observation, so it passes the same filter. The cached set is already
|
|
824
|
-
// inline-suppression-clean (the record below stores `inlineKept`), so
|
|
825
|
-
// only the revocable store/config filters run here.
|
|
826
|
-
const cachedPolicy = applyProbeFindingPolicy(file, demotedCached, cwd, replayContentForStrictMarks(file, cwd), severity);
|
|
827
|
-
const cachedDiags = cachedPolicy.kept;
|
|
828
|
-
const filteredDiags = applySeverityFilter(cachedDiags, severity);
|
|
829
|
-
// #692: cached.diagnostics were already suppression-/skipTestFiles-
|
|
830
|
-
// filtered at write time (this same code path); no file content was
|
|
831
|
-
// cached alongside them, so `undefined` here is a safe re-check, not
|
|
832
|
-
// a gap.
|
|
833
|
-
// #1093: this is a CACHE HIT — a replay of an OLD observation. Stamp the
|
|
834
|
-
// footer's `touchedAt` with the cache entry's original `scannedAt`, NOT
|
|
835
|
-
// now(), or a repeat check that only re-serves the cache would keep
|
|
836
|
-
// re-arming the mtime-staleness gate and a resolved finding would render
|
|
837
|
-
// forever (the #1092 defect).
|
|
838
|
-
reconcileWidgetFromLspResult(file, cachedDiags, confirmation, writeIndex, cwd, undefined,
|
|
839
|
-
// This branch only runs when the cache binding is not a mismatch
|
|
840
|
-
// (guarded above), so boundMismatch is false; #1093's observedAt is
|
|
841
|
-
// the cache entry's original scan time.
|
|
842
|
-
false, cached.scannedAt);
|
|
843
|
-
return {
|
|
844
|
-
file,
|
|
845
|
-
diagnostics: diagnosticsToFileDiags(file, filteredDiags),
|
|
846
|
-
confirmation,
|
|
847
|
-
primaryServerId: primaryServerId(file),
|
|
848
|
-
...(cachedPolicy.suppressed > 0 && {
|
|
849
|
-
dispositionSuppressed: cachedPolicy.suppressed,
|
|
850
|
-
}),
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
const { diagnostics: rawDiags, timedOut, confirmedByTouch, unconfirmedServerIds, diagnosticsUnsupportedServerIds, content: collectedContent, binding, tooLargeReason, skipReason, } = await collectDiagnosticsForFile(file, lspService, cwd, waitMs, serverScope);
|
|
855
|
-
if (tooLargeReason !== undefined) {
|
|
856
|
-
return { file, diagnostics: [], outcome: "too_large", tooLargeReason };
|
|
857
|
-
}
|
|
858
|
-
const health = lspService.getDiagnosticsHealth?.(file);
|
|
859
|
-
// #570: a timed-out priming check is never a confirmed "clean" — treat it
|
|
860
|
-
// as unconfirmed without consulting the (unrelated) silent-tier
|
|
861
|
-
// classifier, and remember why so the rendered text is accurate.
|
|
862
|
-
// #611: a genuinely empty (not just severity-filtered-away) push-based
|
|
863
|
-
// result gets a shot at the tier-3 sync escape hatch before "unconfirmed"
|
|
864
|
-
// — it may surface real diagnostics the server never published, which must
|
|
865
|
-
// be merged in rather than discarded.
|
|
866
|
-
let effectiveRawDiags = rawDiags;
|
|
867
|
-
let confirmation;
|
|
868
|
-
if (diagnosticsUnsupportedServerIds.length > 0) {
|
|
869
|
-
// No diagnostic provider and no push evidence is a capability boundary,
|
|
870
|
-
// not a clean result and not a timeout.
|
|
871
|
-
confirmation = undefined;
|
|
872
|
-
}
|
|
873
|
-
else if (skipReason !== undefined) {
|
|
874
|
-
confirmation = "unconfirmed";
|
|
875
|
-
}
|
|
876
|
-
else if (timedOut) {
|
|
877
|
-
if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
878
|
-
confirmation = "unconfirmed";
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
else if (canTrustTouchConfirmation(file, serverScope, confirmedByTouch)) {
|
|
882
|
-
if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
883
|
-
confirmation = "clean";
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
else if (rawDiags.length === 0) {
|
|
887
|
-
const resolved = await resolveEmptyResult(file, lspService);
|
|
888
|
-
effectiveRawDiags = resolved.diagnostics;
|
|
889
|
-
confirmation = resolved.confirmed
|
|
890
|
-
? resolved.diagnostics.length === 0
|
|
891
|
-
? "clean"
|
|
892
|
-
: undefined
|
|
893
|
-
: "unconfirmed";
|
|
894
|
-
}
|
|
895
|
-
else if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
896
|
-
confirmation = await classifyEmptyResult(file, lspService);
|
|
897
|
-
}
|
|
898
|
-
// #1095: a result whose content binding demonstrably mismatches disk is
|
|
899
|
-
// demoted to "unconfirmed" — it must neither confirm the footer nor be cached
|
|
900
|
-
// as clean, regardless of how many diagnostics it carries (the #1092
|
|
901
|
-
// re-cementing path). "unknown"/true bindings leave the verdict untouched.
|
|
902
|
-
const boundMismatch = binding?.boundToCurrentDisk === false;
|
|
903
|
-
if (boundMismatch)
|
|
904
|
-
confirmation = "unconfirmed";
|
|
905
|
-
// #1470/#1493: an auxiliary that never reported — cut off by the aux grace timer,
|
|
906
|
-
// or silent with nothing published for this content — contributed no evidence
|
|
907
|
-
// about this file, so a "clean" verdict computed from the merged result would
|
|
908
|
-
// be claiming coverage this batch does not have. Demote it — that also keeps
|
|
909
|
-
// the entry out of the workspace cache below, which would otherwise replay the
|
|
910
|
-
// partial answer as a confirmed clean on every later sweep.
|
|
911
|
-
if (unconfirmedServerIds.length > 0 && confirmation === "clean") {
|
|
912
|
-
confirmation = "unconfirmed";
|
|
913
|
-
}
|
|
914
|
-
// #1645 review F3: one demotion rule for every writer of the widget store.
|
|
915
|
-
effectiveRawDiags = await demoteInferredForFile(file, effectiveRawDiags, cwd, lspService);
|
|
916
|
-
// #3088: one filter for the output, the counts and the footer reconcile.
|
|
917
|
-
const policy = applyProbeFindingPolicy(file, effectiveRawDiags, cwd, collectedContent, severity);
|
|
918
|
-
effectiveRawDiags = policy.kept;
|
|
919
|
-
const filteredDiags = applySeverityFilter(effectiveRawDiags, severity);
|
|
920
|
-
const verdict = reconcileWidgetFromLspResult(file, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
|
|
921
|
-
if (verdict.confirmed && !verdict.blocking) {
|
|
922
|
-
onConfirmedNoBlockers?.({
|
|
923
|
-
filePath: file,
|
|
924
|
-
writeIndex,
|
|
925
|
-
coveredSources: coveredSourcesForCheck(file, serverScope, unconfirmedServerIds),
|
|
926
|
-
cwd,
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
// #671: only a CONFIRMED outcome ("clean", or a non-empty result — either
|
|
930
|
-
// is definitionally confirmed per this function's own doctrine above) is
|
|
931
|
-
// safe to cache; "unconfirmed" (timeout OR a silent-tier server's
|
|
932
|
-
// unescapable empty push) must never be persisted as a cacheable clean
|
|
933
|
-
// result — same false-clean bug class `runWorkspaceDiagnostics`'s cache
|
|
934
|
-
// wiring guards against. #1095: the entry carries the content fingerprint so a
|
|
935
|
-
// later lookup can verify it against disk beyond the mtime proxy.
|
|
936
|
-
if (cacheCtx &&
|
|
937
|
-
scopeKey !== undefined &&
|
|
938
|
-
confirmation !== "unconfirmed" &&
|
|
939
|
-
diagnosticsUnsupportedServerIds.length === 0 &&
|
|
940
|
-
unconfirmedServerIds.length === 0) {
|
|
941
|
-
// #3088: the entry records the INLINE-suppressed set, never the
|
|
942
|
-
// disposition/rule-policy-filtered one. An inline `pi-lens-ignore` comment
|
|
943
|
-
// cannot change without invalidating this content-keyed entry, so baking it
|
|
944
|
-
// in is safe and makes every later replay consistent with a fresh probe; a
|
|
945
|
-
// disposition mark or a `.pi-lens.json` rule can be revoked at any moment,
|
|
946
|
-
// so caching THEIR effect would keep a finding hidden after its mark was
|
|
947
|
-
// removed. The replay branch above re-applies those two on every serve.
|
|
948
|
-
cacheCtx.record(file, scopeKey, policy.inlineKept, stat.mtimeMs, collectedContent !== undefined
|
|
949
|
-
? hashDiagnosticContent(collectedContent)
|
|
950
|
-
: undefined, stat.size);
|
|
951
|
-
}
|
|
952
|
-
return {
|
|
953
|
-
file,
|
|
954
|
-
diagnostics: diagnosticsToFileDiags(file, filteredDiags),
|
|
955
|
-
unavailable: lspUnavailableMessage(file, health),
|
|
956
|
-
confirmation,
|
|
957
|
-
timedOut: confirmation === "unconfirmed" ? timedOut : undefined,
|
|
958
|
-
...(skipReason !== undefined && { skipReason }),
|
|
959
|
-
primaryServerId: primaryServerId(file),
|
|
960
|
-
diagnosticsUnsupported: diagnosticsUnsupportedServerIds.length > 0,
|
|
961
|
-
...(policy.suppressed > 0 && {
|
|
962
|
-
dispositionSuppressed: policy.suppressed,
|
|
963
|
-
}),
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWriteIndex, serverScope = "all", cwd = process.cwd(),
|
|
967
|
-
// #1561: see `createLspDiagnosticsTool`'s parameter doc. This is the path the
|
|
968
|
-
// live incident took (`mode: "file"`).
|
|
969
|
-
onConfirmedNoBlockers) {
|
|
970
|
-
// Reserve the token before awaiting this file's LSP result. The direct-file
|
|
971
|
-
// path performs its own confirmation/reconciliation below (#1198).
|
|
972
|
-
const writeIndex = nextWriteIndex?.();
|
|
973
|
-
const { diagnostics: rawDiags, timedOut, confirmedByTouch, unconfirmedServerIds, diagnosticsUnsupportedServerIds, content: collectedContent, binding, tooLargeReason, skipReason, } = await collectDiagnosticsForFile(absPath, lspService, cwd, waitMs, serverScope);
|
|
974
|
-
if (tooLargeReason !== undefined) {
|
|
975
|
-
return {
|
|
976
|
-
content: [{ type: "text", text: tooLargeReason }],
|
|
977
|
-
details: {
|
|
978
|
-
filePath: absPath,
|
|
979
|
-
mode: "file",
|
|
980
|
-
severity,
|
|
981
|
-
serverScope,
|
|
982
|
-
outcome: "too_large",
|
|
983
|
-
tooLargeReason,
|
|
984
|
-
diagnostics: [],
|
|
985
|
-
totalDiagnostics: 0,
|
|
986
|
-
},
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
const lspHealth = lspService.getDiagnosticsHealth?.(absPath);
|
|
990
|
-
const unavailable = lspUnavailableMessage(absPath, lspHealth);
|
|
991
|
-
// #533: an empty result needs a confirmed/unconfirmed verdict — a push-only,
|
|
992
|
-
// silent-on-clean server (classic typescript) publishes nothing on a
|
|
993
|
-
// clean→clean edit, so "0 diagnostics" from it is unverifiable, not clean.
|
|
994
|
-
// #570: a timed-out priming check is a second, distinct reason a result
|
|
995
|
-
// can be unconfirmed — checked first since it's a property of THIS check,
|
|
996
|
-
// not a general server-capability classification.
|
|
997
|
-
// #611: a genuinely empty (not just severity-filtered-away) result gets a
|
|
998
|
-
// shot at the tier-3 sync escape hatch before "unconfirmed" — real
|
|
999
|
-
// diagnostics it surfaces are merged in, not discarded.
|
|
1000
|
-
let effectiveRawDiags = rawDiags;
|
|
1001
|
-
let confirmation;
|
|
1002
|
-
if (diagnosticsUnsupportedServerIds.length > 0) {
|
|
1003
|
-
confirmation = undefined;
|
|
1004
|
-
}
|
|
1005
|
-
else if (skipReason !== undefined) {
|
|
1006
|
-
confirmation = "unconfirmed";
|
|
1007
|
-
}
|
|
1008
|
-
else if (timedOut) {
|
|
1009
|
-
if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
1010
|
-
confirmation = "unconfirmed";
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
else if (canTrustTouchConfirmation(absPath, serverScope, confirmedByTouch)) {
|
|
1014
|
-
if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
1015
|
-
confirmation = "clean";
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
else if (rawDiags.length === 0) {
|
|
1019
|
-
const resolved = await resolveEmptyResult(absPath, lspService);
|
|
1020
|
-
effectiveRawDiags = resolved.diagnostics;
|
|
1021
|
-
confirmation = resolved.confirmed
|
|
1022
|
-
? resolved.diagnostics.length === 0
|
|
1023
|
-
? "clean"
|
|
1024
|
-
: undefined
|
|
1025
|
-
: "unconfirmed";
|
|
1026
|
-
}
|
|
1027
|
-
else if (applySeverityFilter(rawDiags, severity).length === 0) {
|
|
1028
|
-
confirmation = await classifyEmptyResult(absPath, lspService);
|
|
1029
|
-
}
|
|
1030
|
-
// #1095: demote a result whose content binding mismatches disk to
|
|
1031
|
-
// "unconfirmed" (the #1092 re-cementing path) — a non-empty stale result is no
|
|
1032
|
-
// longer "definitionally confirmed". "unknown"/true bindings are unchanged.
|
|
1033
|
-
const boundMismatch = binding?.boundToCurrentDisk === false;
|
|
1034
|
-
if (boundMismatch)
|
|
1035
|
-
confirmation = "unconfirmed";
|
|
1036
|
-
// #1470/#1493: NARROWED, not collapsed. An auxiliary that never reported — the aux
|
|
1037
|
-
// grace timer cut it off, or it stayed silent with nothing published for this
|
|
1038
|
-
// content — makes the FILE-level verdict unconfirmed. The merged result is missing
|
|
1039
|
-
// whatever that scanner would have said, and this tool is the security lane's read surface. The
|
|
1040
|
-
// PRIMARY's own verdict is untouched (`primaryCoverageGapOnly` below), so a
|
|
1041
|
-
// TypeScript answer stays "confirmed clean" on its own line while an explicit
|
|
1042
|
-
// line names the scanner whose coverage is absent.
|
|
1043
|
-
const primaryCoverageGapOnly = unconfirmedServerIds.length > 0 && confirmation === "clean";
|
|
1044
|
-
if (primaryCoverageGapOnly)
|
|
1045
|
-
confirmation = "unconfirmed";
|
|
1046
|
-
// #1645 review F3: one demotion rule for every writer of the widget store.
|
|
1047
|
-
effectiveRawDiags = await demoteInferredForFile(absPath, effectiveRawDiags, cwd, lspService);
|
|
1048
|
-
// #3088: one filter for the output, the counts and the footer reconcile.
|
|
1049
|
-
const policy = applyProbeFindingPolicy(absPath, effectiveRawDiags, cwd, collectedContent, severity);
|
|
1050
|
-
effectiveRawDiags = policy.kept;
|
|
1051
|
-
const filtered = applySeverityFilter(effectiveRawDiags, severity);
|
|
1052
|
-
const total = filtered.length;
|
|
1053
|
-
const truncated = total > MAX_DIAGNOSTICS;
|
|
1054
|
-
const limited = truncated ? filtered.slice(0, MAX_DIAGNOSTICS) : filtered;
|
|
1055
|
-
const unconfirmed = confirmation === "unconfirmed";
|
|
1056
|
-
const verdict = reconcileWidgetFromLspResult(absPath, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
|
|
1057
|
-
// #1561: a confirmed current view with nothing at the blocking tier retires
|
|
1058
|
-
// this file's stale inline blocker — the store #571 corrected the footer for
|
|
1059
|
-
// but never reached, which is how a resolved cross-file finding kept being
|
|
1060
|
-
// injected as "Unresolved from this turn" for the rest of the session.
|
|
1061
|
-
if (verdict.confirmed && !verdict.blocking) {
|
|
1062
|
-
onConfirmedNoBlockers?.({
|
|
1063
|
-
filePath: absPath,
|
|
1064
|
-
writeIndex,
|
|
1065
|
-
coveredSources: coveredSourcesForCheck(absPath, serverScope, unconfirmedServerIds),
|
|
1066
|
-
cwd,
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
const primaryId = primaryServerId(absPath);
|
|
1070
|
-
const primaryDiags = limited.filter((d) => d.serverId === primaryId);
|
|
1071
|
-
const auxiliaryDiags = limited.filter((d) => d.serverId !== primaryId);
|
|
1072
|
-
// Primary confirmation is always its own line, independent of how many
|
|
1073
|
-
// auxiliary findings exist — a wall of ast-grep/opengrep noise must never
|
|
1074
|
-
// bury whether the actual language server confirmed the file clean.
|
|
1075
|
-
const primaryLine = (() => {
|
|
1076
|
-
if (diagnosticsUnsupportedServerIds.length > 0) {
|
|
1077
|
-
return `Primary LSP${primaryId ? ` (${primaryId})` : ""}: navigation-only — diagnostics unsupported (no pull provider or publish evidence).`;
|
|
1078
|
-
}
|
|
1079
|
-
if (timedOut) {
|
|
1080
|
-
return ("Primary LSP: check timed out — NOT the same as 0 diagnostics; the " +
|
|
1081
|
-
"file may still have errors that just hadn't been reported yet. " +
|
|
1082
|
-
"Re-check after the server settles, or increase waitMs.");
|
|
1083
|
-
}
|
|
1084
|
-
// #1470: a file demoted ONLY because an auxiliary was cut off must not render
|
|
1085
|
-
// the silent-on-clean text — the primary did confirm, and saying otherwise is
|
|
1086
|
-
// the same overclaim in the opposite direction. The coverage line below names
|
|
1087
|
-
// what is actually missing.
|
|
1088
|
-
if (unconfirmed && !primaryCoverageGapOnly) {
|
|
1089
|
-
return (`Primary LSP${primaryId ? ` (${primaryId})` : ""}: unconfirmed — ` +
|
|
1090
|
-
"cannot confirm clean (push-only, silent-on-clean, e.g. classic " +
|
|
1091
|
-
"typescript-language-server never publishes on a clean re-check). " +
|
|
1092
|
-
"NOT the same as 0 diagnostics; re-check after an edit, or use " +
|
|
1093
|
-
"waitMs to wait longer.");
|
|
1094
|
-
}
|
|
1095
|
-
if (primaryDiags.length === 0) {
|
|
1096
|
-
return `Primary LSP${primaryId ? ` (${primaryId})` : ""}: confirmed clean.`;
|
|
1097
|
-
}
|
|
1098
|
-
return `Primary LSP${primaryId ? ` (${primaryId})` : ""}: ${primaryDiags.length} diagnostic${primaryDiags.length === 1 ? "" : "s"}.`;
|
|
1099
|
-
})();
|
|
1100
|
-
// #1470: this is the narrowing made readable. It states exactly which servers
|
|
1101
|
-
// this result does NOT speak for, so "no auxiliary findings" can never be read
|
|
1102
|
-
// as "the security scanners found nothing".
|
|
1103
|
-
const coverageLine = unconfirmedServerIds.length > 0
|
|
1104
|
-
? `Auxiliary coverage INCOMPLETE — ${[...unconfirmedServerIds].join(", ")} did not answer within the wait budget, so ${unconfirmedServerIds.length === 1 ? "its findings are" : "their findings are"} NOT included here. This is not a clean bill of health for ${unconfirmedServerIds.length === 1 ? "that scanner" : "those scanners"}; re-check after the next edit. waitMs can extend an ordinary wait, but it cannot override a session-demoted scanner.`
|
|
1105
|
-
: undefined;
|
|
1106
|
-
// #3088: the #1616 suppressed-bucket rule on this lane — a policy drop is
|
|
1107
|
-
// stated as a count, never left to read as "nothing was wrong here"
|
|
1108
|
-
// (AGENTS.md shape 10). Bounded: one line per call, whatever the count.
|
|
1109
|
-
const suppressedLine = dispositionSuppressedLine(policy.suppressed);
|
|
1110
|
-
let text;
|
|
1111
|
-
if (total === 0) {
|
|
1112
|
-
text = [
|
|
1113
|
-
primaryLine,
|
|
1114
|
-
"",
|
|
1115
|
-
unavailable ?? "No auxiliary findings.",
|
|
1116
|
-
...(coverageLine ? [coverageLine] : []),
|
|
1117
|
-
...(suppressedLine ? [suppressedLine] : []),
|
|
1118
|
-
].join("\n");
|
|
1119
|
-
}
|
|
1120
|
-
else {
|
|
1121
|
-
const lines = [primaryLine, ""];
|
|
1122
|
-
if (primaryDiags.length > 0) {
|
|
1123
|
-
lines.push(...primaryDiags.map(formatDiag), "");
|
|
1124
|
-
}
|
|
1125
|
-
if (auxiliaryDiags.length > 0) {
|
|
1126
|
-
lines.push(`Auxiliary findings (${auxiliaryDiags.length}):`);
|
|
1127
|
-
lines.push(...auxiliaryDiags.map(formatDiag));
|
|
1128
|
-
}
|
|
1129
|
-
if (coverageLine)
|
|
1130
|
-
lines.push("", coverageLine);
|
|
1131
|
-
if (suppressedLine)
|
|
1132
|
-
lines.push("", suppressedLine);
|
|
1133
|
-
if (unavailable)
|
|
1134
|
-
lines.unshift(unavailable, "");
|
|
1135
|
-
if (truncated) {
|
|
1136
|
-
lines.unshift(`Found ${total} diagnostics (showing first ${MAX_DIAGNOSTICS}):`);
|
|
1137
|
-
}
|
|
1138
|
-
text = lines.join("\n");
|
|
1139
|
-
}
|
|
1140
|
-
return {
|
|
1141
|
-
content: [{ type: "text", text }],
|
|
1142
|
-
details: {
|
|
1143
|
-
filePath: absPath,
|
|
1144
|
-
mode: "file",
|
|
1145
|
-
severity,
|
|
1146
|
-
serverScope,
|
|
1147
|
-
...(unavailable ? { unavailable } : {}),
|
|
1148
|
-
primaryServerId: primaryId,
|
|
1149
|
-
primaryDiagnosticsCount: primaryDiags.length,
|
|
1150
|
-
auxiliaryDiagnosticsCount: auxiliaryDiags.length,
|
|
1151
|
-
diagnostics: limited.map((d) => ({
|
|
1152
|
-
line: d.range?.start?.line,
|
|
1153
|
-
character: d.range?.start?.character,
|
|
1154
|
-
severity: d.severity,
|
|
1155
|
-
message: d.message,
|
|
1156
|
-
source: d.source,
|
|
1157
|
-
code: d.code,
|
|
1158
|
-
})),
|
|
1159
|
-
totalDiagnostics: total,
|
|
1160
|
-
...(policy.suppressed > 0 && {
|
|
1161
|
-
dispositionSuppressed: policy.suppressed,
|
|
1162
|
-
}),
|
|
1163
|
-
truncated,
|
|
1164
|
-
unconfirmed,
|
|
1165
|
-
timedOut: unconfirmed ? timedOut : undefined,
|
|
1166
|
-
navigationOnlyFiles: diagnosticsUnsupportedServerIds.length > 0 ? 1 : undefined,
|
|
1167
|
-
...(skipReason !== undefined && { skipReason }),
|
|
1168
|
-
// #1470: which servers this result does NOT speak for. Absent when it
|
|
1169
|
-
// speaks for all of them.
|
|
1170
|
-
...(unconfirmedServerIds.length > 0 && {
|
|
1171
|
-
unconfirmedServerIds: [...unconfirmedServerIds],
|
|
1172
|
-
}),
|
|
1173
|
-
lspHealth,
|
|
1174
|
-
waitMs,
|
|
1175
|
-
},
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
/**
|
|
1179
|
-
* #533: tally the per-file discriminated outcome across a batch/directory
|
|
1180
|
-
* result set. `unconfirmed` files are those whose diagnostics collapsed to an
|
|
1181
|
-
* empty array from a push-only, silent-on-clean server (see
|
|
1182
|
-
* `classifyEmptyResult`) — they must never be folded into "clean" in the
|
|
1183
|
-
* aggregate render, or a majority-unconfirmed result reads as a false "0
|
|
1184
|
-
* diagnostics across N files".
|
|
1185
|
-
*/
|
|
1186
|
-
function tallyConfirmation(results) {
|
|
1187
|
-
let clean = 0;
|
|
1188
|
-
let unconfirmed = 0;
|
|
1189
|
-
let timedOut = 0;
|
|
1190
|
-
let navigationOnly = 0;
|
|
1191
|
-
let tooLarge = 0;
|
|
1192
|
-
for (const result of results) {
|
|
1193
|
-
if (result.tooLargeReason) {
|
|
1194
|
-
tooLarge += 1;
|
|
1195
|
-
continue;
|
|
1196
|
-
}
|
|
1197
|
-
if (result.diagnosticsUnsupported) {
|
|
1198
|
-
navigationOnly += 1;
|
|
1199
|
-
continue;
|
|
1200
|
-
}
|
|
1201
|
-
if (result.diagnostics.length > 0)
|
|
1202
|
-
continue;
|
|
1203
|
-
if (result.confirmation === "unconfirmed") {
|
|
1204
|
-
unconfirmed += 1;
|
|
1205
|
-
// #570: timed-out checks are a subset of "unconfirmed" — tallied
|
|
1206
|
-
// separately so the aggregate text can say WHY, not just THAT.
|
|
1207
|
-
if (result.timedOut)
|
|
1208
|
-
timedOut += 1;
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
clean += 1;
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
return { clean, unconfirmed, timedOut, navigationOnly, tooLarge };
|
|
1215
|
-
}
|
|
1216
|
-
function classifyBatchFileOutcome(result) {
|
|
1217
|
-
if (result.error)
|
|
1218
|
-
return "failed";
|
|
1219
|
-
if (result.tooLargeReason)
|
|
1220
|
-
return "too_large";
|
|
1221
|
-
if (result.diagnosticsUnsupported)
|
|
1222
|
-
return "unsupported";
|
|
1223
|
-
// A timed-out/unconfirmed answer may contain partial findings, but it cannot
|
|
1224
|
-
// honestly be called a complete findings result. Keep the raw findings for
|
|
1225
|
-
// investigation while making the aggregate incomplete.
|
|
1226
|
-
if (result.timedOut || result.confirmation === "unconfirmed") {
|
|
1227
|
-
return "inconclusive";
|
|
1228
|
-
}
|
|
1229
|
-
if (result.diagnostics.length > 0)
|
|
1230
|
-
return "findings";
|
|
1231
|
-
if (result.unavailable) {
|
|
1232
|
-
return result.primaryServerId ? "unavailable" : "unsupported";
|
|
1233
|
-
}
|
|
1234
|
-
if (!result.primaryServerId)
|
|
1235
|
-
return "unsupported";
|
|
1236
|
-
return "clean";
|
|
1237
|
-
}
|
|
1238
|
-
function inconclusiveBatchResult(file, reason) {
|
|
1239
|
-
return {
|
|
1240
|
-
file,
|
|
1241
|
-
diagnostics: [],
|
|
1242
|
-
confirmation: "unconfirmed",
|
|
1243
|
-
timedOut: true,
|
|
1244
|
-
outcome: "inconclusive",
|
|
1245
|
-
inconclusiveReason: reason,
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
/**
|
|
1249
|
-
* #570: build the explanatory clause for a batch/directory result that has
|
|
1250
|
-
* unconfirmed files, distinguishing timed-out checks from the pre-existing
|
|
1251
|
-
* #533 silent-on-clean-server bucket — both are "unconfirmed" for counting,
|
|
1252
|
-
* but the reason differs and misreporting a timeout as "server can't confirm
|
|
1253
|
-
* clean" would itself be misleading.
|
|
1254
|
-
*/
|
|
1255
|
-
function unconfirmedReasonClause(unconfirmed, timedOut) {
|
|
1256
|
-
const silent = unconfirmed - timedOut;
|
|
1257
|
-
if (timedOut > 0 && silent > 0) {
|
|
1258
|
-
return (`${timedOut} timed out (check didn't complete within budget) and ` +
|
|
1259
|
-
`${silent} from a server that cannot confirm clean (push-only, ` +
|
|
1260
|
-
"silent-on-clean).");
|
|
1261
|
-
}
|
|
1262
|
-
if (timedOut > 0) {
|
|
1263
|
-
return `${timedOut} timed out (check didn't complete within the wait budget).`;
|
|
1264
|
-
}
|
|
1265
|
-
return ("from a server that cannot confirm clean (push-only, silent-on-clean; " +
|
|
1266
|
-
"e.g. classic typescript-language-server does not publish on a clean " +
|
|
1267
|
-
"re-check).");
|
|
1268
|
-
}
|
|
1269
|
-
/**
|
|
1270
|
-
* Fan out `collectFileDiagnosticResult` across a file list at bounded
|
|
1271
|
-
* concurrency and reduce the results into the shape both batch-style callers
|
|
1272
|
-
* (`runBatchFileDiagnostics`/`runDirectoryDiagnostics`) render from —
|
|
1273
|
-
* previously duplicated identically between them (SonarCloud
|
|
1274
|
-
* `new_duplicated_lines_density` gate, surfaced when #571 added the
|
|
1275
|
-
* `nextWriteIndex` threading to both call sites). Purely mechanical
|
|
1276
|
-
* extraction: no behavior change, and does NOT touch the confirmed/
|
|
1277
|
-
* unconfirmed semantics `collectFileDiagnosticResult`/`tallyConfirmation`
|
|
1278
|
-
* already encode — those, and `lens_diagnostics` mode=full's separate,
|
|
1279
|
-
* deliberately different confirmation gate in `tools/lens-diagnostics.ts`,
|
|
1280
|
-
* are unrelated to this file's internal duplication and are left exactly
|
|
1281
|
-
* as they were.
|
|
1282
|
-
*/
|
|
1283
|
-
async function collectBatchDiagnostics(files, severity, lspService, options) {
|
|
1284
|
-
// #671: one cache context for this whole batch/directory sweep — loaded
|
|
1285
|
-
// once, written back once after every file has been processed (see the
|
|
1286
|
-
// `persist()` call below), rather than round-tripping the on-disk cache
|
|
1287
|
-
// file per-file. Shared store with `runWorkspaceDiagnostics` (`lens_
|
|
1288
|
-
// diagnostics mode=full`'s engine); `scopeKey` keeps the two tools'
|
|
1289
|
-
// differently-scoped touches (this tool never excludes any server, that
|
|
1290
|
-
// one excludes opengrep — see `buildScopeKey`'s doc) from cross-serving
|
|
1291
|
-
// entries that wouldn't actually match what each asked for.
|
|
1292
|
-
const resolvedCwd = options.cwd ?? process.cwd();
|
|
1293
|
-
const cacheCtx = createWorkspaceDiagnosticsCacheContext(resolvedCwd);
|
|
1294
|
-
const scopeKey = buildScopeKey(options.serverScope ?? "all");
|
|
1295
|
-
const resultsByIndex = new Map();
|
|
1296
|
-
let completed = 0;
|
|
1297
|
-
const pendingIndices = new Map();
|
|
1298
|
-
files.forEach((file, index) => {
|
|
1299
|
-
const queue = pendingIndices.get(file);
|
|
1300
|
-
if (queue)
|
|
1301
|
-
queue.push(index);
|
|
1302
|
-
else
|
|
1303
|
-
pendingIndices.set(file, [index]);
|
|
1304
|
-
});
|
|
1305
|
-
const groups = groupFilesByPrimaryServer(files);
|
|
1306
|
-
await runPerServerGroups(groups, options.concurrency, async (group) => {
|
|
1307
|
-
if (options.signal?.aborted)
|
|
1308
|
-
return;
|
|
1309
|
-
const first = group.files[0];
|
|
1310
|
-
if (first &&
|
|
1311
|
-
lspService &&
|
|
1312
|
-
!isWarmAttached() &&
|
|
1313
|
-
typeof lspService.ensureWarmForSweep === "function") {
|
|
1314
|
-
await lspService.ensureWarmForSweep(first, {
|
|
1315
|
-
signal: options.signal,
|
|
1316
|
-
});
|
|
1317
|
-
if (options.signal?.aborted)
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
await mapWithConcurrency(group.files, 1, async (file) => {
|
|
1321
|
-
// Keep duplicate explicit paths in their original result slots.
|
|
1322
|
-
const index = pendingIndices.get(file).shift();
|
|
1323
|
-
const work = collectFileDiagnosticResult(file, severity, lspService, options.waitMs, options.nextWriteIndex, options.serverScope, cacheCtx, scopeKey, resolvedCwd, options.onConfirmedNoBlockers);
|
|
1324
|
-
const bounded = withDeadline(work, {
|
|
1325
|
-
ms: batchFileDeadlineMs(),
|
|
1326
|
-
onTimeout: "undefined",
|
|
1327
|
-
onReject: "undefined",
|
|
1328
|
-
});
|
|
1329
|
-
const result = options.signal
|
|
1330
|
-
? await Promise.race([
|
|
1331
|
-
bounded,
|
|
1332
|
-
new Promise((resolve) => {
|
|
1333
|
-
if (options.signal?.aborted) {
|
|
1334
|
-
resolve(undefined);
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
options.signal?.addEventListener("abort", () => resolve(undefined), {
|
|
1338
|
-
once: true,
|
|
1339
|
-
});
|
|
1340
|
-
}),
|
|
1341
|
-
])
|
|
1342
|
-
: await bounded;
|
|
1343
|
-
resultsByIndex.set(index, result ??
|
|
1344
|
-
inconclusiveBatchResult(file, options.signal?.aborted
|
|
1345
|
-
? "Batch aborted before this file completed."
|
|
1346
|
-
: `File check exceeded ${batchFileDeadlineMs()}ms.`));
|
|
1347
|
-
completed += 1;
|
|
1348
|
-
options.onProgress?.(completed, files.length);
|
|
1349
|
-
}, options.signal);
|
|
1350
|
-
}, options.signal);
|
|
1351
|
-
// The retired local pool returned only slots whose mapper had run. Keep that
|
|
1352
|
-
// dense abort contract while restoring the original file order for callers.
|
|
1353
|
-
const results = [...resultsByIndex.entries()]
|
|
1354
|
-
.sort(([left], [right]) => left - right)
|
|
1355
|
-
.map(([, result]) => result);
|
|
1356
|
-
// Persist whatever was recorded, including a partial/aborted sweep's
|
|
1357
|
-
// already-completed files — same "don't throw away confirmed work"
|
|
1358
|
-
// posture as `runWorkspaceDiagnostics`.
|
|
1359
|
-
cacheCtx.persist();
|
|
1360
|
-
const fileErrors = results.flatMap((result) => result.error ? [result.error] : []);
|
|
1361
|
-
const lspHealthWarnings = results.flatMap((result) => result.unavailable ? [result.unavailable] : []);
|
|
1362
|
-
const allDiags = results.flatMap((result) => result.diagnostics);
|
|
1363
|
-
const total = allDiags.length;
|
|
1364
|
-
const truncated = total > MAX_DIAGNOSTICS;
|
|
1365
|
-
const display = truncated ? allDiags.slice(0, MAX_DIAGNOSTICS) : allDiags;
|
|
1366
|
-
const { clean, unconfirmed, timedOut } = tallyConfirmation(results);
|
|
1367
|
-
for (const result of results) {
|
|
1368
|
-
result.outcome = classifyBatchFileOutcome(result);
|
|
1369
|
-
}
|
|
1370
|
-
const outcomeCounts = Object.fromEntries([
|
|
1371
|
-
"clean",
|
|
1372
|
-
"findings",
|
|
1373
|
-
"unsupported",
|
|
1374
|
-
"unavailable",
|
|
1375
|
-
"failed",
|
|
1376
|
-
"too_large",
|
|
1377
|
-
"inconclusive",
|
|
1378
|
-
].map((outcome) => [
|
|
1379
|
-
outcome,
|
|
1380
|
-
results.filter((result) => result.outcome === outcome).length,
|
|
1381
|
-
]));
|
|
1382
|
-
// Per-file primary-server lookup so a flattened multi-file `display` list
|
|
1383
|
-
// can still be split into "primary findings" vs "auxiliary findings" —
|
|
1384
|
-
// `clean`/`unconfirmed` above already reflect ONLY the primary server's
|
|
1385
|
-
// confirmation; this split does the same job for the listed diagnostics.
|
|
1386
|
-
const primaryIdByFile = new Map(results.map((r) => [r.file, r.primaryServerId]));
|
|
1387
|
-
const primaryDisplay = display.filter((d) => d.serverId === primaryIdByFile.get(d.file));
|
|
1388
|
-
const auxiliaryDisplay = display.filter((d) => d.serverId !== primaryIdByFile.get(d.file));
|
|
1389
|
-
return {
|
|
1390
|
-
results,
|
|
1391
|
-
fileErrors,
|
|
1392
|
-
lspHealthWarnings,
|
|
1393
|
-
total,
|
|
1394
|
-
truncated,
|
|
1395
|
-
display,
|
|
1396
|
-
primaryDisplay,
|
|
1397
|
-
auxiliaryDisplay,
|
|
1398
|
-
clean,
|
|
1399
|
-
unconfirmed,
|
|
1400
|
-
timedOut,
|
|
1401
|
-
outcomeCounts,
|
|
1402
|
-
incompleteFiles: Math.max(0, files.length - results.length),
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
async function runBatchFileDiagnostics(absPaths, severity, lspService, options) {
|
|
1406
|
-
if (absPaths.length === 0) {
|
|
1407
|
-
return {
|
|
1408
|
-
content: [{ type: "text", text: "No file paths provided." }],
|
|
1409
|
-
isError: true,
|
|
1410
|
-
details: { mode: "batch", severity, filesChecked: 0 },
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
const { results, fileErrors, lspHealthWarnings, total, truncated, display, primaryDisplay, auxiliaryDisplay, clean, unconfirmed, timedOut, outcomeCounts, incompleteFiles, } = await collectBatchDiagnostics(absPaths, severity, lspService, options);
|
|
1414
|
-
const navigationOnly = results.filter((result) => result.diagnosticsUnsupported).length;
|
|
1415
|
-
// #3088: the batch's own suppressed bucket — one summed line, never one per
|
|
1416
|
-
// file or per finding.
|
|
1417
|
-
const dispositionSuppressed = results.reduce((sum, result) => sum + (result.dispositionSuppressed ?? 0), 0);
|
|
1418
|
-
const lines = [
|
|
1419
|
-
`Files checked: ${results.length}`,
|
|
1420
|
-
`Total diagnostics: ${total}`,
|
|
1421
|
-
`Concurrency: ${options.concurrency}`,
|
|
1422
|
-
];
|
|
1423
|
-
if (options.waitMs !== undefined)
|
|
1424
|
-
lines.push(`Wait budget: ${options.waitMs}ms`);
|
|
1425
|
-
lines.push(`Outcomes: ${Object.entries(outcomeCounts)
|
|
1426
|
-
.map(([outcome, count]) => `${outcome}=${count}`)
|
|
1427
|
-
.join(", ")}`);
|
|
1428
|
-
const notConfirmed = outcomeCounts.inconclusive +
|
|
1429
|
-
outcomeCounts.unavailable +
|
|
1430
|
-
outcomeCounts.unsupported +
|
|
1431
|
-
outcomeCounts.failed +
|
|
1432
|
-
outcomeCounts.too_large +
|
|
1433
|
-
incompleteFiles;
|
|
1434
|
-
if (notConfirmed > 0) {
|
|
1435
|
-
lines.push("", `Checks not confirmed: ${notConfirmed} (inconclusive/unavailable/unsupported/failed or not started).`);
|
|
1436
|
-
}
|
|
1437
|
-
if (incompleteFiles > 0) {
|
|
1438
|
-
lines.push("", `Batch incomplete: ${incompleteFiles} file${incompleteFiles === 1 ? "" : "s"} were not confirmed because the batch was aborted.`);
|
|
1439
|
-
}
|
|
1440
|
-
if (fileErrors.length > 0)
|
|
1441
|
-
lines.push("", "File errors:", ...fileErrors);
|
|
1442
|
-
const outcomeDetails = results.map((result) => ({
|
|
1443
|
-
file: result.file,
|
|
1444
|
-
outcome: result.outcome,
|
|
1445
|
-
reason: result.tooLargeReason ??
|
|
1446
|
-
result.inconclusiveReason ??
|
|
1447
|
-
result.error ??
|
|
1448
|
-
result.unavailable,
|
|
1449
|
-
}));
|
|
1450
|
-
const tooLargeLines = renderBatchOutcomeLines(outcomeDetails);
|
|
1451
|
-
if (tooLargeLines.length > 0) {
|
|
1452
|
-
lines.push("", "Files too large for LSP diagnostics:", ...tooLargeLines);
|
|
1453
|
-
}
|
|
1454
|
-
if (lspHealthWarnings.length > 0) {
|
|
1455
|
-
lines.push("", "LSP health warnings:", ...lspHealthWarnings.slice(0, 10));
|
|
1456
|
-
}
|
|
1457
|
-
// #533/#570: surface unconfirmed files regardless of whether OTHER files in
|
|
1458
|
-
// the batch found real diagnostics — a mixed found/unconfirmed result must
|
|
1459
|
-
// not let the unconfirmed files silently pass as clean just because the
|
|
1460
|
-
// batch as a whole isn't "0 diagnostics". This tally is primary-server-only
|
|
1461
|
-
// (see collectFileDiagnosticResult) — it's the batch-level equivalent of
|
|
1462
|
-
// the single-file "Primary LSP: ..." line, always reported on its own.
|
|
1463
|
-
if (unconfirmed > 0) {
|
|
1464
|
-
lines.push("", `${clean} file${clean === 1 ? "" : "s"} confirmed clean, ${unconfirmed} unconfirmed: ` +
|
|
1465
|
-
`${unconfirmedReasonClause(unconfirmed, timedOut)} NOT the same as 0 diagnostics.`);
|
|
1466
|
-
}
|
|
1467
|
-
if (display.length === 0) {
|
|
1468
|
-
if (unconfirmed === 0 && outcomeCounts.clean === results.length) {
|
|
1469
|
-
lines.push("", "No diagnostics found.");
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
else {
|
|
1473
|
-
if (primaryDisplay.length > 0) {
|
|
1474
|
-
lines.push("", `Primary findings (${primaryDisplay.length}):`);
|
|
1475
|
-
lines.push(...primaryDisplay.map(formatDisplayDiag));
|
|
1476
|
-
}
|
|
1477
|
-
if (auxiliaryDisplay.length > 0) {
|
|
1478
|
-
lines.push("", `Auxiliary findings (${auxiliaryDisplay.length}):`);
|
|
1479
|
-
lines.push(...auxiliaryDisplay.map(formatDisplayDiag));
|
|
1480
|
-
}
|
|
1481
|
-
if (truncated) {
|
|
1482
|
-
lines.push("", `... (${total - MAX_DIAGNOSTICS} more diagnostics not shown)`);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
const suppressedLine = dispositionSuppressedLine(dispositionSuppressed);
|
|
1486
|
-
if (suppressedLine)
|
|
1487
|
-
lines.push("", suppressedLine);
|
|
1488
|
-
return {
|
|
1489
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
1490
|
-
details: {
|
|
1491
|
-
mode: "batch",
|
|
1492
|
-
severity,
|
|
1493
|
-
serverScope: options.serverScope ?? "all",
|
|
1494
|
-
filesChecked: results.length,
|
|
1495
|
-
concurrency: options.concurrency,
|
|
1496
|
-
waitMs: options.waitMs,
|
|
1497
|
-
diagnostics: display,
|
|
1498
|
-
primaryDiagnosticsCount: primaryDisplay.length,
|
|
1499
|
-
auxiliaryDiagnosticsCount: auxiliaryDisplay.length,
|
|
1500
|
-
totalDiagnostics: total,
|
|
1501
|
-
...(dispositionSuppressed > 0 && { dispositionSuppressed }),
|
|
1502
|
-
truncated,
|
|
1503
|
-
cleanFiles: clean,
|
|
1504
|
-
unconfirmedFiles: unconfirmed,
|
|
1505
|
-
navigationOnlyFiles: navigationOnly > 0 ? navigationOnly : undefined,
|
|
1506
|
-
timedOutFiles: timedOut > 0 ? timedOut : undefined,
|
|
1507
|
-
outcomes: results.map((result) => ({
|
|
1508
|
-
file: result.file,
|
|
1509
|
-
outcome: result.outcome,
|
|
1510
|
-
reason: result.tooLargeReason ??
|
|
1511
|
-
result.inconclusiveReason ??
|
|
1512
|
-
result.error ??
|
|
1513
|
-
result.unavailable,
|
|
1514
|
-
primaryDiagnosticsCount: result.diagnostics.filter((diagnostic) => diagnostic.serverId === result.primaryServerId).length,
|
|
1515
|
-
auxiliaryDiagnosticsCount: result.diagnostics.filter((diagnostic) => diagnostic.serverId !== result.primaryServerId).length,
|
|
1516
|
-
})),
|
|
1517
|
-
outcomeCounts,
|
|
1518
|
-
incompleteFiles: incompleteFiles > 0 ? incompleteFiles : undefined,
|
|
1519
|
-
fileErrors: fileErrors.length > 0 ? fileErrors : undefined,
|
|
1520
|
-
lspHealthWarnings: lspHealthWarnings.length > 0 ? lspHealthWarnings : undefined,
|
|
1521
|
-
},
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
/**
|
|
1525
|
-
* The scan-language DECISION `runDirectoryDiagnostics` makes, pulled out as
|
|
1526
|
-
* its own function so `tests/tools/lsp-diagnostics-scan-family.test.ts` can
|
|
1527
|
-
* pin it against the golden `LANG_EXTENSIONS` table for every 1- and
|
|
1528
|
-
* 2-extension combination in that table's universe without spinning up one
|
|
1529
|
-
* real directory (and the full LSP-mocked tool) per combination — `hasMatch`
|
|
1530
|
-
* is the only I/O-shaped seam, so the caller decides whether it is backed by
|
|
1531
|
-
* a real `collectFiles` walk (production) or an in-memory extension-set probe
|
|
1532
|
-
* (tests).
|
|
1533
|
-
*
|
|
1534
|
-
* Walks `SCAN_LANGUAGE_PRIORITY`'s FAMILIES in order and unions each member
|
|
1535
|
-
* id's {@link extensionsForLanguage} into ONE `hasMatch` call — the first
|
|
1536
|
-
* family with any match wins the whole directory for this pass. Grouping by
|
|
1537
|
-
* family (not trying each id alone) is what makes a directory mixing BOTH
|
|
1538
|
-
* extensions of a registry-split pair (`.ts`+`.tsx`, `.css`+`.scss`, ...)
|
|
1539
|
-
* behavior-preserving vs the pre-#2434 bundled-key table: a flat per-id loop
|
|
1540
|
-
* stopped at the first id with ANY match, silently losing the sibling half of
|
|
1541
|
-
* a split pair present in the same directory (#2458 fix-round F1).
|
|
1542
|
-
*/
|
|
1543
|
-
export async function resolveDirectoryScanExtensions(hasMatch) {
|
|
1544
|
-
for (const family of SCAN_LANGUAGE_PRIORITY) {
|
|
1545
|
-
const exts = family.flatMap((languageId) => extensionsForLanguage(languageId));
|
|
1546
|
-
if (await hasMatch(exts)) {
|
|
1547
|
-
return exts;
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
return undefined;
|
|
1551
|
-
}
|
|
1552
|
-
async function runDirectoryDiagnostics(absPath, severity, lspService, options) {
|
|
1553
|
-
let collectedFiles = [];
|
|
1554
|
-
const isIgnored = projectIgnorePredicate(absPath);
|
|
1555
|
-
await resolveDirectoryScanExtensions(async (exts) => {
|
|
1556
|
-
collectedFiles = await collectFiles(absPath, [...exts], MAX_FILES + 1, isIgnored);
|
|
1557
|
-
return collectedFiles.length > 0;
|
|
1558
|
-
});
|
|
1559
|
-
if (collectedFiles.length === 0) {
|
|
1560
|
-
return {
|
|
1561
|
-
content: [
|
|
1562
|
-
{
|
|
1563
|
-
type: "text",
|
|
1564
|
-
text: `No supported source files found in: ${absPath}`,
|
|
1565
|
-
},
|
|
1566
|
-
],
|
|
1567
|
-
details: {
|
|
1568
|
-
filePath: absPath,
|
|
1569
|
-
mode: "directory",
|
|
1570
|
-
severity,
|
|
1571
|
-
filesScanned: 0,
|
|
1572
|
-
},
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
const wasCapped = collectedFiles.length > MAX_FILES;
|
|
1576
|
-
const filesToProcess = collectedFiles.slice(0, MAX_FILES);
|
|
1577
|
-
const { results, fileErrors, lspHealthWarnings, total, truncated, display, primaryDisplay, auxiliaryDisplay, clean, unconfirmed, timedOut, outcomeCounts, } = await collectBatchDiagnostics(filesToProcess, severity, lspService, options);
|
|
1578
|
-
const navigationOnly = results.filter((result) => result.diagnosticsUnsupported).length;
|
|
1579
|
-
// #3088: the directory scan's own suppressed bucket — one summed line.
|
|
1580
|
-
const dispositionSuppressed = results.reduce((sum, result) => sum + (result.dispositionSuppressed ?? 0), 0);
|
|
1581
|
-
const suppressedLine = dispositionSuppressedLine(dispositionSuppressed);
|
|
1582
|
-
const outcomeDetails = results.map((result) => ({
|
|
1583
|
-
file: result.file,
|
|
1584
|
-
outcome: result.outcome,
|
|
1585
|
-
reason: result.tooLargeReason ??
|
|
1586
|
-
result.inconclusiveReason ??
|
|
1587
|
-
result.error ??
|
|
1588
|
-
result.unavailable,
|
|
1589
|
-
}));
|
|
1590
|
-
const tooLargeLines = renderBatchOutcomeLines(outcomeDetails, (file) => path.relative(absPath, file));
|
|
1591
|
-
let text;
|
|
1592
|
-
if (total === 0) {
|
|
1593
|
-
// #533/#570: an unconfirmed-containing directory result must never
|
|
1594
|
-
// render as a bare "no diagnostics" — that reads as an affirmative
|
|
1595
|
-
// clean scan the server never actually gave for those files.
|
|
1596
|
-
const cleanLine = unconfirmed > 0
|
|
1597
|
-
? `${clean} clean · ${unconfirmed} unconfirmed: ` +
|
|
1598
|
-
`${unconfirmedReasonClause(unconfirmed, timedOut)} NOT the same as 0 diagnostics.`
|
|
1599
|
-
: "No diagnostics found.";
|
|
1600
|
-
text = [
|
|
1601
|
-
`Directory: ${absPath}`,
|
|
1602
|
-
`Files scanned: ${filesToProcess.length}${wasCapped ? ` (capped at ${MAX_FILES})` : ""}`,
|
|
1603
|
-
...(lspHealthWarnings.length > 0
|
|
1604
|
-
? [
|
|
1605
|
-
"LSP unavailable for one or more files:",
|
|
1606
|
-
...lspHealthWarnings.slice(0, 10),
|
|
1607
|
-
]
|
|
1608
|
-
: unconfirmed > 0 || outcomeCounts.clean === filesToProcess.length
|
|
1609
|
-
? [cleanLine]
|
|
1610
|
-
: []),
|
|
1611
|
-
...(tooLargeLines.length > 0
|
|
1612
|
-
? ["", "Files too large for LSP diagnostics:", ...tooLargeLines]
|
|
1613
|
-
: []),
|
|
1614
|
-
...(suppressedLine ? ["", suppressedLine] : []),
|
|
1615
|
-
].join("\n");
|
|
1616
|
-
}
|
|
1617
|
-
else {
|
|
1618
|
-
const lines = [
|
|
1619
|
-
`Directory: ${absPath}`,
|
|
1620
|
-
`Files scanned: ${filesToProcess.length}${wasCapped ? ` (capped at ${MAX_FILES})` : ""}`,
|
|
1621
|
-
`Files with errors: ${new Set(display.map((d) => d.file)).size}`,
|
|
1622
|
-
`Total diagnostics: ${total}`,
|
|
1623
|
-
...(lspHealthWarnings.length > 0
|
|
1624
|
-
? ["", "LSP health warnings:", ...lspHealthWarnings.slice(0, 10)]
|
|
1625
|
-
: []),
|
|
1626
|
-
// #533/#570: the remaining clean-looking files in a mixed scan may
|
|
1627
|
-
// still be unconfirmed — say so even though the directory as a
|
|
1628
|
-
// whole found diagnostics elsewhere.
|
|
1629
|
-
...(unconfirmed > 0
|
|
1630
|
-
? [
|
|
1631
|
-
"",
|
|
1632
|
-
`${clean} other file${clean === 1 ? "" : "s"} confirmed clean, ${unconfirmed} unconfirmed: ` +
|
|
1633
|
-
unconfirmedReasonClause(unconfirmed, timedOut),
|
|
1634
|
-
]
|
|
1635
|
-
: []),
|
|
1636
|
-
"",
|
|
1637
|
-
];
|
|
1638
|
-
const toRelative = (d) => ({
|
|
1639
|
-
...d,
|
|
1640
|
-
file: path.relative(absPath, d.file),
|
|
1641
|
-
});
|
|
1642
|
-
if (primaryDisplay.length > 0) {
|
|
1643
|
-
lines.push(`Primary findings (${primaryDisplay.length}):`);
|
|
1644
|
-
lines.push(...primaryDisplay.map(toRelative).map(formatDisplayDiag));
|
|
1645
|
-
lines.push("");
|
|
1646
|
-
}
|
|
1647
|
-
if (auxiliaryDisplay.length > 0) {
|
|
1648
|
-
lines.push(`Auxiliary findings (${auxiliaryDisplay.length}):`);
|
|
1649
|
-
lines.push(...auxiliaryDisplay.map(toRelative).map(formatDisplayDiag));
|
|
1650
|
-
}
|
|
1651
|
-
if (truncated) {
|
|
1652
|
-
lines.push("", `... (${total - MAX_DIAGNOSTICS} more diagnostics not shown)`);
|
|
1653
|
-
}
|
|
1654
|
-
if (suppressedLine)
|
|
1655
|
-
lines.push("", suppressedLine);
|
|
1656
|
-
if (tooLargeLines.length > 0) {
|
|
1657
|
-
lines.push("", "Files too large for LSP diagnostics:", ...tooLargeLines);
|
|
1658
|
-
}
|
|
1659
|
-
text = lines.join("\n");
|
|
1660
|
-
}
|
|
1661
|
-
return {
|
|
1662
|
-
content: [{ type: "text", text }],
|
|
1663
|
-
details: {
|
|
1664
|
-
filePath: absPath,
|
|
1665
|
-
mode: "directory",
|
|
1666
|
-
severity,
|
|
1667
|
-
serverScope: options.serverScope ?? "all",
|
|
1668
|
-
filesScanned: filesToProcess.length,
|
|
1669
|
-
capped: wasCapped,
|
|
1670
|
-
diagnostics: display.map((d) => ({
|
|
1671
|
-
file: path.relative(absPath, d.file),
|
|
1672
|
-
line: d.line,
|
|
1673
|
-
character: d.character,
|
|
1674
|
-
severity: d.severity,
|
|
1675
|
-
message: d.message,
|
|
1676
|
-
source: d.source,
|
|
1677
|
-
code: d.code,
|
|
1678
|
-
})),
|
|
1679
|
-
primaryDiagnosticsCount: primaryDisplay.length,
|
|
1680
|
-
auxiliaryDiagnosticsCount: auxiliaryDisplay.length,
|
|
1681
|
-
totalDiagnostics: total,
|
|
1682
|
-
...(dispositionSuppressed > 0 && { dispositionSuppressed }),
|
|
1683
|
-
truncated,
|
|
1684
|
-
cleanFiles: clean,
|
|
1685
|
-
unconfirmedFiles: unconfirmed,
|
|
1686
|
-
outcomes: outcomeDetails,
|
|
1687
|
-
outcomeCounts,
|
|
1688
|
-
navigationOnlyFiles: navigationOnly > 0 ? navigationOnly : undefined,
|
|
1689
|
-
timedOutFiles: timedOut > 0 ? timedOut : undefined,
|
|
1690
|
-
fileErrors: fileErrors.length > 0 ? fileErrors : undefined,
|
|
1691
|
-
lspHealthWarnings: lspHealthWarnings.length > 0 ? lspHealthWarnings : undefined,
|
|
1692
|
-
concurrency: options.concurrency,
|
|
1693
|
-
waitMs: options.waitMs,
|
|
1694
|
-
},
|
|
1695
|
-
};
|
|
1696
|
-
}
|
|
1697
|
-
// ── helpers ─────────────────────────────────────────────────────────────
|
|
1698
|
-
/**
|
|
1699
|
-
* #3088: the one rendering of the probe lane's policy drop, shared by the
|
|
1700
|
-
* single-file, batch and directory renders so the three cannot word the same
|
|
1701
|
-
* fact differently. Returns `undefined` when nothing was dropped — the #1616
|
|
1702
|
-
* rule asks for a count when there IS one, not a "0 suppressed" line on every
|
|
1703
|
-
* clean probe.
|
|
1704
|
-
*/
|
|
1705
|
-
function dispositionSuppressedLine(suppressed) {
|
|
1706
|
-
if (suppressed <= 0)
|
|
1707
|
-
return undefined;
|
|
1708
|
-
return (`suppressed by disposition: ${suppressed} finding(s) dropped from this ` +
|
|
1709
|
-
"result because they're marked false-positive/won't-fix, disabled by " +
|
|
1710
|
-
"`.pi-lens.json` rule policy, or suppressed by an inline `pi-lens-ignore` " +
|
|
1711
|
-
"comment. Not a clean verdict for those locations — they were found, " +
|
|
1712
|
-
"then intentionally hidden.");
|
|
1713
|
-
}
|
|
1714
|
-
function applySeverityFilter(diags, severity) {
|
|
1715
|
-
if (severity === "all")
|
|
1716
|
-
return diags;
|
|
1717
|
-
const maxLevel = {
|
|
1718
|
-
error: 1,
|
|
1719
|
-
warning: 2,
|
|
1720
|
-
information: 3,
|
|
1721
|
-
hint: 4,
|
|
1722
|
-
};
|
|
1723
|
-
const max = maxLevel[severity] ?? 0;
|
|
1724
|
-
if (max === 0)
|
|
1725
|
-
return diags;
|
|
1726
|
-
return diags.filter((d) => (d.severity ?? 3) <= max);
|
|
1727
|
-
}
|
|
1728
|
-
function formatDisplayDiag(d) {
|
|
1729
|
-
const sevName = SEVERITY_NAMES[d.severity] ?? "unknown";
|
|
1730
|
-
const loc = d.line !== undefined
|
|
1731
|
-
? `${d.file}:${d.line + 1}:${(d.character ?? 0) + 1}`
|
|
1732
|
-
: d.file;
|
|
1733
|
-
const src = d.source ? `[${d.source}]` : "";
|
|
1734
|
-
const code = d.code ? ` (${d.code})` : "";
|
|
1735
|
-
return `${loc}: ${sevName}${src}${code}: ${d.message}`;
|
|
1736
|
-
}
|
|
1737
|
-
function formatDiag(diag) {
|
|
1738
|
-
const loc = diag.range?.start?.line !== undefined
|
|
1739
|
-
? `L${diag.range.start.line + 1}:${(diag.range.start.character ?? 0) + 1}`
|
|
1740
|
-
: "";
|
|
1741
|
-
const src = diag.source ? `[${diag.source}]` : "";
|
|
1742
|
-
const code = diag.code ? ` (${diag.code})` : "";
|
|
1743
|
-
const sevName = SEVERITY_NAMES[diag.severity] ?? "unknown";
|
|
1744
|
-
return `${loc}: ${sevName}${src}${code}: ${diag.message}`;
|
|
1745
|
-
}
|