@herbertgao/pi-extensions 2026.8.3 → 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 (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +5 -8
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +15 -2
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -9,8 +9,11 @@
9
9
  import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
+ import { logSessionStart } from "../../../sessionstart-logger.js";
12
13
  import { getGlobalPiLensDir } from "../../../file-utils.js";
13
- import { ensureTool, isSpawnableCommand } from "../../../installer/index.js";
14
+ import { PathKeyedMap } from "../../../path-keyed-map.js";
15
+ import { normalizeEphemeralMapKey, normalizeMapKey, } from "../../../path-utils.js";
16
+ import { ensureTool, isSpawnableCommand, resetPathWalkMemo, } from "../../../installer/index.js";
14
17
  import { getServersForFileWithConfig, isServerDisabled, } from "../../../lsp/config.js";
15
18
  import { findGlobalBinary } from "../../../package-manager.js";
16
19
  import { safeSpawnAsync } from "../../../safe-spawn.js";
@@ -83,6 +86,102 @@ export function createVenvFinder(command, windowsExt = "", quoteWindows = false)
83
86
  return command;
84
87
  };
85
88
  }
89
+ /** Typed client-facing install seam for ordered/custom candidate probes. */
90
+ export async function resolveManagedToolClient(options) {
91
+ const probed = await options.probe();
92
+ if (probed.outcome !== "missing")
93
+ return probed;
94
+ if (!shouldAutoInstallTool(options.toolId))
95
+ return probed;
96
+ const state = installStateFor(options.cwd, options.toolId);
97
+ if (state.suppressed)
98
+ return probed;
99
+ const installed = await ensureTool(options.toolId);
100
+ if (!installed) {
101
+ noteInstallFailure(options.toolId, options.cwd);
102
+ return probed;
103
+ }
104
+ const value = await options.acceptInstalled(installed);
105
+ if (value === null) {
106
+ noteInstallFailure(options.toolId, options.cwd);
107
+ return { outcome: "non-installable" };
108
+ }
109
+ noteInstallSuccess(options.toolId, options.cwd);
110
+ return { outcome: "success", value };
111
+ }
112
+ /**
113
+ * Child environment for managed-installable tools. Keeping this beside the
114
+ * probe/install seam makes managed npm shims visible to every standalone
115
+ * client, rather than only to Knip (#1289).
116
+ */
117
+ export async function getManagedToolEnvironment(_toolId, cwd) {
118
+ let env;
119
+ try {
120
+ const { getToolEnvironment } = await import("../../../installer/index.js");
121
+ env = await getToolEnvironment();
122
+ }
123
+ catch {
124
+ // Installer-isolated unit tests historically mock only ensureTool. The
125
+ // ambient fallback preserves that isolation; production always exports it.
126
+ env = { ...process.env };
127
+ }
128
+ if (!cwd)
129
+ return env;
130
+ const separator = process.platform === "win32" ? ";" : ":";
131
+ const currentPath = env.PATH || env.Path || process.env.PATH || "";
132
+ const localBin = path.join(cwd, "node_modules", ".bin");
133
+ const augmentedPath = `${localBin}${separator}${currentPath}`;
134
+ return {
135
+ ...env,
136
+ PATH: augmentedPath,
137
+ ...(process.platform === "win32" ? { Path: augmentedPath } : {}),
138
+ };
139
+ }
140
+ /** Read-only managed/PATH discovery for spawn-time resolution memos. */
141
+ export async function discoverManagedTool(toolId) {
142
+ return (await ensureTool(toolId, { allowInstall: false })) ?? null;
143
+ }
144
+ // This is session-scoped state, not a process-global tool/path cache. The cwd
145
+ // key is normalized by PathKeyedMap and is cleared at session_start. A failed
146
+ // install must not become an install attempt on every eligible file/turn.
147
+ const installAttemptsByCwd = new PathKeyedMap(normalizeMapKey);
148
+ const resolveInstallInFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
149
+ // Checkers are created by runner modules and may also be created dynamically.
150
+ // Keep the session reset as a generation rather than retaining every checker
151
+ // reset closure forever.
152
+ let availabilityStateGeneration = 0;
153
+ function installStateFor(cwd, toolId) {
154
+ let states = installAttemptsByCwd.get(cwd);
155
+ if (!states) {
156
+ states = new Map();
157
+ installAttemptsByCwd.set(cwd, states);
158
+ }
159
+ let state = states.get(toolId);
160
+ if (!state) {
161
+ state = { attempts: 0, suppressed: false };
162
+ states.set(toolId, state);
163
+ }
164
+ return state;
165
+ }
166
+ function noteInstallFailure(toolId, cwd) {
167
+ const state = installStateFor(cwd, toolId);
168
+ state.attempts += 1;
169
+ state.suppressed = true;
170
+ logSessionStart(`dispatch availability ${toolId}: install attempt ${state.attempts} failed; suppressing retries until the next session or a successful install`);
171
+ }
172
+ function noteInstallSuccess(toolId, cwd) {
173
+ const states = installAttemptsByCwd.get(cwd);
174
+ states?.delete(toolId);
175
+ if (states?.size === 0)
176
+ installAttemptsByCwd.delete(cwd);
177
+ }
178
+ /** Reset availability/install suppression at the session boundary. */
179
+ export function resetDispatchAvailabilityState() {
180
+ installAttemptsByCwd.clear();
181
+ resolveInstallInFlightByCwd.clear();
182
+ resetPathWalkMemo();
183
+ availabilityStateGeneration += 1;
184
+ }
86
185
  /**
87
186
  * Create a cached availability checker for a command.
88
187
  * The checker will look for the command in venv first, then global.
@@ -93,49 +192,138 @@ export function createVenvFinder(command, windowsExt = "", quoteWindows = false)
93
192
  * every machine, so toolchains with a non-standard version command must override
94
193
  * this.
95
194
  */
