@herbertgao/pi-extensions 2026.8.4 → 2026.8.5

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 (288) hide show
  1. package/README.md +4 -4
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  13. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  14. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  15. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  16. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  20. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  21. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  22. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  25. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  30. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  31. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  32. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  33. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  34. package/node_modules/pi-lens/README.md +13 -0
  35. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  36. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  37. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  39. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  40. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  41. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  42. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  43. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  44. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  45. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  46. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  47. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  51. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  52. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  53. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  54. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  56. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  57. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  58. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  59. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  60. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  61. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  63. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  64. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  66. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  67. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  68. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  69. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  70. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  71. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  72. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  73. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  74. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  75. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  85. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  86. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  87. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  99. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  100. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  101. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  102. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  103. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  104. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  105. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  106. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  107. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  108. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  109. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  110. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  111. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  112. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  113. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  114. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  115. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  116. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  117. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  118. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  119. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  120. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  121. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  122. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  123. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  124. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  125. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  126. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  127. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  128. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  129. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  130. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  131. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  132. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  134. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  135. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  136. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  137. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  138. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  139. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  140. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  141. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  142. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  143. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  144. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  145. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  146. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  147. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  148. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  149. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  150. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  151. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  152. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  153. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  154. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  155. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  156. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  157. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  158. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  159. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  160. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  161. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  162. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  163. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  164. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  165. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  166. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  167. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  168. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  169. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  170. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  171. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  172. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  173. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  174. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  175. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  177. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  178. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  179. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  180. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  181. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  182. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  183. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  184. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  185. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  186. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  187. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  188. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  189. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  190. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  191. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  192. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  193. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  194. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  196. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  197. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  198. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  199. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  200. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  201. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  202. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  203. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  204. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  205. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  210. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  211. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  212. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  213. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  214. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  215. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  216. package/node_modules/pi-lens/dist/index.js +49361 -36888
  217. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  218. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  219. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  220. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  221. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  222. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  223. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  224. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  225. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  226. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  227. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  228. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  229. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  230. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  231. package/node_modules/pi-lens/docs/fable.md +27 -0
  232. package/node_modules/pi-lens/docs/features.md +31 -2
  233. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  234. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  235. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  236. package/node_modules/pi-lens/docs/mcp.md +37 -4
  237. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  238. package/node_modules/pi-lens/docs/settings.md +12 -1
  239. package/node_modules/pi-lens/docs/tools.md +4 -1
  240. package/node_modules/pi-lens/docs/usage.md +18 -0
  241. package/node_modules/pi-lens/package.json +10 -10
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  252. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  253. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  254. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  255. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  256. package/node_modules/pi-mcp-adapter/README.md +34 -3
  257. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  258. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  259. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  260. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  261. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  262. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  263. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  264. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  265. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  266. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  267. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  268. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  269. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  270. package/node_modules/pi-mcp-adapter/package.json +3 -3
  271. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  272. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  273. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  274. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  275. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  276. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  277. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  278. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  279. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  280. package/package.json +11 -12
  281. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  282. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  283. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  284. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  285. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  286. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  287. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  288. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -1,16 +1,62 @@
