@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

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 (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -4,6 +4,7 @@
4
4
  * Provides utilities for integrating the declarative dispatch system
5
5
  * with the existing index.ts tool_result handler.
6
6
  */
7
+ import { CASCADE_DIAGNOSTICS_TTL_MS } from "../cascade-types.js";
7
8
  import { getDiagnosticLogger } from "../diagnostic-logger.js";
8
9
  import { detectFileKind } from "../file-kinds.js";
9
10
  import { getLspCapableKinds, getPrimaryDispatchGroup, } from "../language-policy.js";
@@ -16,6 +17,7 @@ export { clearLatencyReports, formatLatencyReport, getLatencyReports };
16
17
  import * as nodeFs from "node:fs";
17
18
  import * as nodePath from "node:path";
18
19
  import { fileURLToPath } from "node:url";
20
+ import { CASCADE_NEIGHBOUR_BUDGET, deriveCascadeNeighbourBudget, } from "../cascade-budget.js";
19
21
  import { formatCascadeNeighborDiagnostics } from "../cascade-format.js";
20
22
  import { logCascade } from "../cascade-logger.js";
21
23
  import { getDiagnosticTracker } from "../diagnostic-tracker.js";
@@ -25,7 +27,8 @@ import { getServersForFileWithConfig } from "../lsp/config.js";
25
27
  import { getLSPService } from "../lsp/index.js";
26
28
  import { isExternalOrVendorFile, normalizeMapKey } from "../path-utils.js";
27
29
  import { getProjectIgnoreMatcher } from "../file-utils.js";
28
- import { resetAstGrepUnsupportedLanguageLog } from "./runners/ast-grep-napi.js";
30
+ import { resetAstGrepNapiLoadState, resetAstGrepUnsupportedLanguageLog, } from "./runners/ast-grep-napi.js";
31
+ import { resetTreeSitterClientLoadState } from "../tree-sitter-shared.js";
29
32
  import { isTestRoleCollateral } from "../collateral-test-role.js";
30
33
  import { clearReviewGraphWorkspaceCache, getGraphBuildInfoForGraph, getGraphImportChanges, graphBuildInfoIsTrustworthy, } from "../review-graph/builder.js";
31
34
  import { buildReverseDependencyIndexFromGraph, getAffectedFilesFromIndex, patchReverseDependencyIndex, writeReverseDependencyIndexToSnapshot, } from "../reverse-deps.js";
@@ -298,13 +301,18 @@ export function resetDispatchBaselines(cwd) {
298
301
  if (cwd)
299
302
  applyProjectLensConfig(cwd);
300
303
  resetAstGrepUnsupportedLanguageLog();
304
+ // #1567: a genuine-failure verdict or a live transient cooldown on the
305
+ // napi load latch is session-scoped state, not process-lifetime state.
306
+ resetAstGrepNapiLoadState();
307
+ // #1592: same shape for the web-tree-sitter load latch — an evaluation-
308
+ // shaped rejection is session-scoped state, not process-lifetime state.
309
+ resetTreeSitterClientLoadState();
301
310
  sessionFacts.clearAll();
302
311
  resetSessionSlopScore();
303
312
  clearCoverageNoticeState();
304
313
  clearReviewGraphWorkspaceCache();
305
314
  clearReverseDepsIndexCache();
306
315
  clearModuleGraphCache();
307
- neighborTouchCache.clear();
308
316
  recentlyCleanNeighborCache.clear();
309
317
  primaryFilesThisTurn.clear();
310
318
  cascadeDiagnosticBaselines.clear();
@@ -325,13 +333,11 @@ let cascadeSessionStats = {
325
333
  export function getCascadeSessionStats() {
326
334
  return { ...cascadeSessionStats };
327
335
  }
328
- const neighborTouchCache = new Map();
329
336
  const recentlyCleanNeighborCache = new Map();
330
- /** O(1) entry counts of this module's turn-bounded caches (#1123 item 2
331
- * memory attribution) — both are `Map.size` reads, never iterated. */
337
+ /** O(1) entry count of this module's turn-bounded cache (#1123 item 2
338
+ * memory attribution) — a `Map.size` read, never iterated. */
332
339
  export function getDispatchCascadeCacheStats() {
333
340
  return {
334
- neighborTouchCacheSize: neighborTouchCache.size,
335
341
  recentlyCleanNeighborCacheSize: recentlyCleanNeighborCache.size,
336
342
  };
337
343
  }
@@ -346,14 +352,12 @@ function ensureCascadeTurnScope(turnSeq) {
346
352
  return;
347
353
  cascadeTurnScope = turnSeq;
348
354
  primaryFilesThisTurn.clear();
349
- neighborTouchCache.clear();
350
355
  for (const [key, entry] of recentlyCleanNeighborCache) {
351
356
  if (turnSeq - entry.turnSeq > RECENTLY_CLEAN_TTL_TURNS) {
352
357
  recentlyCleanNeighborCache.delete(key);
353
358
  }
354
359
  }
355
360
  }
356
- const CASCADE_TTL_MS = 240_000;
357
361
  const MAX_PER_FILE = RUNTIME_CONFIG.pipeline.cascadeMaxDiagnosticsPerFile;
358
362
  const MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
359
363
  /**
@@ -411,11 +415,17 @@ function readBoundToCurrentDisk(rawDiags) {
411
415
  * a future flag cannot be silently missed at just one of the several gate sites:
412
416
  * - `inconclusive` (#1093/#571): the notify/diagnostics wait lapsed its deadline,
413
417
  * so a resolved `[]` is NOT a confirmed clean (the #533 false-clean trap).
414
- * - a COVERAGE GAP (#1470): an auxiliary's push wait was cut off by the aux grace
415
- * timer, so the merged result is missing whatever that scanner would have said.
416
- * Such a touch is deliberately NOT `inconclusive` (the primary answered), which
417
- * is exactly why it needs naming here: reading `!inconclusive` alone would let a
418
- * hung opengrep wipe a live footer finding and seed the recently-clean cache.
418
+ * #1549: this now means a PRIMARY's deadline lapsed. A slow auxiliary lands in
419
+ * the coverage-gap bullet below instead of collapsing the whole touch, so this
420
+ * predicate still fails closed for it while the primary's findings survive —
421
+ * which is the point: the two disqualifiers were doing one job for two causes.
422
+ * - a COVERAGE GAP (#1470/#1493): an auxiliary never reported — its push wait was
423
+ * cut off by the aux grace timer (#1470), or it stayed silent with no stored
424
+ * publication for this content (#1493) — so the merged result is missing whatever
425
+ * that scanner would have said. Such a touch is deliberately NOT `inconclusive`
426
+ * (the primary answered), which is exactly why it needs naming here: reading
427
+ * `!inconclusive` alone would let a hung or silent opengrep wipe a live footer
428
+ * finding and seed the recently-clean cache.
419
429
  * - `binding.boundToCurrentDisk === false` (#1095): the diagnostics were computed
420
430
  * against a DIFFERENT disk state than what is on disk now (the server's view
421
431
  * diverged / a pre-fix buffer) — not an observation of current disk. `true` and
@@ -429,6 +439,25 @@ function readBoundToCurrentDisk(rawDiags) {
429
439
  function readInconclusive(rawDiags) {
430
440
  return rawDiags?.inconclusive === true;
431
441
  }
442
+ /**
443
+ * #1549: an inconclusive `neighbor_touch` row used to carry NO attribution, so a
444
+ * forensic sweep could only infer the cause from duration histograms. `touchFile`
445
+ * now names the PRIMARY server(s) whose deadline produced the verdict and which
446
+ * deadline it was (`notify-write` vs `diagnostics-wait`, or `mixed`) — carry both
447
+ * into cascade.log so the record stands on its own. Read off the wrapper, like
448
+ * every other flag here (#1179).
449
+ */
450
+ function readInconclusiveAttribution(rawDiags) {
451
+ const wrapper = rawDiags;
452
+ return {
453
+ ...(wrapper?.inconclusiveServerIds?.length && {
454
+ inconclusiveServerIds: [...wrapper.inconclusiveServerIds],
455
+ }),
456
+ ...(wrapper?.inconclusiveReason && {
457
+ inconclusiveReason: wrapper.inconclusiveReason,
458
+ }),
459
+ };
460
+ }
432
461
  function isConfirmedTouch(rawDiags) {
433
462
  return (!readInconclusive(rawDiags) &&
434
463
  touchCoverageGap(rawDiags).length === 0 &&
@@ -439,7 +468,9 @@ const REVERSE_DEPS_MAX_WARM_ROOTS = 8;
439
468
  const REVERSE_DEPS_IDLE_EVICT_MS_DEFAULT = 20 * 60_000;
440
469
  function reverseDepsIdleEvictMs() {
441
470
  const value = Number.parseInt(process.env.PI_LENS_REVERSE_DEPS_IDLE_EVICT_MS ?? "", 10);
442
- return Number.isSafeInteger(value) && value > 0 ? value : REVERSE_DEPS_IDLE_EVICT_MS_DEFAULT;
471
+ return Number.isSafeInteger(value) && value > 0
472
+ ? value
473
+ : REVERSE_DEPS_IDLE_EVICT_MS_DEFAULT;
443
474
  }
444
475
  function deleteReverseDepsEntry(key) {
445
476
  const entry = reverseDepsIndexCache.get(key);
@@ -500,8 +531,9 @@ export function _seedReverseDepsIndexCacheForTests(key, index, generation) {
500
531
  // one-hop set is always the floor (it sorts first, before depth-2). Both
501
532
  // env-tunable; set depth to 1 to restore the old one-hop-only behaviour.
502
533
  const CASCADE_TRANSITIVE_DEPTH = Math.max(1, Number.parseInt(process.env.PI_LENS_CASCADE_TRANSITIVE_DEPTH ?? "2", 10) || 2);
503
- const CASCADE_NEIGHBOUR_BUDGET = Math.max(MAX_FILES, Number.parseInt(process.env.PI_LENS_CASCADE_NEIGHBOUR_BUDGET ?? "40", 10) ||
504
- 40);
534
+ // #1462: the cap itself, and the derivation that narrows it when this run has
535
+ // already spent the settle window, live in clients/cascade-budget.ts alongside
536
+ // `cascadeSettleWaitMs` — the two knobs only make sense read together.
505
537
  // Exported (not just module-local) so the MCP warm-analyze seam
506
538
  // (clients/mcp/analyze.ts, #536) can gate its own buildOrUpdateGraph call on
507
539
  // the SAME file-kind eligibility this cascade path uses — one source of truth
@@ -605,8 +637,12 @@ function updateWordIndexForCascade(args) {
605
637
  export async function computeCascadeForFile(filePath, cwd, options = {}) {
606
638
  const reverseDepsTimersToRelease = new Set();
607
639
  const reverseDepsEntriesAtStart = new Set(reverseDepsIndexCache.values());
640
+ // #1462: legacy callers use this run's age when sizing the neighbour walk.
641
+ // The pipeline supplies a live turn_end clock so deferred work does not charge
642
+ // time that elapsed before turn_end began waiting.
643
+ const cascadeStart = Date.now();
608
644
  try {
609
- const { hasBlockers = false, dbg, turnSeq = 0, writeSeq, seqState, fileContent, wordIndex, onWordIndexUpdated, } = options;
645
+ const { hasBlockers = false, dbg, turnSeq = 0, writeSeq, seqState, turnEndCascadeSettleStart, fileContent, wordIndex, onWordIndexUpdated, } = options;
610
646
  ensureCascadeTurnScope(turnSeq);
611
647
  if (hasBlockers) {
612
648
  logCascade({
@@ -672,12 +708,24 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
672
708
  let importerSet = new Set();
673
709
  let callerSet = new Set();
674
710
  let referenceCount = 0;
711
+ let cascadeCandidateCount = 0;
712
+ let cascadeEligibleCount = 0;
713
+ let cascadeFilterDroppedCount = 0;
675
714
  // #1446 item 4: how many eligible neighbors the flat CASCADE_NEIGHBOUR_BUDGET
676
715
  // cut off, distinct from candidates dropped by the filters above it
677
716
  // (missing on disk, vendor, ignored, already-primary-this-turn) — those are
678
717
  // never actionable regardless of budget, so counting them as "truncated"
679
718
  // would overstate what a larger budget could actually recover.
680
719
  let cascadeBudgetTruncated = 0;
720
+ let transitiveTruncated = false;
721
+ // #1462: the budget actually in force for THIS run and the settle time left
722
+ // when it was sized. Both are overwritten inside the graph branch below,
723
+ // which is the only path that reaches `cascade_result` — the other branch
724
+ // returns a `non_code` skip before any of this is logged.
725
+ let cascadeNeighbourBudget = CASCADE_NEIGHBOUR_BUDGET;
726
+ let cascadeBudgetRemainingMs = 0;
727
+ let cascadeBudgetZone = "fits";
728
+ let cascadeDeliveryWindowMs = 0;
681
729
  if (CASCADE_GRAPH_KINDS.has(fileKind)) {
682
730
  const graphStart = Date.now();
683
731
  const graph = await buildOrUpdateGraph(cwd, [normalizedFile], sessionFacts, seqState);
@@ -959,6 +1007,29 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
959
1007
  });
960
1008
  }
961
1009
  }
1010
+ // #1462: size the rest of this run against the on-time window it has NOT
1011
+ // already spent. Derived HERE — after the graph build, the reverse-deps
1012
+ // refresh and the LSP-reference expansion, which is where the measured
1013
+ // overruns were bought — and used for both the transitive BFS cap and the
1014
+ // final slice, so a rescued run expands less as well as walking less.
1015
+ // Narrowing happens ONLY inside the rescue band (see cascade-budget.ts):
1016
+ // a run that is merely late keeps the whole set and is delivered complete
1017
+ // by #1443's carry-over, because a dropped neighbour is lost for good
1018
+ // while a late one is not. A wired callback that cannot observe an active
1019
+ // turn_end wait uses zero elapsed time rather than charging pre-turn age.
1020
+ {
1021
+ const settleStart = turnEndCascadeSettleStart?.();
1022
+ const elapsedMs = turnEndCascadeSettleStart
1023
+ ? settleStart !== undefined && Number.isFinite(settleStart)
1024
+ ? Math.max(0, Date.now() - settleStart)
1025
+ : 0
1026
+ : Date.now() - cascadeStart;
1027
+ const decision = deriveCascadeNeighbourBudget({ elapsedMs });
1028
+ cascadeNeighbourBudget = decision.budget;
1029
+ cascadeBudgetRemainingMs = decision.remainingMs;
1030
+ cascadeBudgetZone = decision.zone;
1031
+ cascadeDeliveryWindowMs = decision.deliveryWindowMs;
1032
+ }
962
1033
  // Bounded transitive expansion: add depth>1 dependents (indirect
963
1034
  // importers/callers/referencers) so the blast radius isn't limited to one
964
1035
  // hop. The one-hop sets above remain the floor (they sort first); these
@@ -966,8 +1037,9 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
966
1037
  if (CASCADE_TRANSITIVE_DEPTH > 1) {
967
1038
  const transitive = computeTransitiveImpact(graph, normalizedFile, {
968
1039
  maxDepth: CASCADE_TRANSITIVE_DEPTH,
969
- maxHits: CASCADE_NEIGHBOUR_BUDGET,
1040
+ maxHits: cascadeNeighbourBudget,
970
1041
  });
1042
+ transitiveTruncated = transitive.truncated;
971
1043
  const added = [
972
1044
  ...new Set(transitive.hits
973
1045
  .map((hit) => hit.file)
@@ -1003,6 +1075,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1003
1075
  // touched solely for cascade diagnostics. Composes the shared `detectFileRole`
1004
1076
  // seam; a classifier failure RETAINS the candidate (honest — never a false
1005
1077
  // clean). The project ignore filter below is separate and unchanged (#297).
1078
+ cascadeCandidateCount = impact.neighborFiles.length;
1006
1079
  impact.directImporters = impact.directImporters.filter((f) => !isTestRoleCollateral(f));
1007
1080
  impact.directCallers = impact.directCallers.filter((f) => !isTestRoleCollateral(f));
1008
1081
  impact.neighborFiles = impact.neighborFiles.filter((f) => !isTestRoleCollateral(f));
@@ -1029,8 +1102,42 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1029
1102
  const rank = (p) => importerSet.has(p) ? 0 : callerSet.has(p) ? 1 : 2;
1030
1103
  return rank(a) - rank(b);
1031
1104
  });
1032
- cascadeBudgetTruncated = Math.max(0, eligibleNeighbors.length - CASCADE_NEIGHBOUR_BUDGET);
1033
- sortedNeighbors = eligibleNeighbors.slice(0, CASCADE_NEIGHBOUR_BUDGET);
1105
+ cascadeEligibleCount = eligibleNeighbors.length;
1106
+ cascadeFilterDroppedCount = Math.max(0, cascadeCandidateCount - cascadeEligibleCount);
1107
+ cascadeBudgetTruncated = Math.max(0, eligibleNeighbors.length - cascadeNeighbourBudget);
1108
+ sortedNeighbors = eligibleNeighbors.slice(0, cascadeNeighbourBudget);
1109
+ if (cascadeBudgetTruncated > 0 || transitiveTruncated) {
1110
+ const budget = {
1111
+ candidateCount: cascadeCandidateCount,
1112
+ eligibleCount: cascadeEligibleCount,
1113
+ selectedCount: sortedNeighbors.length,
1114
+ truncatedCount: cascadeBudgetTruncated,
1115
+ ...(transitiveTruncated && { transitiveTruncated: true }),
1116
+ };
1117
+ const detailParts = [
1118
+ ...(cascadeBudgetTruncated > 0
1119
+ ? [
1120
+ `cascade neighbor budget checked ${budget.selectedCount} of ${budget.eligibleCount} eligible dependents ` +
1121
+ `(${budget.truncatedCount} omitted)`,
1122
+ ]
1123
+ : []),
1124
+ ...(transitiveTruncated
1125
+ ? [
1126
+ "transitive expansion was capped before all eligible dependents were enumerated",
1127
+ ]
1128
+ : []),
1129
+ ];
1130
+ const detail = detailParts.join("; ");
1131
+ impact.indeterminate = impact.indeterminate
1132
+ ? {
1133
+ ...impact.indeterminate,
1134
+ detail: impact.indeterminate.detail
1135
+ ? `${impact.indeterminate.detail}; ${detail}`
1136
+ : detail,
1137
+ budget,
1138
+ }
1139
+ : { reason: "budget_truncated", detail, budget };
1140
+ }
1034
1141
  }
1035
1142
  else {
1036
1143
  logCascade({
@@ -1056,7 +1163,13 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1056
1163
  callerCount: impact.directCallers.length,
1057
1164
  referenceCount: Math.max(0, referenceCount),
1058
1165
  riskFlags: impact.riskFlags,
1059
- metadata: { neighbors: sortedNeighbors.slice(0, 10) },
1166
+ metadata: {
1167
+ neighbors: sortedNeighbors.slice(0, 10),
1168
+ candidateNeighborCount: cascadeCandidateCount,
1169
+ eligibleNeighborCount: cascadeEligibleCount,
1170
+ filterDroppedCount: cascadeFilterDroppedCount,
1171
+ budgetTrimmedCount: cascadeBudgetTruncated,
1172
+ },
1060
1173
  });
1061
1174
  const lspService = getLSPService();
1062
1175
  // Hoist passive snapshot once — used for auto-propagating LSPs and fallback path.
@@ -1079,28 +1192,33 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1079
1192
  // the latter must not collapse into a clean-looking result (#1104 honesty
1080
1193
  // rule, same doctrine as #1023's graph-degraded indeterminate marker).
1081
1194
  let fallbackBindingRejected = false;
1195
+ const noLspCandidatePaths = new Set();
1082
1196
  // #1444: neighbours whose in-lane wait was skipped for the quiet-window
1083
1197
  // reconcile to answer later. Logged on `cascade_result` so a cascade that
1084
1198
  // deferred EVERY neighbour is distinguishable from a genuine leaf (both are
1085
1199
  // `neighborCount: 0` with no output otherwise).
1086
1200
  let collectLaterSkipped = 0;
1087
- // #1446 item 5: `recentlyCleanNeighborCache` and `neighborTouchCache` hits
1088
- // are the whole point of both caches, but neither was ever counted — the
1089
- // only visible signal was 267s/day of touch wall time with no way to tell
1090
- // whether the caches were absorbing repeat work or every touch was cold.
1201
+ // #1446 item 5: `recentlyCleanNeighborCache` hits are the whole point of the
1202
+ // cache, but they were never counted — the only visible signal was 267s/day
1203
+ // of touch wall time with no way to tell whether the cache was absorbing
1204
+ // repeat work or every touch was cold. (#1899 removed the sibling
1205
+ // `cacheHits` counter along with the dead `neighborTouchCache`.)
1091
1206
  let recentlyCleanHits = 0;
1092
- let cacheHits = 0;
1093
1207
  // F1 (#1446 follow-up): `coldTouches` must be counted at the point each
1094
1208
  // neighbour's OUTCOME is actually known, not derived from `coldSnapshotPaths`
1095
1209
  // (finalized earlier, before the cache-hit checks below run against it). Using
1096
1210
  // the pre-outcome list let a neighbour double-count (cold-snapshot AND cache/
1097
1211
  // recently-clean hit) or vanish from every bucket (an `activePaths` neighbour —
1098
- // e.g. Python/Go — that misses both caches). These four counters partition the
1099
- // touched-neighbour set `[...activePaths, ...coldSnapshotPaths]` exactly once
1100
- // each: a neighbour with no LSP server configured is the only outcome
1101
- // deliberately excluded (never attempted, no bucket).
1212
+ // e.g. Python/Go — that misses both caches). These counters partition the
1213
+ // selected-neighbour outcomes; passive snapshots and no-server selections are
1214
+ // tracked separately instead of silently disappearing from the denominator.
1215
+ // `coldTouches` includes every genuine active touch attempt, while
1216
+ // `touchFailures` is its separate bounded failure subtype.
1102
1217
  let deferredTouches = 0;
1103
1218
  let coldTouches = 0;
1219
+ let passiveSnapshotHits = 0;
1220
+ let noLspConfigured = 0;
1221
+ let touchFailures = 0;
1104
1222
  if (sortedNeighbors.length > 0) {
1105
1223
  const snapshotPaths = sortedNeighbors.filter(shouldReadCascadeFromSnapshot);
1106
1224
  const activePaths = sortedNeighbors.filter((n) => !shouldReadCascadeFromSnapshot(n));
@@ -1115,7 +1233,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1115
1233
  const snapshotAgeSec = entry
1116
1234
  ? Math.round((Date.now() - entry.ts) / 1000)
1117
1235
  : undefined;
1118
- const ttlFresh = entry != null && Date.now() - entry.ts < CASCADE_TTL_MS;
1236
+ const ttlFresh = entry != null && Date.now() - entry.ts < CASCADE_DIAGNOSTICS_TTL_MS;
1119
1237
  // #1095: content binding is the INNER gate; TTL stays the outer bound.
1120
1238
  // false → the server's diagnostics were computed against a DIFFERENT
1121
1239
  // disk state (e.g. the PRE-fix content) — don't trust or
@@ -1163,6 +1281,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1163
1281
  // had no remaining purpose and is simply dropped rather than migrated.
1164
1282
  const diags = convertLspDiagnostics(entry.diags.filter((d) => d.severity === 1).slice(0, MAX_PER_FILE), neighborPath);
1165
1283
  producedLspData = true;
1284
+ passiveSnapshotHits++;
1166
1285
  const durationMs = Date.now() - neighborStart;
1167
1286
  logCascade({
1168
1287
  phase: "neighbor_snapshot",
@@ -1184,7 +1303,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1184
1303
  // LSP warning on the neighbor is preserved. Keyed by the primary edit's
1185
1304
  // `writeSeq` so a genuinely newer per-edit write still wins the
1186
1305
  // WriteOrderingGuard. `observedAt = entry.ts` (the snapshot's own publish
1187
- // time, up to CASCADE_TTL_MS old) — NOT now() — so replaying an aging
1306
+ // time, up to CASCADE_DIAGNOSTICS_TTL_MS old) — NOT now() — so replaying an aging
1188
1307
  // snapshot never re-arms the mtime-staleness gate (the same #1092
1189
1308
  // re-arming defect this PR fixes for cache hits).
1190
1309
  //
@@ -1214,7 +1333,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1214
1333
  const cacheKey = normalizeMapKey(neighborPath);
1215
1334
  const passiveEntry = allDiags.get(cacheKey);
1216
1335
  const hasFreshPassiveErrors = passiveEntry != null &&
1217
- Date.now() - passiveEntry.ts < CASCADE_TTL_MS &&
1336
+ Date.now() - passiveEntry.ts < CASCADE_DIAGNOSTICS_TTL_MS &&
1218
1337
  passiveEntry.diags.some((d) => d.severity === 1);
1219
1338
  const recentlyClean = recentlyCleanNeighborCache.get(cacheKey);
1220
1339
  if (recentlyClean &&
@@ -1244,33 +1363,13 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1244
1363
  durationMs,
1245
1364
  };
1246
1365
  }
1247
- // A5: skip re-touch if this neighbor was already diagnosed at the current
1248
- // write sequence. A new write (higher writeSeq) invalidates the cache entry.
1249
- const cached = writeSeq != null ? neighborTouchCache.get(cacheKey) : undefined;
1250
- if (cached?.turnSeq === turnSeq && cached?.writeSeq === writeSeq) {
1251
- producedLspData = true;
1252
- cacheHits++;
1253
- const durationMs = Date.now() - neighborStart;
1254
- logCascade({
1255
- phase: "neighbor_snapshot",
1256
- filePath,
1257
- neighborFile: neighborPath,
1258
- diagnosticCount: cached.diagnostics.length,
1259
- durationMs,
1260
- autoPropagate: false,
1261
- snapshotMissing: false,
1262
- metadata: { cachedWriteSeq: writeSeq },
1263
- });
1264
- return {
1265
- filePath: neighborPath,
1266
- reason: neighborReason(importerSet, callerSet, neighborPath),
1267
- diagnostics: cached.diagnostics,
1268
- lspTouched: false,
1269
- durationMs,
1270
- };
1271
- }
1366
+ // #1899: the A5 same-write cache read stood here. See the removal note
1367
+ // at the module's cache declarations — the gate could only pass inside
1368
+ // a single write, which never runs two cascades.
1272
1369
  const configuredServerCount = getServersForFileWithConfig(neighborPath).length;
1273
1370
  if (configuredServerCount === 0) {
1371
+ noLspConfigured++;
1372
+ noLspCandidatePaths.add(cacheKey);
1274
1373
  logCascade({
1275
1374
  phase: "neighbor_fallback",
1276
1375
  filePath,
@@ -1341,8 +1440,8 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1341
1440
  },
1342
1441
  });
1343
1442
  // Deliberately NOT cached as clean/diagnosed — the wait was
1344
- // skipped, not resolved, so neither neighborTouchCache nor
1345
- // recentlyCleanNeighborCache may treat this as a real answer
1443
+ // skipped, not resolved, so recentlyCleanNeighborCache must
1444
+ // not treat this as a real answer
1346
1445
  // (#240 doctrine). Return undefined: the degraded-fallback
1347
1446
  // path below still has a chance to surface a passive/stale
1348
1447
  // snapshot, same as any other "no fresh data this touch" case.
@@ -1367,13 +1466,27 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1367
1466
  // which is finalized before any of the above checks run and includes
1368
1467
  // neighbours that resolve via cache/recently-clean instead.
1369
1468
  coldTouches++;
1469
+ // #1720: language-server scope only, matching the tier-aware touch
1470
+ // above (`clientScope: "primary"`, integration.ts:1826). A neighbor's
1471
+ // content did not change — only its import target did — so an
1472
+ // auxiliary scanner's (ast-grep/opengrep/typos) file-local verdict for
1473
+ // it cannot have changed; only cross-file type semantics (the
1474
+ // language server) can. `reconcileCascadeNeighborLspErrors` already
1475
+ // merges errors-only and preserves the neighbor's existing aux
1476
+ // findings by construction (see its doc comment and
1477
+ // `cascadeReconcilableLspErrors` above), so an `"all"`-scope touch
1478
+ // paid full aux notify traffic and aux confirmation-wait latency for
1479
+ // a re-derivation nothing downstream ever reads. `"primary"` resolves
1480
+ // per `neighborPath` (`getClientForFile` → `getServersForFileWithConfig`),
1481
+ // so a multi-language neighbor still gets its OWN language server,
1482
+ // not the primary edit's.
1370
1483
  const rawDiags = await lspService.touchFile(neighborPath, content, {
1371
1484
  diagnostics: "document",
1372
1485
  collectDiagnostics: true,
1373
1486
  maxClientWaitMs: isColdSnapshot ? 1000 : 2000,
1374
1487
  silent: true,
1375
1488
  source: "cascade",
1376
- clientScope: "all",
1489
+ clientScope: "primary",
1377
1490
  });
1378
1491
  if (!rawDiags)
1379
1492
  return undefined;
@@ -1392,9 +1505,10 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1392
1505
  const confirmed = isConfirmedTouch(rawDiags);
1393
1506
  const bindingRejected = readBoundToCurrentDisk(rawDiags) === false;
1394
1507
  const inconclusive = readInconclusive(rawDiags);
1395
- // #1470: the third, independent reason a touch is unconfirmed — an
1396
- // auxiliary our grace timer cut off. Logged alongside the other two so
1397
- // cascade.log alone still tells the three apart.
1508
+ // #1470/#1493: the third, independent reason a touch is unconfirmed — an
1509
+ // auxiliary that never reported, cut off by our grace timer or silent
1510
+ // with nothing published for this content. Logged alongside the other two
1511
+ // so cascade.log alone still tells the three apart.
1398
1512
  const unconfirmedServerIds = touchCoverageGap(rawDiags);
1399
1513
  // #692: `source: "cascade"` no longer overrides `rule` (see the
1400
1514
  // doc comment on the sibling call above) — dropped rather than
@@ -1405,15 +1519,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1405
1519
  // wrapper and are unaffected by this copy (the shape-5 fix).
1406
1520
  const diags = convertLspDiagnostics(rawDiags.diags.filter((d) => d.severity === 1).slice(0, MAX_PER_FILE), neighborPath);
1407
1521
  const durationMs = Date.now() - neighborStart;
1408
- // Cache only a confirmed answer. An inconclusive or binding-rejected
1409
- // result must not become a confirmed cache hit on the next cascade.
1410
- if (writeSeq != null && confirmed) {
1411
- neighborTouchCache.set(cacheKey, {
1412
- turnSeq,
1413
- writeSeq,
1414
- diagnostics: diags,
1415
- });
1416
- }
1522
+ // #1899: the A5 same-write cache write stood here.
1417
1523
  if (diags.length === 0) {
1418
1524
  // Only a CONFIRMED clean touch may seed the recently-clean cache
1419
1525
  // (#1095: a bound-false touch is unconfirmed, exactly like inconclusive).
@@ -1446,7 +1552,12 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1446
1552
  // stays conditional since "bound" carries no extra signal.
1447
1553
  metadata: {
1448
1554
  inconclusive,
1449
- ...(bindingRejected && { bindingState: bindingStateLabel(false) }),
1555
+ // #1549: WHICH primary and WHICH deadline, present only when the
1556
+ // touch actually reported itself inconclusive.
1557
+ ...(inconclusive && readInconclusiveAttribution(rawDiags)),
1558
+ ...(bindingRejected && {
1559
+ bindingState: bindingStateLabel(false),
1560
+ }),
1450
1561
  ...(unconfirmedServerIds.length > 0 && {
1451
1562
  unconfirmedServerIds: [...unconfirmedServerIds],
1452
1563
  }),
@@ -1476,6 +1587,15 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1476
1587
  diagnostics: diags,
1477
1588
  lspTouched: true,
1478
1589
  ...(inconclusive && { inconclusive: true }),
1590
+ // #1459: carry the coverage gap to the agent-facing surface. A
1591
+ // scanner whose breaker was open, whose resync the fan-out gate
1592
+ // deferred, or whom the aux grace timer cut off never looked at this
1593
+ // file — without this the neighbour reaches the agent as
1594
+ // `{ diagnostics: [] }` with no marker, which is the false-clean the
1595
+ // touch already refuses to claim for itself.
1596
+ ...(unconfirmedServerIds.length > 0 && {
1597
+ unconfirmedServerIds: [...unconfirmedServerIds],
1598
+ }),
1479
1599
  durationMs,
1480
1600
  };
1481
1601
  }));
@@ -1488,10 +1608,11 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1488
1608
  neighbors.push(result.value);
1489
1609
  }
1490
1610
  else {
1611
+ touchFailures++;
1491
1612
  // A3: one failed LSP doesn't kill the rest — fall back to passive snapshot
1492
1613
  dbg?.(`cascade neighbor touch error for ${neighborPath}: ${result.reason}`);
1493
1614
  const entry = allDiags.get(normalizeMapKey(neighborPath));
1494
- const ttlFresh = entry != null && Date.now() - entry.ts < CASCADE_TTL_MS;
1615
+ const ttlFresh = entry != null && Date.now() - entry.ts < CASCADE_DIAGNOSTICS_TTL_MS;
1495
1616
  // #1104: consult binding before trusting a TTL-fresh fallback snapshot —
1496
1617
  // MATCH #1100/#1095 semantics (false → skip, "unknown" → keep the
1497
1618
  // pre-#1104 TTL-only behavior, true → use). Without this, a failed
@@ -1538,8 +1659,15 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1538
1659
  }
1539
1660
  }
1540
1661
  // CR-3/A2: degraded fallback when no neighbor produced trustworthy LSP data —
1541
- // not merely when the graph returned zero neighbors.
1542
- if (!producedLspData) {
1662
+ // not merely when the graph returned zero neighbors. In a mixed run, only
1663
+ // candidates that had no configured LSP may use passive fallback; broadening
1664
+ // to unrelated cache entries would make the partial active result misleading.
1665
+ if (producedLspData && noLspCandidatePaths.size > 0) {
1666
+ const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, noLspCandidatePaths);
1667
+ if (bindingRejected)
1668
+ fallbackBindingRejected = true;
1669
+ }
1670
+ else if (!producedLspData) {
1543
1671
  const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath);
1544
1672
  if (bindingRejected)
1545
1673
  fallbackBindingRejected = true;
@@ -1570,6 +1698,12 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1570
1698
  };
1571
1699
  }
1572
1700
  const filesWithErrors = visibleNeighbors.filter((n) => n.diagnostics.length > 0).length;
1701
+ const selectedOutcomeCount = passiveSnapshotHits +
1702
+ recentlyCleanHits +
1703
+ deferredTouches +
1704
+ coldTouches +
1705
+ noLspConfigured;
1706
+ const selectedOutcomeGap = Math.max(0, sortedNeighbors.length - selectedOutcomeCount);
1573
1707
  logCascade({
1574
1708
  phase: "cascade_result",
1575
1709
  filePath,
@@ -1578,6 +1712,16 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1578
1712
  metadata: {
1579
1713
  filesWithErrors,
1580
1714
  hasOutput: formatted.length > 0,
1715
+ indeterminateReason: impact.indeterminate?.reason,
1716
+ candidateNeighborCount: cascadeCandidateCount,
1717
+ eligibleNeighborCount: cascadeEligibleCount,
1718
+ filterDroppedCount: cascadeFilterDroppedCount,
1719
+ selectedNeighborCount: sortedNeighbors.length,
1720
+ selectedOutcomeCount,
1721
+ selectedOutcomeGap,
1722
+ passiveSnapshotHits,
1723
+ noLspConfigured,
1724
+ touchFailures,
1581
1725
  // #1444: >0 means "answers are still outstanding", not "nothing found".
1582
1726
  collectLaterSkipped,
1583
1727
  // Log when cascade ran but found nothing — distinguishes "clean" from "no signal"
@@ -1586,21 +1730,42 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1586
1730
  // #1446 item 5: cache effectiveness as a number instead of an inference
1587
1731
  // from `coldSnapshot`/`snapshotMissing` flags scattered across
1588
1732
  // per-neighbor `neighbor_touch`/`neighbor_snapshot` rows.
1589
- // F1: cacheHits + recentlyCleanHits + deferredTouches + coldTouches
1590
- // partition `[...activePaths, ...coldSnapshotPaths]` exactly — each
1591
- // counter increments at the point its neighbour's outcome is actually
1592
- // decided, not from `coldSnapshotPaths` (a pre-outcome list finalized
1593
- // before the cache-hit checks run). A neighbour with no LSP server
1594
- // configured is the one deliberately uncounted outcome (never touched).
1595
- cacheHits,
1733
+ // F1: recentlyCleanHits + deferredTouches + coldTouches
1734
+ // retain their outcome semantics. Passive snapshots and no-server selections
1735
+ // complete the selected-neighbor denominator; touchFailures is a bounded
1736
+ // subtype of coldTouches, not an additional outcome.
1596
1737
  recentlyCleanHits,
1597
1738
  deferredTouches,
1598
1739
  coldTouches,
1599
1740
  // #1446 item 4: the budget in force and how many eligible candidates
1600
1741
  // it cut off this run — the correctness half (a truncated run being
1601
- // silently discarded) is #1443; this is observability only.
1602
- neighborBudget: CASCADE_NEIGHBOUR_BUDGET,
1742
+ // silently discarded) is #1443.
1743
+ // #1462: `neighborBudget` is now the DERIVED budget (still "the one in
1744
+ // force"); `neighborBudgetCeiling` is the flat cap, `budgetRemainingMs`
1745
+ // the on-time window left when it was sized, and `budgetZone` the rule
1746
+ // that decided it. Only `budgetZone: "narrowed"` shortens a walk — the
1747
+ // other three keep the flat cap, so a row where budget < ceiling is a
1748
+ // deliberate rescue, never a cold-start stub. `budgetDeliveryWindowMs`
1749
+ // records how long the pipeline (turn_end settle + quiet-window drain)
1750
+ // keeps a slow run alive, which is why the other zones can afford to
1751
+ // stay wide.
1752
+ //
1753
+ // NOTE for whoever reads these rows: `budgetTruncated` INVERTS under a
1754
+ // narrowed budget — it is `eligible - budget`, so a smaller budget
1755
+ // mechanically raises it. Compare truncation WITHIN a zone, never
1756
+ // across one.
1757
+ neighborBudget: cascadeNeighbourBudget,
1758
+ neighborBudgetCeiling: CASCADE_NEIGHBOUR_BUDGET,
1759
+ budgetRemainingMs: cascadeBudgetRemainingMs,
1760
+ budgetZone: cascadeBudgetZone,
1761
+ budgetDeliveryWindowMs: cascadeDeliveryWindowMs,
1603
1762
  budgetTruncated: cascadeBudgetTruncated,
1763
+ transitiveTruncated,
1764
+ budgetTrimmedCount: cascadeBudgetTruncated,
1765
+ budgetDerivationDisabled: cascadeBudgetZone === "no-rescue-window",
1766
+ budgetDerivationDisabledReason: cascadeBudgetZone === "no-rescue-window"
1767
+ ? "no-rescue-window"
1768
+ : undefined,
1604
1769
  neighbors: visibleNeighbors.slice(0, 10).map((n) => ({
1605
1770
  file: n.filePath.replace(/\\/g, "/").split("/").slice(-2).join("/"),
1606
1771
  diagnostics: n.diagnostics.length,
@@ -1624,6 +1789,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
1624
1789
  diagnosticCount: diagCount,
1625
1790
  skipReason: "indeterminate",
1626
1791
  indeterminate: impact.indeterminate,
1792
+ selectedNeighborPaths: sortedNeighbors.slice(),
1627
1793
  };
1628
1794
  }
1629
1795
  const skipReason = visibleNeighbors.length === 0 ? "no_neighbors" : "clean";
@@ -1701,7 +1867,7 @@ function applyCascadeDeltaBaselines(neighbors) {
1701
1867
  * because its content binding was rejected (#1104) — the caller folds this
1702
1868
  * into the run-level `fallbackBindingRejected` flag for the honesty check.
1703
1869
  */
1704
- function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath) {
1870
+ function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, allowedPaths) {
1705
1871
  const now = Date.now();
1706
1872
  const seen = new Set(neighbors.map((n) => normalizeMapKey(n.filePath)));
1707
1873
  let bindingRejected = false;
@@ -1710,6 +1876,8 @@ function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, fi
1710
1876
  const diagKey = normalizeMapKey(diagPath);
1711
1877
  if (diagKey === normalizedFileKey || seen.has(diagKey))
1712
1878
  continue;
1879
+ if (allowedPaths && !allowedPaths.has(diagKey))
1880
+ continue;
1713
1881
  if (primaryFilesThisTurn.has(diagKey))
1714
1882
  continue;
1715
1883
  if (isExternalOrVendorFile(diagPath, cwd))
@@ -1723,7 +1891,7 @@ function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, fi
1723
1891
  continue;
1724
1892
  if (!nodeFs.existsSync(diagPath))
1725
1893
  continue;
1726
- if (now - ts > CASCADE_TTL_MS)
1894
+ if (now - ts > CASCADE_DIAGNOSTICS_TTL_MS)
1727
1895
  continue;
1728
1896
  // #1104: a TTL-fresh entry is not automatically trustworthy — consult
1729
1897
  // binding the same way the reconcile path (#1100) and the touch-error