@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
@@ -14,9 +14,10 @@ import { isFileKind } from "./file-kinds.js";
14
14
  import { getGlobalPiLensDir } from "./file-utils.js";
15
15
  import { findGlobalBinary } from "./package-manager.js";
16
16
  import { safeSpawnAsync } from "./safe-spawn.js";
17
+ import { createSingleFlight } from "./single-flight.js";
17
18
  import { biomeConfigArgs } from "./tool-policy.js";
18
19
  import { resolveManagedToolClient, } from "./dispatch/runners/utils/runner-helpers.js";
19
- import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
20
+ import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
20
21
  // --- Client ---
21
22
  const PROBE_TIMEOUT_MS = 10_000;
22
23
  export class BiomeClient {
@@ -39,6 +40,8 @@ export class BiomeClient {
39
40
  */
40
41
  lastProbeElapsedMs = 0;
41
42
  lastProbeHostStallMs = 0;
43
+ /** Raw facts from the last probe, carried into the decision record (#1500). */
44
+ lastProbeEvidence;
42
45
  // Per-cwd cache of the resolved biome binary. Keying by cwd matters in
43
46
  // monorepos where different sub-packages each ship their own biome
44
47
  // installation; sharing one slot across the whole client would cause
@@ -48,7 +51,11 @@ export class BiomeClient {
48
51
  // (lives under ~/.pi-lens/tools), so it's stored separately from the
49
52
  // per-cwd cache and used as a final fallback before npx.
50
53
  autoInstalledBinaryPath = null;
51
- ensureInFlight = null;
54
+ /**
55
+ * At-most-one probe/auto-install in flight, via the shared primitive
56
+ * (#1753). One instance owns one question, so the key is a constant.
57
+ */
58
+ ensureFlight = createSingleFlight();
52
59
  log;
53
60
  constructor(verbose = false) {
54
61
  this.log = verbose
@@ -70,13 +77,14 @@ export class BiomeClient {
70
77
  const cached = this.localBinaryByCwd.get(resolveCwd);
71
78
  if (cached)
72
79
  return { cmd: cached, args: [] };
73
- if (this.autoInstalledBinaryPath) {
74
- return { cmd: this.autoInstalledBinaryPath, args: [] };
75
- }
76
- // Walk up from cwd looking for node_modules/.bin/biome.
77
- // Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
78
- // so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when
79
- // the tool is already installed but not in the project's node_modules.
80
+ // Walk up from cwd looking for node_modules/.bin/biome BEFORE trusting
81
+ // `autoInstalledBinaryPath` (#1731). That field is set once, the first
82
+ // time `ensureAvailable()` auto-installs for ANY cwd this session, and
83
+ // every later call for every OTHER cwd short-circuited on it — so a
84
+ // project that ships its own biome never won once the session's first
85
+ // managed install had already happened. Project-local-first (discipline
86
+ // B, #1721) means the project's pinned version and config resolution
87
+ // always outrank a managed copy, autoinstalled or not.
80
88
  // On Windows prefer .cmd (native batch) over the sh wrapper — 2x faster.
81
89
  const isWin = process.platform === "win32";
82
90
  const piLensBin = path.join(getGlobalPiLensDir(), "tools", "node_modules", ".bin");
@@ -84,20 +92,29 @@ export class BiomeClient {
84
92
  ? [
85
93
  path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
86
94
  path.join(resolveCwd, "node_modules", ".bin", "biome"),
87
- path.join(piLensBin, "biome.cmd"),
88
- path.join(piLensBin, "biome"),
89
95
  ]
90
- : [
91
- path.join(resolveCwd, "node_modules", ".bin", "biome"),
92
- path.join(resolveCwd, "node_modules", ".bin", "biome.cmd"),
93
- path.join(piLensBin, "biome"),
94
- ];
96
+ : [path.join(resolveCwd, "node_modules", ".bin", "biome")];
95
97
  for (const p of candidates) {
96
98
  if (fs.existsSync(p)) {
97
99
  this.localBinaryByCwd.set(resolveCwd, p);
98
100
  return { cmd: p, args: [] };
99
101
  }
100
102
  }
103
+ if (this.autoInstalledBinaryPath) {
104
+ return { cmd: this.autoInstalledBinaryPath, args: [] };
105
+ }
106
+ // Also check ~/.pi-lens/tools (where ensureTool("biome") auto-installs),
107
+ // so we avoid the ~1.5s `npx @biomejs/biome --version` fallback when the
108
+ // tool is already installed but not in the project's node_modules.
109
+ const managedCandidates = isWin
110
+ ? [path.join(piLensBin, "biome.cmd"), path.join(piLensBin, "biome")]
111
+ : [path.join(piLensBin, "biome")];
112
+ for (const p of managedCandidates) {
113
+ if (fs.existsSync(p)) {
114
+ this.localBinaryByCwd.set(resolveCwd, p);
115
+ return { cmd: p, args: [] };
116
+ }
117
+ }
101
118
  // Any package manager's global bin dir (npm/pnpm/yarn/bun) before npx —
102
119
  // catches `pnpm add -g @biomejs/biome` installs that PATH misses (#375).
103
120
  const global = await findGlobalBinary("biome");
@@ -116,10 +133,11 @@ export class BiomeClient {
116
133
  * Ensure Biome is available, auto-installing if necessary.
117
134
  * Prefer this over isAvailable() for auto-install behavior.
118
135
  *
119
- * Re-entrancy safe: concurrent first-time callers share a single
120
- * `ensureInFlight` promise so probing/auto-install isn't duplicated.
121
- * Mirrors the dedupe pattern in `SgRunner` / `KnipClient` /
122
- * `DependencyChecker`.
136
+ * Re-entrancy safe: concurrent first-time callers share one flight, so
137
+ * probing/auto-install isn't duplicated. The share and the clear-in-finally
138
+ * belong to `singleFlight` (#1753); this method owns only the latch
139
+ * short-circuit above it. #1690 exists because the hand-rolled clear here
140
+ * was never exercised by a test.
123
141
  *
124
142
  * The memo returns `null` when the last verdict was transient and its
125
143
  * cooldown expired, which re-enters the probe: "biome is not installed" is a
@@ -129,15 +147,7 @@ export class BiomeClient {
129
147
  const memo = this.availabilityLatch.read();
130
148
  if (memo !== null)
131
149
  return memo;
132
- if (this.ensureInFlight)
133
- return this.ensureInFlight;
134
- this.ensureInFlight = this.doEnsureAvailable();
135
- try {
136
- return await this.ensureInFlight;
137
- }
138
- finally {
139
- this.ensureInFlight = null;
140
- }
150
+ return this.ensureFlight.run("biome", () => this.doEnsureAvailable());
141
151
  }
142
152
  /**
143
153
  * Probe `biome --version` and classify the failure through the shared
@@ -177,7 +187,7 @@ export class BiomeClient {
177
187
  // `unclassifiedFailureOutcome: "missing"` preserves the pre-#1476
178
188
  // meaning of a plain non-zero exit (npx reporting no biome package):
179
189
  // still "missing", still installable. Only the timeout/abort arm changes.
180
- const { outcome, cause } = classifyProbeFailure(result, {
190
+ const { outcome, cause, evidence } = classifyProbeFailure(result, {
181
191
  hostStallMs,
182
192
  unclassifiedFailureOutcome: "missing",
183
193
  });
@@ -187,6 +197,7 @@ export class BiomeClient {
187
197
  this.lastProbeCause = cause;
188
198
  this.lastProbeElapsedMs = elapsedMs;
189
199
  this.lastProbeHostStallMs = hostStallMs;
200
+ this.lastProbeEvidence = evidence;
190
201
  // The record is emitted by `doEnsureAvailable`, once the latch has said how
191
202
  // long this verdict holds.
192
203
  return { outcome: failureOutcome };
@@ -224,6 +235,19 @@ export class BiomeClient {
224
235
  // session exactly as before.
225
236
  const cause = this.lastProbeCause ?? "not-found";
226
237
  const retryAfterMs = this.availabilityLatch.noteUnavailable(resolved.outcome, cause);
238
+ // `missing` IS the install-failure arm (#1500 review). `resolveManagedToolClient`
239
+ // only reaches the installer when the probe said `missing`, so every `missing`
240
+ // verdict here has already been through it — declined, suppressed, or tried
241
+ // and failed, which is what `describeInstallAttempt` separates. The earlier
242
+ // marker sat on the `non-installable` arm instead, and that arm cannot happen
243
+ // for biome at all: its `acceptInstalled` always accepts. So the one row that
244
+ // needed the fact was the one shipping without it.
245
+ let installEvidence;
246
+ if (resolved.outcome === "missing") {
247
+ const { getInstallAttempt } = await import("./installer/index.js");
248
+ installEvidence = describeInstallAttempt(getInstallAttempt("biome"));
249
+ }
250
+ const evidence = { ...this.lastProbeEvidence, ...installEvidence };
227
251
  logAvailabilityDecision({
228
252
  tool: "biome",
229
253
  verdict: "unavailable",
@@ -234,6 +258,13 @@ export class BiomeClient {
234
258
  hostStallMs: this.lastProbeHostStallMs,
235
259
  ...(retryAfterMs > 0 && { retryAfterMs }),
236
260
  budgetMs: PROBE_TIMEOUT_MS,
261
+ // Per arm: a probe-derived verdict says `probe`, while a verdict asserted
262
+ // by the install seam — or a cause that fell back because no probe ran —
263
+ // says `caller`.
264
+ classifiedBy: this.lastProbeCause === null || resolved.outcome === "non-installable"
265
+ ? "caller"
266
+ : "probe",
267
+ ...(Object.keys(evidence).length > 0 && { evidence }),
237
268
  });
238
269
  if (resolved.outcome === "transient") {
239
270
  this.log("biome availability probe timed out; will retry (not installing)");
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Turn-boundary freshness gate for cached inline blockers (#1631).
3
+ *
4
+ * An inline blocker recorded for a file F is a verdict about F *and everything F
5
+ * imports*. The existing invalidation paths all key on F alone — a later dispatch
6
+ * of the SAME path returning no blockers, the path ceasing to exist (#1245), and a
7
+ * fresh confirmed-clean verdict (#1561/#1573). When only a DEPENDENCY of F changes,
8
+ * none of those events fire, so F's stale verdict re-served at every turn end for
9
+ * the rest of the session. The live instances in #1631 are exactly that shape: the
10
+ * missing export was added to the dependency (one via a bash script, so the
11
+ * dependency was never dispatched at all — even the same-path event was unreachable
12
+ * by construction).
13
+ *
14
+ * The gate here is a READ-time freshness sweep, not the #1561 dependency-axis
15
+ * invalidation (which stays blocked on a maintainer design decision). Before a cached
16
+ * blocking finding is re-served at turn end, stat the file and its forward imports.
17
+ * The file's own import list comes from the parse layer — no reverse-dependency index
18
+ * is needed, sidestepping the tests-free-index blocker that holds #1561's remainder.
19
+ *
20
+ * Drifted entries are DEMOTED, not dropped (#1419 precedent, as applied by sibling
21
+ * issue #1622): a surviving file whose content drifted gets re-served with a
22
+ * `[stale — re-run to confirm]` marker and out of the authoritative blocker channel,
23
+ * rather than being silently deleted or re-asserted at full authority. The gate never
24
+ * re-pulls LSP verdicts on its own — a drifted entry is marked stale and left for an
25
+ * explicit re-run or the next dispatch to resolve. That keeps the sweep bounded and
26
+ * free of the document-resync hazard the issue calls out (re-querying an LSP whose
27
+ * in-memory dependency document is itself stale would regenerate the stale verdict).
28
+ *
29
+ * Scope (#1631 review F4): an inline blocker is not always LSP-only —
30
+ * `dispatcher.ts` builds it from every `semantic === "blocking"` diagnostic across
31
+ * all runners, so an eslint, biome-check, or ast-grep security-rule (hardcoded
32
+ * secret, CVE) finding can land here too. Only a language-server verdict is
33
+ * actually invalidated by an IMPORT changing; an ast-grep secret match doesn't stop
34
+ * being true because a file it imports was edited. The sweep therefore demotes an
35
+ * entry only when its recorded `sources` (`InlineBlockerRecord.sources`) are ALL
36
+ * `"lsp"` — a mixed or non-LSP-sourced blocker is left fully authoritative, the same
37
+ * fail-closed shape `retireInlineBlockerOnConfirmedClean` already uses for
38
+ * provenance it can't yet reason about.
39
+ *
40
+ * Resolution boundary (#1631 review F8): `extractForwardImportPaths` parses static
41
+ * import/require syntax via tree-sitter. It does not, and cannot, resolve a
42
+ * dependency reached only through a dynamic `import()` or `require()` behind a
43
+ * runtime condition — that edge is invisible to this gate, so a blocker whose only
44
+ * stale dependency arrives that way survives a replay. This is an honest boundary,
45
+ * not a silently-closed one.
46
+ */
47
+ import * as fs from "node:fs";
48
+ import { normalizeEphemeralMapKey } from "./path-utils.js";
49
+ import { resolveImportToFiles } from "./review-graph/import-resolvers.js";
50
+ import { getSharedTreeSitterClient, resolveTreeSitterLanguage, } from "./tree-sitter-shared.js";
51
+ import { TreeSitterSymbolExtractor } from "./tree-sitter-symbol-extractor.js";
52
+ /**
53
+ * Upper bound on the number of forward imports stat-checked for a single blocker.
54
+ * Blocking entries are rare, but one file can import many modules; the sweep must
55
+ * stay bounded so it cannot inflate a turn end.
56
+ */
57
+ const MAX_DRIFT_CHECK_IMPORTS = 128;
58
+ // Per-language extractor cache, mirroring review-graph/builder.ts and
59
+ // module-report.ts. Memoize failures too: a grammar that fails to load once is not
60
+ // re-probed for every blocker of that language within the process.
61
+ const extractorCache = new Map();
62
+ function getExtractor(languageId) {
63
+ let cached = extractorCache.get(languageId);
64
+ if (!cached) {
65
+ cached = (async () => {
66
+ const client = getSharedTreeSitterClient();
67
+ if (!client)
68
+ return null;
69
+ const extractor = new TreeSitterSymbolExtractor(languageId, client);
70
+ const ok = await extractor.init();
71
+ return ok ? extractor : null;
72
+ })().catch(() => null);
73
+ extractorCache.set(languageId, cached);
74
+ }
75
+ return cached;
76
+ }
77
+ /**
78
+ * #1631 review F6: memoized forward-import RESOLUTION (the parse step, not the
79
+ * per-dependency mtime stats — those must always be read fresh so drift is never
80
+ * missed). Reviewer measurement: ~450ms/turn at 8 blockers × 300 imports
81
+ * unmemoized, ~435ms per `mode=all` call, because both the turn-end sweep and the
82
+ * `mode=all` gate independently re-parse the SAME consumer files' import lists.
83
+ *
84
+ * Keyed on (path, own mtime) — a cache hit is safe by construction: if the file's
85
+ * mtime hasn't moved, its parsed import LIST cannot have changed (the imports
86
+ * themselves may still have drifted; that's what the mtime stats after this cache
87
+ * check are for). Scoped PER TURN, not process-lifetime (the latch screen this
88
+ * class of state must pass): `noteImportResolutionMemoTurn` clears it whenever the
89
+ * `RuntimeCoordinator`'s `turnIndex` advances, so a resolver injected for one test
90
+ * or a project whose import graph is edited mid-session is never served a result
91
+ * from a stale earlier turn. When no turn index is available (mode=all called
92
+ * outside a tracked turn, or a test with no runtime), the cache is left alone —
93
+ * still correct because of the mtime key, just not turn-bounded in that case.
94
+ */
95
+ const importResolutionMemo = new Map();
96
+ let importResolutionMemoTurnIndex;
97
+ function noteImportResolutionMemoTurn(turnIndex) {
98
+ if (turnIndex === undefined)
99
+ return;
100
+ if (importResolutionMemoTurnIndex !== turnIndex) {
101
+ importResolutionMemo.clear();
102
+ importResolutionMemoTurnIndex = turnIndex;
103
+ }
104
+ }
105
+ async function resolveForwardImportsMemoized(cwd, filePath, resolveForwardImports) {
106
+ const sig = await statSignature(filePath);
107
+ if (sig === undefined) {
108
+ // Can't key reliably (deleted/unreadable) — resolve uncached rather than
109
+ // risk serving a memo entry for content that may no longer exist.
110
+ try {
111
+ return await resolveForwardImports(cwd, filePath);
112
+ }
113
+ catch {
114
+ return [];
115
+ }
116
+ }
117
+ const cached = importResolutionMemo.get(filePath);
118
+ // mtime alone collides for writes within the same timestamp tick (~1ms);
119
+ // size disambiguates the shrink-then-restore case (#1641's measurement).
120
+ if (cached && cached.mtimeMs === sig.mtimeMs && cached.size === sig.size)
121
+ return cached.imports;
122
+ let imports;
123
+ try {
124
+ imports = await resolveForwardImports(cwd, filePath);
125
+ }
126
+ catch {
127
+ imports = [];
128
+ }
129
+ importResolutionMemo.set(filePath, { mtimeMs: sig.mtimeMs, size: sig.size, imports });
130
+ return imports;
131
+ }
132
+ /** Test-only: clear the extractor and import-resolution memos. */
133
+ export function _resetBlockerFreshnessForTests() {
134
+ extractorCache.clear();
135
+ importResolutionMemo.clear();
136
+ importResolutionMemoTurnIndex = undefined;
137
+ }
138
+ /**
139
+ * Production forward-import resolver. Parses `filePath` with its tree-sitter grammar
140
+ * and resolves every extracted import source to in-project files. Degrades to an
141
+ * empty list (never throws) when the grammar, client, or file is unavailable — the
142
+ * sweep then falls back to checking only the blocker file's own drift.
143
+ */
144
+ export async function extractForwardImportPaths(cwd, filePath) {
145
+ const languageId = resolveTreeSitterLanguage(filePath);
146
+ if (!languageId)
147
+ return [];
148
+ const client = getSharedTreeSitterClient();
149
+ if (!client)
150
+ return [];
151
+ const extractor = await getExtractor(languageId);
152
+ if (!extractor)
153
+ return [];
154
+ let content;
155
+ try {
156
+ content = fs.readFileSync(filePath, "utf-8");
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ const outcome = await client.withParsedTree(filePath, languageId, content, (tree) => extractor.extract(tree, filePath, content).imports);
162
+ if (!outcome.parsed)
163
+ return [];
164
+ const resolved = new Set();
165
+ for (const ref of outcome.value) {
166
+ for (const target of resolveImportToFiles(cwd, filePath, languageId, ref.source)) {
167
+ resolved.add(target);
168
+ }
169
+ }
170
+ return [...resolved];
171
+ }
172
+ async function statSignature(filePath) {
173
+ try {
174
+ const stat = await fs.promises.stat(filePath);
175
+ return { mtimeMs: stat.mtimeMs, size: stat.size };
176
+ }
177
+ catch {
178
+ return undefined;
179
+ }
180
+ }
181
+ async function statMtimeMs(filePath) {
182
+ try {
183
+ const stat = await fs.promises.stat(filePath);
184
+ return stat.mtimeMs;
185
+ }
186
+ catch {
187
+ return undefined;
188
+ }
189
+ }
190
+ /**
191
+ * Resolve `filePath`'s forward imports and stat each, returning the imports that
192
+ * exist together with their mtime. Shared by the inline-blocker sweep and the
193
+ * widget-store dependency reconcile so both gates weigh drift identically. A path
194
+ * that cannot be stat'ed (deleted/unreadable) is omitted — a deleted dependency is
195
+ * not a content drift this gate reports.
196
+ */
197
+ export async function collectForwardImportMtimes(cwd, filePath, resolveForwardImports = extractForwardImportPaths, turnIndex) {
198
+ noteImportResolutionMemoTurn(turnIndex);
199
+ let imports = [];
200
+ try {
201
+ imports = await resolveForwardImportsMemoized(cwd, filePath, resolveForwardImports);
202
+ }
203
+ catch {
204
+ imports = [];
205
+ }
206
+ const truncated = imports.length > MAX_DRIFT_CHECK_IMPORTS;
207
+ const out = [];
208
+ for (const dep of imports.slice(0, MAX_DRIFT_CHECK_IMPORTS)) {
209
+ const mtimeMs = await statMtimeMs(dep);
210
+ if (mtimeMs !== undefined)
211
+ out.push({ path: dep, mtimeMs });
212
+ }
213
+ return { mtimes: out, truncated };
214
+ }
215
+ /**
216
+ * Detect drift for one blocker entry. Returns the set of paths (the file itself plus
217
+ * any forward import) whose mtime is strictly newer than the verdict baseline. A
218
+ * missing/unstat-able path contributes nothing here — a DELETED blocker file is
219
+ * already dropped by `reconcileInlineBlockers`, and a deleted dependency is not a
220
+ * content drift this gate is responsible for.
221
+ *
222
+ * +50ms tolerance. The verdict baseline is `Date.now()`, but on Windows a file's
223
+ * mtime can LEAD `Date.now()` by a measurable margin — not just sub-millisecond
224
+ * rounding. Reviewer measurement across 200 writes on Windows: 184/200 mtimes read
225
+ * ahead of the immediately-following `Date.now()`, by up to ~11.4ms, the same host
226
+ * skew #1491/#1498 hit for install-probe timestamps. A +1ms tolerance (the
227
+ * convention borrowed from `reconcileStaleWidgetFiles` /
228
+ * `reconcileProjectDiagnosticsSnapshot`, both same-process single-writer paths
229
+ * without this skew) demoted a blocker recorded immediately after its own file
230
+ * write with zero real drift — 42 false demotions in 50 runs, and two of this
231
+ * gate's own tests went host-dependent (red 3/6 runs on Windows, green 6/6 at
232
+ * +50ms). +50ms comfortably clears the measured skew while staying far below the
233
+ * gap between genuinely distinct edits.
234
+ */
235
+ export const MTIME_DRIFT_TOLERANCE_MS = 50;
236
+ async function detectDrift(cwd, filePath, recordedAtMs, resolveForwardImports, turnIndex) {
237
+ const drifted = [];
238
+ const ownMtimeMs = await statMtimeMs(filePath);
239
+ if (ownMtimeMs !== undefined && ownMtimeMs > recordedAtMs + MTIME_DRIFT_TOLERANCE_MS) {
240
+ drifted.push(filePath);
241
+ }
242
+ const { mtimes, truncated } = await collectForwardImportMtimes(cwd, filePath, resolveForwardImports, turnIndex);
243
+ for (const { path, mtimeMs } of mtimes) {
244
+ if (mtimeMs > recordedAtMs + MTIME_DRIFT_TOLERANCE_MS)
245
+ drifted.push(path);
246
+ }
247
+ return { drifted, truncated };
248
+ }
249
+ /**
250
+ * Whether a population entry is eligible for the drift check at all — the same
251
+ * three gates the main sweep loop applies (not already stale, has a timestamp
252
+ * baseline, all-`"lsp"` sources). Factored out so the #1790 review F5 dedup
253
+ * decision below (chain vs. separate row) asks the IDENTICAL question the main
254
+ * loop will ask, rather than a second hand-written approximation of it that
255
+ * could drift from the real gates.
256
+ */
257
+ function isEligibleForDriftCheck(entry) {
258
+ if (entry.stale)
259
+ return false;
260
+ if (entry.recordedAtMs === undefined)
261
+ return false;
262
+ return (entry.sources !== undefined &&
263
+ entry.sources.length > 0 &&
264
+ entry.sources.every((source) => source === "lsp"));
265
+ }
266
+ /**
267
+ * Freshness sweep over every blocking row the widget currently serves — the cached
268
+ * inline blockers (`RuntimeCoordinator`) AND, since #1790, any widget-store row
269
+ * reached only through a cache-served replay (`options.additionalEntries`, injected
270
+ * by the call site to avoid an import cycle with `widget-state.ts`). Called at turn
271
+ * end before the blockers are re-served. Entries whose own file or forward imports
272
+ * drifted since the verdict are demoted via `demote`; the turn-end renderer then
273
+ * serves them out of the advisory channel with a `[stale — re-run to confirm]`
274
+ * marker instead of as an authoritative blocker.
275
+ *
276
+ * #1790: a file present in BOTH stores — the common case, since a live dispatch
277
+ * writes an inline blocker (`runtime-tool-result.ts`) AND a widget-store record
278
+ * (`pipeline.ts`) for the SAME verdict — is drift-checked ONCE, via its
279
+ * inline-blocker entry, so the sweep's one drift check never runs twice over the
280
+ * same file WHEN that inline entry is itself eligible for the check. But
281
+ * `markInlineBlockerStale` only ever touches `RuntimeCoordinator`'s map, never
282
+ * the widget store; on drift for a duplicated path, the widget demote is
283
+ * CHAINED onto the inline entry's `demote` (both stores write) rather than the
284
+ * widget row being silently dropped — the reviewer's F1 probe caught an earlier
285
+ * revision that discarded it: `revalidated:1` while the widget's own
286
+ * `isBlocking` for the file still read true, the exact ghost #1790 exists to
287
+ * kill.
288
+ *
289
+ * #1790 review F5: chaining is conditional on the inline entry actually being
290
+ * ELIGIBLE for the drift check (`isEligibleForDriftCheck` — not already stale,
291
+ * timestamped, all-LSP sources). An ineligible inline entry never reaches
292
+ * `demote()` in the loop below, so chaining onto one is ALSO a silent drop —
293
+ * just one store removed from F1's. The widget row's eligibility belongs to the
294
+ * WIDGET STORE, not to whatever inline entry happens to share its file path; an
295
+ * ineligible duplicate therefore gets its own separate population row instead
296
+ * of being chained, so a file can legitimately count twice (once per store) when
297
+ * the two stores disagree on eligibility.
298
+ *
299
+ * Never throws: any internal failure leaves the entry untouched (existing re-serve
300
+ * behavior) rather than failing the turn end.
301
+ */
302
+ export async function sweepInlineBlockerFreshness(runtime, cwd, options) {
303
+ const counts = {
304
+ total: 0,
305
+ kept: 0,
306
+ revalidated: 0,
307
+ alreadyStale: 0,
308
+ truncatedImports: 0,
309
+ };
310
+ const resolveForwardImports = options?.resolveForwardImports ?? extractForwardImportPaths;
311
+ let turnIndex;
312
+ try {
313
+ turnIndex = runtime.turnIndex;
314
+ }
315
+ catch {
316
+ turnIndex = undefined;
317
+ }
318
+ let inlineEntries;
319
+ try {
320
+ inlineEntries = runtime.getInlineBlockersSnapshot();
321
+ }
322
+ catch {
323
+ return counts;
324
+ }
325
+ const population = inlineEntries.map((entry) => ({
326
+ filePath: entry.filePath,
327
+ stale: entry.stale ?? false,
328
+ recordedAtMs: entry.recordedAtMs,
329
+ sources: entry.sources,
330
+ demote: () => runtime.markInlineBlockerStale(entry.filePath, "dependency-drift"),
331
+ }));
332
+ // #1790 review F2: dedup key is `normalizeEphemeralMapKey`, not
333
+ // `normalizeMapKey` — the latter realpaths a live file and walks up the
334
+ // directory tree for a missing one (measured ~313µs per deleted path, exactly
335
+ // the population this sweep processes) on a turn-end hot path. The widget
336
+ // store this population is reconciled against keys on
337
+ // `normalizeEphemeralMapKey` too (see `widget-state.ts`'s module doc on why
338
+ // `normalizeMapKey` is wrong for this in-process, single-walk kind of key),
339
+ // so this also keeps the dedup consistent with the store it is deduping
340
+ // against, not just cheaper.
341
+ const inlineByKey = new Map();
342
+ for (const entry of population) {
343
+ inlineByKey.set(normalizeEphemeralMapKey(entry.filePath), entry);
344
+ }
345
+ for (const extra of options?.additionalEntries ?? []) {
346
+ const key = normalizeEphemeralMapKey(extra.filePath);
347
+ const inlineEntry = inlineByKey.get(key);
348
+ // #1790 review F5: chaining onto an INELIGIBLE inline entry is a silent
349
+ // drop, not a merge. The main loop below short-circuits BEFORE ever
350
+ // calling `demote()` for an already-stale entry (the one-way
351
+ // dependency-drift latch — a forever-ghost once it fires once), an
352
+ // unstamped legacy record, or a non-LSP/mixed-sources entry (e.g. an
353
+ // unrelated ast-grep finding on the same file). Any of those swallows a
354
+ // chained widget demote even though the widget row is pure-LSP by
355
+ // construction (`getWidgetBlockingFilesForSweep` only emits LSP-sourced
356
+ // rows) and carries its OWN baseline. Eligibility belongs to the STORE
357
+ // the row came from, not the file path two stores happen to share — so
358
+ // only chain when the inline entry would itself reach `demote()`;
359
+ // otherwise give the widget row its own population entry so its own
360
+ // gates and its own drift check decide its own fate.
361
+ if (inlineEntry && isEligibleForDriftCheck(inlineEntry)) {
362
+ // #1790 review F1: a duplicated path is counted and drift-checked ONCE
363
+ // (via the inline entry above), but BOTH stores must record the
364
+ // verdict — `markInlineBlockerStale` only ever touches
365
+ // `RuntimeCoordinator`'s map, so without this chain the widget store's
366
+ // own diagnostic for this file stays fully blocking (`isBlocking` true)
367
+ // even after the inline entry demotes. Chain, don't replace: either
368
+ // store's own untouched failure path must not suppress the other's
369
+ // write.
370
+ const demoteInline = inlineEntry.demote;
371
+ const demoteWidget = extra.demote;
372
+ inlineEntry.demote = () => {
373
+ const inlineChanged = demoteInline();
374
+ const widgetChanged = demoteWidget();
375
+ return inlineChanged || widgetChanged;
376
+ };
377
+ continue;
378
+ }
379
+ population.push({
380
+ filePath: extra.filePath,
381
+ stale: false,
382
+ recordedAtMs: extra.recordedAtMs,
383
+ sources: ["lsp"],
384
+ demote: extra.demote,
385
+ });
386
+ }
387
+ counts.total = population.length;
388
+ for (const entry of population) {
389
+ try {
390
+ if (entry.stale) {
391
+ counts.alreadyStale += 1;
392
+ continue;
393
+ }
394
+ // No timestamp baseline (legacy/unstamped record) — we cannot order disk
395
+ // mtimes against the verdict, so leave it untouched (fail toward the
396
+ // existing behavior rather than fabricating a drift signal).
397
+ if (entry.recordedAtMs === undefined) {
398
+ counts.kept += 1;
399
+ continue;
400
+ }
401
+ // #1631 review F4: fail-closed on non-LSP (or unknown) provenance. Import
402
+ // drift only invalidates a language-server verdict; a mixed or non-`"lsp"`
403
+ // `sources` list is kept at full authority rather than demoted.
404
+ const isLspSourced = entry.sources !== undefined &&
405
+ entry.sources.length > 0 &&
406
+ entry.sources.every((source) => source === "lsp");
407
+ if (!isLspSourced) {
408
+ counts.kept += 1;
409
+ continue;
410
+ }
411
+ const { drifted, truncated } = await detectDrift(cwd, entry.filePath, entry.recordedAtMs, resolveForwardImports, turnIndex);
412
+ if (truncated)
413
+ counts.truncatedImports += 1;
414
+ if (drifted.length > 0) {
415
+ entry.demote();
416
+ counts.revalidated += 1;
417
+ }
418
+ else {
419
+ counts.kept += 1;
420
+ }
421
+ }
422
+ catch {
423
+ // Per-entry failure: keep the entry as-is.
424
+ counts.kept += 1;
425
+ }
426
+ }
427
+ return counts;
428
+ }