@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
@@ -67,11 +67,12 @@ import * as path from "node:path";
67
67
  import { writeFileAtomicAsync } from "./atomic-write.js";
68
68
  import { sweepOwnStagingFiles, } from "./atomic-write-staging.js";
69
69
  import { acquireQuarantinePidFileLock } from "./bounded-pid-file-lock.js";
70
- import { spawnCollectStdoutResult, unrefChildAndPipes, } from "./child-unref.js";
70
+ import { unrefChildAndPipes, } from "./child-unref.js";
71
71
  import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
72
72
  import { getGlobalPiLensDir } from "./file-utils.js";
73
73
  import { isInstanceRegistryEnabled, pruneDeadInstances as pruneDeadRegistryInstances, readInstanceRegistry, } from "./instance-registry.js";
74
74
  import { logLatency } from "./latency-logger.js";
75
+ import { queryProcessTable, windowsExe } from "./process-snapshot.js";
75
76
  const isWindows = process.platform === "win32";
76
77
  /**
77
78
  * KILL eligibility: pid-confirmed-dead ONLY. Heartbeat staleness must never
@@ -249,31 +250,14 @@ export async function sweepAtomicWriteStages(directories, options = {}) {
249
250
  const isPidAlive = options.isPidAlive ?? realIsPidAlive;
250
251
  return sweepOwnStagingFiles(directories, { maxEntries, isPidAlive });
251
252
  }
252
- function windowsExe(name) {
253
- return path.join(process.env.SystemRoot ?? String.raw `C:\Windows`, "System32", name);
254
- }
255
- /** Resolve an absolute path to `ps` (S4036: never spawn via bare PATH lookup).
256
- * Prefers `/bin/ps` (present on virtually every POSIX system), falls back to
257
- * `/usr/bin/ps`, and defaults back to `/bin/ps` if neither probe succeeds
258
- * (spawn will then fail closed rather than silently resolving via PATH). */
259
- function posixPsPath() {
260
- if (fs.existsSync("/bin/ps"))
261
- return "/bin/ps";
262
- if (fs.existsSync("/usr/bin/ps"))
263
- return "/usr/bin/ps";
264
- return "/bin/ps";
265
- }
266
- /** Escape a value for embedding in a WQL LIKE clause: WQL uses `'` as the
267
- * string delimiter (doubled to escape) and `%`/`_` as wildcards — the marker
268
- * is an opaque path string, so escape all three before interpolating. */
269
- function escapeWqlLikeValue(value) {
270
- return value.replaceAll("'", "''").replaceAll(/[%_]/g, (ch) => `[${ch}]`);
271
- }
272
- /** Escape a value for a WQL EQUALITY comparison: only the string delimiter
273
- * needs escaping — `%`/`_` are literal outside `LIKE`. */
274
- function escapeWqlStringValue(value) {
275
- return value.replaceAll("'", "''");
276
- }
253
+ /**
254
+ * Interpreter resolution, WQL escaping and the platform listing itself all
255
+ * live in the ONE process-table seam (#2443): `scripts/lib/process-scan.mjs`,
256
+ * reached from clients/ through `clients/process-snapshot.ts`. This file used
257
+ * to carry its own copy of each, which is how the three queries below drifted
258
+ * apart from the two in `clients/resource-sampler.ts` and the two in
259
+ * `scripts/`. `windowsExe` is re-exported by the seam and imported above.
260
+ */
277
261
  /** Search running processes whose command line contains `marker` (Windows,
278
262
  * via CIM/WQL). Returns matching pids. Best-effort: any failure ⇒ [], but a
279
263
  * failure is RECORDED (#1857 class sweep) — "the search found no orphan" and
@@ -282,14 +266,26 @@ function escapeWqlStringValue(value) {
282
266
  async function findPidsByMarkerWindows(marker) {
283
267
  if (!isWindows || !marker)
284
268
  return [];
285
- const escaped = escapeWqlLikeValue(marker);
286
- // $PID exclusion: the query's own powershell.exe command line embeds the
287
- // marker string, so it would match itself.
288
- const psScript = `Get-CimInstance Win32_Process -Filter "CommandLine LIKE '%${escaped}%'" ` +
289
- `| Where-Object { $_.ProcessId -ne $PID } ` +
290
- `| Select-Object -ExpandProperty ProcessId`;
291
- const powershell = windowsExe("WindowsPowerShell\\v1.0\\powershell.exe");
292
- const result = await spawnCollectStdoutResult(powershell, ["-NoProfile", "-NonInteractive", "-Command", psScript], { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS });
269
+ const result = await queryProcessTable({
270
+ fields: ["pid"],
271
+ filter: { column: "CommandLine", op: "like", values: [marker] },
272
+ // The query's own powershell.exe command line embeds the marker, so
273
+ // without this exclusion the search matches itself.
274
+ excludeSelfPid: true,
275
+ }, {
276
+ timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
277
+ // #2527 review F2: omitting `onTimeout` falls through to
278
+ // child-unref.ts's default — one bare, unverified `child.kill()`, no
279
+ // tree kill, no identity-carrying record — inside the orphan backstop
280
+ // path, the exact abandonment `terminateScannerChild`'s own doc
281
+ // comment says it exists to prevent. This query shares the backstop's
282
+ // scanner identity and budget (same producer as
283
+ // `enumerateManagedProcesses`), so it gets the same kind.
284
+ onTimeout: (child) => terminateScannerChild(child, {
285
+ kind: "orphan-backstop-scanner-escalated",
286
+ timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
287
+ }),
288
+ });
293
289
  if (result.status !== "ok") {
294
290
  recordDegradationOnce({
295
291
  kind: "orphan-backstop-scan-failed",
@@ -297,10 +293,7 @@ async function findPidsByMarkerWindows(marker) {
297
293
  reason: `marker command-line search ${result.status}`,
298
294
  });
299
295
  }
300
- return result.stdout
301
- .split(/\r?\n/)
302
- .map((line) => Number(line.trim()))
303
- .filter((n) => Number.isFinite(n) && n > 0);
296
+ return result.rows.map((row) => row.pid);
304
297
  }
305
298
  /** Fetch command lines for a set of pids in one query (Windows: CIM; POSIX:
306
299
  * `ps`). Returns a pid → command-line map; pids that can't be resolved are
@@ -317,59 +310,35 @@ async function queryCommandLines(pids) {
317
310
  const map = new Map();
318
311
  if (valid.length === 0)
319
312
  return map;
320
- const noteFailure = (status) => {
321
- if (status === "ok")
322
- return;
313
+ const result = await queryProcessTable({
314
+ fields: ["pid", "command"],
315
+ filter: { column: "ProcessId", op: "eq", values: valid },
316
+ }, {
317
+ timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
318
+ // #2527 review F2: same rationale as findPidsByMarkerWindows above —
319
+ // route through the reaper's own tree-kill-and-verify machinery with
320
+ // the backstop's kind rather than falling through to the unverified
321
+ // default kill with no ledger record.
322
+ onTimeout: (child) => terminateScannerChild(child, {
323
+ kind: "orphan-backstop-scanner-escalated",
324
+ timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS,
325
+ }),
326
+ });
327
+ // POSIX `ps -p` exits nonzero when NONE of the requested pids exist, which
328
+ // is a legitimate clean result for this caller, so that one status is not
329
+ // recorded on that platform. The shared collector calls it an exit failure;
330
+ // this caller's command contract makes it the expected empty table. Windows
331
+ // CIM has no such convention, so an exit failure there is a real failure.
332
+ const psReportedNoSuchPid = !isWindows && result.status === "exit-error";
333
+ if (result.status !== "ok" && !psReportedNoSuchPid) {
323
334
  recordDegradationOnce({
324
335
  kind: "orphan-backstop-scan-failed",
325
336
  subject: "identity-query",
326
- reason: `command-line identity query ${status}; reap suppressed this sweep`,
337
+ reason: `command-line identity query ${result.status}; reap suppressed this sweep`,
327
338
  });
328
- };
329
- if (isWindows) {
330
- const filter = valid.map((p) => `ProcessId=${p}`).join(" OR ");
331
- const psScript = `Get-CimInstance Win32_Process -Filter "${filter}" ` +
332
- `| ForEach-Object { "$($_.ProcessId)\t$($_.CommandLine)" }`;
333
- const powershell = windowsExe("WindowsPowerShell\\v1.0\\powershell.exe");
334
- const result = await spawnCollectStdoutResult(powershell, ["-NoProfile", "-NonInteractive", "-Command", psScript], { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS });
335
- noteFailure(result.status);
336
- for (const line of result.stdout.split(/\r?\n/)) {
337
- const tab = line.indexOf("\t");
338
- if (tab <= 0)
339
- continue;
340
- const pid = Number(line.slice(0, tab).trim());
341
- if (Number.isFinite(pid) && pid > 0)
342
- map.set(pid, line.slice(tab + 1));
343
- }
344
- return map;
345
- }
346
- const result = await spawnCollectStdoutResult(posixPsPath(), ["-p", valid.join(","), "-o", "pid=,args="], { shell: false, stdio: ["ignore", "pipe", "ignore"] }, { timeoutMs: BACKSTOP_SCAN_TIMEOUT_MS });
347
- // `ps -p` exits nonzero when NONE of the pids exist, which is a legitimate
348
- // clean result here, so only spawn/timeout failures are recorded. The
349
- // shared collector calls that an exit failure, but this caller's command
350
- // contract makes that status the expected empty-table result.
351
- if (result.status !== "exit-error")
352
- noteFailure(result.status);
353
- for (const line of result.stdout.split(/\r?\n/)) {
354
- // Linear parse (S8786/S6594: avoid regex backtracking on
355
- // attacker-lengthenable ps output) — trim leading whitespace,
356
- // then split on the first whitespace run: " 1234 args here".
357
- const trimmed = line.trimStart();
358
- if (!trimmed)
359
- continue;
360
- let i = 0;
361
- while (i < trimmed.length && trimmed[i] >= "0" && trimmed[i] <= "9")
362
- i++;
363
- if (i === 0)
364
- continue;
365
- const pidStr = trimmed.slice(0, i);
366
- let j = i;
367
- while (j < trimmed.length && (trimmed[j] === " " || trimmed[j] === "\t"))
368
- j++;
369
- const pid = Number(pidStr);
370
- if (Number.isFinite(pid) && pid > 0)
371
- map.set(pid, trimmed.slice(j));
372
339
  }
340
+ for (const row of result.rows)
341
+ map.set(row.pid, row.command);
373
342
  return map;
374
343
  }
375
344
  /**
@@ -401,8 +370,8 @@ export function buildIdentityMatcher(cmdlines) {
401
370
  /** Post-kill liveness poll budget. `taskkill /F /T` returns before the kernel
402
371
  * has finished tearing the tree down, so a single immediate check would
403
372
  * report false `alive`s. */
