@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
@@ -7,7 +7,51 @@
7
7
  import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
+ import { classifyBundledResourceDir, reportBundledResourceDirHealth, } from "./bundled-resource-health.js";
11
+ import { getDegradationLedgerGeneration } from "./degradation-ledger.js";
10
12
  import { resolvePackagePath } from "./package-root.js";
13
+ /**
14
+ * The bundled `rules/tree-sitter-queries` root — the ONE spelling of this
15
+ * path, imported by `clients/cache/rule-cache.ts` (re-exported there as
16
+ * `BUNDLED_RULES_ROOT`, #2636 review F4) rather than computed a second time,
17
+ * so the "one ledger row, not two" claim for `tree-sitter-queries-dir-missing`
18
+ * rests on reference equality, not merely two independently-resolved strings
19
+ * that happen to match.
20
+ */
21
+ export const BUNDLED_QUERIES_ROOT = resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries");
22
+ let cachedBundledQueriesRootHealth;
23
+ let cachedBundledQueriesRootHealthGeneration;
24
+ /**
25
+ * #2636 review F6/round-2 F3: memoized, but keyed on the degradation
26
+ * ledger's OWN generation counter (bumped by `resetDegradationLedger`, wired
27
+ * into `handleSessionStart`) rather than "compute once, forever" — round 1's
28
+ * version overclaimed that the underlying fact "cannot change mid-process
29
+ * any more than the package's own install location can", but a managed
30
+ * extension cache RELOCATING a live install mid-process is exactly the
31
+ * failure #2587/#2626 investigated: a permanently-cached "absent" verdict
32
+ * from the FIRST probe would never notice the directory coming back (or
33
+ * disappearing later), the same silent-zero shape this issue exists to end.
34
+ * Re-probing once per SESSION (not once per call) is the right middle
35
+ * ground: a real, measured `readdirSync` cost (4.2 µs) is paid once per
36
+ * session rather than on every dispatched file — same generation-keyed
37
+ * pattern `clients/ast-grep-client.ts`'s `ensureRulesHealthReported` uses
38
+ * for its own per-instance re-check.
39
+ */
40
+ export function getBundledQueriesRootHealth() {
41
+ const generation = getDegradationLedgerGeneration();
42
+ if (cachedBundledQueriesRootHealth === undefined ||
43
+ cachedBundledQueriesRootHealthGeneration !== generation) {
44
+ cachedBundledQueriesRootHealthGeneration = generation;
45
+ cachedBundledQueriesRootHealth =
46
+ classifyBundledResourceDir(BUNDLED_QUERIES_ROOT);
47
+ }
48
+ return cachedBundledQueriesRootHealth;
49
+ }
50
+ /** Test-only: clear the memo so a scenario can simulate a different install layout. */
51
+ export function _resetBundledQueriesRootHealthForTests() {
52
+ cachedBundledQueriesRootHealth = undefined;
53
+ cachedBundledQueriesRootHealthGeneration = undefined;
54
+ }
11
55
  export function isDisabledQueryDirectoryName(name) {
12
56
  return name.endsWith("-disabled");
13
57
  }
