@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
@@ -21,6 +21,7 @@ import { recordDegradation } from "./degradation-ledger.js";
21
21
  import { logExtension } from "./extension-log.js";
22
22
  import { isFullyQualifiedWin32 } from "./path-utils.js";
23
23
  import { startSpawnUsageSampler } from "./resource-sampler.js";
24
+ import { compareOrdinal } from "./string-utils.js";
24
25
  /** Intent-level spawn failure. `cause` retains the original OS Error/errno. */
25
26
  export class SpawnFailureError extends Error {
26
27
  kind;
@@ -701,7 +702,7 @@ export function resolveWindowsCommandForEnvironment(command, cwd, env) {
701
702
  .flatMap(([key, value]) => /^=[A-Za-z]:$/.test(key)
702
703
  ? [[key.toLowerCase(), value]]
703
704
  : [])
704
- .sort(([left], [right]) => left.localeCompare(right));
705
+ .sort(([left], [right]) => compareOrdinal(left, right));
705
706
  // Keep presence separate from value: absent PATHEXT means the Windows
706
707
  // default extension list, while PATHEXT="" means no implicit extensions.
707
708
  // The per-drive snapshot is equally important: it is provenance for
@@ -792,6 +793,13 @@ function ensureUtf8ConsoleCodePageOnce() {
792
793
  spawnSync(cmdExe, ["/d", "/s", "/c", "chcp 65001 >nul 2>&1"], {
793
794
  shell: false,
794
795
  windowsHide: true,
796
+ // #1980 population sweep: a synchronous spawn with no timeout parks
797
+ // the event loop for as long as the child takes, and nothing here
798
+ // bounds that. `chcp` is normally instant, but this runs before the
799
+ // FIRST real spawn of the process, which is exactly when a cold or
800
+ // antivirus-throttled cmd.exe is slowest. Matches the 5s bound every
801
+ // other synchronous child-process site in this repo already carries.
802
+ timeout: 5000,
795
803
  });
796
804
  }
797
805
  catch {
@@ -891,6 +899,8 @@ export async function safeSpawnAsync(command, args, options) {
891
899
  let aborted = false;
892
900
  let killed = false;
893
901
  let outputTruncated = false;
902
+ let killedForOutputCap = false;
903
+ let streamingMatch = false;
894
904
  // #1651 review: a single boolean the close/error handlers both check
895
905
  // AND set, so whichever one decides the outcome first wins outright —
896
906
  // the DECISION itself is shape-based (see the `close` handler below),
@@ -937,21 +947,116 @@ export async function safeSpawnAsync(command, args, options) {
937
947
  options.maxOutputBytes > 0
938
948
  ? Math.floor(options.maxOutputBytes)
939
949
  : undefined;
940
- const appendOutput = (current, chunk) => {
950
+ const outputTruncationMarker = "\n...[output truncated]...\n";
951
+ let retainedHead = [];
952
+ let retainedTail = [];
953
+ let retainedTailBytes = 0;
954
+ let retainedTailLimit = 0;
955
+ let truncationStream = "stderr";
956
+ const streamingMatcher = options?.matchWhileStreaming;
957
+ const streamingCarryBytes = streamingMatcher
958
+ ? Math.max(0, streamingMatcher.source.length - 1)
959
+ : 0;
960
+ const streamingCarry = {
961
+ stdout: "",
962
+ stderr: "",
963
+ };
964
+ const bytePrefix = (text, bytes) => Buffer.from(text).subarray(0, bytes).toString();
965
+ const byteSuffix = (text, bytes) => {
966
+ const buffer = Buffer.from(text);
967
+ return buffer.subarray(Math.max(0, buffer.byteLength - bytes)).toString();
968
+ };
969
+ const appendTail = (part, maxBytes) => {
970
+ if (maxBytes <= 0)
971
+ return;
972
+ retainedTail.push({ stream: part.stream, text: part.text });
973
+ retainedTailBytes += Buffer.byteLength(part.text);
974
+ while (retainedTailBytes > maxBytes && retainedTail.length > 0) {
975
+ const first = retainedTail[0];
976
+ const excess = retainedTailBytes - maxBytes;
977
+ const firstBytes = Buffer.byteLength(first.text);
978
+ if (firstBytes <= excess) {
979
+ retainedTail.shift();
980
+ retainedTailBytes -= firstBytes;
981
+ }
982
+ else {
983
+ first.text = byteSuffix(first.text, firstBytes - excess);
984
+ retainedTailBytes -= excess;
985
+ }
986
+ }
987
+ };
988
+ const renderOutput = (stream) => {
989
+ const head = retainedHead
990
+ .filter((part) => part.stream === stream)
991
+ .map((part) => part.text)
992
+ .join("");
993
+ const tail = retainedTail
994
+ .filter((part) => part.stream === stream)
995
+ .map((part) => part.text)
996
+ .join("");
997
+ const marker = stream === truncationStream ? outputTruncationMarker : "";
998
+ return head + marker + tail;
999
+ };
1000
+ const refreshRetainedOutputs = () => {
1001
+ stdout = renderOutput("stdout");
1002
+ stderr = renderOutput("stderr");
1003
+ };
1004
+ const appendOutput = (stream, current, chunk) => {
941
1005
  const text = typeof chunk === "string" ? chunk : chunk.toString();
942
- if (maxOutputBytes === undefined)
943
- return current + text;
944
- const used = Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
945
- const remaining = maxOutputBytes - used;
946
- if (remaining <= 0) {
947
- outputTruncated = true;
948
- return current;
1006
+ if (maxOutputBytes === undefined || outputTruncated) {
1007
+ if (maxOutputBytes === undefined)
1008
+ return current + text;
1009
+ appendTail({ stream, text }, retainedTailLimit);
1010
+ truncationStream = stream;
1011
+ return renderOutput(stream);
949
1012
  }
1013
+ const used = Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
950
1014
  const bytes = Buffer.byteLength(text);
951
- if (bytes <= remaining)
1015
+ if (used + bytes <= maxOutputBytes)
952
1016
  return current + text;
953
1017
  outputTruncated = true;
954
- return current + Buffer.from(text).subarray(0, remaining).toString();
1018
+ truncationStream = stream;
1019
+ const available = Math.max(0, maxOutputBytes - Buffer.byteLength(outputTruncationMarker));
1020
+ const headBytes = Math.floor(available / 2);
1021
+ const tailBytes = available - headBytes;
1022
+ retainedTailLimit = tailBytes;
1023
+ const parts = [
1024
+ { stream: "stdout", text: stdout },
1025
+ { stream: "stderr", text: stderr },
1026
+ { stream, text },
1027
+ ];
1028
+ let headRemaining = headBytes;
1029
+ for (const part of parts) {
1030
+ if (headRemaining <= 0)
1031
+ break;
1032
+ const kept = bytePrefix(part.text, headRemaining);
1033
+ if (kept)
1034
+ retainedHead.push({ stream: part.stream, text: kept });
1035
+ headRemaining -= Buffer.byteLength(kept);
1036
+ }
1037
+ let tailRemaining = tailBytes;
1038
+ for (let index = parts.length - 1; index >= 0 && tailRemaining > 0; index--) {
1039
+ const part = parts[index];
1040
+ const kept = byteSuffix(part.text, tailRemaining);
1041
+ if (kept) {
1042
+ retainedTail.unshift({ stream: part.stream, text: kept });
1043
+ tailRemaining -= Buffer.byteLength(kept);
1044
+ }
1045
+ }
1046
+ retainedTailBytes = tailBytes - tailRemaining;
1047
+ return renderOutput(stream);
1048
+ };
1049
+ const matchStreamingChunk = (stream, chunk) => {
1050
+ if (streamingMatch || !streamingMatcher)
1051
+ return;
1052
+ const text = typeof chunk === "string" ? chunk : chunk.toString();
1053
+ streamingMatcher.lastIndex = 0;
1054
+ streamingMatch = streamingMatcher.test(streamingCarry[stream] + text);
1055
+ if (streamingCarryBytes > 0) {
1056
+ streamingCarry[stream] = Buffer.from(streamingCarry[stream] + text)
1057
+ .subarray(-streamingCarryBytes)
1058
+ .toString();
1059
+ }
955
1060
  };
956
1061
  // Spawn the process (non-blocking). Keeping Node's `shell` option false
957
1062
  // is important on every path here: shell:true concatenates tainted
@@ -1091,6 +1196,12 @@ export async function safeSpawnAsync(command, args, options) {
1091
1196
  }));
1092
1197
  return;
1093
1198
  }
1199
+ // Keep historical open stdin by default. An opted-in payload is written
1200
+ // completely and followed by EOF; input: "" closes stdin immediately.
1201
+ if (options?.input !== undefined) {
1202
+ child.stdin?.on("error", () => { });
1203
+ child.stdin?.end(options.input);
1204
+ }
1094
1205
  if (child.pid && (posixProcessGroup || options?.lifetimeCoupled)) {
1095
1206
  // #2026: POSIX registers unconditionally - detached children no
1096
1207
  // longer receive terminal signals directly, so the lifetime
@@ -1201,11 +1312,17 @@ export async function safeSpawnAsync(command, args, options) {
1201
1312
  };
1202
1313
  abortSignal?.addEventListener("abort", onAbort, { once: true });
1203
1314
  // Output-cap kills are awaited by finalize() below, just like
1204
- // timeout/abort kills. This keeps a noisy CLI from continuing in the
1205
- // background after its retained output has been bounded.
1315
+ // timeout/abort kills. An armed, unmatched streaming matcher is the one
1316
+ // exception: retention remains bounded, but the caller's timeout is the
1317
+ // bound while the child gets a chance to emit its rescue line.
1206
1318
  let killPromise;
1207
1319
  const stopForOutputLimit = () => {
1208
- if (outputTruncated && !killed && !child.killed) {
1320
+ const waitingForStreamingMatch = streamingMatcher !== undefined && !streamingMatch;
1321
+ if (outputTruncated &&
1322
+ !waitingForStreamingMatch &&
1323
+ !killed &&
1324
+ !child.killed) {
1325
+ killedForOutputCap = true;
1209
1326
  killed = true;
1210
1327
  killPromise = killTree();
1211
1328
  }
@@ -1214,12 +1331,18 @@ export async function safeSpawnAsync(command, args, options) {
1214
1331
  child.stdout?.setEncoding("utf-8");
1215
1332
  child.stderr?.setEncoding("utf-8");
1216
1333
  child.stdout?.on("data", (data) => {
1217
- stdout = appendOutput(stdout, data);
1334
+ matchStreamingChunk("stdout", data);
1335
+ stdout = appendOutput("stdout", stdout, data);
1336
+ if (outputTruncated)
1337
+ refreshRetainedOutputs();
1218
1338
  stopForOutputLimit();
1219
1339
  rearmIdleGrace?.();
1220
1340
  });
1221
1341
  child.stderr?.on("data", (data) => {
1222
- stderr = appendOutput(stderr, data);
1342
+ matchStreamingChunk("stderr", data);
1343
+ stderr = appendOutput("stderr", stderr, data);
1344
+ if (outputTruncated)
1345
+ refreshRetainedOutputs();
1223
1346
  stopForOutputLimit();
1224
1347
  rearmIdleGrace?.();
1225
1348
  });
@@ -1415,7 +1538,11 @@ export async function safeSpawnAsync(command, args, options) {
1415
1538
  resolved = true;
1416
1539
  await waitForPipeIdle();
1417
1540
  const resourceUsage = finishResourceUsage();
1418
- const outputInfo = outputTruncated ? { outputTruncated: true } : {};
1541
+ const outputInfo = {
1542
+ ...(outputTruncated ? { outputTruncated: true } : {}),
1543
+ ...(killedForOutputCap ? { killedForOutputCap: true } : {}),
1544
+ };
1545
+ const streamingMatchInfo = streamingMatch ? { streamingMatch: true } : {};
1419
1546
  // #1816: surface the signal name as a field on every path where one
1420
1547
  // exists, so callers can NAME it instead of scraping `error.message`.
1421
1548
  const signalInfo = signal ? { signal } : {};
@@ -1440,6 +1567,7 @@ export async function safeSpawnAsync(command, args, options) {
1440
1567
  ...(timeoutTeardown && { timeoutTeardown }),
1441
1568
  spawnFailure: new SpawnFailureError("timeout", cause.message, cause),
1442
1569
  ...outputInfo,
1570
+ ...streamingMatchInfo,
1443
1571
  resourceUsage,
1444
1572
  });
1445
1573
  }
@@ -1454,6 +1582,7 @@ export async function safeSpawnAsync(command, args, options) {
1454
1582
  ...signalInfo,
1455
1583
  spawnFailure: new SpawnFailureError("killed", cause.message, cause),
1456
1584
  ...outputInfo,
1585
+ ...streamingMatchInfo,
1457
1586
  resourceUsage,
1458
1587
  });
1459
1588
  }
@@ -1468,6 +1597,7 @@ export async function safeSpawnAsync(command, args, options) {
1468
1597
  ...signalInfo,
1469
1598
  spawnFailure: new SpawnFailureError("killed", cause.message, cause),
1470
1599
  ...outputInfo,
1600
+ ...streamingMatchInfo,
1471
1601
  resourceUsage,
1472
1602
  });
1473
1603
  }
@@ -1497,11 +1627,19 @@ export async function safeSpawnAsync(command, args, options) {
1497
1627
  failure: "spawn",
1498
1628
  spawnFailure,
1499
1629
  ...outputInfo,
1630
+ ...streamingMatchInfo,
1500
1631
  resourceUsage,
1501
1632
  });
1502
1633
  }
1503
1634
  else {
1504
- resolve({ stdout, stderr, status: code, ...outputInfo, resourceUsage });
1635
+ resolve({
1636
+ stdout,
1637
+ stderr,
1638
+ status: code,
1639
+ ...outputInfo,
1640
+ ...streamingMatchInfo,
1641
+ resourceUsage,
1642
+ });
1505
1643
  }
1506
1644
  };
