@herbertgao/pi-extensions 2026.9.5 → 2026.9.7

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 (276) hide show
  1. package/README.md +5 -22
  2. package/THIRD_PARTY_NOTICES.md +0 -4
  3. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +1 -1
  4. package/node_modules/@herbertgao/sol-pi/package.json +1 -1
  5. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +0 -5
  6. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +3 -1
  7. package/node_modules/@narumitw/pi-btw/README.md +2 -0
  8. package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
  9. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  10. package/node_modules/@narumitw/pi-btw/package.json +52 -53
  11. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
  12. package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
  13. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
  14. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
  15. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
  16. package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
  17. package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
  18. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
  19. package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
  20. package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
  22. package/node_modules/pi-lens/CHANGELOG.md +239 -0
  23. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
  24. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
  25. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
  26. package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
  27. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
  28. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
  29. package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
  30. package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
  31. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
  32. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
  33. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
  34. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
  35. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
  36. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
  37. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
  38. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
  39. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
  40. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
  41. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
  44. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
  45. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
  46. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
  47. package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
  48. package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
  49. package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
  50. package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
  51. package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
  52. package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
  53. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
  54. package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
  55. package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
  56. package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
  57. package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
  58. package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
  59. package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
  60. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
  61. package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
  62. package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
  63. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  64. package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
  65. package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
  66. package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
  67. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
  68. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
  69. package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
  70. package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
  71. package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
  72. package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
  73. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
  74. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
  75. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
  76. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
  77. package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
  78. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
  79. package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
  80. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
  81. package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
  82. package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
  83. package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
  84. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  85. package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
  86. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
  87. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
  88. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
  89. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
  90. package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
  91. package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
  92. package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
  93. package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
  94. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
  95. package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
  96. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
  97. package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
  98. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
  99. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
  100. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
  101. package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
  102. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
  103. package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
  104. package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
  105. package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
  106. package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
  107. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
  108. package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
  109. package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
  110. package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
  111. package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
  112. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
  113. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
  114. package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
  115. package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
  116. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
  117. package/node_modules/pi-lens/dist/index.js +27416 -24153
  118. package/node_modules/pi-lens/dist/mcp/server.js +5 -2
  119. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
  120. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
  121. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
  122. package/node_modules/pi-lens/docs/audit1.md +5 -5
  123. package/node_modules/pi-lens/docs/dependencies.md +11 -0
  124. package/node_modules/pi-lens/docs/dispositions.md +52 -0
  125. package/node_modules/pi-lens/docs/environment-variables.md +2 -2
  126. package/node_modules/pi-lens/docs/features.md +4 -4
  127. package/node_modules/pi-lens/docs/globalconfig.md +3 -1
  128. package/node_modules/pi-lens/docs/language-coverage.md +1 -0
  129. package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
  130. package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
  131. package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
  132. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
  133. package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
  134. package/node_modules/pi-lens/docs/settings.md +7 -4
  135. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  136. package/node_modules/pi-lens/docs/usage.md +2 -0
  137. package/node_modules/pi-lens/package.json +1 -1
  138. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
  139. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
  140. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
  141. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
  142. package/package.json +6 -17
  143. package/examples/pi-footer.json +0 -275
  144. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +0 -70
  145. package/node_modules/@czottmann/pi-automode/LICENSE.md +0 -21
  146. package/node_modules/@czottmann/pi-automode/README.md +0 -215
  147. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +0 -39
  148. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +0 -46
  149. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +0 -60
  150. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +0 -6
  151. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +0 -428
  152. package/node_modules/@czottmann/pi-automode/docs/configuration.md +0 -171
  153. package/node_modules/@czottmann/pi-automode/docs/defaults.md +0 -178
  154. package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +0 -90
  155. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +0 -160
  156. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +0 -44
  157. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +0 -692
  158. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +0 -852
  159. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +0 -904
  160. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +0 -228
  161. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +0 -954
  162. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +0 -429
  163. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +0 -173
  164. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model-selector.ts +0 -113
  165. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/model.ts +0 -13
  166. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +0 -297
  167. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +0 -623
  168. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +0 -102
  169. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/transcript.ts +0 -236
  170. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +0 -205
  171. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +0 -54
  172. package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +0 -26
  173. package/node_modules/@czottmann/pi-automode/package.json +0 -61
  174. package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +0 -63
  175. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
  176. package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
  177. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
  178. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
  179. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
  180. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
  181. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
  182. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  183. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  184. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
  185. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
  186. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
  187. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
  188. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
  189. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
  190. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
  191. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
  192. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
  193. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
  194. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
  195. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
  196. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
  197. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
  198. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
  199. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
  200. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
  201. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
  202. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
  203. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
  204. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
  205. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
  206. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
  207. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
  208. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
  209. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
  210. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
  211. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
  212. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
  213. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
  214. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
  215. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
  216. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
  217. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
  218. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
  219. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
  220. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
  221. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
  222. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
  223. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
  224. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
  225. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
  226. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
  227. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
  228. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
  229. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
  230. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
  231. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
  232. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
  233. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
  234. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
  235. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
  236. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
  237. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
  238. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
  239. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
  240. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
  241. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
  242. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
  243. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
  244. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
  245. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
  246. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
  247. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
  248. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
  249. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
  250. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
  251. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
  252. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
  253. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
  254. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
  255. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
  256. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
  257. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
  258. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
  259. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
  260. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
  261. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
  262. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
  263. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
  264. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
  265. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
  266. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
  267. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
  268. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
  269. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
  270. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
  271. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
  272. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
  273. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
  274. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
  275. package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
  276. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
