@herbertgao/pi-extensions 2026.9.0 → 2026.9.2

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 (360) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  12. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  13. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  14. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  15. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  17. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  20. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  22. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  23. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  24. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  25. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  26. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  27. package/node_modules/pi-lens/CHANGELOG.md +1072 -0
  28. package/node_modules/pi-lens/README.md +3 -0
  29. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  30. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  31. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  33. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  34. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  35. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  36. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  37. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  39. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
  40. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  41. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
  42. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  43. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  44. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
  46. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  47. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
  51. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  52. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
  53. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  54. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  56. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  57. package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
  58. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  59. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  60. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  61. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
  62. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  63. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  64. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  65. package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
  66. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  67. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  68. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  69. package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
  70. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  71. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  72. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
  73. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  74. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  75. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  76. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  77. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  78. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  79. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  80. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  81. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
  82. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  83. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
  84. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
  86. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  87. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  90. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  146. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
  148. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  149. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  150. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  153. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  154. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  155. package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  158. package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
  159. package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
  160. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  161. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
  162. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  163. package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
  164. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  165. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  166. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  167. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  168. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  169. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  170. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  171. package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
  172. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  173. package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
  174. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  175. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  176. package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
  177. package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
  178. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
  179. package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
  180. package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
  181. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  182. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
  183. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
  185. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  186. package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
  187. package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
  188. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  189. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
  191. package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
  192. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
  194. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
  197. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  198. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  199. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  200. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  201. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
  202. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  203. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  204. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  205. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  206. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  207. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  208. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  209. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  210. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  211. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  212. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
  213. package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
  214. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  215. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  216. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  217. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
  218. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  219. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  220. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
  221. package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
  222. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  223. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  224. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  225. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  226. package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
  227. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  228. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  229. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  230. package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
  231. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  232. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  233. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  234. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  235. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  236. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  237. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  238. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  239. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  240. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  241. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
  242. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  243. package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
  244. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  245. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  246. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  247. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  248. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  249. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  250. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  251. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
  252. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  253. package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
  254. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  255. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  256. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
  257. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  258. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  259. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  260. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  261. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  262. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
  263. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  264. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  265. package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
  266. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
  267. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  268. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  269. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  270. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  271. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
  272. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  273. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  274. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  275. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  277. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  278. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  279. package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
  280. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  281. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  282. package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
  283. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  284. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  285. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  286. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  287. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
  288. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  289. package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
  290. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  291. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
  292. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  293. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  294. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  295. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  296. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  297. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  298. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
  299. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  300. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
  301. package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
  302. package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
  303. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  304. package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
  305. package/node_modules/pi-lens/dist/index.js +65899 -55340
  306. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  307. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  308. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  309. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  310. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  311. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  312. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
  313. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
  314. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  315. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  316. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  317. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  318. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  319. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  320. package/node_modules/pi-lens/docs/configuration.md +222 -0
  321. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  322. package/node_modules/pi-lens/docs/features.md +44 -5
  323. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  324. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  325. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  326. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  327. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  328. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
  329. package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
  330. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  331. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  332. package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
  333. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  334. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  335. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  336. package/node_modules/pi-lens/docs/word-index.md +1 -1
  337. package/node_modules/pi-lens/package.json +18 -35
  338. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  339. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  340. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  341. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  342. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  343. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  344. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  345. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  346. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  347. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  348. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  349. package/node_modules/pi-web-access/README.md +62 -15
  350. package/node_modules/pi-web-access/curator-page.ts +3 -1
  351. package/node_modules/pi-web-access/curator-server.ts +5 -1
  352. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  353. package/node_modules/pi-web-access/github-extract.ts +242 -1
  354. package/node_modules/pi-web-access/index.ts +118 -64
  355. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  356. package/node_modules/pi-web-access/package.json +2 -2
  357. package/node_modules/pi-web-access/perplexity.ts +14 -1
  358. package/node_modules/pi-web-access/utils.ts +23 -6
  359. package/node_modules/pi-web-access/xai-search.ts +96 -33
  360. package/package.json +6 -6
