@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,1701 +0,0 @@
1
- /**
2
- * Shared runner utilities for pi-lens dispatch system
3
- *
4
- * Extracted common patterns from multiple runners to reduce duplication:
5
- * - Venv-aware command finders
6
- * - Availability checkers with caching
7
- * - Config file finders
8
- */
9
- import * as fs from "node:fs";
10
- import * as path from "node:path";
11
- import { fileURLToPath } from "node:url";
12
- import { logSessionStart } from "../../../sessionstart-logger.js";
13
- import { incrementDegradationCount } from "../../../degradation-ledger.js";
14
- import { getGlobalPiLensDir } from "../../../file-utils.js";
15
- import { createGenerationSource, } from "../../../generation-guard.js";
16
- import { PathKeyedMap } from "../../../path-keyed-map.js";
17
- import { normalizeEphemeralMapKey, normalizeMapKey, } from "../../../path-utils.js";
18
- import { ensureTool, findManagedToolBinary, getInstallAttempt, getLastEnsureResolutionSource, getToolInstallStrategy, isSpawnableCommand, resetPathWalkMemo, } from "../../../installer/index.js";
19
- import { getServersForFileWithConfig, isServerDisabled, } from "../../../lsp/config.js";
20
- import { findGlobalBinary, findLocalBinAt, findLocalBinsUpwards, findLocalBinUpwards, VENDOR_BIN_DIRS, VENV_BIN_DIRS, } from "../../../package-manager.js";
21
- import { logLatency } from "../../../latency-logger.js";
22
- import { safeSpawnAsync } from "../../../safe-spawn.js";
23
- import { probeToolAsync } from "../../../tool-probe.js";
24
- import { compareOrdinal } from "../../../string-utils.js";
25
- import { getToolCommandSpec, shouldAutoInstallTool, } from "../../../tool-policy.js";
26
- import { isInSpawnTimeoutCooldown } from "../../../spawn-timeout-cooldown.js";
27
- import { resolveToolCwd } from "../../../tool-cwd.js";
28
- import { createAvailabilityProbeFlight } from "../../../availability-probe-flight.js";
29
- import { classifyProbeFailure, createAvailabilityLatch, describeInstallAttempt, describeProbeEvidence, isLatchingOutcome, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./availability-policy.js";
30
- export { createAvailabilityLatch, classifyProbeFailure, describeProbeEvidence, describeUnavailability, isTransientDecision, logAvailabilityDecision, startHostStallSampler, } from "./availability-policy.js";
31
- /**
32
- * True when the LSP runner will cover `ctx.filePath` via the given PRIMARY server
33
- * id. Used by CLI runners that duplicate a linter a warm LSP already wraps
34
- * (taplo↔`toml` LSP = `taplo lsp`; shellcheck↔`bash` LSP runs shellcheck
35
- * internally) so they SELF-SKIP and stop double-reporting the same findings (#233)
36
- * — the same dormant-when-LSP-covers pattern the ast-grep napi runner uses.
37
- *
38
- * Non-spawning and conservative: honors the `no-lsp` kill switch + per-server
39
- * disable/config, and only matches when this server is the SELECTED primary for
40
- * the file (first non-auxiliary candidate). The caller additionally gates on tool
41
- * availability, so coverage never regresses when the LSP is absent/disabled.
42
- */
43
- export function lspPrimaryCoversFile(ctx, serverId) {
44
- if (ctx.pi?.getFlag?.("no-lsp"))
45
- return false;
46
- if (isServerDisabled(serverId, ctx.filePath))
47
- return false;
48
- const primary = getServersForFileWithConfig(ctx.filePath).find((s) => s.role !== "auxiliary");
49
- return primary?.id === serverId;
50
- }
51
- const _realThisDir = (() => {
52
- if (typeof __dirname !== "undefined")
53
- return __dirname;
54
- return path.dirname(fileURLToPath(import.meta.url));
55
- })();
56
- let _thisDir = _realThisDir;
57
- /**
58
- * Test-only override for pi-lens's OWN install-tree root (#2544 round 4 F1).
59
- * `_thisDir` is fixed at module load from the running file's own location, so
60
- * a test cannot otherwise place it under a synthetic HOME to prove the
61
- * own-install walk's `ceiling: false` exemption without writing into this
62
- * process's REAL, shared `node_modules` tree.
63
- */
64
- export function _setThisDirForTests(dir) {
65
- _thisDir = dir ?? _realThisDir;
66
- }
67
- // Managed tools directory (~/.pi-lens/tools) — where ensureTool() installs binaries
68
- const _managedToolsDir = path.join(getGlobalPiLensDir(), "tools");
69
- /**
70
- * Managed-shim candidates for a Node CLI tool, in the installer's own
71
- * preference order (`getToolPath`'s managed-first branch: `.cmd`, then `.exe`,
72
- * then extensionless on Windows; extensionless elsewhere).
73
- *
74
- * The pi-lens dir is read per call, never memoized at module load, so tests that
75
- * point `getGlobalPiLensDir` at a temp home still see their own tree.
76
- */
77
- function managedNodeToolCandidates(tool) {
78
- const base = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin", tool);
79
- return process.platform === "win32"
80
- ? [`${base}.cmd`, `${base}.exe`, base]
81
- : [base];
82
- }
83
- /**
84
- * Verification verdicts for managed shims, keyed by path + mtime + size, so a
85
- * reinstall re-verifies and a session start re-arms.
86
- *
87
- * A plain `Map`, not a `PathKeyedMap`: every path keyed here is produced by
88
- * `path.join` from one of two producers — `managedNodeToolCandidates` and the
89
- * installer's `findManagedToolBinary` (#2140) — so the write and read forms are
90
- * the same string by construction and cannot diverge on case or separator.
91
- */
92
- const managedBinaryVerdicts = new Map();
93
- /**
94
- * Verifications in progress, keyed the same way. Concurrent first touches of
95
- * one shim share the single probe instead of each spawning their own — the
96
- * same in-flight-share shape `resolveInstallInFlightByCwd` uses for installs
97
- * (#1674 review F2).
98
- */
99
- const managedVerifyInFlight = new Map();
100
- /**
101
- * Budget for the managed-shim verification spawn.
102
- *
103
- * 5s, not the checker's default probe budget: a cold npm shim on a cold file
104
- * cache measured just over 2s in the #1674 review, so a tighter budget spends
105
- * the whole wait and still learns nothing. This runs once per shim per session
106
- * on the success path, and the installer's own verification keeps its more
107
- * generous 10s.
108
- */
109
- const MANAGED_VERIFY_TIMEOUT_MS = 5000;
110
- /** First cooldown after a verification that never got a fair run. */
111
- const MANAGED_VERIFY_COOLDOWN_MS = 60_000;
112
- /**
113
- * Ceiling for the cooldown ladder — a shim that keeps stalling is still
114
- * re-probed every 15 minutes.
115
- */
116
- const MANAGED_VERIFY_COOLDOWN_MAX_MS = 15 * 60_000;
117
- function managedVerifyCooldownMs(attempts) {
118
- return Math.min(MANAGED_VERIFY_COOLDOWN_MAX_MS, MANAGED_VERIFY_COOLDOWN_MS * 2 ** (attempts - 1));
119
- }
120
- async function runManagedVerification(candidate, stamp, priorAttempts, generation, verificationArgs = ["--version"]) {
121
- let transient = false;
122
- let ok;
123
- try {
124
- const { verifyToolBinary } = await import("../../../installer/index.js");
125
- ok = await verifyToolBinary(candidate, undefined, () => {
126
- transient = true;
127
- }, MANAGED_VERIFY_TIMEOUT_MS, verificationArgs);
128
- }
129
- catch {
130
- // The verifier itself could not run — installer-isolated unit tests mock
131
- // this module without it, and a throw here says nothing about the shim.
132
- // "Cannot verify" is not "broken": keep the on-disk answer.
133
- transient = true;
134
- ok = false;
135
- }
136
- // An unspawnable prober is never a durable verdict (#1569): a timeout or a
137
- // spawn-boundary failure says nothing about the shim, so it never demotes
138
- // the candidate. It IS remembered, under a cooldown, so the wait is paid
139
- // once per window rather than on every resolve (#1674 review F1).
140
- // A verification that straddles a session boundary answers ITS caller, but
141
- // its verdict belongs to the session that asked. Writing it into the fresh
142
- // session would hand the new session the old one's cooldown — the exact
143
- // re-arm this state clears at `session_start` (#1674 review F5). Same guard
144
- // the install seam's in-flight share uses.
145
- if (!ok && transient) {
146
- const attempts = priorAttempts + 1;
147
- generation.guardedWrite(stamp, () => {
148
- managedBinaryVerdicts.set(stamp, {
149
- verdict: "unverified",
150
- attempts,
151
- retryAtMs: Date.now() + managedVerifyCooldownMs(attempts),
152
- });
153
- });
154
- return "unverified";
155
- }
156
- generation.guardedWrite(stamp, () => {
157
- managedBinaryVerdicts.set(stamp, { verdict: ok ? "ok" : "broken" });
158
- });
159
- if (!ok) {
160
- // Once per shim per session, at the moment the verdict is reached — the
161
- // memo answers every later call, so this cannot become per-dispatch spam.
162
- logSessionStart(`dispatch availability: managed shim ${candidate} exists but does not run; falling through to PATH`);
163
- }
164
- return ok ? "ok" : "broken";
165
- }
166
- async function verifyManagedCandidate(candidate, verificationArgs = ["--version"]) {
167
- let stamp;
168
- try {
169
- const stat = fs.statSync(candidate);
170
- stamp = `${candidate}:${stat.mtimeMs}:${stat.size}`;
171
- }
172
- catch {
173
- return "absent";
174
- }
175
- const memo = managedBinaryVerdicts.get(stamp);
176
- let priorAttempts = 0;
177
- if (memo) {
178
- if (memo.verdict !== "unverified")
179
- return memo.verdict;
180
- if (Date.now() < memo.retryAtMs)
181
- return "unverified";
182
- priorAttempts = memo.attempts;
183
- }
184
- const existing = managedVerifyInFlight.get(stamp);
185
- if (existing)
186
- return existing;
187
- const generation = availabilityGeneration.capture();
188
- const probe = runManagedVerification(candidate, stamp, priorAttempts, generation, verificationArgs).finally(() => {
189
- // A settling old-session probe must not evict the live entry a new
190
- // session already started for the same shim (#1674 review F5).
191
- generation.guardedWrite(stamp, () => {
192
- if (managedVerifyInFlight.get(stamp) === probe) {
193
- managedVerifyInFlight.delete(stamp);
194
- }
195
- });
196
- });
197
- managedVerifyInFlight.set(stamp, probe);
198
- return probe;
199
- }
200
- /**
201
- * The managed shim for a Node CLI tool (`~/.pi-lens/tools/node_modules/.bin/<tool>`),
202
- * or null when no candidate on disk actually runs.
203
- *
204
- * Verification uses the installer's own `verifyToolBinary`, the same check its
205
- * managed-first branch runs before returning a managed path. A bare
206
- * `existsSync` let a broken shim shadow a working PATH binary — the on-disk
207
- * file wins the race and then fails every spawn behind it (#1657). knip and
208
- * jscpd each carried a line-for-line copy of this resolver; #1476 folds them
209
- * into one definition so the next managed tool inherits the fast path instead
210
- * of a fourth copy.
211
- *
212
- * A settled verdict is memoized per file identity for the session, so the fast
213
- * path answers without a spawn after the first call (#1467). A probe that
214
- * cannot run keeps the optimistic answer rather than turning a stall into a
215
- * "missing tool", and is memoized under a bounded cooldown ladder so the wait
216
- * is paid once per window instead of on every resolve — while a shim that
217
- * stalls on a cold cache still gets re-probed later (#1674 review F1).
218
- * Concurrent first touches share one probe (#1674 review F2).
219
- */
220
- export async function findManagedNodeToolBinary(tool, verificationArgs = ["--version"]) {
221
- for (const candidate of managedNodeToolCandidates(tool)) {
222
- const verdict = await verifyManagedCandidate(candidate, verificationArgs);
223
- if (verdict === "ok" || verdict === "unverified")
224
- return candidate;
225
- }
226
- return null;
227
- }
228
- /**
229
- * The release-managed binary for a tool (`~/.pi-lens/bin/<tool>`), or null when
230
- * the registry does not put this tool there / nothing on disk actually runs.
231
- *
232
- * `findManagedToolBinary` is the INSTALLER's own lookup — the one `getToolPath`
233
- * runs before PATH for every github/maven/archive-strategy tool, and the one
234
- * `getToolEnvironment` puts at the front of a spawn's PATH. Calling it here is
235
- * what makes probe and spawn resolve through a single definition instead of two
236
- * (#2140): before this, the probe fell straight to the bare command name, missed
237
- * PATH, and latched `missing` — while the install fallback a few hundred ms
238
- * later handed the caller the very binary sitting in that directory. Every
239
- * session, for every tool the user already had (7 unavailable/available pairs in
240
- * one 3h dogfood window).
241
- *
242
- * Verification is the same `verifyManagedCandidate` the npm-shim rung above
243
- * uses, for the same reason (#1657): `findManagedToolBinary` answers from a bare
244
- * `fs.access`, and an on-disk binary that cannot run must not shadow a working
245
- * PATH binary — it falls through instead.
246
- */
247
- async function findManagedReleaseBinary(tool, verificationArgs = ["--version"]) {
248
- const candidate = await findManagedToolBinary(tool);
249
- if (!candidate)
250
- return null;
251
- const verdict = await verifyManagedCandidate(candidate, verificationArgs);
252
- return verdict === "ok" || verdict === "unverified" ? candidate : null;
253
- }
254
- // =============================================================================
255
- // VENV-AWARE COMMAND FINDER
256
- // =============================================================================
257
- /**
258
- * Find a command in venv first, then fall back to global.
259
- * Checks common venv locations (.venv, venv) before trying global.
260
- *
261
- * The resolved path is returned verbatim: every spawn consumer runs with
262
- * `shell: false` (safe-spawn, #817), so wrapping it in quotes would make it
263
- * a literal filename that ENOENTs on every platform (#1508).
264
- *
265
- * The venv directory list is `VENV_BIN_DIRS` (`package-manager.ts`), shared
266
- * with `formatters.ts`'s `findInVenv` and `dead-code-client.ts`'s vulture
267
- * candidates — three independent spellings of the same four directories before
268
- * #2544 review F1.
269
- *
270
- * Resolution is a fixed lookup at `cwd` ONLY (`findLocalBinAt`, no ancestor
271
- * walk). #2544 review round 3 folded this onto the shared ANCESTOR walker,
272
- * on the reasoning that a monorepo package should find its repo-root venv —
273
- * but that was an unrequested behaviour change with no #2514 defect behind
274
- * it (#2514 is about the HOME ceiling, not about climbing at all), and the
275
- * inversion is real: `~/code/app` with no venv of its own would resolve
276
- * `~/code/.venv/bin/ruff` — an unrelated SIBLING project's interpreter —
277
- * ahead of PATH. Reverted in round 4 F2. The HOME ceiling is moot here: a
278
- * lookup at exactly `cwd` can never climb past it.
279
- */
280
- export function createVenvFinder(command, windowsExt = "", verificationArgs = ["--version"]) {
281
- return async (cwd) => {
282
- const venvBin = findLocalBinAt(command, cwd, {
283
- windowsExt,
284
- binDirs: VENV_BIN_DIRS,
285
- });
286
- if (venvBin)
287
- return venvBin;
288
- // Managed-dir install (~/.pi-lens/tools/node_modules/.bin/<command>) — the
289
- // same shim `ensureTool()` installs npm-strategy tools into. Checked BEFORE
290
- // the bare-name PATH fallback (#1638): without this, a tool that only
291
- // exists in the managed dir never resolves here, so `resolveAvailableOrInstall`
292
- // clears the checker's cache on every install recovery for nothing — the
293
- // very next probe misses PATH again, spawns a doomed `--version` process,
294
- // and only THEN falls through to `ensureTool`'s own cache. One managed-dir
295
- // check settles it without a spawn — after one verification per shim per
296
- // session, so a shim that cannot run falls through to PATH instead of
297
- // shadowing a working binary (#1657).
298
- const managed = await findManagedNodeToolBinary(command, verificationArgs);
299
- if (managed)
300
- return managed;
301
- // Release-managed install (`~/.pi-lens/bin/<command>`) — where `ensureTool`
302
- // puts every github/maven/archive-strategy tool. Same rung, same reason as
303
- // the npm shim above, for the other half of the managed families (#2140).
304
- const release = await findManagedReleaseBinary(command, verificationArgs);
305
- if (release)
306
- return release;
307
- // Fall back to global
308
- return command;
309
- };
310
- }
311
- /** Typed client-facing install seam for ordered/custom candidate probes. */
312
- export async function resolveManagedToolClient(options) {
313
- const probed = await options.probe();
314
- if (probed.outcome !== "missing")
315
- return probed;
316
- if (!shouldAutoInstallTool(options.toolId))
317
- return probed;
318
- const state = installStateFor(options.cwd, options.toolId);
319
- if (state.suppressed)
320
- return probed;
321
- const installed = await ensureTool(options.toolId);
322
- if (!installed) {
323
- noteInstallFailure(options.toolId, options.cwd);
324
- return probed;
325
- }
326
- const value = await options.acceptInstalled(installed);
327
- if (value === null) {
328
- noteInstallFailure(options.toolId, options.cwd);
329
- return { outcome: "non-installable" };
330
- }
331
- noteInstallSuccess(options.toolId, options.cwd);
332
- return { outcome: "success", value };
333
- }
334
- /**
335
- * Child environment for managed-installable tools. Keeping this beside the
336
- * probe/install seam makes managed npm shims visible to every standalone
337
- * client, rather than only to Knip (#1289).
338
- */
339
- export async function getManagedToolEnvironment(_toolId, cwd) {
340
- let env;
341
- try {
342
- const { getToolEnvironment } = await import("../../../installer/index.js");
343
- env = await getToolEnvironment();
344
- }
345
- catch {
346
- // Installer-isolated unit tests historically mock only ensureTool. The
347
- // ambient fallback preserves that isolation; production always exports it.
348
- env = { ...process.env };
349
- }
350
- if (!cwd)
351
- return env;
352
- const separator = process.platform === "win32" ? ";" : ":";
353
- const currentPath = env.PATH || env.Path || process.env.PATH || "";
354
- const localBin = path.join(cwd, "node_modules", ".bin");
355
- const augmentedPath = `${localBin}${separator}${currentPath}`;
356
- return {
357
- ...env,
358
- PATH: augmentedPath,
359
- ...(process.platform === "win32" ? { Path: augmentedPath } : {}),
360
- };
361
- }
362
- /** Read-only managed/PATH discovery for spawn-time resolution memos. */
363
- export async function discoverManagedTool(toolId) {
364
- return (await ensureTool(toolId, { allowInstall: false })) ?? null;
365
- }
366
- // This is session-scoped state, not a process-global tool/path cache. The cwd
367
- // key is normalized by PathKeyedMap and is cleared at session_start. A failed
368
- // install must not become an install attempt on every eligible file/turn.
369
- const installAttemptsByCwd = new PathKeyedMap(normalizeMapKey);
370
- const resolveInstallInFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
371
- // Which (cwd, toolId) pairs already got their ONE compensating `available`
372
- // row this session for a GENUINE correction (#1612 review F2).
373
- // `checker.reset()` clears the checker's own cache on every install recovery,
374
- // so the NEXT call re-probes PATH from scratch — and that probe keeps missing
375
- // for a managed-dir-only install (createVenvFinder never searches the managed
376
- // tools dir; tracked separately as its own bug). Without this memo, every one
377
- // of those repeat probe failures looks like a fresh correction and re-logs
378
- // "available", even though only the first call actually ran an install.
379
- //
380
- // A correction is genuine only when a latched `unavailable` row for the same
381
- // pair stood before it. `verifyOrInstallCommand` also emits through this seam
382
- // with NO prior latch behind it (biome-check and oxlint reach it with no
383
- // checker probe at all), and burning this memo on such a row silences the
384
- // LATER genuine latch-then-recover for the same pair — the #1606 defect back
385
- // through the #1612 seam (#1657). Those rows dedupe against
386
- // `uncorrectedEmissionsByCwd` instead, so repeats stay suppressed without
387
- // pre-empting the real correction.
388
- const correctedAvailabilityByCwd = new PathKeyedMap(normalizeMapKey);
389
- // Which (cwd, toolId) pairs already emitted an `available` row that corrected
390
- // nothing (no latched `unavailable` stood before it). Same one-row-per-session
391
- // bound as the memo above, kept in a SEPARATE scope so it cannot answer for a
392
- // correction that has not happened yet (#1657).
393
- const uncorrectedEmissionsByCwd = new PathKeyedMap(normalizeMapKey);
394
- // Which (cwd, tool) pairs currently stand latched `unavailable` in the durable
395
- // log. Recorded where such a row is actually written, so "was there anything
396
- // to correct" is read from the log's own history rather than assumed by the
397
- // caller. Session-scoped: `resetDispatchAvailabilityState` clears it, so a new
398
- // session re-arms instead of inheriting the last one's latches (#1657).
399
- const latchedUnavailableByCwd = new PathKeyedMap(normalizeMapKey);
400
- // Checkers are created by runner modules and may also be created dynamically.
401
- // Keep the session reset as a generation rather than retaining every checker
402
- // reset closure forever.
403
- //
404
- // #1754: the counter itself is now the shared `GenerationSource` primitive, so
405
- // the capture-before-await/check-after guards below are one implementation
406
- // instead of five hand-rolled ones, and a dropped straddling write is visible
407
- // in the degradation ledger instead of silent.
408
- const availabilityGeneration = createGenerationSource("dispatch-availability");
409
- export const getDispatchAvailabilityGeneration = () => availabilityGeneration.current();
410
- const checkerProbeFlights = createAvailabilityProbeFlight({ generation: getDispatchAvailabilityGeneration });
411
- export function recordAvailabilityProbeOverrun(tool, key, elapsedMs, budgetMs, failure) {
412
- if (budgetMs === undefined || elapsedMs <= budgetMs)
413
- return;
414
- if (failure === "timeout" && elapsedMs <= budgetMs * 2)
415
- return;
416
- incrementDegradationCount({
417
- kind: "availability-probe-overrun",
418
- subject: `${tool}:${key}`,
419
- reason: `probe took ${Math.round(elapsedMs)}ms; budget was ${Math.round(budgetMs)}ms`,
420
- });
421
- }
422
- function installStateFor(cwd, toolId) {
423
- let states = installAttemptsByCwd.get(cwd);
424
- if (!states) {
425
- states = new Map();
426
- installAttemptsByCwd.set(cwd, states);
427
- }
428
- let state = states.get(toolId);
429
- if (!state) {
430
- state = { attempts: 0, suppressed: false };
431
- states.set(toolId, state);
432
- }
433
- return state;
434
- }
435
- function noteInstallFailure(toolId, cwd) {
436
- const state = installStateFor(cwd, toolId);
437
- state.attempts += 1;
438
- state.suppressed = true;
439
- logSessionStart(`dispatch availability ${toolId}: install attempt ${state.attempts} failed; suppressing retries until the next session or a successful install`);
440
- }
441
- function noteInstallSuccess(toolId, cwd) {
442
- const states = installAttemptsByCwd.get(cwd);
443
- states?.delete(toolId);
444
- if (states?.size === 0)
445
- installAttemptsByCwd.delete(cwd);
446
- }
447
- function hasId(map, cwd, id) {
448
- return map.get(cwd)?.has(id) ?? false;
449
- }
450
- function addId(map, cwd, id) {
451
- let ids = map.get(cwd);
452
- if (!ids) {
453
- ids = new Set();
454
- map.set(cwd, ids);
455
- }
456
- ids.add(id);
457
- }
458
- function deleteIds(map, cwd, ids) {
459
- const existing = map.get(cwd);
460
- if (!existing)
461
- return;
462
- for (const id of ids)
463
- existing.delete(id);
464
- if (existing.size === 0)
465
- map.delete(cwd);
466
- }
467
- /**
468
- * True once a compensating `available` row has already fired for this
469
- * (cwd, toolId) pair this session; `noteAvailabilityCorrected` records it the
470
- * first time. #1612 review F2's "once per correction" — see
471
- * `correctedAvailabilityByCwd`'s comment for why repeats need suppressing.
472
- */
473
- function hasCorrectedAvailability(cwd, toolId) {
474
- return hasId(correctedAvailabilityByCwd, cwd, toolId);
475
- }
476
- function noteAvailabilityCorrected(cwd, toolId) {
477
- addId(correctedAvailabilityByCwd, cwd, toolId);
478
- }
479
- /**
480
- * Record that a latched `unavailable` row now stands for this pair. Called
481
- * from each seam that writes one (the availability checker and the shared
482
- * probe latch), so a later compensating row can tell a genuine correction
483
- * from a no-op emission (#1657).
484
- */
485
- function noteLatchedUnavailable(cwd, tool) {
486
- addId(latchedUnavailableByCwd, cwd, tool);
487
- }
488
- /**
489
- * Drop the latch record for a pair, because the row it described no longer
490
- * stands. Called on every `available` verdict as well as on the compensating
491
- * row itself, so a latch cannot outlive the state it describes and later back
492
- * a "correction" that corrects nothing (#1674 review F3).
493
- */
494
- function clearLatchedUnavailable(cwd, ...tools) {
495
- deleteIds(latchedUnavailableByCwd, cwd, tools);
496
- }
497
- /**
498
- * Every name a latched row for this pair could have been written under. A
499
- * checker logs its COMMAND (sometimes a resolved absolute path), the install
500
- * seam knows a toolId, and the two are not always the same string. One
501
- * derivation feeds BOTH the lookup and the clear, so the two can never
502
- * disagree about which key the latch lives under (#1674 review F3).
503
- */
504
- function latchKeysFor(toolId, command) {
505
- const keys = [toolId];
506
- if (command)
507
- keys.push(command, path.basename(command));
508
- return keys;
509
- }
510
- /**
511
- * True when a latched `unavailable` row stands for any of these keys. A false
512
- * positive only restores the pre-#1657 behavior (the row emits and burns the
513
- * correction memo); a false negative still emits the row, under the
514
- * uncorrected scope. Neither can swallow a genuine correction.
515
- */
516
- function hasLatchedUnavailable(cwd, tools) {
517
- return tools.some((tool) => hasId(latchedUnavailableByCwd, cwd, tool));
518
- }
519
- /**
520
- * Which installer family resolved a tool, for the compensating row's
521
- * `evidence.source` (#1612 review F1). Reads the registry's own
522
- * `installStrategy` rather than hand-mapping per toolId — the two cannot
523
- * drift apart because there is only one list.
524
- */
525
- function sourceTagForToolId(toolId) {
526
- switch (getToolInstallStrategy(toolId)) {
527
- case "npm":
528
- return "managed-dir";
529
- case "pip":
530
- return "pip-user";
531
- case "github":
532
- return "github-release";
533
- case "archive":
534
- return "archive-dist";
535
- case "maven":
536
- return "maven-jar";
537
- default:
538
- // "gem" has no CLI runner behind this seam yet, and an unknown toolId
539
- // means the registry lookup itself missed — either way, no tag beats
540
- // a guessed one.
541
- return undefined;
542
- }
543
- }
544
- /**
545
- * `binary`/`source` for a probe that resolved through one of pi-lens's OWN
546
- * managed installs rather than through PATH or a project venv (#2140). A reader
547
- * of latency.log could otherwise not tell the two apart, and the whole point of
548
- * the fix is that the managed directories now answer where PATH used to miss.
549
- *
550
- * BOTH managed rungs are asked, in the order `createVenvFinder` walks them
551
- * (#2140 review F1). The first version asked only about the release directory,
552
- * so every npm-shim hit — knip, jscpd, madge, pyright, biome, htmlhint,
553
- * stylelint — logged an evidence-free row indistinguishable from a PATH hit,
554
- * while the doc claimed the opposite. Each rung is asked of THE function that
555
- * produced its own paths (`managedNodeToolCandidates`, `findManagedToolBinary`)
556
- * and settled by string identity, never by a path-prefix predicate over a
557
- * directory: a second opinion about which paths are managed is exactly the
558
- * parallel-list drift `sourceTagForToolId` exists to avoid, and it would answer
559
- * differently for case or separator variants.
560
- *
561
- * `binary` present IS the managed hit; `source` names the family whenever the
562
- * command is a registry id (`sourceTagForToolId` reads the registry's own
563
- * `installStrategy`). A managed shim whose COMMAND is not itself a registry id
564
- * — `markdownlint-cli2`, installed under registry id `markdownlint` — carries
565
- * `binary` alone rather than a guessed family. `binary` is a BASENAME, never
566
- * the absolute path — same rule as every other evidence field (#1568 review).
567
- */
568
- async function describeManagedResolution(tool, resolved) {
569
- const managed = managedNodeToolCandidates(tool).includes(resolved) ||
570
- (await findManagedToolBinary(tool)) === resolved;
571
- if (!managed)
572
- return {};
573
- const source = sourceTagForToolId(tool);
574
- return {
575
- binary: path.basename(resolved),
576
- ...(source !== undefined && { source }),
577
- };
578
- }
579
- /**
580
- * How to tag a not-fresh install resolution in the compensating row's
581
- * evidence (#1636 review, carried over from #1615's verify). `getInstallAttempt`
582
- * answers `undefined` for BOTH a genuine cache hit and a plain PATH/managed-dir
583
- * discovery, and answers `declined`/`skipped` for a policy refusal that still
584
- * hands back whatever discovery found — three different facts a single
585
- * `"cache"` tag used to flatten into one. `declined`/`skipped` take priority
586
- * over the source map: a project-trust decline overwrites whatever the
587
- * discovery pass underneath it found (see `ensureTool`'s trust-gate branch in
588
- * installer/index.ts), so by the time this reads it, "declined" is the honest
589
- * answer regardless of how the binary was actually found.
590
- */
591
- function resolvedTagForAttempt(toolId, attempt) {
592
- if (attempt?.outcome === "declined" || attempt?.outcome === "skipped") {
593
- return "declined";
594
- }
595
- return getLastEnsureResolutionSource(toolId) === "path" ? "path" : "cache";
596
- }
597
- /**
598
- * Emit the ONE compensating `available` row for a (cwd, toolId) correction,
599
- * after a latched unavailable probe recovers via `ensureTool`. This is the
600
- * SINGLE row constructor for every caller behind this seam —
601
- * `resolveAvailableOrInstallUnshared` (#1612) and `verifyOrInstallCommand`
602
- * (#1636) both call it rather than each hand-rolling their own, which is
603
- * exactly the drift #1610's own review caught the first time this shape
604
- * shipped twice.
605
- *
606
- * Gated on the once-per-correction memo (`correctedAvailabilityByCwd`,
607
- * #1612 review F2): `checker.reset()` / a cleared probe cache means the NEXT
608
- * call for the same (cwd, toolId) can resolve the same tool again with no
609
- * fresh attempt behind it, and that must not re-log a "fresh correction"
610
- * every time.
611
- *
612
- * `latchKeys` are every name a latched `unavailable` row for this pair could
613
- * carry. A row that clears one of them is a genuine correction and burns the
614
- * correction memo; a row with nothing to correct dedupes in its own scope, so
615
- * it cannot silence the genuine latch-then-recover that follows it for the
616
- * same pair (#1657).
617
- */
618
- function emitCompensatingAvailableRow(cwd, toolId, installStartedAt, installedPath, latchKeys) {
619
- if (hasCorrectedAvailability(cwd, toolId))
620
- return;
621
- const correctsLatchedRow = hasLatchedUnavailable(cwd, latchKeys);
622
- if (correctsLatchedRow) {
623
- noteAvailabilityCorrected(cwd, toolId);
624
- // Clear every key the latch could live under, not just the toolId: the
625
- // checker records the row under its COMMAND, and a leftover entry would
626
- // later back a "correction" with nothing behind it (#1674 review F3).
627
- clearLatchedUnavailable(cwd, ...latchKeys);
628
- }
629
- else {
630
- if (hasId(uncorrectedEmissionsByCwd, cwd, toolId))
631
- return;
632
- addId(uncorrectedEmissionsByCwd, cwd, toolId);
633
- }
634
- // `installed`/`installedPath` can come back truthy WITHOUT ensureTool
635
- // running an install this call — its in-memory cache, its on-disk probe
636
- // cache, getToolPath finding it already present, or a concurrent peer's
637
- // install all return a path with no fresh attempt recorded. Deriving from
638
- // `getInstallAttempt` (rather than asserting "succeeded" because the path
639
- // is truthy, or hand-rolling a second mapping) is what #1610's OWN
640
- // failure-path mirror bug was caught on review; read the REAL outcome
641
- // through the one function that already knows how, don't assume it. Hard
642
- // import, no `typeof` guard (#1636 review): a genuinely removed export
643
- // must throw here, not silently read as "not attempted".
644
- const attempt = getInstallAttempt(toolId);
645
- const installEvidence = describeInstallAttempt(attempt);
646
- const evidence = {
647
- ...installEvidence,
648
- binary: path.basename(installedPath),
649
- source: sourceTagForToolId(toolId),
650
- // Set alongside any non-"succeeded" outcome: the path is truthy but
651
- // nothing ran this call, so it came from an already-known-good answer
652
- // rather than a fresh attempt (#1612 review F2).
653
- ...(installEvidence.install !== "succeeded" && {
654
- resolved: resolvedTagForAttempt(toolId, attempt),
655
- }),
656
- // The whole point of the memo split, on the record: a reader can tell a
657
- // row that cleared a latched `unavailable` from one that corrected
658
- // nothing, without re-deriving it from the surrounding rows (#1674
659
- // review F4).
660
- correctsLatchedRow,
661
- };
662
- logAvailabilityDecision({
663
- tool: toolId,
664
- verdict: "available",
665
- outcome: "success",
666
- cause: "ok",
667
- elapsedMs: Date.now() - installStartedAt,
668
- // `checker.reset()`/a fresh probe just cleared whatever cache stood
669
- // behind this call, so nothing is actually held pinned the instant
670
- // this row is written — the very next probe starts fresh
671
- // (#1612 review F3). Writing `true` here would claim a durability
672
- // this call doesn't have.
673
- latched: false,
674
- classifiedBy: "caller",
675
- evidence,
676
- }, cwd);
677
- }
678
- /** Reset availability/install suppression at the session boundary. */
679
- export function resetDispatchAvailabilityState() {
680
- installAttemptsByCwd.clear();
681
- resolveInstallInFlightByCwd.clear();
682
- correctedAvailabilityByCwd.clear();
683
- uncorrectedEmissionsByCwd.clear();
684
- latchedUnavailableByCwd.clear();
685
- managedBinaryVerdicts.clear();
686
- managedVerifyInFlight.clear();
687
- resetPathWalkMemo();
688
- availabilityGeneration.bump();
689
- checkerProbeFlights.clear();
690
- cwdProbeFlights.clear();
691
- // The generation bump invalidates dispatcherProbeFlights in its owner module.
692
- }
693
- /**
694
- * Create a cached availability checker for a command.
695
- * The checker will look for the command in venv first, then global.
696
- *
697
- * `versionArgs` defaults to `["--version"]` but some tools reject that flag and
698
- * expose version under a subcommand instead (e.g. `zig version`, not
699
- * `zig --version`). Passing the wrong probe makes the runner silently skip on
700
- * every machine, so toolchains with a non-standard version command must override
701
- * this.
702
- *
703
- * ## Latch policy (#1467)
704
- *
705
- * A `missing` / `non-installable` verdict is durable and is cached for the
706
- * session. A `transient` verdict — timeout, abort, EAGAIN — is NOT: it is
707
- * cached only for a bounded cooldown, after which the next caller re-probes.
708
- * An installed tool therefore recovers on its own, without a host restart.
709
- */
710
- export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"], options = {}) {
711
- const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
712
- const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
713
- let checkerGeneration = availabilityGeneration.current();
714
- let checkerFlightGeneration = 0;
715
- const findCommand = createVenvFinder(command, windowsExt, versionArgs);
716
- function ensureCurrentGeneration() {
717
- if (checkerGeneration === availabilityGeneration.current())
718
- return;
719
- cacheByCwd.clear();
720
- inFlightByCwd.clear();
721
- checkerGeneration = availabilityGeneration.current();
722
- checkerFlightGeneration++;
723
- }
724
- const reset = () => {
725
- cacheByCwd.clear();
726
- inFlightByCwd.clear();
727
- checkerGeneration = availabilityGeneration.current();
728
- checkerFlightGeneration++;
729
- };
730
- function getCache(cwd) {
731
- ensureCurrentGeneration();
732
- const key = path.resolve(cwd || process.cwd());
733
- const existing = cacheByCwd.get(key);
734
- if (existing)
735
- return existing;
736
- const created = {
737
- available: null,
738
- command: null,
739
- outcome: null,
740
- cause: null,
741
- elapsedMs: 0,
742
- retryAtMs: 0,
743
- transientAttempts: 0,
744
- };
745
- cacheByCwd.set(key, created);
746
- return created;
747
- }
748
- /** Record a verdict on the cache and emit exactly one decision record. */
749
- function noteDecision(cache, resolvedCwd, verdict) {
750
- cache.available = verdict.available;
751
- cache.outcome = verdict.outcome;
752
- cache.cause = verdict.cause;
753
- cache.elapsedMs = verdict.elapsedMs;
754
- let retryAfterMs;
755
- if (verdict.available) {
756
- cache.retryAtMs = 0;
757
- cache.transientAttempts = 0;
758
- }
759
- else if (isLatchingOutcome(verdict.outcome)) {
760
- cache.retryAtMs = 0;
761
- cache.transientAttempts = 0;
762
- }
763
- else {
764
- cache.transientAttempts += 1;
765
- retryAfterMs = transientRetryDelayMs(cache.transientAttempts, verdict.cause);
766
- cache.retryAtMs = Date.now() + retryAfterMs;
767
- }
768
- const latched = verdict.available || isLatchingOutcome(verdict.outcome);
769
- // Remember the pair whose durable row now says "unavailable and staying
770
- // that way" — that is the row a later compensating `available` row
771
- // corrects, and the only thing that makes such a row a correction
772
- // (#1657).
773
- if (!verdict.available && latched) {
774
- noteLatchedUnavailable(resolvedCwd, command);
775
- }
776
- else if (verdict.available) {
777
- // The latched row this pair carried no longer stands: THIS row says
778
- // the tool is back. Dropping the record here keeps a stale latch from
779
- // backing a later "correction" that corrects nothing (#1674 F3).
780
- clearLatchedUnavailable(resolvedCwd, ...latchKeysFor(command));
781
- }
782
- logAvailabilityDecision({
783
- tool: command,
784
- verdict: verdict.available ? "available" : "unavailable",
785
- outcome: verdict.outcome,
786
- cause: verdict.cause,
787
- elapsedMs: verdict.elapsedMs,
788
- latched,
789
- // Every caller passes this (#2209); a direct key, not a conditional
790
- // spread, keeps it visible to the call-shaped classifiedBy sweep.
791
- classifiedBy: verdict.classifiedBy,
792
- ...(verdict.evidence !== undefined && { evidence: verdict.evidence }),
793
- ...(verdict.hostStallMs !== undefined && {
794
- hostStallMs: verdict.hostStallMs,
795
- }),
796
- ...(retryAfterMs !== undefined && { retryAfterMs }),
797
- budgetMs: options.probeTimeout ?? 5000,
798
- }, resolvedCwd);
799
- }
800
- async function isAvailableAsync(cwd) {
801
- ensureCurrentGeneration();
802
- const resolvedCwd = cwd || process.cwd();
803
- const cache = getCache(resolvedCwd);
804
- if (cache.available === false) {
805
- // A durable "this machine does not have the tool" stays cached; a
806
- // transient probe failure only holds until its cooldown expires, so an
807
- // installed tool cannot be disabled for the life of the process by one
808
- // slow second at warm-up (#1467).
809
- if (cache.outcome !== "transient")
810
- return false;
811
- if (Date.now() < cache.retryAtMs)
812
- return false;
813
- cache.available = null;
814
- }
815
- if (cache.available === true && cache.command) {
816
- if (await isSpawnableCommand(cache.command))
817
- return true;
818
- // Cached-positive spawn feedback: a removed absolute path or vanished
819
- // PATH command must fall through to a fresh probe immediately.
820
- cache.available = null;
821
- cache.command = null;
822
- cache.outcome = null;
823
- cache.cause = null;
824
- }
825
- const key = path.resolve(resolvedCwd);
826
- const existing = inFlightByCwd.get(key);
827
- if (existing)
828
- return existing;
829
- const promiseGeneration = checkerGeneration;
830
- let promise;
831
- promise = (async () => {
832
- const fastPath = await options.fastPath?.();
833
- if (fastPath) {
834
- cache.command = fastPath;
835
- noteDecision(cache, resolvedCwd, {
836
- available: true,
837
- outcome: "success",
838
- cause: "fast-path",
839
- elapsedMs: 0,
840
- // Caller-asserted, and justifiably: an on-disk shim IS the tool, so
841
- // there is no spawn to derive anything from (#1500).
842
- classifiedBy: "caller",
843
- });
844
- return true;
845
- }
846
- // A bad/removed workspace must not be mistaken for a missing tool and
847
- // trigger an install. This async probe stays off the synchronous dispatch
848
- // burst and makes the failure taxonomy explicit at the seam.
849
- try {
850
- const cwdStat = await fs.promises.stat(resolvedCwd);
851
- if (!cwdStat.isDirectory()) {
852
- noteDecision(cache, resolvedCwd, {
853
- available: false,
854
- outcome: "non-installable",
855
- cause: "bad-cwd",
856
- elapsedMs: 0,
857
- // The workspace is gone. Asserted from a stat, not a probe, and it
858
- // must never read as "the tool is missing" (#1500).
859
- classifiedBy: "caller",
860
- });
861
- return false;
862
- }
863
- }
864
- catch {
865
- noteDecision(cache, resolvedCwd, {
866
- available: false,
867
- outcome: "non-installable",
868
- cause: "bad-cwd",
869
- elapsedMs: 0,
870
- classifiedBy: "caller",
871
- });
872
- return false;
873
- }
874
- // Resolution is measured separately from the spawn (#2140 review F2).
875
- // It is no longer free — the managed rungs stat two directories and,
876
- // on a first touch, pay their own verification spawn — and it runs
877
- // BEFORE `startedAt`, so neither `durationMs` nor
878
- // `recordAvailabilityProbeOverrun` can see it. Folding it into
879
- // `durationMs` would instead charge the probe budget for work that
880
- // budget does not govern, so the two spans are reported side by side.
881
- const resolveStartedAt = Date.now();
882
- const cmd = await findCommand(resolvedCwd);
883
- const resolveMs = Date.now() - resolveStartedAt;
884
- // #1995: a command cooling down after a RUNTIME timeout (lint or
885
- // autofix lane blew its real budget) must not re-probe on every
886
- // edit - the positive verdict is effectively cooled. Consult-only:
887
- // probe timeouts do NOT arm the cooldown here, because a single
888
- // slow --version under host stall (#1467) is transient evidence,
889
- // and session-long suppression from it would contradict the
890
- // #1494 retry ladder this classification exists for.
891
- if (isInSpawnTimeoutCooldown(cmd)) {
892
- noteDecision(cache, resolvedCwd, {
893
- available: false,
894
- outcome: "transient",
895
- cause: "probe-timeout",
896
- elapsedMs: 0,
897
- classifiedBy: "caller",
898
- evidence: {
899
- command: cmd,
900
- status: null,
901
- },
902
- });
903
- return false;
904
- }
905
- const env = await options.environment?.(resolvedCwd);
906
- // The probe budget is enforced by a HOST-side timer, so host event-loop
907
- // stalls are charged to the child. Measure the stall that overlapped the
908
- // window and hand it to the classifier (#1467).
909
- const stallSampler = startHostStallSampler();
910
- const startedAt = Date.now();
911
- let result;
912
- let hostStallMs;
913
- let probeJoined = false;
914
- try {
915
- const shared = checkerProbeFlights.run(`checker:${command}|${versionArgs.join("|")}|${key}|${checkerFlightGeneration}|${windowsExt}|${cmd}|${JSON.stringify(Object.entries(env ?? {}).sort(([a], [b]) => compareOrdinal(a, b)))}`, () => safeSpawnAsync(cmd, versionArgs, {
916
- timeout: options.probeTimeout ?? 5000,
917
- cwd: resolvedCwd,
918
- env,
919
- input: "",
920
- }));
921
- probeJoined = shared.joined;
922
- result = await shared.promise;
923
- }
924
- finally {
925
- hostStallMs = stallSampler.stop();
926
- }
927
- const elapsedMs = Date.now() - startedAt;
928
- recordAvailabilityProbeOverrun(command, resolvedCwd, elapsedMs, options.probeTimeout ?? 5000, result.failure);
929
- if (!result.error && result.status === 0) {
930
- cache.command = cmd;
931
- noteDecision(cache, resolvedCwd, {
932
- available: true,
933
- outcome: "success",
934
- cause: "ok",
935
- elapsedMs,
936
- hostStallMs,
937
- classifiedBy: probeJoined ? "joined" : "probe",
938
- evidence: {
939
- ...describeProbeEvidence(result),
940
- ...(await describeManagedResolution(command, cmd)),
941
- resolveMs,
942
- },
943
- });
944
- return true;
945
- }
946
- const { outcome, cause, evidence } = classifyProbeFailure(result, {
947
- hostStallMs,
948
- unclassifiedFailureOutcome: options.unclassifiedFailureOutcome,
949
- });
950
- // Only a TYPED tool-not-found invalidates the PATH walk memo; an
951
- // `unclassifiedFailureOutcome: "missing"` compatibility verdict is a
952
- // guess, not evidence that PATH changed.
953
- if (result.spawnFailure?.kind === "tool-not-found")
954
- resetPathWalkMemo();
955
- noteDecision(cache, resolvedCwd, {
956
- available: false,
957
- outcome,
958
- cause,
959
- elapsedMs,
960
- hostStallMs,
961
- classifiedBy: probeJoined ? "joined" : "probe",
962
- evidence,
963
- });
964
- return false;
965
- })().finally(() => {
966
- // A session reset clears this map and a caller may immediately start a
967
- // replacement probe for the same cwd. The old promise must not delete
968
- // that newer-generation entry when it settles.
969
- if (checkerGeneration === promiseGeneration &&
970
- inFlightByCwd.get(key) === promise) {
971
- inFlightByCwd.delete(key);
972
- }
973
- });
974
- inFlightByCwd.set(key, promise);
975
- return promise;
976
- }
977
- function getCommand(cwd) {
978
- ensureCurrentGeneration();
979
- const cache = getCache(cwd || process.cwd());
980
- return cache.command;
981
- }
982
- function getOutcome(cwd) {
983
- ensureCurrentGeneration();
984
- return getCache(cwd || process.cwd()).outcome;
985
- }
986
- function getVerdict(cwd) {
987
- ensureCurrentGeneration();
988
- const cache = getCache(cwd || process.cwd());
989
- return {
990
- outcome: cache.outcome,
991
- cause: cache.cause,
992
- elapsedMs: cache.elapsedMs,
993
- latched: cache.available !== false ||
994
- isLatchingOutcome(cache.outcome ?? "missing"),
995
- retryAtMs: cache.retryAtMs,
996
- };
997
- }
998
- return { isAvailableAsync, getCommand, getOutcome, getVerdict, reset };
999
- }
1000
- const cwdProbeFlights = createAvailabilityProbeFlight({
1001
- generation: getDispatchAvailabilityGeneration,
1002
- });
1003
- /**
1004
- * Per-cwd cached availability probe for spawn signatures that don't fit
1005
- * `createAvailabilityChecker` — multi-arg subcommands like `cargo clippy
1006
- * --version` and `mix credo --version`, or a dynamically-resolved
1007
- * `<cmd> --version` such as eslint's local-then-global binary. Each cwd is
1008
- * probed at most once per verdict; concurrent first-time callers share the
1009
- * in-flight promise.
1010
- *
1011
- * Those three ARE the consumers. `npx biome --version` used to be named here as
1012
- * one and never was: biome resolves through `resolveManagedToolClient` and its
1013
- * own latch in `biome-client.ts`.
1014
- *
1015
- * ## Latch policy (#1494)
1016
- *
1017
- * This used to cache the boolean forever, so one stalled `eslint --version`
1018
- * disabled eslint for that cwd until the process restarted. The verdict now
1019
- * goes through `availability-policy.ts`: the probe returns the SPAWN RESULT,
1020
- * the policy classifies it, and only a durable outcome (`missing` /
1021
- * `non-installable`) latches. A timeout, abort or host stall expires on a
1022
- * bounded cooldown and the next caller re-probes.
1023
- */
1024
- export function createCwdCachedProbe(probe, options) {
1025
- const latchByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
1026
- const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
1027
- let probeGeneration = availabilityGeneration.current();
1028
- function clear() {
1029
- latchByCwd.clear();
1030
- inFlightByCwd.clear();
1031
- }
1032
- function ensureCurrentGeneration() {
1033
- if (probeGeneration === availabilityGeneration.current())
1034
- return;
1035
- clear();
1036
- probeGeneration = availabilityGeneration.current();
1037
- }
1038
- function latchFor(key) {
1039
- const existing = latchByCwd.get(key);
1040
- if (existing)
1041
- return existing;
1042
- const created = createAvailabilityLatch();
1043
- latchByCwd.set(key, created);
1044
- return created;
1045
- }
1046
- function note(latch, key, verdict) {
1047
- let retryAfterMs;
1048
- if (verdict.available) {
1049
- latch.noteAvailable(verdict.cause);
1050
- }
1051
- else {
1052
- const delay = latch.noteUnavailable(verdict.outcome, verdict.cause);
1053
- if (delay > 0)
1054
- retryAfterMs = delay;
1055
- }
1056
- const latched = verdict.available || isLatchingOutcome(verdict.outcome);
1057
- // Same correction bookkeeping as the checker seam above (#1657).
1058
- if (!verdict.available && latched) {
1059
- noteLatchedUnavailable(key, options.tool);
1060
- }
1061
- else if (verdict.available) {
1062
- clearLatchedUnavailable(key, ...latchKeysFor(options.tool));
1063
- }
1064
- logAvailabilityDecision({
1065
- tool: options.tool,
1066
- verdict: verdict.available ? "available" : "unavailable",
1067
- outcome: verdict.outcome,
1068
- cause: verdict.cause,
1069
- elapsedMs: verdict.elapsedMs,
1070
- latched,
1071
- hostStallMs: verdict.hostStallMs,
1072
- ...(retryAfterMs !== undefined && { retryAfterMs }),
1073
- ...(options.budgetMs !== undefined && { budgetMs: options.budgetMs }),
1074
- // Every verdict here is derived from the probe this seam just ran.
1075
- classifiedBy: verdict.classifiedBy ?? "probe",
1076
- ...(verdict.evidence !== undefined && { evidence: verdict.evidence }),
1077
- }, key);
1078
- }
1079
- const run = (cwd) => {
1080
- ensureCurrentGeneration();
1081
- const key = path.resolve(cwd || process.cwd());
1082
- const latch = latchFor(key);
1083
- const memo = latch.read();
1084
- if (memo !== null)
1085
- return Promise.resolve(memo);
1086
- const existing = inFlightByCwd.get(key);
1087
- if (existing)
1088
- return existing;
1089
- const promiseGeneration = probeGeneration;
1090
- let promise;
1091
- promise = (async () => {
1092
- // The probe budget is enforced by a HOST-side timer, so a stalled event
1093
- // loop expires it while the child is still healthy. Measure the stall
1094
- // that overlapped the window and hand it to the classifier (#1467).
1095
- const stallSampler = startHostStallSampler();
1096
- const startedAt = Date.now();
1097
- let result;
1098
- let thrown;
1099
- let probeJoined = false;
1100
- try {
1101
- const shared = cwdProbeFlights.run(`cwd:${options.tool}|${options.flightKeyComponent ?? ""}|${key}`, () => probe(key));
1102
- probeJoined = shared.joined;
1103
- result = await shared.promise;
1104
- }
1105
- catch (error) {
1106
- thrown = error;
1107
- }
1108
- const hostStallMs = stallSampler.stop();
1109
- const elapsedMs = Date.now() - startedAt;
1110
- const shape = result ?? {
1111
- error: thrown instanceof Error ? thrown : new Error(String(thrown)),
1112
- };
1113
- recordAvailabilityProbeOverrun(options.tool, key, elapsedMs, options.budgetMs, shape.failure);
1114
- // A probe that threw carries its errno in the Error, which is exactly
1115
- // what the taxonomy reads — so a thrown EAGAIN stays transient instead
1116
- // of collapsing into an untyped `false`.
1117
- if (result && !result.error && result.status === 0) {
1118
- note(latch, key, {
1119
- available: true,
1120
- outcome: "success",
1121
- cause: "ok",
1122
- elapsedMs,
1123
- hostStallMs,
1124
- classifiedBy: probeJoined ? "joined" : "probe",
1125
- evidence: describeProbeEvidence(result, options.tool),
1126
- });
1127
- return true;
1128
- }
1129
- const { outcome, cause, evidence } = classifyProbeFailure(shape, {
1130
- hostStallMs,
1131
- command: options.tool,
1132
- unclassifiedFailureOutcome: options.unclassifiedFailureOutcome,
1133
- });
1134
- note(latch, key, {
1135
- available: false,
1136
- outcome,
1137
- cause,
1138
- elapsedMs,
1139
- hostStallMs,
1140
- classifiedBy: probeJoined ? "joined" : "probe",
1141
- evidence,
1142
- });
1143
- return false;
1144
- })().finally(() => {
1145
- // A session reset clears the map and a caller may immediately start a
1146
- // replacement probe for the same cwd; the old promise must not delete
1147
- // that newer-generation entry when it settles.
1148
- if (probeGeneration === promiseGeneration &&
1149
- inFlightByCwd.get(key) === promise) {
1150
- inFlightByCwd.delete(key);
1151
- }
1152
- });
1153
- inFlightByCwd.set(key, promise);
1154
- return promise;
1155
- };
1156
- const cached = run;
1157
- cached.getVerdict = (cwd) => {
1158
- ensureCurrentGeneration();
1159
- const latch = latchFor(path.resolve(cwd || process.cwd()));
1160
- const outcome = latch.getOutcome();
1161
- return {
1162
- outcome,
1163
- cause: latch.getCause(),
1164
- elapsedMs: 0,
1165
- // Read the OUTCOME, never `read()`: `read()` answers `null` both for a
1166
- // cwd that was never probed and for a transient verdict whose cooldown
1167
- // expired, and `!== false` turned both of those into `latched: true` —
1168
- // the opposite of the truth in the expired case.
1169
- latched: outcome !== null && isLatchingOutcome(outcome),
1170
- retryAtMs: latch.getRetryAtMs(),
1171
- };
1172
- };
1173
- cached.reset = () => {
1174
- clear();
1175
- probeGeneration = availabilityGeneration.current();
1176
- };
1177
- return cached;
1178
- }
1179
- export function resolveNodeToolCommand(cwd, toolName, windowsExt = ".cmd") {
1180
- const isWin = process.platform === "win32";
1181
- const binName = isWin ? `${toolName}${windowsExt}` : toolName;
1182
- const local = path.join(cwd, "node_modules", ".bin", binName);
1183
- if (fs.existsSync(local))
1184
- return local;
1185
- return toolName;
1186
- }
1187
- export function resolveToolCommand(cwd, toolId) {
1188
- const spec = getToolCommandSpec(toolId);
1189
- if (!spec)
1190
- return null;
1191
- return resolveNodeToolCommand(cwd, spec.command, spec.windowsExt ?? ".cmd");
1192
- }
1193
- /**
1194
- * Composer's `vendor/bin/<tool>` from the nearest ancestor that has one.
1195
- *
1196
- * Delegates to THE shared bin walker (#2544 review F1): this was a fifth
1197
- * private copy of that climb, unceilinged, so a `vendor/bin` at or above
1198
- * `$HOME` — which can never be this project's own Composer install — was
1199
- * resolved as the project's tool (#2514, same class as `findInVendorBin`,
1200
- * which had already been folded).
1201
- */
1202
- export function resolveVendorToolCommand(cwd, toolName, windowsExt = ".bat") {
1203
- return (findLocalBinUpwards(toolName, cwd, {
1204
- windowsExt,
1205
- binDirs: VENDOR_BIN_DIRS,
1206
- }) ?? null);
1207
- }
1208
- export async function resolveToolCommandWithInstallFallback(cwd, toolId, timeout = 5000) {
1209
- const spec = getToolCommandSpec(toolId);
1210
- if (!spec)
1211
- return null;
1212
- return resolveCommandWithInstallFallback(resolveToolCommand(cwd, toolId) ?? spec.command, spec.managedToolId ?? toolId, cwd, spec.versionArgs ?? ["--version"], timeout);
1213
- }
1214
- async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
1215
- // Skip the --version spawn when the command isn't even on disk — the ~μs
1216
- // stat/PATH walk beats a guaranteed-to-fail spawn round-trip.
1217
- const spawnable = await isSpawnableCommand(command);
1218
- if (spawnable) {
1219
- const versionCheck = await safeSpawnAsync(command, versionArgs, {
1220
- timeout,
1221
- cwd,
1222
- input: "",
1223
- });
1224
- if (!versionCheck.error && versionCheck.status === 0) {
1225
- return command;
1226
- }
1227
- // A command that was found but rejected its probe is not fixed by a
1228
- // reinstall. This also covers permissions and malformed shims.
1229
- return null;
1230
- }
1231
- if (!shouldAutoInstallTool(toolId))
1232
- return null;
1233
- const state = installStateFor(cwd, toolId);
1234
- if (state.suppressed)
1235
- return null;
1236
- const installStartedAt = Date.now();
1237
- const installed = await ensureTool(toolId);
1238
- if (installed) {
1239
- noteInstallSuccess(toolId, cwd);
1240
- // A latched `unavailable` row MAY stand here, written by whatever probe
1241
- // the caller ran first — but not always. The on-disk pre-check above
1242
- // logs nothing, and two runners reach this seam with no checker probe at
1243
- // all (biome-check and oxlint call
1244
- // `resolveToolCommandWithInstallFallback` directly, #1657). So the row is
1245
- // emitted either way, and only a call that really clears a latched row is
1246
- // recorded as a correction. Without the row, the durable log keeps saying
1247
- // the tool is off after the installer just brought it back — the third
1248
- // seam of #1606/#1610/#1612 (#1636).
1249
- emitCompensatingAvailableRow(cwd, toolId, installStartedAt, installed, latchKeysFor(toolId, command));
1250
- return installed;
1251
- }
1252
- noteInstallFailure(toolId, cwd);
1253
- return null;
1254
- }
1255
- export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
1256
- const versionCheck = await safeSpawnAsync(command.cmd, [...command.args, ...versionArgs], { timeout, cwd, input: "" });
1257
- if (!versionCheck.error && versionCheck.status === 0) {
1258
- return command;
1259
- }
1260
- const installed = await verifyOrInstallCommand(command.cmd, toolId, cwd, versionArgs, timeout);
1261
- if (!installed) {
1262
- return null;
1263
- }
1264
- if (installed === command.cmd) {
1265
- return command;
1266
- }
1267
- return { cmd: installed, args: [] };
1268
- }
1269
- export async function resolveCommandWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
1270
- return verifyOrInstallCommand(command, toolId, cwd, versionArgs, timeout);
1271
- }
1272
- async function resolveAvailableOrInstallUnshared(checker, toolId, cwd) {
1273
- const available = await checker.isAvailableAsync(cwd);
1274
- if (available) {
1275
- return checker.getCommand(cwd);
1276
- }
1277
- // Only a typed ENOENT/missing-command result is repairable by installing.
1278
- // Probe failures caused by bad cwd, permissions, rejected flags, aborts, and
1279
- // timeouts are unavailable/non-installable and must not enter an install loop.
1280
- if (checker.getOutcome?.(cwd) !== "missing")
1281
- return null;
1282
- if (!shouldAutoInstallTool(toolId))
1283
- return null;
1284
- const state = installStateFor(cwd, toolId);
1285
- if (state.suppressed) {
1286
- return null;
1287
- }
1288
- const installStartedAt = Date.now();
1289
- const installed = await ensureTool(toolId);
1290
- if (installed) {
1291
- noteInstallSuccess(toolId, cwd);
1292
- checker.reset?.();
1293
- // The PATH probe above already wrote a latched `unavailable` row via
1294
- // `checker.isAvailableAsync`. Without a compensating row here, the durable
1295
- // log keeps saying the tool is off after the installer just brought it
1296
- // back — the shape #1606/PR #1610 fixed in `ensureViaInstaller`, shared
1297
- // here by ~16 runners behind this helper (#1612), and by
1298
- // `verifyOrInstallCommand` for the third seam (#1636).
1299
- //
1300
- // `checker.reset()` above clears the checker's cache, so the NEXT call
1301
- // re-probes PATH from scratch — and for a managed-dir-only install that
1302
- // probe misses again unless `createVenvFinder` finds it first (#1638).
1303
- // Left unguarded, every one of those repeat misses would re-fire this
1304
- // row, claiming a fresh install each time — `emitCompensatingAvailableRow`
1305
- // gates on the once-per-correction memo to prevent that.
1306
- //
1307
- // The probe above returned the latching `missing` outcome, so a latched
1308
- // `unavailable` row stands for this pair right now — recorded under the
1309
- // checker's own command name. Record it under the toolId too, so the
1310
- // emitter can READ that a correction is happening from the same ledger
1311
- // every other caller reads, instead of being told so by an argument
1312
- // (#1674 review F3).
1313
- noteLatchedUnavailable(cwd, toolId);
1314
- // This IS a genuine correction: the probe above returned the latching
1315
- // `missing` outcome, so a latched `unavailable` row stands for this pair
1316
- // right now (#1657).
1317
- emitCompensatingAvailableRow(cwd, toolId, installStartedAt, installed, latchKeysFor(toolId, checker.getCommand(cwd) ?? undefined));
1318
- return installed;
1319
- }
1320
- noteInstallFailure(toolId, cwd);
1321
- return null;
1322
- }
1323
- /** Share the complete probe/install transaction for each cwd/tool pair. */
1324
- export function resolveAvailableOrInstall(checker, toolId, cwd) {
1325
- const key = normalizeEphemeralMapKey(cwd);
1326
- let byTool = resolveInstallInFlightByCwd.get(key);
1327
- if (!byTool) {
1328
- byTool = new Map();
1329
- resolveInstallInFlightByCwd.set(key, byTool);
1330
- }
1331
- const existing = byTool.get(toolId);
1332
- if (existing)
1333
- return existing;
1334
- // Same capture-before-await guard as the managed-verify seam, via the same
1335
- // primitive (#1754): a settling old-session transaction must not evict the
1336
- // entry a NEW session already installed for this (cwd, tool).
1337
- const generation = availabilityGeneration.capture();
1338
- const promise = resolveAvailableOrInstallUnshared(checker, toolId, cwd).finally(() => {
1339
- generation.guardedWrite(`${toolId}@${key}`, () => {
1340
- const current = resolveInstallInFlightByCwd.get(key);
1341
- if (current?.get(toolId) === promise) {
1342
- current.delete(toolId);
1343
- if (current.size === 0)
1344
- resolveInstallInFlightByCwd.delete(key);
1345
- }
1346
- });
1347
- });
1348
- byTool.set(toolId, promise);
1349
- return promise;
1350
- }
1351
- // =============================================================================
1352
- // SHARED AST-GREP AVAILABILITY
1353
- // =============================================================================
1354
- /**
1355
- * Shared ast-grep availability across all slop runners, behind the transient-
1356
- * aware latch (#1476). This module-level memo carried the same shape `SgRunner`
1357
- * did — one failed sweep, including a timeout, disabled ast-grep for every slop
1358
- * runner for the life of the process.
1359
- */
1360
- const sgLatch = createAvailabilityLatch();
1361
- let sgCmd = null;
1362
- let sgCmdArgs = [];
1363
- /** Classification of the current sweep, accumulated across candidates. */
1364
- let sgSweepSawTransient = false;
1365
- let sgSweepTransientCause = "probe-timeout";
1366
- let sgSweepHostStallMs = 0;
1367
- /**
1368
- * Candidates that were UNREACHABLE, in ask order (#1568).
1369
- *
1370
- * The sweep stops at the first candidate that answers, so at the moment of a
1371
- * win this list is exactly the set of candidates ahead of the winner that never
1372
- * got a fair hearing — i.e. the preferred tiers the winner did not really beat.
1373
- *
1374
- * Basenames, because tier 1 is an absolute `node_modules/.bin` path and this
1375
- * list is written to latency.log (#1568 review F3).
1376
- */
1377
- let sgSweepUnreachable = [];
1378
- /**
1379
- * Candidates that were probed and answered DURABLY missing this sweep — a
1380
- * real ENOENT/non-installable verdict, not a stall (#1593). The retained-arm
1381
- * fallback below only knows the sweep saw SOME transient candidate; without
1382
- * this list it cannot tell "the memoized winner itself just proved absent"
1383
- * from "an unrelated sibling merely stalled", and re-serves a command this
1384
- * very sweep disproved.
1385
- */
1386
- let sgSweepDurablyMissing = [];
1387
- function isAstGrepVersionOutput(output) {
1388
- return /\bast[- ]grep\b/i.test(output);
1389
- }
1390
- async function probeAstGrepCommandAsync(cmd, argsPrefix = []) {
1391
- const sampler = startHostStallSampler();
1392
- let check;
1393
- let hostStallMs;
1394
- try {
1395
- check = await probeToolAsync(cmd, [...argsPrefix, "--version"], {
1396
- timeout: 5000,
1397
- });
1398
- }
1399
- finally {
1400
- hostStallMs = sampler.stop();
1401
- sgSweepHostStallMs += hostStallMs;
1402
- }
1403
- if (!check.error &&
1404
- check.status === 0 &&
1405
- isAstGrepVersionOutput(`${check.stdout}\n${check.stderr}`)) {
1406
- return true;
1407
- }
1408
- const { outcome, cause } = classifyProbeFailure(check, { hostStallMs });
1409
- if (outcome === "transient") {
1410
- sgSweepSawTransient = true;
1411
- sgSweepTransientCause = cause;
1412
- const name = path.basename(cmd);
1413
- if (!sgSweepUnreachable.includes(name))
1414
- sgSweepUnreachable.push(name);
1415
- }
1416
- else if (!sgSweepDurablyMissing.includes(cmd)) {
1417
- sgSweepDurablyMissing.push(cmd);
1418
- }
1419
- return false;
1420
- }
1421
- /** Pre-filter local node_modules/.bin candidates that actually exist on disk. */
1422
- function buildSgLocalBins() {
1423
- const isWin = process.platform === "win32";
1424
- const hasBash = !!(process.env.MSYSTEM ||
1425
- process.env.GIT_SHELL ||
1426
- process.env.BASH);
1427
- const extensions = isWin
1428
- ? hasBash
1429
- ? ["", ".exe", ".cmd"]
1430
- : [".cmd", ".exe", ""]
1431
- : [""];
1432
- const binaryCandidates = ["ast-grep", "sg"].flatMap((base) => extensions.map((ext) => `${base}${ext}`));
1433
- // THE shared bin walk (#2544 review F1). This used to be a private
1434
- // `findNodeBinRoots` climb with no HOME ceiling, so on a box with a
1435
- // home-level `node_modules` (the pi-extensions manifest installs its own
1436
- // bins, #2514) `$HOME` itself came back as a bin root and an unrelated
1437
- // ast-grep was probed as if it were this project's. `windowsExt: ""`
1438
- // because the candidate names already carry their extensions — in a
1439
- // shell-dependent ORDER (`hasBash` above) that a single `windowsExt`
1440
- // cannot express.
1441
- //
1442
- // The PROJECT walk (from `process.cwd()`) keeps the default HOME ceiling —
1443
- // a `node_modules/.bin/ast-grep` at or above `$HOME` can never be THIS
1444
- // project's own dependency. pi-lens's OWN install-tree walk (from
1445
- // `_thisDir`) is exempt (`ceiling: false`, #2544 round 4 F1): `pi install
1446
- // npm:pi-lens` installs into the HOME-level manifest
1447
- // (`~/package.json` → `~/node_modules/pi-lens`), so pi-lens's bundled
1448
- // `@ast-grep/cli` shim can sit AT `~/node_modules/.bin/ast-grep` — exactly
1449
- // the directory the PROJECT ceiling exists to stop at. A previous version
1450
- // of this comment claimed the own install always lives "under"
1451
- // `~/.pi/extensions/…`, below the ceiling; that is false for the
1452
- // HOME-level manifest layout, and the round-2 ceiling silently made this
1453
- // walk return nothing for it (PATH, `findGlobalBinary` and npx do not
1454
- // cover this location either). `_managedToolsDir` (`~/.pi-lens/tools`) is
1455
- // a fixed location, not a walk, so it is checked directly as before —
1456
- // also own-install, since `ensureTool()` puts npm-strategy installs there.
1457
- const projectWalkOptions = { windowsExt: "" };
1458
- const ownInstallWalkOptions = { windowsExt: "", ceiling: false };
1459
- const bins = [
1460
- ...findLocalBinsUpwards(binaryCandidates, _thisDir, ownInstallWalkOptions).map((binPath) => ({ path: binPath, source: "own-install" })),
1461
- ...findLocalBinsUpwards(binaryCandidates, process.cwd(), projectWalkOptions).map((binPath) => ({ path: binPath, source: "project" })),
1462
- ];
1463
- for (const candidate of binaryCandidates) {
1464
- const managedBin = path.join(_managedToolsDir, "node_modules", ".bin", candidate);
1465
- if (fs.existsSync(managedBin)) {
1466
- bins.push({ path: managedBin, source: "own-install" });
1467
- }
1468
- }
1469
- return bins;
1470
- }
1471
- let sgAvailableInFlight = null;
1472
- let sgAvailabilityGeneration = availabilityGeneration.current();
1473
- function ensureCurrentSgGeneration() {
1474
- if (sgAvailabilityGeneration === availabilityGeneration.current())
1475
- return;
1476
- sgLatch.reset();
1477
- sgCmd = null;
1478
- sgCmdArgs = [];
1479
- sgAvailableInFlight = null;
1480
- sgAvailabilityGeneration = availabilityGeneration.current();
1481
- }
1482
- export async function isSgAvailableAsync() {
1483
- ensureCurrentSgGeneration();
1484
- // `read()` returns null when the last verdict was transient and its cooldown
1485
- // expired: re-probe rather than stay dead for the session (#1476).
1486
- const memo = sgLatch.read();
1487
- if (memo !== null)
1488
- return memo;
1489
- if (sgAvailableInFlight)
1490
- return sgAvailableInFlight;
1491
- const flight = (async () => {
1492
- const startedAt = Date.now();
1493
- sgSweepSawTransient = false;
1494
- sgSweepTransientCause = "probe-timeout";
1495
- sgSweepHostStallMs = 0;
1496
- sgSweepUnreachable = [];
1497
- sgSweepDurablyMissing = [];
1498
- // 1. Local node_modules/.bin (own-install tree, then project tree)
1499
- for (const candidate of buildSgLocalBins()) {
1500
- if (await probeAstGrepCommandAsync(candidate.path)) {
1501
- sgCmd = candidate.path;
1502
- sgCmdArgs = [];
1503
- noteSgAvailable(startedAt, { source: candidate.source });
1504
- return true;
1505
- }
1506
- }
1507
- // 2. Global PATH
1508
- for (const cmd of ["ast-grep", "sg"]) {
1509
- if (await probeAstGrepCommandAsync(cmd)) {
1510
- sgCmd = cmd;
1511
- sgCmdArgs = [];
1512
- noteSgAvailable(startedAt, { source: "global" });
1513
- return true;
1514
- }
1515
- }
1516
- // 2b. Any package manager's global bin dir (npm/pnpm/yarn/bun) — catches
1517
- // `pnpm add -g @ast-grep/cli` installs whose bin dir is off PATH (#375).
1518
- for (const name of ["ast-grep", "sg"]) {
1519
- const globalBin = await findGlobalBinary(name);
1520
- if (globalBin && (await probeAstGrepCommandAsync(globalBin))) {
1521
- sgCmd = globalBin;
1522
- sgCmdArgs = [];
1523
- noteSgAvailable(startedAt, { source: "global" });
1524
- return true;
1525
- }
1526
- }
1527
- // 3. npx --no (cache-only, no silent download).
1528
- if (await probeAstGrepCommandAsync("npx", ["--no", "--", "ast-grep"])) {
1529
- sgCmd = "npx";
1530
- sgCmdArgs = ["--no", "--", "ast-grep"];
1531
- noteSgAvailable(startedAt, { source: "npx" });
1532
- return true;
1533
- }
1534
- // Nothing answered, and nothing answered TRANSIENTLY, while the verdict we
1535
- // are about to overwrite is a provisional win whose command we still hold.
1536
- // #1476's principle applies to the result as much as to the probe: a
1537
- // timeout says nothing about the tool, so it cannot erase a command this
1538
- // process proved working one cooldown ago. Keep serving it and re-arm
1539
- // (#1568 review F1) — UNLESS this very sweep just proved the memoized
1540
- // command durably missing (#1593): a sibling tier stalling is not license
1541
- // to re-serve a winner that ENOENTed a moment ago in the same pass.
1542
- if (sgSweepSawTransient &&
1543
- sgLatch.isProvisional() &&
1544
- sgCmd !== null &&
1545
- !sgSweepDurablyMissing.includes(sgCmd)) {
1546
- noteSgAvailable(startedAt, { retained: true });
1547
- return true;
1548
- }
1549
- // A timeout on ANY candidate is evidence about the host, not the tool.
1550
- noteSgUnavailable(startedAt, sgSweepSawTransient ? "transient" : "missing", sgSweepSawTransient ? sgSweepTransientCause : "not-found");
1551
- return false;
1552
- })().finally(() => {
1553
- // Identity-guarded release (#1968's pattern): clear the slot only if THIS
1554
- // flight still owns it. `ensureCurrentSgGeneration()` above nulls the slot
1555
- // on a generation bump (a session reset arriving mid-flight) so the NEXT
1556
- // caller starts a fresh flight rather than sharing this one's stale-generation
1557
- // answer — that fresh flight is a live successor. Without the guard, this
1558
- // flight's own late settlement would stomp the slot back to null out from
1559
- // under that successor, and the caller after it would start a redundant
1560
- // THIRD probe instead of sharing the still-running second one.
1561
- if (sgAvailableInFlight === flight)
1562
- sgAvailableInFlight = null;
1563
- });
1564
- sgAvailableInFlight = flight;
1565
- return flight;
1566
- }
1567
- /**
1568
- * Record a successful shared-ast-grep sweep, with one decision record.
1569
- *
1570
- * A win reached while an EARLIER candidate was unreachable is provisional
1571
- * (#1568). The sweep stops at the first candidate that answers, so
1572
- * `sgSweepSawTransient` at this point means precisely "a tier this one is
1573
- * supposed to lose to never got a fair hearing" — the winner is used now, but
1574
- * caching it for the session would pin a healthy PATH ast-grep behind `npx`
1575
- * until the next restart.
1576
- *
1577
- * `retained` marks the other provisional case (#1568 review F1): no candidate
1578
- * answered at all, so the winner being reported is the one the previous sweep
1579
- * found, kept rather than discarded on a timeout.
1580
- *
1581
- * `source` (omitted for a `retained` win, which resolved nothing new this
1582
- * sweep) logs one `tool_bin_resolved` phase so F1's own-install-vs-project
1583
- * ceiling distinction is visible in `latency.log` instead of only inferable
1584
- * from a bin's absence (#2544 round 4 F4).
1585
- */
1586
- function noteSgAvailable(startedAt, opts = {}) {
1587
- const provisional = sgSweepSawTransient;
1588
- let retryAfterMs = 0;
1589
- if (provisional) {
1590
- retryAfterMs = sgLatch.noteProvisionallyAvailable(sgSweepTransientCause);
1591
- }
1592
- else {
1593
- sgLatch.noteAvailable();
1594
- }
1595
- if (opts.source) {
1596
- logLatency({
1597
- type: "phase",
1598
- phase: "tool_bin_resolved",
1599
- filePath: "<pi-lens>",
1600
- durationMs: Math.round(Date.now() - startedAt),
1601
- metadata: {
1602
- tool: "ast-grep",
1603
- source: opts.source,
1604
- ceilingApplied: opts.source === "project",
1605
- },
1606
- });
1607
- }
1608
- logAvailabilityDecision({
1609
- tool: "ast-grep",
1610
- verdict: "available",
1611
- outcome: "success",
1612
- cause: provisional ? sgSweepTransientCause : "ok",
1613
- elapsedMs: Date.now() - startedAt,
1614
- latched: !provisional,
1615
- classifiedBy: "probe",
1616
- hostStallMs: sgSweepHostStallMs,
1617
- budgetMs: 5000,
1618
- ...(provisional && {
1619
- provisional: true,
1620
- unreachablePreferred: [...sgSweepUnreachable],
1621
- ...(opts.retained === true && { retained: true }),
1622
- ...(retryAfterMs > 0 && { retryAfterMs }),
1623
- }),
1624
- });
1625
- }
1626
- /** Record a failed shared-ast-grep sweep; a transient verdict expires. */
1627
- function noteSgUnavailable(startedAt, outcome, cause) {
1628
- const retryAfterMs = sgLatch.noteUnavailable(outcome, cause);
1629
- logAvailabilityDecision({
1630
- tool: "ast-grep",
1631
- verdict: "unavailable",
1632
- outcome,
1633
- cause,
1634
- elapsedMs: Date.now() - startedAt,
1635
- latched: outcome !== "transient",
1636
- hostStallMs: sgSweepHostStallMs,
1637
- ...(retryAfterMs > 0 && { retryAfterMs }),
1638
- budgetMs: 5000,
1639
- // Sibling of noteSgAvailable's "probe": both report the same
1640
- // candidate sweep this function ran (#2209).
1641
- classifiedBy: "probe",
1642
- });
1643
- }
1644
- export function getSgCommand() {
1645
- ensureCurrentSgGeneration();
1646
- return {
1647
- cmd: sgCmd ?? "npx",
1648
- args: sgCmdArgs.length ? sgCmdArgs : ["--no", "--", "ast-grep"],
1649
- };
1650
- }
1651
- // =============================================================================
1652
- // LOCAL-FIRST BINARY RESOLUTION
1653
- // =============================================================================
1654
- /**
1655
- * Find a tool binary preferring local node_modules/.bin, then any installed
1656
- * package manager's global bin dir (npm/pnpm/yarn/bun), then global PATH. Only
1657
- * falls back to `npx --no` as a last resort — the universal cache-only exec
1658
- * (npx ships with node and never silently downloads), so this stays
1659
- * manager-agnostic without risking a surprise `dlx` fetch on pnpm/yarn/bun.
1660
- *
1661
- * Returns: { cmd, args } where args may include the `["--no", toolName]` npx
1662
- * preamble.
1663
- */
1664
- export async function resolveLocalFirstAsync(toolName, cwd, windowsExt = ".cmd") {
1665
- const isWin = process.platform === "win32";
1666
- const binName = isWin ? `${toolName}${windowsExt}` : toolName;
1667
- // 1. Local node_modules/.bin (project-installed)
1668
- const local = path.join(cwd, "node_modules", ".bin", binName);
1669
- if (fs.existsSync(local))
1670
- return { cmd: local, args: [] };
1671
- // 2. Global bin dir of ANY installed manager (npm/pnpm/yarn/bun) — direct
1672
- // file lookup, so it finds tools installed via `pnpm add -g` / `bun add -g`
1673
- // (whose bin dirs are often off PATH) and survives PATH staleness after an
1674
- // `install -g`. No spawn.
1675
- const globalBin = await findGlobalBinary(toolName, windowsExt);
1676
- if (globalBin)
1677
- return { cmd: globalBin, args: [] };
1678
- // 3. Global PATH (already installed system-wide, on PATH)
1679
- const globalCheck = await probeToolAsync(toolName, ["--version"], {
1680
- timeout: 3000,
1681
- });
1682
- if (!globalCheck.error && globalCheck.status === 0) {
1683
- return { cmd: toolName, args: [] };
1684
- }
1685
- // 4. npx --no fallback — universal cache-only exec (no silent download)
1686
- return { cmd: "npx", args: ["--no", toolName] };
1687
- }
1688
- // =============================================================================
1689
- // PRE-BUILT CHECKERS FOR COMMON TOOLS
1690
- // =============================================================================
1691
- export const pyright = createAvailabilityChecker("pyright", ".exe");
1692
- export const ruff = createAvailabilityChecker("ruff", ".exe");
1693
- export const biome = createAvailabilityChecker("biome");
1694
- export const sg = {
1695
- isAvailableAsync: isSgAvailableAsync,
1696
- getCommand: getSgCommand,
1697
- };
1698
- /** Shared cwd seam for runner probes and analysis children (#2777). */
1699
- export function resolveRunnerCwd(ctx, tool) {
1700
- return resolveToolCwd("runner", tool, ctx.filePath, ctx).cwd;
1701
- }