@@ -2,8 +2,9 @@ import * as nodeCrypto from "node:crypto";
2
2
  import * as nodeFs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { noteAuthoritativeContentAttachment } from "./agent-nudge.js";
5
- import { captureFileStats, diffFileStats, getOpaqueBaselineStore, recoverOpaqueChangesViaGit, } from "./opaque-mutation-scan.js";
5
+ import { captureFileStats, diffFileContent, getOpaqueBaselineStore, recoverOpaqueChangesViaGit, } from "./opaque-mutation-scan.js";
6
6
  import { normalizeMapKey } from "./path-utils.js";
7
+ import { detectFileKind } from "./file-kinds.js";
7
8
  import { extractReadPathsFromCommand, extractDeletedPathsFromCommand, extractGrepSearchReadsFromOutput, extractWrittenPathsFromCommand, tokenizeShellCommand, } from "./bash-file-access.js";
8
9
  import { registerSearchReads, } from "./search-read-registration.js";
9
10
  import { createFileTime } from "./file-time.js";
@@ -21,6 +22,7 @@ import { replayThroughMutationBridge, storedLineHashesFor, } from "./observed-mu
21
22
  import { getAmbientAbortSignal } from "./safe-spawn.js";
22
23
  import { resolveToolCallCorrelationId } from "./tool-event.js";
23
24
  import { boundedIndexesForCount, createReadGuardEditBatchSummary, getReadGuardCorrelationId, logReadGuardEvent, } from "./read-guard-logger.js";
25
+ import { countFileLines } from "./read-guard-tool-lines.js";
24
26
  import { notifyExternalFileChange, resyncGitChangedFiles, } from "./lsp/index.js";
25
27
  import { runPipeline } from "./pipeline.js";
26
28
  import { renderPostAutofixNotice, } from "./post-autofix-notice.js";
@@ -28,7 +30,14 @@ import { syncGitGuardRecord } from "./git-guard.js";
28
30
  import { scheduleWordIndexPersist } from "./word-index.js";
29
31
  import { RUNTIME_CONFIG } from "./runtime-config.js";
30
32
  import { getActiveSessionId } from "./session-lifecycle.js";
33
+ import { requestBootstrapClients } from "./bootstrap.js";
34
+ import { bounded } from "./deadline-utils.js";
35
+ import { HOOK_WALL_BUDGET_MS } from "./hook-budgets.js";
36
+ import { incrementDegradationCount, recordDegradationOnce, } from "./degradation-ledger.js";
31
37
  const AUTHORITATIVE_CONTENT_MAX_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
38
+ // Keep same-turn analysis below the observed-directory capture bound so one
39
+ // opaque codemod cannot monopolize the hook or analyzer fleet.
40
+ const OBSERVED_DISPATCH_MAX_PATHS = 32;
32
41
  /**
33
42
  * Git subcommands that import ANOTHER commit's content into the index. The
34
43
  * whole family, with a verdict for each (#2060):
@@ -93,6 +102,29 @@ export function isFailedGitIntegrationCommand(command, isError) {
93
102
  return false;
94
103
  });
95
104
  }
105
+ function ensureToolResultClients(deps) {
106
+ if (deps.biomeClient && deps.ruffClient && deps.metricsClient)
107
+ return true;
108
+ const request = {
109
+ reason: "tool-result-analysis",
110
+ hook: "tool_result_edit",
111
+ timeoutMs: HOOK_WALL_BUDGET_MS.tool_result_edit,
112
+ ...(deps.signal === undefined ? {} : { signal: deps.signal }),
113
+ };
114
+ return bounded(requestBootstrapClients(request), {
115
+ ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
116
+ signal: deps.signal,
117
+ hook: "tool_result_edit",
118
+ label: "tool-result-bootstrap-demand",
119
+ }).then((clients) => {
120
+ if (!clients)
121
+ return false;
122
+ deps.biomeClient = clients.biomeClient;
123
+ deps.ruffClient = clients.ruffClient;
124
+ deps.metricsClient = clients.metricsClient;
125
+ return true;
126
+ });
127
+ }
96
128
  function parseDiffRanges(diff) {
97
129
  const changedLines = [];
98
130
  for (const line of diff.split("\n")) {
@@ -144,13 +176,11 @@ export function clearLastAnalyzedStateCache() {
144
176
  * with one owner (#2464 review round 3, F1) rather than two open-coded blocks
145
177
  * ~80 lines apart whose invariants only line up if a reader checks both.
146
178
  *
147
- * Exported for the isolation red in
148
- * `tests/clients/observed-mutation-integration.test.ts`: with
149
- * `claimPipelineDispatch` below now consulted by BOTH dispatch call sites, the
150
- * identity guard in the release has no reachable production trigger left, so
151
- * the only honest way to prove it is to drive the seam directly.
179
+ * Kept private because both dispatch call sites now use the shared claim before
180
+ * registration; publishing this seam without the claim obligation would make
181
+ * the identity guard's precondition easy to violate again.
152
182
  */
