@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
@@ -12,6 +12,7 @@
12
12
  *
13
13
  * Refs: #130, #131, #132
14
14
  */
15
+ import { createSubsystemLogger } from "./extension-log.js";
15
16
  import { safeSpawnAsync } from "./safe-spawn.js";
16
17
  export class SecurityScanClient {
17
18
  toolName;
@@ -27,7 +28,7 @@ export class SecurityScanClient {
27
28
  constructor(toolName, verbose = false) {
28
29
  this.toolName = toolName;
29
30
  this.log = verbose
30
- ? (msg) => console.error(`[${toolName}] ${msg}`)
31
+ ? createSubsystemLogger(toolName)
31
32
  : () => { };
32
33
  }
33
34
  /**
@@ -13,7 +13,7 @@ import * as path from "node:path";
13
13
  import { writeFileAtomicAsync } from "./atomic-write.js";
14
14
  import { getProjectDataDir } from "./file-utils.js";
15
15
  import { readJsonCacheAsync } from "./json-cache-read.js";
16
- const STATE_VERSION = 1;
16
+ export const STATE_VERSION = 1;
17
17
  export function sessionStartMode(reason, hasPendingForkSnapshot) {
18
18
  if (reason === "fork" && hasPendingForkSnapshot)
19
19
  return "fork";
@@ -52,7 +52,8 @@ export async function saveSessionState(cwd, sessionId, widget, readGuard) {
52
52
  // bestEffort (default): a failed write/rename just means this snapshot is
53
53
  // lost, matching this store's documented "start clean" fallback — never
54
54
  // throw for the caller. (Tmp naming is now the shared
55
- // `${target}.tmp-${pid}` shape rather than this site's former
55
+ // `${target}.tmp-${pid}-${seq}` shape (unique per call, not just per process,
56
+ // since #1205) rather than this site's former
56
57
  // `${file}.${pid}.tmp` — no behavioral difference: nothing reads the
57
58
  // intermediate tmp filename.)
58
59
  await writeFileAtomicAsync(file, JSON.stringify(payload));
@@ -4,25 +4,14 @@
4
4
  * Extracted from AstGrepClient to simplify the main client.
5
5
  * Handles: spawn, spawnSync, temp dir management, JSON parsing.
6
6
  */
7
- import { spawn } from "node:child_process";
7
+ import { createSubsystemLogger } from "./extension-log.js";
8
8
  import * as fs from "node:fs";
9
9
  import * as os from "node:os";
10
10
  import * as path from "node:path";
11
- import { getSgCommand } from "./dispatch/runners/utils/runner-helpers.js";
11
+ import { getSgCommand, resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
12
12
  import { getProjectIgnoreGlobs } from "./file-utils.js";
13
13
  import { findGlobalBinary } from "./package-manager.js";
14
14
  import { safeSpawnAsync } from "./safe-spawn.js";
15
- /**
16
- * Escape an argument for Windows cmd.exe shell execution.
17
- * Handles spaces, quotes, and special characters.
18
- */
19
- function escapeWindowsArg(arg) {
20
- // If no special characters, return as-is
21
- if (!/[\s"]/.test(arg))
22
- return arg;
23
- // Escape quotes by doubling them
24
- return `"${arg.replace(/"/g, '""')}"`;
25
- }
26
15
  /**
27
16
  * Build the `bash -c` argv that runs `cmd` with `allArgs` as POSITIONAL
28
17
  * parameters. The script is the constant `"$0" "$@"`, so bash re-emits the
@@ -43,6 +32,8 @@ function sgExcludeArgsForProject(rootDir) {
43
32
  `!${glob}`,
44
33
  ]);
45
34
  }
35
+ const DEFAULT_EXEC_TIMEOUT_MS = 30_000;
36
+ const MAX_SG_OUTPUT_BYTES = 8 * 1024 * 1024;
46
37
  /**
47
38
  * Format metavariable captures for display below a match line.
48
39
  * Single captures: $VAR=x $NAME=foo
@@ -72,6 +63,33 @@ function formatMetaVarCaptures(mv) {
72
63
  return undefined;
73
64
  return ` ${parts.join(" ")}`;
74
65
  }
66
+ /**
67
+ * Parse ast-grep `--json` stdout into a match array, or return `null` when the
68
+ * text is not a match payload. ast-grep emits either a JSON array or (rarely) a
69
+ * single object; both are normalized to an array. A JSON scalar/`null` is
70
+ * rejected (returns `null`) so a hypothetical error-report scalar on stdout can
71
+ * never be misread as a phantom match.
72
+ */
73
+ function tryParseSgMatches(stdout) {
74
+ try {
75
+ const parsed = JSON.parse(stdout);
76
+ if (Array.isArray(parsed))
77
+ return parsed;
78
+ if (parsed !== null && typeof parsed === "object")
79
+ return [parsed];
80
+ return null;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ function tryParseNonZeroSgMatches(result) {
87
+ if (result.outputTruncated || !result.stdout.trim())
88
+ return null;
89
+ // Trimmed on purpose: a leading BOM survives JSON.parse's whitespace
90
+ // tolerance but String.trim() strips it (matches the pre-refactor guard).
91
+ return tryParseSgMatches(result.stdout.trim());
92
+ }
75
93
  export class SgRunner {
76
94
  log;
77
95
  sgPath = null;
@@ -80,7 +98,7 @@ export class SgRunner {
80
98
  ensureInFlight = null;
81
99
  constructor(verbose = false) {
82
100
  this.log = verbose
83
- ? (msg) => console.error(`[sg-runner] ${msg}`)
101
+ ? createSubsystemLogger("sg-runner")
84
102
  : () => { };
85
103
  }
86
104
  /**
@@ -156,15 +174,19 @@ export class SgRunner {
156
174
  return true;
157
175
  }
158
176
  }
159
- // Step 4: auto-install via pi-lens installer.
160
- this.log("ast-grep not found, attempting auto-install...");
161
- const { ensureTool } = await import("./installer/index.js");
162
- const installedPath = await ensureTool("ast-grep");
163
- if (installedPath && (await this.probeCommand(installedPath, []))) {
164
- this.sgPath = installedPath;
177
+ // Step 4: install via the typed shared seam, then validate the returned
178
+ // absolute binary before publishing it.
179
+ const installed = await resolveManagedToolClient({
180
+ toolId: "ast-grep",
181
+ cwd: process.cwd(),
182
+ probe: async () => ({ outcome: "missing" }),
183
+ acceptInstalled: async (installedPath) => (await this.probeCommand(installedPath, [])) ? installedPath : null,
184
+ });
185
+ if (installed.outcome === "success") {
186
+ this.sgPath = installed.value;
165
187
  this.sgArgsPrefix = [];
166
188
  this.available = true;
167
- this.log(`ast-grep auto-installed: ${installedPath}`);
189
+ this.log(`ast-grep auto-installed: ${installed.value}`);
168
190
  return true;
169
191
  }
170
192
  this.available = false;
@@ -206,7 +228,8 @@ export class SgRunner {
206
228
  for (const root of searchRoots) {
207
229
  const candidate = path.join(root, pkgName, exeName);
208
230
  try {
209
- if (fs.existsSync(candidate) && (await this.probeCommand(candidate, []))) {
231
+ if (fs.existsSync(candidate) &&
232
+ (await this.probeCommand(candidate, []))) {
210
233
  return candidate;
211
234
  }
212
235
  }
@@ -233,7 +256,8 @@ export class SgRunner {
233
256
  return undefined;
234
257
  for (const name of ["ast-grep", "sg"]) {
235
258
  const candidate = path.join(prefix, "bin", name);
236
- if (fs.existsSync(candidate) && (await this.probeCommand(candidate, []))) {
259
+ if (fs.existsSync(candidate) &&
260
+ (await this.probeCommand(candidate, []))) {
237
261
  return candidate;
238
262
  }
239
263
  }
@@ -271,147 +295,140 @@ export class SgRunner {
271
295
  argsPrefix: this.sgArgsPrefix,
272
296
  };
273
297
  }
274
- async execRaw(args, timeout = 30000) {
298
+ failureForSpawnResult(result) {
299
+ if (result.failure === "aborted")
300
+ return "aborted";
301
+ switch (result.spawnFailure?.kind) {
302
+ case "tool-not-found":
303
+ return "unavailable";
304
+ case "timeout":
305
+ return "timeout";
306
+ case "killed":
307
+ return result.failure === "aborted" ? "aborted" : "cli-failure";
308
+ case "cwd-unresolvable":
309
+ case "permission-denied":
310
+ case "spawn-failed":
311
+ case undefined:
312
+ return result.error ? "cli-failure" : undefined;
313
+ }
314
+ }
315
+ formatPatternError(stderr, args) {
316
+ if (stderr.includes("Multiple AST nodes are detected")) {
317
+ return (`Invalid AST pattern: The pattern appears to contain multiple AST nodes or is malformed.\n` +
318
+ `Common causes:\n` +
319
+ ` 1. Missing parentheses: use it($TEST) not it"test"\n` +
320
+ ` 2. Raw text without structure: use console.log($MSG) not just "console.log"\n` +
321
+ ` 3. Unclosed quotes or brackets\n\n` +
322
+ `Original error: ${stderr}`);
323
+ }
324
+ if (stderr.includes("Cannot parse query")) {
325
+ return (`Pattern syntax error: The pattern could not be parsed as valid code.\n` +
326
+ `Tips:\n` +
327
+ ` - Patterns must be valid ${args.includes("--lang") ? args[args.indexOf("--lang") + 1] : "language"} syntax\n` +
328
+ ` - Use metavariables like $NAME, $ARGS for variable parts\n` +
329
+ ` - Example: 'function $NAME($$$PARAMS) { $$$BODY }'\n\n` +
330
+ `Original error: ${stderr}`);
331
+ }
332
+ return stderr;
333
+ }
334
+ async execRaw(args, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
275
335
  const command = this.getSgCommand();
276
- const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], { timeout });
336
+ const result = await safeSpawnAsync(command.cmd, [...command.argsPrefix, ...args], {
337
+ timeout,
338
+ deadlineAt: options.deadlineAt,
339
+ signal: options.signal,
340
+ maxOutputBytes: MAX_SG_OUTPUT_BYTES,
341
+ });
342
+ const failure = this.failureForSpawnResult(result);
277
343
  return {
278
344
  stdout: result.stdout,
279
345
  stderr: result.stderr,
280
346
  status: result.status,
281
347
  error: result.error?.message,
348
+ failure,
349
+ ...(result.outputTruncated ? { outputTruncated: true } : {}),
282
350
  };
283
351
  }
284
352
  /**
285
- * Run ast-grep asynchronously, return parsed matches
353
+ * Run ast-grep asynchronously, return parsed matches. The Windows Git
354
+ * Bash/MSYS path deliberately remains positional-argument based, but the
355
+ * child itself now goes through safeSpawnAsync so cancellation, deadlines,
356
+ * tree-kill, and output caps are shared with every other CLI runner.
286
357
  */
287
- async exec(args) {
288
- return new Promise((resolve) => {
289
- const command = this.getSgCommand();
290
- const allArgs = [...command.argsPrefix, ...args];
291
- // On Windows with Git Bash/MSYS2, we need to use bash to properly
292
- // handle $variables in patterns (prevent shell expansion)
293
- const isWindows = process.platform === "win32";
294
- const hasBash = process.env.MSYSTEM || process.env.GIT_SHELL;
295
- const empty = () => ({
296
- matches: [],
297
- totalMatches: 0,
298
- truncated: false,
299
- });
300
- let proc;
301
- try {
302
- if (isWindows && hasBash) {
303
- // Run via bash (Git Bash/MSYS2) so $-metavariables in ast-grep
304
- // patterns aren't shell-expanded. Pass the command + args as
305
- // POSITIONAL parameters (`"$0"`/`"$@"`) instead of interpolating
306
- // them into the -c string: bash re-emits `"$@"` verbatim — no
307
- // parameter expansion, no word-splitting — so patterns stay literal
308
- // AND an environment-derived command path cannot inject shell
309
- // (fixes CodeQL js/shell-command-injection-from-environment). This
310
- // also removes the brittle hand-rolled quoting it replaced.
311
- proc = spawn("bash", buildBashRunArgs(command.cmd, allArgs), {
312
- stdio: ["ignore", "pipe", "pipe"],
313
- windowsHide: true,
314
- });
315
- }
316
- else if (isWindows) {
317
- // Fallback: shell:true needed for npm-installed .cmd wrappers on Windows.
318
- // Pass cmd and args separately — do not concatenate into one string.
319
- proc = spawn(command.cmd, allArgs.map(escapeWindowsArg), {
320
- stdio: ["ignore", "pipe", "pipe"],
321
- shell: true,
322
- windowsHide: true,
323
- });
324
- }
325
- else {
326
- // Unix: normal spawn without shell
327
- proc = spawn(command.cmd, allArgs, {
328
- stdio: ["ignore", "pipe", "pipe"],
329
- });
330
- }
331
- }
332
- catch (err) {
333
- // A SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL — the
334
- // pidusage bug class, #533) would reject this Promise; `exec` is
335
- // contracted to always resolve (callers read `error`, never catch),
336
- // so resolve gracefully instead of letting it escape as an
337
- // unhandledRejection. Async spawn `'error'` events are already
338
- // handled by the `proc.on("error", ...)` listener below.
339
- resolve({
340
- ...empty(),
341
- error: err instanceof Error ? err.message : String(err),
342
- });
343
- return;
344
- }
345
- let stdout = "";
346
- let stderr = "";
347
- proc.stdout?.on("data", (data) => (stdout += data.toString()));
348
- proc.stderr?.on("data", (data) => (stderr += data.toString()));
349
- proc.on("error", (err) => {
350
- if (err.message.includes("ENOENT")) {
351
- resolve({
352
- ...empty(),
353
- error: "ast-grep CLI not found. Install: npm i -D @ast-grep/cli",
354
- });
355
- }
356
- else {
357
- resolve({ ...empty(), error: err.message });
358
- }
359
- });
360
- proc.on("close", (code) => {
361
- if (code !== 0 && !stdout.trim()) {
362
- const stderrMsg = stderr.trim();
363
- if (code === 1 && !stderrMsg) {
364
- resolve(empty());
365
- return;
366
- }
367
- if (stderrMsg.includes("Multiple AST nodes are detected")) {
368
- resolve({
369
- ...empty(),
370
- error: `Invalid AST pattern: The pattern appears to contain multiple AST nodes or is malformed.\n` +
371
- `Common causes:\n` +
372
- ` 1. Missing parentheses: use it($TEST) not it"test"\n` +
373
- ` 2. Raw text without structure: use console.log($MSG) not just "console.log"\n` +
374
- ` 3. Unclosed quotes or brackets\n\n` +
375
- `Original error: ${stderrMsg}`,
376
- });
377
- return;
378
- }
379
- if (stderrMsg.includes("Cannot parse query")) {
380
- resolve({
381
- ...empty(),
382
- error: `Pattern syntax error: The pattern could not be parsed as valid code.\n` +
383
- `Tips:\n` +
384
- ` - Patterns must be valid ${args.includes("--lang") ? args[args.indexOf("--lang") + 1] : "language"} syntax\n` +
385
- ` - Use metavariables like $NAME, $ARGS for variable parts\n` +
386
- ` - Example: 'function $NAME($$$PARAMS) { $$$BODY }'\n\n` +
387
- `Original error: ${stderrMsg}`,
388
- });
389
- return;
390
- }
391
- resolve({
392
- ...empty(),
393
- error: stderrMsg || `Command failed with exit code ${code}`,
394
- });
395
- return;
396
- }
397
- if (!stdout.trim()) {
398
- resolve(empty());
399
- return;
400
- }
401
- try {
402
- const parsed = JSON.parse(stdout);
403
- const matches = Array.isArray(parsed) ? parsed : [parsed];
404
- resolve({
358
+ async exec(args, options = {}) {
359
+ const command = this.getSgCommand();
360
+ const allArgs = [...command.argsPrefix, ...args];
361
+ const isWindows = process.platform === "win32";
362
+ const hasBash = Boolean(process.env.MSYSTEM || process.env.GIT_SHELL);
363
+ const useBash = isWindows && hasBash;
364
+ const result = await safeSpawnAsync(useBash ? "bash" : command.cmd, useBash ? buildBashRunArgs(command.cmd, allArgs) : allArgs, {
365
+ timeout: DEFAULT_EXEC_TIMEOUT_MS,
366
+ deadlineAt: options.deadlineAt,
367
+ signal: options.signal,
368
+ maxOutputBytes: MAX_SG_OUTPUT_BYTES,
369
+ });
370
+ const empty = () => ({
371
+ matches: [],
372
+ totalMatches: 0,
373
+ truncated: false,
374
+ });
375
+ const spawnFailure = this.failureForSpawnResult(result);
376
+ if (spawnFailure) {
377
+ return {
378
+ ...empty(),
379
+ error: spawnFailure === "unavailable"
380
+ ? "ast-grep CLI not found. Install: npm i -D @ast-grep/cli"
381
+ : result.error?.message || "ast-grep CLI failed to start",
382
+ };
383
+ }
384
+ if (result.status !== 0) {
385
+ const stdout = result.stdout.trim();
386
+ const stderr = result.stderr.trim();
387
+ // ast-grep's linter-style contract: a rule with `severity: error`
388
+ // that MATCHES exits 1 with valid JSON matches on stdout (stderr
389
+ // carries "Scan succeeded and found error level diagnostics"). An
390
+ // exit code that means "scan succeeded with findings" must never be
391
+ // classified as a CLI failure — parse the matches. Only fall through
392
+ // to failure when the JSON isn't parseable (a real diagnostic).
393
+ if (result.status === 1 && stdout) {
394
+ const matches = tryParseNonZeroSgMatches(result);
395
+ if (matches) {
396
+ return {
405
397
  matches,
406
398
  totalMatches: matches.length,
407
399
  truncated: false,
408
- });
409
- }
410
- catch {
411
- resolve({ ...empty(), error: "Failed to parse output" });
400
+ };
412
401
  }
413
- });
414
- });
402
+ }
403
+ // ast-grep uses status 1 with no output for a genuine no-match in
404
+ // some CLI versions. Preserve that historical empty-result behavior;
405
+ // any stderr (including an invalid kind/YAML diagnostic) is a failure.
406
+ if (result.status === 1 && !stdout && !stderr)
407
+ return empty();
408
+ return {
409
+ ...empty(),
410
+ error: this.formatPatternError(stderr || `Command failed with exit code ${result.status}`, args),
411
+ };
412
+ }
413
+ if (!result.stdout.trim())
414
+ return empty();
415
+ if (result.outputTruncated) {
416
+ return {
417
+ ...empty(),
418
+ error: "Failed to parse output: output was truncated",
419
+ };
420
+ }
421
+ {
422
+ const matches = tryParseSgMatches(result.stdout);
423
+ if (matches) {
424
+ return {
425
+ matches,
426
+ totalMatches: matches.length,
427
+ truncated: false,
428
+ };
429
+ }
430
+ return { ...empty(), error: "Failed to parse output" };
431
+ }
415
432
  }
416
433
  // --- Shared helpers for temp-dir rule scans ---
417
434
  prepareTempScan(ruleId, ruleYaml) {
@@ -423,12 +440,6 @@ export class SgRunner {
423
440
  fs.writeFileSync(path.join(rulesSubdir, `${ruleId}.yml`), ruleYaml);
424
441
  return { sessionDir, configFile };
425
442
  }
426
- parseScanOutput(output) {
427
- if (!output.trim())
428
- return [];
429
- const items = JSON.parse(output);
430
- return Array.isArray(items) ? items : [items];
431
- }
432
443
  cleanupTempScan(sessionDir) {
433
444
  try {
434
445
  fs.rmSync(sessionDir, { recursive: true, force: true });
@@ -437,7 +448,67 @@ export class SgRunner {
437
448
  this.log(`Cleanup failed: ${err.message}`);
438
449
  }
439
450
  }
440
- async tempScanAsync(dir, ruleId, ruleYaml, timeout = 30000) {
451
+ interpretScanResult(result, args) {
452
+ const failure = this.failureForSpawnResult(result);
453
+ if (failure) {
454
+ return {
455
+ matches: [],
456
+ status: result.status,
457
+ error: result.error?.message || "ast-grep CLI failed to start",
458
+ failure,
459
+ };
460
+ }
461
+ if (result.status !== 0) {
462
+ const stdout = result.stdout.trim();
463
+ const stderr = result.stderr.trim();
464
+ // ast-grep's linter-style contract: a rule with `severity: error`
465
+ // that MATCHES exits 1 with valid JSON matches on stdout (stderr
466
+ // carries "Scan succeeded and found error level diagnostics"). An
467
+ // exit code that means "scan succeeded with findings" must never be
468
+ // classified as a CLI failure — parse the matches. Only fall through
469
+ // to failure when the JSON isn't parseable (a real diagnostic).
470
+ if (result.status === 1 && stdout) {
471
+ const matches = tryParseNonZeroSgMatches(result);
472
+ if (matches) {
473
+ return { matches, status: result.status };
474
+ }
475
+ }
476
+ // Preserve ast-grep's status-1/no-output no-match convention. A
477
+ // diagnostic on stderr is never treated as a no-match.
478
+ if (result.status === 1 && !stdout && !stderr) {
479
+ return { matches: [], status: result.status };
480
+ }
481
+ return {
482
+ matches: [],
483
+ status: result.status,
484
+ error: this.formatPatternError(stderr || `ast-grep scan failed with exit code ${result.status}`, args),
485
+ failure: "cli-failure",
486
+ };
487
+ }
488
+ if (!result.stdout.trim())
489
+ return { matches: [], status: result.status };
490
+ if (result.outputTruncated) {
491
+ return {
492
+ matches: [],
493
+ status: result.status,
494
+ error: "Failed to parse ast-grep scan output: output was truncated",
495
+ failure: "parse-failure",
496
+ };
497
+ }
498
+ {
499
+ const matches = tryParseSgMatches(result.stdout);
500
+ if (matches) {
501
+ return { matches, status: result.status };
502
+ }
503
+ return {
504
+ matches: [],
505
+ status: result.status,
506
+ error: "Failed to parse ast-grep scan output: invalid JSON",
507
+ failure: "parse-failure",
508
+ };
509
+ }
510
+ }
511
+ async tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
441
512
  const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
442
513
  try {
443
514
  const { cmd: sgCmd, args: sgPre } = getSgCommand();
@@ -449,42 +520,81 @@ export class SgRunner {
449
520
  "--json",
450
521
  ...sgExcludeArgsForProject(dir),
451
522
  dir,
452
- ], { timeout });
453
- return this.parseScanOutput(result.stdout || result.stderr || "");
454
- }
455
- catch {
456
- return [];
523
+ ], {
524
+ timeout,
525
+ deadlineAt: options.deadlineAt,
526
+ signal: options.signal,
527
+ maxOutputBytes: MAX_SG_OUTPUT_BYTES,
528
+ });
529
+ return this.interpretScanResult(result, ["scan"]);
457
530
  }
458
531
  finally {
459
532
  this.cleanupTempScan(sessionDir);
460
533
  }
461
534
  }
535
+ /** Backwards-compatible match-only wrapper for existing session scanners. */
536
+ async tempScanAsync(dir, ruleId, ruleYaml, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
537
+ const result = await this.tempScanDetailedAsync(dir, ruleId, ruleYaml, timeout, options);
538
+ return result.matches;
539
+ }
462
540
  /**
463
541
  * Run a rule scan with optional fix application.
464
542
  * Dry-run: --json (returns matches for preview).
465
543
  * Apply: --update-all (writes fixes defined in the YAML `fix:` field).
466
544
  */
467
- async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout = 30000) {
545
+ async tempScanWithFixAsync(dir, ruleId, ruleYaml, applyFixes, timeout = DEFAULT_EXEC_TIMEOUT_MS, options = {}) {
468
546
  const { sessionDir, configFile } = this.prepareTempScan(ruleId, ruleYaml);
469
547
  try {
470
548
  const { cmd: sgCmd, args: sgPre } = getSgCommand();
549
+ const scanArgs = [
550
+ ...sgPre,
551
+ "scan",
552
+ "--config",
553
+ configFile,
554
+ "--json",
555
+ ...sgExcludeArgsForProject(dir),
556
+ dir,
557
+ ];
558
+ const spawnOptions = {
559
+ timeout,
560
+ deadlineAt: options.deadlineAt,
561
+ signal: options.signal,
562
+ maxOutputBytes: MAX_SG_OUTPUT_BYTES,
563
+ };
471
564
  if (!applyFixes) {
472
- const result = await safeSpawnAsync(sgCmd, [...sgPre, "scan", "--config", configFile, "--json",
473
- ...sgExcludeArgsForProject(dir), dir], { timeout });
474
- return { matches: this.parseScanOutput(result.stdout || result.stderr || "") };
565
+ const result = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
566
+ const scan = this.interpretScanResult(result, ["scan"]);
567
+ return scan.failure || scan.error
568
+ ? { matches: [], error: scan.error }
569
+ : { matches: scan.matches };
475
570
  }
476
571
  // Apply: capture matches BEFORE writing — once --update-all applies
477
572
  // the fix the rule no longer matches, so a post-apply json pass would
478
573
  // report zero even on a successful apply. Count first, then write.
479
- const jsonResult = await safeSpawnAsync(sgCmd, [...sgPre, "scan", "--config", configFile, "--json",
480
- ...sgExcludeArgsForProject(dir), dir], { timeout });
481
- const matches = this.parseScanOutput(jsonResult.stdout || jsonResult.stderr || "");
482
- const applyResult = await safeSpawnAsync(sgCmd, [...sgPre, "scan", "--config", configFile, "--update-all",
483
- ...sgExcludeArgsForProject(dir), dir], { timeout });
484
- if (applyResult.error) {
485
- return { matches: [], error: applyResult.error.message };
574
+ const jsonResult = await safeSpawnAsync(sgCmd, scanArgs, spawnOptions);
575
+ const scan = this.interpretScanResult(jsonResult, ["scan"]);
576
+ if (scan.failure || scan.error) {
577
+ return { matches: [], error: scan.error };
578
+ }
579
+ const applyResult = await safeSpawnAsync(sgCmd, [
580
+ ...sgPre,
581
+ "scan",
582
+ "--config",
583
+ configFile,
584
+ "--update-all",
585
+ ...sgExcludeArgsForProject(dir),
586
+ dir,
587
+ ], spawnOptions);
588
+ if (applyResult.error ||
589
+ applyResult.failure ||
590
+ applyResult.status !== 0) {
591
+ return {
592
+ matches: [],
593
+ error: applyResult.error?.message ||
594
+ `ast-grep apply failed with exit code ${applyResult.status}`,
595
+ };
486
596
  }
487
- return { matches };
597
+ return { matches: scan.matches };
488
598
  }
489
599
  catch (err) {
490
600
  return { matches: [], error: String(err) };
@@ -27,6 +27,7 @@
27
27
  * values are read lazily at call time, not module load, and `Number(...)` is
28
28
  * gated through `Number.isFinite` before use.
29
29
  */
30
+ import { BoundedLruCache } from "./bounded-cache.js";
30
31
  import * as fs from "node:fs";
31
32
  import * as path from "node:path";
32
33
  import { toPositiveFinite } from "./env-utils.js";
@@ -95,7 +96,7 @@ export function probeSlowFs(rootDir) {
95
96
  }
96
97
  /** Process-lifetime memo of the slow-FS verdict, keyed by normalized cwd so
97
98
  * `/` vs `\` inputs share one entry (see path-key invariant tests). */
98
- const slowFsVerdictCache = new Map();
99
+ const slowFsVerdictCache = new BoundedLruCache(32);
99
100
  /**
100
101
  * Resolve (and memoize) the slow-FS verdict for `cwd`. Resolution order:
101
102
  * 1. `PI_LENS_ALLOW_SLOW_FS_SCAN=1` — kill switch, always false.