@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
@@ -28,13 +28,34 @@
28
28
  * behavior), matching the lazy-env-read house style (see
29
29
  * `subagent-mode.ts` / `runtime-config.ts`).
30
30
  */
31
- /** Module-scope state — deliberately shared by construction. This module is
32
- * loaded once per process by pi's process-global extension cache, so a
33
- * concurrent in-process subagent session sees the SAME instance as the
34
- * parent, which is exactly the signal this guard relies on. */
35
- let activeCtx;
36
- let activeSessionId;
37
- let secondarySessionCount = 0;
31
+ import { normalizeFilePath } from "./path-utils.js";
32
+ import { getProcessSingleton } from "./process-singletons.js";
33
+ const SESSION_LIFECYCLE_FAMILY = "session-lifecycle.primary-registration";
34
+ /** Bump when {@link SessionLifecycleState}'s shape changes. */
35
+ const SESSION_LIFECYCLE_VERSION = 1;
36
+ function state() {
37
+ return getProcessSingleton(SESSION_LIFECYCLE_FAMILY, SESSION_LIFECYCLE_VERSION, () => ({
38
+ activeCtx: undefined,
39
+ activeSessionId: undefined,
40
+ activeRoot: undefined,
41
+ secondarySessionCount: 0,
42
+ }));
43
+ }
44
+ /** The stable id of the currently registered primary session, if known. */
45
+ export function getActiveSessionId() {
46
+ return state().activeSessionId;
47
+ }
48
+ /**
49
+ * The normalized project root of the currently registered primary session
50
+ * (#2129), or `undefined` when no primary has registered a root yet.
51
+ *
52
+ * This is the process's answer to "which directory does pi-lens actually
53
+ * serve", and it is what `memory_sample` carries as its root discriminator
54
+ * (#2130) so a record from a multi-root host is attributable.
55
+ */
56
+ export function getActivePrimaryRoot() {
57
+ return state().activeRoot;
58
+ }
38
59
  /**
39
60
  * PURE classifier — no I/O, no throws, fully unit-testable in isolation.
40
61
  *
@@ -49,19 +70,57 @@ let secondarySessionCount = 0;
49
70
  * this IS the sequential case pi's own contract covers).
50
71
  * 4. Prior exists, `priorCtxActive === true`, different session id →
51
72
  * `concurrent-secondary` (positive evidence of a live sibling).
52
- * 5. Prior exists, `priorCtxActive === undefined` (probe inconclusive) →
73
+ * 5. Prior exists, different session id, `sameRoot === false` (positive
74
+ * evidence of a DIFFERENT project root) → `secondary-root` (#2129).
75
+ * 6. Prior exists, `priorCtxActive === undefined` (probe inconclusive) →
53
76
  * `sequential-replacement` (fail toward today's behavior).
77
+ *
78
+ * WHY ROOT IDENTITY IS AN INPUT AT ALL (#2129). Branch 3 alone made a subagent
79
+ * temp worktree — a session_start in a DIFFERENT directory, arriving after the
80
+ * host's real session had already been disposed or had an unprobeable ctx —
81
+ * classify as a sequential replacement. It then re-registered itself as the
82
+ * process's primary and ran the full session_start body: `resetLSPService`
83
+ * killed the host's warm LSP fleet, and the whole async battery (opengrep,
84
+ * word-index rebuild, review-graph build) re-ran per temp root over content
85
+ * that had not changed. Two temp roots in one host cost ~50s of opengrep and
86
+ * ~53s of word-index rebuild EACH, and drove host RSS from 290MB to 1.1GB in
87
+ * four minutes.
88
+ *
89
+ * A start in a different root is therefore never allowed to steal primary. It
90
+ * is a `secondary-root`, which the caller treats exactly like a
91
+ * `concurrent-secondary`: skip the destructive resets and the expensive
92
+ * battery, leave the registered primary's ctx/session id/root untouched. The
93
+ * root still gets served — `initLSPConfig` registers session roots lazily,
94
+ * per file (`clients/lsp/session-roots.ts:48`), not from this handler.
95
+ *
96
+ * Ordering note: the root check sits BELOW the `priorCtxActive === true`
97
+ * branch so a live sibling still reports the more specific
98
+ * `concurrent-secondary`, and it deliberately fires even when
99
+ * `priorCtxActive === false`. "The prior ctx was invalidated" is exactly the
100
+ * state a temp-worktree start arrives in, so deferring to it would restore the
101
+ * defect.
102
+ *
103
+ * Accepted trade-off: an in-process SEQUENTIAL replacement that genuinely
104
+ * moves to a new directory (a host that switches sessions across cwds within
105
+ * one process) now takes the reduced path instead of a full start. It keeps
106
+ * working — the LSP still attaches per file — but skips the startup battery
107
+ * for the new root until a same-root start re-registers. `sameRoot` is only
108
+ * ever `false` on positive evidence, and
109
+ * `PI_LENS_CONCURRENT_SESSION_GUARD=0` disables this branch with the rest of
110
+ * the guard.
54
111
  */