@@ -60,7 +104,7 @@ export function ruleFilesForLanguage(languageId, rootDir = process.cwd()) {
60
104
  for (const lang of ruleSourceLanguages(languageId)) {
61
105
  for (const dir of [
62
106
  path.join(resolvedRoot, "rules", "tree-sitter-queries", lang),
63
- resolvePackagePath(import.meta.url, "rules", "tree-sitter-queries", lang),
107
+ path.join(BUNDLED_QUERIES_ROOT, lang),
64
108
  ]) {
65
109
  if (!fs.existsSync(dir))
66
110
  continue;
@@ -70,6 +114,42 @@ export function ruleFilesForLanguage(languageId, rootDir = process.cwd()) {
70
114
  }
71
115
  }
72
116
  }
117
+ // #2636 (review F2): a language resolving zero files here is NORMAL when
118
+ // nobody has authored bundled/project queries for it — seven REACHABLE
119
+ // grammars have none by design: bash, dart, elixir, lua, ocaml, swift, zig
120
+ // (`.sh`/`.bash`, `.dart`, `.ex`/`.exs`, `.lua`, `.ml`/`.mli`, `.swift`,
121
+ // `.zig` — see `language-registry.ts`'s `EXTENSION_TO_GRAMMAR`). cobol and
122
+ // plsql are NOT in that registry at all — only their `-disabled` query
123
+ // directories exist — so `ruleFilesForLanguage` never actually resolves
124
+ // those two languageIds in production; they are not examples of this
125
+ // case. A BUG looks identical from an empty `files` set alone: the
126
+ // bundled root itself relocated out from under the package (same
127
+ // managed-cache-relocation shape #2626 fixed for skills/). Only pay for
128
+ // the extra classification in this COLD branch (never on the common,
129
+ // non-empty path), key it on the shared ROOT rather than this call's
130
+ // `languageId` (every language hitting an actually-missing root collapses
131
+ // into the SAME ledger row instead of one per language), and only RECORD
132
+ // when the root is actually unhealthy — one of the seven by-design-empty
133
+ // languages is touched routinely (any `.sh`/`.lua` edit).
134
+ //
135
+ // #2636 review round 2, F2: NO separate phase/latency row here (unlike
136
+ // the ast-grep/skills sibling sites, which log one PER CONSTRUCTION or
137
+ // PER REQUEST — a bounded cardinality). This branch runs on EVERY
138
+ // dispatched file while the root stays broken (AGENTS.md's "no raw
139
+ // per-occurrence log for repeats"): 200 touches of a by-design-empty
140
+ // language against a broken root would otherwise write 200 raw
141
+ // `latency.log` rows. `reportBundledResourceDirHealth`'s
142
+ // `incrementDegradationCount` already answers "did this run" AND "how
143
+ // many times" in a BOUNDED way — one row per session at count 1, then
144
+ // only at power-of-two milestones (1, 2, 4, 8, … so 200 occurrences write
145
+ // exactly 8 durable rows) — so a second, unbounded record here would add
146
+ // nothing the ledger row lacks.
147
+ if (files.size === 0) {
148
+ const health = getBundledQueriesRootHealth();
149
+ if (health.status !== "healthy") {
150
+ reportBundledResourceDirHealth("tree-sitter-queries-dir-missing", BUNDLED_QUERIES_ROOT, health, "bundled tree-sitter query rules");
151
+ }
152
+ }
73
153
  return [...files];
74
154
  }
75
155
  /**
@@ -16,6 +16,7 @@
16
16
  import { notifyUserDegradation } from "./user-notify.js";
17
17
  import * as path from "node:path";
18
18
  import { TreeSitterClient, } from "./tree-sitter-client.js";
19
+ import { EXTENSION_TO_GRAMMAR } from "./language-registry.js";
19
20
  import { logTreeSitter } from "./tree-sitter-logger.js";
20
21
  let _shared = null;
21
22
  let _wasmAborted = false;
@@ -80,56 +81,60 @@ export function _resetSharedTreeSitterClientForTests() {
80
81
  _wasmAborted = false;
81
82
  _wasmAbortedAt = undefined;
82
83
  }
83
- // Grammar selection by extension — the single ext→grammar-id authority. `.tsx` →
84
- // the tsx grammar (parses JSX); `.jsx` → the javascript grammar. The project
85
- // scanner (project-diagnostics/scanner.ts) DERIVES its map from this one and
86
- // module-report (module-report.ts `tsLangForFile`, #887) resolves its
87
- // extension-split kinds (jsts/cxx) through `resolveTreeSitterLanguage` below,
88
- // so neither can drift: post-#877 both key `.tsx`→tsx (the old note here said the
89
- // scanner mapped `.tsx`→typescript to reuse typescript queries — that stopped
90
- // being true when #877 moved typescript-rule inheritance into
91
- // `queriesForLanguage`). The scanner layers only java/kotlin on top, whose
92
- // grammars + rule dirs exist but are not wired into a per-edit runner `appliesTo`.
93
- export const EXT_TO_LANG = {
94
- ".ts": "typescript",
95
- ".mts": "typescript",
96
- ".cts": "typescript",
97
- ".tsx": "tsx",
98
- ".js": "javascript",
99
- ".mjs": "javascript",
100
- ".cjs": "javascript",
101
- ".jsx": "javascript",
102
- ".py": "python",
103
- ".go": "go",
104
- ".rs": "rust",
105
- ".rb": "ruby",
106
- ".c": "c",
107
- ".h": "c",
108
- ".cc": "cpp",
109
- ".cpp": "cpp",
110
- ".cxx": "cpp",
111
- ".c++": "cpp",
112
- ".hh": "cpp",
113
- ".hpp": "cpp",
114
- ".hxx": "cpp",
115
- ".inl": "cpp",
116
- ".ipp": "cpp",
117
- ".tpp": "cpp",
118
- ".txx": "cpp",
119
- ".cu": "cpp",
120
- ".hip": "cpp",
121
- ".cs": "csharp",
122
- ".php": "php",
123
- ".phtml": "php",
124
- ".php3": "php",
125
- ".php4": "php",
126
- ".php5": "php",
127
- ".css": "css",
128
- };
84
+ // Grammar selection by extension — projected from the canonical language
85
+ // registry (language-registry.ts, #2424), which is now THE ext→grammar
86
+ // authority. `.tsx` → the tsx grammar (parses JSX); `.jsx` → the javascript
87
+ // grammar. The project scanner (project-diagnostics/scanner.ts), read expansion
88
+ // (read-expansion.ts) and module-report (`tsLangForFile`, #887) all project the
89
+ // same registry column, so none of them can drift from this one: before #2424
90
+ // the scanner spread this map and layered java/kotlin on top while read
91
+ // expansion kept a hand-copied near-copy of it.
92
+ // `Readonly` because the projection is `Object.freeze`d at the source, and the
93
+ // mutable `Record` type let a caller type-check a write that would throw at
94
+ // runtime (#2424 review, S4).
95
+ export const EXT_TO_LANG = EXTENSION_TO_GRAMMAR;
129
96
  /** Resolve a tree-sitter grammar/language id from a file path's extension. */
