@herbertgao/pi-extensions 2026.8.3 → 2026.8.5

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 (408) hide show
  1. package/README.md +45 -4
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +10 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +10 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  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 +5 -8
  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 +8 -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 +15 -2
  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/schedule-menu.ts +9 -8
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  31. package/node_modules/@narumitw/pi-btw/README.md +8 -7
  32. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  33. package/node_modules/@narumitw/pi-btw/src/menu.ts +1 -0
  34. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  35. package/node_modules/pi-footer/LICENSE +21 -0
  36. package/node_modules/pi-footer/README.md +354 -0
  37. package/node_modules/pi-footer/package.json +68 -0
  38. package/node_modules/pi-footer/src/cache.ts +106 -0
  39. package/node_modules/pi-footer/src/colors.ts +237 -0
  40. package/node_modules/pi-footer/src/config.ts +202 -0
  41. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  42. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  43. package/node_modules/pi-footer/src/git.ts +135 -0
  44. package/node_modules/pi-footer/src/index.ts +268 -0
  45. package/node_modules/pi-footer/src/metrics.ts +141 -0
  46. package/node_modules/pi-footer/src/presets.ts +383 -0
  47. package/node_modules/pi-footer/src/render.ts +114 -0
  48. package/node_modules/pi-footer/src/separators.ts +52 -0
  49. package/node_modules/pi-footer/src/types.ts +122 -0
  50. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  51. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  52. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  53. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  54. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  55. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  56. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  57. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  58. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  59. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  60. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  61. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  62. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  63. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  64. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  65. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  66. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  67. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  68. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  69. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  70. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  71. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  72. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  73. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  74. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  75. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  76. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  77. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  78. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  79. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  80. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  81. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  82. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  83. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  84. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  85. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  86. package/node_modules/pi-footer/src/ui.ts +66 -0
  87. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  88. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  89. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  90. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  91. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  92. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  93. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  94. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  95. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  96. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  97. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  99. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  100. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  102. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  103. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  105. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  108. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  109. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  111. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  114. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  115. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  116. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  117. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  118. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  119. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  120. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  121. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  122. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  123. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  124. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  125. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  126. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  127. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  128. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  130. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  132. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  133. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  134. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  135. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  136. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  137. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  138. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  148. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  149. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  150. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  151. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  152. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  153. package/node_modules/pi-lens/CHANGELOG.md +2440 -954
  154. package/node_modules/pi-lens/README.md +13 -0
  155. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  156. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  157. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  158. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  159. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  160. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  161. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  162. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  163. package/node_modules/pi-lens/dist/clients/biome-client.js +29 -25
  164. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  165. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  166. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  167. package/node_modules/pi-lens/dist/clients/bus-publish.js +10 -3
  168. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  169. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  170. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  171. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  172. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  173. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  174. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  175. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/console-guard-install.js +14 -0
  177. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  178. package/node_modules/pi-lens/dist/clients/dead-code-client.js +2 -1
  179. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  180. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  181. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  182. package/node_modules/pi-lens/dist/clients/dependency-checker.js +347 -75
  183. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +61 -11
  184. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  185. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +63 -20
  186. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  187. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  188. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  189. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  190. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  191. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1085 -681
  192. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  193. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  194. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  195. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  196. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  197. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  198. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  199. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  201. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  202. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +14 -6
  204. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  205. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  206. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +277 -26
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  219. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  220. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  221. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  222. package/node_modules/pi-lens/dist/clients/extension-log.js +139 -0
  223. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  224. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  225. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  226. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  227. package/node_modules/pi-lens/dist/clients/format-events-publish.js +16 -3
  228. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  229. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  230. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  231. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  232. package/node_modules/pi-lens/dist/clients/git-guard.js +681 -11
  233. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  234. package/node_modules/pi-lens/dist/clients/go-client.js +2 -1
  235. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +8 -0
  236. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  237. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  238. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  239. package/node_modules/pi-lens/dist/clients/installer/index.js +390 -99
  240. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  241. package/node_modules/pi-lens/dist/clients/jscpd-client.js +31 -54
  242. package/node_modules/pi-lens/dist/clients/knip-client.js +15 -36
  243. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  244. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  245. package/node_modules/pi-lens/dist/clients/latency-logger.js +23 -1
  246. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  247. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  248. package/node_modules/pi-lens/dist/clients/lens-events.js +27 -4
  249. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  250. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  251. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +30 -0
  252. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  253. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +1 -1
  254. package/node_modules/pi-lens/dist/clients/lsp/client.js +493 -48
  255. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  256. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +86 -0
  257. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  258. package/node_modules/pi-lens/dist/clients/lsp/index.js +1807 -780
  259. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  260. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  261. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  262. package/node_modules/pi-lens/dist/clients/lsp/server.js +196 -32
  263. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  264. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  265. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  266. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +123 -32
  267. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  268. package/node_modules/pi-lens/dist/clients/mcp/session.js +216 -8
  269. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  270. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  271. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  272. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  273. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  274. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  275. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  277. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  278. package/node_modules/pi-lens/dist/clients/pipeline.js +47 -13
  279. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  280. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  281. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  282. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  283. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  284. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  285. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  286. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  287. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  288. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  289. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  290. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  291. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  292. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  293. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  294. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  295. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  296. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  297. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  298. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  299. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  300. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  301. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  302. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +107 -73
  303. package/node_modules/pi-lens/dist/clients/runtime-context.js +36 -12
  304. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +74 -6
  305. package/node_modules/pi-lens/dist/clients/runtime-session.js +405 -117
  306. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  307. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +218 -23
  308. package/node_modules/pi-lens/dist/clients/runtime-turn.js +237 -64
  309. package/node_modules/pi-lens/dist/clients/rust-client.js +2 -1
  310. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  311. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  313. package/node_modules/pi-lens/dist/clients/sg-runner.js +284 -174
  314. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  315. package/node_modules/pi-lens/dist/clients/smells-rollup.js +224 -0
  316. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  317. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  318. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  319. package/node_modules/pi-lens/dist/clients/startup-timing.js +1 -1
  320. package/node_modules/pi-lens/dist/clients/test-runner-client.js +38 -8
  321. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  322. package/node_modules/pi-lens/dist/clients/tool-policy.js +388 -39
  323. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  324. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  325. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  326. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  327. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  328. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  329. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  330. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  331. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  332. package/node_modules/pi-lens/dist/clients/warm-attach.js +33 -13
  333. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  334. package/node_modules/pi-lens/dist/clients/word-index.js +391 -64
  335. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  336. package/node_modules/pi-lens/dist/index.js +49361 -36888
  337. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  338. package/node_modules/pi-lens/dist/mcp/server.js +211 -44
  339. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  340. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +200 -36
  341. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +153 -40
  342. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  343. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  344. package/node_modules/pi-lens/docs/agent-guide.md +2 -2
  345. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  346. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  347. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  348. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +5 -2
  349. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  350. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  351. package/node_modules/pi-lens/docs/fable.md +27 -0
  352. package/node_modules/pi-lens/docs/features.md +31 -2
  353. package/node_modules/pi-lens/docs/globalconfig.md +45 -1
  354. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  355. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  356. package/node_modules/pi-lens/docs/mcp.md +37 -4
  357. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  358. package/node_modules/pi-lens/docs/settings.md +12 -1
  359. package/node_modules/pi-lens/docs/tools.md +4 -1
  360. package/node_modules/pi-lens/docs/usage.md +18 -0
  361. package/node_modules/pi-lens/package.json +10 -10
  362. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  363. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  364. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  365. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  366. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  367. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  368. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  369. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  370. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  371. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  372. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  373. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  374. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +55 -2
  375. package/node_modules/pi-mcp-adapter/CHANGELOG.md +25 -0
  376. package/node_modules/pi-mcp-adapter/README.md +34 -3
  377. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  378. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  379. package/node_modules/pi-mcp-adapter/direct-tools.ts +54 -7
  380. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  381. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  382. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  383. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  384. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  385. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  386. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  387. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  388. package/node_modules/pi-mcp-adapter/metadata-cache.ts +27 -4
  389. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  390. package/node_modules/pi-mcp-adapter/package.json +3 -3
  391. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  392. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  393. package/node_modules/pi-mcp-adapter/server-manager.ts +36 -7
  394. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  395. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  396. package/node_modules/pi-mcp-adapter/tool-metadata.ts +42 -4
  397. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  398. package/node_modules/pi-mcp-adapter/types.ts +93 -27
  399. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  400. package/package.json +17 -13
  401. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  402. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  403. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  404. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  405. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  406. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  407. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  408. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -1,6 +1,5 @@