55
112
  export function classifySessionStart(input) {
56
- const { hasPrior, priorCtxActive, sameSessionId } = input;
113
+ const { hasPrior, priorCtxActive, sameSessionId, sameRoot } = input;
57
114
  if (!hasPrior)
58
115
  return "primary";
59
116
  if (sameSessionId)
60
117
  return "sequential-replacement";
61
- if (priorCtxActive === false)
62
- return "sequential-replacement";
63
118
  if (priorCtxActive === true)
64
119
  return "concurrent-secondary";
120
+ if (sameRoot === false)
121
+ return "secondary-root";
122
+ if (priorCtxActive === false)
123
+ return "sequential-replacement";
65
124
  // priorCtxActive === undefined: inconclusive probe — fail-safe.
66
125
  return "sequential-replacement";
67
126
  }
@@ -121,15 +180,91 @@ export function probeCtxActive(ctx) {
121
180
  * `primary` and `sequential-replacement` classifications — a sequential
122
181
  * replacement re-registers itself as the (new) primary, matching today's
123
182
  * one-active-session-at-a-time behavior. */
124
- export function registerPrimarySession(ctx, sessionId) {
125
- activeCtx = ctx;
126
- activeSessionId = sessionId;
127
- secondarySessionCount = 0;
183
+ export function registerPrimarySession(ctx, sessionId, root) {
184
+ const s = state();
185
+ s.activeCtx = ctx;
186
+ s.activeSessionId = sessionId;
187
+ // #2129: a re-registration that carries NO root must not erase a root the
188
+ // previous primary did record — losing it would make every later start's
189
+ // `sameRoot` read `undefined` (unknown) and silently restore the pre-fix
190
+ // "any root may steal primary" behavior.
191
+ if (root !== undefined)
192
+ s.activeRoot = normalizeRootForCompare(root);
193
+ s.secondarySessionCount = 0;
194
+ }
195
+ /**
196
+ * Normalize a project root for identity comparison (#2129).
197
+ *
198
+ * Uses `normalizeFilePath` — the SAME comparator `registerInstance`
199
+ * (`clients/instance-registry.ts:213`) writes roots with — so drive-letter
200
+ * case, separators, and symlinked temp dirs cannot make two spellings of one
201
+ * root look like two roots (catalog shape 1). Never throws: an unresolvable
202
+ * path degrades to `undefined`, which reads as "root unknown" and leaves the
203
+ * classification exactly where it was before this input existed.
204
+ */
205
+ function normalizeRootForCompare(root) {
206
+ if (typeof root !== "string" || root.length === 0)
207
+ return undefined;
208
+ try {
209
+ return normalizeFilePath(root);
210
+ }
211
+ catch {
212
+ return undefined;
213
+ }
214
+ }
215
+ /**
216
+ * Release the primary registration when the primary session itself shuts down
217
+ * (#2129 review F3).
218
+ *
219
+ * WHY THIS EXISTS. Before root identity was an input, a stale `activeCtx` left
220
+ * behind by a departed primary was benign: the next start probed it, got
221
+ * `false` (dead ctx), and classified `sequential-replacement`, so it took over
222
+ * as the new primary. Root identity made that stale state DECISIVE — a start in
223
+ * a different root behind a dead-but-still-registered primary now classifies
224
+ * `secondary-root` and declines. Without an explicit release, root A's primary
225
+ * ending would mean every later start in root B declines FOREVER: never
226
+ * primary, never a full start, no re-arm.
227
+ *
228
+ * This is the catalog's process-lifetime-latch shape (state that must re-arm at
229
+ * a session boundary must not outlive it). `session_shutdown`'s primary path is
230
+ * that boundary. Deliberately NOT called on a secondary's shutdown — that path
231
+ * returns before the shared teardown precisely because the primary is still
232
+ * live.
233
+ *
234
+ * A concurrent secondary that outlives the primary now classifies `primary` on
235
+ * its later emissions rather than `concurrent-secondary`. That is the fail-safe
236
+ * direction this module has always taken (run the handler), and it is correct
237
+ * here: with the primary gone there is no live sibling to protect.
238
+ *
239
+ * WHAT PROTECTS A SURVIVING SECONDARY (#2130 round 2, remainder N3/F3). Not the
240
+ * count zeroed below, and not this module-level classification at all: it is the
241
+ * per-activation `ownedSessionRole` closure in `index.ts`. Each activation
242
+ * records the role IT was classified as at its own session_start and consults
243
+ * that at its own shutdown, so a secondary whose primary has already released
244
+ * still takes the secondary teardown path.
245
+ *
246
+ * `secondarySessionCount` is therefore an OBSERVABILITY counter, not a guard.
247
+ * Zeroing it here under-reports for the window between the primary's release
248
+ * and the next primary's registration. That is real and accepted: the only
249
+ * reader is `concurrent_session_bind.metadata.secondaryCount` in `latency.log`,
250
+ * and during that window no primary is registered, so every arriving start
251
+ * classifies `primary` and re-registers — which zeroes the count anyway —
252
+ * instead of emitting a bind record. `decrementSecondarySessionCount` clamps at
253
+ * zero, so a late secondary's shutdown cannot underflow it. Documented rather
254
+ * than changed, because a count that outlived the registration it is scoped to
255
+ * would disagree with `getActivePrimaryRoot()` in the same record.
256
+ */
257
+ export function releasePrimarySession() {
258
+ const s = state();
259
+ s.activeCtx = undefined;
260
+ s.activeSessionId = undefined;
261
+ s.activeRoot = undefined;
262
+ s.secondarySessionCount = 0;
128
263
  }
129
264
  /** Register a concurrently-bound secondary (subagent) session. Does not
130
265
  * touch the primary's ctx/session id. */
131
266
  export function registerSecondarySession() {
132
- secondarySessionCount += 1;
267
+ state().secondarySessionCount += 1;
133
268
  }
134
269
  /**
135
270
  * Classifies a `session_shutdown` firing the same fail-safe way as
@@ -152,6 +287,27 @@ export function registerSecondarySession() {
152
287
  * now classifies `primary` (conservative miss — its teardown runs and hurts
153
288
  * the parent, same as pre-#473 behavior), because uncertainty must never
154
289
  * classify `secondary`.
290
+ *
291
+ * ROOT IDENTITY (#2146 review F1). The probe-based branch above is the only
292
+ * evidence this function had, and it is exactly the evidence that is missing in
293
+ * the state #2146 describes: the host's ctx is already invalidated, which is
294
+ * WHY `secondary-root` fires on the start side. A subagent's teardown therefore
295
+ * read "the primary's ctx is dead, so I must be the primary", and index.ts's
296
+ * primary path called `releasePrimarySession()` and wiped the SHARED process
297
+ * registration. The decline then survived exactly one subagent: the next one
298
+ * classified `primary` and ran the full battery.
299
+ *
300
+ * So this function takes the same root discriminator `decideSessionStart` has,
301
+ * with the same rule and the same fail-safe direction. A shutdown whose root is
302
+ * POSITIVELY different from the registered primary's root is a `secondary`,
303
+ * even when the primary's ctx probes `false` or `undefined` — deferring to the
304
+ * dead-ctx branch is what restored the defect. `undefined` on either side means
305
+ * "root unknown" and changes no verdict.
306
+ *
307
+ * Ordering: the root check sits BELOW the id-unknown guard, not above it. That
308
+ * guard is the #472 fix — a session with an unreadable session id must still
309
+ * tear itself down — and a root comparison cannot establish "different session"
310
+ * when the ids that identify sessions are unavailable.
155
311
  */
156
312
  export function noteSessionShutdown(
157
313
  // Load-bearing: ctx OBJECT IDENTITY is the definitive discriminator when
@@ -160,25 +316,40 @@ export function noteSessionShutdown(
160
316
  // (Note: pi's ExtensionRunner.emit() builds a FRESH ctx object per emit,
161
317
  // so identity match is not expected with today's SDK — this check is
162
318
  // defense-in-depth for SDK versions/paths that reuse a ctx.)
163
- ctx, sessionId) {
164
- if (ctx !== undefined && ctx === activeCtx) {
319
+ ctx, sessionId,
320
+ /** This session's own project root (`ctx.cwd`), when readable. `undefined`
321
+ * means "root unknown" and never on its own changes a verdict. */
322
+ root) {
323
+ const s = state();
324
+ if (ctx !== undefined && ctx === s.activeCtx) {
165
325
  return "primary";
166
326
  }
167
- if (activeCtx === undefined && activeSessionId === undefined) {
327
+ if (s.activeCtx === undefined && s.activeSessionId === undefined) {
168
328
  return "primary";
169
329
  }
170
- if (sessionId !== undefined && sessionId === activeSessionId) {
330
+ if (sessionId !== undefined && sessionId === s.activeSessionId) {
171
331
  return "primary";
172
332
  }
173
333
  // Uncertainty guard: if EITHER side's session id is unknown we cannot
174
334
  // positively establish "different session", so never classify secondary.
175
- if (sessionId === undefined || activeSessionId === undefined) {
335
+ if (sessionId === undefined || s.activeSessionId === undefined) {
176
336
  return "primary";
177
337
  }
178
- const primaryStillActive = probeCtxActive(activeCtx);
338
+ const primaryStillActive = probeCtxActive(s.activeCtx);
179
339
  if (primaryStillActive === true) {
180
340
  return "secondary";
181
341
  }
342
+ // #2146 F1: positive evidence of a DIFFERENT root, in a session positively
343
+ // identified as not the primary. Deliberately below the probe-true branch
344
+ // (a live sibling is already answered) and deliberately ABOVE the
345
+ // dead-ctx fail-safe, because a dead primary ctx is precisely the state a
346
+ // subagent teardown arrives in.
347
+ const shutdownRoot = normalizeRootForCompare(root);
348
+ if (s.activeRoot !== undefined &&
349
+ shutdownRoot !== undefined &&
350
+ s.activeRoot !== shutdownRoot) {
351
+ return "secondary";
352
+ }
182
353
  // primaryStillActive is false or undefined: fail-safe to primary.
183
354
  return "primary";
184
355
  }
@@ -203,27 +374,29 @@ ctx, sessionId) {
203
374
  export function classifyCurrentSessionEmission(ctx, sessionId) {
204
375
  if (!guardEnabled())
205
376
  return "primary";
206
- if (activeCtx === undefined && activeSessionId === undefined)
377
+ const s = state();
378
+ if (s.activeCtx === undefined && s.activeSessionId === undefined)
207
379
  return "primary";
208
- if (ctx !== undefined && ctx === activeCtx)
380
+ if (ctx !== undefined && ctx === s.activeCtx)
209
381
  return "primary";
210
- if (sessionId !== undefined && sessionId === activeSessionId)
382
+ if (sessionId !== undefined && sessionId === s.activeSessionId)
211
383
  return "primary";
212
384
  // Uncertainty guard: if EITHER side's session id is unknown we cannot
213
385
  // positively establish "different session", so never classify secondary.
214
- if (sessionId === undefined || activeSessionId === undefined)
386
+ if (sessionId === undefined || s.activeSessionId === undefined)
215
387
  return "primary";
216
- const primaryStillActive = probeCtxActive(activeCtx);
388
+ const primaryStillActive = probeCtxActive(s.activeCtx);
217
389
  if (primaryStillActive === true)
218
390
  return "concurrent-secondary";
219
391
  return "primary";
220
392
  }
221
393
  export function getSecondarySessionCount() {
222
- return secondarySessionCount;
394
+ return state().secondarySessionCount;
223
395
  }
224
396
  export function decrementSecondarySessionCount() {
225
- if (secondarySessionCount > 0)
226
- secondarySessionCount -= 1;
397
+ const s = state();
398
+ if (s.secondarySessionCount > 0)
399
+ s.secondarySessionCount -= 1;
227
400
  }
228
401
  /**
229
402
  * Guard-aware wrapper used by callers (index.ts) so the kill switch lives in
@@ -238,9 +411,14 @@ export function classifySessionStartGuarded(input) {
238
411
  /** Test-only: clears all module-scope state (house style — see
239
412
  * `_resetSubagentModeForTests` / `slow-fs.ts`). */
240
413
  export function _resetSessionLifecycleForTests() {
241
- activeCtx = undefined;
242
- activeSessionId = undefined;
243
- secondarySessionCount = 0;
414
+ // Resets the PROCESS state, not a module-local copy: a reset that cleared
415
+ // only module scope would leave the real registration behind and make every
416
+ // suite that relies on isolation pass vacuously (#2146, catalog shape 7).
417
+ const s = state();
418
+ s.activeCtx = undefined;
419
+ s.activeSessionId = undefined;
420
+ s.activeRoot = undefined;
421
+ s.secondarySessionCount = 0;
244
422
  }
245
423
  /**
246
424
  * Single entry point `index.ts`'s `session_start` handler delegates to, so
@@ -253,36 +431,54 @@ export function _resetSessionLifecycleForTests() {
253
431
  * fake object in tests is safe). `sessionId` is the STABLE session id
254
432
  * (`ctx.sessionManager.getSessionId()`), which may be `undefined`.
255
433
  */
256
- export function decideSessionStart(ctx, sessionId) {
257
- const hasPrior = activeCtx !== undefined || activeSessionId !== undefined;
258
- const priorCtxActive = hasPrior ? probeCtxActive(activeCtx) : undefined;
434
+ export function decideSessionStart(ctx, sessionId, root) {
435
+ const s = state();
436
+ const hasPrior = s.activeCtx !== undefined || s.activeSessionId !== undefined;
437
+ const priorCtxActive = hasPrior ? probeCtxActive(s.activeCtx) : undefined;
259
438
  // ctx OBJECT IDENTITY: if the SDK ever hands the SAME ctx object to a
260
439
  // repeated session_start, that is by definition the same session
261
440
  // re-announcing itself — sequential, never concurrent. (Not expected with
262
441
  // today's SDK — ExtensionRunner.emit() builds a fresh ctx per emit — but
263
442
  // identity is the one signal that can't false-positive, so honor it.)
264
- const sameCtx = hasPrior && ctx !== undefined && ctx === activeCtx;
443
+ const sameCtx = hasPrior && ctx !== undefined && ctx === s.activeCtx;
265
444
  const sameSessionId = sameCtx ||
266
- (hasPrior && sessionId !== undefined && sessionId === activeSessionId);
445
+ (hasPrior && sessionId !== undefined && sessionId === s.activeSessionId);
446
+ // #2129: compare THIS start's cwd against the registered primary's root.
447
+ // `undefined` on either side means "unknown", never "different" — see
448
+ // `classifySessionStart`'s fail-safe note.
449
+ const incomingRoot = normalizeRootForCompare(root);
450
+ const sameRoot = hasPrior && s.activeRoot !== undefined && incomingRoot !== undefined
451
+ ? s.activeRoot === incomingRoot
452
+ : undefined;
453
+ // #2129 review F5: capture the primary root BEFORE any registration mutates
454
+ // it, so the reported value is genuinely the decision-time input the
455
+ // classifier consulted rather than the value this call just wrote.
456
+ const primaryRootAtDecision = s.activeRoot;
267
457
  const classification = classifySessionStartGuarded({
268
458
  hasPrior,
269
459
  priorCtxActive,
270
460
  sameSessionId,
461
+ sameRoot,
271
462
  });
272
- if (classification === "concurrent-secondary") {
463
+ if (classification === "concurrent-secondary" ||
464
+ classification === "secondary-root") {
273
465
  registerSecondarySession();
274
466
  return {
275
467
  classification,
276
468
  runFullSessionStart: false,
277
- secondaryCount: secondarySessionCount,
469
+ secondaryCount: s.secondarySessionCount,
470
+ sameRoot,
471
+ primaryRoot: primaryRootAtDecision,
278
472
  };
279
473
  }
280
474
  // "primary" or "sequential-replacement": register as the (new) primary
281
475
  // and proceed exactly as today.
282
- registerPrimarySession(ctx, sessionId);
476
+ registerPrimarySession(ctx, sessionId, root);
283
477
  return {
284
478
  classification,
285
479
  runFullSessionStart: true,
286
- secondaryCount: secondarySessionCount,
480
+ secondaryCount: s.secondarySessionCount,
481
+ sameRoot,
482
+ primaryRoot: primaryRootAtDecision,
287
483
  };
288
484
  }
@@ -12,6 +12,7 @@ import { getSgCommand, resolveManagedToolClient, } from "./dispatch/runners/util
12
12
  import { getProjectIgnoreGlobs } from "./file-utils.js";
13
13
  import { findGlobalBinary } from "./package-manager.js";
14
14
  import { safeSpawnAsync } from "./safe-spawn.js";
15
+ import { truncatedByOutputCap } from "./spawn-output-cap.js";
15
16
  import { createSingleFlight } from "./single-flight.js";
16
17
  import { classifyProbeFailure, describeInstallAttempt, createAvailabilityLatch, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
17
18
  /**
@@ -87,8 +88,10 @@ function tryParseSgMatches(stdout) {
87
88
  return null;
88
89
  }
89
90
  }
91
+ // The truncation veto lives at the head of `exec`/`interpretScanResult` now
92
+ // (#2100), so this stays a pure parser rather than re-asking a settled question.
90
93
  function tryParseNonZeroSgMatches(result) {
91
- if (result.outputTruncated || !result.stdout.trim())
94
+ if (!result.stdout.trim())
92
95
  return null;
93
96
  // Trimmed on purpose: a leading BOM survives JSON.parse's whitespace
94
97
  // tolerance but String.trim() strips it (matches the pre-refactor guard).
@@ -322,6 +325,7 @@ export class SgRunner {
322
325
  cause: provisional ? this.sweepTransientCause : "ok",
323
326
  elapsedMs: Date.now() - startedAt,
324
327
  latched: !provisional,
328
+ classifiedBy: "probe",
325
329
  hostStallMs: this.sweepHostStallMs,
326
330
  budgetMs: PROBE_TIMEOUT_MS,
327
331
  ...(provisional && {
@@ -582,6 +586,17 @@ export class SgRunner {
582
586
  totalMatches: 0,
583
587
  truncated: false,
584
588
  });
589
+ // FIRST (#2100): hitting the cap makes safe-spawn SIGTERM ast-grep, so a
590
+ // truncated run also classifies as a killed spawn. Read after the two
591
+ // gates below, this guard could only ever describe a run that exited
592
+ // before the signal reached it. A timed-out or aborted run carries the
593
+ // flag too and keeps its own classification below.
594
+ if (truncatedByOutputCap(result)) {
595
+ return {
596
+ ...empty(),
597
+ error: "Failed to parse output: output was truncated",
598
+ };
599
+ }
585
600
  const spawnFailure = this.failureForSpawnResult(result);
586
601
  if (spawnFailure) {
587
602
  return {
@@ -622,12 +637,6 @@ export class SgRunner {
622
637
  }
623
638
  if (!result.stdout.trim())
624
639
  return empty();
625
- if (result.outputTruncated) {
626
- return {
627
- ...empty(),
628
- error: "Failed to parse output: output was truncated",
629
- };
630
- }
631
640
  {
632
641
  const matches = tryParseSgMatches(result.stdout);
633
642
  if (matches) {
@@ -659,6 +668,17 @@ export class SgRunner {
659
668
  }
660
669
  }
661
670
  interpretScanResult(result, args) {
671
+ // FIRST (#2100): same reason as `exec` above — the cap kill is our own
672
+ // SIGTERM, so `failureForSpawnResult` answers "cli-failure" for it and
673
+ // the truncation guard below never speaks.
674
+ if (truncatedByOutputCap(result)) {
675
+ return {
676
+ matches: [],
677
+ status: result.status,
678
+ error: "Failed to parse ast-grep scan output: output was truncated",
679
+ failure: "parse-failure",
680
+ };
681
+ }
662
682
  const failure = this.failureForSpawnResult(result);
663
683
  if (failure) {
664
684
  return {
@@ -697,14 +717,6 @@ export class SgRunner {
697
717
  }
698
718
  if (!result.stdout.trim())
699
719
  return { matches: [], status: result.status };
700
- if (result.outputTruncated) {
701
- return {
702
- matches: [],
703
- status: result.status,
704
- error: "Failed to parse ast-grep scan output: output was truncated",
705
- failure: "parse-failure",
706
- };
707
- }
708
720
  {
709
721
  const matches = tryParseSgMatches(result.stdout);
710
722
  if (matches) {