96
- export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"]) {
97
- const cacheByCwd = new Map();
98
- const inFlightByCwd = new Map();
195
+ export function createAvailabilityChecker(command, windowsExt = "", versionArgs = ["--version"], options = {}) {
196
+ const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
197
+ const inFlightByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
198
+ let checkerGeneration = availabilityStateGeneration;
99
199
  const findCommand = createVenvFinder(command, windowsExt, true);
200
+ function ensureCurrentGeneration() {
201
+ if (checkerGeneration === availabilityStateGeneration)
202
+ return;
203
+ cacheByCwd.clear();
204
+ inFlightByCwd.clear();
205
+ checkerGeneration = availabilityStateGeneration;
206
+ }
207
+ const reset = () => {
208
+ cacheByCwd.clear();
209
+ inFlightByCwd.clear();
210
+ checkerGeneration = availabilityStateGeneration;
211
+ };
100
212
  function getCache(cwd) {
213
+ ensureCurrentGeneration();
101
214
  const key = path.resolve(cwd || process.cwd());
102
215
  const existing = cacheByCwd.get(key);
103
216
  if (existing)
104
217
  return existing;
105
- const created = { available: null, command: null };
218
+ const created = {
219
+ available: null,
220
+ command: null,
221
+ outcome: null,
222
+ };
106
223
  cacheByCwd.set(key, created);
107
224
  return created;
108
225
  }
109
226
  async function isAvailableAsync(cwd) {
227
+ ensureCurrentGeneration();
110
228
  const resolvedCwd = cwd || process.cwd();
111
229
  const cache = getCache(resolvedCwd);
112
- if (cache.available !== null)
113
- return cache.available;
230
+ if (cache.available === false)
231
+ return false;
232
+ if (cache.available === true && cache.command) {
233
+ if (await isSpawnableCommand(cache.command))
234
+ return true;
235
+ // Cached-positive spawn feedback: a removed absolute path or vanished
236
+ // PATH command must fall through to a fresh probe immediately.
237
+ cache.available = null;
238
+ cache.command = null;
239
+ cache.outcome = null;
240
+ }
114
241
  const key = path.resolve(resolvedCwd);
115
242
  const existing = inFlightByCwd.get(key);
116
243
  if (existing)
117
244
  return existing;
118
- const promise = (async () => {
245
+ const promiseGeneration = checkerGeneration;
246
+ let promise;
247
+ promise = (async () => {
248
+ const fastPath = options.fastPath?.();
249
+ if (fastPath) {
250
+ cache.available = true;
251
+ cache.outcome = "success";
252
+ cache.command = fastPath;
253
+ return true;
254
+ }
255
+ // A bad/removed workspace must not be mistaken for a missing tool and
256
+ // trigger an install. This async probe stays off the synchronous dispatch
257
+ // burst and makes the failure taxonomy explicit at the seam.
258
+ try {
259
+ const cwdStat = await fs.promises.stat(resolvedCwd);
260
+ if (!cwdStat.isDirectory()) {
261
+ cache.available = false;
262
+ cache.outcome = "non-installable";
263
+ return false;
264
+ }
265
+ }
266
+ catch {
267
+ cache.available = false;
268
+ cache.outcome = "non-installable";
269
+ return false;
270
+ }
119
271
  const cmd = findCommand(resolvedCwd);
272
+ const env = await options.environment?.(resolvedCwd);
120
273
  const result = await safeSpawnAsync(cmd, versionArgs, {
121
- timeout: 5000,
274
+ timeout: options.probeTimeout ?? 5000,
275
+ cwd: resolvedCwd,
276
+ env,
122
277
  });
123
- cache.available = !result.error && result.status === 0;
124
- if (cache.available) {
278
+ if (!result.error && result.status === 0) {
279
+ cache.available = true;
280
+ cache.outcome = "success";
125
281
  cache.command = cmd;
282
+ return true;
283
+ }
284
+ cache.available = false;
285
+ const errorCode = result.error?.code;
286
+ if (result.spawnFailure?.kind === "tool-not-found") {
287
+ resetPathWalkMemo();
288
+ cache.outcome = "missing";
289
+ }
290
+ else if (result.failure === "timeout" ||
291
+ result.failure === "aborted" ||
292
+ result.failure === "signal" ||
293
+ result.spawnFailure?.kind === "killed" ||
294
+ errorCode === "EAGAIN" ||
295
+ errorCode === "EBUSY") {
296
+ cache.outcome = "transient";
126
297
  }
127
- return cache.available;
298
+ else {
299
+ // A present command that rejects its version probe (or an EACCES/
300
+ // EINVAL/UNKNOWN failure) is not repaired by reinstalling it.
301
+ cache.outcome =
302
+ options.unclassifiedFailureOutcome ?? "non-installable";
303
+ }
304
+ return false;
128
305
  })().finally(() => {
129
- inFlightByCwd.delete(key);
306
+ // A session reset clears this map and a caller may immediately start a
307
+ // replacement probe for the same cwd. The old promise must not delete
308
+ // that newer-generation entry when it settles.
309
+ if (checkerGeneration === promiseGeneration &&
310
+ inFlightByCwd.get(key) === promise) {
311
+ inFlightByCwd.delete(key);
312
+ }
130
313
  });
131
314
  inFlightByCwd.set(key, promise);
132
315
  return promise;
133
316
  }
134
317
  function getCommand(cwd) {
318
+ ensureCurrentGeneration();
135
319
  const cache = getCache(cwd || process.cwd());
136
320
  return cache.command;
137
321
  }
138
- return { isAvailableAsync, getCommand };
322
+ function getOutcome(cwd) {
323
+ ensureCurrentGeneration();
324
+ return getCache(cwd || process.cwd()).outcome;
325
+ }
326
+ return { isAvailableAsync, getCommand, getOutcome, reset };
139
327
  }
140
328
  /**
141
329
  * Per-cwd cached availability probe for spawn signatures that don't fit
@@ -150,8 +338,13 @@ export function createAvailabilityChecker(command, windowsExt = "", versionArgs
150
338
  * fresh probe for retry-after-install flows.
151
339
  */
152
340
  export function createCwdCachedProbe(probe) {
153
- const cacheByCwd = new Map();
341
+ const cacheByCwd = new PathKeyedMap(normalizeEphemeralMapKey);
342
+ let probeGeneration = availabilityStateGeneration;
154
343
  return (cwd) => {
344
+ if (probeGeneration !== availabilityStateGeneration) {
345
+ cacheByCwd.clear();
346
+ probeGeneration = availabilityStateGeneration;
347
+ }
155
348
  const key = path.resolve(cwd || process.cwd());
156
349
  const existing = cacheByCwd.get(key);
157
350
  if (existing)
@@ -209,7 +402,8 @@ export async function resolveToolCommandWithInstallFallback(cwd, toolId, timeout
209
402
  async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
210
403
  // Skip the --version spawn when the command isn't even on disk — the ~μs
211
404
  // stat/PATH walk beats a guaranteed-to-fail spawn round-trip.
212
- if (await isSpawnableCommand(command)) {
405
+ const spawnable = await isSpawnableCommand(command);
406
+ if (spawnable) {
213
407
  const versionCheck = await safeSpawnAsync(command, versionArgs, {
214
408
  timeout,
215
409
  cwd,
@@ -217,16 +411,22 @@ async function verifyOrInstallCommand(command, toolId, cwd, versionArgs = ["--ve
217
411
  if (!versionCheck.error && versionCheck.status === 0) {
218
412
  return command;
219
413
  }
414
+ // A command that was found but rejected its probe is not fixed by a
415
+ // reinstall. This also covers permissions and malformed shims.
416
+ return null;
220
417
  }
221
- if (!shouldAutoInstallTool(toolId)) {
418
+ if (!shouldAutoInstallTool(toolId))
419
+ return null;
420
+ const state = installStateFor(cwd, toolId);
421
+ if (state.suppressed)
222
422
  return null;
423
+ const installed = await ensureTool(toolId);
424
+ if (installed) {
425
+ noteInstallSuccess(toolId, cwd);
426
+ return installed;
223
427
  }
224
- // ensureTool's result is already trusted: a probe-cache hit validated
225
- // path+mtime, and a fresh install verified the binary. Re-probing
226
- // --version here booted the tool's node shim once per invocation for
227
- // zero new information; a broken install now surfaces as the runner's
228
- // own spawn error instead of a silent null.
229
- return (await ensureTool(toolId)) ?? null;
428
+ noteInstallFailure(toolId, cwd);
429
+ return null;
230
430
  }
231
431
  export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
232
432
  const versionCheck = await safeSpawnAsync(command.cmd, [...command.args, ...versionArgs], { timeout, cwd });
@@ -245,16 +445,55 @@ export async function resolveCommandArgsWithInstallFallback(command, toolId, cwd
245
445
  export async function resolveCommandWithInstallFallback(command, toolId, cwd, versionArgs = ["--version"], timeout = 5000) {
246
446
  return verifyOrInstallCommand(command, toolId, cwd, versionArgs, timeout);
247
447
  }
248
- export async function resolveAvailableOrInstall(checker, toolId, cwd) {
448
+ async function resolveAvailableOrInstallUnshared(checker, toolId, cwd) {
249
449
  const available = await checker.isAvailableAsync(cwd);
250
450
  if (available) {
251
451
  return checker.getCommand(cwd);
252
452
  }
253
- if (!shouldAutoInstallTool(toolId)) {
453
+ // Only a typed ENOENT/missing-command result is repairable by installing.
454
+ // Probe failures caused by bad cwd, permissions, rejected flags, aborts, and
455
+ // timeouts are unavailable/non-installable and must not enter an install loop.
456
+ if (checker.getOutcome?.(cwd) !== "missing")
457
+ return null;
458
+ if (!shouldAutoInstallTool(toolId))
459
+ return null;
460
+ const state = installStateFor(cwd, toolId);
461
+ if (state.suppressed) {
254
462
  return null;
255
463
  }
256
464
  const installed = await ensureTool(toolId);
257
- return installed ?? null;
465
+ if (installed) {
466
+ noteInstallSuccess(toolId, cwd);
467
+ checker.reset?.();
468
+ return installed;
469
+ }
470
+ noteInstallFailure(toolId, cwd);
471
+ return null;
472
+ }
473
+ /** Share the complete probe/install transaction for each cwd/tool pair. */
474
+ export function resolveAvailableOrInstall(checker, toolId, cwd) {
475
+ const key = normalizeEphemeralMapKey(cwd);
476
+ let byTool = resolveInstallInFlightByCwd.get(key);
477
+ if (!byTool) {
478
+ byTool = new Map();
479
+ resolveInstallInFlightByCwd.set(key, byTool);
480
+ }
481
+ const existing = byTool.get(toolId);
482
+ if (existing)
483
+ return existing;
484
+ const generation = availabilityStateGeneration;
485
+ const promise = resolveAvailableOrInstallUnshared(checker, toolId, cwd).finally(() => {
486
+ if (generation !== availabilityStateGeneration)
487
+ return;
488
+ const current = resolveInstallInFlightByCwd.get(key);
489
+ if (current?.get(toolId) === promise) {
490
+ current.delete(toolId);
491
+ if (current.size === 0)
492
+ resolveInstallInFlightByCwd.delete(key);
493
+ }
494
+ });
495
+ byTool.set(toolId, promise);
496
+ return promise;
258
497
  }
259
498
  // =============================================================================
260
499
  // SHARED AST-GREP AVAILABILITY
@@ -302,7 +541,18 @@ function buildSgLocalBins() {
302
541
  return bins;
303
542
  }
304
543
  let sgAvailableInFlight = null;
544
+ let sgAvailabilityGeneration = availabilityStateGeneration;
545
+ function ensureCurrentSgGeneration() {
546
+ if (sgAvailabilityGeneration === availabilityStateGeneration)
547
+ return;
548
+ sgAvailable = null;
549
+ sgCmd = null;
550
+ sgCmdArgs = [];
551
+ sgAvailableInFlight = null;
552
+ sgAvailabilityGeneration = availabilityStateGeneration;
553
+ }
305
554
  export async function isSgAvailableAsync() {
555
+ ensureCurrentSgGeneration();
306
556
  if (sgAvailable !== null)
307
557
  return sgAvailable;
308
558
  if (sgAvailableInFlight)
@@ -352,6 +602,7 @@ export async function isSgAvailableAsync() {
352
602
  return sgAvailableInFlight;
353
603
  }
354
604
  export function getSgCommand() {
605
+ ensureCurrentSgGeneration();
355
606
  return {
356
607
  cmd: sgCmd ?? "npx",
357
608
  args: sgCmdArgs.length ? sgCmdArgs : ["--no", "--", "ast-grep"],
@@ -0,0 +1,22 @@
1
+ /**
2
+ * True when a tool both failed and produced nothing to parse — it never got as
3
+ * far as linting the file. Callers map this to `skipped`, never `succeeded`.
4
+ *
5
+ * Guarding on `result.error` alone covers only half of it: safe-spawn sets
6
+ * `error` for spawn/timeout/signal failures and resolves a NORMAL exit with no
7
+ * `error` at any status (see `SpawnResult.failure` — "nonzero exit statuses are
8
+ * not spawn failures"). An unknown subcommand, a rejected flag, or a config
9
+ * that fails to load exits non-zero with the message on stderr and an empty
10
+ * stdout, so an `error`-only guard falls through, parses "" into zero
11
+ * diagnostics, and reports a clean file that nothing ever checked.
12
+ *
13
+ * Tools that exit non-zero *because* they found something are unaffected: their
14
+ * findings are on stdout, so the output half of the test is false.
15
+ *
16
+ * `output` defaults to stdout. Runners that parse stderr too pass the exact
17
+ * string they are about to hand the parser, so "nothing to parse" means both
18
+ * streams rather than just one.
19
+ */
20
+ export function spawnFailedWithNoOutput(result, output = result.stdout) {
21
+ return (Boolean(result.error) || result.status !== 0) && !output?.trim();
22
+ }
@@ -15,6 +15,7 @@ import { createHash } from "node:crypto";
15
15
  import * as fs from "node:fs";
16
16
  import * as path from "node:path";
17
17
  import yaml from "../../deps/js-yaml.js";
18
+ import { BoundedLruCache } from "../../bounded-cache.js";
18
19
  // --- Constants ---
19
20
  /** Overly broad patterns that match everything (cause false positive explosions) */
20
21
  export const OVERLY_BROAD_PATTERNS = [
@@ -28,10 +29,10 @@ export const OVERLY_BROAD_PATTERNS = [
28
29
  /** Maximum complexity score for rules in blockingOnly mode */
29
30
  export const MAX_BLOCKING_RULE_COMPLEXITY = 8;
30
31
  // --- Caches ---
31
- const rulesCache = new Map();
32
- const blockingRulesCache = new Map();
33
- const contentRulesCache = new Map();
34
- const contentBlockingRulesCache = new Map();
32
+ const rulesCache = new BoundedLruCache(64);
33
+ const blockingRulesCache = new BoundedLruCache(64);
34
+ const contentRulesCache = new BoundedLruCache(64);
35
+ const contentBlockingRulesCache = new BoundedLruCache(64);
35
36
  // --- Public API ---
36
37
  export function clearRulesCache() {
37
38
  rulesCache.clear();
@@ -24,9 +24,10 @@
24
24
  import { logBusEvent } from "./bus-events-logger.js";
25
25
  import { isBusPublishEnabled } from "./bus-publish.js";
26
26
  import { normalizeFilePath } from "./path-utils.js";
27
+ import { createLiveBusEmitter, recordStaleBusFailure, } from "./live-bus-emitter.js";
27
28
  export const BUS_DISPOSITION_EVENT = "pilens:diagnostic:disposition";
28
29
  export const BUS_DISPOSITION_VERSION = 1;
29
- let busEmit;
30
+ const liveEmitter = createLiveBusEmitter();
30
31
  let hasLoggedFailure = false;
31
32
  let hasLoggedUnwired = false;
32
33
  let hasLoggedDisabled = false;
@@ -38,11 +39,14 @@ let hasLoggedDisabled = false;
38
39
  * the MCP server path run in (no pi host, no `pi.events`).
39
40
  */
40
41
  export function wireDispositionBusEmitter(emitFn) {
41
- busEmit = emitFn;
42
+ liveEmitter.wire(emitFn);
43
+ }
44
+ export function wireDispositionBusEmitterGetter(getter) {
45
+ liveEmitter.wireGetter(getter);
42
46
  }
43
47
  /** Test-only: reset module state between test files. */
44
48
  export function _resetDispositionPublishForTests() {
45
- busEmit = undefined;
49
+ liveEmitter.reset();
46
50
  hasLoggedFailure = false;
47
51
  hasLoggedUnwired = false;
48
52
  hasLoggedDisabled = false;
@@ -63,6 +67,7 @@ export function publishDisposition(args) {
63
67
  }
64
68
  return;
65
69
  }
70
+ const busEmit = liveEmitter.get();
66
71
  if (!busEmit) {
67
72
  if (!hasLoggedUnwired) {
68
73
  hasLoggedUnwired = true;
@@ -88,6 +93,7 @@ export function publishDisposition(args) {
88
93
  reason: args.reason,
89
94
  };
90
95
  busEmit(BUS_DISPOSITION_EVENT, payload);
96
+ hasLoggedFailure = false;
91
97
  logBusEvent({
92
98
  event: BUS_DISPOSITION_EVENT,
93
99
  outcome: "emitted",
@@ -103,6 +109,7 @@ export function publishDisposition(args) {
103
109
  });
104
110
  if (!hasLoggedFailure) {
105
111
  hasLoggedFailure = true;
112
+ recordStaleBusFailure(BUS_DISPOSITION_EVENT, err);
106
113
  args.dbg?.(`disposition-publish: pilens:diagnostic:disposition emit failed (further failures suppressed): ${err}`);
107
114
  }
108
115
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Synchronous locked read-modify-write commit seam for behavior-gating JSON
3
+ * stores shared by multiple pi-lens processes.
4
+ *
5
+ * The authoritative read happens only after the bounded PID lock is held; the
6
+ * caller merges only its delta, publication is a throwing atomic replacement,
7
+ * and success telemetry runs only after publication succeeds.
8
+ *
9
+ * Short synchronous commits use a file lock. Awaited commits use the shared
10
+ * quarantine directory lock, preserving bounded contention and stale-owner
11
+ * recovery without allowing a late release to delete a replacement owner.
12
+ */
13
+ import * as fs from "node:fs";
14
+ import fsp from "node:fs/promises";
15
+ import { writeFileAtomic, writeFileAtomicAsync } from "./atomic-write.js";
16
+ import { acquireBoundedPidFileLock, acquireQuarantinePidFileLock, } from "./bounded-pid-file-lock.js";
17
+ function readLocked(path) {
18
+ try {
19
+ return fs.readFileSync(path, "utf8");
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ export function commitDurableStore(options) {
26
+ const release = options.onContention === "skip-log"
27
+ ? acquireBoundedPidFileLock(`${options.path}.lock`, {
28
+ waitMs: options.waitMs,
29
+ retryMs: options.retryMs,
30
+ timeoutMessage: options.timeoutMessage,
31
+ onContention: "skip-log",
32
+ logContention: options.logContention,
33
+ })
34
+ : acquireBoundedPidFileLock(`${options.path}.lock`, {
35
+ waitMs: options.waitMs,
36
+ retryMs: options.retryMs,
37
+ timeoutMessage: options.timeoutMessage,
38
+ onContention: "throw",
39
+ });
40
+ if (!release)
41
+ return undefined;
42
+ let committed;
43
+ try {
44
+ const current = options.deserialize(readLocked(options.path));
45
+ committed = options.merge(current);
46
+ writeFileAtomic(options.path, options.serialize(committed), {
47
+ bestEffort: false,
48
+ });
49
+ options.afterWriteLocked?.(committed);
50
+ }
51
+ finally {
52
+ release();
53
+ }
54
+ return committed;
55
+ }
56
+ async function readLockedAsync(path) {
57
+ try {
58
+ return await fsp.readFile(path, "utf8");
59
+ }
60
+ catch (error) {
61
+ if (error.code === "ENOENT")
62
+ return undefined;
63
+ throw error;
64
+ }
65
+ }
66
+ export async function commitDurableStoreAsync(options) {
67
+ const release = options.onContention === "skip-log"
68
+ ? await acquireQuarantinePidFileLock(`${options.path}.lock`, {
69
+ waitMs: options.waitMs,
70
+ retryMs: options.retryMs,
71
+ staleMs: options.staleMs,
72
+ timeoutMessage: options.timeoutMessage,
73
+ onContention: "skip-log",
74
+ logContention: options.logContention,
75
+ })
76
+ : await acquireQuarantinePidFileLock(`${options.path}.lock`, {
77
+ waitMs: options.waitMs,
78
+ retryMs: options.retryMs,
79
+ staleMs: options.staleMs,
80
+ timeoutMessage: options.timeoutMessage,
81
+ onContention: "throw",
82
+ });
83
+ if (!release)
84
+ return undefined;
85
+ try {
86
+ const current = options.deserialize(await readLockedAsync(options.path));
87
+ const committed = options.merge(current);
88
+ await writeFileAtomicAsync(options.path, options.serialize(committed), {
89
+ bestEffort: false,
90
+ });
91
+ await options.afterWriteLocked?.(committed);
92
+ return committed;
93
+ }
94
+ finally {
95
+ await release();
96
+ }
97
+ }