@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.
Files changed (769) hide show
  1. package/README.md +16 -12
  2. package/THIRD_PARTY_NOTICES.md +3 -3
  3. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
  4. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
  5. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
  6. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
  7. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
  8. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
  9. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/index.js +4 -0
  10. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +43 -3
  11. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +142 -59
  12. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
  13. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  14. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
  15. package/node_modules/@dietrichgebert/ponytail/README.md +52 -242
  16. package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +3 -3
  17. package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +4 -4
  18. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +70 -16
  19. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +8 -7
  20. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
  21. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
  22. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +46 -23
  23. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +39 -7
  24. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +20 -3
  25. package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +3 -3
  26. package/node_modules/@dietrichgebert/ponytail/package.json +3 -3
  27. package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +47 -17
  28. package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -7
  29. package/node_modules/@dietrichgebert/ponytail/skills/plugin.json +5 -0
  30. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
  31. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -27
  32. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
  33. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +21 -18
  34. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +12 -13
  35. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -52
  36. package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
  37. package/node_modules/@herbertgao/pi-agency/README.md +40 -0
  38. package/node_modules/@herbertgao/pi-agency/package.json +53 -0
  39. package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
  45. package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
  46. package/node_modules/@narumitw/pi-caffeinate/package.json +4 -4
  47. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  48. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  49. package/node_modules/@tifan/pi-recap/package.json +1 -1
  50. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  51. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  52. package/node_modules/pi-antigravity/README.md +23 -2
  53. package/node_modules/pi-antigravity/package.json +5 -3
  54. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  55. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  56. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  58. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  59. package/node_modules/pi-antigravity/src/index.ts +52 -137
  60. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  61. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  62. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  63. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  64. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  65. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  66. package/node_modules/pi-lens/CHANGELOG.md +1064 -0
  67. package/node_modules/pi-lens/README.md +115 -25
  68. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  69. package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
  70. package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
  71. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  72. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  73. package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
  74. package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
  75. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  76. package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
  77. package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
  78. package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
  79. package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
  80. package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
  81. package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
  82. package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
  83. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  84. package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
  85. package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
  86. package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
  87. package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
  88. package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
  89. package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
  90. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  91. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  92. package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
  93. package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
  94. package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
  95. package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
  96. package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
  97. package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
  98. package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
  99. package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
  100. package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
  101. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  102. package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
  103. package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
  104. package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
  105. package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
  106. package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
  107. package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
  108. package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
  109. package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
  110. package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
  111. package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
  112. package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
  113. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  114. package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
  115. package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
  116. package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
  117. package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
  118. package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
  119. package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
  120. package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
  121. package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
  122. package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
  123. package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
  124. package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
  125. package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
  126. package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
  127. package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
  128. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  129. package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
  130. package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
  131. package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
  132. package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
  133. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  134. package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
  135. package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
  136. package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
  137. package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
  138. package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
  139. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  140. package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
  141. package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
  142. package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
  143. package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
  144. package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
  145. package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
  146. package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
  147. package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
  148. package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
  149. package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
  150. package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
  151. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  152. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  153. package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
  154. package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
  155. package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
  156. package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
  157. package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
  158. package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
  159. package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
  160. package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
  161. package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
  162. package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
  163. package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
  164. package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
  165. package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
  166. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  167. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  168. package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
  169. package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
  170. package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
  171. package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
  172. package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
  173. package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
  174. package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
  175. package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
  176. package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
  177. package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
  178. package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
  179. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  180. package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
  181. package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
  182. package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
  183. package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
  184. package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
  185. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  186. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  187. package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
  188. package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
  189. package/node_modules/pi-lens/dist/index.js +16456 -8335
  190. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  191. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  192. package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
  193. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  194. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  195. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  196. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  197. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  198. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  199. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
  200. package/node_modules/pi-lens/docs/configuration.md +20 -1
  201. package/node_modules/pi-lens/docs/environment-variables.md +52 -2
  202. package/node_modules/pi-lens/docs/features.md +8 -4
  203. package/node_modules/pi-lens/docs/globalconfig.md +1 -0
  204. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  205. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
  206. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  207. package/node_modules/pi-lens/docs/mcp.md +21 -5
  208. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  209. package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
  210. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  211. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
  212. package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
  213. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  214. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
  215. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  216. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  217. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  218. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  219. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  220. package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
  221. package/node_modules/pi-lens/docs/settings.md +1 -0
  222. package/node_modules/pi-lens/docs/usage.md +5 -1
  223. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  224. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  225. package/node_modules/pi-lens/package.json +18 -6
  226. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  227. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  228. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  229. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  230. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  231. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  232. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  233. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  234. package/node_modules/pi-typesafe/README.md +1 -1
  235. package/node_modules/pi-typesafe/dist/backends.d.ts +15 -5
  236. package/node_modules/pi-typesafe/dist/backends.js +18 -0
  237. package/node_modules/pi-typesafe/dist/client.d.ts +4 -3
  238. package/node_modules/pi-typesafe/dist/client.js +22 -7
  239. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  240. package/node_modules/pi-typesafe/dist/index.js +1 -1
  241. package/node_modules/pi-typesafe/dist/usage.d.ts +14 -4
  242. package/node_modules/pi-typesafe/dist/usage.js +147 -42
  243. package/node_modules/pi-typesafe/package.json +1 -1
  244. package/node_modules/pi-web-access/CHANGELOG.md +48 -0
  245. package/node_modules/pi-web-access/README.md +57 -11
  246. package/node_modules/pi-web-access/brave-rate-limit.ts +8 -2
  247. package/node_modules/pi-web-access/curator-page.ts +5 -1
  248. package/node_modules/pi-web-access/curator-server.ts +2 -0
  249. package/node_modules/pi-web-access/declared-web-links.ts +2 -2
  250. package/node_modules/pi-web-access/degoog.ts +323 -0
  251. package/node_modules/pi-web-access/dist/index.js +3483 -2745
  252. package/node_modules/pi-web-access/dist/mcp-cli.js +27443 -0
  253. package/node_modules/pi-web-access/exa.ts +5 -1
  254. package/node_modules/pi-web-access/extract.ts +41 -3
  255. package/node_modules/pi-web-access/firecrawl.ts +35 -4
  256. package/node_modules/pi-web-access/gemini-search.ts +34 -11
  257. package/node_modules/pi-web-access/gemini-web.ts +14 -9
  258. package/node_modules/pi-web-access/index.ts +153 -1202
  259. package/node_modules/pi-web-access/keenable.ts +221 -0
  260. package/node_modules/pi-web-access/mcp-cli.ts +40 -0
  261. package/node_modules/pi-web-access/mcp-server.ts +81 -0
  262. package/node_modules/pi-web-access/mcp-tool-definitions.ts +117 -0
  263. package/node_modules/pi-web-access/openai-search.ts +34 -1
  264. package/node_modules/pi-web-access/package.json +23 -5
  265. package/node_modules/pi-web-access/perplexity.ts +64 -56
  266. package/node_modules/pi-web-access/tool-arguments.ts +20 -0
  267. package/node_modules/pi-web-access/video-extract.ts +1 -1
  268. package/node_modules/pi-web-access/web-tool-contract.ts +86 -0
  269. package/node_modules/pi-web-access/web-tool-core.ts +1502 -0
  270. package/node_modules/pi-web-access/youtube-extract.ts +2 -5
  271. package/package.json +17 -12
  272. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -329
  273. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -346
  274. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +0 -21
  275. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +0 -62
  276. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
  277. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +0 -115
  278. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +0 -27
  279. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +0 -27
  280. package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
  281. package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
  282. package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +0 -4
  283. package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +0 -4
  284. package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
  285. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
  286. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
  287. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
  288. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  289. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  290. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  291. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  292. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  293. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  294. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  295. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  296. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  297. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  298. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  299. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  300. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  301. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  302. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  303. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  304. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  305. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  306. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  307. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  308. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  309. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  310. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  311. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  312. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  313. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  314. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  315. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  316. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  317. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  318. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  319. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  320. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  321. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  322. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  323. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  324. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  325. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  326. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  327. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  328. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  329. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  330. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  331. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  332. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  333. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  334. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  335. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  336. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  337. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  338. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  339. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  340. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  341. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  342. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  343. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  344. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  345. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  346. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  347. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  348. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  349. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  350. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  351. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  352. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  353. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  354. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  355. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  356. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  357. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  358. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  359. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  360. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  361. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  362. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  363. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  364. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  365. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  366. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  367. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  368. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  369. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  370. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  371. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  372. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  373. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  374. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  375. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  376. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  377. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  378. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  379. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  380. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  381. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  382. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  383. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  384. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  385. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  386. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  387. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  388. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  389. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  390. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  391. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  392. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  393. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  394. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  395. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  396. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  397. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  398. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  399. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  400. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  401. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  402. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  403. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  404. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  405. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  406. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  407. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  408. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  409. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  410. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  411. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  412. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  413. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  414. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  415. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  416. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  417. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  418. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  419. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  420. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  421. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  422. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  423. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  424. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  425. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  426. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  427. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  428. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  429. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  430. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  431. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  432. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  433. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  434. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  435. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  436. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  437. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  438. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  439. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  440. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  441. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  442. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  443. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  444. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  445. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  446. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  447. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  448. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  449. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  450. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  451. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  452. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  453. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  454. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  455. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  456. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  457. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  458. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  459. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  460. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  461. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  462. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  463. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  464. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  465. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  466. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  467. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  468. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  469. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  470. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  471. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  472. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  473. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  474. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  475. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  476. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  477. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  478. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  479. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  480. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  481. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  482. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  483. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  484. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  485. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  486. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  487. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  488. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  489. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  490. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  491. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  492. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  493. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  494. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  495. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  496. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  497. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  498. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  499. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  500. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  501. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  502. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  503. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  504. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  505. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  506. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  507. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  508. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  509. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  510. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  511. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  512. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  513. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  514. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  515. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  516. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  517. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  518. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  519. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  520. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  521. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  522. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  523. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  524. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  525. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  526. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  527. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  528. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  529. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  530. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  531. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  532. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  533. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  534. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  535. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  536. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  537. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  538. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  539. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  540. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  541. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  542. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  543. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  544. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  545. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  546. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  547. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  548. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  549. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  550. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  551. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  552. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  553. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  554. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  555. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  556. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  557. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  558. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  559. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  560. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  561. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  562. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  563. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  564. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  565. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  566. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  567. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  568. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  569. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  570. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  571. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  572. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  573. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  574. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  575. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  576. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  577. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  578. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  579. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  580. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  581. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  582. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  583. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  584. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  585. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  586. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  587. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  588. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  589. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  590. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  591. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  592. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  593. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  594. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  595. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  596. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  597. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  598. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  599. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  600. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  601. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  602. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  603. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  604. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  605. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  606. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  607. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  608. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  609. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  610. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  611. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  612. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  613. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  614. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  615. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  616. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  617. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  618. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  619. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  620. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  621. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  622. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  623. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  624. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  625. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  626. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  627. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  628. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  629. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  630. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  631. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  632. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  633. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  634. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  635. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  636. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  637. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  638. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  639. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  640. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  641. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  642. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  643. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  644. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  645. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  646. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  647. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  648. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  649. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  650. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  651. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  652. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  653. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  654. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  655. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  656. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  657. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  658. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  659. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  660. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  661. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  662. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  663. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  664. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  665. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  666. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  667. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  668. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  669. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  670. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  671. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  672. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  673. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  674. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  675. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  676. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  677. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  678. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  679. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  680. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  681. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  682. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  683. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  684. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  685. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  686. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  687. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  688. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  689. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  690. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  691. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  692. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  693. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  694. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  695. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  696. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  697. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  698. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  699. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  700. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  701. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  702. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  703. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  704. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  705. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  706. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  707. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  708. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  709. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  710. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  711. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  712. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  713. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  714. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  715. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  716. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  717. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  718. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  719. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  720. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  721. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  722. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  723. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  724. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  725. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  726. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  727. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  728. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  729. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  730. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  731. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  732. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  733. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  734. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  735. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  736. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  737. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  738. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  739. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  740. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  741. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  742. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  743. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  744. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  745. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  746. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  747. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  748. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  749. package/node_modules/pi-lens/dist/i18n.js +0 -69
  750. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  751. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  752. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  753. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  754. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  755. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  756. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  757. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  758. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  759. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  760. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  761. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  762. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  763. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  764. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  765. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  766. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  767. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  768. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  769. 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
- }