@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

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 (360) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -56,7 +56,7 @@
56
56
  import { createHash } from "node:crypto";
57
57
  import * as fs from "node:fs";
58
58
  import * as path from "node:path";
59
- import { writeFileAtomic } from "./atomic-write.js";
59
+ import { commitDurableStore } from "./durable-store.js";
60
60
  import { logDispositionEvent } from "./disposition-logger.js";
61
61
  import { publishDisposition } from "./disposition-publish.js";
62
62
  import { getProjectDataDir } from "./file-utils.js";
@@ -140,11 +140,27 @@ function statePath(cwd) {
140
140
  return path.join(getProjectDataDir(cwd), "cache", "diagnostic-dispositions.json");
141
141
  }
142
142
  let stateCache = null;
143
+ const DISPOSITION_LOCK_WAIT_MS = 2_000;
144
+ const DISPOSITION_LOCK_RETRY_MS = 10;
145
+ let beforeDispositionCommitForTests = null;
146
+ let beforeDispositionCacheRefreshForTests = null;
147
+ let dispositionStatSync = fs.statSync;
148
+ /** Test seam after the caller's cached read and before commit lock acquisition. */
149
+ export function _setBeforeDispositionCommitForTests(hook) {
150
+ beforeDispositionCommitForTests = hook;
151
+ }
152
+ /** Test seam immediately before the committed state refreshes the cache. */
153
+ export function _setBeforeDispositionCacheRefreshForTests(hook) {
154
+ beforeDispositionCacheRefreshForTests = hook;
155
+ }
156
+ export function _setDispositionStatForTests(statSync) {
157
+ dispositionStatSync = statSync ?? fs.statSync;
158
+ }
143
159
  function readState(cwd) {
144
160
  const p = statePath(cwd);
145
161
  let stat;
146
162
  try {
147
- stat = fs.statSync(p);
163
+ stat = dispositionStatSync(p);
148
164
  }
149
165
  catch {
150
166
  if (stateCache && stateCache.path === p && stateCache.missing) {
@@ -188,6 +204,17 @@ function readState(cwd) {
188
204
  };
189
205
  return state;
190
206
  }
207
+ function deserializeState(contents) {
208
+ try {
209
+ const parsed = JSON.parse(contents ?? "");
210
+ return parsed && typeof parsed === "object"
211
+ ? parsed
212
+ : {};
213
+ }
214
+ catch {
215
+ return {};
216
+ }
217
+ }
191
218
  // Atomic tmp+rename via clients/atomic-write.ts (#762; shared with
192
219
  // instance-registry.ts / recent-touches.ts / review-graph/builder.ts): a
193
220
  // cross-process reader must never observe a partially-written file —
@@ -198,10 +225,7 @@ function readState(cwd) {
198
225
  // failure still propagates (matches the pre-atomic writeFileSync's behavior,
199
226
  // which never swallowed errors either) — a disposition mark silently vanishing
200
227
  // is a correctness bug for this store, not just a lost observability sample.
201
- function writeState(cwd, state) {
202
- const p = statePath(cwd);
203
- fs.mkdirSync(path.dirname(p), { recursive: true });
204
- writeFileAtomic(p, JSON.stringify(state, null, 2), { bestEffort: false });
228
+ function refreshStateCache(p, state) {
205
229
  // Refresh the cache from the write we just did instead of invalidating it —
206
230
  // avoids an immediate re-stat+re-parse of the file we already have in hand,
207
231
  // and guards against coarse filesystem mtime granularity making a
@@ -215,6 +239,33 @@ function writeState(cwd, state) {
215
239
  state,
216
240
  };
217
241
  }
242
+ function commitDisposition(cwd, anchor, entry) {
243
+ const p = statePath(cwd);
244
+ fs.mkdirSync(path.dirname(p), { recursive: true });
245
+ const hook = beforeDispositionCommitForTests;
246
+ beforeDispositionCommitForTests = null;
247
+ hook?.();
248
+ commitDurableStore({
249
+ path: p,
250
+ deserialize: deserializeState,
251
+ merge: (state) => {
252
+ state.dispositions ??= {};
253
+ state.dispositions[anchor] = entry;
254
+ return state;
255
+ },
256
+ serialize: (state) => JSON.stringify(state, null, 2),
257
+ waitMs: DISPOSITION_LOCK_WAIT_MS,
258
+ retryMs: DISPOSITION_LOCK_RETRY_MS,
259
+ timeoutMessage: "timed out acquiring diagnostic disposition store lock",
260
+ onContention: "throw",
261
+ afterWriteLocked: (state) => {
262
+ const cacheHook = beforeDispositionCacheRefreshForTests;
263
+ beforeDispositionCacheRefreshForTests = null;
264
+ cacheHook?.();
265
+ refreshStateCache(p, state);
266
+ },
267
+ });
268
+ }
218
269
  /** Test-only escape hatch — the state cache is module-level, so tests that
219
270
  * write the store file out-of-band (or across separate cwds sharing a stat
220
271
  * coincidence) need to reset it between cases. */
@@ -227,7 +278,7 @@ export function _resetStateCacheForTests() {
227
278
  * Neither can throw into the mark path; both are already internally
228
279
  * fail-safe, but the try/catch keeps a future regression in either from
229
280
  * breaking a mark. */
230
- function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing) {
281
+ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity) {
231
282
  try {
232
283
  logDispositionEvent({
233
284
  event: "mark",
@@ -239,6 +290,8 @@ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing) {
239
290
  reason,
240
291
  anchor,
241
292
  previousDisposition: existing?.disposition,
293
+ model: identity?.model || undefined,
294
+ provider: identity?.provider || undefined,
242
295
  });
243
296
  publishDisposition({
244
297
  cwd,
@@ -266,7 +319,7 @@ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing) {
266
319
  * hang off it, so the agent tool and any future UI caller are covered without
267
320
  * per-caller wiring.
268
321
  */
269
- export function markDisposition(cwd, target, disposition, reason) {
322
+ export function markDisposition(cwd, target, disposition, reason, identity) {
270
323
  const anchor = disposition === "false-positive"
271
324
  ? computeStrictAnchor(target)
272
325
  : computeWeakAnchor(target);
@@ -274,19 +327,17 @@ export function markDisposition(cwd, target, disposition, reason) {
274
327
  // entry can already exist at the same weak anchor (a prior flagged/suppress
275
328
  // mark) — the log should record what this mark shadowed either way.
276
329
  const existing = readState(cwd).dispositions?.[anchor];
277
- emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing);
278
330
  if (disposition === "defer") {
279
331
  deferredThisSession.add(anchor);
332
+ emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
280
333
  return anchor;
281
334
  }
282
- const state = readState(cwd);
283
- state.dispositions ??= {};
284
335
  const now = new Date().toISOString();
285
336
  const capturesFixContext = disposition === "flagged";
286
337
  const lineText = capturesFixContext
287
338
  ? (target.content?.split(/\r?\n/)[target.line !== undefined ? target.line - 1 : -1] ?? existing?.lineText)?.trim()
288
339
  : existing?.lineText;
289
- state.dispositions[anchor] = {
340
+ const entry = {
290
341
  disposition,
291
342
  reason: reason ?? existing?.reason,
292
343
  createdAt: existing?.createdAt ?? now,
@@ -294,7 +345,8 @@ export function markDisposition(cwd, target, disposition, reason) {
294
345
  line: capturesFixContext ? (target.line ?? existing?.line) : existing?.line,
295
346
  lineText,
296
347
  };
297
- writeState(cwd, state);
348
+ commitDisposition(cwd, anchor, entry);
349
+ emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
298
350
  return anchor;
299
351
  }
300
352
  export function getDisposition(cwd, anchor) {
@@ -61,12 +61,13 @@
61
61
  */
62
62
  import { logBusEvent } from "./bus-events-logger.js";
63
63
  import { normalizeFilePath } from "./path-utils.js";
64
+ import { createLiveBusEmitter, recordStaleBusFailure, resolveLiveBusEmitter, } from "./live-bus-emitter.js";
64
65
  import { isBusPublishEnabled } from "./bus-publish.js";
65
66
  export const BUS_DIAGNOSTICS_EVENT = "pilens:diagnostics";
66
67
  export const BUS_DIAGNOSTICS_VERSION = 1;
67
68
  /** Max diagnostics carried per file per event — aligned with the widget's own per-file storage cap (`MAX_STORED_DIAGNOSTICS_PER_FILE`, clients/widget-state.ts). */
68
69
  export const MAX_DIAGNOSTICS_PER_FILE_EVENT = 12;
69
- let busEmit;
70
+ const liveEmitter = createLiveBusEmitter();
70
71
  let hasLoggedFailure = false;
71
72
  let hasLoggedUnwired = false;
72
73
  let hasLoggedDisabled = false;
@@ -79,11 +80,14 @@ const reportedDirtyPaths = new Set();
79
80
  * producers share the identical `pi.events.emit` binding.
80
81
  */
81
82
  export function wireDiagnosticsBusEmitter(emitFn) {
82
- busEmit = emitFn;
83
+ liveEmitter.wire(emitFn);
84
+ }
85
+ export function wireDiagnosticsBusEmitterGetter(getter) {
86
+ liveEmitter.wireGetter(getter);
83
87
  }
84
88
  /** Test-only: reset module state between test files. */
85
89
  export function _resetDiagnosticsPublishForTests() {
86
- busEmit = undefined;
90
+ liveEmitter.reset();
87
91
  hasLoggedFailure = false;
88
92
  hasLoggedUnwired = false;
89
93
  hasLoggedDisabled = false;
@@ -145,7 +149,13 @@ export function publishDiagnostics(args) {
145
149
  }
146
150
  return;
147
151
  }
148
- if (!busEmit) {
152
+ const resolution = resolveLiveBusEmitter(liveEmitter, () => ({
153
+ event: BUS_DIAGNOSTICS_EVENT,
154
+ cwd: normalizeFilePath(args.cwd),
155
+ }));
156
+ if (resolution.outcome === "stale-session")
157
+ return;
158
+ if (resolution.outcome === "unwired") {
149
159
  if (!hasLoggedUnwired) {
150
160
  hasLoggedUnwired = true;
151
161
  logBusEvent({
@@ -156,6 +166,7 @@ export function publishDiagnostics(args) {
156
166
  }
157
167
  return;
158
168
  }
169
+ const busEmit = resolution.emit;
159
170
  try {
160
171
  const fileEntries = args.files.map((f) => {
161
172
  const normPath = normalizeFilePath(f.path);
@@ -184,6 +195,7 @@ export function publishDiagnostics(args) {
184
195
  files: fileEntries,
185
196
  };
186
197
  busEmit(BUS_DIAGNOSTICS_EVENT, payload);
198
+ hasLoggedFailure = false;
187
199
  logBusEvent({
188
200
  event: BUS_DIAGNOSTICS_EVENT,
189
201
  outcome: "emitted",
@@ -198,9 +210,11 @@ export function publishDiagnostics(args) {
198
210
  outcome: "emit_failed",
199
211
  cwd: normalizeFilePath(args.cwd),
200
212
  error: String(err),
213
+ ctxSource: resolution.ctxSource,
201
214
  });
202
215
  if (!hasLoggedFailure) {
203
216
  hasLoggedFailure = true;
217
+ recordStaleBusFailure(BUS_DIAGNOSTICS_EVENT, err);
204
218
  args.dbg?.(`diagnostics-publish: pilens:diagnostics emit failed (further failures suppressed): ${err}`);
205
219
  }
206
220
  }
@@ -13,6 +13,7 @@
13
13
  * - OutputSemantic: How to display (blocking, warning, silent, etc.)
14
14
  * - BaselineStore: Track pre-existing issues for delta mode
15
15
  */
16
+ import { logExtension } from "../extension-log.js";
16
17
  import * as fs from "node:fs";
17
18
  import * as path from "node:path";
18
19
  import { recordRunner } from "../widget-state.js";
@@ -28,10 +29,12 @@ import { loadPiLensProjectConfig } from "../project-lens-config.js";
28
29
  import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
29
30
  import { safeSpawnAsync } from "../safe-spawn.js";
30
31
  import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
32
+ import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./runners/utils/availability-policy.js";
31
33
  import { applyDispositions } from "../diagnostic-dispositions.js";
32
34
  import { applyInlineSuppressions } from "./inline-suppressions.js";
33
35
  import { getToolPlan } from "./plan.js";
34
36
  import { resolveRunnerPath } from "./runner-context.js";
37
+ import { applyRulePolicy, rulePolicyMapFromConfig } from "./rule-policy.js";
35
38
  import { getToolProfile } from "./tool-profile.js";
36
39
  import { formatDiagnostics } from "./utils/format-utils.js";
37
40
  // --- Runner Registry ---
@@ -74,12 +77,49 @@ export function normalizeCacheKey(cmd) {
74
77
  const normalized = cmd.replace(/\.(cmd|exe)$/i, "").toLowerCase();
75
78
  return `session.toolCache.${normalized}`;
76
79
  }
77
- async function checkToolAvailability(command, facts) {
80
+ /** Probe budget for the generic `hasTool` availability check, ms. */
81
+ const TOOL_PROBE_TIMEOUT_MS = 5000;
82
+ /**
83
+ * Session fact holding the epoch ms after which a TRANSIENT verdict for a
84
+ * command may be re-probed. Kept beside the boolean fact so both share the
85
+ * session's lifetime — no second global to reset (#1476).
86
+ */
87
+ function transientRetryKey(command) {
88
+ return `${normalizeCacheKey(command)}.retryAt`;
89
+ }
90
+ /**
91
+ * Consecutive transient verdicts for a command, so the cooldown ESCALATES the
92
+ * way the policy documents (30 s, 60 s, 120 s … capped at 5 min) instead of
93
+ * sitting flat at 30 s.
94
+ *
95
+ * This is the highest-traffic availability consumer in the product. A flat
96
+ * cooldown here means a permanently sick host is re-probed every 30 s per
97
+ * command for the whole session — ten times the storm the policy claims to
98
+ * bound, and the one place the bound matters most. `createAvailabilityChecker`
99
+ * tracks the same counter; this keeps the two seams telling one story.
100
+ */
101
+ function transientAttemptsKey(command) {
102
+ return `${normalizeCacheKey(command)}.transientAttempts`;
103
+ }
104
+ /**
105
+ * Is `command` usable right now? Cached per session.
106
+ *
107
+ * Latch policy (#1467/#1476): a `false` from a genuine absence is durable and
108
+ * is remembered for the session. A `false` from a TIMED-OUT probe is not — this
109
+ * is the highest-traffic availability consumer in the codebase, and caching a
110
+ * host stall here silently disabled a healthy tool for every later dispatch in
111
+ * the session. A transient verdict instead holds only for a bounded cooldown,
112
+ * which also stops a sick host from being re-probed on every dispatch.
113
+ */
114
+ export async function checkToolAvailability(command, facts) {
78
115
  const key = normalizeCacheKey(command);
79
116
  const cached = facts.getSessionFact(key);
80
117
  if (cached !== undefined) {
81
118
  return cached;
82
119
  }
120
+ const retryAt = facts.getSessionFact(transientRetryKey(command));
121
+ if (retryAt !== undefined && Date.now() < retryAt)
122
+ return false;
83
123
  // A command that isn't even on disk can't pass a --version probe; the ~μs
84
124
  // stat/PATH walk saves a guaranteed-to-fail spawn round-trip per cold tool.
85
125
  if (!(await isSpawnableCommand(command))) {
@@ -87,12 +127,68 @@ async function checkToolAvailability(command, facts) {
87
127
  return false;
88
128
  }
89
129
  try {
90
- const result = await safeSpawnAsync(command, ["--version"], {
91
- timeout: 5000,
130
+ // The budget is enforced by a HOST-side timer, so measure the loop stall
131
+ // that overlapped the window and let the shared classifier read it.
132
+ const sampler = startHostStallSampler();
133
+ const startedAt = Date.now();
134
+ let result;
135
+ let hostStallMs;
136
+ try {
137
+ result = await safeSpawnAsync(command, ["--version"], {
138
+ timeout: TOOL_PROBE_TIMEOUT_MS,
139
+ });
140
+ }
141
+ finally {
142
+ hostStallMs = sampler.stop();
143
+ }
144
+ const elapsedMs = Date.now() - startedAt;
145
+ if (result.status === 0) {
146
+ facts.setSessionFact(key, true);
147
+ // The tool answered: retire the cooldown facts rather than leaving a
148
+ // stale retry deadline behind a `true`.
149
+ facts.setSessionFact(transientRetryKey(command), 0);
150
+ facts.setSessionFact(transientAttemptsKey(command), 0);
151
+ logAvailabilityDecision({
152
+ tool: command,
153
+ verdict: "available",
154
+ outcome: "success",
155
+ cause: "ok",
156
+ elapsedMs,
157
+ latched: true,
158
+ hostStallMs,
159
+ budgetMs: TOOL_PROBE_TIMEOUT_MS,
160
+ });
161
+ return true;
162
+ }
163
+ // `missing` for anything unclassified preserves the pre-#1476 meaning of
164
+ // a non-zero exit: durable, cached for the session.
165
+ const { outcome, cause } = classifyProbeFailure(result, {
166
+ hostStallMs,
167
+ unclassifiedFailureOutcome: "missing",
168
+ });
169
+ let retryAfterMs;
170
+ if (outcome === "transient") {
171
+ const attempts = (facts.getSessionFact(transientAttemptsKey(command)) ?? 0) + 1;
172
+ facts.setSessionFact(transientAttemptsKey(command), attempts);
173
+ retryAfterMs = transientRetryDelayMs(attempts, cause);
174
+ facts.setSessionFact(transientRetryKey(command), Date.now() + retryAfterMs);
175
+ }
176
+ else {
177
+ facts.setSessionFact(key, false);
178
+ facts.setSessionFact(transientAttemptsKey(command), 0);
179
+ }
180
+ logAvailabilityDecision({
181
+ tool: command,
182
+ verdict: "unavailable",
183
+ outcome,
184
+ cause,
185
+ elapsedMs,
186
+ latched: outcome !== "transient",
187
+ hostStallMs,
188
+ ...(retryAfterMs !== undefined && { retryAfterMs }),
189
+ budgetMs: TOOL_PROBE_TIMEOUT_MS,
92
190
  });
93
- const available = result.status === 0;
94
- facts.setSessionFact(key, available);
95
- return available;
191
+ return false;
96
192
  }
97
193
  catch {
98
194
  facts.setSessionFact(key, false);
@@ -122,9 +218,16 @@ function readFilePrefix(filePath, maxBytes = 4096) {
122
218
  }
123
219
  }
124
220
  }
125
- export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, modifiedRanges) {
221
+ export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, modifiedRanges,
222
+ /** Authoritative workspace root; `cwd` may be a nested language root. */
223
+ projectRoot,
224
+ /** Ordered per-file pipeline token, when this is a post-write dispatch. */
225
+ writeIndex,
226
+ /** Runtime telemetry identity, when known (#1448) — threaded to the
227
+ * worklog append; see DispatchContext.telemetryModel's doc. */
228
+ telemetryModel, telemetryProvider) {
126
229
  const absoluteFilePath = resolveRunnerPath(cwd, filePath);
127
- const normalizedProjectRoot = normalizeMapKey(path.resolve(cwd));
230
+ const normalizedProjectRoot = normalizeMapKey(path.resolve(projectRoot ?? cwd));
128
231
  const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
129
232
  const normalizedFilePath = normalizeMapKey(absoluteFilePath);
130
233
  const kind = detectFileKind(normalizedFilePath);
@@ -143,11 +246,20 @@ export function createDispatchContext(filePath, cwd, pi, facts, blockingOnly, mo
143
246
  projectConfig,
144
247
  blockingOnly,
145
248
  modifiedRanges,
249
+ writeIndex,
250
+ telemetryModel,
251
+ telemetryProvider,
146
252
  async hasTool(command) {
147
253
  return checkToolAvailability(command, facts);
148
254
  },
149
255
  log(message) {
150
- console.error(`[dispatch] ${message}`);
256
+ // #1333: pi owns the terminal — a runner advisory must never be a raw
257
+ // write. Every DispatchContext.log line lands in extension.log instead.
258
+ logExtension({
259
+ subsystem: "dispatch",
260
+ message,
261
+ metadata: { filePath: normalizedFilePath, kind },
262
+ });
151
263
  },
152
264
  };
153
265
  }
@@ -493,7 +605,7 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
493
605
  }
494
606
  : undefined,
495
607
  });
496
- recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration);
608
+ recordRunner(ctx.filePath, runnerId, result.status, result.diagnostics.length, duration, ctx.writeIndex);
497
609
  diagnostics.push(...result.diagnostics);
498
610
  const resultSemantic = result.semantic ?? semantic;
499
611
  if ((resultSemantic === "blocking" && result.diagnostics.length > 0) ||
@@ -563,26 +675,56 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
563
675
  // Apply delta mode ONCE across the full diagnostic set.
564
676
  // This avoids partial-baseline corruption when processing multiple groups.
565
677
  const dedupedDiagnostics = dedupeOverlappingDiagnostics(allDiagnostics);
566
- const dockerOverlapSuppressed = suppressTrivyConfigDockerOverlap(dedupedDiagnostics);
567
- const overlapSuppressed = suppressLintOverlapsWithLsp(dockerOverlapSuppressed);
568
678
  const fileContent = ctx.facts.getFileFact(ctx.filePath, "file.content") ?? "";
569
- const inlineSuppressed = applyInlineSuppressions(overlapSuppressed, fileContent);
570
- // #690: agent/user disposition layer — drop false-positive/suppress marks
571
- // and anything deferred this session. flagged marks are left in place;
572
- // lens_diagnostics tags them at render time via the same anchor.
679
+ // Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`).
573
680
  //
574
- // #1030: anchor + store MUST key off the PROJECT ROOT, not ctx.cwd. In a
575
- // monorepo ctx.cwd is the nested language root (resolveLanguageRootForFile,
576
- // used for tool/config resolution), but the mark tool writes dispositions
577
- // under runtime.projectRoot. Keying the read on ctx.cwd computed a different
578
- // anchor AND opened a different diagnostic-dispositions.json (getProjectDataDir
579
- // is keyed on cwd), so every false-positive/flagged/defer on a file under a
580
- // nested marker silently no-op'd. Read from the project root to match the write.
581
- const dispositionFiltered = applyDispositions(inlineSuppressed, ctx.projectRoot ?? ctx.cwd, ctx.filePath, fileContent);
582
- let visibleDiagnostics = dispositionFiltered;
681
+ // Resolved from `ctx.projectRoot` (falling back to `ctx.cwd`), NOT
682
+ // `ctx.projectConfig` — `ctx.projectConfig` is loaded from the nested
683
+ // LANGUAGE root (`resolveLanguageRootForFile`), so in a monorepo where a
684
+ // package directory has its own `.pi-lens.json`, `discoverPiLensProjectConfig`'s
685
+ // upward walk stops there and never sees a repo-root policy.
686
+ // `lens_diagnostics` loads its policy map from `runtime.projectRoot`; using
687
+ // the same root here keeps the two surfaces in agreement. `ctx.projectConfig`
688
+ // itself is untouched — thresholds and mutation flags keep their existing
689
+ // language-root resolution.
690
+ const rulePolicy = rulePolicyMapFromConfig(loadPiLensProjectConfig(ctx.projectRoot ?? ctx.cwd).rules);
691
+ // The output-only filter pipeline: LSP/docker overlap suppression + inline
692
+ // `pi-lens-ignore` + agent/user dispositions + project rule policy. Applied
693
+ // AFTER dedupe so the pi renderer, widget, and delta all see one filtered
694
+ // set. #690: dispositions drop false-positive/suppress marks and anything
695
+ // deferred this session (flagged marks stay; lens_diagnostics tags them at
696
+ // render time). #1030: the disposition anchor + store MUST key off the
697
+ // PROJECT ROOT, not ctx.cwd — the mark tool writes dispositions under
698
+ // runtime.projectRoot, so reading from ctx.cwd (the nested language root in
699
+ // a monorepo) opened a different diagnostic-dispositions.json and silently
700
+ // no-op'd every mark under a nested marker.
701
+ //
702
+ // #1087: "silencing is not fixing" applies to the WHOLE class, not just the
703
+ // policy member. This single pipeline is applied identically to the live set
704
+ // AND (below) to the delta baseline, so a finding persistently dropped by
705
+ // ANY layer — overlap, inline suppression, disposition, or policy — is
706
+ // absent from both sides of the delta and never oscillates into `fixed`
707
+ // (which `trackAgentFixed` would otherwise inflate forever). The stored
708
+ // baseline remains the unfiltered `dedupedDiagnostics`, so editing a
709
+ // project's policy/suppressions never resets or corrupts the user-authored
710
+ // delta baseline — the filtering is re-derived on read for both sides.
711
+ const applyOutputFilters = (diags) => {
712
+ const dockerOverlap = suppressTrivyConfigDockerOverlap(diags);
713
+ const overlap = suppressLintOverlapsWithLsp(dockerOverlap);
714
+ const inline = applyInlineSuppressions(overlap, fileContent);
715
+ const disposition = applyDispositions(inline, ctx.projectRoot ?? ctx.cwd, ctx.filePath, fileContent);
716
+ return applyRulePolicy(disposition, rulePolicy);
717
+ };
718
+ let visibleDiagnostics = applyOutputFilters(dedupedDiagnostics);
583
719
  let resolvedCount = 0;
584
720
  if (ctx.deltaMode && previousBaseline) {
585
- const filtered = filterDelta(visibleDiagnostics, previousBaseline, (d) => d.id);
721
+ // Silencing a rule is not fixing it. The stored baseline is deliberately
722
+ // unfiltered (below), so compare against a fully-filtered view of it
723
+ // through the SAME pipeline — otherwise every persistently-suppressed
724
+ // finding sits in `fixed` on every dispatch and inflates the agent's
725
+ // resolved tally (trackAgentFixed) forever. Only `fixed` changes:
726
+ // filtered-out ids are absent from `after` either way.
727
+ const filtered = filterDelta(visibleDiagnostics, applyOutputFilters(previousBaseline), (d) => d.id);
586
728
  visibleDiagnostics = promoteDeltaUnusedToBlockers(filtered.new);
587
729
  resolvedCount = filtered.fixed.length;
588
730
  }
@@ -595,11 +737,16 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
595
737
  const blockers = visibleDiagnostics.filter((d) => d.semantic === "blocking");
596
738
  const warnings = visibleDiagnostics.filter((d) => d.semantic === "warning" || d.semantic === "none");
597
739
  const fixedItems = visibleDiagnostics.filter((d) => d.semantic === "fixed");
598
- // Append fixed and fixable diagnostics to the persistent worklog
740
+ // Append fixed and fixable diagnostics to the persistent worklog, attributed
741
+ // to the runtime's active model/provider when known (#1448).
742
+ const worklogIdentity = {
743
+ model: ctx.telemetryModel,
744
+ provider: ctx.telemetryProvider,
745
+ };
599
746
  if (fixedItems.length > 0) {
600
747
  import("../fix-worklog.js")
601
748
  .then(({ appendToWorklog }) => {
602
- appendToWorklog(ctx.cwd, fixedItems, true);
749
+ appendToWorklog(ctx.cwd, fixedItems, true, worklogIdentity);
603
750
  })
604
751
  .catch(() => { });
605
752
  }
@@ -607,7 +754,7 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
607
754
  if (fixableWarnings.length > 0) {
608
755
  import("../fix-worklog.js")
609
756
  .then(({ appendToWorklog }) => {
610
- appendToWorklog(ctx.cwd, fixableWarnings, false);
757
+ appendToWorklog(ctx.cwd, fixableWarnings, false, worklogIdentity);
611
758
  })
612
759
  .catch(() => { });
613
760
  }
@@ -1,3 +1,4 @@
1
+ import { isJstsFactFile } from "../../file-kinds.js";
1
2
  import { findOwnerName } from "../../symbol-containment.js";
2
3
  import { extractFactsFromTree, firstChildOfType, walk, } from "./tree-sitter-facts.js";
3
4
  const BOUNDARY_PREFIXES = [
@@ -56,12 +57,26 @@ function getParameters(node) {
56
57
  if (!fp)
57
58
  return [];
58
59
  return (fp.children ?? []).filter((c) => c &&
59
- (c.type === "required_parameter" ||
60
+ (c.type === "identifier" ||
61
+ c.type === "required_parameter" ||
60
62
  c.type === "optional_parameter" ||
61
- c.type === "rest_pattern"));
63
+ c.type === "rest_pattern" ||
64
+ c.type === "assignment_pattern" ||
65
+ // Plain JavaScript's grammar places a top-level destructured
66
+ // parameter directly as an object_pattern/array_pattern child
67
+ // of formal_parameters — no required_parameter wrapper (the TS
68
+ // grammar always wraps every parameter, destructured or not,
69
+ // which is why the wrapper types above already covered TS).
70
+ // Without this, `function f({a, b})` counted 0 params in JS
71
+ // while the TS-annotated equivalent counted correctly (#1089 P3-4).
72
+ c.type === "object_pattern" ||
73
+ c.type === "array_pattern"));
62
74
  }
63
75
  function parameterName(param) {
64
- return firstNamedChild(param)?.text ?? "";
76
+ // JavaScript's grammar keeps a leaf parameter as the identifier itself;
77
+ // there is no named child to unwrap. TypeScript parameter wrapper nodes still
78
+ // use their first named child (type annotation/default/rest target).
79
+ return param.type === "identifier" ? param.text : firstNamedChild(param)?.text ?? "";
65
80
  }
66
81
  function getFunctionName(node) {
67
82
  if (node.type === "function_declaration") {
@@ -254,10 +269,10 @@ function hasReturnAwaitCall(node) {
254
269
  }
255
270
  export const functionFactProvider = {
256
271
  id: "fact.file.functions",
257
- provides: ["file.functionSummaries"],
272
+ provides: ["file.functionSummaries", "file.functionFactsCoverage"],
258
273
  requires: ["file.content"],
259
274
  appliesTo(ctx) {
260
- return /\.tsx?$/.test(ctx.filePath);
275
+ return isJstsFactFile(ctx.filePath);
261
276
  },
262
277
  async run(ctx, store) {
263
278
  await extractFactsFromTree(ctx, store, { "file.functionSummaries": [] }, (root) => {
@@ -309,7 +324,8 @@ export const functionFactProvider = {
309
324
  addSummary(node);
310
325
  if (node.type === "class_declaration" ||
311
326
  node.type === "interface_declaration") {
312
- const nameNode = firstChildOfType(node, "type_identifier");
327
+ const nameNode = firstChildOfType(node, "type_identifier") ??
328
+ firstChildOfType(node, "identifier");
313
329
  if (nameNode) {
314
330
  containers.push({
315
331
  name: nameNode.text,
@@ -325,6 +341,6 @@ export const functionFactProvider = {
325
341
  summary.owner = owner;
326
342
  }
327
343
  return { "file.functionSummaries": summaries };
328
- });
344
+ }, "file.functionFactsCoverage");
329
345
  },
330
346
  };
@@ -1,18 +1,6 @@
1
1
  import { logLatency } from "../../latency-logger.js";
2
+ import { isJstsFactFile } from "../../file-kinds.js";
2
3
  import { childrenOfType, firstChildOfType, withFactTree, walk, } from "./tree-sitter-facts.js";
3
- // JS/TS extensions this provider handles (→ tree-sitter typescript/tsx/javascript
4
- // grammars via resolveTreeSitterLanguage). Ported off the `typescript` compiler API
5
- // onto tree-sitter (#402) — the parse is served from the shared, cached client.
6
- const JSTS_EXTS = new Set([
7
- ".ts",
8
- ".tsx",
9
- ".mts",
10
- ".cts",
11
- ".js",
12
- ".jsx",
13
- ".mjs",
14
- ".cjs",
15
- ]);
16
4
  function stripQuotes(raw) {
17
5
  return raw.replace(/^["'`]+|["'`]+$/g, "");
18
6
  }
@@ -78,19 +66,19 @@ function parseReExport(node) {
78
66
  }
79
67
  export const importFactProvider = {
80
68
  id: "fact.file.imports",
81
- provides: ["file.imports", "file.reexports"],
69
+ provides: ["file.imports", "file.reexports", "file.importFactsCoverage"],
82
70
  requires: ["file.content"],
83
71
  appliesTo(ctx) {
84
- const ext = ctx.filePath.slice(ctx.filePath.lastIndexOf(".")).toLowerCase();
85
- return JSTS_EXTS.has(ext);
72
+ return isJstsFactFile(ctx.filePath);
86
73
  },
87
74
  async run(ctx, store) {
88
75
  const content = store.getFileFact(ctx.filePath, "file.content");
89
- const setEmpty = () => {
76
+ const setEmpty = (complete = false) => {
90
77
  store.setFileFact(ctx.filePath, "file.imports", []);
91
78
  store.setFileFact(ctx.filePath, "file.reexports", []);
79
+ store.setFileFact(ctx.filePath, "file.importFactsCoverage", complete ? "complete" : "unavailable");
92
80
  };
93
- if (!content) {
81
+ if (content == null) {
94
82
  setEmpty();
95
83
  return;
96
84
  }
@@ -205,5 +193,7 @@ export const importFactProvider = {
205
193
  });
206
194
  if (!parsed.parsed)
207
195
  setEmpty();
196
+ else
197
+ store.setFileFact(ctx.filePath, "file.importFactsCoverage", "complete");
208
198
  },
209
199
  };