@herbertgao/pi-extensions 2026.8.13 → 2026.8.15

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 (263) hide show
  1. package/README.md +19 -14
  2. package/THIRD_PARTY_NOTICES.md +0 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
  4. package/node_modules/@czottmann/pi-automode/README.md +5 -1
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@herbertgao/{pi-stash → pi-bark}/LICENSE +4 -5
  11. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  12. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  13. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  14. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  15. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  16. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  19. package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
  20. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
  21. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  22. package/node_modules/@narumitw/pi-btw/README.md +125 -88
  23. package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
  24. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  25. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  26. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  27. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
  28. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  29. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  30. package/node_modules/@tifan/pi-handoff/README.md +36 -13
  31. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  32. package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
  33. package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
  34. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  35. package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
  36. package/node_modules/@tifan/pi-recap/README.md +1 -1
  37. package/node_modules/@tifan/pi-recap/package.json +2 -2
  38. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  39. package/node_modules/@tifan/pi-rename/README.md +42 -21
  40. package/node_modules/@tifan/pi-rename/package.json +1 -1
  41. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  42. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  43. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  44. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  45. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  46. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  47. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  48. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  50. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  51. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  52. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  53. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  54. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  55. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  56. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  57. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  58. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  59. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  60. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  61. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  63. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  64. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  66. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  67. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  68. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  69. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  85. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  86. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  87. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  88. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  89. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  90. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  91. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  92. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  93. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  94. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  95. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  96. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  97. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  98. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  99. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  100. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  101. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  102. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  103. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  104. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  105. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  106. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  107. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  108. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  109. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  110. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  111. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  112. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  113. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  114. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  115. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  116. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  117. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  118. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  119. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  120. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  121. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  122. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  123. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  124. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  125. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  126. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  127. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  128. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  129. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  130. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  131. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  132. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  133. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  134. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  135. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  136. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  137. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  138. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  139. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  140. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  141. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  142. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  143. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  144. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  145. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  146. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  147. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  148. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  149. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  150. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  151. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  152. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  153. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  154. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  155. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  156. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  157. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  158. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  159. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  160. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  161. package/node_modules/pi-lens/dist/index.js +11243 -6651
  162. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  163. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  164. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  165. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  166. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  167. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  168. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  169. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  170. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  171. package/node_modules/pi-lens/package.json +5 -1
  172. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  173. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  174. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  175. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  176. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  177. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  178. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  179. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  212. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  215. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  216. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  217. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  218. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  219. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  220. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  221. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  222. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  223. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  224. package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
  225. package/node_modules/pi-mcp-adapter/README.md +5 -2
  226. package/node_modules/pi-mcp-adapter/cli.js +4 -4
  227. package/node_modules/pi-mcp-adapter/commands.ts +1 -1
  228. package/node_modules/pi-mcp-adapter/config.ts +1 -0
  229. package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
  230. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  231. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
  232. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
  233. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
  234. package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
  235. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  236. package/node_modules/pi-mcp-adapter/index.ts +90 -6
  237. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
  238. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
  239. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
  240. package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
  241. package/node_modules/pi-mcp-adapter/package.json +1 -1
  242. package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
  243. package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
  244. package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
  245. package/node_modules/pi-mcp-adapter/types.ts +3 -1
  246. package/node_modules/pi-web-access/CHANGELOG.md +42 -0
  247. package/node_modules/pi-web-access/README.md +26 -10
  248. package/node_modules/pi-web-access/curator-page.ts +12 -3
  249. package/node_modules/pi-web-access/curator-server.ts +3 -1
  250. package/node_modules/pi-web-access/extract.ts +88 -13
  251. package/node_modules/pi-web-access/gemini-search.ts +10 -5
  252. package/node_modules/pi-web-access/github-extract.ts +47 -3
  253. package/node_modules/pi-web-access/index.ts +40 -40
  254. package/node_modules/pi-web-access/package.json +1 -1
  255. package/node_modules/pi-web-access/page-query.ts +61 -8
  256. package/node_modules/pi-web-access/xcrawl.ts +264 -0
  257. package/package.json +19 -22
  258. package/node_modules/@herbertgao/pi-stash/README.md +0 -34
  259. package/node_modules/@herbertgao/pi-stash/package.json +0 -51
  260. package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
  261. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  262. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  263. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -2,9 +2,13 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import { BoundedLruCache } from "../bounded-cache.js";