1
1
  /**
2
2
  * Shared atomic tmp+rename file writer (closes #762).
3
3
  *
4
- * The `${target}.tmp-${process.pid}` + `renameSync` shape was independently
5
- * hand-rolled in five places (`instance-registry.ts`, `session-state-store.ts`,
4
+ * The `${target}.tmp-…` + `renameSync` shape was independently hand-rolled in
5
+ * five places (`instance-registry.ts`, `session-state-store.ts`,
6
6
  * `recent-touches.ts`, `review-graph/builder.ts`, `diagnostic-dispositions.ts`)
7
- * as each one picked up the need for a cross-process reader to never observe a
8
- * partially-written file: `rename()` replaces the destination atomically on
9
- * both POSIX and Windows (libuv uses `MOVEFILE_REPLACE_EXISTING`), so a
10
- * concurrent reader always sees either the fully-old or fully-new file, never
11
- * a torn write. Five independent copies of the same shape invite drift (e.g.
12
- * forgetting the tmp-file cleanup on the failure path) — this module is the
13
- * single implementation the rest re-use.
7
+ * as each one picked up the need for a reader to never observe a
8
+ * partially-written file. Five independent copies of the same shape invite
9
+ * drift (e.g. forgetting the tmp-file cleanup on the failure path) — this
10
+ * module is the single implementation the rest re-use.
11
+ *
12
+ * ## What this guarantees (#1205)
13
+ *
14
+ * Data is written in full to a staging file that no other writer can name,
15
+ * then `rename()`d over the target. On POSIX, `rename()` replaces the
16
+ * destination atomically by construction. On Windows, libuv uses
17
+ * `MoveFileEx` with `MOVEFILE_REPLACE_EXISTING`, which is atomic in practice
18
+ * on a same-volume NTFS replace but — unlike POSIX `rename()` — is not a
19
+ * formally guaranteed atomic primitive (`ReplaceFile` /
20
+ * `SetFileInformationByHandle` are the stronger APIs). Therefore, modulo that
21
+ * caveat:
22
+ *
23
+ * - **Crash-safe replacement.** A crash mid-write leaves the target holding
24
+ * its previous complete contents plus an orphan staging file; it never
25
+ * leaves the target half-written.
26
+ * - **Tear-free publication.** Any reader — same process or not — observes
27
+ * either the fully-old or the fully-new file at the target path. Because
28
+ * the staging name is unique *per call* (pid + thread id + a monotonic
29
+ * per-thread counter), this now holds for concurrent writes from the same
30
+ * process too, on any thread. Before #1205 the staging name was only
31
+ * `.tmp-${pid}`, so two in-flight same-process writes to one target
32
+ * shared a staging inode; the first `rename` published it while the
33
+ * second writer was still writing into it, publishing a torn hybrid file.
34
+ *
35
+ * ## What this explicitly does NOT provide
36
+ *
37
+ * - **No read-modify-write isolation.** Concurrent read → mutate → write
38
+ * cycles still lose updates: each writer publishes the state it computed
39
+ * from the snapshot it read, and last-rename-wins silently discards the
40
+ * other's mutation. Callers needing this must serialize themselves (see
41
+ * `lspChildRemovalTail` in `instance-registry.ts`).
42
+ * - **No ordering.** Nothing sequences concurrent writers, within or across
43
+ * processes. The winner is whichever `rename` lands last, which is not
44
+ * necessarily the write that started or finished last.
45
+ * - **No mutual exclusion.** This is not a lock. Overlapping writes are
46
+ * permitted and expected; only *torn* output is prevented.
47
+ * - **No fsync/durability guarantee.** Neither the staging file nor the
48
+ * parent directory is fsync'd, so a power loss (as opposed to a process
49
+ * crash) may lose the rename.
50
+ * - **No self-reaping.** A process killed between the staging write and the
51
+ * rename leaves an orphan staging file. The next session_start performs a
52
+ * bounded, liveness-checked sweep through the instance-reaper lifecycle
53
+ * seam (#1228); this writer never watches or reaps its own files.
54
+ * - **Windows-specific loss under `bestEffort: true`.** `MoveFileEx` with
55
+ * `MOVEFILE_REPLACE_EXISTING` fails if a reader holds the destination
56
+ * open without `FILE_SHARE_DELETE`. On Windows this silently drops the
57
+ * write (swallowed like any other `bestEffort: true` failure); the
58
+ * identical code path succeeds on POSIX, where `rename()` does not care
59
+ * whether a reader has the destination open.
14
60
  *
15
61
  * Per-write-site error policy varies and is NOT a detail this helper should
16
62
  * paper over:
@@ -29,8 +75,62 @@
29
75
  * it swallowed. See the #757 CHANGELOG entry for the full reasoning.
30
76
  */