1507
1645
  child.on("exit", onChildSettle);
@@ -1545,6 +1683,8 @@ export async function safeSpawnAsync(command, args, options) {
1545
1683
  failure,
1546
1684
  spawnFailure,
1547
1685
  ...(outputTruncated ? { outputTruncated: true } : {}),
1686
+ ...(killedForOutputCap ? { killedForOutputCap: true } : {}),
1687
+ ...(streamingMatch ? { streamingMatch: true } : {}),
1548
1688
  resourceUsage,
1549
1689
  });
1550
1690
  if (controlFailure)
@@ -16,6 +16,9 @@ import * as path from "node:path";
16
16
  import { createSubsystemLogger } from "./extension-log.js";
17
17
  import { safeSpawnAsync } from "./safe-spawn.js";
18
18
  import { classifyProbeFailure, createAvailabilityLatch, describeInstallAttempt, describeProbeEvidence, logAvailabilityDecision, startHostStallSampler, } from "./dispatch/runners/utils/availability-policy.js";
19
+ import { createAvailabilityProbeFlight } from "./availability-probe-flight.js";
20
+ import { createSingleFlight } from "./single-flight.js";
21
+ const securityProbeFlights = createAvailabilityProbeFlight();
19
22
  export class SecurityScanClient {
20
23
  toolName;
21
24
  /**
@@ -79,7 +82,8 @@ export class SecurityScanClient {
79
82
  }
80
83
  /** Outcome of the most recent `probeVersion` call. */
81
84
  lastProbeOutcome = null;
82
- ensureInFlight = null;
85
+ /** Availability resolution is instance-owned; only its external probe is shared. */
86
+ ensureFlight = createSingleFlight();
83
87
  inFlight = new Map();
84
88
  binaryPath = null;
85
89
  log;
@@ -99,15 +103,7 @@ export class SecurityScanClient {
99
103
  async ensureAvailable() {
100
104
  if (this.available !== null)
101
105
  return this.available;
102
- if (this.ensureInFlight)
103
- return this.ensureInFlight;
104
- this.ensureInFlight = this.doEnsureAvailable();
105
- try {
106
- return await this.ensureInFlight;
107
- }
108
- finally {
109
- this.ensureInFlight = null;
110
- }
106
+ return this.ensureFlight.run("availability", () => this.doEnsureAvailable());
111
107
  }
112
108
  /**
113
109
  * Spawn `toolName <versionArgs>` and report whether it answered cleanly.
@@ -128,10 +124,26 @@ export class SecurityScanClient {
128
124
  const startedAt = Date.now();
129
125
  let probe;
130
126
  let hostStallMs;
127
+ let resolvedBinaryPath = null;
128
+ let probeJoined = false;
131
129
  try {
132
- probe = await safeSpawnAsync(this.toolName, versionArgs, {
133
- timeout: 5000,
130
+ const shared = securityProbeFlights.run(`security:${this.toolName}|${versionArgs.join("|")}`, async () => {
131
+ const { findManagedToolBinary } = await import("./installer/index.js");
132
+ const managed = await findManagedToolBinary(this.toolName);
133
+ return {
134
+ probe: await safeSpawnAsync(managed ?? this.toolName, versionArgs, {
135
+ timeout: 5000,
136
+ }),
137
+ binaryPath: managed ?? null,
138
+ };
134
139
  });
140
+ probeJoined = shared.joined;
141
+ const flightResult = await shared.promise;
142
+ probe = flightResult.probe;
143
+ resolvedBinaryPath = flightResult.binaryPath;
144
+ if (!probe.error && probe.status === 0 && flightResult.binaryPath) {
145
+ this.binaryPath = flightResult.binaryPath;
146
+ }
135
147
  }
136
148
  finally {
137
149
  hostStallMs = sampler.stop();
@@ -149,8 +161,14 @@ export class SecurityScanClient {
149
161
  latched: true,
150
162
  hostStallMs,
151
163
  budgetMs: 5000,
152
- classifiedBy: "probe",
153
- evidence: describeProbeEvidence(probe),
164
+ classifiedBy: probeJoined ? "joined" : "probe",
165
+ evidence: {
166
+ ...describeProbeEvidence(probe),
167
+ ...(resolvedBinaryPath && {
168
+ binary: path.basename(resolvedBinaryPath),
169
+ source: "managed-dir",
170
+ }),
171
+ },
154
172
  });
155
173
  return true;
156
174
  }
@@ -175,8 +193,14 @@ export class SecurityScanClient {
175
193
  hostStallMs,
176
194
  ...(retryAfterMs > 0 && { retryAfterMs }),
177
195
  budgetMs: 5000,
178
- classifiedBy: "probe",
179
- evidence,
196
+ classifiedBy: probeJoined ? "joined" : "probe",
197
+ evidence: {
198
+ ...evidence,
199
+ ...(resolvedBinaryPath && {
200
+ binary: path.basename(resolvedBinaryPath),
201
+ source: "managed-dir",
202
+ }),
203
+ },
180
204
  });
181
205
  return false;
182
206
  }
@@ -311,7 +335,14 @@ export class SecurityScanClient {
311
335
  this.log(`Scan already in flight for ${key}; sharing result`);
312
336
  return existing;
313
337
  }
314
- const promise = run().finally(() => this.inFlight.delete(key));
338
+ // Identity-guarded release (#1968's pattern): delete only if THIS run is
339
+ // still the registered one. A bare delete-by-key lets a late-settling run
340
+ // evict a live successor a second writer registered under the same key
341
+ // mid-flight, after which the next caller starts a duplicate scan.
342
+ const promise = run().finally(() => {
343
+ if (this.inFlight.get(key) === promise)
344
+ this.inFlight.delete(key);
345
+ });
315
346
  this.inFlight.set(key, promise);
316
347
  return promise;
317
348
  }