@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
@@ -1,18 +1,23 @@
1
1
  import * as nodeCrypto from "node:crypto";
2
2
  import * as nodeFs from "node:fs";
3
3
  import * as path from "node:path";
4
- import { extractGrepSearchReadsFromOutput, extractWrittenPathsFromCommand, } from "./bash-file-access.js";
4
+ import { extractReadPathsFromCommand, extractGrepSearchReadsFromOutput, extractWrittenPathsFromCommand, } from "./bash-file-access.js";
5
5
  import { registerSearchReads, } from "./search-read-registration.js";
6
6
  import { createFileTime } from "./file-time.js";
7
7
  import { publishFormatQueued } from "./format-events-publish.js";
8
8
  import { isPathIgnoredByProject } from "./file-utils.js";
9
9
  import { getFormatService } from "./format-service.js";
10
- import { isExternalOrVendorFile } from "./path-utils.js";
10
+ import { isExternalOrVendorFile, normalizeEphemeralMapKey } from "./path-utils.js";
11
+ import { PathKeyedMap } from "./path-keyed-map.js";
11
12
  import { resolveLanguageRootForFile } from "./language-profile.js";
12
13
  import { logLatency } from "./latency-logger.js";
14
+ import { boundedIndexesForCount, createReadGuardEditBatchSummary, getReadGuardCorrelationId, logReadGuardEvent, } from "./read-guard-logger.js";
13
15
  import { runPipeline } from "./pipeline.js";
14
16
  import { appendProjectChange, } from "./project-changes.js";
17
+ import { syncGitGuardRecord } from "./git-guard.js";
15
18
  import { scheduleWordIndexPersist } from "./word-index.js";
