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