153
- export function registerInFlightPipeline(filePath, stateHash, pipeline) {
183
+ function registerInFlightPipeline(filePath, stateHash, pipeline) {
154
184
  let filePipelines = inFlightPipelines.get(filePath);
155
185
  if (!filePipelines) {
156
186
  filePipelines = new Map();
@@ -172,7 +202,7 @@ export function registerInFlightPipeline(filePath, stateHash, pipeline) {
172
202
  * deduping and its `participantIds`/`participantTotal` under-count. Delete only
173
203
  * when the outer map still points at the very map this registration went into.
174
204
  */
175
- export function releaseInFlightPipeline(filePath, stateHash, registered) {
205
+ function releaseInFlightPipeline(filePath, stateHash, registered) {
176
206
  registered.delete(stateHash);
177
207
  if (registered.size === 0 && inFlightPipelines.get(filePath) === registered) {
178
208
  inFlightPipelines.delete(filePath);
@@ -208,7 +238,7 @@ export function releaseInFlightPipeline(filePath, stateHash, registered) {
208
238
  * already analysed this turn — the duplicate-recording inversion #2464 exists
209
239
  * to remove.
210
240
  */
211
- export function claimPipelineDispatch(args) {
241
+ function claimPipelineDispatch(args) {
212
242
  const { filePath, stateHash, turnIndex, participantId, dbg } = args;
213
243
  // Deduplicate concurrent calls for the same final file state (pi can fire one
214
244
  // tool_result per edit hunk). Do not dedupe by file alone: a distinct later
@@ -454,9 +484,10 @@ function refreshCachedExports(runtime, filePath) {
454
484
  * pipeline AND record" block could not express.
455
485
  */
456
486
  async function dispatchPipelineAnalysis(args) {
457
- const { deps, runtime, filePath, dispatchCwd, turnStateCwd, autofixMode, modifiedRanges, writeIndex, initialStateHash, readGuardCorrelationId, requestedEditIndexes, requestedEditTotal, isPartialApplyResult, toolResultStart, nativeAppliedPairs, } = args;
487
+ const { deps, runtime, filePath, dispatchCwd, turnStateCwd, autofixMode, modifiedRanges, writeIndex, initialStateHash, readGuardCorrelationId, requestedEditIndexes, requestedEditTotal, isPartialApplyResult, toolResultStart, nativeAppliedPairs, allowReadGuardWrites, } = args;
458
488
  const { event, getFlag, getFlagSource, dbg, biomeClient, ruffClient, metricsClient, resetLSPService, } = deps;
459
489
  const pipelinePromise = runPipeline({
490
+ signal: deps.signal,
460
491
  filePath,
461
492
  cwd: dispatchCwd,
462
493
  projectRoot: turnStateCwd,
@@ -497,9 +528,9 @@ async function dispatchPipelineAnalysis(args) {
497
528
  scheduleWordIndexPersist(dispatchCwd, index, dbg);
498
529
  },
499
530
  }, {
500
- biomeClient,
501
- ruffClient,
502
- metricsClient,
531
+ biomeClient: biomeClient,
532
+ ruffClient: ruffClient,
533
+ metricsClient: metricsClient,
503
534
  getFormatService,
504
535
  fixedThisTurn: runtime.fixedThisTurn,
505
536
  });
@@ -632,11 +663,20 @@ async function dispatchPipelineAnalysis(args) {
632
663
  // ladder (the #2402 after-write hash was never stamped), and a duplicate
633
664
  // tool_result for the same bytes analysed the file a second time (the
634
665
  // already-analysed latch was never set).
635
- const finalStateHash = getFileStateHash(filePath);
636
- lastAnalyzedStateByFile.set(filePath, {
637
- turnIndex: runtime.turnIndex,
638
- stateHash: finalStateHash,
639
- });
666
+ // The latch identifies the bytes this pipeline actually analysed. A pipeline
667
+ // that reports no write analysed its input state, even if another writer
668
+ // changed disk while the await was parked. A pipeline-reported write selects
669
+ // only the identity captured by runPipeline before analysis awaits (#2499).
670
+ const pipelineOwnedWriteHash = result.fileModified
671
+ ? result.postWriteStateHash
672
+ : undefined;
673
+ const finalStateHash = pipelineOwnedWriteHash ?? initialStateHash;
674
+ if (!result.fileModified || pipelineOwnedWriteHash !== undefined) {
675
+ lastAnalyzedStateByFile.set(filePath, {
676
+ turnIndex: runtime.turnIndex,
677
+ stateHash: finalStateHash,
678
+ });
679
+ }
640
680
  // #2402: pi-lens' own immediate format/autofix may have rewritten the file
641
681
  // after the native edit was recorded by the caller. Stamp the post-pipeline
642
682
  // state so an identical retry against the formatted bytes is still
@@ -645,7 +685,7 @@ async function dispatchPipelineAnalysis(args) {
645
685
  // `initialStateHash` IS the post-write hash at both call sites — the
646
686
  // classified chain passes `postWriteStateHash` verbatim, the observed path
647
687
  // passes the same pre-settle digest.
648
- if (finalStateHash !== initialStateHash) {
688
+ if (pipelineOwnedWriteHash !== undefined) {
649
689
  for (const pair of nativeAppliedPairs) {
650
690
  runtime.partialApplyRecords.noteAfterWriteHash(filePath, pair.oldText, pair.newText, finalStateHash);
651
691
  }
@@ -653,7 +693,7 @@ async function dispatchPipelineAnalysis(args) {
653
693
  // The model's write/edit and pi-lens' own immediate format/autofix are now
654
694
  // reflected on disk. Refresh read-guard staleness stamps so a follow-up edit
655
695
  // is judged by read-range coverage, not by our own previous write.
656
- if (!getFlag("no-read-guard")) {
696
+ if (!getFlag("no-read-guard") && allowReadGuardWrites) {
657
697
  const changedForReadGuard = new Set([
658
698
  path.resolve(filePath),
659
699
  ...(result.changedFiles ?? []).map((changedFile) => path.resolve(changedFile)),
@@ -670,6 +710,7 @@ export async function handleToolResult(deps) {
670
710
  const { event, getFlag, dbg, runtime, cacheManager, agentBehaviorRecord, formatBehaviorWarnings, } = deps;
671
711
  const rawFilePath = event.input.path;
672
712
  const workspaceRoot = runtime.projectRoot || process.cwd();
713
+ let bashAuthorshipConfirmed = deps._readGuardAuthorship ?? event.toolName !== "bash";
673
714
  // #1642: a gitignored worktree edit got re-attributed onto a
674
715
  // same-relative-path file in the parent checkout because this handler
675
716
  // used to always resolve a relative path against `workspaceRoot`
@@ -791,10 +832,16 @@ export async function handleToolResult(deps) {
791
832
  // THIS set, not raw recognized - otherwise a redirect target dropped
792
833
  // by the isError filter would be subtracted from recovery AND
793
834
  // excluded here, attributed nowhere.
794
- const recognizedWritten = event.isError !== true
835
+ let recognizedWritten = event.isError !== true
795
836
  ? recognized.filter((wp) => !isExternalOrVendorFile(wp, workspaceRoot) &&
796
837
  !isPathIgnoredByProject(wp, workspaceRoot, false))
797
838
  : [];
839
+ // Fence the mtime-based session-authored fallback before any async
840
+ // observation. Only later content evidence may clear this fence.
841
+ if (!getFlag("no-read-guard")) {
842
+ for (const recognizedPath of recognizedWritten)
843
+ deps.readGuard?.recordUnchanged?.(recognizedPath);
844
+ }
798
845
  // #2000 phase 2: when the extractor recognizes NOTHING, the command is
799
846
  // opaque-candidate — recover its actual changed set by diffing the pre
800
847
  // snapshot taken at tool_call. Partial writes that landed before a
@@ -802,6 +849,8 @@ export async function handleToolResult(deps) {
802
849
  // them) — a deliberate divergence from the isError filter above, which
803
850
  // exists for restore semantics where attribution would lie.
804
851
  let opaquePaths = [];
852
+ let observedChangedKeys;
853
+ let recognizedAuthored = [];
805
854
  // Recovery runs for EVERY bash command with a pending baseline - not
806
855
  // only recognized-empty ones. A mixed command (`python x.py > out.ts`
807
856
  // plus script-internal writes) previously skipped observation entirely
@@ -829,6 +878,7 @@ export async function handleToolResult(deps) {
829
878
  if (recovery.verdict === "recovered") {
830
879
  opaquePaths = recovery.paths.filter((p) => !isExternalOrVendorFile(p, scanRoot) &&
831
880
  !isPathIgnoredByProject(p, scanRoot, false));
881
+ observedChangedKeys = new Set(opaquePaths);
832
882
  }
833
883
  else if (recovery.verdict === "unknown") {
834
884
  // #2060: deliberately WIDER than the old `recognized.length > 0`
@@ -837,6 +887,12 @@ export async function handleToolResult(deps) {
837
887
  // nothing at all.
838
888
  unknownReason = recovery.unknownReason;
839
889
  }
890
+ else {
891
+ // A clean git verdict is complete evidence that no path
892
+ // changed; it is not missing evidence. Keep recognized writes
893
+ // out of authorship because git found no changed bytes.
894
+ observedChangedKeys = new Set();
895
+ }
840
896
  // #2060: both counts are bounded (one record per tool_result, no
841
897
  // per-path logging) and exist because filtering is invisible in
842
898
  // production otherwise - the dropped paths simply never appear.
@@ -862,9 +918,11 @@ export async function handleToolResult(deps) {
862
918
  else if (pending.stats) {
863
919
  const outcome = await captureFileStats(scanRoot, {
864
920
  withHashes: true,
921
+ ...deps._opaqueCaptureOptions,
865
922
  });
866
923
  if (outcome.snapshot && !outcome.unknownReason) {
867
- opaquePaths = diffFileStats(pending.stats, outcome.snapshot);
924
+ opaquePaths = diffFileContent(pending.stats, outcome.snapshot);
925
+ observedChangedKeys = new Set(opaquePaths);
868
926
  }
869
927
  else {
870
928
  unknownReason =
@@ -880,6 +938,16 @@ export async function handleToolResult(deps) {
880
938
  const survivingKeys = new Set(recognizedWritten.map((p) => normalizeMapKey(path.resolve(p))));
881
939
  opaquePaths = opaquePaths.filter((p) => !survivingKeys.has(p));
882
940
  }
941
+ if (observedChangedKeys) {
942
+ recognizedAuthored = recognizedWritten.filter((file) => observedChangedKeys.has(normalizeMapKey(path.resolve(file))));
943
+ if (recognizedAuthored.length === 0)
944
+ recognizedWritten = [];
945
+ }
946
+ else if (unknownReason) {
947
+ recognizedAuthored = [];
948
+ // The bounded opaque_mutation_coverage_unknown record below remains
949
+ // the production proof that authorship evidence was unavailable.
950
+ }
883
951
  if (unknownReason) {
884
952
  logLatency({
885
953
  type: "phase",
@@ -906,9 +974,15 @@ export async function handleToolResult(deps) {
906
974
  // set must hold those exact strings - no re-resolution.
907
975
  const opaqueSet = new Set(opaquePaths);
908
976
  const written = [...recognizedWritten, ...opaquePaths];
977
+ const recognizedAuthoredSet = new Set(recognizedAuthored);
978
+ bashAuthorshipConfirmed =
979
+ recognizedAuthored.length > 0 || opaquePaths.length > 0;
909
980
  for (const wp of written) {
910
- if (!getFlag("no-read-guard"))
981
+ if (!getFlag("no-read-guard") &&
982
+ (opaqueSet.has(wp) || recognizedAuthoredSet.has(wp)))
911
983
  deps.readGuard?.recordWritten(wp);
984
+ else if (!getFlag("no-read-guard") && recognizedWritten.includes(wp))
985
+ deps.readGuard?.recordUnchanged?.(wp);
912
986
  const receipt = runtime
913
987
  .recordMutationToolReceipt;
914
988
  const autofixMode = receipt
@@ -939,6 +1013,7 @@ export async function handleToolResult(deps) {
939
1013
  _autofixMode: autofixMode,
940
1014
  _attachmentBudget: syntheticAttachmentBudget,
941
1015
  _mutationSourceOverride: isOpaque ? "opaque-script" : undefined,
1016
+ _readGuardAuthorship: opaqueSet.has(wp) || recognizedAuthoredSet.has(wp),
942
1017
  });
943
1018
  if (syntheticResult) {
944
1019
  // #1590: forward verbatim. The synthetic call already charged the
@@ -949,7 +1024,57 @@ export async function handleToolResult(deps) {
949
1024
  }
950
1025
  }
951
1026
  if (event.isError !== true && !getFlag("no-read-guard")) {
952
- for (const span of extractReadPathsFromCommand(command, workspaceRoot)) {
1027
+ const truncation = event.details?.truncation;
1028
+ const parsedSpans = extractReadPathsFromCommand(command, workspaceRoot);
1029
+ let spans = parsedSpans;
1030
+ if (truncation?.truncated === true &&
1031
+ typeof truncation.outputLines === "number") {
1032
+ if (parsedSpans.length === 1) {
1033
+ const span = parsedSpans[0];
1034
+ if (span) {
1035
+ // countFileLines intentionally preserves the split-based guard
1036
+ // convention, where a trailing newline contributes an empty final
1037
+ // element. The host's output line count does not include that
1038
+ // element, so remove it from the span basis before taking the tail.
1039
+ const hasTrailingNewline = nodeFs
1040
+ .readFileSync(span.filePath, "utf8")
1041
+ .endsWith("\n");
1042
+ const spanLineCount = hasTrailingNewline
1043
+ ? Math.max(1, span.limit - 1)
1044
+ : span.limit;
1045
+ const shown = Math.min(truncation.outputLines, spanLineCount);
1046
+ spans =
1047
+ shown > 0
1048
+ ? [
1049
+ {
1050
+ ...span,
1051
+ offset: span.offset + spanLineCount - shown,
1052
+ limit: shown,
1053
+ },
1054
+ ]
1055
+ : [];
1056
+ }
1057
+ }
1058
+ else if (parsedSpans.length > 1) {
1059
+ recordDegradationOnce({
1060
+ kind: "bash-view-clipped",
1061
+ subject: "multi-span",
1062
+ reason: "truncated bash view contained multiple spans",
1063
+ });
1064
+ spans = [];
1065
+ }
1066
+ if (parsedSpans.length === 1 &&
1067
+ spans.length === 1 &&
1068
+ (spans[0]?.offset !== parsedSpans[0]?.offset ||
1069
+ spans[0]?.limit !== parsedSpans[0]?.limit)) {
1070
+ recordDegradationOnce({
1071
+ kind: "bash-view-clipped",
1072
+ subject: parsedSpans[0]?.filePath ?? "unknown",
1073
+ reason: "truncated bash view narrowed a single span",
1074
+ });
1075
+ }
1076
+ }
1077
+ for (const span of spans) {
953
1078
  if (isExternalOrVendorFile(span.filePath, workspaceRoot))
954
1079
  continue;
955
1080
  if (isPathIgnoredByProject(span.filePath, workspaceRoot, false))
@@ -1029,6 +1154,74 @@ export async function handleToolResult(deps) {
1029
1154
  });
1030
1155
  }
1031
1156
  }
1157
+ // Native read results are the authoritative read boundary. The tool_call
1158
+ // input can request past EOF, and the host can cap bytes or lines before the
1159
+ // result reaches the model. Register only the delivered range (#2802).
1160
+ if (deps.readGuard &&
1161
+ event.toolName === "read" &&
1162
+ event.isError !== true &&
1163
+ filePath &&
1164
+ !getFlag("no-read-guard") &&
1165
+ !isExternalOrVendorFile(filePath, workspaceRoot)) {
1166
+ const deliveredFilePath = attribution?.resolvedPath && nodeFs.existsSync(attribution.resolvedPath)
1167
+ ? attribution.resolvedPath
1168
+ : filePath;
1169
+ if (nodeFs.existsSync(deliveredFilePath)) {
1170
+ const nativeReadToolCallId = resolveToolCallCorrelationId(event);
1171
+ const input = event.input;
1172
+ const requestedOffset = input.offset ?? 1;
1173
+ const requestedLimit = input.limit;
1174
+ const truncation = event.details?.truncation;
1175
+ const available = Math.max(0, countFileLines(deliveredFilePath) - requestedOffset + 1);
1176
+ const deliveredLimit = Math.min(available, truncation?.outputLines ?? requestedLimit ?? available);
1177
+ if (deliveredLimit > 0) {
1178
+ if (truncation?.truncated === true && deliveredLimit < available) {
1179
+ recordDegradationOnce({
1180
+ kind: "native-read-clipped",
1181
+ subject: deliveredFilePath,
1182
+ reason: "native read result was clipped before delivery",
1183
+ });
1184
+ }
1185
+ logReadGuardEvent({
1186
+ event: "read_pattern",
1187
+ sessionId: runtime.telemetrySessionId,
1188
+ filePath: deliveredFilePath,
1189
+ requestedOffset,
1190
+ requestedLimit: requestedLimit ?? deliveredLimit,
1191
+ effectiveOffset: requestedOffset,
1192
+ effectiveLimit: deliveredLimit,
1193
+ metadata: {
1194
+ totalLines: countFileLines(deliveredFilePath),
1195
+ isPartial: deliveredLimit < available,
1196
+ fileKind: detectFileKind(deliveredFilePath) ?? "unknown",
1197
+ fractionRead: available > 0
1198
+ ? Math.round((deliveredLimit / available) * 100) / 100
1199
+ : 1,
1200
+ expandedByTs: false,
1201
+ },
1202
+ });
1203
+ const deliveredRecord = {
1204
+ filePath: deliveredFilePath,
1205
+ requestedOffset,
1206
+ requestedLimit: requestedLimit ?? deliveredLimit,
1207
+ effectiveOffset: requestedOffset,
1208
+ effectiveLimit: deliveredLimit,
1209
+ expandedByLsp: false,
1210
+ turnIndex: runtime.turnIndex,
1211
+ writeIndex: runtime.peekWriteIndex(),
1212
+ timestamp: Date.now(),
1213
+ };
1214
+ if (nativeReadToolCallId) {
1215
+ deps.readGuard.recordRead(deliveredRecord, {
1216
+ supersedes: { toolCallId: nativeReadToolCallId },
1217
+ });
1218
+ }
1219
+ else {
1220
+ deps.readGuard.recordRead(deliveredRecord);
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1032
1225
  // #2423: THE inbound gate. Everything below this line is the mutation
1033
1226
  // bookkeeping chain — read-guard staleness stamp, turn state, change-log
1034
1227
  // receipt, deferred autofix and format. Before the seam this compared
@@ -1198,14 +1391,36 @@ export async function handleToolResult(deps) {
1198
1391
  // "edit", so the un-receipted default is "deferred" either way.
1199
1392
  const observedAutofixMode = receiptOutcome?.autofixMode ??
1200
1393
  (observedKind === "edit" ? "deferred" : "immediate");
1201
- // Analyse the file the observation actually RECORDED, not merely the
1202
- // one the tool named: a directory-target tool names a path that is not
1203
- // a file at all, and `runPipeline` on it is meaningless. `changedPaths`
1204
- // is already filtered by the `isRecordable` predicate handed to the
1205
- // settle above, so membership here also implies not-vendored and
1206
- // not-gitignored — the same two gates the classified chain applies
1207
- // before its own dispatch.
1208
- if (observedChangedPaths.some((candidate) => pathsEqual(candidate, filePath))) {
1394
+ // Analyse the files the observation actually RECORDED, not merely the
1395
+ // path the tool named. A directory is never a pipeline target.
1396
+ const observedDispatchPaths = observedChangedPaths
1397
+ .filter((candidate) => {
1398
+ try {
1399
+ return !nodeFs.statSync(candidate).isDirectory();
1400
+ }
1401
+ catch {
1402
+ return false;
1403
+ }
1404
+ })
1405
+ .slice(0, OBSERVED_DISPATCH_MAX_PATHS);
1406
+ const observedDispatchDropped = observedChangedPaths.length - observedDispatchPaths.length;
1407
+ if (observedDispatchDropped > 0) {
1408
+ incrementDegradationCount({
1409
+ kind: "observed-mutation-dispatch-cap",
1410
+ subject: event.toolName,
1411
+ reason: `observed mutation dispatch capped at ${OBSERVED_DISPATCH_MAX_PATHS}; ${observedDispatchDropped} path(s) not dispatched`,
1412
+ });
1413
+ }
1414
+ if (observedDispatchPaths.length > 0) {
1415
+ const observedClients = ensureToolResultClients(deps);
1416
+ if (observedClients !== true &&
1417
+ !(await bounded(Promise.resolve(observedClients), {
1418
+ ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
1419
+ signal: deps.signal ?? undefined,
1420
+ hook: "tool_result_edit",
1421
+ label: "observed-tool-result-analysis",
1422
+ })))
1423
+ return;
1209
1424
  const observedReadGuardCorrelationId = getReadGuardCorrelationId(event);
1210
1425
  // #2464 review round 3 (F1): the SAME two pre-conditions the
1211
1426
  // classified site consults, through the same shared claim. Round 2
@@ -1215,60 +1430,72 @@ export async function handleToolResult(deps) {
1215
1430
  // second overwrote the first's registry entry, and the first's
1216
1431
  // release then evicted the outer entry a live, unrelated classified
1217
1432
  // pipeline had since re-created under it.
1218
- const observedClaim = claimPipelineDispatch({
1219
- filePath,
1220
- stateHash: observedStateHash,
1221
- turnIndex: runtime.turnIndex,
1222
- participantId: observedReadGuardCorrelationId,
1223
- dbg,
1224
- });
1225
- if (!observedClaim.proceed) {
1226
- if (observedClaim.joined) {
1227
- await observedClaim.joined;
1433
+ for (const observedPath of observedDispatchPaths) {
1434
+ const observedDispatchSignal = deps.signal;
1435
+ const observedJoinSignal = deps.signal;
1436
+ const observedStateHashForPath = getFileStateHash(observedPath);
1437
+ const observedClaim = claimPipelineDispatch({
1438
+ filePath: observedPath,
1439
+ stateHash: observedStateHashForPath,
1440
+ turnIndex: runtime.turnIndex,
1441
+ participantId: observedReadGuardCorrelationId,
1442
+ dbg,
1443
+ });
1444
+ if (!observedClaim.proceed) {
1445
+ if (observedClaim.joined) {
1446
+ await bounded(observedClaim.joined, {
1447
+ ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
1448
+ signal: observedJoinSignal,
1449
+ hook: "tool_result_edit",
1450
+ label: "observed-tool-result-join",
1451
+ });
1452
+ }
1453
+ continue;
1454
+ }
1455
+ const observedDispatchOutcome = await bounded(dispatchPipelineAnalysis({
1456
+ deps,
1457
+ runtime,
1458
+ filePath: observedPath,
1459
+ dispatchCwd: resolveLanguageRootForFile(observedPath, workspaceRoot),
1460
+ turnStateCwd: path.resolve(workspaceRoot),
1461
+ autofixMode: observedAutofixMode,
1462
+ modifiedRanges: undefined,
1463
+ writeIndex: runtime.nextWriteIndex(),
1464
+ initialStateHash: observedStateHashForPath,
1465
+ readGuardCorrelationId: observedReadGuardCorrelationId,
1466
+ requestedEditIndexes: getRequestedEditIndexes(event, observedKind),
1467
+ requestedEditTotal: getRequestedEditCount(event, observedKind),
1468
+ isPartialApplyResult: (event.details ?? {})
1469
+ .piLensPartialApply === true,
1470
+ participantIds: [observedReadGuardCorrelationId],
1471
+ participantTotal: 1,
1472
+ toolResultStart,
1473
+ nativeAppliedPairs: observedAppliedPairs,
1474
+ // #2802 authorship rule (master): the observational
1475
+ // settle already recorded this edit through the
1476
+ // bridge, so the dispatch's read-guard write refresh
1477
+ // carries evidence and stays enabled on every
1478
+ // per-path dispatch.
1479
+ allowReadGuardWrites: true,
1480
+ }), {
1481
+ ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
1482
+ signal: observedDispatchSignal,
1483
+ hook: "tool_result_edit",
1484
+ label: "observed-tool-result-analysis",
1485
+ });
1486
+ if (observedDispatchOutcome?.crashed) {
1487
+ // #2464 review round 2, S6: parity with the classified chain — a
1488
+ // pipeline crash surfaces its notice to the agent instead of being
1489
+ // swallowed into a dbg line the model never sees. The recorded
1490
+ // edit stands either way; only the analysis was lost.
1491
+ dbg(`tool_result: pipeline analysis crashed for the observed mutation on ${observedPath}; the recorded edit stands, analysis did not run this turn`);
1492
+ return observedDispatchOutcome.response;
1228
1493
  }
1229
- // Same terminal value as the block's own exit below: the bridge
1230
- // already recorded this edit, and the analysis it would have asked
1231
- // for is either running or already done.
1232
- return syntheticWriteContent.length > 0
1233
- ? { content: [...event.content, ...syntheticWriteContent] }
1234
- : undefined;
1235
- }
1236
- const observedDispatchOutcome = await dispatchPipelineAnalysis({
1237
- deps,
1238
- runtime,
1239
- filePath,
1240
- dispatchCwd: resolveLanguageRootForFile(filePath, workspaceRoot),
1241
- turnStateCwd: path.resolve(workspaceRoot),
1242
- autofixMode: observedAutofixMode,
1243
- // #2423: no adapter/diff ranges exist for a "learned" (unnamed)
1244
- // tool — the classified chain below hits the same gap for this
1245
- // provenance and also leaves `modifiedRanges` undefined, so the
1246
- // dispatch runs unscoped (whole-file) exactly as it would there.
1247
- modifiedRanges: undefined,
1248
- writeIndex: runtime.nextWriteIndex(),
1249
- initialStateHash: observedStateHash,
1250
- readGuardCorrelationId: observedReadGuardCorrelationId,
1251
- requestedEditIndexes: getRequestedEditIndexes(event, observedKind),
1252
- requestedEditTotal: getRequestedEditCount(event, observedKind),
1253
- isPartialApplyResult: (event.details ?? {})
1254
- .piLensPartialApply === true,
1255
- participantIds: [observedReadGuardCorrelationId],
1256
- participantTotal: 1,
1257
- toolResultStart,
1258
- nativeAppliedPairs: observedAppliedPairs,
1259
- });
1260
- if (observedDispatchOutcome.crashed) {
1261
- // #2464 review round 2, S6: parity with the classified chain — a
1262
- // pipeline crash surfaces its notice to the agent instead of being
1263
- // swallowed into a dbg line the model never sees. The recorded
1264
- // edit stands either way; only the analysis was lost.
1265
- dbg(`tool_result: pipeline analysis crashed for the observed mutation on ${filePath}; the recorded edit stands, analysis did not run this turn`);
1266
- return observedDispatchOutcome.response;
1267
1494
  }
1268
1495
  dbg(`tool_result: the observational settle already recorded ${observedReplayed} mutation(s) for "${event.toolName}"; kept the applied-edit records, mutation receipt, cachedExports refresh, and ran the pipeline dispatch (#2464); skipped the bridge-covered staleness stamp / turn-state ranges / change-log receipt / deferred autofix+format`);
1269
1496
  }
1270
1497
  else {
1271
- dbg(`tool_result: the observational settle recorded ${observedReplayed} mutation(s) for "${event.toolName}", none of them ${filePath} nothing to analyse under the named path`);
1498
+ dbg(`tool_result: the observational settle recorded ${observedReplayed} mutation(s) for "${event.toolName}", but no dispatchable changed paths remained`);
1272
1499
  }
1273
1500
  }
1274
1501
  return syntheticWriteContent.length > 0
@@ -1379,7 +1606,8 @@ export async function handleToolResult(deps) {
1379
1606
  }
1380
1607
  // Refresh the read-guard's FileTime stamp so that the model's own write
1381
1608
  // doesn't trigger a spurious "file_modified" block on the next edit.
1382
- deps.readGuard?.recordWritten(filePath);
1609
+ if (bashAuthorshipConfirmed)
1610
+ deps.readGuard?.recordWritten(filePath);
1383
1611
  // Keep cachedExports in sync after each write/edit so the pre-write STOP
1384
1612
  // check doesn't fire on names that were removed from this file this session.
1385
1613
  refreshCachedExports(runtime, filePath);
@@ -1411,7 +1639,7 @@ export async function handleToolResult(deps) {
1411
1639
  // tool_result is emitted after write/edit has already been applied.
1412
1640
  // Asserting pre-write stamps here produces false positives on rapid edits.
1413
1641
  sessionFileTime.read(filePath);
1414
- if (!getFlag("no-read-guard")) {
1642
+ if (!getFlag("no-read-guard") && bashAuthorshipConfirmed) {
1415
1643
  const readGuard = runtime.readGuard;
1416
1644
  readGuard?.recordWritten?.(filePath);
1417
1645
  }
@@ -1532,7 +1760,16 @@ export async function handleToolResult(deps) {
1532
1760
  // (defined above `handleToolResult`), shared with the observed-mutation
1533
1761
  // early return. This call site is otherwise unchanged — same arguments, same
1534
1762
  // crash-then-return / success-then-continue shape as before the split.
1535
- const dispatchOutcome = await dispatchPipelineAnalysis({
1763
+ const classifiedClients = ensureToolResultClients(deps);
1764
+ if (classifiedClients !== true &&
1765
+ !(await bounded(Promise.resolve(classifiedClients), {
1766
+ ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
1767
+ signal: deps.signal,
1768
+ hook: "tool_result_edit",
1769
+ label: "classified-tool-result-analysis",
1770
+ })))
1771
+ return;
1772
+ const dispatchOutcome = await bounded(dispatchPipelineAnalysis({
1536
1773
  deps,
1537
1774
  runtime,
1538
1775
  filePath,
@@ -1550,7 +1787,15 @@ export async function handleToolResult(deps) {
1550
1787
  participantTotal,
1551
1788
  toolResultStart,
1552
1789
  nativeAppliedPairs,
1790
+ allowReadGuardWrites: bashAuthorshipConfirmed,
1791
+ }), {
1792
+ ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
1793
+ signal: deps.signal || undefined,
1794
+ hook: "tool_result_edit",
1795
+ label: "pipeline-analysis",
1553
1796
  });
1797
+ if (!dispatchOutcome)
1798
+ return;
1554
1799
  if (dispatchOutcome.crashed) {
1555
1800
  return dispatchOutcome.response;
1556
1801
  }
@@ -1684,7 +1929,7 @@ export async function handleToolResult(deps) {
1684
1929
  // #1561: stamp the verdict with THIS dispatch's write token — the same
1685
1930
  // counter `lsp_diagnostics`' reconciliation seam draws from — so a later
1686
1931
  // confirmed-clean result can be ordered against it instead of racing it.
1687
- runtime.recordInlineBlockers(filePath, result.inlineBlockerSummary, writeIndex, result.inlineBlockerSources, result.inlineBlockerLines);
1932
+ runtime.recordInlineBlockers(filePath, result.inlineBlockerSummary, writeIndex, result.inlineBlockerSources, result.inlineBlockerLines, result.inlineBlockerFileContent);
1688
1933
  }
1689
1934
  else {
1690
1935
  runtime.clearInlineBlockers(filePath);