@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
@@ -62,6 +62,7 @@
62
62
  * healing direction never re-trigger a resync — only the rising edge does.
63
63
  */
64
64
  import * as fs from "node:fs";
65
+ import { BoundedFifoMap } from "./bounded-cache.js";
65
66
  import { logLatency } from "./latency-logger.js";
66
67
  import { toProjectRelativePath } from "./path-utils.js";
67
68
  import { STALE_LINE_MARKER } from "./stale-marker.js";
@@ -90,16 +91,12 @@ export function createLineCountCache() {
90
91
  // mismatched mtime always recomputes (see the module doc comment). Bounded
91
92
  // via FIFO eviction (mirrors `READ_GUARD_MAX_FILES`'s precedent) so a long
92
93
  // session's file count cannot grow this without limit.
93
- const MAX_SHARED_CACHE_ENTRIES = 512;
94
- const sharedLineCountCache = new Map();
94
+ export const MAX_SHARED_CACHE_ENTRIES = 512;
95
+ const sharedLineCountCache = new BoundedFifoMap(MAX_SHARED_CACHE_ENTRIES);
95
96
  function rememberInSharedCache(cache, filePath, entry) {
96
- if (cache === sharedLineCountCache &&
97
- cache.size >= MAX_SHARED_CACHE_ENTRIES &&
98
- !cache.has(filePath)) {
99
- const oldest = cache.keys().next().value;
100
- if (oldest !== undefined)
101
- cache.delete(oldest);
102
- }
97
+ // `cache` is either the shared BoundedFifoMap (bounds itself on `set`) or a
98
+ // test-isolated plain `Map` (unbounded by design) — either way, this is
99
+ // just a write.
103
100
  cache.set(filePath, entry);
104
101
  }
105
102
  /** Test-only: drop the shared memo between test files/cases. */
@@ -117,6 +114,12 @@ export function _resetSharedLineCountCacheForTests() {
117
114
  export function _seedSharedLineCountCacheForTests(filePath, entry) {
118
115
  sharedLineCountCache.set(filePath, entry);
119
116
  }
117
+ /** #2442 test-only: membership check that bypasses the real-stat requirement
118
+ * `getCachedLineCount` imposes on every call — proves capacity eviction
119
+ * directly, without needing 513 real files on disk. */
120
+ export function _sharedLineCountCacheHasForTests(filePath) {
121
+ return sharedLineCountCache.has(filePath);
122
+ }
120
123
  /**
121
124
  * Count newline bytes in `filePath` via bounded raw reads — never a full
122
125
  * `readFileSync`/UTF-8 decode of the whole file. `0x0A` (LF) never appears as
@@ -5,10 +5,11 @@
5
5
  */
6
6
  import * as path from "node:path";
7
7
  import { isTestMode } from "./env-utils.js";
8
- import { getGlobalPiLensDir } from "./file-utils.js";
8
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
9
+ import { getMaxLogSizeMB } from "./log-cleanup.js";
9
10
  import { createNdjsonLogger } from "./ndjson-logger.js";
10
11
  function getLogDir() {
11
- return path.join(getGlobalPiLensDir(), "logs");
12
+ return path.join(getGlobalPiLensLogDir(), "logs");
12
13
  }
13
14
  function getLogFile() {
14
15
  const date = new Date().toISOString().split("T")[0];
@@ -22,10 +23,19 @@ export function getDiagnosticLogger() {
22
23
  }
23
24
  return _logger;
24
25
  }
25
- export function createDiagnosticLogger() {
26
+ function createDiagnosticLogger() {
26
27
  // Lazy filePath: the log file is keyed on the current date, resolved per
27
- // drain so a long-lived logger rolls over at midnight.
28
- const writer = createNdjsonLogger({ filePath: () => getLogFile() });
28
+ // drain so a long-lived logger rolls over at midnight. A daily rollover
29
+ // is NOT a size bound (#2505): one very busy day, or a long-lived
30
+ // process that never re-runs the session-start sweep, grows a single
31
+ // day file without limit. The backup keeps the .jsonl extension so the
32
+ // retention sweep in log-cleanup.ts (which matches /\.jsonl$/ under
33
+ // logs/) reaps it like any other daily file.
34
+ const writer = createNdjsonLogger({
35
+ filePath: () => getLogFile(),
36
+ maxBytes: getMaxLogSizeMB() * 1024 * 1024,
37
+ backupPath: () => getLogFile().replace(/\.jsonl$/, ".rotated.jsonl"),
38
+ });
29
39
  return {
30
40
  log(entry) {
31
41
  if (isTestMode()) {
@@ -33,7 +33,3 @@ export function observeRunnerLatency(options) {
33
33
  export function resetObservedRunnerLatency() {
34
34
  slowRunners.clear();
35
35
  }
36
- /** Test-only inspection without exposing mutable state. */
37
- export function observedRunnerTierForTests(projectRoot, runnerId) {
38
- return classifyObservedRunner(projectRoot, runnerId);
39
- }
@@ -17,15 +17,16 @@ import { logExtension } from "../extension-log.js";
17
17
  import * as fs from "node:fs";
18
18
  import * as path from "node:path";
19
19
  import { recordRunner } from "../widget-state.js";
20
- import { incrementDegradationCount } from "../degradation-ledger.js";
20
+ import { incrementDegradationCount, recordDegradationOnce, } from "../degradation-ledger.js";
21
21
  import { detectFileKind } from "../file-kinds.js";
22
22
  import { detectFileRole } from "../file-role.js";
23
+ import { classifyGeneratedOrArtifactDetailed, } from "../generated-artifacts.js";
23
24
  import { isTestFile } from "../file-utils.js";
24
25
  import { getPrimaryDispatchGroup } from "../language-policy.js";
25
26
  import { resolveLanguageRootForFile } from "../language-profile.js";
26
27
  import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
27
28
  import { isSpawnableCommand } from "../installer/index.js";
28
- import { normalizeEphemeralMapKey, normalizeMapKey } from "../path-utils.js";
29
+ import { isAbsoluteFilePath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
29
30
  import { loadPiLensProjectConfig } from "../project-lens-config.js";
30
31
  import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
31
32
  import { safeSpawnAsync } from "../safe-spawn.js";
@@ -61,7 +62,7 @@ export class RunnerRegistry {
61
62
  for (const runner of this.runners.values()) {
62
63
  if (isTest && runner.skipTestFiles)
63
64
  continue;
64
- if (runner.appliesTo.includes(kind) || runner.appliesTo.length === 0) {
65
+ if (runnerAppliesToKind(runner, kind)) {
65
66
  matching.push(runner);
66
67
  }
67
68
  }
@@ -74,6 +75,10 @@ export class RunnerRegistry {
74
75
  this.runners.clear();
75
76
  }
76
77
  }
78
+ function runnerAppliesToKind(runner, kind) {
79
+ return (runner.appliesTo.length === 0 ||
80
+ (kind !== undefined && runner.appliesTo.includes(kind)));
81
+ }
77
82
  // --- Tool Availability Cache ---
78
83
  /**
79
84
  * Normalize a command name to a FactStore session key.
@@ -245,7 +250,19 @@ telemetryModel, telemetryProvider) {
245
250
  const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
246
251
  const normalizedFilePath = normalizeMapKey(absoluteFilePath);
247
252
  const kind = detectFileKind(normalizedFilePath);
248
- const fileRole = detectFileRole(normalizedFilePath, readFilePrefix(normalizedFilePath));
253
+ const contentPrefix = readFilePrefix(normalizedFilePath);
254
+ const fileRole = detectFileRole(normalizedFilePath, contentPrefix);
255
+ // Captured once here so the generated short-circuit below can emit a
256
+ // `dispatch_skipped_generated` record carrying the deciding evidence tier
257
+ // and the measured line-shape statistic — without re-reading the file
258
+ // (refs #2346). The content passed is the same 4096-byte prefix already
259
+ // read for role detection, so this classification costs no extra I/O.
260
+ const generatedDetail = fileRole === "generated"
261
+ ? classifyGeneratedOrArtifactDetailed(normalizedFilePath, {
262
+ content: contentPrefix,
263
+ includeDeclarations: false,
264
+ })
265
+ : undefined;
249
266
  const projectConfig = loadPiLensProjectConfig(normalizedCwd);
250
267
  return {
251
268
  filePath: normalizedFilePath,
@@ -253,6 +270,8 @@ telemetryModel, telemetryProvider) {
253
270
  cwd: normalizedCwd,
254
271
  kind,
255
272
  fileRole,
273
+ generatedEvidence: generatedDetail?.evidence,
274
+ generatedLineShapeMean: generatedDetail?.lineShapeMean,
256
275
  pi,
257
276
  autofix: false,
258
277
  deltaMode: !pi.getFlag("no-delta"),
@@ -462,7 +481,9 @@ function buildCoverageNotice(ctx, runnerLatencies) {
462
481
  const primaryHasCoverage = relevant.some((r) => r.status === "succeeded" || r.status === "failed");
463
482
  if (primaryHasCoverage)
464
483
  return undefined;
465
- const allPrimarySkipped = relevant.every((r) => r.status === "skipped" || r.status === "when_skipped");
484
+ const allPrimarySkipped = relevant.every((r) => r.status === "skipped" ||
485
+ r.status === "when_skipped" ||
486
+ r.status === "test_file_skipped");
466
487
  if (!allPrimarySkipped)
467
488
  return undefined;
468
489
  const plan = getToolPlan(ctx.kind);
@@ -499,6 +520,11 @@ function buildCoverageNotice(ctx, runnerLatencies) {
499
520
  }
500
521
  const latencyReports = [];
501
522
  const coverageNoticeSeen = new Set();
523
+ // One `dispatch_skipped_generated` phase record per file per process (refs
524
+ // #2346): a generated file dispatched repeatedly must not spam latency.log,
525
+ // so only the first skip of each file emits the row; the degradation ledger
526
+ // below still tallies every repetition with a bounded per-subject count.
527
+ const generatedSkipRecorded = new Set();
502
528
  export function getLatencyReports() {
503
529
  return [...latencyReports];
504
530
  }
@@ -507,6 +533,7 @@ export function clearLatencyReports() {
507
533
  }
508
534
  export function clearCoverageNoticeState() {
509
535
  coverageNoticeSeen.clear();
536
+ generatedSkipRecorded.clear();
510
537
  }
511
538
  export function formatLatencyReport(report) {
512
539
  const lines = [];
@@ -588,6 +615,59 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
588
615
  });
589
616
  continue;
590
617
  }
618
+ // Same skipTestFiles gate RunnerRegistry.getForKind applies for the
619
+ // per-edit path (#2337): the plan/group path resolves runners by id via
620
+ // registry.get() instead, which bypasses getForKind entirely, so a
621
+ // runner declaring skipTestFiles never had it enforced here.
622
+ if (runner.skipTestFiles && isTestFile(ctx.filePath)) {
623
+ latencies.push({
624
+ runnerId,
625
+ startTime: runnerStart,
626
+ endTime: Date.now(),
627
+ durationMs: Date.now() - runnerStart,
628
+ status: "test_file_skipped",
629
+ diagnosticCount: 0,
630
+ semantic: "none",
631
+ });
632
+ logLatency({
633
+ type: "runner",
634
+ filePath: ctx.filePath,
635
+ runnerId,
636
+ durationMs: 0,
637
+ status: "test_file_skipped",
638
+ diagnosticCount: 0,
639
+ semantic: "none",
640
+ });
641
+ continue;
642
+ }
643
+ // Keep explicit groups aligned with RunnerRegistry.getForKind(): an empty
644
+ // appliesTo list means every kind, while a populated list must contain the
645
+ // current kind. A filtered runner remains visible as skipped telemetry so
646
+ // language mismatches cannot be mistaken for runner failures.
647
+ const appliesToCurrentKind = runnerAppliesToKind(runner, ctx.kind);
648
+ if (!appliesToCurrentKind) {
649
+ const runnerEnd = Date.now();
650
+ latencies.push({
651
+ runnerId,
652
+ startTime: runnerStart,
653
+ endTime: runnerEnd,
654
+ durationMs: 0,
655
+ status: "skipped",
656
+ diagnosticCount: 0,
657
+ semantic: "unknown",
658
+ });
659
+ logLatency({
660
+ type: "runner",
661
+ filePath: ctx.filePath,
662
+ runnerId,
663
+ durationMs: 0,
664
+ status: "skipped",
665
+ diagnosticCount: 0,
666
+ semantic: "unknown",
667
+ metadata: { reason: "applies_to", kind: ctx.kind },
668
+ });
669
+ continue;
670
+ }
591
671
  // Check preconditions
592
672
  let shouldRun = true;
593
673
  if (runner.when) {
@@ -802,6 +882,31 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
802
882
  export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
803
883
  const _overallStart = Date.now();
804
884
  if (ctx.fileRole === "generated") {
885
+ // The generated short-circuit (refs #2346): never ran before this fix
886
+ // for name-less machine-emitted files (a scraped/minified page has no
887
+ // `.min.js` pattern to catch it), so the classification now also has a
888
+ // content-shape tier. The skip is observable — one `dispatch_skipped_generated`
889
+ // phase record per file per process carrying the deciding evidence tier
890
+ // and the measured line-shape statistic. Deliberately NOT a degradation
891
+ // ledger entry: skipping a generated file is healthy behavior working
892
+ // as designed, and the ledger's bounded kind slots are reserved for
893
+ // genuine degradations (#2348 review F3 — the ledger precedent at the
894
+ // collect-later tier flip below records an actual capability loss).
895
+ const evidence = ctx.generatedEvidence;
896
+ const lineShapeMean = ctx.generatedLineShapeMean;
897
+ if (!generatedSkipRecorded.has(ctx.filePath)) {
898
+ generatedSkipRecorded.add(ctx.filePath);
899
+ logLatency({
900
+ type: "phase",
901
+ filePath: ctx.filePath,
902
+ phase: "dispatch_skipped_generated",
903
+ durationMs: 0,
904
+ metadata: {
905
+ evidence: evidence ?? "unknown",
906
+ ...(lineShapeMean !== undefined && { lineShapeMean }),
907
+ },
908
+ });
909
+ }
805
910
  return {
806
911
  diagnostics: [],
807
912
  blockers: [],
@@ -836,17 +941,46 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
836
941
  // group order so output is deterministic.
837
942
  const groupResults = await Promise.all(groups.map((group) => runGroup(ctx, group, registry, onRunnerResult)));
838
943
  // Count baseline warnings before filtering (for delta count display)
839
- const relativeKey = path.relative(ctx.cwd, ctx.filePath).replace(/\\/g, "/");
944
+ //
945
+ // #2489: `ctx.filePath` is `createDispatchContext`'s `normalizedFilePath`
946
+ // (`resolveRunnerPath` -> `path.resolve` -> `normalizeMapKey`), which is
947
+ // ALWAYS absolute — the #2016 invariant this seam already relies on. A
948
+ // prior relative-path fallback key (`session.baseline.<path relative to
949
+ // ctx.cwd>`) existed here for self-consistency across the abs/rel read
950
+ // and write, but on `ctx.facts` singletons that outlive one project root
951
+ // (`dispatch/integration.ts`'s module-scope `sessionFacts`, reached by
952
+ // the warm `pilens_analyze` MCP route across any number of cwds) two
953
+ // different projects dispatching files that share a relative path (e.g.
954
+ // both have `src/index.ts`) collided on that fallback key, so project
955
+ // B's first-ever delta baseline read could silently return project A's
956
+ // stored diagnostics. Since the absolute key can never collide across
957
+ // projects (an absolute path is unique on the filesystem) and both
958
+ // writes always land together, the relative fallback is both unneeded
959
+ // and the sole source of the collision — removed rather than cwd-scoped
960
+ // (`cwd` is not a stable identity across this seam's callers, #2494).
961
+ //
962
+ // #2489 round 2: the #2016 invariant above is enforced by
963
+ // `createDispatchContext`, the sole real constructor, but nothing enforced
964
+ // it AT THIS SEAM — a hand-built `DispatchContext` (test scaffolding, a
965
+ // future caller) carrying a relative `filePath` would silently key the
966
+ // baseline under a value the constructor could never produce, and every
967
+ // subsequent same-session dispatch of the real (absolute-keyed) file would
968
+ // read back `undefined`, never the collision itself. Mirrors the sibling
969
+ // guard `recordEntitySnapshotDiff` already enforces for the entity-snapshot
970
+ // seam (`clients/review-graph/service.ts`, #2477): reject visibly and skip
971
+ // the baseline read/write for this dispatch rather than compute one under
972
+ // an unreachable key.
973
+ const baselinePathSafe = isAbsoluteFilePath(ctx.filePath);
974
+ if (ctx.deltaMode && !baselinePathSafe) {
975
+ recordDegradationOnce({
976
+ kind: "dispatch-non-absolute-baseline-path",
977
+ subject: ctx.filePath,
978
+ reason: "dispatchForFile requires an absolute ctx.filePath to key the delta baseline (refs #2489)",
979
+ });
980
+ }
840
981
  const baselineAbsKey = `session.baseline.${ctx.filePath}`;
841
- // #2016: `relativeKey` is relative to `ctx.cwd`. `normalizeMapKey` resolved
842
- // it against the process cwd instead, so on Windows this "relative" key
843
- // became an absolute path anchored on the wrong root while POSIX left it
844
- // relative. Both the read here and the write below use this const, so the
845
- // key stays self-consistent within a session.
846
- const baselineRelKey = `session.baseline.${normalizeEphemeralMapKey(relativeKey)}`;
847
- const previousBaseline = ctx.deltaMode
848
- ? (ctx.facts.getSessionFact(baselineAbsKey) ??
849
- ctx.facts.getSessionFact(baselineRelKey))
982
+ const previousBaseline = ctx.deltaMode && baselinePathSafe
983
+ ? ctx.facts.getBoundedSessionFact(baselineAbsKey)
850
984
  : undefined;
851
985
  const baselineWarnings = previousBaseline?.filter((d) => d.semantic === "warning" || d.semantic === "none");
852
986
  const baselineWarningCount = baselineWarnings?.length ?? 0;
@@ -913,9 +1047,12 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
913
1047
  resolvedCount = filtered.fixed.length;
914
1048
  }
915
1049
  // Persist full current snapshot for next run (not delta-filtered subset).
916
- if (ctx.deltaMode) {
917
- ctx.facts.setSessionFact(baselineAbsKey, [...dedupedDiagnostics]);
918
- ctx.facts.setSessionFact(baselineRelKey, [...dedupedDiagnostics]);
1050
+ // Gated on `baselinePathSafe` too: a non-absolute `ctx.filePath` already
1051
+ // skipped the read above (the degradation record fired there), and writing
1052
+ // under that same unreachable key here would just seed a baseline no real
1053
+ // reader (always absolute) could ever read back.
1054
+ if (ctx.deltaMode && baselinePathSafe) {
1055
+ ctx.facts.setBoundedSessionFact(baselineAbsKey, [...dedupedDiagnostics]);
919
1056
  }
920
1057
  // Categorize results
921
1058
  const blockers = visibleDiagnostics.filter((d) => d.semantic === "blocking");
@@ -1003,6 +1140,13 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
1003
1140
  })),
1004
1141
  totalDiagnostics: visibleDiagnostics.length,
1005
1142
  blockers: blockers.length,
1143
+ // #2489 round 2: distinguishes a delta baseline HIT (a prior snapshot
1144
+ // was read for this file this session) from a MISS (first dispatch, or
1145
+ // the non-absolute-path guard above skipped the read) — `dispatch_start`
1146
+ // (above, `metadata: { groupCount, kind, runners }`) fires before the
1147
+ // baseline read ever runs and cannot carry either value.
1148
+ baselineHit: previousBaseline !== undefined,
1149
+ baselineWarningCount,
1006
1150
  },
1007
1151
  });
1008
1152
  return {
@@ -1093,29 +1237,3 @@ async function runRunner(ctx, runner, defaultSemantic) {
1093
1237
  }
1094
1238
  }
1095
1239
  // --- Simple Integration Helper ---
1096
- /**
1097
- * @internal
1098
- * Low-level dispatch entry point. Use `dispatchLint` from `./integration.js` instead —
1099
- * that version provides session-persistent baselines and FactStore.
1100
- * This function creates an ephemeral FactStore per call; facts do not persist across calls.
1101
- */
1102
- export async function dispatchLint(filePath, cwd, pi, facts, registry) {
1103
- // By default, only run BLOCKING rules for fast feedback on file write
1104
- const ctx = createDispatchContext(filePath, cwd, pi, facts, true);
1105
- // Get runners for this file kind
1106
- if (!ctx.kind)
1107
- return "";
1108
- const runners = registry.getForKind(ctx.kind, ctx.filePath);
1109
- if (runners.length === 0) {
1110
- return "";
1111
- }
1112
- // Create groups from registered runners (all in fallback mode)
1113
- const groups = [
1114
- {
1115
- mode: "fallback",
1116
- runnerIds: runners.map((r) => r.id),
1117
- },
1118
- ];
1119
- const result = await dispatchForFile(ctx, groups, registry);
1120
- return result.output;
1121
- }
@@ -1,3 +1,4 @@
1
+ import { BoundedLruCache } from "../bounded-cache.js";
1
2
  import { normalizeMapKey } from "../path-utils.js";
2
3
  let capacityEvictionReporter;
3
4
  export function setFactStoreEvictionReporter(reporter) {
@@ -17,11 +18,60 @@ const MAX_FILE_FACT_RECORDS = 1024;
17
18
  // preventing a small number of generated or vendored files from retaining
18
19
  // hundreds of MiB for the process lifetime (#2247).
19
20
  const MAX_FILE_FACT_CONTENT_BYTES = 64 * 1024 * 1024;
21
+ // Pinned records are exempt from capacity eviction. A dispatch reads its
22
+ // file's facts back after the runner groups settle (`file.content` misses read as
23
+ // empty content, not as "re-derive"), and the fire-and-forget blast-radius
24
+ // build runs a whole-project walk against the SAME store meanwhile — plain LRU
25
+ // recency would let that walk evict the file being dispatched. Every other holder
26
+ // re-derives an evicted fact from disk.
27
+ //
28
+ // #2243 item 2: a dispatch pins its file at start (`clearFileFactsFor`) and
29
+ // releases it at completion (`endDispatchFor`, in the dispatch's finally). The
30
+ // pin set therefore tracks dispatches actually IN FLIGHT, not the last N files
31
+ // touched.
32
+ // #2282: `sessionFacts` below is a flat `Map<factId, value>` shared by two
33
+ // unrelated fact shapes — a small FIXED vocabulary (tool availability keyed by
34
+ // command, the singleton "session.reviewGraph") and PER-FILE families that
35
+ // mint one key per file touched (session baseline, cascade baseline, review-
36
+ // graph entity snapshots) and never shrink. Only the second shape grows with
37
+ // batch size, so only it gets a bound; the fixed vocabulary stays on the plain
38
+ // map, unbounded, because its cardinality is small and stable. Per-file
39
+ // session facts opt into `boundedSessionFacts` instead, reusing fileFacts'
40
+ // LRU-count-cap-and-report discipline rather than adding a third mechanism.
41
+ // No pin exemption here (unlike fileFacts): every current per-file session
42
+ // fact is read once near dispatch start and written once at dispatch end, so
43
+ // nothing re-reads its OWN key later in the same dispatch the way a runner
44
+ // re-reads `file.content` — an eviction mid-batch only means the next dispatch
45
+ // of that path sees no previous baseline, the same safe state as its first
46
+ // ever dispatch this session.
47
+ //
48
+ // That equivalence holds only where an absent baseline means "report
49
+ // everything", which is true for the diagnostic baselines and false for the
50
+ // review-graph entity snapshot: there, an absent snapshot read as an empty one
51
+ // puts every symbol in `added` and drives a blast-radius run for a file that
52
+ // did not change. So an eviction leaves a tombstone, and a reader that needs
53
+ // the distinction asks `wasBoundedSessionFactEvicted`. Tombstones are keys
54
+ // only, and carry the same cap, so the coldest of them fall off in turn and
55
+ // that key reverts to first-dispatch semantics.
56
+ //
57
+ // Four per-file keys are minted per dispatched file today (diagnostic
58
+ // baseline, cascade baseline, entity snapshot, changed symbols), so 4096
59
+ // records covers roughly 1024 distinct files before the coldest file's
60
+ // baseline starts degrading.
61
+ const MAX_SESSION_FACT_RECORDS = 4096;
20
62
  export class FactStore {
21
63
  subject;
22
64
  // Insertion order is eviction order: the oldest-used record is evicted first.
23
65
  fileFacts = new Map();
24
66
  sessionFacts = new Map();
67
+ // Bounded sibling of `sessionFacts` for per-file-keyed session facts (#2282).
68
+ // True LRU: every fetch (get or create, see getBoundedSessionFact) refreshes
69
+ // recency, so the oldest-UNUSED record is evicted first, not merely the
70
+ // oldest-inserted.
71
+ boundedSessionFacts = new BoundedLruCache(MAX_SESSION_FACT_RECORDS);
72
+ // Keys evicted from `boundedSessionFacts` and not written since, so a reader
73
+ // can tell "evicted" from "never seen" (#2282). Same cap, same FIFO order.
74
+ evictedSessionFactKeys = new Set();
25
75
  // Pin refcount per file. A file is exempt from capacity eviction while its
26
76
  // count is > 0. Refcounted, not a Set, so two overlapping dispatches of the
27
77
  // same file both hold the pin until BOTH settle (#2243 item 2).
@@ -269,10 +319,57 @@ export class FactStore {
269
319
  hasSessionFact(factId) {
270
320
  return this.sessionFacts.has(factId);
271
321
  }
322
+ /** Bounded counterpart to {@link getSessionFact} for per-file-keyed session
323
+ * facts (#2282) — session baselines, cascade baselines, review-graph entity
324
+ * snapshots. LRU touch: a read moves the record off the eviction end. */
325
+ getBoundedSessionFact(factId) {
326
+ if (!this.boundedSessionFacts.has(factId))
327
+ return undefined;
328
+ return this.boundedSessionFacts.get(factId);
329
+ }
330
+ /** Bounded counterpart to {@link setSessionFact}. Callers use this instead
331
+ * of `setSessionFact` for any family that mints one key per file/entity
332
+ * touched, so the store's count stays bounded across a large batch instead
333
+ * of retaining one entry per distinct path for the process lifetime. */
334
+ setBoundedSessionFact(factId, value) {
335
+ this.evictedSessionFactKeys.delete(factId);
336
+ const evicted = this.boundedSessionFacts.set(factId, value);
337
+ for (const [oldestKey] of evicted) {
338
+ this.evictedSessionFactKeys.add(oldestKey);
339
+ while (this.evictedSessionFactKeys.size > MAX_SESSION_FACT_RECORDS) {
340
+ const oldestTombstone = this.evictedSessionFactKeys
341
+ .values()
342
+ .next().value;
343
+ if (oldestTombstone === undefined)
344
+ break;
345
+ this.evictedSessionFactKeys.delete(oldestTombstone);
346
+ }
347
+ capacityEvictionReporter?.(this.subject, "session-count", `session-fact store axis=count exceeded ${MAX_SESSION_FACT_RECORDS} records; evicted least-recently-used fact for ${oldestKey}`);
348
+ }
349
+ }
350
+ hasBoundedSessionFact(factId) {
351
+ return this.boundedSessionFacts.has(factId);
352
+ }
353
+ /** True when this key held a bounded session fact that capacity eviction
354
+ * dropped and nothing has rewritten since (#2282). Readers whose "absent"
355
+ * branch is not a safe default — the review-graph entity snapshot, where
356
+ * absent would read as "every symbol is new" — use this to tell an evicted
357
+ * key from one this session has never seen. */
358
+ wasBoundedSessionFactEvicted(factId) {
359
+ return this.evictedSessionFactKeys.has(factId);
360
+ }
361
+ /** O(1) entry count for memory-attribution sampling (#2282 Observability):
362
+ * the fixed-vocabulary map plus the bounded per-file map, so a caller gets
363
+ * this store's whole `sessionFacts` footprint in one number. */
364
+ getSessionFactEntryCount() {
365
+ return this.sessionFacts.size + this.boundedSessionFacts.size;
366
+ }
272
367
  /** Call on session reset only. Clears everything including tool cache and baselines. */
273
368
  clearAll() {
274
369
  this.fileFacts.clear();
275
370
  this.sessionFacts.clear();
371
+ this.boundedSessionFacts.clear();
372
+ this.evictedSessionFactKeys.clear();
276
373
  this.pinnedFiles.clear();
277
374
  this.retainedContentBytes = 0;
278
375
  this.pinnedContentBytesTotal = 0;
@@ -139,7 +139,6 @@ setFactStoreEvictionReporter((subject, axis, reason) => {
139
139
  reason,
140
140
  });
141
141
  });
142
- const cascadeDiagnosticBaselines = new Map();
143
142
  const sessionRunnerRegistry = new RunnerRegistry();
144
143
  registerDefaultRunners(sessionRunnerRegistry);
145
144
  const LSP_CAPABLE_KINDS = new Set(getLspCapableKinds());
@@ -385,7 +384,6 @@ export function resetDispatchBaselines(cwd) {
385
384
  clearModuleGraphCache();
386
385
  recentlyCleanNeighborCache.clear();
387
386
  primaryFilesThisTurn.clear();
388
- cascadeDiagnosticBaselines.clear();
389
387
  cascadeSessionStats = {
390
388
  runs: 0,
391
389
  diagnosticsSurfaced: 0,
@@ -405,10 +403,16 @@ export function getCascadeSessionStats() {
405
403
  }
406
404
  const recentlyCleanNeighborCache = new Map();
407
405
  /** O(1) entry count of this module's turn-bounded cache (#1123 item 2
408
- * memory attribution) — a `Map.size` read, never iterated. */
406
+ * memory attribution) — a `Map.size` read, never iterated.
407
+ * `sessionFactEntries` (#2282) is the dispatch `FactStore`'s own
408
+ * `sessionFacts` footprint: the fixed-vocabulary map (small, unbounded by
409
+ * design) plus the bounded per-file map (capped at `MAX_SESSION_FACT_RECORDS`
410
+ * — see `fact-store.ts`), so this number is now itself bounded rather than
411
+ * invisible growth. */
409
412
  export function getDispatchCascadeCacheStats() {
410
413
  return {
411
414
  recentlyCleanNeighborCacheSize: recentlyCleanNeighborCache.size,
415
+ sessionFactEntries: sessionFacts.getSessionFactEntryCount(),
412
416
  };
413
417
  }
414
418
  const RECENTLY_CLEAN_TTL_TURNS = 5;
@@ -1485,7 +1489,9 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1485
1489
  try {
1486
1490
  const snapshots = (await lspService.getCapabilitySnapshots?.(neighborPath)) ?? [];
1487
1491
  const tier = classifyCascadeWaitTier(lspService, neighborPath, snapshots);
1488
- if (tier === "tier3-silent" || tier === "collect-later") {
1492
+ if (tier === "tier3-silent" ||
1493
+ tier === "collect-later" ||
1494
+ tier === "diagnostics-unsupported") {
1489
1495
  const spawnedForTouch = await lspService.getClientForFile(neighborPath);
1490
1496
  if (spawnedForTouch) {
1491
1497
  // Sampled BEFORE the touchFile notify: a publish landing
@@ -1501,6 +1507,21 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1501
1507
  source: "cascade",
1502
1508
  clientScope: "primary",
1503
1509
  });
1510
+ if (tier === "diagnostics-unsupported") {
1511
+ logCascade({
1512
+ phase: "cascade_skip",
1513
+ filePath,
1514
+ neighborFile: neighborPath,
1515
+ durationMs: Date.now() - neighborStart,
1516
+ lspServerCount: configuredServerCount,
1517
+ coldSnapshot: isColdSnapshot,
1518
+ metadata: {
1519
+ serverId: spawnedForTouch.client.serverId,
1520
+ waitTier: tier,
1521
+ },
1522
+ });
1523
+ return undefined;
1524
+ }
1504
1525
  recordOutstandingCascadeTouch({
1505
1526
  filePath: neighborPath,
1506
1527
  serverId: spawnedForTouch.client.serverId,
@@ -1940,10 +1961,8 @@ function diagnosticDeltaKey(diagnostic) {
1940
1961
  function applyCascadeDeltaBaselines(neighbors) {
1941
1962
  return neighbors.map((neighbor) => {
1942
1963
  const baselineKey = `session.baseline.cascade.${normalizeMapKey(neighbor.filePath)}`;
1943
- const previous = cascadeDiagnosticBaselines.get(baselineKey) ??
1944
- sessionFacts.getSessionFact(baselineKey);
1945
- cascadeDiagnosticBaselines.set(baselineKey, [...neighbor.diagnostics]);
1946
- sessionFacts.setSessionFact(baselineKey, [...neighbor.diagnostics]);
1964
+ const previous = sessionFacts.getBoundedSessionFact(baselineKey);
1965
+ sessionFacts.setBoundedSessionFact(baselineKey, [...neighbor.diagnostics]);
1947
1966
  if (!previous)
1948
1967
  return neighbor;
1949
1968
  const before = new Set(previous.map(diagnosticDeltaKey));
@@ -9,9 +9,3 @@ export function warmDispatchIntegration() {
9
9
  export function loadDispatchIntegration() {
10
10
  return warmDispatchIntegration();
11
11
  }
12
- /** Test-only reset; production sessions intentionally retain the promise
13
- * across successful loads (a rejection already evicts itself — see
14
- * `createLazyImport`). */
15
- export function resetDispatchIntegrationForTests() {
16
- lazyIntegration.resetForTests();
17
- }
@@ -1,7 +1,7 @@
1
1
  /** Turn-end handoff for runners moved off the post-write critical path. */
2
2
  import { incrementDegradationCount } from "../degradation-ledger.js";
3
3
  const pending = [];
4
- export const MAX_PENDING_RUNNER_FINDINGS = 50;
4
+ const MAX_PENDING_RUNNER_FINDINGS = 50;
5
5
  export function deferRunnerFindings(entry) {
6
6
  const tracked = { ...entry, settled: false };
7
7
  // Attach exactly once at ownership time. Re-attaching at every turn end
@@ -276,6 +276,3 @@ export const TOOL_PLANS = Object.fromEntries(Object.entries(LANGUAGE_CAPABILITY_
276
276
  export function getToolPlan(kind) {
277
277
  return TOOL_PLANS[kind];
278
278
  }
279
- export function getAllToolPlans() {
280
- return TOOL_PLANS;
281
- }
@@ -3,8 +3,8 @@ import { isTestSuiteOrganizer } from "./framework-call-noise.js";
3
3
  // projects without a config. Project-specific overrides are read from the
4
4
  // per-dispatch context; these mutable fallbacks exist for tests/legacy direct
5
5
  // rule evaluation only.
6
- export const DEFAULT_HIGH_COMPLEXITY_THRESHOLD = 15;
7
- export const DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD = 6;
6
+ const DEFAULT_HIGH_COMPLEXITY_THRESHOLD = 15;
7
+ const DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD = 6;
8
8
  let ccThreshold = DEFAULT_HIGH_COMPLEXITY_THRESHOLD;
9
9
  let depthThreshold = DEFAULT_HIGH_COMPLEXITY_DEPTH_THRESHOLD;
10
10
  function isPositiveFiniteThreshold(value) {
@@ -3,7 +3,7 @@ import { isTestFrameworkNoiseCall, isTestSuiteOrganizer, } from "./framework-cal
3
3
  // projects without a config. Project-specific overrides are read from the
4
4
  // per-dispatch context; this mutable fallback exists for tests/legacy direct
5
5
  // rule evaluation only.
6
- export const DEFAULT_HIGH_FAN_OUT_THRESHOLD = 20;
6
+ const DEFAULT_HIGH_FAN_OUT_THRESHOLD = 20;
7
7
  let fanOutThreshold = DEFAULT_HIGH_FAN_OUT_THRESHOLD;
8
8
  /** Override fallback threshold for tests/legacy direct rule evaluation. */
9
9
  export function setHighFanOutThreshold(n) {
@@ -61,7 +61,6 @@ const actionlintRunner = {
61
61
  id: "actionlint",
62
62
  appliesTo: ["yaml"],
63
63
  priority: PRIORITY.YAML_LINT + 1,
64
- enabledByDefault: true,
65
64
  skipTestFiles: false,
66
65
  when(ctx) {
67
66
  return isGitHubWorkflowFile(ctx.filePath);