@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

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 (385) hide show
  1. package/README.md +8 -6
  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 +320 -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 +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -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 +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  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 +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -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 +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  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 +15 -1
  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-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -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,28 @@
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
+ import { getSinkWriteFailures, resetSinkWriteFailures } from "./ndjson-logger.js";
6
+ // Re-exported so existing importers keep one name for the ledger's bound.
7
+ export { LEDGER_FIELD_MAX, truncateForLedger };
3
8
  const ENTRIES_PER_KIND = 20;
4
9
  const MAX_DISTINCT_KINDS = 32;
5
10
  const OVERFLOW_KIND = "other";
6
11
  const groups = new Map();
7
12
  const onceKeys = new Set();
8
13
  const tallies = new Map();
14
+ // Monotonic session-boundary counter (#1536 review F5): callers that keep
15
+ // their OWN once-per-session latch outside the ledger (a per-instance Set
16
+ // the ledger itself doesn't own) can compare this lazily at use time and
17
+ // clear their latch on a mismatch — the same clear-on-transition shape as
18
+ // project-trust.ts's trustGeneration, but keyed to the ledger's own reset
19
+ // (resetDegradationLedger, wired into handleSessionStart) rather than a
20
+ // trust change.
21
+ let ledgerGeneration = 0;
22
+ /** Current session generation. Bump on every `resetDegradationLedger()`. */
23
+ export function getDegradationLedgerGeneration() {
24
+ return ledgerGeneration;
25
+ }
9
26
  export function recordDegradation(record) {
10
27
  try {
11
28
  const kind = boundedKind(record.kind);
@@ -16,6 +33,7 @@ export function recordDegradation(record) {
16
33
  group = { count: 0, entries: [] };
17
34
  groups.set(kind, group);
18
35
  }
36
+ const admitted = group.entries.length < ENTRIES_PER_KIND;
19
37
  group.count += 1;
20
38
  // Bounded at RECORD time (#1366 review): reasons carry arbitrary error
21
39
  // text; a 10KB message must never become a 10KB health line or a 10KB
@@ -23,10 +41,12 @@ export function recordDegradation(record) {
23
41
  group.entries.push({ subject, reason });
24
42
  if (group.entries.length > ENTRIES_PER_KIND)
25
43
  group.entries.shift();
44
+ return admitted;
26
45
  }
27
46
  catch (error) {
28
47
  debugLedgerFailure("record", error);
29
48
  // Telemetry must never break the observed path.
49
+ return false;
30
50
  }
31
51
  }
32
52
  /** Record at most once per kind/subject during the current session. */
@@ -38,7 +58,9 @@ export function recordDegradationOnce(record) {
38
58
  if (onceKeys.has(key))
39
59
  return;
40
60
  onceKeys.add(key);
41
- recordDegradation({ kind, subject, reason: record.reason });
61
+ if (recordDegradation({ kind, subject, reason: record.reason })) {
62
+ logDurableDegradation(kind, subject, 1);
63
+ }
42
64
  }
43
65
  catch (error) {
44
66
  debugLedgerFailure("record-once", error);
@@ -48,6 +70,14 @@ export function recordDegradationOnce(record) {
48
70
  /**
49
71
  * Count a repeated degradation while retaining one latest-reason entry per
50
72
  * kind/subject. The group count remains the exact event total.
73
+ *
74
+ * Returns `true` when this call is the FIRST occurrence recorded for this
75
+ * kind/subject pair (the ledger is the single source of truth for that
76
+ * tally already — via `tallies` — so callers that need a once-per-subject
77
+ * "rising edge" signal, e.g. to gate a verbose one-time log line before
78
+ * falling back to the bounded count, read it off this return value instead
79
+ * of hand-rolling their own parallel `Set`/latch). #1716 reuses this same
80
+ * signal to gate `navRequest`'s detailed timeout/late-answer log writes.
51
81
  */
52
82
  export function incrementDegradationCount(record) {
53
83
  try {
@@ -62,24 +92,49 @@ export function incrementDegradationCount(record) {
62
92
  group = { count: 0, entries: [] };
63
93
  groups.set(kind, group);
64
94
  }
65
- group.count += 1;
66
- const entry = { subject, reason: truncateForLedger(`${reason} (count: ${count})`) };
67
95
  const existing = group.entries.findIndex((candidate) => candidate.subject === subject);
96
+ const admitted = existing >= 0 || group.entries.length < ENTRIES_PER_KIND;
97
+ group.count += 1;
98
+ // #1816: append the count AFTER truncation, never before. `reason` is
99
+ // already bounded above, so re-truncating the concatenation pushed the
100
+ // suffix past LEDGER_FIELD_MAX and silently ate it — a 200-char reason
101
+ // lost the one field that says how often the degradation fired.
102
+ const entry = { subject, reason: `${reason} (count: ${count})` };
68
103
  if (existing >= 0)
69
104
  group.entries.splice(existing, 1);
70
105
  group.entries.push(entry);
71
106
  if (group.entries.length > ENTRIES_PER_KIND)
72
107
  group.entries.shift();
108
+ // Durable rows use the summary's admission and emit the first event and
109
+ // power-of-two milestones only, keeping the sink bounded.
110
+ if (admitted && isPowerOfTwo(count)) {
111
+ logDurableDegradation(kind, subject, count);
112
+ }
113
+ return count === 1;
73
114
  }
74
115
  catch (error) {
75
116
  debugLedgerFailure("increment", error);
76
117
  // Telemetry must never break the observed path.
118
+ return false;
77
119
  }
78
120
  }
79
- /** Detached snapshot, grouped in first-seen kind order. */
80
- const LEDGER_FIELD_MAX = 200;
81
- function normalizeForLedger(value) {
82
- return String(value ?? "unknown");
121
+ /**
122
+ * Persist the accepted ledger mutation through the existing rotated NDJSON
123
+ * latency stream. `logLatency` owns the timestamp, PID, serialization, secret
124
+ * redaction, and write queue. The subject and kind were already bounded by the
125
+ * ledger before reaching this seam.
126
+ */
127
+ function logDurableDegradation(kind, subject, count) {
128
+ logLatency({
129
+ type: "phase",
130
+ phase: "degradation_ledger",
131
+ filePath: subject,
132
+ durationMs: 0,
133
+ metadata: { kind, subject, count, ledgerGeneration },
134
+ });
135
+ }
136
+ function isPowerOfTwo(value) {
137
+ return value > 0 && (value & (value - 1)) === 0;
83
138
  }
84
139
  function boundedKind(value) {
85
140
  const kind = truncateForLedger(value);
@@ -88,19 +143,29 @@ function boundedKind(value) {
88
143
  // Keep one slot available for all kinds beyond the cardinality bound.
89
144
  return groups.size < MAX_DISTINCT_KINDS - 1 ? kind : OVERFLOW_KIND;
90
145
  }
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
146
  export function getDegradationSummary() {
98
- return [...groups.entries()].map(([kind, group]) => ({
147
+ const summary = [...groups.entries()].map(([kind, group]) => ({
99
148
  kind,
100
149
  count: group.count,
101
150
  droppedCount: group.count - group.entries.length,
102
151
  latestReasons: group.entries.map((entry) => ({ ...entry })),
103
152
  }));
153
+ // Folded in at read time, not written into `groups` (#1970) — see the
154
+ // `log-sink-write-failure` doc comment on `DegradationKind` for why this
155
+ // kind never goes through `recordDegradation`.
156
+ const sinkFailures = getSinkWriteFailures();
157
+ if (sinkFailures.length > 0) {
158
+ summary.push({
159
+ kind: "log-sink-write-failure",
160
+ count: sinkFailures.reduce((total, sink) => total + sink.droppedCount, 0),
161
+ droppedCount: 0,
162
+ latestReasons: sinkFailures.map((sink) => ({
163
+ subject: truncateForLedger(sink.file),
164
+ reason: truncateForLedger(`${sink.droppedCount} dropped write(s) after reopen-retry failed`),
165
+ })),
166
+ });
167
+ }
168
+ return summary;
104
169
  }
105
170
  function isRenderableSummary(value) {
106
171
  if (!Array.isArray(value))
@@ -148,6 +213,11 @@ export function resetDegradationLedger() {
148
213
  groups.clear();
149
214
  onceKeys.clear();
150
215
  tallies.clear();
216
+ ledgerGeneration++;
217
+ // #1970, catalog shape 17: the sink write-failure tally is a
218
+ // process-lifetime latch too — it re-arms alongside the rest of the
219
+ // ledger rather than surviving past the session that observed it.
220
+ resetSinkWriteFailures();
151
221
  }
152
222
  export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
153
223
  export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Body degradation for a DEMOTED finding (#1944).
3
+ *
4
+ * The demotion gates (#1631 dependency-drift, #1641 past-EOF) move a record
5
+ * out of the authoritative blocker channel and into the advisory channel.
6
+ * Until this module existed they changed the CHANNEL and nothing else: the
7
+ * advisory embedded the blocker body verbatim, so the agent still read
8
+ * "🔴 STOP — 11 issue(s) must be fixed" with dead line numbers under two
9
+ * hedge lines it ignored. Live evidence (session 01a0234c, 2026-08-21): the
10
+ * agent spent four re-run detours and about 7.4k tokens on a record whose
11
+ * coordinates no longer existed, then explained an unrelated REAL blocker
12
+ * away as "mid-edit state" in the same session.
13
+ *
14
+ * The rule this module owns, once, for every gated surface: a demoted body
15
+ * loses its authority vocabulary and its dead coordinates, and it says so in
16
+ * its own words. `clients/finding-delivery-gate.ts` documents the surrounding
17
+ * contract; this is the one implementation of its "Demotion rendering" arm
18
+ * for surfaces that carry a RENDERED BODY rather than a per-diagnostic row.
19
+ * Per-row surfaces (`widget-state.ts`, `tools/lens-diagnostics.ts`) already
20
+ * swap the coordinate for `PAST_EOF_STALE_MARKER` at render time and have no
21
+ * body to degrade.
22
+ *
23
+ * Pure and leaf: no store, no coordinator, no I/O. The caller supplies the
24
+ * dead lines it already computed while deciding to demote, so this module
25
+ * never re-derives a verdict from rendered prose (the
26
+ * re-derivation-vs-correlation screen).
27
+ */
28
+ /** Suffix that replaces a cited coordinate the file no longer has. */
29
+ export const DEAD_LINE_ANNOTATION = "(line no longer exists)";
30
+ /**
31
+ * Authority vocabulary a demoted body must not carry, with its degraded
32
+ * replacement. Ordered: the full banner rewrite runs first so the narrower
33
+ * fallbacks only ever see text the banner rule did not already cover.
34
+ *
35
+ * This table is the single source of truth for the rule. A surface must not
36
+ * hand-roll its own "strip the STOP" pass — that is how #1631, #1641, and
37
+ * #1664 each ended up with a different answer to the same question.
38
+ *
39
+ * POSITION-ANCHORED (#1944 review F1). These patterns run over the BANNER
40
+ * region only, never the whole body. Diagnostic messages are arbitrary text
41
+ * from arbitrary runners, and they legitimately contain both tokens: the
42
+ * reviewer's probe turned ts(2304) `Cannot find name 'STOP'` into `Cannot
43
+ * find name 'Previously flagged'`, and mangled an English "must be fixed"
44
+ * inside a rule message. Rewriting a finding's own words is a worse defect
45
+ * than the banner it was cleaning up. See `splitBannerRegion`.
46
+ */
47
+ const AUTHORITY_REWRITES = [
48
+ [
49
+ /[^\S\n]*(?:🔴|⛔|❌)?[^\S\n]*STOP[^\S\n]*[—–-][^\S\n]*(\d+)[^\S\n]+issue\(s\)[^\S\n]+must be fixed:?/g,
50
+ "$1 issue(s) were flagged before this file changed; the coordinates below may no longer exist:",
51
+ ],
52
+ [/(?:🔴|⛔|❌)[^\S\n]*STOP\b[^\S\n]*[—–-]?/g, "Previously flagged —"],
53
+ [/\bSTOP\b/g, "Previously flagged"],
54
+ [/\bmust be fixed\b/g, "were flagged"],
55
+ ];
56
+ /** ` L310: message` / `L310 message` at the head of a rendered row. */
57
+ const CITED_LINE_RE = /^([^\S\n]*)L(\d+)\b/;
58
+ /**
59
+ * Split a rendered body into its BANNER region and its CONTENT region
60
+ * (#1944 review F1).
61
+ *
62
+ * The body this module degrades is `InlineBlockerRecord.summary`, and its one
63
+ * producer is `formatDiagnostics(inlineBlockers, "blocking")`
64
+ * (`clients/dispatch/utils/format-utils.ts`), reaching the store as
65
+ * `dispatchResult.blockerOutput` -> `PipelineResult.inlineBlockerSummary` ->
66
+ * `recordInlineBlockers`. (`buildEnrichedBlockerOutput` in
67
+ * `clients/pipeline.ts` renders the same shape with code snippets, but it
68
+ * feeds the TOOL-RESULT output shown inline during the write, not this
69
+ * stored summary. #1944 review round: naming it here as a producer was
70
+ * wrong.) The shape is a header, then one ` L<n>: <message>` row per
71
+ * diagnostic, then indented continuations (`💡 Fix: …`) and an optional
72
+ * `… and N more` tail. So the first cited-line row is the boundary:
73
+ * everything above it is ours to rewrite, everything from it down is the
74
+ * findings' own words.
75
+ *
76
+ * A body with no cited row at all — `formatDiagnostics` omits `L<n>` for a
77
+ * lineless diagnostic — falls back to the first NON-BLANK line. Leading
78
+ * blanks must be skipped rather than counted (#1944 verify round, Edge-D):
79
+ * `formatDiagnostics` opens its output with a newline, so a naive "first
80
+ * line is the banner" rule degrades an empty string and leaves the real
81
+ * header at full authority. Production is currently saved from that only by
82
+ * an undocumented `.trim()` at `clients/pipeline.ts` (`inlineBlockerSummary`),
83
+ * and this module claims to own the rule, so it must hold on its own input.
84
+ */
85
+ function splitBannerRegion(lines) {
86
+ const firstCitedRow = lines.findIndex((line) => CITED_LINE_RE.test(line));
87
+ if (firstCitedRow !== -1)
88
+ return { bannerEnd: firstCitedRow };
89
+ const firstNonBlank = lines.findIndex((line) => line.trim() !== "");
90
+ return { bannerEnd: firstNonBlank === -1 ? 0 : firstNonBlank + 1 };
91
+ }
92
+ /**
93
+ * Degrade one demoted finding's rendered body.
94
+ *
95
+ * Two independent transforms, deliberately separable so each can be proven by
96
+ * its own test (and so deleting either one reds a different assertion):
97
+ *
98
+ * 1. Authority vocabulary: the STOP banner and the "must be fixed"
99
+ * imperative are rewritten to past-tense, non-directive wording.
100
+ * 2. Dead coordinates: every cited `L<n>` whose line the file no longer has
101
+ * renders as `L<n> (line no longer exists)`. The message text stays —
102
+ * demote, never drop (#1419).
103
+ */
104
+ export function degradeDemotedFindingBody(summary, options = {}) {
105
+ const deadLines = new Set(options.deadLines ?? []);
106
+ let authorityMarkersRemoved = 0;
107
+ const sourceLines = summary.split("\n");
108
+ const { bannerEnd } = splitBannerRegion(sourceLines);
109
+ const degradedLines = sourceLines.map((line, index) => {
110
+ // Content rows keep every character. Only the banner region is ours.
111
+ if (index >= bannerEnd)
112
+ return line;
113
+ let rewritten = line;
114
+ for (const [pattern, replacement] of AUTHORITY_REWRITES) {
115
+ rewritten = rewritten.replace(pattern, (...args) => {
116
+ authorityMarkersRemoved += 1;
117
+ // Expand `$n` against this match's capture groups by hand. A nested
118
+ // `String.replace` with the SAME global regex would clobber the
119
+ // outer call's `lastIndex` mid-iteration.
120
+ return replacement.replace(/\$(\d)/g, (_, digit) => String(args[Number(digit)] ?? ""));
121
+ });
122
+ }
123
+ return rewritten;
124
+ });
125
+ let body = degradedLines.join("\n");
126
+ const deadLinesAnnotated = [];
127
+ if (deadLines.size > 0) {
128
+ body = degradedLines
129
+ .map((line) => {
130
+ const hit = CITED_LINE_RE.exec(line);
131
+ if (!hit)
132
+ return line;
133
+ const cited = Number.parseInt(hit[2] ?? "", 10);
134
+ if (!Number.isFinite(cited) || !deadLines.has(cited))
135
+ return line;
136
+ deadLinesAnnotated.push(cited);
137
+ return `${hit[1] ?? ""}L${cited} ${DEAD_LINE_ANNOTATION}${line.slice(hit[0].length)}`;
138
+ })
139
+ .join("\n");
140
+ }
141
+ return { body, authorityMarkersRemoved, deadLinesAnnotated };
142
+ }
143
+ /**
144
+ * The one-line note a surface appends when it RETIRES a demoted finding after
145
+ * its single degraded delivery. Rendered inside the advisory itself, so an
146
+ * empty advisory section can only ever mean "nothing to say" — if something
147
+ * was dropped, this line is in the payload (#1944 AC3).
148
+ */
149
+ export function formatRetirementNote(deadLines) {
150
+ const cited = deadLines.length > 0
151
+ ? ` (cited ${deadLines.length === 1 ? "line" : "lines"} ${deadLines.join(", ")})`
152
+ : "";
153
+ return (`Retired after this delivery${cited}: the file no longer has these lines, ` +
154
+ "so this finding cannot be re-confirmed and will not be served again.");
155
+ }