@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
@@ -1,5 +1,135 @@
1
+ /**
2
+ * Analyzer bootstrap: the 17 shell-out analysis clients, loaded ON DEMAND.
3
+ *
4
+ * #2467: nothing about extension activation needs these clients. They are
5
+ * consumed by session startup's background scans, by the bootstrap-dependent
6
+ * `tool_call` branches, and by the project-diagnostics tools — every one of
7
+ * which can prove it needs them before the graph is paid for. So activation
8
+ * never touches this seam, and the load itself sits behind ONE shared
9
+ * retryable promise every consumer joins: `clients/single-flight.ts`'s
10
+ * primitive owns the identity-guarded release, so a rejected load frees the
11
+ * key for the next demand instead of latching (#1690/#1674) and concurrent
12
+ * demands never start a second load.
13
+ *
14
+ * The measured cost this moves off the interactive path is ~16ms of module
15
+ * evaluation plus ~3ms of construction, on top of whatever the 17 client
16
+ * modules are not already resident for — see the PR body for the numbers.
17
+ * The first session of a process runs in QUICK mode, which uses none of these
18
+ * clients, so that whole cost used to be paid for nothing on exactly the
19
+ * start the user is waiting on.
20
+ *
21
+ * Two accessors, deliberately different contracts:
22
+ *
23
+ * - {@link loadBootstrapClients} is the STRICT form the callers that cannot
24
+ * proceed without the clients keep using. It rejects rather than inventing
25
+ * an answer.
26
+ * - {@link requestBootstrapClients} is the BOUNDED, FAIL-OPEN form (#2467):
27
+ * it bounds the caller's wait on both axes AGENTS.md requires — a
28
+ * wall-clock ceiling and the caller's abort signal — and returns `null`
29
+ * rather than throwing, so the caller proceeds without the clients and the
30
+ * degradation is counted once in the ledger instead of surfacing as a
31
+ * failed tool call.
32
+ *
33
+ * Neither bound cancels the LOAD; promises are not cancellable and a dynamic
34
+ * import has no abort seam. They bound the WAIT. The shared flight keeps
35
+ * running, so a later demand reuses whatever it produced — the same
36
+ * abandon-the-wait, keep-the-work shape as `lsp-pull-late-answer`.
37
+ */
1
38
  import { logExtension } from "./extension-log.js";
