@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
@@ -14,6 +14,7 @@ import { primaryServerId } from "../clients/lsp/config.js";
14
14
  import { combineAbortSignals, withDeadline } from "../clients/deadline-utils.js";
15
15
  import { applyAuxiliarySuppressions, retagAuxiliaryDiagnostics, } from "../clients/dispatch/auxiliary-lsp.js";
16
16
  import { detectFileRole } from "../clients/file-role.js";
17
+ import { hashDiagnosticContent, touchCompletedConfirmationPolicy, touchCoverageGap, } from "../clients/lsp/diagnostic-binding.js";
17
18
  import { classifyCascadeWaitTier } from "../clients/lsp/wait-policy/index.js";
18
19
  import { attemptTsserverSyncDiagnostics, } from "../clients/lsp/tsserver-sync.js";
19
20
  import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
@@ -184,14 +185,30 @@ function projectIgnorePredicate(root) {
184
185
  return () => false;
185
186
  }
186
187
  }
187
- function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
188
+ /**
189
+ * #1137: async so a directory read never blocks the event loop (and pi's TUI).
190
+ *
191
+ * This walk was fully synchronous with NO yielding of any kind, bounded only by
192
+ * `maxFiles` *kept* — an ignored-heavy or cloud-backed (OneDrive/network) tree
193
+ * could traverse unboundedly many entries, and a single stalled `readdirSync`
194
+ * held the loop for the whole stall. It is also called once PER LANGUAGE in
195
+ * `runDirectoryDiagnostics`'s `LANG_EXTENSIONS` loop, so a directory-mode
196
+ * `lsp_diagnostics` could pay that cost several times over.
197
+ *
198
+ * The traversal is deliberately still **depth-first with immediate descent**
199
+ * (not the shared stack-based `walkTreeStackAsync`): the `maxFiles` cap makes
200
+ * traversal ORDER observable — a stack walk would keep a different subset of
201
+ * files on an over-large tree. Only scheduling changes here; the returned list
202
+ * is identical. Awaiting each directory read is itself the yield point.
203
+ */
204
+ async function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
188
205
  const files = [];