1
1
  import * as nodeFs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { extractReadPathsFromCommand, extractWrittenPathsFromCommand, } from "./bash-file-access.js";
4
3
  import { loadBootstrapClients } from "./bootstrap.js";
5
4
  import { detectFileKind } from "./file-kinds.js";
6
5
  import { isPathIgnoredByProject } from "./file-utils.js";
@@ -14,7 +13,7 @@ import { computeTrailingWhitespaceOldTextPatch, findUniqueMatchLineRange, } from
14
13
  import { applyPartiallyApplicableEdits } from "./partial-edit-apply.js";
15
14
  import { isExternalOrVendorFile } from "./path-utils.js";
16
15
  import { EXPANSION_BUDGET_MS, EXPANSION_LIMIT_LINES, tryExpandRead, } from "./read-expansion.js";
17
- import { logReadGuardEvent } from "./read-guard-logger.js";
16
+ import { boundedIndexesForCount, createReadGuardEditBatchSummary, getReadGuardCorrelationId, logReadGuardEvent, } from "./read-guard-logger.js";
18
17
  import { countFileLines, getTouchedLinesForGuard, relocateEditRange, tryCorrectIndentationMismatch, tryCorrectIndentationMismatchFromContent, } from "./read-guard-tool-lines.js";
19
18
  import { handleToolResult } from "./runtime-tool-result.js";
20
19
  import { isToolCallEventType } from "./tool-event.js";
@@ -142,7 +141,42 @@ function getNewContentFromToolCall(event) {
142
141
  }