130
97
  export function resolveTreeSitterLanguage(filePath) {
131
98
  return EXT_TO_LANG[path.extname(filePath).toLowerCase()];
132
99
  }
100
+ /**
101
+ * The grammars `clients/complexity-client.ts` has a node mapping for.
102
+ *
103
+ * Lives HERE, beside the ext→grammar resolver, because the question "can this
104
+ * path produce a complexity baseline?" has to be answerable WITHOUT loading
105
+ * the analyzer graph (#2467): `clients/runtime-tool-call.ts` asks it on every
106
+ * tool call to decide whether the seventeen-module load is worth starting, and
107
+ * the client that owns the answer is behind that very load.
108
+ *
109
+ * It is not a second extension table — the extension half still comes from the
110
+ * one canonical registry through {@link resolveTreeSitterLanguage}. It is the
111
+ * language-id half, and `LANGUAGE_NODES` is typed
112
+ * `Record<ComplexityLanguageId, …>`, so adding a grammar there without adding
113
+ * it here (or the reverse) is a COMPILE error rather than a predicate that
114
+ * quietly drifts from the client it is supposed to mirror.
115
+ */
116
+ export const COMPLEXITY_LANGUAGE_IDS = [
117
+ "typescript",
118
+ "tsx",
119
+ "javascript",
120
+ "python",
121
+ "go",
122
+ "rust",
123
+ ];
124
+ /** Narrow a resolved grammar id to one complexity can analyze. */
125
+ export function isComplexityLanguageId(languageId) {
126
+ return COMPLEXITY_LANGUAGE_IDS.includes(languageId);
127
+ }
128
+ /**
129
+ * Can a complexity baseline be produced for this path? Pure, synchronous, and
130
+ * free of every analyzer module — the guard `handleToolCallImpl` needs above
131
+ * its demand on the bootstrap seam. `ComplexityClient.isSupportedFile`
132
+ * delegates to it, so there is exactly one answer.
133
+ */
134
+ export function isComplexitySupportedFile(filePath) {
135
+ const languageId = resolveTreeSitterLanguage(filePath);
136
+ return languageId !== undefined && isComplexityLanguageId(languageId);
137
+ }
133
138
  export async function withTreeSitterRoot(filePath, content, consume) {
134
139
  const languageId = resolveTreeSitterLanguage(filePath);
135
140
  const client = getSharedTreeSitterClient();
@@ -3,6 +3,7 @@
3
3
  * Extracts definitions and references from source files
4
4
  */
5
5
  import { logTreeSitterDiagnostic } from "./tree-sitter-logger.js";
6
+ import { EXTENSION_TO_GRAMMAR, KIND_TO_GRAMMAR } from "./language-registry.js";
6
7
  import * as path from "node:path";
7
8
  import { loadWebTreeSitter } from "./deps/web-tree-sitter.js";
8
9
  // Tree-sitter query patterns for symbol extraction
@@ -563,6 +564,33 @@ SYMBOL_QUERIES.javascript = {
563
564
  constructor: (identifier) @newIdent) @newRef
564
565
  `,
565
566
  };
567
+ /**
568
+ * The grammar a KIND-keyed consumer (review-graph builder, module-report
569
+ * outline) should extract `filePath` with, or undefined when this file is not
570
+ * something the symbol extractor can read.
571
+ *
572
+ * Three registry-derived steps, no hand-kept table (#2424 replaced one switch
573
+ * in review-graph/builder.ts and one map in module-report.ts with this):
574
+ * 1. the kind must have a single grammar-bearing language, or declare a
575
+ * `kindFallback` one — so extension-split `jsts` is excluded and its
576
+ * callers keep resolving it by path;
577
+ * 2. the file's own extension wins where it has grammar wiring, which is what
578
+ * splits `cxx` into `.c`/`.h` -> c and the rest -> cpp;
579
+ * 3. the grammar must actually have symbol queries here, which is what keeps
580
+ * css/json/yaml/html/toml (grammars that ship, queries that do not) out.
581
+ */
582
+ export function symbolExtractionGrammar(kind, filePath) {
583
+ if (!kind)
584
+ return undefined;
585
+ const kindGrammar = KIND_TO_GRAMMAR[kind];
586
+ if (!kindGrammar)
587
+ return undefined;
588
+ const byExtension = filePath
589
+ ? EXTENSION_TO_GRAMMAR[path.extname(filePath).toLowerCase()]
590
+ : undefined;
591
+ const grammar = byExtension ?? kindGrammar;
592
+ return grammar in SYMBOL_QUERIES ? grammar : undefined;
593
+ }
566
594
  /** Language keys with symbol queries; used by fixture drift guards. */
567
595
  export function getSymbolQueryLanguages() {
568
596
  return Object.keys(SYMBOL_QUERIES);
@@ -18,10 +18,6 @@
18
18
  import { truncateToWidth } from "./deps/pi-tui.js";
19
19
  const ELLIPSIS_KIND = { "…": 0, "...": 1, "": 2 };
20
20
  let truncateIsNative = null;
21
- /** Test-only: reset the signature probe between test cases. */
22
- export function _resetTruncateProbeForTests() {
23
- truncateIsNative = null;
24
- }
25
21
  export function fitLine(s, maxWidth, ellipsis = "...") {
26
22
  const w = Math.max(0, maxWidth);
27
23
  const fn = truncateToWidth;
@@ -19,6 +19,11 @@ export const LOCAL_TYPOS_CONFIG_NAMES = [
19
19
  "_typos.toml",
20
20
  ".typos.toml",
21
21
  ];
22
+ // Deliberately UNCEILINGED at $HOME (refs #2472 review round 3, F1): typos
23
+ // discovers `~/typos.toml` as the user's legitimate global config, and
24
+ // `typos-lsp` merges an injected config with its own discovery (injected
25
+ // wins) — a ceilinged search here silently hid the user's config from that
26
+ // merge and let pi-lens's own shipped `_typos.toml` clobber it instead.
22
27
  export function findLocalTyposConfig(startDir) {
23
28
  return findLocalToolConfig(startDir, LOCAL_TYPOS_CONFIG_NAMES);
24
29
  }
@@ -19,6 +19,7 @@
19
19
  * is a no-op and the ndjson sink remains the sole destination — the message is
20
20
  * never lost, only un-rendered.
21
21
  */
22
+ import { withConfigDiagnosticCode, } from "./config-diagnostic-codes.js";
22
23
  let notifierGetter;
23
24
  /** Called once from the extension entry with a getter over the live `ctx.ui.notify`. */
24
25
  export function wireUserNotifier(portsOrGetter) {
@@ -31,19 +32,18 @@ export function wireUserNotifier(portsOrGetter) {
31
32
  export function resetUserNotifier() {
32
33
  notifierGetter = undefined;
33
34
  }
34
- /** True when a host render path is available (for callers that want to branch). */
35
- export function hasUserNotifier() {
36
- return notifierGetter !== undefined;
37
- }
38
35
  /**
39
36
  * Best-effort delivery of a user-facing degradation. Never throws — a stale
40
37
  * `ctx` after a session replacement must degrade to a no-op, not break the
41
38
  * diagnostic path that called it.
42
39
  */
43
- export function notifyUserDegradation(message, level = "warning") {
40
+ export function notifyUserDegradation(message, level = "warning", options) {
44
41
  try {
45
42
  const notify = notifierGetter?.();
46
- notify?.(message, level);
43
+ const coded = options?.code
44
+ ? withConfigDiagnosticCode(message, options.code)
45
+ : message;
46
+ notify?.(coded, level);
47
47
  }
48
48
  catch {
49
49
  // The ndjson sink already holds this message; a dead host is not an error.
@@ -1,6 +1,7 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
+ import { BoundedFifoMap } from "./bounded-cache.js";
4
5
  import { demotePastEofDiagnostics, } from "./diagnostic-line-freshness.js";
5
6
  import { visibleWidth } from "./deps/pi-tui.js";
6
7
  import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
@@ -58,7 +59,6 @@ export function isBlocking(d) {
58
59
  }
59
60
  // ── Module state ─────────────────────────────────────────────────────────────
60
61
  const files = new Map();
61
- const lspServers = new Map();
62
62
  let sessionLanguages = [];
63
63
  let requestRenderFn = null;
64
64
  /**
@@ -81,7 +81,8 @@ const runnerWriteGuard = new WriteOrderingGuard();
81
81
  const MAX_STORED_DIAGNOSTICS_PER_FILE = 12;
82
82
  const MAX_INACTIVE_FILE_RECORDS = 1024;
83
83
  const ACTIVE_FILE_IDLE_MS = 30 * 60_000;
84
- const MAX_LSP_SERVER_RECORDS = 128;
84
+ export const MAX_LSP_SERVER_RECORDS = 128;
85
+ const lspServers = new BoundedFifoMap(MAX_LSP_SERVER_RECORDS);
85
86
  // Pruning is a cold-size-boundary operation. Do not walk the whole file map
86
87
  // for every record in a large diagnostics reconciliation; the full-scan path
87
88
  // can legitimately create thousands of records in one synchronous batch.
@@ -110,8 +111,23 @@ function maybePruneInactiveFileRecords() {
110
111
  export function setRenderCallback(fn) {
111
112
  requestRenderFn = fn;
112
113
  }
114
+ /**
115
+ * #2275 review F1: files whose dependency-drift-demoted rows the footer has
116
+ * ACTUALLY DRAWN since the last turn end — the delivery population
117
+ * `runtime-turn.ts`'s cap step drains.
118
+ *
119
+ * The footer is not a broadcast of the whole store: `renderWidget` picks ONE
120
+ * record (`withBlocking[0]`) and its first five qualifying entries, and it
121
+ * may not be drawn at all (headless, or a turn with no repaint). Counting a
122
+ * delivery per turn end therefore charged the cap for rows the agent never
123
+ * saw, and retired them a delivery early. A Set (not a counter) is the right
124
+ * shape: the widget repaints many times per turn, and all of those repaints
125
+ * are the SAME one delivery.
126
+ */
127
+ const renderedDependencyDriftFiles = new Set();
113
128
  export function clearWidgetState() {
114
129
  files.clear();
130
+ renderedDependencyDriftFiles.clear();
115
131
  lspServers.clear();
116
132
  sessionLanguages = [];
117
133
  requestRenderFn = null;
@@ -174,7 +190,19 @@ export function exportWidgetState() {
174
190
  */
175
191
  function migrateEntryStamps(entries, recordTouchedAt) {
176
192
  return (entries ?? []).map((d) => {
177
- const { stale: _stale, ...rest } = d;
193
+ // #2275: strip staleDeliveryCount and footerRetired alongside stale — a
194
+ // delivery count (and the footer-hide it earned) without the demotion
195
+ // it counts is meaningless, and a resumed session re-evaluates
196
+ // dependency-drift from scratch (#1631 review F3) same as the `stale`
197
+ // bit itself. A restored footer has shown the row zero times.
198
+ // Fix-round 3 (#2275 review F2): `staleReason` left the round trip
199
+ // unstripped, so a resumed row could carry `staleReason:
200
+ // "dependency-drift"` with `stale` gone — `isDependencyDriftDemoted`
201
+ // reads `stale === true`, so that combination reads as clean today, but
202
+ // the stray reason is still a demotion label with no demotion behind
203
+ // it, the same "meaningless without the bit it counts" shape as the
204
+ // other three fields. Strip it alongside them.
205
+ const { stale: _stale, staleDeliveryCount: _staleDeliveryCount, footerRetired: _footerRetired, staleReason: _staleReason, ...rest } = d;
178
206
  return rest.observedAt == null
179
207
  ? { ...rest, observedAt: recordTouchedAt }
180
208
  : rest;
@@ -271,9 +299,9 @@ export function getFailedLspServerIds() {
271
299
  }
272
300
  return ids;
273
301
  }