19
+ import { RUNTIME_CONFIG } from "./runtime-config.js";
20
+ const AUTHORITATIVE_CONTENT_MAX_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
16
21
  function parseDiffRanges(diff) {
17
22
  const changedLines = [];
18
23
  for (const line of diff.split("\n")) {
@@ -40,19 +45,23 @@ function parseDiffRanges(diff) {
40
45
  ranges.push({ start: rangeStart, end: rangeEnd });
41
46
  return ranges;
42
47
  }
43
- // Deduplicates tool_result calls for the same post-write file state.
44
- // The pi framework can emit one tool_result per edit hunk; those events often
45
- // observe the same final file content. Deduping by file alone is unsafe because
46
- // a later same-turn edit to the same file must still run the pipeline.
47
- const inFlightPipelines = new Map();
48
- const lastAnalyzedStateByFile = new Map();
48
+ // Keyed by (normalized) filePath, then by the raw stateHash — the path portion
49
+ // needs normalizing (divergent Windows spellings must collapse to one entry),
50
+ // the stateHash suffix must NOT be folded into the path key (a real content
51
+ // change for the same file has to stay a distinct entry). A flat
52
+ // `PathKeyedMap<InFlightPipeline>` keyed by a composite `${filePath}:${hash}`
53
+ // string can't express that split cleanly (the normalizer only sees the whole
54
+ // composite string, so it can't fold the path half without also mangling the
55
+ // hash half); nesting keeps each axis normalized/compared with its own rules.
56
+ const inFlightPipelines = new PathKeyedMap(normalizeEphemeralMapKey);
57
+ const lastAnalyzedStateByFile = new PathKeyedMap(normalizeEphemeralMapKey);
49
58
  // Called at turn_start — entries from the previous turn can never match the new
50
59
  // turnIndex so they're dead weight. Clearing here keeps the map bounded to the
51
60
  // files touched in the current turn only (typically < 20).
52
61
  export function clearLastAnalyzedStateCache() {
53
62
  lastAnalyzedStateByFile.clear();
54
63
  }
55
- const debouncedPipelines = new Map();
64
+ const debouncedPipelines = new PathKeyedMap(normalizeEphemeralMapKey);
56
65
  const DEFAULT_DEBOUNCE_MS = 0;
57
66
  const MAX_DEBOUNCE_MS = 1000;
58
67
  function getDebounceMs() {
@@ -98,7 +107,13 @@ function scheduleDebounced(filePath, debounceMs, deps) {
98
107
  const existing = debouncedPipelines.get(filePath);
99
108
  if (existing) {
100
109
  clearTimeout(existing.timer);
101
- existing.latestDeps = deps;
110
+ const incomingId = deps._telemetryParticipantIds?.[0] ?? getReadGuardCorrelationId(deps.event);
111
+ const priorIds = existing.latestDeps._telemetryParticipantIds ?? [];
112
+ existing.latestDeps = {
113
+ ...deps,
114
+ _telemetryParticipantIds: [...priorIds, incomingId].slice(0, 100),
115
+ _telemetryParticipantTotal: (existing.latestDeps._telemetryParticipantTotal ?? priorIds.length) + 1,
116
+ };
102
117
  existing.coalescedCount += 1;
103
118
  existing.timer = setTimeout(() => {
104
119
  debouncedPipelines.delete(filePath);
@@ -114,6 +129,7 @@ function scheduleDebounced(filePath, debounceMs, deps) {
114
129
  resolveFn = res;
115
130
  rejectFn = rej;
116
131
  });
132
+ const initialParticipantIds = deps._telemetryParticipantIds ?? [getReadGuardCorrelationId(deps.event)];
117
133
  const entry = {
118
134
  timer: setTimeout(() => {
119
135
  debouncedPipelines.delete(filePath);
@@ -122,7 +138,11 @@ function scheduleDebounced(filePath, debounceMs, deps) {
122
138
  promise,
123
139
  resolve: resolveFn,
124
140
  reject: rejectFn,
125
- latestDeps: deps,
141
+ latestDeps: {
142
+ ...deps,
143
+ _telemetryParticipantIds: initialParticipantIds.slice(0, 100),
144
+ _telemetryParticipantTotal: deps._telemetryParticipantTotal ?? initialParticipantIds.length,
145
+ },
126
146
  scheduledAt: Date.now(),
127
147
  coalescedCount: 1,
128
148
  };
@@ -139,6 +159,15 @@ function getFileStateHash(filePath) {
139
159
  return `unreadable:${code}`;
140
160
  }
141
161
  }
162
+ function getRequestedEditCount(event) {
163
+ if (event.toolName === "write")
164
+ return 1;
165
+ const edits = event.input?.edits;
166
+ return Array.isArray(edits) && edits.length > 0 ? edits.length : 1;
167
+ }
168
+ function getRequestedEditIndexes(event) {
169
+ return boundedIndexesForCount(getRequestedEditCount(event));
170
+ }
142
171
  function sourceForToolName(toolName, details) {
143
172
  if (details
144
173
  ?.piLensPartialApply) {
@@ -180,6 +209,8 @@ export async function handleToolResult(deps) {
180
209
  : path.resolve(workspaceRoot, rawFilePath)
181
210
  : rawFilePath;
182
211
  const behaviorWarnings = agentBehaviorRecord(event.toolName, filePath);
212
+ const syntheticWriteContent = [];
213
+ let syntheticAttachmentBytes = 0;
183
214
  // Bash writes (redirects, tee, sed -i, cp/mv, touch, git checkout/restore) —
184
215
  // these change file content but never go through the edit tool, so bash
185
216
  // early-returns before the dispatch pipeline below. For each in-project file
@@ -191,23 +222,91 @@ export async function handleToolResult(deps) {
191
222
  if (event.toolName === "bash" &&
192
223
  typeof event.input.command === "string") {
193
224
  const command = event.input.command;
194
- const written = extractWrittenPathsFromCommand(command, workspaceRoot).filter((wp) => !isExternalOrVendorFile(wp, workspaceRoot) &&
225
+ const written = extractWrittenPathsFromCommand(command, workspaceRoot).filter((wp) => event.isError !== true &&
226
+ !isExternalOrVendorFile(wp, workspaceRoot) &&
195
227
  !isPathIgnoredByProject(wp, workspaceRoot, false));
196
228
  for (const wp of written) {
197
229
  if (!getFlag("no-read-guard"))
198
230
  deps.readGuard?.recordWritten(wp);
199
- await handleToolResult({
231
+ const receipt = runtime.recordMutationToolReceipt;
232
+ const autofixMode = receipt
233
+ ? receipt.call(runtime, wp, "write").autofixMode
234
+ : "immediate";
235
+ const syntheticResult = await handleToolResult({
200
236
  ...deps,
201
237
  event: { ...event, toolName: "write", input: { path: wp } },
202
238
  _bypassDebounce: true,
239
+ _autofixMode: autofixMode,
203
240
  });
241
+ if (syntheticResult) {
242
+ // The per-attachment cap bounds each file, but a multi-file bash
243
+ // write (`sed -i` over globs, `;`-chained rewrites) appends one
244
+ // attachment per path — share ONE authoritative-content budget
245
+ // across the whole command so the aggregate tool result stays
246
+ // bounded too. Past the budget, degrade to the re-read warning.
247
+ for (const block of syntheticResult.content.slice(event.content.length)) {
248
+ const blockBytes = typeof block.text === "string"
249
+ ? Buffer.byteLength(block.text, "utf-8")
250
+ : 0;
251
+ const isAuthoritativeAttachment = typeof block.text === "string" &&
252
+ block.text.startsWith("pi-lens applied autofix to ");
253
+ if (isAuthoritativeAttachment &&
254
+ syntheticAttachmentBytes + blockBytes >
255
+ AUTHORITATIVE_CONTENT_MAX_BYTES) {
256
+ // S3e (#1432 review): this is the SECOND
257
+ // `authoritative_content_attachment_decision` row for `wp` —
258
+ // the synthetic `handleToolResult` call above already logged
259
+ // an "attached" row for the same path under its per-file
260
+ // cap. This outer, aggregate-budget row is logged later and
261
+ // is the one that matches what the caller actually sees
262
+ // (the re-read warning below, not the attachment), so it
263
+ // wins for `wp`; the inner "attached" row is a stale
264
+ // per-file view superseded by this shared-budget decision.
265
+ logLatency({
266
+ type: "phase",
267
+ phase: "authoritative_content_attachment_decision",
268
+ filePath: wp,
269
+ durationMs: 0,
270
+ metadata: { path: wp, bytes: blockBytes, decision: "aggregate-budget-degraded" },
271
+ });
272
+ syntheticWriteContent.push({
273
+ type: "text",
274
+ text: `⚠️ **File was modified by auto-format/fix. You MUST re-read ${wp} before making any further edits — the aggregate authoritative content for this command is too large to attach.**`,
275
+ });
276
+ continue;
277
+ }
278
+ if (isAuthoritativeAttachment) {
279
+ syntheticAttachmentBytes += blockBytes;
280
+ }
281
+ syntheticWriteContent.push(block);
282
+ }
283
+ }
284
+ }
285
+ if (event.isError !== true && !getFlag("no-read-guard")) {
286
+ for (const span of extractReadPathsFromCommand(command, workspaceRoot)) {
287
+ if (isExternalOrVendorFile(span.filePath, workspaceRoot))
288
+ continue;
289
+ if (isPathIgnoredByProject(span.filePath, workspaceRoot, false))
290
+ continue;
291
+ deps.readGuard?.recordRead({
292
+ filePath: span.filePath,
293
+ requestedOffset: span.offset,
294
+ requestedLimit: span.limit,
295
+ effectiveOffset: span.offset,
296
+ effectiveLimit: span.limit,
297
+ expandedByLsp: false,
298
+ turnIndex: runtime.turnIndex,
299
+ writeIndex: runtime.peekWriteIndex(),
300
+ timestamp: Date.now(),
301
+ });
302
+ }
204
303
  }
205
304
  }
206
305
  // Search tools reveal specific lines (file:line) the agent then edits — register
207
306
  // those shown lines (± context) as reads so the follow-up edit isn't blocked (#169).
208
307
  // Our tools attach locations as `details.searchReads`; bash grep is parsed from
209
308
  // `grep -n` output. Only shown lines are registered, never the whole file.
210
- if (deps.readGuard && !getFlag("no-read-guard")) {
309
+ if (deps.readGuard && event.isError !== true && !getFlag("no-read-guard")) {
211
310
  const searchReads = [];
212
311
  const detailSearchReads = event.details?.searchReads;
213
312
  if (Array.isArray(detailSearchReads))
@@ -230,7 +329,9 @@ export async function handleToolResult(deps) {
230
329
  }
231
330
  if (event.toolName !== "write" && event.toolName !== "edit") {
232
331
  dbg(`tool_result: skipped turn tracking - toolName="${event.toolName}" (not write/edit)`);
233
- return;
332
+ return syntheticWriteContent.length > 0
333
+ ? { content: [...event.content, ...syntheticWriteContent] }
334
+ : undefined;
234
335
  }
235
336
  if (!filePath) {
236
337
  dbg(`tool_result: skipped turn tracking - no filePath for toolName="${event.toolName}"`);
@@ -240,13 +341,65 @@ export async function handleToolResult(deps) {
240
341
  dbg(`tool_result: skipped pipeline - file outside project root or in node_modules: ${filePath}`);
241
342
  return;
242
343
  }
344
+ const readGuardCorrelationId = getReadGuardCorrelationId(event);
345
+ const resultDetails = (event.details ?? {});
346
+ const isPartialApplyResult = resultDetails.piLensPartialApply === true;
347
+ const requestedEditIndexes = getRequestedEditIndexes(event);
348
+ const requestedEditTotal = getRequestedEditCount(event);
349
+ const participantIds = [
350
+ ...(deps._telemetryParticipantIds ?? []),
351
+ readGuardCorrelationId,
352
+ ].slice(0, 100);
353
+ const participantTotal = (deps._telemetryParticipantTotal ?? 0) +
354
+ (deps._telemetryParticipantIds?.includes(readGuardCorrelationId) ? 0 : 1);
355
+ const hostToolResultFailed = event.isError === true || resultDetails.isError === true;
356
+ if (hostToolResultFailed) {
357
+ logReadGuardEvent({
358
+ event: "edit_batch_summary",
359
+ correlationId: readGuardCorrelationId,
360
+ filePath,
361
+ metadata: {
362
+ tool: event.toolName,
363
+ source: "host_tool_result",
364
+ editBatchSummary: createReadGuardEditBatchSummary({
365
+ requestedIndexes: requestedEditIndexes,
366
+ requestedTotal: requestedEditTotal,
367
+ rejectedReasons: requestedEditIndexes.map((index) => ({
368
+ index,
369
+ code: "write_failed",
370
+ })),
371
+ rejectedTotal: requestedEditTotal,
372
+ participantIds: [readGuardCorrelationId],
373
+ participantTotal: 1,
374
+ commitStatus: "failed",
375
+ terminalStatus: "failed",
376
+ }),
377
+ },
378
+ });
379
+ return { content: event.content, isError: true };
380
+ }
381
+ // Must happen before debounce admission: latestDeps intentionally retains only
382
+ // the latest event, but write -> edit is a sticky turn transition.
383
+ const receipt = runtime.recordMutationToolReceipt;
384
+ const autofixMode = deps._bypassDebounce
385
+ ? (deps._autofixMode ?? (event.toolName === "edit" ? "deferred" : "immediate"))
386
+ : receipt
387
+ ? receipt.call(runtime, filePath, event.toolName).autofixMode
388
+ : event.toolName === "edit"
389
+ ? "deferred"
390
+ : "immediate";
243
391
  // Coalesce sequential edits to the same file into one pipeline run against
244
392
  // the final state. Only the debounce-fired call (with _bypassDebounce=true)
245
393
  // proceeds to the pipeline body; in-window callers share its promise.
246
394
  if (!deps._bypassDebounce) {
247
395
  const debounceMs = getDebounceMs();
248
396
  if (debounceMs > 0) {
249
- return scheduleDebounced(filePath, debounceMs, deps);
397
+ return scheduleDebounced(filePath, debounceMs, {
398
+ ...deps,
399
+ _autofixMode: autofixMode,
400
+ _telemetryParticipantIds: [readGuardCorrelationId],
401
+ _telemetryParticipantTotal: 1,
402
+ });
250
403
  }
251
404
  }
252
405
  // Refresh the read-guard's FileTime stamp so that the model's own write
@@ -275,13 +428,18 @@ export async function handleToolResult(deps) {
275
428
  }
276
429
  }
277
430
  const initialStateHash = getFileStateHash(filePath);
278
- const pipelineDedupeKey = `${filePath}:${initialStateHash}`;
279
431
  // Deduplicate concurrent calls for the same final file state (pi can fire one
280
432
  // tool_result per edit hunk). Do not dedupe by file alone: a distinct later
281
433
  // same-turn edit to this file must still be analyzed.
282
- if (inFlightPipelines.has(pipelineDedupeKey)) {
434
+ const inFlight = inFlightPipelines.get(filePath)?.get(initialStateHash);
435
+ if (inFlight) {
283
436
  dbg(`tool_result: skipping duplicate concurrent state for ${filePath}`);
284
- await inFlightPipelines.get(pipelineDedupeKey);
437
+ const duplicateId = readGuardCorrelationId;
438
+ if (inFlight.participantIds.length < 100) {
439
+ inFlight.participantIds.push(duplicateId);
440
+ }
441
+ inFlight.participantTotal += 1;
442
+ await inFlight.promise;
285
443
  return;
286
444
  }
287
445
  // Deduplicate sequential duplicate events for the same post-write state in the
@@ -319,6 +477,13 @@ export async function handleToolResult(deps) {
319
477
  const writeIndex = runtime.nextWriteIndex();
320
478
  let modifiedRanges;
321
479
  try {
480
+ // #1334 S6: the host DECLARES this payload (`EditToolDetails`, a
481
+ // type-only export), so use it instead of re-declaring `{ diff?: string }`
482
+ // here — the ad-hoc shape hid the sibling `patch`/`firstChangedLine`
483
+ // fields. `Partial<>` keeps the defensive posture: the host types mark
484
+ // `diff` required, but this runs against whatever a live host actually
485
+ // sent, and the `details?.diff` truthiness check below is what the code
486
+ // has always relied on.
322
487
  const details = event.details;
323
488
  dbg(`tool_result: details.diff=${details?.diff ? "present" : "missing"}, details keys: ${Object.keys(event.details || {}).join(", ")}`);
324
489
  if (event.toolName === "edit" && details?.diff) {
@@ -367,13 +532,17 @@ export async function handleToolResult(deps) {
367
532
  const pipelinePromise = runPipeline({
368
533
  filePath,
369
534
  cwd: dispatchCwd,
535
+ projectRoot: turnStateCwd,
370
536
  toolName: event.toolName,
537
+ autofixMode,
371
538
  modifiedRanges,
372
539
  telemetry: {
373
540
  model: runtime.telemetryModel,
374
541
  sessionId: runtime.telemetrySessionId,
375
542
  turnIndex: runtime.turnIndex,
376
543
  writeIndex,
544
+ modelId: runtime.telemetryModelId,
545
+ provider: runtime.telemetryProviderId,
377
546
  },
378
547
  getFlag,
379
548
  getFlagSource,
@@ -404,12 +573,57 @@ export async function handleToolResult(deps) {
404
573
  getFormatService,
405
574
  fixedThisTurn: runtime.fixedThisTurn,
406
575
  });
407
- inFlightPipelines.set(pipelineDedupeKey, pipelinePromise);
576
+ const pipelineTelemetry = {
577
+ promise: pipelinePromise,
578
+ participantIds: [...new Set(participantIds)].slice(0, 100),
579
+ participantTotal,
580
+ };
581
+ let filePipelines = inFlightPipelines.get(filePath);
582
+ if (!filePipelines) {
583
+ filePipelines = new Map();
584
+ inFlightPipelines.set(filePath, filePipelines);
585
+ }
586
+ filePipelines.set(initialStateHash, pipelineTelemetry);
408
587
  try {
409
588
  result = await pipelinePromise;
410
589
  }
411
590
  catch (pipelineErr) {
591
+ if (getFlag("lens-guard")) {
592
+ runtime.markGitGuardCacheUnknown("pipeline_crash");
593
+ }
412
594
  dbg(`runPipeline crashed: ${pipelineErr}`);
595
+ logReadGuardEvent({
596
+ event: "edit_post_edit_pipeline_failed",
597
+ correlationId: readGuardCorrelationId,
598
+ filePath,
599
+ metadata: {
600
+ tool: event.toolName,
601
+ commitStatus: "committed",
602
+ reasonCode: "pipeline_failed",
603
+ },
604
+ });
605
+ logReadGuardEvent({
606
+ event: "edit_batch_summary",
607
+ correlationId: readGuardCorrelationId,
608
+ filePath,
609
+ metadata: {
610
+ tool: event.toolName,
611
+ editBatchSummary: createReadGuardEditBatchSummary({
612
+ requestedIndexes: requestedEditIndexes,
613
+ requestedTotal: requestedEditTotal,
614
+ resolvedIndexes: requestedEditIndexes,
615
+ resolvedTotal: requestedEditTotal,
616
+ appliedIndexes: requestedEditIndexes,
617
+ appliedTotal: requestedEditTotal,
618
+ participantIds: pipelineTelemetry.participantIds,
619
+ participantTotal: pipelineTelemetry.participantTotal,
620
+ commitStatus: "committed",
621
+ postEditStatus: "failed",
622
+ terminalStatus: "failed",
623
+ durationMs: Date.now() - toolResultStart,
624
+ }),
625
+ },
626
+ });
413
627
  dbg(`runPipeline crash stack: ${pipelineErr.stack}`);
414
628
  if (!getFlag("no-lsp")) {
415
629
  resetLSPService({ fast: true, reason: "pipeline_crash" });
@@ -422,14 +636,57 @@ export async function handleToolResult(deps) {
422
636
  result: "pipeline_crash",
423
637
  });
424
638
  const notice = runtime.formatPipelineCrashNotice(filePath, pipelineErr);
425
- if (!notice)
426
- return;
427
639
  return {
428
- content: [...event.content, { type: "text", text: notice }],
640
+ content: notice
641
+ ? [...event.content, { type: "text", text: notice }]
642
+ : event.content,
643
+ isError: true,
429
644
  };
430
645
  }
431
646
  finally {
432
- inFlightPipelines.delete(pipelineDedupeKey);
647
+ // Prune the per-file inner map once it's empty so a file touched once
648
+ // this session doesn't leave a permanent empty entry in the outer map.
649
+ filePipelines.delete(initialStateHash);
650
+ if (filePipelines.size === 0) {
651
+ inFlightPipelines.delete(filePath);
652
+ }
653
+ }
654
+ if (!isPartialApplyResult) {
655
+ const postEditStatus = result.isError ? "failed" : "succeeded";
656
+ if (result.isError) {
657
+ logReadGuardEvent({
658
+ event: "edit_post_edit_pipeline_failed",
659
+ correlationId: readGuardCorrelationId,
660
+ filePath,
661
+ metadata: {
662
+ tool: event.toolName,
663
+ commitStatus: "committed",
664
+ reasonCode: "pipeline_failed",
665
+ },
666
+ });
667
+ }
668
+ logReadGuardEvent({
669
+ event: "edit_batch_summary",
670
+ correlationId: readGuardCorrelationId,
671
+ filePath,
672
+ metadata: {
673
+ tool: event.toolName,
674
+ editBatchSummary: createReadGuardEditBatchSummary({
675
+ requestedIndexes: requestedEditIndexes,
676
+ requestedTotal: requestedEditTotal,
677
+ resolvedIndexes: requestedEditIndexes,
678
+ resolvedTotal: requestedEditTotal,
679
+ appliedIndexes: requestedEditIndexes,
680
+ appliedTotal: requestedEditTotal,
681
+ participantIds: pipelineTelemetry.participantIds,
682
+ participantTotal: pipelineTelemetry.participantTotal,
683
+ commitStatus: "committed",
684
+ postEditStatus,
685
+ terminalStatus: postEditStatus === "failed" ? "failed" : "success",
686
+ durationMs: Date.now() - toolResultStart,
687
+ }),
688
+ },
689
+ });
433
690
  }
434
691
  lastAnalyzedStateByFile.set(filePath, {
435
692
  turnIndex: runtime.turnIndex,
@@ -449,11 +706,19 @@ export async function handleToolResult(deps) {
449
706
  }
450
707
  }
451
708
  }
709
+ let autofixNewlyQueued = false;
710
+ if (!result.isError && autofixMode === "deferred" && nodeFs.existsSync(filePath)) {
711
+ autofixNewlyQueued =
712
+ runtime.deferMutation?.call(runtime, filePath, dispatchCwd, event.toolName, turnStateCwd, "autofix", deps.sessionId) ?? false;
713
+ dbg(`tool_result: queued deferred autofix for ${filePath}`);
714
+ }
715
+ let formatQueued = false;
452
716
  if (!result.isError &&
453
717
  !getFlag("no-autoformat", filePath) &&
454
- !getFlag("immediate-format") &&
718
+ (autofixMode === "deferred" || !getFlag("immediate-format")) &&
455
719
  nodeFs.existsSync(filePath)) {
456
720
  const isNewlyQueued = runtime.deferFormat(filePath, dispatchCwd, event.toolName, turnStateCwd, deps.sessionId);
721
+ formatQueued = true;
457
722
  dbg(`tool_result: queued deferred format for ${filePath}`);
458
723
  logLatency({
459
724
  type: "phase",
@@ -463,19 +728,21 @@ export async function handleToolResult(deps) {
463
728
  durationMs: 0,
464
729
  metadata: { cwd: dispatchCwd },
465
730
  });
466
- // #673: only publish on first queue entry — a re-touch of an already
467
- // queued file (a second edit before agent_end) is a structural no-op
468
- // for a listener that just wants to know "has this file entered the
469
- // queue", so re-emitting would be spam with zero new information.
470
- if (isNewlyQueued) {
731
+ // Publish a file's first queue entry and each newly added kind. A same-kind
732
+ // re-touch before agent_end carries no new information and stays silent.
733
+ if (isNewlyQueued || autofixNewlyQueued) {
471
734
  publishFormatQueued({
472
735
  filePath,
473
736
  cwd: dispatchCwd,
474
737
  tool: event.toolName,
475
738
  dbg,
739
+ kinds: autofixMode === "deferred" ? ["autofix", "format"] : ["format"],
476
740
  });
477
741
  }
478
742
  }
743
+ if (autofixNewlyQueued && !formatQueued) {
744
+ publishFormatQueued({ filePath, cwd: dispatchCwd, tool: event.toolName, kinds: ["autofix"], dbg });
745
+ }
479
746
  for (const changedFile of result.changedFiles ?? []) {
480
747
  const resolvedChanged = path.resolve(changedFile);
481
748
  if (!nodeFs.existsSync(resolvedChanged))
@@ -549,6 +816,13 @@ export async function handleToolResult(deps) {
549
816
  else {
550
817
  runtime.clearInlineBlockers(filePath);
551
818
  }
819
+ runtime.updateGitGuardStatus(result.hasBlockers, result.output);
820
+ if (getFlag("lens-guard")) {
821
+ syncGitGuardRecord(runtime, cacheManager, turnStateCwd, filePath);
822
+ if (result.isError && !result.hasBlockers) {
823
+ runtime.markGitGuardCacheUnknown("pipeline_error");
824
+ }
825
+ }
552
826
  if (result.isError) {
553
827
  return {
554
828
  content: [...event.content, { type: "text", text: result.output }],
@@ -556,7 +830,6 @@ export async function handleToolResult(deps) {
556
830
  };
557
831
  }
558
832
  let output = result.output;
559
- runtime.updateGitGuardStatus(result.hasBlockers, result.output);
560
833
  if (behaviorWarnings.length > 0 && !result.hasBlockers) {
561
834
  output += `\n\n${formatBehaviorWarnings(behaviorWarnings)}`;
562
835
  }
@@ -569,9 +842,45 @@ export async function handleToolResult(deps) {
569
842
  result: output ? "completed" : "no_output",
570
843
  });
571
844
  runtime.reportedThisTurn.add(filePath);
572
- if (!output)
845
+ const postMutation = result.postMutation;
846
+ const attachAuthoritativeContent = postMutation !== undefined &&
847
+ Buffer.byteLength(postMutation.content, "utf-8") <= AUTHORITATIVE_CONTENT_MAX_BYTES;
848
+ if (postMutation) {
849
+ const bytes = Buffer.byteLength(postMutation.content, "utf-8");
850
+ // S3e (#1432 review): when this call is the synthetic per-file
851
+ // `handleToolResult` recursion a multi-file bash write drives (see the
852
+ // bash branch above), the OUTER aggregate-budget loop may log a SECOND
853
+ // `authoritative_content_attachment_decision` row for this same
854
+ // `filePath` right after this one, downgrading an "attached" here to
855
+ // "aggregate-budget-degraded" once the shared budget is exhausted.
856
+ // Both rows are intentional (this one reflects the per-file cap
857
+ // decision; the outer one reflects the aggregate-budget decision that
858
+ // can override it) — the outer row, logged later, wins for that path.
859
+ logLatency({
860
+ type: "phase",
861
+ phase: "authoritative_content_attachment_decision",
862
+ filePath: postMutation.filePath,
863
+ durationMs: 0,
864
+ metadata: { path: postMutation.filePath, bytes, decision: attachAuthoritativeContent ? "attached" : "size-capped" },
865
+ });
866
+ }
867
+ const returnedContent = attachAuthoritativeContent
868
+ ? [
869
+ ...event.content,
870
+ {
871
+ type: "text",
872
+ text: `pi-lens applied autofix to ${postMutation.filePath}. The following full content is authoritative for subsequent edits:\n\n${postMutation.content}`,
873
+ },
874
+ ]
875
+ : event.content;
876
+ if (postMutation && !attachAuthoritativeContent) {
877
+ output = `${output ? `${output}\n\n` : ""}⚠️ **File was modified by auto-format/fix. You MUST re-read ${postMutation.filePath} before making any further edits — the authoritative content is too large to attach.**`;
878
+ }
879
+ if (!output && !result.postMutation)
573
880
  return;
574
881
  return {
575
- content: [...event.content, { type: "text", text: output }],
882
+ content: output
883
+ ? [...returnedContent, { type: "text", text: output }]
884
+ : returnedContent,
576
885
  };
577
886
  }