@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
@@ -207,7 +207,13 @@ function delimitedSubstitutionBody(command, start, opener, closer) {
207
207
  }
208
208
  return undefined;
209
209
  }
210
- function containsGuardedSubstitution(command, depth) {
210
+ const COMMIT_PUSH_MATCHER = {
211
+ id: "commit-push",
212
+ matchesVerb: (verb) => verb === "commit" || verb === "push",
213
+ indirectAlwaysMatches: true,
214
+ suppressPostVerbHelp: false,
215
+ };
216
+ function containsGuardedSubstitution(command, depth, matcher) {
211
217
  if (depth > 3)
212
218
  return false;
213
219
  let quote;
@@ -249,8 +255,8 @@ function containsGuardedSubstitution(command, depth) {
249
255
  : undefined;
250
256
  if (substitution && substitution.end >= 0) {
251
257
  const nested = canonicalizeGuardCommand(substitution.body);
252
- if (containsGuardedSubstitution(nested, depth + 1) ||
253
- tokenizeShellCommand(nested).some((segment) => containsCommitOrPush(segment.tokens, depth + 1))) {
258
+ if (containsGuardedSubstitution(nested, depth + 1, matcher) ||
259
+ tokenizeShellCommand(nested).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher))) {
254
260
  return true;
255
261
  }
256
262
  i = substitution.end;
@@ -258,7 +264,169 @@ function containsGuardedSubstitution(command, depth) {
258
264
  }
259
265
  return false;
260
266
  }
261
- function containsCommitOrPush(tokens, depth) {
267
+ /** Git global options that consume the following token as their value. */
268
+ const GIT_GLOBAL_OPTIONS_WITH_VALUE = new Set([
269
+ "-C",
270
+ "-c",
271
+ "--config-env",
272
+ "--git-dir",
273
+ "--work-tree",
274
+ "--exec-path",
275
+ "--namespace",
276
+ ]);
277
+ const GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE = [
278
+ "--config-env",
279
+ "--git-dir",
280
+ "--work-tree",
281
+ "--exec-path",
282
+ "--namespace",
283
+ ];
284
+ /**
285
+ * Index of git's subcommand token in `gitTokens` (which starts at the `git`
286
+ * executable), after skipping global options and their values. Returns
287
+ * `undefined` when the invocation has no subcommand at all — `git --help`,
288
+ * `git --version`, or a trailing option with no verb behind it.
289
+ *
290
+ * Shared by the direct and indirect paths so both ask the same question about
291
+ * the same position (#2007).
292
+ */
293
+ function gitVerbIndex(gitTokens) {
294
+ let i = 1;
295
+ while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
296
+ const option = gitTokens[i];
297
+ if (["--help", "-h", "--version", "-v", "-V"].includes(option)) {
298
+ return undefined;
299
+ }
300
+ if (option === "--")
301
+ return i + 1 < gitTokens.length ? i + 1 : undefined;
302
+ if (["-C", "-c"].some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
303
+ i += 1;
304
+ continue;
305
+ }
306
+ if (GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE.some((prefix) => option.startsWith(`${prefix}=`))) {
307
+ i += 1;
308
+ continue;
309
+ }
310
+ i += GIT_GLOBAL_OPTIONS_WITH_VALUE.has(option) ? 2 : 1;
311
+ }
312
+ return i < gitTokens.length ? i : undefined;
313
+ }
314
+ /**
315
+ * True when the subcommand in git's command position is one the matcher
316
+ * governs. `gitTokens[0]` is the `git` executable token.
317
+ */
318
+ export function matchGitVerbAtCommandPosition(gitTokens, matcher) {
319
+ const index = gitVerbIndex(gitTokens);
320
+ if (index === undefined)
321
+ return false;
322
+ const argsAfterVerb = gitTokens.slice(index + 1);
323
+ // `git checkout --help` prints documentation and touches nothing, and the
324
+ // global-option walk above cannot see it, because it sits AFTER the verb.
325
+ //
326
+ // Only the FIRST argument counts, and only for matchers that opt in. `-h`
327
+ // deeper in the argv may be an option VALUE rather than a help request —
328
+ // `git commit -m -h` commits with the message `-h`, `git clean -e -h`
329
+ // excludes a pattern named `-h`. Reading any position would need a
330
+ // per-verb table of value-taking options for every governed verb, which is
331
+ // exactly the hand-maintained parallel list this repo forbids, and every
332
+ // gap in it would silently UNDER-block. Leading-position-only needs no
333
+ // table, covers how help is actually invoked, and every other spelling
334
+ // falls through to a match, which is the safe direction for a guard.
335
+ if (matcher.suppressPostVerbHelp &&
336
+ (argsAfterVerb[0] === "--help" || argsAfterVerb[0] === "-h")) {
337
+ return false;
338
+ }
339
+ const verbs = expandGuardVerbToken(gitTokens[index] ?? "");
340
+ return verbs.length === 1 && matcher.matchesVerb(verbs[0], argsAfterVerb);
341
+ }
342
+ /** Strip one layer of shell quoting the lexer preserved on a value token. */
343
+ function unquoteGuardValue(value) {
344
+ const trimmed = value.trim();
345
+ if (trimmed.length >= 2) {
346
+ const first = trimmed[0];
347
+ const last = trimmed[trimmed.length - 1];
348
+ if ((first === '"' || first === "'") && first === last) {
349
+ return trimmed.slice(1, -1);
350
+ }
351
+ }
352
+ return trimmed;
353
+ }
354
+ /**
355
+ * The directory a git invocation actually targets (#2007).
356
+ *
357
+ * `git -C <dir>` and `--work-tree` retarget the command at a DIFFERENT
358
+ * directory, so a guard that evaluates the caller's cwd would inspect the
359
+ * wrong working tree and allow a destructive command against a shared one.
360
+ * `-C` composes cumulatively, exactly as git applies it; `--work-tree` names
361
+ * the working tree directly and therefore wins.
362
+ *
363
+ * Returns the resolved absolute directory, or `cwd` when the invocation
364
+ * carries no retargeting option.
365
+ */
366
+ export function resolveGitTargetDirectory(gitTokens, cwd) {
367
+ let base = cwd;
368
+ let workTree;
369
+ let i = 1;
370
+ while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
371
+ const option = gitTokens[i];
372
+ if (["--help", "-h", "--version", "-v", "-V"].includes(option))
373
+ break;
374
+ if (option === "--")
375
+ break;
376
+ if (option === "-C" && i + 1 < gitTokens.length) {
377
+ base = path.resolve(base, unquoteGuardValue(gitTokens[i + 1]));
378
+ i += 2;
379
+ continue;
380
+ }
381
+ if (option.startsWith("-C") && option.length > 2) {
382
+ base = path.resolve(base, unquoteGuardValue(option.slice(2)));
383
+ i += 1;
384
+ continue;
385
+ }
386
+ if (option === "--work-tree" && i + 1 < gitTokens.length) {
387
+ workTree = unquoteGuardValue(gitTokens[i + 1]);
388
+ i += 2;
389
+ continue;
390
+ }
391
+ if (option.startsWith("--work-tree=")) {
392
+ workTree = unquoteGuardValue(option.slice("--work-tree=".length));
393
+ i += 1;
394
+ continue;
395
+ }
396
+ if (option.startsWith("-c") &&
397
+ option.length > 2 &&
398
+ !option.startsWith("--")) {
399
+ i += 1;
400
+ continue;
401
+ }
402
+ if (GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE.some((prefix) => option.startsWith(`${prefix}=`))) {
403
+ i += 1;
404
+ continue;
405
+ }
406
+ i += GIT_GLOBAL_OPTIONS_WITH_VALUE.has(option) ? 2 : 1;
407
+ }
408
+ return workTree === undefined ? base : path.resolve(base, workTree);
409
+ }
410
+ /**
411
+ * Every git invocation the command contains, as token arrays starting at the
412
+ * `git` executable. Used by callers that must know WHICH directory a matched
413
+ * command targets, not merely that it matched (#2007).
414
+ */
415
+ export function collectGitInvocations(toolName, input) {
416
+ if (toolName !== "bash")
417
+ return [];
418
+ const command = getShellCommand(input);
419
+ if (!command)
420
+ return [];
421
+ const invocations = [];
422
+ for (const segment of tokenizeShellCommand(canonicalizeGuardCommand(command))) {
423
+ const gitIndex = segment.tokens.findIndex((token) => isGitExecutable(token));
424
+ if (gitIndex >= 0)
425
+ invocations.push(segment.tokens.slice(gitIndex));
426
+ }
427
+ return invocations;
428
+ }
429
+ function containsGuardedGitVerb(tokens, depth, matcher) {
262
430
  if (depth > 3 || tokens.length === 0)
263
431
  return false;
264
432
  let commandTokens = tokens;
@@ -276,7 +444,7 @@ function containsCommitOrPush(tokens, depth) {
276
444
  const runIndex = lower.findIndex((token) => token === "--run");
277
445
  if (runIndex >= 0 && runIndex + 1 < commandTokens.length) {
278
446
  const nestedCommand = commandTokens.slice(runIndex + 2).join(" ");
279
- return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsCommitOrPush(segment.tokens, depth + 1));
447
+ return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
280
448
  }
281
449
  }
282
450
  // These commands consume their following words as text/patterns; a bare
@@ -290,43 +458,14 @@ function containsCommitOrPush(tokens, depth) {
290
458
  if (gitIndex >= 0) {
291
459
  // Any non-leading git invocation is indirect. Do not maintain a wrapper
292
460
  // or flag allowlist: unknown launchers are the security boundary here.
293
- if (gitIndex > 0)
461
+ if (gitIndex > 0 && matcher.indirectAlwaysMatches)
294
462
  return true;
295
- const gitTokens = commandTokens.slice(gitIndex);
296
- let i = 1;
297
- const takesValue = new Set([
298
- "-C",
299
- "-c",
300
- "--config-env",
301
- "--git-dir",
302
- "--work-tree",
303
- "--exec-path",
304
- "--namespace",
305
- ]);
306
- while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
307
- const option = gitTokens[i];
308
- if (["--help", "-h", "--version", "-v", "-V"].includes(option))
309
- return false;
310
- if (option === "--")
311
- return gitTokens[i + 1] === "commit" || gitTokens[i + 1] === "push";
312
- if (["-C", "-c"].some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
313
- i += 1;
314
- continue;
315
- }
316
- if ([
317
- "--config-env",
318
- "--git-dir",
319
- "--work-tree",
320
- "--exec-path",
321
- "--namespace",
322
- ].some((prefix) => option.startsWith(`${prefix}=`))) {
323
- i += 1;
324
- continue;
325
- }
326
- i += takesValue.has(option) ? 2 : 1;
327
- }
328
- const verbs = expandGuardVerbToken(gitTokens[i] ?? "");
329
- return verbs.length === 1 && (verbs[0] === "commit" || verbs[0] === "push");
463
+ // #2007: a guard that protects the agent from its own accident must not
464
+ // decline `xargs git status`, so the indirect path stays armed only when
465
+ // a governed verb sits in git's real COMMAND POSITION. Scanning every
466
+ // token instead would fire on `find . -name checkout | xargs git add`,
467
+ // where `checkout` is a positional value and not a subcommand at all.
468
+ return matchGitVerbAtCommandPosition(commandTokens.slice(gitIndex), matcher);
330
469
  }
331
470
  const leadingExecutable = commandTokens[0] ?? "";
332
471
  const knownCommandStringWrapper = isShellWrapper(leadingExecutable) ||
@@ -344,7 +483,7 @@ function containsCommitOrPush(tokens, depth) {
344
483
  if (commandIndex >= commandTokens.length)
345
484
  return false;
346
485
  const nestedCommand = commandTokens.slice(commandIndex).join(" ");
347
- return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsCommitOrPush(segment.tokens, depth + 1));
486
+ return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
348
487
  }