4
4
  import * as path from "node:path";
5
- import { findGoverningTsconfigDir, getDirectoryMarkers, } from "../workspace-topology.js";
5
+ import { findGoverningTsconfigDir, getDirectoryMarkers, registerWorkspaceTopologyReset, } from "../workspace-topology.js";
6
6
  const cache = new BoundedLruCache(64);
7
7
  const referencesCache = new BoundedLruCache(64);
8
+ registerWorkspaceTopologyReset(() => {
9
+ cache.clear();
10
+ referencesCache.clear();
11
+ });
8
12
  /** Strip JSONC comments and trailing commas without touching string contents. */
9
13
  function parseJsonc(content) {
10
14
  let output = "";
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
3
  import { getGlobalPiLensDir } from "./file-utils.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
+ import { normalizeFilePath } from "./path-utils.js";
5
6
  const REVIEW_GRAPH_LOG_DIR = getGlobalPiLensDir();
6
7
  const REVIEW_GRAPH_LOG_FILE = path.join(REVIEW_GRAPH_LOG_DIR, "review-graph.log");
7
8
  const writer = createNdjsonLogger({ filePath: REVIEW_GRAPH_LOG_FILE });
@@ -27,6 +28,9 @@ export function makeReviewGraphBuildMetadata(graph, options = {}) {
27
28
  graph.persistCoverage?.sourceFilesTruncated
28
29
  ? { sourceFilesTruncated: true }
29
30
  : {}),
31
+ ...(options.pathNormalizeCalls === undefined
32
+ ? {}
33
+ : { pathNormalizeCalls: options.pathNormalizeCalls }),
30
34
  nodes: graph.nodes.size,
31
35
  edges: graph.edges.length,
32
36
  ...(graph.persistCoverage
@@ -34,11 +38,27 @@ export function makeReviewGraphBuildMetadata(graph, options = {}) {
34
38
  : {}),
35
39
  };
36
40
  }
41
+ /**
42
+ * #2141: call sites feed this a mix of raw `cwd` params and already-normalized
43
+ * cache keys (`normalizeMapKey(path.resolve(cwd))`), so the same project root
44
+ * showed up in the log as both `C:\...\pi-free` and `C:/.../pi-free`. Rather
45
+ * than auditing every call site, normalize once here — the single emit
46
+ * seam — matching the form `normalizeFilePath` already produces for every
47
+ * other structured log's `cwd` field (bus-publish, disposition-publish,
48
+ * diagnostics-publish, format-events-publish all normalize at their publish
49
+ * call sites; this pulls the same conversion in-house so no future call site
50
+ * can regress it).
51
+ */
37
52
  export function logReviewGraph(entry) {
38
53
  if (isTestMode()) {
39
54
  return;
40
55
  }
41
- writer.log({ ts: new Date().toISOString(), ...entry, pid: process.pid });
56
+ writer.log({
57
+ ts: new Date().toISOString(),
58
+ ...entry,
59
+ cwd: normalizeFilePath(entry.cwd),
60
+ pid: process.pid,
61
+ });
42
62
  }
