@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
@@ -15,6 +15,7 @@ import { createSubsystemLogger } from "./extension-log.js";
15
15
  import { incrementDegradationCount } from "./degradation-ledger.js";
16
16
  import * as fs from "node:fs";
17
17
  import * as path from "node:path";
18
+ import { findLocalBinsAt, VENV_BIN_DIRS } from "./package-manager.js";
18
19
  import { findNearestMarkerRoot } from "./path-utils.js";
19
20
  import { getScratchTreeFnmatchPatterns } from "./scratch-tree-policy.js";
20
21
  import { safeSpawnAsync } from "./safe-spawn.js";
@@ -171,22 +172,26 @@ export class PythonDeadCodeClient {
171
172
  * whatever happens to be active in the CALLING shell, not the project's
172
173
  * (#1731, discipline B — the same venv-first shape sqlfluff's binary
173
174
  * resolution already uses, `runner-helpers.ts` `createVenvFinder`).
175
+ *
176
+ * The directory list is the shared `VENV_BIN_DIRS` — this was a third
177
+ * private spelling of the same four venv directories, with a
178
+ * `process.platform` branch that meant the ubuntu lane never saw the
179
+ * `Scripts` half at all (defect shape 30).
180
+ *
181
+ * The search is a fixed lookup at `root` ONLY (`findLocalBinsAt`, no
182
+ * ancestor walk) — #2544 review round 3 folded this onto the shared
183
+ * ancestor walker, reasoning a monorepo package should find its
184
+ * repo-root venv, but that was an unrequested behaviour change with no
185
+ * #2514 defect behind it. Reverted in round 4 F2. `findLocalBinsAt`
186
+ * returns EVERY match at `root` rather than the first, because
187
+ * `doEnsureAvailable` probes candidates in order and must keep the
188
+ * `venv/` fallback when a `.venv/` vulture exists but cannot run.
174
189
  */
175
190
  venvCandidates(root) {
176
- const isWin = process.platform === "win32";
177
- const relPaths = isWin
178
- ? [
179
- path.join(".venv", "Scripts", "vulture.exe"),
180
- path.join("venv", "Scripts", "vulture.exe"),
181
- ]
182
- : [
183
- path.join(".venv", "bin", "vulture"),
184
- path.join("venv", "bin", "vulture"),
185
- ];
186
- return relPaths
187
- .map((rel) => path.join(root, rel))
188
- .filter((full) => fs.existsSync(full))
189
- .map((full) => ({ cmd: full, prefix: [] }));
191
+ return findLocalBinsAt(["vulture"], root, {
192
+ windowsExt: ".exe",
193
+ binDirs: VENV_BIN_DIRS,
194
+ }).map((full) => ({ cmd: full, prefix: [] }));
190
195
  }
191
196
  async doEnsureAvailable(root) {
192
197
  // Presence-gated, NOT auto-installed. vulture is a pure-Python package
@@ -7,9 +7,9 @@
7
7
  */
8
8
  import * as path from "node:path";
9
9
  import { isTestMode } from "./env-utils.js";
10
- import { getGlobalPiLensDir } from "./file-utils.js";
10
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
11
11
  import { createNdjsonLogger } from "./ndjson-logger.js";
12
- const LOG_DIR = getGlobalPiLensDir();
12
+ const LOG_DIR = getGlobalPiLensLogDir();
13
13
  const LOG_FILE = path.join(LOG_DIR, "dead-code.log");
14
14
  const LOG_BACKUP_FILE = path.join(LOG_DIR, "dead-code.log.1");
15
15
  const MAX_LOG_BYTES = Math.max(128 * 1024, Number.parseInt(process.env.PI_LENS_DEAD_CODE_LOG_MAX_BYTES ?? "1048576", 10) || 1048576);
@@ -28,7 +28,3 @@ export function logDeadCodeScan(event) {
28
28
  return;
29
29
  writer.log({ ts: new Date().toISOString(), ...event });
30
30
  }
31
- /** Resolve once all enqueued dead-code writes are on disk (tests/shutdown). */
32
- export function flushDeadCodeLog() {
33
- return writer.flush();
34
- }
@@ -10,6 +10,7 @@
10
10
  * promise's late rejection (an unhandled rejection if the timer won first), and
11
11
  * `withinRemaining` never cleared its timer.
12
12
  */
13
+ import { recordDegradationOnce } from "./degradation-ledger.js";
13
14
  /**
14
15
  * Combine multiple abort signals into one that aborts when ANY of them does.
15
16
  * Returns the single signal unchanged when only one is live, and `undefined`
@@ -100,3 +101,180 @@ export function withinRemaining(promise, deadlineAt) {
100
101
  onReject: "undefined",
101
102
  });
102
103
  }
104
+ /**
105
+ * The abort half of a bound, as a value the race can settle on. A symbol, not
106
+ * `undefined`, so `bounded` can tell "the signal fired" from "the awaited work
107
+ * genuinely resolved `undefined`" — the distinction AGENTS.md defect shape 10
108
+ * (silencing counted as fixing) exists to protect.
109
+ */
110
+ const BOUND_ABORTED = Symbol("pi-lens/bounded/aborted");
111
+ /**
112
+ * Await `promise` under BOTH bounds a hook needs: a wall-clock budget AND the
113
+ * hook's own abort signal (#2523 AC2).
114
+ *
115
+ * ## Why the type refuses one bound
116
+ *
117
+ * Every bound in this codebase lived at a LEAF — a spawn timeout, an LSP wait
118
+ * — so a dependency that wedged before reaching the leaf was unreachable by
119
+ * all of them, and `wrapSessionEventHandler` adds neither. The two halves fail
120
+ * differently and neither substitutes for the other: a deadline alone means
121
+ * Escape cannot release the hook (measured: still blocked 30s after the abort
122
+ * fired), and a signal alone means a dependency that wedges without anyone
123
+ * pressing Escape never returns at all (measured: 400s, the harness ceiling).
124
+ * `ms` and `signal` are therefore both non-optional properties, so
125
+ * `bounded(p, { ms: 500, hook, label })` is a COMPILE error, not a review
126
+ * catch. `tests/clients/bounded-hook-await.test.ts` pins that with
127
+ * `@ts-expect-error`: weaken either property and those directives become
128
+ * unused, which `tsc` reports as TS2578.
129
+ *
130
+ * ## Semantics
131
+ *
132
+ * - Resolves to the promise's value when it settles inside both bounds.
133
+ * - Resolves to `undefined` when the budget elapses OR a signal aborts,
134
+ * whichever comes first — and firing a signal settles IMMEDIATELY, it
135
+ * never waits the remaining deadline out.
136
+ * - Rejections propagate: this helper decides how LONG to wait, never what an
137
+ * error means. Once a bound has fired, a later rejection from the
138
+ * superseded promise is suppressed (a no-op catch is attached to the work
139
+ * up front) so it cannot surface as an unhandled rejection.
140
+ *
141
+ * ## What reaches the ledger, and what deliberately does not
142
+ *
143
+ * Which bound fired decides this, via {@link BoundCause}. Only the DEADLINE
144
+ * records `hook-await-exceeded` — ONE rising-edge row per (hook, label) via
145
+ * `recordDegradationOnce`, naming the hook, the await, the budget, the actual
146
+ * elapsed ms and the cause. Bounded by construction: a hook that blows its
147
+ * budget on every turn of a long session writes one ledger row, not one per
148
+ * turn. Promote it to `incrementDegradationCount` if slice 2 finds the exact
149
+ * tally is needed.
150
+ *
151
+ * A `"caller-abort"` records NOTHING. The caller's signal is the user pressing
152
+ * Escape or a turn being cancelled — a deliberate action, not a degraded
153
+ * environment. `clients/bootstrap.ts` already carries this lesson at its
154
+ * `if (unavailableReason !== "aborted")` guard: writing a cancel to the ledger
155
+ * made it indistinguishable from an unhealthy analyzer graph in
156
+ * `pilens_health`. This helper is meant to become THE bound primitive at
157
+ * ~187 call sites, so recording a cancel here would ship that inversion
158
+ * everywhere at once.
159
+ *
160
+ * A `"shutdown"` records `hook-await-abandoned`, which is in
161
+ * `INFORMATIONAL_DEGRADATION_KINDS` — teardown abandoning in-flight work is
162
+ * the design, so it is a tally rather than a `⚠`.
163
+ *
164
+ * `undefined` therefore means "no answer inside the bound" and must never be
165
+ * read as an empty/clean ANSWER (defect shape 10, and the #240 lesson that a
166
+ * failed diagnostic pull is not a clean file). The caller decides what the
167
+ * absence means; the ledger row is what makes it visible either way.
168
+ *
169
+ * The promise is not cancelled — nothing here can cancel work already
170
+ * started. It is abandoned, with the abandonment recorded. Work that must not
171
+ * outlive its hook needs the signal threaded INTO it as well (#2523 slice 2's
172
+ * `TurnEndDeps`/`AgentEndDeps`/`SessionStartDeps` change); wrapping the await
173
+ * bounds the HOOK, not the work.
174
+ */
175
+ export async function bounded(promise, options) {
176
+ const { signal, shutdownSignal, hook, label } = options;
177
+ // A non-finite budget settles immediately rather than throwing. The type
178
+ // forbids it, but a JS caller (or a `@ts-expect-error` probe) can still
179
+ // hand one over, and `setTimeout(fn, NaN)` would fire on the next tick
180
+ // with a warning — a helper whose whole job is keeping a hook from blowing
181
+ // up must not itself misbehave inside one. Same
182
+ // `Number.isFinite`-before-`Math.max` gate the runtime-config readers use.
183
+ const ms = Number.isFinite(options.ms) ? Math.max(0, options.ms) : 0;
184
+ const startedAt = Date.now();
185
+ // Late rejection from work a bound has already abandoned must not surface
186
+ // as an unhandled rejection. Attached to the BOXED promise, which is what
187
+ // the race below actually subscribes to; the caller still sees a rejection
188
+ // that arrives inside the bounds, because `Promise.race` attaches its own
189
+ // handler to the same promise.
190
+ const boxed = promise.then((value) => ({ value }));
191
+ void boxed.catch(() => { });
192
+ // ONE timer and ONE listener per source signal, all released in `finally`
193
+ // (#2530 review F2/F4).
194
+ //
195
+ // The round-1 implementation folded the wall clock in as
196
+ // `AbortSignal.timeout(ms)` and combined it through `AbortSignal.any`,
197
+ // then ALSO passed `ms` to `withDeadline` — two timers for one budget, and
198
+ // a composite signal per call. `AbortSignal.any` does not add a listener
199
+ // to its sources: it appends the composite to an internal dependant list
200
+ // on each of them, which lives as long as the SOURCE does. The hook signal
201
+ // outlives one await by a whole turn, so 20 000 successful `bounded()`
202
+ // calls on one hook signal left 20 000 entries on it, and the `finally`'s
203
+ // `removeEventListener` — aimed at the throwaway composite — removed
204
+ // nothing from the source. Listening on the sources directly is what makes
205
+ // the cleanup reach the thing that outlives the call.
206
+ let cause;
207
+ let timer;
208
+ let onCallerAbort;
209
+ let onShutdownAbort;
210
+ const abandoned = new Promise((resolve) => {
211
+ // `abandonedError`'s precedence, unchanged: the caller's own signal
212
+ // first, then teardown, then the clock. A turn cancelled while the
213
+ // session also happens to be tearing down is still a cancel.
214
+ const fire = (reason) => {
215
+ cause ??= reason;
216
+ resolve(BOUND_ABORTED);
217
+ };
218
+ // `signal?.` — a missing signal is treated as one that never aborts.
219
+ // The KEY is required (so a call that never mentions a signal does not
220
+ // compile), but its type admits `undefined` because several seams
221
+ // genuinely hold `AbortSignal | undefined` — see `BoundedOptions.signal`
222
+ // for which ones and why. This branch is what lets them pass it straight
223
+ // through instead of each spelling its own never-aborting stand-in
224
+ // (#2557 review F2), and it is also what keeps a JS caller, or a
225
+ // `@ts-expect-error` probe, from making a helper whose whole job is
226
+ // keeping a hook from blowing up throw `Cannot read properties of
227
+ // undefined` (#2530 round 3 F5).
228
+ if (signal?.aborted)
229
+ return fire("caller-abort");
230
+ if (shutdownSignal?.aborted)
231
+ return fire("shutdown");
232
+ // A zero budget ("this hook may not await at all") is a bound that has
233
+ // already elapsed, not a timer to arm.
234
+ if (ms <= 0)
235
+ return fire("deadline");
236
+ timer = setTimeout(() => fire("deadline"), ms);
237
+ // Never hold the event loop open past the hook this bounds.
238
+ timer.unref?.();
239
+ onCallerAbort = () => fire("caller-abort");
240
+ signal?.addEventListener("abort", onCallerAbort, { once: true });
241
+ if (shutdownSignal) {
242
+ onShutdownAbort = () => fire("shutdown");
243
+ shutdownSignal.addEventListener("abort", onShutdownAbort, {
244
+ once: true,
245
+ });
246
+ }
247
+ });
248
+ try {
249
+ // The value is boxed so a promise that legitimately resolves
250
+ // `undefined` stays distinguishable from a bound firing (defect shape
251
+ // 10: a silenced answer must not read as a clean one).
252
+ const settled = await Promise.race([boxed, abandoned]);
253
+ if (settled !== BOUND_ABORTED)
254
+ return settled.value;
255
+ const elapsedMs = Date.now() - startedAt;
256
+ const fired = cause ?? "deadline";
257
+ // A caller's own abort is Escape or a cancelled turn — deliberate, not
258
+ // a degradation, and deliberately absent from the ledger.
259
+ if (fired === "deadline" || fired === "shutdown") {
260
+ recordDegradationOnce({
261
+ kind: fired === "deadline" ? "hook-await-exceeded" : "hook-await-abandoned",
262
+ subject: `${hook}:${label}`,
263
+ reason: fired === "deadline"
264
+ ? `exceeded ${ms}ms budget after ${elapsedMs}ms`
265
+ : `abandoned after ${elapsedMs}ms: session is shutting down (budget ${ms}ms)`,
266
+ metadata: { hook, label, budgetMs: ms, elapsedMs, cause: fired },
267
+ });
268
+ }
269
+ return undefined;
270
+ }
271
+ finally {
272
+ if (timer !== undefined)
273
+ clearTimeout(timer);
274
+ if (onCallerAbort)
275
+ signal?.removeEventListener("abort", onCallerAbort);
276
+ if (onShutdownAbort) {
277
+ shutdownSignal?.removeEventListener("abort", onShutdownAbort);
278
+ }
279
+ }
280
+ }
@@ -43,12 +43,12 @@
43
43
  */
44
44
  import * as asyncHooks from "node:async_hooks";
45
45
  import * as path from "node:path";
46
- import { getGlobalPiLensDir } from "./file-utils.js";
46
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
47
47
  import { getMaxLogSizeMB } from "./log-cleanup.js";
48
48
  import { createNdjsonLogger } from "./ndjson-logger.js";
49
49
  /** Read once at module load — see module docstring. Not re-read per call. */
50
50
  const DEBUG_HANDLES_ENABLED = process.env.PI_LENS_DEBUG_HANDLES === "1";
51
- const DEBUG_HANDLES_LOG_FILE = path.join(getGlobalPiLensDir(), "debug-handles.log");
51
+ const DEBUG_HANDLES_LOG_FILE = path.join(getGlobalPiLensLogDir(), "debug-handles.log");
52
52
  /** Cap on tracked in-flight creation sites (bounded past this). */
53
53
  export const TRACKER_MAX_ENTRIES = 500;
54
54
  /** The first N insertion-order entries are never evicted — see the module
@@ -50,7 +50,7 @@
50
50
  import * as fs from "node:fs";
51
51
  import * as path from "node:path";
52
52
  import * as v8 from "node:v8";
53
- import { getGlobalPiLensDir } from "./file-utils.js";
53
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
54
54
  import { getMaxLogSizeMB } from "./log-cleanup.js";
55
55
  import { createNdjsonLogger } from "./ndjson-logger.js";
56
56
  /** Read once at module load — see module docstring. Not re-read per call. */
@@ -61,7 +61,7 @@ const DEBUG_HEAP_ENABLED = process.env.PI_LENS_DEBUG_HEAP === "1";
61
61
  export const SNAPSHOT_RETENTION = 3;
62
62
  const SNAPSHOT_PREFIX = "heap-";
63
63
  const SNAPSHOT_SUFFIX = ".heapsnapshot";
64
- const HEAP_SNAPSHOT_LOG_FILE = path.join(getGlobalPiLensDir(), "heap-snapshots.log");
64
+ const HEAP_SNAPSHOT_LOG_FILE = path.join(getGlobalPiLensLogDir(), "heap-snapshots.log");
65
65
  // Only ever constructed when the flag is on — the entire cost of this module
66
66
  // when unset is the one boolean check at the top of each export below.
67
67
  const writer = DEBUG_HEAP_ENABLED
@@ -138,7 +138,7 @@ export function pruneOldSnapshots(dir, keep = SNAPSHOT_RETENTION) {
138
138
  export function writeHeapSnapshotNow(label) {
139
139
  if (!DEBUG_HEAP_ENABLED)
140
140
  return null;
141
- const dir = getGlobalPiLensDir();
141
+ const dir = getGlobalPiLensLogDir();
142
142
  try {
143
143
  fs.mkdirSync(dir, { recursive: true });
144
144
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The one owner of "work that keeps talking to the LSP service after the hook
3
+ * that started it has returned" (#2504 review round 2, F3).
4
+ *
5
+ * #2504 moved the cold-cache actionable-warnings fresh-pull loop off the
6
+ * awaited `turn_end` hook. That loop then had effectively ONE bound: it
7
+ * captured the COMPLETED turn's `ctx.signal` — which `index.ts` clears from
8
+ * the ambient slot in its `finally`, so it can never fire — plus a 60 s wall
9
+ * deadline checked only BETWEEN files. A wedged `getDiagnostics` was therefore
10
+ * unbounded, the loop kept opening files (and could re-spawn servers) after
11
+ * `turn_end` returned and after the LSP idle reset, a `session_shutdown`
12
+ * landing mid-loop hit the #234 spawn-at-teardown shape, and the handle was a
13
+ * module-level `let` with no reset that a second deferral simply overwrote.
14
+ *
15
+ * This module supplies the missing halves: ONE registered handle (held by
16
+ * whichever deferral got there first, released when its work settles — see
17
+ * `armDeferredLspWork`) and ONE abort signal that
18
+ * `resetLSPService` fires — the single choke point through which
19
+ * `session_shutdown`, `session_start` and the idle reset all retire the
20
+ * service, so no caller has to remember to wire each lifecycle event
21
+ * separately. Aborting only ever settles a promise; it never spawns, which is
22
+ * what AGENTS.md's #234 teardown rule requires of anything reachable from
23
+ * `session_shutdown`.
24
+ *
25
+ * Deliberately a zero-dependency leaf, for the same reason
26
+ * `map-with-concurrency.ts` is one: `clients/lsp/index.ts` must not gain an
27
+ * import edge to `actionable-warnings.ts` (and through it `lsp/edits.ts`,
28
+ * `lsp-mutation.ts`, the durable store …) for a 20-line handle registry.
29
+ */
30
+ let deferredController;
31
+ let deferredWork;
32
+ /**
33
+ * Abort the in-flight deferred LSP work, if any, and drop the slot.
34
+ *
35
+ * Safe at teardown: it settles a promise and spawns nothing. Idempotent — a
36
+ * second call with no armed work is a no-op.
37
+ */
38
+ export function abortDeferredLspWork(reason) {
39
+ const controller = deferredController;
40
+ deferredController = undefined;
41
+ deferredWork = undefined;
42
+ controller?.abort(new Error(reason));
43
+ }
44
+ /**
45
+ * Claim the deferred-work slot, or `undefined` when an incumbent still holds
46
+ * it. THE INCUMBENT WINS (#2504 review round 3, F-A(d)).
47
+ *
48
+ * Round 2 had arming ABORT the previous deferral, reasoning that two loops
49
+ * must not run concurrently against one LSP service. The first half of that is
50
+ * right and still holds — there is exactly one slot. The second half was
51
+ * backwards about which loop to keep. A deferral is armed by a turn that
52
+ * primed NO LSP cache, which in a live editing session is every turn; so every
53
+ * turn cancelled its predecessor, and an aborted loop publishes nothing by
54
+ * design (its service is presumed gone). Back-to-back editing turns therefore
55
+ * delivered NOTHING AT ALL — the exact delivery AC #2504 exists to preserve.
56
+ *
57
+ * Keeping the incumbent inverts that: the loop that is already talking to the
58
+ * service runs to completion and publishes, and the newcomer is declined and
59
+ * says so out loud. The cost is stated rather than silent — the declining
60
+ * turn's cold files go unchecked on this channel until a later turn defers.
61
+ *
62
+ * Declining cannot latch. `registerDeferredLspWork` releases the slot the
63
+ * moment the work settles, and the loop itself is bounded twice over
64
+ * (`ACTIONABLE_WARNINGS_DEFERRED_BUDGET_MS` between files, a per-round-trip
65
+ * timeout inside them), so an incumbent always yields the slot in bounded
66
+ * time. Teardown is unaffected: `abortDeferredLspWork` still evicts the
67
+ * incumbent unconditionally, which is how `resetLSPService` retires it.
68
+ */
69
+ export function armDeferredLspWork() {
70
+ if (isDeferredLspWorkArmed())
71
+ return undefined;
72
+ const controller = new AbortController();
73
+ deferredController = controller;
74
+ deferredWork = undefined;
75
+ return controller.signal;
76
+ }
77
+ /**
78
+ * Register the armed work's promise so callers (and tests) have something to
79
+ * await. Ignored when the slot has already been re-armed or aborted since — a
80
+ * late registration must not resurrect a retired handle.
81
+ *
82
+ * Registration also arranges the RELEASE (#2504 review round 3, F-A(d)):
83
+ * because the slot now turns a newcomer away rather than evicting the
84
+ * incumbent, something has to hand it back, and the only correct moment is
85
+ * when the work settles. Guarded on identity, so a slot re-armed or aborted in
86
+ * the meantime is never cleared by a stale finalizer.
87
+ */
88
+ export function registerDeferredLspWork(signal, work) {
89
+ if (deferredController?.signal !== signal)
90
+ return;
91
+ deferredWork = work;
92
+ void work.finally(() => {
93
+ if (deferredController?.signal !== signal)
94
+ return;
95
+ deferredController = undefined;
96
+ deferredWork = undefined;
97
+ });
98
+ }
99
+ /** The in-flight deferred work, or an already-resolved promise when idle. */
100
+ export function awaitDeferredLspWork() {
101
+ return deferredWork ?? Promise.resolve();
102
+ }
103
+ /** True while a deferral holds the slot and has not been aborted. */
104
+ export function isDeferredLspWorkArmed() {
105
+ return deferredController?.signal.aborted === false;
106
+ }
@@ -1,17 +1,25 @@
1
1
  /** Bounded, process-local telemetry for behavior degraded during one session. */
2
2
  import { logExtension } from "./extension-log.js";
3
- import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
3
+ import { DEGRADATION_ENTRIES_PER_KIND, LEDGER_FIELD_MAX, truncateForLedger, } from "./ledger-bounds.js";
4
4
  import { logLatency } from "./latency-logger.js";
5
- import { getSinkWriteFailures, resetSinkWriteFailures, } from "./ndjson-logger.js";
5
+ import { getSinkRotations, getSinkWriteFailures, resetSinkRotations, resetSinkWriteFailures, } from "./ndjson-logger.js";
6
6
  // #2146: pulled at READ time, never pushed. `process-singletons.ts` is a
7
7
  // dependency leaf on purpose — it cannot import this module without closing a
8
8
  // no-client-cycles cycle through instance-registry/instance-reaper — so the
9
9
  // ledger reaches IN for its reset log, the same inversion `getSinkWriteFailures`
10
10
  // above uses.
11
11
  import { getProcessSingletonResets, PROCESS_SINGLETON_RESET_KIND, } from "./process-singletons.js";
12
+ // #2506: same inversion, same reason — the log-dir resolver cannot import this
13
+ // module (directly OR dynamically). THIS module is on a no-client-cycles cycle
14
+ // via extension-log.ts; the resolver's whole correctness argument is that it
15
+ // sits off every cycle, so an edge in that direction would put it back on one.
16
+ // `getGlobalPiLensLogDir()`'s probe-home-redirect event is therefore written
17
+ // into `probe-home-state.ts` and read back here instead. See that module's doc
18
+ // comment for the full account.
19
+ import { getProbeHomeRedirectEvent } from "./probe-home-state.js";
12
20
  // Re-exported so existing importers keep one name for the ledger's bound.
13
21
  export { LEDGER_FIELD_MAX, truncateForLedger };
14
- const ENTRIES_PER_KIND = 20;
22
+ const ENTRIES_PER_KIND = DEGRADATION_ENTRIES_PER_KIND;
15
23
  const MAX_DISTINCT_KINDS = 32;
16
24
  const OVERFLOW_KIND = "other";
17
25
  const groups = new Map();
@@ -65,7 +73,7 @@ export function recordDegradationOnce(record) {
65
73
  return;
66
74
  onceKeys.add(key);
67
75
  if (recordDegradation({ kind, subject, reason: record.reason })) {
68
- logDurableDegradation(kind, subject, 1, record.metadata);
76
+ logDurableDegradation(kind, subject, 1, record.metadata, record.code);
69
77
  }
70
78
  }
71
79
  catch (error) {
@@ -114,7 +122,7 @@ export function incrementDegradationCount(record) {
114
122
  // Durable rows use the summary's admission and emit the first event and
115
123
  // power-of-two milestones only, keeping the sink bounded.
116
124
  if (admitted && isPowerOfTwo(count)) {
117
- logDurableDegradation(kind, subject, count, record.metadata);
125
+ logDurableDegradation(kind, subject, count, record.metadata, record.code);
118
126
  }
119
127
  return count === 1;
120
128
  }
@@ -130,7 +138,7 @@ export function incrementDegradationCount(record) {
130
138
  * redaction, and write queue. The subject and kind were already bounded by the
131
139
  * ledger before reaching this seam.
132
140
  */
133
- function logDurableDegradation(kind, subject, count, metadata) {
141
+ function logDurableDegradation(kind, subject, count, metadata, code) {
134
142
  const boundedMetadata = boundLedgerMetadata(metadata);
135
143
  logLatency({
136
144
  type: "phase",
@@ -143,6 +151,9 @@ function logDurableDegradation(kind, subject, count, metadata) {
143
151
  subject,
144
152
  count,
145
153
  ledgerGeneration,
154
+ // Reserved row field, written AFTER the bounded caller metadata so the
155
+ // `MAX_METADATA_KEYS` cap cannot evict the stable code (#2418).
156
+ ...(code ? { code } : {}),
146
157
  },
147
158
  });
148
159
  }
@@ -190,6 +201,38 @@ export function getDegradationSummary() {
190
201
  })),
191
202
  });
192
203
  }
204
+ // #2505, same read-time fold: a mid-session rotation is visible without
205
+ // this module writing about it through the very sink family it is
206
+ // reporting on — see the `log-sink-rotated` doc comment on
207
+ // `DegradationKind`.
208
+ const sinkRotations = getSinkRotations();
209
+ const rotated = sinkRotations.filter((sink) => sink.rotationCount > 0);
210
+ if (rotated.length > 0) {
211
+ summary.push({
212
+ kind: "log-sink-rotated",
213
+ count: rotated.reduce((total, sink) => total + sink.rotationCount, 0),
214
+ droppedCount: 0,
215
+ latestReasons: rotated.map((sink) => ({
216
+ subject: truncateForLedger(sink.file),
217
+ reason: truncateForLedger(`${sink.rotationCount} rotation(s) at the configured byte bound`),
218
+ })),
219
+ });
220
+ }
221
+ // A rotation the writer ATTEMPTED and could not complete is a different
222
+ // fact from a rotation that happened, and the only signal that a sink is
223
+ // growing past its bound right now (#2505 review F2).
224
+ const rotateFailed = sinkRotations.filter((sink) => sink.failureCount > 0);
225
+ if (rotateFailed.length > 0) {
226
+ summary.push({
227
+ kind: "log-sink-rotate-failed",
228
+ count: rotateFailed.reduce((total, sink) => total + sink.failureCount, 0),
229
+ droppedCount: 0,
230
+ latestReasons: rotateFailed.map((sink) => ({
231
+ subject: truncateForLedger(sink.file),
232
+ reason: truncateForLedger(`${sink.failureCount} failed rotation attempt(s); this sink is growing past its byte bound`),
233
+ })),
234
+ });
235
+ }
193
236
  // #2146, same read-time fold: process-singleton resets live in the leaf
194
237
  // module's own bounded log. One entry per family, so this group's count is
195
238
  // the number of families this build could not adopt, never an event tally.
@@ -205,6 +248,23 @@ export function getDegradationSummary() {
205
248
  })),
206
249
  });
