@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
@@ -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.",
@@ -12,10 +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";
18
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";
19
24
  function emptyResult(language) {
20
25
  return {
21
26
  success: false,
@@ -28,17 +33,18 @@ function emptyResult(language) {
28
33
  }
29
34
  const ANALYSIS_TIMEOUT_MS = 30_000;
30
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/*"];
31
45
  const VULTURE_EXCLUDES = [
32
- "*/.venv/*",
33
- "*/venv/*",
34
- "*/.tox/*",
35
- "*/build/*",
36
- "*/dist/*",
37
- "*/node_modules/*",
38
- "*/.git/*",
39
- "*/site-packages/*",
40
- "*/__pycache__/*",
41
- "*/.eggs/*",
46
+ ...getScratchTreeFnmatchPatterns(),
47
+ ...VULTURE_PYTHON_ONLY_EXCLUDES,
42
48
  ];
43
49
  // Decorators whose target is called by a framework, never by name in the tree.
44
50
  // vulture cannot see those call sites, so every such symbol is a permanent
@@ -145,13 +151,13 @@ export class PythonDeadCodeClient {
145
151
  "tox.ini",
146
152
  ], { boundaries: [".git", ".hg", ".svn"], homeDir: homeDirOverride });
147
153
  }
148
- async ensureAvailable() {
154
+ async ensureAvailable(root) {
149
155
  const memo = this.availabilityLatch.read();
150
156
  if (memo !== null)
151
157
  return memo;
152
158
  if (this.ensureInFlight)
153
159
  return this.ensureInFlight;
154
- this.ensureInFlight = this.doEnsureAvailable();
160
+ this.ensureInFlight = this.doEnsureAvailable(root);
155
161
  try {
156
162
  return await this.ensureInFlight;
157
163
  }
@@ -159,15 +165,39 @@ export class PythonDeadCodeClient {
159
165
  this.ensureInFlight = null;
160
166
  }
161
167
  }
162
- 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) {
163
191
  // Presence-gated, NOT auto-installed. vulture is a pure-Python package
164
192
  // with no standalone binary, so "auto-install" would mean `pip install`
165
193
  // into whatever Python environment happens to be active — wrong and
166
194
  // intrusive for uv / poetry / conda / pipx users. So we only use vulture
167
- // when the user already has it, probing both the `vulture` console script
168
- // and `python -m vulture` (the script dir is frequently not on PATH even
169
- // 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.
170
199
  const candidates = [
200
+ ...(root ? this.venvCandidates(root) : []),
171
201
  { cmd: "vulture", prefix: [] },
172
202
  { cmd: "python", prefix: ["-m", "vulture"] },
173
203
  { cmd: "python3", prefix: ["-m", "vulture"] },
@@ -183,6 +213,10 @@ export class PythonDeadCodeClient {
183
213
  // numbers.
184
214
  const sweepStartedAt = Date.now();
185
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;
186
220
  for (const c of candidates) {
187
221
  const sampler = startHostStallSampler();
188
222
  const startedAt = Date.now();
@@ -213,7 +247,11 @@ export class PythonDeadCodeClient {
213
247
  });
214
248
  return true;
215
249
  }
216
- const classified = classifyProbeFailure(probe, { hostStallMs });
250
+ const classified = classifyProbeFailure(probe, {
251
+ hostStallMs,
252
+ command: c.cmd,
253
+ });
254
+ sweepEvidence = classified.evidence;
217
255
  if (classified.outcome === "transient") {
218
256
  sawTransient = true;
219
257
  transientCause = classified.cause;
@@ -232,6 +270,8 @@ export class PythonDeadCodeClient {
232
270
  latched: false,
233
271
  retryAfterMs,
234
272
  budgetMs: 5000,
273
+ classifiedBy: "probe",
274
+ ...(sweepEvidence !== undefined && { evidence: sweepEvidence }),
235
275
  });
236
276
  return false;
237
277
  }
@@ -246,6 +286,9 @@ export class PythonDeadCodeClient {
246
286
  hostStallMs: sweepHostStallMs,
247
287
  latched: true,
248
288
  budgetMs: 5000,
289
+ // Every candidate probe was classified, and none was transient (#1500).
290
+ classifiedBy: "probe",
291
+ ...(sweepEvidence !== undefined && { evidence: sweepEvidence }),
249
292
  });
250
293
  return false;
251
294
  }
@@ -258,7 +301,7 @@ export class PythonDeadCodeClient {
258
301
  summary: "No Python project root found; vulture skipped",
259
302
  };
260
303
  }
261
- if (!(await this.ensureAvailable())) {
304
+ if (!(await this.ensureAvailable(root))) {
262
305
  return {
263
306
  ...emptyResult(this.language),
264
307
  success: true,
@@ -273,14 +316,35 @@ export class PythonDeadCodeClient {
273
316
  this.inFlight.set(key, promise);
274
317
  return promise;
275
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
+ }
276
336
  async runAnalyze(root) {
277
337
  const startMs = Date.now();
278
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);
279
343
  const args = [
280
344
  ...invocation.prefix,
281
345
  ".",
282
- `--min-confidence=${this.minConfidence}`,
283
- `--exclude=${VULTURE_EXCLUDES.join(",")}`,
346
+ ...(hasConfig ? [] : [`--min-confidence=${this.minConfidence}`]),
347
+ ...(hasConfig ? [] : [`--exclude=${VULTURE_EXCLUDES.join(",")}`]),
284
348
  `--ignore-decorators=${VULTURE_IGNORE_DECORATORS.join(",")}`,
285
349
  ];
286
350
  const result = await safeSpawnAsync(invocation.cmd, args, {
@@ -299,14 +363,36 @@ export class PythonDeadCodeClient {
299
363
  }
300
364
  // vulture writes parse/usage errors to stderr and exits 1 with no
301
365
  // stdout findings; distinguish that from "found dead code" (exit 1 WITH
302
- // 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.
303
372
  const output = result.stdout || "";
304
373
  if (!output.trim()) {
305
374
  const stderr = (result.stderr || "").trim();
306
- 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
+ });
307
393
  return {
308
394
  ...emptyResult(this.language),
309
- summary: `vulture error: ${stderr.split("\n")[0]}`,
395
+ summary: stderr ? `vulture error: ${firstOutputLine(stderr)}` : reason,
310
396
  durationMs,
311
397
  };
312
398
  }
@@ -357,16 +443,22 @@ export function deadCodeIssues(result) {
357
443
  ];
358
444
  }
359
445
  /**
360
- * Stable identity for diffing one scan against the previous one.
361
- *
362
- * Deliberately excludes the line number. An edit shifts every line below it,
363
- * and the delta is then filtered to exactly the files the edit touched — so a
364
- * line in the key turns each shifted pre-existing finding into a "newly unused"
365
- * report under a heading that blames the agent's own edit for orphaning it.
366
- * Inserting four lines above one real finding produced four false ones.
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.
367
455
  */
456
+ export function stableFindingKey(...parts) {
457
+ return parts.map((part) => part ?? "").join(":");
458
+ }
459
+ /** Stable identity for diffing one scan against the previous one. */
368
460
  export function deadCodeIssueKey(issue) {
369
- return `${issue.category}:${issue.file ?? ""}:${issue.name}`;
461
+ return stableFindingKey(issue.category, issue.file, issue.name);
370
462
  }
371
463
  /**
372
464
  * Format this turn's ATTRIBUTABLE delta — symbols that became unused in files
@@ -38,8 +38,15 @@ export function withDeadline(promise, options) {
38
38
  const onReject = options.onReject ?? "propagate";
39
39
  const ms = options.ms ??
40
40
  (options.deadlineAt !== undefined ? options.deadlineAt - Date.now() : 0);
41
- // Past deadline / non-positive budget: settle immediately, no timer.
41
+ // Past deadline / non-positive budget: settle immediately, no timer. This
42
+ // branch returns before the race below ever runs, so — same reason as the
43
+ // loser-leg catch a few lines down — `promise` needs its own no-op catch
44
+ // here too: without it, a `promise` that eventually rejects (the caller
45
+ // already invoked it; this function only decides how long to wait on it)
46
+ // surfaces as an unhandled rejection instead of being silently superseded
47
+ // by the immediate timeout/undefined settlement.
42
48
  if (ms <= 0) {
49
+ promise.catch(() => { });
43
50
  return onTimeout === "undefined"
44
51
  ? Promise.resolve(undefined)
45
52
  : Promise.reject(new Error(`Timeout after ${Math.max(0, ms)}ms`));
@@ -140,6 +140,11 @@ export function flushDebugHandlesLog() {
140
140
  }
141
141
  function safeGetActiveResourcesInfo() {
142
142
  try {
143
+ // SAFETY: `process.getActiveResourcesInfo` is a real Node API (>=17.3)
144
+ // that @types/node does not declare on `Process`. The cast only widens
145
+ // the type to say the method MIGHT be there; the `typeof fn ===
146
+ // "function"` guard on the next line is what actually decides, so a
147
+ // runtime without it returns [] rather than throwing.
143
148
  const fn = process.getActiveResourcesInfo;
144
149
  return typeof fn === "function" ? fn.call(process) : [];
145
150
  }
@@ -1,11 +1,27 @@
1
1
  /** Bounded, process-local telemetry for behavior degraded during one session. */
2
2
  import { logExtension } from "./extension-log.js";
3
+ import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
4
+ import { logLatency } from "./latency-logger.js";
5
+ // Re-exported so existing importers keep one name for the ledger's bound.
6
+ export { LEDGER_FIELD_MAX, truncateForLedger };
3
7
  const ENTRIES_PER_KIND = 20;
4
8
  const MAX_DISTINCT_KINDS = 32;
5
9
  const OVERFLOW_KIND = "other";
6
10
  const groups = new Map();
7
11
  const onceKeys = new Set();
8
12
  const tallies = new Map();
13
+ // Monotonic session-boundary counter (#1536 review F5): callers that keep
14
+ // their OWN once-per-session latch outside the ledger (a per-instance Set
15
+ // the ledger itself doesn't own) can compare this lazily at use time and
16
+ // clear their latch on a mismatch — the same clear-on-transition shape as
17
+ // project-trust.ts's trustGeneration, but keyed to the ledger's own reset
18
+ // (resetDegradationLedger, wired into handleSessionStart) rather than a
19
+ // trust change.
20
+ let ledgerGeneration = 0;
21
+ /** Current session generation. Bump on every `resetDegradationLedger()`. */
22
+ export function getDegradationLedgerGeneration() {
23
+ return ledgerGeneration;
24
+ }
9
25
  export function recordDegradation(record) {
10
26
  try {
11
27
  const kind = boundedKind(record.kind);
@@ -16,6 +32,7 @@ export function recordDegradation(record) {
16
32
  group = { count: 0, entries: [] };
17
33
  groups.set(kind, group);
18
34
  }
35
+ const admitted = group.entries.length < ENTRIES_PER_KIND;
19
36
  group.count += 1;
20
37
  // Bounded at RECORD time (#1366 review): reasons carry arbitrary error
21
38
  // text; a 10KB message must never become a 10KB health line or a 10KB
@@ -23,10 +40,12 @@ export function recordDegradation(record) {
23
40
  group.entries.push({ subject, reason });
24
41
  if (group.entries.length > ENTRIES_PER_KIND)
25
42
  group.entries.shift();
43
+ return admitted;
26
44
  }
27
45
  catch (error) {
28
46
  debugLedgerFailure("record", error);
29
47
  // Telemetry must never break the observed path.
48
+ return false;
30
49
  }
31
50
  }
32
51
  /** Record at most once per kind/subject during the current session. */
@@ -38,7 +57,9 @@ export function recordDegradationOnce(record) {
38
57
  if (onceKeys.has(key))
39
58
  return;
40
59
  onceKeys.add(key);
41
- recordDegradation({ kind, subject, reason: record.reason });
60
+ if (recordDegradation({ kind, subject, reason: record.reason })) {
61
+ logDurableDegradation(kind, subject, 1);
62
+ }
42
63
  }
43
64
  catch (error) {
44
65
  debugLedgerFailure("record-once", error);
@@ -48,6 +69,14 @@ export function recordDegradationOnce(record) {
48
69
  /**
49
70
  * Count a repeated degradation while retaining one latest-reason entry per
50
71
  * kind/subject. The group count remains the exact event total.
72
+ *
73
+ * Returns `true` when this call is the FIRST occurrence recorded for this
74
+ * kind/subject pair (the ledger is the single source of truth for that
75
+ * tally already — via `tallies` — so callers that need a once-per-subject
76
+ * "rising edge" signal, e.g. to gate a verbose one-time log line before
77
+ * falling back to the bounded count, read it off this return value instead
78
+ * of hand-rolling their own parallel `Set`/latch). #1716 reuses this same
79
+ * signal to gate `navRequest`'s detailed timeout/late-answer log writes.
51
80
  */
52
81
  export function incrementDegradationCount(record) {
53
82
  try {
@@ -62,24 +91,49 @@ export function incrementDegradationCount(record) {
62
91
  group = { count: 0, entries: [] };
63
92
  groups.set(kind, group);
64
93
  }
65
- group.count += 1;
66
- const entry = { subject, reason: truncateForLedger(`${reason} (count: ${count})`) };
67
94
  const existing = group.entries.findIndex((candidate) => candidate.subject === subject);
95
+ const admitted = existing >= 0 || group.entries.length < ENTRIES_PER_KIND;
96
+ group.count += 1;
97
+ // #1816: append the count AFTER truncation, never before. `reason` is
98
+ // already bounded above, so re-truncating the concatenation pushed the
99
+ // suffix past LEDGER_FIELD_MAX and silently ate it — a 200-char reason
100
+ // lost the one field that says how often the degradation fired.
101
+ const entry = { subject, reason: `${reason} (count: ${count})` };
68
102
  if (existing >= 0)
69
103
  group.entries.splice(existing, 1);
70
104
  group.entries.push(entry);
71
105
  if (group.entries.length > ENTRIES_PER_KIND)
72
106
  group.entries.shift();
107
+ // Durable rows use the summary's admission and emit the first event and
108
+ // power-of-two milestones only, keeping the sink bounded.
109
+ if (admitted && isPowerOfTwo(count)) {
110
+ logDurableDegradation(kind, subject, count);
111
+ }
112
+ return count === 1;
73
113
  }
74
114
  catch (error) {
75
115
  debugLedgerFailure("increment", error);
76
116
  // Telemetry must never break the observed path.
117
+ return false;
77
118
  }
78
119
  }
79
- /** Detached snapshot, grouped in first-seen kind order. */
80
- const LEDGER_FIELD_MAX = 200;
81
- function normalizeForLedger(value) {
82
- return String(value ?? "unknown");
120
+ /**
121
+ * Persist the accepted ledger mutation through the existing rotated NDJSON
122
+ * latency stream. `logLatency` owns the timestamp, PID, serialization, secret
123
+ * redaction, and write queue. The subject and kind were already bounded by the
124
+ * ledger before reaching this seam.
125
+ */
126
+ function logDurableDegradation(kind, subject, count) {
127
+ logLatency({
128
+ type: "phase",
129
+ phase: "degradation_ledger",
130
+ filePath: subject,
131
+ durationMs: 0,
132
+ metadata: { kind, subject, count, ledgerGeneration },
133
+ });
134
+ }
135
+ function isPowerOfTwo(value) {
136
+ return value > 0 && (value & (value - 1)) === 0;
83
137
  }
84
138
  function boundedKind(value) {
85
139
  const kind = truncateForLedger(value);
@@ -88,12 +142,6 @@ function boundedKind(value) {
88
142
  // Keep one slot available for all kinds beyond the cardinality bound.
89
143
  return groups.size < MAX_DISTINCT_KINDS - 1 ? kind : OVERFLOW_KIND;
90
144
  }
91
- function truncateForLedger(value) {
92
- const text = normalizeForLedger(value);
93
- return text.length > LEDGER_FIELD_MAX
94
- ? `${text.slice(0, LEDGER_FIELD_MAX)}…`
95
- : text;
96
- }
97
145
  export function getDegradationSummary() {
98
146
  return [...groups.entries()].map(([kind, group]) => ({
99
147
  kind,
@@ -148,6 +196,7 @@ export function resetDegradationLedger() {
148
196
  groups.clear();
149
197
  onceKeys.clear();
150
198
  tallies.clear();
199
+ ledgerGeneration++;
151
200
  }
152
201
  export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
153
202
  export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;