@herbertgao/pi-extensions 2026.8.5 → 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 (389) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
  19. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  20. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  21. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  24. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
  25. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  26. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  27. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  28. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  29. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  30. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  32. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  33. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
  34. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  35. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  51. package/node_modules/@narumitw/pi-btw/README.md +119 -103
  52. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  53. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  54. package/node_modules/@narumitw/pi-btw/package.json +13 -9
  55. package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
  56. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  57. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  58. package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
  59. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  60. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  61. package/node_modules/pi-lens/CHANGELOG.md +1004 -0
  62. package/node_modules/pi-lens/README.md +11 -3
  63. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  64. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  65. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
  66. package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
  67. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  68. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  69. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  70. package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
  71. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  72. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  73. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  74. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  75. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  76. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  77. package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
  78. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  79. package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
  80. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  81. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  82. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  83. package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
  84. package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
  85. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  86. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  87. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  88. package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
  89. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
  90. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  91. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
  93. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
  94. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  95. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  129. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  130. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  131. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  132. package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
  133. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  134. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  135. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  136. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  137. package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
  138. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  139. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  140. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  141. package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
  142. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  143. package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
  144. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
  145. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  146. package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
  147. package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
  148. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  149. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  150. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  151. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
  154. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  155. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
  157. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  158. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  159. package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
  160. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  161. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
  162. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  163. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
  164. package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
  165. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
  166. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  167. package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
  168. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  169. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  170. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  171. package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
  172. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  173. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
  175. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  176. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
  177. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  178. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  179. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  180. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  181. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
  182. package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
  183. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  184. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  185. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  186. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  187. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  188. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  189. package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
  190. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  191. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  195. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  198. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  200. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  201. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  204. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  205. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  206. package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  217. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  218. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  219. package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
  220. package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
  221. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  222. package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
  223. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  224. package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
  225. package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
  226. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  227. package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
  228. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  229. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  230. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  233. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  234. package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
  235. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  236. package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
  237. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  238. package/node_modules/pi-lens/dist/index.js +64677 -49484
  239. package/node_modules/pi-lens/dist/mcp/server.js +8 -2
  240. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  241. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  242. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  243. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
  244. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
  245. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  246. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  247. package/node_modules/pi-lens/docs/agent-guide.md +44 -14
  248. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
  249. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  250. package/node_modules/pi-lens/docs/features.md +33 -6
  251. package/node_modules/pi-lens/docs/globalconfig.md +14 -0
  252. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  253. package/node_modules/pi-lens/docs/mcp.md +2 -2
  254. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  255. package/node_modules/pi-lens/docs/settings.md +25 -1
  256. package/node_modules/pi-lens/docs/usage.md +24 -5
  257. package/node_modules/pi-lens/docs/word-index.md +35 -0
  258. package/node_modules/pi-lens/package.json +10 -6
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  319. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  320. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  321. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  322. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  323. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  324. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
  325. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  326. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  327. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
  328. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  329. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
  330. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
  331. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  332. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  333. package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
  334. package/node_modules/pi-mcp-adapter/README.md +51 -6
  335. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  336. package/node_modules/pi-mcp-adapter/cli.js +188 -5
  337. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  338. package/node_modules/pi-mcp-adapter/config.ts +28 -10
  339. package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
  340. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  341. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  342. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  343. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  344. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  345. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  346. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  347. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  348. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  349. package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
  350. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  351. package/node_modules/pi-mcp-adapter/package.json +4 -1
  352. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  353. package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
  354. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  355. package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
  356. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  357. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  358. package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
  359. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  360. package/node_modules/pi-mcp-adapter/types.ts +95 -10
  361. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  362. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  363. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  364. package/node_modules/pi-web-access/README.md +38 -14
  365. package/node_modules/pi-web-access/anysearch.ts +4 -2
  366. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  367. package/node_modules/pi-web-access/brave.ts +16 -4
  368. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  369. package/node_modules/pi-web-access/curator-page.ts +8 -3
  370. package/node_modules/pi-web-access/curator-server.ts +5 -1
  371. package/node_modules/pi-web-access/exa.ts +18 -7
  372. package/node_modules/pi-web-access/extract.ts +134 -37
  373. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  374. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  375. package/node_modules/pi-web-access/gemini-search.ts +33 -7
  376. package/node_modules/pi-web-access/index.ts +160 -104
  377. package/node_modules/pi-web-access/package.json +2 -2
  378. package/node_modules/pi-web-access/page-query.ts +4 -2
  379. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  380. package/node_modules/pi-web-access/parallel.ts +2 -2
  381. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  382. package/node_modules/pi-web-access/serper.ts +211 -0
  383. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  384. package/node_modules/pi-web-access/summary-review.ts +51 -23
  385. package/node_modules/pi-web-access/tavily.ts +16 -4
  386. package/node_modules/pi-web-access/utils.ts +87 -0
  387. package/node_modules/pi-web-access/valyu.ts +199 -0
  388. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  389. package/package.json +13 -13
