@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
@@ -213,11 +213,19 @@ Thanks goes to these wonderful people:
213
213
  </tr>
214
214
  <tr>
215
215
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pppobear"><img src="https://avatars.githubusercontent.com/u/21952175?v=4" width="100px;" alt=""/><br /><sub><b>pppobear</b></sub></a><br /><a href="#bug-pppobear" title="Bug reports">🐛</a></td>
216
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/bcachet"><img src="https://avatars.githubusercontent.com/u/45542?v=4" width="100px;" alt=""/><br /><sub><b>Bertrand Cachet</b></sub></a><br /><a href="#code-bcachet" title="Code">💻</a></td>
217
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/EarthChen"><img src="https://avatars.githubusercontent.com/u/20179425?v=4" width="100px;" alt=""/><br /><sub><b>earthchen</b></sub></a><br /><a href="#code-EarthChen" title="Code">💻</a></td>
218
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Don-Yin"><img src="https://avatars.githubusercontent.com/u/65135356?v=4" width="100px;" alt=""/><br /><sub><b>Don Yin</b></sub></a><br /><a href="#code-Don-Yin" title="Code">💻</a></td>
219
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ELA718"><img src="https://avatars.githubusercontent.com/u/193304463?v=4" width="100px;" alt=""/><br /><sub><b>ELA718</b></sub></a><br /><a href="#doc-ELA718" title="Documentation">📖</a></td>
220
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/sujeito-operator/pilot"><img src="https://avatars.githubusercontent.com/u/313599463?v=4" width="100px;" alt=""/><br /><sub><b>Sujeito Operator</b></sub></a><br /><a href="#code-sujeito-operator" title="Code">💻</a></td>
221
+ <td align="center" valign="top" width="14.28%"><a href="http://www.blahs.life/"><img src="https://avatars.githubusercontent.com/u/620336?v=4" width="100px;" alt=""/><br /><sub><b>Dan Blah</b></sub></a><br /><a href="#bug-danblah" title="Bug reports">🐛</a></td>
222
+ </tr>
223
+ <tr>
216
224
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/3choBoomer"><img src="https://avatars.githubusercontent.com/u/521828?v=4" width="100px;" alt=""/><br /><sub><b>Nathan Cooke</b></sub></a><br /><a href="#code-3choBoomer" title="Code">💻</a></td>
217
225
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/white-hat"><img src="https://avatars.githubusercontent.com/u/922988?v=4" width="100px;" alt=""/><br /><sub><b>Eli Stark</b></sub></a><br /><a href="#code-white-hat" title="Code">💻</a></td>
218
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/marvtub"><img src="https://avatars.githubusercontent.com/u/33159023?v=4" width="100px;" alt=""/><br /><sub><b>Marvin Aziz</b></sub></a><br /><a href="#code-marvtub" title="Code">💻</a></td>
219
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/mjfaga"><img src="https://avatars.githubusercontent.com/u/7584015?v=4" width="100px;" alt=""/><br /><sub><b>Mark Faga</b></sub></a><br /><a href="#code-mjfaga" title="Code">💻</a></td>
220
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/aeturnal"><img src="https://avatars.githubusercontent.com/u/219271200?v=4" width="100px;" alt=""/><br /><sub><b>aeturnal</b></sub></a><br /><a href="#code-aeturnal" title="Code">💻</a></td>
226
+ <td align="center" valign="top" width="14.28%"><a href="https://marvinaziz.de/"><img src="https://avatars.githubusercontent.com/u/33159023?v=4" width="100px;" alt=""/><br /><sub><b>Marvin Aziz</b></sub></a><br /><a href="#code-marvtub" title="Code">💻</a></td>
227
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/mjfaga"><img src="https://avatars.githubusercontent.com/u/7584015?v=4" width="100px;" alt=""/><br /><sub><b>Mark</b></sub></a><br /><a href="#code-mjfaga" title="Code">💻</a></td>
228
+ <td align="center" valign="top" width="14.28%"><a href="https://aeturnal.io/"><img src="https://avatars.githubusercontent.com/u/219271200?v=4" width="100px;" alt=""/><br /><sub><b>aeturnal</b></sub></a><br /><a href="#code-aeturnal" title="Code">💻</a></td>
221
229
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/floatGray"><img src="https://avatars.githubusercontent.com/u/44295302?v=4" width="100px;" alt=""/><br /><sub><b>floatGray</b></sub></a><br /><a href="#code-floatGray" title="Code">💻</a></td>
222
230
  </tr>
