@herbertgao/pi-extensions 2026.10.1 → 2026.10.3

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 (934) hide show
  1. package/README.md +14 -17
  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/{remote-pi → @herbertgao/pi-agency}/LICENSE +1 -1
  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/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  34. package/node_modules/@herbertgao/pi-subagents/README.md +1 -1
  35. package/node_modules/@herbertgao/pi-subagents/package.json +4 -5
  36. package/node_modules/@herbertgao/pi-subagents/src/index.ts +1 -1
  37. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +1 -1
  38. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +48 -72
  39. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +1 -1
  40. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +15 -6
  41. package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +4 -1
  42. package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +9 -8
  43. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +6 -1
  44. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
  45. package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
  46. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +13 -1
  47. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  48. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  49. package/node_modules/@tifan/pi-recap/package.json +1 -1
  50. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  51. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  52. package/node_modules/pi-antigravity/README.md +23 -2
  53. package/node_modules/pi-antigravity/package.json +5 -3
  54. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  55. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  56. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  58. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  59. package/node_modules/pi-antigravity/src/index.ts +52 -137
  60. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  61. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  62. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  63. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  64. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  65. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  66. package/node_modules/pi-lens/CHANGELOG.md +1159 -0
  67. package/node_modules/pi-lens/README.md +116 -25
  68. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  69. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  70. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  71. package/node_modules/pi-lens/dist/chunk-2FQ62SPM.js +83 -0
  72. package/node_modules/pi-lens/dist/chunk-2UMYMRI3.js +118 -0
  73. package/node_modules/pi-lens/dist/chunk-34OCDWOS.js +48 -0
  74. package/node_modules/pi-lens/dist/chunk-3FERWMCH.js +144 -0
  75. package/node_modules/pi-lens/dist/chunk-3JL6IFD7.js +97 -0
  76. package/node_modules/pi-lens/dist/chunk-3JSEWUWI.js +327 -0
  77. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  78. package/node_modules/pi-lens/dist/chunk-3MRXIJ6I.js +79 -0
  79. package/node_modules/pi-lens/dist/chunk-3P4PF2BX.js +2250 -0
  80. package/node_modules/pi-lens/dist/chunk-3PNFWVHT.js +201 -0
  81. package/node_modules/pi-lens/dist/chunk-3TTPKMEF.js +162 -0
  82. package/node_modules/pi-lens/dist/chunk-5JHXUETW.js +326 -0
  83. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  84. package/node_modules/pi-lens/dist/chunk-64LU2OVH.js +341 -0
  85. package/node_modules/pi-lens/dist/chunk-6B5I6ICN.js +44 -0
  86. package/node_modules/pi-lens/dist/chunk-6P54TMWL.js +274 -0
  87. package/node_modules/pi-lens/dist/chunk-6UYA642J.js +145 -0
  88. package/node_modules/pi-lens/dist/chunk-6XX5HCLK.js +398 -0
  89. package/node_modules/pi-lens/dist/chunk-7AXO3ZDF.js +30 -0
  90. package/node_modules/pi-lens/dist/chunk-7BX5DWSA.js +1907 -0
  91. package/node_modules/pi-lens/dist/chunk-7ELY2CSM.js +902 -0
  92. package/node_modules/pi-lens/dist/chunk-7M4T5N3V.js +38 -0
  93. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  94. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  95. package/node_modules/pi-lens/dist/chunk-AFZYMAIV.js +756 -0
  96. package/node_modules/pi-lens/dist/chunk-ALQRYH6O.js +112 -0
  97. package/node_modules/pi-lens/dist/chunk-AXKPQ6ZI.js +1610 -0
  98. package/node_modules/pi-lens/dist/chunk-BHWHXMQ2.js +62 -0
  99. package/node_modules/pi-lens/dist/chunk-BJF25SUG.js +22 -0
  100. package/node_modules/pi-lens/dist/chunk-BNKLYOOO.js +18 -0
  101. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  102. package/node_modules/pi-lens/dist/chunk-CBD2B5CP.js +599 -0
  103. package/node_modules/pi-lens/dist/chunk-CFYISHUW.js +42 -0
  104. package/node_modules/pi-lens/dist/chunk-CMU4RMJB.js +30 -0
  105. package/node_modules/pi-lens/dist/chunk-CULQYFLU.js +14 -0
  106. package/node_modules/pi-lens/dist/chunk-D65GOCJB.js +289 -0
  107. package/node_modules/pi-lens/dist/chunk-D6YF5HKX.js +59 -0
  108. package/node_modules/pi-lens/dist/chunk-DMS52BEP.js +342 -0
  109. package/node_modules/pi-lens/dist/chunk-ELVWJYRV.js +211 -0
  110. package/node_modules/pi-lens/dist/chunk-EUT3B22Y.js +27 -0
  111. package/node_modules/pi-lens/dist/chunk-F5VLZHUW.js +146 -0
  112. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  113. package/node_modules/pi-lens/dist/chunk-FGALODG3.js +55 -0
  114. package/node_modules/pi-lens/dist/chunk-G3RLJYA2.js +2358 -0
  115. package/node_modules/pi-lens/dist/chunk-GAYGGICG.js +32 -0
  116. package/node_modules/pi-lens/dist/chunk-H2W2AXD3.js +419 -0
  117. package/node_modules/pi-lens/dist/chunk-H5EV3VMF.js +114 -0
  118. package/node_modules/pi-lens/dist/chunk-HREZQCT3.js +106 -0
  119. package/node_modules/pi-lens/dist/chunk-HSIRMK2D.js +517 -0
  120. package/node_modules/pi-lens/dist/chunk-IMSQR4WO.js +184 -0
  121. package/node_modules/pi-lens/dist/chunk-IQZXOZ7S.js +4816 -0
  122. package/node_modules/pi-lens/dist/chunk-IR6XFA5P.js +37 -0
  123. package/node_modules/pi-lens/dist/chunk-IUKPRZER.js +78 -0
  124. package/node_modules/pi-lens/dist/chunk-IUR76K6A.js +262 -0
  125. package/node_modules/pi-lens/dist/chunk-J3FZR3YY.js +98 -0
  126. package/node_modules/pi-lens/dist/chunk-J6DFZDFA.js +562 -0
  127. package/node_modules/pi-lens/dist/chunk-J7WJZ6W7.js +50 -0
  128. package/node_modules/pi-lens/dist/chunk-JONGANYH.js +202 -0
  129. package/node_modules/pi-lens/dist/chunk-KJCWXUUS.js +425 -0
  130. package/node_modules/pi-lens/dist/chunk-KJN4IOEJ.js +17 -0
  131. package/node_modules/pi-lens/dist/chunk-KMGPUKFD.js +25 -0
  132. package/node_modules/pi-lens/dist/chunk-LD7WFTRX.js +4533 -0
  133. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  134. package/node_modules/pi-lens/dist/chunk-LNOMA3MP.js +169 -0
  135. package/node_modules/pi-lens/dist/chunk-MGIWSROG.js +2334 -0
  136. package/node_modules/pi-lens/dist/chunk-MQA2YBAH.js +308 -0
  137. package/node_modules/pi-lens/dist/chunk-MTDRLSI7.js +21720 -0
  138. package/node_modules/pi-lens/dist/chunk-N2IRDBZI.js +2355 -0
  139. package/node_modules/pi-lens/dist/chunk-N77WFUL7.js +50 -0
  140. package/node_modules/pi-lens/dist/chunk-N7YNJE5R.js +466 -0
  141. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  142. package/node_modules/pi-lens/dist/chunk-NFXP47RV.js +999 -0
  143. package/node_modules/pi-lens/dist/chunk-NJF2X5XF.js +45 -0
  144. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  145. package/node_modules/pi-lens/dist/chunk-O7HZX7CA.js +309 -0
  146. package/node_modules/pi-lens/dist/chunk-OC7LYAU5.js +708 -0
  147. package/node_modules/pi-lens/dist/chunk-OEX24ECI.js +348 -0
  148. package/node_modules/pi-lens/dist/chunk-OYOB3ZFG.js +78 -0
  149. package/node_modules/pi-lens/dist/chunk-OYTMZXRF.js +41 -0
  150. package/node_modules/pi-lens/dist/chunk-OZJINN4O.js +84 -0
  151. package/node_modules/pi-lens/dist/chunk-PCHDGUKT.js +23 -0
  152. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  153. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  154. package/node_modules/pi-lens/dist/chunk-RJFED73B.js +79 -0
  155. package/node_modules/pi-lens/dist/chunk-RTQNRM5X.js +27 -0
  156. package/node_modules/pi-lens/dist/chunk-S6AMWKPC.js +189 -0
  157. package/node_modules/pi-lens/dist/chunk-T4QZ6FEH.js +308 -0
  158. package/node_modules/pi-lens/dist/chunk-T54VJRAD.js +5456 -0
  159. package/node_modules/pi-lens/dist/chunk-TA5L5KWQ.js +167 -0
  160. package/node_modules/pi-lens/dist/chunk-TQFBZVCG.js +27 -0
  161. package/node_modules/pi-lens/dist/chunk-U52SAGRM.js +62 -0
  162. package/node_modules/pi-lens/dist/chunk-UEAHAMEI.js +5814 -0
  163. package/node_modules/pi-lens/dist/chunk-UGSMCQ4D.js +327 -0
  164. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  165. package/node_modules/pi-lens/dist/chunk-USHG4DLH.js +459 -0
  166. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  167. package/node_modules/pi-lens/dist/chunk-V5MEBST3.js +4139 -0
  168. package/node_modules/pi-lens/dist/chunk-VGEVL5EQ.js +29 -0
  169. package/node_modules/pi-lens/dist/chunk-VPWKF3GN.js +87 -0
  170. package/node_modules/pi-lens/dist/chunk-VSMJK3ZJ.js +1250 -0
  171. package/node_modules/pi-lens/dist/chunk-VYKKMYVT.js +560 -0
  172. package/node_modules/pi-lens/dist/chunk-VZHJS6DR.js +442 -0
  173. package/node_modules/pi-lens/dist/chunk-WATC3IQR.js +213 -0
  174. package/node_modules/pi-lens/dist/chunk-WPE5FPGR.js +54 -0
  175. package/node_modules/pi-lens/dist/chunk-WSEWKCXU.js +188 -0
  176. package/node_modules/pi-lens/dist/chunk-WW5BLB74.js +659 -0
  177. package/node_modules/pi-lens/dist/chunk-XGUL4SLQ.js +406 -0
  178. package/node_modules/pi-lens/dist/chunk-XHSCVSOL.js +170 -0
  179. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  180. package/node_modules/pi-lens/dist/chunk-YLN4K6WD.js +385 -0
  181. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  182. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  183. package/node_modules/pi-lens/dist/chunk-Z6BB4JLQ.js +478 -0
  184. package/node_modules/pi-lens/dist/chunk-Z7QQA2KT.js +27 -0
  185. package/node_modules/pi-lens/dist/chunk-ZETYB3XG.js +2626 -0
  186. package/node_modules/pi-lens/dist/chunk-ZMOMPX2Z.js +87 -0
  187. package/node_modules/pi-lens/dist/chunk-ZOEYVXSV.js +149 -0
  188. package/node_modules/pi-lens/dist/chunk-ZZUFZUQZ.js +12120 -0
  189. package/node_modules/pi-lens/dist/index.js +39712 -29876
  190. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  191. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  192. package/node_modules/pi-lens/dist/mcp/server.js +22688 -1573
  193. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  194. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  195. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  196. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  197. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  198. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  199. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -4
  200. package/node_modules/pi-lens/docs/configuration.md +28 -2
  201. package/node_modules/pi-lens/docs/custom-rules.md +25 -2
  202. package/node_modules/pi-lens/docs/environment-variables.md +86 -8
  203. package/node_modules/pi-lens/docs/features.md +45 -5
  204. package/node_modules/pi-lens/docs/globalconfig.md +2 -1
  205. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  206. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +37 -4
  207. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  208. package/node_modules/pi-lens/docs/mcp.md +21 -5
  209. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  210. package/node_modules/pi-lens/docs/pi-lens-fixer.md +160 -88
  211. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  212. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +736 -0
  213. package/node_modules/pi-lens/docs/pi-lens-monitor.md +75 -7
  214. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  215. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +189 -48
  216. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  217. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  218. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  219. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  220. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  221. package/node_modules/pi-lens/docs/schema/pi-lens-config-v1.json +530 -0
  222. package/node_modules/pi-lens/docs/servercapabilities.md +56 -49
  223. package/node_modules/pi-lens/docs/settings.md +1 -0
  224. package/node_modules/pi-lens/docs/usage.md +5 -1
  225. package/node_modules/pi-lens/docs/word-index.md +6 -0
  226. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  227. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  228. package/node_modules/pi-lens/package.json +18 -6
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-identity-operator-on-literals-test.yml +12 -2
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-js-test.yml +2 -0
  232. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-test.yml +2 -0
  233. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  234. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-identity-operator-on-literals.yml +20 -12
  235. package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error-js.yml +2 -2
  236. package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error.yml +2 -2
  237. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  238. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  239. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  240. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  241. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  242. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  243. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  244. package/node_modules/pi-web-access/README.md +10 -6
  245. package/node_modules/pi-web-access/ceramic.ts +219 -0
  246. package/node_modules/pi-web-access/curator-page.ts +3 -1
  247. package/node_modules/pi-web-access/curator-server.ts +1 -0
  248. package/node_modules/pi-web-access/dist/index.js +846 -588
  249. package/node_modules/pi-web-access/dist/mcp-cli.js +725 -516
  250. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  251. package/node_modules/pi-web-access/github-extract.ts +34 -2
  252. package/node_modules/pi-web-access/index.ts +72 -30
  253. package/node_modules/pi-web-access/openai-search.ts +3 -3
  254. package/node_modules/pi-web-access/package.json +2 -2
  255. package/node_modules/pi-web-access/page-query.ts +6 -9
  256. package/node_modules/pi-web-access/parallel-mcp.ts +2 -0
  257. package/node_modules/pi-web-access/query-rewrite.ts +2 -2
  258. package/node_modules/pi-web-access/storage.ts +49 -0
  259. package/node_modules/pi-web-access/summary-review.ts +7 -6
  260. package/node_modules/pi-web-access/utils.ts +21 -6
  261. package/node_modules/pi-web-access/web-tool-core.ts +1 -0
  262. package/package.json +19 -19
  263. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
  264. package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
  265. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
  266. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
  267. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  268. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  269. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  270. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  271. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  272. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  273. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  274. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  275. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  276. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  277. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  278. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  279. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  280. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  281. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  282. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  283. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  284. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  285. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  286. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  287. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  288. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  289. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  290. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  291. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  292. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  293. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  294. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  295. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  296. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  297. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  298. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  299. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  300. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  301. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  302. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  303. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  304. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  305. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  306. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  307. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  308. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  309. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  310. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  311. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  312. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  313. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  314. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  315. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  316. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  317. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  318. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  319. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  320. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  321. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  322. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  323. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  324. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  325. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  326. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  327. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  328. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  329. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  330. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  331. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  332. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  333. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  334. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  335. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  336. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  337. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  338. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  339. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  340. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  341. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  342. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  343. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  344. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  345. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  346. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  347. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  348. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  349. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  350. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  351. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  352. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  353. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  354. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  355. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  356. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  357. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  358. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  359. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  360. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  361. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  362. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  363. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  364. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  365. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  366. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  367. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  368. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  369. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  370. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  371. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  372. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  373. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  374. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  375. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  376. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  377. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  378. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  379. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  380. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  381. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  382. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  383. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  384. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  385. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  386. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  387. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  388. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  389. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  390. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  391. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  392. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  393. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  394. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  395. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  396. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  397. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  398. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  399. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  400. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  401. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  402. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  403. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  404. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  405. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  406. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  407. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  408. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  409. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  410. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  411. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  412. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  413. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  414. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  415. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  416. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  417. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  418. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  419. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  420. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  421. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  422. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  423. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  424. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  425. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  426. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  427. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  428. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  429. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  430. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  431. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  432. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  433. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  434. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  435. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  436. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  437. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  438. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  439. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  440. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  441. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  442. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  443. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  444. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  445. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  446. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  447. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  448. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  449. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  450. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  451. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  452. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  453. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  454. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  455. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  456. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  457. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  458. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  459. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  460. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  461. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  462. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  463. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  464. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  465. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  466. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  467. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  468. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  469. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  470. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  471. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  472. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  473. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  474. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  475. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  476. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  477. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  478. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  479. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  480. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  481. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  482. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  483. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  484. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  485. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  486. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  487. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  488. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  489. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  490. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  491. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  492. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  493. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  494. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  495. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  496. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  497. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  498. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  499. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  500. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  501. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  502. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  503. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  504. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  505. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  506. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  507. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  508. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  509. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  510. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  511. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  512. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  513. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  514. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  515. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  516. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  517. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  518. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  519. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  520. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  521. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  522. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  523. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  524. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  525. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  526. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  527. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  528. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  529. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  530. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  531. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  532. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  533. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  534. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  535. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  536. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  537. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  538. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  539. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  540. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  541. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  542. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  543. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  544. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  545. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  546. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  547. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  548. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  549. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  550. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  551. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  552. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  553. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  554. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  555. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  556. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  557. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  558. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  559. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  560. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  561. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  562. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  563. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  564. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  565. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  566. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  567. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  568. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  569. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  570. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  571. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  572. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  573. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  574. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  575. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  576. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  577. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  578. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  579. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  580. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  581. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  582. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  583. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  584. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  585. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  586. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  587. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  588. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  589. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  590. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  591. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  592. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  593. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  594. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  595. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  596. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  597. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  598. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  599. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  600. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  601. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  602. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  603. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  604. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  605. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  606. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  607. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  608. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  609. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  610. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  611. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  612. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  613. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  614. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  615. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  616. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  617. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  618. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  619. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  620. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  621. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  622. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  623. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  624. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  625. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  626. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  627. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  628. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  629. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  630. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  631. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  632. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  633. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  634. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  635. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  636. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  637. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  638. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  639. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  640. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  641. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  642. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  643. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  644. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  645. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  646. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  647. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  648. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  649. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  650. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  651. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  652. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  653. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  654. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  655. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  656. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  657. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  658. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  659. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  660. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  661. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  662. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  663. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  664. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  665. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  666. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  667. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  668. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  669. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  670. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  671. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  672. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  673. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  674. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  675. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  676. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  677. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  678. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  679. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  680. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  681. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  682. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  683. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  684. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  685. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  686. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  687. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  688. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  689. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  690. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  691. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  692. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  693. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  694. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  695. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  696. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  697. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  698. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  699. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  700. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  701. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  702. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  703. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  704. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  705. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  706. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  707. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  708. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  709. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  710. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  711. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  712. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  713. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  714. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  715. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  716. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  717. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  718. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  719. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  720. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  721. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  722. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  723. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  724. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  725. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  726. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  727. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  728. package/node_modules/pi-lens/dist/i18n.js +0 -69
  729. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  730. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  731. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  732. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  733. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  734. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  735. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  736. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  737. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  738. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  739. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  740. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  741. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  742. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  743. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  744. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  745. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  746. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  747. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  748. package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
  749. package/node_modules/pi-web-access/opencode-session-headers.ts +0 -24
  750. package/node_modules/remote-pi/README.md +0 -675
  751. package/node_modules/remote-pi/dist/actions/handlers.d.ts +0 -122
  752. package/node_modules/remote-pi/dist/actions/handlers.js +0 -160
  753. package/node_modules/remote-pi/dist/actions/handlers.js.map +0 -1
  754. package/node_modules/remote-pi/dist/actions/registry.d.ts +0 -39
  755. package/node_modules/remote-pi/dist/actions/registry.js +0 -58
  756. package/node_modules/remote-pi/dist/actions/registry.js.map +0 -1
  757. package/node_modules/remote-pi/dist/bin/supervisord.d.ts +0 -2
  758. package/node_modules/remote-pi/dist/bin/supervisord.js +0 -86
  759. package/node_modules/remote-pi/dist/bin/supervisord.js.map +0 -1
  760. package/node_modules/remote-pi/dist/commands/builtin_mirror.d.ts +0 -58
  761. package/node_modules/remote-pi/dist/commands/builtin_mirror.js +0 -71
  762. package/node_modules/remote-pi/dist/commands/builtin_mirror.js.map +0 -1
  763. package/node_modules/remote-pi/dist/commands/list_commands.d.ts +0 -60
  764. package/node_modules/remote-pi/dist/commands/list_commands.js +0 -73
  765. package/node_modules/remote-pi/dist/commands/list_commands.js.map +0 -1
  766. package/node_modules/remote-pi/dist/config.d.ts +0 -62
  767. package/node_modules/remote-pi/dist/config.js +0 -115
  768. package/node_modules/remote-pi/dist/config.js.map +0 -1
  769. package/node_modules/remote-pi/dist/daemon/client.d.ts +0 -20
  770. package/node_modules/remote-pi/dist/daemon/client.js +0 -131
  771. package/node_modules/remote-pi/dist/daemon/client.js.map +0 -1
  772. package/node_modules/remote-pi/dist/daemon/control_protocol.d.ts +0 -176
  773. package/node_modules/remote-pi/dist/daemon/control_protocol.js +0 -63
  774. package/node_modules/remote-pi/dist/daemon/control_protocol.js.map +0 -1
  775. package/node_modules/remote-pi/dist/daemon/cron_log.d.ts +0 -45
  776. package/node_modules/remote-pi/dist/daemon/cron_log.js +0 -72
  777. package/node_modules/remote-pi/dist/daemon/cron_log.js.map +0 -1
  778. package/node_modules/remote-pi/dist/daemon/cron_registry.d.ts +0 -80
  779. package/node_modules/remote-pi/dist/daemon/cron_registry.js +0 -194
  780. package/node_modules/remote-pi/dist/daemon/cron_registry.js.map +0 -1
  781. package/node_modules/remote-pi/dist/daemon/id.d.ts +0 -24
  782. package/node_modules/remote-pi/dist/daemon/id.js +0 -36
  783. package/node_modules/remote-pi/dist/daemon/id.js.map +0 -1
  784. package/node_modules/remote-pi/dist/daemon/install.d.ts +0 -170
  785. package/node_modules/remote-pi/dist/daemon/install.js +0 -547
  786. package/node_modules/remote-pi/dist/daemon/install.js.map +0 -1
  787. package/node_modules/remote-pi/dist/daemon/registry.d.ts +0 -63
  788. package/node_modules/remote-pi/dist/daemon/registry.js +0 -153
  789. package/node_modules/remote-pi/dist/daemon/registry.js.map +0 -1
  790. package/node_modules/remote-pi/dist/daemon/rpc_child.d.ts +0 -194
  791. package/node_modules/remote-pi/dist/daemon/rpc_child.js +0 -389
  792. package/node_modules/remote-pi/dist/daemon/rpc_child.js.map +0 -1
  793. package/node_modules/remote-pi/dist/daemon/supervisor.d.ts +0 -93
  794. package/node_modules/remote-pi/dist/daemon/supervisor.js +0 -596
  795. package/node_modules/remote-pi/dist/daemon/supervisor.js.map +0 -1
  796. package/node_modules/remote-pi/dist/extension_ui_bridge.d.ts +0 -26
  797. package/node_modules/remote-pi/dist/extension_ui_bridge.js +0 -341
  798. package/node_modules/remote-pi/dist/extension_ui_bridge.js.map +0 -1
  799. package/node_modules/remote-pi/dist/index.d.ts +0 -213
  800. package/node_modules/remote-pi/dist/index.js +0 -4996
  801. package/node_modules/remote-pi/dist/index.js.map +0 -1
  802. package/node_modules/remote-pi/dist/mcp/mesh_result.d.ts +0 -10
  803. package/node_modules/remote-pi/dist/mcp/mesh_result.js +0 -42
  804. package/node_modules/remote-pi/dist/mcp/mesh_result.js.map +0 -1
  805. package/node_modules/remote-pi/dist/mcp/mesh_server.d.ts +0 -16
  806. package/node_modules/remote-pi/dist/mcp/mesh_server.js +0 -327
  807. package/node_modules/remote-pi/dist/mcp/mesh_server.js.map +0 -1
  808. package/node_modules/remote-pi/dist/mesh/canonical.d.ts +0 -30
  809. package/node_modules/remote-pi/dist/mesh/canonical.js +0 -61
  810. package/node_modules/remote-pi/dist/mesh/canonical.js.map +0 -1
  811. package/node_modules/remote-pi/dist/mesh/client.d.ts +0 -18
  812. package/node_modules/remote-pi/dist/mesh/client.js +0 -125
  813. package/node_modules/remote-pi/dist/mesh/client.js.map +0 -1
  814. package/node_modules/remote-pi/dist/mesh/encoding.d.ts +0 -38
  815. package/node_modules/remote-pi/dist/mesh/encoding.js +0 -216
  816. package/node_modules/remote-pi/dist/mesh/encoding.js.map +0 -1
  817. package/node_modules/remote-pi/dist/mesh/self_revoke.d.ts +0 -73
  818. package/node_modules/remote-pi/dist/mesh/self_revoke.js +0 -401
  819. package/node_modules/remote-pi/dist/mesh/self_revoke.js.map +0 -1
  820. package/node_modules/remote-pi/dist/mesh/siblings.d.ts +0 -49
  821. package/node_modules/remote-pi/dist/mesh/siblings.js +0 -159
  822. package/node_modules/remote-pi/dist/mesh/siblings.js.map +0 -1
  823. package/node_modules/remote-pi/dist/mesh/types.d.ts +0 -34
  824. package/node_modules/remote-pi/dist/mesh/types.js +0 -11
  825. package/node_modules/remote-pi/dist/mesh/types.js.map +0 -1
  826. package/node_modules/remote-pi/dist/mesh/verify.d.ts +0 -17
  827. package/node_modules/remote-pi/dist/mesh/verify.js +0 -86
  828. package/node_modules/remote-pi/dist/mesh/verify.js.map +0 -1
  829. package/node_modules/remote-pi/dist/pairing/crypto.d.ts +0 -8
  830. package/node_modules/remote-pi/dist/pairing/crypto.js +0 -22
  831. package/node_modules/remote-pi/dist/pairing/crypto.js.map +0 -1
  832. package/node_modules/remote-pi/dist/pairing/handshake.d.ts +0 -28
  833. package/node_modules/remote-pi/dist/pairing/handshake.js +0 -113
  834. package/node_modules/remote-pi/dist/pairing/handshake.js.map +0 -1
  835. package/node_modules/remote-pi/dist/pairing/noise-sha256.d.ts +0 -16
  836. package/node_modules/remote-pi/dist/pairing/noise-sha256.js +0 -103
  837. package/node_modules/remote-pi/dist/pairing/noise-sha256.js.map +0 -1
  838. package/node_modules/remote-pi/dist/pairing/qr.d.ts +0 -59
  839. package/node_modules/remote-pi/dist/pairing/qr.js +0 -125
  840. package/node_modules/remote-pi/dist/pairing/qr.js.map +0 -1
  841. package/node_modules/remote-pi/dist/pairing/storage.d.ts +0 -120
  842. package/node_modules/remote-pi/dist/pairing/storage.js +0 -604
  843. package/node_modules/remote-pi/dist/pairing/storage.js.map +0 -1
  844. package/node_modules/remote-pi/dist/protocol/codec.d.ts +0 -7
  845. package/node_modules/remote-pi/dist/protocol/codec.js +0 -49
  846. package/node_modules/remote-pi/dist/protocol/codec.js.map +0 -1
  847. package/node_modules/remote-pi/dist/protocol/types.d.ts +0 -393
  848. package/node_modules/remote-pi/dist/protocol/types.js +0 -2
  849. package/node_modules/remote-pi/dist/protocol/types.js.map +0 -1
  850. package/node_modules/remote-pi/dist/rooms.d.ts +0 -29
  851. package/node_modules/remote-pi/dist/rooms.js +0 -57
  852. package/node_modules/remote-pi/dist/rooms.js.map +0 -1
  853. package/node_modules/remote-pi/dist/session/address.d.ts +0 -49
  854. package/node_modules/remote-pi/dist/session/address.js +0 -58
  855. package/node_modules/remote-pi/dist/session/address.js.map +0 -1
  856. package/node_modules/remote-pi/dist/session/agent_bridge.d.ts +0 -55
  857. package/node_modules/remote-pi/dist/session/agent_bridge.js +0 -146
  858. package/node_modules/remote-pi/dist/session/agent_bridge.js.map +0 -1
  859. package/node_modules/remote-pi/dist/session/bridge.d.ts +0 -37
  860. package/node_modules/remote-pi/dist/session/bridge.js +0 -130
  861. package/node_modules/remote-pi/dist/session/bridge.js.map +0 -1
  862. package/node_modules/remote-pi/dist/session/broker.d.ts +0 -189
  863. package/node_modules/remote-pi/dist/session/broker.js +0 -507
  864. package/node_modules/remote-pi/dist/session/broker.js.map +0 -1
  865. package/node_modules/remote-pi/dist/session/broker_remote.d.ts +0 -74
  866. package/node_modules/remote-pi/dist/session/broker_remote.js +0 -583
  867. package/node_modules/remote-pi/dist/session/broker_remote.js.map +0 -1
  868. package/node_modules/remote-pi/dist/session/cwd_lock.d.ts +0 -33
  869. package/node_modules/remote-pi/dist/session/cwd_lock.js +0 -126
  870. package/node_modules/remote-pi/dist/session/cwd_lock.js.map +0 -1
  871. package/node_modules/remote-pi/dist/session/envelope.d.ts +0 -33
  872. package/node_modules/remote-pi/dist/session/envelope.js +0 -117
  873. package/node_modules/remote-pi/dist/session/envelope.js.map +0 -1
  874. package/node_modules/remote-pi/dist/session/global_config.d.ts +0 -33
  875. package/node_modules/remote-pi/dist/session/global_config.js +0 -73
  876. package/node_modules/remote-pi/dist/session/global_config.js.map +0 -1
  877. package/node_modules/remote-pi/dist/session/ipc.d.ts +0 -27
  878. package/node_modules/remote-pi/dist/session/ipc.js +0 -22
  879. package/node_modules/remote-pi/dist/session/ipc.js.map +0 -1
  880. package/node_modules/remote-pi/dist/session/leader_election.d.ts +0 -32
  881. package/node_modules/remote-pi/dist/session/leader_election.js +0 -106
  882. package/node_modules/remote-pi/dist/session/leader_election.js.map +0 -1
  883. package/node_modules/remote-pi/dist/session/local_config.d.ts +0 -55
  884. package/node_modules/remote-pi/dist/session/local_config.js +0 -157
  885. package/node_modules/remote-pi/dist/session/local_config.js.map +0 -1
  886. package/node_modules/remote-pi/dist/session/mesh_message.d.ts +0 -20
  887. package/node_modules/remote-pi/dist/session/mesh_message.js +0 -28
  888. package/node_modules/remote-pi/dist/session/mesh_message.js.map +0 -1
  889. package/node_modules/remote-pi/dist/session/mesh_node.d.ts +0 -116
  890. package/node_modules/remote-pi/dist/session/mesh_node.js +0 -573
  891. package/node_modules/remote-pi/dist/session/mesh_node.js.map +0 -1
  892. package/node_modules/remote-pi/dist/session/peer.d.ts +0 -139
  893. package/node_modules/remote-pi/dist/session/peer.js +0 -451
  894. package/node_modules/remote-pi/dist/session/peer.js.map +0 -1
  895. package/node_modules/remote-pi/dist/session/peer_inventory.d.ts +0 -13
  896. package/node_modules/remote-pi/dist/session/peer_inventory.js +0 -48
  897. package/node_modules/remote-pi/dist/session/peer_inventory.js.map +0 -1
  898. package/node_modules/remote-pi/dist/session/peer_limits.d.ts +0 -9
  899. package/node_modules/remote-pi/dist/session/peer_limits.js +0 -21
  900. package/node_modules/remote-pi/dist/session/peer_limits.js.map +0 -1
  901. package/node_modules/remote-pi/dist/session/setup_wizard.d.ts +0 -39
  902. package/node_modules/remote-pi/dist/session/setup_wizard.js +0 -63
  903. package/node_modules/remote-pi/dist/session/setup_wizard.js.map +0 -1
  904. package/node_modules/remote-pi/dist/session/tool_gate.d.ts +0 -5
  905. package/node_modules/remote-pi/dist/session/tool_gate.js +0 -11
  906. package/node_modules/remote-pi/dist/session/tool_gate.js.map +0 -1
  907. package/node_modules/remote-pi/dist/session/tools.d.ts +0 -24
  908. package/node_modules/remote-pi/dist/session/tools.js +0 -252
  909. package/node_modules/remote-pi/dist/session/tools.js.map +0 -1
  910. package/node_modules/remote-pi/dist/session/wizard.d.ts +0 -13
  911. package/node_modules/remote-pi/dist/session/wizard.js +0 -20
  912. package/node_modules/remote-pi/dist/session/wizard.js.map +0 -1
  913. package/node_modules/remote-pi/dist/settings.d.ts +0 -15
  914. package/node_modules/remote-pi/dist/settings.js +0 -52
  915. package/node_modules/remote-pi/dist/settings.js.map +0 -1
  916. package/node_modules/remote-pi/dist/transport/peer_channel.d.ts +0 -37
  917. package/node_modules/remote-pi/dist/transport/peer_channel.js +0 -98
  918. package/node_modules/remote-pi/dist/transport/peer_channel.js.map +0 -1
  919. package/node_modules/remote-pi/dist/transport/pi_forward_client.d.ts +0 -29
  920. package/node_modules/remote-pi/dist/transport/pi_forward_client.js +0 -62
  921. package/node_modules/remote-pi/dist/transport/pi_forward_client.js.map +0 -1
  922. package/node_modules/remote-pi/dist/transport/relay_client.d.ts +0 -91
  923. package/node_modules/remote-pi/dist/transport/relay_client.js +0 -206
  924. package/node_modules/remote-pi/dist/transport/relay_client.js.map +0 -1
  925. package/node_modules/remote-pi/dist/ui/footer.d.ts +0 -32
  926. package/node_modules/remote-pi/dist/ui/footer.js +0 -34
  927. package/node_modules/remote-pi/dist/ui/footer.js.map +0 -1
  928. package/node_modules/remote-pi/docs/daemon.md +0 -289
  929. package/node_modules/remote-pi/package.json +0 -92
  930. package/node_modules/remote-pi/service-templates/launchd.plist.template +0 -35
  931. package/node_modules/remote-pi/service-templates/systemd.service.template +0 -19
  932. package/node_modules/remote-pi/service-templates/task-launcher.vbs.template +0 -10
  933. package/node_modules/remote-pi/service-templates/task-scheduler.xml.template +0 -38
  934. package/node_modules/remote-pi/skills/agent-network/SKILL.md +0 -310