274
- export function recordFormatter(filePath, formatter, changed, success) {
302
+ export function recordFormatter(filePath, formatter, changed, success, outcome) {
275
303
  const rec = getOrCreate(filePath);
276
- rec.formatters.set(formatter, { changed, success });
304
+ rec.formatters.set(formatter, { changed, success, outcome });
277
305
  rec.touchedAt = Date.now();
278
306
  files.set(fileMapKey(filePath), rec);
279
307
  requestRender();
@@ -381,7 +409,19 @@ function commitDiagnostics(rec, filePath, normalized, observedAt, key = fileMapK
381
409
  files.set(key, rec);
382
410
  requestRender();
383
411
  }
384
- /** Recompute the {blocking, errors, warnings} tally for a diagnostic set. */
412
+ export function classifyDiagnosticTier(d) {
413
+ if (d.severity === "error")
414
+ return "error";
415
+ if (d.severity === "warning")
416
+ return "warning";
417
+ if (d.severity === "hint" || d.severity === "info")
418
+ return "advisory";
419
+ return undefined;
420
+ }
421
+ /** Recompute the {blocking, errors, warnings} tally for a diagnostic set.
422
+ * `hint`/`info` tier findings are tallied separately — see
423
+ * {@link classifyDiagnosticTier} and {@link countAdvisories} — and never
424
+ * inflate `warnings` (#2414). */
385
425
  function countDiagnostics(diags) {
386
426
  let blocking = 0;
387
427
  let errors = 0;
@@ -398,22 +438,34 @@ function countDiagnostics(diags) {
398
438
  if (diagnostic.stale &&
399
439
  (diagnostic.staleReason ?? "past-eof") === "past-eof")
400
440
  continue;
401
- if (diagnostic.severity === "error")
441
+ const tier = classifyDiagnosticTier(diagnostic);
442
+ if (tier === "error")
402
443
  errors++;
403
- // #1777: `hint` and `info` tally alongside `warning`. The dispatch path
404
- // now preserves all four ast-grep tiers; before, the runner collapsed
405
- // them to "warning" here. An exact `=== "warning"` test would drop those
406
- // findings out of the footer entirely, so a file with real findings would
407
- // render clean. The footer stays a blocking/error/warning summary on
408
- // purpose — the tier distinction is rendered by the code-quality-warnings
409
- // advisory, not by these counters.
410
- else if (diagnostic.severity === "warning" ||
411
- diagnostic.severity === "hint" ||
412
- diagnostic.severity === "info")
444
+ else if (tier === "warning")
413
445
  warnings++;
446
+ // tier === "advisory" (hint/info): intentionally excluded from the
447
+ // footer's warning tally (#2414) — see `countAdvisories` for the
448
+ // parallel count `getFileDiagnosticSummaries` exposes to
449
+ // `lens_diagnostics` so a hint-only file still shows up as "has issues".
414
450
  }
415
451
  return { blocking, errors, warnings };
416
452
  }
453
+ /** Count `hint`/`info` tier findings using the same past-EOF exclusion as
454
+ * {@link countDiagnostics} (#2414). Kept as a standalone accessor rather than
455
+ * a new `FileRecord.diagnosticCounts` field — the footer never renders this
456
+ * number, only `getFileDiagnosticSummaries` (mode=all) needs it, to keep a
457
+ * hint-only file from being silently dropped from a detailed listing. */
458
+ function countAdvisories(diags) {
459
+ let advisories = 0;
460
+ for (const diagnostic of diags) {
461
+ if (diagnostic.stale &&
462
+ (diagnostic.staleReason ?? "past-eof") === "past-eof")
463
+ continue;
464
+ if (classifyDiagnosticTier(diagnostic) === "advisory")
465
+ advisories++;
466
+ }
467
+ return advisories;
468
+ }
417
469
  /**
418
470
  * Apply the #1641 past-EOF gate to `rec` in place: demote (never drop) any
419
471
  * stored diagnostic whose cited line exceeds the file's CURRENT on-disk line
@@ -804,6 +856,102 @@ export function markWidgetFileBlockersStale(filePath, reason) {
804
856
  }
805
857
  return changed;
806
858
  }
859
+ /**
860
+ * #2275: a dependency-drift demotion the footer is still allowed to draw —
861
+ * the one predicate the render marker, the delivery counter and the
862
+ * footer-hide all ask, so "which rows does this cap govern" cannot drift
863
+ * apart between them.
864
+ */
865
+ function isDependencyDriftDemoted(d) {
866
+ return (d.stale === true &&
867
+ d.staleReason === "dependency-drift" &&
868
+ d.footerRetired !== true);
869
+ }
870
+ /**
871
+ * #2275 review F1: take (and clear) the files whose dependency-drift
872
+ * demotions the footer actually RENDERED since the last call — the delivery
873
+ * population `runtime-turn.ts`'s per-turn cap step walks at turn end.
874
+ *
875
+ * Deliberately NOT a store-wide query over everything currently demoted.
876
+ * `renderWidget` serves one record per pass, so a store-wide walk charged a
877
+ * delivery to every demoted file every turn — including files the footer had
878
+ * never shown and one demoted moments earlier by the same turn end, which
879
+ * also made the ledger's "capped after N deliveries" overstate N by one.
880
+ * Draining here is what makes many repaints within one turn count as the one
881
+ * delivery they are, and a turn with no repaint count as none.
882
+ */
883
+ export function drainRenderedDependencyDriftFilePaths() {
884
+ const out = [...renderedDependencyDriftFiles];
885
+ renderedDependencyDriftFiles.clear();
886
+ return out;
887
+ }
888
+ /**
889
+ * #2275: commit one more RENDERED delivery of `filePath`'s
890
+ * dependency-drift-demoted diagnostics. Every qualifying entry on the record
891
+ * advances together — `markWidgetFileBlockersStale` demotes them as one
892
+ * batch per file, so they share one delivery history, mirroring that
893
+ * write's own file-level scope. Returns the new count, or 0 when the file
894
+ * has nothing this cap governs (never demoted, or already footer-retired —
895
+ * a hidden row is delivered no more, so it must stop advancing).
896
+ */
897
+ export function incrementWidgetDependencyDriftDelivery(filePath) {
898
+ const rec = files.get(fileMapKey(filePath));
899
+ if (!rec)
900
+ return 0;
901
+ let next = 0;
902
+ for (const d of rec.allDiagnostics) {
903
+ if (isDependencyDriftDemoted(d)) {
904
+ next = (d.staleDeliveryCount ?? 0) + 1;
905
+ d.staleDeliveryCount = next;
906
+ }
907
+ }
908
+ return next;
909
+ }
910
+ /**
911
+ * #2275 review F2: retire every dependency-drift-demoted diagnostic on
912
+ * `filePath` once its delivery cap is reached — by HIDING it from the
913
+ * footer, not by dropping it.
914
+ *
915
+ * `RuntimeCoordinator.retireDemotedDependencyDriftBlocker` can delete its
916
+ * whole `_pendingInlineBlockers` record because that store backs exactly one
917
+ * surface (the turn-end advisory text). This store does not: `allDiagnostics`
918
+ * is also what `getFileDiagnosticSummaries` serves to `lens_diagnostics
919
+ * mode=all` and what `lens_diagnostic_mark` cross-checks to reanchor. Removing
920
+ * the entry there turned an unconfirmed LSP error into a CLEAN read and took
921
+ * it out of the error tally #1631 requires it to stay in. So the cap marks
922
+ * `footerRetired` and leaves everything else — severity, counts, the
923
+ * `stale`/`dependency-drift` demotion — exactly as it was; mode=all carries
924
+ * the "capped, still confirmable" wording in its own note. Returns true iff
925
+ * something changed (idempotent on a second call).
926
+ */
927
+ export function retireWidgetDependencyDriftBlockers(filePath) {
928
+ const rec = files.get(fileMapKey(filePath));
929
+ if (!rec)
930
+ return false;
931
+ let changed = false;
932
+ for (const d of rec.allDiagnostics) {
933
+ if (isDependencyDriftDemoted(d)) {
934
+ d.footerRetired = true;
935
+ changed = true;
936
+ }
937
+ }
938
+ if (!changed)
939
+ return false;
940
+ // The record's severity tallies are unchanged by design (the finding is
941
+ // still an error, still non-blocking) — only what the footer draws moves.
942
+ // Fix-round 3 (#2275 review F1): this delete is live only for the
943
+ // multi-file drain re-render case — `drainRenderedDependencyDriftFilePaths`
944
+ // already clears the whole Set before this loop starts, so by the time a
945
+ // given `wPath` reaches this call its own entry is normally long gone. It
946
+ // only does something when a render for a DIFFERENT file interleaves
947
+ // between the drain and this retire (adding `rec.filePath` back to the
948
+ // Set mid-loop) or a render fires between retirement and the caller's next
949
+ // drain — either way, defensive: it keeps a just-retired file from being
950
+ // double-drained (and double-charged) on the next pass.
951
+ renderedDependencyDriftFiles.delete(rec.filePath);
952
+ requestRenderFn?.();
953
+ return true;
954
+ }
807
955
  /**
808
956
  * Keep the TUI honest (#298 follow-up). `reconcileStaleWidgetFiles` drops
809
957
  * widget entries whose file changed on disk after they were last recorded
@@ -840,6 +988,7 @@ export function getFileDiagnosticSummaries() {
840
988
  blocking: rec.diagnosticCounts.blocking,
841
989
  errors: rec.diagnosticCounts.errors,
842
990
  warnings: rec.diagnosticCounts.warnings,
991
+ advisories: countAdvisories(rec.allDiagnostics),
843
992
  hasFinalSnapshot: rec.hasFinalDiagnosticsSnapshot,
844
993
  diagnostics: rec.allDiagnostics.map((d) => ({ ...d })),
845
994
  };
@@ -893,12 +1042,6 @@ export function recordLsp(serverId, root, status, durationMs) {
893
1042
  ? "ready"
894
1043
  : "failed";
895
1044
  lspServers.set(key, { serverId, root, status: mapped, durationMs });
896
- while (lspServers.size > MAX_LSP_SERVER_RECORDS) {
897
- const oldest = lspServers.keys().next().value;
898
- if (oldest === undefined)
899
- break;
900
- lspServers.delete(oldest);
901
- }
902
1045
  requestRender();
903
1046
  }
904
1047
  // ── Render ────────────────────────────────────────────────────────────────────
@@ -961,7 +1104,12 @@ export function renderWidget(width, theme) {
961
1104
  // the footer entirely instead of demoting visibly. `isBlockingOrDemoted` keeps
962
1105
  // it in the list; the per-entry render below distinguishes the two with a
963
1106
  // dimmed marker instead of the red dot.
964
- const isBlockingOrDemoted = (d) => isBlocking(d) || d.stale === true;
1107
+ //
1108
+ // #2275: a demotion that reached its delivery cap is hidden from THIS
1109
+ // surface only (`footerRetired`) — it stays in `allDiagnostics` for
1110
+ // mode=all and lens_diagnostic_mark, which is where the agent is told it
1111
+ // was capped and can still be confirmed by a re-run.
1112
+ const isBlockingOrDemoted = (d) => (isBlocking(d) || d.stale === true) && d.footerRetired !== true;
965
1113
  const withBlocking = recencySorted.filter((r) => r.diagnostics.some(isBlockingOrDemoted));
966
1114
  if (withBlocking.length > 0) {
967
1115
  const rec = withBlocking[0];
@@ -979,6 +1127,18 @@ export function renderWidget(width, theme) {
979
1127
  lines.push(fitLine(` ${dim(path.basename(rec.filePath))}`, w));
980
1128
  }
981
1129
  const blockers = rec.diagnostics.filter(isBlockingOrDemoted).slice(0, 5);
1130
+ // #2275 review F1: THIS is the point where a demoted row is delivered to
1131
+ // a human — after the `withBlocking[0]` pick and the top-five slice, not
1132
+ // before them. Mark the file so the next turn end can charge exactly one
1133
+ // delivery against its cap. File-level, matching
1134
+ // `markWidgetFileBlockersStale`'s own file-level demotion batch (all of a
1135
+ // file's drift demotions share one delivery history); a file holding more
1136
+ // than five demoted rows delivers the visible ones and counts the batch,
1137
+ // which is the conservative direction — it retires no LATER than the rows
1138
+ // the agent actually read.
1139
+ if (blockers.some(isDependencyDriftDemoted)) {
1140
+ renderedDependencyDriftFiles.add(rec.filePath);
1141
+ }
982
1142
  for (const d of blockers) {
983
1143
  // A past-EOF demotion's coordinate is untrustworthy — render the
984
1144
  // marker instead of the line (#1641); drift demotions keep theirs.
@@ -1062,7 +1222,10 @@ function formatFileRowVertical(rec, theme) {
1062
1222
  .filter(([, f]) => f.changed && f.success)
1063
1223
  .map(([name]) => name);
1064
1224
  const failedFormatters = [...rec.formatters.entries()]
1065
- .filter(([, f]) => !f.success)
1225
+ // An `unavailable` tool is not a code failure (#2413): it never earns a red
1226
+ // `fmt-failed` marker. Its result already carries success:true, so this
1227
+ // guard is a belt that keeps the intent explicit at the render seam.
1228
+ .filter(([, f]) => !f.success && f.outcome !== "unavailable")
1066
1229
  .map(([name]) => name);
1067
1230
  const formatMark = (failedFormatters.length > 0
1068
1231
  ? red(` fmt-failed:${failedFormatters.join(",")}`)
@@ -1205,7 +1368,9 @@ function hasChangedFormatter(rec) {
1205
1368
  return [...rec.formatters.values()].some((f) => f.changed && f.success);
1206
1369
  }
1207
1370
  function hasFailedFormatter(rec) {
1208
- return [...rec.formatters.values()].some((f) => !f.success);
1371
+ // `unavailable` (#2413) is success:true and must never count as a failure —
1372
+ // the explicit guard keeps that true even if a future path mislabels it.
1373
+ return [...rec.formatters.values()].some((f) => !f.success && f.outcome !== "unavailable");
1209
1374
  }
1210
1375
  function shouldRenderFile(rec) {
1211
1376
  return (rec.hasFinalDiagnosticsSnapshot ||