@@ -0,0 +1,583 @@
1
+ // Process-table access for BOTH trees, in two halves.
2
+ //
3
+ // The PURE half (#476, Layer B assertion 3: zero surviving LSP-server child
4
+ // processes after pi exits — the #472 orphan class) answers "does this
5
+ // command line look like a leaked LSP server, and is it NEW since the
6
+ // baseline snapshot", and is unit-testable with fake process tables.
7
+ //
8
+ // The IMPURE half is the platform listing itself: `buildProcessQuery` (the
9
+ // one place a Windows CIM/WQL or POSIX `ps` command is composed),
10
+ // `parseProcessTable` (the one place its output is read back), and
11
+ // `snapshotProcesses` (the scripts-side bounded spawn around the pair).
12
+ //
13
+ // #2443 folded the LAST copies in here. Before it there were five: this
14
+ // file's two scripts/ callers plus `clients/instance-reaper.ts` (three
15
+ // queries) and `clients/resource-sampler.ts` (two) — the same projection,
16
+ // the same fail-to-an-empty-table shape, written out five times, which is
17
+ // why PR #2438's exit-code hardening (a `ps` that prints a partial table and
18
+ // then dies must not read as a complete one) reached one copy and not the
19
+ // rest. `tests/config/process-table-seam.test.ts` is the guard that keeps it
20
+ // at one.
21
+ //
22
+ // WHY THE SEAM LIVES IN scripts/ AND NOT IN clients/ (#2443 boundary
23
+ // decision). The obvious alternative — a `clients/process-snapshot.ts`
24
+ // compiled to `.js` and imported by scripts/ — is how ~20 other scripts
25
+ // consume client code, and it was rejected for ONE reason: `clients/*.js` is
26
+ // gitignored build output, and `scripts/prune-agent-worktrees.mjs` is a
27
+ // SessionStart/SubagentStop hook that runs inside freshly created agent
28
+ // worktrees, which have neither `node_modules` nor a build yet. A hygiene
29
+ // hook that throws ERR_MODULE_NOT_FOUND is strictly worse than the
30
+ // duplication it removes. This file therefore stays dependency-free (node:
31
+ // builtins only) and clients/ reaches it through
32
+ // `clients/process-snapshot.ts`, the single crossing point, typed by the
33
+ // sibling `process-scan.d.mts`.
34
+ import { spawn } from "node:child_process";
35
+ import fs from "node:fs";
36
+ import path from "node:path";
37
+ /**
38
+ * Read the platform LIVE rather than as a module-load constant, so both the
39
+ * Windows CIM branch and the POSIX `ps` branch of `buildProcessQuery` are
40
+ * exercisable in unit tests regardless of the host OS (the same reasoning
41
+ * `clients/resource-sampler.ts` states for its own platform check — the
42
+ * Windows half of this seam ships to machines no POSIX CI runner can cover,
43
+ * and vice versa).
44
+ */
45
+ function runningOnWindows() {
46
+ return process.platform === "win32";
47
+ }
48
+ /** Default ceiling for one process listing. */
49
+ const DEFAULT_SNAPSHOT_TIMEOUT_MS = 4_000;
50
+ /**
51
+ * @typedef {"pid" | "ppid" | "ageMs" | "rssBytes" | "cpuKernel100ns" | "cpuUser100ns" | "startedAt" | "command"} ProcessField
52
+ * @typedef {{ pid: number, ppid: number, command: string, ageMs?: number, rssBytes?: number, cpuKernel100ns?: number, cpuUser100ns?: number, startedAt?: string, cwd?: string }} ProcRow
53
+ * @typedef {{ column: "ProcessId" | "Name" | "CommandLine", op: "eq" | "like", values: ReadonlyArray<string|number> }} ProcessFilter
54
+ */
55
+ /**
56
+ * Per-field platform column names and token shapes. `ps`'s `args` and CIM's
57
+ * `CommandLine` are the same question asked twice; keeping the mapping in one
58
+ * table is what lets a caller say `["pid", "command"]` and get the right
59
+ * query, and the right parse, on both platforms.
60
+ *
61
+ * `ps: null` marks a field the POSIX listing cannot answer. Asking for one on
62
+ * POSIX THROWS rather than yielding a silently absent column — the callers
63
+ * that need those fields (the resource sampler's CPU/RSS query) are
64
+ * Windows-only by construction, and a wrong-shaped `ps` invocation would
65
+ * return zero rows, which reads exactly like "no such process".
66
+ */
67
+ const FIELD_COLUMNS = Object.freeze({
68
+ pid: { wmi: "ProcessId", ps: "pid", token: "int" },
69
+ ppid: { wmi: "ParentProcessId", ps: "ppid", token: "int" },
70
+ ageMs: { wmi: "CreationDate", ps: "etime", token: "age" },
71
+ rssBytes: { wmi: "WorkingSetSize", ps: null, token: "int" },
72
+ cpuKernel100ns: { wmi: "KernelModeTime", ps: null, token: "int" },
73
+ cpuUser100ns: { wmi: "UserModeTime", ps: null, token: "int" },
74
+ startedAt: { wmi: "CreationDate", ps: null, token: "text" },
75
+ command: { wmi: "CommandLine", ps: "args", token: "tail" },
76
+ });
77
+ /**
78
+ * The canonical column ORDER, which every parser assumes. `command` is LAST
79
+ * because it is the only column that can contain the delimiter — both parsers
80
+ * take it as the tail of the line. Derived from `FIELD_COLUMNS` rather than
81
+ * written out again, so a field cannot exist in one list and not the other.
82
+ */
83
+ const FIELD_ORDER = Object.freeze(Object.keys(FIELD_COLUMNS));
84
+ /**
85
+ * The DEFAULT projection: the three columns every platform can answer, which
86
+ * is what a caller that names no fields gets. It is deliberately NOT the full
87
+ * field set — `rssBytes`/`cpuKernel100ns`/`cpuUser100ns`/`startedAt` exist
88
+ * only on the Windows side, so defaulting to them would make an unprojected
89
+ * listing throw on POSIX instead of doing the obvious thing.
90
+ */
91
+ export const ALL_PROCESS_FIELDS = Object.freeze(["pid", "ppid", "command"]);
92
+ /** Columns a filter may name. A closed set, so the column half of a WQL
93
+ * clause can never carry caller-supplied text. */
94
+ const FILTERABLE_COLUMNS = new Set(["ProcessId", "Name", "CommandLine"]);
95
+ /**
96
+ * Absolute path to a System32 executable. Windows resolves a bare
97
+ * `powershell.exe` through PATH, which a caller can shadow; the callers spawn
98
+ * this to decide what to KILL, so the interpreter is named absolutely.
99
+ *
100
+ * @param {string} name
101
+ * @returns {string}
102
+ */
103
+ export function windowsExe(name) {
104
+ return path.join(process.env.SystemRoot ?? path.join("C:", "Windows"), "System32", name);
105
+ }
106
+ /**
107
+ * Absolute path to POSIX `ps` (S4036: never spawn via a bare PATH lookup) —
108
+ * the same reasoning as `windowsExe` applied to the other platform, since
109
+ * this listing decides what gets killed. Prefers `/bin/ps`, then
110
+ * `/usr/bin/ps`, and falls back to `/bin/ps` when neither probe succeeds, so
111
+ * the spawn fails CLOSED instead of quietly resolving through a PATH a caller
112
+ * can shadow. (#2443 adopted the reaper's stricter form over this file's
113
+ * earlier bare-`ps` fallback: one seam, the safer of the two behaviours.)
114
+ *
115
+ * @returns {string}
116
+ */
117
+ export function posixPsPath() {
118
+ try {
119
+ if (fs.existsSync("/bin/ps"))
120
+ return "/bin/ps";
121
+ if (fs.existsSync("/usr/bin/ps"))
122
+ return "/usr/bin/ps";
123
+ }
124
+ catch {
125
+ /* unreadable filesystem — fall through to the absolute default */
126
+ }
127
+ return "/bin/ps";
128
+ }
129
+ /**
130
+ * Normalize and order a requested field list. `pid` is always present (it is
131
+ * the row identity) and the order always follows `ALL_PROCESS_FIELDS`, so the
132
+ * parser never has to be told the layout it is reading.
133
+ *
134
+ * @param {ReadonlyArray<ProcessField>|null|undefined} fields
135
+ * @returns {ProcessField[]}
136
+ */
137
+ export function normalizeProcessFields(fields) {
138
+ const requested = new Set((fields ?? ALL_PROCESS_FIELDS).filter((field) => field in FIELD_COLUMNS));
139
+ requested.add("pid");
140
+ return FIELD_ORDER.filter((field) => requested.has(field));
141
+ }
142
+ /**
143
+ * Escape a value for embedding in a WQL LIKE clause: WQL uses `'` as the
144
+ * string delimiter (doubled to escape) and `%`/`_` as wildcards — a marker is
145
+ * an opaque path string, so escape all three before interpolating.
146
+ *
147
+ * @param {string|number} value
148
+ * @returns {string}
149
+ */
150
+ export function escapeWqlLikeValue(value) {
151
+ return String(value)
152
+ .replaceAll("'", "''")
153
+ .replaceAll(/[%_]/g, (ch) => `[${ch}]`);
154
+ }
155
+ /**
156
+ * Escape a value for a WQL EQUALITY comparison: only the string delimiter
157
+ * needs escaping — `%`/`_` are literal outside `LIKE`.
158
+ *
159
+ * @param {string|number} value
160
+ * @returns {string}
161
+ */
162
+ export function escapeWqlStringValue(value) {
163
+ return String(value).replaceAll("'", "''");
164
+ }
165
+ /**
166
+ * A pid about to be interpolated into a query, validated as a positive
167
+ * integer so the numeric branch can never carry query text.
168
+ *
169
+ * @param {string|number} value
170
+ * @returns {number}
171
+ */
172
+ function assertPid(value) {
173
+ const pid = Number(value);
174
+ if (!Number.isInteger(pid) || pid <= 0) {
175
+ throw new Error(`process filter: invalid pid ${String(value)}`);
176
+ }
177
+ return pid;
178
+ }
179
+ /**
180
+ * One WQL boolean expression for a filter. `ProcessId` is compared
181
+ * numerically (every value validated as a positive integer first, so nothing
182
+ * caller-supplied reaches the query text); every other column is compared as
183
+ * an escaped string.
184
+ *
185
+ * @param {ProcessFilter} filter
186
+ * @returns {string}
187
+ */
188
+ function wqlFilterExpression(filter) {
189
+ const { column, op, values } = filter;
190
+ if (!FILTERABLE_COLUMNS.has(column)) {
191
+ throw new Error(`process filter: unsupported column ${String(column)}`);
192
+ }
193
+ const list = [...values];
194
+ if (list.length === 0) {
195
+ throw new Error(`process filter: ${column} filter has no values`);
196
+ }
197
+ if (column === "ProcessId") {
198
+ return list.map((value) => `ProcessId=${assertPid(value)}`).join(" OR ");
199
+ }
200
+ if (op === "like") {
201
+ return list
202
+ .map((value) => `${column} LIKE '%${escapeWqlLikeValue(value)}%'`)
203
+ .join(" OR ");
204
+ }
205
+ return list
206
+ .map((value) => `${column} = '${escapeWqlStringValue(value)}'`)
207
+ .join(" OR ");
208
+ }
209
+ /**
210
+ * Compose the platform command that lists processes.
211
+ *
212
+ * Windows uses `Get-CimInstance` through `powershell -NoProfile` with an
213
+ * explicit WQL projection (measured ~316ms for the query vs ~570ms for the
214
+ * unprojected form on the #2435 box, plus ~208ms powershell startup) —
215
+ * `tasklist` exposes no parent pid and no command line, and `wmic` is gone
216
+ * from Windows 11. POSIX uses `ps`, either `-eo <cols>` (every process) or
217
+ * `-p <pids> -o <cols>` when the filter is a pid set, which is the only
218
+ * filter `ps` can apply server-side.
219
+ *
220
+ * `serverSideFiltered` reports whether the platform actually applied the
221
+ * filter. A `Name`/`CommandLine` filter has no `ps` equivalent, so on POSIX
222
+ * the caller gets the whole table and must narrow it itself — said out loud
223
+ * here rather than left as a per-caller assumption.
224
+ *
225
+ * `ageMs` on Windows is computed IN PowerShell as a millisecond delta, never
226
+ * exported as `CreationDate.Ticks`: `Ticks` is the LOCAL-time representation,
227
+ * so subtracting the Unix epoch in JS produced an age wrong by the machine's
228
+ * UTC offset (#1857 — measured on a UTC+3 host as -8358s for a process
229
+ * started 40 minutes earlier). A delta computed on one side of the boundary
230
+ * has no timezone to get wrong. The explicit `[datetime]` test matters too:
231
+ * subtracting `$null` from `(Get-Date)` yields a two-thousand-year TimeSpan,
232
+ * which would read as "ancient" and defeat a spawn-grace guard exactly where
233
+ * the data is missing.
234
+ *
235
+ * @param {ReadonlyArray<ProcessField>} [fields]
236
+ * @param {{ filter?: ProcessFilter, excludeSelfPid?: boolean }} [options]
237
+ * @returns {{ command: string, args: string[], tabSeparated: boolean, fields: ProcessField[], serverSideFiltered: boolean }}
238
+ */
239
+ export function buildProcessQuery(fields = ALL_PROCESS_FIELDS, options = {}) {
240
+ const layout = normalizeProcessFields(fields);
241
+ const filter = options.filter;
242
+ if (runningOnWindows()) {
243
+ const columns = [
244
+ ...new Set(layout.map((field) => FIELD_COLUMNS[field].wmi)),
245
+ ];
246
+ const where = filter ? ` WHERE ${wqlFilterExpression(filter)}` : "";
247
+ // A CommandLine filter's own marker is embedded in the command line of
248
+ // the powershell.exe running the query, so the search would match
249
+ // itself. `$PID` is the only identity that can exclude it, and it is
250
+ // knowable only inside the child.
251
+ const excludeSelf = options.excludeSelfPid
252
+ ? " | Where-Object { $_.ProcessId -ne $PID }"
253
+ : "";
254
+ const prelude = layout.includes("ageMs")
255
+ ? "$age = if ($_.CreationDate -is [datetime]) { [int64]((Get-Date) - $_.CreationDate).TotalMilliseconds } else { '' }; "
256
+ : "";
257
+ const row = layout
258
+ .map((field) => field === "ageMs" ? "$age" : `$($_.${FIELD_COLUMNS[field].wmi})`)
259
+ .join("`t");
260
+ return {
261
+ command: windowsExe("WindowsPowerShell\\v1.0\\powershell.exe"),
262
+ args: [
263
+ "-NoProfile",
264
+ "-NonInteractive",
265
+ "-Command",
266
+ `Get-CimInstance -Query "SELECT ${columns.join(",")} FROM Win32_Process${where}"${excludeSelf} | ForEach-Object { ${prelude}"${row}" }`,
267
+ ],
268
+ tabSeparated: true,
269
+ fields: layout,
270
+ serverSideFiltered: Boolean(filter),
271
+ };
272
+ }
273
+ const unsupported = layout.filter((field) => !FIELD_COLUMNS[field].ps);
274
+ if (unsupported.length > 0) {
275
+ throw new Error(`process query: ${unsupported.join(", ")} has no POSIX ps column`);
276
+ }
277
+ const columns = layout
278
+ .map((field) => `${FIELD_COLUMNS[field].ps}=`)
279
+ .join(",");
280
+ const pidFilter = filter && filter.column === "ProcessId" && filter.op === "eq"
281
+ ? [...filter.values].map((value) => assertPid(value))
282
+ : null;
283
+ return {
284
+ command: posixPsPath(),
285
+ args: pidFilter
286
+ ? ["-p", pidFilter.join(","), "-o", columns]
287
+ : ["-eo", columns],
288
+ tabSeparated: false,
289
+ fields: layout,
290
+ serverSideFiltered: Boolean(pidFilter),
291
+ };
292
+ }
293
+ /**
294
+ * Parse a non-negative integer token, rejecting anything else. A nonsensical
295
+ * value means the value is UNKNOWN, not zero — a caller reasoning from a
296
+ * fabricated 0 is how the #1857 local-time age bug stayed invisible.
297
+ *
298
+ * @param {string|undefined} raw
299
+ * @returns {number|undefined}
300
+ */
301
+ function parseNonNegativeInt(raw) {
302
+ const token = String(raw ?? "").trim();
303
+ if (!/^\d+$/.test(token))
304
+ return undefined;
305
+ const value = Number(token);
306
+ return Number.isFinite(value) ? value : undefined;
307
+ }
308
+ /**
309
+ * Parse a `ps -o etime` token: `[[dd-]hh:]mm:ss`. Returns undefined for any
310
+ * shape the column did not produce (a `ps` without the column emits the
311
+ * command line where the token was expected).
312
+ *
313
+ * @param {string} raw
314
+ * @returns {number|undefined}
315
+ */
316
+ export function ageMsFromPosixEtime(raw) {
317
+ const token = String(raw ?? "").trim();
318
+ if (!/^(?:\d+-)?(?:\d+:)?\d+:\d+$/.test(token))
319
+ return undefined;
320
+ let days = 0;
321
+ let rest = token;
322
+ const dash = rest.indexOf("-");
323
+ if (dash >= 0) {
324
+ days = Number(rest.slice(0, dash));
325
+ rest = rest.slice(dash + 1);
326
+ }
327
+ const parts = rest.split(":").map(Number);
328
+ if (parts.some((part) => !Number.isFinite(part)))
329
+ return undefined;
330
+ const [hours, minutes, seconds] = parts.length === 3 ? parts : [0, parts[0], parts[1]];
331
+ return ((days * 24 + hours) * 60 * 60 + minutes * 60 + seconds) * 1000;
332
+ }
333
+ /**
334
+ * POSIX token pattern per field: every column but the trailing command is a
335
+ * single whitespace-delimited token, and `etime` is not purely numeric.
336
+ *
337
+ * @param {ProcessField} field
338
+ * @returns {string}
339
+ */
340
+ function posixTokenPattern(field) {
341
+ return FIELD_COLUMNS[field].token === "age" ? "(\\S+)" : "(\\d+)";
342
+ }
343
+ /**
344
+ * Parse a platform listing into rows.
345
+ *
346
+ * Windows CIM emits tab-joined fields, one per requested column. `ps` emits
347
+ * whitespace-aligned columns whose LAST field (`args`) contains spaces, so it
348
+ * is parsed positionally with the tail taken whole.
349
+ *
350
+ * `pid`, `ppid` and `command` are always present on the row at their zero
351
+ * value (`ppid: 0`, `command: ""`) so consumers see one row shape regardless
352
+ * of the projection; every other field appears only when it was requested,
353
+ * and is `undefined` when the platform emitted a value that could not be
354
+ * trusted.
355
+ *
356
+ * @param {string} out
357
+ * @param {boolean} tabSeparated
358
+ * @param {ReadonlyArray<ProcessField>} [fields]
359
+ * @returns {ProcRow[]}
360
+ */
361
+ export function parseProcessTable(out, tabSeparated, fields = ALL_PROCESS_FIELDS) {
362
+ const layout = normalizeProcessFields(fields);
363
+ const commandIndex = layout.indexOf("command");
364
+ const positional = tabSeparated
365
+ ? null
366
+ : new RegExp(`^\\s*${layout
367
+ .filter((field) => field !== "command")
368
+ .map((field) => posixTokenPattern(field))
369
+ .join("\\s+")}${commandIndex === -1 ? "\\s*$" : "\\s+(.*)$"}`);
370
+ const rows = [];
371
+ for (const line of String(out ?? "").split(/\r?\n/)) {
372
+ if (!line.trim())
373
+ continue;
374
+ /** @type {string[]} */
375
+ let parts;
376
+ if (tabSeparated) {
377
+ const split = line.split("\t");
378
+ if (split.length < layout.length - (commandIndex === -1 ? 0 : 1))
379
+ continue;
380
+ // The command is the tail: it may itself contain tabs.
381
+ parts =
382
+ commandIndex === -1
383
+ ? split
384
+ : [
385
+ ...split.slice(0, commandIndex),
386
+ split.slice(commandIndex).join("\t"),
387
+ ];
388
+ }
389
+ else {
390
+ const match = positional?.exec(line);
391
+ if (!match)
392
+ continue;
393
+ parts = match.slice(1);
394
+ }
395
+ const value = (field) => {
396
+ const at = layout.indexOf(field);
397
+ return at === -1 ? undefined : parts[at];
398
+ };
399
+ const pid = Number(value("pid"));
400
+ if (!Number.isInteger(pid) || pid <= 0)
401
+ continue;
402
+ const ppid = Number(value("ppid"));
403
+ /** @type {ProcRow} */
404
+ const row = {
405
+ pid,
406
+ ppid: Number.isInteger(ppid) && ppid > 0 ? ppid : 0,
407
+ command: value("command") ?? "",
408
+ };
409
+ for (const field of layout) {
410
+ if (field === "pid" || field === "ppid" || field === "command")
411
+ continue;
412
+ const raw = value(field);
413
+ if (field === "ageMs") {
414
+ row.ageMs = tabSeparated
415
+ ? parseNonNegativeInt(raw)
416
+ : ageMsFromPosixEtime(raw ?? "");
417
+ }
418
+ else if (field === "startedAt") {
419
+ row.startedAt = String(raw ?? "").trim();
420
+ }
421
+ else {
422
+ row[field] = parseNonNegativeInt(raw);
423
+ }
424
+ }
425
+ rows.push(row);
426
+ }
427
+ return rows;
428
+ }
429
+ /**
430
+ * Snapshot the process table, for scripts/ — one bounded spawn around
431
+ * `buildProcessQuery` plus `parseProcessTable`.
432
+ *
433
+ * clients/ does NOT call this: it needs the extension's own spawn rails (an
434
+ * unref'd child and piped stdout, a tree-kill-and-verify timeout handler, the
435
+ * degradation ledger), so `clients/process-snapshot.ts` composes the same two
436
+ * halves over `spawnCollectStdoutResult` instead. The QUERY and the PARSE are
437
+ * shared; only the spawn differs, and it differs for a reason.
438
+ *
439
+ * Never rejects. Returns `{ rows, ok }`: `ok` is false when the listing
440
+ * failed, timed out, never spawned, or EXITED NON-ZERO — the last of which
441
+ * used to be ignored (PR #2438 review S5), so a `ps` that printed a partial
442
+ * table and then died read as a complete one. `ok` is the only evidence a
443
+ * caller has that an ABSENCE from the table means anything. Bounded by
444
+ * `timeoutMs` so a hook can never hang a session on a wedged WMI service.
445
+ *
446
+ * The timed-out child is killed through the HELD HANDLE, never by spawning a
447
+ * killer (#234: spawning during teardown aborts libuv on Windows).
448
+ *
449
+ * @param {ReadonlyArray<ProcessField>} [fields]
450
+ * @param {number} [timeoutMs]
451
+ * @param {{ filter?: ProcessFilter, excludeSelfPid?: boolean }} [options]
452
+ * @returns {Promise<{ rows: ProcRow[], ok: boolean }>}
453
+ */
454
+ export function snapshotProcesses(fields = ALL_PROCESS_FIELDS, timeoutMs = DEFAULT_SNAPSHOT_TIMEOUT_MS, options = {}) {
455
+ let query;
456
+ try {
457
+ query = buildProcessQuery(fields, options);
458
+ }
459
+ catch {
460
+ // An unbuildable query (a Windows-only column asked for on POSIX, an
461
+ // empty filter) is a caller bug, not a runtime outage — but this seam's
462
+ // contract is never to throw at a best-effort caller, so it reports the
463
+ // same not-ok empty table every other failure reports.
464
+ return Promise.resolve({ rows: [], ok: false });
465
+ }
466
+ return new Promise((resolve) => {
467
+ let settled = false;
468
+ const finish = (rows, ok) => {
469
+ if (settled)
470
+ return;
471
+ settled = true;
472
+ clearTimeout(timer);
473
+ resolve({ rows, ok });
474
+ };
475
+ let child;
476
+ const timer = setTimeout(() => {
477
+ try {
478
+ child?.kill();
479
+ }
480
+ catch {
481
+ /* already gone */
482
+ }
483
+ finish([], false);
484
+ }, timeoutMs);
485
+ try {
486
+ child = spawn(query.command, query.args, {
487
+ shell: false,
488
+ windowsHide: true,
489
+ stdio: ["ignore", "pipe", "ignore"],
490
+ });
491
+ }
492
+ catch {
493
+ finish([], false);
494
+ return;
495
+ }
496
+ let out = "";
497
+ child.stdout.on("data", (chunk) => {
498
+ out += chunk.toString();
499
+ });
500
+ child.once("error", () => finish([], false));
501
+ // A non-zero exit (or a signal) means the listing is PARTIAL at best.
502
+ // Whatever it printed is not evidence of what is NOT running, which is
503
+ // exactly the question the orphan predicate asks of it.
504
+ child.once("close", (code, signal) => finish(parseProcessTable(out, query.tabSeparated, query.fields), code === 0 && !signal));
505
+ });
506
+ }
507
+ /**
508
+ * Command-line substrings that identify an LSP server process we launch.
509
+ * Deliberately narrow — matching on the distinctive binary/module name, not
510
+ * a generic "node" or "language-server" fragment, so the scan doesn't flag
511
+ * unrelated node processes on a shared CI runner.
512
+ */
513
+ export const LSP_PROCESS_MARKERS = [
514
+ "typescript-language-server",
515
+ "ast-grep lsp",
516
+ "ast-grep-lsp",
517
+ "pyright-langserver",
518
+ "vscode-json-languageserver",
519
+ ];
520
+ /**
521
+ * @typedef {{ pid: number, command: string }} ProcessRow
522
+ */
523
+ /**
524
+ * True iff `command` looks like one of the LSP servers pi-lens spawns.
525
+ *
526
+ * @param {string} command
527
+ */
528
+ export function isLspServerCommand(command) {
529
+ const lower = command.toLowerCase();
530
+ return LSP_PROCESS_MARKERS.some((marker) => lower.includes(marker.toLowerCase()));
531
+ }
532
+ /**
533
+ * Diff a `before` and `after` process snapshot and return the LSP-server
534
+ * rows that are NEW in `after` (i.e. survived/spawned during the run and are
535
+ * still alive after pi exited — the orphan class #472 fixed). Matches by pid
536
+ * — a row present in both snapshots with the same pid is presumed to be an
537
+ * unrelated pre-existing process, not something this run leaked.
538
+ *
539
+ * @param {ProcessRow[]} before
540
+ * @param {ProcessRow[]} after
541
+ * @returns {ProcessRow[]}
542
+ */
543
+ export function diffSurvivingLspProcesses(before, after) {
544
+ const beforePids = new Set(before.map((r) => r.pid));
545
+ return after.filter((row) => !beforePids.has(row.pid) && isLspServerCommand(row.command));
546
+ }
547
+ /**
548
+ * @typedef {{ rows: ProcessRow[], ok: boolean }} ProcessSnapshot
549
+ */
550
+ /**
551
+ * The "no surviving LSP process" assertion (compat-smoke-behavioral.mjs,
552
+ * Layer B assertion 3), extracted so it is unit-testable without spawning a
553
+ * real `ps`/CIM listing.
554
+ *
555
+ * An absence of a row from `after` only means something when BOTH snapshots
556
+ * are known-complete: `snapshotProcesses`'s `ok` is the only evidence of
557
+ * that (see its doc comment). A failed or timed-out listing yields an empty
558
+ * table, which must never read as "no leak" — that is silence, not
559
+ * evidence, and would make a caller-side outage report a false pass
560
+ * (PR #2438 review round 4, F-A).
561
+ *
562
+ * @param {ProcessSnapshot} before
563
+ * @param {ProcessSnapshot} after
564
+ * @returns {{ id: string, pass: boolean, detail: string }}
565
+ */
566
+ export function evaluateNoSurvivingLspProcesses(before, after) {
567
+ const id = "no-surviving-lsp-processes";
568
+ if (!before.ok || !after.ok) {
569
+ return {
570
+ id,
571
+ pass: false,
572
+ detail: `process listing unavailable (before.ok=${before.ok}, after.ok=${after.ok}); cannot verify no LSP process survived`,
573
+ };
574
+ }
575
+ const surviving = diffSurvivingLspProcesses(before.rows, after.rows);
576
+ return {
577
+ id,
578
+ pass: surviving.length === 0,
579
+ detail: surviving.length === 0
580
+ ? "no new LSP-server processes survived pi's exit"
581
+ : `${surviving.length} surviving process(es): ${surviving.map((p) => `pid=${p.pid} ${p.command.slice(0, 80)}`).join("; ")}`,
582
+ };
583
+ }