43
63
  export function getReviewGraphLogPath() {
44
64
  return REVIEW_GRAPH_LOG_FILE;
@@ -734,6 +734,74 @@ export class RuntimeCoordinator {
734
734
  this._pendingInlineBlockers.delete(key);
735
735
  return true;
736
736
  }
737
+ /**
738
+ * #1950 fix-round F1: read the current delivery count WITHOUT committing
739
+ * an increment. `runtime-turn.ts` needs the count a delivery would reach
740
+ * BEFORE it knows whether that delivery will actually reach the agent —
741
+ * the turn-end signature dedupe (`turn-end-findings-last`) can suppress a
742
+ * turn whose rendered content is identical to the last one, and a
743
+ * suppressed turn must not advance the counter (the agent never saw it).
744
+ * The caller peeks, renders the tentative body, and only calls
745
+ * {@link incrementInlineBlockerStaleDelivery} once it knows the turn's
746
+ * content is NOT being suppressed.
747
+ */
748
+ peekInlineBlockerStaleDeliveryCount(filePath) {
749
+ const key = path.resolve(filePath);
750
+ return this._pendingInlineBlockers.get(key)?.staleDeliveryCount ?? 0;
751
+ }
752
+ /**
753
+ * #1950: commit one more turn end that re-served this record while
754
+ * `stale`. The caller (`runtime-turn.ts`) only calls this for a
755
+ * `"dependency-drift"` demotion — a `"past-eof"` demotion retires after
756
+ * its own single delivery (#1944, `retireDemotedPastEofBlocker`) and
757
+ * never accumulates a count — and only AFTER confirming the turn's
758
+ * content will not be suppressed by the signature dedupe (see
759
+ * {@link peekInlineBlockerStaleDeliveryCount}), so this counts ACTUAL
760
+ * deliveries the agent saw, not turn-end loop iterations. Returns the new
761
+ * count, or 0 when there is no entry to count against (already retired,
762
+ * or never recorded).
763
+ */
764
+ incrementInlineBlockerStaleDelivery(filePath) {
765
+ const key = path.resolve(filePath);
766
+ const existing = this._pendingInlineBlockers.get(key);
767
+ if (!existing)
768
+ return 0;
769
+ const next = (existing.staleDeliveryCount ?? 0) + 1;
770
+ this._pendingInlineBlockers.set(key, {
771
+ ...existing,
772
+ staleDeliveryCount: next,
773
+ });
774
+ return next;
775
+ }
776
+ /**
777
+ * #1950: retire a dependency-drift demotion once it has been delivered
778
+ * `DEPENDENCY_DRIFT_MAX_DELIVERIES` times with no re-run confirming or
779
+ * clearing it. Unlike {@link retireDemotedPastEofBlocker}, the record is
780
+ * NOT provably unrecoverable — its coordinates are still in bounds, and a
781
+ * fresh dispatch of the file could still confirm or clear it. This is a
782
+ * bounded-noise cap, not a "this can never resolve" verdict, and the
783
+ * caller's ledger reason must say so (`demoted-finding-retired`'s "capped,
784
+ * re-run can still confirm" vs. past-EOF's "retired, unrecoverable").
785
+ *
786
+ * Only ever touches this gate's OWN demotion (`staleReason ===
787
+ * "dependency-drift"`) — a past-EOF demotion on the same file is that
788
+ * gate's own record to retire, not this one's.
789
+ *
790
+ * @returns true when an entry was retired — the caller records it, so the
791
+ * suppression is never silent (#1432 Gap 1).
792
+ */
793
+ retireDemotedDependencyDriftBlocker(filePath) {
794
+ const key = path.resolve(filePath);
795
+ const existing = this._pendingInlineBlockers.get(key);
796
+ if (!existing)
797
+ return false;
798
+ if (!existing.stale)
799
+ return false;
800
+ if (existing.staleReason !== "dependency-drift")
801
+ return false;
802
+ this._pendingInlineBlockers.delete(key);
803
+ return true;
804
+ }
737
805
  /**
738
806
  * Retire a file's inline blocker because a FRESH, content-bound diagnostic
739
807
  * verdict proved it gone (#1561).
@@ -1,6 +1,7 @@
1
1
  import * as nodeFs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { resetBoundedTelemetry } from "./bounded-telemetry.js";
4
+ import { rotateMessageEndAttribution } from "./message-end-attribution.js";
4
5
  import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
5
6
  import { deadCodeIssueCount } from "./dead-code-client.js";
6
7
  import { logDeadCodeScan } from "./dead-code-logger.js";
@@ -10,6 +11,8 @@ import { resetPsScriptAnalyzerAvailability } from "./dispatch/runners/psscriptan
10
11
  import { resetInstallRetryLatches } from "./dispatch/runners/utils/availability-policy.js";
11
12
  import { resetLazyInstallAttempts } from "./dispatch/runners/utils/lazy-installer.js";
12
13
  import { resetDispatchAvailabilityState } from "./dispatch/runners/utils/runner-helpers.js";
14
+ import { resetObservedRunnerLatency } from "./dispatch/collect-later-tier.js";
15
+ import { resetPendingRunnerFindings } from "./dispatch/pending-runner-findings.js";
13
16
  import { clearAllSessions as clearFileTimeSessions } from "./file-time.js";
14
17
  import { getGlobalPiLensDir, getKnipIgnorePatterns, getProjectDataDir, } from "./file-utils.js";
15
18
  import { GitleaksClient } from "./gitleaks-client.js";
@@ -22,7 +25,7 @@ import { runLogCleanup } from "./log-cleanup.js";
22
25
  import { resetCascadeTierSessionState } from "./lsp/cascade-tier.js";
23
26
  import { initLSPConfig, loadLSPConfig } from "./lsp/config.js";
24
27
  import { resetWorkspaceDiagnosticsCacheSession } from "./lsp/workspace-diagnostics-session.js";
25
- import { resetDirectLspCommandAvailability } from "./lsp/server.js";
28
+ import { resetDirectLspCommandAvailability, resetLSPCaseSensitivityState, } from "./lsp/server.js";
26
29
  import { loadLspService } from "./lsp-lazy.js";
27
30
  import { resetManagedToolRefreshSession } from "./installer/managed-tool-refresh-session.js";
28
31
  import { resetResolvedPathCache } from "./installer/index.js";
@@ -32,7 +35,6 @@ import { isAtOrAboveHomeDir } from "./path-utils.js";
32
35
  import { isPrintMode } from "./print-mode.js";
33
36
  import { readLatestProjectSequence, readLatestProjectSequenceAsync, } from "./project-changes.js";
34
37
  import { getProjectSnapshotPath, getProjectSnapshotLegacyPath, hydrateRuntimeFromProjectSnapshot, hydrateRuntimeFromProjectSnapshotIfIdle, isProjectSnapshotFresh, isProjectSnapshotMetaStale, loadProjectSnapshot, loadProjectSnapshotExportsAndRules, PROJECT_SNAPSHOT_VERSION, readProjectSnapshotMeta, saveRuntimeProjectSnapshot, } from "./project-snapshot.js";
35
- import { clearTsconfigPathsCache } from "./review-graph/tsconfig-paths.js";
36
38
  import { scanProjectRules } from "./rules-scanner.js";
37
39
  import { resetSafeSpawnWindowsCommandCache } from "./safe-spawn.js";
38
40
  import { getSlowFsVerdict, isSlowFs, slowFsDegradationNotice, } from "./slow-fs.js";
@@ -623,7 +625,7 @@ async function buildOrRefreshWordIndex(args) {
623
625
  const snapshot = loadProjectSnapshot(snapshotRoot);
624
626
  const snapshotLoadMs = Date.now() - snapshotLoadStartMs;
625
627
  if (snapshot?.wordIndex) {
626
- const { deserializeWordIndex, refreshWordIndexIncrementally } = await import("./word-index.js");
628
+ const { deserializeWordIndex, refreshWordIndexIncrementally, countWordIndexPostingEntries, estimateWordIndexResidentBytes, } = await import("./word-index.js");
627
629
  const deserializeStartMs = Date.now();
628
630
  const index = deserializeWordIndex(snapshot.wordIndex);
629
631
  const deserializeMs = Date.now() - deserializeStartMs;
@@ -675,6 +677,8 @@ async function buildOrRefreshWordIndex(args) {
675
677
  durationMs: Date.now() - startMs,
676
678
  indexedFileCount: index.docCount,
677
679
  tokens: index.postings.size,
680
+ postingEntries: countWordIndexPostingEntries(index),
681
+ residentBytes: estimateWordIndexResidentBytes(index),
678
682
  truncated: index.truncated,
679
683
  phaseDurationsMs: {
680
684
  snapshotLoadMs,
@@ -714,7 +718,7 @@ async function buildOrRefreshWordIndex(args) {
714
718
  // implementation backs this task, the quick-mode warmup call below, AND the
715
719
  // stateless cold-query background trigger in word-index.ts — a bound/skip
716
720
  // -rule change lands once, not in three copies.
717
- const { buildWordIndexAsync, collectWordIndexDocs } = await import("./word-index.js");
721
+ const { buildWordIndexAsync, collectWordIndexDocs, countWordIndexPostingEntries, estimateWordIndexResidentBytes, } = await import("./word-index.js");
718
722
  const docs = await collectWordIndexDocs(analysisRoot, () => runtime.isCurrentSession(sessionGeneration), rebuildPreflightFiles);
719
723
  if (!runtime.isCurrentSession(sessionGeneration))
720
724
  return;
@@ -751,6 +755,8 @@ async function buildOrRefreshWordIndex(args) {
751
755
  durationMs: Date.now() - startMs,
752
756
  indexedFileCount: runtime.wordIndex.docCount,
753
757
  tokens: runtime.wordIndex.postings.size,
758
+ postingEntries: countWordIndexPostingEntries(runtime.wordIndex),
759
+ residentBytes: estimateWordIndexResidentBytes(runtime.wordIndex),
754
760
  truncated: runtime.wordIndex.truncated,
755
761
  skipped: docs.skipped,
756
762
  });
@@ -1150,7 +1156,12 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
1150
1156
  // Build (or hydrate) the canonical review graph first. The call graph is a
1151
1157
  // derived projection of that graph, so its freshness is the review graph's
1152
1158
  // version/signature—not a second source walk and mtime policy.
1153
- const sessionFacts = new FactStore();
1159
+ // Subject labels this store's capacity-eviction telemetry distinctly
1160
+ // from the other five production FactStore instances (#2243 review
1161
+ // round 3, F1) — this session-start walk runs BEFORE any dispatch and
1162
+ // can visit every file in the project, so a shared subject would let
1163
+ // it consume the dispatch store's once-per-session ledger slot first.
1164
+ const sessionFacts = new FactStore("runtime-session-call-graph");
1154
1165
  const graph = await buildOrUpdateGraph(analysisRoot, [], sessionFacts);
1155
1166
  if (!runtime.isCurrentSession(sessionGeneration))
1156
1167
  return;
@@ -1294,6 +1305,10 @@ export async function handleSessionStart(deps) {
1294
1305
  // numbering at 0, so without this a stale count could survive a session
1295
1306
  // boundary that happened to land on the same index.
1296
1307
  resetBoundedTelemetry();
1308
+ // #1956 R3: stale message_end events drain after replacement. Rotate the
1309
+ // live anchor into one bounded previous slot so the queued event keeps the
1310
+ // replaced session's attribution until a newer live message_end is seen.
1311
+ rotateMessageEndAttribution();
1297
1312
  const handlerEnteredAt = Date.now();
1298
1313
  const sessionStartMs = deps.sessionStartFiredAt ?? handlerEnteredAt;
1299
1314
  const cwdForTelemetry = deps.ctxCwd ?? process.cwd();
@@ -1603,14 +1618,10 @@ export async function handleSessionStart(deps) {
1603
1618
  clearFileTimeSessions();
1604
1619
  runtime.complexityBaselines.clear();
1605
1620
  resetDispatchBaselines(ctxCwd);
1606
- // #806: drop the shared per-directory marker index (and any consumer
1607
- // cache layered on top, e.g. tsconfig-paths' matcher cache) so a
1608
- // `.pi-lens.json`/`tsconfig.json`/workspace-manifest edit made between
1609
- // sessions is picked up fresh instead of only on process restart — this
1610
- // also fixes #805's mid-session tsconfig staleness (the matcher cache was
1611
- // previously session-lived with no reset hook at all).
1621
+ // #806: clear the shared per-directory marker index and registered
1622
+ // topology-derived caches only at session start. Mid-session edits are NOT
1623
+ // detected; #805's tsconfig matcher cache follows the same registry reset.
1612
1624
  resetWorkspaceTopology();
1613
- clearTsconfigPathsCache();
1614
1625
  // #2000: opaque-recovery baselines are keyed cwd:generation (unreachable
1615
1626
  // after reset) and the git-worktree memo must re-probe after a session
1616
1627
  // that may have seen a non-git dir become one.
@@ -1630,6 +1641,10 @@ export async function handleSessionStart(deps) {
1630
1641
  // the tool for the rest of the process lifetime. Clear it here, same
1631
1642
  // boundary as the other per-session caches on this line.
1632
1643
  resetDispatchAvailabilityState();
1644
+ // Runner collect-later observations and their late findings are session
1645
+ // scoped. A new session must re-probe rather than inherit a process latch.
1646
+ resetObservedRunnerLatency();
1647
+ resetPendingRunnerFindings();
1633
1648
  // #1995: a command that TIMED OUT (not merely failed a probe) cools down
1634
1649
  // for the rest of the session so a hot loop of edits cannot hand the same
1635
1650
  // wedged .cmd shim a second budget. A new session may retry: the executable
@@ -1670,6 +1685,9 @@ export async function handleSessionStart(deps) {
1670
1685
  // #1897: direct-LSP negative availability and bare installer paths are
1671
1686
  // session facts. A command or PATH entry can appear between sessions.
1672
1687
  resetDirectLspCommandAvailability();
1688
+ // #2052: a root may not exist when the case-sensitivity probe first sees it.
1689
+ // Re-probe it at the next session boundary after the workspace is created.
1690
+ resetLSPCaseSensitivityState();
1673
1691
  resetResolvedPathCache();
1674
1692
  // #1653: pnpm/yarn/bun/npm's availability latches (package-manager.ts) are
1675
1693
  // module-local, same #1490/#1535 shape as the two lines above — the
@@ -5,6 +5,7 @@ import { recordDegradationOnce } from "./degradation-ledger.js";
5
5
  import { detectFileKind } from "./file-kinds.js";
6
6
  import { isPathIgnoredByProject } from "./file-utils.js";
7
7
  import { evaluateGitGuard, isGitCommitOrPushAttempt } from "./git-guard.js";
8
+ import { evaluateSharedCheckoutGuard } from "./shared-checkout-guard.js";
8
9
  import { logLatency } from "./latency-logger.js";
9
10
  import { normalizeMapKey } from "./path-utils.js";
10
11
  import { captureFileStats, getOpaqueBaselineStore, isGitWorktree, } from "./opaque-mutation-scan.js";
@@ -383,6 +384,20 @@ async function handleToolCallImpl(deps) {
383
384
  };
384
385
  }
385
386
  }
387
+ // #2007: a sibling session's branch switch destroys uncommitted work in a
388
+ // shared checkout. Independent of `lens-guard`: that gate is about blocker
389
+ // hygiene before a commit, this one is about not deleting a peer's WIP.
390
+ // The evaluator short-circuits on a pure string classification, so a
391
+ // non-git bash command pays no I/O here.
392
+ if (getFlag("lens-checkout-guard")) {
393
+ const sharedCheckout = await evaluateSharedCheckoutGuard(toolName, event.input, ctx.cwd ?? runtime.projectRoot ?? process.cwd());
394
+ if (sharedCheckout.block) {
395
+ return {
396
+ block: true,
397
+ reason: sharedCheckout.reason,
398
+ };
399
+ }
400
+ }
386
401
  const rawFilePath = getToolCallRawFilePath(toolName, event);
387
402
  const pathResolution = resolveToolCallFilePath(rawFilePath, ctx.cwd, runtime.projectRoot);
388
403
  filePath = pathResolution?.path;
@@ -4,11 +4,11 @@ import * as path from "node:path";
4
4
  import { noteAuthoritativeContentAttachment } from "./agent-nudge.js";
5
5
  import { captureFileStats, diffFileStats, getOpaqueBaselineStore, recoverOpaqueChangesViaGit, } from "./opaque-mutation-scan.js";
6
6
  import { normalizeMapKey } from "./path-utils.js";
7
- import { extractReadPathsFromCommand, extractDeletedPathsFromCommand, extractGrepSearchReadsFromOutput, extractWrittenPathsFromCommand, } from "./bash-file-access.js";
7
+ import { extractReadPathsFromCommand, extractDeletedPathsFromCommand, extractGrepSearchReadsFromOutput, extractWrittenPathsFromCommand, tokenizeShellCommand, } from "./bash-file-access.js";
8
8
  import { registerSearchReads, } from "./search-read-registration.js";
9
9
  import { createFileTime } from "./file-time.js";
10
10
  import { publishFormatQueued } from "./format-events-publish.js";
11
- import { isPathIgnoredByProject } from "./file-utils.js";
11
+ import { invalidateProjectIgnoreMatcherForPath, isPathIgnoredByProject, } from "./file-utils.js";
12
12
  import { getFormatService } from "./format-service.js";
13
13
  import { isExternalOrVendorFile, normalizeEphemeralMapKey, pathsEqual, } from "./path-utils.js";
14
14
  import { PathKeyedMap } from "./path-keyed-map.js";
@@ -22,7 +22,72 @@ import { renderPostAutofixNotice, } from "./post-autofix-notice.js";
22
22
  import { syncGitGuardRecord } from "./git-guard.js";
23
23
  import { scheduleWordIndexPersist } from "./word-index.js";
24
24
  import { RUNTIME_CONFIG } from "./runtime-config.js";
25
+ import { getActiveSessionId } from "./session-lifecycle.js";
25
26
  const AUTHORITATIVE_CONTENT_MAX_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
27
+ /**
28
+ * Git subcommands that import ANOTHER commit's content into the index. The
29
+ * whole family, with a verdict for each (#2060):
30
+ * - merge, rebase, cherry-pick, pull, revert, am: IN. Each stages the other
31
+ * side's clean files beside the unmerged ones. `pull` is fetch+merge and
32
+ * `am --3way` reaches the same unmerged state, both probed on git 2.55.
33
+ * - stash pop / stash apply: OUT. A conflicted pop leaves `M ` entries too,
34
+ * but that content is the agent's OWN stashed work. Excluding it would
35
+ * destroy exactly what opaque recovery exists to capture.
36
+ * - checkout -m: OUT. Its "incoming" side is the agent's local modifications
37
+ * carried across the switch, so the same reasoning applies.
38
+ * - apply -3 / --3way: OUT. The patch is normally one the agent wrote, and
39
+ * `apply` is far more often used without conflicts, so the narrower default
40
+ * is to keep capturing.
41
+ * Membership only ARMS the filter; it still needs a real unmerged entry to do
42
+ * anything, so a non-integration use of a listed subcommand is inert.
43
+ */
44
+ const GIT_INTEGRATION_SUBCOMMANDS = new Set([
45
+ "merge",
46
+ "rebase",
47
+ "cherry-pick",
48
+ "pull",
49
+ "revert",
50
+ "am",
51
+ ]);
52
+ const GIT_GLOBAL_OPTIONS_WITH_VALUE = new Set([
53
+ "-C",
54
+ "-c",
55
+ "--config-env",
56
+ "--git-dir",
57
+ "--namespace",
58
+ "--work-tree",
59
+ ]);
60
+ /**
61
+ * Failed integration commands are the one opaque-recovery case where Git's
62
+ * index contains changes made by the other branch rather than the agent.
63
+ * Keep this narrow: ordinary scripts and successful Git operations retain the
64
+ * normal recovery contract.
65
+ */
66
+ export function isFailedGitIntegrationCommand(command, isError) {
67
+ if (isError !== true)
68
+ return false;
69
+ return tokenizeShellCommand(command).some(({ tokens, unsupported }) => {
70
+ if (unsupported)
71
+ return false;
72
+ const executable = path.win32
73
+ .basename(tokens[0] ?? "")
74
+ .toLowerCase()
75
+ .replace(/\.(?:cmd|exe)$/, "");
76
+ if (executable !== "git")
77
+ return false;
78
+ for (let index = 1; index < tokens.length; index += 1) {
79
+ const token = tokens[index] ?? "";
80
+ if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(token)) {
81
+ index += 1;
82
+ continue;
83
+ }
84
+ if (token.startsWith("-"))
85
+ continue;
86
+ return GIT_INTEGRATION_SUBCOMMANDS.has(token);
87
+ }
88
+ return false;
89
+ });
90
+ }
26
91
  function parseDiffRanges(diff) {
27
92
  const changedLines = [];
28
93
  for (const line of diff.split("\n")) {
@@ -269,6 +334,8 @@ export async function handleToolResult(deps) {
269
334
  ? rawFilePath
270
335
  : path.resolve(resolutionBasis, rawFilePath)
271
336
  : rawFilePath;
337
+ if (filePath)
338
+ invalidateProjectIgnoreMatcherForPath(filePath);
272
339
  // Purely diagnostic: tool_call's call-time verdict (computed on ITS OWN
273
340
  // resolved path, which may since have been superseded) disagreed with
274
341
  // what actually executed. This does NOT gate anything by itself — the
@@ -351,16 +418,41 @@ export async function handleToolResult(deps) {
351
418
  }
352
419
  else if (pending.strategy === "git") {
353
420
  // Git-first: no universe cap - works on any repo size.
354
- const recovery = await recoverOpaqueChangesViaGit(scanRoot, pending.startedAt);
421
+ const recovery = await recoverOpaqueChangesViaGit(scanRoot, pending.startedAt, {
422
+ excludeIndexOnlyWhenUnmerged: isFailedGitIntegrationCommand(command, event.isError),
423
+ });
355
424
  if (recovery.verdict === "recovered") {
356
425
  opaquePaths = recovery.paths.filter((p) => !isExternalOrVendorFile(p, scanRoot) &&
357
426
  !isPathIgnoredByProject(p, scanRoot, false));
358
427
  }
359
- else if (recovery.verdict === "unknown" && recognized.length > 0) {
360
- // Git hiccup on a partially-recognized command: the
361
- // remainder's coverage is unknown, never clean-by-default.
428
+ else if (recovery.verdict === "unknown") {
429
+ // #2060: deliberately WIDER than the old `recognized.length > 0`
430
+ // guard. A fully opaque command whose probe failed is the shape
431
+ // whose coverage is least knowable, and it used to record
432
+ // nothing at all.
362
433
  unknownReason = recovery.unknownReason;
363
434
  }
435
+ // #2060: both counts are bounded (one record per tool_result, no
436
+ // per-path logging) and exist because filtering is invisible in
437
+ // production otherwise - the dropped paths simply never appear.
438
+ if ((recovery.excludedIncomingCount ?? 0) > 0) {
439
+ logLatency({
440
+ type: "phase",
441
+ phase: "opaque_mutation_incoming_excluded",
442
+ filePath: command.slice(0, 80),
443
+ durationMs: Date.now() - started,
444
+ result: `excluded:${recovery.excludedIncomingCount}`,
445
+ });
446
+ }
447
+ if ((recovery.unknownStatusCount ?? 0) > 0) {
448
+ logLatency({
449
+ type: "phase",
450
+ phase: "opaque_mutation_status_pair_unknown",
451
+ filePath: command.slice(0, 80),
452
+ durationMs: Date.now() - started,
453
+ result: `kept:${recovery.unknownStatusCount}`,
454
+ });
455
+ }
364
456
  }
365
457
  else if (pending.stats) {
366
458
  const outcome = await captureFileStats(scanRoot, {
@@ -831,7 +923,14 @@ export async function handleToolResult(deps) {
831
923
  },
832
924
  });
833
925
  dbg(`runPipeline crash stack: ${pipelineErr.stack}`);
834
- if (!getFlag("no-lsp")) {
926
+ // The LSP fleet is process-wide, but a pipeline crash belongs to one
927
+ // evaluation. A registered primary owns the fleet; a known secondary
928
+ // must not tear it down. Keep the historical reset when no registration
929
+ // exists because synthetic callers and early startup have no role evidence.
930
+ const activePrimarySessionId = getActiveSessionId();
931
+ const crashBelongsToPrimary = activePrimarySessionId === undefined ||
932
+ activePrimarySessionId === runtime.telemetrySessionId;
933
+ if (!getFlag("no-lsp") && crashBelongsToPrimary) {
835
934
  resetLSPService({ fast: true, reason: "pipeline_crash" });
836
935
  }
837
936
  logLatency({