@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
@@ -60,13 +60,24 @@ export function unrefChildAndPipes(child) {
60
60
  * here already has).
61
61
  */
62
62
  export function spawnCollectStdout(command, args, options) {
63
+ return spawnCollectStdoutResult(command, args, options).then((result) => result.stdout);
64
+ }
65
+ /**
66
+ * `spawnCollectStdout` plus the reason the output is what it is. Same spawn,
67
+ * unref, collect, settle plumbing; the only additions are an optional hard
68
+ * timeout and a status discriminator. Never rejects.
69
+ */
70
+ export function spawnCollectStdoutResult(command, args, options, collectOptions = {}) {
63
71
  return new Promise((resolve) => {
64
72
  let settled = false;
65
- const settle = (value) => {
73
+ let timer;
74
+ const settle = (result) => {
66
75
  if (settled)
67
76
  return;
68
77
  settled = true;
69
- resolve(value);
78
+ if (timer)
79
+ clearTimeout(timer);
80
+ resolve(result);
70
81
  };
71
82
  try {
72
83
  const child = nodeSpawn(command, args, options);
@@ -75,11 +86,48 @@ export function spawnCollectStdout(command, args, options) {
75
86
  child.stdout?.on("data", (chunk) => {
76
87
  out += chunk.toString();
77
88
  });
78
- child.once("error", () => settle(""));
79
- child.once("close", () => settle(out));
89
+ child.once("error", (error) => settle({ stdout: "", status: "spawn-error", error }));
90
+ child.once("close", (code, signal) => settle(code === 0 && signal === null
91
+ ? { stdout: out, status: "ok" }
92
+ : {
93
+ stdout: "",
94
+ status: "exit-error",
95
+ exitCode: code,
96
+ exitSignal: signal,
97
+ }));
98
+ const timeoutMs = collectOptions.timeoutMs;
99
+ if (typeof timeoutMs === "number" && timeoutMs > 0) {
100
+ timer = setTimeout(() => {
101
+ // Settle only AFTER the child's fate is known. Killing and
102
+ // resolving in the same tick bounds the caller and abandons the
103
+ // child, which is the leak this timeout exists to prevent.
104
+ void terminateTimedOutChild(child, collectOptions.onTimeout).then((timeoutKill) => settle({ stdout: out, status: "timeout", timeoutKill }));
105
+ }, timeoutMs);
106
+ timer.unref();
107
+ }
80
108
  }
81
- catch {
82
- settle("");
109
+ catch (error) {
110
+ settle({ stdout: "", status: "spawn-error", error });
83
111
  }
84
112
  });
85
113
  }
114
+ /** Run the caller's termination handler, or the bare-signal default, and
115
+ * never let either throw out of the timeout path. */
116
+ async function terminateTimedOutChild(child, onTimeout) {
117
+ if (onTimeout) {
118
+ try {
119
+ return await onTimeout(child);
120
+ }
121
+ catch {
122
+ // A handler that threw did not verify anything.
123
+ return "unverified";
124
+ }
125
+ }
126
+ try {
127
+ child.kill();
128
+ }
129
+ catch {
130
+ // best-effort — the child may already be gone
131
+ }
132
+ return "unverified";
133
+ }
@@ -1,29 +1,47 @@
1
- import { createHash } from "node:crypto";
2
1
  import * as fs from "node:fs";
3
2
  import * as path from "node:path";
4
3
  import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
5
4
  import { getProjectDataDir } from "./file-utils.js";
5
+ import { normalizeMessage, stableFindingId } from "./finding-identity.js";
6
6
  import { normalizeMapKey } from "./path-utils.js";
7
- function normalizeMessage(message) {
8
- return message.replace(/\s+/g, " ").trim().toLowerCase();
9
- }
10
- function hashText(value, length = 10) {
11
- return createHash("sha256").update(value).digest("hex").slice(0, length);
12
- }
13
- function relativeFile(filePath, cwd) {
14
- const rel = path.relative(cwd, filePath).replace(/\\/g, "/");
15
- return rel && !rel.startsWith("..") ? rel : normalizeMapKey(filePath);
16
- }
7
+ /**
8
+ * Report-budget precedence (#1777). A hint is an opinion; a warning is a
9
+ * finding. When the report is capped, warnings must be spent first, or an
10
+ * alphabetically-early file's hints can push every real warning out of the
11
+ * agent's view — the exact "hints dominate the stream" failure the hint tier
12
+ * exists to prevent.
13
+ */
14
+ const TIER_BUDGET_ORDER = [
15
+ "warning",
16
+ "info",
17
+ "hint",
18
+ ];
19
+ /** #1816: was a local `relativeFile|tool|rule|code|normalizedMessage|line`
20
+ * hash (raw, non-canonicalized `relativeFile`, 10-char hash), independently
21
+ * hand-rolled from `actionable-warnings.ts`'s twin and
22
+ * `diagnostic-dispositions.ts`'s canonicalizing original. Now the shared
23
+ * `finding-identity.js` builder (canonicalizes `cwd`/`filePath` through
24
+ * `normalizeMapKey`, hashes to 12 chars). No back-compat migration needed
25
+ * here: `cq:` ids are never a keyed lookup — `buildCodeQualityWarningsReport`
26
+ * regenerates the whole report fresh every turn, callers only ever cache and
27
+ * replay the report wholesale (`tools/lens-diagnostics.ts` reads it via
28
+ * `cacheManager.readCache<CodeQualityWarningsReport>`, never by `id`), and
29
+ * `appendCodeQualityWarningsHistory` just append-only-logs the id for
30
+ * observability, never look it back up. Contrast `actionable-warnings.ts`,
31
+ * whose `actionable-warning-state.json` DOES key a persisted suppression
32
+ * store on `aw:` — that one migrates. */
17
33
  function createCodeQualityWarningId(args) {
18
- const parts = [
19
- relativeFile(args.filePath, args.cwd),
20
- args.tool ?? "",
21
- args.rule ?? "",
22
- String(args.code ?? ""),
23
- normalizeMessage(args.message),
24
- String(args.line ?? ""),
25
- ];
26
- return `cq:${hashText(parts.join("|"))}`;
34
+ return stableFindingId("cq:", {
35
+ cwd: args.cwd,
36
+ filePath: args.filePath,
37
+ parts: [
38
+ args.tool,
39
+ args.rule,
40
+ args.code,
41
+ normalizeMessage(args.message),
42
+ args.line,
43
+ ],
44
+ });
27
45
  }
28
46
  function categorize(diagnostic) {
29
47
  const haystack = `${diagnostic.tool} ${diagnostic.rule ?? ""} ${diagnostic.code ?? ""} ${diagnostic.message}`.toLowerCase();
@@ -93,11 +111,22 @@ export function buildCodeQualityWarningsReport(args) {
93
111
  continue;
94
112
  byId.set(warning.id, warning);
95
113
  }
96
- const merged = [...byId.values()]
97
- .sort((a, b) => a.displayPath.localeCompare(b.displayPath) ||
114
+ const byDisplayOrder = (a, b) => a.displayPath.localeCompare(b.displayPath) ||
98
115
  (a.line ?? 0) - (b.line ?? 0) ||
99
- a.message.localeCompare(b.message))
100
- .slice(0, maxWarnings);
116
+ a.message.localeCompare(b.message);
117
+ // Spend the budget tier by tier (see TIER_BUDGET_ORDER), then restore
118
+ // display order so the report still reads file by file, line by line.
119
+ const candidates = [...byId.values()].sort(byDisplayOrder);
120
+ const merged = [];
121
+ for (const tier of TIER_BUDGET_ORDER) {
122
+ for (const warning of candidates) {
123
+ if (merged.length >= maxWarnings)
124
+ break;
125
+ if (warning.severity === tier)
126
+ merged.push(warning);
127
+ }
128
+ }
129
+ merged.sort(byDisplayOrder);
101
130
  const byFile = new Map();
102
131
  for (const warning of merged) {
103
132
  const arr = byFile.get(warning.filePath) ?? [];
@@ -131,6 +160,11 @@ export function buildCodeQualityWarningsReport(args) {
131
160
  summary: {
132
161
  warnings: merged.length,
133
162
  files: files.length,
163
+ byTier: {
164
+ warning: merged.filter((w) => w.severity === "warning").length,
165
+ info: merged.filter((w) => w.severity === "info").length,
166
+ hint: merged.filter((w) => w.severity === "hint").length,
167
+ },
134
168
  topRules,
135
169
  },
136
170
  };
@@ -205,8 +239,26 @@ export function formatCodeQualityWarningsAdvisory(report) {
205
239
  .slice(0, 3)
206
240
  .map((entry) => `${entry.rule}×${entry.count}`)
207
241
  .join(", ");
242
+ // #1777: name the tiers, in the same descending-loudness order the on-demand
243
+ // ast-grep surface uses (`formatDiagnostics`, clients/ast-grep-client.ts).
244
+ // A tier with no findings is omitted rather than printed as zero.
245
+ const byTier = report.summary.byTier ?? { warning: 0, info: 0, hint: 0 };
246
+ // The line is worth its space only when a quiet tier is present; an
247
+ // all-warning turn already says everything in the count above.
248
+ const tiers = byTier.hint || byTier.info
249
+ ? [
250
+ byTier.warning ? `${byTier.warning} warning` : undefined,
251
+ byTier.info ? `${byTier.info} info` : undefined,
252
+ byTier.hint ? `${byTier.hint} hint` : undefined,
253
+ ]
254
+ .filter(Boolean)
255
+ .join(", ")
256
+ : "";
208
257
  return [
209
258
  `Code-quality warnings introduced/touched this turn: ${report.summary.warnings} across ${report.summary.files} file(s).`,
259
+ tiers
260
+ ? `By tier: ${tiers}. Hint and info are style opinions, not defects.`
261
+ : undefined,
210
262
  topRules ? `Top rules: ${topRules}` : undefined,
211
263
  "Details written to .pi-lens/cache/code-quality-warnings.json",
212
264
  "No action required unless you are already refactoring these areas.",
@@ -1,7 +1,9 @@
1
- import { performance } from "node:perf_hooks";
2
1
  // Capture this before the guard's other module initialization so host boot is
3
- // not charged to extension evaluation (#1374).
4
- export const PI_LENS_EVAL_STARTED_MS = performance.now();
2
+ // not charged to extension evaluation (#1374). Lives in its own
3
+ // side-effect-free module (#1434 S3d) — see eval-timestamp.ts's docstring for
4
+ // why: `startup-timing.ts` needs this constant but must never trigger a
5
+ // console-guard install merely by importing it.
6
+ import { PI_LENS_EVAL_STARTED_MS } from "./eval-timestamp.js";
5
7
  // #1333: install the console guard as an import side-effect so it runs before
6
8
  // any other module's initialization code. Installing inside the extension
7
9
  // factory is too late — by the time the factory runs, every static/transitive
@@ -10,5 +12,15 @@ export const PI_LENS_EVAL_STARTED_MS = performance.now();
10
12
  // MUST remain the first import of index.ts; the enforcement test pins that.
11
13
  // installConsoleGuard() itself is idempotent and no-ops under test mode and
12
14
  // PI_LENS_CONSOLE_GUARD=0.
13
- import { installConsoleGuard } from "./extension-log.js";
15
+ // #1434: the guard captures only while pi-lens owns execution, so the module
16
+ // window must be open for the rest of the import graph. index.ts closes it on
17
+ // its last line; an unref'd backstop closes it if that never runs.
18
+ import { installConsoleGuard, openModuleLoadConsoleWindow, } from "./extension-log.js";
14
19
  installConsoleGuard();
20
+ openModuleLoadConsoleWindow();
21
+ // Not re-exported: nothing should import the timestamp from this module.
22
+ // `clients/startup-timing.ts` imports it straight from `eval-timestamp.ts`,
23
+ // which is what keeps that import side-effect-free (S3d). The `void` keeps
24
+ // the import from looking unused to lint while documenting that this module
25
+ // only needs the ordering (evaluate eval-timestamp.ts first), not the value.
26
+ void PI_LENS_EVAL_STARTED_MS;
@@ -12,9 +12,15 @@
12
12
  * implementing DeadCodeClient and adding to getDeadCodeClients().
13
13
  */
14
14
  import { createSubsystemLogger } from "./extension-log.js";
15
+ import { incrementDegradationCount } from "./degradation-ledger.js";
16
+ import * as fs from "node:fs";
15
17
  import * as path from "node:path";
16
18
  import { findNearestMarkerRoot } from "./path-utils.js";
19
+ import { getScratchTreeFnmatchPatterns } from "./scratch-tree-policy.js";
17
20
  import { safeSpawnAsync } from "./safe-spawn.js";
21
+ import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
22
+ import { firstOutputLine, spawnFailedWithNoOutput, } from "./dispatch/runners/utils/spawn-outcome.js";
23
+ import { formatToolFailure } from "./dispatch/runners/utils/tool-failure.js";
18
24
  function emptyResult(language) {
19
25
  return {
20
26
  success: false,
@@ -27,17 +33,33 @@ function emptyResult(language) {
27
33
  }
28
34
  const ANALYSIS_TIMEOUT_MS = 30_000;
29
35
  // Directories never worth scanning for the user's own dead code.
36
+ //
37
+ // Single-sourced from `scratch-tree-policy.ts`'s `EXCLUDED_DIRS`-derived list
38
+ // (#1562 sweep finding: this array had hand-drifted from `EXCLUDED_DIRS` —
39
+ // missing `.pi`/`.claude`/`.next`/`.turbo`/etc, the same single-source-of-
40
+ // truth defect #883 named for per-language lists). `site-packages`/`.eggs`
41
+ // are Python-packaging-specific and have no `EXCLUDED_DIRS` counterpart (no
42
+ // other scanner needs them), so they stay as a small local addition rather
43
+ // than polluting the shared cross-tool list.
44
+ const VULTURE_PYTHON_ONLY_EXCLUDES = ["*/site-packages/*", "*/.eggs/*"];
30
45
  const VULTURE_EXCLUDES = [
31
- "*/.venv/*",
32
- "*/venv/*",
33
- "*/.tox/*",
34
- "*/build/*",
35
- "*/dist/*",
36
- "*/node_modules/*",
37
- "*/.git/*",
38
- "*/site-packages/*",
39
- "*/__pycache__/*",
40
- "*/.eggs/*",
46
+ ...getScratchTreeFnmatchPatterns(),
47
+ ...VULTURE_PYTHON_ONLY_EXCLUDES,
48
+ ];
49
+ // Decorators whose target is called by a framework, never by name in the tree.
50
+ // vulture cannot see those call sites, so every such symbol is a permanent
51
+ // false positive that reappears in every scan — the fastest way to teach a
52
+ // reader to stop reading the advisory. Glob-matched by vulture itself.
53
+ const VULTURE_IGNORE_DECORATORS = [
54
+ "@pytest.fixture",
55
+ "@pytest.fixture(*",
56
+ "@fixture",
57
+ "@fixture(*",
58
+ "@app.*",
59
+ "@router.*",
60
+ "@celery.task*",
61
+ "@task",
62
+ "@shared_task*",
41
63
  ];
42
64
  // vulture line: `path/to/file.py:12: unused function 'foo' (60% confidence)`
43
65
  const VULTURE_LINE = /^(.*?):(\d+): unused (\w[\w ]*?) '([^']+)' \((\d+)% confidence\)\s*$/;
@@ -86,7 +108,12 @@ export function parseVultureOutput(output, root) {
86
108
  export class PythonDeadCodeClient {
87
109
  id = "python";
88
110
  language = "Python";
89
- available = null;
111
+ /**
112
+ * Transient-aware memo (#1467): only a durable "vulture is not installed"
113
+ * verdict is remembered for the session. A probe that timed out expires and
114
+ * is re-probed, so vulture cannot be disabled for the process by one stall.
115
+ */
116
+ availabilityLatch = createAvailabilityLatch();
90
117
  resolved = null;
91
118
  ensureInFlight = null;
92
119
  inFlight = new Map();
@@ -103,6 +130,10 @@ export class PythonDeadCodeClient {
103
130
  detect(cwd) {
104
131
  return this.resolveProjectRoot(cwd) !== null;
105
132
  }
133
+ owns(filePath) {
134
+ const ext = path.extname(filePath).toLowerCase();
135
+ return ext === ".py" || ext === ".pyi";
136
+ }
106
137
  /**
107
138
  * Nearest dir with a Python project marker, never at/above $HOME and never
108
139
  * escaping a VCS boundary — same containment rules as KnipClient so a scan
@@ -120,12 +151,13 @@ export class PythonDeadCodeClient {
120
151
  "tox.ini",
121
152
  ], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
122
153
  }
123
- async ensureAvailable() {
124
- if (this.available !== null)
125
- return this.available;
154
+ async ensureAvailable(root) {
155
+ const memo = this.availabilityLatch.read();
156
+ if (memo !== null)
157
+ return memo;
126
158
  if (this.ensureInFlight)
127
159
  return this.ensureInFlight;
128
- this.ensureInFlight = this.doEnsureAvailable();
160
+ this.ensureInFlight = this.doEnsureAvailable(root);
129
161
  try {
130
162
  return await this.ensureInFlight;
131
163
  }
@@ -133,32 +165,131 @@ export class PythonDeadCodeClient {
133
165
  this.ensureInFlight = null;
134
166
  }
135
167
  }
136
- async doEnsureAvailable() {
168
+ /**
169
+ * `<root>/.venv/bin/vulture` (and `venv`/Windows `Scripts` equivalents),
170
+ * checked by existence only — no spawn. A project's own venv is the vulture
171
+ * that project's own config and dependency set were written against; the
172
+ * bare `vulture`/`python -m vulture` PATH probes below only ever see
173
+ * whatever happens to be active in the CALLING shell, not the project's
174
+ * (#1731, discipline B — the same venv-first shape sqlfluff's binary
175
+ * resolution already uses, `runner-helpers.ts` `createVenvFinder`).
176
+ */
177
+ venvCandidates(root) {
178
+ const isWin = process.platform === "win32";
179
+ const relPaths = isWin
180
+ ? [
181
+ path.join(".venv", "Scripts", "vulture.exe"),
182
+ path.join("venv", "Scripts", "vulture.exe"),
183
+ ]
184
+ : [path.join(".venv", "bin", "vulture"), path.join("venv", "bin", "vulture")];
185
+ return relPaths
186
+ .map((rel) => path.join(root, rel))
187
+ .filter((full) => fs.existsSync(full))
188
+ .map((full) => ({ cmd: full, prefix: [] }));
189
+ }
190
+ async doEnsureAvailable(root) {
137
191
  // Presence-gated, NOT auto-installed. vulture is a pure-Python package
138
192
  // with no standalone binary, so "auto-install" would mean `pip install`
139
193
  // into whatever Python environment happens to be active — wrong and
140
194
  // intrusive for uv / poetry / conda / pipx users. So we only use vulture
141
- // when the user already has it, probing both the `vulture` console script
142
- // and `python -m vulture` (the script dir is frequently not on PATH even
143
- // when the package is installed). Mirrors govulncheck's no-install gating.
195
+ // when the user already has it, probing the project's own venv first
196
+ // (#1731), then the `vulture` console script and `python -m vulture` on
197
+ // PATH (the script dir is frequently not on PATH even when the package
198
+ // is installed). Mirrors govulncheck's no-install gating.
144
199
  const candidates = [
200
+ ...(root ? this.venvCandidates(root) : []),
145
201
  { cmd: "vulture", prefix: [] },
146
202
  { cmd: "python", prefix: ["-m", "vulture"] },
147
203
  { cmd: "python3", prefix: ["-m", "vulture"] },
148
204
  ];
205
+ // A timeout on ANY candidate means the machine, not the tool, answered —
206
+ // the run gets a bounded retry instead of a permanent skip (#1467).
207
+ let sawTransient = false;
208
+ let transientCause = "probe-timeout";
209
+ // Accumulated across ALL candidates, because the failure verdicts below
210
+ // are about the whole sweep rather than any one probe. Reporting zero
211
+ // here would erase the evidence that cracked #1467: four 5s probes and
212
+ // a stalled host look identical to an absent tool without these two
213
+ // numbers.
214
+ const sweepStartedAt = Date.now();
215
+ let sweepHostStallMs = 0;
216
+ /** What the last classified candidate returned (#1500 review): this was
217
+ * computed and thrown away, so every vulture row said "missing" with no
218
+ * trace of which candidate reported what. */
219
+ let sweepEvidence;
149
220
  for (const c of candidates) {
150
- const probe = await safeSpawnAsync(c.cmd, [...c.prefix, "--version"], {
151
- timeout: 5000,
152
- });
221
+ const sampler = startHostStallSampler();
222
+ const startedAt = Date.now();
223
+ let probe;
224
+ let hostStallMs;
225
+ try {
226
+ probe = await safeSpawnAsync(c.cmd, [...c.prefix, "--version"], {
227
+ timeout: 5000,
228
+ });
229
+ }
230
+ finally {
231
+ hostStallMs = sampler.stop();
232
+ sweepHostStallMs += hostStallMs;
233
+ }
153
234
  if (!probe.error && probe.status === 0) {
154
235
  this.resolved = c;
155
- this.available = true;
236
+ this.availabilityLatch.noteAvailable();
156
237
  this.log(`vulture found: ${[c.cmd, ...c.prefix].join(" ")}`);
238
+ logAvailabilityDecision({
239
+ tool: "vulture",
240
+ verdict: "available",
241
+ outcome: "success",
242
+ cause: "ok",
243
+ elapsedMs: Date.now() - startedAt,
244
+ latched: true,
245
+ hostStallMs,
246
+ budgetMs: 5000,
247
+ });
157
248
  return true;
158
249
  }
250
+ const classified = classifyProbeFailure(probe, {
251
+ hostStallMs,
252
+ command: c.cmd,
253
+ });
254
+ sweepEvidence = classified.evidence;
255
+ if (classified.outcome === "transient") {
256
+ sawTransient = true;
257
+ transientCause = classified.cause;
258
+ }
159
259
  }
160
- this.available = false;
260
+ if (sawTransient) {
261
+ const retryAfterMs = this.availabilityLatch.noteUnavailable("transient", transientCause);
262
+ this.log("vulture probe timed out; will retry (not treated as missing)");
263
+ logAvailabilityDecision({
264
+ tool: "vulture",
265
+ verdict: "unavailable",
266
+ outcome: "transient",
267
+ cause: transientCause,
268
+ elapsedMs: Date.now() - sweepStartedAt,
269
+ hostStallMs: sweepHostStallMs,
270
+ latched: false,
271
+ retryAfterMs,
272
+ budgetMs: 5000,
273
+ classifiedBy: "probe",
274
+ ...(sweepEvidence !== undefined && { evidence: sweepEvidence }),
275
+ });
276
+ return false;
277
+ }
278
+ this.availabilityLatch.noteUnavailable("missing", "not-found");
161
279
  this.log("vulture not installed; skipping (no auto-install)");
280
+ logAvailabilityDecision({
281
+ tool: "vulture",
282
+ verdict: "unavailable",
283
+ outcome: "missing",
284
+ cause: "not-found",
285
+ elapsedMs: Date.now() - sweepStartedAt,
286
+ hostStallMs: sweepHostStallMs,
287
+ latched: true,
288
+ budgetMs: 5000,
289
+ // Every candidate probe was classified, and none was transient (#1500).
290
+ classifiedBy: "probe",
291
+ ...(sweepEvidence !== undefined && { evidence: sweepEvidence }),
292
+ });
162
293
  return false;
163
294
  }
164
295
  async analyze(cwd) {
@@ -170,7 +301,7 @@ export class PythonDeadCodeClient {
170
301
  summary: "No Python project root found; vulture skipped",
171
302
  };
172
303
  }
173
- if (!(await this.ensureAvailable())) {
304
+ if (!(await this.ensureAvailable(root))) {
174
305
  return {
175
306
  ...emptyResult(this.language),
176
307
  success: true,
@@ -185,14 +316,36 @@ export class PythonDeadCodeClient {
185
316
  this.inFlight.set(key, promise);
186
317
  return promise;
187
318
  }
319
+ /**
320
+ * True when `<root>/pyproject.toml` carries a `[tool.vulture]` table.
321
+ * vulture's own config discovery reads that table, but CLI flags override
322
+ * it — passing `--min-confidence`/`--exclude` unconditionally silently
323
+ * overrode a project's own thresholds and ignore list (#1731, discipline A).
324
+ * Same shape as `hasSqlfluffConfig`/`hasMypyConfig` in `tool-policy.ts`: a
325
+ * plain string search, no TOML parser, matching the section header only.
326
+ */
327
+ hasProjectVultureConfig(root) {
328
+ try {
329
+ const content = fs.readFileSync(path.join(root, "pyproject.toml"), "utf-8");
330
+ return content.includes("[tool.vulture]");
331
+ }
332
+ catch {
333
+ return false;
334
+ }
335
+ }
188
336
  async runAnalyze(root) {
189
337
  const startMs = Date.now();
190
338
  const invocation = this.resolved ?? { cmd: "vulture", prefix: [] };
339
+ // Let the project's own [tool.vulture] config win (#1731, discipline A):
340
+ // vulture discovers it unaided, but these two flags override it when
341
+ // passed, so they are omitted whenever the project ships that table.
342
+ const hasConfig = this.hasProjectVultureConfig(root);
191
343
  const args = [
192
344
  ...invocation.prefix,
193
345
  ".",
194
- `--min-confidence=${this.minConfidence}`,
195
- `--exclude=${VULTURE_EXCLUDES.join(",")}`,
346
+ ...(hasConfig ? [] : [`--min-confidence=${this.minConfidence}`]),
347
+ ...(hasConfig ? [] : [`--exclude=${VULTURE_EXCLUDES.join(",")}`]),
348
+ `--ignore-decorators=${VULTURE_IGNORE_DECORATORS.join(",")}`,
196
349
  ];
197
350
  const result = await safeSpawnAsync(invocation.cmd, args, {
198
351
  timeout: ANALYSIS_TIMEOUT_MS,
@@ -210,14 +363,36 @@ export class PythonDeadCodeClient {
210
363
  }
211
364
  // vulture writes parse/usage errors to stderr and exits 1 with no
212
365
  // stdout findings; distinguish that from "found dead code" (exit 1 WITH
213
- // findings on stdout).
366
+ // findings on stdout). #1736 sweep: the ORIGINAL guard here required
367
+ // non-empty stderr to call it an error, so a nonzero exit with BOTH
368
+ // empty stdout and empty stderr (a silent crash) still fell through to
369
+ // "No dead code found" -- the same empty-distinguishes-clean-from-errored
370
+ // gap the knip fix closes. A nonzero exit with no findings on stdout is
371
+ // never clean now, regardless of whether stderr said anything.
214
372
  const output = result.stdout || "";
215
373
  if (!output.trim()) {
216
374
  const stderr = (result.stderr || "").trim();
217
- if (result.status !== 0 && stderr) {
375
+ // Same discriminator every dispatch/runners linter uses
376
+ // (`spawnFailedWithNoOutput`) rather than a parallel hand-rolled
377
+ // check (result.error is already handled above, so this reduces to
378
+ // the `status !== 0` half).
379
+ if (spawnFailedWithNoOutput(result, output)) {
380
+ // #1816: one shared wording, one truncation, signal named.
381
+ const reason = formatToolFailure({
382
+ tool: "vulture",
383
+ status: result.status,
384
+ signal: result.signal,
385
+ stderr: result.stderr,
386
+ });
387
+ this.log(reason);
388
+ incrementDegradationCount({
389
+ kind: "runner-empty-result",
390
+ subject: "vulture",
391
+ reason,
392
+ });
218
393
  return {
219
394
  ...emptyResult(this.language),
220
- summary: `vulture error: ${stderr.split("\n")[0]}`,
395
+ summary: stderr ? `vulture error: ${firstOutputLine(stderr)}` : reason,
221
396
  durationMs,
222
397
  };
223
398
  }
@@ -258,30 +433,52 @@ export function deadCodeIssueCount(result) {
258
433
  result.unusedDeps.length +
259
434
  result.unlistedDeps.length);
260
435
  }
436
+ /** Every bucket flattened — delta diffing and diagnostic mapping want one list. */
437
+ export function deadCodeIssues(result) {
438
+ return [
439
+ ...result.unusedExports,
440
+ ...result.unusedFiles,
441
+ ...result.unusedDeps,
442
+ ...result.unlistedDeps,
443
+ ];
444
+ }
261
445
  /**
262
- * Format cached dead-code results into one turn_end advisory, or "" when there
263
- * is nothing to report. Merges multiple languages (polyglot repos) under one
264
- * `[Dead code]` heading so it reads consistently next to the Knip advisory.
265
- * Advisory-only: these are project-wide signals, never blockers.
446
+ * Joins finding-identity parts into a stable key for diffing one scan
447
+ * against the previous one. The string-only signature is load-bearing: a
448
+ * line/column number cannot be passed without a compile error. An edit
449
+ * shifts every line below it, and a scan delta is then filtered to exactly
450
+ * the files the edit touched — so a line in the key turns each shifted
451
+ * pre-existing finding into a "newly unused" report under a heading that
452
+ * blames the agent's own edit for orphaning it. Inserting four lines above
453
+ * one real finding produced four false ones (#1477, #1483). Shared by
454
+ * knip's per-turn delta in runtime-turn.ts so the rule lives in one place.
266
455
  */
267
- export function formatDeadCodeAdvisory(results, maxPerLang = 10) {
268
- const withFindings = results.filter((r) => r.success && deadCodeIssueCount(r) > 0);
269
- if (withFindings.length === 0)
456
+ export function stableFindingKey(...parts) {
457
+ return parts.map((part) => part ?? "").join(":");
458
+ }
459
+ /** Stable identity for diffing one scan against the previous one. */
460
+ export function deadCodeIssueKey(issue) {
461
+ return stableFindingKey(issue.category, issue.file, issue.name);
462
+ }
463
+ /**
464
+ * Format this turn's ATTRIBUTABLE delta — symbols that became unused in files
465
+ * the agent just edited — or "" when there is nothing to report. Mirrors the
466
+ * Knip advisory's contract deliberately: the project-wide list is not injected
467
+ * per turn (hundreds of pre-existing findings would drown the blockers and burn
468
+ * context every turn), it stays available on demand via lens_diagnostics.
469
+ */
470
+ export function formatDeadCodeDelta(issues, language, max = 5) {
471
+ if (issues.length === 0)
270
472
  return "";
271
- const lines = [];
272
- for (const r of withFindings) {
273
- const count = deadCodeIssueCount(r);
274
- lines.push(`${r.language}: ${count} unused symbol(s)`);
275
- for (const issue of r.unusedExports.slice(0, maxPerLang)) {
276
- const loc = issue.file
277
- ? ` (${issue.file}${issue.line ? `:${issue.line}` : ""})`
278
- : "";
279
- lines.push(` - unused ${issue.kind} '${issue.name}'${loc}`);
280
- }
281
- if (r.unusedExports.length > maxPerLang) {
282
- lines.push(` … and ${r.unusedExports.length - maxPerLang} more`);
283
- }
473
+ let report = `💀 Newly unused ${language} symbols in files you edited — check if callers need updating (dead-code):\n`;
474
+ for (const issue of issues.slice(0, max)) {
475
+ const loc = issue.file
476
+ ? `${issue.file}${issue.line ? `:${issue.line}` : ""}`
477
+ : "(unknown)";
478
+ report += ` ${loc} — unused ${issue.kind} ${issue.name}\n`;
479
+ }
480
+ if (issues.length > max) {
481
+ report += ` … and ${issues.length - max} more\n`;
284
482
  }
285
- return ("💀 [Dead code] project-wide unused symbols — verify before removing:\n " +
286
- lines.join("\n "));
483
+ return report;
287
484
  }