189
- function walk(current) {
206
+ async function walk(current) {
190
207
  if (files.length >= maxFiles)
191
208
  return;
192
209
  let entries;
193
210
  try {
194
- entries = fs.readdirSync(current, { withFileTypes: true });
211
+ entries = await fs.promises.readdir(current, { withFileTypes: true });
195
212
  }
196
213
  catch {
197
214
  return;
@@ -204,7 +221,7 @@ function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
204
221
  const full = path.join(current, entry.name);
205
222
  if (entry.isDirectory()) {
206
223
  if (!isExcludedDirName(entry.name) && !isIgnored(full, true))
207
- walk(full);
224
+ await walk(full);
208
225
  }
209
226
  else if (entry.isFile() && extensions.includes(path.extname(full))) {
210
227
  if (isIgnored(full, false))
@@ -213,15 +230,15 @@ function collectFiles(dir, extensions, maxFiles, isIgnored = () => false) {
213
230
  }
214
231
  }
215
232
  }
216
- walk(dir);
233
+ await walk(dir);
217
234
  return files;
218
235
  }
219
236
  export function createLspDiagnosticsTool(
220
- // #571: same shared write-ordering token source `lens_diagnostics` mode=full
221
- // uses (index.ts injects `() => runtime.nextWriteIndex()`) — a confirmed
222
- // fresh result this tool reconciles into the footer draws a fresh token so
223
- // `WriteOrderingGuard` can tell it apart from a concurrent, genuinely newer
224
- // per-edit write for the same file. Optional/undefined in tests.
237
+ // #571/#1198: same shared write-ordering token source `lens_diagnostics`
238
+ // mode=full uses (index.ts injects `() => runtime.nextWriteIndex()`). A
239
+ // confirmed result reconciled into the footer reserves its token when the
240
+ // per-file check starts, so settlement order cannot make an older result look
241
+ // newer. Optional/undefined in tests.
225
242
  nextWriteIndex) {
226
243
  return {
227
244
  name: "lsp_diagnostics",
@@ -419,19 +436,14 @@ nextWriteIndex) {
419
436
  async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope = "all") {
420
437
  let timedOut = false;
421
438
  let content;
422
- // #629: `touched` (when defined) is ALREADY the correctly-scoped,
423
- // already-collected diagnostics array for this touch — `touchFile` below
424
- // is called with `collectDiagnostics: true` and `clientScope: serverScope`,
425
- // so its return value only contains diagnostics from the servers
426
- // `serverScope` asked for. Previously this function discarded `touched`
427
- // (reading only `.inconclusive` off it) and made a SECOND, unconditionally
428
- // -unscoped `getDiagnostics()` call for the actual content meaning every
429
- // touchFile-branch call paid for two LSP round trips instead of one, and
430
- // `serverScope: "primary"` never actually skipped the auxiliary scanners
431
- // (getDiagnostics always queries every registered server for the file).
432
- // `touched` is only undefined when touchFile itself couldn't produce a
433
- // result (service destroyed, no clients resolved) — that's the one case
434
- // that still needs the getDiagnostics() fallback below.
439
+ // `touchFile` is the authoritative collection boundary for every scope: it
440
+ // preserves per-touch timeout, content-binding, and silent-clean confirmation
441
+ // metadata while returning diagnostics from only the requested clients. The
442
+ // legacy openFile/getDiagnostics path remains only for an older/mock service
443
+ // without touchFile, or when touchFile cannot resolve any clients.
444
+ // #1179: the result is an explicit wrapper so side-channel fields survive
445
+ // array copies; confirmation was added when Marksman's lower-level clean verdict
446
+ // proved that a successful empty collection also needs explicit provenance.
435
447
  let touched;
436
448
  let usedTouch = false;
437
449
  try {
@@ -447,12 +459,33 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
447
459
  ? attached.response.diagnostics.filter((item) => item.source === primaryServerId(absPath))
448
460
  : attached.response.diagnostics;
449
461
  const filtered = applyAuxiliarySuppressions(scopedDiagnostics, content, { fileRole: detectFileRole(absPath, content) });
450
- return { diagnostics: filtered, timedOut: false, content };
462
+ return {
463
+ diagnostics: filtered,
464
+ timedOut: false,
465
+ // #1253: the incumbent's touch carries the same confirmation
466
+ // provenance a local touch does (an `available: true` answer is
467
+ // already gated on `fresh && !inconclusive`, but that is NOT
468
+ // evidence a silent-on-clean server was confirmed — only the
469
+ // explicit flag is). The incumbent always touches with
470
+ // `clientScope: "with-auxiliary"`, so this is an AGGREGATE
471
+ // confirmation: it carries the same caveat all-scope local
472
+ // touches do, and classic TypeScript still needs the primary-only
473
+ // tsserver sync check below rather than this verdict.
474
+ // #1470: `"partial"` counts, for the same reason the local touch
475
+ // branch below accepts it — the incumbent's primary confirmed; only
476
+ // a named auxiliary did not.
477
+ confirmedByTouch: attached.response.confirmation !== undefined &&
478
+ primaryServerId(absPath) !== "typescript",
479
+ // #1470: the incumbent's narrowed verdict, carried as an explicit DTO
480
+ // field across the socket. An older incumbent omits it → empty → the
481
+ // pre-#1470 handling, unchanged.
482
+ unconfirmedServerIds: attached.response.unconfirmedServerIds ?? [],
483
+ content,
484
+ };
451
485
  }
452
486
  }
453
487
  const serviceWithTouch = lspService;
454
- if ((waitMs !== undefined || serverScope === "primary") &&
455
- typeof serviceWithTouch.touchFile === "function") {
488
+ if (typeof serviceWithTouch.touchFile === "function") {
456
489
  usedTouch = true;
457
490
  touched = await serviceWithTouch.touchFile(absPath, content, {
458
491
  diagnostics: "document",
@@ -480,7 +513,7 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
480
513
  // serverScope:"primary" actually skip auxiliary scanners and drops the
481
514
  // common case back to a single LSP round trip instead of two.
482
515
  const diagnostics = usedTouch && touched !== undefined
483
- ? touched
516
+ ? touched.diags
484
517
  : await lspService.getDiagnostics(absPath, waitMs !== undefined ? "document" : "full");
485
518
  // #586: honor each auxiliary profile's native inline-suppression comment
486
519
  // (e.g. opengrep's `// nosemgrep`, #441) the same way the per-edit dispatch
@@ -496,7 +529,27 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
496
529
  fileRole: detectFileRole(absPath, content),
497
530
  })
498
531
  : diagnostics;
499
- return { diagnostics: filtered, timedOut, content };
532
+ // #1095: surface the touch's content binding (only the touch path carries
533
+ // one; the openFile-only / getDiagnostics fallback leaves it undefined →
534
+ // "unknown", no demotion).
535
+ const binding = usedTouch ? touched?.binding : undefined;
536
+ // #1470: `"partial"` counts here. `confirmedByTouch` feeds
537
+ // `canTrustTouchConfirmation`, which asks about the PRIMARY's own verdict —
538
+ // and a partial touch is one whose primary confirmed while an auxiliary was
539
+ // cut off. Excluding it would render "Primary LSP: unconfirmed" for a primary
540
+ // that did confirm. The coverage gap is carried separately, below.
541
+ const confirmedByTouch = usedTouch && touchCompletedConfirmationPolicy(touched);
542
+ return {
543
+ diagnostics: filtered,
544
+ timedOut,
545
+ confirmedByTouch,
546
+ // #1470: only a touch actually contributes a coverage gap; the
547
+ // openFile+getDiagnostics fallback never reports one, which is honest —
548
+ // that path claims no confirmation at all.
549
+ unconfirmedServerIds: usedTouch ? touchCoverageGap(touched) : [],
550
+ content,
551
+ binding,
552
+ };
500
553
  }
501
554
  function diagnosticsToFileDiags(file, diagnostics) {
502
555
  return diagnostics.map((d) => ({
@@ -567,6 +620,17 @@ async function resolveEmptyResult(file, lspService) {
567
620
  return { confirmed: true, diagnostics: [] };
568
621
  }
569
622
  }
623
+ /**
624
+ * A primary-scope touch is authoritative because `touchFile` runs that
625
+ * primary's own confirmation path (including TypeScript's sync fallback). For
626
+ * all-scope TypeScript touches, the aggregate silent-clean gate can settle
627
+ * without that primary-only sync request, so preserve `resolveEmptyResult`'s
628
+ * existing tsserver fallback instead of accepting the aggregate verdict.
629
+ */
630
+ function canTrustTouchConfirmation(file, serverScope, confirmedByTouch) {
631
+ return (confirmedByTouch &&
632
+ (serverScope === "primary" || primaryServerId(file) !== "typescript"));
633
+ }
570
634
  /**
571
635
  * #571: reconcile this tool's fresh LSP result into the footer cache
572
636
  * (`widget-state.ts`'s `allDiagnostics`) — same shared choke point
@@ -574,6 +638,11 @@ async function resolveEmptyResult(file, lspService) {
574
638
  * `reconcileScanDiagnostics`). A manual `lsp_diagnostics` check that proves a
575
639
  * stale footer error is actually gone (the real-world case that surfaced
576
640
  * #571) is exactly the kind of confirmed result that should correct it.
641
+ * Direct `lsp_diagnostics` deliberately does not perform a second disk read at
642
+ * this reconciliation seam: its collecting touch already read the content and
643
+ * supplies the binding verdict. `false` is unconfirmed and is never written;
644
+ * `true` is trusted, while an absent/`unknown` verdict preserves the legacy
645
+ * fail-open policy for servers that cannot bind their diagnostics to content.
577
646
  *
578
647
  * `rawDiags` (pre-severity-filter) is what gets written — the footer records
579
648
  * the true known state, independent of this call's display-only severity
@@ -592,8 +661,22 @@ async function resolveEmptyResult(file, lspService) {
592
661
  * /skipTestFiles-filtered at write time — an empty string here is then a safe
593
662
  * no-op re-check, not a behavior gap).
594
663
  */
595
- function reconcileWidgetFromLspResult(file, rawDiags, confirmation, nextWriteIndex, cwd, content) {
596
- const confirmed = rawDiags.length > 0 || confirmation !== "unconfirmed";
664
+ function reconcileWidgetFromLspResult(file, rawDiags, confirmation, writeIndex, cwd, content,
665
+ // #1095: true when the result's content binding demonstrably mismatches disk
666
+ // (boundToCurrentDisk === false). Such a result — even a NON-EMPTY one, which
667
+ // the pre-#1095 "non-empty is definitionally confirmed" doctrine would have
668
+ // written straight through — must NOT re-cement the footer with a stale view
669
+ // (#1092's window-1 transcript: 17 live-looking diagnostics while tsc exits
670
+ // 0). "unknown"/true bindings leave the doctrine unchanged (fallback preserved
671
+ // for servers that never bind a version).
672
+ boundMismatch = false,
673
+ // #1093: when these `rawDiags` were actually OBSERVED. Fresh touches observe
674
+ // now (`undefined` → `Date.now()`); a cache HIT replays diagnostics scanned
675
+ // earlier, so its caller passes the cache entry's `scannedAt` here so the
676
+ // footer's `touchedAt` isn't re-armed to now() and the mtime-staleness gate
677
+ // stays live (the #1092 re-arming defect).
678
+ observedAt) {
679
+ const confirmed = (rawDiags.length > 0 || confirmation !== "unconfirmed") && !boundMismatch;
597
680
  if (!confirmed)
598
681
  return;
599
682
  try {
@@ -603,7 +686,7 @@ function reconcileWidgetFromLspResult(file, rawDiags, confirmation, nextWriteInd
603
686
  scanOrigin: "lsp_diagnostics",
604
687
  });
605
688
  const retagged = retagAuxiliaryDiagnostics(diagnostics, rawDiags, content ?? "", { cwd, fileRole: detectFileRole(file, content) });
606
- reconcileScanDiagnostics(file, retagged, true, nextWriteIndex?.());
689
+ reconcileScanDiagnostics(file, retagged, true, writeIndex, observedAt);
607
690
  }
608
691
  catch {
609
692
  // Never let a footer-reconciliation hiccup fail the diagnostics check.
@@ -621,6 +704,10 @@ cacheCtx, scopeKey,
621
704
  // `process.cwd()` for any call site that predates this (there are none
622
705
  // today besides the two below, both of which now pass it explicitly).
623
706
  cwd = process.cwd()) {
707
+ // Reserve the ordering token when this diagnostic operation starts, not when
708
+ // its LSP promise settles. A slower old result must not receive a newer token
709
+ // merely because it completed later (#1198).
710
+ const writeIndex = nextWriteIndex?.();
624
711
  let stat;
625
712
  try {
626
713
  stat = fs.statSync(file);
@@ -633,14 +720,27 @@ cwd = process.cwd()) {
633
720
  }
634
721
  if (cacheCtx && scopeKey !== undefined) {
635
722
  const cached = cacheCtx.lookup(file, scopeKey);
636
- if (cached) {
723
+ // #1095: a cached entry whose content binding demonstrably mismatches disk
724
+ // (bytes changed without the mtime bump the freshness gate would catch) is
725
+ // NOT served — fall through to a fresh touch instead of replaying a stale
726
+ // cached result. "unknown"/true bindings serve as before.
727
+ if (cached && cached.binding.boundToCurrentDisk !== false) {
637
728
  const filteredDiags = applySeverityFilter(cached.diagnostics, severity);
638
729
  const confirmation = cached.diagnostics.length === 0 ? "clean" : undefined;
639
730
  // #692: cached.diagnostics were already suppression-/skipTestFiles-
640
731
  // filtered at write time (this same code path); no file content was
641
732
  // cached alongside them, so `undefined` here is a safe re-check, not
642
733
  // a gap.
643
- reconcileWidgetFromLspResult(file, cached.diagnostics, confirmation, nextWriteIndex, cwd, undefined);
734
+ // #1093: this is a CACHE HIT — a replay of an OLD observation. Stamp the
735
+ // footer's `touchedAt` with the cache entry's original `scannedAt`, NOT
736
+ // now(), or a repeat check that only re-serves the cache would keep
737
+ // re-arming the mtime-staleness gate and a resolved finding would render
738
+ // forever (the #1092 defect).
739
+ reconcileWidgetFromLspResult(file, cached.diagnostics, confirmation, writeIndex, cwd, undefined,
740
+ // This branch only runs when the cache binding is not a mismatch
741
+ // (guarded above), so boundMismatch is false; #1093's observedAt is
742
+ // the cache entry's original scan time.
743
+ false, cached.scannedAt);
644
744
  return {
645
745
  file,
646
746
  diagnostics: diagnosticsToFileDiags(file, filteredDiags),
@@ -649,7 +749,7 @@ cwd = process.cwd()) {
649
749
  };
650
750
  }
651
751
  }
652
- const { diagnostics: rawDiags, timedOut, content: collectedContent, } = await collectDiagnosticsForFile(file, lspService, waitMs, serverScope);
752
+ const { diagnostics: rawDiags, timedOut, confirmedByTouch, unconfirmedServerIds, content: collectedContent, binding, } = await collectDiagnosticsForFile(file, lspService, waitMs, serverScope);
653
753
  const health = lspService.getDiagnosticsHealth?.(file);
654
754
  // #570: a timed-out priming check is never a confirmed "clean" — treat it
655
755
  // as unconfirmed without consulting the (unrelated) silent-tier
@@ -665,6 +765,11 @@ cwd = process.cwd()) {
665
765
  confirmation = "unconfirmed";
666
766
  }
667
767
  }
768
+ else if (canTrustTouchConfirmation(file, serverScope, confirmedByTouch)) {
769
+ if (applySeverityFilter(rawDiags, severity).length === 0) {
770
+ confirmation = "clean";
771
+ }
772
+ }
668
773
  else if (rawDiags.length === 0) {
669
774
  const resolved = await resolveEmptyResult(file, lspService);
670
775
  effectiveRawDiags = resolved.diagnostics;
@@ -677,16 +782,34 @@ cwd = process.cwd()) {
677
782
  else if (applySeverityFilter(rawDiags, severity).length === 0) {
678
783
  confirmation = await classifyEmptyResult(file, lspService);
679
784
  }
785
+ // #1095: a result whose content binding demonstrably mismatches disk is
786
+ // demoted to "unconfirmed" — it must neither confirm the footer nor be cached
787
+ // as clean, regardless of how many diagnostics it carries (the #1092
788
+ // re-cementing path). "unknown"/true bindings leave the verdict untouched.
789
+ const boundMismatch = binding?.boundToCurrentDisk === false;
790
+ if (boundMismatch)
791
+ confirmation = "unconfirmed";
792
+ // #1470: an auxiliary cut off by the aux grace timer contributed no evidence
793
+ // about this file, so a "clean" verdict computed from the merged result would
794
+ // be claiming coverage this batch does not have. Demote it — that also keeps
795
+ // the entry out of the workspace cache below, which would otherwise replay the
796
+ // partial answer as a confirmed clean on every later sweep.
797
+ if (unconfirmedServerIds.length > 0 && confirmation === "clean") {
798
+ confirmation = "unconfirmed";
799
+ }
680
800
  const filteredDiags = applySeverityFilter(effectiveRawDiags, severity);
681
- reconcileWidgetFromLspResult(file, effectiveRawDiags, confirmation, nextWriteIndex, cwd, collectedContent);
801
+ reconcileWidgetFromLspResult(file, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
682
802
  // #671: only a CONFIRMED outcome ("clean", or a non-empty result — either
683
803
  // is definitionally confirmed per this function's own doctrine above) is
684
804
  // safe to cache; "unconfirmed" (timeout OR a silent-tier server's
685
805
  // unescapable empty push) must never be persisted as a cacheable clean
686
806
  // result — same false-clean bug class `runWorkspaceDiagnostics`'s cache
687
- // wiring guards against.
807
+ // wiring guards against. #1095: the entry carries the content fingerprint so a
808
+ // later lookup can verify it against disk beyond the mtime proxy.
688
809
  if (cacheCtx && scopeKey !== undefined && confirmation !== "unconfirmed") {
689
- cacheCtx.record(file, scopeKey, effectiveRawDiags, stat.mtimeMs);
810
+ cacheCtx.record(file, scopeKey, effectiveRawDiags, stat.mtimeMs, collectedContent !== undefined
811
+ ? hashDiagnosticContent(collectedContent)
812
+ : undefined);
690
813
  }
691
814
  return {
692
815
  file,
@@ -698,7 +821,10 @@ cwd = process.cwd()) {
698
821
  };
699
822
  }
700
823
  async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWriteIndex, serverScope = "all", cwd = process.cwd()) {
701
- const { diagnostics: rawDiags, timedOut, content: collectedContent, } = await collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope);
824
+ // Reserve the token before awaiting this file's LSP result. The direct-file
825
+ // path performs its own confirmation/reconciliation below (#1198).
826
+ const writeIndex = nextWriteIndex?.();
827
+ const { diagnostics: rawDiags, timedOut, confirmedByTouch, unconfirmedServerIds, content: collectedContent, binding, } = await collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope);
702
828
  const lspHealth = lspService.getDiagnosticsHealth?.(absPath);
703
829
  const unavailable = lspUnavailableMessage(absPath, lspHealth);
704
830
  // #533: an empty result needs a confirmed/unconfirmed verdict — a push-only,
@@ -717,6 +843,11 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
717
843
  confirmation = "unconfirmed";
718
844
  }
719
845
  }
846
+ else if (canTrustTouchConfirmation(absPath, serverScope, confirmedByTouch)) {
847
+ if (applySeverityFilter(rawDiags, severity).length === 0) {
848
+ confirmation = "clean";
849
+ }
850
+ }
720
851
  else if (rawDiags.length === 0) {
721
852
  const resolved = await resolveEmptyResult(absPath, lspService);
722
853
  effectiveRawDiags = resolved.diagnostics;
@@ -729,12 +860,27 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
729
860
  else if (applySeverityFilter(rawDiags, severity).length === 0) {
730
861
  confirmation = await classifyEmptyResult(absPath, lspService);
731
862
  }
863
+ // #1095: demote a result whose content binding mismatches disk to
864
+ // "unconfirmed" (the #1092 re-cementing path) — a non-empty stale result is no
865
+ // longer "definitionally confirmed". "unknown"/true bindings are unchanged.
866
+ const boundMismatch = binding?.boundToCurrentDisk === false;
867
+ if (boundMismatch)
868
+ confirmation = "unconfirmed";
869
+ // #1470: NARROWED, not collapsed. An auxiliary the aux grace timer cut off makes
870
+ // the FILE-level verdict unconfirmed — the merged result is missing whatever that
871
+ // scanner would have said, and this tool is the security lane's read surface. The
872
+ // PRIMARY's own verdict is untouched (`primaryCoverageGapOnly` below), so a
873
+ // TypeScript answer stays "confirmed clean" on its own line while an explicit
874
+ // line names the scanner whose coverage is absent.
875
+ const primaryCoverageGapOnly = unconfirmedServerIds.length > 0 && confirmation === "clean";
876
+ if (primaryCoverageGapOnly)
877
+ confirmation = "unconfirmed";
732
878
  const filtered = applySeverityFilter(effectiveRawDiags, severity);
733
879
  const total = filtered.length;
734
880
  const truncated = total > MAX_DIAGNOSTICS;
735
881
  const limited = truncated ? filtered.slice(0, MAX_DIAGNOSTICS) : filtered;
736
882
  const unconfirmed = confirmation === "unconfirmed";
737
- reconcileWidgetFromLspResult(absPath, effectiveRawDiags, confirmation, nextWriteIndex, cwd, collectedContent);
883
+ reconcileWidgetFromLspResult(absPath, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
738
884
  const primaryId = primaryServerId(absPath);
739
885
  const primaryDiags = limited.filter((d) => d.source === primaryId);
740
886
  const auxiliaryDiags = limited.filter((d) => d.source !== primaryId);
@@ -747,7 +893,11 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
747
893
  "file may still have errors that just hadn't been reported yet. " +
748
894
  "Re-check after the server settles, or increase waitMs.");
749
895
  }
750
- if (unconfirmed) {
896
+ // #1470: a file demoted ONLY because an auxiliary was cut off must not render
897
+ // the silent-on-clean text — the primary did confirm, and saying otherwise is
898
+ // the same overclaim in the opposite direction. The coverage line below names
899
+ // what is actually missing.
900
+ if (unconfirmed && !primaryCoverageGapOnly) {
751
901
  return (`Primary LSP${primaryId ? ` (${primaryId})` : ""}: unconfirmed — ` +
752
902
  "cannot confirm clean (push-only, silent-on-clean, e.g. classic " +
753
903
  "typescript-language-server never publishes on a clean re-check). " +
@@ -759,9 +909,20 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
759
909
  }
760
910
  return `Primary LSP${primaryId ? ` (${primaryId})` : ""}: ${primaryDiags.length} diagnostic${primaryDiags.length === 1 ? "" : "s"}.`;
761
911
  })();
912
+ // #1470: this is the narrowing made readable. It states exactly which servers
913
+ // this result does NOT speak for, so "no auxiliary findings" can never be read
914
+ // as "the security scanners found nothing".
915
+ const coverageLine = unconfirmedServerIds.length > 0
916
+ ? `Auxiliary coverage INCOMPLETE — ${[...unconfirmedServerIds].join(", ")} did not answer within the wait budget, so ${unconfirmedServerIds.length === 1 ? "its findings are" : "their findings are"} NOT included here. This is not a clean bill of health for ${unconfirmedServerIds.length === 1 ? "that scanner" : "those scanners"}; re-check after the next edit, or use waitMs to wait longer.`
917
+ : undefined;
762
918
  let text;
763
919
  if (total === 0) {
764
- text = [primaryLine, "", unavailable ?? "No auxiliary findings."].join("\n");
920
+ text = [
921
+ primaryLine,
922
+ "",
923
+ unavailable ?? "No auxiliary findings.",
924
+ ...(coverageLine ? [coverageLine] : []),
925
+ ].join("\n");
765
926
  }
766
927
  else {
767
928
  const lines = [primaryLine, ""];
@@ -772,6 +933,8 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
772
933
  lines.push(`Auxiliary findings (${auxiliaryDiags.length}):`);
773
934
  lines.push(...auxiliaryDiags.map(formatDiag));
774
935
  }
936
+ if (coverageLine)
937
+ lines.push("", coverageLine);
775
938
  if (unavailable)
776
939
  lines.unshift(unavailable, "");
777
940
  if (truncated) {
@@ -801,6 +964,11 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
801
964
  truncated,
802
965
  unconfirmed,
803
966
  timedOut: unconfirmed ? timedOut : undefined,
967
+ // #1470: which servers this result does NOT speak for. Absent when it
968
+ // speaks for all of them.
969
+ ...(unconfirmedServerIds.length > 0 && {
970
+ unconfirmedServerIds: [...unconfirmedServerIds],
971
+ }),
804
972
  lspHealth,
805
973
  waitMs,
806
974
  },
@@ -1072,7 +1240,7 @@ async function runDirectoryDiagnostics(absPath, severity, lspService, options) {
1072
1240
  let collectedFiles = [];
1073
1241
  const isIgnored = projectIgnorePredicate(absPath);
1074
1242
  for (const [ext, exts] of Object.entries(LANG_EXTENSIONS)) {
1075
- collectedFiles = collectFiles(absPath, exts, MAX_FILES + 1, isIgnored);
1243
+ collectedFiles = await collectFiles(absPath, exts, MAX_FILES + 1, isIgnored);
1076
1244
  if (collectedFiles.length > 0) {
1077
1245
  extension = ext;
1078
1246
  break;
@@ -8,6 +8,7 @@ import * as path from "node:path";
8
8
  import { fileURLToPath, pathToFileURL } from "node:url";
9
9
  import { Type } from "../clients/deps/typebox.js";
10
10
  import { logLatency } from "../clients/latency-logger.js";
11
+ import { newLspMutationCorrelationId, recordLspMutationOutcome, } from "../clients/lsp-mutation.js";
11
12
  import { compactRenderResult } from "./render-compact.js";
12
13
  import { applyWorkspaceEdit, summarizeWorkspaceEdit, } from "../clients/lsp/edits.js";
13
14
  import { getLSPService } from "../clients/lsp/index.js";
@@ -550,7 +551,7 @@ export function createLspNavigationTool(
550
551
  * constructed in; callers that only have a single fixed project root (e.g.
551
552
  * pi's own `getLensFlag`) may ignore the second argument.
552
553
  */
553
- getFlag) {
554
+ getFlag, mutationDeps) {
554
555
  return {
555
556
  name: "lsp_navigation",
556
557
  label: "LSP Navigate",
@@ -675,7 +676,15 @@ getFlag) {
675
676
  let supported = null;
676
677
  let diagnosticsMode = "unknown";
677
678
  let columnResolution;
679
+ let mutationContext;
680
+ let requestedApply = false;
678
681
  const finalize = (payload, meta) => {
682
+ if (payload.isError &&
683
+ requestedApply &&
684
+ mutationContext &&
685
+ ["rename", "rename_file", "executeCommand"].includes(meta.operation)) {
686
+ recordLspMutationOutcome(mutationContext, "failed");
687
+ }
679
688
  const normalizedFilePath = meta.filePath.replace(/\\/g, "/");
680
689
  logLatency({
681
690
  type: "phase",
@@ -759,6 +768,21 @@ getFlag) {
759
768
  });
760
769
  }
761
770
  const operation = normalizedOperation;
771
+ requestedApply = apply === true;
772
+ if (requestedApply &&
773
+ ["rename", "rename_file", "executeCommand"].includes(operation)) {
774
+ const cwd = ctx.cwd || ".";
775
+ mutationContext = {
776
+ cwd,
777
+ correlationId: newLspMutationCorrelationId(_toolCallId),
778
+ tool: "lsp_navigation",
779
+ source: "lsp-edit",
780
+ ...mutationDeps,
781
+ readGuard: getFlag("no-read-guard", cwd)
782
+ ? undefined
783
+ : mutationDeps?.readGuard,
784
+ };
785
+ }
762
786
  const isCallHierarchyTraversal = operation === "incomingCalls" || operation === "outgoingCalls";
763
787
  const needsFilePath = operation !== "workspaceDiagnostics" &&
764
788
  operation !== "workspaceSymbol" &&
@@ -1060,7 +1084,7 @@ getFlag) {
1060
1084
  edit,
1061
1085
  };
1062
1086
  }
1063
- const applied = await applyWorkspaceEdit(edit, ctx.cwd || ".");
1087
+ const applied = await applyWorkspaceEdit(edit, ctx.cwd || ".", { mutationContext });
1064
1088
  for (const touchedFile of applied.files) {
1065
1089
  try {
1066
1090
  await openFileBestEffort(lspService, touchedFile, false);
@@ -1081,6 +1105,7 @@ getFlag) {
1081
1105
  const result = await lspService.renameFile(filePath, resolvedNewFilePath, {
1082
1106
  cwd: ctx.cwd || ".",
1083
1107
  apply: apply ?? false,
1108
+ ...(mutationContext ? { mutationContext } : {}),
1084
1109
  });
1085
1110
  if (result.applied) {
1086
1111
  for (const touchedFile of result.files ?? []) {
@@ -1122,7 +1147,7 @@ getFlag) {
1122
1147
  if (!isAdvertised) {
1123
1148
  throw new Error(`__UNSUPPORTED__ command "${command}" is not advertised by the active LSP server (advertised: ${advertised.join(", ") || "none"})`);
1124
1149
  }
1125
- return lspService.executeCommand(rawPath ? filePath : undefined, command, commandArguments);
1150
+ return lspService.executeCommand(rawPath ? filePath : undefined, command, commandArguments, mutationContext);
1126
1151
  }
1127
1152
  case "incomingCalls": {
1128
1153
  if (!callHierarchyItem) {
@@ -1177,6 +1202,24 @@ getFlag) {
1177
1202
  }
1178
1203
  }
1179
1204
  }
1205
+ if (mutationContext &&
1206
+ requestedApply &&
1207
+ !mutationContext.summaryEmitted) {
1208
+ const outcome = operation === "rename_file" &&
1209
+ result &&
1210
+ typeof result === "object" &&
1211
+ "applied" in result &&
1212
+ result.applied === false
1213
+ ? "failed"
1214
+ : operation === "executeCommand" &&
1215
+ result &&
1216
+ typeof result === "object" &&
1217
+ "executed" in result &&
1218
+ result.executed === false
1219
+ ? "failed"
1220
+ : "skipped";
1221
+ recordLspMutationOutcome(mutationContext, outcome);
1222
+ }
1180
1223
  }
1181
1224
  catch (err) {
1182
1225
  const msg = err instanceof Error ? err.message : String(err);
@@ -29,6 +29,7 @@ export function createModuleReportTool(getProjectRoot) {
29
29
  description: "Structured, navigable overview of a source module — a token-efficient substitute for reading the whole file. Returns each symbol's name/kind/signature/line-range (plus a first-line `doc` summary when a doc comment is attached), important inline callbacks/closures/lambdas with stable handles, plus who-uses-this, risk flags, and ranked recommendedReads. To read a symbol's body: call read/read_symbol with offset=startLine, limit=endLine-startLine+1 on THIS report's `path` — those aren't repeated per symbol. Prefer this before a full read; then use read_symbol (or read) for the exact body you need.\n" +
30
30
  "Single mode: language-uniform tree-sitter outline + review-graph who-uses-this + inline executable extraction; degrades to outline-only when no cached graph is available. `semantic.source` reports whether graph data was used.\n" +
31
31
  'Pass `blastRadius: true` to also get the cross-file blast radius — the transitive dependents of this module aggregated to ranked file `read` args ("if you change this, verify these files"). Read-only over the cached graph; omitted on a cold cache. Supersedes the standalone impact query.\n' +
32
+ 'Pass `callGraph: true` to include bounded derived callers/callees from the cached FunctionCallGraph; unavailable cache state is explicit and never reported as zero calls.\n' +
32
33
  '`view: "compact"` returns a line-oriented text rendering (one line per symbol/callback, cheapest option) instead of JSON — same data, roughly a quarter of the token cost; use it for a quick skim. Default view returns JSON. An outline shows shape, not bodies — it does NOT count as having read a symbol\'s body for editing; use read_symbol for that.',
33
34
  promptSnippet: "Navigable file outline — a cheap substitute for reading a whole file",
34
35
  renderResult: compactRenderResult(({ details, args, isError }) => {
@@ -65,6 +66,12 @@ export function createModuleReportTool(getProjectRoot) {
65
66
  blastRadiusDepth: Type.Optional(Type.Number({
66
67
  description: "Max hops for the blast-radius walk (default 3). Only used with blastRadius.",
67
68
  })),
69
+ callGraph: Type.Optional(Type.Boolean({
70
+ description: "Include bounded derived callers/callees from the cached FunctionCallGraph; cold or stale cache state is explicit.",
71
+ })),
72
+ maxCallGraphEntries: Type.Optional(Type.Number({
73
+ description: "Per-direction cap for call-graph relations (default 20).",
74
+ })),
68
75
  }),
69
76
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
70
77
  // Resolve the file against the agent's cwd (sibling-tool convention); build
@@ -79,6 +86,8 @@ export function createModuleReportTool(getProjectRoot) {
79
86
  view: params.view,
80
87
  blastRadius: params.blastRadius,
81
88
  blastRadiusDepth: params.blastRadiusDepth,
89
+ callGraph: params.callGraph,
90
+ maxCallGraphEntries: params.maxCallGraphEntries,
82
91
  });
83
92
  }
84
93
  catch (err) {
@@ -33,7 +33,7 @@ export function createSymbolSearchTool(getProjectRoot) {
33
33
  }),
34
34
  parameters: Type.Object({
35
35
  query: Type.String({
36
- description: "Identifier-ish query, e.g. 'authenticate user'.",
36
+ description: "Identifier-ish query, e.g. 'authenticate user'. Mix in composable prefix filters: lang:<kind> (e.g. lang:jsts, lang:python — kinds from file-kinds.ts), file:<substr> (path substring), ext:<ext> (e.g. ext:ts or ext:.ts), each optionally negated with a leading '-' (-file:test). Filters apply before ranking; e.g. 'lang:jsts file:clients/ -file:test rank'. Unknown prefixes/kinds error with the supported list.",
37
37
  }),
38
38
  limit: Type.Optional(Type.Number({
39
39
  description: "Max files to return (default 20).",