@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
@@ -0,0 +1,4533 @@
1
+ import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
2
+ import {
3
+ resolveToolCwd
4
+ } from "./chunk-RUOG5D2C.js";
5
+ import {
6
+ allAvailableGlobalBinDirs,
7
+ installArgs,
8
+ pmBinary,
9
+ resolveNodePackageManager
10
+ } from "./chunk-U3VGR4VU.js";
11
+ import {
12
+ assertInstallAllowed,
13
+ projectTrustDenialReason
14
+ } from "./chunk-NTUJEGDZ.js";
15
+ import {
16
+ probeToolAsync
17
+ } from "./chunk-EOKRQ3SA.js";
18
+ import {
19
+ TRANSIENT_MAX_COOLDOWN_MS,
20
+ classifyProbeFailure,
21
+ createAvailabilityLatch,
22
+ logAvailabilityDecision,
23
+ startHostStallSampler
24
+ } from "./chunk-WONERJTP.js";
25
+ import {
26
+ acquireBoundedPidFileLock,
27
+ acquireQuarantinePidFileLock,
28
+ createGenerationMap,
29
+ getGlobalPiLensDir,
30
+ heartbeatIntervalMs,
31
+ isLockContention,
32
+ ownsTopGeneration,
33
+ recordGenerationTakeover,
34
+ recordLegacyLockHeld,
35
+ releaseGeneration,
36
+ resetSafeSpawnWindowsCommandCache,
37
+ safeSpawnAsync,
38
+ startGenerationHeartbeat,
39
+ tryAcquireGeneration
40
+ } from "./chunk-VN2AXLEX.js";
41
+ import {
42
+ DEFAULT_MAX_OUTPUT_BYTES,
43
+ createBoundedOutputSink,
44
+ writeFileAtomic,
45
+ writeFileAtomicAsync
46
+ } from "./chunk-2EECBNC5.js";
47
+ import {
48
+ getDegradationLedgerGeneration,
49
+ incrementDegradationCount,
50
+ recordDegradationOnce
51
+ } from "./chunk-N3YQJI6O.js";
52
+ import {
53
+ logExtension
54
+ } from "./chunk-O6TQT6RI.js";
55
+ import {
56
+ createNdjsonLogger,
57
+ getGlobalPiLensLogDir,
58
+ getMaxLogSizeMB,
59
+ isTestMode
60
+ } from "./chunk-UK3CMEAL.js";
61
+ import {
62
+ BoundedLruCache
63
+ } from "./chunk-ABBOA7UD.js";
64
+ import {
65
+ findLocalToolConfig,
66
+ isFullyQualified
67
+ } from "./chunk-Q5U6FMDI.js";
68
+ import {
69
+ __require
70
+ } from "./chunk-NXL4FFQQ.js";
71
+
72
+ // dist/clients/installer/index.js
73
+ import { spawn } from "node:child_process";
74
+ import { existsSync, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync } from "node:fs";
75
+ import fs2 from "node:fs/promises";
76
+ import { createHash, randomUUID } from "node:crypto";
77
+ import https from "node:https";
78
+ import { createRequire } from "node:module";
79
+ import os from "node:os";
80
+ import path3 from "node:path";
81
+ import { createGunzip } from "node:zlib";
82
+
83
+ // dist/clients/durable-store.js
84
+ import * as fs from "node:fs";
85
+ import fsp from "node:fs/promises";
86
+ function readLocked(path4) {
87
+ try {
88
+ return fs.readFileSync(path4, "utf8");
89
+ } catch {
90
+ return void 0;
91
+ }
92
+ }
93
+ function commitDurableStore(options) {
94
+ const release = options.onContention === "skip-log" ? acquireBoundedPidFileLock(`${options.path}.lock`, {
95
+ waitMs: options.waitMs,
96
+ retryMs: options.retryMs,
97
+ timeoutMessage: options.timeoutMessage,
98
+ onContention: "skip-log",
99
+ logContention: options.logContention
100
+ }) : acquireBoundedPidFileLock(`${options.path}.lock`, {
101
+ waitMs: options.waitMs,
102
+ retryMs: options.retryMs,
103
+ timeoutMessage: options.timeoutMessage,
104
+ onContention: "throw"
105
+ });
106
+ if (!release)
107
+ return void 0;
108
+ let committed;
109
+ try {
110
+ const current = options.deserialize(readLocked(options.path));
111
+ committed = options.merge(current);
112
+ writeFileAtomic(options.path, options.serialize(committed), {
113
+ bestEffort: false
114
+ });
115
+ options.afterWriteLocked?.(committed);
116
+ } finally {
117
+ release();
118
+ }
119
+ return committed;
120
+ }
121
+ async function readLockedAsync(path4) {
122
+ try {
123
+ return await fsp.readFile(path4, "utf8");
124
+ } catch (error) {
125
+ if (error.code === "ENOENT")
126
+ return void 0;
127
+ throw error;
128
+ }
129
+ }
130
+ async function commitDurableStoreAsync(options) {
131
+ const release = options.onContention === "skip-log" ? await acquireQuarantinePidFileLock(`${options.path}.lock`, {
132
+ waitMs: options.waitMs,
133
+ retryMs: options.retryMs,
134
+ staleMs: options.staleMs,
135
+ timeoutMessage: options.timeoutMessage,
136
+ onContention: "skip-log",
137
+ logContention: options.logContention
138
+ }) : await acquireQuarantinePidFileLock(`${options.path}.lock`, {
139
+ waitMs: options.waitMs,
140
+ retryMs: options.retryMs,
141
+ staleMs: options.staleMs,
142
+ timeoutMessage: options.timeoutMessage,
143
+ onContention: "throw"
144
+ });
145
+ if (!release)
146
+ return void 0;
147
+ try {
148
+ const current = options.deserialize(await readLockedAsync(options.path));
149
+ const committed = options.merge(current);
150
+ await writeFileAtomicAsync(options.path, options.serialize(committed), {
151
+ bestEffort: false
152
+ });
153
+ await options.afterWriteLocked?.(committed);
154
+ return committed;
155
+ } finally {
156
+ await release();
157
+ }
158
+ }
159
+
160
+ // dist/clients/sessionstart-logger.js
161
+ import * as path from "node:path";
162
+ var SESSIONSTART_LOG_FILE = path.join(getGlobalPiLensLogDir(), "sessionstart.log");
163
+ var writer = createNdjsonLogger({
164
+ filePath: SESSIONSTART_LOG_FILE,
165
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
166
+ backupPath: `${SESSIONSTART_LOG_FILE}.1`
167
+ });
168
+ function logSessionStart(message) {
169
+ if (isTestMode())
170
+ return;
171
+ writer.append(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`);
172
+ }
173
+
174
+ // dist/clients/zizmor-config.js
175
+ import * as path2 from "node:path";
176
+ var LOCAL_ZIZMOR_CONFIG_NAMES = [
177
+ path2.join(".github", "zizmor.yml"),
178
+ path2.join(".github", "zizmor.yaml"),
179
+ "zizmor.yml",
180
+ "zizmor.yaml"
181
+ ];
182
+ function findLocalZizmorConfig(startDir) {
183
+ return findLocalToolConfig(startDir, LOCAL_ZIZMOR_CONFIG_NAMES);
184
+ }
185
+ function isZizmorAuditTarget(filePath) {
186
+ const normalized = filePath.replace(/\\/g, "/");
187
+ const base = path2.basename(normalized).toLowerCase();
188
+ if (/(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(normalized))
189
+ return true;
190
+ if (base === "action.yml" || base === "action.yaml")
191
+ return true;
192
+ if (/(^|\/)\.github\/dependabot\.ya?ml$/i.test(normalized))
193
+ return true;
194
+ return false;
195
+ }
196
+ var ZIZMOR_TOKEN_MAX_COOLDOWN_MS = 12e4;
197
+ var ghTokenLatch = createAvailabilityLatch({
198
+ maxCooldownMs: ZIZMOR_TOKEN_MAX_COOLDOWN_MS
199
+ });
200
+ var cachedToken;
201
+ var GH_TOKEN_PROBE_TIMEOUT_MS = 5e3;
202
+ function resetZizmorTokenAvailability() {
203
+ ghTokenLatch.reset();
204
+ cachedToken = void 0;
205
+ }
206
+ function classifyGhTokenFailure(res, hostStallMs) {
207
+ const neverAnswered = res.status === null || (res.status ?? 0) < 0;
208
+ if (!res.error && !neverAnswered) {
209
+ return {
210
+ outcome: "non-installable",
211
+ cause: "probe-rejected",
212
+ classifiedBy: "caller"
213
+ };
214
+ }
215
+ if (res.spawnFailure?.kind === "tool-not-found") {
216
+ return { outcome: "missing", cause: "not-found", classifiedBy: "caller" };
217
+ }
218
+ const classified = classifyProbeFailure(res, { hostStallMs });
219
+ if (classified.outcome === "transient" || classified.outcome === "missing") {
220
+ return { ...classified, classifiedBy: "probe" };
221
+ }
222
+ return {
223
+ outcome: "transient",
224
+ cause: classified.cause,
225
+ classifiedBy: "caller"
226
+ };
227
+ }
228
+ async function deriveGhCliToken(consumer) {
229
+ const sampler = startHostStallSampler();
230
+ const startedAt = Date.now();
231
+ const res = await safeSpawnAsync("gh", ["auth", "token"], {
232
+ timeout: GH_TOKEN_PROBE_TIMEOUT_MS,
233
+ ignoreAmbientSignal: true
234
+ });
235
+ const hostStallMs = sampler.stop();
236
+ const elapsedMs = Date.now() - startedAt;
237
+ if (!res.error && res.status === 0) {
238
+ const token = res.stdout.trim();
239
+ if (token.length > 0) {
240
+ ghTokenLatch.noteAvailable();
241
+ logAvailabilityDecision({
242
+ tool: consumer === "zizmor" ? "zizmor-gh-token" : "github-token",
243
+ verdict: "available",
244
+ outcome: "success",
245
+ cause: "ok",
246
+ elapsedMs,
247
+ latched: true,
248
+ hostStallMs,
249
+ budgetMs: GH_TOKEN_PROBE_TIMEOUT_MS,
250
+ classifiedBy: "probe"
251
+ });
252
+ return token;
253
+ }
254
+ return recordGhTokenUnavailable(consumer, {
255
+ outcome: "non-installable",
256
+ cause: "empty-result",
257
+ classifiedBy: "caller"
258
+ }, elapsedMs, hostStallMs);
259
+ }
260
+ return recordGhTokenUnavailable(consumer, classifyGhTokenFailure(res, hostStallMs), elapsedMs, hostStallMs);
261
+ }
262
+ function recordGhTokenUnavailable(consumer, { outcome, cause, classifiedBy }, elapsedMs, hostStallMs) {
263
+ const retryAfterMs = ghTokenLatch.noteUnavailable(outcome, cause);
264
+ if (consumer === "zizmor" && outcome === "transient") {
265
+ recordZizmorOfflineDegradation(`gh auth token probe ${cause}; running offline until the next zizmor start (retry allowed in ${Math.round(retryAfterMs / 1e3)}s)`);
266
+ }
267
+ logAvailabilityDecision({
268
+ tool: consumer === "zizmor" ? "zizmor-gh-token" : "github-token",
269
+ verdict: "unavailable",
270
+ outcome,
271
+ cause,
272
+ elapsedMs,
273
+ latched: outcome !== "transient",
274
+ hostStallMs,
275
+ ...retryAfterMs > 0 && { retryAfterMs },
276
+ budgetMs: GH_TOKEN_PROBE_TIMEOUT_MS,
277
+ // Shared tail for both call paths; the caller carries whether ITS
278
+ // own verdict was a `classifyProbeFailure` passthrough or one of
279
+ // this module's own assertions (#2226 review F2).
280
+ classifiedBy
281
+ });
282
+ return void 0;
283
+ }
284
+ function recordZizmorOfflineDegradation(reason) {
285
+ incrementDegradationCount({
286
+ kind: "mode-suppression",
287
+ subject: "zizmor",
288
+ reason
289
+ });
290
+ }
291
+ async function resolveZizmorGitHubToken() {
292
+ if (process.env.ZIZMOR_OFFLINE)
293
+ return void 0;
294
+ return resolveGitHubToken(["ZIZMOR_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"], { consumer: "zizmor" });
295
+ }
296
+ async function resolveGitHubToken(envKeys = ["GITHUB_TOKEN", "GH_TOKEN"], options = {}) {
297
+ const consumer = options.consumer ?? "github-api";
298
+ const fromEnv = envKeys.map((key) => process.env[key]).find(Boolean);
299
+ if (fromEnv)
300
+ return fromEnv;
301
+ const memo = ghTokenLatch.read();
302
+ if (memo !== null) {
303
+ if (consumer === "zizmor" && memo === false && ghTokenLatch.getOutcome() === "transient") {
304
+ const cause = ghTokenLatch.getCause();
305
+ if (cause === null) {
306
+ throw new Error("zizmor gh-token latch: transient outcome with no cause (invariant violated)");
307
+ }
308
+ const retryAfterMs = Math.max(0, ghTokenLatch.getRetryAtMs() - Date.now());
309
+ recordZizmorOfflineDegradation(`gh auth token still cooling down (${cause}); serving cached offline verdict, retry allowed in ${Math.round(retryAfterMs / 1e3)}s`);
310
+ logAvailabilityDecision({
311
+ tool: "zizmor-gh-token",
312
+ verdict: "unavailable",
313
+ outcome: "transient",
314
+ cause,
315
+ elapsedMs: 0,
316
+ latched: false,
317
+ hostStallMs: 0,
318
+ ...retryAfterMs > 0 && { retryAfterMs },
319
+ budgetMs: GH_TOKEN_PROBE_TIMEOUT_MS,
320
+ // No probe ran here: the latch's own remembered cause is replayed
321
+ // as-is, so the call site is the one asserting it (#2209).
322
+ classifiedBy: "caller"
323
+ });
324
+ }
325
+ return memo ? cachedToken : void 0;
326
+ }
327
+ cachedToken = await deriveGhCliToken(consumer);
328
+ return cachedToken;
329
+ }
330
+
331
+ // dist/clients/installer/index.js
332
+ var _installerRequire = createRequire(import.meta.url);
333
+ var TOOLS_DIR = path3.join(getGlobalPiLensDir(), "tools");
334
+ var INSTALL_LOCK_PATH = path3.join(TOOLS_DIR, ".install.lock");
335
+ var INSTALL_LOCK_GENERATIONS = `${INSTALL_LOCK_PATH}s`;
336
+ var activeInstallLocks = /* @__PURE__ */ new Set();
337
+ var installLockExitCleanupRegistered = false;
338
+ function getManagedToolsDir() {
339
+ return TOOLS_DIR;
340
+ }
341
+ function isProcessAlive(pid) {
342
+ try {
343
+ process.kill(pid, 0);
344
+ return true;
345
+ } catch (error) {
346
+ return error.code === "EPERM";
347
+ }
348
+ }
349
+ function installLockMaxAgeMs() {
350
+ return (Number(process.env.PI_LENS_INSTALL_TIMEOUT_MS) || 12e4) + 6e4;
351
+ }
352
+ function legacyInstallLockIsStale(maxAgeMs) {
353
+ try {
354
+ const owner = JSON.parse(readFileSync2(INSTALL_LOCK_PATH, "utf8"));
355
+ const expired = Number.isFinite(owner.createdAt) && Date.now() - owner.createdAt > maxAgeMs;
356
+ return expired || Number.isInteger(owner.pid) && owner.pid > 0 && !isProcessAlive(owner.pid);
357
+ } catch {
358
+ try {
359
+ return Date.now() - statSync(INSTALL_LOCK_PATH).mtimeMs > maxAgeMs;
360
+ } catch {
361
+ return false;
362
+ }
363
+ }
364
+ }
365
+ function createLegacyInstallLock(token) {
366
+ try {
367
+ writeFileSync(INSTALL_LOCK_PATH, token, { flag: "wx" });
368
+ return true;
369
+ } catch (cause) {
370
+ if (isLockContention(cause))
371
+ return false;
372
+ throw cause;
373
+ }
374
+ }
375
+ function takeLegacyInstallLock(maxAgeMs, token) {
376
+ if (createLegacyInstallLock(token))
377
+ return true;
378
+ if (!legacyInstallLockIsStale(maxAgeMs))
379
+ return false;
380
+ try {
381
+ unlinkSync(INSTALL_LOCK_PATH);
382
+ } catch {
383
+ return false;
384
+ }
385
+ return createLegacyInstallLock(token);
386
+ }
387
+ function installLockOwner() {
388
+ try {
389
+ const owner = JSON.parse(readFileSync2(INSTALL_LOCK_PATH, "utf8"));
390
+ return `pid=${owner.pid} createdAt=${owner.createdAt}`;
391
+ } catch {
392
+ return "unknown owner";
393
+ }
394
+ }
395
+ function tryAcquireInstallLock(maxAgeMs) {
396
+ const hold = tryAcquireGeneration(INSTALL_LOCK_GENERATIONS, maxAgeMs);
397
+ if (!hold)
398
+ return "busy";
399
+ if (hold.tookOverStale)
400
+ recordGenerationTakeover(hold);
401
+ const heartbeat = startGenerationHeartbeat(hold, heartbeatIntervalMs(maxAgeMs));
402
+ const token = JSON.stringify({
403
+ pid: process.pid,
404
+ createdAt: Date.now(),
405
+ nonce: randomUUID()
406
+ });
407
+ let took;
408
+ try {
409
+ took = takeLegacyInstallLock(maxAgeMs, token);
410
+ } catch (cause) {
411
+ heartbeat.stop();
412
+ releaseGeneration(hold);
413
+ throw cause;
414
+ }
415
+ if (took)
416
+ return { generation: hold, token, heartbeat };
417
+ heartbeat.stop();
418
+ releaseGeneration(hold);
419
+ return "legacy-held";
420
+ }
421
+ function releaseInstallLock(hold) {
422
+ activeInstallLocks.delete(hold);
423
+ hold.heartbeat.stop();
424
+ try {
425
+ if (readFileSync2(INSTALL_LOCK_PATH, "utf8") === hold.token)
426
+ unlinkSync(INSTALL_LOCK_PATH);
427
+ } catch {
428
+ }
429
+ releaseGeneration(hold.generation);
430
+ }
431
+ async function acquireInstallLock() {
432
+ const timeoutMs = Number(process.env.PI_LENS_INSTALL_LOCK_TIMEOUT_MS) || 15e4;
433
+ const deadline = Date.now() + timeoutMs;
434
+ const maxAgeMs = installLockMaxAgeMs();
435
+ let legacyHeldRecorded = false;
436
+ while (Date.now() < deadline) {
437
+ const hold = tryAcquireInstallLock(maxAgeMs);
438
+ if (hold === "legacy-held" && !legacyHeldRecorded) {
439
+ legacyHeldRecorded = true;
440
+ recordLegacyLockHeld(INSTALL_LOCK_PATH);
441
+ }
442
+ if (typeof hold === "object") {
443
+ activeInstallLocks.add(hold);
444
+ if (!installLockExitCleanupRegistered) {
445
+ installLockExitCleanupRegistered = true;
446
+ process.once("exit", () => {
447
+ for (const held of activeInstallLocks)
448
+ releaseInstallLock(held);
449
+ });
450
+ }
451
+ let released = false;
452
+ return {
453
+ release: async () => {
454
+ if (released)
455
+ return;
456
+ released = true;
457
+ releaseInstallLock(hold);
458
+ },
459
+ ownsLock: () => ownsTopGeneration(hold.generation)
460
+ };
461
+ }
462
+ await new Promise((resolve) => setTimeout(resolve, 100));
463
+ }
464
+ return {
465
+ reason: `timed out after ${timeoutMs}ms waiting for shared tools install lock (${installLockOwner()})`
466
+ };
467
+ }
468
+ var GITHUB_BIN_DIR = path3.join(getGlobalPiLensDir(), "bin");
469
+ var DEBUG = process.env.PI_LENS_DEBUG === "1" || process.argv.includes("--debug");
470
+ function installerPlatform() {
471
+ const override = process.env.PI_LENS_TEST_PLATFORM;
472
+ if (override === "win32" || override === "linux") {
473
+ return override;
474
+ }
475
+ return process.platform;
476
+ }
477
+ function debugLog(...args) {
478
+ if (DEBUG) {
479
+ logExtension({
480
+ subsystem: "auto-install",
481
+ level: "debug",
482
+ message: args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" ")
483
+ });
484
+ }
485
+ }
486
+ function archAssetMatch(table) {
487
+ return (platform, arch) => {
488
+ if (arch !== "x64" && arch !== "arm64")
489
+ return void 0;
490
+ return table[platform]?.[arch];
491
+ };
492
+ }
493
+ var OS_ARCH_ZIP_ASSETS = {
494
+ linux: { x64: "linux_amd64.zip", arm64: "linux_arm64.zip" },
495
+ darwin: { x64: "darwin_amd64.zip", arm64: "darwin_arm64.zip" },
496
+ win32: { x64: "windows_amd64.zip", arm64: "windows_arm64.zip" }
497
+ };
498
+ function managedPackageFormatterTool(spec) {
499
+ return {
500
+ id: spec.id,
501
+ name: spec.name,
502
+ checkCommand: spec.id,
503
+ checkArgs: ["--version"],
504
+ installStrategy: spec.installStrategy,
505
+ packageName: spec.packageName,
506
+ binaryName: spec.id
507
+ };
508
+ }
509
+ var MANAGED_PACKAGE_FORMATTERS = [
510
+ { id: "black", name: "Black", installStrategy: "pip", packageName: "black" },
511
+ {
512
+ id: "cmake-format",
513
+ name: "cmake-format",
514
+ installStrategy: "pip",
515
+ // The `yaml` EXTRA, never the bare distribution (#3312). cmakelang reads a
516
+ // `.cmake-format.yaml` project config through `import yaml`, and upstream
517
+ // puts PyYAML behind an extra: cmakelang 0.6.13's PyPI metadata declares
518
+ // `pyyaml (>=5.3) ; extra == 'yaml'`. A bare install still answers
519
+ // `cmake-format --version` with status 0 and then dies with
520
+ // `ModuleNotFoundError: No module named 'yaml'` on the first YAML config —
521
+ // the nightly's red cmake row. Every rung of the pip ladder passes this
522
+ // string verbatim to pip/pipx, which both accept the `pkg[extra]` spec.
523
+ packageName: "cmakelang[yaml]"
524
+ },
525
+ { id: "oxfmt", name: "oxfmt", installStrategy: "npm", packageName: "oxfmt" }
526
+ ];
527
+ function managedGitHubFormatterTool(spec) {
528
+ return {
529
+ id: spec.id,
530
+ name: spec.name,
531
+ checkCommand: spec.id,
532
+ checkArgs: ["--version"],
533
+ installStrategy: "github",
534
+ binaryName: spec.id,
535
+ github: {
536
+ repo: `${spec.owner}/${spec.repo}`,
537
+ assetMatch: spec.assetPattern,
538
+ ...spec.binaryInArchive && { binaryInArchive: spec.binaryInArchive },
539
+ ...spec.kind === "phar" && { launcher: "php" },
540
+ ...spec.kind === "jar" && { launcher: "java" }
541
+ }
542
+ };
543
+ }
544
+ var MANAGED_GITHUB_FORMATTERS = [
545
+ {
546
+ id: "typstyle",
547
+ name: "typstyle",
548
+ owner: "typstyle-rs",
549
+ repo: "typstyle",
550
+ assetPattern: archAssetMatch({
551
+ linux: {
552
+ x64: "typstyle-x86_64-unknown-linux-gnu",
553
+ arm64: "typstyle-aarch64-unknown-linux-gnu"
554
+ },
555
+ darwin: {
556
+ x64: "typstyle-x86_64-apple-darwin",
557
+ arm64: "typstyle-aarch64-apple-darwin"
558
+ },
559
+ win32: {
560
+ x64: "typstyle-x86_64-pc-windows-msvc.exe",
561
+ arm64: "typstyle-aarch64-pc-windows-msvc.exe"
562
+ }
563
+ }),
564
+ kind: "binary"
565
+ },
566
+ {
567
+ id: "stylua",
568
+ name: "StyLua",
569
+ owner: "JohnnyMorganz",
570
+ repo: "StyLua",
571
+ assetPattern: archAssetMatch({
572
+ linux: { x64: "linux-x86_64.zip", arm64: "linux-aarch64.zip" },
573
+ darwin: { x64: "macos-x86_64.zip", arm64: "macos-aarch64.zip" },
574
+ win32: { x64: "windows-x86_64.zip" }
575
+ }),
576
+ kind: "binary",
577
+ binaryInArchive: "stylua"
578
+ },
579
+ {
580
+ id: "php-cs-fixer",
581
+ name: "PHP CS Fixer",
582
+ owner: "PHP-CS-Fixer",
583
+ repo: "PHP-CS-Fixer",
584
+ assetPattern: (platform) => platform === "linux" || platform === "darwin" || platform === "win32" ? "php-cs-fixer.phar" : void 0,
585
+ kind: "phar"
586
+ },
587
+ {
588
+ id: "cljfmt",
589
+ name: "cljfmt",
590
+ owner: "weavejester",
591
+ repo: "cljfmt",
592
+ assetPattern: (platform, arch) => {
593
+ if (platform === "linux")
594
+ return arch === "arm64" ? "standalone.jar" : "linux-amd64-static.tar.gz";
595
+ if (platform === "darwin")
596
+ return "standalone.jar";
597
+ if (platform === "win32")
598
+ return "win-amd64.zip";
599
+ return void 0;
600
+ },
601
+ kind: "jar",
602
+ binaryInArchive: "cljfmt"
603
+ }
604
+ ];
605
+ var MANAGED_MAVEN_FORMATTERS = [
606
+ {
607
+ id: "google-java-format",
608
+ name: "google-java-format",
609
+ groupId: "com.google.googlejavaformat",
610
+ artifactId: "google-java-format",
611
+ version: "1.27.0",
612
+ classifier: "all-deps"
613
+ }
614
+ ];
615
+ function managedMavenFormatterTool(spec) {
616
+ return {
617
+ id: spec.id,
618
+ name: spec.name,
619
+ checkCommand: spec.id,
620
+ checkArgs: ["--version"],
621
+ installStrategy: "maven",
622
+ binaryName: spec.id,
623
+ maven: {
624
+ groupId: spec.groupId,
625
+ artifactId: spec.artifactId,
626
+ version: spec.version,
627
+ classifier: spec.classifier
628
+ }
629
+ };
630
+ }
631
+ var TOOLS = [
632
+ // Core LSP servers
633
+ {
634
+ id: "typescript-language-server",
635
+ name: "TypeScript Language Server",
636
+ checkCommand: "typescript-language-server",
637
+ checkArgs: ["--version"],
638
+ installStrategy: "npm",
639
+ // Pinned below the package's own floor: 6.0.0 declares
640
+ // engines.node >=22.22.2, above the pi host's own floor (pi-lens
641
+ // must never require more than pi does — #2633) — an unpinned
642
+ // install here resolves latest and prints EBADENGINE on any pi
643
+ // host's supported Node. 5.3.0's own floor is engines.node >=20.
644
+ packageName: "typescript-language-server@5.3.0",
645
+ binaryName: "typescript-language-server"
646
+ },
647
+ {
648
+ id: "typescript",
649
+ name: "TypeScript",
650
+ checkCommand: "tsc",
651
+ checkArgs: ["--version"],
652
+ installStrategy: "npm",
653
+ // The managed compiler serves the classic typescript-language-server
654
+ // fallback. TypeScript 7 removed lib/tsserver.js and is selected only from
655
+ // project-local installs through the native `tsc --lsp --stdio` path.
656
+ // Revisit when typescript-language-server supports TS 7 — refs #1436.
657
+ packageName: "typescript@5.9.3",
658
+ binaryName: "tsc"
659
+ },
660
+ {
661
+ id: "pyright",
662
+ name: "Pyright",
663
+ checkCommand: "pyright",
664
+ checkArgs: ["--version"],
665
+ installStrategy: "npm",
666
+ packageName: "pyright",
667
+ binaryName: "pyright"
668
+ },
669
+ // Linting/formatting tools
670
+ {
671
+ id: "prettier",
672
+ name: "Prettier",
673
+ checkCommand: "prettier",
674
+ checkArgs: ["--version"],
675
+ installStrategy: "npm",
676
+ packageName: "prettier",
677
+ binaryName: "prettier"
678
+ },
679
+ ...MANAGED_PACKAGE_FORMATTERS.map(managedPackageFormatterTool),
680
+ {
681
+ id: "ruff",
682
+ name: "Ruff",
683
+ checkCommand: "ruff",
684
+ checkArgs: ["--version"],
685
+ installStrategy: "pip",
686
+ packageName: "ruff",
687
+ binaryName: "ruff"
688
+ },
689
+ {
690
+ // Alternate Python LSP (fallback when pyright/the `python` server is
691
+ // unavailable or disabled). Used as a managedToolId by PythonJediServer.
692
+ id: "jedi-language-server",
693
+ name: "Jedi Language Server",
694
+ checkCommand: "jedi-language-server",
695
+ checkArgs: ["--version"],
696
+ installStrategy: "pip",
697
+ packageName: "jedi-language-server",
698
+ binaryName: "jedi-language-server"
699
+ },
700
+ {
701
+ id: "biome",
702
+ name: "Biome",
703
+ checkCommand: "biome",
704
+ checkArgs: ["--version"],
705
+ installStrategy: "npm",
706
+ packageName: "@biomejs/biome",
707
+ binaryName: "biome",
708
+ platformPackage: {
709
+ base: "@biomejs/cli",
710
+ suffixes: {
711
+ "linux-x64": "linux-x64",
712
+ "linux-arm64": "linux-arm64",
713
+ "darwin-x64": "darwin-x64",
714
+ "darwin-arm64": "darwin-arm64",
715
+ "win32-x64": "win32-x64",
716
+ "win32-arm64": "win32-arm64"
717
+ },
718
+ binaries: ["biome"]
719
+ }
720
+ },
721
+ // Analysis tools (run at session start / turn end)
722
+ {
723
+ id: "madge",
724
+ name: "Madge",
725
+ checkCommand: "madge",
726
+ checkArgs: ["--version"],
727
+ installStrategy: "npm",
728
+ packageName: "madge",
729
+ binaryName: "madge"
730
+ },
731
+ {
732
+ id: "jscpd",
733
+ name: "jscpd",
734
+ checkCommand: "jscpd",
735
+ checkArgs: ["--version"],
736
+ installStrategy: "npm",
737
+ packageName: "jscpd@5.4.0",
738
+ // v5.4.0 is the repository's exact devDependency; the v4 packaging defect that required the older v5.0.12 pin is gone, and parseReport() reads the unchanged clone-report fields.
739
+ binaryName: "jscpd"
740
+ },
741
+ // Structural search and dead code detection
742
+ {
743
+ id: "ast-grep",
744
+ name: "ast-grep CLI",
745
+ checkCommand: "ast-grep",
746
+ checkArgs: ["--version"],
747
+ installStrategy: "npm",
748
+ packageName: "@ast-grep/cli",
749
+ binaryName: "ast-grep",
750
+ platformPackage: {
751
+ suffixes: {
752
+ "linux-x64": "linux-x64-gnu",
753
+ "linux-arm64": "linux-arm64-gnu",
754
+ "darwin-x64": "darwin-x64",
755
+ "darwin-arm64": "darwin-arm64",
756
+ "win32-x64": "win32-x64-msvc",
757
+ "win32-arm64": "win32-arm64-msvc",
758
+ "win32-ia32": "win32-ia32-msvc"
759
+ },
760
+ binaries: ["ast-grep", "sg"]
761
+ }
762
+ },
763
+ {
764
+ id: "knip",
765
+ name: "Knip",
766
+ checkCommand: "knip",
767
+ checkArgs: ["--version"],
768
+ installStrategy: "npm",
769
+ packageName: "knip",
770
+ binaryName: "knip"
771
+ },
772
+ {
773
+ id: "yamllint",
774
+ name: "yamllint",
775
+ checkCommand: "yamllint",
776
+ checkArgs: ["--version"],
777
+ installStrategy: "pip",
778
+ packageName: "yamllint",
779
+ binaryName: "yamllint"
780
+ },
781
+ {
782
+ id: "sqlfluff",
783
+ name: "sqlfluff",
784
+ checkCommand: "sqlfluff",
785
+ checkArgs: ["--version"],
786
+ installStrategy: "pip",
787
+ packageName: "sqlfluff",
788
+ binaryName: "sqlfluff"
789
+ },
790
+ {
791
+ id: "bash-language-server",
792
+ name: "Bash Language Server",
793
+ checkCommand: "bash-language-server",
794
+ checkArgs: ["--version"],
795
+ // The #2188 sweep measured a 9,667ms cold `--version` start with closed
796
+ // stdin — close enough to the 10s installer default that modest host
797
+ // contention pushes it over and emits a false verification degradation.
798
+ // 20s gives the same kind of headroom Vue's 30s bound gives its own
799
+ // slower cold start (#2176), without inventing a shared literal (#2194).
800
+ verificationTimeoutMs: 2e4,
801
+ installStrategy: "npm",
802
+ packageName: "bash-language-server",
803
+ binaryName: "bash-language-server"
804
+ },
805
+ {
806
+ id: "fish-lsp",
807
+ name: "Fish Language Server",
808
+ checkCommand: "fish-lsp",
809
+ checkArgs: ["--version"],
810
+ installStrategy: "npm",
811
+ packageName: "fish-lsp",
812
+ binaryName: "fish-lsp"
813
+ },
814
+ {
815
+ id: "cmake-language-server",
816
+ name: "CMake Language Server",
817
+ checkCommand: "cmake-language-server",
818
+ checkArgs: ["--version"],
819
+ installStrategy: "pip",
820
+ packageName: "cmake-language-server",
821
+ binaryName: "cmake-language-server",
822
+ // Upstream 0.1.11 (the latest release) imports `LanguageServer` from
823
+ // `pygls.server`, a symbol pygls 2 removed, while declaring only
824
+ // `pygls>=1.1.1` — so an unconstrained install resolves pygls 2.1.1 and
825
+ // produces a launcher that cannot start. Measured against PyPI for the
826
+ // nightly's interpreter (#3311): `pip download --python-version 3.12
827
+ // cmake-language-server` → `pygls-2.1.1`; with this constraint →
828
+ // `pygls-1.3.1` + `lsprotocol-2023.0.1`, and `cmake-language-server
829
+ // --version` then prints `cmake-language-server 0.1.11` and exits 0.
830
+ pipConstraints: ["pygls<2"]
831
+ },
832
+ {
833
+ id: "yaml-language-server",
834
+ name: "YAML Language Server",
835
+ checkCommand: "yaml-language-server",
836
+ checkArgs: ["--version"],
837
+ installStrategy: "npm",
838
+ packageName: "yaml-language-server",
839
+ binaryName: "yaml-language-server"
840
+ },
841
+ {
842
+ id: "vscode-json-language-server",
843
+ name: "VSCode JSON Language Server",
844
+ checkCommand: "vscode-json-language-server",
845
+ checkArgs: ["--version"],
846
+ // The #2188 sweep measured an 11,047ms cold `--version` start with closed
847
+ // stdin — over the 10s installer default, so a cold or contended host can
848
+ // see a false verification degradation before the binary ever answers.
849
+ // 20s mirrors the bash-language-server bound above (#2194).
850
+ verificationTimeoutMs: 2e4,
851
+ installStrategy: "npm",
852
+ packageName: "vscode-langservers-extracted",
853
+ binaryName: "vscode-json-language-server"
854
+ },
855
+ {
856
+ id: "vscode-html-languageserver-bin",
857
+ name: "VSCode HTML Language Server",
858
+ checkCommand: "vscode-html-language-server",
859
+ checkArgs: ["--version"],
860
+ installStrategy: "npm",
861
+ packageName: "vscode-langservers-extracted",
862
+ binaryName: "vscode-html-language-server"
863
+ },
864
+ {
865
+ id: "htmlhint",
866
+ name: "HTMLHint",
867
+ checkCommand: "htmlhint",
868
+ checkArgs: ["--version"],
869
+ installStrategy: "npm",
870
+ packageName: "htmlhint",
871
+ binaryName: "htmlhint"
872
+ },
873
+ {
874
+ id: "hadolint",
875
+ name: "Hadolint",
876
+ checkCommand: "hadolint",
877
+ checkArgs: ["--version"],
878
+ installStrategy: "github",
879
+ binaryName: "hadolint",
880
+ github: {
881
+ repo: "hadolint/hadolint",
882
+ assetMatch: (platform, arch) => {
883
+ if (platform === "linux")
884
+ return arch === "arm64" ? "linux.aarch64" : "linux.x86_64";
885
+ if (platform === "darwin")
886
+ return arch === "arm64" ? "macos-arm64" : "macos-x86_64";
887
+ if (platform === "win32")
888
+ return "windows-x86_64.exe";
889
+ return void 0;
890
+ }
891
+ }
892
+ },
893
+ {
894
+ id: "helm",
895
+ name: "Helm",
896
+ checkCommand: "helm",
897
+ // intended: version --short — unverified against real binary (shape 16), do not wire until probed
898
+ checkArgs: ["--version"],
899
+ installStrategy: "github",
900
+ binaryName: "helm",
901
+ github: {
902
+ repo: "helm/helm",
903
+ assetMatch: (platform, arch) => {
904
+ const cpu = arch === "arm64" ? "arm64" : "amd64";
905
+ if (platform === "linux")
906
+ return `linux-${cpu}.tar.gz`;
907
+ if (platform === "darwin")
908
+ return `darwin-${cpu}.tar.gz`;
909
+ if (platform === "win32")
910
+ return `windows-${cpu}.zip`;
911
+ return void 0;
912
+ },
913
+ // Release archives nest the executable under an OS/arch directory;
914
+ // the installer searches recursively and adds the Windows suffix.
915
+ binaryInArchive: "helm"
916
+ }
917
+ },
918
+ {
919
+ // Opengrep: a single standalone binary per platform on GitHub releases —
920
+ // no login, no telemetry (the reason for switching off Semgrep, #111).
921
+ id: "opengrep",
922
+ name: "Opengrep",
923
+ checkCommand: "opengrep",
924
+ checkArgs: ["--version"],
925
+ installStrategy: "github",
926
+ binaryName: "opengrep",
927
+ github: {
928
+ repo: "opengrep/opengrep",
929
+ assetMatch: (platform, arch) => {
930
+ if (platform === "linux")
931
+ return arch === "arm64" ? "opengrep_manylinux_aarch64" : "opengrep_manylinux_x86";
932
+ if (platform === "darwin")
933
+ return arch === "arm64" ? "opengrep_osx_arm64" : "opengrep_osx_x86";
934
+ if (platform === "win32")
935
+ return "opengrep_windows_x86.exe";
936
+ return void 0;
937
+ }
938
+ }
939
+ },
940
+ {
941
+ id: "vscode-css-languageserver",
942
+ name: "VSCode CSS Language Server",
943
+ checkCommand: "vscode-css-language-server",
944
+ checkArgs: ["--version"],
945
+ installStrategy: "npm",
946
+ packageName: "vscode-langservers-extracted",
947
+ binaryName: "vscode-css-language-server"
948
+ },
949
+ {
950
+ id: "dockerfile-language-server-nodejs",
951
+ name: "Dockerfile Language Server",
952
+ checkCommand: "docker-langserver",
953
+ checkArgs: ["--version"],
954
+ installStrategy: "npm",
955
+ packageName: "dockerfile-language-server-nodejs",
956
+ binaryName: "docker-langserver"
957
+ },
958
+ {
959
+ // #2722: intelephense has no CLI — its entry calls createConnection()
960
+ // unconditionally — and Node prints the offending source line before the
961
+ // error. The bundle is one ~4 MB minified line, so the transport-required
962
+ // marker #208 rescues on lands at byte 4,154,741 of a 4,423,356-byte
963
+ // stderr while Node truncates piped stderr at 1 MiB on exit. Measured on
964
+ // intelephense@1.18.5, linux, Node v22.22.1:
965
+ // $ intelephense --version 2>&1 | wc -c -> 1048576
966
+ // $ intelephense --version 2>&1 | grep -c "Connection input stream is not set" -> 0
967
+ // $ intelephense --version 2>err.txt; wc -c < err.txt -> 4423356
968
+ // Same run over the alternatives, each with stdin closed the way
969
+ // verifyToolBinary closes it (`input: ""`):
970
+ // --help, -v, --socket=0 -> identical dump (exit 1, 4423356 bytes,
971
+ // marker at 4154741, 1048576 through a pipe, marker absent)
972
+ // --stdio -> exit 1 with ZERO bytes on either stream,
973
+ // so it carries no marker to rescue on either.
974
+ // No checkArgs value produces a verdict. Verified spawn-free instead.
975
+ id: "intelephense",
976
+ name: "Intelephense",
977
+ checkCommand: "intelephense",
978
+ checkArgs: ["--version"],
979
+ verification: "package-entry",
980
+ installStrategy: "npm",
981
+ packageName: "intelephense",
982
+ binaryName: "intelephense"
983
+ },
984
+ {
985
+ id: "@prisma/language-server",
986
+ name: "Prisma Language Server",
987
+ checkCommand: "prisma-language-server",
988
+ checkArgs: ["--version"],
989
+ // #2169: a real closed-stdin cold run measured 27,265ms, and a warm-cache
990
+ // rerun still took 9,860ms — well past the 10s installer default. 40s
991
+ // keeps the same margin-over-worst-observed ratio Vue's 30s bound uses
992
+ // (#2176) rather than trimming it for a slower binary.
993
+ verificationTimeoutMs: 4e4,
994
+ installStrategy: "npm",
995
+ packageName: "@prisma/language-server",
996
+ binaryName: "prisma-language-server"
997
+ },
998
+ {
999
+ id: "@vue/language-server",
1000
+ name: "Vue Language Server",
1001
+ checkCommand: "vue-language-server",
1002
+ checkArgs: ["--version"],
1003
+ // Vue's launcher loads the full language-service bundle before answering
1004
+ // --version. Its cold start exceeds the dispatch probe budget on some hosts
1005
+ // even though warm starts complete quickly (#2176).
1006
+ verificationTimeoutMs: 3e4,
1007
+ installStrategy: "npm",
1008
+ packageName: "@vue/language-server",
1009
+ binaryName: "vue-language-server"
1010
+ },
1011
+ {
1012
+ id: "svelte-language-server",
1013
+ name: "Svelte Language Server",
1014
+ checkCommand: "svelteserver",
1015
+ checkArgs: ["--version"],
1016
+ // #2169: a real closed-stdin cold run measured 12,410ms — over the 10s
1017
+ // installer default, matching the bash/JSON class of false verification
1018
+ // degradation from a cold-cache host (#2194). 20s mirrors that bound.
1019
+ verificationTimeoutMs: 2e4,
1020
+ installStrategy: "npm",
1021
+ packageName: "svelte-language-server",
1022
+ binaryName: "svelteserver"
1023
+ },
1024
+ {
1025
+ id: "markdownlint",
1026
+ name: "markdownlint-cli2",
1027
+ checkCommand: "markdownlint-cli2",
1028
+ // `--version` is interpreted as a file glob by markdownlint-cli2. Use
1029
+ // stdin with globs disabled so verification cannot scan the workspace.
1030
+ checkArgs: ["--no-globs", "-"],
1031
+ installStrategy: "npm",
1032
+ packageName: "markdownlint-cli2",
1033
+ binaryName: "markdownlint-cli2"
1034
+ },
1035
+ {
1036
+ id: "mypy",
1037
+ name: "mypy",
1038
+ checkCommand: "mypy",
1039
+ checkArgs: ["--version"],
1040
+ installStrategy: "pip",
1041
+ packageName: "mypy",
1042
+ binaryName: "mypy"
1043
+ },
1044
+ {
1045
+ id: "rubocop",
1046
+ name: "RuboCop",
1047
+ checkCommand: "rubocop",
1048
+ checkArgs: ["--version"],
1049
+ installStrategy: "gem",
1050
+ packageName: "rubocop",
1051
+ binaryName: "rubocop"
1052
+ },
1053
+ {
1054
+ id: "stylelint",
1055
+ name: "Stylelint",
1056
+ checkCommand: "stylelint",
1057
+ checkArgs: ["--version"],
1058
+ installStrategy: "npm",
1059
+ packageName: "stylelint",
1060
+ binaryName: "stylelint"
1061
+ },
1062
+ {
1063
+ id: "oxlint",
1064
+ name: "Oxlint",
1065
+ checkCommand: "oxlint",
1066
+ checkArgs: ["--version"],
1067
+ installStrategy: "npm",
1068
+ packageName: "oxlint",
1069
+ binaryName: "oxlint"
1070
+ },
1071
+ // GitHub release binaries
1072
+ {
1073
+ id: "shellcheck",
1074
+ name: "ShellCheck",
1075
+ checkCommand: "shellcheck",
1076
+ checkArgs: ["--version"],
1077
+ installStrategy: "github",
1078
+ binaryName: "shellcheck",
1079
+ github: {
1080
+ repo: "koalaman/shellcheck",
1081
+ assetMatch: (platform, arch) => {
1082
+ if (platform === "linux")
1083
+ return arch === "arm64" ? "linux.aarch64.tar.xz" : "linux.x86_64.tar.xz";
1084
+ if (platform === "darwin")
1085
+ return arch === "arm64" ? "darwin.aarch64.tar.xz" : "darwin.x86_64.tar.xz";
1086
+ if (platform === "win32")
1087
+ return "zip";
1088
+ return void 0;
1089
+ },
1090
+ binaryInArchive: "shellcheck"
1091
+ }
1092
+ },
1093
+ {
1094
+ id: "shfmt",
1095
+ name: "shfmt",
1096
+ checkCommand: "shfmt",
1097
+ checkArgs: ["--version"],
1098
+ installStrategy: "github",
1099
+ binaryName: "shfmt",
1100
+ github: {
1101
+ repo: "mvdan/sh",
1102
+ assetMatch: (platform, arch) => {
1103
+ if (platform === "linux")
1104
+ return arch === "arm64" ? "linux_arm64" : "linux_amd64";
1105
+ if (platform === "darwin")
1106
+ return arch === "arm64" ? "darwin_arm64" : "darwin_amd64";
1107
+ if (platform === "win32")
1108
+ return arch === "arm64" ? "windows_arm64.exe" : "windows_amd64.exe";
1109
+ return void 0;
1110
+ }
1111
+ // bare binary, no archive
1112
+ }
1113
+ },
1114
+ ...MANAGED_GITHUB_FORMATTERS.map(managedGitHubFormatterTool),
1115
+ ...MANAGED_MAVEN_FORMATTERS.map(managedMavenFormatterTool),
1116
+ {
1117
+ id: "rust-analyzer",
1118
+ name: "rust-analyzer",
1119
+ checkCommand: "rust-analyzer",
1120
+ checkArgs: ["--version"],
1121
+ installStrategy: "github",
1122
+ binaryName: "rust-analyzer",
1123
+ github: {
1124
+ repo: "rust-lang/rust-analyzer",
1125
+ assetMatch: (platform, arch) => {
1126
+ if (platform === "linux")
1127
+ return arch === "arm64" ? "aarch64-unknown-linux-gnu.gz" : "x86_64-unknown-linux-gnu.gz";
1128
+ if (platform === "darwin")
1129
+ return arch === "arm64" ? "aarch64-apple-darwin.gz" : "x86_64-apple-darwin.gz";
1130
+ if (platform === "win32")
1131
+ return "x86_64-pc-windows-msvc.zip";
1132
+ return void 0;
1133
+ }
1134
+ // Linux/macOS: bare .gz; Windows: .zip archive containing rust-analyzer.exe
1135
+ }
1136
+ },
1137
+ {
1138
+ // Alternate JS/TS LSP (fallback when the `typescript` server is unavailable
1139
+ // or disabled — e.g. Deno projects). Used as a managedToolId by DenoServer.
1140
+ // Every platform ships a .zip containing the `deno` binary (the github
1141
+ // strategy extracts it, as it does for rust-analyzer's Windows .zip).
1142
+ id: "deno",
1143
+ name: "Deno",
1144
+ checkCommand: "deno",
1145
+ checkArgs: ["--version"],
1146
+ installStrategy: "github",
1147
+ binaryName: "deno",
1148
+ github: {
1149
+ repo: "denoland/deno",
1150
+ assetMatch: (platform, arch) => {
1151
+ if (platform === "linux")
1152
+ return arch === "arm64" ? "deno-aarch64-unknown-linux-gnu.zip" : "deno-x86_64-unknown-linux-gnu.zip";
1153
+ if (platform === "darwin")
1154
+ return arch === "arm64" ? "deno-aarch64-apple-darwin.zip" : "deno-x86_64-apple-darwin.zip";
1155
+ if (platform === "win32")
1156
+ return "deno-x86_64-pc-windows-msvc.zip";
1157
+ return void 0;
1158
+ }
1159
+ }
1160
+ },
1161
+ {
1162
+ id: "golangci-lint",
1163
+ name: "golangci-lint",
1164
+ checkCommand: "golangci-lint",
1165
+ checkArgs: ["--version"],
1166
+ installStrategy: "github",
1167
+ binaryName: "golangci-lint",
1168
+ github: {
1169
+ repo: "golangci/golangci-lint",
1170
+ assetMatch: (platform, arch) => {
1171
+ if (platform === "linux")
1172
+ return arch === "arm64" ? "linux-arm64.tar.gz" : "linux-amd64.tar.gz";
1173
+ if (platform === "darwin")
1174
+ return arch === "arm64" ? "darwin-arm64.tar.gz" : "darwin-amd64.tar.gz";
1175
+ if (platform === "win32")
1176
+ return arch === "arm64" ? "windows-arm64.zip" : "windows-amd64.zip";
1177
+ return void 0;
1178
+ },
1179
+ binaryInArchive: "golangci-lint"
1180
+ }
1181
+ },
1182
+ {
1183
+ id: "ktlint",
1184
+ name: "ktlint",
1185
+ checkCommand: "ktlint",
1186
+ checkArgs: ["--version"],
1187
+ installStrategy: "github",
1188
+ binaryName: "ktlint",
1189
+ github: {
1190
+ // ktlint ships a self-executable `ktlint` (a JAR with a shell preamble)
1191
+ // for Linux/macOS, plus a `ktlint.bat` wrapper for Windows that runs
1192
+ // `java -jar %~dp0ktlint`. On Windows BOTH files are needed: the .bat AND
1193
+ // the `ktlint` jar it wraps (#218). No arm64-specific asset.
1194
+ repo: "pinterest/ktlint",
1195
+ assetMatch: (platform, _arch) => {
1196
+ if (platform === "linux")
1197
+ return "ktlint";
1198
+ if (platform === "darwin")
1199
+ return "ktlint";
1200
+ if (platform === "win32")
1201
+ return "ktlint.bat";
1202
+ return void 0;
1203
+ },
1204
+ extraAssets: (platform) => platform === "win32" ? ["ktlint"] : []
1205
+ }
1206
+ },
1207
+ {
1208
+ // ktfmt (Meta's opinionated Kotlin formatter) ships only as a Maven-Central
1209
+ // fat JAR — no native binary, no npm package — so it uses the maven strategy
1210
+ // (#129). Run via a `java -jar` launcher; requires a JRE.
1211
+ id: "ktfmt",
1212
+ name: "ktfmt",
1213
+ checkCommand: "ktfmt",
1214
+ checkArgs: ["--version"],
1215
+ installStrategy: "maven",
1216
+ binaryName: "ktfmt",
1217
+ maven: {
1218
+ groupId: "com.facebook",
1219
+ artifactId: "ktfmt",
1220
+ version: "0.63",
1221
+ classifier: "with-dependencies"
1222
+ }
1223
+ },
1224
+ {
1225
+ // SpotBugs (bytecode bug-pattern analyzer for Java/Kotlin/Scala/Groovy)
1226
+ // ships as a distribution archive — a lib/ of many JARs + bin/ launchers,
1227
+ // NOT a runnable fat JAR — so it uses the archive strategy, not maven
1228
+ // (refs #133). Requires a JRE (gated by the runner, not the install).
1229
+ id: "spotbugs",
1230
+ name: "SpotBugs",
1231
+ checkCommand: "spotbugs",
1232
+ // intended: -version — unverified against real binary (shape 16), do not wire until probed
1233
+ checkArgs: ["--version"],
1234
+ installStrategy: "archive",
1235
+ binaryName: "spotbugs",
1236
+ archive: {
1237
+ url: "https://github.com/spotbugs/spotbugs/releases/download/4.10.2/spotbugs-4.10.2.tgz",
1238
+ kind: "tgz",
1239
+ launcher: "bin/spotbugs",
1240
+ sha256: {
1241
+ "https://github.com/spotbugs/spotbugs/releases/download/4.10.2/spotbugs-4.10.2.tgz": "63d7687c35fba12cbc8e55ec2a889a2bbf1b9be299dea91f2b0d351dc285308a"
1242
+ }
1243
+ }
1244
+ },
1245
+ {
1246
+ // kotlin-language-server (fwcd, #3400) — a platform-agnostic launcher ZIP
1247
+ // (`server/{bin,lib}`, 87 MB) whose `bin/kotlin-language-server` script
1248
+ // starts a JVM on ANY argument and has no stable `--version`, so it is
1249
+ // verified at install by manifest, not by spawn: the pinned sha256, the
1250
+ // launcher on disk with an exec bit, and `java` on PATH or JAVA_HOME before
1251
+ // the download (`verification: "tree-manifest"`). The 2025 release publishes
1252
+ // no digest, so its pin is trust-on-first-use (the hash of the download). The initialize handshake is the nightly
1253
+ // tool-smoke `kotlin` row. fwcd 1.3.13 (2025-01-18) rather than JetBrains'
1254
+ // kotlin-lsp (v263.4702.0): its CDN ships the macOS standalone archives as
1255
+ // `.sit`, which this installer's tar/unzip/Expand-Archive paths cannot
1256
+ // extract, and its six per-platform archives would need six pins.
1257
+ id: "kotlin-language-server",
1258
+ name: "Kotlin Language Server",
1259
+ checkCommand: "kotlin-language-server",
1260
+ checkArgs: ["--version"],
1261
+ installStrategy: "archive",
1262
+ binaryName: "kotlin-language-server",
1263
+ verification: "tree-manifest",
1264
+ archive: {
1265
+ url: "https://github.com/fwcd/kotlin-language-server/releases/download/1.3.13/server.zip",
1266
+ kind: "zip",
1267
+ launcher: "bin/kotlin-language-server",
1268
+ runtime: "java",
1269
+ sha256: {
1270
+ "https://github.com/fwcd/kotlin-language-server/releases/download/1.3.13/server.zip": "4fe7d71d087b307c7869036171bd9d8c6a4284cd7c25b89098b0a24eb2d9b6d2"
1271
+ }
1272
+ }
1273
+ },
1274
+ {
1275
+ // PowerShell Editor Services (#278). NOT a single binary — a multi-folder
1276
+ // PowerShell MODULE BUNDLE launched via `pwsh Start-EditorServices.ps1
1277
+ // -Stdio` (see PowerShellServer.spawn). archive TREE BUNDLE: the release zip
1278
+ // extracts sibling module dirs (PowerShellEditorServices/, PSReadLine/,
1279
+ // PSScriptAnalyzer/) at the root with no wrapping dir, so stripComponents:0
1280
+ // + no launcher — the whole tree is kept and resolved to its extract dir.
1281
+ // checkCommand "pwsh" documents the runtime but is unused for resolution
1282
+ // (tree bundles resolve only via the extract dir + treeMarker).
1283
+ id: "powershell-editor-services",
1284
+ name: "PowerShell Editor Services",
1285
+ checkCommand: "pwsh",
1286
+ checkArgs: [
1287
+ "-NoProfile",
1288
+ "-Command",
1289
+ "$PSVersionTable.PSVersion.ToString()"
1290
+ ],
1291
+ installStrategy: "archive",
1292
+ binaryName: "powershell-editor-services",
1293
+ archive: {
1294
+ url: "https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.6.0/PowerShellEditorServices.zip",
1295
+ kind: "zip",
1296
+ stripComponents: 0,
1297
+ treeMarker: "PowerShellEditorServices/Start-EditorServices.ps1",
1298
+ sha256: {
1299
+ "https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.6.0/PowerShellEditorServices.zip": "0d91898f73d4faeb64291336f6386f0c890a933df012827571adf7008480a04a"
1300
+ }
1301
+ }
1302
+ },
1303
+ {
1304
+ // clangd (C/C++/Obj-C LSP, #241) — a self-contained native TREE BUNDLE: the
1305
+ // release zip wraps `clangd_<ver>/{bin,lib}` (bin/clangd[.exe] + the bundled
1306
+ // libclang headers under lib/), so stripComponents:1 drops the version dir and
1307
+ // the whole tree is kept (no launcher). Unlike PSES there is no external
1308
+ // runtime — CppServer launches `<bundle>/bin/clangd` directly. checkCommand
1309
+ // documents the binary but is unused for resolution (tree bundles resolve only
1310
+ // via the extract dir + treeMarker). Platform-matched url: clangd ships x64
1311
+ // prebuilts; arm runs the x64 build under Rosetta/emulation (darwin/win32),
1312
+ // while linux/arm64 has no official build → undefined (graceful unavailable).
1313
+ id: "clangd",
1314
+ name: "clangd",
1315
+ checkCommand: "clangd",
1316
+ checkArgs: ["--version"],
1317
+ installStrategy: "archive",
1318
+ binaryName: "clangd",
1319
+ archive: {
1320
+ url: (platform, arch) => {
1321
+ const version = "22.1.0";
1322
+ const base = `https://github.com/clangd/clangd/releases/download/${version}`;
1323
+ if (platform === "linux")
1324
+ return arch === "x64" ? `${base}/clangd-linux-${version}.zip` : void 0;
1325
+ if (platform === "darwin")
1326
+ return `${base}/clangd-mac-${version}.zip`;
1327
+ if (platform === "win32")
1328
+ return `${base}/clangd-windows-${version}.zip`;
1329
+ return void 0;
1330
+ },
1331
+ kind: "zip",
1332
+ stripComponents: 1,
1333
+ treeMarker: "bin",
1334
+ sha256: {
1335
+ "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-linux-22.1.0.zip": "c54e57dbff3ccc9e8352367ddb7030ad3f624073ec58c7477424e7919f578572",
1336
+ "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-mac-22.1.0.zip": "71eddc5303da9a5bc5e8b509488b5b2c5acf45f20e33b8394e71a12a56d67198",
1337
+ "https://github.com/clangd/clangd/releases/download/22.1.0/clangd-windows-22.1.0.zip": "e31e271fe11f6dcd7cf87ca74be4a12788ff8ce5a0b07762583e335c058e939a"
1338
+ }
1339
+ }
1340
+ },
1341
+ {
1342
+ // lua-language-server (#564, split from #241) — same self-contained native
1343
+ // TREE BUNDLE shape as clangd: bin/lua-language-server[.exe] + bundled
1344
+ // locale/meta files, no external runtime. UNLIKE clangd, the release
1345
+ // archive has NO wrapping version dir (verified by inspecting the actual
1346
+ // 3.18.2 linux-x64 .tar.gz and win32-x64 .zip contents: `bin/`, `LICENSE`,
1347
+ // `locale/`, … sit at archive root) — so stripComponents:0, not 1.
1348
+ // LuaServer launches `<bundle>/bin/lua-language-server` directly.
1349
+ // checkCommand documents the binary but is unused for resolution (tree
1350
+ // bundles resolve only via the extract dir + treeMarker). Platform-matched
1351
+ // url: LuaLS publishes darwin/linux x64+arm64 and win32 x64 (no win32/arm64
1352
+ // build as of 3.18.2 → undefined, graceful unavailable); asset naming
1353
+ // verified against the live GitHub release listing, not guessed.
1354
+ id: "lua-language-server",
1355
+ name: "lua-language-server",
1356
+ checkCommand: "lua-language-server",
1357
+ checkArgs: ["--version"],
1358
+ installStrategy: "archive",
1359
+ binaryName: "lua-language-server",
1360
+ archive: {
1361
+ url: (platform, arch) => {
1362
+ const version = "3.18.2";
1363
+ const base = `https://github.com/LuaLS/lua-language-server/releases/download/${version}`;
1364
+ if (platform === "linux")
1365
+ return arch === "arm64" ? `${base}/lua-language-server-${version}-linux-arm64.tar.gz` : `${base}/lua-language-server-${version}-linux-x64.tar.gz`;
1366
+ if (platform === "darwin")
1367
+ return arch === "arm64" ? `${base}/lua-language-server-${version}-darwin-arm64.tar.gz` : `${base}/lua-language-server-${version}-darwin-x64.tar.gz`;
1368
+ if (platform === "win32")
1369
+ return arch === "arm64" ? void 0 : `${base}/lua-language-server-${version}-win32-x64.zip`;
1370
+ return void 0;
1371
+ },
1372
+ kind: (platform) => platform === "win32" ? "zip" : "tgz",
1373
+ stripComponents: 0,
1374
+ treeMarker: "bin",
1375
+ sha256: {
1376
+ "https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-linux-arm64.tar.gz": "273af33f26f4a1143f27c96d9f9e1188aba619c71e0807042134f66b4bd27f24",
1377
+ "https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-linux-x64.tar.gz": "ca71415dd19f19e30aaa35a4915aefca9fdb5fec31b98331cc3d77f778d539c5",
1378
+ "https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-darwin-arm64.tar.gz": "cec99d70b1f612acec4a10a79a03664e3aa0c229d4d8a586cb3f928ec37d509e",
1379
+ "https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-darwin-x64.tar.gz": "e26cfefe423dd7326fc7c649539e4d4aaa4f35f34d2fefd8af2ed7090b72c556",
1380
+ "https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-win32-x64.zip": "a4439a8f5e8e9e6505c11f045a7bf45db602124a1e246371c1dbe34924f3cf71"
1381
+ }
1382
+ }
1383
+ },
1384
+ {
1385
+ id: "actionlint",
1386
+ name: "actionlint",
1387
+ checkCommand: "actionlint",
1388
+ checkArgs: ["--version"],
1389
+ installStrategy: "github",
1390
+ binaryName: "actionlint",
1391
+ github: {
1392
+ repo: "rhysd/actionlint",
1393
+ assetMatch: (platform, arch) => {
1394
+ if (platform === "linux")
1395
+ return arch === "arm64" ? "linux_arm64.tar.gz" : "linux_amd64.tar.gz";
1396
+ if (platform === "darwin")
1397
+ return arch === "arm64" ? "darwin_arm64.tar.gz" : "darwin_amd64.tar.gz";
1398
+ if (platform === "win32")
1399
+ return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
1400
+ return void 0;
1401
+ },
1402
+ binaryInArchive: "actionlint"
1403
+ }
1404
+ },
1405
+ {
1406
+ // zizmor: GitHub Actions workflow security scanner that speaks LSP (#272).
1407
+ // cargo-dist release archives, one per target triple, each holding a single
1408
+ // `zizmor` binary (extracted via the recursive binary find). Online audits
1409
+ // (known-vulnerable-actions, unpinned-uses, …) need a GitHub token — the LSP
1410
+ // spawn forwards one via resolveZizmorGitHubToken (clients/zizmor-config.ts).
1411
+ id: "zizmor",
1412
+ name: "zizmor",
1413
+ checkCommand: "zizmor",
1414
+ checkArgs: ["--version"],
1415
+ installStrategy: "github",
1416
+ binaryName: "zizmor",
1417
+ github: {
1418
+ repo: "zizmorcore/zizmor",
1419
+ assetMatch: (platform, arch) => {
1420
+ if (platform === "linux")
1421
+ return arch === "arm64" ? "aarch64-unknown-linux-gnu.tar.gz" : "x86_64-unknown-linux-gnu.tar.gz";
1422
+ if (platform === "darwin")
1423
+ return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
1424
+ if (platform === "win32")
1425
+ return "x86_64-pc-windows-msvc.zip";
1426
+ return void 0;
1427
+ },
1428
+ binaryInArchive: "zizmor"
1429
+ }
1430
+ },
1431
+ {
1432
+ // typos-lsp: source-code spell checker that speaks LSP (#283). cargo-dist
1433
+ // release archives, one per target triple, each holding a single `typos-lsp`
1434
+ // binary (extracted via the recursive binary find). NO token / network — the
1435
+ // dictionary is compiled in. The binary takes no `--version` (it ignores args
1436
+ // and serves the LSP on stdin/stdout); the PATH probe ignores checkArgs and
1437
+ // verifyToolBinary runs with stdin:ignore so the server gets EOF and exits.
1438
+ id: "typos-lsp",
1439
+ name: "typos-lsp",
1440
+ checkCommand: "typos-lsp",
1441
+ checkArgs: ["--version"],
1442
+ installStrategy: "github",
1443
+ binaryName: "typos-lsp",
1444
+ github: {
1445
+ repo: "tekumara/typos-lsp",
1446
+ assetMatch: (platform, arch) => {
1447
+ if (platform === "linux")
1448
+ return arch === "arm64" ? "aarch64-unknown-linux-gnu.tar.gz" : "x86_64-unknown-linux-gnu.tar.gz";
1449
+ if (platform === "darwin")
1450
+ return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
1451
+ if (platform === "win32")
1452
+ return arch === "arm64" ? "aarch64-pc-windows-msvc.zip" : "x86_64-pc-windows-msvc.zip";
1453
+ return void 0;
1454
+ },
1455
+ binaryInArchive: "typos-lsp"
1456
+ }
1457
+ },
1458
+ {
1459
+ id: "tflint",
1460
+ name: "tflint",
1461
+ checkCommand: "tflint",
1462
+ checkArgs: ["--version"],
1463
+ installStrategy: "github",
1464
+ binaryName: "tflint",
1465
+ github: {
1466
+ repo: "terraform-linters/tflint",
1467
+ assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
1468
+ binaryInArchive: "tflint"
1469
+ }
1470
+ },
1471
+ {
1472
+ // Terragrunt ships a bare native binary per platform on GitHub releases.
1473
+ // Windows arm64 uses the x64 binary through Windows' built-in emulation —
1474
+ // there is no terragrunt_windows_arm64.exe upstream.
1475
+ id: "terragrunt",
1476
+ name: "terragrunt",
1477
+ checkCommand: "terragrunt",
1478
+ checkArgs: ["--version"],
1479
+ installStrategy: "github",
1480
+ binaryName: "terragrunt",
1481
+ github: {
1482
+ repo: "gruntwork-io/terragrunt",
1483
+ assetMatch: archAssetMatch({
1484
+ linux: {
1485
+ x64: "terragrunt_linux_amd64",
1486
+ arm64: "terragrunt_linux_arm64"
1487
+ },
1488
+ darwin: {
1489
+ x64: "terragrunt_darwin_amd64",
1490
+ arm64: "terragrunt_darwin_arm64"
1491
+ },
1492
+ win32: {
1493
+ x64: "terragrunt_windows_amd64.exe",
1494
+ arm64: "terragrunt_windows_amd64.exe"
1495
+ }
1496
+ })
1497
+ // bare binary — no binaryInArchive
1498
+ }
1499
+ },
1500
+ {
1501
+ id: "gitleaks",
1502
+ name: "gitleaks",
1503
+ checkCommand: "gitleaks",
1504
+ // intended: version — unverified against real binary (shape 16), do not wire until probed
1505
+ checkArgs: ["--version"],
1506
+ installStrategy: "github",
1507
+ binaryName: "gitleaks",
1508
+ github: {
1509
+ repo: "gitleaks/gitleaks",
1510
+ // gitleaks asset naming uses `x64` not `amd64` (unlike most Go-built
1511
+ // tools). Substring match is exact-enough — release assets are
1512
+ // named e.g. `gitleaks_8.18.4_linux_x64.tar.gz`.
1513
+ assetMatch: archAssetMatch({
1514
+ linux: { x64: "linux_x64.tar.gz", arm64: "linux_arm64.tar.gz" },
1515
+ darwin: { x64: "darwin_x64.tar.gz", arm64: "darwin_arm64.tar.gz" },
1516
+ win32: { x64: "windows_x64.zip", arm64: "windows_arm64.zip" }
1517
+ }),
1518
+ binaryInArchive: "gitleaks"
1519
+ }
1520
+ },
1521
+ {
1522
+ id: "trivy",
1523
+ name: "Trivy",
1524
+ checkCommand: "trivy",
1525
+ checkArgs: ["--version"],
1526
+ installStrategy: "github",
1527
+ binaryName: "trivy",
1528
+ github: {
1529
+ repo: "aquasecurity/trivy",
1530
+ // Trivy asset naming is `trivy_<ver>_<OS>-<bits>.{tar.gz,zip}` with a
1531
+ // capitalized OS and `64bit`/`ARM64` arch tokens — e.g.
1532
+ // `trivy_0.71.2_Linux-64bit.tar.gz`, `trivy_0.71.2_macOS-ARM64.tar.gz`.
1533
+ // No windows-arm64 asset exists (win32.arm64 omitted), so (like
1534
+ // swiftlint) trivy is absent from GITHUB_TOOLS and covered by the
1535
+ // weaker "at least one platform" guard.
1536
+ assetMatch: archAssetMatch({
1537
+ linux: { x64: "Linux-64bit.tar.gz", arm64: "Linux-ARM64.tar.gz" },
1538
+ darwin: { x64: "macOS-64bit.tar.gz", arm64: "macOS-ARM64.tar.gz" },
1539
+ win32: { x64: "windows-64bit.zip" }
1540
+ }),
1541
+ binaryInArchive: "trivy"
1542
+ }
1543
+ },
1544
+ {
1545
+ id: "swiftlint",
1546
+ name: "SwiftLint",
1547
+ checkCommand: "swiftlint",
1548
+ checkArgs: ["--version"],
1549
+ installStrategy: "github",
1550
+ binaryName: "swiftlint",
1551
+ github: {
1552
+ repo: "realm/SwiftLint",
1553
+ assetMatch: (platform, arch) => {
1554
+ if (platform === "darwin")
1555
+ return "portable_swiftlint.zip";
1556
+ if (platform === "linux")
1557
+ return arch === "arm64" ? "swiftlint_linux_arm64.zip" : "swiftlint_linux_amd64.zip";
1558
+ return void 0;
1559
+ },
1560
+ binaryInArchive: "swiftlint"
1561
+ }
1562
+ },
1563
+ {
1564
+ id: "taplo",
1565
+ name: "taplo",
1566
+ checkCommand: "taplo",
1567
+ checkArgs: ["--version"],
1568
+ installStrategy: "github",
1569
+ binaryName: "taplo",
1570
+ github: {
1571
+ repo: "tamasfe/taplo",
1572
+ assetMatch: (platform, arch) => {
1573
+ if (platform === "linux")
1574
+ return arch === "arm64" ? "taplo-linux-aarch64.gz" : "taplo-linux-x86_64.gz";
1575
+ if (platform === "darwin")
1576
+ return arch === "arm64" ? "taplo-darwin-aarch64.gz" : "taplo-darwin-x86_64.gz";
1577
+ if (platform === "win32")
1578
+ return "taplo-windows-x86_64.gz";
1579
+ return void 0;
1580
+ }
1581
+ }
1582
+ },
1583
+ {
1584
+ id: "vale",
1585
+ name: "Vale",
1586
+ checkCommand: "vale",
1587
+ checkArgs: ["--version"],
1588
+ installStrategy: "github",
1589
+ binaryName: "vale",
1590
+ github: {
1591
+ repo: "vale-cli/vale",
1592
+ assetMatch: (platform, arch) => {
1593
+ const version = "3.14.2";
1594
+ if (platform === "linux")
1595
+ return arch === "arm64" ? `vale_${version}_Linux_arm64.tar.gz` : `vale_${version}_Linux_64-bit.tar.gz`;
1596
+ if (platform === "darwin")
1597
+ return arch === "arm64" ? `vale_${version}_macOS_arm64.tar.gz` : `vale_${version}_macOS_64-bit.tar.gz`;
1598
+ if (platform === "win32")
1599
+ return `vale_${version}_Windows_64-bit.zip`;
1600
+ return void 0;
1601
+ },
1602
+ binaryInArchive: "vale"
1603
+ }
1604
+ },
1605
+ {
1606
+ id: "terraform-ls",
1607
+ name: "terraform-ls",
1608
+ checkCommand: "terraform-ls",
1609
+ // intended: version — unverified against real binary (shape 16), do not wire until probed
1610
+ checkArgs: ["--version"],
1611
+ installStrategy: "github",
1612
+ binaryName: "terraform-ls",
1613
+ github: {
1614
+ repo: "hashicorp/terraform-ls",
1615
+ hashiCorpReleaseProduct: "terraform-ls",
1616
+ assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
1617
+ binaryInArchive: "terraform-ls"
1618
+ }
1619
+ },
1620
+ {
1621
+ id: "zls",
1622
+ name: "zls",
1623
+ checkCommand: "zls",
1624
+ checkArgs: ["--version"],
1625
+ installStrategy: "github",
1626
+ binaryName: "zls",
1627
+ github: {
1628
+ repo: "zigtools/zls",
1629
+ assetMatch: (platform, arch) => {
1630
+ if (platform === "linux")
1631
+ return arch === "arm64" ? "aarch64-linux.tar.xz" : "x86_64-linux.tar.xz";
1632
+ if (platform === "darwin")
1633
+ return arch === "arm64" ? "aarch64-macos.tar.xz" : "x86_64-macos.tar.xz";
1634
+ if (platform === "win32")
1635
+ return arch === "arm64" ? "aarch64-windows.zip" : "x86_64-windows.zip";
1636
+ return void 0;
1637
+ },
1638
+ binaryInArchive: "zls"
1639
+ }
1640
+ },
1641
+ {
1642
+ // clojure-lsp ships a self-contained native (GraalVM) binary per platform
1643
+ // on GitHub releases — no JVM needed. Used as managedToolId by ClojureServer.
1644
+ // The .zip carries the bare binary (located recursively on extract).
1645
+ id: "clojure-lsp",
1646
+ name: "clojure-lsp",
1647
+ checkCommand: "clojure-lsp",
1648
+ checkArgs: ["--version"],
1649
+ installStrategy: "github",
1650
+ binaryName: "clojure-lsp",
1651
+ github: {
1652
+ repo: "clojure-lsp/clojure-lsp",
1653
+ assetMatch: (platform, arch) => {
1654
+ if (platform === "linux")
1655
+ return arch === "arm64" ? "native-linux-aarch64.zip" : "native-linux-amd64.zip";
1656
+ if (platform === "darwin")
1657
+ return arch === "arm64" ? "native-macos-aarch64.zip" : "native-macos-amd64.zip";
1658
+ if (platform === "win32")
1659
+ return "native-windows-amd64.zip";
1660
+ return void 0;
1661
+ },
1662
+ binaryInArchive: "clojure-lsp"
1663
+ }
1664
+ },
1665
+ {
1666
+ // CUE ships a single native binary per platform on GitHub releases;
1667
+ // the LSP runs via `cue lsp serve`. Used as managedToolId by CueServer.
1668
+ id: "cue",
1669
+ name: "CUE",
1670
+ checkCommand: "cue",
1671
+ // Probed locally with CUE v0.17.1: `cue version` exits 0.
1672
+ checkArgs: ["version"],
1673
+ installStrategy: "github",
1674
+ binaryName: "cue",
1675
+ github: {
1676
+ repo: "cue-lang/cue",
1677
+ assetMatch: (platform, arch) => {
1678
+ if (platform === "linux")
1679
+ return arch === "arm64" ? "linux_arm64.tar.gz" : "linux_amd64.tar.gz";
1680
+ if (platform === "darwin")
1681
+ return arch === "arm64" ? "darwin_arm64.tar.gz" : "darwin_amd64.tar.gz";
1682
+ if (platform === "win32")
1683
+ return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
1684
+ return void 0;
1685
+ },
1686
+ binaryInArchive: "cue"
1687
+ }
1688
+ },
1689
+ {
1690
+ // gleam ships a single static binary per platform on GitHub releases; the
1691
+ // LSP runs via `gleam lsp`. Used as managedToolId by GleamServer. The linux
1692
+ // build is a FLAT musl tarball (a bare `gleam`), handled by the recursive
1693
+ // tar-binary lookup in installGitHubTool.
1694
+ id: "gleam",
1695
+ name: "Gleam",
1696
+ checkCommand: "gleam",
1697
+ checkArgs: ["--version"],
1698
+ installStrategy: "github",
1699
+ binaryName: "gleam",
1700
+ github: {
1701
+ repo: "gleam-lang/gleam",
1702
+ assetMatch: (platform, arch) => {
1703
+ if (platform === "linux")
1704
+ return arch === "arm64" ? "aarch64-unknown-linux-musl.tar.gz" : "x86_64-unknown-linux-musl.tar.gz";
1705
+ if (platform === "darwin")
1706
+ return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
1707
+ if (platform === "win32")
1708
+ return arch === "arm64" ? "aarch64-pc-windows-msvc.zip" : "x86_64-pc-windows-msvc.zip";
1709
+ return void 0;
1710
+ },
1711
+ binaryInArchive: "gleam"
1712
+ }
1713
+ },
1714
+ {
1715
+ // Tinymist publishes cargo-dist archives containing the `tinymist` binary
1716
+ // for the supported desktop targets. The LSP server uses `tinymist lsp`.
1717
+ id: "tinymist",
1718
+ name: "Tinymist",
1719
+ checkCommand: "tinymist",
1720
+ checkArgs: ["--version"],
1721
+ installStrategy: "github",
1722
+ binaryName: "tinymist",
1723
+ github: {
1724
+ repo: "Myriad-Dreamin/tinymist",
1725
+ assetMatch: archAssetMatch({
1726
+ linux: {
1727
+ x64: "tinymist-x86_64-unknown-linux-gnu.tar.gz",
1728
+ arm64: "tinymist-aarch64-unknown-linux-gnu.tar.gz"
1729
+ },
1730
+ darwin: {
1731
+ x64: "tinymist-x86_64-apple-darwin.tar.gz",
1732
+ arm64: "tinymist-aarch64-apple-darwin.tar.gz"
1733
+ },
1734
+ win32: {
1735
+ x64: "tinymist-x86_64-pc-windows-msvc.zip",
1736
+ arm64: "tinymist-aarch64-pc-windows-msvc.zip"
1737
+ }
1738
+ }),
1739
+ binaryInArchive: "tinymist"
1740
+ }
1741
+ },
1742
+ {
1743
+ // marksman ships a single BARE (uncompressed) binary per platform on GitHub
1744
+ // releases — no archive, so it lands via the bare-binary branch of
1745
+ // installGitHubTool (the `else` that writes the asset directly, like shfmt).
1746
+ // Used as managedToolId by MarksmanServer; LSP entrypoint is `marksman
1747
+ // server` (stdio). macOS ships a universal binary; Windows has only x64
1748
+ // (runs on arm64 via emulation) — so all six platform/arch combos resolve.
1749
+ id: "marksman",
1750
+ name: "Marksman",
1751
+ checkCommand: "marksman",
1752
+ checkArgs: ["--version"],
1753
+ installStrategy: "github",
1754
+ binaryName: "marksman",
1755
+ github: {
1756
+ repo: "artempyanykh/marksman",
1757
+ assetMatch: (platform, arch) => {
1758
+ if (platform === "linux")
1759
+ return arch === "arm64" ? "marksman-linux-arm64" : "marksman-linux-x64";
1760
+ if (platform === "darwin")
1761
+ return "marksman-macos";
1762
+ if (platform === "win32")
1763
+ return "marksman.exe";
1764
+ return void 0;
1765
+ }
1766
+ // bare binary — no binaryInArchive
1767
+ }
1768
+ },
1769
+ {
1770
+ // Expert ships a bare native binary per platform on GitHub releases. Its
1771
+ // `--stdio` flag is required to start the LSP transport. Windows arm64 uses
1772
+ // the x64 binary through Windows' built-in x64 emulation.
1773
+ id: "expert",
1774
+ name: "Expert",
1775
+ checkCommand: "expert",
1776
+ checkArgs: ["--version"],
1777
+ installStrategy: "github",
1778
+ binaryName: "expert",
1779
+ github: {
1780
+ repo: "expert-lsp/expert",
1781
+ assetMatch: (platform, arch) => {
1782
+ if (arch !== "x64" && arch !== "arm64")
1783
+ return void 0;
1784
+ if (platform === "linux")
1785
+ return arch === "arm64" ? "expert_linux_arm64" : "expert_linux_amd64";
1786
+ if (platform === "darwin")
1787
+ return arch === "arm64" ? "expert_darwin_arm64" : "expert_darwin_amd64";
1788
+ if (platform === "win32")
1789
+ return "expert_windows_amd64.exe";
1790
+ return void 0;
1791
+ }
1792
+ // bare binary — no binaryInArchive
1793
+ }
1794
+ }
1795
+ ];
1796
+ function getToolVerificationTimeout(tool) {
1797
+ return tool.verificationTimeoutMs ?? 1e4;
1798
+ }
1799
+ var ensureInFlight = /* @__PURE__ */ new Map();
1800
+ var installFailureReasons = /* @__PURE__ */ new Map();
1801
+ function getInstallFailureReason(toolId) {
1802
+ return installFailureReasons.get(toolId);
1803
+ }
1804
+ var installAttempts = /* @__PURE__ */ new Map();
1805
+ function noteInstallAttempt(toolId, outcome, reason) {
1806
+ installAttempts.set(toolId, { outcome, reason, at: Date.now() });
1807
+ }
1808
+ function noteInstallAttemptIfUnrecorded(toolId, outcome, reason) {
1809
+ if (installAttempts.has(toolId))
1810
+ return;
1811
+ noteInstallAttempt(toolId, outcome, reason);
1812
+ }
1813
+ function getInstallAttempt(toolId) {
1814
+ return installAttempts.get(toolId);
1815
+ }
1816
+ var lastEnsureResolutionSource = /* @__PURE__ */ new Map();
1817
+ function getLastEnsureResolutionSource(toolId) {
1818
+ return lastEnsureResolutionSource.get(toolId);
1819
+ }
1820
+ var resolvedPathCache = new BoundedLruCache(256);
1821
+ function resetResolvedPathCache() {
1822
+ resolvedPathCache.clear();
1823
+ }
1824
+ var PROBE_CACHE_PATH = path3.join(getGlobalPiLensDir(), "probe-cache.json");
1825
+ var PROBE_CACHE_LOCK_STALE_MS = 18e4;
1826
+ var PROBE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1827
+ var PROBE_CACHE_TRANSIENT_COOLDOWN_MS = TRANSIENT_MAX_COOLDOWN_MS;
1828
+ var PROBE_CACHE_FLUSH_LOCK_WAIT_MS = 250;
1829
+ var PROBE_CACHE_FLUSH_RETRY_DELAY_MS = 300;
1830
+ var PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS = 3e4;
1831
+ var _probeCache = null;
1832
+ var _probeCacheDirty = false;
1833
+ var _probeCacheFlushTimer = null;
1834
+ var _probeCacheWriteInFlight = null;
1835
+ var _probeCacheRetryAttempt = 0;
1836
+ var _probeCacheChangeGeneration = 0;
1837
+ var _probeCacheChanges = /* @__PURE__ */ new Map();
1838
+ var _probeCacheChangeVersions = /* @__PURE__ */ new Map();
1839
+ async function readProbeCache() {
1840
+ if (_probeCache !== null)
1841
+ return _probeCache;
1842
+ try {
1843
+ const raw = await fs2.readFile(PROBE_CACHE_PATH, "utf-8");
1844
+ const parsed = JSON.parse(raw);
1845
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1846
+ throw new Error("probe-cache root is not an object");
1847
+ }
1848
+ _probeCache = parsed;
1849
+ } catch (err) {
1850
+ const code = err?.code;
1851
+ if (code !== "ENOENT") {
1852
+ logSessionStart(`auto-install probe-cache: read failed (${code ?? "invalid"}); treating cache as unavailable`);
1853
+ }
1854
+ _probeCache = {};
1855
+ }
1856
+ return _probeCache;
1857
+ }
1858
+ function markProbeCacheChange(toolId, entry) {
1859
+ _probeCacheChanges.set(toolId, entry);
1860
+ _probeCacheChangeVersions.set(toolId, ++_probeCacheChangeGeneration);
1861
+ _probeCacheDirty = true;
1862
+ scheduleProbeFlush();
1863
+ }
1864
+ function scheduleProbeFlush(delayMs = PROBE_CACHE_FLUSH_RETRY_DELAY_MS) {
1865
+ if (_probeCacheFlushTimer !== null)
1866
+ return;
1867
+ _probeCacheFlushTimer = setTimeout(() => {
1868
+ void flushProbeCache();
1869
+ }, delayMs);
1870
+ _probeCacheFlushTimer.unref?.();
1871
+ }
1872
+ function scheduleProbeFlushRetry() {
1873
+ const delay = Math.min(PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS, PROBE_CACHE_FLUSH_RETRY_DELAY_MS * 2 ** Math.min(_probeCacheRetryAttempt, 6));
1874
+ _probeCacheRetryAttempt += 1;
1875
+ scheduleProbeFlush(delay);
1876
+ }
1877
+ function snapshotProbeCacheChanges() {
1878
+ return {
1879
+ changes: new Map(_probeCacheChanges),
1880
+ versions: new Map(_probeCacheChangeVersions)
1881
+ };
1882
+ }
1883
+ function deserializeProbeCache(contents) {
1884
+ if (contents === void 0)
1885
+ return {};
1886
+ try {
1887
+ const parsed = JSON.parse(contents);
1888
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1889
+ throw new Error("probe-cache root is not an object");
1890
+ }
1891
+ return parsed;
1892
+ } catch (err) {
1893
+ logSessionStart(`auto-install probe-cache: write-side read was corrupt (${err.message}); recovering as empty`);
1894
+ return {};
1895
+ }
1896
+ }
1897
+ function applyProbeCacheChanges(disk, changes) {
1898
+ for (const [toolId, entry] of changes) {
1899
+ if (entry === null)
1900
+ delete disk[toolId];
1901
+ else
1902
+ disk[toolId] = entry;
1903
+ }
1904
+ }
1905
+ function ageProbeCache(disk) {
1906
+ const now = Date.now();
1907
+ for (const [toolId, entry] of Object.entries(disk)) {
1908
+ const ttl = entry.transient ? PROBE_CACHE_TRANSIENT_COOLDOWN_MS : PROBE_CACHE_TTL_MS;
1909
+ if (!Number.isFinite(entry.cachedAt) || entry.cachedAt < now - ttl) {
1910
+ delete disk[toolId];
1911
+ }
1912
+ }
1913
+ }
1914
+ function publishProbeCacheWrite(disk, snapshotVersions) {
1915
+ const pendingAfterWrite = new Map(_probeCacheChanges);
1916
+ _probeCache = disk;
1917
+ for (const [toolId, entry] of pendingAfterWrite) {
1918
+ if (entry === null)
1919
+ delete _probeCache[toolId];
1920
+ else
1921
+ _probeCache[toolId] = entry;
1922
+ }
1923
+ for (const [toolId, version] of snapshotVersions) {
1924
+ if (_probeCacheChangeVersions.get(toolId) === version) {
1925
+ _probeCacheChanges.delete(toolId);
1926
+ _probeCacheChangeVersions.delete(toolId);
1927
+ }
1928
+ }
1929
+ _probeCacheDirty = _probeCacheChanges.size > 0;
1930
+ _probeCacheRetryAttempt = 0;
1931
+ return _probeCacheDirty ? "written-with-pending" : "written";
1932
+ }
1933
+ async function writeProbeCache() {
1934
+ try {
1935
+ const { changes, versions } = snapshotProbeCacheChanges();
1936
+ let result = "written";
1937
+ const committed = await commitDurableStoreAsync({
1938
+ path: PROBE_CACHE_PATH,
1939
+ deserialize: deserializeProbeCache,
1940
+ merge: (disk) => {
1941
+ ageProbeCache(disk);
1942
+ applyProbeCacheChanges(disk, changes);
1943
+ return disk;
1944
+ },
1945
+ serialize: (disk) => JSON.stringify(disk, null, 2),
1946
+ waitMs: PROBE_CACHE_FLUSH_LOCK_WAIT_MS,
1947
+ retryMs: 25,
1948
+ staleMs: PROBE_CACHE_LOCK_STALE_MS,
1949
+ timeoutMessage: "Timed out waiting for probe-cache lock",
1950
+ onContention: "skip-log",
1951
+ logContention: () => {
1952
+ logSessionStart("auto-install probe-cache: flush deferred because another process owns the lock");
1953
+ },
1954
+ afterWriteLocked: (disk) => {
1955
+ result = publishProbeCacheWrite(disk, versions);
1956
+ }
1957
+ });
1958
+ if (committed === void 0) {
1959
+ scheduleProbeFlushRetry();
1960
+ return "deferred";
1961
+ }
1962
+ return result;
1963
+ } catch (err) {
1964
+ logSessionStart(`auto-install probe-cache: flush failed (${err?.code ?? "write error"}); pending update retained`);
1965
+ scheduleProbeFlushRetry();
1966
+ return "failed";
1967
+ }
1968
+ }
1969
+ async function flushProbeCache() {
1970
+ if (_probeCacheFlushTimer !== null) {
1971
+ clearTimeout(_probeCacheFlushTimer);
1972
+ _probeCacheFlushTimer = null;
1973
+ }
1974
+ if (_probeCacheWriteInFlight) {
1975
+ await _probeCacheWriteInFlight;
1976
+ if (!_probeCacheDirty)
1977
+ return "written";
1978
+ }
1979
+ if (!_probeCacheDirty || _probeCache === null)
1980
+ return "idle";
1981
+ const write = writeProbeCache();
1982
+ _probeCacheWriteInFlight = write;
1983
+ try {
1984
+ return await write;
1985
+ } finally {
1986
+ if (_probeCacheWriteInFlight === write)
1987
+ _probeCacheWriteInFlight = null;
1988
+ }
1989
+ }
1990
+ function isAstGrepVersionOutput(output) {
1991
+ return /\bast[- ]grep\b/i.test(output);
1992
+ }
1993
+ async function verifyAstGrepProbePath(binPath) {
1994
+ const result = await probeToolAsync(binPath, ["--version"], {
1995
+ timeout: 5e3
1996
+ });
1997
+ return !result.error && result.status === 0 && isAstGrepVersionOutput(`${result.stdout}${result.stderr}`);
1998
+ }
1999
+ async function checkProbeCache(toolId) {
2000
+ const cache = await readProbeCache();
2001
+ const entry = cache[toolId];
2002
+ if (!entry)
2003
+ return void 0;
2004
+ const ttl = entry.transient ? PROBE_CACHE_TRANSIENT_COOLDOWN_MS : PROBE_CACHE_TTL_MS;
2005
+ if (Date.now() - entry.cachedAt > ttl) {
2006
+ logSessionStart(`auto-install probe-cache ${toolId}: miss (${entry.transient ? "transient cooldown" : "ttl"} expired)`);
2007
+ delete cache[toolId];
2008
+ markProbeCacheChange(toolId, null);
2009
+ return void 0;
2010
+ }
2011
+ try {
2012
+ await fs2.access(entry.path);
2013
+ const stat = await fs2.stat(entry.path);
2014
+ if (stat.mtimeMs !== entry.mtimeMs || entry.sizeBytes !== void 0 && stat.size !== entry.sizeBytes) {
2015
+ logSessionStart(`auto-install probe-cache ${toolId}: miss (mtime/size changed)`);
2016
+ delete cache[toolId];
2017
+ markProbeCacheChange(toolId, null);
2018
+ return void 0;
2019
+ }
2020
+ if (toolId === "ast-grep" && !await verifyAstGrepProbePath(entry.path)) {
2021
+ logSessionStart(`auto-install probe-cache ${toolId}: miss (not ast-grep: ${entry.path})`);
2022
+ delete cache[toolId];
2023
+ markProbeCacheChange(toolId, null);
2024
+ return void 0;
2025
+ }
2026
+ return entry.path;
2027
+ } catch {
2028
+ logSessionStart(`auto-install probe-cache ${toolId}: miss (gone: ${entry.path})`);
2029
+ delete cache[toolId];
2030
+ markProbeCacheChange(toolId, null);
2031
+ return void 0;
2032
+ }
2033
+ }
2034
+ async function updateProbeCache(toolId, resolvedPath, transient = false) {
2035
+ try {
2036
+ const stat = await fs2.stat(resolvedPath);
2037
+ const cache = await readProbeCache();
2038
+ const entry = {
2039
+ path: resolvedPath,
2040
+ mtimeMs: stat.mtimeMs,
2041
+ sizeBytes: stat.size,
2042
+ cachedAt: Date.now(),
2043
+ ...transient && { transient: true }
2044
+ };
2045
+ cache[toolId] = entry;
2046
+ markProbeCacheChange(toolId, entry);
2047
+ } catch {
2048
+ }
2049
+ }
2050
+ function resetProbeCacheStateForTesting() {
2051
+ _probeCache = null;
2052
+ _probeCacheDirty = false;
2053
+ _probeCacheChanges.clear();
2054
+ _probeCacheChangeVersions.clear();
2055
+ _probeCacheChangeGeneration = 0;
2056
+ _probeCacheRetryAttempt = 0;
2057
+ resetResolvedPathCache();
2058
+ ensureInFlight.clear();
2059
+ installFailureReasons.clear();
2060
+ installAttempts.clear();
2061
+ lastEnsureResolutionSource.clear();
2062
+ lastManagedInstallVersion.clear();
2063
+ lastResolveTransient.clear();
2064
+ resetPathWalkMemo();
2065
+ if (_probeCacheFlushTimer !== null) {
2066
+ clearTimeout(_probeCacheFlushTimer);
2067
+ _probeCacheFlushTimer = null;
2068
+ }
2069
+ }
2070
+ function _peekEnsureInFlightForTesting() {
2071
+ return ensureInFlight;
2072
+ }
2073
+ var pathWalkMemo = /* @__PURE__ */ new Map();
2074
+ function hashSync(value) {
2075
+ let hash = 2166136261;
2076
+ for (let i = 0; i < value.length; i += 1) {
2077
+ hash ^= value.charCodeAt(i);
2078
+ hash = Math.imul(hash, 16777619);
2079
+ }
2080
+ return (hash >>> 0).toString(16);
2081
+ }
2082
+ function resetPathWalkMemo() {
2083
+ pathWalkMemo.clear();
2084
+ }
2085
+ async function isCommandAvailable(command, _args) {
2086
+ const isWindows = installerPlatform() === "win32";
2087
+ const pathEnv = process.env.PATH || process.env.Path || process.env.path || "";
2088
+ const dirs = pathEnv.split(path3.delimiter);
2089
+ const names = isWindows ? [command, `${command}.exe`, `${command}.cmd`, `${command}.bat`] : [command];
2090
+ for (const dir of dirs) {
2091
+ if (!dir)
2092
+ continue;
2093
+ for (const name of names) {
2094
+ const candidate = path3.join(dir, name);
2095
+ try {
2096
+ const stat = statSync(candidate);
2097
+ if (stat.isFile() && stat.size > 0) {
2098
+ return true;
2099
+ }
2100
+ } catch {
2101
+ }
2102
+ }
2103
+ }
2104
+ return false;
2105
+ }
2106
+ async function isSpawnableCommand(command) {
2107
+ if (/[\\/]/.test(command)) {
2108
+ try {
2109
+ const stat = statSync(command);
2110
+ return stat.isFile() && stat.size > 0;
2111
+ } catch {
2112
+ return false;
2113
+ }
2114
+ }
2115
+ const memoKey = `${command}:${hashSync(process.env.PATH || "")}`;
2116
+ if (pathWalkMemo.has(memoKey))
2117
+ return pathWalkMemo.get(memoKey) ?? false;
2118
+ const isSpawnable = await isCommandAvailable(command);
2119
+ pathWalkMemo.set(memoKey, isSpawnable);
2120
+ return isSpawnable;
2121
+ }
2122
+ function isLspTransportRequiredError(output) {
2123
+ return /Connection (?:input|output) stream is not set|Use arguments of createConnection/i.test(output);
2124
+ }
2125
+ var lspTransportRequiredMatcher = /Connection (?:input|output) stream is not set|Use arguments of createConnection/i;
2126
+ function parsePinnedVersion(packageName) {
2127
+ const at = packageName.lastIndexOf("@");
2128
+ if (at <= 0)
2129
+ return void 0;
2130
+ return packageName.slice(at + 1) || void 0;
2131
+ }
2132
+ function extractVersionToken(output) {
2133
+ return output.match(/\d+\.\d+\.\d+(?:[-+][\w.]+)?/)?.[0];
2134
+ }
2135
+ var lastManagedInstallVersion = /* @__PURE__ */ new Map();
2136
+ function packageEntryVerification(tool) {
2137
+ return tool.verification === "package-entry" ? tool.packageName : void 0;
2138
+ }
2139
+ async function verifyNpmPackageEntry(binPath, packageName) {
2140
+ const nodeModulesDir = path3.dirname(path3.dirname(binPath));
2141
+ const pinned = parsePinnedVersion(packageName);
2142
+ const bareName = pinned ? packageName.slice(0, -(pinned.length + 1)) : packageName;
2143
+ const packageDir = path3.join(nodeModulesDir, ...bareName.split("/"));
2144
+ const shimName = path3.basename(binPath).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
2145
+ const fail = (reason) => {
2146
+ logSessionStart(`auto-install verify: failed for ${binPath} (check=package-entry, kind=${reason})`);
2147
+ return false;
2148
+ };
2149
+ try {
2150
+ const shim = statSync(binPath);
2151
+ if (!shim.isFile() || shim.size === 0)
2152
+ return fail("shim-not-a-file");
2153
+ } catch {
2154
+ return fail("shim-missing");
2155
+ }
2156
+ let manifest;
2157
+ try {
2158
+ manifest = JSON.parse(await fs2.readFile(path3.join(packageDir, "package.json"), "utf8"));
2159
+ } catch {
2160
+ return fail("package-json-unreadable");
2161
+ }
2162
+ if (typeof manifest.version !== "string" || !manifest.version) {
2163
+ return fail("package-json-no-version");
2164
+ }
2165
+ const bin = manifest.bin;
2166
+ const entry = typeof bin === "string" ? bin : bin && typeof bin === "object" ? Object.entries(bin).find(
2167
+ // Case-folded on purpose: on a case-insensitive filesystem the
2168
+ // shim on disk can differ in case from the manifest key npm
2169
+ // wrote it from, and `path.basename` reads the disk name.
2170
+ ([name]) => name.toLowerCase() === shimName
2171
+ )?.[1] : void 0;
2172
+ if (typeof entry !== "string" || !entry) {
2173
+ return fail("package-json-no-entry");
2174
+ }
2175
+ try {
2176
+ const stat = statSync(path3.join(packageDir, entry));
2177
+ if (!stat.isFile() || stat.size === 0)
2178
+ return fail("entry-not-a-file");
2179
+ } catch {
2180
+ return fail("entry-missing");
2181
+ }
2182
+ logSessionStart(`auto-install verify: succeeded for ${binPath} (check=package-entry, version=${manifest.version}, entry=${entry})`);
2183
+ return true;
2184
+ }
2185
+ async function verifyToolBinary(binPath, onVersionOutput, onTransient, timeoutMs = 1e4, verificationArgs = ["--version"], packageEntryOf, onInconclusive) {
2186
+ if (packageEntryOf !== void 0) {
2187
+ return verifyNpmPackageEntry(binPath, packageEntryOf);
2188
+ }
2189
+ const isWindows = installerPlatform() === "win32";
2190
+ const hasKnownWindowsExt = /\.(cmd|exe|ps1)$/i.test(binPath);
2191
+ let execPath = isWindows && !hasKnownWindowsExt ? `${binPath}.cmd` : binPath;
2192
+ if (isWindows && /\.ps1$/i.test(execPath)) {
2193
+ const cmdSibling = `${execPath.slice(0, -4)}.cmd`;
2194
+ if (__require("node:fs").existsSync(cmdSibling)) {
2195
+ execPath = cmdSibling;
2196
+ }
2197
+ }
2198
+ const useShell = isWindows && /\.(cmd|bat)$/i.test(execPath);
2199
+ void useShell;
2200
+ try {
2201
+ const result = await safeSpawnAsync(execPath, verificationArgs, {
2202
+ timeout: timeoutMs,
2203
+ input: "",
2204
+ // A broken or version-blind language server can emit a bundled
2205
+ // megabytes-long diagnostic on stderr. Keep verification bounded even
2206
+ // when the child reaches its normal timeout first.
2207
+ maxOutputBytes: 64 * 1024,
2208
+ matchWhileStreaming: lspTransportRequiredMatcher
2209
+ });
2210
+ const output = `${result.stdout}
2211
+ ${result.stderr}`;
2212
+ if (result.outputTruncated) {
2213
+ recordDegradationOnce({
2214
+ kind: "installer-verification-output-truncated",
2215
+ subject: binPath,
2216
+ reason: `verification output exceeded 65536 bytes (${verificationArgs.join(" ")})`
2217
+ });
2218
+ }
2219
+ if (result.status === 0 && !result.error) {
2220
+ debugLog(`Verified: ${binPath} (${verificationArgs.join(" ")}: ${result.stdout.trim()})`);
2221
+ onVersionOutput?.(result.stdout);
2222
+ return true;
2223
+ }
2224
+ if (result.streamingMatch || isLspTransportRequiredError(output)) {
2225
+ debugLog(`Verified (stdio LSP, transport-required): ${binPath}`);
2226
+ return true;
2227
+ }
2228
+ if (result.signal !== void 0 || result.spawnFailure)
2229
+ onTransient?.();
2230
+ if (result.outputTruncated && result.signal === void 0 && !result.spawnFailure) {
2231
+ onInconclusive?.();
2232
+ recordDegradationOnce({
2233
+ kind: "installer-verification-inconclusive",
2234
+ subject: binPath,
2235
+ reason: `transport-required marker unresolved in truncated output (${verificationArgs.join(" ")})`
2236
+ });
2237
+ }
2238
+ const kind = result.spawnFailure?.kind ?? (result.signal ? `killed-signal=${result.signal}` : result.error ? "spawn-error" : "exit-nonzero");
2239
+ logSessionStart(`auto-install verify: failed for ${binPath} (check=${verificationArgs.join(" ")}, kind=${kind}${result.status !== null ? `, exit=${result.status}` : ""})`);
2240
+ return false;
2241
+ } catch (err) {
2242
+ logSessionStart(`auto-install verify: spawn threw for ${binPath} (${err instanceof Error ? err.message : String(err)})`);
2243
+ onTransient?.();
2244
+ return false;
2245
+ }
2246
+ }
2247
+ async function getAllToolStatuses() {
2248
+ const statuses = [];
2249
+ for (const tool of TOOLS) {
2250
+ const status = {
2251
+ id: tool.id,
2252
+ name: tool.name,
2253
+ installed: false,
2254
+ source: "not-installed",
2255
+ strategy: tool.installStrategy
2256
+ };
2257
+ if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
2258
+ const bundleDir = await getArchiveTreeBundlePath(tool);
2259
+ if (bundleDir) {
2260
+ status.installed = true;
2261
+ status.source = "archive-dist";
2262
+ status.path = bundleDir;
2263
+ }
2264
+ statuses.push(status);
2265
+ continue;
2266
+ }
2267
+ if (await isCommandAvailable(tool.checkCommand, tool.checkArgs)) {
2268
+ status.installed = true;
2269
+ status.source = "global-path";
2270
+ status.path = tool.checkCommand;
2271
+ if (tool.verification !== "tree-manifest") {
2272
+ const probe = await probeToolAsync(tool.checkCommand, ["--version"], {
2273
+ timeout: 5e3
2274
+ });
2275
+ status.version = `${probe.stdout}${probe.stderr}`.trim().split("\n")[0]?.slice(0, 30) || void 0;
2276
+ }
2277
+ statuses.push(status);
2278
+ continue;
2279
+ }
2280
+ if (tool.installStrategy === "npm") {
2281
+ const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, void 0, tool.checkArgs, getToolVerificationTimeout(tool));
2282
+ if (npmPath) {
2283
+ status.installed = true;
2284
+ status.source = "npm-global";
2285
+ status.path = npmPath;
2286
+ statuses.push(status);
2287
+ continue;
2288
+ }
2289
+ }
2290
+ if (tool.installStrategy === "pip") {
2291
+ const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, void 0, tool.checkArgs, getToolVerificationTimeout(tool));
2292
+ if (pipPath) {
2293
+ status.installed = true;
2294
+ status.source = "pip-user";
2295
+ status.path = pipPath;
2296
+ statuses.push(status);
2297
+ continue;
2298
+ }
2299
+ }
2300
+ if (tool.installStrategy === "github" || tool.installStrategy === "maven" || tool.installStrategy === "archive") {
2301
+ const githubPath = await findGitHubToolPath(tool.binaryName || tool.id);
2302
+ if (githubPath) {
2303
+ status.installed = true;
2304
+ status.source = tool.installStrategy === "maven" ? "maven-jar" : tool.installStrategy === "archive" ? "archive-dist" : "github-release";
2305
+ status.path = githubPath;
2306
+ statuses.push(status);
2307
+ continue;
2308
+ }
2309
+ }
2310
+ const localBase = path3.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
2311
+ const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
2312
+ try {
2313
+ await fs2.access(localPath);
2314
+ if (await verifyToolBinary(localPath, void 0, void 0, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2315
+ status.installed = true;
2316
+ status.source = "pi-lens-auto";
2317
+ status.path = localPath;
2318
+ statuses.push(status);
2319
+ continue;
2320
+ }
2321
+ } catch {
2322
+ }
2323
+ if (tool.installStrategy === "npm") {
2324
+ status.source = "npx-fallback";
2325
+ }
2326
+ statuses.push(status);
2327
+ }
2328
+ return statuses;
2329
+ }
2330
+ async function isToolInstalled(toolId) {
2331
+ return await getToolPath(toolId) !== void 0;
2332
+ }
2333
+ async function getArchiveTreeBundlePath(tool) {
2334
+ if (tool.installStrategy !== "archive" || tool.archive?.launcher) {
2335
+ return void 0;
2336
+ }
2337
+ const extractDir = path3.join(TOOLS_DIR, tool.id);
2338
+ const marker = tool.archive?.treeMarker ? path3.join(extractDir, ...tool.archive.treeMarker.split("/")) : extractDir;
2339
+ try {
2340
+ await fs2.access(marker);
2341
+ return extractDir;
2342
+ } catch {
2343
+ return void 0;
2344
+ }
2345
+ }
2346
+ function resolvePlatformPackageBinary(tool) {
2347
+ const spec = tool.platformPackage;
2348
+ if (!spec || !tool.packageName)
2349
+ return void 0;
2350
+ const suffix = spec.suffixes[`${installerPlatform()}-${process.arch}`];
2351
+ if (!suffix)
2352
+ return void 0;
2353
+ const platformPkg = `${spec.base ?? tool.packageName}-${suffix}`;
2354
+ try {
2355
+ const mainPkgJson = _installerRequire.resolve(`${tool.packageName}/package.json`);
2356
+ const fromMain = createRequire(mainPkgJson);
2357
+ let pkgDir;
2358
+ try {
2359
+ pkgDir = path3.dirname(fromMain.resolve(`${platformPkg}/package.json`));
2360
+ } catch {
2361
+ pkgDir = path3.dirname(_installerRequire.resolve(`${platformPkg}/package.json`));
2362
+ }
2363
+ const isWin = installerPlatform() === "win32";
2364
+ for (const bin of spec.binaries) {
2365
+ for (const name of isWin ? [`${bin}.exe`, bin] : [bin]) {
2366
+ const candidate = path3.join(pkgDir, name);
2367
+ if (existsSync(candidate))
2368
+ return candidate;
2369
+ }
2370
+ }
2371
+ } catch {
2372
+ }
2373
+ return void 0;
2374
+ }
2375
+ var lastResolveTransient = /* @__PURE__ */ new Map();
2376
+ var lastInstallResolutionId = /* @__PURE__ */ new Map();
2377
+ function wasLastResolveTransient(toolId) {
2378
+ return lastResolveTransient.get(toolId) ?? false;
2379
+ }
2380
+ async function getToolPath(toolId) {
2381
+ let sawTransient = false;
2382
+ const markTransient = () => {
2383
+ sawTransient = true;
2384
+ };
2385
+ const result = await getToolPathResolved(toolId, markTransient);
2386
+ lastResolveTransient.set(toolId, sawTransient);
2387
+ return result;
2388
+ }
2389
+ async function getToolPathResolved(toolId, onTransient) {
2390
+ const tool = TOOLS.find((t) => t.id === toolId);
2391
+ if (!tool)
2392
+ return void 0;
2393
+ if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
2394
+ return getArchiveTreeBundlePath(tool);
2395
+ }
2396
+ const pinnedVersion = tool.installStrategy === "npm" && tool.packageName ? parsePinnedVersion(tool.packageName) : void 0;
2397
+ if (pinnedVersion)
2398
+ lastManagedInstallVersion.delete(toolId);
2399
+ const recordVersion = pinnedVersion ? (output) => {
2400
+ const seen = extractVersionToken(output);
2401
+ if (seen)
2402
+ lastManagedInstallVersion.set(toolId, seen);
2403
+ } : void 0;
2404
+ const localBase = path3.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
2405
+ if (installerPlatform() === "win32") {
2406
+ const cmdPath = `${localBase}.cmd`;
2407
+ try {
2408
+ await fs2.access(cmdPath);
2409
+ if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2410
+ return cmdPath;
2411
+ }
2412
+ logSessionStart(`auto-install verify: ${cmdPath} exists but is broken, will reinstall`);
2413
+ } catch {
2414
+ }
2415
+ const exePath = `${localBase}.exe`;
2416
+ try {
2417
+ await fs2.access(exePath);
2418
+ if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2419
+ return exePath;
2420
+ }
2421
+ logSessionStart(`auto-install verify: ${exePath} exists but is broken, will reinstall`);
2422
+ } catch {
2423
+ }
2424
+ }
2425
+ if (installerPlatform() !== "win32") {
2426
+ try {
2427
+ await fs2.access(localBase);
2428
+ if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
2429
+ return localBase;
2430
+ }
2431
+ logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
2432
+ } catch {
2433
+ }
2434
+ }
2435
+ if (tool.platformPackage) {
2436
+ const platformBin = resolvePlatformPackageBinary(tool);
2437
+ if (platformBin && await verifyToolBinary(platformBin, void 0, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
2438
+ logSessionStart(`auto-install ${toolId}: resolved platform-package binary at ${platformBin}`);
2439
+ return platformBin;
2440
+ }
2441
+ logSessionStart(`auto-install ${toolId}: platform-package binary not resolved (${process.platform}-${process.arch}, base=${tool.platformPackage.base ?? tool.packageName}) \u2014 falling back to PATH/managed install`);
2442
+ }
2443
+ if (tool.installStrategy === "github" || tool.installStrategy === "maven" || tool.installStrategy === "archive") {
2444
+ const githubPath = await findGitHubToolPath(tool.binaryName || tool.id);
2445
+ if (githubPath)
2446
+ return githubPath;
2447
+ }
2448
+ if (await isCommandAvailable(tool.checkCommand)) {
2449
+ if (packageEntryVerification(tool) !== void 0)
2450
+ return tool.checkCommand;
2451
+ if (tool.verification === "tree-manifest")
2452
+ return tool.checkCommand;
2453
+ let probeStalled = false;
2454
+ const verified = await verifyToolBinary(tool.checkCommand, void 0, () => {
2455
+ probeStalled = true;
2456
+ onTransient();
2457
+ }, getToolVerificationTimeout(tool), tool.checkArgs, void 0, () => {
2458
+ probeStalled = true;
2459
+ });
2460
+ if (verified || probeStalled)
2461
+ return tool.checkCommand;
2462
+ recordDegradationOnce({
2463
+ kind: "installer-path-candidate-unrunnable",
2464
+ subject: toolId,
2465
+ reason: `${tool.checkCommand} on PATH failed its own check (${tool.checkArgs.join(" ")}); ignoring PATH for this tool`
2466
+ });
2467
+ logSessionStart(`auto-install ${toolId}: PATH candidate ${tool.checkCommand} failed ${tool.checkArgs.join(" ")} \u2014 ignoring PATH, trying managed install`);
2468
+ }
2469
+ if (tool.installStrategy === "npm") {
2470
+ const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
2471
+ if (npmPath) {
2472
+ return npmPath;
2473
+ }
2474
+ }
2475
+ if (tool.installStrategy === "pip") {
2476
+ const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
2477
+ if (pipPath) {
2478
+ return pipPath;
2479
+ }
2480
+ }
2481
+ return void 0;
2482
+ }
2483
+ async function findGitHubToolPath(binaryName) {
2484
+ const isWindows = process.platform === "win32";
2485
+ const candidates = isWindows ? [
2486
+ path3.join(GITHUB_BIN_DIR, `${binaryName}.exe`),
2487
+ path3.join(GITHUB_BIN_DIR, `${binaryName}.bat`),
2488
+ path3.join(GITHUB_BIN_DIR, `${binaryName}.cmd`),
2489
+ path3.join(GITHUB_BIN_DIR, binaryName)
2490
+ ] : [path3.join(GITHUB_BIN_DIR, binaryName)];
2491
+ for (const candidate of candidates) {
2492
+ try {
2493
+ await fs2.access(candidate);
2494
+ return candidate;
2495
+ } catch {
2496
+ }
2497
+ }
2498
+ return void 0;
2499
+ }
2500
+ async function findManagedToolBinary(toolId) {
2501
+ const tool = TOOLS.find((candidate) => candidate.id === toolId);
2502
+ if (!tool)
2503
+ return void 0;
2504
+ if (tool.installStrategy !== "github" && tool.installStrategy !== "maven" && tool.installStrategy !== "archive") {
2505
+ return void 0;
2506
+ }
2507
+ return findGitHubToolPath(tool.binaryName || tool.id);
2508
+ }
2509
+ function hasExecutableExtension(name) {
2510
+ return /\.(exe|bat|cmd|ps1)$/i.test(name);
2511
+ }
2512
+ function getGitHubInstalledBinaryName(binaryName, platform, assetName) {
2513
+ if (platform !== "win32")
2514
+ return binaryName;
2515
+ if (hasExecutableExtension(binaryName))
2516
+ return binaryName;
2517
+ if (assetName.endsWith(".bat"))
2518
+ return `${binaryName}.bat`;
2519
+ if (assetName.endsWith(".cmd"))
2520
+ return `${binaryName}.cmd`;
2521
+ return `${binaryName}.exe`;
2522
+ }
2523
+ function launcherForGitHubAsset(spec, assetName) {
2524
+ if (spec.launcher === "php" && assetName.endsWith(".phar"))
2525
+ return "php";
2526
+ if (spec.launcher === "java" && assetName.endsWith(".jar"))
2527
+ return "java";
2528
+ return void 0;
2529
+ }
2530
+ function getArchiveBinaryCandidates(binaryName, platform, assetName) {
2531
+ if (platform !== "win32")
2532
+ return [binaryName];
2533
+ if (hasExecutableExtension(binaryName))
2534
+ return [binaryName];
2535
+ const candidates = /* @__PURE__ */ new Set();
2536
+ if (assetName.endsWith(".bat"))
2537
+ candidates.add(`${binaryName}.bat`);
2538
+ if (assetName.endsWith(".cmd"))
2539
+ candidates.add(`${binaryName}.cmd`);
2540
+ candidates.add(`${binaryName}.exe`);
2541
+ candidates.add(binaryName);
2542
+ candidates.add(`${binaryName}.bat`);
2543
+ candidates.add(`${binaryName}.cmd`);
2544
+ return [...candidates];
2545
+ }
2546
+ async function findNpmGlobalToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 1e4) {
2547
+ const isWindows = process.platform === "win32";
2548
+ const binDirs = await getNpmGlobalBinCandidates(onTransient);
2549
+ for (const dir of binDirs) {
2550
+ const candidates = isWindows ? [
2551
+ path3.join(dir, `${binaryName}.cmd`),
2552
+ path3.join(dir, `${binaryName}.exe`)
2553
+ ] : [path3.join(dir, binaryName)];
2554
+ for (const candidate of candidates) {
2555
+ try {
2556
+ await fs2.access(candidate);
2557
+ if (await verifyToolBinary(candidate, void 0, onTransient, verificationTimeoutMs, verificationArgs)) {
2558
+ return candidate;
2559
+ }
2560
+ } catch {
2561
+ }
2562
+ }
2563
+ }
2564
+ return void 0;
2565
+ }
2566
+ async function getNpmGlobalBinCandidates(onTransient) {
2567
+ const dirs = [];
2568
+ const seen = /* @__PURE__ */ new Set();
2569
+ const add = (value) => {
2570
+ if (!value)
2571
+ return;
2572
+ const normalized = path3.resolve(value.trim());
2573
+ if (!normalized)
2574
+ return;
2575
+ if (seen.has(normalized))
2576
+ return;
2577
+ seen.add(normalized);
2578
+ dirs.push(normalized);
2579
+ };
2580
+ if (process.platform === "win32") {
2581
+ add(path3.join(process.env.APPDATA || "", "npm"));
2582
+ } else {
2583
+ add(path3.join(os.homedir(), ".npm-global", "bin"));
2584
+ }
2585
+ for (const dir of await allAvailableGlobalBinDirs(onTransient)) {
2586
+ add(dir);
2587
+ }
2588
+ return dirs;
2589
+ }
2590
+ async function findPipUserToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 1e4) {
2591
+ const isWindows = installerPlatform() === "win32";
2592
+ const userBaseCandidates = [
2593
+ path3.join(getGlobalPiLensDir(), "pip-tools"),
2594
+ path3.join(getGlobalPiLensDir(), "pip-user"),
2595
+ ...await getPythonUserBaseCandidates()
2596
+ ];
2597
+ for (const userBase of userBaseCandidates) {
2598
+ const scriptDirs = [pipScriptsDir(userBase, installerPlatform())];
2599
+ if (isWindows) {
2600
+ try {
2601
+ const children = await fs2.readdir(userBase, { withFileTypes: true });
2602
+ for (const entry of children) {
2603
+ if (!entry.isDirectory())
2604
+ continue;
2605
+ if (!/^python\d+$/i.test(entry.name))
2606
+ continue;
2607
+ scriptDirs.push(path3.join(userBase, entry.name, "Scripts"));
2608
+ }
2609
+ } catch {
2610
+ }
2611
+ }
2612
+ for (const dir of scriptDirs) {
2613
+ const candidates = isWindows ? [
2614
+ path3.join(dir, `${binaryName}.exe`),
2615
+ path3.join(dir, `${binaryName}.cmd`),
2616
+ path3.join(dir, binaryName)
2617
+ ] : [path3.join(dir, binaryName)];
2618
+ for (const candidate of candidates) {
2619
+ try {
2620
+ await fs2.access(candidate);
2621
+ if (await verifyToolBinary(candidate, void 0, onTransient, verificationTimeoutMs, verificationArgs)) {
2622
+ return candidate;
2623
+ }
2624
+ } catch {
2625
+ }
2626
+ }
2627
+ }
2628
+ }
2629
+ return void 0;
2630
+ }
2631
+ function userBaseProbeResult(command, code, stdout) {
2632
+ if (stdout.truncated) {
2633
+ recordDegradationOnce({
2634
+ kind: "spawn-output-cap-truncated",
2635
+ subject: `user-base-probe:${command}`,
2636
+ reason: `\`${command} -m site --user-base\` reached the ${DEFAULT_MAX_OUTPUT_BYTES}-byte default cap after ${stdout.observedBytes} bytes; the probed user base is unusable`,
2637
+ metadata: {
2638
+ capBytes: DEFAULT_MAX_OUTPUT_BYTES,
2639
+ capSource: "default",
2640
+ observedBytes: stdout.observedBytes,
2641
+ killed: false
2642
+ }
2643
+ });
2644
+ return "";
2645
+ }
2646
+ return code === 0 ? stdout.text.trim() : "";
2647
+ }
2648
+ async function getPythonUserBaseCandidates() {
2649
+ const candidates = [];
2650
+ const seen = /* @__PURE__ */ new Set();
2651
+ const add = (value) => {
2652
+ if (!value)
2653
+ return;
2654
+ const normalized = value.trim();
2655
+ if (!normalized)
2656
+ return;
2657
+ if (seen.has(normalized))
2658
+ return;
2659
+ seen.add(normalized);
2660
+ candidates.push(normalized);
2661
+ };
2662
+ if (process.platform === "win32") {
2663
+ add(path3.join(process.env.APPDATA || "", "Python"));
2664
+ }
2665
+ const probes = process.platform === "win32" ? [
2666
+ { command: "py", args: ["-m", "site", "--user-base"] },
2667
+ { command: "python", args: ["-m", "site", "--user-base"] }
2668
+ ] : [
2669
+ { command: "python3", args: ["-m", "site", "--user-base"] },
2670
+ { command: "python", args: ["-m", "site", "--user-base"] }
2671
+ ];
2672
+ for (const probe of probes) {
2673
+ const userBase = await new Promise((resolve) => {
2674
+ const isWin = process.platform === "win32";
2675
+ const spawnCmd = isWin ? [probe.command, ...probe.args].join(" ") : probe.command;
2676
+ let proc;
2677
+ try {
2678
+ proc = spawn(spawnCmd, isWin ? [] : probe.args, {
2679
+ stdio: ["ignore", "pipe", "pipe"],
2680
+ shell: isWin
2681
+ });
2682
+ } catch {
2683
+ resolve("");
2684
+ return;
2685
+ }
2686
+ const stdout = createBoundedOutputSink();
2687
+ proc.stdout?.on("data", (data) => stdout.append(data));
2688
+ proc.on("exit", (code) => resolve(userBaseProbeResult(probe.command, code, stdout)));
2689
+ proc.on("error", () => resolve(""));
2690
+ });
2691
+ add(userBase);
2692
+ }
2693
+ return candidates;
2694
+ }
2695
+ async function githubApiAuthHeaders() {
2696
+ const token = await resolveGitHubToken();
2697
+ return token ? { Authorization: `Bearer ${token}` } : {};
2698
+ }
2699
+ var HttpStatusError = class extends Error {
2700
+ statusCode;
2701
+ constructor(statusCode, url) {
2702
+ super(url ? `HTTP ${statusCode} for ${url}` : `HTTP ${statusCode}`);
2703
+ this.name = "HttpStatusError";
2704
+ this.statusCode = statusCode;
2705
+ }
2706
+ };
2707
+ var GitHubHttpError = class extends HttpStatusError {
2708
+ anonymousRateLimitExhausted;
2709
+ constructor(statusCode, requestHeaders, responseHeaders) {
2710
+ super(statusCode);
2711
+ this.name = "GitHubHttpError";
2712
+ this.message = `GitHub API HTTP ${statusCode}`;
2713
+ this.anonymousRateLimitExhausted = statusCode === 403 && !Object.keys(requestHeaders).some((key) => key.toLowerCase() === "authorization") && readHeader(responseHeaders, "x-ratelimit-remaining") === "0";
2714
+ }
2715
+ };
2716
+ function readHeader(headers, name) {
2717
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
2718
+ return Array.isArray(entry) ? entry[0]?.trim() : entry?.trim();
2719
+ }
2720
+ function isGitHubApiUrl(url) {
2721
+ try {
2722
+ return new URL(url).host.toLowerCase() === "api.github.com";
2723
+ } catch {
2724
+ return false;
2725
+ }
2726
+ }
2727
+ function sameHost(a, b) {
2728
+ try {
2729
+ return new URL(a).host === new URL(b).host;
2730
+ } catch {
2731
+ return false;
2732
+ }
2733
+ }
2734
+ function httpsGetWithMeta(url, maxRedirects = 5, headers = {}) {
2735
+ return new Promise((resolve, reject) => {
2736
+ https.get(url, { headers: { "User-Agent": "pi-lens/1.0", ...headers } }, (res) => {
2737
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
2738
+ if (maxRedirects === 0)
2739
+ return reject(new Error("Too many redirects"));
2740
+ const location = res.headers.location;
2741
+ const nextHeaders = sameHost(url, location) ? headers : (() => {
2742
+ const { Authorization: _drop, ...rest } = headers;
2743
+ return rest;
2744
+ })();
2745
+ return resolve(httpsGetWithMeta(location, maxRedirects - 1, nextHeaders));
2746
+ }
2747
+ const etag = typeof res.headers.etag === "string" ? res.headers.etag : void 0;
2748
+ if (res.statusCode === 304) {
2749
+ res.resume();
2750
+ return resolve({ statusCode: 304, body: Buffer.alloc(0), etag });
2751
+ }
2752
+ if (res.statusCode !== 200) {
2753
+ res.resume();
2754
+ return reject(isGitHubApiUrl(url) ? new GitHubHttpError(res.statusCode ?? 0, headers, res.headers) : new HttpStatusError(res.statusCode ?? 0, url));
2755
+ }
2756
+ const chunks = [];
2757
+ res.on("data", (chunk) => chunks.push(chunk));
2758
+ res.on("end", () => resolve({ statusCode: 200, body: Buffer.concat(chunks), etag }));
2759
+ res.on("error", reject);
2760
+ }).on("error", reject);
2761
+ });
2762
+ }
2763
+ async function httpsGet(url, maxRedirects = 5, headers = {}) {
2764
+ const response = await httpsGetWithMeta(url, maxRedirects, headers);
2765
+ if (response.statusCode !== 200) {
2766
+ throw new Error(`HTTP ${response.statusCode} for ${url}`);
2767
+ }
2768
+ return response.body;
2769
+ }
2770
+ function runCommand(command, args, cwd) {
2771
+ return new Promise((resolve) => {
2772
+ let proc;
2773
+ try {
2774
+ proc = spawn(command, args, {
2775
+ cwd,
2776
+ stdio: "ignore",
2777
+ shell: process.platform === "win32"
2778
+ });
2779
+ } catch {
2780
+ resolve(false);
2781
+ return;
2782
+ }
2783
+ proc.on("exit", (code) => resolve(code === 0));
2784
+ proc.on("error", () => resolve(false));
2785
+ });
2786
+ }
2787
+ async function installGitHubTool(tool, prefetchedRelease) {
2788
+ const spec = tool.github;
2789
+ if (!spec)
2790
+ return void 0;
2791
+ const platform = process.platform;
2792
+ const arch = process.arch;
2793
+ const assetSubstring = spec.assetMatch(platform, arch);
2794
+ if (!assetSubstring) {
2795
+ logSessionStart(`github-install ${tool.id}: unsupported platform=${platform} arch=${arch}`);
2796
+ return void 0;
2797
+ }
2798
+ let releaseJson;
2799
+ if (prefetchedRelease) {
2800
+ releaseJson = prefetchedRelease;
2801
+ logSessionStart(`github-install ${tool.id}: using caller-supplied release metadata for ${spec.repo} (${prefetchedRelease.tag_name ?? "untagged"})`);
2802
+ } else {
2803
+ logSessionStart(`github-install ${tool.id}: fetching release metadata from ${spec.repo}`);
2804
+ try {
2805
+ const body = await httpsGet(`https://api.github.com/repos/${spec.repo}/releases/latest`, 5, await githubApiAuthHeaders());
2806
+ releaseJson = JSON.parse(body.toString("utf8"));
2807
+ } catch (err) {
2808
+ const reason = err instanceof GitHubHttpError && err.anonymousRateLimitExhausted ? "GitHub API rate limit exhausted; authenticate with `gh auth login` or set GITHUB_TOKEN/GH_TOKEN and retry" : `release fetch failed: ${err.message}`;
2809
+ if (err instanceof GitHubHttpError && err.anonymousRateLimitExhausted) {
2810
+ recordDegradationOnce({
2811
+ kind: "github-api-rate-limit",
2812
+ subject: tool.id,
2813
+ reason
2814
+ });
2815
+ }
2816
+ logSessionStart(`github-install ${tool.id}: ${reason}`);
2817
+ return void 0;
2818
+ }
2819
+ }
2820
+ if (releaseJson.tag_name) {
2821
+ lastInstallResolutionId.set(tool.id, releaseJson.tag_name);
2822
+ }
2823
+ if (!Array.isArray(releaseJson.assets)) {
2824
+ logSessionStart(`github-install ${tool.id}: release metadata has no assets array`);
2825
+ return void 0;
2826
+ }
2827
+ const asset = pickReleaseAsset(releaseJson.assets, assetSubstring) ?? deriveHashiCorpReleaseAsset(tool, releaseJson.tag_name, assetSubstring);
2828
+ if (!asset) {
2829
+ logSessionStart(`github-install ${tool.id}: no asset matched "${assetSubstring}"`);
2830
+ return void 0;
2831
+ }
2832
+ logSessionStart(`github-install ${tool.id}: downloading ${asset.name}`);
2833
+ debugLog(`[github] downloading ${asset.name} from ${asset.browser_download_url}`);
2834
+ const downloadStart = Date.now();
2835
+ let assetBuffer;
2836
+ try {
2837
+ assetBuffer = await httpsGet(asset.browser_download_url);
2838
+ logSessionStart(`github-install ${tool.id}: downloaded ${asset.name} (${assetBuffer.length} bytes, ${Date.now() - downloadStart}ms)`);
2839
+ } catch (err) {
2840
+ logSessionStart(`github-install ${tool.id}: download failed: ${err.message}`);
2841
+ return void 0;
2842
+ }
2843
+ await fs2.mkdir(GITHUB_BIN_DIR, { recursive: true });
2844
+ const binaryName = tool.binaryName ?? tool.id;
2845
+ const isWindows = platform === "win32";
2846
+ const finalBinaryName = getGitHubInstalledBinaryName(binaryName, platform, asset.name);
2847
+ let destPath = path3.join(GITHUB_BIN_DIR, finalBinaryName);
2848
+ const assetName = asset.name;
2849
+ const launcherRuntime = launcherForGitHubAsset(spec, assetName);
2850
+ if (launcherRuntime && !await isCommandAvailable(launcherRuntime, ["--version"])) {
2851
+ logSessionStart(`github-install ${tool.id}: ${launcherRuntime} not found \u2014 asset requires a runtime launcher`);
2852
+ return void 0;
2853
+ }
2854
+ try {
2855
+ if (launcherRuntime) {
2856
+ const assetPath = path3.join(GITHUB_BIN_DIR, `${tool.id}${assetName.endsWith(".phar") ? ".phar" : ".jar"}`);
2857
+ await writeFileAtomicAsync(assetPath, assetBuffer, {
2858
+ bestEffort: false,
2859
+ mode: 488
2860
+ });
2861
+ const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
2862
+ destPath = path3.join(GITHUB_BIN_DIR, launcherName);
2863
+ const runtimeTarget = assetName.endsWith(".phar") ? `${tool.id}.phar` : `${tool.id}.jar`;
2864
+ const command = isWindows ? `@echo off\r
2865
+ ${launcherRuntime} "%~dp0${runtimeTarget}" %*\r
2866
+ ` : `#!/bin/sh
2867
+ exec ${launcherRuntime} "$(dirname "$0")/${runtimeTarget}" "$@"
2868
+ `;
2869
+ await writeFileAtomicAsync(destPath, command, {
2870
+ bestEffort: false,
2871
+ mode: isWindows ? void 0 : 488
2872
+ });
2873
+ } else if (assetName.endsWith(".gz") && !assetName.endsWith(".tar.gz")) {
2874
+ const decompressed = await new Promise((resolve, reject) => {
2875
+ const gunzip = createGunzip();
2876
+ const chunks = [];
2877
+ gunzip.on("data", (chunk) => chunks.push(chunk));
2878
+ gunzip.on("end", () => resolve(Buffer.concat(chunks)));
2879
+ gunzip.on("error", reject);
2880
+ gunzip.end(assetBuffer);
2881
+ });
2882
+ await writeFileAtomicAsync(destPath, decompressed, {
2883
+ bestEffort: false,
2884
+ mode: 488
2885
+ });
2886
+ } else if (assetName.endsWith(".tar.gz") || assetName.endsWith(".tar.xz")) {
2887
+ const tmpArchive = path3.join(GITHUB_BIN_DIR, `_tmp_${assetName}`);
2888
+ await fs2.writeFile(tmpArchive, assetBuffer);
2889
+ const tmpDir = path3.join(GITHUB_BIN_DIR, `_tmp_extract_${tool.id}`);
2890
+ await fs2.mkdir(tmpDir, { recursive: true });
2891
+ const extracted = await runCommand("tar", ["xf", tmpArchive, "-C", tmpDir], GITHUB_BIN_DIR);
2892
+ await fs2.rm(tmpArchive, { force: true });
2893
+ if (!extracted) {
2894
+ await fs2.rm(tmpDir, { recursive: true, force: true });
2895
+ logSessionStart(`github-install ${tool.id}: tar extraction failed for ${assetName}`);
2896
+ return void 0;
2897
+ }
2898
+ const tarBinaryName = spec.binaryInArchive ?? binaryName;
2899
+ const tarSrcBinary = await findFirstFileRecursive(tmpDir, getArchiveBinaryCandidates(tarBinaryName, platform, assetName));
2900
+ if (!tarSrcBinary) {
2901
+ await fs2.rm(tmpDir, { recursive: true, force: true });
2902
+ logSessionStart(`github-install ${tool.id}: binary candidates ${JSON.stringify(getArchiveBinaryCandidates(tarBinaryName, platform, assetName))} not found in tar ${assetName}`);
2903
+ return void 0;
2904
+ }
2905
+ await fs2.rename(tarSrcBinary, destPath);
2906
+ await fs2.rm(tmpDir, { recursive: true, force: true });
2907
+ if (!isWindows)
2908
+ await fs2.chmod(destPath, 488);
2909
+ } else if (assetName.endsWith(".zip")) {
2910
+ const tmpArchive = path3.join(GITHUB_BIN_DIR, `_tmp_${assetName}`);
2911
+ await fs2.writeFile(tmpArchive, assetBuffer);
2912
+ const tmpDir = path3.join(GITHUB_BIN_DIR, `_tmp_extract_${tool.id}`);
2913
+ await fs2.mkdir(tmpDir, { recursive: true });
2914
+ const extracted = isWindows ? await runCommand("powershell", [
2915
+ "-NoProfile",
2916
+ "-Command",
2917
+ `Expand-Archive -LiteralPath '${tmpArchive}' -DestinationPath '${tmpDir}' -Force`
2918
+ ], GITHUB_BIN_DIR) : await runCommand("unzip", ["-q", "-o", tmpArchive, "-d", tmpDir], GITHUB_BIN_DIR);
2919
+ await fs2.rm(tmpArchive, { force: true });
2920
+ if (!extracted) {
2921
+ await fs2.rm(tmpDir, { recursive: true, force: true });
2922
+ logSessionStart(`github-install ${tool.id}: zip extraction failed for ${assetName}`);
2923
+ return void 0;
2924
+ }
2925
+ const archiveBinaryName = spec.binaryInArchive ?? binaryName;
2926
+ const srcBinary = await findFirstFileRecursive(tmpDir, getArchiveBinaryCandidates(archiveBinaryName, platform, assetName));
2927
+ if (!srcBinary) {
2928
+ await fs2.rm(tmpDir, { recursive: true, force: true });
2929
+ logSessionStart(`github-install ${tool.id}: binary candidates ${JSON.stringify(getArchiveBinaryCandidates(archiveBinaryName, platform, assetName))} not found in zip ${assetName}`);
2930
+ return void 0;
2931
+ }
2932
+ await fs2.rename(srcBinary, destPath);
2933
+ await fs2.rm(tmpDir, { recursive: true, force: true });
2934
+ if (!isWindows)
2935
+ await fs2.chmod(destPath, 488);
2936
+ } else {
2937
+ await writeFileAtomicAsync(destPath, assetBuffer, {
2938
+ bestEffort: false,
2939
+ mode: 488
2940
+ });
2941
+ }
2942
+ } catch (err) {
2943
+ logSessionStart(`github-install ${tool.id}: install failed: ${err.message}`);
2944
+ return void 0;
2945
+ }
2946
+ for (const extraName of spec.extraAssets?.(platform, arch) ?? []) {
2947
+ const extraAsset = releaseJson.assets.find((a) => a.name === extraName);
2948
+ if (!extraAsset) {
2949
+ logSessionStart(`github-install ${tool.id}: required extra asset "${extraName}" not found`);
2950
+ return void 0;
2951
+ }
2952
+ try {
2953
+ const extraBuffer = await httpsGet(extraAsset.browser_download_url);
2954
+ await writeFileAtomicAsync(path3.join(GITHUB_BIN_DIR, extraName), extraBuffer, { bestEffort: false, mode: 488 });
2955
+ logSessionStart(`github-install ${tool.id}: installed extra asset ${extraName} (${extraBuffer.length} bytes)`);
2956
+ } catch (err) {
2957
+ logSessionStart(`github-install ${tool.id}: extra asset ${extraName} download failed: ${err.message}`);
2958
+ return void 0;
2959
+ }
2960
+ }
2961
+ debugLog(`[github] installed ${tool.name} \u2192 ${destPath}`);
2962
+ logSessionStart(`github-install ${tool.id}: installed \u2192 ${destPath}`);
2963
+ return destPath;
2964
+ }
2965
+ async function findFirstFileRecursive(dir, names) {
2966
+ const wanted = new Set(names.map((name) => name.toLowerCase()));
2967
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
2968
+ for (const entry of entries) {
2969
+ const full = path3.join(dir, entry.name);
2970
+ if (entry.isDirectory()) {
2971
+ const found = await findFirstFileRecursive(full, names);
2972
+ if (found)
2973
+ return found;
2974
+ } else if (wanted.has(entry.name.toLowerCase())) {
2975
+ return full;
2976
+ }
2977
+ }
2978
+ return void 0;
2979
+ }
2980
+ var NEEDS_POSTINSTALL = /* @__PURE__ */ new Set([
2981
+ "@biomejs/biome",
2982
+ "@ast-grep/cli",
2983
+ // postinstall copies platform binary (ast-grep.exe/sg.exe) into place
2984
+ "@ast-grep/napi",
2985
+ "esbuild",
2986
+ "intelephense"
2987
+ // postinstall fetches platform binary; --ignore-scripts breaks install
2988
+ ]);
2989
+ function npmToolNeedsPostinstall(packageName) {
2990
+ return NEEDS_POSTINSTALL.has(packageName);
2991
+ }
2992
+ function getRefreshableManagedNpmTools() {
2993
+ const seenPackages = /* @__PURE__ */ new Set();
2994
+ return getRefreshableManagedTools().filter((candidate) => candidate.strategy === "npm").map((candidate) => ({
2995
+ toolId: candidate.toolId,
2996
+ // The npm branch of getRefreshableManagedTools only admits entries that
2997
+ // have both, so these are total.
2998
+ packageName: candidate.packageName,
2999
+ binaryName: candidate.binaryName
3000
+ })).filter((candidate) => {
3001
+ if (seenPackages.has(candidate.packageName))
3002
+ return false;
3003
+ seenPackages.add(candidate.packageName);
3004
+ return true;
3005
+ });
3006
+ }
3007
+ function mavenCoordinate(spec) {
3008
+ return [
3009
+ spec.repoBaseUrl ?? MAVEN_CENTRAL_BASE,
3010
+ spec.groupId,
3011
+ spec.artifactId,
3012
+ spec.version,
3013
+ spec.classifier ?? ""
3014
+ ].join(":");
3015
+ }
3016
+ function getRefreshableManagedTools() {
3017
+ const refreshable = [];
3018
+ for (const tool of TOOLS) {
3019
+ switch (tool.installStrategy) {
3020
+ case "npm": {
3021
+ if (!tool.packageName)
3022
+ continue;
3023
+ if (parsePinnedVersion(tool.packageName) !== void 0)
3024
+ continue;
3025
+ if (!tool.binaryName)
3026
+ continue;
3027
+ refreshable.push({
3028
+ toolId: tool.id,
3029
+ strategy: "npm",
3030
+ checkArgs: tool.checkArgs,
3031
+ packageName: tool.packageName,
3032
+ binaryName: tool.binaryName,
3033
+ verificationTimeoutMs: tool.verificationTimeoutMs,
3034
+ packageEntryOf: packageEntryVerification(tool)
3035
+ });
3036
+ break;
3037
+ }
3038
+ case "pip":
3039
+ case "gem": {
3040
+ if (!tool.packageName)
3041
+ continue;
3042
+ refreshable.push({
3043
+ toolId: tool.id,
3044
+ strategy: tool.installStrategy,
3045
+ checkArgs: tool.checkArgs,
3046
+ packageName: tool.packageName
3047
+ });
3048
+ break;
3049
+ }
3050
+ case "github": {
3051
+ if (!tool.github)
3052
+ continue;
3053
+ refreshable.push({
3054
+ toolId: tool.id,
3055
+ strategy: "github",
3056
+ checkArgs: tool.checkArgs
3057
+ });
3058
+ break;
3059
+ }
3060
+ case "maven": {
3061
+ if (!tool.maven)
3062
+ continue;
3063
+ refreshable.push({
3064
+ toolId: tool.id,
3065
+ strategy: "maven",
3066
+ checkArgs: tool.checkArgs,
3067
+ pinnedCoordinate: mavenCoordinate(tool.maven)
3068
+ });
3069
+ break;
3070
+ }
3071
+ case "archive": {
3072
+ if (!tool.archive)
3073
+ continue;
3074
+ const url = resolveArchiveUrl(tool.archive);
3075
+ if (!url)
3076
+ continue;
3077
+ refreshable.push({
3078
+ toolId: tool.id,
3079
+ strategy: "archive",
3080
+ checkArgs: tool.checkArgs,
3081
+ pinnedCoordinate: url
3082
+ });
3083
+ break;
3084
+ }
3085
+ }
3086
+ }
3087
+ return refreshable;
3088
+ }
3089
+ async function isManagedToolPresent(toolId) {
3090
+ const tool = TOOLS.find((t) => t.id === toolId);
3091
+ if (!tool)
3092
+ return false;
3093
+ switch (tool.installStrategy) {
3094
+ case "npm": {
3095
+ if (!tool.packageName)
3096
+ return false;
3097
+ try {
3098
+ await fs2.access(path3.join(TOOLS_DIR, "node_modules", tool.packageName, "package.json"));
3099
+ return true;
3100
+ } catch {
3101
+ return false;
3102
+ }
3103
+ }
3104
+ case "github":
3105
+ case "maven":
3106
+ return await findGitHubToolPath(tool.binaryName ?? tool.id) !== void 0;
3107
+ case "archive":
3108
+ return await findGitHubToolPath(tool.binaryName ?? tool.id) !== void 0 || await getArchiveTreeBundlePath(tool) !== void 0;
3109
+ case "pip":
3110
+ case "gem": {
3111
+ const cached = (await readProbeCache())[toolId];
3112
+ return cached?.path !== void 0 && existsSync(cached.path);
3113
+ }
3114
+ default:
3115
+ return false;
3116
+ }
3117
+ }
3118
+ async function probeManagedToolVersion(tool) {
3119
+ const cached = (await readProbeCache())[tool.id];
3120
+ if (!cached?.path || !existsSync(cached.path))
3121
+ return void 0;
3122
+ try {
3123
+ const result = await probeToolAsync(cached.path, tool.checkArgs, {
3124
+ timeout: getToolVerificationTimeout(tool),
3125
+ input: "",
3126
+ ignoreAmbientSignal: true,
3127
+ resourceLabel: `tool-refresh-version:${tool.id}`
3128
+ });
3129
+ if (result.status !== 0)
3130
+ return void 0;
3131
+ return extractVersionToken(`${result.stdout}
3132
+ ${result.stderr}`);
3133
+ } catch {
3134
+ return void 0;
3135
+ }
3136
+ }
3137
+ async function acquireManagedInstallGate(context) {
3138
+ if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
3139
+ return {
3140
+ ok: false,
3141
+ reason: "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1"
3142
+ };
3143
+ }
3144
+ if (!assertInstallAllowed(context)) {
3145
+ return {
3146
+ ok: false,
3147
+ reason: `project trust: ${projectTrustDenialReason()}`
3148
+ };
3149
+ }
3150
+ const lock = await acquireInstallLock();
3151
+ if (!lock.release) {
3152
+ return { ok: false, reason: lock.reason ?? "install lock held" };
3153
+ }
3154
+ return { ok: true, release: lock.release };
3155
+ }
3156
+ async function refreshManagedTool(toolId, known = {}) {
3157
+ const tool = TOOLS.find((t) => t.id === toolId);
3158
+ if (!tool) {
3159
+ return { ok: false, unchanged: true, reason: "unknown tool id" };
3160
+ }
3161
+ if (tool.installStrategy !== "github" && tool.installStrategy !== "pip" && tool.installStrategy !== "gem" && tool.installStrategy !== "maven" && tool.installStrategy !== "archive") {
3162
+ return {
3163
+ ok: false,
3164
+ unchanged: true,
3165
+ reason: `strategy ${tool.installStrategy} is not refreshable here`
3166
+ };
3167
+ }
3168
+ const strategy = tool.installStrategy;
3169
+ const gate = await acquireManagedInstallGate(`managed tool refresh: ${toolId}`);
3170
+ if (!gate.ok) {
3171
+ return { ok: false, unchanged: true, declined: true, reason: gate.reason };
3172
+ }
3173
+ try {
3174
+ switch (strategy) {
3175
+ case "github":
3176
+ return await refreshGitHubManagedTool(tool, known);
3177
+ case "pip":
3178
+ case "gem":
3179
+ return await refreshPackageManagerManagedTool(tool);
3180
+ case "maven":
3181
+ case "archive":
3182
+ return await refreshPinnedManagedTool(tool, known);
3183
+ default:
3184
+ return {
3185
+ ok: false,
3186
+ unchanged: true,
3187
+ reason: `strategy ${strategy} is not refreshable here`
3188
+ };
3189
+ }
3190
+ } finally {
3191
+ await gate.release?.();
3192
+ }
3193
+ }
3194
+ async function refreshGitHubManagedTool(tool, known) {
3195
+ const spec = tool.github;
3196
+ if (!spec)
3197
+ return { ok: false, unchanged: true, reason: "no github spec" };
3198
+ let response;
3199
+ try {
3200
+ response = await httpsGetWithMeta(`https://api.github.com/repos/${spec.repo}/releases/latest`, 5, {
3201
+ ...await githubApiAuthHeaders(),
3202
+ ...known.etag ? { "If-None-Match": known.etag } : {}
3203
+ });
3204
+ } catch (err) {
3205
+ const reason = err instanceof GitHubHttpError && err.anonymousRateLimitExhausted ? "GitHub API rate limit exhausted; authenticate with `gh auth login` or set GITHUB_TOKEN/GH_TOKEN and retry" : `release query failed: ${err.message}`;
3206
+ if (err instanceof GitHubHttpError && err.anonymousRateLimitExhausted) {
3207
+ recordDegradationOnce({
3208
+ kind: "github-api-rate-limit",
3209
+ subject: tool.id,
3210
+ reason
3211
+ });
3212
+ }
3213
+ return {
3214
+ ok: false,
3215
+ unchanged: true,
3216
+ reason
3217
+ };
3218
+ }
3219
+ if (response.statusCode === 304) {
3220
+ return {
3221
+ ok: true,
3222
+ unchanged: true,
3223
+ resolutionId: known.resolutionId,
3224
+ etag: known.etag,
3225
+ version: known.version
3226
+ };
3227
+ }
3228
+ let release;
3229
+ try {
3230
+ const parsed = JSON.parse(response.body.toString("utf8"));
3231
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3232
+ throw new Error("release metadata is not an object");
3233
+ }
3234
+ release = parsed;
3235
+ } catch (err) {
3236
+ return {
3237
+ ok: false,
3238
+ unchanged: true,
3239
+ reason: `release metadata unparseable: ${err.message}`
3240
+ };
3241
+ }
3242
+ const tag = release.tag_name;
3243
+ if (!tag) {
3244
+ return { ok: false, unchanged: true, reason: "release has no tag_name" };
3245
+ }
3246
+ if (known.resolutionId === tag) {
3247
+ return {
3248
+ ok: true,
3249
+ unchanged: true,
3250
+ resolutionId: tag,
3251
+ etag: response.etag ?? known.etag,
3252
+ version: known.version ?? tag
3253
+ };
3254
+ }
3255
+ const installed = await installGitHubTool(tool, release);
3256
+ if (!installed) {
3257
+ return {
3258
+ ok: false,
3259
+ unchanged: true,
3260
+ // Keep the ETag off the failure stamp: replaying it would make the next
3261
+ // attempt a 304 and skip the download this one never completed.
3262
+ resolutionId: known.resolutionId,
3263
+ version: known.version,
3264
+ reason: `install from release ${tag} failed`
3265
+ };
3266
+ }
3267
+ const verified = await verifyRefreshedArtifact(tool, installed);
3268
+ if (!verified) {
3269
+ return {
3270
+ ok: false,
3271
+ unchanged: false,
3272
+ resolutionId: known.resolutionId,
3273
+ version: known.version,
3274
+ reason: `release ${tag} installed but its binary does not run`
3275
+ };
3276
+ }
3277
+ return {
3278
+ ok: true,
3279
+ unchanged: false,
3280
+ resolutionId: tag,
3281
+ etag: response.etag,
3282
+ version: tag
3283
+ };
3284
+ }
3285
+ async function verifyRefreshedArtifact(tool, installedPath) {
3286
+ if (tool.installStrategy === "archive" && (!tool.archive?.launcher || tool.verification === "tree-manifest")) {
3287
+ await updateProbeCache(tool.id, installedPath);
3288
+ return true;
3289
+ }
3290
+ if (!await verifyToolBinary(installedPath, void 0, void 0, getToolVerificationTimeout(tool), tool.checkArgs)) {
3291
+ logSessionStart(`managed-tool-refresh ${tool.id}: refreshed artifact at ${installedPath} failed its verification check`);
3292
+ return false;
3293
+ }
3294
+ await updateProbeCache(tool.id, installedPath);
3295
+ return true;
3296
+ }
3297
+ async function refreshPackageManagerManagedTool(tool) {
3298
+ if (!tool.packageName) {
3299
+ return { ok: false, unchanged: true, reason: "no package name" };
3300
+ }
3301
+ const previous = await probeManagedToolVersion(tool);
3302
+ const installed = tool.installStrategy === "pip" ? (
3303
+ // `-U` is the whole fix: without it pip treats the installed copy as
3304
+ // satisfying the requirement and the day-one version never moves.
3305
+ await installPipTool(tool.id, tool.packageName, tool.binaryName ?? tool.id, {
3306
+ upgrade: true
3307
+ })
3308
+ ) : (
3309
+ // `gem install` always fetches the newest version that satisfies the
3310
+ // requirement, so the install command IS the upgrade command.
3311
+ await installGemTool(tool.id, tool.packageName)
3312
+ );
3313
+ if (!installed) {
3314
+ return {
3315
+ ok: false,
3316
+ unchanged: true,
3317
+ version: previous,
3318
+ reason: `${tool.installStrategy} upgrade failed`
3319
+ };
3320
+ }
3321
+ const current = await probeManagedToolVersion(tool);
3322
+ if (previous !== void 0 && current === void 0) {
3323
+ return {
3324
+ ok: false,
3325
+ unchanged: false,
3326
+ version: previous,
3327
+ reason: `${tool.installStrategy} upgrade left the binary unable to report a version`
3328
+ };
3329
+ }
3330
+ return {
3331
+ ok: true,
3332
+ // An unreadable version on both sides cannot prove a move, so it reads as
3333
+ // unchanged rather than inventing one.
3334
+ unchanged: previous === void 0 || current === void 0 || previous === current,
3335
+ version: current ?? previous
3336
+ };
3337
+ }
3338
+ async function refreshPinnedManagedTool(tool, known) {
3339
+ const coordinate = tool.installStrategy === "maven" ? tool.maven && mavenCoordinate(tool.maven) : tool.archive && resolveArchiveUrl(tool.archive);
3340
+ if (!coordinate) {
3341
+ return {
3342
+ ok: false,
3343
+ unchanged: true,
3344
+ reason: "no coordinate for this platform"
3345
+ };
3346
+ }
3347
+ if (known.resolutionId === coordinate) {
3348
+ return {
3349
+ ok: true,
3350
+ unchanged: true,
3351
+ resolutionId: coordinate,
3352
+ version: known.version
3353
+ };
3354
+ }
3355
+ const installed = tool.installStrategy === "maven" ? await installMavenTool(tool) : await installArchiveTool(tool);
3356
+ if (!installed) {
3357
+ return {
3358
+ ok: false,
3359
+ unchanged: true,
3360
+ resolutionId: known.resolutionId,
3361
+ version: known.version,
3362
+ reason: `reinstall from ${coordinate} failed`
3363
+ };
3364
+ }
3365
+ if (!await verifyRefreshedArtifact(tool, installed)) {
3366
+ return {
3367
+ ok: false,
3368
+ unchanged: false,
3369
+ resolutionId: known.resolutionId,
3370
+ version: known.version,
3371
+ reason: `reinstall from ${coordinate} produced an artifact that does not run`
3372
+ };
3373
+ }
3374
+ return {
3375
+ ok: true,
3376
+ // A first-ever stamp is an adoption, not a version move: the coordinate did
3377
+ // not change, pi-lens simply had no record of it. Reporting it as a move
3378
+ // would put a false "x → y" row in the log.
3379
+ unchanged: known.resolutionId === void 0,
3380
+ resolutionId: coordinate,
3381
+ version: coordinate
3382
+ };
3383
+ }
3384
+ function resolveManagedNpmBinPath(binaryName) {
3385
+ const binBase = path3.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
3386
+ return installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
3387
+ }
3388
+ function invalidateManagedToolResolution(toolId) {
3389
+ resolvedPathCache.delete(toolId);
3390
+ if (_probeCache !== null)
3391
+ delete _probeCache[toolId];
3392
+ markProbeCacheChange(toolId, null);
3393
+ }
3394
+ var MAVEN_CENTRAL_BASE = "https://repo1.maven.org/maven2";
3395
+ async function installMavenTool(tool) {
3396
+ const spec = tool.maven;
3397
+ if (!spec)
3398
+ return void 0;
3399
+ const binaryName = tool.binaryName ?? tool.id;
3400
+ const isWindows = process.platform === "win32";
3401
+ if (!await isCommandAvailable("java", ["-version"])) {
3402
+ logSessionStart(`maven-install ${tool.id}: java not found \u2014 a JAR tool can't run without a JRE`);
3403
+ return void 0;
3404
+ }
3405
+ let base = spec.repoBaseUrl ?? MAVEN_CENTRAL_BASE;
3406
+ while (base.endsWith("/"))
3407
+ base = base.slice(0, -1);
3408
+ const groupPath = spec.groupId.replace(/\./g, "/");
3409
+ const jarFile = `${spec.artifactId}-${spec.version}${spec.classifier ? `-${spec.classifier}` : ""}.jar`;
3410
+ const url = `${base}/${groupPath}/${spec.artifactId}/${spec.version}/${jarFile}`;
3411
+ lastInstallResolutionId.set(tool.id, mavenCoordinate(spec));
3412
+ logSessionStart(`maven-install ${tool.id}: downloading ${url}`);
3413
+ let jarBuffer;
3414
+ try {
3415
+ jarBuffer = await httpsGet(url);
3416
+ } catch (err) {
3417
+ logSessionStart(`maven-install ${tool.id}: download failed: ${err.message}`);
3418
+ return void 0;
3419
+ }
3420
+ try {
3421
+ await fs2.mkdir(GITHUB_BIN_DIR, { recursive: true });
3422
+ const jarPath = path3.join(GITHUB_BIN_DIR, `${tool.id}.jar`);
3423
+ await writeFileAtomicAsync(jarPath, jarBuffer, { bestEffort: false });
3424
+ const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
3425
+ const launcherPath = path3.join(GITHUB_BIN_DIR, launcherName);
3426
+ if (isWindows) {
3427
+ await writeFileAtomicAsync(launcherPath, `@echo off\r
3428
+ java -jar "%~dp0${tool.id}.jar" %*\r
3429
+ `, { bestEffort: false });
3430
+ } else {
3431
+ await writeFileAtomicAsync(launcherPath, `#!/bin/sh
3432
+ exec java -jar "$(dirname "$0")/${tool.id}.jar" "$@"
3433
+ `, { bestEffort: false, mode: 488 });
3434
+ }
3435
+ logSessionStart(`maven-install ${tool.id}: installed \u2192 ${launcherPath} (${jarBuffer.length} bytes)`);
3436
+ debugLog(`[maven] installed ${tool.name} \u2192 ${launcherPath}`);
3437
+ return launcherPath;
3438
+ } catch (err) {
3439
+ logSessionStart(`maven-install ${tool.id}: install failed: ${err.message}`);
3440
+ return void 0;
3441
+ }
3442
+ }
3443
+ function resolveArchiveUrl(spec, platform = installerPlatform(), arch = process.arch) {
3444
+ return typeof spec.url === "function" ? spec.url(platform, arch) : spec.url;
3445
+ }
3446
+ function resolveArchiveKind(spec, platform = installerPlatform(), arch = process.arch) {
3447
+ return typeof spec.kind === "function" ? spec.kind(platform, arch) : spec.kind;
3448
+ }
3449
+ function recordArchiveExtractionDegradation(toolId, format, reason) {
3450
+ recordDegradationOnce({
3451
+ kind: "managed-tool-install",
3452
+ subject: `${toolId}:${format}`,
3453
+ reason: `archive extraction ${reason}`
3454
+ });
3455
+ }
3456
+ async function stripExtractedArchiveRoot(dir, components) {
3457
+ for (let i = 0; i < components; i++) {
3458
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
3459
+ const [entry] = entries;
3460
+ if (entries.length !== 1 || !entry?.isDirectory())
3461
+ return false;
3462
+ const root = path3.join(dir, entry.name);
3463
+ for (const child of await fs2.readdir(root))
3464
+ await fs2.rename(path3.join(root, child), path3.join(dir, child));
3465
+ await fs2.rm(root, { recursive: true, force: true });
3466
+ }
3467
+ return true;
3468
+ }
3469
+ async function swapExtractedDir(toolId, tmpDir, finalDir) {
3470
+ const backupDir = `${finalDir}.rollback`;
3471
+ await fs2.rm(backupDir, { recursive: true, force: true });
3472
+ let hadPrevious = false;
3473
+ try {
3474
+ await fs2.rename(finalDir, backupDir);
3475
+ hadPrevious = true;
3476
+ } catch (err) {
3477
+ if (err.code !== "ENOENT")
3478
+ throw err;
3479
+ }
3480
+ try {
3481
+ await fs2.rename(tmpDir, finalDir);
3482
+ } catch (err) {
3483
+ if (hadPrevious) {
3484
+ try {
3485
+ await fs2.rename(backupDir, finalDir);
3486
+ } catch (rollbackErr) {
3487
+ recordDegradationOnce({
3488
+ kind: "managed-tool-refresh",
3489
+ subject: toolId,
3490
+ reason: `swap rollback failed after a failed install: working copy orphaned at ${backupDir} (${rollbackErr.message})`
3491
+ });
3492
+ logSessionStart(`archive-install ${toolId}: swap rollback failed \u2014 working copy orphaned at ${backupDir} (${rollbackErr.message})`);
3493
+ }
3494
+ }
3495
+ throw err;
3496
+ }
3497
+ if (hadPrevious) {
3498
+ await fs2.rm(backupDir, { recursive: true, force: true }).catch(() => {
3499
+ });
3500
+ }
3501
+ }
3502
+ var ARCHIVE_RUNTIME_HOMES = {
3503
+ java: { env: "JAVA_HOME", dir: "bin" }
3504
+ };
3505
+ function runtimeHomeVerdict(runtime) {
3506
+ const home = ARCHIVE_RUNTIME_HOMES[runtime];
3507
+ const root = home ? process.env[home.env] : void 0;
3508
+ if (!home || !root)
3509
+ return void 0;
3510
+ const windows = installerPlatform() === "win32";
3511
+ const names = windows ? [`${runtime}.exe`, runtime] : [runtime];
3512
+ return names.some((name) => {
3513
+ try {
3514
+ const stat = statSync(path3.join(root, home.dir, name));
3515
+ return stat.isFile() && stat.size > 0 && (windows || (stat.mode & 73) !== 0);
3516
+ } catch {
3517
+ return false;
3518
+ }
3519
+ });
3520
+ }
3521
+ async function installArchiveTool(tool) {
3522
+ const spec = tool.archive;
3523
+ if (!spec)
3524
+ return void 0;
3525
+ const binaryName = tool.binaryName ?? tool.id;
3526
+ const platform = installerPlatform();
3527
+ const isWindows = platform === "win32";
3528
+ const archiveKind = resolveArchiveKind(spec, platform, process.arch);
3529
+ const url = resolveArchiveUrl(spec, platform, process.arch);
3530
+ if (!url) {
3531
+ logSessionStart(`archive-install ${tool.id}: no archive for ${process.platform}/${process.arch} \u2014 unsupported, skipping`);
3532
+ return void 0;
3533
+ }
3534
+ lastInstallResolutionId.set(tool.id, url);
3535
+ logSessionStart(`archive-install ${tool.id}: downloading ${url}`);
3536
+ let archiveBuffer;
3537
+ try {
3538
+ archiveBuffer = await httpsGet(url);
3539
+ } catch (err) {
3540
+ logSessionStart(`archive-install ${tool.id}: download failed: ${err.message}`);
3541
+ return void 0;
3542
+ }
3543
+ const pinned = spec.sha256?.[url];
3544
+ const digest = createHash("sha256").update(archiveBuffer).digest("hex");
3545
+ if (digest !== pinned) {
3546
+ const reason = pinned === void 0 ? "sha256-unpinned" : "sha256-mismatch";
3547
+ recordDegradationOnce({
3548
+ kind: "managed-tool-install",
3549
+ subject: `${tool.id}:${archiveKind}:integrity`,
3550
+ reason: `archive integrity ${reason}`
3551
+ });
3552
+ installFailureReasons.set(tool.id, `archive integrity ${reason} (${archiveBuffer.length} bytes from ${url})`);
3553
+ logSessionStart(`archive-install ${tool.id}: refused ${reason} (got ${digest}, pinned ${pinned ?? "none"}) \u2014 keeping installed version`);
3554
+ return void 0;
3555
+ }
3556
+ const extractName = tool.id;
3557
+ const tmpExtractName = `${extractName}.refresh-tmp`;
3558
+ const archiveName = `${tool.id}.download.${archiveKind === "zip" ? "zip" : "tgz"}`;
3559
+ const extractDir = path3.join(TOOLS_DIR, extractName);
3560
+ const tmpExtractDir = path3.join(TOOLS_DIR, tmpExtractName);
3561
+ const tmpArchive = path3.join(TOOLS_DIR, archiveName);
3562
+ try {
3563
+ await fs2.mkdir(TOOLS_DIR, { recursive: true });
3564
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true });
3565
+ await fs2.mkdir(tmpExtractDir, { recursive: true });
3566
+ await fs2.writeFile(tmpArchive, archiveBuffer);
3567
+ const stripComponents = spec.stripComponents ?? 1;
3568
+ const extractionArgs = archiveKind === "zip" && isWindows ? [
3569
+ "-NoProfile",
3570
+ "-Command",
3571
+ `Expand-Archive -LiteralPath '${archiveName}' -DestinationPath '${tmpExtractName}' -Force`
3572
+ ] : archiveKind === "zip" ? ["-q", "-o", archiveName, "-d", tmpExtractName] : [
3573
+ "-xzf",
3574
+ archiveName,
3575
+ "-C",
3576
+ tmpExtractName,
3577
+ ...stripComponents > 0 ? [`--strip-components=${stripComponents}`] : []
3578
+ ];
3579
+ const extractor = archiveKind === "zip" && isWindows ? `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` : archiveKind === "zip" ? "unzip" : isWindows ? `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\tar.exe` : "tar";
3580
+ const extractionResult = await safeSpawnAsync(extractor, extractionArgs, {
3581
+ cwd: TOOLS_DIR,
3582
+ timeout: 12e4,
3583
+ ignoreAmbientSignal: true,
3584
+ lifetimeCoupled: true
3585
+ });
3586
+ const extracted = {
3587
+ ok: extractionResult.status === 0,
3588
+ reason: extractionResult.spawnFailure?.kind === "tool-not-found" ? "extractor-unavailable" : extractionResult.spawnFailure ? "extractor-error" : "extractor-exit"
3589
+ };
3590
+ await fs2.rm(tmpArchive, { force: true });
3591
+ if (!extracted.ok) {
3592
+ recordArchiveExtractionDegradation(tool.id, archiveKind, extracted.reason);
3593
+ logSessionStart(`archive-install ${tool.id}: ${archiveKind} extraction failed (${extracted.reason}) \u2014 keeping installed version`);
3594
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
3595
+ });
3596
+ return void 0;
3597
+ }
3598
+ if (archiveKind === "zip" && !await stripExtractedArchiveRoot(tmpExtractDir, stripComponents)) {
3599
+ recordArchiveExtractionDegradation(tool.id, archiveKind, "layout-invalid");
3600
+ logSessionStart(`archive-install ${tool.id}: ${archiveKind} layout invalid \u2014 keeping installed version`);
3601
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
3602
+ });
3603
+ return void 0;
3604
+ }
3605
+ if (!spec.launcher) {
3606
+ const tmpMarker = spec.treeMarker ? path3.join(tmpExtractDir, ...spec.treeMarker.split("/")) : tmpExtractDir;
3607
+ try {
3608
+ await fs2.access(tmpMarker);
3609
+ } catch {
3610
+ recordArchiveExtractionDegradation(tool.id, archiveKind, "marker-missing");
3611
+ logSessionStart(`archive-install ${tool.id}: ${archiveKind} marker missing after extraction \u2014 keeping installed version`);
3612
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
3613
+ });
3614
+ return void 0;
3615
+ }
3616
+ await swapExtractedDir(tool.id, tmpExtractDir, extractDir);
3617
+ logSessionStart(`archive-install ${tool.id}: installed tree bundle \u2192 ${extractDir} (extracted ${archiveBuffer.length} bytes, sha256 verified ${digest})`);
3618
+ debugLog(`[archive] installed ${tool.name} bundle \u2192 ${extractDir}`);
3619
+ return extractDir;
3620
+ }
3621
+ const launcherParts = spec.launcher.split("/").map((p) => p);
3622
+ const tmpInnerLauncher = path3.join(tmpExtractDir, ...launcherParts);
3623
+ const tmpResolvedInner = isWindows ? `${tmpInnerLauncher}.bat` : tmpInnerLauncher;
3624
+ try {
3625
+ await fs2.access(tmpResolvedInner);
3626
+ } catch {
3627
+ recordArchiveExtractionDegradation(tool.id, archiveKind, "launcher-missing");
3628
+ logSessionStart(`archive-install ${tool.id}: ${archiveKind} launcher missing after extraction \u2014 keeping installed version`);
3629
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
3630
+ });
3631
+ return void 0;
3632
+ }
3633
+ if (!isWindows)
3634
+ await fs2.chmod(tmpResolvedInner, 488).catch(() => {
3635
+ });
3636
+ const launcherStat = await fs2.stat(tmpResolvedInner).catch(() => void 0);
3637
+ const launcherRunnable = launcherStat?.isFile() === true && launcherStat.size > 0 && (isWindows || (launcherStat.mode & 73) !== 0);
3638
+ if (!launcherRunnable) {
3639
+ recordArchiveExtractionDegradation(tool.id, archiveKind, "launcher-invalid");
3640
+ logSessionStart(`archive-install ${tool.id}: ${archiveKind} launcher is empty or not executable \u2014 keeping installed version`);
3641
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
3642
+ });
3643
+ return void 0;
3644
+ }
3645
+ await swapExtractedDir(tool.id, tmpExtractDir, extractDir);
3646
+ const innerLauncher = path3.join(extractDir, ...launcherParts);
3647
+ const resolvedInner = isWindows ? `${innerLauncher}.bat` : innerLauncher;
3648
+ await fs2.mkdir(GITHUB_BIN_DIR, { recursive: true });
3649
+ const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
3650
+ const shimPath = path3.join(GITHUB_BIN_DIR, launcherName);
3651
+ if (isWindows) {
3652
+ await writeFileAtomicAsync(shimPath, `@echo off\r
3653
+ call "${resolvedInner}" %*\r
3654
+ `, { bestEffort: false });
3655
+ } else {
3656
+ await writeFileAtomicAsync(shimPath, `#!/bin/sh
3657
+ exec "${resolvedInner}" "$@"
3658
+ `, { bestEffort: false, mode: 488 });
3659
+ }
3660
+ logSessionStart(`archive-install ${tool.id}: installed \u2192 ${shimPath} (extracted ${archiveBuffer.length} bytes, sha256 verified ${digest})`);
3661
+ debugLog(`[archive] installed ${tool.name} \u2192 ${shimPath}`);
3662
+ return shimPath;
3663
+ } catch (err) {
3664
+ await fs2.rm(tmpArchive, { force: true }).catch(() => {
3665
+ });
3666
+ await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
3667
+ });
3668
+ logSessionStart(`archive-install ${tool.id}: install failed: ${err.message} \u2014 keeping installed version`);
3669
+ return void 0;
3670
+ }
3671
+ }
3672
+ function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
3673
+ const message = boundInstallError(err.message);
3674
+ logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
3675
+ installFailureReasons.set(toolId, message);
3676
+ return void 0;
3677
+ }
3678
+ var INSTALL_ERROR_LINE_LIMIT = 1e3;
3679
+ var INSTALL_CANDIDATE_ERROR_LIMIT = 200;
3680
+ function boundInstallError(value, limit = INSTALL_ERROR_LINE_LIMIT) {
3681
+ const line = value.replace(/[\r\n]+/g, " ").trim();
3682
+ return line.length > limit ? `${line.slice(0, limit - 3)}...` : line;
3683
+ }
3684
+ var InstallLockLostError = class extends Error {
3685
+ };
3686
+ async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 1e4, packageEntryOf, ownsLock) {
3687
+ const assertOwnsLock = (context) => {
3688
+ if (ownsLock && !ownsLock()) {
3689
+ recordDegradationOnce({
3690
+ kind: "install-lock-lost-mid-install",
3691
+ subject: toolId,
3692
+ reason: `install lock generation lost before ${context}; aborting rather than risk a second writer in TOOLS_DIR`
3693
+ });
3694
+ throw new InstallLockLostError(`install lock lost before ${context} for ${packageName} (#3515)`);
3695
+ }
3696
+ };
3697
+ try {
3698
+ await fs2.mkdir(TOOLS_DIR, { recursive: true });
3699
+ const packageJsonPath = path3.join(TOOLS_DIR, "package.json");
3700
+ try {
3701
+ await fs2.access(packageJsonPath);
3702
+ } catch {
3703
+ await writeFileAtomicAsync(packageJsonPath, JSON.stringify({ name: "pi-lens-tools", version: "1.0.0" }, null, 2), { bestEffort: false });
3704
+ }
3705
+ const isWindows = installerPlatform() === "win32";
3706
+ const pm = await resolveNodePackageManager(TOOLS_DIR);
3707
+ const testNpmScript = process.env.PI_LENS_TEST_MODE === "1" ? process.env.PI_LENS_TEST_NPM_SCRIPT : void 0;
3708
+ const pmCommand = testNpmScript ? process.execPath : pmBinary(pm);
3709
+ const needsScripts = NEEDS_POSTINSTALL.has(packageName);
3710
+ const baseInstallArgs = installArgs(pm, packageName, {
3711
+ ignoreScripts: !needsScripts
3712
+ });
3713
+ const INSTALL_TIMEOUT_MS = Number(process.env.PI_LENS_INSTALL_TIMEOUT_MS) || 12e4;
3714
+ const runInstallAttempt = async (args) => {
3715
+ const result = await safeSpawnAsync(pmCommand, args, {
3716
+ cwd: TOOLS_DIR,
3717
+ timeout: INSTALL_TIMEOUT_MS,
3718
+ ignoreAmbientSignal: true,
3719
+ lifetimeCoupled: true
3720
+ });
3721
+ return {
3722
+ ok: result.status === 0,
3723
+ stderr: result.error?.message ?? result.stderr
3724
+ };
3725
+ };
3726
+ assertOwnsLock(`spawning ${pmCommand} install`);
3727
+ let outcome = await runInstallAttempt([
3728
+ ...testNpmScript ? [testNpmScript] : [],
3729
+ ...baseInstallArgs
3730
+ ]);
3731
+ const erResolve = outcome.ok === false && /npm\s+error\s+ERESOLVE|\bERESOLVE\b|could not resolve/i.test(outcome.stderr);
3732
+ if (pm === "npm" && erResolve) {
3733
+ assertOwnsLock(`retrying ${pmCommand} install with --legacy-peer-deps`);
3734
+ const retryArgs = installArgs(pm, packageName, {
3735
+ ignoreScripts: !needsScripts,
3736
+ legacyPeerDeps: true
3737
+ });
3738
+ logSessionStart(`auto-install npm ${packageName}: retry with --legacy-peer-deps after ERESOLVE`);
3739
+ outcome = await runInstallAttempt([
3740
+ ...testNpmScript ? [testNpmScript] : [],
3741
+ ...retryArgs
3742
+ ]);
3743
+ }
3744
+ if (!outcome.ok) {
3745
+ throw new Error(`Failed to install ${packageName}: ${outcome.stderr}`);
3746
+ }
3747
+ const binBase = path3.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
3748
+ const binPath = installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
3749
+ if (installerPlatform() !== "win32") {
3750
+ try {
3751
+ await fs2.chmod(binPath, 488);
3752
+ } catch {
3753
+ }
3754
+ }
3755
+ await new Promise((r) => setTimeout(r, 500));
3756
+ debugLog(`Verifying ${binaryName}...`);
3757
+ let isValid = false;
3758
+ let lastAttemptTransient = false;
3759
+ let lastAttemptInconclusive = false;
3760
+ for (let attempt = 1; attempt <= 3; attempt++) {
3761
+ lastAttemptTransient = false;
3762
+ lastAttemptInconclusive = false;
3763
+ isValid = await verifyToolBinary(binPath, void 0, () => {
3764
+ lastAttemptTransient = true;
3765
+ }, verificationTimeoutMs, verificationArgs, packageEntryOf, () => {
3766
+ lastAttemptInconclusive = true;
3767
+ });
3768
+ if (isValid)
3769
+ break;
3770
+ if (attempt < 3) {
3771
+ logSessionStart(`auto-install verify ${binaryName}: attempt ${attempt} failed, retrying in ${attempt}s`);
3772
+ await new Promise((r) => setTimeout(r, 1e3 * attempt));
3773
+ }
3774
+ }
3775
+ if (!isValid && lastAttemptInconclusive) {
3776
+ if (await verifyNpmPackageEntry(binPath, packageName)) {
3777
+ logSessionStart(`auto-install ${packageName}: verification inconclusive (output truncated before the transport-required marker) but the installed tree is intact; keeping installation for re-probe`);
3778
+ return void 0;
3779
+ }
3780
+ logSessionStart(`auto-install ${packageName}: verification inconclusive AND the installed tree is incomplete; cleaning up so the next install can repair it`);
3781
+ }
3782
+ if (!isValid && lastAttemptTransient) {
3783
+ logSessionStart(`auto-install ${packageName}: verification inconclusive (transient); keeping installation for re-probe`);
3784
+ return void 0;
3785
+ }
3786
+ if (!isValid) {
3787
+ logSessionStart(`auto-install ${packageName}: installed but verification failed, cleaning up`);
3788
+ try {
3789
+ const packagePath = path3.join(TOOLS_DIR, "node_modules", packageName);
3790
+ await fs2.rm(packagePath, { recursive: true, force: true });
3791
+ await fs2.rm(binBase, { force: true });
3792
+ if (isWindows) {
3793
+ await fs2.rm(`${binBase}.cmd`, { force: true });
3794
+ await fs2.rm(`${binBase}.ps1`, { force: true });
3795
+ }
3796
+ } catch {
3797
+ }
3798
+ return void 0;
3799
+ }
3800
+ return binPath;
3801
+ } catch (err) {
3802
+ return recordPackageManagerInstallException(toolId, "npm", packageName, err);
3803
+ }
3804
+ }
3805
+ function pipCommandCandidates() {
3806
+ return process.platform === "win32" ? ["pip", "py", "python"] : ["pip3", "pip", "python3", "python"];
3807
+ }
3808
+ function pipScriptsDir(base, platform = installerPlatform()) {
3809
+ return path3.join(base, platform === "win32" ? "Scripts" : "bin");
3810
+ }
3811
+ var pipPep668LoggedRefusals = createGenerationMap("installer-pep668-log");
3812
+ async function pipConstraintEnvFor(toolId) {
3813
+ const constraints = TOOLS.find((t) => t.id === toolId)?.pipConstraints;
3814
+ if (!constraints || constraints.length === 0)
3815
+ return {};
3816
+ const directories = [
3817
+ path3.join(getGlobalPiLensDir(), "pip-constraints"),
3818
+ path3.join(os.tmpdir(), "pi-lens-pip-constraints")
3819
+ ];
3820
+ const directory = directories.find((candidate) => !/\s/.test(candidate));
3821
+ if (!directory) {
3822
+ recordDegradationOnce({
3823
+ kind: "pip-constraint-path-unusable",
3824
+ subject: toolId,
3825
+ reason: `no whitespace-free directory for the constraints file (tried ${directories.join(", ")}); installing unconstrained`
3826
+ });
3827
+ return {};
3828
+ }
3829
+ const file = path3.join(directory, `${toolId}.txt`);
3830
+ try {
3831
+ await fs2.mkdir(path3.dirname(file), { recursive: true });
3832
+ await writeFileAtomicAsync(file, `${constraints.join("\n")}
3833
+ `, {
3834
+ bestEffort: false
3835
+ });
3836
+ } catch (err) {
3837
+ recordDegradationOnce({
3838
+ kind: "pip-constraint-file-unwritable",
3839
+ subject: toolId,
3840
+ reason: `${file}: ${err instanceof Error ? err.message : String(err)}`
3841
+ });
3842
+ return {};
3843
+ }
3844
+ logSessionStart(`auto-install pip ${toolId}: constraining resolution with ${constraints.join(", ")} (${file})`);
3845
+ return { PIP_CONSTRAINT: file, UV_CONSTRAINT: file };
3846
+ }
3847
+ async function installPipTool(toolId, packageName, binaryName, options = {}) {
3848
+ try {
3849
+ const isWindows = installerPlatform() === "win32";
3850
+ const verb = options.upgrade ? ["install", "-U"] : ["install"];
3851
+ const pipConstraintEnv = await pipConstraintEnvFor(toolId);
3852
+ const pipCandidates = pipCommandCandidates().map((command) => ({
3853
+ command,
3854
+ args: command === "pip" || command === "pip3" ? [...verb, packageName] : ["-m", "pip", ...verb, packageName]
3855
+ }));
3856
+ const pep668 = /externally-managed-environment/i;
3857
+ const refuse = (strategy, reason) => {
3858
+ if (!pep668.test(reason))
3859
+ return;
3860
+ const subject = `${toolId}:${strategy}`;
3861
+ recordDegradationOnce({
3862
+ kind: "pip-pep668-strategy-refused",
3863
+ subject,
3864
+ reason
3865
+ });
3866
+ const logKey = `${getDegradationLedgerGeneration()}:${subject}`;
3867
+ if (pipPep668LoggedRefusals.current(logKey) === 0) {
3868
+ pipPep668LoggedRefusals.bump(logKey);
3869
+ logSessionStart(`auto-install pip ${packageName}: ${strategy} refused by PEP 668 (${boundInstallError(reason)})`);
3870
+ }
3871
+ };
3872
+ const succeeded = (strategy, binaryPath) => {
3873
+ recordDegradationOnce({
3874
+ kind: "pip-install-strategy-succeeded",
3875
+ subject: `${toolId}:${strategy}`,
3876
+ reason: binaryPath
3877
+ });
3878
+ return binaryPath;
3879
+ };
3880
+ const run = (command, args, env) => {
3881
+ const spawnEnv = Object.keys(pipConstraintEnv).length > 0 ? { ...env ?? process.env, ...pipConstraintEnv } : env;
3882
+ return safeSpawnAsync(command, args, {
3883
+ timeout: 12e4,
3884
+ ignoreAmbientSignal: true,
3885
+ lifetimeCoupled: true,
3886
+ cwd: resolveToolCwd("runner", toolId, getGlobalPiLensDir(), {
3887
+ cwd: getGlobalPiLensDir(),
3888
+ suppressTelemetry: true
3889
+ }).cwd,
3890
+ ...spawnEnv ? { env: spawnEnv } : {}
3891
+ });
3892
+ };
3893
+ const addBinToPath = async (binDir) => {
3894
+ try {
3895
+ await fs2.access(binDir);
3896
+ } catch {
3897
+ return void 0;
3898
+ }
3899
+ const currentPath = process.env.PATH || process.env.Path || "";
3900
+ const separator = isWindows ? ";" : path3.delimiter;
3901
+ if (!currentPath.toLowerCase().split(separator).includes(binDir.toLowerCase())) {
3902
+ const updatedPath = `${binDir}${separator}${currentPath}`;
3903
+ process.env.PATH = updatedPath;
3904
+ if (isWindows)
3905
+ process.env.Path = updatedPath;
3906
+ }
3907
+ const names = isWindows ? [`${binaryName}.exe`, `${binaryName}.cmd`, binaryName] : [binaryName];
3908
+ for (const name of names) {
3909
+ const candidate = path3.join(binDir, name);
3910
+ try {
3911
+ await fs2.access(candidate);
3912
+ return candidate;
3913
+ } catch {
3914
+ }
3915
+ }
3916
+ return void 0;
3917
+ };
3918
+ if (await isCommandAvailable("pipx")) {
3919
+ const result = await run("pipx", options.upgrade ? ["upgrade", packageName] : ["install", "--force", packageName]);
3920
+ const error = (result.error?.message ?? result.stderr).trim();
3921
+ if (result.status === 0) {
3922
+ const location = await run("pipx", [
3923
+ "environment",
3924
+ "--value",
3925
+ "PIPX_BIN_DIR"
3926
+ ]);
3927
+ const binDir = location.status === 0 && location.stdout.trim() ? location.stdout.trim() : path3.join(os.homedir(), ".local", "bin");
3928
+ const binaryPath = await addBinToPath(binDir);
3929
+ if (binaryPath)
3930
+ return succeeded("pipx", binaryPath);
3931
+ throw new Error(`pipx installed ${packageName} but ${binaryName} is not resolvable`);
3932
+ }
3933
+ refuse("pipx", error);
3934
+ }
3935
+ const pythonCandidates = pipCandidates.filter(({ command }) => command === "python3" || command === "python" || command === "py");
3936
+ const pythonAvailability = await Promise.all(pythonCandidates.map(({ command }) => isCommandAvailable(command)));
3937
+ const availablePythonCandidates = pythonCandidates.filter((_, index) => pythonAvailability[index]);
3938
+ const venvRoot = path3.join(getGlobalPiLensDir(), "pip-tools");
3939
+ for (const candidate of availablePythonCandidates) {
3940
+ const venvBin = pipScriptsDir(venvRoot, installerPlatform());
3941
+ let venvPip = path3.join(venvBin, isWindows ? "pip.exe" : "pip");
3942
+ try {
3943
+ await fs2.access(venvPip);
3944
+ } catch {
3945
+ const created = await run(candidate.command, ["-m", "venv", venvRoot]);
3946
+ const error2 = (created.error?.message ?? created.stderr).trim();
3947
+ if (created.status !== 0) {
3948
+ refuse("venv", error2 || "python venv module unavailable");
3949
+ continue;
3950
+ }
3951
+ }
3952
+ try {
3953
+ await fs2.access(venvPip);
3954
+ } catch {
3955
+ venvPip = path3.join(venvBin, isWindows ? "pip.cmd" : "pip3");
3956
+ try {
3957
+ await fs2.access(venvPip);
3958
+ } catch {
3959
+ continue;
3960
+ }
3961
+ }
3962
+ const result = await run(venvPip, [...verb, packageName]);
3963
+ const error = (result.error?.message ?? result.stderr).trim();
3964
+ if (result.status === 0) {
3965
+ const binaryPath = await addBinToPath(venvBin);
3966
+ if (binaryPath)
3967
+ return succeeded("venv", binaryPath);
3968
+ throw new Error(`venv installed ${packageName} but ${binaryName} is not resolvable`);
3969
+ }
3970
+ refuse("venv", error);
3971
+ }
3972
+ const candidateErrors = [];
3973
+ let userRefused = false;
3974
+ for (const candidate of pipCandidates) {
3975
+ const args = candidate.command === "pip" || candidate.command === "pip3" ? [...verb, "--user", packageName] : ["-m", "pip", ...verb, "--user", packageName];
3976
+ const result = await run(candidate.command, args);
3977
+ const error = (result.error?.message ?? result.stderr).trim();
3978
+ if (result.status === 0) {
3979
+ const base = await new Promise((resolve) => {
3980
+ let probe;
3981
+ try {
3982
+ probe = spawn(candidate.command, ["-m", "site", "--user-base"], {
3983
+ stdio: ["ignore", "pipe", "pipe"],
3984
+ shell: isWindows
3985
+ });
3986
+ } catch {
3987
+ resolve("");
3988
+ return;
3989
+ }
3990
+ const stdout = createBoundedOutputSink();
3991
+ probe.stdout?.on("data", (data) => stdout.append(data));
3992
+ probe.on("exit", (code) => resolve(userBaseProbeResult(candidate.command, code, stdout)));
3993
+ probe.on("error", () => resolve(""));
3994
+ });
3995
+ const binaryPath = base ? await addBinToPath(pipScriptsDir(base, installerPlatform())) : void 0;
3996
+ return succeeded("user", binaryPath ?? packageName);
3997
+ }
3998
+ const candidateError = `${candidate.command} ${args.join(" ")}: ${boundInstallError(error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
3999
+ candidateErrors.push(candidateError);
4000
+ if (pep668.test(error)) {
4001
+ userRefused = true;
4002
+ refuse("user", error);
4003
+ }
4004
+ }
4005
+ if (!userRefused)
4006
+ throw new Error(`pip install failed: ${candidateErrors.join(" | ") || "unknown error"}`);
4007
+ const privateBase = path3.join(getGlobalPiLensDir(), "pip-user");
4008
+ const privateEnv = { ...process.env, PYTHONUSERBASE: privateBase };
4009
+ for (const candidate of pipCandidates) {
4010
+ const args = candidate.command === "pip" || candidate.command === "pip3" ? [...verb, "--user", "--break-system-packages", packageName] : [
4011
+ "-m",
4012
+ "pip",
4013
+ ...verb,
4014
+ "--user",
4015
+ "--break-system-packages",
4016
+ packageName
4017
+ ];
4018
+ const result = await run(candidate.command, args, privateEnv);
4019
+ const error = (result.error?.message ?? result.stderr).trim();
4020
+ if (result.status !== 0) {
4021
+ const candidateError = `${candidate.command} ${args.join(" ")}: ${boundInstallError(error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
4022
+ candidateErrors.push(candidateError);
4023
+ continue;
4024
+ }
4025
+ const binaryPath = await addBinToPath(pipScriptsDir(privateBase, installerPlatform()));
4026
+ if (binaryPath)
4027
+ return succeeded("private-prefix", binaryPath);
4028
+ throw new Error(`private-prefix pip installed ${packageName} but ${binaryName} is not resolvable`);
4029
+ }
4030
+ throw new Error(`pip install failed: ${candidateErrors.join(" | ") || "unknown error"}`);
4031
+ } catch (err) {
4032
+ return recordPackageManagerInstallException(toolId, "pip", packageName, err);
4033
+ }
4034
+ }
4035
+ async function installGemTool(toolId, packageName) {
4036
+ try {
4037
+ const gemResult = await safeSpawnAsync("gem", ["install", packageName, "--no-document"], {
4038
+ timeout: 12e4,
4039
+ ignoreAmbientSignal: true,
4040
+ lifetimeCoupled: true
4041
+ });
4042
+ const outcome = {
4043
+ ok: gemResult.status === 0,
4044
+ error: (gemResult.error?.message ?? gemResult.stderr).trim()
4045
+ };
4046
+ if (!outcome.ok) {
4047
+ throw new Error(`Failed to install ${packageName} via gem: ${outcome.error}`);
4048
+ }
4049
+ return packageName;
4050
+ } catch (err) {
4051
+ return recordPackageManagerInstallException(toolId, "gem", packageName, err);
4052
+ }
4053
+ }
4054
+ async function stampInstallResolution(toolId) {
4055
+ const tool = TOOLS.find((t) => t.id === toolId);
4056
+ if (tool?.installStrategy === "npm")
4057
+ return;
4058
+ try {
4059
+ const resolutionId = lastInstallResolutionId.get(toolId);
4060
+ const refresh = await import("./chunk-T4ERT73Y.js");
4061
+ await refresh.stampManagedToolInstall(toolId, resolutionId);
4062
+ } catch {
4063
+ }
4064
+ }
4065
+ async function finishInstallAttempt(toolId, ok, startedAt) {
4066
+ logSessionStart(`auto-install ${toolId}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
4067
+ if (ok)
4068
+ await stampInstallResolution(toolId);
4069
+ noteInstallAttempt(toolId, ok ? "succeeded" : "failed", ok ? void 0 : installFailureReasons.get(toolId) ?? "install failed");
4070
+ if (ok) {
4071
+ resetSafeSpawnWindowsCommandCache();
4072
+ try {
4073
+ const { resetMadgeManagedPathMemo } = await import("./chunk-T6D4Q4AQ.js");
4074
+ resetMadgeManagedPathMemo();
4075
+ } catch (err) {
4076
+ logSessionStart(`auto-install ${toolId}: madge memo reset failed: ${err.message}`);
4077
+ }
4078
+ }
4079
+ return ok;
4080
+ }
4081
+ async function installTool(toolId, ownsLock) {
4082
+ if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
4083
+ installFailureReasons.set(toolId, "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
4084
+ noteInstallAttempt(toolId, "declined", "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
4085
+ logSessionStart(`auto-install ${toolId}: refused \u2014 PI_LENS_DISABLE_TOOL_INSTALL=1`);
4086
+ return false;
4087
+ }
4088
+ const tool = TOOLS.find((t) => t.id === toolId);
4089
+ if (!tool) {
4090
+ noteInstallAttempt(toolId, "declined", "unknown tool id");
4091
+ logSessionStart(`auto-install ${toolId}: unknown tool id`);
4092
+ return false;
4093
+ }
4094
+ const startedAt = Date.now();
4095
+ logSessionStart(`auto-install ${tool.id}: start strategy=${tool.installStrategy} package=${tool.packageName ?? "n/a"}`);
4096
+ try {
4097
+ switch (tool.installStrategy) {
4098
+ case "npm": {
4099
+ if (!tool.packageName || !tool.binaryName)
4100
+ return false;
4101
+ const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool), packageEntryVerification(tool), ownsLock);
4102
+ if (npmPath !== void 0) {
4103
+ await import("./chunk-T4ERT73Y.js").then((m) => m.stampManagedToolInstalled(tool.id, tool.packageName)).catch(() => {
4104
+ });
4105
+ }
4106
+ return finishInstallAttempt(tool.id, npmPath !== void 0, startedAt);
4107
+ }
4108
+ case "pip": {
4109
+ if (!tool.packageName)
4110
+ return false;
4111
+ const pipPath = await installPipTool(tool.id, tool.packageName, tool.binaryName ?? tool.id);
4112
+ return finishInstallAttempt(tool.id, pipPath !== void 0, startedAt);
4113
+ }
4114
+ case "gem": {
4115
+ if (!tool.packageName)
4116
+ return false;
4117
+ const gemPath = await installGemTool(tool.id, tool.packageName);
4118
+ return finishInstallAttempt(tool.id, gemPath !== void 0, startedAt);
4119
+ }
4120
+ case "github": {
4121
+ if (!tool.github)
4122
+ return false;
4123
+ if (!tool.github.assetMatch(process.platform, process.arch)) {
4124
+ const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
4125
+ noteInstallAttempt(tool.id, "unavailable", reason);
4126
+ logSessionStart(`auto-install ${tool.id}: ${reason}`);
4127
+ return false;
4128
+ }
4129
+ const ghPath = await installGitHubTool(tool);
4130
+ return finishInstallAttempt(tool.id, ghPath !== void 0, startedAt);
4131
+ }
4132
+ case "maven": {
4133
+ if (!tool.maven)
4134
+ return false;
4135
+ const mavenPath = await installMavenTool(tool);
4136
+ return finishInstallAttempt(tool.id, mavenPath !== void 0, startedAt);
4137
+ }
4138
+ case "archive": {
4139
+ if (!tool.archive)
4140
+ return false;
4141
+ if (!resolveArchiveUrl(tool.archive)) {
4142
+ const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
4143
+ noteInstallAttempt(tool.id, "unavailable", reason);
4144
+ logSessionStart(`auto-install ${tool.id}: ${reason}`);
4145
+ return false;
4146
+ }
4147
+ const runtime = tool.archive.runtime;
4148
+ const viaHome = runtime ? runtimeHomeVerdict(runtime) : void 0;
4149
+ if (runtime && !(viaHome ?? await isCommandAvailable(runtime))) {
4150
+ const reason = viaHome === void 0 ? `runtime ${runtime} not found on PATH` : `runtime ${runtime} is not an executable file under ${ARCHIVE_RUNTIME_HOMES[runtime]?.env}`;
4151
+ noteInstallAttempt(tool.id, "unavailable", reason);
4152
+ logSessionStart(`auto-install ${tool.id}: ${reason}`);
4153
+ return false;
4154
+ }
4155
+ const archivePath = await installArchiveTool(tool);
4156
+ return finishInstallAttempt(tool.id, archivePath !== void 0, startedAt);
4157
+ }
4158
+ default:
4159
+ logSessionStart(`auto-install ${tool.id}: unsupported strategy`);
4160
+ return false;
4161
+ }
4162
+ } catch (err) {
4163
+ logSessionStart(`auto-install ${tool.id}: exception ${err.message} (${Date.now() - startedAt}ms)`);
4164
+ return false;
4165
+ }
4166
+ }
4167
+ async function ensureTool(toolId, opts) {
4168
+ if (opts?.allowInstall !== false && !assertInstallAllowed(`managed tool ensure: ${toolId}`)) {
4169
+ logSessionStart(`auto-install ensure ${toolId}: install gated \u2014 ${projectTrustDenialReason()}; discovery only`);
4170
+ const denialReason = projectTrustDenialReason();
4171
+ const discovered = await ensureToolResolved(toolId, {
4172
+ ...opts,
4173
+ allowInstall: false
4174
+ });
4175
+ noteInstallAttempt(toolId, "declined", `project trust: ${denialReason}`);
4176
+ return discovered;
4177
+ }
4178
+ return ensureToolResolved(toolId, opts);
4179
+ }
4180
+ async function ensureToolResolved(toolId, opts) {
4181
+ installFailureReasons.delete(toolId);
4182
+ installAttempts.delete(toolId);
4183
+ lastEnsureResolutionSource.delete(toolId);
4184
+ const cacheResolvedPath = (result) => {
4185
+ if (result) {
4186
+ resolvedPathCache.set(toolId, result);
4187
+ void updateProbeCache(toolId, result, wasLastResolveTransient(toolId));
4188
+ }
4189
+ return result;
4190
+ };
4191
+ if (opts?.forceReinstall) {
4192
+ const ensureStartMs2 = Date.now();
4193
+ logSessionStart(`auto-install ensure ${toolId}: force reinstall \u2014 clearing caches`);
4194
+ resolvedPathCache.delete(toolId);
4195
+ try {
4196
+ const probeCache = await readProbeCache();
4197
+ delete probeCache[toolId];
4198
+ markProbeCacheChange(toolId, null);
4199
+ } catch {
4200
+ }
4201
+ if (opts.allowInstall === false) {
4202
+ noteInstallAttempt(toolId, "declined", "install disabled by caller");
4203
+ logSessionStart(`auto-install ensure ${toolId}: force reinstall blocked \u2014 install disabled, discovery only (${Date.now() - ensureStartMs2}ms)`);
4204
+ return cacheResolvedPath(await getToolPath(toolId));
4205
+ }
4206
+ if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
4207
+ installFailureReasons.set(toolId, "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
4208
+ noteInstallAttempt(toolId, "declined", "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
4209
+ logSessionStart(`auto-install ensure ${toolId}: refused \u2014 PI_LENS_DISABLE_TOOL_INSTALL=1`);
4210
+ return void 0;
4211
+ }
4212
+ const lock = await acquireInstallLock();
4213
+ if (!lock.release) {
4214
+ noteInstallAttempt(toolId, "skipped", lock.reason ?? "install lock held");
4215
+ logSessionStart(`auto-install ensure ${toolId}: ${lock.reason}`);
4216
+ return void 0;
4217
+ }
4218
+ let installed;
4219
+ try {
4220
+ installed = await installTool(toolId, lock.ownsLock);
4221
+ } finally {
4222
+ await lock.release();
4223
+ }
4224
+ if (!installed) {
4225
+ noteInstallAttemptIfUnrecorded(toolId, "failed", "install failed");
4226
+ logSessionStart(`auto-install ensure ${toolId}: force reinstall failed (${Date.now() - ensureStartMs2}ms)`);
4227
+ return void 0;
4228
+ }
4229
+ const result = cacheResolvedPath(await getToolPath(toolId));
4230
+ if (result) {
4231
+ logSessionStart(`auto-install ensure ${toolId}: force reinstall success at ${result} (${Date.now() - ensureStartMs2}ms)`);
4232
+ }
4233
+ return result;
4234
+ }
4235
+ const cached = resolvedPathCache.get(toolId);
4236
+ if (cached) {
4237
+ if (!isFullyQualified(cached)) {
4238
+ lastEnsureResolutionSource.set(toolId, "session-cache");
4239
+ return cached;
4240
+ }
4241
+ try {
4242
+ await fs2.access(cached);
4243
+ lastEnsureResolutionSource.set(toolId, "session-cache");
4244
+ return cached;
4245
+ } catch {
4246
+ }
4247
+ resolvedPathCache.delete(toolId);
4248
+ const probeCache = await readProbeCache();
4249
+ delete probeCache[toolId];
4250
+ markProbeCacheChange(toolId, null);
4251
+ logSessionStart(`auto-install ensure ${toolId}: cached path disappeared; re-probing`);
4252
+ }
4253
+ const diskCached = await checkProbeCache(toolId);
4254
+ if (diskCached) {
4255
+ resolvedPathCache.set(toolId, diskCached);
4256
+ lastEnsureResolutionSource.set(toolId, "probe-cache");
4257
+ logSessionStart(`auto-install ensure ${toolId}: probe cache hit \u2192 ${diskCached}`);
4258
+ return diskCached;
4259
+ }
4260
+ const inFlightKey = opts?.allowInstall === false ? `${toolId}:discovery-only` : toolId;
4261
+ const inFlight = ensureInFlight.get(inFlightKey);
4262
+ if (inFlight) {
4263
+ logSessionStart(`auto-install ensure ${toolId}: waiting for in-flight ensure (${inFlightKey})`);
4264
+ return inFlight;
4265
+ }
4266
+ const ensureStartMs = Date.now();
4267
+ const ensurePromise = (async () => {
4268
+ logSessionStart(`auto-install ensure ${toolId}: start`);
4269
+ const existingPath = await getToolPath(toolId);
4270
+ if (existingPath) {
4271
+ const tool = TOOLS.find((t) => t.id === toolId);
4272
+ const pinnedVersion = tool?.installStrategy === "npm" && tool.packageName ? parsePinnedVersion(tool.packageName) : void 0;
4273
+ if (pinnedVersion) {
4274
+ const seenVersion = lastManagedInstallVersion.get(toolId);
4275
+ if (seenVersion && seenVersion !== pinnedVersion) {
4276
+ lastManagedInstallVersion.delete(toolId);
4277
+ logSessionStart(`auto-install ensure ${toolId}: version drift (installed ${seenVersion} != pinned ${pinnedVersion}) \u2014 forcing reinstall (${Date.now() - ensureStartMs}ms)`);
4278
+ return ensureTool(toolId, {
4279
+ forceReinstall: true,
4280
+ allowInstall: opts?.allowInstall
4281
+ });
4282
+ }
4283
+ }
4284
+ resolvedPathCache.set(toolId, existingPath);
4285
+ void updateProbeCache(toolId, existingPath, wasLastResolveTransient(toolId));
4286
+ lastEnsureResolutionSource.set(toolId, "path");
4287
+ logSessionStart(`auto-install ensure ${toolId}: already available at ${existingPath} (${Date.now() - ensureStartMs}ms)`);
4288
+ return existingPath;
4289
+ }
4290
+ if (opts?.allowInstall === false) {
4291
+ noteInstallAttempt(toolId, "declined", "install disabled by caller");
4292
+ logSessionStart(`auto-install ensure ${toolId}: install disabled \u2014 discovery only, not found (${Date.now() - ensureStartMs}ms)`);
4293
+ return void 0;
4294
+ }
4295
+ if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
4296
+ installFailureReasons.set(toolId, "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
4297
+ noteInstallAttempt(toolId, "declined", "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
4298
+ logSessionStart(`auto-install ensure ${toolId}: refused \u2014 PI_LENS_DISABLE_TOOL_INSTALL=1 (${Date.now() - ensureStartMs}ms)`);
4299
+ return void 0;
4300
+ }
4301
+ const lock = await acquireInstallLock();
4302
+ if (!lock.release) {
4303
+ installFailureReasons.set(toolId, lock.reason ?? "install lock failed");
4304
+ noteInstallAttempt(toolId, "skipped", lock.reason ?? "install lock held");
4305
+ logSessionStart(`auto-install ensure ${toolId}: ${lock.reason}`);
4306
+ return void 0;
4307
+ }
4308
+ let installed;
4309
+ try {
4310
+ const installedByPeer = await getToolPath(toolId);
4311
+ if (installedByPeer) {
4312
+ noteInstallAttempt(toolId, "succeeded", "installed by a concurrent process");
4313
+ resolvedPathCache.set(toolId, installedByPeer);
4314
+ void updateProbeCache(toolId, installedByPeer, wasLastResolveTransient(toolId));
4315
+ return installedByPeer;
4316
+ }
4317
+ installed = await installTool(toolId, lock.ownsLock);
4318
+ } finally {
4319
+ await lock.release();
4320
+ }
4321
+ if (!installed) {
4322
+ noteInstallAttemptIfUnrecorded(toolId, "failed", "install failed");
4323
+ logSessionStart(`auto-install ensure ${toolId}: unavailable (${Date.now() - ensureStartMs}ms)`);
4324
+ return void 0;
4325
+ }
4326
+ const result = await getToolPath(toolId);
4327
+ if (result) {
4328
+ resolvedPathCache.set(toolId, result);
4329
+ void updateProbeCache(toolId, result, wasLastResolveTransient(toolId));
4330
+ logSessionStart(`auto-install ensure ${toolId}: success at ${result} (${Date.now() - ensureStartMs}ms)`);
4331
+ } else {
4332
+ logSessionStart(`auto-install ensure ${toolId}: unavailable (${Date.now() - ensureStartMs}ms)`);
4333
+ }
4334
+ return result;
4335
+ })();
4336
+ ensureInFlight.set(inFlightKey, ensurePromise);
4337
+ try {
4338
+ return await ensurePromise;
4339
+ } finally {
4340
+ if (ensureInFlight.get(inFlightKey) === ensurePromise) {
4341
+ ensureInFlight.delete(inFlightKey);
4342
+ }
4343
+ }
4344
+ }
4345
+ async function getToolEnvironment() {
4346
+ const localBin = path3.join(TOOLS_DIR, "node_modules", ".bin");
4347
+ const currentPath = process.env.PATH || process.env.Path || process.env.path || "";
4348
+ const separator = process.platform === "win32" ? ";" : ":";
4349
+ const nodeDir = path3.dirname(process.execPath);
4350
+ const withNode = nodeDir ? `${nodeDir}${separator}${currentPath}` : currentPath;
4351
+ const augmentedPath = `${GITHUB_BIN_DIR}${separator}${localBin}${separator}${withNode}`;
4352
+ const env = {
4353
+ ...process.env,
4354
+ PATH: augmentedPath
4355
+ };
4356
+ if (process.platform === "win32") {
4357
+ env.Path = augmentedPath;
4358
+ }
4359
+ return env;
4360
+ }
4361
+ async function checkAllTools() {
4362
+ const results = [];
4363
+ for (const tool of TOOLS) {
4364
+ const path4 = await getToolPath(tool.id);
4365
+ results.push({
4366
+ id: tool.id,
4367
+ name: tool.name,
4368
+ installed: path4 !== void 0,
4369
+ path: path4
4370
+ });
4371
+ }
4372
+ return results;
4373
+ }
4374
+ function isKnownToolId(toolId) {
4375
+ return TOOLS.some((tool) => tool.id === toolId);
4376
+ }
4377
+ function getToolInstallStrategy(toolId) {
4378
+ return TOOLS.find((tool) => tool.id === toolId)?.installStrategy;
4379
+ }
4380
+ var GITHUB_TOOLS = [
4381
+ "cljfmt",
4382
+ "php-cs-fixer",
4383
+ "shellcheck",
4384
+ "shfmt",
4385
+ "rust-analyzer",
4386
+ "golangci-lint",
4387
+ "ktlint",
4388
+ "actionlint",
4389
+ "zizmor",
4390
+ "typos-lsp",
4391
+ "tflint",
4392
+ "terragrunt",
4393
+ "terraform-ls",
4394
+ "zls",
4395
+ "hadolint",
4396
+ "helm",
4397
+ "gitleaks",
4398
+ "taplo",
4399
+ "vale",
4400
+ "opengrep",
4401
+ "deno",
4402
+ "clojure-lsp",
4403
+ "cue",
4404
+ "gleam",
4405
+ "typstyle",
4406
+ "tinymist",
4407
+ "marksman",
4408
+ "expert"
4409
+ ];
4410
+ function resolveGitHubAsset(toolId, platform, arch) {
4411
+ const tool = TOOLS.find((t) => t.id === toolId);
4412
+ return tool?.github?.assetMatch(platform, arch);
4413
+ }
4414
+ function resolveGitHubAssetLauncher(toolId, _platform, assetName) {
4415
+ const tool = TOOLS.find((t) => t.id === toolId);
4416
+ return tool?.github ? launcherForGitHubAsset(tool.github, assetName) : void 0;
4417
+ }
4418
+ function resolveGitHubInstalledBinaryName(toolId, platform, assetName) {
4419
+ const tool = TOOLS.find((t) => t.id === toolId);
4420
+ if (!tool)
4421
+ return void 0;
4422
+ return getGitHubInstalledBinaryName(tool.binaryName ?? tool.id, platform, assetName);
4423
+ }
4424
+ function resolveGitHubArchiveBinaryCandidates(toolId, platform, assetName) {
4425
+ const tool = TOOLS.find((t) => t.id === toolId);
4426
+ if (!tool)
4427
+ return void 0;
4428
+ const binaryName = tool.github?.binaryInArchive ?? tool.binaryName ?? tool.id;
4429
+ return getArchiveBinaryCandidates(binaryName, platform, assetName);
4430
+ }
4431
+ var ASSET_SIDECAR_SUFFIXES = [
4432
+ ".asc",
4433
+ ".sig",
4434
+ ".minisig",
4435
+ ".pem",
4436
+ ".cert",
4437
+ ".sbom",
4438
+ ".sha256",
4439
+ ".sha256sum",
4440
+ ".md5"
4441
+ ];
4442
+ function isAssetSidecar(name) {
4443
+ const lower = name.toLowerCase();
4444
+ return ASSET_SIDECAR_SUFFIXES.some((suffix) => lower.endsWith(suffix));
4445
+ }
4446
+ function pickReleaseAsset(assets, assetSubstring) {
4447
+ return assets.find((a) => a.name === assetSubstring) ?? assets.find((a) => a.name.includes(assetSubstring) && !isAssetSidecar(a.name));
4448
+ }
4449
+ function deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring) {
4450
+ const product = tool.github?.hashiCorpReleaseProduct;
4451
+ if (!product || !tagName)
4452
+ return void 0;
4453
+ const version = tagName.replace(/^v/, "").trim();
4454
+ if (!version)
4455
+ return void 0;
4456
+ const assetName = `${product}_${version}_${assetSubstring}`;
4457
+ return {
4458
+ name: assetName,
4459
+ browser_download_url: `https://releases.hashicorp.com/${product}/${version}/${assetName}`
4460
+ };
4461
+ }
4462
+ function resolveDerivedHashiCorpReleaseAsset(toolId, tagName, platform, arch) {
4463
+ const tool = TOOLS.find((t) => t.id === toolId);
4464
+ if (!tool)
4465
+ return void 0;
4466
+ const assetSubstring = tool.github?.assetMatch(platform, arch);
4467
+ if (!assetSubstring)
4468
+ return void 0;
4469
+ return deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring);
4470
+ }
4471
+
4472
+ export {
4473
+ commitDurableStore,
4474
+ commitDurableStoreAsync,
4475
+ findLocalZizmorConfig,
4476
+ isZizmorAuditTarget,
4477
+ resetZizmorTokenAvailability,
4478
+ resolveZizmorGitHubToken,
4479
+ logSessionStart,
4480
+ getManagedToolsDir,
4481
+ TOOLS,
4482
+ getToolVerificationTimeout,
4483
+ getInstallFailureReason,
4484
+ getInstallAttempt,
4485
+ getLastEnsureResolutionSource,
4486
+ resetResolvedPathCache,
4487
+ flushProbeCache,
4488
+ checkProbeCache,
4489
+ updateProbeCache,
4490
+ resetProbeCacheStateForTesting,
4491
+ _peekEnsureInFlightForTesting,
4492
+ resetPathWalkMemo,
4493
+ isCommandAvailable,
4494
+ isSpawnableCommand,
4495
+ isLspTransportRequiredError,
4496
+ parsePinnedVersion,
4497
+ packageEntryVerification,
4498
+ verifyNpmPackageEntry,
4499
+ verifyToolBinary,
4500
+ getAllToolStatuses,
4501
+ isToolInstalled,
4502
+ resolvePlatformPackageBinary,
4503
+ wasLastResolveTransient,
4504
+ getToolPath,
4505
+ findManagedToolBinary,
4506
+ userBaseProbeResult,
4507
+ npmToolNeedsPostinstall,
4508
+ getRefreshableManagedNpmTools,
4509
+ getRefreshableManagedTools,
4510
+ isManagedToolPresent,
4511
+ acquireManagedInstallGate,
4512
+ refreshManagedTool,
4513
+ resolveManagedNpmBinPath,
4514
+ invalidateManagedToolResolution,
4515
+ resolveArchiveUrl,
4516
+ resolveArchiveKind,
4517
+ swapExtractedDir,
4518
+ pipCommandCandidates,
4519
+ pipScriptsDir,
4520
+ installTool,
4521
+ ensureTool,
4522
+ getToolEnvironment,
4523
+ checkAllTools,
4524
+ isKnownToolId,
4525
+ getToolInstallStrategy,
4526
+ GITHUB_TOOLS,
4527
+ resolveGitHubAsset,
4528
+ resolveGitHubAssetLauncher,
4529
+ resolveGitHubInstalledBinaryName,
4530
+ resolveGitHubArchiveBinaryCandidates,
4531
+ pickReleaseAsset,
4532
+ resolveDerivedHashiCorpReleaseAsset
4533
+ };