349
488
  const lower = commandTokens.slice(1).map((token) => token.toLowerCase());
350
489
  const switchIndex = lower.findIndex((token) => token === "-c" ||
@@ -369,19 +508,30 @@ function containsCommitOrPush(tokens, depth) {
369
508
  if (commandTokens[commandIndex] === "--")
370
509
  commandIndex += 1;
371
510
  const nestedCommand = commandTokens.slice(commandIndex).join(" ");
372
- return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsCommitOrPush(segment.tokens, depth + 1));
511
+ return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
373
512
  }
374
- /** Analyze actual executable invocations, not substrings in shell text. */
375
- export function isGitCommitOrPushAttempt(toolName, input) {
513
+ /**
514
+ * Analyze actual executable invocations, not substrings in shell text.
515
+ *
516
+ * Shared entry point for every guard that needs "is this bash input really a
517
+ * git invocation of a verb I govern" (#2007). The verb question is the only
518
+ * parameter; wrapper, substitution, and text-consumer analysis are identical
519
+ * for all callers by construction.
520
+ */
521
+ export function detectGuardedGitVerb(toolName, input, matcher) {
376
522
  if (toolName !== "bash")
377
523
  return false;
378
524
  const command = getShellCommand(input);
379
525
  if (!command)
380
526
  return false;
381
527
  const canonical = canonicalizeGuardCommand(command);
382
- if (containsGuardedSubstitution(canonical, 0))
528
+ if (containsGuardedSubstitution(canonical, 0, matcher))
383
529
  return true;
384
- return tokenizeShellCommand(canonical).some((segment) => containsCommitOrPush(segment.tokens, 0));
530
+ return tokenizeShellCommand(canonical).some((segment) => containsGuardedGitVerb(segment.tokens, 0, matcher));
531
+ }
532
+ /** The #1063 commit gate's own question. */
533
+ export function isGitCommitOrPushAttempt(toolName, input) {
534
+ return detectGuardedGitVerb(toolName, input, COMMIT_PUSH_MATCHER);
385
535
  }
386
536
  function isTurnEndFindingsCache(value) {
387
537
  if (!value || typeof value !== "object")
@@ -33,8 +33,25 @@
33
33
  */
34
34
  import * as path from "node:path";
35
35
  import { isExcludedDirName } from "./file-utils.js";
36
+ import { recordDegradationOnce } from "./degradation-ledger.js";
36
37
  import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
37
38
  import { safeSpawnAsync } from "./safe-spawn.js";
39
+ import { truncatedByOutputCap } from "./spawn-output-cap.js";
40
+ function recordTruncatedLsFiles(parseSite) {
41
+ recordDegradationOnce({
42
+ kind: "git-tracked-ignore-truncated",
43
+ subject: `git-ls-files:${parseSite}`,
44
+ reason: "safeSpawnAsync capped git ls-files stdout",
45
+ });
46
+ }
47
+ // The binding listing is the untracked-ignored one: a mid-size project's
48
+ // node_modules alone yields a ~66k-line ignored list (roughly 4-6 MiB), and
49
+ // a monorepo with several dependency trees can multiply that. 16 MiB leaves
50
+ // that headroom while stopping safeSpawnAsync from retaining unbounded
51
+ // stdout (an uncapped probe held 20 MiB in full). The tracked listing is
52
+ // far smaller (~133 KiB in this repository). A capped read fails closed to
53
+ // unavailable with one bounded ledger record per site.
54
+ const MAX_LS_FILES_OUTPUT_BYTES = 16 * 1024 * 1024;
38
55
  /**
39
56
  * Parses `git ls-files --others --ignored --exclude-standard` output
40
57
  * (repo-relative paths, one per line) into normalized map-key file ids.
@@ -62,7 +79,16 @@ export function parseUntrackedIgnoredOutput(stdout, cwd) {
62
79
  }
63
80
  async function fetchUntrackedIgnoredIds(cwd) {
64
81
  try {
65
- const result = await safeSpawnAsync("git", ["ls-files", "--others", "--ignored", "--exclude-standard"], { cwd, timeout: 10_000, resourceLabel: "git-tracked-ignore" });
82
+ const result = await safeSpawnAsync("git", ["ls-files", "--others", "--ignored", "--exclude-standard"], {
83
+ cwd,
84
+ timeout: 10_000,
85
+ maxOutputBytes: MAX_LS_FILES_OUTPUT_BYTES,
86
+ resourceLabel: "git-tracked-ignore",
87
+ });
88
+ if (truncatedByOutputCap(result)) {
89
+ recordTruncatedLsFiles("untracked-ignored");
90
+ return undefined;
91
+ }
66
92
  if (result.error || result.status !== 0)
67
93
  return undefined;
68
94
  return parseUntrackedIgnoredOutput(result.stdout, cwd);
@@ -141,8 +167,13 @@ async function fetchTrackedFiles(cwd) {
141
167
  const result = await safeSpawnAsync("git", ["ls-files"], {
142
168
  cwd,
143
169
  timeout: 10_000,
170
+ maxOutputBytes: MAX_LS_FILES_OUTPUT_BYTES,
144
171
  resourceLabel: "git-tracked-ignore",
145
172
  });
173
+ if (truncatedByOutputCap(result)) {
174
+ recordTruncatedLsFiles("tracked");
175
+ return undefined;
176
+ }
146
177
  if (result.error || result.status !== 0)
147
178
  return undefined;
148
179
  return parseTrackedFilesOutput(result.stdout, cwd);
@@ -110,6 +110,7 @@ export class GovulncheckClient extends SecurityScanClient {
110
110
  hostStallMs: goHostStallMs,
111
111
  ...(retryAfterMs > 0 && { retryAfterMs }),
112
112
  budgetMs: 5000,
113
+ classifiedBy: "probe",
113
114
  });
114
115
  return false;
115
116
  }
@@ -188,8 +189,10 @@ export class GovulncheckClient extends SecurityScanClient {
188
189
  // row says so and carries the install facts behind it. Every retry
189
190
  // DID run a `go install` that failed, so `install: "failed"` is
190
191
  // earned, and the reason names the ceiling rather than the spawn.
192
+ // Below the ceiling, outcome/cause are fresh off `classifyProbeFailure`
193
+ // above, so that row is still `"probe"` (#2209).
194
+ classifiedBy: exhausted ? "caller" : "probe",
191
195
  ...(exhausted && {
192
- classifiedBy: "caller",
193
196
  evidence: {
194
197
  ...describeProbeEvidence(install, "go install"),
195
198
  install: "failed",
@@ -313,6 +316,7 @@ export class GovulncheckClient extends SecurityScanClient {
313
316
  hostStallMs: reprobeHostStallMs,
314
317
  ...(retryAfterMs > 0 && { retryAfterMs }),
315
318
  budgetMs: 5000,
319
+ classifiedBy: "probe",
316
320
  });
317
321
  return false;
318
322
  }
@@ -39,6 +39,10 @@ import { stagePathFor } from "./atomic-write-staging.js";
39
39
  * to a main-thread rewrite on an error result).
40
40
  */
41
41
  export async function writeGzipStageFile(data, stagePath, testDelayMs, options) {
42
+ // Worker-path duration starts inside this stage, so it includes the worker's
43
+ // test delay, serialization, gzip, and rename, but excludes postMessage queue
44
+ // latency. Main-thread callers measure from their inclusive persist entry.
45
+ const startedAt = performance.now();
42
46
  const tmpPath = stagePathFor(stagePath);
43
47
  try {
44
48
  if (testDelayMs) {
@@ -56,6 +60,7 @@ export async function writeGzipStageFile(data, stagePath, testDelayMs, options)
56
60
  gzBytes: 0,
57
61
  serializeMs,
58
62
  writeMs: 0,
63
+ durationMs: performance.now() - startedAt,
59
64
  semanticFingerprint,
60
65
  skippedUnchanged: true,
61
66
  };
@@ -77,6 +82,7 @@ export async function writeGzipStageFile(data, stagePath, testDelayMs, options)
77
82
  gzBytes,
78
83
  serializeMs,
79
84
  writeMs,
85
+ durationMs: performance.now() - startedAt,
80
86
  semanticFingerprint,
81
87
  };
82
88
  }
@@ -116,6 +122,7 @@ export function serveGzipStageWorker(buildBaseResult, options) {
116
122
  result.gzBytes = metrics.gzBytes;
117
123
  result.serializeMs = metrics.serializeMs;
118
124
  result.writeMs = metrics.writeMs;
125
+ result.durationMs = metrics.durationMs;
119
126
  result.semanticFingerprint = metrics.semanticFingerprint;
120
127
  result.skippedUnchanged = metrics.skippedUnchanged;
121
128
  }