207
250
  }
251
+ // #2506, same read-time fold: `getGlobalPiLensLogDir()`'s probe-home redirect
252
+ // fires at most once per process (see `probe-home-state.ts`), so this is a
253
+ // presence check, not a tally.
254
+ const probeHomeRedirect = getProbeHomeRedirectEvent();
255
+ if (probeHomeRedirect) {
256
+ summary.push({
257
+ kind: "global-dir-probe-redirect",
258
+ count: 1,
259
+ droppedCount: 0,
260
+ latestReasons: [
261
+ {
262
+ subject: truncateForLedger(probeHomeRedirect.probeHome),
263
+ reason: truncateForLedger(`PI_LENS_HOME unset with cwd in an agent worktree/tmp probe context (${probeHomeRedirect.cwd}), or PILENS_PROBE=1 forced it; LOGS redirected away from the real home directory (tools, bin and instances.json are unaffected)`),
264
+ },
265
+ ],
266
+ });
267
+ }
208
268
  return summary;
209
269
  }
210
270
  function isRenderableSummary(value) {
@@ -223,6 +283,34 @@ function isRenderableSummary(value) {
223
283
  typeof entry.reason === "string"));
224
284
  });
225
285
  }
286
+ /**
287
+ * Kinds that record something the system did ON PURPOSE, correctly, and
288
+ * that a reader only needs a tally of — never a call to action (#2505
289
+ * review). A routine log rotation at the configured bound is the writer
290
+ * working as designed; giving it the same warning marker a real
291
+ * degradation gets trains the reader to ignore the marker. The FAILED
292
+ * rotation is the line that has to stand out, so it is deliberately NOT
293
+ * in this set.
294
+ */
295
+ const INFORMATIONAL_DEGRADATION_KINDS = new Set([
296
+ "log-sink-rotated",
297
+ // #2504 review round 8 (S1): both fire on the ordinary re-edit cadence
298
+ // (a carry-forward window closing, or a session boundary) and are
299
+ // self-healing -- the next turn's analysis or deferral re-observes the
300
+ // file -- so a `⚠` overstates them the same way a routine log rotation
301
+ // would.
302
+ "actionable-warnings-inband-superseded",
303
+ "actionable-warnings-deferred-superseded",
304
+ // #2523: a hook await abandoned because the session is tearing down. The
305
+ // teardown signal firing is the design working, so it is a tally; the
306
+ // BUDGET blow-out (`hook-await-exceeded`) is the line that has to stand
307
+ // out, and a caller's own Escape records nothing at all.
308
+ "hook-await-abandoned",
309
+ // #2524: can fire on a query that ultimately settles `ok` (see the kind's
310
+ // doc comment above) and is frequent/self-healing by design — a `⚠` would
311
+ // cry wolf on the sampler's ordinary best-effort data loss.
312
+ "resource-sampler-scanner-escalated",
313
+ ]);
226
314
  export function renderDegradationLines(summary = getDegradationSummary()) {
227
315
  if (!isRenderableSummary(summary))
228
316
  return [];
@@ -231,6 +319,9 @@ export function renderDegradationLines(summary = getDegradationSummary()) {
231
319
  return [
232
320
  "Degradations:",
233
321
  ...summary.map((group) => {
322
+ if (INFORMATIONAL_DEGRADATION_KINDS.has(group.kind)) {
323
+ return ` ${group.kind}: ${group.count}`;
324
+ }
234
325
  const latest = group.latestReasons.at(-1);
235
326
  return ` ⚠ ${group.kind}: ${group.count}${latest ? ` — ${latest.subject}: ${latest.reason}` : ""}`;
236
327
  }),
@@ -258,6 +349,10 @@ export function resetDegradationLedger() {
258
349
  // process-lifetime latch too — it re-arms alongside the rest of the
259
350
  // ledger rather than surviving past the session that observed it.
260
351
  resetSinkWriteFailures();
352
+ // #2505, same catalog shape 17 re-arm: a rotation tally recurs (new writes
353
+ // keep crossing the bound), so clearing it costs nothing and a later
354
+ // session re-observes the fact fresh.
355
+ resetSinkRotations();
261
356
  // #2146 review F3: the OTHER pulled source, `getProcessSingletonResets()`,
262
357
  // deliberately does NOT re-arm here, and the difference from its neighbour
263
358
  // above is the point. A sink write failure recurs — new writes fail, so
@@ -270,6 +365,17 @@ export function resetDegradationLedger() {
270
365
  // session (one entry per family, capped at 16), so leaving it costs a fixed
271
366
  // handful of lines and keeps a process-scope fact visible for the process's
272
367
  // life. Deliberate exception to catalog shape 17, not an oversight.
368
+ //
369
+ // `getProbeHomeRedirectEvent()` (#2506) is the same shape as the
370
+ // process-singleton case above and for the same reason: the redirect is
371
+ // resolved at most once per PROCESS (memoized in the `globalThis` slot
372
+ // `probe-home-state.ts` owns, not a session-scoped one), so it cannot
373
+ // recur within this process's life either — clearing it here would hide
374
+ // the fact from every session after the first in the same probe process.
273
375
  }
274
- export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
376
+ // Re-exported so every existing importer keeps its specifier. The value now
377
+ // lives in the `ledger-bounds.js` leaf (#2426), so a producer that only needs
378
+ // the BOUND does not have to import the ledger — and cannot be broken by a test
379
+ // that mocks it wholesale.
380
+ export { DEGRADATION_ENTRIES_PER_KIND };
275
381
  export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
@@ -11,6 +11,7 @@
11
11
  import { createSubsystemLogger } from "./extension-log.js";
12
12
  import * as fs from "node:fs";
13
13
  import * as path from "node:path";
14
+ import { mapWithConcurrency } from "./map-with-concurrency.js";
14
15
  import { findNodeToolBinary } from "./package-manager.js";
15
16
  import { isFullyQualified } from "./path-utils.js";
16
17
  import { safeSpawnAsync } from "./safe-spawn.js";
@@ -138,28 +139,16 @@ async function resolvedCommandIsStale(resolved, spawnableCache) {
138
139
  }
139
140
  /**
140
141
  * Run `mapper` over `items` with at most `concurrency` in flight at once.
142
+ *
141
143
  * Exported (#1810 review F6) so `clients/dispatch/runners/biome-check.ts`'s
142
144
  * `resolveBiomeFixKinds` can reuse this exact worker-pool shape for its
143
- * `biome explain` fan-out instead of carrying a second copy — this file
144
- * predates that need but is otherwise unrelated to it; a shared home for
145
- * this one helper wasn't worth a whole new module for two call sites.
145
+ * `biome explain` fan-out instead of carrying a second copy. #2504 moved the
146
+ * VALUE to the zero-dependency `map-with-concurrency.js` leaf — a third caller
147
+ * (`runtime-turn.ts`) needs the pool without pulling this analyzer client into
148
+ * the eager startup graph — and re-exports it here so every existing importer
149
+ * keeps its specifier. This stays the helper's documented home.
146
150
  */
147
- export async function mapWithConcurrency(items, concurrency, mapper) {
148
- if (items.length === 0)
149
- return;
150
- let nextIndex = 0;
151
- const workerCount = Math.max(1, Math.min(concurrency, items.length));
152
- const worker = async () => {
153
- while (true) {
154
- const index = nextIndex++;
155
- if (index >= items.length)
156
- return;
157
- await mapper(items[index]);
158
- }
159
- };
160
- const workers = Array.from({ length: workerCount }, () => worker());
161
- await Promise.all(workers);
162
- }
151
+ export { mapWithConcurrency };
163
152
  // --- Client ---
164
153
  export class DependencyChecker {
165
154
  madgeAvailability = createAvailabilityChecker("madge", ".cmd", ["--version"], {
@@ -128,7 +128,7 @@ export function computeStrictAnchor(args) {
128
128
  });
129
129
  }
130
130
  /** Intent-level anchor — see module doc. Used for defer/flagged/suppress. */
131
- export function computeWeakAnchor(args) {
131
+ function computeWeakAnchor(args) {
132
132
  return stableFindingId("ddw:", {
133
133
  cwd: args.cwd,
134
134
  filePath: args.filePath,