@herbertgao/pi-extensions 2026.8.5 → 2026.8.7

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 (389) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
  19. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  20. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  21. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  24. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
  25. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  26. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  27. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  28. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  29. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  30. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  32. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  33. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
  34. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  35. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  51. package/node_modules/@narumitw/pi-btw/README.md +119 -103
  52. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  53. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  54. package/node_modules/@narumitw/pi-btw/package.json +13 -9
  55. package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
  56. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  57. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  58. package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
  59. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  60. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  61. package/node_modules/pi-lens/CHANGELOG.md +1004 -0
  62. package/node_modules/pi-lens/README.md +11 -3
  63. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  64. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  65. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
  66. package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
  67. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  68. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  69. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  70. package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
  71. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  72. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  73. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  74. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  75. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  76. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  77. package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
  78. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  79. package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
  80. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  81. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  82. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  83. package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
  84. package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
  85. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  86. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  87. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  88. package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
  89. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
  90. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  91. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
  93. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
  94. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  95. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  129. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  130. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  131. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  132. package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
  133. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  134. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  135. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  136. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  137. package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
  138. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  139. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  140. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  141. package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
  142. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  143. package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
  144. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
  145. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  146. package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
  147. package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
  148. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  149. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  150. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  151. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
  154. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  155. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
  157. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  158. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  159. package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
  160. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  161. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
  162. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  163. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
  164. package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
  165. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
  166. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  167. package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
  168. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  169. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  170. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  171. package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
  172. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  173. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
  175. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  176. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
  177. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  178. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  179. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  180. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  181. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
  182. package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
  183. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  184. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  185. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  186. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  187. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  188. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  189. package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
  190. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  191. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  195. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  198. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  200. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  201. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  204. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  205. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  206. package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  217. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  218. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  219. package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
  220. package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
  221. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  222. package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
  223. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  224. package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
  225. package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
  226. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  227. package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
  228. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  229. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  230. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  233. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  234. package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
  235. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  236. package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
  237. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  238. package/node_modules/pi-lens/dist/index.js +64677 -49484
  239. package/node_modules/pi-lens/dist/mcp/server.js +8 -2
  240. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  241. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  242. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  243. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
  244. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
  245. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  246. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  247. package/node_modules/pi-lens/docs/agent-guide.md +44 -14
  248. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
  249. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  250. package/node_modules/pi-lens/docs/features.md +33 -6
  251. package/node_modules/pi-lens/docs/globalconfig.md +14 -0
  252. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  253. package/node_modules/pi-lens/docs/mcp.md +2 -2
  254. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  255. package/node_modules/pi-lens/docs/settings.md +25 -1
  256. package/node_modules/pi-lens/docs/usage.md +24 -5
  257. package/node_modules/pi-lens/docs/word-index.md +35 -0
  258. package/node_modules/pi-lens/package.json +10 -6
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  319. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  320. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  321. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  322. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  323. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  324. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
  325. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  326. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  327. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
  328. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  329. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
  330. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
  331. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  332. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  333. package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
  334. package/node_modules/pi-mcp-adapter/README.md +51 -6
  335. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  336. package/node_modules/pi-mcp-adapter/cli.js +188 -5
  337. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  338. package/node_modules/pi-mcp-adapter/config.ts +28 -10
  339. package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
  340. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  341. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  342. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  343. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  344. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  345. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  346. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  347. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  348. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  349. package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
  350. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  351. package/node_modules/pi-mcp-adapter/package.json +4 -1
  352. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  353. package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
  354. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  355. package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
  356. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  357. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  358. package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
  359. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  360. package/node_modules/pi-mcp-adapter/types.ts +95 -10
  361. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  362. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  363. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  364. package/node_modules/pi-web-access/README.md +38 -14
  365. package/node_modules/pi-web-access/anysearch.ts +4 -2
  366. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  367. package/node_modules/pi-web-access/brave.ts +16 -4
  368. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  369. package/node_modules/pi-web-access/curator-page.ts +8 -3
  370. package/node_modules/pi-web-access/curator-server.ts +5 -1
  371. package/node_modules/pi-web-access/exa.ts +18 -7
  372. package/node_modules/pi-web-access/extract.ts +134 -37
  373. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  374. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  375. package/node_modules/pi-web-access/gemini-search.ts +33 -7
  376. package/node_modules/pi-web-access/index.ts +160 -104
  377. package/node_modules/pi-web-access/package.json +2 -2
  378. package/node_modules/pi-web-access/page-query.ts +4 -2
  379. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  380. package/node_modules/pi-web-access/parallel.ts +2 -2
  381. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  382. package/node_modules/pi-web-access/serper.ts +211 -0
  383. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  384. package/node_modules/pi-web-access/summary-review.ts +51 -23
  385. package/node_modules/pi-web-access/tavily.ts +16 -4
  386. package/node_modules/pi-web-access/utils.ts +87 -0
  387. package/node_modules/pi-web-access/valyu.ts +199 -0
  388. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  389. package/package.json +13 -13
@@ -13,7 +13,7 @@ import path from "node:path";
13
13
  import { getGlobalPiLensDir, getProjectIgnoreGlobs, isPathIgnoredByProject, } from "../file-utils.js";
14
14
  import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
15
15
  import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
16
- import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, } from "../path-utils.js";
16
+ import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, isWindowsPath, pathsEqual, } from "../path-utils.js";
17
17
  import { ensureTool, getToolEnvironment, getToolPath, } from "../installer/index.js";
18
18
  import { resolveOpengrepConfig } from "../opengrep-config.js";
19
19
  import { isZizmorAuditTarget, resolveZizmorGitHubToken } from "../zizmor-config.js";