2
- let bootstrapPromise = null;
39
+ import { bounded } from "./deadline-utils.js";
40
+ import { logLatency } from "./latency-logger.js";
41
+ import { emitBounded } from "./bounded-telemetry.js";
42
+ import { recordDegradationOnce } from "./degradation-ledger.js";
43
+ import { createSingleFlight } from "./single-flight.js";
44
+ /**
45
+ * The clients of a COMPLETED load, or `null` while none has completed.
46
+ *
47
+ * Process-lifetime on purpose: the 17 clients are stateless constructions and
48
+ * every session-scoped latch that lives ON them is re-armed at `session_start`
49
+ * by its own registered reset (`resetInstallRetryLatches`, #1497, is the
50
+ * canonical one). Dropping the clients at a session boundary would re-pay the
51
+ * whole load for nothing and would silently orphan those resets.
52
+ *
53
+ * Only a SUCCESSFUL load writes it, which is what makes retry-after-failure
54
+ * work at all: a rejected load leaves this `null`, so the next demand starts a
55
+ * fresh flight instead of replaying the rejection (#1570's eviction, now owned
56
+ * by the single-flight primitive's identity-guarded release).
57
+ */
58
+ let residentClients = null;
59
+ /**
60
+ * Set by {@link markAnalyzerBootstrapShutdown} when the PRIMARY session tears
61
+ * down. It refuses NEW loads only — a demand that already joined the in-flight
62
+ * load keeps its promise and still settles (#2467 AC4). Session-scoped, not
63
+ * process-scoped: a replacement session in the same process must be able to
64
+ * load again, which is what {@link resetAnalyzerBootstrapSessionState} restores.
65
+ */
66
+ let bootstrapShutdown = false;
67
+ /**
68
+ * The abort signal every BOUNDED demand races, aborted by
69
+ * {@link markAnalyzerBootstrapShutdown} and replaced at `session_start`.
70
+ *
71
+ * #2467 review: the two session-start schedulers must NOT bind the ambient
72
+ * TURN signal — a `session_start` that lands mid-turn (sequential
73
+ * replacement, `/new`) would otherwise cancel every startup scan with no
74
+ * retry, which is #1394's exact lesson. They are not turn-scoped work. But
75
+ * "no abort at all" is not the answer either: AGENTS.md's both-bounds rule
76
+ * wants a wall-clock ceiling AND an abort race, and the bound that is
77
+ * genuinely theirs is the SESSION's own teardown. So the seam supplies it,
78
+ * for every caller, rather than each caller inventing one.
79
+ */
80
+ let bootstrapShutdownController = new AbortController();
81
+ /**
82
+ * Consecutive BUILD failures, reset by a successful build and by
83
+ * `session_start`. See {@link BOOTSTRAP_FAILURE_STRIKE_LIMIT}.
84
+ */
85
+ let bootstrapFailureStrikes = 0;
86
+ /**
87
+ * How many BUILDS have been started this process (not how many demands were
88
+ * made — concurrent demands share one build). Stamped into the
89
+ * `bootstrap_clients_load` record as `attempt`, which is the discrimination
90
+ * #2467's observability section asks for: it tells a field reader whether a
91
+ * record is the initial attempt or a retry after a transient failure, without
92
+ * adding a per-demand record.
93
+ */
94
+ let bootstrapLoadAttempts = 0;
95
+ /**
96
+ * The one key in the flight registry. `createSingleFlight` is keyed because
97
+ * most of its sites dedupe per file or per server; this one has a single
98
+ * subject, so it pays one map entry for the four guards the primitive owns.
99
+ */
100
+ const BOOTSTRAP_FLIGHT_KEY = "analyzer-bootstrap";
101
+ /**
102
+ * No `generation` hook. The primitive's own doc states the rule: a generation
103
+ * is for an answer a session boundary INVALIDATES. These clients are stateless
104
+ * and outlive every session in the process, so a pre-reset flight's result is
105
+ * exactly as correct for a post-reset caller — a generation check here could
106
+ * never kill a test, which is the vacuous guard this repo keeps deleting.
107
+ */
108
+ const bootstrapFlight = createSingleFlight();
109
+ /**
110
+ * Wall-clock ceiling on ONE caller's wait, not on the load.
111
+ *
112
+ * Measured cold cost of the load is ~100ms from an empty module graph and
113
+ * ~19ms once `index.ts`'s own graph is resident, so 10s is not a performance
114
+ * budget — it is the liveness bound AGENTS.md's both-bounds rule requires, so
115
+ * a wedged module evaluation cannot park a `tool_call` handler forever.
116
+ */
117
+ const BOOTSTRAP_LOAD_TIMEOUT_MS = 10_000;
118
+ /**
119
+ * How many consecutive failed builds before the seam stops rebuilding.
120
+ *
121
+ * Retry-after-failure is right for a TRANSIENT fault, which is the case the
122
+ * flight's identity-guarded release was written for. It is wrong for a
123
+ * PERMANENT one: an analyzer module that cannot resolve under the host's
124
+ * package layout (#285/#335) fails identically every time, and without a
125
+ * latch every later demand re-ran seventeen dynamic imports plus
126
+ * `collectInstallDiagnostics` — on the tool-call hot path — for an answer
127
+ * that could not change. Three strikes distinguishes "the filesystem
128
+ * hiccuped" from "this environment cannot load these analyzers"; after that
129
+ * the demand fails OPEN immediately, and `session_start` re-arms the latch so
130
+ * a repaired environment is never locked out for the life of the process.
131
+ */
132
+ const BOOTSTRAP_FAILURE_STRIKE_LIMIT = 3;
3
133
  /**
4
134
  * A stand-in for an analysis client whose module failed to load (an unresolved
5
135
  * runtime dependency under a package-manager layout the resolver can't traverse
@@ -48,81 +178,387 @@ async function logBootstrapFailures(failures) {
48
178
  }
49
179
  }
50
180
  /**
51
- * Every per-client load below is individually fail-soft (`load`/`loadList`
52
- * degrade to a stub instead of throwing), so this promise is not expected to
53
- * reject in practice. It is still memoized with eviction-on-rejection —
54
- * consistent with the other lazy-import memos (#1570) — so a genuinely
55
- * unexpected throw (e.g. `logBootstrapFailures`) cannot latch a permanently
56
- * rejected bootstrap for the rest of the process.
181
+ * Build the 17 clients once. Every per-client load below is individually
182
+ * fail-soft (`load`/`loadList` degrade to a stub instead of throwing), so this
183
+ * is not expected to reject in practice — a genuinely unexpected throw (e.g.
184
+ * `logBootstrapFailures`) is what the retryable flight above exists for.
185
+ *
186
+ * Not exported: callers go through {@link loadBootstrapClients} or
187
+ * {@link requestBootstrapClients} so the sharing, the retry, and the shutdown
188
+ * gate cannot be bypassed by a new call site.
189
+ */
190
+ async function buildBootstrapClients() {
191
+ const failures = [];
192
+ // Load + construct one client in isolation; on failure record it and
193
+ // substitute a degraded no-op stub so the others still load — single-seam
194
+ // fail-soft, consumers never special-case it.
195
+ async function load(name, make) {
196
+ try {
197
+ return await make();
198
+ }
199
+ catch (err) {
200
+ failures.push({ name, err });
201
+ return degradedClient();
202
+ }
203
+ }
204
+ // Lists degrade to empty (a stub Proxy isn't iterable), e.g. dead-code.
205
+ async function loadList(name, make) {
206
+ try {
207
+ return await make();
208
+ }
209
+ catch (err) {
210
+ failures.push({ name, err });
211
+ return [];
212
+ }
213
+ }
214
+ const [ruffClient, biomeClient, knipClient, todoScanner, jscpdClient, depChecker, testRunnerClient, metricsClient, complexityClient, goClient, govulncheckClient, gitleaksClient, trivyClient, opengrepClient, rustClient, agentBehaviorClient, deadCodeClients,] = await Promise.all([
215
+ load("ruff", async () => new (await import("./ruff-client.js")).RuffClient()),
216
+ load("biome", async () => new (await import("./biome-client.js")).BiomeClient()),
217
+ load("knip", async () => new (await import("./knip-client.js")).KnipClient()),
218
+ load("todo", async () => new (await import("./todo-scanner.js")).TodoScanner()),
219
+ load("jscpd", async () => new (await import("./jscpd-client.js")).JscpdClient()),
220
+ load("dependency-checker", async () => new (await import("./dependency-checker.js")).DependencyChecker()),
221
+ load("test-runner", async () => new (await import("./test-runner-client.js")).TestRunnerClient()),
222
+ load("metrics", async () => new (await import("./metrics-client.js")).MetricsClient()),
223
+ load("complexity", async () => new (await import("./complexity-client.js")).ComplexityClient()),
224
+ // The process-wide singleton, NOT a fresh instance (#2455 fix round 4,
225
+ // F2). `handleSessionStart` reads this object for its "Active tools"
226
+ // line while `resetGoAvailability` re-arms the singleton's latch; a
227
+ // second instance here meant the reset never reached the surface a
228
+ // user actually sees.
229
+ load("go", async () => (await import("./go-client.js")).goClient),
230
+ load("govulncheck", async () => new (await import("./govulncheck-client.js")).GovulncheckClient()),
231
+ load("gitleaks", async () => new (await import("./gitleaks-client.js")).GitleaksClient()),
232
+ load("trivy", async () => new (await import("./trivy-client.js")).TrivyClient()),
233
+ load("opengrep", async () => new (await import("./opengrep-client.js")).OpengrepClient()),
234
+ // The process-wide singleton, for the same reason as "go" above.
235
+ load("rust", async () => (await import("./rust-client.js")).rustClient),
236
+ load("agent-behavior", async () => new (await import("./agent-behavior-client.js")).AgentBehaviorClient()),
237
+ loadList("dead-code", async () => (await import("./dead-code-client.js")).getDeadCodeClients()),
238
+ ]);
239
+ if (failures.length > 0)
240
+ await logBootstrapFailures(failures);
241
+ return {
242
+ ruffClient,
243
+ biomeClient,
244
+ knipClient,
245
+ todoScanner,
246
+ jscpdClient,
247
+ depChecker,
248
+ testRunnerClient,
249
+ metricsClient,
250
+ complexityClient,
251
+ goClient,
252
+ govulncheckClient,
253
+ gitleaksClient,
254
+ trivyClient,
255
+ opengrepClient,
256
+ rustClient,
257
+ agentBehaviorClient,
258
+ deadCodeClients,
259
+ };
260
+ }
261
+ /**
262
+ * Why a demand could not be served: `shutdown` (the primary session already
263
+ * tore down, so no new load may start), `timeout` (the caller's wall-clock
264
+ * ceiling elapsed), `aborted` (the caller's own signal fired — Escape, turn
265
+ * abort), `failed` (the load itself rejected), or `latched`
266
+ * ({@link BOOTSTRAP_FAILURE_STRIKE_LIMIT} consecutive builds failed, so the
267
+ * seam stopped rebuilding). A `null` from
268
+ * {@link requestBootstrapClients} never says which on its own, so the reason
269
+ * travels in the bounded record and the ledger entry instead.
270
+ */
271
+ const BOOTSTRAP_UNAVAILABLE_REASONS = [
272
+ "shutdown",
273
+ "timeout",
274
+ "aborted",
275
+ "failed",
276
+ "latched",
277
+ ];
278
+ /** A demand that could not be served. Carries the discriminating reason. */
279
+ class BootstrapUnavailableError extends Error {
280
+ unavailableReason;
281
+ constructor(unavailableReason, message) {
282
+ super(message);
283
+ this.name = "BootstrapUnavailableError";
284
+ this.unavailableReason = unavailableReason;
285
+ }
286
+ }
287
+ /**
288
+ * The clients a completed load produced, or `null`.
289
+ *
290
+ * NEVER starts a load. This is the accessor for work that must touch the
291
+ * clients only if they already exist — session start's `metricsClient.reset()`
292
+ * and `knipClient.resetSessionState()` are exactly that: re-arming state on a
293
+ * client that was never constructed is vacuous, and loading 17 modules in
294
+ * order to reset nothing is the cost #2467 removes.
295
+ */
296
+ export function peekBootstrapClients() {
297
+ return residentClients;
298
+ }
299
+ /**
300
+ * A {@link SessionBootstrapAccess} over clients that are ALREADY loaded.
301
+ *
302
+ * `clients/mcp/session.ts` builds its whole session context up front — an MCP
303
+ * call has nothing to defer to — so it holds the seventeen clients before it
304
+ * ever calls `handleSessionStart`. This is the two-line adapter that lets the
305
+ * handler take ONE shape instead of admitting a second, untyped one in which
306
+ * fifteen individually-optional client fields stood in for the seam ("exactly
307
+ * one of the two is supplied" was prose, not a type; a dropped
308
+ * `metricsClient` compiled and silently skipped every startup scan).
309
+ */
310
+ export function residentBootstrapAccess(clients) {
311
+ return {
312
+ peek: () => clients,
313
+ request: async () => clients,
314
+ };
315
+ }
316
+ /**
317
+ * The STRICT accessor: resolve the shared clients or reject.
318
+ *
319
+ * Concurrent callers join one flight (identity-guarded by
320
+ * `clients/single-flight.ts`, so a late settle can never evict a successor's
321
+ * entry). A rejected load releases the key, so the NEXT demand retries rather
322
+ * than replaying the rejection — with the caveat `clients/lazy-import.ts`
323
+ * documents: a module that threw during EVALUATION is memoized as rejected by
324
+ * Node's ESM loader, so a retry re-runs `buildBootstrapClients` but that one
325
+ * module's `import()` replays its cached rejection. Every per-client load is
326
+ * individually fail-soft, so that case degrades one analyzer, not the set.
57
327
  */