404
- export const KILL_VERIFY_ATTEMPTS = 5;
405
- export const KILL_VERIFY_INTERVAL_MS = 100;
373
+ const KILL_VERIFY_ATTEMPTS = 5;
374
+ const KILL_VERIFY_INTERVAL_MS = 100;
406
375
  /** `setTimeout` as an awaitable, with the timer unref'd so a settled one-shot
407
376
  * `pi --print` never waits on best-effort kill verification. */
408
377
  function sleepUnref(ms) {
@@ -490,7 +459,7 @@ async function verifyPidGone(pid, options = {}) {
490
459
  * (script path), not the process image name, exactly like the existing
491
460
  * marker-search's WQL LIKE query below.
492
461
  */
493
- export const MANAGED_BINARIES = [
462
+ const MANAGED_BINARIES = [
494
463
  { name: "ast-grep", launcher: "native" },
495
464
  { name: "opengrep-core", launcher: "native" },
496
465
  { name: "opengrep", launcher: "native" },
@@ -605,104 +574,42 @@ export function partitionBackstopCandidates(processes, registry, isPidAlive, opt
605
574
  }
606
575
  /** Enumerate live OS processes whose command line contains one of
607
576
  * `MANAGED_BINARY_NAMES` (#658), independent of the instance registry.
608
- * Windows: one batched CIM/WQL query (mirrors `findPidsByMarkerWindows`'s
609
- * query pattern). POSIX: `ps -eo pid=,ppid=,args=`, filtered in JS. Returns
610
- * `{pid, parentPid, command}` rows. Best-effort: any failure ⇒ []. */
577
+ * One projected process-table query through the shared seam, narrowed to
578
+ * managed binaries in JS; returns `{pid, parentPid, command, ageMs}` rows.
579
+ * Best-effort: any failure ⇒ [], with the scan status carried alongside so
580
+ * an empty table is never mistaken for a scan that did not run. */
611
581
  async function enumerateManagedProcesses(options = {}) {
612
- const timeoutMs = options.timeoutMs ?? BACKSTOP_SCAN_TIMEOUT_MS;
613
- const collect = {
614
- timeoutMs,
615
- onTimeout: (child) => terminateScannerChild(child, options),
616
- };
617
- if (isWindows) {
618
- // #1857: `Name = '…'` equality replaces eight leading-wildcard
619
- // `CommandLine LIKE '%…%'` clauses, and the query now also projects
620
- // `CreationDate` so the spawn-grace guard has an age to work with. The
621
- // image-name set is DERIVED from MANAGED_BINARIES (MANAGED_IMAGE_NAMES),
622
- // so adding a managed binary cannot leave a stale parallel list behind.
623
- const clauses = MANAGED_IMAGE_NAMES.map((name) => `Name = '${escapeWqlStringValue(name)}'`).join(" OR ");
624
- // The age column is computed IN PowerShell as a millisecond delta, not
625
- // exported as `CreationDate.Ticks`. `Ticks` is the LOCAL-time
626
- // representation, so subtracting the Unix epoch in JS produced an age
627
- // wrong by the machine's UTC offset — measured on a UTC+3 host as
628
- // -8358s for a process started 40 minutes earlier. A delta computed on
629
- // one side of the boundary has no timezone to get wrong. The explicit
630
- // `[datetime]` test matters: `(Get-Date) - $null` yields a two-thousand
631
- // year TimeSpan, which would read as "ancient" and defeat the grace
632
- // guard exactly where the data is missing.
633
- const psScript = `Get-CimInstance -Query "SELECT ProcessId,ParentProcessId,CreationDate,CommandLine FROM Win32_Process WHERE ${clauses}" ` +
634
- `| ForEach-Object { $age = if ($_.CreationDate -is [datetime]) { [int64]((Get-Date) - $_.CreationDate).TotalMilliseconds } else { '' }; ` +
635
- `"$($_.ProcessId)\t$($_.ParentProcessId)\t$age\t$($_.CommandLine)" }`;
636
- const powershell = windowsExe("WindowsPowerShell\\v1.0\\powershell.exe");
637
- const result = await spawnCollectStdoutResult(powershell, ["-NoProfile", "-NonInteractive", "-Command", psScript], { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }, collect);
638
- const processes = [];
639
- for (const line of result.stdout.split(/\r?\n/)) {
640
- const firstTab = line.indexOf("\t");
641
- if (firstTab <= 0)
642
- continue;
643
- const secondTab = line.indexOf("\t", firstTab + 1);
644
- if (secondTab <= 0)
645
- continue;
646
- const thirdTab = line.indexOf("\t", secondTab + 1);
647
- if (thirdTab <= 0)
648
- continue;
649
- const pid = Number(line.slice(0, firstTab).trim());
650
- const parentPid = Number(line.slice(firstTab + 1, secondTab).trim());
651
- const ageMs = parseNonNegativeMs(line.slice(secondTab + 1, thirdTab));
652
- const command = line.slice(thirdTab + 1);
653
- if (!Number.isFinite(pid) || pid <= 0)
654
- continue;
655
- processes.push({ pid, parentPid, command, ageMs });
656
- }
657
- return narrowToManagedBinaries(processes, result);
658
- }
659
- // POSIX: enumerate everything, filter in JS by managed-name substring —
660
- // there is no single-query WQL-style server-side filter available.
661
- // `etime` is the POSIX-standard elapsed-time column (Linux and macOS both
662
- // support it; `etimes` is Linux-only), and supplies the spawn-grace age.
663
- const result = await spawnCollectStdoutResult(posixPsPath(), [...POSIX_PS_ARGS], { shell: false, stdio: ["ignore", "pipe", "ignore"] }, collect);
664
- const processes = [];
665
- for (const line of result.stdout.split(/\r?\n/)) {
666
- // Linear parse (S8786/S6594): " pid ppid etime args here".
667
- const trimmed = line.trimStart();
668
- if (!trimmed)
669
- continue;
670
- let i = 0;
671
- while (i < trimmed.length && trimmed[i] >= "0" && trimmed[i] <= "9")
672
- i++;
673
- if (i === 0)
674
- continue;
675
- const pid = Number(trimmed.slice(0, i));
676
- let rest = trimmed.slice(i);
677
- let k = 0;
678
- while (k < rest.length && (rest[k] === " " || rest[k] === "\t"))
679
- k++;
680
- rest = rest.slice(k);
681
- let j = 0;
682
- while (j < rest.length && rest[j] >= "0" && rest[j] <= "9")
683
- j++;
684
- if (j === 0)
685
- continue;
686
- const parentPid = Number(rest.slice(0, j));
687
- rest = rest.slice(j);
688
- let m = 0;
689
- while (m < rest.length && (rest[m] === " " || rest[m] === "\t"))
690
- m++;
691
- rest = rest.slice(m);
692
- // etime token: a non-whitespace run of digits, ':' and '-'.
693
- let e = 0;
694
- while (e < rest.length && rest[e] !== " " && rest[e] !== "\t")
695
- e++;
696
- const ageMs = ageMsFromPosixEtime(rest.slice(0, e));
697
- let n = e;
698
- while (n < rest.length && (rest[n] === " " || rest[n] === "\t"))
699
- n++;
700
- const args = rest.slice(n);
701
- if (!Number.isFinite(pid) || pid <= 0)
702
- continue;
703
- processes.push({ pid, parentPid, command: args, ageMs });
704
- }
705
- return narrowToManagedBinaries(processes, result);
582
+ // #1857: `Name = '…'` equality replaces eight leading-wildcard
583
+ // `CommandLine LIKE '%…%'` clauses, and the projection carries an age so
584
+ // the spawn-grace guard has something to reason from. The image-name set is
585
+ // DERIVED from MANAGED_BINARIES (MANAGED_IMAGE_NAMES), so adding a managed
586
+ // binary cannot leave a stale parallel list behind.
587
+ //
588
+ // POSIX `ps` cannot express that filter — the seam reports it as
589
+ // `serverSideFiltered: false` and hands back the whole table — which is why
590
+ // `narrowToManagedBinaries` runs on BOTH platforms rather than only on the
591
+ // POSIX branch. Windows over-collects for its own reason anyway
592
+ // (`node.exe` is a superset image name covering every node process on the
593
+ // machine).
594
+ const scanTimeoutMs = options.timeoutMs ?? BACKSTOP_SCAN_TIMEOUT_MS;
595
+ const result = await queryProcessTable({
596
+ fields: ["pid", "ppid", "ageMs", "command"],
597
+ filter: { column: "Name", op: "eq", values: MANAGED_IMAGE_NAMES },
598
+ }, {
599
+ timeoutMs: scanTimeoutMs,
600
+ onTimeout: (child) => terminateScannerChild(child, {
601
+ verifyAttempts: options.verifyAttempts,
602
+ verifyIntervalMs: options.verifyIntervalMs,
603
+ kind: "orphan-backstop-scanner-escalated",
604
+ timeoutMs: scanTimeoutMs,
605
+ }),
606
+ });
607
+ return narrowToManagedBinaries(result.rows.map((row) => ({
608
+ pid: row.pid,
609
+ parentPid: row.ppid,
610
+ command: row.command,
611
+ ageMs: row.ageMs,
612
+ })), result);
706
613
  }
707
614
  /**
708
615
  * The ONE place a raw OS-process snapshot is narrowed to managed binaries.
@@ -730,6 +637,18 @@ function narrowToManagedBinaries(processes, result) {
730
637
  * exists to fix, so the scanner now gets exactly what an orphan gets:
731
638
  * `taskkill /F /T` or a POSIX group kill, then a verified liveness poll. The
732
639
  * escalation is recorded with the scanner's identity, never swallowed.
640
+ *
641
+ * The kill attempt itself is real regardless of what happens next: this
642
+ * handler is invoked the instant the caller's timer elapses, which can race
643
+ * a query child that is genuinely about to close successfully (its own
644
+ * "close" event may still win the overall settle — see
645
+ * `child-unref.ts#spawnCollectStdoutResult`, which resolves on whichever of
646
+ * "close" or the timeout handler's own settle lands first). This function has
647
+ * no way to observe that outcome — it runs strictly BEFORE the caller's
648
+ * promise resolves — so it always records the escalation it actually
649
+ * attempted; callers whose escalation kind can fire on that race classify it
650
+ * as informational rather than a warning (see
651
+ * `degradation-ledger.ts`'s `resource-sampler-scanner-escalated` doc comment).
733
652
  */
734
653
  export async function terminateScannerChild(child, options) {
735
654
  const pid = child.pid;
@@ -740,9 +659,9 @@ export async function terminateScannerChild(child, options) {
740
659
  })
741
660
  : "invalid";
742
661
  incrementDegradationCount({
743
- kind: "orphan-backstop-scanner-escalated",
662
+ kind: options.kind,
744
663
  subject: `${isWindows ? "win32-cim" : "posix-ps"}#${pid ?? "no-pid"}`,
745
- reason: `scan exceeded its timeout; tree kill reported ${outcome}`,
664
+ reason: `scan exceeded its ${options.timeoutMs}ms timeout; tree kill reported ${outcome}`,
746
665
  });
747
666
  return outcome;
748
667
  }
@@ -755,51 +674,6 @@ function matchesManagedBinary(command) {
755
674
  const lower = command.toLowerCase();
756
675
  return MANAGED_BINARY_NAMES.some((name) => lower.includes(name.toLowerCase()));
757
676
  }
758
- /**
759
- * Parse the Windows age column: a non-negative integer millisecond count, or
760
- * anything else. A negative or malformed value is `undefined`, not zero and
761
- * not a clamp — a nonsensical age means the age is UNKNOWN, and the grace
762
- * guard must spare an unknown-age process rather than reason from a number it
763
- * cannot trust. That rule is what surfaced the local-time tick bug during the
764
- * host probe instead of silently sparing everything.
765
- */
766
- function parseNonNegativeMs(raw) {
767
- const token = raw.trim();
768
- if (!/^\d+$/.test(token))
769
- return undefined;
770
- const value = Number(token);
771
- return Number.isFinite(value) ? value : undefined;
772
- }
773
- /**
774
- * POSIX enumeration columns. Exported so a real-`ps` test can assert this
775
- * exact argument vector is accepted by the host's `ps` — a rejected column
776
- * would make the whole backstop silently return zero rows, and this is the
777
- * one part of the fix that cannot be checked from a Windows dev machine.
778
- */
779
- export const POSIX_PS_ARGS = [
780
- "-eo",
781
- "pid=,ppid=,etime=,args=",
782
- ];
783
- /** Parse `ps -o etime` output: `[[dd-]hh:]mm:ss`. Returns undefined for any
784
- * shape the column did not produce (a `ps` without the column emits the
785
- * command line where the token was expected). */
786
- export function ageMsFromPosixEtime(raw) {
787
- const token = raw.trim();
788
- if (!/^(?:\d+-)?(?:\d+:)?\d+:\d+$/.test(token))
789
- return undefined;
790
- let days = 0;
791
- let rest = token;
792
- const dash = rest.indexOf("-");
793
- if (dash >= 0) {
794
- days = Number(rest.slice(0, dash));
795
- rest = rest.slice(dash + 1);
796
- }
797
- const parts = rest.split(":").map(Number);
798
- if (parts.some((part) => !Number.isFinite(part)))
799
- return undefined;
800
- const [hours, minutes, seconds] = parts.length === 3 ? parts : [0, parts[0], parts[1]];
801
- return ((days * 24 + hours) * 60 * 60 + minutes * 60 + seconds) * 1000;
802
- }
803
677
  /**
804
678
  * Fire-and-forget REGISTRY-INDEPENDENT backstop sweep (#658): finds
805
679
  * pi-lens-managed LSP/scanner processes the registry-driven `sweepOrphans`
@@ -970,11 +844,11 @@ function classifyKillOutcome(killed, unverified) {
970
844
  * not time-critical, so paying its cost once per half hour instead of once
971
845
  * per session_start removes essentially all of its aggregate cost.
972
846
  */
973
- export const BACKSTOP_COOLDOWN_MS = 30 * 60 * 1000;
847
+ const BACKSTOP_COOLDOWN_MS = 30 * 60 * 1000;
974
848
  /** How long after session_start the deferred sweep fires. Chosen to clear the
975
849
  * warmup window it was measured starving: `warmup_total` median 3288ms,
976
850
  * worst on record 5480ms (#1857). */
977
- export const BACKSTOP_START_DELAY_MS = 30_000;
851
+ const BACKSTOP_START_DELAY_MS = 30_000;
978
852
  /** Cap on process identities embedded in one latency record. */
979
853
  const BACKSTOP_IDENTITY_LOG_LIMIT = 10;
980
854
  /**
@@ -982,7 +856,7 @@ const BACKSTOP_IDENTITY_LOG_LIMIT = 10;
982
856
  * a candidate that was 1ms too fresh is comfortably past the grace by the
983
857
  * time the retry looks at it again.
984
858
  */
985
- export const BACKSTOP_GRACE_RETRY_MARGIN_MS = 30_000;
859
+ const BACKSTOP_GRACE_RETRY_MARGIN_MS = 30_000;
986
860
  /** How long the sweep lock may be held before another process reclaims it.
987
861
  * Well above the scan timeout plus the kill budget, and irrelevant when the
988
862
  * holder dies — the lock reclaims immediately on a dead owner pid
@@ -192,9 +192,12 @@ export function getInstanceRoots(entry) {
192
192
  * pi-lens process reads on every heartbeat. Eviction drops the OLDEST
193
193
  * NON-PRIMARY root: `projectRoot` is the host's own working directory and the
194
194
  * one the shared-checkout guard most needs, so it is never evicted.
195
- * Deliberately smaller than `SESSION_ROOT_CAP` (128,
196
- * `clients/lsp/session-roots.ts:45`) because that set lives in memory for one
197
- * process while this one is serialized to disk for all of them.
195
+ * Deliberately smaller than `SESSION_ROOT_CAP` (128, `clients/lsp/
196
+ * session-roots.ts`) because that registry lives in memory for one process
197
+ * while this one is serialized to disk for all of them. The two are not
198
+ * mirrors: they answer different questions about different populations, so
199
+ * #2518's one-store rule (a root's LSP config cannot be evicted while the
200
+ * root is served) does not extend to this cap.
198
201
  */
199
202
  const INSTANCE_ROOT_CAP = 32;
200
203
  /**
@@ -160,15 +160,6 @@ function computeProjectLanguageProfile(projectRoot, sourceFiles) {
160
160
  detectedKinds,
161
161
  };
162
162
  }
163
- export function hasLanguage(profile, kind) {
164
- return !!profile.present[kind];
165
- }
166
- export function hasAnyLanguage(profile, kinds) {
167
- return kinds.some((kind) => hasLanguage(profile, kind));
168
- }
169
- export function isLanguageConfigured(profile, kind) {
170
- return !!profile.configured[kind];
171
- }
172
163
  export function getDefaultStartupTools(profile) {
173
164
  const tools = new Set();
174
165
  for (const kind of Object.keys(LANGUAGE_POLICY)) {