223
231
  </tbody>
@@ -16,10 +16,52 @@ select = [
16
16
  "SIM", # flake8-simplify (simplifiable constructs)
17
17
  "C4", # flake8-comprehensions (unnecessary comprehension patterns)
18
18
  "PIE", # flake8-pie (misc lints)
19
+ "S", # flake8-bandit (Bandit-equivalent security checks; refs #1757)
19
20
  ]
20
21
 
22
+ # C90 (mccabe cyclomatic complexity) — considered and NOT enabled (refs #1757).
23
+ # Measured against `requests` (mature, well-styled: 2 hits over the default
24
+ # threshold of 10, both genuinely complex) and `pandas` (379 hits) — the
25
+ # signal is real complexity, not false positives, but the volume on a large
26
+ # pre-existing codebase would drown the security signal `S` adds in the same
27
+ # pass. C90 is a maintainability metric, not a Bandit-equivalent security
28
+ # check, so it stays out of scope for this issue. Revisit as its own opt-in
29
+ # if pi-lens grows a "code health" ruleset.
30
+
31
+ # S sub-rules excluded below were triaged by running `ruff check --select S`
32
+ # against tests/fixtures/real-bugs/python/silent-and-security.py (this repo's
33
+ # own security fixture — 3/3 true positives: S110, S105, S307) and against the
34
+ # `requests` and `pip` packages installed under a local Python 3.11 (394
35
+ # combined S findings across two mature, widely-audited real-world
36
+ # codebases) — chosen as the "2-3 real fixtures" because pi-lens's own repo
37
+ # only has ~15 Python files, too small to expose over-firing sub-rules.
21
38
  ignore = [
22
39
  "E501", # line too long — formatter handles this
23
40
  "B008", # function call in default argument — common FastAPI/typer pattern
24
41
  "SIM108", # ternary operator — readability is subjective
42
+
43
+ # S101 assert: 354 of 394 S hits (90%) across requests+pip were bare
44
+ # `assert` statements — the risk `S101` warns about (asserts stripped under
45
+ # `python -O`) is real only for security-enforcement code, not general
46
+ # application or test code, where `assert` is idiomatic (pytest). Pure
47
+ # noise at this volume.
48
+ "S101",
49
+ # S311 suspicious-non-cryptographic-random-usage: every hit observed
50
+ # (e.g. pip/_internal/utils/filesystem.py building a temp-file suffix) was
51
+ # a non-security use of `random` — file names, jitter, sampling. Ruff
52
+ # cannot distinguish security-sensitive randomness from ordinary use.
53
+ "S311",
54
+ # S603 subprocess-without-shell-equals-true: fires on every list-argument
55
+ # `subprocess.run(...)`/`Popen(...)` call regardless of whether the
56
+ # arguments are attacker-controlled — this is the RECOMMENDED safe form
57
+ # (no shell=True), yet it was the rule's only trigger condition in every
58
+ # one of the 10 hits observed in pip. Bandit's own docs note this rule's
59
+ # high false-positive rate; excluded for the same reason upstream projects
60
+ # commonly disable it.
61
+ "S603",
62
+ # S607 start-process-with-partial-path: fires whenever a subprocess call
63
+ # names its executable by PATH lookup (`["rustc", "--version"]`) instead of
64
+ # an absolute path — standard practice for any CLI-wrapping code, and both
65
+ # observed hits in pip were exactly that pattern, not a real vulnerability.
66
+ "S607",
25
67
  ]
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
+ import { hashText, normalizeMessage, stableFindingId, } from "./finding-identity.js";
4
5
  import { applyWorkspaceEdit } from "./lsp/edits.js";
5
6
  import { getLSPService } from "./lsp/index.js";
6
7
  import { normalizeMapKey } from "./path-utils.js";
@@ -14,19 +15,47 @@ let beforeWarningStateLockForTests = null;
14
15
  export function _setBeforeWarningStateLockForTests(hook) {
15
16
  beforeWarningStateLockForTests = hook;
16
17
  }
