@herbertgao/pi-extensions 2026.8.6 → 2026.8.7

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 (343) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  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 +272 -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 +1 -1
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -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 +1 -0
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -10
  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 +2 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  49. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  50. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  51. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  52. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  53. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  54. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  55. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  56. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  57. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  58. package/node_modules/pi-lens/CHANGELOG.md +909 -0
  59. package/node_modules/pi-lens/README.md +11 -3
  60. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  61. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  62. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  63. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  64. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  65. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  66. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  67. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  68. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  69. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  70. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  71. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  72. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  73. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  74. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  75. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  76. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  77. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  78. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  79. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  80. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  81. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  82. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  83. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  84. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  86. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  87. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  122. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  123. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  124. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  125. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  126. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  127. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  128. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  129. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  130. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  131. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  134. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  135. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  136. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  137. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  138. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  139. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  140. package/node_modules/pi-lens/dist/clients/jscpd-client.js +82 -6
  141. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  142. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  143. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  144. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  145. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  146. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  147. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  148. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
  149. package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
  150. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  151. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  152. package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
  153. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  154. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  155. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  156. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  157. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  158. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  159. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  160. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  161. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  162. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  163. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  164. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  165. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  166. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  167. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  168. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  169. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  170. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  171. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  172. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  173. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  174. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  175. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  176. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  177. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  178. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  179. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  180. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  181. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  182. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  183. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  185. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  186. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  187. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  188. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  189. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  190. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
  191. package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
  192. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  193. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  194. package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
  195. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  196. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  197. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  198. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  199. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  200. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  201. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  202. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  203. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  204. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  205. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  210. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  211. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  212. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  213. package/node_modules/pi-lens/dist/index.js +67738 -55739
  214. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
  215. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  216. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  217. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  218. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  219. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  220. package/node_modules/pi-lens/docs/features.md +19 -5
  221. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  222. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  223. package/node_modules/pi-lens/docs/mcp.md +2 -2
  224. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  225. package/node_modules/pi-lens/docs/settings.md +19 -1
  226. package/node_modules/pi-lens/docs/usage.md +1 -0
  227. package/node_modules/pi-lens/package.json +10 -6
  228. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  232. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  233. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  234. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  235. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  236. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  237. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  238. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  239. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  240. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  241. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  252. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  253. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  254. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  255. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  256. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  257. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  258. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  288. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  289. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  290. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  291. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  292. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  293. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  294. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  295. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  296. package/node_modules/pi-mcp-adapter/README.md +47 -5
  297. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  298. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  299. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  300. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  301. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  302. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  303. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  304. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  305. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  306. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  307. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  308. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  309. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  310. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  311. package/node_modules/pi-mcp-adapter/package.json +3 -1
  312. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  313. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  314. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  315. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  316. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  317. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  318. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  319. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  320. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  321. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  322. package/node_modules/pi-web-access/CHANGELOG.md +23 -0
  323. package/node_modules/pi-web-access/README.md +27 -9
  324. package/node_modules/pi-web-access/anysearch.ts +4 -2
  325. package/node_modules/pi-web-access/brave.ts +16 -4
  326. package/node_modules/pi-web-access/curator-page.ts +6 -3
  327. package/node_modules/pi-web-access/curator-server.ts +4 -1
  328. package/node_modules/pi-web-access/exa.ts +18 -7
  329. package/node_modules/pi-web-access/extract.ts +28 -3
  330. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  331. package/node_modules/pi-web-access/index.ts +41 -57
  332. package/node_modules/pi-web-access/package.json +1 -1
  333. package/node_modules/pi-web-access/page-query.ts +4 -2
  334. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  335. package/node_modules/pi-web-access/parallel.ts +2 -2
  336. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  337. package/node_modules/pi-web-access/serper.ts +211 -0
  338. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  339. package/node_modules/pi-web-access/summary-review.ts +44 -22
  340. package/node_modules/pi-web-access/tavily.ts +16 -4
  341. package/node_modules/pi-web-access/utils.ts +87 -0
  342. package/node_modules/pi-web-access/valyu.ts +199 -0
  343. package/package.json +13 -13
@@ -1,10 +1,14 @@
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 { demotePastEofDiagnostics, } from "./diagnostic-line-freshness.js";
4
5
  import { visibleWidth } from "./deps/pi-tui.js";
5
6
  import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
6
7
  import { fitLine } from "./tui-fit.js";