143
142
  export async function handleToolCall(deps) {
144
143
  const { event, ctx, lensEnabled, getFlag, dbg, runtime, cacheManager, ensureLSPConfigInitialized, updateLspStatus, resetLSPService, getTreeSitterClient = getSharedTreeSitterClient, } = deps;
144
+ const readGuardCorrelationId = getReadGuardCorrelationId(event);
145
+ let filePath;
146
+ const logToolReadGuardEvent = (entry) => logReadGuardEvent({ ...entry, correlationId: readGuardCorrelationId });
145
147
  const toolName = event.toolName ?? "";
148
+ const editInputForTelemetry = event.input;
149
+ const requestedEditIndexes = toolName === "write"
150
+ ? [0]
151
+ : Array.isArray(editInputForTelemetry?.edits)
152
+ ? boundedIndexesForCount(editInputForTelemetry.edits.length)
153
+ : [0];
154
+ const logBlockedEditSummary = (source) => logToolReadGuardEvent({
155
+ event: "edit_batch_summary",
156
+ filePath: filePath ?? "",
157
+ metadata: {
158
+ tool: toolName,
159
+ source,
160
+ editBatchSummary: createReadGuardEditBatchSummary({
161
+ requestedIndexes: requestedEditIndexes,
162
+ requestedTotal: toolName === "write"
163
+ ? 1
164
+ : Array.isArray(editInputForTelemetry?.edits)
165
+ ? editInputForTelemetry.edits.length
166
+ : 1,
167
+ rejectedReasons: requestedEditIndexes.map((index) => ({
168
+ index,
169
+ code: "preflight_blocked",
170
+ })),
171
+ rejectedTotal: toolName === "write"
172
+ ? 1
173
+ : Array.isArray(editInputForTelemetry?.edits)
174
+ ? editInputForTelemetry.edits.length
175
+ : 1,
176
+ terminalStatus: "blocked",
177
+ }),
178
+ },
179
+ });
146
180
  if (!lensEnabled)
147
181
  return;
148
182
  if (getFlag("lens-guard") &&
@@ -156,7 +190,7 @@ export async function handleToolCall(deps) {
156
190
  }
157
191
  }
158
192
  const rawFilePath = getToolCallRawFilePath(toolName, event);
159
- const filePath = resolveToolCallFilePath(rawFilePath, ctx.cwd, runtime.projectRoot);
193
+ filePath = resolveToolCallFilePath(rawFilePath, ctx.cwd, runtime.projectRoot);
160
194
  if (!getFlag("no-lsp")) {
161
195
  try {
162
196
  const configCwd = filePath
@@ -312,7 +346,7 @@ export async function handleToolCall(deps) {
312
346
  else {
313
347
  enclosingSymbol = expansion.enclosingSymbol;
314
348
  }
315
- logReadGuardEvent({
349
+ logToolReadGuardEvent({
316
350
  event: "ts_range_expanded",
317
351
  sessionId: runtime.telemetrySessionId,
318
352
  filePath,
@@ -348,7 +382,7 @@ export async function handleToolCall(deps) {
348
382
  if (toolName === "read" && filePath && !isExternalOrVendor) {
349
383
  const totalLines = countFileLines(filePath);
350
384
  const deliveredLimit = effectiveReadLimit ?? 1;
351
- logReadGuardEvent({
385
+ logToolReadGuardEvent({
352
386
  event: "read_pattern",
353
387
  sessionId: runtime.telemetrySessionId,
354
388
  filePath,
@@ -379,41 +413,6 @@ export async function handleToolCall(deps) {
379
413
  timestamp: Date.now(),
380
414
  });
381
415
  }
382
- // --- Read-Before-Edit Guard: register file access done via `bash` ---
383
- // Mirrors how the Read/Write tools are tracked. Only the bash tool —
384
- // grep/find tools (and their patterns) are not contiguous file access.
385
- // reads (cat/head/tail/sed -n) → recordRead with the exact range shown
386
- // writes (>, >>, tee, sed -i, cp/mv dest, touch) → noteCreatedFile, so the
387
- // agent "owns" the file (recordWritten fires at tool_result), same
388
- // as the Write tool.
389
- if (toolName === "bash" && !getFlag("no-read-guard")) {
390
- const cmd = event.input?.command;
391
- if (typeof cmd === "string" && cmd) {
392
- const effectiveCwd = ctx.cwd ?? runtime.projectRoot ?? process.cwd();
393
- const inScope = (fp) => !isPathIgnoredByProject(fp, runtime.projectRoot, false) &&
394
- !isExternalOrVendorFile(fp, runtime.projectRoot);
395
- for (const span of extractReadPathsFromCommand(cmd, effectiveCwd)) {
396
- if (!inScope(span.filePath))
397
- continue;
398
- runtime.readGuard.recordRead({
399
- filePath: span.filePath,
400
- requestedOffset: span.offset,
401
- requestedLimit: span.limit,
402
- effectiveOffset: span.offset,
403
- effectiveLimit: span.limit,
404
- expandedByLsp: false,
405
- turnIndex: runtime.turnIndex,
406
- writeIndex: runtime.peekWriteIndex(),
407
- timestamp: Date.now(),
408
- });
409
- }
410
- for (const wp of extractWrittenPathsFromCommand(cmd, effectiveCwd)) {
411
- if (!inScope(wp))
412
- continue;
413
- runtime.readGuard.noteCreatedFile(wp, runtime.turnIndex, runtime.peekWriteIndex());
414
- }
415
- }
416
- }
417
416
  const { complexityClient } = await loadBootstrapClients();
418
417
  // Record complexity baseline for historical tracking (booboo/tdi).
419
418
  // Not shown inline - just captured for delta analysis.
@@ -511,7 +510,7 @@ export async function handleToolCall(deps) {
511
510
  continue;
512
511
  entry.apply(escaped);
513
512
  entry.value = escaped;
514
- logReadGuardEvent({
513
+ logToolReadGuardEvent({
515
514
  event: "oldtext_escape_autopatched",
516
515
  sessionId: runtime.telemetrySessionId,
517
516
  filePath,
@@ -541,7 +540,7 @@ export async function handleToolCall(deps) {
541
540
  entry.applyNewText(patch.newText);
542
541
  entry.newText = patch.newText;
543
542
  }
544
- logReadGuardEvent({
543
+ logToolReadGuardEvent({
545
544
  event: "oldtext_trailing_ws_autopatched",
546
545
  sessionId: runtime.telemetrySessionId,
547
546
  filePath,
@@ -612,7 +611,7 @@ export async function handleToolCall(deps) {
612
611
  if (correctedNewText !== undefined) {
613
612
  entry.applyNewText(correctedNewText);
614
613
  }
615
- logReadGuardEvent({
614
+ logToolReadGuardEvent({
616
615
  event: "oldtext_indent_autopatched",
617
616
  sessionId: runtime.telemetrySessionId,
618
617
  filePath,
@@ -651,20 +650,25 @@ export async function handleToolCall(deps) {
651
650
  const isExistingFile = typeof readGuard?.isNewFile !== "function" ||
652
651
  !readGuard.isNewFile(filePath);
653
652
  if (readGuard && isExistingFile && !isExternalOrVendor) {
654
- const { touchedLines, editRanges, preflightError, partiallyApplicable, contentMatchValidated, } = getTouchedLinesForGuard(event, filePath, runtime.telemetrySessionId);
653
+ const { touchedLines, editRanges, preflightError, partiallyApplicable, contentMatchValidated, editBatchSummary, } = getTouchedLinesForGuard(event, filePath, runtime.telemetrySessionId, readGuardCorrelationId);
655
654
  if (preflightError) {
656
655
  if (partiallyApplicable && partiallyApplicable.length > 0) {
657
656
  try {
658
657
  const partial = await applyPartiallyApplicableEdits({
659
658
  filePath,
660
659
  edits: partiallyApplicable,
660
+ summary: editBatchSummary,
661
+ correlationId: readGuardCorrelationId,
661
662
  afterWrite: async () => {
662
663
  const { biomeClient, ruffClient, metricsClient, agentBehaviorClient, } = await loadBootstrapClients();
663
664
  const result = await handleToolResult({
664
665
  event: {
665
666
  toolName: "write",
666
667
  input: { path: filePath },
667
- details: { piLensPartialApply: true },
668
+ details: {
669
+ piLensPartialApply: true,
670
+ readGuardCorrelationId,
671
+ },
668
672
  content: [],
669
673
  provider: event.provider,
670
674
  model: event.model,
@@ -683,20 +687,35 @@ export async function handleToolCall(deps) {
683
687
  agentBehaviorRecord: (toolName, analyzedPath) => agentBehaviorClient.recordToolCall(toolName, analyzedPath),
684
688
  formatBehaviorWarnings: (warnings) => agentBehaviorClient.formatWarnings(warnings),
685
689
  });
690
+ if (result?.isError) {
691
+ throw new Error("post-edit pipeline rejected synthetic partial apply");
692
+ }
686
693
  return result?.content
687
694
  ?.map((item) => item.text)
688
695
  .filter((text) => !!text)
689
696
  .join("\n\n");
690
697
  },
691
698
  });
699
+ if (partial.postEditStatus === "failed") {
700
+ return {
701
+ block: true,
702
+ reason: `${preflightError}\n\nPartial apply pipeline failed after ${partial.appliedCount} edit${partial.appliedCount === 1 ? "" : "s"} committed.`,
703
+ };
704
+ }
692
705
  if (partial.appliedCount > 0) {
693
- logReadGuardEvent({
706
+ logToolReadGuardEvent({
694
707
  event: "edit_partial_apply",
695
708
  sessionId: runtime.telemetrySessionId,
696
709
  filePath,
697
710
  metadata: {
698
711
  appliedCount: partial.appliedCount,
712
+ appliedTotal: partial.appliedTotal,
699
713
  appliedIndices: partial.appliedIndices,
714
+ skippedCount: partial.skippedCount ?? 0,
715
+ skippedTotal: partial.skippedTotal ?? 0,
716
+ skippedIndices: partial.skippedIndices ?? "",
717
+ indexesTruncated: partial.indexesTruncated ?? false,
718
+ editBatchSummary: partial.summary,
700
719
  routedThroughPostEditPipeline: true,
701
720
  },
702
721
  });
@@ -711,9 +730,11 @@ export async function handleToolCall(deps) {
711
730
  // fall through to full block
712
731
  }
713
732
  }
733
+ if (!editBatchSummary)
734
+ logBlockedEditSummary("preflight");
714
735
  return { block: true, reason: preflightError };
715
736
  }
716
- logReadGuardEvent({
737
+ logToolReadGuardEvent({
717
738
  event: "edit_check_started",
718
739
  sessionId: runtime.telemetrySessionId,
719
740
  filePath,
@@ -750,7 +771,7 @@ export async function handleToolCall(deps) {
750
771
  writeIndex: 0,
751
772
  timestamp: Date.now(),
752
773
  });
753
- logReadGuardEvent({
774
+ logToolReadGuardEvent({
754
775
  event: "edit_range_relocated",
755
776
  sessionId: runtime.telemetrySessionId,
756
777
  filePath,
@@ -763,10 +784,12 @@ export async function handleToolCall(deps) {
763
784
  // Relocation applied — let the re-targeted edit proceed.
764
785
  }
765
786
  else if (verdict.action === "block") {
787
+ logBlockedEditSummary("range_relocated_blocked");
766
788
  return { block: true, reason: verdict.reason };
767
789
  }
768
790
  }
769
791
  else if (verdict.action === "block") {
792
+ logBlockedEditSummary("read_guard_blocked");
770
793
  return {
771
794
  block: true,
772
795
  reason: verdict.reason,
@@ -1,17 +1,20 @@
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";
16
19
  function parseDiffRanges(diff) {
17
20
  const changedLines = [];
@@ -40,19 +43,23 @@ function parseDiffRanges(diff) {
40
43
  ranges.push({ start: rangeStart, end: rangeEnd });
41
44
  return ranges;
42
45
  }
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();
46
+ // Keyed by (normalized) filePath, then by the raw stateHash — the path portion
47
+ // needs normalizing (divergent Windows spellings must collapse to one entry),
48
+ // the stateHash suffix must NOT be folded into the path key (a real content
49
+ // change for the same file has to stay a distinct entry). A flat
50
+ // `PathKeyedMap<InFlightPipeline>` keyed by a composite `${filePath}:${hash}`
51
+ // string can't express that split cleanly (the normalizer only sees the whole
52
+ // composite string, so it can't fold the path half without also mangling the
53
+ // hash half); nesting keeps each axis normalized/compared with its own rules.
54
+ const inFlightPipelines = new PathKeyedMap(normalizeEphemeralMapKey);
55
+ const lastAnalyzedStateByFile = new PathKeyedMap(normalizeEphemeralMapKey);
49
56
  // Called at turn_start — entries from the previous turn can never match the new
50
57
  // turnIndex so they're dead weight. Clearing here keeps the map bounded to the
51
58
  // files touched in the current turn only (typically < 20).
52
59
  export function clearLastAnalyzedStateCache() {
53
60
  lastAnalyzedStateByFile.clear();
54
61
  }
55
- const debouncedPipelines = new Map();
62
+ const debouncedPipelines = new PathKeyedMap(normalizeEphemeralMapKey);
56
63
  const DEFAULT_DEBOUNCE_MS = 0;
57
64
  const MAX_DEBOUNCE_MS = 1000;
58
65
  function getDebounceMs() {
@@ -98,7 +105,13 @@ function scheduleDebounced(filePath, debounceMs, deps) {
98
105
  const existing = debouncedPipelines.get(filePath);
99
106
  if (existing) {
100
107
  clearTimeout(existing.timer);
101
- existing.latestDeps = deps;
108
+ const incomingId = deps._telemetryParticipantIds?.[0] ?? getReadGuardCorrelationId(deps.event);
109
+ const priorIds = existing.latestDeps._telemetryParticipantIds ?? [];
110
+ existing.latestDeps = {
111
+ ...deps,
112
+ _telemetryParticipantIds: [...priorIds, incomingId].slice(0, 100),
113
+ _telemetryParticipantTotal: (existing.latestDeps._telemetryParticipantTotal ?? priorIds.length) + 1,
114
+ };
102
115
  existing.coalescedCount += 1;
103
116
  existing.timer = setTimeout(() => {
104
117
  debouncedPipelines.delete(filePath);
@@ -114,6 +127,7 @@ function scheduleDebounced(filePath, debounceMs, deps) {
114
127
  resolveFn = res;
115
128
  rejectFn = rej;
116
129
  });
130
+ const initialParticipantIds = deps._telemetryParticipantIds ?? [getReadGuardCorrelationId(deps.event)];
117
131
  const entry = {
118
132
  timer: setTimeout(() => {
119
133
  debouncedPipelines.delete(filePath);
@@ -122,7 +136,11 @@ function scheduleDebounced(filePath, debounceMs, deps) {
122
136
  promise,
123
137
  resolve: resolveFn,
124
138
  reject: rejectFn,
125
- latestDeps: deps,
139
+ latestDeps: {
140
+ ...deps,
141
+ _telemetryParticipantIds: initialParticipantIds.slice(0, 100),
142
+ _telemetryParticipantTotal: deps._telemetryParticipantTotal ?? initialParticipantIds.length,
143
+ },
126
144
  scheduledAt: Date.now(),
127
145
  coalescedCount: 1,
128
146
  };
@@ -139,6 +157,15 @@ function getFileStateHash(filePath) {
139
157
  return `unreadable:${code}`;
140
158
  }
141
159
  }
160
+ function getRequestedEditCount(event) {
161
+ if (event.toolName === "write")
162
+ return 1;
163
+ const edits = event.input?.edits;
164
+ return Array.isArray(edits) && edits.length > 0 ? edits.length : 1;
165
+ }
166
+ function getRequestedEditIndexes(event) {
167
+ return boundedIndexesForCount(getRequestedEditCount(event));
168
+ }
142
169
  function sourceForToolName(toolName, details) {
143
170
  if (details
144
171
  ?.piLensPartialApply) {
@@ -191,7 +218,8 @@ export async function handleToolResult(deps) {
191
218
  if (event.toolName === "bash" &&
192
219
  typeof event.input.command === "string") {
193
220
  const command = event.input.command;
194
- const written = extractWrittenPathsFromCommand(command, workspaceRoot).filter((wp) => !isExternalOrVendorFile(wp, workspaceRoot) &&
221
+ const written = extractWrittenPathsFromCommand(command, workspaceRoot).filter((wp) => event.isError !== true &&
222
+ !isExternalOrVendorFile(wp, workspaceRoot) &&
195
223
  !isPathIgnoredByProject(wp, workspaceRoot, false));
196
224
  for (const wp of written) {
197
225
  if (!getFlag("no-read-guard"))
@@ -202,12 +230,31 @@ export async function handleToolResult(deps) {
202
230
  _bypassDebounce: true,
203
231
  });
204
232
  }
233
+ if (event.isError !== true && !getFlag("no-read-guard")) {
234
+ for (const span of extractReadPathsFromCommand(command, workspaceRoot)) {
235
+ if (isExternalOrVendorFile(span.filePath, workspaceRoot))
236
+ continue;
237
+ if (isPathIgnoredByProject(span.filePath, workspaceRoot, false))
238
+ continue;
239
+ deps.readGuard?.recordRead({
240
+ filePath: span.filePath,
241
+ requestedOffset: span.offset,
242
+ requestedLimit: span.limit,
243
+ effectiveOffset: span.offset,
244
+ effectiveLimit: span.limit,
245
+ expandedByLsp: false,
246
+ turnIndex: runtime.turnIndex,
247
+ writeIndex: runtime.peekWriteIndex(),
248
+ timestamp: Date.now(),
249
+ });
250
+ }
251
+ }
205
252
  }
206
253
  // Search tools reveal specific lines (file:line) the agent then edits — register
207
254
  // those shown lines (± context) as reads so the follow-up edit isn't blocked (#169).
208
255
  // Our tools attach locations as `details.searchReads`; bash grep is parsed from
209
256
  // `grep -n` output. Only shown lines are registered, never the whole file.
210
- if (deps.readGuard && !getFlag("no-read-guard")) {
257
+ if (deps.readGuard && event.isError !== true && !getFlag("no-read-guard")) {
211
258
  const searchReads = [];
212
259
  const detailSearchReads = event.details?.searchReads;
213
260
  if (Array.isArray(detailSearchReads))
@@ -240,13 +287,54 @@ export async function handleToolResult(deps) {
240
287
  dbg(`tool_result: skipped pipeline - file outside project root or in node_modules: ${filePath}`);
241
288
  return;
242
289
  }
290
+ const readGuardCorrelationId = getReadGuardCorrelationId(event);
291
+ const resultDetails = (event.details ?? {});
292
+ const isPartialApplyResult = resultDetails.piLensPartialApply === true;
293
+ const requestedEditIndexes = getRequestedEditIndexes(event);
294
+ const requestedEditTotal = getRequestedEditCount(event);
295
+ const participantIds = [
296
+ ...(deps._telemetryParticipantIds ?? []),
297
+ readGuardCorrelationId,
298
+ ].slice(0, 100);
299
+ const participantTotal = (deps._telemetryParticipantTotal ?? 0) +
300
+ (deps._telemetryParticipantIds?.includes(readGuardCorrelationId) ? 0 : 1);
301
+ const hostToolResultFailed = event.isError === true || resultDetails.isError === true;
302
+ if (hostToolResultFailed) {
303
+ logReadGuardEvent({
304
+ event: "edit_batch_summary",
305
+ correlationId: readGuardCorrelationId,
306
+ filePath,
307
+ metadata: {
308
+ tool: event.toolName,
309
+ source: "host_tool_result",
310
+ editBatchSummary: createReadGuardEditBatchSummary({
311
+ requestedIndexes: requestedEditIndexes,
312
+ requestedTotal: requestedEditTotal,
313
+ rejectedReasons: requestedEditIndexes.map((index) => ({
314
+ index,
315
+ code: "write_failed",
316
+ })),
317
+ rejectedTotal: requestedEditTotal,
318
+ participantIds: [readGuardCorrelationId],
319
+ participantTotal: 1,
320
+ commitStatus: "failed",
321
+ terminalStatus: "failed",
322
+ }),
323
+ },
324
+ });
325
+ return { content: event.content, isError: true };
326
+ }
243
327
  // Coalesce sequential edits to the same file into one pipeline run against
244
328
  // the final state. Only the debounce-fired call (with _bypassDebounce=true)
245
329
  // proceeds to the pipeline body; in-window callers share its promise.
246
330
  if (!deps._bypassDebounce) {
247
331
  const debounceMs = getDebounceMs();
248
332
  if (debounceMs > 0) {
249
- return scheduleDebounced(filePath, debounceMs, deps);
333
+ return scheduleDebounced(filePath, debounceMs, {
334
+ ...deps,
335
+ _telemetryParticipantIds: [readGuardCorrelationId],
336
+ _telemetryParticipantTotal: 1,
337
+ });
250
338
  }
251
339
  }
252
340
  // Refresh the read-guard's FileTime stamp so that the model's own write
@@ -275,13 +363,18 @@ export async function handleToolResult(deps) {
275
363
  }
276
364
  }
277
365
  const initialStateHash = getFileStateHash(filePath);
278
- const pipelineDedupeKey = `${filePath}:${initialStateHash}`;
279
366
  // Deduplicate concurrent calls for the same final file state (pi can fire one
280
367
  // tool_result per edit hunk). Do not dedupe by file alone: a distinct later
281
368
  // same-turn edit to this file must still be analyzed.
282
- if (inFlightPipelines.has(pipelineDedupeKey)) {
369
+ const inFlight = inFlightPipelines.get(filePath)?.get(initialStateHash);
370
+ if (inFlight) {
283
371
  dbg(`tool_result: skipping duplicate concurrent state for ${filePath}`);
284
- await inFlightPipelines.get(pipelineDedupeKey);
372
+ const duplicateId = readGuardCorrelationId;
373
+ if (inFlight.participantIds.length < 100) {
374
+ inFlight.participantIds.push(duplicateId);
375
+ }
376
+ inFlight.participantTotal += 1;
377
+ await inFlight.promise;
285
378
  return;
286
379
  }
287
380
  // Deduplicate sequential duplicate events for the same post-write state in the
@@ -319,6 +412,13 @@ export async function handleToolResult(deps) {
319
412
  const writeIndex = runtime.nextWriteIndex();
320
413
  let modifiedRanges;
321
414
  try {
415
+ // #1334 S6: the host DECLARES this payload (`EditToolDetails`, a
416
+ // type-only export), so use it instead of re-declaring `{ diff?: string }`
417
+ // here — the ad-hoc shape hid the sibling `patch`/`firstChangedLine`
418
+ // fields. `Partial<>` keeps the defensive posture: the host types mark
419
+ // `diff` required, but this runs against whatever a live host actually
420
+ // sent, and the `details?.diff` truthiness check below is what the code
421
+ // has always relied on.
322
422
  const details = event.details;
323
423
  dbg(`tool_result: details.diff=${details?.diff ? "present" : "missing"}, details keys: ${Object.keys(event.details || {}).join(", ")}`);
324
424
  if (event.toolName === "edit" && details?.diff) {
@@ -367,6 +467,7 @@ export async function handleToolResult(deps) {
367
467
  const pipelinePromise = runPipeline({
368
468
  filePath,
369
469
  cwd: dispatchCwd,
470
+ projectRoot: turnStateCwd,
370
471
  toolName: event.toolName,
371
472
  modifiedRanges,
372
473
  telemetry: {
@@ -404,12 +505,57 @@ export async function handleToolResult(deps) {
404
505
  getFormatService,
405
506
  fixedThisTurn: runtime.fixedThisTurn,
406
507
  });
407
- inFlightPipelines.set(pipelineDedupeKey, pipelinePromise);
508
+ const pipelineTelemetry = {
509
+ promise: pipelinePromise,
510
+ participantIds: [...new Set(participantIds)].slice(0, 100),
511
+ participantTotal,
512
+ };
513
+ let filePipelines = inFlightPipelines.get(filePath);
514
+ if (!filePipelines) {
515
+ filePipelines = new Map();
516
+ inFlightPipelines.set(filePath, filePipelines);
517
+ }
518
+ filePipelines.set(initialStateHash, pipelineTelemetry);
408
519
  try {
409
520
  result = await pipelinePromise;
410
521
  }
411
522
  catch (pipelineErr) {
523
+ if (getFlag("lens-guard")) {
524
+ runtime.markGitGuardCacheUnknown("pipeline_crash");
525
+ }
412
526
  dbg(`runPipeline crashed: ${pipelineErr}`);
527
+ logReadGuardEvent({
528
+ event: "edit_post_edit_pipeline_failed",
529
+ correlationId: readGuardCorrelationId,
530
+ filePath,
531
+ metadata: {
532
+ tool: event.toolName,
533
+ commitStatus: "committed",
534
+ reasonCode: "pipeline_failed",
535
+ },
536
+ });
537
+ logReadGuardEvent({
538
+ event: "edit_batch_summary",
539
+ correlationId: readGuardCorrelationId,
540
+ filePath,
541
+ metadata: {
542
+ tool: event.toolName,
543
+ editBatchSummary: createReadGuardEditBatchSummary({
544
+ requestedIndexes: requestedEditIndexes,
545
+ requestedTotal: requestedEditTotal,
546
+ resolvedIndexes: requestedEditIndexes,
547
+ resolvedTotal: requestedEditTotal,
548
+ appliedIndexes: requestedEditIndexes,
549
+ appliedTotal: requestedEditTotal,
550
+ participantIds: pipelineTelemetry.participantIds,
551
+ participantTotal: pipelineTelemetry.participantTotal,
552
+ commitStatus: "committed",
553
+ postEditStatus: "failed",
554
+ terminalStatus: "failed",
555
+ durationMs: Date.now() - toolResultStart,
556
+ }),
557
+ },
558
+ });
413
559
  dbg(`runPipeline crash stack: ${pipelineErr.stack}`);
414
560
  if (!getFlag("no-lsp")) {
415
561
  resetLSPService({ fast: true, reason: "pipeline_crash" });
@@ -422,14 +568,57 @@ export async function handleToolResult(deps) {
422
568
  result: "pipeline_crash",
423
569
  });
424
570
  const notice = runtime.formatPipelineCrashNotice(filePath, pipelineErr);
425
- if (!notice)
426
- return;
427
571
  return {
428
- content: [...event.content, { type: "text", text: notice }],
572
+ content: notice
573
+ ? [...event.content, { type: "text", text: notice }]
574
+ : event.content,
575
+ isError: true,
429
576
  };
430
577
  }
431
578
  finally {
432
- inFlightPipelines.delete(pipelineDedupeKey);
579
+ // Prune the per-file inner map once it's empty so a file touched once
580
+ // this session doesn't leave a permanent empty entry in the outer map.
581
+ filePipelines.delete(initialStateHash);
582
+ if (filePipelines.size === 0) {
583
+ inFlightPipelines.delete(filePath);
584
+ }
585
+ }
586
+ if (!isPartialApplyResult) {
587
+ const postEditStatus = result.isError ? "failed" : "succeeded";
588
+ if (result.isError) {
589
+ logReadGuardEvent({
590
+ event: "edit_post_edit_pipeline_failed",
591
+ correlationId: readGuardCorrelationId,
592
+ filePath,
593
+ metadata: {
594
+ tool: event.toolName,
595
+ commitStatus: "committed",
596
+ reasonCode: "pipeline_failed",
597
+ },
598
+ });
599
+ }
600
+ logReadGuardEvent({
601
+ event: "edit_batch_summary",
602
+ correlationId: readGuardCorrelationId,
603
+ filePath,
604
+ metadata: {
605
+ tool: event.toolName,
606
+ editBatchSummary: createReadGuardEditBatchSummary({
607
+ requestedIndexes: requestedEditIndexes,
608
+ requestedTotal: requestedEditTotal,
609
+ resolvedIndexes: requestedEditIndexes,
610
+ resolvedTotal: requestedEditTotal,
611
+ appliedIndexes: requestedEditIndexes,
612
+ appliedTotal: requestedEditTotal,
613
+ participantIds: pipelineTelemetry.participantIds,
614
+ participantTotal: pipelineTelemetry.participantTotal,
615
+ commitStatus: "committed",
616
+ postEditStatus,
617
+ terminalStatus: postEditStatus === "failed" ? "failed" : "success",
618
+ durationMs: Date.now() - toolResultStart,
619
+ }),
620
+ },
621
+ });
433
622
  }
434
623
  lastAnalyzedStateByFile.set(filePath, {
435
624
  turnIndex: runtime.turnIndex,
@@ -549,6 +738,13 @@ export async function handleToolResult(deps) {
549
738
  else {
550
739
  runtime.clearInlineBlockers(filePath);
551
740
  }
741
+ runtime.updateGitGuardStatus(result.hasBlockers, result.output);
742
+ if (getFlag("lens-guard")) {
743
+ syncGitGuardRecord(runtime, cacheManager, turnStateCwd, filePath);
744
+ if (result.isError && !result.hasBlockers) {
745
+ runtime.markGitGuardCacheUnknown("pipeline_error");
746
+ }
747
+ }
552
748
  if (result.isError) {
553
749
  return {
554
750
  content: [...event.content, { type: "text", text: result.output }],
@@ -556,7 +752,6 @@ export async function handleToolResult(deps) {
556
752
  };
557
753
  }
558
754
  let output = result.output;
559
- runtime.updateGitGuardStatus(result.hasBlockers, result.output);
560
755
  if (behaviorWarnings.length > 0 && !result.hasBlockers) {
561
756
  output += `\n\n${formatBehaviorWarnings(behaviorWarnings)}`;
562
757
  }