@@ -78,17 +78,22 @@ const PROJECT_BOUNDARY_MARKERS = [
78
78
  // with FALLBACK_PROJECT_MARKERS, the shared fallback root-policy marker set.
79
79
  const loggedRootCeilingClamps = new Set();
80
80
  function isSameOrWithin(ancestor, candidate) {
81
- const relative = path.relative(path.resolve(ancestor), path.resolve(candidate));
82
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
81
+ const windowsShaped = isWindowsPath(ancestor) || isWindowsPath(candidate);
82
+ const pathApi = windowsShaped ? path.win32 : path;
83
+ const relative = pathApi.relative(pathApi.resolve(ancestor), pathApi.resolve(candidate));
84
+ return (relative === "" ||
85
+ (!relative.startsWith("..") && !pathApi.isAbsolute(relative)));
83
86
  }
84
87
  /** Enforce the session cwd as the hard boundary for LSP client root selection. */
85
88
  export function enforceLspRootCeiling(root, sessionCwd, filePath) {
86
- const resolvedRoot = path.resolve(root);
87
- const resolvedCwd = path.resolve(sessionCwd);
89
+ const windowsShaped = isWindowsPath(root) || isWindowsPath(sessionCwd);
90
+ const pathApi = windowsShaped ? path.win32 : path;
91
+ const resolvedRoot = pathApi.resolve(root);
92
+ const resolvedCwd = pathApi.resolve(sessionCwd);
88
93
  // Callers may explicitly inspect an out-of-session file (notably isolated
89
94
  // tests and API consumers). The cwd ceiling governs roots for files that are
90
95
  // actually inside the declared session project.
91
- if (filePath && !isSameOrWithin(resolvedCwd, path.resolve(filePath))) {
96
+ if (filePath && !isSameOrWithin(resolvedCwd, pathApi.resolve(filePath))) {
92
97
  return resolvedRoot;
93
98
  }
94
99
  if (isSameOrWithin(resolvedCwd, resolvedRoot))
@@ -187,6 +192,15 @@ function canInstall(allowInstall) {
187
192
  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);
188
193
  const directLspCommandUnavailableUntil = new Map();
189
194
  const directLspCommandSkipLoggedUntil = new Map();
195
+ /** Re-arm direct-command availability for the next session. */
196
+ export function resetDirectLspCommandAvailability() {
197
+ directLspCommandUnavailableUntil.clear();
198
+ directLspCommandSkipLoggedUntil.clear();
199
+ }
200
+ /** Test seam for seeding the real negative-cache path. */
201
+ export function _markDirectLspCommandUnavailableForTests(command) {
202
+ markDirectLspCommandUnavailable(command);
203
+ }
190
204
  function pruneExpiredDirectLspNegativeEntries(now = Date.now()) {
191
205
  for (const [command, until] of directLspCommandUnavailableUntil) {
192
206
  if (until <= now) {
@@ -707,7 +721,10 @@ async function markerExists(dir, pattern) {
707
721
  * NearestRoot(includePatterns, excludePatterns?) → RootFunction
708
722
  *
709
723
  * - includePatterns: file/dir names that signal the project root (e.g. ["package.json"])
710
- * - excludePatterns: if any of these exist in a directory, skip it (e.g. ["node_modules"])
724
+ * - excludePatterns: if any of these exist in a directory, SKIP that directory and
725
+ * keep walking up toward stopDir — it does not abort resolution. A directory that
726
+ * matches an exclude pattern is simply never returned as a root; the walk still
727
+ * continues past it looking for an include-pattern hit higher up (#1671).
711
728
  * - stopDir: walk stops here (defaults to filesystem root; set to project cwd for safety)
712
729
  *
713
730
  * Equivalent to createRootDetector; exported under both names for clarity.
@@ -717,6 +734,12 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
717
734
  // different servers (e.g. TypeScript vs Go) with different marker sets never
718
735
  // share entries. vi.resetModules() in tests resets module state between cases.
719
736
  const cache = new Map();
737
+ // Only cache successful hits. Undefined results are NOT cached so that a
738
+ // newly-created root marker (e.g. package.json or tsconfig.json scaffolded
739
+ // mid-session by the agent) is detected on the next call — the absent →
740
+ // present transition must work without a process restart. The uncached
741
+ // re-walk cost for configless repos is a known trade-off; bounding the
742
+ // walk with stopDir for in-cwd files is the tracked optimization (#1412).
720
743
  const inFlight = new Map();
721
744
  return async (file) => {
722
745
  // Cache key is the resolved directory — all files in the same dir share a root.
@@ -774,9 +797,6 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
774
797
  inFlight.set(dirKey, promise);
775
798
  try {
776
799
  const result = await promise;
777
- // Only cache successful hits. Undefined results are not cached so that
778
- // a newly-created root marker (e.g. package.json added mid-session) is
779
- // detected on the next call.
780
800
  if (result !== undefined)
781
801
  cache.set(dirKey, result);
782
802
  return result;
@@ -829,44 +849,185 @@ export async function tryDotnetToolInstall(tool) {
829
849
  return !updateResult.error && updateResult.status === 0;
830
850
  }
831
851
  /**
832
- * Locate tsserver.js — tries local project, then pi-lens managed TypeScript.
833
- * Returns the path to tsserver.js, or undefined if not found.
852
+ * #1412 M1: walk up from `startDir` (inclusive) looking for a file at
853
+ * `startDir/<relativeSegments>`, `dirname(startDir)/<relativeSegments>`, and so
854
+ * on — the same ancestor walk `findNativeTypeScriptLsp` uses, bounded the same
855
+ * way (`isAtOrAboveHomeDir`). A nested config root (e.g. a `cypress/tsconfig.json`
856
+ * LSP root inside a repo whose `node_modules` only exists at the repo root) must
857
+ * still resolve tooling installed at an ancestor, not just directly under the
858
+ * LSP root — mirrors how node module resolution itself walks up.
834
859
  */
835
- async function findTsserverPath(root, allowInstall) {
860
+ async function findAncestorFile(startDir, ...relativeSegments) {
861
+ return findAncestorFileAmong(startDir, [relativeSegments]);
862
+ }
863
+ /**
864
+ * Same ancestor walk as `findAncestorFile`, but checks every candidate
865
+ * relative-path in `candidateSegmentLists` AT EACH LEVEL before moving up —
866
+ * so the nearest ancestor wins regardless of which candidate name matched
867
+ * there, matching normal node_modules resolution priority (nearest install
868
+ * shadows a further one, never the reverse).
869
+ */
870
+ async function findAncestorFileAmong(startDir, candidateSegmentLists) {
836
871
  const fs = await import("node:fs/promises");
837
- const candidates = [
838
- path.join(root, "node_modules", "typescript", "lib", "tsserver.js"),
839
- path.join(process.cwd(), "node_modules", "typescript", "lib", "tsserver.js"),
872
+ let currentDir = path.resolve(startDir);
873
+ while (!isAtOrAboveHomeDir(currentDir)) {
874
+ for (const segments of candidateSegmentLists) {
875
+ const candidate = path.join(currentDir, ...segments);
876
+ try {
877
+ await fs.access(candidate);
878
+ return candidate;
879
+ }
880
+ catch {
881
+ /* not found at this level */
882
+ }
883
+ }
884
+ const parent = path.dirname(currentDir);
885
+ if (parent === currentDir)
886
+ break;
887
+ currentDir = parent;
888
+ }
889
+ return undefined;
890
+ }
891
+ /**
892
+ * A failed classic-compiler repair must not repeat within a session.
893
+ * `ensureTool` caches successful installs, so a repair that works
894
+ * short-circuits later calls on its own. A repair that fails leaves nothing
895
+ * behind, and `findTsserverPath` has three call sites (TypeScript, Vue,
896
+ * Svelte). Without this guard an offline or partial install re-runs a 120 s
897
+ * forced reinstall on every spawn.
898
+ *
899
+ * The guard is a plain module-level flag, so without an explicit re-arm it
900
+ * would latch for the whole extension-host process — a repair that failed
901
+ * once (transient registry hiccup) would stay unrepairable for every later
902
+ * session in that process. `resetClassicTsRepairGuard` re-arms it; callers
903
+ * wire that into `session_start` alongside the other per-session resets
904
+ * (#1570).
905
+ */
906
+ let classicTsRepairAttempted = false;
907
+ /** Re-arm the classic-repair guard so a new session gets its own attempt. */
908
+ export function resetClassicTsRepairGuard() {
909
+ classicTsRepairAttempted = false;
910
+ }
911
+ /** Test hook — clears the per-process classic-repair guard. */
912
+ export function _resetClassicTsRepairForTests() {
913
+ resetClassicTsRepairGuard();
914
+ }
915
+ /**
916
+ * Directories that may hold the TypeScript package next to a resolved `tsc`
917
+ * binary: `<bin>/../typescript` (npm-global layout) and
918
+ * `<bin>/../../typescript` (managed `node_modules/.bin` layout).
919
+ */
920
+ function typescriptDirsForTsc(tscPath) {
921
+ const binDir = path.dirname(tscPath);
922
+ return [
923
+ path.join(binDir, "..", "typescript"),
924
+ path.join(binDir, "..", "..", "typescript"),
840
925
  ];
841
- for (const p of candidates) {
926
+ }
927
+ /**
928
+ * Read the major version of the TypeScript package that backs a resolved
929
+ * `tsc` binary. Returns undefined when the version is unknowable: `ensureTool`
930
+ * returns the bare string `"tsc"` for a PATH hit, and `path.dirname("tsc")` is
931
+ * `"."`, so the candidates would go cwd-relative. Callers must not repair on
932
+ * an unknown version — a healthy global TypeScript 5.x would be reinstalled
933
+ * for nothing.
934
+ */
935
+ async function typescriptVersionForTsc(tscPath) {
936
+ if (!path.isAbsolute(tscPath))
937
+ return undefined;
938
+ for (const dir of typescriptDirsForTsc(tscPath)) {
939
+ let manifest;
842
940
  try {
843
- await fs.access(p);
844
- return p;
941
+ manifest = await readFile(path.join(dir, "package.json"), "utf8");
845
942
  }
846
943
  catch {
847
- /* not found */
944
+ continue;
848
945
  }
946
+ let version;
947
+ try {
948
+ const parsed = JSON.parse(manifest);
949
+ if (typeof parsed !== "object" ||
950
+ parsed === null ||
951
+ !("version" in parsed) ||
952
+ typeof parsed.version !== "string") {
953
+ return undefined;
954
+ }
955
+ version = parsed.version;
956
+ }
957
+ catch {
958
+ return undefined;
959
+ }
960
+ const majorText = version.split(".", 1)[0] ?? "";
961
+ const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
962
+ if (!Number.isFinite(major))
963
+ return undefined;
964
+ return { version, major };
849
965
  }
850
- // Discover the typescript install (PATH / npm-global) even when install is
851
- // disabled; only the download is gated by allowInstall.
852
- const tscPath = await ensureTool("typescript", {
853
- allowInstall: canInstall(allowInstall),
854
- });
855
- if (tscPath) {
856
- for (const p of [
857
- path.join(path.dirname(tscPath), "..", "typescript", "lib", "tsserver.js"),
858
- path.join(path.dirname(tscPath), "..", "..", "typescript", "lib", "tsserver.js"),
859
- ]) {
966
+ return undefined;
967
+ }
968
+ /**
969
+ * Locate tsserver.js — tries local project (walking up from root, #1412 M1),
970
+ * then process.cwd() as a last-resort fallback, then pi-lens managed
971
+ * TypeScript. Returns the path to tsserver.js, or undefined if not found.
972
+ */
973
+ async function findTsserverPath(root, allowInstall) {
974
+ const fs = await import("node:fs/promises");
975
+ const ancestorHit = await findAncestorFile(root, "node_modules", "typescript", "lib", "tsserver.js");
976
+ if (ancestorHit)
977
+ return ancestorHit;
978
+ const cwdCandidate = path.join(process.cwd(), "node_modules", "typescript", "lib", "tsserver.js");
979
+ try {
980
+ await fs.access(cwdCandidate);
981
+ return cwdCandidate;
982
+ }
983
+ catch {
984
+ /* not found */
985
+ }
986
+ const tsserverForTsc = async (tscPath) => {
987
+ if (!tscPath)
988
+ return undefined;
989
+ for (const dir of typescriptDirsForTsc(tscPath)) {
990
+ const candidate = path.join(dir, "lib", "tsserver.js");
860
991
  try {
861
- await fs.access(p);
862
- return p;
992
+ await fs.access(candidate);
993
+ return candidate;
863
994
  }
864
995
  catch {
865
996
  /* not found */
866
997
  }
867
998
  }
999
+ return undefined;
1000
+ };
1001
+ // Discover the TypeScript install (PATH / npm-global) even when installation
1002
+ // is disabled; only the download is gated by allowInstall.
1003
+ const installAllowed = canInstall(allowInstall);
1004
+ const discoveredTsc = await ensureTool("typescript", {
1005
+ allowInstall: installAllowed,
1006
+ });
1007
+ const discoveredTsserver = await tsserverForTsc(discoveredTsc);
1008
+ if (discoveredTsserver ||
1009
+ !discoveredTsc ||
1010
+ !installAllowed ||
1011
+ classicTsRepairAttempted) {
1012
+ return discoveredTsserver;
868
1013
  }
869
- return undefined;
1014
+ // Repair only a compiler we can prove is TypeScript 7+. TypeScript 7 dropped
1015
+ // lib/tsserver.js, so the classic wrapper cannot start against it. Any other
1016
+ // version — or a version we cannot read, such as a bare PATH `tsc` — is left
1017
+ // alone rather than force-reinstalled.
1018
+ const discoveredVersion = await typescriptVersionForTsc(discoveredTsc);
1019
+ if (!discoveredVersion || discoveredVersion.major < 7)
1020
+ return undefined;
1021
+ // An older managed tree took `latest` before the registry pinned the classic
1022
+ // compiler. Reinstall the pinned version once so that tree self-heals,
1023
+ // without deleting user or project-local TypeScript installations.
1024
+ classicTsRepairAttempted = true;
1025
+ logSessionStart(`lsp typescript: managed compiler resolved to TypeScript ${discoveredVersion.version}, which ships no tsserver.js; reinstalling pinned classic fallback`);
1026
+ const repairedTsc = await ensureTool("typescript", {
1027
+ allowInstall: true,
1028
+ forceReinstall: true,
1029
+ });
1030
+ return tsserverForTsc(repairedTsc);
870
1031
  }
871
1032
  /**
872
1033
  * TypeScript 7+ ships the native typescript-go language server through the
@@ -1046,10 +1207,17 @@ export async function detectPythonVenv(root) {
1046
1207
  }
1047
1208
  // --- Server Definitions ---
1048
1209
  const JS_TS_LSP_EXTENSIONS = KIND_EXTENSIONS["jsts"].filter((ext) => ext !== ".svelte" && ext !== ".vue");
1049
- // Marker set used for both the unbounded TypeScriptProjectRoot walk and the
1050
- // extension-bounded walk below. Kept in one place so both code paths look
1051
- // for the same project signals.
1052
- const TS_PROJECT_MARKERS = [
1210
+ // TypeScript identity and tooling discovery deliberately use separate marker
1211
+ // families. A governing config wins even when a package directory supplies
1212
+ // hoisted binaries. Keep configs out of PROJECT_BOUNDARY_MARKERS: #1373 still
1213
+ // coalesces a config-only nested root when an ancestor client was hosted first
1214
+ // (nested-config-first remains intentionally open-order-sensitive). #1412
1215
+ // accepted risk (M2, not fixed here): honoring nested config roots at all
1216
+ // enlarges the population of directories that can independently coalesce or
1217
+ // diverge under #1373's open-order sensitivity — the same pre-existing
1218
+ // blast-radius, just triggered by more roots than before.
1219
+ const TS_CONFIG_MARKERS = ["tsconfig.json", "jsconfig.json"];
1220
+ const TS_TOOLING_MARKERS = [
1053
1221
  "package-lock.json",
1054
1222
  "bun.lockb",
1055
1223
  "bun.lock",
@@ -1057,21 +1225,71 @@ const TS_PROJECT_MARKERS = [
1057
1225
  "yarn.lock",
1058
1226
  "package.json",
1059
1227
  ];
1060
- const TypeScriptProjectRoot = IgnoreHomeRoot(createRootDetector([...TS_PROJECT_MARKERS]));
1228
+ // #1412 M3: tsserver associates jsconfig.json with JS files only (its identity
1229
+ // probe reports a jsconfig-governed .ts file as unassociated) — so a TS-family
1230
+ // file under a jsconfig-only directory must NOT root there; keep walking up
1231
+ // for a real tsconfig.json. A JS-family file accepts either: tsconfig also
1232
+ // governs plain JS via `allowJs`, so accepting tsconfig for a .js file is
1233
+ // correct, and jsconfig obviously is too.
1234
+ const TS_FAMILY_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"]);
1235
+ function isTsFamilyFile(file) {
1236
+ return TS_FAMILY_EXTENSIONS.has(path.extname(file).toLowerCase());
1237
+ }
1238
+ function tsConfigMarkersForFile(file) {
1239
+ return isTsFamilyFile(file) ? ["tsconfig.json"] : TS_CONFIG_MARKERS;
1240
+ }
1241
+ // Two detector instances (not one parameterized by file) so each keeps its own
1242
+ // per-directory NearestRoot cache valid for its fixed marker set — a shared
1243
+ // cache keyed only by directory would conflate the TS-only and either-config
1244
+ // answers for the same directory.
1245
+ const TypeScriptConfigRootTsOnly = IgnoreHomeRoot(createRootDetector(["tsconfig.json"]));
1246
+ const TypeScriptConfigRootEither = IgnoreHomeRoot(createRootDetector([...TS_CONFIG_MARKERS]));
1247
+ const TypeScriptToolingRoot = IgnoreHomeRoot(createRootDetector([...TS_TOOLING_MARKERS]));
1248
+ async function findTypeScriptProjectRoot(file) {
1249
+ const configDetector = isTsFamilyFile(file)
1250
+ ? TypeScriptConfigRootTsOnly
1251
+ : TypeScriptConfigRootEither;
1252
+ const [configRoot, toolingRoot] = await Promise.all([
1253
+ configDetector(file),
1254
+ TypeScriptToolingRoot(file),
1255
+ ]);
1256
+ if (!configRoot)
1257
+ return toolingRoot;
1258
+ if (!toolingRoot)
1259
+ return configRoot;
1260
+ // A config inside (or beside) the nearest package governs its files. A
1261
+ // config above a nearer package must not erase that topology boundary.
1262
+ return isSameOrWithin(toolingRoot, configRoot) ? configRoot : toolingRoot;
1263
+ }
1061
1264
  /**
1062
- * Walk up from the file's directory looking for a TypeScript project marker,
1063
- * but stop at `extensionRootKey` so we never escape the .pi/agent/extensions
1064
- * boundary into a higher-up project (e.g. ~/.pi/agent/package.json which
1065
- * would pull every extension in the directory into one LSP workspace).
1265
+ * Walk up from the file's directory looking for a TypeScript project marker
1266
+ * (a governing config first, per-directory, per #1412 M3's extension-family
1267
+ * filter; else a tooling/lockfile marker), but stop at `extensionRootKey` so
1268
+ * we never escape the .pi/agent/extensions boundary into a higher-up project
1269
+ * (e.g. ~/.pi/agent/package.json which would pull every extension in the
1270
+ * directory into one LSP workspace).
1066
1271
  *
1067
- * Returns the nearest directory containing a marker, or undefined if none
1068
- * is found between the file and the extensions root inclusive.
1272
+ * #1412 L4: each directory returns immediately on its first match (config or
1273
+ * tooling) — there is no cross-level "nearest tooling root" to carry forward,
1274
+ * since a match always wins on the spot. Returns the nearest directory
1275
+ * containing a marker, or undefined if none is found between the file and the
1276
+ * extensions root inclusive.
1069
1277
  */
1070
1278
  async function findExtensionBoundedRoot(file, extensionRootKey) {
1071
1279
  const startDir = path.resolve(path.dirname(file));
1072
1280
  let currentDir = startDir;
1281
+ const configMarkers = tsConfigMarkersForFile(file);
1073
1282
  while (true) {
1074
- for (const pattern of TS_PROJECT_MARKERS) {
1283
+ for (const pattern of configMarkers) {
1284
+ try {
1285
+ await stat(path.join(currentDir, pattern));
1286
+ return currentDir;
1287
+ }
1288
+ catch {
1289
+ /* not found, try next marker */
1290
+ }
1291
+ }
1292
+ for (const pattern of TS_TOOLING_MARKERS) {
1075
1293
  try {
1076
1294
  await stat(path.join(currentDir, pattern));
1077
1295
  return currentDir;
@@ -1102,7 +1320,7 @@ async function hasAgentLevelProjectMarker(extensionRootKey) {
1102
1320
  const agentDir = path.dirname(extensionRootKey);
1103
1321
  if (!agentDir || agentDir === extensionRootKey)
1104
1322
  return false;
1105
- for (const pattern of TS_PROJECT_MARKERS) {
1323
+ for (const pattern of [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]) {
1106
1324
  try {
1107
1325
  await stat(path.join(agentDir, pattern));
1108
1326
  return true;
@@ -1133,7 +1351,7 @@ const TypeScriptRoot = DenoExcludeRoot(async (file) => {
1133
1351
  // analyze a lone .ts file with no package.json above or below).
1134
1352
  return undefined;
1135
1353
  }
1136
- const projectRoot = await TypeScriptProjectRoot(file);
1354
+ const projectRoot = await findTypeScriptProjectRoot(file);
1137
1355
  if (projectRoot)
1138
1356
  return projectRoot;
1139
1357
  return FileDirRoot(file);
@@ -1159,20 +1377,13 @@ export const TypeScriptServer = {
1159
1377
  let source = "direct";
1160
1378
  // TypeScript <=6 uses typescript-language-server + tsserver.js. Prefer a
1161
1379
  // project-local wrapper, then fall back to discovered/managed tooling.
1162
- let lspPath;
1163
- const localLsp = path.join(root, "node_modules", ".bin", "typescript-language-server");
1164
- const localLspCmd = path.join(root, "node_modules", ".bin", "typescript-language-server.cmd");
1165
- // Check for local version first (Windows .cmd first, then Unix)
1166
- for (const checkPath of [localLspCmd, localLsp]) {
1167
- try {
1168
- await fs.access(checkPath);
1169
- lspPath = checkPath;
1170
- break;
1171
- }
1172
- catch {
1173
- /* not found */
1174
- }
1175
- }
1380
+ // #1412 M1: walk up from root (Windows .cmd first, then Unix at each
1381
+ // level) — a nested config root's node_modules/.bin lives at an ancestor,
1382
+ // not necessarily directly under the LSP root.
1383
+ let lspPath = await findAncestorFileAmong(root, [
1384
+ ["node_modules", ".bin", "typescript-language-server.cmd"],
1385
+ ["node_modules", ".bin", "typescript-language-server"],
1386
+ ]);
1176
1387
  // Fall back to a discovered or managed install. ensureTool() runs PATH /
1177
1388
  // npm-global discovery even when install is disabled (only the download is
1178
1389
  // gated by canInstall), so a globally-installed typescript-language-server
@@ -1372,15 +1583,120 @@ export const GoServer = {
1372
1583
  return { ...result, initialization: { ui: { semanticTokens: true } } };
1373
1584
  },
1374
1585
  };
1375
- async function hasWorkspaceSection(cargoPath) {
1586
+ async function readTextFileOrUndefined(filePath) {
1376
1587
  try {
1377
- const { readFile } = await import("node:fs/promises");
1378
- const content = await readFile(cargoPath, "utf-8");
1379
- return /^\s*\[workspace\]/m.test(content);
1588
+ return await readFile(filePath, "utf-8");
1380
1589
  }
1381
1590
  catch {
1591
+ return undefined;
1592
+ }
1593
+ }
1594
+ /**
1595
+ * Slice out ONE top-level TOML table's raw body — from its `[name]` heading
1596
+ * to the next top-level `[...]`/`[[...]]` heading or EOF. `members`/`exclude`
1597
+ * must be read from the `[workspace]` table specifically: `[package]` has its
1598
+ * OWN `exclude` key (the standard cargo-publish exclude list, conventionally
1599
+ * written above `[workspace]` in a virtual-manifest-less root crate), and a
1600
+ * whole-file regex would misread it as workspace membership (#1671 F4).
1601
+ */
1602
+ function extractTomlTableSection(content, tableName) {
1603
+ const heading = new RegExp(`^\\[${tableName}\\][ \\t]*(?:#.*)?$`, "m");
1604
+ const match = heading.exec(content);
1605
+ if (!match)
1606
+ return "";
1607
+ const rest = content.slice(match.index + match[0].length);
1608
+ const nextHeading = rest.match(/^\[{1,2}[^\]]+\]{1,2}[ \t]*(?:#.*)?$/m);
1609
+ return nextHeading?.index !== undefined ? rest.slice(0, nextHeading.index) : rest;
1610
+ }
1611
+ function parseTomlStringArray(content, key) {
1612
+ const match = content.match(new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*\\[([\\s\\S]*?)\\]`, "m"));
1613
+ if (!match)
1614
+ return [];
1615
+ return [...match[1].matchAll(/"([^"]*)"|'([^']*)'/g)].map((m) => (m[1] ?? m[2] ?? "").trim());
1616
+ }
1617
+ /** Turn one `/`-delimited glob SEGMENT into a regex source: `*` matches any
1618
+ * run of characters within the segment, `?` matches exactly one character,
1619
+ * everything else is escaped and literal. */
1620
+ function segmentGlobToRegExpSource(segment) {
1621
+ let out = "";
1622
+ for (const ch of segment) {
1623
+ if (ch === "*")
1624
+ out += "[^/]*";
1625
+ else if (ch === "?")
1626
+ out += "[^/]";
1627
+ else
1628
+ out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
1629
+ }
1630
+ return out;
1631
+ }
1632
+ /**
1633
+ * A `members`/`exclude` entry names a path exactly, or globs it segment by
1634
+ * segment: `*` and `?` inside a segment work at any depth — one wildcard
1635
+ * segment (`crates/*`), a bare `*`, or several chained together for a
1636
+ * deeper fixed-depth layout — matching cargo's own `glob`-crate semantics
1637
+ * for a fixed number of path components.
1638
+ *
1639
+ * KNOWN LIMITATION (#1671 F6, documented rather than implemented): a
1640
+ * recursive `**` segment (matching a variable number of path components) is
1641
+ * NOT supported and never matches — cargo workspaces that rely on `**` to
1642
+ * pull in an arbitrarily-nested crate tree will under-hoist (the crate stays
1643
+ * independently rooted instead of joining the workspace). This is
1644
+ * deliberately out of #1671's scope: the common, and the issue's fixture,
1645
+ * shape is an explicit fixed-depth `members` list.
1646
+ */
1647
+ function matchesCargoWorkspacePattern(pattern, relativePath) {
1648
+ const normalized = pattern.replace(/\/+$/, "");
1649
+ if (normalized.includes("**"))
1650
+ return false;
1651
+ const patternSegments = normalized.split("/");
1652
+ const pathSegments = relativePath.split("/");
1653
+ if (patternSegments.length !== pathSegments.length)
1654
+ return false;
1655
+ return patternSegments.every((segment, i) => new RegExp(`^${segmentGlobToRegExpSource(segment)}$`).test(pathSegments[i]));
1656
+ }
1657
+ /**
1658
+ * Given an ancestor Cargo.toml's raw contents that already contains a
1659
+ * `[workspace]` table, decide whether it actually claims `childDir` as a
1660
+ * member. Cargo's own default is that a bare `[workspace]` with no `members`
1661
+ * key claims only the manifest's own package — a nested crate is NOT
1662
+ * implicitly swept in just because it sits underneath the workspace root on
1663
+ * disk. Without this check any Cargo.toml with a `[workspace]` table would
1664
+ * hoist every crate below it, including ones the workspace never declared
1665
+ * (#1671) — the same defect shape as an undeclared Maven sibling module.
1666
+ *
1667
+ * KNOWN LIMITATION (#1671 F7, documented rather than implemented): cargo also
1668
+ * treats a crate as an implicit workspace member when the workspace root's
1669
+ * OWN `[package]` has a `path = "..."` dependency on it and no explicit
1670
+ * `members` key is present at all. That dependency-graph inference is not
1671
+ * evaluated here — a workspace relying on it alone (no `members` key) reads
1672
+ * as claiming nothing, so its crates stay independently rooted rather than
1673
+ * being (correctly, per cargo) swept in. An explicit `members` list — the
1674
+ * common case and the one #1671's fixtures exercise — is unaffected.
1675
+ */
1676
+ function cargoWorkspaceDeclaresMember(workspaceContent, parentDir, childDir) {
1677
+ const relativePath = path.relative(parentDir, childDir).split(path.sep).join("/");
1678
+ if (relativePath === "" || relativePath.startsWith(".."))
1679
+ return false;
1680
+ const workspaceSection = extractTomlTableSection(workspaceContent, "workspace");
1681
+ const excluded = parseTomlStringArray(workspaceSection, "exclude");
1682
+ if (excluded.some((pattern) => matchesCargoWorkspacePattern(pattern, relativePath))) {
1382
1683
  return false;
1383
1684
  }
1685
+ const members = parseTomlStringArray(workspaceSection, "members");
1686
+ return members.some((pattern) => matchesCargoWorkspacePattern(pattern, relativePath));
1687
+ }
1688
+ /**
1689
+ * Bound a monorepo-hoist walk-up by the session ceiling: when `file` is inside
1690
+ * the session cwd, the walk must stop AT the cwd rather than climb past it —
1691
+ * matching enforceLspRootCeiling's clamp semantics but avoiding the wasted stat
1692
+ * calls of walking past a boundary we would clamp back down anyway. When
1693
+ * `file` is outside the session (isolated tests, out-of-session API callers),
1694
+ * enforceLspRootCeiling is a no-op, so the walk is left unbounded (`undefined`).
1695
+ */
1696
+ function sessionHoistCeiling(sessionCwd, file) {
1697
+ return isSameOrWithin(path.resolve(sessionCwd), path.resolve(file))
1698
+ ? path.resolve(sessionCwd)
1699
+ : undefined;
1384
1700
  }
1385
1701
  function RustWorkspaceRoot() {
1386
1702
  const crateRoot = createRootDetector(["Cargo.toml", "Cargo.lock"]);
@@ -1388,21 +1704,131 @@ function RustWorkspaceRoot() {
1388
1704
  const root = await crateRoot(file);
1389
1705
  if (!root)
1390
1706
  return undefined;
1707
+ const sessionCwd = process.cwd();
1708
+ // The walk-up for an ancestor Cargo.toml with a [workspace] table stays
1709
+ // bounded by the same session ceiling the crate-root lookup above already
1710
+ // enforces (enforceLspRootCeiling) — a monorepo hoist must never cross the
1711
+ // session boundary just because a workspace manifest happens to sit above
1712
+ // it (#1671).
1713
+ const stop = sessionHoistCeiling(sessionCwd, file);
1391
1714
  let current = root;
1392
1715
  const fsRoot = path.parse(current).root;
1393
1716
  while (true) {
1717
+ if (stop !== undefined && current === stop)
1718
+ break;
1394
1719
  const parent = path.dirname(current);
1395
1720
  if (parent === current || parent === fsRoot)
1396
1721
  break;
1397
- const parentCargo = path.join(parent, "Cargo.toml");
1398
- if (await hasWorkspaceSection(parentCargo)) {
1399
- return parent;
1722
+ const parentCargoPath = path.join(parent, "Cargo.toml");
1723
+ const parentCargoContent = await readTextFileOrUndefined(parentCargoPath);
1724
+ if (parentCargoContent !== undefined && /^\s*\[workspace\]/m.test(parentCargoContent)) {
1725
+ // Test membership against the ORIGINAL crate dir (`root`), never the
1726
+ // walk cursor (`current`, which may already have climbed through
1727
+ // gap directories that have no Cargo.toml of their own) — a
1728
+ // `members = ["crates/foo"]` or `["crates/*"]` entry is relative to
1729
+ // the workspace root, spanning the whole gap in one hop (#1671 F1).
1730
+ if (cargoWorkspaceDeclaresMember(parentCargoContent, parent, root)) {
1731
+ return enforceLspRootCeiling(parent, sessionCwd, file);
1732
+ }
1733
+ // A workspace root exists here but its `members`/`exclude` tables do
1734
+ // not claim this crate — stop climbing; the crate stays independently
1735
+ // rooted rather than being swept into a workspace it opted out of.
1736
+ break;
1400
1737
  }
1401
1738
  current = parent;
1402
1739
  }
1403
1740
  return root;
1404
1741
  };
1405
1742
  }
1743
+ /**
1744
+ * Resolve whether `parentPomPath`'s <modules> block declares `childDir` as one
1745
+ * of its member modules. Maven multi-module hoisting must only chain through
1746
+ * poms that actually declare the child (an undeclared sibling directory that
1747
+ * merely happens to sit next to a parent pom stays independently rooted) —
1748
+ * this is the "Maven module-chain verification" #1671 asks for, as opposed to
1749
+ * rust-analyzer's simpler "any ancestor [workspace] wins" hoist.
1750
+ */
1751
+ /**
1752
+ * Strip XML comments to a fixed point rather than in one pass: a single
1753
+ * `.replace()` can leave a residual `<!--` behind on adversarially-nested
1754
+ * input (CodeQL flags this class as "incomplete multi-character
1755
+ * sanitization" — the removal of one comment can expose a delimiter that
1756
+ * was itself inside another). Looping until nothing changes closes that gap.
1757
+ */
1758
+ function stripXmlComments(content) {
1759
+ let result = content;
1760
+ let previous;
1761
+ do {
1762
+ previous = result;
1763
+ result = result.replace(/<!--[\s\S]*?-->/g, "");
1764
+ } while (result !== previous);
1765
+ return result;
1766
+ }
1767
+ async function declaresMavenModule(parentDir, parentPomPath, childDir) {
1768
+ try {
1769
+ const content = stripXmlComments(await readFile(parentPomPath, "utf-8"));
1770
+ const modulesBlock = content.match(/<modules>([\s\S]*?)<\/modules>/);
1771
+ if (!modulesBlock)
1772
+ return false;
1773
+ const resolvedChild = path.resolve(childDir);
1774
+ for (const match of modulesBlock[1].matchAll(/<module>\s*([^<]+?)\s*<\/module>/g)) {
1775
+ // Case-insensitive / realpath-aware compare (#1671 F8): a declared
1776
+ // `<module>Foo</module>` must still match a directory actually named
1777
+ // `foo` on a case-insensitive filesystem, the same class of check the
1778
+ // rest of this codebase does via `pathsEqual` (#1139/#1150).
1779
+ if (pathsEqual(path.resolve(parentDir, match[1]), resolvedChild))
1780
+ return true;
1781
+ }
1782
+ return false;
1783
+ }
1784
+ catch {
1785
+ return false;
1786
+ }
1787
+ }
1788
+ function JavaWorkspaceRoot() {
1789
+ const moduleRoot = createRootDetector(["pom.xml", "build.gradle", ".classpath"]);
1790
+ return async (file) => {
1791
+ const root = await moduleRoot(file);
1792
+ if (!root)
1793
+ return undefined;
1794
+ // Only a Maven (pom.xml) module chain-hoists here — Gradle/.classpath module
1795
+ // roots are returned as found; multi-module Gradle wiring (settings.gradle)
1796
+ // is a different shape and out of scope for #1671.
1797
+ if (!(await markerExists(root, "pom.xml")))
1798
+ return root;
1799
+ const sessionCwd = process.cwd();
1800
+ const stop = sessionHoistCeiling(sessionCwd, file);
1801
+ // `current` is the walk CURSOR — it climbs over gap directories (ones
1802
+ // with no pom.xml of their own, e.g. a `<module>sub/dir</module>` entry
1803
+ // spanning more than one filesystem level). `lastHop` is the last
1804
+ // CONFIRMED module boundary; it is what the next ancestor pom's
1805
+ // <modules> must declare, and it is what actually gets returned — never
1806
+ // the cursor, which can land on a bare gap directory that is not itself
1807
+ // a valid module root (#1671 F2).
1808
+ let current = root;
1809
+ let lastHop = root;
1810
+ const fsRoot = path.parse(current).root;
1811
+ while (true) {
1812
+ if (stop !== undefined && current === stop)
1813
+ break;
1814
+ const parent = path.dirname(current);
1815
+ if (parent === current || parent === fsRoot)
1816
+ break;
1817
+ if (await markerExists(parent, "pom.xml")) {
1818
+ const parentPom = path.join(parent, "pom.xml");
1819
+ if (!(await declaresMavenModule(parent, parentPom, lastHop)))
1820
+ break;
1821
+ current = parent;
1822
+ lastHop = parent;
1823
+ continue;
1824
+ }
1825
+ // No pom.xml here — a gap directory, not a module boundary. Keep
1826
+ // climbing without advancing `lastHop`.
1827
+ current = parent;
1828
+ }
1829
+ return enforceLspRootCeiling(lastHop, sessionCwd, file);
1830
+ };
1831
+ }
1406
1832
  export const RustServer = {
1407
1833
  id: "rust",
1408
1834
  name: "rust-analyzer",
@@ -1632,7 +2058,7 @@ export const JavaServer = createInteractiveServer({
1632
2058
  id: "java",
1633
2059
  name: "JDT Language Server",
1634
2060
  extensions: KIND_EXTENSIONS["java"],
1635
- root: RootWithFallback(createRootDetector(["pom.xml", "build.gradle", ".classpath"])),
2061
+ root: RootWithFallback(JavaWorkspaceRoot()),
1636
2062
  language: "java",
1637
2063
  command: () => process.env.JDTLS_PATH || "jdtls",
1638
2064
  args: (root) => createLombokJdtlsArgs(root),
@@ -1837,6 +2263,20 @@ export const ClojureServer = {
1837
2263
  }, options?.allowInstall);
1838
2264
  },
1839
2265
  };
2266
+ export const CueServer = {
2267
+ id: "cue",
2268
+ name: "CUE Language Server",
2269
+ extensions: KIND_EXTENSIONS["cue"],
2270
+ root: RootWithFallback(createRootDetector(["cue.mod", ".git"])),
2271
+ async spawn(root, options) {
2272
+ return resolveAndLaunch({
2273
+ candidates: ["cue"],
2274
+ args: ["lsp", "serve"],
2275
+ cwd: root,
2276
+ managedToolId: "cue",
2277
+ }, options?.allowInstall);
2278
+ },
2279
+ };
1840
2280
  export const TerraformServer = {
1841
2281
  id: "terraform",
1842
2282
  name: "Terraform LSP",
@@ -2215,6 +2655,13 @@ export const AstGrepServer = {
2215
2655
  // (it falls back to napi when the ast-grep binary is absent — Gate B).
2216
2656
  root: RootWithFallback(createRootDetector(["sgconfig.yml", "sgconfig.yaml"]), RootWithFallback(NearestRoot([".git"]), async () => process.cwd())),
2217
2657
  availabilityKey: "ast-grep",
2658
+ // #1714: the one auxiliary with a measured wedge ceiling. A `lens_diagnostics
2659
+ // mode=full` sweep of 225 files drove this server into an unrecoverable stall
2660
+ // twice in two exposures (writes outstanding 5.9 s and 9.4 s, then a forced
2661
+ // shutdown that timed out both the request and the exit notify). It re-parses
2662
+ // the whole file on every didOpen, so it absorbs a sweep more slowly than the
2663
+ // other scanners — hold it to half the shared default.
2664
+ notifyInflightLimit: 4,
2218
2665
  // First scan of a session compiles the rules.
2219
2666
  initializeTimeoutMs: 15000,
2220
2667
  async spawn(root, options) {
@@ -2377,6 +2824,7 @@ export const LSP_SERVERS = [
2377
2824
  MarksmanServer,
2378
2825
  OCamlServer,
2379
2826
  ClojureServer,
2827
+ CueServer,
2380
2828
  TerraformServer,
2381
2829
  NixServer,
2382
2830
  BashServer,