7
8
  import { WriteOrderingGuard } from "./write-ordering-guard.js";
9
+ import { collectForwardImportMtimes, MTIME_DRIFT_TOLERANCE_MS, } from "./blocker-freshness.js";
10
+ import { PAST_EOF_STALE_MARKER } from "./diagnostic-line-freshness.js";
11
+ import { STALE_LINE_MARKER } from "./stale-marker.js";
8
12
  /**
9
13
  * Canonical key for the `files` map (and `diagnosticsWriteGuard`) — #1020.
10
14
  *
@@ -34,8 +38,17 @@ function fileMapKey(filePath) {
34
38
  * (`semantic === "blocking"`). Falls back to severity for sources that
35
39
  * don't set `semantic` (raw tsc/eslint diagnostics) so the red dot still
36
40
  * fires on traditional compile errors.
41
+ *
42
+ * #1561: exported so the blocker-retire decision in `tools/lsp-diagnostics.ts`
43
+ * asks THIS predicate rather than growing a second severity rule that can drift
44
+ * from what the footer counts.
37
45
  */
38
- function isBlocking(d) {
46
+ export function isBlocking(d) {
47
+ // #1631: a dependency-drift-demoted finding is no longer a hard stop. The gate
48
+ // sets `stale` rather than dropping the entry (#1419 demote-not-drop), so every
49
+ // tally and render that asks "is this blocking?" must answer no once demoted.
50
+ if (d.stale)
51
+ return false;
39
52
  if (d.semantic === "blocking")
40
53
  return true;
41
54
  if (d.semantic == null && d.severity === "error")
@@ -111,7 +124,17 @@ export function clearWidgetState() {
111
124
  // migrates a v1 record by inheriting each entry's `observedAt` from the record's
112
125
  // `touchedAt`. A v1 file must never be rejected (that would silently drop resume
113
126
  // diagnostics) nor crash.
114
- export const WIDGET_STATE_VERSION = 2;
127
+ //
128
+ // v2 → v3 (#1631 review F3): a dependency-drift `stale` demotion is a verdict
129
+ // about THIS session's disk state — the next session starts with a fresh (and
130
+ // possibly different) dependency graph, so a demotion from the last session
131
+ // must not outlive it. `exportWidgetState` had been serializing `stale: true`
132
+ // verbatim; on restore, a genuine type error stayed non-blocking across a
133
+ // resume with the dependency pinned back into the past. Same shape as the
134
+ // #1348 failed-formatter precedent one field over: `importWidgetState` strips
135
+ // `stale` on every restored entry so a resumed session re-evaluates from
136
+ // scratch instead of inheriting a possibly-stale-itself verdict.
137
+ export const WIDGET_STATE_VERSION = 3;
115
138
  /**
116
139
  * Snapshot the per-file widget diagnostics for persistence (#190). Excludes
117
140
  * `lspServers` — those are process-bound (servers re-spawn fresh on the next
@@ -143,9 +166,19 @@ export function exportWidgetState() {
143
166
  * with the record's `touchedAt` (the single stamp the whole record shared under
144
167
  * v1). Non-mutating; entries that already carry a stamp (a v2 record) pass
145
168
  * through untouched.
169
+ *
170
+ * #1631 review F3 (v2→v3): also strips `stale` from every entry. A dependency
171
+ * drift demotion is scoped to the session that observed the drift; restoring it
172
+ * verbatim would let a real blocking finding resume as permanently non-blocking
173
+ * (#1348 precedent — see `WIDGET_STATE_VERSION`'s doc comment).
146
174
  */
147
175
  function migrateEntryStamps(entries, recordTouchedAt) {
148
- return (entries ?? []).map((d) => d.observedAt == null ? { ...d, observedAt: recordTouchedAt } : d);
176
+ return (entries ?? []).map((d) => {
177
+ const { stale: _stale, ...rest } = d;
178
+ return rest.observedAt == null
179
+ ? { ...rest, observedAt: recordTouchedAt }
180
+ : rest;
181
+ });
149
182
  }
150
183
  export function importWidgetState(state) {
151
184
  // Accept any known-or-older version and migrate (#1186): reject a missing
@@ -180,6 +213,19 @@ export function importWidgetState(state) {
180
213
  // gate has a concrete observation time and never treats `undefined` as
181
214
  // epoch-0 (which would drop every migrated entry on the first sweep).
182
215
  const recordTouchedAt = f.touchedAt ?? Date.now();
216
+ // #1631 review V1: `diagnosticCounts` is DERIVED from `allDiagnostics`
217
+ // everywhere else in this module (see the `countDiagnostics(rec.allDiagnostics)`
218
+ // call sites) — it must be recomputed here too, from the just-migrated
219
+ // (stale-stripped) entries, not restored verbatim from the snapshot. The
220
+ // persisted counts were computed while a finding was still demoted
221
+ // (`blocking: 0`); once F3 strips `stale` so `isBlocking` reports true
222
+ // again, a verbatim count would still say `blocking: 0` for a record
223
+ // whose entries ARE blocking — inverting the one-predicate invariant
224
+ // `isBlocking` exists to hold. Every consumer (`getFileDiagnosticSummaries`,
225
+ // the record-tier classifier, the footer) trusts `diagnosticCounts`, not a
226
+ // live re-scan of the entries, so a derived count out of sync with its own
227
+ // entries is silently wrong everywhere at once.
228
+ const migratedAllDiagnostics = migrateEntryStamps(f.allDiagnostics, recordTouchedAt);
183
229
  files.set(fileMapKey(f.filePath), {
184
230
  filePath: f.filePath,
185
231
  runners: new Map(f.runners ?? []),
@@ -190,12 +236,8 @@ export function importWidgetState(state) {
190
236
  // clear it). Successes rehydrate as before.
191
237
  formatters: new Map((f.formatters ?? []).filter(([, outcome]) => outcome?.success !== false)),
192
238
  diagnostics: migrateEntryStamps(f.diagnostics, recordTouchedAt),
193
- allDiagnostics: migrateEntryStamps(f.allDiagnostics, recordTouchedAt),
194
- diagnosticCounts: f.diagnosticCounts ?? {
195
- blocking: 0,
196
- errors: 0,
197
- warnings: 0,
198
- },
239
+ allDiagnostics: migratedAllDiagnostics,
240
+ diagnosticCounts: countDiagnostics(migratedAllDiagnostics),
199
241
  hasFinalDiagnosticsSnapshot: f.hasFinalDiagnosticsSnapshot ?? false,
200
242
  touchedAt: recordTouchedAt,
201
243
  });
@@ -262,6 +304,13 @@ export function recordRunner(filePath, runnerId, status, diagnosticCount, durati
262
304
  function toSingleLineMessage(message) {
263
305
  return (message ?? "").replace(/\s+/g, " ").trim();
264
306
  }
307
+ /** Build the OSC-8 target used by every stored diagnostic row. */
308
+ export function widgetDiagnosticUri(filePath, line, column) {
309
+ const base = pathToFileURL(filePath).href;
310
+ return line != null
311
+ ? `${base}#L${line}${column != null ? `:${column}` : ""}`
312
+ : base;
313
+ }
265
314
  export function recordDiagnostics(filePath, diagnostics, writeIndex,
266
315
  // #1093: when the truth was OBSERVED, not when it's being written. Defaults
267
316
  // to `Date.now()` for the per-edit/live path (observed now). A reconcile
@@ -298,12 +347,8 @@ observedAt) {
298
347
  * of diagnostics was observed — so the per-entry stale gate can later drop just
299
348
  * the entries older than the file's mtime rather than the whole record. */
300
349
  function normalizeDiagnostics(filePath, diagnostics, observedTs) {
301
- const base = pathToFileURL(filePath).href;
302
350
  return diagnostics.map((d) => {
303
351
  const rule = d.rule ?? d.id;
304
- const uri = d.line != null
305
- ? `${base}#L${d.line}${d.column != null ? `:${d.column}` : ""}`
306
- : base;
307
352
  return {
308
353
  severity: d.severity ?? "info",
309
354
  semantic: d.semantic,
@@ -312,7 +357,7 @@ function normalizeDiagnostics(filePath, diagnostics, observedTs) {
312
357
  col: d.column,
313
358
  rule,
314
359
  tool: d.tool,
315
- uri,
360
+ uri: widgetDiagnosticUri(filePath, d.line, d.column),
316
361
  observedAt: observedTs,
317
362
  };
318
363
  });
@@ -344,13 +389,69 @@ function countDiagnostics(diags) {
344
389
  for (const diagnostic of diags) {
345
390
  if (isBlocking(diagnostic))
346
391
  blocking++;
392
+ // A past-EOF stale entry keeps its severity for display purposes but is
393
+ // excluded from the error/warning tallies alongside blocking — its cited
394
+ // coordinate is no longer trustworthy, same reasoning as `isBlocking`.
395
+ // A dependency-drift demotion (#1631 criterion 3) is different: the
396
+ // finding itself is still real evidence, only its BLOCKING authority is
397
+ // revoked until re-confirmed, so it stays in the error/warning tally.
398
+ if (diagnostic.stale && (diagnostic.staleReason ?? "past-eof") === "past-eof")
399
+ continue;
347
400
  if (diagnostic.severity === "error")
348
401
  errors++;
349
- else if (diagnostic.severity === "warning")
402
+ // #1777: `hint` and `info` tally alongside `warning`. The dispatch path
403
+ // now preserves all four ast-grep tiers; before, the runner collapsed
404
+ // them to "warning" here. An exact `=== "warning"` test would drop those
405
+ // findings out of the footer entirely, so a file with real findings would
406
+ // render clean. The footer stays a blocking/error/warning summary on
407
+ // purpose — the tier distinction is rendered by the code-quality-warnings
408
+ // advisory, not by these counters.
409
+ else if (diagnostic.severity === "warning" ||
410
+ diagnostic.severity === "hint" ||
411
+ diagnostic.severity === "info")
350
412
  warnings++;
351
413
  }
352
414
  return { blocking, errors, warnings };
353
415
  }
416
+ /**
417
+ * Apply the #1641 past-EOF gate to `rec` in place: demote (never drop) any
418
+ * stored diagnostic whose cited line exceeds the file's CURRENT on-disk line
419
+ * count, then recompute the capped/full diagnostic lists and the counts so
420
+ * every reader (the TUI render loop, `getFileDiagnosticSummaries`,
421
+ * `getFileDiagnostics`) sees one consistent, already-gated record instead of
422
+ * each re-deriving its own verdict. Cheap on the common case: one memoized
423
+ * stat per record (see `getCachedLineCount`), a full recount only when the
424
+ * file's mtime moved since the last check.
425
+ *
426
+ * No `resync` callback here deliberately: this gate runs on the TUI's render
427
+ * path and the bus-publish/mark-tool read accessors, all of which fire far
428
+ * more often than an agent's own edit/tool cadence. Triggering a document
429
+ * resync from every one of those reads would storm the LSP with didOpen calls
430
+ * for a file whose drift hasn't yet been fixed. `tools/lens-diagnostics.ts`'s
431
+ * `formatAllMode` — an explicit, agent-invoked tool call — is the resync
432
+ * trigger point (#1641 criterion 2); this gate still demotes/logs on its own.
433
+ *
434
+ * The gate RE-DERIVES every entry's `stale` flag on every call — it is never
435
+ * a one-way latch. A transient shrink that later restores (truncate-then-
436
+ * write, a formatter pass, a checkout) un-demotes on its own next read, so
437
+ * this always persists the freshly-derived array back into the record, not
438
+ * only when `demotedCount` (which counts RISING edges only, for telemetry)
439
+ * is nonzero.
440
+ */
441
+ function applyPastEofGate(rec, lineCountCache) {
442
+ if (rec.allDiagnostics.length === 0)
443
+ return;
444
+ const { diagnostics } = demotePastEofDiagnostics({
445
+ store: "widget-state",
446
+ cwd: process.cwd(),
447
+ filePath: rec.filePath,
448
+ diagnostics: rec.allDiagnostics,
449
+ lineCountCache,
450
+ });
451
+ rec.allDiagnostics = diagnostics;
452
+ rec.diagnostics = capStoredDiagnostics(diagnostics);
453
+ rec.diagnosticCounts = countDiagnostics(diagnostics);
454
+ }
354
455
  /** The newest per-entry `observedAt` in `diags`, or `fallback` when empty (or no
355
456
  * entry carries a stamp). Used as the record-level `touchedAt` (#1186). */
356
457
  function freshestObservation(diags, fallback) {
@@ -453,6 +554,29 @@ export function reconcileScanDiagnostics(filePath, diagnostics, confirmed, write
453
554
  return;
454
555
  recordDiagnostics(filePath, diagnostics, writeIndex, observedAt);
455
556
  }
557
+ /**
558
+ * Commit the already-correlated result of a full diagnostics scan.
559
+ *
560
+ * Unlike {@link reconcileScanDiagnostics}, this seam accepts widget diagnostics
561
+ * that have already been merged across producing lanes. `lens_diagnostics`
562
+ * uses it after correlating the confirmed LSP sweep, the cheap project scan,
563
+ * and retained widget rows. This prevents a broken LSP lane from hiding
564
+ * independently-produced `ast-grep-napi` findings in the widget count (#1888).
565
+ * Existing per-entry observation times survive the merge; newly-added project
566
+ * rows inherit the scan's observation time.
567
+ */
568
+ export function reconcileCorrelatedScanDiagnostics(filePath, diagnostics, writeIndex, observedAt) {
569
+ const key = fileMapKey(filePath);
570
+ if (!diagnosticsWriteGuard.shouldWrite(key, writeIndex))
571
+ return;
572
+ runnerWriteGuard.shouldWrite(key, writeIndex);
573
+ const observedTs = observedAt ?? Date.now();
574
+ const correlated = diagnostics.map((diagnostic) => ({
575
+ ...diagnostic,
576
+ observedAt: diagnostic.observedAt ?? observedTs,
577
+ }));
578
+ commitDiagnostics(getOrCreate(filePath, key), filePath, correlated, observedTs, key);
579
+ }
456
580
  /**
457
581
  * Drop widget entries whose file changed on disk after pi-lens last recorded
458
582
  * them (`mtimeMs > touchedAt` → the recorded diagnostics predate the current
@@ -479,8 +603,18 @@ export async function reconcileStaleWidgetFiles() {
479
603
  // A clean record (no findings) has no per-entry stamps to consult —
480
604
  // gate it on the record's own `touchedAt` exactly as before, so a ✓
481
605
  // entry for a file that changed on disk still drops.
606
+ //
607
+ // #1631 review F11 follow-up: a THIRD site hitting the same Windows
608
+ // mtime-vs-`Date.now()` skew F2 named for `blocker-freshness.ts` and
609
+ // `reconcileStaleWidgetDependencyBlockers` — a file's mtime can lead
610
+ // `Date.now()` by up to ~11.4ms measured (#1491/#1498 precedent), so a
611
+ // record touched immediately after its own write dropped here at +1ms
612
+ // with zero real drift. Found investigating F11: `lens-diagnostics-
613
+ // mode-all-freshness.test.ts`'s "control" case (and its sibling) failed
614
+ // under this gate, not from cross-test state. Same tolerance, same
615
+ // constant, for the same reason.
482
616
  if (rec.allDiagnostics.length === 0) {
483
- return mtimeMs > rec.touchedAt + 1
617
+ return mtimeMs > rec.touchedAt + MTIME_DRIFT_TOLERANCE_MS
484
618
  ? { mapKey, action: "drop" }
485
619
  : { mapKey, action: "keep" };
486
620
  }
@@ -489,9 +623,9 @@ export async function reconcileStaleWidgetFiles() {
489
623
  // preserved entry beside an entry replayed from an aging snapshot, so a
490
624
  // per-RECORD gate over-cleared the whole record (the residual documented
491
625
  // at dispatch/integration.ts). A missing per-entry stamp (a migrated
492
- // pre-#1186 record) inherits the record's `touchedAt`. +1ms tolerance:
493
- // a freshly-recorded file has observedAt >= mtime.
494
- const survivors = rec.allDiagnostics.filter((d) => !(mtimeMs > (d.observedAt ?? rec.touchedAt) + 1));
626
+ // pre-#1186 record) inherits the record's `touchedAt`. Tolerance matches
627
+ // the Windows host-clock skew rationale above.
628
+ const survivors = rec.allDiagnostics.filter((d) => !(mtimeMs > (d.observedAt ?? rec.touchedAt) + MTIME_DRIFT_TOLERANCE_MS));
495
629
  if (survivors.length === rec.allDiagnostics.length) {
496
630
  return { mapKey, action: "keep" }; // nothing stale
497
631
  }
@@ -524,6 +658,140 @@ export async function reconcileStaleWidgetFiles() {
524
658
  requestRenderFn?.();
525
659
  return dropped;
526
660
  }
661
+ export async function reconcileStaleWidgetDependencyBlockers(cwd, turnIndex) {
662
+ let demoted = 0;
663
+ let truncatedImports = 0;
664
+ for (const [, rec] of files.entries()) {
665
+ // #1631 review F4: narrow demotion to LSP-sourced findings. The import-closure
666
+ // drift check only knows how to reason about IMPORT graphs — an ast-grep
667
+ // hardcoded-secret or a govulncheck CVE finding doesn't stop being true because
668
+ // a file this diagnostic's file imports changed; only a language-server verdict
669
+ // (`tool === "lsp"`, the same predicate `isLspErrorEntry` above uses) is actually
670
+ // invalidated by that shape of drift. Documented trade: a non-LSP blocking
671
+ // finding on a file with drifted imports stays fully authoritative until its own
672
+ // content changes or it is explicitly re-run.
673
+ if (!rec.allDiagnostics.some((d) => isBlocking(d) && d.tool === "lsp"))
674
+ continue;
675
+ let importMtimes = [];
676
+ try {
677
+ const result = await collectForwardImportMtimes(cwd, rec.filePath, undefined, turnIndex);
678
+ importMtimes = result.mtimes;
679
+ if (result.truncated)
680
+ truncatedImports += 1;
681
+ }
682
+ catch {
683
+ importMtimes = [];
684
+ }
685
+ if (importMtimes.length === 0)
686
+ continue;
687
+ let changed = false;
688
+ for (const d of rec.allDiagnostics) {
689
+ if (!isBlocking(d) || d.tool !== "lsp")
690
+ continue;
691
+ const baseline = d.observedAt ?? rec.touchedAt;
692
+ // +50ms tolerance (#1631 review F2): a whole-millisecond `Date.now()`
693
+ // baseline vs. sub-millisecond mtime precision only needs +1ms, but on
694
+ // Windows a file's mtime can LEAD `Date.now()` by up to ~11.4ms (measured
695
+ // across 200 writes; #1491/#1498 precedent for the same host-clock skew).
696
+ // +1ms produced 42 false demotions in 50 runs on Windows; +50ms clears the
697
+ // measured skew while staying far below the gap between real edits.
698
+ if (importMtimes.some((im) => im.mtimeMs > baseline + MTIME_DRIFT_TOLERANCE_MS)) {
699
+ d.stale = true;
700
+ d.staleReason = "dependency-drift";
701
+ changed = true;
702
+ demoted += 1;
703
+ }
704
+ }
705
+ if (changed) {
706
+ rec.diagnosticCounts = countDiagnostics(rec.allDiagnostics);
707
+ rec.diagnostics = capStoredDiagnostics(rec.allDiagnostics);
708
+ }
709
+ }
710
+ if (demoted > 0)
711
+ requestRenderFn?.();
712
+ return { demoted, truncatedImports };
713
+ }
714
+ /**
715
+ * #1790: sweep-population feed for `sweepInlineBlockerFreshness` (the turn-end
716
+ * gate in `clients/blocker-freshness.ts`). That sweep's population was built
717
+ * solely from `RuntimeCoordinator`'s inline-blocker map — a live-dispatch-only
718
+ * store. A workspace-diagnostics CACHE HIT never touches that map; it calls
719
+ * `reconcileScanDiagnostics` straight into `files` here (the cache-serve branch
720
+ * in `tools/lsp-diagnostics.ts`), so a cache-served blocking row could render in
721
+ * the widget while the turn-end sweep's `total` count never saw it (the live
722
+ * 2026-08-20 dogfood: `total:1 kept:1` against five stale cache-served blocking
723
+ * rows on screen).
724
+ *
725
+ * One entry per file with at least one CURRENTLY blocking (non-stale),
726
+ * LSP-sourced diagnostic — the same provenance narrowing
727
+ * `reconcileStaleWidgetDependencyBlockers` already applies, so every row this
728
+ * feeds into the sweep is one its `isLspSourced` gate would keep anyway.
729
+ * `recordedAtMs` is the EARLIEST `observedAt` among them: the conservative
730
+ * baseline, since using the latest could hide drift that predates an earlier
731
+ * diagnostic's own observation.
732
+ */
733
+ export function getWidgetBlockingFilesForSweep() {
734
+ const out = [];
735
+ for (const rec of files.values()) {
736
+ let earliest;
737
+ for (const d of rec.allDiagnostics) {
738
+ if (!isBlocking(d) || d.tool !== "lsp")
739
+ continue;
740
+ const observedAt = d.observedAt ?? rec.touchedAt;
741
+ if (earliest === undefined || observedAt < earliest)
742
+ earliest = observedAt;
743
+ }
744
+ if (earliest !== undefined)
745
+ out.push({ filePath: rec.filePath, recordedAtMs: earliest });
746
+ }
747
+ return out;
748
+ }
749
+ /**
750
+ * #1790: demote every currently-blocking, LSP-sourced diagnostic for `filePath`
751
+ * to stale (#1419 demote-not-drop). Called by the turn-end sweep once its own
752
+ * drift check (`detectDrift`/`collectForwardImportMtimes`, shared with the
753
+ * inline-blocker branch) independently confirms drift for a file that reached
754
+ * the sweep's population through `getWidgetBlockingFilesForSweep` rather than an
755
+ * inline blocker. Same store and same write `reconcileStaleWidgetDependencyBlockers`
756
+ * already performs per-diagnostic — this is the single-file entry point the
757
+ * sweep drives once ITS check (not a second implementation of the check) says
758
+ * to. Returns true iff something changed.
759
+ *
760
+ * #1790 review F4 (latent): unlike `reconcileStaleWidgetDependencyBlockers`, which
761
+ * checks drift per-diagnostic against EACH diagnostic's own `observedAt`, this
762
+ * demotes every currently-blocking LSP diagnostic for the file against the ONE
763
+ * file-level baseline `getWidgetBlockingFilesForSweep` computed (the earliest
764
+ * `observedAt` among them) — because the sweep's `detectDrift` contract is
765
+ * file-level, not per-diagnostic. That is safe ONLY as long as every LSP
766
+ * diagnostic on a `FileRecord` shares (or is more conservative than) that
767
+ * earliest stamp, which holds today because every writer that populates
768
+ * `allDiagnostics` (`recordDiagnostics`, `reconcileCascadeNeighborLspErrors`)
769
+ * replaces the file's LSP errors wholesale with one `observedAt` per write. If a
770
+ * future writer ever merges LSP diagnostics with genuinely MIXED per-entry
771
+ * stamps on one record, this function would over-demote entries observed after
772
+ * the earliest one drifted — re-derive per-diagnostic (mirroring
773
+ * `reconcileStaleWidgetDependencyBlockers`) rather than assume the single
774
+ * baseline still holds.
775
+ */
776
+ export function markWidgetFileBlockersStale(filePath, reason) {
777
+ const rec = files.get(fileMapKey(filePath));
778
+ if (!rec)
779
+ return false;
780
+ let changed = false;
781
+ for (const d of rec.allDiagnostics) {
782
+ if (!isBlocking(d) || d.tool !== "lsp")
783
+ continue;
784
+ d.stale = true;
785
+ d.staleReason = reason;
786
+ changed = true;
787
+ }
788
+ if (changed) {
789
+ rec.diagnosticCounts = countDiagnostics(rec.allDiagnostics);
790
+ rec.diagnostics = capStoredDiagnostics(rec.allDiagnostics);
791
+ requestRenderFn?.();
792
+ }
793
+ return changed;
794
+ }
527
795
  /**
528
796
  * Keep the TUI honest (#298 follow-up). `reconcileStaleWidgetFiles` drops
529
797
  * widget entries whose file changed on disk after they were last recorded
@@ -553,14 +821,17 @@ export function scheduleStaleReconcile() {
553
821
  * everything, not just the 12 the TUI keeps for rendering.
554
822
  */
555
823
  export function getFileDiagnosticSummaries() {
556
- return [...files.values()].map((rec) => ({
557
- filePath: rec.filePath,
558
- blocking: rec.diagnosticCounts.blocking,
559
- errors: rec.diagnosticCounts.errors,
560
- warnings: rec.diagnosticCounts.warnings,
561
- hasFinalSnapshot: rec.hasFinalDiagnosticsSnapshot,
562
- diagnostics: rec.allDiagnostics.map((d) => ({ ...d })),
563
- }));
824
+ return [...files.values()].map((rec) => {
825
+ applyPastEofGate(rec);
826
+ return {
827
+ filePath: rec.filePath,
828
+ blocking: rec.diagnosticCounts.blocking,
829
+ errors: rec.diagnosticCounts.errors,
830
+ warnings: rec.diagnosticCounts.warnings,
831
+ hasFinalSnapshot: rec.hasFinalDiagnosticsSnapshot,
832
+ diagnostics: rec.allDiagnostics.map((d) => ({ ...d })),
833
+ };
834
+ });
564
835
  }
565
836
  /**
566
837
  * Return the current FULL (uncapped) diagnostic set for a single file, as
@@ -584,6 +855,7 @@ export function getFileDiagnostics(filePath) {
584
855
  const rec = files.get(fileMapKey(filePath));
585
856
  if (!rec)
586
857
  return undefined;
858
+ applyPastEofGate(rec);
587
859
  return rec.allDiagnostics.map((d) => ({ ...d }));
588
860
  }
589
861
  /** @internal Test-only helpers. Do not use in production code. */
@@ -666,24 +938,52 @@ export function renderWidget(width, theme) {
666
938
  lines.push(fitLine(formatFileRowVertical(rec, theme), w));
667
939
  }
668
940
  }
669
- // Diagnostics — blocking only, from the most recently touched file that has them.
670
- // Vertical mode keeps the divider/filename context; horizontal already shows the
671
- // filename on the packed row above, so we drop the extra header noise there.
672
- const withBlocking = recencySorted.filter((r) => r.diagnostics.some(isBlocking));
941
+ // Diagnostics — blocking (or dependency-drift-demoted) only, from the most
942
+ // recently touched file that has them. Vertical mode keeps the divider/filename
943
+ // context; horizontal already shows the filename on the packed row above, so
944
+ // we drop the extra header noise there.
945
+ //
946
+ // #1631 review F10: `isBlocking` answers false for a demoted (`stale`) finding
947
+ // by design (#1419 demote-not-drop) — but this render loop used that same
948
+ // predicate to pick which findings to SHOW, so a demoted finding vanished from
949
+ // the footer entirely instead of demoting visibly. `isBlockingOrDemoted` keeps
950
+ // it in the list; the per-entry render below distinguishes the two with a
951
+ // dimmed marker instead of the red dot.
952
+ const isBlockingOrDemoted = (d) => isBlocking(d) || d.stale === true;
953
+ const withBlocking = recencySorted.filter((r) => r.diagnostics.some(isBlockingOrDemoted));
673
954
  if (withBlocking.length > 0) {
674
955
  const rec = withBlocking[0];
956
+ // #1641: gate only the ONE record whose line numbers are about to be
957
+ // rendered, not the whole (potentially session-long) `deduped` list the
958
+ // header counts read — one memoized stat per redraw, never a per-file
959
+ // scan of every file pi-lens has touched this session. A past-EOF
960
+ // citation demoted here can leave the header's aggregate blocking count
961
+ // one turn stale; `reconcileStaleWidgetFiles`'s existing debounced sweep
962
+ // (scheduleStaleReconcile) already re-derives that aggregate on its own
963
+ // cadence, so this is a bounded, self-correcting gap, not a silent one.
964
+ applyPastEofGate(rec);
675
965
  if (!useHorizontal) {
676
966
  lines.push(fitLine(dim("─".repeat(Math.min(w, 60))), w));
677
967
  lines.push(fitLine(` ${dim(path.basename(rec.filePath))}`, w));
678
968
  }
679
- const blockers = rec.diagnostics.filter(isBlocking).slice(0, 5);
969
+ const blockers = rec.diagnostics.filter(isBlockingOrDemoted).slice(0, 5);
680
970
  for (const d of blockers) {
681
- const loc = d.line != null ? osc8(d.uri ?? "", `L${d.line}`) : "";
971
+ // A past-EOF demotion's coordinate is untrustworthy render the
972
+ // marker instead of the line (#1641); drift demotions keep theirs.
973
+ const pastEof = d.stale && (d.staleReason ?? "past-eof") === "past-eof";
974
+ // No link for a past-EOF demotion: the anchor would carry the same
975
+ // untrustworthy line the marker exists to replace.
976
+ const loc = pastEof
977
+ ? PAST_EOF_STALE_MARKER
978
+ : d.line != null
979
+ ? osc8(d.uri ?? "", `L${d.line}`)
980
+ : "";
682
981
  const rule = d.rule ? dim(` ${d.rule}`) : "";
683
- const prefix = ` ${red("")} ${loc}${rule} `;
684
- const msgWidth = Math.max(1, w - visibleWidth(prefix));
982
+ const staleTag = d.stale ? dim(` ${STALE_LINE_MARKER}`) : "";
983
+ const prefix = ` ${d.stale ? dim("○") : red("●")} ${loc}${rule} `;
984
+ const msgWidth = Math.max(1, w - visibleWidth(prefix) - visibleWidth(staleTag));
685
985
  const msg = fitLine(d.message, msgWidth, "…");
686
- lines.push(fitLine(`${prefix}${msg}`, w));
986
+ lines.push(fitLine(`${prefix}${msg}${staleTag}`, w));
687
987
  }
688
988
  }
689
989
  // LSP status tail — only in vertical mode; horizontal folds into header