31
77
  import * as fs from "node:fs";
78
+ export { STAGE_TMP_PATTERN, stageOwnerPidFromName, stagePathFor, } from "./atomic-write-staging.js";
79
+ import { stagePathFor } from "./atomic-write-staging.js";
80
+ /**
81
+ * Monotonic counter that makes each staging path unique per CALL, not merely
82
+ * per process (#1205).
83
+ *
84
+ * A counter is used rather than `randomBytes`: it is allocation-free and
85
+ * synchronous on what are hot per-turn/per-touch write paths, and it is
86
+ * *exactly* as unique as needed — collisions only matter between two writes
87
+ * live at the same instant, and a monotonic counter makes those impossible by
88
+ * construction rather than merely improbable.
89
+ *
90
+ * The counter alone is per-THREAD, not per-process: a worker gets its own
91
+ * module instance and so its own `_stageSeq` starting at 0, while sharing
92
+ * `process.pid` with the main thread and every sibling worker. Two threads
93
+ * would therefore each mint `${target}.tmp-<samepid>-0` — the exact collision
94
+ * the counter exists to prevent. `threadId` (0 on the main thread, distinct
95
+ * per worker) closes that axis, so the three parts cover the three ways two
96
+ * writes can be concurrent: `process.pid` across processes, `threadId` across
97
+ * threads, `_stageSeq` within one thread (#1217).
98
+ */
99
+ /**
100
+ * Staging path for one write call: `${targetPath}.tmp-<pid>-<threadId>-<seq>`.
101
+ *
102
+ * Exported so that callers which cannot use {@link writeFileAtomic} wholesale
103
+ * — `gzip-stage-write.ts` needs its own streaming pipeline — still source the
104
+ * *name* here rather than re-deriving the scheme, and so that sweepers of
105
+ * orphaned staging files stay in lockstep with it (see
106
+ * {@link STAGE_TMP_PATTERN}).
107
+ */
108
+ /**
109
+ * Matches the trailing staging-file marker produced by {@link stagePathFor},
110
+ * for the session-start sweeper that garbage-collects staging files orphaned
111
+ * by a crashed process (#1228). The writer itself does not reap or watch files.
112
+ *
113
+ * Only the current four-component shape is matched:
114
+ * `.tmp-<pid>-<threadId>-<seq>` (#1217). Older one- and two-number suffixes
115
+ * are deliberately left alone: their shape is indistinguishable from
116
+ * legitimate user filenames, so deletion safety outweighs reclaiming the
117
+ * handful of pre-#1208 leftovers.
118
+ *
119
+ * CAUTION for any sweeper adopting this pattern: it matches staging files by
120
+ * shape, not by writer identity, so it also matches this process's own
121
+ * still-being-written staging files. A safe sweeper must separately protect
122
+ * `process.pid` and use the shared conservative liveness check for foreign
123
+ * pids. The review-graph builder's narrower stage sweep intentionally remains
124
+ * separate because it owns a different `.stage-<pid>-<generation>` namespace.
125
+ *
126
+ * Anchoring is correct for both consumer styles in this repo today
127
+ * (matching against a full path or a bare basename), but this is confined to
128
+ * pi-lens's own cache directories — a consumer sweeping a directory it does
129
+ * not fully control should not assume every match is one of this module's
130
+ * staging files.
131
+ */
32
132
  /**
33
- * Synchronous atomic write of text or binary data to `${targetPath}.tmp-${pid}`,
133
+ * Synchronous atomic write of text or binary data to a per-call staging file,
34
134
  * then `fs.renameSync` over `targetPath`. On any failure (from either step),
35
135
  * attempts a best-effort `fs.rmSync(tmp, { force: true })` cleanup, then
36
136
  * either swallows (default) or rethrows per `options.bestEffort`.
@@ -41,7 +141,7 @@ import * as fs from "node:fs";
41
141
  */