58
328
  export function loadBootstrapClients() {
59
- bootstrapPromise ??= (async () => {
60
- const failures = [];
61
- // Load + construct one client in isolation; on failure record it and
62
- // substitute a degraded no-op stub so the others still load — single-seam
63
- // fail-soft, consumers never special-case it.
64
- async function load(name, make) {
65
- try {
66
- return await make();
67
- }
68
- catch (err) {
69
- failures.push({ name, err });
70
- return degradedClient();
71
- }
329
+ // The memo short-circuit sits ABOVE the flight check deliberately, and is
330
+ // written only on success — so it can never hide the in-flight path from a
331
+ // concurrent caller the way #1690's did. Before the first success there is
332
+ // no memo, which is exactly the window sharing has to cover.
333
+ if (residentClients)
334
+ return Promise.resolve(residentClients);
335
+ // #2467: refuse to START a load after primary shutdown, but never
336
+ // invalidate a waiter. `has()` is the whole guard: when a flight is already
337
+ // running the call below joins it, so the demand that was mid-air when
338
+ // shutdown landed still settles with real clients.
339
+ if (bootstrapShutdown && !bootstrapFlight.has(BOOTSTRAP_FLIGHT_KEY)) {
340
+ return Promise.reject(new BootstrapUnavailableError("shutdown", "analyzer bootstrap declined: primary session is shutting down"));
341
+ }
342
+ // Unlike the shutdown gate above, this one has no `!has()` half: strikes
343
+ // only ever reach the limit inside `noteBootstrapBuildFailure`, which runs
344
+ // synchronously INSIDE this flight's own callback, immediately before it
345
+ // rethrows and the flight's `release` runs — there is no synchronous window
346
+ // in which a fresh call can observe strikes at the limit while that same
347
+ // flight is still registered. A `!has()` half here would only ever be
348
+ // false, so it stayed an untested branch (#2467 review, F2) rather than a
349
+ // second guard.
350
+ if (bootstrapFailureStrikes >= BOOTSTRAP_FAILURE_STRIKE_LIMIT) {
351
+ return Promise.reject(new BootstrapUnavailableError("latched", `analyzer bootstrap declined: ${bootstrapFailureStrikes} consecutive load failures`));
352
+ }
353
+ return bootstrapFlight.run(BOOTSTRAP_FLIGHT_KEY, async () => {
354
+ const attempt = (bootstrapLoadAttempts += 1);
355
+ const startedAt = Date.now();
356
+ let clients;
357
+ try {
358
+ clients = await buildBootstrapClients();
72
359
  }
73
- // Lists degrade to empty (a stub Proxy isn't iterable), e.g. dead-code.
74
- async function loadList(name, make) {
75
- try {
76
- return await make();
77
- }
78
- catch (err) {
79
- failures.push({ name, err });
80
- return [];
81
- }
360
+ catch (err) {
361
+ noteBootstrapBuildFailure(err);
362
+ throw err;
82
363
  }
83
- const [ruffClient, biomeClient, knipClient, todoScanner, jscpdClient, depChecker, testRunnerClient, metricsClient, complexityClient, goClient, govulncheckClient, gitleaksClient, trivyClient, opengrepClient, rustClient, agentBehaviorClient, deadCodeClients,] = await Promise.all([
84
- load("ruff", async () => new (await import("./ruff-client.js")).RuffClient()),
85
- load("biome", async () => new (await import("./biome-client.js")).BiomeClient()),
86
- load("knip", async () => new (await import("./knip-client.js")).KnipClient()),
87
- load("todo", async () => new (await import("./todo-scanner.js")).TodoScanner()),
88
- load("jscpd", async () => new (await import("./jscpd-client.js")).JscpdClient()),
89
- load("dependency-checker", async () => new (await import("./dependency-checker.js")).DependencyChecker()),
90
- load("test-runner", async () => new (await import("./test-runner-client.js")).TestRunnerClient()),
91
- load("metrics", async () => new (await import("./metrics-client.js")).MetricsClient()),
92
- load("complexity", async () => new (await import("./complexity-client.js")).ComplexityClient()),
93
- load("go", async () => new (await import("./go-client.js")).GoClient()),
94
- load("govulncheck", async () => new (await import("./govulncheck-client.js")).GovulncheckClient()),
95
- load("gitleaks", async () => new (await import("./gitleaks-client.js")).GitleaksClient()),
96
- load("trivy", async () => new (await import("./trivy-client.js")).TrivyClient()),
97
- load("opengrep", async () => new (await import("./opengrep-client.js")).OpengrepClient()),
98
- load("rust", async () => new (await import("./rust-client.js")).RustClient()),
99
- load("agent-behavior", async () => new (await import("./agent-behavior-client.js")).AgentBehaviorClient()),
100
- loadList("dead-code", async () => (await import("./dead-code-client.js")).getDeadCodeClients()),
101
- ]);
102
- if (failures.length > 0)
103
- await logBootstrapFailures(failures);
104
- return {
105
- ruffClient,
106
- biomeClient,
107
- knipClient,
108
- todoScanner,
109
- jscpdClient,
110
- depChecker,
111
- testRunnerClient,
112
- metricsClient,
113
- complexityClient,
114
- goClient,
115
- govulncheckClient,
116
- gitleaksClient,
117
- trivyClient,
118
- opengrepClient,
119
- rustClient,
120
- agentBehaviorClient,
121
- deadCodeClients,
122
- };
123
- })().catch((err) => {
124
- bootstrapPromise = null;
125
- throw err;
364
+ // A SUCCESS is what makes the strikes consecutive rather than a running
365
+ // total, so one bad build in a healthy process never edges the latch shut.
366
+ bootstrapFailureStrikes = 0;
367
+ residentClients = clients;
368
+ // The one timing record #2467's observability section keeps. It used to
369
+ // be emitted by `handleSessionStart` from timestamps `index.ts` took
370
+ // around its eager pre-handler load; now it is emitted where the load
371
+ // actually happens, so it stays truthful once the load is on demand.
372
+ // `attempt` distinguishes a retry from the initial attempt without
373
+ // adding a per-demand record.
374
+ logLatency({
375
+ type: "phase",
376
+ phase: "bootstrap_clients_load",
377
+ filePath: process.cwd(),
378
+ startedAt: new Date(startedAt).toISOString(),
379
+ durationMs: Date.now() - startedAt,
380
+ metadata: { attempt },
381
+ });
382
+ return clients;
126
383
  });
127
- return bootstrapPromise;
384
+ }
385
+ /**
386
+ * Count a failed build and, on the transition that closes the latch, write the
387
+ * ONE durable row saying this environment has stopped being retried.
388
+ *
389
+ * `recordDegradationOnce` is the repo's own once-per-kind/subject recorder, so
390
+ * the latch needs no parallel "have I logged this yet" boolean beside the one
391
+ * the ledger already keeps.
392
+ */
393
+ function noteBootstrapBuildFailure(err) {
394
+ bootstrapFailureStrikes += 1;
395
+ if (bootstrapFailureStrikes !== BOOTSTRAP_FAILURE_STRIKE_LIMIT)
396
+ return;
397
+ recordDegradationOnce({
398
+ kind: "analyzer-bootstrap-latched",
399
+ subject: BOOTSTRAP_FLIGHT_KEY,
400
+ reason: `${BOOTSTRAP_FAILURE_STRIKE_LIMIT} consecutive load failures; no further builds this session: ${err?.message ?? String(err)}`,
401
+ });
402
+ }
403
+ /**
404
+ * The BOUNDED, FAIL-OPEN accessor (#2467).
405
+ *
406
+ * Bounds the caller's WAIT on both axes AGENTS.md's both-bounds rule names —
407
+ * a wall-clock ceiling AND the caller's abort signal — and answers `null`
408
+ * instead of throwing, so the caller proceeds without the clients. Every
409
+ * `null` caused by the SEAM (shutdown, timeout, a rejected load, or the
410
+ * strike latch) is counted in the degradation ledger under
411
+ * `analyzer-bootstrap-unavailable`, rising-edge per subject, so a repeated
412
+ * failure stays one record with an exact count rather than per-demand spam.
413
+ * A `null` caused by the CALLER'S OWN signal firing is not: that is the
414
+ * caller choosing to stop waiting (Escape, a turn ending), not the analyzer
415
+ * graph degrading, and counting it inverted the two in `pilens_health`.
416
+ */
417
+ export async function requestBootstrapClients(options) {
418
+ if (residentClients)
419
+ return residentClients;
420
+ // The seam's own session-teardown bound travels with EVERY demand, so a
421
+ // caller whose work is not turn-scoped (the two session-start schedulers)
422
+ // still satisfies AGENTS.md's both-bounds rule without binding a turn
423
+ // signal that would cancel it for the wrong reason.
424
+ const shutdownSignal = bootstrapShutdownController.signal;
425
+ const timeoutMs = options.timeoutMs ?? BOOTSTRAP_LOAD_TIMEOUT_MS;
426
+ try {
427
+ // #2523 slice 2: this used to be `awaitWithinBounds`, a private copy of
428
+ // exactly what `bounded()` does — same three-way precedence, same
429
+ // remove-the-listener-in-`finally`, same abandon-the-wait-keep-the-work
430
+ // contract. `bounded()` is a true superset of it (it also records the
431
+ // abandonment), so the copy is gone rather than kept in sync by hand.
432
+ //
433
+ // `undefined` here can only mean a bound fired: `loadBootstrapClients`
434
+ // resolves `BootstrapClients` or rejects, never `undefined`.
435
+ const clients = await bounded(loadBootstrapClients(), {
436
+ ms: timeoutMs,
437
+ // Genuinely absent for the three session-start schedulers — see
438
+ // `SessionBootstrapAccess.request` for why binding a turn signal
439
+ // there is the wrong bound. `shutdownSignal` is the second live
440
+ // bound in that case, so both still hold.
441
+ signal: options.signal,
442
+ shutdownSignal,
443
+ // The hook on the hook axis, the demand on the label (#2557 F7).
444
+ // `<hook>:loadBootstrapClients:<reason>` joins against
445
+ // `HOOK_WALL_BUDGET_MS` and still says WHICH demand blew the budget.
446
+ hook: options.hook,
447
+ label: `loadBootstrapClients:${options.reason}`,
448
+ });
449
+ if (clients)
450
+ return clients;
451
+ throw abandonedError(options.signal, shutdownSignal, timeoutMs);
452
+ }
453
+ catch (err) {
454
+ const unavailableReason = err instanceof BootstrapUnavailableError
455
+ ? err.unavailableReason
456
+ : "failed";
457
+ // "aborted" means the CALLER'S OWN signal fired — a user pressing Escape
458
+ // mid-tool-call, a turn ending. That is the caller choosing to stop
459
+ // waiting, not the analyzer graph degrading, and writing it to the ledger
460
+ // inverted the two: a deliberate cancel surfaced identically to an
461
+ // unhealthy environment in `pilens_health`. `shutdown`/`timeout`/`failed`/
462
+ // `latched` all describe the SEAM failing to serve the demand, so those
463
+ // still count.
464
+ if (unavailableReason !== "aborted") {
465
+ emitBounded("bootstrap_clients_unavailable", options.reason, {
466
+ durationMs: 0,
467
+ metadata: {
468
+ unavailableReason,
469
+ attempt: bootstrapLoadAttempts,
470
+ detail: err?.message ?? String(err),
471
+ },
472
+ }, {
473
+ ledgerKind: "analyzer-bootstrap-unavailable",
474
+ risingEdgePer: "identity",
475
+ reason: unavailableReason,
476
+ });
477
+ }
478
+ return null;
479
+ }
480
+ }
481
+ /**
482
+ * Which bound won, read off the caller's own signal rather than guessed.
483
+ *
484
+ * Read AFTER `bounded()` has settled, exactly as the deleted `awaitWithinBounds`
485
+ * read it after its own race — and in the same precedence `bounded()` applies
486
+ * internally (caller, then teardown, then the clock), so the reason this
487
+ * reconstructs is the reason that fired.
488
+ */
489
+ function abandonedError(signal, shutdownSignal, timeoutMs) {
490
+ if (signal?.aborted) {
491
+ return new BootstrapUnavailableError("aborted", "analyzer bootstrap wait abandoned: caller aborted");
492
+ }
493
+ if (shutdownSignal.aborted) {
494
+ return new BootstrapUnavailableError("shutdown", "analyzer bootstrap wait abandoned: primary session is shutting down");
495
+ }
496
+ return new BootstrapUnavailableError("timeout", `analyzer bootstrap wait abandoned after ${timeoutMs}ms`);
497
+ }
498
+ /**
499
+ * Refuse NEW loads from here on — the primary session is tearing down.
500
+ *
501
+ * Deliberately does NOT clear `residentClients` or the in-flight entry: a
502
+ * demand already waiting must still settle (#2467), and the clients a
503
+ * completed load produced stay usable for whatever teardown work is still
504
+ * draining. Nothing here spawns, which is what AGENTS.md's #234 teardown rule
505
+ * requires of a `session_shutdown`-time call.
506
+ */
507
+ export function markAnalyzerBootstrapShutdown() {
508
+ bootstrapShutdown = true;
509
+ // Unparks any bounded demand still waiting on a load rather than leaving it
510
+ // to burn the full wall-clock ceiling for clients its session will never use.
511
+ bootstrapShutdownController.abort();
512
+ }
513
+ /**
514
+ * Re-arm the shutdown gate at `session_start`.
515
+ *
516
+ * The gate is a per-SESSION claim ("this session is over") held in
517
+ * process-lived storage — AGENTS.md defect shape 17 exactly. Without this a
518
+ * replacement session in the same process would find the analyzers refused
519
+ * for the rest of the process. The resident clients are NOT dropped: see
520
+ * `residentClients`' own comment for why re-paying the load at every session
521
+ * boundary would be wrong.
522
+ */
523
+ export function resetAnalyzerBootstrapSessionState() {
524
+ bootstrapShutdown = false;
525
+ bootstrapShutdownController = new AbortController();
526
+ // The strike latch is a per-SESSION claim too: an environment repaired
527
+ // between sessions (a dependency installed, a package layout fixed) must be
528
+ // retried, not written off for the life of the process.
529
+ bootstrapFailureStrikes = 0;
530
+ }
531
+ /**
532
+ * Is the shutdown gate closed — i.e. would a fresh demand be refused?
533
+ *
534
+ * A synchronous read of the same flag {@link loadBootstrapClients} checks,
535
+ * so the session-state registry's probe can prove the session_start reset
536
+ * re-arms it without awaiting a load.
537
+ */
538
+ export function isAnalyzerBootstrapShutdown() {
539
+ return bootstrapShutdown;
540
+ }
541
+ /**
542
+ * How many consecutive builds have failed — the session-state registry's probe
543
+ * for the strike latch, which cannot be read off {@link isAnalyzerBootstrapShutdown}.
544
+ */
545
+ export function _analyzerBootstrapFailureStrikes() {
546
+ return bootstrapFailureStrikes;
547
+ }
548
+ /** Test-only: close the strike latch without paying three real failed builds. */
549
+ export function _armAnalyzerBootstrapLatchForTests() {
550
+ bootstrapFailureStrikes = BOOTSTRAP_FAILURE_STRIKE_LIMIT;
551
+ }
552
+ /** Test-only: drop the resident clients, the flight, and the gate. */
553
+ export function _resetAnalyzerBootstrapForTests() {
554
+ residentClients = null;
555
+ bootstrapShutdown = false;
556
+ bootstrapShutdownController = new AbortController();
557
+ bootstrapFailureStrikes = 0;
558
+ bootstrapLoadAttempts = 0;
559
+ bootstrapFlight.clear();
560
+ }
561
+ /** Test/diagnostic: how many builds have been STARTED this process. */
562
+ export function _analyzerBootstrapLoadAttempts() {
563
+ return bootstrapLoadAttempts;
128
564
  }