17
- function normalizeMessage(message) {
18
- return message.replace(/\s+/g, " ").trim().toLowerCase();
19
- }
20
- function hashText(value, length = 10) {
21
- return createHash("sha256").update(value).digest("hex").slice(0, length);
22
- }
23
- function relativeFile(filePath, cwd) {
24
- const rel = path.relative(cwd, filePath).replace(/\\/g, "/");
25
- return rel && !rel.startsWith("..") ? rel : normalizeMapKey(filePath);
26
- }
18
+ /** #1816: was a local `relativeFile|tool|source|code|rule|normalizedMessage|
19
+ * line` hash, hand-rolled independently of `diagnostic-dispositions.ts`'s
20
+ * canonicalizing version — this is now the shared `finding-identity.js`
21
+ * builder (which DOES canonicalize both `cwd` and `filePath` through
22
+ * `normalizeMapKey` before relativizing, and hashes to 12 chars, matching
23
+ * dispositions). See `legacyActionableWarningId` below for the pre-#1816
24
+ * formula, kept only for on-disk suppression-store migration. */
27
25
  export function createActionableWarningId(args) {
26
+ return stableFindingId("aw:", {
27
+ cwd: args.cwd,
28
+ filePath: args.filePath,
29
+ parts: [
30
+ args.tool,
31
+ args.source,
32
+ args.code,
33
+ args.rule,
34
+ normalizeMessage(args.message),
35
+ args.line,
36
+ ],
37
+ });
38
+ }
39
+ /** PRE-#1816 id formula (raw, non-canonicalized `relativeFile`; 10-char
40
+ * hash). `actionable-warning-state.json` is a keyed, persisted store — a
41
+ * warning suppressed under the old formula must not silently reappear as
42
+ * unsuppressed just because this module unified onto the canonical,
43
+ * 12-char id. `suppressionFor`/`updateWarningState` use this ONLY to look up
44
+ * and migrate a still-pending old entry forward; nothing ever WRITES under
45
+ * this id. Do not canonicalize this function — that would make it identical
46
+ * to `createActionableWarningId` and silently defeat the migration lookup
47
+ * for every path that actually needed canonicalizing (the #533 class this
48
+ * whole item exists to fix). Review-round F3 (#1816): this guard is only
49
+ * provable under a MIS-CASED path fixture — a fixture whose raw and
50
+ * canonical forms coincide (a bare mkdtempSync path) makes canonicalizing
51
+ * this function a no-op, so the regression test must seed under a mis-cased
52
+ * segment (see `actionable-warnings.test.ts`'s migration describe block) or
53
+ * the guard passes vacuously either way. */
54
+ function legacyActionableWarningId(args) {
55
+ const rel = path.relative(args.cwd, args.filePath).replace(/\\/g, "/");
56
+ const legacyRelativeFile = rel && !rel.startsWith("..") ? rel : normalizeMapKey(args.filePath);
28
57
  const parts = [
29
- relativeFile(args.filePath, args.cwd),
58
+ legacyRelativeFile,
30
59
  args.tool ?? "",
31
60
  args.source ?? "",
32
61
  String(args.code ?? ""),
@@ -34,7 +63,7 @@ export function createActionableWarningId(args) {
34
63
  normalizeMessage(args.message),
35
64
  String(args.line ?? ""),
36
65
  ];
37
- return `aw:${hashText(parts.join("|"))}`;
66
+ return `aw:${hashText(parts.join("|"), 10)}`;
38
67
  }
39
68
  function actionSafety(action) {
40
69
  const kind = action.kind ?? "";
@@ -93,7 +122,28 @@ function updateWarningState(cwd, warnings) {
93
122
  const now = new Date().toISOString();
94
123
  state.warnings ??= {};
95
124
  for (const warning of warnings) {
96
- const existing = state.warnings[warning.id] ?? {};
125
+ // #1816 migration: this warning may still be recorded under the
126
+ // pre-#1816 id (raw relativeFile, 10-char hash) from before this
127
+ // store unified onto the canonical id. Fold that entry forward
128
+ // onto the current id and drop the stale key, so a warning
129
+ // suppressed before the migration stays suppressed, and repeated
130
+ // re-encounters converge the store onto one id per warning
131
+ // instead of accumulating both forever.
132
+ //
133
+ // Review-round F1: this MUST read `warning.legacyId` — the value
134
+ // `suppressionFor` already computed from the exact identity args
135
+ // used at lookup time — and must NEVER re-derive a legacy id from
136
+ // `warning.rule`/`tool`/`source`/`code`. Those fields hold
137
+ // DISPLAY values that diverge from the id-construction args for
138
+ // LSP-origin records (see `ActionableWarningRecord.legacyId`'s
139
+ // doc comment), so a re-derivation here would compute a
140
+ // different legacy id than the one `suppressionFor` checked,
141
+ // permanently bifurcating the store.
142
+ const legacyId = warning.legacyId;
143
+ const legacyEntry = legacyId && legacyId !== warning.id
144
+ ? state.warnings[legacyId]
145
+ : undefined;
146
+ const existing = state.warnings[warning.id] ?? legacyEntry ?? {};
97
147
  state.warnings[warning.id] = {
98
148
  ...existing,
99
149
  status: existing.status ?? "active",
@@ -101,6 +151,8 @@ function updateWarningState(cwd, warnings) {
101
151
  lastSeenAt: now,
102
152
  seenCount: (existing.seenCount ?? 0) + 1,
103
153
  };
154
+ if (legacyEntry && legacyId)
155
+ delete state.warnings[legacyId];
104
156
  }
105
157
  return state;
106
158
  },
@@ -115,36 +167,64 @@ function updateWarningState(cwd, warnings) {
115
167
  }),
116
168
  });