@@ -38,8 +38,15 @@ export function withDeadline(promise, options) {
38
38
  const onReject = options.onReject ?? "propagate";
39
39
  const ms = options.ms ??
40
40
  (options.deadlineAt !== undefined ? options.deadlineAt - Date.now() : 0);
41
- // Past deadline / non-positive budget: settle immediately, no timer.
41
+ // Past deadline / non-positive budget: settle immediately, no timer. This
42
+ // branch returns before the race below ever runs, so — same reason as the
43
+ // loser-leg catch a few lines down — `promise` needs its own no-op catch
44
+ // here too: without it, a `promise` that eventually rejects (the caller
45
+ // already invoked it; this function only decides how long to wait on it)
46
+ // surfaces as an unhandled rejection instead of being silently superseded
47
+ // by the immediate timeout/undefined settlement.
42
48
  if (ms <= 0) {
49
+ promise.catch(() => { });
43
50
  return onTimeout === "undefined"
44
51
  ? Promise.resolve(undefined)
45
52
  : Promise.reject(new Error(`Timeout after ${Math.max(0, ms)}ms`));
@@ -140,6 +140,11 @@ export function flushDebugHandlesLog() {
140
140
  }
141
141
  function safeGetActiveResourcesInfo() {
142
142
  try {
143
+ // SAFETY: `process.getActiveResourcesInfo` is a real Node API (>=17.3)
144
+ // that @types/node does not declare on `Process`. The cast only widens
145
+ // the type to say the method MIGHT be there; the `typeof fn ===
146
+ // "function"` guard on the next line is what actually decides, so a
147
+ // runtime without it returns [] rather than throwing.
143
148
  const fn = process.getActiveResourcesInfo;
144
149
  return typeof fn === "function" ? fn.call(process) : [];
145
150
  }
@@ -1,11 +1,27 @@
1
1
  /** Bounded, process-local telemetry for behavior degraded during one session. */
2
2
  import { logExtension } from "./extension-log.js";
3
+ import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
4
+ import { logLatency } from "./latency-logger.js";
5
+ // Re-exported so existing importers keep one name for the ledger's bound.
6
+ export { LEDGER_FIELD_MAX, truncateForLedger };
3
7
  const ENTRIES_PER_KIND = 20;
4
8
  const MAX_DISTINCT_KINDS = 32;
5
9
  const OVERFLOW_KIND = "other";
6
10
  const groups = new Map();
7
11
  const onceKeys = new Set();
8
12
  const tallies = new Map();
13
+ // Monotonic session-boundary counter (#1536 review F5): callers that keep
14
+ // their OWN once-per-session latch outside the ledger (a per-instance Set
15
+ // the ledger itself doesn't own) can compare this lazily at use time and
16
+ // clear their latch on a mismatch — the same clear-on-transition shape as
17
+ // project-trust.ts's trustGeneration, but keyed to the ledger's own reset
18
+ // (resetDegradationLedger, wired into handleSessionStart) rather than a
19
+ // trust change.
20
+ let ledgerGeneration = 0;
21
+ /** Current session generation. Bump on every `resetDegradationLedger()`. */
22
+ export function getDegradationLedgerGeneration() {
23
+ return ledgerGeneration;
24
+ }
9
25
  export function recordDegradation(record) {
10
26
  try {
11
27
  const kind = boundedKind(record.kind);
@@ -16,6 +32,7 @@ export function recordDegradation(record) {
16
32
  group = { count: 0, entries: [] };
17
33
  groups.set(kind, group);
18
34
  }
35
+ const admitted = group.entries.length < ENTRIES_PER_KIND;
19
36
  group.count += 1;
20
37
  // Bounded at RECORD time (#1366 review): reasons carry arbitrary error
21
38
  // text; a 10KB message must never become a 10KB health line or a 10KB
@@ -23,10 +40,12 @@ export function recordDegradation(record) {
23
40
  group.entries.push({ subject, reason });
24
41
  if (group.entries.length > ENTRIES_PER_KIND)
25
42
  group.entries.shift();
43
+ return admitted;
26
44
  }
27
45
  catch (error) {
28
46
  debugLedgerFailure("record", error);
29
47
  // Telemetry must never break the observed path.
48
+ return false;
30
49
  }
31
50
  }
32
51
  /** Record at most once per kind/subject during the current session. */
@@ -38,7 +57,9 @@ export function recordDegradationOnce(record) {
38
57
  if (onceKeys.has(key))
39
58
  return;
40
59
  onceKeys.add(key);
41
- recordDegradation({ kind, subject, reason: record.reason });
60
+ if (recordDegradation({ kind, subject, reason: record.reason })) {
61
+ logDurableDegradation(kind, subject, 1);
62
+ }
42
63
  }
43
64
  catch (error) {
44
65
  debugLedgerFailure("record-once", error);
@@ -48,6 +69,14 @@ export function recordDegradationOnce(record) {
48
69
  /**
49
70
  * Count a repeated degradation while retaining one latest-reason entry per
50
71
  * kind/subject. The group count remains the exact event total.
72
+ *
73
+ * Returns `true` when this call is the FIRST occurrence recorded for this
74
+ * kind/subject pair (the ledger is the single source of truth for that
75
+ * tally already — via `tallies` — so callers that need a once-per-subject
76
+ * "rising edge" signal, e.g. to gate a verbose one-time log line before
77
+ * falling back to the bounded count, read it off this return value instead
78
+ * of hand-rolling their own parallel `Set`/latch). #1716 reuses this same
79
+ * signal to gate `navRequest`'s detailed timeout/late-answer log writes.
51
80
  */
52
81
  export function incrementDegradationCount(record) {
53
82
  try {
@@ -62,24 +91,49 @@ export function incrementDegradationCount(record) {
62
91
  group = { count: 0, entries: [] };
63
92
  groups.set(kind, group);
64
93
  }
65
- group.count += 1;
66
- const entry = { subject, reason: truncateForLedger(`${reason} (count: ${count})`) };
67
94
  const existing = group.entries.findIndex((candidate) => candidate.subject === subject);
95
+ const admitted = existing >= 0 || group.entries.length < ENTRIES_PER_KIND;
96
+ group.count += 1;
97
+ // #1816: append the count AFTER truncation, never before. `reason` is
98
+ // already bounded above, so re-truncating the concatenation pushed the
99
+ // suffix past LEDGER_FIELD_MAX and silently ate it — a 200-char reason
100
+ // lost the one field that says how often the degradation fired.
101
+ const entry = { subject, reason: `${reason} (count: ${count})` };
68
102
  if (existing >= 0)
69
103
  group.entries.splice(existing, 1);
70
104
  group.entries.push(entry);
71
105
  if (group.entries.length > ENTRIES_PER_KIND)
72
106
  group.entries.shift();
107
+ // Durable rows use the summary's admission and emit the first event and
108
+ // power-of-two milestones only, keeping the sink bounded.
109
+ if (admitted && isPowerOfTwo(count)) {
110
+ logDurableDegradation(kind, subject, count);
111
+ }
112
+ return count === 1;
73
113
  }
74
114
  catch (error) {
75
115
  debugLedgerFailure("increment", error);
76
116
  // Telemetry must never break the observed path.
117
+ return false;
77
118
  }
78
119
  }
79
- /** Detached snapshot, grouped in first-seen kind order. */
80
- const LEDGER_FIELD_MAX = 200;
81
- function normalizeForLedger(value) {
82
- return String(value ?? "unknown");
120
+ /**
121
+ * Persist the accepted ledger mutation through the existing rotated NDJSON
122
+ * latency stream. `logLatency` owns the timestamp, PID, serialization, secret
123
+ * redaction, and write queue. The subject and kind were already bounded by the
124
+ * ledger before reaching this seam.
125
+ */
126
+ function logDurableDegradation(kind, subject, count) {
127
+ logLatency({
128
+ type: "phase",
129
+ phase: "degradation_ledger",
130
+ filePath: subject,
131
+ durationMs: 0,
132
+ metadata: { kind, subject, count, ledgerGeneration },
133
+ });
134
+ }
135
+ function isPowerOfTwo(value) {
136
+ return value > 0 && (value & (value - 1)) === 0;
83
137
  }
84
138
  function boundedKind(value) {
85
139
  const kind = truncateForLedger(value);
@@ -88,12 +142,6 @@ function boundedKind(value) {
88
142
  // Keep one slot available for all kinds beyond the cardinality bound.
89
143
  return groups.size < MAX_DISTINCT_KINDS - 1 ? kind : OVERFLOW_KIND;
90
144
  }
91
- function truncateForLedger(value) {
92
- const text = normalizeForLedger(value);
93
- return text.length > LEDGER_FIELD_MAX
94
- ? `${text.slice(0, LEDGER_FIELD_MAX)}…`
95
- : text;
96
- }
97
145
  export function getDegradationSummary() {
98
146
  return [...groups.entries()].map(([kind, group]) => ({
99
147
  kind,
@@ -148,6 +196,7 @@ export function resetDegradationLedger() {
148
196
  groups.clear();
149
197
  onceKeys.clear();
150
198
  tallies.clear();
199
+ ledgerGeneration++;
151
200
  }
152
201
  export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
153
202
  export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
@@ -15,6 +15,7 @@ import { findNodeToolBinary } from "./package-manager.js";
15
15
  import { isFullyQualified } from "./path-utils.js";
16
16
  import { safeSpawnAsync } from "./safe-spawn.js";
17
17
  import { createAvailabilityChecker, discoverManagedTool, getManagedToolEnvironment, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
18
+ import { createAvailabilityLatch, } from "./dispatch/runners/utils/availability-policy.js";
18
19
  /**
19
20
  * Build madge's argv for a circular-dependency scan.
20
21
  *
@@ -135,8 +136,15 @@ async function resolvedCommandIsStale(resolved, spawnableCache) {
135
136
  spawnableCache.set(resolved.cmd, spawnable);
136
137
  return !spawnable;
137
138
  }
138
- /** Run `mapper` over `items` with at most `concurrency` in flight at once. */
139
- async function mapWithConcurrency(items, concurrency, mapper) {
139
+ /**
140
+ * Run `mapper` over `items` with at most `concurrency` in flight at once.
141
+ * Exported (#1810 review F6) so `clients/dispatch/runners/biome-check.ts`'s
142
+ * `resolveBiomeFixKinds` can reuse this exact worker-pool shape for its
143
+ * `biome explain` fan-out instead of carrying a second copy — this file
144
+ * predates that need but is otherwise unrelated to it; a shared home for
145
+ * this one helper wasn't worth a whole new module for two call sites.
146
+ */
147
+ export async function mapWithConcurrency(items, concurrency, mapper) {
140
148
  if (items.length === 0)
141
149
  return;
142
150
  let nextIndex = 0;
@@ -158,7 +166,8 @@ export class DependencyChecker {
158
166
  environment: (cwd) => getManagedToolEnvironment("madge", cwd),
159
167
  unclassifiedFailureOutcome: "missing",
160
168
  });
161
- available = null;
169
+ /** Transient-aware availability memo — see `ensureAvailable` (#1467). */
170
+ availabilityLatch = createAvailabilityLatch();
162
171
  ensureInFlight = null;
163
172
  checkInFlight = new Map();
164
173
  scanInFlight = new Map();
@@ -320,12 +329,17 @@ export class DependencyChecker {
320
329
  this.circularFiles = circularFiles;
321
330
  }
322
331
  /**
323
- * Check if madge is available, auto-install if not
332
+ * Check if madge is available, auto-install if not.
333
+ *
334
+ * Shares knip's latch policy (#1467): only a durable verdict is memoized, so
335
+ * a probe that timed out — madge's `--version` is ~0.7 s today, but a host
336
+ * event-loop stall can expire any budget — does not disable madge for the
337
+ * life of the process.
324
338
  */
325
339
  async ensureAvailable(cwd = process.cwd()) {
326
- // Fast path: already checked
327
- if (this.available !== null)
328
- return this.available;
340
+ const memo = this.availabilityLatch.read();
341
+ if (memo !== null)
342
+ return memo;
329
343
  if (this.ensureInFlight)
330
344
  return this.ensureInFlight;
331
345
  this.ensureInFlight = this.doEnsureAvailable(cwd);
@@ -338,8 +352,28 @@ export class DependencyChecker {
338
352
  }
339
353
  async doEnsureAvailable(cwd) {
340
354
  const resolved = await resolveAvailableOrInstall(this.madgeAvailability, "madge", cwd);
341
- this.available = resolved !== null;
342
- return this.available;
355
+ if (resolved !== null) {
356
+ this.availabilityLatch.noteAvailable();
357
+ return true;
358
+ }
359
+ const verdict = this.madgeAvailability.getVerdict(cwd);
360
+ this.availabilityLatch.noteUnavailable(verdict.outcome ?? "missing", verdict.cause ?? "not-found");
361
+ return false;
362
+ }
363
+ /**
364
+ * #1623: public availability verdict for callers outside the dispatch
365
+ * graph (mode=full's fresh-fetch) that need to say WHY `ensureAvailable()`
366
+ * most recently returned false, using the SAME outcome/cause this class
367
+ * already latched above — not a re-guessed "madge binary unavailable"
368
+ * that can't tell a transient retry-cooldown probe from a durable
369
+ * absence.
370
+ */
371
+ getAvailabilityVerdict() {
372
+ return {
373
+ outcome: this.availabilityLatch.getOutcome(),
374
+ cause: this.availabilityLatch.getCause(),
375
+ retryAtMs: this.availabilityLatch.getRetryAtMs(),
376
+ };
343
377
  }
344
378
  /**
345
379
  * Check if a file is part of a circular dependency (from cache)
@@ -53,26 +53,40 @@
53
53
  * assumption proves wrong in practice, a surrounding-window hash can be
54
54
  * layered on later without changing the store shape.
55
55
  */
56
- import { createHash } from "node:crypto";
57
56
  import * as fs from "node:fs";
58
57
  import * as path from "node:path";
59
58
  import { commitDurableStore } from "./durable-store.js";
60
59
  import { logDispositionEvent } from "./disposition-logger.js";
61
60
  import { publishDisposition } from "./disposition-publish.js";
62
61
  import { getProjectDataDir } from "./file-utils.js";
62
+ import { normalizeMessage as sharedNormalizeMessage, relativeFile as sharedRelativeFile, stableFindingId, } from "./finding-identity.js";
63
63
  import { normalizeMapKey } from "./path-utils.js";
64
64
  import { lineContentHash } from "./read-guard.js";
65
65
  // "defer" is session-ephemeral by design (#690) — held only in memory so it
66
66
  // resurfaces for free on the next process run, with no expiry/pruning logic
67
67
  // needed. Stores WEAK anchors (see module doc) so a deferred finding stays
68
68
  // hidden all session even if the flagged line itself is edited.
69
+ //
70
+ // Review-round F3 (#1625): this Set is process-GLOBAL, but a weak anchor
71
+ // itself only encodes a RELATIVE path (`relativeFile` derives it from
72
+ // `path.relative(cwd, filePath)`, never the cwd's own identity) plus
73
+ // tool/rule/normalizedMessage — two unrelated projects sharing the same
74
+ // relative path/tool/rule/message (e.g. "src/auth.ts" flagged by the same
75
+ // eslint rule in project A and project B) collapse onto the IDENTICAL weak
76
+ // anchor string. A defer in project A therefore silently suppressed the same
77
+ // finding in project B for the rest of the process's life. Every read/write
78
+ // below goes through `deferredKey`, which folds the resolved cwd into the Set
79
+ // key, so the two projects can no longer collide.
69
80
  const deferredThisSession = new Set();
81
+ function deferredKey(cwd, anchor) {
82
+ return `${normalizeMapKey(cwd)}::${anchor}`;
83
+ }
70
84
  /** Exported (#802) so lens-diagnostic-mark's cross-check against live widget
71
85
  * diagnostics matches a message the same way anchor derivation does — a
72
- * second, slightly different normalizer would make a real match invisible. */
73
- export function normalizeMessage(message) {
74
- return message.replace(/\s+/g, " ").trim().toLowerCase();
75
- }
86
+ * second, slightly different normalizer would make a real match invisible.
87
+ * Re-exports the shared `finding-identity.js` normalizer (#1816) so this
88
+ * module keeps its existing public surface. */
89
+ export const normalizeMessage = sharedNormalizeMessage;
76
90
  // Anchor derivation chokepoint (#1024, #210 class): the `dd:`/`ddw:` id builders
77
91
  // (computeStrictAnchor/computeWeakAnchor) both derive their path component here,
78
92
  // so a mark and its later lookup diverge whenever the two callers pass different
@@ -91,34 +105,35 @@ export function normalizeMessage(message) {
91
105
  // widget hot path, and the read side already pays exactly this cost. Semantics
92
106
  // are unchanged: the `..`-escape fallback still returns the canonical filePath,
93
107
  // only now in the same canonical form the non-escape branch uses.
94
- function relativeFile(filePath, cwd) {
95
- const canonicalCwd = normalizeMapKey(cwd);
96
- const canonicalFile = normalizeMapKey(filePath);
97
- const rel = path.relative(canonicalCwd, canonicalFile).replace(/\\/g, "/");
98
- return rel && !rel.startsWith("..") ? rel : canonicalFile;
99
- }
108
+ //
109
+ // #1816: this used to be a local copy; it is now `finding-identity.js`'s
110
+ // `relativeFile` (identical body — canonicalize both inputs through
111
+ // `normalizeMapKey` before relativizing), re-exported so the rest of this
112
+ // file, and the module doc/comments above referencing `relativeFile`, keep
113
+ // working unchanged.
114
+ const relativeFile = sharedRelativeFile;
100
115
  /** Site-specific anchor — see module doc. Used only for false-positive. */
101
116
  export function computeStrictAnchor(args) {
102
117
  const lines = args.content?.split(/\r?\n/);
103
118
  const lineText = args.line !== undefined && lines ? (lines[args.line - 1] ?? "") : "";
104
- const parts = [
105
- relativeFile(args.filePath, args.cwd),
106
- args.tool ?? "",
107
- args.rule ?? "",
108
- normalizeMessage(args.message),
109
- lineContentHash(lineText),
110
- ];
111
- return `dd:${createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 12)}`;
119
+ return stableFindingId("dd:", {
120
+ cwd: args.cwd,
121
+ filePath: args.filePath,
122
+ parts: [
123
+ args.tool ?? "",
124
+ args.rule ?? "",
125
+ normalizeMessage(args.message),
126
+ lineContentHash(lineText),
127
+ ],
128
+ });
112
129
  }
113
130
  /** Intent-level anchor — see module doc. Used for defer/flagged/suppress. */
114
131
  export function computeWeakAnchor(args) {
115
- const parts = [
116
- relativeFile(args.filePath, args.cwd),
117
- args.tool ?? "",
118
- args.rule ?? "",
119
- normalizeMessage(args.message),
120
- ];
121
- return `ddw:${createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 12)}`;
132
+ return stableFindingId("ddw:", {
133
+ cwd: args.cwd,
134
+ filePath: args.filePath,
135
+ parts: [args.tool ?? "", args.rule ?? "", normalizeMessage(args.message)],
136
+ });
122
137
  }
123
138
  /** Both anchors a stored/filtered diagnostic would compute — the one shared
124
139
  * derivation both the dispatch-pipeline filter and lens-diagnostics' flagged
@@ -156,6 +171,15 @@ export function _setBeforeDispositionCacheRefreshForTests(hook) {
156
171
  export function _setDispositionStatForTests(statSync) {
157
172
  dispositionStatSync = statSync ?? fs.statSync;
158
173
  }
174
+ // Test seam for `applyDispositionsMultiFile`'s per-group content read (#1625
175
+ // F2) — Vitest can't `vi.spyOn` a bare ESM `node:fs` named export directly
176
+ // ("Module namespace is not configurable in ESM"), so the F2 regression test
177
+ // (proving the empty-store hoist means zero reads, not just fast ones)
178
+ // swaps this indirection instead, mirroring `dispositionStatSync` above.
179
+ let multiFileReadFileSync = fs.readFileSync;
180
+ export function _setMultiFileReadForTests(readFileSync) {
181
+ multiFileReadFileSync = readFileSync ?? fs.readFileSync;
182
+ }
159
183
  function readState(cwd) {
160
184
  const p = statePath(cwd);
161
185
  let stat;
@@ -278,7 +302,7 @@ export function _resetStateCacheForTests() {
278
302
  * Neither can throw into the mark path; both are already internally
279
303
  * fail-safe, but the try/catch keeps a future regression in either from
280
304
  * breaking a mark. */
281
- function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing) {
305
+ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity) {
282
306
  try {
283
307
  logDispositionEvent({
284
308
  event: "mark",
@@ -290,6 +314,8 @@ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing) {
290
314
  reason,
291
315
  anchor,
292
316
  previousDisposition: existing?.disposition,
317
+ model: identity?.model || undefined,
318
+ provider: identity?.provider || undefined,
293
319
  });
294
320
  publishDisposition({
295
321
  cwd,
@@ -317,7 +343,7 @@ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing) {
317
343
  * hang off it, so the agent tool and any future UI caller are covered without
318
344
  * per-caller wiring.
319
345
  */
320
- export function markDisposition(cwd, target, disposition, reason) {
346
+ export function markDisposition(cwd, target, disposition, reason, identity) {
321
347
  const anchor = disposition === "false-positive"
322
348
  ? computeStrictAnchor(target)
323
349
  : computeWeakAnchor(target);
@@ -326,8 +352,8 @@ export function markDisposition(cwd, target, disposition, reason) {
326
352
  // mark) — the log should record what this mark shadowed either way.
327
353
  const existing = readState(cwd).dispositions?.[anchor];
328
354
  if (disposition === "defer") {
329
- deferredThisSession.add(anchor);
330
- emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing);
355
+ deferredThisSession.add(deferredKey(cwd, anchor));
356
+ emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
331
357
  return anchor;
332
358
  }
333
359
  const now = new Date().toISOString();
@@ -344,14 +370,18 @@ export function markDisposition(cwd, target, disposition, reason) {
344
370
  lineText,
345
371
  };
346
372
  commitDisposition(cwd, anchor, entry);
347
- emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing);
373
+ emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
348
374
  return anchor;
349
375
  }
350
376
  export function getDisposition(cwd, anchor) {
351
377
  return readState(cwd).dispositions?.[anchor];
352
378
  }
353
- export function isDeferredThisSession(anchor) {
354
- return deferredThisSession.has(anchor);
379
+ /** #1625 F3: `anchor` alone is ambiguous across projects — a weak anchor
380
+ * encodes only a RELATIVE path plus tool/rule/message, never the project's
381
+ * own identity, so `cwd` must be supplied to disambiguate which project's
382
+ * defer is being checked. */
383
+ export function isDeferredThisSession(cwd, anchor) {
384
+ return deferredThisSession.has(deferredKey(cwd, anchor));
355
385
  }
356
386
  /** Test-only escape hatch — defer state is module-level (one process = one
357
387
  * session), so tests need to reset it between cases. */
@@ -367,6 +397,14 @@ export function _resetDeferredForTests() {
367
397
  * Computes both anchors per diagnostic (cheap — same hash primitive, twice)
368
398
  * since false-positive is keyed strict while defer/suppress are keyed weak;
369
399
  * see module doc for why each disposition binds the way it does.
400
+ *
401
+ * Review-round F1 (#1625): a `d.semantic === "blocking"` diagnostic can be
402
+ * dropped ONLY by the STRICT (false-positive) branch — never by a WEAK-
403
+ * anchored suppress or defer. Two distinct secrets sharing the same
404
+ * rule/message (e.g. two different AWS keys on different lines) collapse
405
+ * onto the SAME weak anchor, so a weak suppress/defer on one would silently
406
+ * silence the other too; unacceptable for a STOP-tier finding. `flagged` is
407
+ * unaffected either way — it never drops anything, blocking or not.
370
408
  */
371
409
  export function applyDispositions(diagnostics, cwd, filePath, content) {
372
410
  if (!diagnostics.length)
@@ -376,16 +414,21 @@ export function applyDispositions(diagnostics, cwd, filePath, content) {
376
414
  return diagnostics;
377
415
  return diagnostics.filter((d) => {
378
416
  const { strict, weak } = anchorsForDiagnostic(cwd, filePath, d, content);
379
- if (deferredThisSession.has(weak))
417
+ const isBlocking = d.semantic === "blocking";
418
+ if (!isBlocking && deferredThisSession.has(deferredKey(cwd, weak))) {
380
419
  return false;
420
+ }
381
421
  if (dispositions?.[strict]?.disposition === "false-positive")
382
422
  return false;
383
423
  // Belt-and-braces: the inline `pi-lens-ignore` comment is the real
384
424
  // suppress enforcement (see suppress-writer.ts) and normally already
385
425
  // dropped this finding upstream via applyInlineSuppressions. This is a
386
- // harmless second cover for the store-only audit trail case.
387
- if (dispositions?.[weak]?.disposition === "suppress")
426
+ // harmless second cover for the store-only audit trail case — gated
427
+ // off blocking findings per F1 above (the inline comment, unaffected
428
+ // by this gate, remains the real suppress mechanism for those too).
429
+ if (!isBlocking && dispositions?.[weak]?.disposition === "suppress") {
388
430
  return false;
431
+ }
389
432
  return true;
390
433
  });
391
434
  }
@@ -405,6 +448,13 @@ export function applyDispositions(diagnostics, cwd, filePath, content) {
405
448
  * content-based filter). suppress/defer, being intent-level and weak-anchored,
406
449
  * are the marks that must apply the instant a query re-serves cached findings,
407
450
  * and they do so here without any read.
451
+ *
452
+ * Review-round F1 (#1625): this filter is ENTIRELY weak-anchored — it has no
453
+ * strict-anchor branch to fall back to — so a `d.semantic === "blocking"`
454
+ * diagnostic is never dropped here at all, regardless of any suppress/defer
455
+ * mark. The full `applyDispositions` (content in hand, at the next per-edit
456
+ * dispatch or mode=full merge) is the only place a blocking finding can ever
457
+ * be suppressed, and only via its STRICT branch.
408
458
  */
409
459
  export function applyWeakDispositions(diagnostics, cwd, filePath) {
410
460
  if (!diagnostics.length)
@@ -413,6 +463,8 @@ export function applyWeakDispositions(diagnostics, cwd, filePath) {
413
463
  if (!dispositions && deferredThisSession.size === 0)
414
464
  return diagnostics;
415
465
  return diagnostics.filter((d) => {
466
+ if (d.semantic === "blocking")
467
+ return true;
416
468
  const weak = computeWeakAnchor({
417
469
  cwd,
418
470
  filePath,
@@ -421,10 +473,74 @@ export function applyWeakDispositions(diagnostics, cwd, filePath) {
421
473
  message: d.message,
422
474
  line: d.line,
423
475
  });
424
- if (deferredThisSession.has(weak))
476
+ if (deferredThisSession.has(deferredKey(cwd, weak)))
425
477
  return false;
426
478
  if (dispositions?.[weak]?.disposition === "suppress")
427
479
  return false;
428
480
  return true;
429
481
  });
430
482
  }
483
+ /**
484
+ * Multi-file variant of {@link applyDispositions} for lanes whose findings
485
+ * span many files in ONE report — gitleaks/trivy/govulncheck/opengrep
486
+ * project-wide scans (#1617), unlike the dispatch path's one-`ctx.filePath`-
487
+ * at-a-time shape. Groups by `filePathOf(diagnostic)`, reads each file's
488
+ * CURRENT content once, then delegates to `applyDispositions` per group —
489
+ * same anchor derivation, same strict/weak split, no cloned logic (#1617's
490
+ * single-source-of-truth requirement).
491
+ *
492
+ * Content read is best-effort: a file that no longer exists, or that this
493
+ * process can't read, degrades to `content: ""` for that group rather than
494
+ * dropping its diagnostics outright. `applyDispositions` already treats a
495
+ * missing/empty content argument safely — the STRICT (false-positive) anchor
496
+ * hashes the diagnostic's own line, so an empty content can't collide with a
497
+ * mark made against the file's real content and the finding simply isn't
498
+ * matched (fails OPEN — still reported); the WEAK anchor (suppress/defer)
499
+ * never looks at content at all, so those marks keep applying regardless.
500
+ * This is a deliberate choice, not an oversight: a security finding must
501
+ * never silently vanish just because its file went unreadable at filter
502
+ * time (a security finding staying VISIBLE on a read failure is the safe
503
+ * default; a stale mark quietly reappearing is recoverable, a leaked secret
504
+ * quietly disappearing is not).
505
+ *
506
+ * Review-round F2 (#1625): the empty-store early return is checked HERE,
507
+ * before any per-group file read — not left to `applyDispositions` to notice
508
+ * once already inside the per-group loop. Every mode=full analyzer call
509
+ * (9 lanes) runs through this on every scan, and the overwhelmingly common
510
+ * case is zero marks in the project; measured ~270ms of synchronous
511
+ * `fs.readFileSync` per analyzer per run for that common case before this
512
+ * hoist (reading every finding's file just to discover there was nothing to
513
+ * filter), ~0-1ms after.
514
+ */
515
+ export function applyDispositionsMultiFile(diagnostics, cwd, filePathOf) {
516
+ if (!diagnostics.length)
517
+ return diagnostics;
518
+ const dispositions = readState(cwd).dispositions;
519
+ if (!dispositions && deferredThisSession.size === 0)
520
+ return diagnostics;
521
+ const groups = new Map();
522
+ for (const d of diagnostics) {
523
+ const filePath = filePathOf(d);
524
+ const group = groups.get(filePath);
525
+ if (group)
526
+ group.push(d);
527
+ else
528
+ groups.set(filePath, [d]);
529
+ }
530
+ const kept = new Set();
531
+ for (const [filePath, group] of groups) {
532
+ let content = "";
533
+ try {
534
+ content = multiFileReadFileSync(filePath, "utf-8");
535
+ }
536
+ catch {
537
+ // Unreadable/missing — fail open, see doc above.
538
+ }
539
+ for (const d of applyDispositions(group, cwd, filePath, content)) {
540
+ kept.add(d);
541
+ }
542
+ }
543
+ // Preserve the caller's original order/dedup by reference rather than the
544
+ // per-group insertion order above.
545
+ return diagnostics.filter((d) => kept.has(d));
546
+ }