42
142
  export function writeFileAtomic(targetPath, data, options) {
43
143
  const bestEffort = options?.bestEffort ?? true;
44
- const tmpPath = `${targetPath}.tmp-${process.pid}`;
144
+ const tmpPath = stagePathFor(targetPath);
45
145
  try {
46
146
  fs.writeFileSync(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
47
147
  fs.renameSync(tmpPath, targetPath);
@@ -60,12 +160,12 @@ export function writeFileAtomic(targetPath, data, options) {
60
160
  /**
61
161
  * Async counterpart of {@link writeFileAtomic}, built on `fs.promises` instead
62
162
  * of the sync `fs` API — for call sites that must not block the event loop
63
- * (e.g. writes on a hot per-turn/per-touch path). Same tmp-naming, cleanup,
64
- * and `bestEffort` semantics.
163
+ * (e.g. writes on a hot per-turn/per-touch path). Same per-call tmp-naming,
164
+ * cleanup, and `bestEffort` semantics.
65
165
  */
66
166
  export async function writeFileAtomicAsync(targetPath, data, options) {
67
167
  const bestEffort = options?.bestEffort ?? true;
68
- const tmpPath = `${targetPath}.tmp-${process.pid}`;
168
+ const tmpPath = stagePathFor(targetPath);
69
169
  try {
70
170
  await fs.promises.writeFile(tmpPath, data, typeof data === "string" ? "utf-8" : undefined);
71
171
  await fs.promises.rename(tmpPath, targetPath);
@@ -15,10 +15,8 @@
15
15
  */
16
16
  import * as nodeFs from "node:fs";
17
17
  import * as path from "node:path";
18
+ import { isReadableSourceFile } from "./file-kinds.js";
18
19
  import { countFileLines } from "./read-guard-tool-lines.js";
19
- // Source-ish extensions worth registering. Anchored end-check → linear (no
20
- // catastrophic backtracking).
21
- const READABLE_EXT_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|py|sh|rs|go|cs|java|kt|rb|php|c|cpp|cc|h|hpp|json|jsonc|yaml|yml|toml|md|txt|env|cfg|conf|ini|html|css|scss|less|xml|sql|vue|svelte)$/i;
22
20
  function stripQuotes(token) {
23
21
  if (token.length >= 2) {
24
22
  const first = token[0];
@@ -32,14 +30,123 @@ function stripQuotes(token) {
32
30
  function stripAnsi(value) {
33
31
  return value.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
34
32
  }
35
- function tokenizeSegment(segment) {
36
- const matches = segment.match(/'[^']*'|"(?:[^"\\]|\\.)*"|\S+/g);
37
- return matches ?? [];
33
+ export function tokenizeShellCommand(command) {
34
+ const segments = [];
35
+ let tokens = [];
36
+ let word = "";
37
+ let quote;
38
+ let escaped = false;
39
+ let unsupported = false;
40
+ let atTokenStart = true;
41
+ const flushWord = () => {
42
+ if (word)
43
+ tokens.push(word);
44
+ word = "";
45
+ atTokenStart = true;
46
+ };
47
+ const flushSegment = () => {
48
+ flushWord();
49
+ if (tokens.length > 0 || unsupported)
50
+ segments.push({ tokens, unsupported });
51
+ tokens = [];
52
+ unsupported = false;
53
+ atTokenStart = true;
54
+ };
55
+ for (let i = 0; i < command.length; i++) {
56
+ const ch = command[i];
57
+ const next = command[i + 1];
58
+ if (quote === "single") {
59
+ if (ch === "'")
60
+ quote = undefined;
61
+ else
62
+ word += ch;
63
+ continue;
64
+ }
65
+ if (quote === "double") {
66
+ if (escaped) {
67
+ word += ch;
68
+ escaped = false;
69
+ }
70
+ else if (ch === "\\")
71
+ escaped = true;
72
+ else if (ch === '"')
73
+ quote = undefined;
74
+ else
75
+ word += ch;
76
+ continue;
77
+ }
78
+ if (escaped) {
79
+ // A backslash-newline is a shell line continuation, not part of the
80
+ // command argument. Keeping the newline here makes a continued git command
81
+ // visible to command guards.
82
+ if (ch !== "\n")
83
+ word += ch;
84
+ escaped = false;
85
+ atTokenStart = false;
86
+ continue;
87
+ }
88
+ if (ch === "\\") {
89
+ // Bash uses backslash as a general escape, but command inputs on
90
+ // Windows routinely contain native paths (C:\\src\\file.ts). Preserve
91
+ // backslashes before ordinary path characters while still honoring
92
+ // shell escapes and line continuations.
93
+ if (next === "\n" || /[\s\\'\";$|&<>]/.test(next ?? "")) {
94
+ escaped = true;
95
+ }
96
+ else {
97
+ word += ch;
98
+ atTokenStart = false;
99
+ }
100
+ atTokenStart = false;
101
+ continue;
102
+ }
103
+ if (ch === "'") {
104
+ quote = "single";
105
+ atTokenStart = false;
106
+ continue;
107
+ }
108
+ if (ch === '"') {
109
+ quote = "double";
110
+ atTokenStart = false;
111
+ continue;
112
+ }
113
+ if (ch === "#" && atTokenStart) {
114
+ while (i + 1 < command.length && command[i + 1] !== "\n")
115
+ i++;
116
+ continue;
117
+ }
118
+ if (/\s/.test(ch)) {
119
+ flushWord();
120
+ continue;
121
+ }
122
+ if (ch === ";" || ch === "\n" || ch === "|" || ch === "&") {
123
+ flushWord();
124
+ if (ch === "|" && next === "|")
125
+ i++;
126
+ else if (ch === "&" && next === "&")
127
+ i++;
128
+ else if (ch === "|" || ch === "&")
129
+ unsupported = true;
130
+ flushSegment();
131
+ continue;
132
+ }
133
+ if (ch === "<" || ch === ">") {
134
+ flushWord();
135
+ unsupported = true;
136
+ continue;
137
+ }
138
+ word += ch;
139
+ atTokenStart = false;
140
+ }
141
+ if (quote || escaped)
142
+ unsupported = true;
143
+ flushSegment();
144
+ return segments;
38
145
  }
39
146
  /** Resolve a token to an absolute path if it looks like a source file. */
40
147
  function resolveCandidate(token, cwd) {
41
148
  const cleaned = stripQuotes(token);
42
- if (!cleaned || cleaned.startsWith("-") || !READABLE_EXT_RE.test(cleaned)) {
149
+ if (!cleaned || cleaned.startsWith("-") || !isReadableSourceFile(cleaned)) {
43
150
  return null;
44
151
  }
45
152
  return path.isAbsolute(cleaned) ? cleaned : path.resolve(cwd, cleaned);
@@ -52,8 +159,67 @@ function parseCountFlag(token) {
52
159
  const n = Number.parseInt(digits, 10);
53
160
  return Number.isFinite(n) && n > 0 ? n : undefined;
54
161
  }
55
- function splitSegments(command) {
56
- return command.split(/&&|\|\||[;|\n]/);
162
+ function commandSegments(command) {
163
+ return tokenizeShellCommand(command).map((segment) => segment.tokens);
164
+ }
165
+ /** Find redirect targets without treating quoted `>` characters as syntax. */
166
+ function extractRedirectTargets(command) {
167
+ const targets = [];
168
+ let quote;
169
+ let escaped = false;
170
+ for (let i = 0; i < command.length; i += 1) {
171
+ const ch = command[i];
172
+ if (quote === "single") {
173
+ if (ch === "'")
174
+ quote = undefined;
175
+ continue;
176
+ }
177
+ if (quote === "double") {
178
+ if (escaped)
179
+ escaped = false;
180
+ else if (ch === "\\")
181
+ escaped = true;
182
+ else if (ch === '"')
183
+ quote = undefined;
184
+ continue;
185
+ }
186
+ if (escaped) {
187
+ escaped = false;
188
+ continue;
189
+ }
190
+ if (ch === "\\") {
191
+ escaped = true;
192
+ continue;
193
+ }
194
+ if (ch === "'") {
195
+ quote = "single";
196
+ continue;
197
+ }
198
+ if (ch === '"') {
199
+ quote = "double";
200
+ continue;
201
+ }
202
+ if (ch !== ">")
203
+ continue;
204
+ while (i + 1 < command.length && /\s/.test(command[i + 1]))
205
+ i += 1;
206
+ if (command[i + 1] === ">")
207
+ i += 1;
208
+ while (i + 1 < command.length && /\s/.test(command[i + 1]))
209
+ i += 1;
210
+ let target = "";
211
+ for (i += 1; i < command.length; i += 1) {
212
+ const next = command[i];
213
+ if (/\s/.test(next) || next === ";" || next === "|" || next === "&") {
214
+ i -= 1;
215
+ break;
216
+ }
217
+ target += next;
218
+ }
219
+ if (target)
220
+ targets.push(target);
221
+ }
222
+ return targets;
57
223
  }
58
224
  /**
59
225
  * Extract the line ranges a bash command explicitly showed the agent.
@@ -93,11 +259,9 @@ export function extractReadPathsFromCommand(command, cwd) {
93
259
  seen.add(key);
94
260
  spans.push({ filePath: file.abs, offset, limit });
95
261
  };
96
- for (const rawSegment of splitSegments(command)) {
97
- const segment = rawSegment.trim();
98
- if (!segment)
262
+ for (const tokens of commandSegments(command)) {
263
+ if (tokens.length === 0)
99
264
  continue;
100
- const tokens = segment.split(/\s+/);
101
265
  const verb = path.basename(tokens[0] ?? "");
102
266
  const args = tokens.slice(1);
103
267
  if (["cat", "bat", "less", "more", "nl"].includes(verb)) {
@@ -247,8 +411,7 @@ function parseGrepLineWithoutFile(line, file) {
247
411
  function collectGrepCommandFiles(command, cwd) {
248
412
  const files = new Set();
249
413
  let hasLineNumberGrep = false;
250
- for (const rawSegment of splitSegments(command)) {
251
- const tokens = tokenizeSegment(rawSegment.trim());
414
+ for (const tokens of commandSegments(command)) {
252
415
  const verb = path.basename(stripQuotes(tokens[0] ?? ""));
253
416
  if (verb !== "grep" && verb !== "egrep" && verb !== "fgrep")
254
417
  continue;
@@ -311,15 +474,22 @@ export function extractWrittenPathsFromCommand(command, cwd) {
311
474
  if (abs)
312
475
  out.add(abs);
313
476
  };
314
- for (const rawSegment of splitSegments(command)) {
315
- const segment = rawSegment.trim();
316
- if (!segment)
477
+ for (const tokens of commandSegments(command)) {
478
+ if (tokens.length === 0)
479
+ continue;
480
+ // Redirect targets are collected by a quote-aware scanner. The shared
481
+ // tokenizer supplies the normalized command arguments; it deliberately
482
+ // does not expose shell redirection operators as arguments.
483
+ for (const target of extractRedirectTargets(command))
484
+ add(target);
485
+ // A command may contain multiple segments; only the first pass should
486
+ // attach redirects, otherwise targets would be duplicated harmlessly but
487
+ // needlessly rescanned.
488
+ break;
489
+ }
490
+ for (const tokens of commandSegments(command)) {
491
+ if (tokens.length === 0)
317
492
  continue;
318
- // Redirect targets: the token after `>` / `>>` (optionally prefixed by a
319
- // file descriptor like `2>` or `&>`, with or without a space).
320
- for (const m of segment.matchAll(/>>?\s*([^\s>|&]+)/g))
321
- add(m[1]);
322
- const tokens = segment.split(/\s+/);
323
493
  const verb = path.basename(tokens[0] ?? "");
324
494
  const args = tokens.slice(1);
325
495
  if (verb === "tee" || verb === "touch") {
@@ -7,12 +7,15 @@
7
7
  * Requires: npm install @biomejs/biome (or npx @biomejs/biome)
8
8
  * Docs: https://biomejs.dev/
9
9
  */
10
+ import { createSubsystemLogger } from "./extension-log.js";
10
11
  import * as fs from "node:fs";
11
12
  import * as path from "node:path";
12
13
  import { isFileKind } from "./file-kinds.js";
13
14
  import { getGlobalPiLensDir } from "./file-utils.js";
14
15
  import { findGlobalBinary } from "./package-manager.js";
15
16
  import { safeSpawnAsync } from "./safe-spawn.js";
17
+ import { biomeConfigArgs } from "./tool-policy.js";
18
+ import { resolveManagedToolClient } from "./dispatch/runners/utils/runner-helpers.js";
16
19
  // --- Client ---
17
20
  export class BiomeClient {
18
21
  biomeAvailable = null;
@@ -29,7 +32,7 @@ export class BiomeClient {
29
32
  log;
30
33
  constructor(verbose = false) {
31
34
  this.log = verbose
32
- ? (msg) => console.error(`[biome] ${msg}`)
35
+ ? createSubsystemLogger("biome")
33
36
  : () => { };
34
37
  }
35
38
  /**
@@ -87,7 +90,7 @@ export class BiomeClient {
87
90
  }
88
91
  async spawnBiomeAsync(args, timeout = 15000, cwd) {
89
92
  const { cmd, args: prefix } = await this.getBiomeBinary(cwd);
90
- return safeSpawnAsync(cmd, [...prefix, ...args], { timeout });
93
+ return safeSpawnAsync(cmd, [...prefix, ...args], { timeout, cwd });
91
94
  }
92
95
  /**
93
96
  * Ensure Biome is available, auto-installing if necessary.
@@ -112,27 +115,22 @@ export class BiomeClient {
112
115
  }
113
116
  }
114
117
  async doEnsureAvailable() {
115
- // Check if already available
116
- const result = await this.spawnBiomeAsync(["--version"], 10000);
117
- if (!result.error && result.status === 0) {
118
- this.biomeAvailable = true;
119
- return true;
120
- }
121
- // Auto-install via pi-lens installer
122
- this.log("Biome not found, attempting auto-install...");
123
- const { ensureTool } = await import("./installer/index.js");
124
- const installedPath = await ensureTool("biome");
125
- if (installedPath) {
126
- this.log(`Biome auto-installed: ${installedPath}`);
127
- // Set the installed path as the global fallback so every cwd
128
- // reaches it after its own per-package lookup misses.
129
- this.autoInstalledBinaryPath = installedPath;
130
- this.biomeAvailable = true;
131
- return true;
132
- }
133
- this.log("Biome auto-install failed");
134
- this.biomeAvailable = false;
135
- return false;
118
+ const outcome = await resolveManagedToolClient({
119
+ toolId: "biome",
120
+ cwd: process.cwd(),
121
+ probe: async () => {
122
+ const result = await this.spawnBiomeAsync(["--version"], 10000);
123
+ return !result.error && result.status === 0
124
+ ? { outcome: "success", value: true }
125
+ : { outcome: "missing" };
126
+ },
127
+ acceptInstalled: (installedPath) => {
128
+ this.autoInstalledBinaryPath = installedPath;
129
+ return true;
130
+ },
131
+ });
132
+ this.biomeAvailable = outcome.outcome === "success";
133
+ return this.biomeAvailable;
136
134
  }
137
135
  /**
138
136
  * Check if a file is supported by Biome
@@ -142,8 +140,10 @@ export class BiomeClient {
142
140
  }
143
141
  /**
144
142
  * Async auto-fix variant for pipeline use (non-blocking spawn).
143
+ * `cwd` is the dispatch language root (used for config discovery); when
144
+ * omitted it defaults to the file's directory.
145
145
  */
146
- async fixFileAsync(filePath) {
146
+ async fixFileAsync(filePath, cwd) {
147
147
  if (!(await this.ensureAvailable())) {
148
148
  return {
149
149
  success: false,
@@ -163,7 +163,11 @@ export class BiomeClient {
163
163
  }
164
164
  try {
165
165
  const before = await fs.promises.readFile(absolutePath, "utf-8");
166
- const result = await this.spawnBiomeAsync(["lint", "--write", absolutePath], 15000, path.dirname(absolutePath));
166
+ const configCwd = cwd ?? path.dirname(absolutePath);
167
+ // Shared config-args seam (#1247): the lint runner consumes the same
168
+ // builder, so `lint --write` can never drift to biome's default
169
+ // config when a user config or the package fallback exists.
170
+ const result = await this.spawnBiomeAsync(["lint", "--write", ...biomeConfigArgs(configCwd), absolutePath], 15000, configCwd);
167
171
  if (result.error) {
168
172
  return {
169
173
  success: false,
@@ -1,3 +1,4 @@
1
+ import { logExtension } from "./extension-log.js";
1
2
  let bootstrapPromise = null;
2
3
  /**
3
4
  * A stand-in for an analysis client whose module failed to load (an unresolved
@@ -28,11 +29,19 @@ export function degradedClient() {
28
29
  */
29
30
  async function logBootstrapFailures(failures) {
30
31
  for (const { name, err } of failures) {
31
- console.error(`[pi-lens] analyzer "${name}" disabled (degraded mode): ${err?.message ?? String(err)}`);
32
+ logExtension({
33
+ subsystem: "bootstrap",
34
+ message: `analyzer "${name}" disabled (degraded mode): ${err?.message ?? String(err)}`,
35
+ metadata: { analyzer: name },
36
+ });
32
37
  }
33
38
  try {
34
39
  const { collectInstallDiagnostics, formatInstallDiagnostics } = await import("./install-diagnostics.js");
35
- console.error(formatInstallDiagnostics(collectInstallDiagnostics(), failures[0]?.err));
40
+ logExtension({
41
+ subsystem: "bootstrap",
42
+ message: formatInstallDiagnostics(collectInstallDiagnostics(), failures[0]?.err),
43
+ metadata: { kind: "install_diagnostics" },
44
+ });
36
45
  }
37
46
  catch {
38
47
  // the per-analyzer lines above already named the failures
@@ -0,0 +1,34 @@
1
+ /** Small insertion-ordered LRU used for process-lifetime memo tables. */
2
+ export class BoundedLruCache {
3
+ maxEntries;
4
+ entries = new Map();
5
+ constructor(maxEntries) {
6
+ this.maxEntries = maxEntries;
7
+ }
8
+ get(key) {
9
+ const value = this.entries.get(key);
10
+ if (value !== undefined) {
11
+ this.entries.delete(key);
12
+ this.entries.set(key, value);
13
+ }
14
+ return value;
15
+ }
16
+ has(key) { return this.entries.has(key); }
17
+ set(key, value) {
18
+ this.entries.delete(key);
19
+ this.entries.set(key, value);
20
+ while (this.entries.size > this.maxEntries) {
21
+ const oldest = this.entries.keys().next().value;
22
+ if (oldest === undefined)
23
+ break;
24
+ this.entries.delete(oldest);
25
+ }
26
+ }
27
+ delete(key) { return this.entries.delete(key); }
28
+ clear() { this.entries.clear(); }
29
+ get size() { return this.entries.size; }
30
+ keys() { return this.entries.keys(); }
31
+ values() { return this.entries.values(); }
32
+ entriesArray() { return [...this.entries.entries()]; }
33
+ [Symbol.iterator]() { return this.entries[Symbol.iterator](); }
34
+ }