117
169
  }
118
- function suppressionFor(cwd, id) {
119
- const entry = readSuppressionState(cwd).warnings?.[id];
170
+ /** Looks up suppression under the current id, falling back to the pre-#1816
171
+ * id (see `legacyActionableWarningId`) so a warning suppressed before this
172
+ * migration doesn't silently reappear as unsuppressed. `args` is the exact
173
+ * identity shape both id builders take.
174
+ *
175
+ * Also RETURNS the `legacyId` it computed (review-round F1, #1816): the
176
+ * caller carries it onto the record's `legacyId` field so
177
+ * `updateWarningState` can migrate the SAME legacy id this lookup used,
178
+ * instead of re-deriving one from the record's own `rule`/`tool`/`source`/
179
+ * `code` fields later. Re-deriving is unsound for LSP-origin records —
180
+ * `recordFromLspDiagnostic` passes no `rule` into this function (LSP
181
+ * diagnostics don't have one), then sets `record.rule` afterward to
182
+ * `${source}:${code}` purely for display. Recomputing from that display
183
+ * value would silently compute a DIFFERENT legacy id than the one actually
184
+ * checked here, permanently bifurcating the store: a suppression written
185
+ * this turn under the current id would never be found again next turn. */
186
+ function suppressionFor(cwd, id, args) {
187
+ const state = readSuppressionState(cwd);
188
+ const legacyId = legacyActionableWarningId({ cwd, ...args });
189
+ const entry = state.warnings?.[id] ??
190
+ (legacyId !== id ? state.warnings?.[legacyId] : undefined);
120
191
  return {
121
192
  suppressed: entry?.status === "suppressed",
122
193
  reason: entry?.reason,
194
+ legacyId,
123
195
  };
124
196
  }
125
197
  export function recordFromDispatchDiagnostic(diagnostic, cwd) {
126
- if (diagnostic.semantic !== "warning" || diagnostic.severity !== "warning")
198
+ if (diagnostic.semantic !== "warning")
199
+ return undefined;
200
+ // #1777: the old gate demanded `severity === "warning"` exactly, which was
201
+ // invisible while the ast-grep runner collapsed every non-error tier to
202
+ // "warning". Now that hint and info survive the dispatch path, admit them:
203
+ // a fix is a fix, and the tier governs how loudly a finding renders, not
204
+ // whether its fix is worth offering. `error` still routes to the blocking
205
+ // path, so it stays out even when a runner leaves `semantic` at "warning".
206
+ if (diagnostic.severity === "error")
127
207
  return undefined;
128
208
  if (!diagnostic.fixable && !diagnostic.fixSuggestion)
129
209
  return undefined;
130
210
  const filePath = path.resolve(cwd, diagnostic.filePath);
131
- const id = createActionableWarningId({
132
- cwd,
211
+ const identityArgs = {
133
212
  filePath,
134
213
  tool: diagnostic.tool,
135
214
  code: diagnostic.code,
136
215
  rule: diagnostic.rule,
137
216
  message: diagnostic.message,
138
217
  line: diagnostic.line,
139
- });
140
- const suppression = suppressionFor(cwd, id);
218
+ };
219
+ const id = createActionableWarningId({ cwd, ...identityArgs });
220
+ const suppression = suppressionFor(cwd, id, identityArgs);
141
221
  return {
142
222
  id,
143
223
  filePath,
144
224
  displayPath: toRunnerDisplayPath(cwd, filePath),
145
225
  line: diagnostic.line,
146
226
  column: diagnostic.column,
147
- severity: "warning",
227
+ severity: diagnostic.severity,
148
228
  tool: diagnostic.tool,
149
229
  code: diagnostic.code,
150
230
  rule: diagnostic.rule,
@@ -155,6 +235,7 @@ export function recordFromDispatchDiagnostic(diagnostic, cwd) {
155
235
  actions: [],
156
236
  suppressed: suppression.suppressed,
157
237
  suppressionReason: suppression.reason,
238
+ legacyId: suppression.legacyId,
158
239
  origin: "dispatch",
159
240
  };
160
241
  }
@@ -170,16 +251,16 @@ function recordFromLspDiagnostic(diag, filePath, cwd) {
170
251
  const column = diag.range.start.character + 1;
171
252
  const source = diag.source ?? "lsp";
172
253
  const code = diag.code === undefined ? undefined : String(diag.code);
173
- const id = createActionableWarningId({
174
- cwd,
254
+ const identityArgs = {
175
255
  filePath,
176
256
  tool: "lsp",
177
257
  source,
178
258
  code,
179
259
  message: diag.message,
180
260
  line,
181
- });
182
- const suppression = suppressionFor(cwd, id);
261
+ };
262
+ const id = createActionableWarningId({ cwd, ...identityArgs });
263
+ const suppression = suppressionFor(cwd, id, identityArgs);
183
264
  return {
184
265
  id,
185
266
  filePath,
@@ -195,6 +276,7 @@ function recordFromLspDiagnostic(diag, filePath, cwd) {
195
276
  actions: [],
196
277
  suppressed: suppression.suppressed,
197
278
  suppressionReason: suppression.reason,
279
+ legacyId: suppression.legacyId,
198
280
  origin: "lsp",
199
281
  };
200
282
  }
@@ -329,8 +411,13 @@ export async function buildActionableWarningsReport(args) {
329
411
  }
330
412
  const merged = mergeWarnings(records);
331
413
  updateWarningState(cwd, merged);
414
+ // legacyId is #1816 migration bookkeeping for updateWarningState above —
415
+ // strip it before the report leaves this function, so it never lands in
416
+ // the `.pi-lens/cache/actionable-warnings.json` cache file or any
417
+ // agent-facing rendering of a warning record.
418
+ const reportWarnings = merged.map(({ legacyId: _legacyId, ...rest }) => rest);
332
419
  const byFile = new Map();
333
- for (const warning of merged) {
420
+ for (const warning of reportWarnings) {
334
421
  const arr = byFile.get(warning.filePath) ?? [];
335
422
  arr.push(warning);
336
423
  byFile.set(warning.filePath, arr);
@@ -342,9 +429,16 @@ export async function buildActionableWarningsReport(args) {
342
429
  warnings,
343
430
  }));
344
431
  const allActions = merged.flatMap((warning) => warning.actions);
432
+ const unsuppressed = merged.filter((warning) => !warning.suppressed);
433
+ const countTier = (tier) => unsuppressed.filter((warning) => warning.severity === tier).length;
345
434
  const summary = {
346
435
  warnings: merged.length,
347
- unsuppressed: merged.filter((warning) => !warning.suppressed).length,
436
+ unsuppressed: unsuppressed.length,
437
+ byTier: {
438
+ warning: countTier("warning"),
439
+ info: countTier("info"),
440
+ hint: countTier("hint"),
441
+ },
348
442
  suppressed: merged.filter((warning) => warning.suppressed).length,
349
443
  files: files.length,
350
444
  actions: allActions.length,
@@ -586,8 +680,17 @@ export function formatActionableWarningsAdvisory(report) {
586
680
  const safe = report.summary.autoFixEligible > 0
587
681
  ? ` ${report.summary.autoFixEligible} appear to have conservative preferred quickfixes.`
588
682
  : "";
683
+ // #1777: hint and info are style opinions, so say how much of the count is
684
+ // opinion. The line appears only when a quiet tier is actually present —
685
+ // an all-warning turn already says everything in the count above.
686
+ const byTier = report.summary.byTier;
687
+ const quiet = byTier ? byTier.hint + byTier.info : 0;
688
+ const tierLine = quiet > 0
689
+ ? `${quiet} of those are hint/info tier — style opinions, worth fixing only while you are already in that code.`
690
+ : undefined;
589
691
  return [
590
692
  `🟡 Fixable warnings introduced this turn: ${report.summary.unsuppressed}.${safe}`,
693
+ tierLine,
591
694
  `Details written to .pi-lens/cache/actionable-warnings.json`,
592
695
  fileList ? `Files:\n${fileList}${more}` : undefined,
593
696
  "If continuing in these files, read that JSON and resolve warnings that are safe and relevant. Do not apply broad refactors unless requested.",
@@ -4,6 +4,7 @@ import * as path from "node:path";
4
4
  import { logLatency } from "./latency-logger.js";
5
5
  import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
6
6
  import { resolveRunnerPath } from "./dispatch/runner-context.js";
7
+ import { MTIME_DRIFT_TOLERANCE_MS } from "./blocker-freshness.js";
7
8
  export const MAX_ADVISORY_AFFECTED_FILES = 256;
8
9
  export function advisoryPathKey(filePath, cwd) {
9
10
  return normalizeMapKey(path.resolve(cwd, filePath));
@@ -199,9 +200,34 @@ export function validateAdvisoryProvenance(record, cwd, runtime) {
199
200
  export const MAX_FINDING_PATH_STATS = MAX_ADVISORY_AFFECTED_FILES;
200
201
  /** How many dead paths a single drop record names before it stops. */
201
202
  const MAX_LOGGED_DEAD_PATHS = 3;
202
- export function findingPathExistence(resolvedPath) {
203
+ /**
204
+ * One stat, one verdict. With `scannedAtMs` supplied, a surviving file whose
205
+ * mtime is newer than the scan reports `stale`.
206
+ *
207
+ * The boundary is `mtime > scannedAt + MTIME_DRIFT_TOLERANCE_MS`: a file
208
+ * scanned AT `scannedAt`, or written within the tolerance window of the scan
209
+ * timestamp being captured, reads live; genuinely past it reads stale.
210
+ *
211
+ * #1708: a bare +1ms tolerance — the convention `reconcileProjectDiagnosticsSnapshot`
212
+ * (clients/project-diagnostics/cache.ts:98) still uses, and the one this gate
213
+ * originally cited-but-withheld ("over-demoting costs a line number,
214
+ * under-demoting replays a false coordinate — the cheaper error wins the
215
+ * tie") — was not enough: on Windows a file's mtime can LEAD the immediately
216
+ * following `Date.now()` read by up to ~11.4ms, the same host skew
217
+ * `MTIME_DRIFT_TOLERANCE_MS` (`blocker-freshness.ts`) was raised to 50ms for
218
+ * (#1491/#1498). At +1ms this gate still demoted a real STOP-blocker (a
219
+ * trivy secret, #1628) to ACTION NEEDED, flaking
220
+ * tests/clients/runtime-turn-secrets-disposition.test.ts. Reusing the shared
221
+ * constant — rather than a second hand-tuned number — keeps one source of
222
+ * truth for the measured skew.
223
+ */
224
+ export function findingPathFreshness(resolvedPath, scannedAtMs) {
203
225
  try {
204
- fs.statSync(resolvedPath);
226
+ const stat = fs.statSync(resolvedPath);
227
+ if (scannedAtMs !== undefined &&
228
+ stat.mtimeMs > scannedAtMs + MTIME_DRIFT_TOLERANCE_MS) {
229
+ return "stale";
230
+ }
205
231
  return "live";
206
232
  }
207
233
  catch (error) {
@@ -211,14 +237,37 @@ export function findingPathExistence(resolvedPath) {
211
237
  return code === "ENOENT" || code === "ENOTDIR" ? "missing" : "unknown";
212
238
  }
213
239
  }
240
+ /** Existence-only probe. Retained for callers that have no scan timestamp. */
241
+ export function findingPathExistence(resolvedPath) {
242
+ return findingPathFreshness(resolvedPath);
243
+ }
244
+ /**
245
+ * Parse a scan timestamp into epoch ms, or `undefined` when it cannot be
246
+ * trusted. `undefined` disables the staleness arm — never demote on a bad
247
+ * clock.
248
+ */
249
+ export function parseScannedAtMs(scannedAt) {
250
+ if (scannedAt === undefined || scannedAt === null || scannedAt === "") {
251
+ return undefined;
252
+ }
253
+ const ms = typeof scannedAt === "number" ? scannedAt : Date.parse(scannedAt);
254
+ return Number.isFinite(ms) ? ms : undefined;
255
+ }
214
256
  /**
215
- * Partition findings into `{live, dropped}` by whether the path each one names
216
- * still exists. Pure apart from the `fs.statSync` probe, which is injectable so
217
- * the partition rules are unit-testable without a filesystem.
257
+ * Partition findings into `{live, stale, dropped}` by what the path each one
258
+ * names looks like now. Pure apart from the `fs.statSync` probe, which is
259
+ * injectable so the partition rules are unit-testable without a filesystem.
260
+ *
261
+ * The stat memo lives in local `Map`s built fresh on every call — per delivery,
262
+ * never per process. A module-level cache here would be the process-lifetime
263
+ * latch shape: it would pin the first verdict for a path and re-serve it for
264
+ * every later turn, which is the very defect this gate exists to close.
218
265
  */
219
266
  export function partitionFindingsByCitedPath(args) {
220
267
  const limit = args.maxUniquePaths ?? MAX_FINDING_PATH_STATS;
221
- const probe = args.existence ?? findingPathExistence;
268
+ const probe = args.existence ?? findingPathFreshness;
269
+ const scannedAtMs = parseScannedAtMs(args.scannedAt);
270
+ const onMissing = args.onMissing ?? "drop";
222
271
  // Two dedup layers, cheapest first. `rawVerdicts` collapses findings that
223
272
  // cite the IDENTICAL string with zero filesystem work — the dominant case
224
273
  // (#1460's live record: 126 findings over a handful of distinct `file`
@@ -232,8 +281,10 @@ export function partitionFindingsByCitedPath(args) {
232
281
  const rawVerdicts = new Map();
233
282
  const verdicts = new Map();
234
283
  const live = [];
284
+ const stale = [];
235
285
  const dropped = [];
236
286
  const deadPaths = [];
287
+ const stalePaths = [];
237
288
  let truncated = false;
238
289
  for (const finding of args.findings) {
239
290
  const cited = args.citedPath(finding);
@@ -261,20 +312,38 @@ export function partitionFindingsByCitedPath(args) {
261
312
  verdict = "live";
262
313
  }
263
314
  else {
264
- verdict = probe(resolved);
315
+ verdict = probe(resolved, scannedAtMs);
316
+ // A store that treats deletion as evidence-loss rather than
317
+ // remediation-loss folds `missing` into the demote arm here, so
318
+ // every downstream list and record sees one consistent verdict.
319
+ if (verdict === "missing" && onMissing === "demote") {
320
+ verdict = "stale";
321
+ }
265
322
  verdicts.set(key, verdict);
266
323
  if (verdict === "missing")
267
324
  deadPaths.push(resolved);
325
+ else if (verdict === "stale")
326
+ stalePaths.push(resolved);
268
327
  }
269
328
  }
270
329
  rawVerdicts.set(cited, verdict);
271
330
  }
272
331
  if (verdict === "missing")
273
332
  dropped.push(finding);
333
+ else if (verdict === "stale")
334
+ stale.push(finding);
274
335
  else
275
336
  live.push(finding);
276
337
  }
277
- return { live, dropped, deadPaths, statCount: verdicts.size, truncated };
338
+ return {
339
+ live,
340
+ stale,
341
+ dropped,
342
+ deadPaths,
343
+ stalePaths,
344
+ statCount: verdicts.size,
345
+ truncated,
346
+ };
278
347
  }
279
348
  /**
280
349
  * Delivery-seam wrapper: partition, then emit one bounded `finding_dead_path_drop`
@@ -285,27 +354,71 @@ export function partitionFindingsByCitedPath(args) {
285
354
  * store per delivery, with a capped path sample; never one per finding.
286
355
  */
287
356
  export function dropFindingsForMissingPaths(args) {
357
+ // No `scannedAt` — existence-only, so `partition.stale` is always empty.
358
+ const partition = partitionFindingsByCitedPath(args);
359
+ emitDeadPathDropRecord(args.store, args.cwd, partition);
360
+ return partition.live;
361
+ }
362
+ /**
363
+ * Delivery-seam wrapper with the #1622 freshness verdict. Emits at most one
364
+ * `finding_dead_path_drop` and one `finding_stale_line_demote` record per store
365
+ * per delivery, each with a capped path sample; never one per finding.
366
+ *
367
+ * Callers that have no scan timestamp keep using `dropFindingsForMissingPaths`.
368
+ */
369
+ export function gateFindingsByPathFreshness(args) {
288
370
  const partition = partitionFindingsByCitedPath(args);
371
+ emitDeadPathDropRecord(args.store, args.cwd, partition);
372
+ emitStaleLineDemoteRecord(args.store, args.cwd, args.scannedAt, partition);
373
+ return { live: partition.live, stale: partition.stale };
374
+ }
375
+ function emitDeadPathDropRecord(store, cwd, partition) {
289
376
  if (partition.dropped.length === 0)
290
- return partition.live;
377
+ return;
291
378
  logLatency({
292
379
  type: "phase",
293
380
  phase: "finding_dead_path_drop",
294
- filePath: args.cwd,
381
+ filePath: cwd,
295
382
  durationMs: 0,
296
383
  metadata: {
297
- store: args.store,
384
+ store,
298
385
  droppedDeadPaths: partition.dropped.length,
299
386
  deadPathCount: partition.deadPaths.length,
300
387
  deliveredCount: partition.live.length,
301
388
  statCount: partition.statCount,
302
389
  samplePaths: partition.deadPaths
303
390
  .slice(0, MAX_LOGGED_DEAD_PATHS)
304
- .map((deadPath) => toProjectRelativePath(deadPath, args.cwd)),
391
+ .map((deadPath) => toProjectRelativePath(deadPath, cwd)),
392
+ ...(partition.truncated ? { truncated: true } : {}),
393
+ },
394
+ });
395
+ }
396
+ /**
397
+ * #1622 criterion 5: the demote path gets the same record shape as the drop
398
+ * path. Before this, a stale-line replay window was reconstructible only by
399
+ * proxy — the blocker payload is written nowhere.
400
+ */
401
+ function emitStaleLineDemoteRecord(store, cwd, scannedAt, partition) {
402
+ if (partition.stale.length === 0)
403
+ return;
404
+ logLatency({
405
+ type: "phase",
406
+ phase: "finding_stale_line_demote",
407
+ filePath: cwd,
408
+ durationMs: 0,
409
+ metadata: {
410
+ store,
411
+ demotedStalePaths: partition.stale.length,
412
+ stalePathCount: partition.stalePaths.length,
413
+ deliveredCount: partition.live.length,
414
+ statCount: partition.statCount,
415
+ samplePaths: partition.stalePaths
416
+ .slice(0, MAX_LOGGED_DEAD_PATHS)
417
+ .map((stalePath) => toProjectRelativePath(stalePath, cwd)),
418
+ ...(scannedAt === undefined ? {} : { scannedAt: String(scannedAt) }),
305
419
  ...(partition.truncated ? { truncated: true } : {}),
306
420
  },
307
421
  });
308
- return partition.live;
309
422
  }
310
423
  export function provenanceStamp(provenance) {
311
424
  if (!isWellFormed(provenance))