@@ -1,3290 +0,0 @@
1
- /**
2
- * LSP Server Definitions for pi-lens
3
- *
4
- * Defines 40+ language servers with:
5
- * - Root detection (monorepo support)
6
- * - Auto-installation strategies
7
- * - Platform-specific handling
8
- */
9
- import { existsSync, mkdirSync, statSync } from "node:fs";
10
- import { access, readFile, readdir, stat } from "node:fs/promises";
11
- import os from "node:os";
12
- import path from "node:path";
13
- import { getGlobalPiLensDir, getProjectIgnoreGlobs, isPathIgnoredByProject, } from "../file-utils.js";
14
- import { augmentPythonEnvironment, detectPythonEnvironment, detectPythonVenv, pythonEnvironmentToolCandidates, } from "../python-environment.js";
15
- export { detectPythonVenv };
16
- import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
17
- import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
18
- import { extensionsForLanguage } from "../language-registry.js";
19
- import { CARGO_WORKSPACE_MEMBER_DIALECT, direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, isWindowsPath, matchesWorkspaceMemberPattern, normalizeEphemeralMapKey, pathsEqual, toPosix, } from "../path-utils.js";
20
- import { ensureTool, findManagedToolBinary, getToolEnvironment, getToolPath, } from "../installer/index.js";
21
- import * as installer from "../installer/index.js";
22
- import { classifyProbeFailure, describeInstallAttempt, logAvailabilityDecision, } from "../dispatch/runners/utils/availability-policy.js";
23
- import { resolveOpengrepConfig } from "../opengrep-config.js";
24
- import { isZizmorAuditTarget, resolveZizmorGitHubToken, } from "../zizmor-config.js";
25
- import { logLatency } from "../latency-logger.js";
26
- import { logSessionStart } from "../sessionstart-logger.js";
27
- import { findLocalSgconfig, resolveBaselineSgconfig } from "../sgconfig.js";
28
- import { findLocalTyposConfig } from "../typos-config.js";
29
- import { resolvePackagePath } from "../package-root.js";
30
- import { resolveToolCwd } from "../tool-cwd.js";
31
- import { dirMtimeRecordsAsync, dirMtimesStillFreshAsync, unknownDirMtimeRecords, } from "../workspace-topology.js";
32
- import { bounded } from "../deadline-utils.js";
33
- import { FRESHNESS_CADENCE_MS } from "../freshness-cadence.js";
34
- import { HOOK_WALL_BUDGET_MS } from "../hook-budgets.js";
35
- import { recordDegradationOnce } from "../degradation-ledger.js";
36
- import { hasCargoWorkspaceTable, readCargoWorkspaceExclude, readCargoWorkspaceMembers, readTextFileOrUndefined, } from "../cargo-manifest.js";
37
- import { resolveAstGrepNativeExe } from "./wait-policy/index.js";
38
- import { hasSpawnFailureKind, isCommandAvailableAsync, safeSpawnAsync, } from "../safe-spawn.js";
39
- import { launchLSP } from "./launch.js";
40
- import { createLombokJdtlsArgs } from "./lombok.js";
41
- import { resolveJavaRuntimeEnv } from "./jvm-runtime.js";
42
- import { normalizeMapKey } from "./path-utils.js";
43
- import { getRubyVersionDirNamesSync } from "./ruby-drive-dirs.js";
44
- import { getProcessSingleton } from "../process-singletons.js";
45
- import { createGenerationSource, } from "../generation-guard.js";
46
- function withRootMarkers(root, markers) {
47
- root.rootMarkers = markers;
48
- return root;
49
- }
50
- /** Resolve a server identity cwd through the shared tool-cwd seam. */
51
- export async function resolveLspServerCwd(server, filePath, sessionCwd, onRootFailure) {
52
- const rootMarkers = server.rootMarkers ?? server.root.rootMarkers;
53
- let serverRoot;
54
- let rootFailed = false;
55
- try {
56
- serverRoot = await server.root(filePath);
57
- }
58
- catch (error) {
59
- const reason = error instanceof Error ? error.message : String(error);
60
- rootFailed = true;
61
- onRootFailure?.(reason);
62
- }
63
- const isFileDirFallback = serverRoot !== undefined &&
64
- rootMarkers?.length &&
65
- path.resolve(serverRoot) === path.resolve(path.dirname(filePath));
66
- if (!serverRoot || isFileDirFallback) {
67
- if (!serverRoot) {
68
- recordDegradationOnce({
69
- kind: "tool-cwd-resolution",
70
- subject: server.id,
71
- reason: `lsp:server-root-${rootFailed ? "failed" : "fallback"}:${filePath}`,
72
- });
73
- }
74
- if (!rootMarkers?.length)
75
- return undefined;
76
- return resolveToolCwd("lsp", server.id, filePath, {
77
- cwd: path.dirname(path.resolve(filePath)),
78
- rootMarkers,
79
- }).cwd;
80
- }
81
- const boundedServerRoot = enforceLspRootCeiling(serverRoot, sessionCwd, filePath);
82
- return resolveToolCwd("lsp", server.id, filePath, {
83
- cwd: sessionCwd,
84
- rootMarkers,
85
- serverRoot: boundedServerRoot,
86
- }).cwd;
87
- }
88
- const FIXTURE_ROOT_SEGMENTS = new Set(["__fixtures__", "testdata"]);
89
- const FALLBACK_PROJECT_MARKERS = [
90
- ".git",
91
- "package.json",
92
- "go.work",
93
- "go.mod",
94
- "Cargo.toml",
95
- "pyproject.toml",
96
- "pom.xml",
97
- "build.gradle",
98
- "build.gradle.kts",
99
- "mix.exs",
100
- "pubspec.yaml",
101
- "Package.swift",
102
- ];
103
- /**
104
- * Markers that make a nested root an independently hosted project rather than
105
- * a config-only directory that should share an ancestor's LSP client.
106
- */
107
- const PROJECT_BOUNDARY_MARKERS = [
108
- "package.json",
109
- "package-lock.json",
110
- "pnpm-lock.yaml",
111
- "yarn.lock",
112
- "bun.lock",
113
- "bun.lockb",
114
- "go.work",
115
- "go.mod",
116
- "Cargo.toml",
117
- "Cargo.lock",
118
- "pyproject.toml",
119
- "uv.lock",
120
- "poetry.lock",
121
- "Pipfile",
122
- "Pipfile.lock",
123
- "pom.xml",
124
- "build.gradle",
125
- "build.gradle.kts",
126
- "settings.gradle",
127
- "settings.gradle.kts",
128
- "mix.exs",
129
- "pubspec.yaml",
130
- "Package.swift",
131
- ];
132
- // This is process-global state, so each candidate pair is logged once per
133
- // process (not once per session). Keep the root-boundary marker below aligned
134
- // with FALLBACK_PROJECT_MARKERS, the shared fallback root-policy marker set.
135
- const loggedRootCeilingClamps = new Set();
136
- const posixCaseInsensitiveByPath = new Map();
137
- function posixFilesystemIsCaseInsensitive(root) {
138
- const resolved = path.resolve(root);
139
- const cached = posixCaseInsensitiveByPath.get(resolved);
140
- if (cached !== undefined)
141
- return cached;
142
- if (!existsSync(resolved)) {
143
- return false;
144
- }
145
- const name = path.basename(resolved);
146
- const alternate = name.toLowerCase() === name ? name.toUpperCase() : name.toLowerCase();
147
- let insensitive = false;
148
- const alternatePath = path.join(path.dirname(resolved), alternate);
149
- if (alternate !== name && existsSync(alternatePath)) {
150
- try {
151
- const actual = statSync(resolved);
152
- const alternateStat = statSync(alternatePath);
153
- insensitive =
154
- actual.dev === alternateStat.dev && actual.ino === alternateStat.ino;
155
- }
156
- catch {
157
- insensitive = false;
158
- }
159
- }
160
- posixCaseInsensitiveByPath.set(resolved, insensitive);
161
- return insensitive;
162
- }
163
- export function resetLSPCaseSensitivityState() {
164
- posixCaseInsensitiveByPath.clear();
165
- }
166
- export function _getPosixCaseSensitivityCacheSizeForTests() {
167
- return posixCaseInsensitiveByPath.size;
168
- }
169
- /**
170
- * Path-shape-aware containment test: is `candidate` the same path as
171
- * `ancestor`, or inside it?
172
- *
173
- * Exported so the session-cwd registry (`clients/lsp/config.ts`) and the
174
- * foreign-root decline gate (`clients/lsp/index.ts`) test containment with the
175
- * SAME comparator the root ceiling uses. A second hand-rolled `path.relative`
176
- * helper alongside this one is how the two drift apart on Windows-shaped
177
- * paths (shape 2 / #1150): `path.isAbsolute("C:\\repo")` is false on POSIX, so
178
- * a host-default comparator silently mis-answers a win32 path on Linux CI.
179
- */
180
- export function isSameOrWithin(ancestor, candidate, deps = {}) {
181
- const windowsShaped = isWindowsPath(ancestor) || isWindowsPath(candidate);
182
- const pathApi = windowsShaped ? path.win32 : path;
183
- const resolvedAncestor = pathApi.resolve(ancestor);
184
- const resolvedCandidate = pathApi.resolve(candidate);
185
- let caseInsensitive = false;
186
- if (!windowsShaped) {
187
- if (deps.caseInsensitiveProbe) {
188
- const cached = posixCaseInsensitiveByPath.get(resolvedAncestor);
189
- caseInsensitive = cached ?? deps.caseInsensitiveProbe(resolvedAncestor);
190
- if (cached === undefined) {
191
- posixCaseInsensitiveByPath.set(resolvedAncestor, caseInsensitive);
192
- }
193
- }
194
- else {
195
- caseInsensitive = posixFilesystemIsCaseInsensitive(resolvedAncestor);
196
- }
197
- }
198
- const relative = pathApi.relative(caseInsensitive ? resolvedAncestor.toLowerCase() : resolvedAncestor, caseInsensitive ? resolvedCandidate.toLowerCase() : resolvedCandidate);
199
- return (relative === "" ||
200
- (!relative.startsWith("..") && !pathApi.isAbsolute(relative)));
201
- }
202
- /** Enforce the session cwd as the hard boundary for LSP client root selection. */
203
- export function enforceLspRootCeiling(root, sessionCwd, filePath) {
204
- const windowsShaped = isWindowsPath(root) || isWindowsPath(sessionCwd);
205
- const pathApi = windowsShaped ? path.win32 : path;
206
- const resolvedRoot = pathApi.resolve(root);
207
- const resolvedCwd = pathApi.resolve(sessionCwd);
208
- // Callers may explicitly inspect an out-of-session file (notably isolated
209
- // tests and API consumers). The cwd ceiling governs roots for files that are
210
- // actually inside the declared session project.
211
- if (filePath && !isSameOrWithin(resolvedCwd, pathApi.resolve(filePath))) {
212
- return resolvedRoot;
213
- }
214
- if (isSameOrWithin(resolvedCwd, resolvedRoot))
215
- return resolvedRoot;
216
- const logKey = `${normalizeMapKey(resolvedCwd)}:${normalizeMapKey(resolvedRoot)}`;
217
- if (!loggedRootCeilingClamps.has(logKey)) {
218
- loggedRootCeilingClamps.add(logKey);
219
- logSessionStart(`lsp root clamped to session cwd: candidate=${resolvedRoot} cwd=${resolvedCwd}`);
220
- }
221
- return resolvedCwd;
222
- }
223
- export async function hasProjectBoundaryMarker(dir) {
224
- // A nested Git checkout is an independently hosted project even when it has
225
- // no language manifest. Keep this directory boundary aligned with the shared
226
- // FALLBACK_PROJECT_MARKERS policy used by nearestNonExcludedFallbackRoot.
227
- if (await markerExists(dir, ".git"))
228
- return true;
229
- for (const marker of PROJECT_BOUNDARY_MARKERS) {
230
- if (await markerExists(dir, marker))
231
- return true;
232
- }
233
- return false;
234
- }
235
- function pathSegments(dir) {
236
- const parsed = path.parse(path.resolve(dir));
237
- return path
238
- .relative(parsed.root, path.resolve(dir))
239
- .split(path.sep)
240
- .filter(Boolean)
241
- .map((segment) => segment.toLowerCase());
242
- }
243
- function hasFixtureConvention(dir) {
244
- const segments = pathSegments(dir);
245
- // Go treats any directory named testdata as fixture data by convention. The
246
- // exclusion is intentionally ancestor-wide so nested fixture projects cannot
247
- // become independent LSP roots, but the segment match itself stays exact.
248
- if (segments.some((segment) => FIXTURE_ROOT_SEGMENTS.has(segment)))
249
- return true;
250
- return segments.some((segment, index) => segment === "tests" && segments[index + 1] === "fixtures");
251
- }
252
- function hasAtomicStageSegment(dir) {
253
- return pathSegments(dir).some((segment) => STAGE_TMP_PATTERN.test(segment));
254
- }
255
- async function findGitBoundary(dir) {
256
- let current = path.resolve(dir);
257
- const fsRoot = path.parse(current).root;
258
- while (true) {
259
- if (await markerExists(current, ".git"))
260
- return current;
261
- if (current === fsRoot)
262
- return undefined;
263
- current = path.dirname(current);
264
- }
265
- }
266
- async function isExcludedLspRoot(dir) {
267
- const candidate = path.resolve(dir);
268
- if (hasFixtureConvention(candidate) || hasAtomicStageSegment(candidate))
269
- return true;
270
- const gitRoot = await findGitBoundary(candidate);
271
- if (!gitRoot || gitRoot === candidate)
272
- return false;
273
- // Avoid constructing the matcher when the project has no positive ignore rules.
274
- // The matcher remains authoritative (including anchored rules and directory form).
275
- if (getProjectIgnoreGlobs(gitRoot).length === 0)
276
- return false;
277
- return isPathIgnoredByProject(candidate, gitRoot, true);
278
- }
279
- async function nearestNonExcludedFallbackRoot(candidate) {
280
- if (!(await isExcludedLspRoot(candidate)))
281
- return path.resolve(candidate);
282
- let current = path.dirname(path.resolve(candidate));
283
- const fsRoot = path.parse(current).root;
284
- let nearestAllowed;
285
- while (true) {
286
- if (!(await isExcludedLspRoot(current))) {
287
- nearestAllowed ??= current;
288
- for (const marker of FALLBACK_PROJECT_MARKERS) {
289
- if (await markerExists(current, marker))
290
- return current;
291
- }
292
- }
293
- if (current === fsRoot)
294
- break;
295
- current = path.dirname(current);
296
- }
297
- // No project marker was available. Keep the file attached to a stable,
298
- // non-excluded ancestor rather than minting a client inside the fixture/stage.
299
- return nearestAllowed ?? fsRoot;
300
- }
301
- function isLspInstallDisabled() {
302
- return process.env.PI_LENS_DISABLE_LSP_INSTALL === "1";
303
- }
304
- function canInstall(allowInstall) {
305
- return allowInstall !== false && !isLspInstallDisabled();
306
- }
307
- const DIRECT_LSP_NEGATIVE_TTL_MS = Math.max(30_000, Number.parseInt(process.env.PI_LENS_DIRECT_LSP_NEGATIVE_TTL_MS ?? "600000", 10) || 600_000);
308
- const directLspCommandUnavailableUntil = new Map();
309
- const directLspCommandSkipLoggedUntil = new Map();
310
- // Availability rows are emitted from the same async launch path that owns the
311
- // live LSP generation. A session reset can retire that generation while a
312
- // managed lookup, install, or launch is still awaiting; stale work must not
313
- // publish into the replacement session (#2351, shape 22).
314
- const lspLaunchAvailabilityGeneration = createGenerationSource("lsp-launch-availability");
315
- export function resetLspLaunchAvailabilityGeneration() {
316
- lspLaunchAvailabilityGeneration.bump();
317
- }
318
- function staleLaunch(generation, subject, proc) {
319
- if (generation.isCurrent())
320
- return false;
321
- try {
322
- proc?.process?.kill();
323
- }
324
- catch {
325
- // Best-effort cleanup for a process returned after service retirement.
326
- }
327
- generation.guardedWrite(subject, () => undefined);
328
- return true;
329
- }
330
- async function installEvidenceForLaunch(toolId, installed, attempt) {
331
- const evidence = describeInstallAttempt(attempt);
332
- const confirmedManagedPath = await findManagedToolBinary(toolId);
333
- return {
334
- ...evidence,
335
- binary: path.basename(installed),
336
- ...(confirmedManagedPath !== undefined &&
337
- pathsEqual(confirmedManagedPath, installed) && {
338
- source: "managed-dir",
339
- }),
340
- };
341
- }
342
- function captureInstallAttempt(toolId) {
343
- try {
344
- // Capture synchronously after ensureTool resolves. Reading this later, after
345
- // launch/evidence awaits, can observe another concurrent ensure's outcome.
346
- return installer.getInstallAttempt?.(toolId);
347
- }
348
- catch {
349
- // Older test doubles do not expose this production export.
350
- }
351
- return undefined;
352
- }
353
- /** Re-arm direct-command availability for the next session. */
354
- export function resetDirectLspCommandAvailability() {
355
- directLspCommandUnavailableUntil.clear();
356
- directLspCommandSkipLoggedUntil.clear();
357
- }
358
- /** Test seam for seeding the real negative-cache path. */
359
- export function _markDirectLspCommandUnavailableForTests(command) {
360
- markDirectLspCommandUnavailable(command);
361
- }
362
- function pruneExpiredDirectLspNegativeEntries(now = Date.now()) {
363
- for (const [command, until] of directLspCommandUnavailableUntil) {
364
- if (until <= now) {
365
- directLspCommandUnavailableUntil.delete(command);
366
- directLspCommandSkipLoggedUntil.delete(command);
367
- }
368
- }
369
- for (const [command, until] of directLspCommandSkipLoggedUntil) {
370
- if (until <= now)
371
- directLspCommandSkipLoggedUntil.delete(command);
372
- }
373
- }
374
- function isSimpleCommand(command) {
375
- return (!isFullyQualified(command) &&
376
- !command.includes("/") &&
377
- !command.includes("\\"));
378
- }
379
- export function isDirectLspCommandTemporarilyUnavailable(command) {
380
- const now = Date.now();
381
- pruneExpiredDirectLspNegativeEntries(now);
382
- const until = directLspCommandUnavailableUntil.get(command);
383
- if (!until || until <= now) {
384
- directLspCommandUnavailableUntil.delete(command);
385
- return false;
386
- }
387
- const loggedUntil = directLspCommandSkipLoggedUntil.get(command) ?? 0;
388
- if (loggedUntil <= now) {
389
- logSessionStart(`lsp direct command ${command}: skipped by negative availability cache (${Math.max(0, until - now)}ms remaining)`);
390
- directLspCommandSkipLoggedUntil.set(command, until);
391
- }
392
- return true;
393
- }
394
- function markDirectLspCommandUnavailable(command) {
395
- if (!isSimpleCommand(command))
396
- return;
397
- directLspCommandUnavailableUntil.set(command, Date.now() + DIRECT_LSP_NEGATIVE_TTL_MS);
398
- directLspCommandSkipLoggedUntil.delete(command);
399
- }
400
- const PI_LENS_BIN_DIR = path.join(getGlobalPiLensDir(), "bin");
401
- export async function resolveAndLaunch(spec, allowInstall) {
402
- const generation = lspLaunchAvailabilityGeneration.capture();
403
- const toolLabel = spec.managedToolId ??
404
- spec.candidates[spec.candidates.length - 1] ??
405
- "unknown";
406
- let lastRuntimeFailure;
407
- const trackRuntimeFailure = (err) => {
408
- const message = err instanceof Error ? err.message : String(err);
409
- if (!hasSpawnFailureKind(err, "tool-not-found")) {
410
- lastRuntimeFailure = err instanceof Error ? err : new Error(message);
411
- }
412
- };
413
- // #2140: consult the release-managed binary directory (~/.pi-lens/bin)
414
- // BEFORE the bare-PATH candidates below. A bare `candidates: ["opengrep"]`
415
- // entry only ever resolves through the OS's own PATH lookup — never that
416
- // directory — so a github/maven/archive-strategy tool installed there with
417
- // no PATH entry ENOENTs every direct candidate first, only to have the
418
- // managed-install step further down find the very same binary a few
419
- // hundred ms later. `findManagedToolBinary` does a bare `fs.access`, no
420
- // spawn, so this costs at most a few stat calls per server launch attempt
421
- // (not per file/dispatch — launches are session-scoped). Mirrors
422
- // `findManagedNodeToolBinary`'s npm-managed fast path (runner-helpers.ts)
423
- // and `SecurityScanClient.probeVersion`'s equivalent fix for the CLI-scan
424
- // half of this same issue (#2140, landed in PR #2148/#2137).
425
- const managedProbeStartedAt = Date.now();
426
- const managedCandidate = spec.managedToolId
427
- ? await findManagedToolBinary(spec.managedToolId)
428
- : undefined;
429
- if (staleLaunch(generation, `${toolLabel}:findManagedToolBinary`)) {
430
- return undefined;
431
- }
432
- const candidates = managedCandidate && !spec.candidates.includes(managedCandidate)
433
- ? [managedCandidate, ...spec.candidates]
434
- : spec.candidates;
435
- // A candidate that fails while a LATER candidate (or managed install)
436
- // succeeds is just fallback, not a failure — logging each immediately floods
437
- // the logs with scary "candidate failed / npm shim failed / Run npm install"
438
- // lines that read as smells even though the launch succeeded. Collect them and
439
- // surface only if ALL direct candidates fail.
440
- const candidateFailures = [];
441
- // Step 1 & 2 — try all explicit candidates (includes bare command = PATH lookup)
442
- for (const [index, command] of candidates.entries()) {
443
- logLatency({
444
- type: "phase",
445
- phase: "lsp_launch_candidate_attempt",
446
- filePath: spec.cwd,
447
- durationMs: 0,
448
- metadata: {
449
- tool: toolLabel,
450
- command,
451
- index,
452
- totalCandidates: candidates.length,
453
- allowInstall: canInstall(allowInstall),
454
- },
455
- });
456
- logSessionStart(`lsp launch candidate attempt tool=${toolLabel} idx=${index}/${candidates.length - 1} command=${command} cwd=${spec.cwd}`);
457
- try {
458
- const proc = await launchLSP(command, spec.args, {
459
- cwd: spec.cwd,
460
- env: spec.env,
461
- });
462
- if (staleLaunch(generation, `${toolLabel}:launchLSP:${command}`, proc)) {
463
- return undefined;
464
- }
465
- logLatency({
466
- type: "phase",
467
- phase: "lsp_launch_candidate_success",
468
- filePath: spec.cwd,
469
- durationMs: 0,
470
- metadata: {
471
- tool: toolLabel,
472
- command,
473
- index,
474
- source: "direct",
475
- },
476
- });
477
- logSessionStart(`lsp launch candidate success tool=${toolLabel} idx=${index} command=${command} source=direct`);
478
- // The managed-dir fast path (#2140) IS the availability probe for a
479
- // release-managed tool: a real spawn just confirmed the binary
480
- // findManagedToolBinary resolved actually launches. Gated on the exact
481
- // managed candidate (never a later bare-PATH fallback), so a session
482
- // start with the binary present emits exactly one decision and a
483
- // developer-PATH-resolved copy (no managed binary at all) emits none —
484
- // unchanged from before this fix.
485
- if (managedCandidate !== undefined && command === managedCandidate) {
486
- logAvailabilityDecision({
487
- tool: toolLabel,
488
- verdict: "available",
489
- outcome: "success",
490
- cause: "ok",
491
- elapsedMs: Date.now() - managedProbeStartedAt,
492
- latched: false,
493
- producer: "lsp-launch",
494
- classifiedBy: "probe",
495
- evidence: {
496
- binary: path.basename(managedCandidate),
497
- source: "managed-dir",
498
- correctsLatchedRow: false,
499
- },
500
- });
501
- }
502
- return { process: proc, source: "direct" };
503
- }
504
- catch (err) {
505
- if (staleLaunch(generation, `${toolLabel}:launchLSP:${command}`)) {
506
- return undefined;
507
- }
508
- const message = err instanceof Error ? err.message : String(err);
509
- // Defer logging: only a failure if no later candidate/install succeeds.
510
- candidateFailures.push({ index, command, message, err });
511
- // try next
512
- }
513
- }
514
- // All direct candidates failed (a successful one returns above). Surface the
515
- // deferred failures now so the all-failed case stays fully diagnosable.
516
- for (const failure of candidateFailures) {
517
- logLatency({
518
- type: "phase",
519
- phase: "lsp_launch_candidate_failed",
520
- filePath: spec.cwd,
521
- durationMs: 0,
522
- metadata: {
523
- tool: toolLabel,
524
- command: failure.command,
525
- index: failure.index,
526
- error: failure.message,
527
- },
528
- });
529
- logSessionStart(`lsp launch candidate failed tool=${toolLabel} idx=${failure.index} command=${failure.command} error=${failure.message}`);
530
- trackRuntimeFailure(failure.err);
531
- }
532
- const hasOnlyRepairableCandidateFailures = candidateFailures.every((failure) => hasSpawnFailureKind(failure.err, "tool-not-found"));
533
- if (!hasOnlyRepairableCandidateFailures) {
534
- if (lastRuntimeFailure)
535
- throw lastRuntimeFailure;
536
- return undefined;
537
- }
538
- if (!canInstall(allowInstall)) {
539
- logSessionStart(`lsp launch install blocked tool=${toolLabel} cwd=${spec.cwd} allowInstall=${allowInstall !== false} globalDisabled=${isLspInstallDisabled()}`);
540
- logLatency({
541
- type: "phase",
542
- phase: "lsp_launch_install_blocked",
543
- filePath: spec.cwd,
544
- durationMs: 0,
545
- metadata: {
546
- tool: toolLabel,
547
- allowInstall,
548
- globalInstallDisabled: isLspInstallDisabled(),
549
- },
550
- });
551
- return undefined;
552
- }
553
- // Step 3 — managed install via installer registry
554
- if (spec.managedToolId) {
555
- // Neither the managed-dir stat (#2140) nor any bare-PATH candidate
556
- // resolved this tool — the negative counterpart to the fast-path
557
- // "available" decision above, mirroring `SecurityScanClient.probeVersion`'s
558
- // failure record so the negative case is visible in latency.log rather
559
- // than swallowed by going straight to the install attempt.
560
- const failedCandidate = candidateFailures.at(-1);
561
- const classifiedFailure = classifyProbeFailure({
562
- error: failedCandidate?.err instanceof Error
563
- ? failedCandidate.err
564
- : undefined,
565
- spawnFailure: {
566
- kind: hasSpawnFailureKind(failedCandidate?.err, "tool-not-found")
567
- ? "tool-not-found"
568
- : undefined,
569
- },
570
- }, { command: failedCandidate?.command });
571
- logAvailabilityDecision({
572
- tool: spec.managedToolId,
573
- verdict: "unavailable",
574
- outcome: classifiedFailure.outcome,
575
- cause: classifiedFailure.cause,
576
- elapsedMs: Date.now() - managedProbeStartedAt,
577
- latched: false,
578
- classifiedBy: "probe",
579
- producer: "lsp-launch",
580
- evidence: classifiedFailure.evidence,
581
- });
582
- logSessionStart(`lsp launch ensure-tool start tool=${spec.managedToolId} cwd=${spec.cwd}`);
583
- const installStartedAt = Date.now();
584
- const installed = await ensureTool(spec.managedToolId);
585
- const installAttempt = captureInstallAttempt(spec.managedToolId);
586
- if (staleLaunch(generation, `${toolLabel}:ensureTool`)) {
587
- return undefined;
588
- }
589
- logSessionStart(`lsp launch ensure-tool result tool=${spec.managedToolId} installed=${installed ? "yes" : "no"} path=${installed ?? ""}`);
590
- logLatency({
591
- type: "phase",
592
- phase: "lsp_launch_ensure_tool_result",
593
- filePath: spec.cwd,
594
- durationMs: 0,
595
- metadata: {
596
- tool: spec.managedToolId,
597
- installed: Boolean(installed),
598
- path: installed,
599
- },
600
- });
601
- if (installed) {
602
- try {
603
- const proc = await launchLSP(installed, spec.args, {
604
- cwd: spec.cwd,
605
- env: spec.env,
606
- });
607
- if (staleLaunch(generation, `${toolLabel}:launchLSP:managed`, proc)) {
608
- return undefined;
609
- }
610
- logSessionStart(`lsp launch managed success tool=${spec.managedToolId} command=${installed} source=managed`);
611
- logLatency({
612
- type: "phase",
613
- phase: "lsp_launch_managed_success",
614
- filePath: spec.cwd,
615
- durationMs: 0,
616
- metadata: {
617
- tool: spec.managedToolId,
618
- command: installed,
619
- },
620
- });
621
- // Compensating row for the "unavailable" decision logged above (#1606
622
- // shape): the install fixed exactly what the fast path found missing,
623
- // so the durable record must not be left saying the tool is off.
624
- // `source` is only asserted when a fresh managed-dir lookup confirms
625
- // the install actually landed in ~/.pi-lens/bin (github/maven/archive
626
- // strategies) rather than an npm/pip/gem install elsewhere, so the
627
- // evidence never claims a resolution this call didn't derive.
628
- const evidence = await installEvidenceForLaunch(spec.managedToolId, installed, installAttempt);
629
- if (staleLaunch(generation, `${toolLabel}:managed-evidence`, proc)) {
630
- return undefined;
631
- }
632
- logAvailabilityDecision({
633
- tool: spec.managedToolId,
634
- verdict: "available",
635
- outcome: "success",
636
- cause: "ok",
637
- elapsedMs: Date.now() - installStartedAt,
638
- latched: false,
639
- classifiedBy: "caller",
640
- producer: "lsp-launch",
641
- evidence: {
642
- ...evidence,
643
- correctsLatchedRow: false,
644
- },
645
- });
646
- return { process: proc, source: "managed" };
647
- }
648
- catch (err) {
649
- if (staleLaunch(generation, `${toolLabel}:launchLSP:managed-rejection`)) {
650
- return undefined;
651
- }
652
- const message = err instanceof Error ? err.message : String(err);
653
- logSessionStart(`lsp launch managed failed tool=${spec.managedToolId} command=${installed} error=${message}`);
654
- logLatency({
655
- type: "phase",
656
- phase: "lsp_launch_managed_failed",
657
- filePath: spec.cwd,
658
- durationMs: 0,
659
- metadata: {
660
- tool: spec.managedToolId,
661
- command: installed,
662
- error: message,
663
- },
664
- });
665
- trackRuntimeFailure(err);
666
- // force-reinstall: when a PATH-resolved tool (bare command name)
667
- // fails to launch (e.g. broken symlink, missing .dll), nuke the
668
- // caches and download a managed copy from the registry.
669
- const looksPathResolved = !installed.includes("/") && !installed.includes("\\");
670
- if (looksPathResolved && hasSpawnFailureKind(err, "tool-not-found")) {
671
- logSessionStart(`lsp launch managed retry force-reinstall tool=${spec.managedToolId}`);
672
- const reinstalled = await ensureTool(spec.managedToolId, {
673
- forceReinstall: true,
674
- });
675
- const reinstallAttempt = captureInstallAttempt(spec.managedToolId);
676
- if (staleLaunch(generation, `${toolLabel}:forceReinstall`)) {
677
- return undefined;
678
- }
679
- if (reinstalled) {
680
- try {
681
- const proc = await launchLSP(reinstalled, spec.args, {
682
- cwd: spec.cwd,
683
- env: spec.env,
684
- });
685
- if (staleLaunch(generation, `${toolLabel}:launchLSP:forceReinstall`, proc)) {
686
- return undefined;
687
- }
688
- logSessionStart(`lsp launch managed force-reinstall success tool=${spec.managedToolId} command=${reinstalled}`);
689
- logLatency({
690
- type: "phase",
691
- phase: "lsp_launch_managed_force_reinstall_success",
692
- filePath: spec.cwd,
693
- durationMs: 0,
694
- metadata: {
695
- tool: spec.managedToolId,
696
- command: reinstalled,
697
- },
698
- });
699
- const evidence = await installEvidenceForLaunch(spec.managedToolId, reinstalled, reinstallAttempt);
700
- if (staleLaunch(generation, `${toolLabel}:forceReinstall-evidence`, proc)) {
701
- return undefined;
702
- }
703
- logAvailabilityDecision({
704
- tool: spec.managedToolId,
705
- verdict: "available",
706
- outcome: "success",
707
- cause: "ok",
708
- elapsedMs: Date.now() - installStartedAt,
709
- latched: false,
710
- classifiedBy: "caller",
711
- producer: "lsp-launch",
712
- evidence: {
713
- ...evidence,
714
- correctsLatchedRow: false,
715
- },
716
- });
717
- return { process: proc, source: "managed" };
718
- }
719
- catch (retryErr) {
720
- logSessionStart(`lsp launch managed force-reinstall failed tool=${spec.managedToolId} error=${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
721
- }
722
- }
723
- }
724
- // fall through
725
- }
726
- }
727
- }
728
- // Step 4 — language-native runtime install (go install, gem install, …)
729
- const runtimeInstall = spec.runtimeInstall;
730
- const runtimeAvailable = runtimeInstall
731
- ? await isOnPath(runtimeInstall.runtimeCommand)
732
- : false;
733
- if (staleLaunch(generation, `${toolLabel}:runtimeAvailability`)) {
734
- return undefined;
735
- }
736
- if (runtimeInstall && runtimeAvailable) {
737
- const ok = await runtimeInstall.install();
738
- if (staleLaunch(generation, `${toolLabel}:runtimeInstall`)) {
739
- return undefined;
740
- }
741
- if (ok) {
742
- const retry = runtimeInstall.retryCandidates ?? spec.candidates;
743
- for (const command of retry) {
744
- try {
745
- const proc = await launchLSP(command, spec.args, {
746
- cwd: spec.cwd,
747
- env: spec.env,
748
- });
749
- if (staleLaunch(generation, `${toolLabel}:launchLSP:runtime`, proc)) {
750
- return undefined;
751
- }
752
- return { process: proc, source: "managed" };
753
- }
754
- catch (err) {
755
- if (staleLaunch(generation, `${toolLabel}:launchLSP:runtime`)) {
756
- return undefined;
757
- }
758
- trackRuntimeFailure(err);
759
- // try next
760
- }
761
- }
762
- }
763
- }
764
- if (lastRuntimeFailure) {
765
- throw lastRuntimeFailure;
766
- }
767
- return undefined;
768
- }
769
- /**
770
- * Launch a language server that ships as a multi-folder MODULE BUNDLE driven by a
771
- * separate runtime (e.g. PowerShell Editor Services via `pwsh ...
772
- * Start-EditorServices.ps1 -Stdio`), rather than a single executable on PATH.
773
- *
774
- * Resolution order: (1) a runtime interpreter must be on PATH — else GRACEFUL
775
- * SKIP (returns undefined → the runner's coverage notice, never a hard fail);
776
- * (2) the bundle must be installed (already-extracted, or installed now when
777
- * `allowInstall`) — else graceful skip; (3) launch the runtime against the
778
- * bundle over stdio. A launch failure is logged and also degrades to a skip.
779
- */
780
- async function resolveAndLaunchBundle(spec, allowInstall) {
781
- // 1. Resolve the runtime interpreter on PATH (don't spawn it bare — that would
782
- // hang; just probe). No runtime → graceful skip (coverage notice).
783
- let runtime;
784
- for (const candidate of spec.runtimeCandidates) {
785
- if (await isOnPath(candidate)) {
786
- runtime = candidate;
787
- break;
788
- }
789
- }
790
- if (!runtime) {
791
- logSessionStart(`lsp launch bundle skip tool=${spec.bundleToolId}: no runtime on PATH (tried ${spec.runtimeCandidates.join(", ")})`);
792
- return undefined;
793
- }
794
- // 2. Resolve the bundle directory: already installed, else install when allowed.
795
- let bundleDir = await getToolPath(spec.bundleToolId);
796
- if (!bundleDir && canInstall(allowInstall)) {
797
- bundleDir = await ensureTool(spec.bundleToolId);
798
- }
799
- if (!bundleDir) {
800
- logSessionStart(`lsp launch bundle skip tool=${spec.bundleToolId}: bundle not installed (allowInstall=${allowInstall !== false})`);
801
- return undefined;
802
- }
803
- // 3. Launch the runtime against the bundle over stdio.
804
- try {
805
- const proc = await launchLSP(runtime, spec.args(bundleDir), {
806
- cwd: spec.cwd,
807
- env: spec.env,
808
- });
809
- logSessionStart(`lsp launch bundle success tool=${spec.bundleToolId} runtime=${runtime} bundle=${bundleDir}`);
810
- return { process: proc, source: "managed" };
811
- }
812
- catch (err) {
813
- const message = err instanceof Error ? err.message : String(err);
814
- logSessionStart(`lsp launch bundle failed tool=${spec.bundleToolId} runtime=${runtime} error=${message}`);
815
- return undefined;
816
- }
817
- }
818
- /**
819
- * Launch a language server that ships as a self-contained native TREE BUNDLE with
820
- * its executable INSIDE the extracted tree (e.g. clangd: `<bundle>/bin/clangd`
821
- * plus the bundled libclang headers under `lib/`), as opposed to a single binary
822
- * on PATH or a runtime-driven module bundle (see {@link resolveAndLaunchBundle}).
823
- *
824
- * Resolution order: (1) PATH candidates first — a system install wins; (2) the
825
- * managed bundle (already-extracted, or installed now when `allowInstall`), then
826
- * launch the bin within it. No external runtime. Anything missing → GRACEFUL SKIP
827
- * (returns undefined → the runner's coverage notice, never a hard fail).
828
- */
829
- async function resolveAndLaunchTreeBinary(spec, allowInstall) {
830
- // 1. PATH-first — a system install wins (user-managed, no 150MB download).
831
- for (const command of spec.candidates) {
832
- try {
833
- const proc = await launchLSP(command, spec.args, {
834
- cwd: spec.cwd,
835
- env: spec.env,
836
- });
837
- return { process: proc, source: "direct" };
838
- }
839
- catch {
840
- // not on PATH (or broken) — fall through to the managed bundle
841
- }
842
- }
843
- // 2. Managed tree bundle: already-extracted, else install when allowed.
844
- let bundleDir = await getToolPath(spec.bundleToolId);
845
- if (!bundleDir && canInstall(allowInstall)) {
846
- bundleDir = await ensureTool(spec.bundleToolId);
847
- }
848
- if (!bundleDir) {
849
- logSessionStart(`lsp launch tree-bin skip tool=${spec.bundleToolId}: not on PATH and bundle not installed (allowInstall=${allowInstall !== false})`);
850
- return undefined;
851
- }
852
- const suffix = process.platform === "win32" ? ".exe" : "";
853
- const binPath = path.join(bundleDir, ...spec.binRelPath.split("/")) + suffix;
854
- try {
855
- const proc = await launchLSP(binPath, spec.args, {
856
- cwd: spec.cwd,
857
- env: spec.env,
858
- });
859
- logSessionStart(`lsp launch tree-bin success tool=${spec.bundleToolId} bin=${binPath}`);
860
- return { process: proc, source: "managed" };
861
- }
862
- catch (err) {
863
- const message = err instanceof Error ? err.message : String(err);
864
- logSessionStart(`lsp launch tree-bin failed tool=${spec.bundleToolId} bin=${binPath} error=${message}`);
865
- return undefined;
866
- }
867
- }
868
- function nodeBinLocalCandidates(root, baseName) {
869
- const localBase = path.join(root, "node_modules", ".bin", baseName);
870
- if (process.platform === "win32") {
871
- return [`${localBase}.cmd`, `${localBase}.exe`];
872
- }
873
- return [localBase];
874
- }
875
- function nodeBinCandidates(root, baseName) {
876
- return [...nodeBinLocalCandidates(root, baseName), baseName];
877
- }
878
- function piAgentExtensionsRootKey(file) {
879
- // #1193 P3: `normalizeEphemeralMapKey` IS this module's former
880
- // `normalizeSlashKey` — slash-fold, then lowercase only on win32 — and these
881
- // directory keys are exactly what it is scoped for: process-local, same-run,
882
- // derived from this process's own `path.resolve`, never persisted and never
883
- // compared against an externally supplied spelling.
884
- const dirKey = normalizeEphemeralMapKey(path.dirname(path.resolve(file)));
885
- const marker = "/.pi/agent/extensions";
886
- const index = dirKey.indexOf(marker);
887
- if (index === -1)
888
- return undefined;
889
- return dirKey.slice(0, index + marker.length);
890
- }
891
- function IgnoreHomeRoot(primary) {
892
- // #1193 P3: the former `normalizeRootKey` folded case on win32 but left the
893
- // separator alone; both sides of the comparison below go through the same
894
- // derivation, so adding the seam's slash fold cannot change the verdict for
895
- // any spelling the old copy already agreed on, and it makes a mixed-separator
896
- // root agree with the home directory it names.
897
- const homeKey = normalizeEphemeralMapKey(path.resolve(os.homedir()));
898
- return withRootMarkers(async (file) => {
899
- const root = await primary(file);
900
- if (!root)
901
- return undefined;
902
- return normalizeEphemeralMapKey(path.resolve(root)) === homeKey
903
- ? undefined
904
- : root;
905
- }, primary.rootMarkers ?? []);
906
- }
907
- function rubyBinCandidates(baseName) {
908
- const candidates = [];
909
- const home = os.homedir();
910
- const isWin = process.platform === "win32";
911
- const ext = isWin ? ".bat" : "";
912
- // mise and asdf version managers — same layout on all platforms
913
- candidates.push(path.join(home, ".local", "share", "mise", "installs", "ruby", "bin", `${baseName}${ext}`));
914
- candidates.push(path.join(home, ".asdf", "installs", "ruby", "bin", `${baseName}${ext}`));
915
- if (isWin) {
916
- // Ruby installer drops versioned dirs on C: by convention, but the drive
917
- // and version suffix vary — scan what's actually present instead of
918
- // hardcoding. Memoized once per process (#1137): a synchronous drive-root
919
- // enumeration per LSP spawn was an event-loop offender.
920
- const driveRoot = path.parse(home).root; // e.g. "C:\"
921
- for (const entry of getRubyVersionDirNamesSync(driveRoot)) {
922
- candidates.push(path.join(driveRoot, entry, "bin", `${baseName}.bat`));
923
- candidates.push(path.join(driveRoot, entry, "bin", baseName));
924
- }
925
- }
926
- return candidates;
927
- }
928
- function createInteractiveServer(spec) {
929
- return {
930
- id: spec.id,
931
- name: spec.name,
932
- extensions: spec.extensions,
933
- root: spec.root,
934
- rootMarkers: spec.root.rootMarkers,
935
- fallbackFor: spec.fallbackFor,
936
- availabilityKey: typeof spec.command === "string" && isSimpleCommand(spec.command)
937
- ? spec.command
938
- : undefined,
939
- async spawn(root) {
940
- const command = typeof spec.command === "function" ? spec.command(root) : spec.command;
941
- const args = typeof spec.args === "function" ? spec.args(root) : spec.args || [];
942
- // Try to launch directly — no auto-install for language-runtime tools
943
- // (C#, Java, Swift, etc. require their SDK; cannot npm/pip install them)
944
- if (isSimpleCommand(command) &&
945
- isDirectLspCommandTemporarilyUnavailable(command)) {
946
- return undefined;
947
- }
948
- // #241: the server binary is run by a language runtime (jdtls → java).
949
- // When that runtime isn't on PATH, inject a discovered install's env so
950
- // it launches instead of silently failing with no_clients.
951
- const runtimeEnv = spec.runtime === "java" ? await resolveJavaRuntimeEnv() : undefined;
952
- try {
953
- const proc = await launchLSP(command, args, {
954
- cwd: root,
955
- ...(runtimeEnv ? { env: runtimeEnv } : {}),
956
- });
957
- const initialization = typeof spec.initialization === "function"
958
- ? spec.initialization(root)
959
- : spec.initialization;
960
- return { process: proc, source: "direct", initialization };
961
- }
962
- catch (err) {
963
- if (hasSpawnFailureKind(err, "tool-not-found")) {
964
- markDirectLspCommandUnavailable(command);
965
- }
966
- return undefined;
967
- }
968
- },
969
- };
970
- }
971
- export function PriorityRoot(markerGroups, excludePatterns, stopDir) {
972
- const resolvers = markerGroups.map((markers) => NearestRoot(markers, excludePatterns, stopDir));
973
- return withRootMarkers(async (file) => {
974
- for (const resolve of resolvers) {
975
- const root = await resolve(file);
976
- if (root)
977
- return root;
978
- }
979
- return undefined;
980
- }, markerGroups.flat());
981
- }
982
- export const FileDirRoot = async (file) => {
983
- const candidate = path.resolve(path.dirname(file));
984
- return nearestNonExcludedFallbackRoot(candidate);
985
- };
986
- export function RootWithFallback(primary, fallback = FileDirRoot) {
987
- return withRootMarkers(async (file) => {
988
- const primaryRoot = await primary(file);
989
- if (primaryRoot)
990
- return primaryRoot;
991
- return fallback(file);
992
- }, primary.rootMarkers ?? []);
993
- }
994
- export function WorkspacePriorityRoot(markerGroups, excludePatterns) {
995
- return withRootMarkers(async (file) => PriorityRoot(markerGroups, excludePatterns, process.cwd())(file), markerGroups.flat());
996
- }
997
- function isPermissionFsError(err) {
998
- const code = err?.code;
999
- return code === "EACCES" || code === "EPERM";
1000
- }
1001
- /**
1002
- * The directory `markerExists(dir, pattern)` reads to answer for `pattern`:
1003
- * `dir` itself for a plain basename, and the named subdirectory for a pattern
1004
- * that carries a path segment (`"prisma/schema.prisma"`). `NearestRoot`'s
1005
- * freshness records are keyed on these directories, so the derivation lives
1006
- * HERE, next to the probe it describes — a second copy could drift and silently
1007
- * stop covering a pattern shape (#3412).
1008
- */
1009
- function markerProbeDir(dir, pattern) {
1010
- const normalized = toPosix(pattern);
1011
- const slash = normalized.lastIndexOf("/");
1012
- const parentPattern = slash >= 0 ? normalized.slice(0, slash) : "";
1013
- return parentPattern
1014
- ? path.join(dir, ...parentPattern.split("/").filter(Boolean))
1015
- : dir;
1016
- }
1017
- async function markerExists(dir, pattern) {
1018
- if (!pattern.includes("*")) {
1019
- try {
1020
- await stat(path.join(dir, pattern));
1021
- return true;
1022
- }
1023
- catch (err) {
1024
- if (isPermissionFsError(err)) {
1025
- logSessionStart(`lsp root marker skipped: permission error stat ${path.join(dir, pattern)}`);
1026
- }
1027
- return false;
1028
- }
1029
- }
1030
- const normalized = pattern.replace(/\\/g, "/");
1031
- const slash = normalized.lastIndexOf("/");
1032
- const basenamePattern = slash >= 0 ? normalized.slice(slash + 1) : normalized;
1033
- if (!basenamePattern)
1034
- return false;
1035
- const targetDir = markerProbeDir(dir, normalized);
1036
- try {
1037
- const entries = await readdir(targetDir, { withFileTypes: true });
1038
- // Match files/symlinks only — a directory named like the marker (e.g. a
1039
- // `Foo.csproj/` dir) is not a project file. Case-insensitive on win32 to
1040
- // match the filesystem (and the project ignore matcher), via the shared
1041
- // marker-glob helper.
1042
- return direntsHaveMarkerGlobMatch(entries, basenamePattern);
1043
- }
1044
- catch (err) {
1045
- if (isPermissionFsError(err)) {
1046
- logSessionStart(`lsp root marker skipped: permission error read ${targetDir}`);
1047
- }
1048
- return false;
1049
- }
1050
- }
1051
- /**
1052
- * One bound for every directory-mtime read this detector awaits. `signal` is
1053
- * `undefined` on purpose: `RootFunction` takes a file path and nothing else, so
1054
- * no hook signal reaches this seam until #2523 AC4 threads one — the wall clock
1055
- * is the live half, and the registry entry says so. The budget is the edit
1056
- * `tool_result` one because that is the hook the per-file touch path runs under
1057
- * and the only hook the contract lets block the host; a tighter number would
1058
- * abandon a revalidation that is strictly CHEAPER than the walk it falls back
1059
- * to, making the hook slower rather than safer.
1060
- */
1061
- const rootMtimeBound = (label) => ({
1062
- ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
1063
- signal: undefined,
1064
- hook: "tool_result_edit",
1065
- label,
1066
- });
1067
- /**
1068
- * Walk up the directory tree looking for project root markers.
1069
- *
1070
- * NearestRoot(includePatterns, excludePatterns?) → RootFunction
1071
- *
1072
- * - includePatterns: file/dir names that signal the project root (e.g. ["package.json"])
1073
- * - excludePatterns: if any of these exist in a directory, SKIP that directory and
1074
- * keep walking up toward stopDir — it does not abort resolution. A directory that
1075
- * matches an exclude pattern is simply never returned as a root; the walk still
1076
- * continues past it looking for an include-pattern hit higher up (#1671).
1077
- * - stopDir: walk stops here (defaults to filesystem root; set to project cwd for safety)
1078
- *
1079
- * Equivalent to createRootDetector; exported under both names for clarity.
1080
- */
1081
- export function NearestRoot(includePatterns, excludePatterns, stopDir) {
1082
- // Per-instance caches — each NearestRoot(markers) call gets its own Map so
1083
- // different servers (e.g. TypeScript vs Go) with different marker sets never
1084
- // share entries. vi.resetModules() in tests resets module state between cases.
1085
- const cache = new Map();
1086
- // Only cache successful hits. Undefined results are NOT cached so that a
1087
- // newly-created root marker (e.g. package.json or tsconfig.json scaffolded
1088
- // mid-session by the agent) is detected on the next call — the absent →
1089
- // present transition must work without a process restart. The uncached
1090
- // re-walk cost for configless repos is a known trade-off; bounding the
1091
- // walk with stopDir for in-cwd files is the tracked optimization (#1412).
1092
- //
1093
- // A HIT is revalidated instead of trusted for the session: it carries the
1094
- // mtime of every directory the walk probed, and is served only while all of
1095
- // them are unchanged. Without that, a nearer marker scaffolded below an
1096
- // already-resolved root (`swift package init` in a subdirectory) — and a
1097
- // marker REMOVED at the resolved root — stayed invisible for the whole
1098
- // session, the other half of the very transition the paragraph above
1099
- // handles (#3412; same shape as #2922 at the dispatch marker seam).
1100
- const inFlight = new Map();
1101
- // The patterns whose probe reads a subdirectory of the walked directory
1102
- // instead of the walked directory itself: a pattern carrying a path segment
1103
- // (`prisma/schema.prisma`) is answered from a subdirectory, so creating that
1104
- // marker bumps the SUBDIRECTORY's mtime and the walked directory's own only
1105
- // when the subdirectory had to be created too. Derived once from this
1106
- // detector's pattern table, so a new pattern shape is covered on arrival
1107
- // without a second list to maintain.
1108
- const subdirPatterns = [];
1109
- for (const pattern of [...includePatterns, ...(excludePatterns ?? [])]) {
1110
- if (markerProbeDir("", pattern))
1111
- subdirPatterns.push(pattern);
1112
- }
1113
- return withRootMarkers(async (file) => {
1114
- // Cache key is the resolved directory — all files in the same dir share a root.
1115
- const startDir = path.resolve(path.dirname(file));
1116
- const dirKey = normalizeMapKey(startDir);
1117
- // Fast path: already resolved for this directory, nothing the walk looked at
1118
- // has changed since, and the entry is younger than the shared re-check
1119
- // cadence. One stat per probed directory replaces the whole walk (markers x
1120
- // directories, plus the hit directory's `isExcludedLspRoot` git-boundary
1121
- // walk), so the memo still pays for itself; the cadence is what bounds the
1122
- // axes a directory mtime cannot see — a same-tick create, and a `.gitignore`
1123
- // above the hit changing the exclusion verdict — to one window instead of
1124
- // the session (#3412 review round 1). A fired bound resolves `undefined`,
1125
- // which is NOT freshness: it falls through to the walk.
1126
- const cached = cache.get(dirKey);
1127
- if (cached !== undefined &&
1128
- Date.now() - cached.walkedAt < FRESHNESS_CADENCE_MS &&
1129
- (await bounded(dirMtimesStillFreshAsync(cached.probedDirs), rootMtimeBound("lsp-root-memo-freshness"))) === true)
1130
- return cached.root;
1131
- // In-flight deduplication: if N parallel pipelines edit files in the same
1132
- // directory simultaneously, only one stat-walk runs; the rest await the same
1133
- // promise. This is the main fix for parallel-turn LSP timeout spikes.
1134
- const flying = inFlight.get(dirKey);
1135
- if (flying)
1136
- return flying;
1137
- const probedDirs = [];
1138
- // Stamped before the walk so the window also covers the walk's own duration
1139
- // — the conservative direction, by the walk's runtime (~0.2 ms measured).
1140
- const walkedAt = Date.now();
1141
- const promise = (async () => {
1142
- let currentDir = startDir;
1143
- const fsRoot = path.parse(currentDir).root;
1144
- const stop = stopDir ? path.resolve(stopDir) : fsRoot;
1145
- while (true) {
1146
- if (stop !== fsRoot &&
1147
- currentDir.startsWith(stop + path.sep) === false &&
1148
- currentDir !== stop) {
1149
- break;
1150
- }
1151
- // Record every directory this step is about to read, BEFORE reading
1152
- // it. Ordering is the invariant: a marker created between the stat
1153
- // and the probe leaves the record STALE (the next call re-walks and
1154
- // finds it), whereas recording afterwards would store the mtime that
1155
- // already includes the creation and hide it for the session. Only
1156
- // directories up to the hit are recorded — a marker created above a
1157
- // hit cannot change which root is nearest.
1158
- const stepDirs = [
1159
- currentDir,
1160
- ...subdirPatterns.map((pattern) => markerProbeDir(currentDir, pattern)),
1161
- ];
1162
- const stepRecords = await bounded(dirMtimeRecordsAsync(stepDirs), rootMtimeBound("lsp-root-walk-dir-mtimes"));
1163
- probedDirs.push(...(stepRecords ?? unknownDirMtimeRecords(stepDirs)));
1164
- // Check exclude patterns — skip this dir (but keep walking up)
1165
- if (excludePatterns) {
1166
- let excluded = false;
1167
- for (const pattern of excludePatterns) {
1168
- if (await markerExists(currentDir, pattern)) {
1169
- excluded = true;
1170
- break;
1171
- }
1172
- }
1173
- if (excluded) {
1174
- currentDir = path.dirname(currentDir);
1175
- continue;
1176
- }
1177
- }
1178
- // Check include patterns. Exact marker names stay cheap (`stat`), while
1179
- // glob markers like `*.csproj` match real project filenames (#201).
1180
- for (const pattern of includePatterns) {
1181
- if ((await markerExists(currentDir, pattern)) &&
1182
- (pattern !== ".git" || currentDir !== os.tmpdir()) &&
1183
- !(await isExcludedLspRoot(currentDir))) {
1184
- return enforceLspRootCeiling(currentDir, process.cwd(), file);
1185
- }
1186
- }
1187
- if (currentDir === stop || currentDir === fsRoot) {
1188
- break;
1189
- }
1190
- currentDir = path.dirname(currentDir);
1191
- }
1192
- return undefined;
1193
- })();
1194
- inFlight.set(dirKey, promise);
1195
- try {
1196
- const result = await promise;
1197
- if (result !== undefined)
1198
- cache.set(dirKey, { root: result, probedDirs, walkedAt });
1199
- return result;
1200
- }
1201
- finally {
1202
- inFlight.delete(dirKey);
1203
- }
1204
- }, includePatterns);
1205
- }
1206
- /** Alias kept for backward compatibility */
1207
- export const createRootDetector = NearestRoot;
1208
- // --- Runtime Tool Helpers ---
1209
- /**
1210
- * Check if a command is available on system PATH.
1211
- *
1212
- * Async (was a blocking `spawnSync("where"/"which")`): runs on the spawn
1213
- * fall-through path (Step 4, runtime-install gate). The shared
1214
- * `isCommandAvailableAsync` spawns the same finder via `safeSpawnAsync` with a
1215
- * 5s timeout, so a stalled finder can no longer freeze the loop. Semantics are
1216
- * preserved: true iff the finder exits 0.
1217
- */
1218
- function isOnPath(command) {
1219
- return isCommandAvailableAsync(command);
1220
- }
1221
- /**
1222
- * Try to install gopls via `go install`. Resolves true if the install succeeded.
1223
- *
1224
- * Async (was a blocking `spawnSync`): runs on the LSP runtime-install gate, off
1225
- * the event loop. `ignoreAmbientSignal` keeps the install running to completion
1226
- * even if the agent turn is interrupted, matching the old uncancellable sync
1227
- * behaviour. Success semantics preserved: true iff the process exits 0.
1228
- */
1229
- export async function tryGoInstallGopls() {
1230
- const isWindows = process.platform === "win32";
1231
- const result = await safeSpawnAsync(isWindows ? "go.exe" : "go", ["install", "golang.org/x/tools/gopls@latest"], { timeout: 180000, ignoreAmbientSignal: true });
1232
- return !result.error && result.status === 0;
1233
- }
1234
- export async function tryDotnetToolInstall(tool) {
1235
- mkdirSync(PI_LENS_BIN_DIR, { recursive: true });
1236
- const result = await safeSpawnAsync("dotnet", ["tool", "install", "--tool-path", PI_LENS_BIN_DIR, tool], { timeout: 180000, ignoreAmbientSignal: true });
1237
- if (!result.error && result.status === 0)
1238
- return true;
1239
- const stderr = result.stderr ?? "";
1240
- if (stderr.includes("No NuGet sources are defined or enabled")) {
1241
- logSessionStart(`lsp dotnet-install: NuGet sources missing — cannot install ${tool}. ` +
1242
- `Run: dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org`);
1243
- return false;
1244
- }
1245
- const updateResult = await safeSpawnAsync("dotnet", ["tool", "update", "--tool-path", PI_LENS_BIN_DIR, tool], { timeout: 180000, ignoreAmbientSignal: true });
1246
- return !updateResult.error && updateResult.status === 0;
1247
- }
1248
- /**
1249
- * #1412 M1: walk up from `startDir` (inclusive) looking for a file at
1250
- * `startDir/<relativeSegments>`, `dirname(startDir)/<relativeSegments>`, and so
1251
- * on — the same ancestor walk `findNativeTypeScriptLsp` uses, bounded the same
1252
- * way (`isAtOrAboveHomeDir`). A nested config root (e.g. a `cypress/tsconfig.json`
1253
- * LSP root inside a repo whose `node_modules` only exists at the repo root) must
1254
- * still resolve tooling installed at an ancestor, not just directly under the
1255
- * LSP root — mirrors how node module resolution itself walks up.
1256
- */
1257
- async function findAncestorFile(startDir, ...relativeSegments) {
1258
- return findAncestorFileAmong(startDir, [relativeSegments]);
1259
- }
1260
- /**
1261
- * Same ancestor walk as `findAncestorFile`, but checks every candidate
1262
- * relative-path in `candidateSegmentLists` AT EACH LEVEL before moving up —
1263
- * so the nearest ancestor wins regardless of which candidate name matched
1264
- * there, matching normal node_modules resolution priority (nearest install
1265
- * shadows a further one, never the reverse).
1266
- */
1267
- async function findAncestorFileAmong(startDir, candidateSegmentLists) {
1268
- const fs = await import("node:fs/promises");
1269
- let currentDir = path.resolve(startDir);
1270
- while (!isAtOrAboveHomeDir(currentDir)) {
1271
- for (const segments of candidateSegmentLists) {
1272
- const candidate = path.join(currentDir, ...segments);
1273
- try {
1274
- await fs.access(candidate);
1275
- return candidate;
1276
- }
1277
- catch {
1278
- /* not found at this level */
1279
- }
1280
- }
1281
- const parent = path.dirname(currentDir);
1282
- if (parent === currentDir)
1283
- break;
1284
- currentDir = parent;
1285
- }
1286
- return undefined;
1287
- }
1288
- /**
1289
- * A failed classic-compiler repair must not repeat within a session.
1290
- * `ensureTool` caches successful installs, so a repair that works
1291
- * short-circuits later calls on its own. A repair that fails leaves nothing
1292
- * behind, and `findTsserverPath` has three call sites (TypeScript, Vue,
1293
- * Svelte). Without this guard an offline or partial install re-runs a 120 s
1294
- * forced reinstall on every spawn.
1295
- *
1296
- * The guard is process-singleton state, so without an explicit re-arm it
1297
- * would latch for the whole extension-host process — a repair that failed
1298
- * once (transient registry hiccup) would stay unrepairable for every later
1299
- * session in that process. `resetClassicTsRepairGuard` re-arms it; callers
1300
- * wire that into `session_start` alongside the other per-session resets
1301
- * (#1570).
1302
- */
1303
- const CLASSIC_TS_REPAIR_FAMILY = "lsp.classic-ts-repair-guard";
1304
- const CLASSIC_TS_REPAIR_VERSION = 1;
1305
- function classicTsRepairState() {
1306
- return getProcessSingleton(CLASSIC_TS_REPAIR_FAMILY, CLASSIC_TS_REPAIR_VERSION, () => ({ attempted: false }));
1307
- }
1308
- /** Re-arm the classic-repair guard so a new session gets its own attempt. */
1309
- export function resetClassicTsRepairGuard() {
1310
- classicTsRepairState().attempted = false;
1311
- }
1312
- /** Test hook — clears the process-singleton classic-repair guard. */
1313
- export function _resetClassicTsRepairForTests() {
1314
- resetClassicTsRepairGuard();
1315
- }
1316
- /**
1317
- * Directories that may hold the TypeScript package next to a resolved `tsc`
1318
- * binary: `<bin>/../typescript` (npm-global layout) and
1319
- * `<bin>/../../typescript` (managed `node_modules/.bin` layout).
1320
- */
1321
- function typescriptDirsForTsc(tscPath) {
1322
- const binDir = path.dirname(tscPath);
1323
- return [
1324
- path.join(binDir, "..", "typescript"),
1325
- path.join(binDir, "..", "..", "typescript"),
1326
- ];
1327
- }
1328
- /**
1329
- * Read the major version of the TypeScript package that backs a resolved
1330
- * `tsc` binary. Returns undefined when the version is unknowable: `ensureTool`
1331
- * returns the bare string `"tsc"` for a PATH hit, and `path.dirname("tsc")` is
1332
- * `"."`, so the candidates would go cwd-relative. Callers must not repair on
1333
- * an unknown version — a healthy global TypeScript 5.x would be reinstalled
1334
- * for nothing.
1335
- */
1336
- async function typescriptVersionForTsc(tscPath) {
1337
- if (!path.isAbsolute(tscPath))
1338
- return undefined;
1339
- for (const dir of typescriptDirsForTsc(tscPath)) {
1340
- let manifest;
1341
- try {
1342
- manifest = await readFile(path.join(dir, "package.json"), "utf8");
1343
- }
1344
- catch {
1345
- continue;
1346
- }
1347
- let version;
1348
- try {
1349
- const parsed = JSON.parse(manifest);
1350
- if (typeof parsed !== "object" ||
1351
- parsed === null ||
1352
- !("version" in parsed) ||
1353
- typeof parsed.version !== "string") {
1354
- return undefined;
1355
- }
1356
- version = parsed.version;
1357
- }
1358
- catch {
1359
- return undefined;
1360
- }
1361
- const majorText = version.split(".", 1)[0] ?? "";
1362
- const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
1363
- if (!Number.isFinite(major))
1364
- return undefined;
1365
- return { version, major };
1366
- }
1367
- return undefined;
1368
- }
1369
- /**
1370
- * Locate tsserver.js — tries local project (walking up from root, #1412 M1),
1371
- * then process.cwd() as a last-resort fallback, then pi-lens managed
1372
- * TypeScript. Returns the path to tsserver.js, or undefined if not found.
1373
- */
1374
- async function findTsserverPath(root, allowInstall) {
1375
- const fs = await import("node:fs/promises");
1376
- const ancestorHit = await findAncestorFile(root, "node_modules", "typescript", "lib", "tsserver.js");
1377
- if (ancestorHit)
1378
- return ancestorHit;
1379
- const cwdCandidate = path.join(process.cwd(), "node_modules", "typescript", "lib", "tsserver.js");
1380
- try {
1381
- await fs.access(cwdCandidate);
1382
- return cwdCandidate;
1383
- }
1384
- catch {
1385
- /* not found */
1386
- }
1387
- const tsserverForTsc = async (tscPath) => {
1388
- if (!tscPath)
1389
- return undefined;
1390
- for (const dir of typescriptDirsForTsc(tscPath)) {
1391
- const candidate = path.join(dir, "lib", "tsserver.js");
1392
- try {
1393
- await fs.access(candidate);
1394
- return candidate;
1395
- }
1396
- catch {
1397
- /* not found */
1398
- }
1399
- }
1400
- return undefined;
1401
- };
1402
- // Discover the TypeScript install (PATH / npm-global) even when installation
1403
- // is disabled; only the download is gated by allowInstall.
1404
- const installAllowed = canInstall(allowInstall);
1405
- const discoveredTsc = await ensureTool("typescript", {
1406
- allowInstall: installAllowed,
1407
- });
1408
- const discoveredTsserver = await tsserverForTsc(discoveredTsc);
1409
- if (discoveredTsserver ||
1410
- !discoveredTsc ||
1411
- !installAllowed ||
1412
- classicTsRepairState().attempted) {
1413
- return discoveredTsserver;
1414
- }
1415
- // Repair only a compiler we can prove is TypeScript 7+. TypeScript 7 dropped
1416
- // lib/tsserver.js, so the classic wrapper cannot start against it. Any other
1417
- // version — or a version we cannot read, such as a bare PATH `tsc` — is left
1418
- // alone rather than force-reinstalled.
1419
- const discoveredVersion = await typescriptVersionForTsc(discoveredTsc);
1420
- if (!discoveredVersion || discoveredVersion.major < 7)
1421
- return undefined;
1422
- // An older managed tree took `latest` before the registry pinned the classic
1423
- // compiler. Reinstall the pinned version once so that tree self-heals,
1424
- // without deleting user or project-local TypeScript installations.
1425
- classicTsRepairState().attempted = true;
1426
- logSessionStart(`lsp typescript: managed compiler resolved to TypeScript ${discoveredVersion.version}, which ships no tsserver.js; reinstalling pinned classic fallback`);
1427
- const repairedTsc = await ensureTool("typescript", {
1428
- allowInstall: true,
1429
- forceReinstall: true,
1430
- });
1431
- return tsserverForTsc(repairedTsc);
1432
- }
1433
- /**
1434
- * TypeScript 7+ ships the native typescript-go language server through the
1435
- * workspace-local `tsc --lsp --stdio` entrypoint and no longer includes
1436
- * `lib/tsserver.js`. Resolve the nearest TypeScript package using normal
1437
- * node_modules ancestor semantics so a monorepo package can use its hoisted
1438
- * compiler, while never falling through to a PATH/global `tsc`.
1439
- */
1440
- async function findNativeTypeScriptLsp(root) {
1441
- let currentDir = path.resolve(root);
1442
- while (!isAtOrAboveHomeDir(currentDir)) {
1443
- const typescriptDir = path.join(currentDir, "node_modules", "typescript");
1444
- const packageJsonPath = path.join(typescriptDir, "package.json");
1445
- let packageJsonText;
1446
- try {
1447
- packageJsonText = await readFile(packageJsonPath, "utf8");
1448
- }
1449
- catch (error) {
1450
- if (error.code !== "ENOENT") {
1451
- return undefined;
1452
- }
1453
- // A `node_modules/typescript/` directory that exists but has no
1454
- // `package.json` is a malformed/partial install at THIS level, not an
1455
- // absent one — stop here (fall back to classic) rather than walking up
1456
- // to an ancestor, or a broken nearest install would let an unrelated
1457
- // ancestor TS 7 binary silently shadow it (Copilot review, PR #526).
1458
- try {
1459
- const dirStat = await stat(typescriptDir);
1460
- if (dirStat.isDirectory())
1461
- return undefined;
1462
- }
1463
- catch {
1464
- /* typescript dir itself doesn't exist here — keep walking up */
1465
- }
1466
- const parent = path.dirname(currentDir);
1467
- if (parent === currentDir)
1468
- return undefined;
1469
- currentDir = parent;
1470
- continue;
1471
- }
1472
- let version;
1473
- try {
1474
- const parsed = JSON.parse(packageJsonText);
1475
- if (typeof parsed !== "object" ||
1476
- parsed === null ||
1477
- !("version" in parsed) ||
1478
- typeof parsed.version !== "string") {
1479
- return undefined;
1480
- }
1481
- version = parsed.version;
1482
- }
1483
- catch {
1484
- return undefined;
1485
- }
1486
- // The nearest installed package shadows any ancestor TypeScript package,
1487
- // matching Node/package-manager resolution. Never skip a local TS <=6 or
1488
- // malformed install just to select an unrelated ancestor TS 7 binary.
1489
- const majorText = version.split(".", 1)[0] ?? "";
1490
- const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
1491
- if (!Number.isFinite(major) || major < 7)
1492
- return undefined;
1493
- const localTsc = path.join(currentDir, "node_modules", ".bin", "tsc");
1494
- const candidates = process.platform === "win32"
1495
- ? [`${localTsc}.cmd`, `${localTsc}.exe`, localTsc]
1496
- : [localTsc];
1497
- for (const command of candidates) {
1498
- try {
1499
- await access(command);
1500
- return { command, version };
1501
- }
1502
- catch {
1503
- /* not found */
1504
- }
1505
- }
1506
- return undefined;
1507
- }
1508
- return undefined;
1509
- }
1510
- function dotnetToolCandidates(tool) {
1511
- const home = os.homedir();
1512
- return [
1513
- path.join(PI_LENS_BIN_DIR, `${tool}.exe`),
1514
- path.join(PI_LENS_BIN_DIR, tool),
1515
- path.join(home, ".dotnet", "tools", `${tool}.exe`),
1516
- path.join(home, ".dotnet", "tools", tool),
1517
- tool,
1518
- ].filter(Boolean);
1519
- }
1520
- /**
1521
- * Both filename forms for a tool in a directory (`.exe` first on Windows). A
1522
- * managed binary may carry the extension or not depending on how the toolchain
1523
- * dropped it, so we try both.
1524
- */
1525
- function binExeVariants(dir, tool) {
1526
- return process.platform === "win32"
1527
- ? [path.join(dir, `${tool}.exe`), path.join(dir, tool)]
1528
- : [path.join(dir, tool)];
1529
- }
1530
- /**
1531
- * Canonical-bin discovery (#241): a runtime-managed server can be installed yet
1532
- * absent from the shell PATH — the toolchain drops it in a well-known dir the
1533
- * user's PATH often omits (fresh installs, Windows, non-login shells). Returning
1534
- * the bare command FIRST keeps PATH authoritative when it resolves; the explicit
1535
- * dir paths are the fallback (and the post-`go install` retry target).
1536
- *
1537
- * Go: `$GOPATH/bin` (first GOPATH entry) or `~/go/bin` — where `go install` lands.
1538
- */
1539
- export function goBinCandidates(tool) {
1540
- const gopath = process.env.GOPATH?.split(path.delimiter)[0] ||
1541
- path.join(os.homedir(), "go");
1542
- return [tool, ...binExeVariants(path.join(gopath, "bin"), tool)];
1543
- }
1544
- /** Rust: `$CARGO_HOME/bin` or `~/.cargo/bin` — cargo/rustup binaries + proxies. */
1545
- export function cargoBinCandidates(tool) {
1546
- const cargoHome = process.env.CARGO_HOME || path.join(os.homedir(), ".cargo");
1547
- return [tool, ...binExeVariants(path.join(cargoHome, "bin"), tool)];
1548
- }
1549
- /**
1550
- * Try to install a gem to the pi-lens bin dir. Resolves true if the install succeeded.
1551
- */
1552
- export async function tryGemInstall(gem) {
1553
- const { join } = await import("node:path");
1554
- const binDir = join(getGlobalPiLensDir(), "bin");
1555
- const { mkdir } = await import("node:fs/promises");
1556
- await mkdir(binDir, { recursive: true });
1557
- const result = await safeSpawnAsync("gem", ["install", gem, "--bindir", binDir, "--no-document"], { timeout: 180000, ignoreAmbientSignal: true });
1558
- const ok = !result.error && result.status === 0;
1559
- // Add binDir to PATH so subsequent lookups find the installed gem
1560
- if (ok) {
1561
- const sep = process.platform === "win32" ? ";" : ":";
1562
- if (!process.env.PATH?.includes(binDir)) {
1563
- process.env.PATH = `${binDir}${sep}${process.env.PATH ?? ""}`;
1564
- }
1565
- }
1566
- return ok;
1567
- }
1568
- /**
1569
- * Wraps a root function so it returns undefined for files inside a Deno project.
1570
- * Prevents TypeScript LSP from being spawned alongside Deno LSP for the same file,
1571
- * which would produce false diagnostics for Deno-specific APIs.
1572
- */
1573
- export function DenoExcludeRoot(primary) {
1574
- const denoDetector = createRootDetector(["deno.json", "deno.jsonc"]);
1575
- return async (file) => {
1576
- const denoRoot = await denoDetector(file);
1577
- if (denoRoot)
1578
- return undefined;
1579
- return primary(file);
1580
- };
1581
- }
1582
- // --- Server Definitions ---
1583
- const JS_TS_LSP_EXTENSIONS = KIND_EXTENSIONS["jsts"].filter((ext) => ext !== ".svelte" && ext !== ".vue");
1584
- // TypeScript identity and tooling discovery deliberately use separate marker
1585
- // families. A governing config wins even when a package directory supplies
1586
- // hoisted binaries. Keep configs out of PROJECT_BOUNDARY_MARKERS: #1373 still
1587
- // coalesces a config-only nested root when an ancestor client was hosted first
1588
- // (nested-config-first remains intentionally open-order-sensitive). #1412
1589
- // accepted risk (M2, not fixed here): honoring nested config roots at all
1590
- // enlarges the population of directories that can independently coalesce or
1591
- // diverge under #1373's open-order sensitivity — the same pre-existing
1592
- // blast-radius, just triggered by more roots than before.
1593
- const TS_CONFIG_MARKERS = ["tsconfig.json", "jsconfig.json"];
1594
- const TS_TOOLING_MARKERS = [
1595
- "package-lock.json",
1596
- "bun.lockb",
1597
- "bun.lock",
1598
- "pnpm-lock.yaml",
1599
- "yarn.lock",
1600
- "package.json",
1601
- ];
1602
- // #1412 M3: tsserver associates jsconfig.json with JS files only (its identity
1603
- // probe reports a jsconfig-governed .ts file as unassociated) — so a TS-family
1604
- // file under a jsconfig-only directory must NOT root there; keep walking up
1605
- // for a real tsconfig.json. A JS-family file accepts either: tsconfig also
1606
- // governs plain JS via `allowJs`, so accepting tsconfig for a .js file is
1607
- // correct, and jsconfig obviously is too.
1608
- const TS_FAMILY_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"]);
1609
- function isTsFamilyFile(file) {
1610
- return TS_FAMILY_EXTENSIONS.has(path.extname(file).toLowerCase());
1611
- }
1612
- function tsConfigMarkersForFile(file) {
1613
- return isTsFamilyFile(file)
1614
- ? ["tsconfig.json"]
1615
- : TS_CONFIG_MARKERS;
1616
- }
1617
- // Two detector instances (not one parameterized by file) so each keeps its own
1618
- // per-directory NearestRoot cache valid for its fixed marker set — a shared
1619
- // cache keyed only by directory would conflate the TS-only and either-config
1620
- // answers for the same directory.
1621
- const TypeScriptConfigRootTsOnly = IgnoreHomeRoot(createRootDetector(["tsconfig.json"]));
1622
- const TypeScriptConfigRootEither = IgnoreHomeRoot(createRootDetector([...TS_CONFIG_MARKERS]));
1623
- const TypeScriptToolingRoot = IgnoreHomeRoot(createRootDetector([...TS_TOOLING_MARKERS]));
1624
- async function findTypeScriptProjectRoot(file) {
1625
- const configDetector = isTsFamilyFile(file)
1626
- ? TypeScriptConfigRootTsOnly
1627
- : TypeScriptConfigRootEither;
1628
- const [configRoot, toolingRoot] = await Promise.all([
1629
- configDetector(file),
1630
- TypeScriptToolingRoot(file),
1631
- ]);
1632
- if (!configRoot)
1633
- return toolingRoot;
1634
- if (!toolingRoot)
1635
- return configRoot;
1636
- // A config inside (or beside) the nearest package governs its files. A
1637
- // config above a nearer package must not erase that topology boundary.
1638
- return isSameOrWithin(toolingRoot, configRoot) ? configRoot : toolingRoot;
1639
- }
1640
- /**
1641
- * Walk up from the file's directory looking for a TypeScript project marker
1642
- * (a governing config first, per-directory, per #1412 M3's extension-family
1643
- * filter; else a tooling/lockfile marker), but stop at `extensionRootKey` so
1644
- * we never escape the .pi/agent/extensions boundary into a higher-up project
1645
- * (e.g. ~/.pi/agent/package.json which would pull every extension in the
1646
- * directory into one LSP workspace).
1647
- *
1648
- * #1412 L4: each directory returns immediately on its first match (config or
1649
- * tooling) — there is no cross-level "nearest tooling root" to carry forward,
1650
- * since a match always wins on the spot. Returns the nearest directory
1651
- * containing a marker, or undefined if none is found between the file and the
1652
- * extensions root inclusive.
1653
- */
1654
- async function findExtensionBoundedRoot(file, extensionRootKey) {
1655
- const startDir = path.resolve(path.dirname(file));
1656
- let currentDir = startDir;
1657
- const configMarkers = tsConfigMarkersForFile(file);
1658
- while (true) {
1659
- for (const pattern of configMarkers) {
1660
- try {
1661
- await stat(path.join(currentDir, pattern));
1662
- return currentDir;
1663
- }
1664
- catch {
1665
- /* not found, try next marker */
1666
- }
1667
- }
1668
- for (const pattern of TS_TOOLING_MARKERS) {
1669
- try {
1670
- await stat(path.join(currentDir, pattern));
1671
- return currentDir;
1672
- }
1673
- catch {
1674
- /* not found, try next marker */
1675
- }
1676
- }
1677
- // Stop at or beyond the extensions root — never walk into the
1678
- // pi-agent-wide scope.
1679
- const currentKey = normalizeEphemeralMapKey(path.resolve(currentDir));
1680
- if (currentKey === extensionRootKey)
1681
- return undefined;
1682
- const parent = path.dirname(currentDir);
1683
- if (parent === currentDir)
1684
- return undefined;
1685
- currentDir = parent;
1686
- }
1687
- }
1688
- /**
1689
- * Check whether the directory immediately containing the extensions folder
1690
- * (i.e. `.pi/agent/`) holds any TypeScript project marker. This narrowly
1691
- * detects the #123 scenario — pi itself installs a package.json at
1692
- * `~/.pi/agent/` and the user's extension has none of its own — without
1693
- * picking up accidental markers further up the filesystem.
1694
- */
1695
- async function hasAgentLevelProjectMarker(extensionRootKey) {
1696
- const agentDir = path.dirname(extensionRootKey);
1697
- if (!agentDir || agentDir === extensionRootKey)
1698
- return false;
1699
- for (const pattern of [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]) {
1700
- try {
1701
- await stat(path.join(agentDir, pattern));
1702
- return true;
1703
- }
1704
- catch {
1705
- /* not found, try next */
1706
- }
1707
- }
1708
- return false;
1709
- }
1710
- const TypeScriptRoot = withRootMarkers(DenoExcludeRoot(async (file) => {
1711
- const extensionRootKey = piAgentExtensionsRootKey(file);
1712
- if (extensionRootKey) {
1713
- // Bounded walk so we never adopt a parent (e.g. ~/.pi/agent/) as the
1714
- // LSP root.
1715
- const bounded = await findExtensionBoundedRoot(file, extensionRootKey);
1716
- if (bounded)
1717
- return bounded;
1718
- // No marker inside the extension boundary. If pi itself has a
1719
- // package.json at ~/.pi/agent/ (the #123 setup), the previous code
1720
- // returned undefined and the LSP silently failed to start. Fall
1721
- // back to a per-file scope so the LSP at least runs.
1722
- if (await hasAgentLevelProjectMarker(extensionRootKey)) {
1723
- return FileDirRoot(file);
1724
- }
1725
- // Truly loose extension file with no project context anywhere
1726
- // relevant — preserve the existing skip behavior (LSP shouldn't
1727
- // analyze a lone .ts file with no package.json above or below).
1728
- return undefined;
1729
- }
1730
- const projectRoot = await findTypeScriptProjectRoot(file);
1731
- if (projectRoot)
1732
- return projectRoot;
1733
- return FileDirRoot(file);
1734
- }), [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]);
1735
- export const TypeScriptServer = {
1736
- id: "typescript",
1737
- name: "TypeScript Language Server",
1738
- extensions: JS_TS_LSP_EXTENSIONS,
1739
- autoPropagateDiagnostics: true,
1740
- root: TypeScriptRoot,
1741
- rootMarkers: TypeScriptRoot.rootMarkers,
1742
- async spawn(root, options) {
1743
- const fs = await import("node:fs/promises");
1744
- const nativeLsp = await findNativeTypeScriptLsp(root);
1745
- if (nativeLsp) {
1746
- const env = await getToolEnvironment();
1747
- logSessionStart(`lsp typescript-native: version=${nativeLsp.version} command=${nativeLsp.command}`);
1748
- const proc = await launchLSP(nativeLsp.command, ["--lsp", "--stdio"], {
1749
- cwd: root,
1750
- env,
1751
- });
1752
- return { process: proc, source: "direct", launchVariant: "native-ts7" };
1753
- }
1754
- let source = "direct";
1755
- // TypeScript <=6 uses typescript-language-server + tsserver.js. Prefer a
1756
- // project-local wrapper, then fall back to discovered/managed tooling.
1757
- // #1412 M1: walk up from root (Windows .cmd first, then Unix at each
1758
- // level) — a nested config root's node_modules/.bin lives at an ancestor,
1759
- // not necessarily directly under the LSP root.
1760
- let lspPath = await findAncestorFileAmong(root, [
1761
- ["node_modules", ".bin", "typescript-language-server.cmd"],
1762
- ["node_modules", ".bin", "typescript-language-server"],
1763
- ]);
1764
- // Fall back to a discovered or managed install. ensureTool() runs PATH /
1765
- // npm-global discovery even when install is disabled (only the download is
1766
- // gated by canInstall), so a globally-installed typescript-language-server
1767
- // resolves even without a per-project node_modules/.bin entry.
1768
- if (!lspPath) {
1769
- lspPath = await ensureTool("typescript-language-server", {
1770
- allowInstall: canInstall(options?.allowInstall),
1771
- });
1772
- if (lspPath)
1773
- source = "managed";
1774
- if (!lspPath) {
1775
- return undefined;
1776
- }
1777
- }
1778
- // Find tsserver.js — also try relative to the LSP binary for local installs
1779
- let tsserverPath = await findTsserverPath(root, options?.allowInstall);
1780
- if (!tsserverPath) {
1781
- const localCandidate = path.join(path.dirname(lspPath), "..", "typescript", "lib", "tsserver.js");
1782
- try {
1783
- await fs.access(localCandidate);
1784
- tsserverPath = localCandidate;
1785
- }
1786
- catch {
1787
- /* not found */
1788
- }
1789
- }
1790
- if (tsserverPath)
1791
- source = "managed";
1792
- // Use absolute path and proper environment
1793
- const env = await getToolEnvironment();
1794
- const proc = await launchLSP(lspPath, ["--stdio"], {
1795
- cwd: root,
1796
- env: {
1797
- ...env,
1798
- TSSERVER_PATH: tsserverPath,
1799
- },
1800
- });
1801
- return {
1802
- process: proc,
1803
- source,
1804
- initialization: tsserverPath
1805
- ? { tsserver: { path: tsserverPath } }
1806
- : undefined,
1807
- launchVariant: "classic",
1808
- };
1809
- },
1810
- };
1811
- export const DenoServer = {
1812
- id: "deno",
1813
- name: "Deno Language Server",
1814
- fallbackFor: "typescript",
1815
- extensions: JS_TS_LSP_EXTENSIONS,
1816
- autoPropagateDiagnostics: true,
1817
- root: createRootDetector(["deno.json", "deno.jsonc"]),
1818
- async spawn(root, options) {
1819
- return resolveAndLaunch({
1820
- candidates: ["deno"],
1821
- args: ["lsp"],
1822
- cwd: root,
1823
- managedToolId: "deno",
1824
- }, options?.allowInstall);
1825
- },
1826
- };
1827
- export const PythonServer = {
1828
- id: "python",
1829
- name: "Pyright Language Server",
1830
- extensions: KIND_EXTENSIONS["python"],
1831
- root: RootWithFallback(createRootDetector([
1832
- ".git",
1833
- "pyproject.toml",
1834
- "setup.py",
1835
- "setup.cfg",
1836
- "requirements.txt",
1837
- "Pipfile",
1838
- "poetry.lock",
1839
- ])),
1840
- async spawn(root, options) {
1841
- const pythonEnvironment = await detectPythonEnvironment(root);
1842
- const env = augmentPythonEnvironment(await getToolEnvironment(), pythonEnvironment);
1843
- let source = "direct";
1844
- // openFilesOnly: true — analyse only open files rather than the full workspace.
1845
- // Avoids the 5–14 s cold-start on large projects caused by workspace-wide
1846
- // analysis on startup. Deep type checking is still available via the standalone
1847
- // pyright CLI runner that runs in parallel.
1848
- const pyrightInit = (pythonPath) => ({
1849
- ...(pythonPath ? { pythonPath } : {}),
1850
- openFilesOnly: true,
1851
- });
1852
- // Project ownership outranks checker preference: exhaust explicit project
1853
- // candidates before a bare command can resolve to pi-lens's managed bin or
1854
- // the host PATH. Within the project tier, preserve the established
1855
- // pyright → basedpyright → ty preference.
1856
- const projectPyright = await resolveAndLaunch({
1857
- candidates: [
1858
- ...pythonEnvironmentToolCandidates(pythonEnvironment, "pyright-langserver"),
1859
- ...nodeBinLocalCandidates(root, "pyright-langserver"),
1860
- ...pythonEnvironmentToolCandidates(pythonEnvironment, "basedpyright-langserver"),
1861
- ...nodeBinLocalCandidates(root, "basedpyright-langserver"),
1862
- ],
1863
- args: ["--stdio"],
1864
- cwd: root,
1865
- env,
1866
- }, false);
1867
- if (projectPyright) {
1868
- return {
1869
- process: projectPyright.process,
1870
- source: projectPyright.source,
1871
- initialization: pyrightInit(pythonEnvironment?.pythonPath),
1872
- };
1873
- }
1874
- // ty uses `ty server`, so it needs a separate launch phase from the
1875
- // Pyright-compatible servers. It has no stable initializationOptions
1876
- // equivalent to pyright's `pythonPath` (astral-sh/ty#2032); the child
1877
- // environment and cwd provide interpreter discovery instead.
1878
- const projectTy = await resolveAndLaunch({
1879
- candidates: pythonEnvironmentToolCandidates(pythonEnvironment, "ty"),
1880
- args: ["server"],
1881
- cwd: root,
1882
- env,
1883
- }, false);
1884
- if (projectTy) {
1885
- return { process: projectTy.process, source: projectTy.source };
1886
- }
1887
- // With no project-owned checker, retain the existing PATH preference and
1888
- // keep ty opt-in: pyright, then basedpyright, then ty. The augmented child
1889
- // PATH includes pi-lens-managed bins before the inherited global PATH.
1890
- const pathPyright = await resolveAndLaunch({
1891
- candidates: ["pyright-langserver", "basedpyright-langserver"],
1892
- args: ["--stdio"],
1893
- cwd: root,
1894
- env,
1895
- }, false);
1896
- if (pathPyright) {
1897
- return {
1898
- process: pathPyright.process,
1899
- source: pathPyright.source,
1900
- initialization: pyrightInit(pythonEnvironment?.pythonPath),
1901
- };
1902
- }
1903
- const pathTy = await resolveAndLaunch({
1904
- candidates: ["ty"],
1905
- args: ["server"],
1906
- cwd: root,
1907
- env,
1908
- }, false);
1909
- if (pathTy) {
1910
- return { process: pathTy.process, source: pathTy.source };
1911
- }
1912
- // Discover a globally-installed pyright even when install is disabled;
1913
- // only the download is gated by canInstall.
1914
- const pyrightPath = await ensureTool("pyright", {
1915
- allowInstall: canInstall(options?.allowInstall),
1916
- });
1917
- if (!pyrightPath)
1918
- return undefined;
1919
- source = "managed";
1920
- const binDir = path.dirname(pyrightPath);
1921
- const isWindows = process.platform === "win32";
1922
- const managedCandidates = isWindows
1923
- ? [
1924
- path.join(binDir, "pyright-langserver.cmd"),
1925
- path.join(binDir, "pyright-langserver"),
1926
- "pyright-langserver",
1927
- ]
1928
- : [path.join(binDir, "pyright-langserver"), "pyright-langserver"];
1929
- const resolved = await resolveAndLaunch({ candidates: managedCandidates, args: ["--stdio"], cwd: root, env }, false);
1930
- if (!resolved)
1931
- return undefined;
1932
- return {
1933
- process: resolved.process,
1934
- source,
1935
- initialization: pyrightInit(pythonEnvironment?.pythonPath),
1936
- };
1937
- },
1938
- };
1939
- export const PythonJediServer = {
1940
- id: "python-jedi",
1941
- name: "Jedi Language Server",
1942
- fallbackFor: "python",
1943
- extensions: KIND_EXTENSIONS["python"],
1944
- root: RootWithFallback(createRootDetector([
1945
- ".git",
1946
- "pyproject.toml",
1947
- "setup.py",
1948
- "setup.cfg",
1949
- "requirements.txt",
1950
- "Pipfile",
1951
- "poetry.lock",
1952
- ])),
1953
- async spawn(root, options) {
1954
- const launched = await resolveAndLaunch({
1955
- candidates: ["jedi-language-server"],
1956
- args: [],
1957
- cwd: root,
1958
- managedToolId: "jedi-language-server",
1959
- }, options?.allowInstall);
1960
- if (!launched)
1961
- return undefined;
1962
- const pythonPath = await detectPythonVenv(root);
1963
- return {
1964
- ...launched,
1965
- initialization: pythonPath
1966
- ? { workspace: { environmentPath: pythonPath } }
1967
- : {},
1968
- };
1969
- },
1970
- };
1971
- export const GoServer = {
1972
- id: "go",
1973
- name: "gopls",
1974
- extensions: KIND_EXTENSIONS["go"],
1975
- root: RootWithFallback(WorkspacePriorityRoot([["go.work"], ["go.mod", "go.sum"], [".git"]])),
1976
- async spawn(root, options) {
1977
- const result = await resolveAndLaunch({
1978
- // Canonical-bin discovery (#241): include $GOPATH/bin so a gopls that
1979
- // `go install` dropped there resolves even when it isn't on PATH —
1980
- // which is also the retry target after the runtimeInstall below.
1981
- candidates: goBinCandidates("gopls"),
1982
- args: [],
1983
- cwd: root,
1984
- runtimeInstall: {
1985
- runtimeCommand: "go",
1986
- install: tryGoInstallGopls,
1987
- },
1988
- }, options?.allowInstall);
1989
- if (!result)
1990
- return undefined;
1991
- return { ...result, initialization: { ui: { semanticTokens: true } } };
1992
- },
1993
- };
1994
- /**
1995
- * Given an ancestor Cargo.toml's raw contents that already contains a
1996
- * `[workspace]` table, decide whether it actually claims `childDir` as a
1997
- * member. Cargo's own default is that a bare `[workspace]` with no `members`
1998
- * key claims only the manifest's own package — a nested crate is NOT
1999
- * implicitly swept in just because it sits underneath the workspace root on
2000
- * disk. Without this check any Cargo.toml with a `[workspace]` table would
2001
- * hoist every crate below it, including ones the workspace never declared
2002
- * (#1671) — the same defect shape as an undeclared Maven sibling module.
2003
- *
2004
- * KNOWN LIMITATION (#1671 F7, documented rather than implemented): cargo also
2005
- * treats a crate as an implicit workspace member when the workspace root's
2006
- * OWN `[package]` has a `path = "..."` dependency on it and no explicit
2007
- * `members` key is present at all. That dependency-graph inference is not
2008
- * evaluated here — a workspace relying on it alone (no `members` key) reads
2009
- * as claiming nothing, so its crates stay independently rooted rather than
2010
- * being (correctly, per cargo) swept in. An explicit `members` list — the
2011
- * common case and the one #1671's fixtures exercise — is unaffected.
2012
- */
2013
- function cargoWorkspaceDeclaresMember(workspaceContent, parentDir, childDir) {
2014
- const relativePath = path
2015
- .relative(parentDir, childDir)
2016
- .split(path.sep)
2017
- .join("/");
2018
- if (relativePath === "" || relativePath.startsWith(".."))
2019
- return false;
2020
- // Reuse the shared, `[workspace]`-scoped readers (review round 2, F4)
2021
- // instead of hand-composing `extractTomlTableSection` +
2022
- // `parseTomlStringArray` here — that hand-composition duplicated exactly
2023
- // what `readCargoWorkspaceMembers`/`readCargoWorkspaceExclude` do, the
2024
- // single-source-of-truth violation #2473 was filed to close for the OTHER
2025
- // two Cargo.toml readers. Pattern matching goes through the ONE
2026
- // workspace-member matcher for the same reason (#2591); cargo's dialect —
2027
- // `**` never matches (#1671 F6), wildcards confined to one component, so
2028
- // pattern and path must have the same component count — is
2029
- // `CARGO_WORKSPACE_MEMBER_DIALECT`, not a private compiler here.
2030
- const matches = (pattern) => matchesWorkspaceMemberPattern(pattern, relativePath, CARGO_WORKSPACE_MEMBER_DIALECT);
2031
- const excluded = readCargoWorkspaceExclude(workspaceContent);
2032
- if (excluded.some(matches))
2033
- return false;
2034
- return readCargoWorkspaceMembers(workspaceContent).some(matches);
2035
- }
2036
- /**
2037
- * Bound a monorepo-hoist walk-up by the session ceiling: when `file` is inside
2038
- * the session cwd, the walk must stop AT the cwd rather than climb past it —
2039
- * matching enforceLspRootCeiling's clamp semantics but avoiding the wasted stat
2040
- * calls of walking past a boundary we would clamp back down anyway. When
2041
- * `file` is outside the session (isolated tests, out-of-session API callers),
2042
- * enforceLspRootCeiling is a no-op, so the walk is left unbounded (`undefined`).
2043
- */
2044
- function sessionHoistCeiling(sessionCwd, file) {
2045
- return isSameOrWithin(path.resolve(sessionCwd), path.resolve(file))
2046
- ? path.resolve(sessionCwd)
2047
- : undefined;
2048
- }
2049
- function RustWorkspaceRoot() {
2050
- const crateRoot = createRootDetector(["Cargo.toml", "Cargo.lock"]);
2051
- return withRootMarkers(async (file) => {
2052
- const root = await crateRoot(file);
2053
- if (!root)
2054
- return undefined;
2055
- const sessionCwd = process.cwd();
2056
- // The walk-up for an ancestor Cargo.toml with a [workspace] table stays
2057
- // bounded by the same session ceiling the crate-root lookup above already
2058
- // enforces (enforceLspRootCeiling) — a monorepo hoist must never cross the
2059
- // session boundary just because a workspace manifest happens to sit above
2060
- // it (#1671).
2061
- const stop = sessionHoistCeiling(sessionCwd, file);
2062
- let current = root;
2063
- const fsRoot = path.parse(current).root;
2064
- while (true) {
2065
- if (stop !== undefined && current === stop)
2066
- break;
2067
- const parent = path.dirname(current);
2068
- if (parent === current || parent === fsRoot)
2069
- break;
2070
- const parentCargoPath = path.join(parent, "Cargo.toml");
2071
- const parentCargoContent = await readTextFileOrUndefined(parentCargoPath);
2072
- if (parentCargoContent !== undefined &&
2073
- hasCargoWorkspaceTable(parentCargoContent)) {
2074
- // Test membership against the ORIGINAL crate dir (`root`), never the
2075
- // walk cursor (`current`, which may already have climbed through
2076
- // gap directories that have no Cargo.toml of their own) — a
2077
- // `members = ["crates/foo"]` or `["crates/*"]` entry is relative to
2078
- // the workspace root, spanning the whole gap in one hop (#1671 F1).
2079
- if (cargoWorkspaceDeclaresMember(parentCargoContent, parent, root)) {
2080
- return enforceLspRootCeiling(parent, sessionCwd, file);
2081
- }
2082
- // A workspace root exists here but its `members`/`exclude` tables do
2083
- // not claim this crate — stop climbing; the crate stays independently
2084
- // rooted rather than being swept into a workspace it opted out of.
2085
- break;
2086
- }
2087
- current = parent;
2088
- }
2089
- return root;
2090
- }, crateRoot.rootMarkers ?? []);
2091
- }
2092
- /**
2093
- * Resolve whether `parentPomPath`'s <modules> block declares `childDir` as one
2094
- * of its member modules. Maven multi-module hoisting must only chain through
2095
- * poms that actually declare the child (an undeclared sibling directory that
2096
- * merely happens to sit next to a parent pom stays independently rooted) —
2097
- * this is the "Maven module-chain verification" #1671 asks for, as opposed to
2098
- * rust-analyzer's simpler "any ancestor [workspace] wins" hoist.
2099
- */
2100
- /**
2101
- * Strip XML comments to a fixed point rather than in one pass: a single
2102
- * `.replace()` can leave a residual `<!--` behind on adversarially-nested
2103
- * input (CodeQL flags this class as "incomplete multi-character
2104
- * sanitization" — the removal of one comment can expose a delimiter that
2105
- * was itself inside another). Looping until nothing changes closes that gap.
2106
- */
2107
- function stripXmlComments(content) {
2108
- let result = content;
2109
- let previous;
2110
- do {
2111
- previous = result;
2112
- result = result.replace(/<!--[\s\S]*?-->/g, "");
2113
- } while (result !== previous);
2114
- return result;
2115
- }
2116
- async function declaresMavenModule(parentDir, parentPomPath, childDir) {
2117
- try {
2118
- const content = stripXmlComments(await readFile(parentPomPath, "utf-8"));
2119
- const modulesBlock = content.match(/<modules>([\s\S]*?)<\/modules>/);
2120
- if (!modulesBlock)
2121
- return false;
2122
- const resolvedChild = path.resolve(childDir);
2123
- for (const match of modulesBlock[1].matchAll(/<module>\s*([^<]+?)\s*<\/module>/g)) {
2124
- // Case-insensitive / realpath-aware compare (#1671 F8): a declared
2125
- // `<module>Foo</module>` must still match a directory actually named
2126
- // `foo` on a case-insensitive filesystem, the same class of check the
2127
- // rest of this codebase does via `pathsEqual` (#1139/#1150).
2128
- if (pathsEqual(path.resolve(parentDir, match[1]), resolvedChild))
2129
- return true;
2130
- }
2131
- return false;
2132
- }
2133
- catch {
2134
- return false;
2135
- }
2136
- }
2137
- function JavaWorkspaceRoot() {
2138
- const moduleRoot = createRootDetector([
2139
- "pom.xml",
2140
- "build.gradle",
2141
- ".classpath",
2142
- ]);
2143
- return withRootMarkers(async (file) => {
2144
- const root = await moduleRoot(file);
2145
- if (!root)
2146
- return undefined;
2147
- // Only a Maven (pom.xml) module chain-hoists here — Gradle/.classpath module
2148
- // roots are returned as found; multi-module Gradle wiring (settings.gradle)
2149
- // is a different shape and out of scope for #1671.
2150
- if (!(await markerExists(root, "pom.xml")))
2151
- return root;
2152
- const sessionCwd = process.cwd();
2153
- const stop = sessionHoistCeiling(sessionCwd, file);
2154
- // `current` is the walk CURSOR — it climbs over gap directories (ones
2155
- // with no pom.xml of their own, e.g. a `<module>sub/dir</module>` entry
2156
- // spanning more than one filesystem level). `lastHop` is the last
2157
- // CONFIRMED module boundary; it is what the next ancestor pom's
2158
- // <modules> must declare, and it is what actually gets returned — never
2159
- // the cursor, which can land on a bare gap directory that is not itself
2160
- // a valid module root (#1671 F2).
2161
- let current = root;
2162
- let lastHop = root;
2163
- const fsRoot = path.parse(current).root;
2164
- while (true) {
2165
- if (stop !== undefined && current === stop)
2166
- break;
2167
- const parent = path.dirname(current);
2168
- if (parent === current || parent === fsRoot)
2169
- break;
2170
- if (await markerExists(parent, "pom.xml")) {
2171
- const parentPom = path.join(parent, "pom.xml");
2172
- if (!(await declaresMavenModule(parent, parentPom, lastHop)))
2173
- break;
2174
- current = parent;
2175
- lastHop = parent;
2176
- continue;
2177
- }
2178
- // No pom.xml here — a gap directory, not a module boundary. Keep
2179
- // climbing without advancing `lastHop`.
2180
- current = parent;
2181
- }
2182
- return enforceLspRootCeiling(lastHop, sessionCwd, file);
2183
- }, moduleRoot.rootMarkers ?? []);
2184
- }
2185
- export const RustServer = {
2186
- id: "rust",
2187
- name: "rust-analyzer",
2188
- extensions: KIND_EXTENSIONS["rust"],
2189
- // No FileDirRoot fallback (#201): rust-analyzer is a heavy workspace server
2190
- // that is useless without a Cargo manifest. With the fallback, every .rs file
2191
- // written before a Cargo.toml exists resolved to its OWN directory as the
2192
- // root, and since clients dedup by `${serverId}:${root}`, each directory
2193
- // spawned a separate rust-analyzer (one per file/dir during scaffolding).
2194
- // Returning undefined here skips the spawn until a Cargo.toml gives a stable,
2195
- // shared crate root — then all files share one server.
2196
- root: RustWorkspaceRoot(),
2197
- async spawn(root, options) {
2198
- // Prefer rustup-installed rust-analyzer; fall back to GitHub-downloaded
2199
- // managed copy. Canonical-bin discovery (#241): include ~/.cargo/bin so a
2200
- // cargo/rustup-managed rust-analyzer resolves before paying for a download
2201
- // even when ~/.cargo/bin isn't on PATH.
2202
- const result = await resolveAndLaunch({
2203
- candidates: cargoBinCandidates("rust-analyzer"),
2204
- args: [],
2205
- cwd: root,
2206
- managedToolId: "rust-analyzer",
2207
- }, options?.allowInstall);
2208
- if (!result)
2209
- return undefined;
2210
- return {
2211
- ...result,
2212
- initialization: {
2213
- cargo: { buildScripts: { enable: true } },
2214
- procMacro: { enable: true },
2215
- diagnostics: { enable: true },
2216
- },
2217
- };
2218
- },
2219
- };
2220
- export const RubyServer = {
2221
- id: "ruby",
2222
- name: "Ruby LSP",
2223
- extensions: KIND_EXTENSIONS["ruby"],
2224
- root: RootWithFallback(PriorityRoot([["Gemfile", ".ruby-version"], [".git"]])),
2225
- // Ruby LSP may need extra time to finish composed-bundle setup before it can
2226
- // answer initialize/documentSymbol on cold start.
2227
- initializeTimeoutMs: 30_000,
2228
- clientWaitTimeoutMs: 30_000,
2229
- async spawn(root, options) {
2230
- // Try ruby-lsp first, then solargraph, then rubocop --lsp
2231
- // Each has different args so we can't use a single resolveAndLaunch call
2232
- const rubylsp = await resolveAndLaunch({
2233
- candidates: ["ruby-lsp", ...rubyBinCandidates("ruby-lsp")],
2234
- args: [],
2235
- cwd: root,
2236
- runtimeInstall: {
2237
- runtimeCommand: "gem",
2238
- install: () => tryGemInstall("ruby-lsp"),
2239
- retryCandidates: ["ruby-lsp", ...rubyBinCandidates("ruby-lsp")],
2240
- },
2241
- }, options?.allowInstall);
2242
- if (rubylsp)
2243
- return rubylsp;
2244
- // Solargraph fallback
2245
- const solargraph = await resolveAndLaunch({
2246
- candidates: ["solargraph", ...rubyBinCandidates("solargraph")],
2247
- args: ["stdio"],
2248
- cwd: root,
2249
- }, false);
2250
- if (solargraph)
2251
- return solargraph;
2252
- // rubocop --lsp fallback
2253
- return resolveAndLaunch({
2254
- candidates: ["rubocop", ...rubyBinCandidates("rubocop")],
2255
- args: ["--lsp"],
2256
- cwd: root,
2257
- }, false);
2258
- },
2259
- };
2260
- // NOTE: Ruby's Solargraph + RuboCop fallbacks live INSIDE RubyServer.spawn
2261
- // (ruby-lsp → solargraph → rubocop --lsp). Primary selection is first-success-
2262
- // wins (one server per file, see LSPService.getClientForFile), so a separate
2263
- // solargraph sibling server could never be reached — RubyServer only returns
2264
- // undefined when solargraph is also absent. A standalone RubySolargraphServer
2265
- // would therefore be dead code; it intentionally does not exist. If a future
2266
- // user-selectable preferred-server config lands, refactor RubyServer to a
2267
- // single binary and register the alternatives as siblings (cf. python/jedi).
2268
- export const PHPServer = {
2269
- id: "php",
2270
- name: "Intelephense",
2271
- extensions: KIND_EXTENSIONS["php"],
2272
- root: RootWithFallback(createRootDetector(["composer.json", "composer.lock"])),
2273
- async spawn(root, options) {
2274
- const result = await resolveAndLaunch({
2275
- candidates: nodeBinCandidates(root, "intelephense"),
2276
- args: ["--stdio"],
2277
- cwd: root,
2278
- managedToolId: "intelephense",
2279
- }, options?.allowInstall);
2280
- if (!result)
2281
- return undefined;
2282
- return {
2283
- ...result,
2284
- initialization: {
2285
- storagePath: path.join(getGlobalPiLensDir(), "intelephense"),
2286
- },
2287
- };
2288
- },
2289
- };
2290
- // PowerShell Editor Services bootstrap (#278). Builds the `pwsh`/`powershell`
2291
- // args that launch the bundled Start-EditorServices.ps1 over stdio. Param set
2292
- // verified against the PSES v4.6.0 bundle. Each spawn gets a private session dir
2293
- // for the required Log/SessionDetails paths.
2294
- function buildPsesArgs(bundleDir) {
2295
- const script = path.join(bundleDir, "PowerShellEditorServices", "Start-EditorServices.ps1");
2296
- const sessionDir = path.join(getGlobalPiLensDir(), "pses", `${process.pid}-${Date.now()}`);
2297
- mkdirSync(sessionDir, { recursive: true });
2298
- const logPath = path.join(sessionDir, "pses.log");
2299
- const sessionDetailsPath = path.join(sessionDir, "session.json");
2300
- // Use -File with each PSES parameter as a SEPARATE argv element (the canonical
2301
- // editor launch form). This deliberately avoids `-Command "& '...'"`: pwsh.exe
2302
- // commonly lives under "C:\Program Files\…" (a space), which forces launchLSP's
2303
- // Windows shell path, and an embedded `&`/quotes in a single -Command string
2304
- // gets mangled by cmd.exe. Plain argv tokens survive shell escaping (our paths
2305
- // are under ~/.pi-lens, no spaces). -Stdio makes PSES speak LSP over this
2306
- // process's stdin/stdout; -LanguageServiceOnly skips the debug adapter.
2307
- return [
2308
- "-NoLogo",
2309
- "-NoProfile",
2310
- "-NonInteractive",
2311
- // Unsigned bundled script + mark-of-the-web on Windows — Bypass so it runs;
2312
- // ignored by non-Windows pwsh.
2313
- "-ExecutionPolicy",
2314
- "Bypass",
2315
- "-File",
2316
- script,
2317
- "-HostName",
2318
- "pi-lens",
2319
- "-HostProfileId",
2320
- "pi-lens",
2321
- "-HostVersion",
2322
- "1.0.0",
2323
- "-BundledModulesPath",
2324
- bundleDir,
2325
- "-LogPath",
2326
- logPath,
2327
- "-LogLevel",
2328
- "Warning",
2329
- "-SessionDetailsPath",
2330
- sessionDetailsPath,
2331
- "-Stdio",
2332
- "-LanguageServiceOnly",
2333
- ];
2334
- }
2335
- export const PowerShellServer = {
2336
- id: "powershell",
2337
- name: "PowerShell Editor Services",
2338
- extensions: KIND_EXTENSIONS["powershell"],
2339
- // Index at the workspace (script modules reference siblings); fall back to the
2340
- // file dir.
2341
- root: RootWithFallback(createRootDetector([".git"])),
2342
- spawn(root, options) {
2343
- // PSES is a module bundle launched via pwsh, not a binary on PATH. Resolve
2344
- // pwsh/powershell + the managed bundle, then launch the bootstrap over
2345
- // stdio. Graceful skip (→ coverage notice) when pwsh or the bundle is
2346
- // unavailable; psscriptanalyzer remains the fallback in the dispatch group.
2347
- return resolveAndLaunchBundle({
2348
- runtimeCandidates: ["pwsh", "powershell"],
2349
- bundleToolId: "powershell-editor-services",
2350
- cwd: root,
2351
- args: buildPsesArgs,
2352
- }, options?.allowInstall);
2353
- },
2354
- };
2355
- export const CSharpServer = {
2356
- id: "csharp",
2357
- name: "csharp-ls",
2358
- extensions: KIND_EXTENSIONS["csharp"],
2359
- // No FileDirRoot fallback (#201): csharp-ls is a workspace server and should
2360
- // not spawn once per source directory before a .sln/.csproj exists. Glob root
2361
- // markers match real project filenames such as `App.csproj` / `App.sln`
2362
- // (shared marker list — see file-kinds.ts, refs #895).
2363
- root: createRootDetector([...DOTNET_CSHARP_ROOT_MARKERS]),
2364
- async spawn(root, options) {
2365
- const candidates = dotnetToolCandidates("csharp-ls");
2366
- return resolveAndLaunch({
2367
- candidates,
2368
- args: [],
2369
- cwd: root,
2370
- runtimeInstall: {
2371
- runtimeCommand: "dotnet",
2372
- install: () => tryDotnetToolInstall("csharp-ls"),
2373
- retryCandidates: candidates,
2374
- },
2375
- }, options?.allowInstall);
2376
- },
2377
- };
2378
- export const OmniSharpServer = createInteractiveServer({
2379
- id: "omnisharp",
2380
- name: "OmniSharp",
2381
- fallbackFor: "csharp",
2382
- extensions: KIND_EXTENSIONS["csharp"],
2383
- root: createRootDetector([...DOTNET_CSHARP_ROOT_MARKERS]),
2384
- language: "csharp",
2385
- command: "OmniSharp",
2386
- args: ["--languageserver"],
2387
- });
2388
- export const FSharpServer = {
2389
- id: "fsharp",
2390
- name: "FSAutocomplete",
2391
- extensions: KIND_EXTENSIONS["fsharp"],
2392
- root: createRootDetector([...DOTNET_FSHARP_ROOT_MARKERS]),
2393
- async spawn(root, options) {
2394
- // fsautocomplete is a `dotnet tool` (#241), exactly like csharp-ls: prefer a
2395
- // managed/.dotnet-tools copy, else `dotnet tool install` when the .NET SDK
2396
- // is on PATH. dotnetToolCandidates covers the install target so the retry
2397
- // resolves it.
2398
- const candidates = dotnetToolCandidates("fsautocomplete");
2399
- return resolveAndLaunch({
2400
- candidates,
2401
- args: [],
2402
- cwd: root,
2403
- runtimeInstall: {
2404
- runtimeCommand: "dotnet",
2405
- install: () => tryDotnetToolInstall("fsautocomplete"),
2406
- retryCandidates: candidates,
2407
- },
2408
- }, options?.allowInstall);
2409
- },
2410
- };
2411
- export const JavaServer = createInteractiveServer({
2412
- id: "java",
2413
- name: "JDT Language Server",
2414
- extensions: KIND_EXTENSIONS["java"],
2415
- root: RootWithFallback(JavaWorkspaceRoot()),
2416
- language: "java",
2417
- command: () => process.env.JDTLS_PATH || "jdtls",
2418
- args: (root) => createLombokJdtlsArgs(root),
2419
- runtime: "java",
2420
- });
2421
- export const KotlinServer = {
2422
- id: "kotlin",
2423
- name: "Kotlin Language Server",
2424
- extensions: KIND_EXTENSIONS["kotlin"],
2425
- root: RootWithFallback(createRootDetector(["build.gradle.kts", "build.gradle", "pom.xml"])),
2426
- async spawn(root, options) {
2427
- // Prefer the newer official Kotlin LSP CLI when available, but keep
2428
- // compatibility with the older fwcd kotlin-language-server command.
2429
- return resolveAndLaunch({
2430
- candidates: ["kotlin-lsp", "kotlin-language-server"],
2431
- args: [],
2432
- cwd: root,
2433
- }, options?.allowInstall);
2434
- },
2435
- };
2436
- export const SwiftServer = createInteractiveServer({
2437
- id: "swift",
2438
- name: "SourceKit-LSP",
2439
- extensions: KIND_EXTENSIONS["swift"],
2440
- root: createRootDetector(["Package.swift"]),
2441
- language: "swift",
2442
- command: "sourcekit-lsp",
2443
- });
2444
- export const DartServer = createInteractiveServer({
2445
- id: "dart",
2446
- name: "Dart Analysis Server",
2447
- extensions: KIND_EXTENSIONS["dart"],
2448
- root: RootWithFallback(createRootDetector(["pubspec.yaml"])),
2449
- language: "dart",
2450
- command: "dart",
2451
- args: ["language-server", "--protocol=lsp"],
2452
- });
2453
- /**
2454
- * Build an {@link LSPServerInfo} for a language server that ships as a
2455
- * self-contained native TREE BUNDLE (single archive, `bin/<binary>` inside,
2456
- * no external runtime — clangd #241, lua-language-server #564, and the
2457
- * kotlin-language-server/elixir-ls follow-on #565). Extracted once both
2458
- * `CppServer` and `LuaServer` turned out to be a near-verbatim structural
2459
- * copy of each other (same `resolveAndLaunchTreeBinary` call shape) —
2460
- * flagged by SonarCloud's new-code duplication gate on PR #567 — so a third
2461
- * and fourth server of this shape (#565) can call this instead of
2462
- * copy-pasting a `spawn` again. Each server's own "why archive-tree, why
2463
- * stripComponents differs, etc." explanation stays as a comment at its own
2464
- * call site below, since that reasoning is genuinely per-server.
2465
- */
2466
- function createTreeBinaryServer(spec) {
2467
- return {
2468
- id: spec.id,
2469
- name: spec.name,
2470
- extensions: spec.extensions,
2471
- root: spec.root,
2472
- spawn(root, options) {
2473
- return resolveAndLaunchTreeBinary({
2474
- candidates: [spec.binaryName],
2475
- bundleToolId: spec.binaryName,
2476
- binRelPath: spec.binRelPath,
2477
- cwd: root,
2478
- args: spec.args ?? [],
2479
- }, options?.allowInstall);
2480
- },
2481
- };
2482
- }
2483
- // lua-language-server ships the same self-contained native TREE BUNDLE shape
2484
- // as clangd (#241/#564): bin/lua-language-server + bundled locale/meta files,
2485
- // no external runtime. Prefer a system install on PATH; else auto-install the
2486
- // managed bundle and launch bin/lua-language-server within it. Graceful skip
2487
- // when neither is available (→ coverage notice).
2488
- export const LuaServer = createTreeBinaryServer({
2489
- id: "lua",
2490
- name: "Lua Language Server",
2491
- extensions: KIND_EXTENSIONS["lua"],
2492
- root: createRootDetector([".luarc.json", ".luacheckrc"]),
2493
- binaryName: "lua-language-server",
2494
- binRelPath: "bin/lua-language-server",
2495
- });
2496
- // clangd ships a self-contained native tree bundle (bin/clangd + bundled
2497
- // libclang headers). Prefer a system clangd on PATH; else auto-install the
2498
- // managed bundle (#241) and launch bin/clangd within it. Graceful skip when
2499
- // neither is available (→ coverage notice); cpp-check stays the fallback.
2500
- export const CppServer = createTreeBinaryServer({
2501
- id: "cpp",
2502
- name: "clangd",
2503
- extensions: KIND_EXTENSIONS["cxx"],
2504
- root: RootWithFallback(createRootDetector([
2505
- "compile_commands.json",
2506
- ".clangd",
2507
- "CMakeLists.txt",
2508
- "Makefile",
2509
- ])),
2510
- binaryName: "clangd",
2511
- binRelPath: "bin/clangd",
2512
- args: ["--background-index"],
2513
- });
2514
- export const ZigServer = {
2515
- id: "zig",
2516
- name: "ZLS",
2517
- extensions: KIND_EXTENSIONS["zig"],
2518
- root: RootWithFallback(createRootDetector(["build.zig"])),
2519
- spawn(root, options) {
2520
- return resolveAndLaunch({
2521
- candidates: ["zls"],
2522
- args: [],
2523
- cwd: root,
2524
- managedToolId: "zls",
2525
- }, options?.allowInstall);
2526
- },
2527
- };
2528
- export const HaskellServer = createInteractiveServer({
2529
- id: "haskell",
2530
- name: "Haskell Language Server",
2531
- extensions: KIND_EXTENSIONS["haskell"],
2532
- root: createRootDetector(["stack.yaml", "cabal.project", "*.cabal"]),
2533
- language: "haskell",
2534
- command: "haskell-language-server-wrapper",
2535
- args: ["--lsp"],
2536
- });
2537
- export const ElixirServer = createInteractiveServer({
2538
- id: "elixir",
2539
- name: "ElixirLS",
2540
- extensions: KIND_EXTENSIONS["elixir"],
2541
- root: RootWithFallback(createRootDetector(["mix.exs"])),
2542
- language: "elixir",
2543
- command: "elixir-ls",
2544
- });
2545
- export const ElixirExpertServer = {
2546
- id: "expert",
2547
- name: "Expert",
2548
- fallbackFor: "elixir",
2549
- extensions: KIND_EXTENSIONS["elixir"],
2550
- root: RootWithFallback(createRootDetector(["mix.exs"])),
2551
- availabilityKey: "expert",
2552
- async spawn(root, options) {
2553
- return resolveAndLaunch({
2554
- candidates: ["expert"],
2555
- args: ["--stdio"],
2556
- cwd: root,
2557
- managedToolId: "expert",
2558
- }, options?.allowInstall);
2559
- },
2560
- autoInstall: async () => Boolean(await ensureTool("expert")),
2561
- };
2562
- export const GleamServer = {
2563
- id: "gleam",
2564
- name: "Gleam LSP",
2565
- extensions: KIND_EXTENSIONS["gleam"],
2566
- root: RootWithFallback(createRootDetector(["gleam.toml"])),
2567
- async spawn(root, options) {
2568
- // Prefer a PATH `gleam` (full toolchain); fall back to the managed
2569
- // GitHub-release binary. `gleam lsp` is the server entrypoint either way.
2570
- return resolveAndLaunch({
2571
- candidates: ["gleam"],
2572
- args: ["lsp"],
2573
- cwd: root,
2574
- managedToolId: "gleam",
2575
- }, options?.allowInstall);
2576
- },
2577
- };
2578
- export const TinymistServer = {
2579
- id: "tinymist",
2580
- name: "Tinymist",
2581
- extensions: extensionsForLanguage("typst"),
2582
- root: RootWithFallback(createRootDetector(["typst.toml", ".git"])),
2583
- availabilityKey: "tinymist",
2584
- async spawn(root, options) {
2585
- return resolveAndLaunch({
2586
- candidates: ["tinymist"],
2587
- args: ["lsp"],
2588
- cwd: root,
2589
- managedToolId: "tinymist",
2590
- }, options?.allowInstall);
2591
- },
2592
- };
2593
- export const MarksmanServer = {
2594
- id: "marksman",
2595
- name: "Marksman",
2596
- extensions: KIND_EXTENSIONS["markdown"],
2597
- // Index at the workspace root so cross-file checks (broken intra-repo links,
2598
- // missing/renamed anchors, heading refs) see the whole tree; fall back to the
2599
- // file's directory when there's no project marker.
2600
- root: RootWithFallback(createRootDetector([".marksman.toml", ".git"])),
2601
- spawn(root, options) {
2602
- // Prefer a PATH `marksman`; fall back to the managed GitHub-release binary.
2603
- // `marksman server` is the stdio LSP entrypoint either way.
2604
- return resolveAndLaunch({
2605
- candidates: ["marksman"],
2606
- args: ["server"],
2607
- cwd: root,
2608
- managedToolId: "marksman",
2609
- }, options?.allowInstall);
2610
- },
2611
- };
2612
- export const OCamlServer = createInteractiveServer({
2613
- id: "ocaml",
2614
- name: "ocamllsp",
2615
- extensions: KIND_EXTENSIONS["ocaml"],
2616
- root: createRootDetector(["dune-project", "opam"]),
2617
- language: "ocaml",
2618
- command: "ocamllsp",
2619
- });
2620
- export const ClojureServer = {
2621
- id: "clojure",
2622
- name: "Clojure LSP",
2623
- extensions: KIND_EXTENSIONS["clojure"],
2624
- root: createRootDetector(["deps.edn", "project.clj"]),
2625
- async spawn(root, options) {
2626
- // Prefer a PATH `clojure-lsp`; fall back to the managed self-contained
2627
- // native (GraalVM) GitHub-release binary — no JVM needed either way.
2628
- return resolveAndLaunch({
2629
- candidates: ["clojure-lsp"],
2630
- args: [],
2631
- cwd: root,
2632
- managedToolId: "clojure-lsp",
2633
- }, options?.allowInstall);
2634
- },
2635
- };
2636
- export const CueServer = {
2637
- id: "cue",
2638
- name: "CUE Language Server",
2639
- extensions: KIND_EXTENSIONS["cue"],
2640
- root: RootWithFallback(createRootDetector(["cue.mod", ".git"])),
2641
- async spawn(root, options) {
2642
- return resolveAndLaunch({
2643
- candidates: ["cue"],
2644
- args: ["lsp", "serve"],
2645
- cwd: root,
2646
- managedToolId: "cue",
2647
- }, options?.allowInstall);
2648
- },
2649
- };
2650
- export const TerraformServer = {
2651
- id: "terraform",
2652
- name: "Terraform LSP",
2653
- extensions: KIND_EXTENSIONS["terraform"],
2654
- root: RootWithFallback(createRootDetector([".terraform.lock.hcl", ".terraform"])),
2655
- spawn(root, options) {
2656
- return resolveAndLaunch({
2657
- candidates: ["terraform-ls"],
2658
- args: ["serve"],
2659
- cwd: root,
2660
- managedToolId: "terraform-ls",
2661
- }, options?.allowInstall);
2662
- },
2663
- };
2664
- export const NixServer = createInteractiveServer({
2665
- id: "nix",
2666
- name: "nixd",
2667
- extensions: KIND_EXTENSIONS["nix"],
2668
- root: createRootDetector(["flake.nix"]),
2669
- language: "nix",
2670
- command: "nixd",
2671
- });
2672
- export const BashServer = {
2673
- id: "bash",
2674
- name: "Bash Language Server",
2675
- extensions: [".bash", ".sh", ".zsh"],
2676
- root: FileDirRoot,
2677
- // #2194 bounded the installer's own verification at 20s; the dispatch
2678
- // touch's client-wait floor stayed at the shared 5s default, so a cold
2679
- // spawn could still race that budget and read as unavailable even after
2680
- // installer verification cleared it. Match the installer bound (#2169).
2681
- // `initializeTimeoutMs` matches it too: the measured cold start
2682
- // (9,667ms, #2194) has headroom under the unraised 15s default, but
2683
- // leaving the two fields mismatched is the same latent shape that made
2684
- // the Prisma/Vue raise a no-op (fix-round F1, #2233) — a slower host
2685
- // than the one measured would still hit the 15s inner kill despite the
2686
- // caller being willing to wait 20s. Equalizing costs nothing on the
2687
- // success path and removes the mismatch outright.
2688
- clientWaitTimeoutMs: 20_000,
2689
- initializeTimeoutMs: 20_000,
2690
- spawn(root, options) {
2691
- return resolveAndLaunch({
2692
- candidates: nodeBinCandidates(root, "bash-language-server"),
2693
- args: ["start"],
2694
- cwd: root,
2695
- managedToolId: "bash-language-server",
2696
- }, options?.allowInstall);
2697
- },
2698
- };
2699
- export const FishServer = {
2700
- id: "fish",
2701
- name: "Fish Language Server",
2702
- extensions: KIND_EXTENSIONS["fish"],
2703
- root: RootWithFallback(createRootDetector([".git"])),
2704
- spawn(root, options) {
2705
- return resolveAndLaunch({
2706
- candidates: nodeBinCandidates(root, "fish-lsp"),
2707
- args: ["start", "--stdio"],
2708
- cwd: root,
2709
- managedToolId: "fish-lsp",
2710
- }, options?.allowInstall);
2711
- },
2712
- };
2713
- export const CMakeServer = {
2714
- id: "cmake",
2715
- name: "CMake Language Server",
2716
- // CMake's canonical project file has no .cmake suffix. The configured-server
2717
- // matcher supports exact basenames as well as extensions.
2718
- extensions: [...KIND_EXTENSIONS["cmake"], "CMakeLists.txt"],
2719
- root: RootWithFallback(createRootDetector(["CMakeLists.txt", ".git"])),
2720
- spawn(root, options) {
2721
- return resolveAndLaunch({
2722
- candidates: ["cmake-language-server"],
2723
- args: [],
2724
- cwd: root,
2725
- managedToolId: "cmake-language-server",
2726
- }, options?.allowInstall);
2727
- },
2728
- };
2729
- export const DockerServer = {
2730
- id: "docker",
2731
- name: "Dockerfile Language Server",
2732
- extensions: [".dockerfile", "Dockerfile"],
2733
- root: RootWithFallback(PriorityRoot([
2734
- [
2735
- "docker-compose.yml",
2736
- "docker-compose.yaml",
2737
- "compose.yml",
2738
- "compose.yaml",
2739
- ],
2740
- [".git"],
2741
- ])),
2742
- spawn(root, options) {
2743
- return resolveAndLaunch({
2744
- candidates: nodeBinCandidates(root, "docker-langserver"),
2745
- args: ["--stdio"],
2746
- cwd: root,
2747
- managedToolId: "dockerfile-language-server-nodejs",
2748
- }, options?.allowInstall);
2749
- },
2750
- };
2751
- export const YamlServer = {
2752
- id: "yaml",
2753
- name: "YAML Language Server",
2754
- extensions: KIND_EXTENSIONS["yaml"],
2755
- root: RootWithFallback(PriorityRoot([
2756
- [".yamllint", "yamllint.yml", "yamllint.yaml", "pyproject.toml"],
2757
- [".git"],
2758
- ])),
2759
- spawn(root, options) {
2760
- return resolveAndLaunch({
2761
- candidates: nodeBinCandidates(root, "yaml-language-server"),
2762
- args: ["--stdio"],
2763
- cwd: root,
2764
- managedToolId: "yaml-language-server",
2765
- }, options?.allowInstall);
2766
- },
2767
- };
2768
- export const JsonServer = {
2769
- id: "json",
2770
- name: "VSCode JSON Language Server",
2771
- extensions: KIND_EXTENSIONS["json"],
2772
- root: RootWithFallback(WorkspacePriorityRoot([
2773
- ["package.json", "tsconfig.json", "jsconfig.json"],
2774
- [".git"],
2775
- ])),
2776
- // See BashServer above: the installer's 20s verification bound (#2194)
2777
- // does not, on its own, raise the dispatch touch's cold-spawn wait floor.
2778
- // Mirror it here so a cold spawn cannot lose that race either (#2169).
2779
- // `initializeTimeoutMs` matches it for the same reason as BashServer
2780
- // above (fix-round F1, #2233).
2781
- clientWaitTimeoutMs: 20_000,
2782
- initializeTimeoutMs: 20_000,
2783
- spawn(root, options) {
2784
- return resolveAndLaunch({
2785
- candidates: ["vscode-json-language-server"],
2786
- args: ["--stdio"],
2787
- cwd: root,
2788
- managedToolId: "vscode-json-language-server",
2789
- }, options?.allowInstall);
2790
- },
2791
- };
2792
- export const HtmlServer = {
2793
- id: "html",
2794
- name: "VSCode HTML Language Server",
2795
- extensions: KIND_EXTENSIONS["html"],
2796
- root: RootWithFallback(IgnoreHomeRoot(PriorityRoot([["package.json", "index.html", "vite.config.ts"]]))),
2797
- spawn(root, options) {
2798
- return resolveAndLaunch({
2799
- candidates: nodeBinCandidates(root, "vscode-html-language-server"),
2800
- args: ["--stdio"],
2801
- cwd: root,
2802
- managedToolId: "vscode-html-languageserver-bin",
2803
- }, options?.allowInstall);
2804
- },
2805
- };
2806
- export const TomlServer = {
2807
- id: "toml",
2808
- name: "Taplo",
2809
- extensions: KIND_EXTENSIONS["toml"],
2810
- root: RootWithFallback(PriorityRoot([["pyproject.toml", "Cargo.toml", "taplo.toml"], [".git"]])),
2811
- spawn(root, options) {
2812
- return resolveAndLaunch({
2813
- candidates: ["taplo"],
2814
- args: ["lsp", "stdio"],
2815
- cwd: root,
2816
- managedToolId: "taplo",
2817
- }, options?.allowInstall);
2818
- },
2819
- };
2820
- export const PrismaServer = {
2821
- id: "prisma",
2822
- name: "Prisma Language Server",
2823
- extensions: KIND_EXTENSIONS["prisma"],
2824
- root: RootWithFallback(createRootDetector(["prisma/schema.prisma", "schema.prisma"])),
2825
- // Matches the installer's 40s verification bound above (#2169): the
2826
- // dispatch touch's cold-spawn wait floor defaults to 5s and would
2827
- // otherwise time the client out well before the binary answers.
2828
- // `initializeTimeoutMs` MUST be set alongside it (RubyServer's own
2829
- // precedent below): unset, it falls back to the 15s
2830
- // `INITIALIZE_TIMEOUT_MS` default in `clients/lsp/client.ts`, which
2831
- // hard-kills the child mid-handshake — a real cold Prisma run measured
2832
- // up to 27.3s, so the spawn would die there before this wait ever gets
2833
- // a chance to matter (fix-round F1, #2233).
2834
- clientWaitTimeoutMs: 40_000,
2835
- initializeTimeoutMs: 40_000,
2836
- spawn(root, options) {
2837
- return resolveAndLaunch({
2838
- candidates: nodeBinCandidates(root, "prisma-language-server"),
2839
- args: ["--stdio"],
2840
- cwd: root,
2841
- managedToolId: "@prisma/language-server",
2842
- }, options?.allowInstall);
2843
- },
2844
- };
2845
- // --- Web Framework & Styling Servers ---
2846
- export const VueServer = {
2847
- id: "vue",
2848
- name: "Vue Language Server",
2849
- extensions: [".vue"],
2850
- root: RootWithFallback(IgnoreHomeRoot(createRootDetector([
2851
- "package.json",
2852
- "package-lock.json",
2853
- "bun.lockb",
2854
- "bun.lock",
2855
- "pnpm-lock.yaml",
2856
- "yarn.lock",
2857
- ]))),
2858
- // Vue's launcher loads the full language-service bundle before answering,
2859
- // matching the installer's 30s verification bound (#2176). The dispatch
2860
- // touch's cold-spawn wait floor defaults to 5s and needs the same raise so
2861
- // a cold spawn cannot time out there instead. `initializeTimeoutMs` MUST
2862
- // match: unset, it falls back to the 15s default in `clients/lsp/client.ts`
2863
- // and hard-kills a cold spawn (measured up to 22.6s in #2188, 16.2s here)
2864
- // mid-handshake before this wait can matter (fix-round F1, #2233).
2865
- clientWaitTimeoutMs: 30_000,
2866
- initializeTimeoutMs: 30_000,
2867
- async spawn(root, options) {
2868
- const tsserverPath = await findTsserverPath(root, options?.allowInstall);
2869
- // Vue Language Server needs Vue dependencies installed to resolve types.
2870
- // Without node_modules, navigation requests will timeout or return empty.
2871
- const hasPackageJson = existsSync(path.join(root, "package.json"));
2872
- const hasNodeModules = existsSync(path.join(root, "node_modules"));
2873
- if (hasPackageJson && !hasNodeModules) {
2874
- logSessionStart(`lsp vue: node_modules missing in ${root} — Vue navigation may be limited. ` +
2875
- `Run: npm install (or pnpm/yarn install) in this project.`);
2876
- }
2877
- const proc = await resolveAndLaunch({
2878
- candidates: nodeBinCandidates(root, "vue-language-server"),
2879
- args: ["--stdio"],
2880
- cwd: root,
2881
- managedToolId: "@vue/language-server",
2882
- }, options?.allowInstall);
2883
- if (!proc)
2884
- return undefined;
2885
- return {
2886
- process: proc.process,
2887
- source: proc.source,
2888
- initialization: tsserverPath
2889
- ? { typescript: { tsdk: path.dirname(tsserverPath) } }
2890
- : undefined,
2891
- };
2892
- },
2893
- };
2894
- export const SvelteServer = {
2895
- id: "svelte",
2896
- name: "Svelte Language Server",
2897
- extensions: [".svelte"],
2898
- root: RootWithFallback(IgnoreHomeRoot(createRootDetector([
2899
- "package.json",
2900
- "package-lock.json",
2901
- "bun.lockb",
2902
- "bun.lock",
2903
- "pnpm-lock.yaml",
2904
- "yarn.lock",
2905
- ]))),
2906
- // Matches the installer's 20s verification bound above (#2169): the
2907
- // dispatch touch's cold-spawn wait floor defaults to 5s and would
2908
- // otherwise time the client out well before the binary answers.
2909
- // `initializeTimeoutMs` matches it for the same reason as BashServer
2910
- // above (fix-round F1, #2233).
2911
- clientWaitTimeoutMs: 20_000,
2912
- initializeTimeoutMs: 20_000,
2913
- async spawn(root, options) {
2914
- const tsserverPath = await findTsserverPath(root, options?.allowInstall);
2915
- const proc = await resolveAndLaunch({
2916
- candidates: [
2917
- ...nodeBinCandidates(root, "svelteserver"),
2918
- ...nodeBinCandidates(root, "svelte-language-server"),
2919
- ],
2920
- args: ["--stdio"],
2921
- cwd: root,
2922
- managedToolId: "svelte-language-server",
2923
- }, options?.allowInstall);
2924
- if (!proc)
2925
- return undefined;
2926
- return {
2927
- process: proc.process,
2928
- source: proc.source,
2929
- initialization: tsserverPath
2930
- ? { typescript: { tsdk: path.dirname(tsserverPath) } }
2931
- : undefined,
2932
- };
2933
- },
2934
- };
2935
- export const CssServer = {
2936
- id: "css",
2937
- name: "CSS Language Server",
2938
- extensions: KIND_EXTENSIONS["css"],
2939
- root: RootWithFallback(IgnoreHomeRoot(PriorityRoot([
2940
- [
2941
- "package.json",
2942
- "postcss.config.js",
2943
- "tailwind.config.js",
2944
- "vite.config.ts",
2945
- ],
2946
- ]))),
2947
- spawn(root, options) {
2948
- return resolveAndLaunch({
2949
- candidates: nodeBinCandidates(root, "vscode-css-language-server"),
2950
- args: ["--stdio"],
2951
- cwd: root,
2952
- managedToolId: "vscode-css-languageserver",
2953
- }, options?.allowInstall);
2954
- },
2955
- };
2956
- // --- Registry ---
2957
- // Opengrep — a cross-language security scanner that speaks LSP. Unlike the
2958
- // per-language servers it attaches to MANY file kinds (the aggregation layer
2959
- // merges its diagnostics with the file's real language server). Running it as a
2960
- // warm LSP server compiles the ruleset once per session instead of paying it on
2961
- // every file (the ~8s CLI-per-file cost #111), bringing per-file scans to ~1.3s.
2962
- // Rules load via `initializationOptions.scan.configuration` (a local rule file
2963
- // if the repo has one, else Opengrep's login-free `auto` set).
2964
- const OPENGREP_KINDS = [
2965
- "csharp",
2966
- "css",
2967
- "cxx",
2968
- "dart",
2969
- "docker",
2970
- "go",
2971
- "html",
2972
- "java",
2973
- "json",
2974
- "jsts",
2975
- "kotlin",
2976
- "lua",
2977
- "php",
2978
- "python",
2979
- "ruby",
2980
- "rust",
2981
- "shell",
2982
- "swift",
2983
- "terraform",
2984
- "yaml",
2985
- ];
2986
- const OPENGREP_EXTENSIONS = Array.from(new Set(OPENGREP_KINDS.flatMap((k) => KIND_EXTENSIONS[k] ?? [])));
2987
- function opengrepInitialization(root) {
2988
- // As an always-on LSP server, enablement is structural (the server is
2989
- // registered); resolveOpengrepConfig here only chooses WHICH rules — a local
2990
- // rule file if present, otherwise `auto`.
2991
- const resolved = resolveOpengrepConfig(root, { enabled: true });
2992
- return {
2993
- scan: {
2994
- configuration: [resolved.configArg ?? "auto"],
2995
- onlyGitDirty: false,
2996
- jobs: 16,
2997
- },
2998
- metrics: { enabled: false },
2999
- doHover: false,
3000
- };
3001
- }
3002
- export const OpengrepServer = {
3003
- id: "opengrep",
3004
- name: "Opengrep Security Scanner",
3005
- role: "auxiliary",
3006
- extensions: OPENGREP_EXTENSIONS,
3007
- // Stable per-repo root so ONE warm server serves the whole project (a
3008
- // per-directory root would spawn a fresh server — and re-pay rule load —
3009
- // for every folder).
3010
- root: RootWithFallback(NearestRoot([".git"]), async () => process.cwd()),
3011
- availabilityKey: "opengrep",
3012
- // Rule compilation can take a few seconds on the first scan of a session.
3013
- initializeTimeoutMs: 15000,
3014
- async spawn(root, options) {
3015
- const launched = await resolveAndLaunch({
3016
- candidates: ["opengrep"],
3017
- args: ["lsp", "--experimental"],
3018
- cwd: root,
3019
- managedToolId: "opengrep",
3020
- }, options?.allowInstall);
3021
- if (!launched)
3022
- return undefined;
3023
- return { ...launched, initialization: opengrepInitialization(root) };
3024
- },
3025
- autoInstall: async () => Boolean(await ensureTool("opengrep")),
3026
- };
3027
- // ast-grep — a polyglot structural linter that speaks LSP. Like Opengrep it is a
3028
- // cross-cutting, diagnostic-only auxiliary (never a file's primary language
3029
- // server). It attaches EVERYWHERE (#239 Phase 2): a project `sgconfig.y[a]ml`
3030
- // surfaces the team's OWN curated rules (auto-discovered), and absent one it
3031
- // launches with `--config <shipped baseline>` so pi-lens's bundled ruleset runs
3032
- // anyway — superseding the in-process napi runner, which steps aside when this
3033
- // server's binary is available (and resumes as the fallback when it isn't —
3034
- // Gate B). NOTE: the napi runner is NOT a subset — it delegates to napi's native
3035
- // engine via root.findAll({rule}) (#206), the SAME Rust core as this LSP and the
3036
- // ast-grep CLI, so rule semantics are identical across all three. The LSP's edge
3037
- // is engine-driven codeAction fixes, not faithfulness of matching. #2347 closed
3038
- // the one known divergence under Gate B: the LSP/CLI resolve embedded `<script>`
3039
- // bodies in HTML and run `language: JavaScript` rules inside them, and the napi
3040
- // fallback now mirrors that (each script body is reparsed as JS and findings are
3041
- // translated back to file coordinates). A future ast-grep embedded-content
3042
- // surface (for example `<style>` bodies, which 0.45.1 does NOT inject) must land
3043
- // on both routes together or be recorded here as an accepted divergence.
3044
- const AST_GREP_KINDS = [
3045
- "csharp",
3046
- "cxx",
3047
- "css",
3048
- "elixir",
3049
- "go",
3050
- "haskell",
3051
- "html",
3052
- "java",
3053
- "json",
3054
- "jsts",
3055
- "kotlin",
3056
- "lua",
3057
- "nix",
3058
- "php",
3059
- "python",
3060
- "ruby",
3061
- "rust",
3062
- "scala",
3063
- "shell",
3064
- "swift",
3065
- "yaml",
3066
- ];
3067
- const AST_GREP_EXTENSIONS = Array.from(new Set(AST_GREP_KINDS.flatMap((k) => KIND_EXTENSIONS[k] ?? [])));
3068
- export const AstGrepServer = {
3069
- id: "ast-grep",
3070
- name: "ast-grep structural linter",
3071
- role: "auxiliary",
3072
- extensions: AST_GREP_EXTENSIONS,
3073
- // Attaches everywhere (#239 Phase 2): prefer a project `sgconfig.y[a]ml` root,
3074
- // else the repo root (.git) or cwd — like Opengrep. When there's no project
3075
- // sgconfig the spawn launches with `--config <shipped baseline>` so the team's
3076
- // rules still run; the napi runner steps aside when this server is available
3077
- // (it falls back to napi when the ast-grep binary is absent — Gate B).
3078
- root: RootWithFallback(createRootDetector(["sgconfig.yml", "sgconfig.yaml"]), RootWithFallback(NearestRoot([".git"]), async () => process.cwd())),
3079
- availabilityKey: "ast-grep",
3080
- // #1714: the one auxiliary with a measured wedge ceiling. A `lens_diagnostics
3081
- // mode=full` sweep of 225 files drove this server into an unrecoverable stall
3082
- // twice in two exposures (writes outstanding 5.9 s and 9.4 s, then a forced
3083
- // shutdown that timed out both the request and the exit notify). It re-parses
3084
- // the whole file on every didOpen, so it absorbs a sweep more slowly than the
3085
- // other scanners — hold it to half the shared default.
3086
- notifyInflightLimit: 4,
3087
- // First scan of a session compiles the rules.
3088
- initializeTimeoutMs: 15000,
3089
- async spawn(root, options) {
3090
- // A project sgconfig wins (the team's curated ruleset, auto-discovered from
3091
- // cwd). Otherwise point `--config` at pi-lens's shipped baseline ruleset.
3092
- const projectSgconfig = findLocalSgconfig(root);
3093
- let args = ["lsp"];
3094
- if (!projectSgconfig) {
3095
- const baseline = resolveBaselineSgconfig(root);
3096
- if (baseline)
3097
- args = ["lsp", "--config", baseline];
3098
- }
3099
- // #472: prefer the platform-native exe directly (one less orphanable
3100
- // node-bin-wrapper layer). Prepended as the first candidate; falls back
3101
- // to the existing "ast-grep" PATH/global-bin resolution when the
3102
- // optional native package isn't installed for this platform/arch.
3103
- const nativeExe = resolveAstGrepNativeExe();
3104
- const candidates = nativeExe ? [nativeExe, "ast-grep"] : ["ast-grep"];
3105
- return resolveAndLaunch({
3106
- candidates,
3107
- args,
3108
- cwd: root,
3109
- managedToolId: "ast-grep",
3110
- }, options?.allowInstall);
3111
- },
3112
- autoInstall: async () => Boolean(await ensureTool("ast-grep")),
3113
- };
3114
- // zizmor — a GitHub Actions workflow-security scanner that speaks LSP (#272).
3115
- // Like Opengrep/ast-grep it is a cross-cutting, diagnostic-only auxiliary. Its
3116
- // extension match (any YAML) is intentionally broad — actual candidacy is
3117
- // narrowed by `pathFilter` (`isZizmorAuditTarget`, #636) to the exact paths
3118
- // zizmor's own input collection audits (`.github/workflows/*`, `action.yml`,
3119
- // `.github/dependabot.yaml`); every other YAML file is a guaranteed no-op —
3120
- // measured directly against a real `zizmor --lsp` process, a non-matching
3121
- // file gets NO `publishDiagnostics` at all, so without the path gate every
3122
- // edit of e.g. a `docker-compose.yml` would burn zizmor's full
3123
- // diagnostics-wait budget for zero signal. Its audit set ("regular" persona)
3124
- // is compiled-in and runs with NO config; a repo `zizmor.yml` only
3125
- // tunes/ignores rules (the blocking opt-in, see the auxiliary profile).
3126
- // Online audits (known-vulnerable-actions, unpinned-uses, …) need a GitHub
3127
- // token — resolveZizmorGitHubToken forwards one (env, else `gh auth token`);
3128
- // without it zizmor runs its offline audit subset.
3129
- const ZIZMOR_EXTENSIONS = KIND_EXTENSIONS["yaml"];
3130
- export const ZizmorServer = {
3131
- id: "zizmor",
3132
- name: "zizmor Actions Security Scanner",
3133
- role: "auxiliary",
3134
- extensions: ZIZMOR_EXTENSIONS,
3135
- pathFilter: isZizmorAuditTarget,
3136
- // Stable per-repo root so ONE warm server serves the whole project (like
3137
- // Opengrep) — config + workflow discovery is repo-relative.
3138
- root: RootWithFallback(NearestRoot([".git"]), async () => process.cwd()),
3139
- availabilityKey: "zizmor",
3140
- async spawn(root, options) {
3141
- // Forward a token so the online audits run; absent one, zizmor self-selects
3142
- // offline mode (the env vars + `gh auth token` are resolved once and merged
3143
- // over process.env by launchLSP).
3144
- const ghToken = await resolveZizmorGitHubToken();
3145
- return resolveAndLaunch({
3146
- candidates: ["zizmor"],
3147
- args: ["--lsp"],
3148
- cwd: root,
3149
- managedToolId: "zizmor",
3150
- ...(ghToken ? { env: { GH_TOKEN: ghToken } } : {}),
3151
- }, options?.allowInstall);
3152
- },
3153
- autoInstall: async () => Boolean(await ensureTool("zizmor")),
3154
- };
3155
- // typos — a source-code spell checker that speaks LSP (#283). Cross-cutting,
3156
- // diagnostic-only auxiliary like Opengrep/ast-grep/zizmor: it attaches to many
3157
- // code kinds AND markdown/docs (option B — a spell checker that skips prose
3158
- // misses its highest-value target; typos is ALLOW-LIST based, so it only flags
3159
- // known misspellings with a known correction, keeping the false-positive rate on
3160
- // technical vocab low). Its built-in dictionary is compiled in — NO config needed
3161
- // to run; a repo `typos.toml`/`_typos.toml`/`.typos.toml` only tunes the
3162
- // dictionary/severity (and is the blocking opt-in, see the auxiliary profile).
3163
- // `typos-lsp` takes NO subcommand/flag — it wires stdin/stdout straight into the
3164
- // LSP server. Default severity is WARNING, so findings are advisory by default.
3165
- const TYPOS_EXTENSIONS = Array.from(new Set([...OPENGREP_EXTENSIONS, ...KIND_EXTENSIONS["markdown"]]));
3166
- // #967: typos-lsp's `initializationOptions.config` is a filesystem PATH to a
3167
- // config file (confirmed against upstream source — crates/typos-lsp/src/lsp.rs
3168
- // reads `config` as a string and tilde-expands it into a PathBuf; it is never
3169
- // an inline TOML string nor a parsed table). typos-lsp then MERGES that config
3170
- // with any repo-local one it discovers itself, with the injected config
3171
- // taking precedence on key collisions — so a project's own config must never
3172
- // be injected alongside ours (see findLocalTyposConfig below): honoring an
3173
- // existing project config means injecting NOTHING, letting typos-lsp read the
3174
- // project's file untouched.
3175
- function typosInitialization(root) {
3176
- const localConfig = findLocalTyposConfig(root);
3177
- if (localConfig) {
3178
- logLatency({
3179
- type: "phase",
3180
- phase: "typos_config_resolved",
3181
- filePath: root,
3182
- durationMs: 0,
3183
- metadata: { mode: "project_config", configPath: localConfig },
3184
- });
3185
- logSessionStart(`typos config resolved mode=project_config configPath=${localConfig}`);
3186
- return undefined;
3187
- }
3188
- const configPath = resolvePackagePath(import.meta.url, "rules", "typos", "_typos.toml");
3189
- logLatency({
3190
- type: "phase",
3191
- phase: "typos_config_resolved",
3192
- filePath: root,
3193
- durationMs: 0,
3194
- metadata: { mode: "injected_default", configPath },
3195
- });
3196
- logSessionStart(`typos config resolved mode=injected_default configPath=${configPath}`);
3197
- return { config: configPath };
3198
- }
3199
- export const TyposServer = {
3200
- id: "typos",
3201
- name: "typos Spell Checker",
3202
- role: "auxiliary",
3203
- extensions: TYPOS_EXTENSIONS,
3204
- // Stable per-repo root so ONE warm server serves the whole project (like the
3205
- // other auxiliaries) — typos.toml discovery is repo-relative.
3206
- root: RootWithFallback(NearestRoot([".git"]), async () => process.cwd()),
3207
- availabilityKey: "typos-lsp",
3208
- async spawn(root, options) {
3209
- const launched = await resolveAndLaunch({
3210
- candidates: ["typos-lsp"],
3211
- args: [],
3212
- cwd: root,
3213
- managedToolId: "typos-lsp",
3214
- }, options?.allowInstall);
3215
- if (!launched)
3216
- return undefined;
3217
- const initialization = typosInitialization(root);
3218
- return initialization ? { ...launched, initialization } : launched;
3219
- },
3220
- autoInstall: async () => Boolean(await ensureTool("typos-lsp")),
3221
- };
3222
- export const LSP_SERVERS = [
3223
- TypeScriptServer,
3224
- DenoServer,
3225
- PythonServer, // pyright / basedpyright — preferred; openFilesOnly avoids cold-start; ty (#717) is a local-only opt-in fallback
3226
- PythonJediServer, // fallback when neither pyright nor basedpyright is available
3227
- GoServer,
3228
- RustServer,
3229
- RubyServer,
3230
- PHPServer,
3231
- PowerShellServer, // PowerShell Editor Services — pwsh-bootstrapped module bundle (#278)
3232
- CSharpServer,
3233
- OmniSharpServer,
3234
- FSharpServer,
3235
- JavaServer,
3236
- KotlinServer,
3237
- SwiftServer,
3238
- DartServer,
3239
- LuaServer,
3240
- CppServer,
3241
- ZigServer,
3242
- HaskellServer,
3243
- ElixirServer,
3244
- ElixirExpertServer,
3245
- GleamServer,
3246
- TinymistServer,
3247
- MarksmanServer,
3248
- OCamlServer,
3249
- ClojureServer,
3250
- CueServer,
3251
- TerraformServer,
3252
- NixServer,
3253
- BashServer,
3254
- FishServer,
3255
- CMakeServer,
3256
- DockerServer,
3257
- YamlServer,
3258
- JsonServer,
3259
- HtmlServer,
3260
- TomlServer,
3261
- PrismaServer,
3262
- // Web frameworks & styling
3263
- VueServer,
3264
- SvelteServer,
3265
- CssServer,
3266
- // Auxiliary (cross-cutting, diagnostic-only) servers go last — never primary.
3267
- OpengrepServer,
3268
- AstGrepServer,
3269
- ZizmorServer,
3270
- TyposServer,
3271
- ];
3272
- /**
3273
- * Get server for a file extension
3274
- */
3275
- export function getServerForExtension(ext) {
3276
- return LSP_SERVERS.find((server) => server.extensions.includes(ext));
3277
- }
3278
- /**
3279
- * Get server by ID
3280
- */
3281
- export function getServerById(id) {
3282
- return LSP_SERVERS.find((server) => server.id === id);
3283
- }
3284
- /**
3285
- * Get all servers for a file (may have multiple matches)
3286
- */
3287
- export function getServersForFile(filePath) {
3288
- const ext = path.extname(filePath).toLowerCase();
3289
- return LSP_SERVERS.filter((server) => server.extensions.includes(ext));
3290
- }