@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
@@ -8,6 +8,7 @@ import { normalizeMapKey } from "../path-utils.js";
8
8
  import { loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
9
9
  import { freshnessFromMtime } from "../freshness.js";
10
10
  import { logLatency } from "../latency-logger.js";
11
+ import { compareOrdinal } from "../string-utils.js";
11
12
  import { workspaceDiagnosticsCacheSessionStart } from "./workspace-diagnostics-session.js";
12
13
  import { createDiskBindingCache, } from "./diagnostic-binding.js";
13
14
  /**
@@ -281,7 +282,11 @@ export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStart
281
282
  * Two invalidation layers:
282
283
  * 1. The file's OWN mtime must be unchanged since it was scanned (exact
283
284
  * match against `entry.mtimeMs`) — any drift, or a stat failure
284
- * (deleted/unreadable), invalidates.
285
+ * (deleted/unreadable), invalidates. #2300: when the entry also carries a
286
+ * `sizeBytes`, the current size must match too — an external rewrite that
287
+ * lands in the same mtime bucket with a different length is otherwise
288
+ * invisible to mtime alone. A pre-#2300 entry without `sizeBytes` fails
289
+ * OPEN to the mtime-only check (see the field's doc comment).
285
290
  * 2. When a reverse-dependency index is available (`getImports` returns an
286
291
  * array rather than `undefined`), every file THIS file imports must not
287
292
  * have changed after `entry.scannedAt` either — this is the fix for the
@@ -326,15 +331,19 @@ export function classifyWorkspaceDiagnosticsCacheExpiry(entry, now, sessionStart
326
331
  * (#1491/#1498) rather than just a redundant re-touch.
327
332
  */
328
333
  export function isEntryFresh(filePath, entry, getImports) {
329
- let ownMtime;
334
+ let ownStat;
330
335
  try {
331
- ownMtime = fs.statSync(filePath).mtimeMs;
336
+ ownStat = fs.statSync(filePath);
332
337
  }
333
338
  catch {
334
339
  return false; // deleted / unreadable
335
340
  }
336
- if (ownMtime !== entry.mtimeMs)
341
+ if (ownStat.mtimeMs !== entry.mtimeMs)
337
342
  return false;
343
+ // #2300: same axis reused from the stat above — no extra syscall.
344
+ if (entry.sizeBytes !== undefined && ownStat.size !== entry.sizeBytes) {
345
+ return false;
346
+ }
338
347
  const imports = getImports(filePath);
339
348
  if (imports === undefined) {
340
349
  // #1793: an entry recorded WITH dependency knowledge must not fall
@@ -376,7 +385,7 @@ export function cacheKeyFor(filePath) {
376
385
  */
377
386
  export function buildScopeKey(clientScope, excludeServerIds) {
378
387
  const excluded = excludeServerIds
379
- ? [...excludeServerIds].sort((a, b) => a.localeCompare(b))
388
+ ? [...excludeServerIds].sort(compareOrdinal)
380
389
  : [];
381
390
  return `${clientScope}|${excluded.join(",")}`;
382
391
  }
@@ -542,7 +551,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
542
551
  scannedAt: entry.scannedAt,
543
552
  };
544
553
  },
545
- record(filePath, scopeKey, diagnostics, mtimeMs, contentHash) {
554
+ record(filePath, scopeKey, diagnostics, mtimeMs, contentHash, sizeBytes) {
546
555
  entries[cacheKeyFor(filePath)] = {
547
556
  diagnostics,
548
557
  count: diagnostics.length,
@@ -556,6 +565,7 @@ export function createWorkspaceDiagnosticsCacheContext(cwd) {
556
565
  // weaker mtime-only path only when that refusal is warranted.
557
566
  depIndexAtScan: hasDepKnowledge(filePath),
558
567
  ...(contentHash !== undefined && { contentHash }),
568
+ ...(sizeBytes !== undefined && { sizeBytes }),
559
569
  };
560
570
  dirty = true;
561
571
  },
@@ -40,22 +40,26 @@
40
40
  * apart again (#1618 acceptance criterion 6).
41
41
  */
42
42
  import { logLatency } from "../latency-logger.js";
43
+ import { getProcessSingleton } from "../process-singletons.js";
43
44
  /** Extra headroom beyond the sweep's own wall-clock ceiling before an
44
45
  * idle-reset delay (or the max-hold-age failsafe) trusts that a real sweep
45
46
  * must be done. Single source for both derivations below AND for every
46
47
  * idle-reset path `clients/runtime-turn.ts` derives (base, subagent, and
47
48
  * budget-shortened) — never a second independently-tunable literal. */
48
49
  export const SWEEP_IDLE_SAFETY_MARGIN_MS = 60_000;
49
- let holds = new Map(); // holdId -> acquiredAt (Date.now())
50
- let nextHoldId = 1;
51
- let idleWaiters = [];
50
+ const WORKSPACE_SWEEP_HOLD_FAMILY = "lsp.workspace-sweep-hold";
51
+ const WORKSPACE_SWEEP_HOLD_VERSION = 1;
52
+ function state() {
53
+ return getProcessSingleton(WORKSPACE_SWEEP_HOLD_FAMILY, WORKSPACE_SWEEP_HOLD_VERSION, () => ({ holds: new Map(), nextHoldId: 1, idleWaiters: [] }));
54
+ }
52
55
  function flushIdleWaitersIfEmpty() {
53
- if (holds.size > 0)
56
+ const current = state();
57
+ if (current.holds.size > 0)
54
58
  return;
55
- if (idleWaiters.length === 0)
59
+ if (current.idleWaiters.length === 0)
56
60
  return;
57
- const waiters = idleWaiters;
58
- idleWaiters = [];
61
+ const waiters = current.idleWaiters;
62
+ current.idleWaiters = [];
59
63
  for (const waiter of waiters) {
60
64
  try {
61
65
  waiter();
@@ -77,15 +81,16 @@ export function getWorkspaceSweepMaxHoldAgeMs() {
77
81
  return getFullScanWallClockMs() + SWEEP_IDLE_SAFETY_MARGIN_MS;
78
82
  }
79
83
  function reapStaleHolds() {
80
- if (holds.size === 0)
84
+ const current = state();
85
+ if (current.holds.size === 0)
81
86
  return;
82
87
  const maxAgeMs = getWorkspaceSweepMaxHoldAgeMs();
83
88
  const now = Date.now();
84
- for (const [holdId, acquiredAt] of holds) {
89
+ for (const [holdId, acquiredAt] of current.holds) {
85
90
  const ageMs = now - acquiredAt;
86
91
  if (ageMs <= maxAgeMs)
87
92
  continue;
88
- holds.delete(holdId);
93
+ current.holds.delete(holdId);
89
94
  logLatency({
90
95
  type: "phase",
91
96
  phase: "lsp_workspace_sweep_hold_force_released",
@@ -104,10 +109,11 @@ function reapStaleHolds() {
104
109
  * force-released (session reset / max-age reap), is a no-op.
105
110
  */
106
111
  export function acquireWorkspaceSweepHold() {
107
- const holdId = nextHoldId++;
108
- holds.set(holdId, Date.now());
112
+ const current = state();
113
+ const holdId = current.nextHoldId++;
114
+ current.holds.set(holdId, Date.now());
109
115
  return () => {
110
- if (!holds.delete(holdId))
116
+ if (!state().holds.delete(holdId))
111
117
  return;
112
118
  flushIdleWaitersIfEmpty();
113
119
  };
@@ -117,7 +123,7 @@ export function acquireWorkspaceSweepHold() {
117
123
  * "active" past its own max age. */
118
124
  export function isWorkspaceSweepActive() {
119
125
  reapStaleHolds();
120
- return holds.size > 0;
126
+ return state().holds.size > 0;
121
127
  }
122
128
  /**
123
129
  * Run `cb` once the hold count next returns to zero. Runs synchronously and
@@ -126,11 +132,12 @@ export function isWorkspaceSweepActive() {
126
132
  */
127
133
  export function runWhenWorkspaceSweepIdle(cb) {
128
134
  reapStaleHolds();
129
- if (holds.size === 0) {
135
+ const current = state();
136
+ if (current.holds.size === 0) {
130
137
  cb();
131
138
  return;
132
139
  }
133
- idleWaiters.push(cb);
140
+ current.idleWaiters.push(cb);
134
141
  }
135
142
  /**
136
143
  * `resetLSPService({reason: "session_start"})` calls this: a session
@@ -142,23 +149,25 @@ export function runWhenWorkspaceSweepIdle(cb) {
142
149
  * silent.
143
150
  */
144
151
  export function clearWorkspaceSweepHoldForSessionStart() {
145
- if (holds.size === 0)
152
+ const current = state();
153
+ if (current.holds.size === 0)
146
154
  return;
147
155
  logLatency({
148
156
  type: "phase",
149
157
  phase: "lsp_workspace_sweep_hold_session_reset",
150
158
  filePath: "",
151
159
  durationMs: 0,
152
- metadata: { clearedHolds: holds.size },
160
+ metadata: { clearedHolds: current.holds.size },
153
161
  });
154
- holds.clear();
162
+ current.holds.clear();
155
163
  flushIdleWaitersIfEmpty();
156
164
  }
157
- /** Test-only: reset the module-scope hold state between tests. */
165
+ /** Test-only: reset the process-singleton hold state between tests. */
158
166
  export function _resetWorkspaceSweepHoldForTests() {
159
- holds = new Map();
160
- idleWaiters = [];
161
- nextHoldId = 1;
167
+ const current = state();
168
+ current.holds.clear();
169
+ current.idleWaiters = [];
170
+ current.nextHoldId = 1;
162
171
  }
163
172
  /**
164
173
  * Wall-clock ceiling for one `lens_diagnostics mode=full` scan
@@ -52,8 +52,22 @@ function combineResults(results) {
52
52
  }
53
53
  function uniqueDetails(files, fileDetails) {
54
54
  const byPath = new Map();
55
+ // #2016: `files` and `fileDetails` name the same paths, so without this the
56
+ // map-build loop and the lookup below each pay `realpathSync.native` for the
57
+ // same path (~200 microseconds per call on Windows; POSIX short-circuits, so
58
+ // CI cannot see it). The memo lives for one call, so it has no staleness
59
+ // window at all and needs no freshness design.
60
+ const keyMemo = new Map();
61
+ const keyFor = (filePath) => {
62
+ let key = keyMemo.get(filePath);
63
+ if (key === undefined) {
64
+ key = normalizeMapKey(path.resolve(filePath));
65
+ keyMemo.set(filePath, key);
66
+ }
67
+ return key;
68
+ };
55
69
  for (const detail of fileDetails) {
56
- const key = normalizeMapKey(path.resolve(detail.filePath));
70
+ const key = keyFor(detail.filePath);
57
71
  const previous = byPath.get(key);
58
72
  if (!previous) {
59
73
  byPath.set(key, detail);
@@ -70,7 +84,7 @@ function uniqueDetails(files, fileDetails) {
70
84
  importsChanged: previous.importsChanged || detail.importsChanged,
71
85
  });
72
86
  }
73
- return files.map((filePath) => byPath.get(normalizeMapKey(path.resolve(filePath))) ?? {
87
+ return files.map((filePath) => byPath.get(keyFor(filePath)) ?? {
74
88
  filePath,
75
89
  // Resource operations have no already-computed text range. A small
76
90
  // range is still enough to invalidate the touched-file turn state;
@@ -24,7 +24,7 @@ import { normalizeMapKey } from "../path-utils.js";
24
24
  import { loadProjectSnapshot } from "../project-snapshot.js";
25
25
  import { buildOrUpdateGraph } from "../review-graph/service.js";
26
26
  import { recordDiagnostics } from "../widget-state.js";
27
- import { deserializeWordIndex, removeWordIndexDocument, scheduleWordIndexPersist, updateWordIndexDocument, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
27
+ import { deserializeWordIndex, removeWordIndexDocumentAsync, scheduleWordIndexPersist, updateWordIndexDocumentForEdit, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
28
28
  import { logWordIndex } from "../word-index-logger.js";
29
29
  import { createMcpHost } from "./host-shim.js";
30
30
  // #536: module-scoped FactStore for the warm-analyze graph seam, mirroring the
@@ -33,7 +33,9 @@ import { createMcpHost } from "./host-shim.js";
33
33
  // tiers key off a stable FactStore instance across calls, so a fresh FactStore
34
34
  // per call would defeat that reuse. Scoped separately from integration.ts's
35
35
  // singleton since this file has no dependency on that module's internal state.
36
- const warmGraphFacts = new FactStore();
36
+ // Subject labels this store's capacity-eviction telemetry distinctly from
37
+ // the other five production FactStore instances (#2243 review round 3, F1).
38
+ const warmGraphFacts = new FactStore("mcp-analyze");
37
39
  // #536 rider (issue body: "when #348 phase 2 lands, the word-index per-edit
38
40
  // update should ride the SAME seam so both indexes stay warm together"):
39
41
  // MCP has no RuntimeCoordinator/`runtime.wordIndex` to hold a live index the
@@ -295,8 +297,9 @@ export async function analyzeFile(filePath, cwd, options = {}) {
295
297
  // index with no `forward` map (or none loaded) ⇒ no-op (no incremental
296
298
  // primitive available — the eventual full rebuild covers it); an
297
299
  // oversized file is REMOVED, never partially indexed; the update is
298
- // synchronous (no interleaving hazard — MCP is single-process, same as
299
- // pi); a successful update schedules the SAME debounced persist
300
+ // cooperative and serialized through the index's own operation queue
301
+ // (#2067 AC4), so this seam no longer holds the event loop for a whole
302
+ // replacement either; a successful update schedules the SAME debounced persist
300
303
  // (`scheduleWordIndexPersist`, `PI_LENS_WORD_INDEX_PERSIST_DEBOUNCE_MS`)
301
304
  // pi's path uses — no second persist mechanism.
302
305
  //
@@ -313,10 +316,13 @@ export async function analyzeFile(filePath, cwd, options = {}) {
313
316
  const content = fs.readFileSync(absPath, "utf8");
314
317
  const byteLength = Buffer.byteLength(content, "utf-8");
315
318
  if (byteLength > WORD_INDEX_MAX_BYTES) {
316
- removeWordIndexDocument(warmIndex, absPath);
319
+ await removeWordIndexDocumentAsync(warmIndex, absPath);
317
320
  }
318
321
  else {
319
- updateWordIndexDocument(warmIndex, { path: absPath, content });
322
+ await updateWordIndexDocumentForEdit(warmIndex, {
323
+ path: absPath,
324
+ content,
325
+ });
320
326
  }
321
327
  scheduleWordIndexPersist(cwd, warmIndex);
322
328
  }
@@ -309,8 +309,13 @@ export function runTurnEndForIpc(cwd) {
309
309
  const inFlight = inFlightIpcTurnEnds.get(cwd);
310
310
  if (inFlight)
311
311
  return inFlight;
312
+ // Identity-guarded release (#1968's pattern): delete only if THIS run is
313
+ // still the registered one. A bare delete-by-key lets a late-settling run
314
+ // evict a live successor a second writer registered under the same cwd
315
+ // mid-flight, after which the next caller starts a duplicate Stop-hook pass.
312
316
  const run = runTurnEndForIpcNow(cwd).finally(() => {
313
- inFlightIpcTurnEnds.delete(cwd);
317
+ if (inFlightIpcTurnEnds.get(cwd) === run)
318
+ inFlightIpcTurnEnds.delete(cwd);
314
319
  });
315
320
  inFlightIpcTurnEnds.set(cwd, run);
316
321
  return run;
@@ -374,3 +379,11 @@ export function _resetTurnEndChain() {
374
379
  pendingTurnEndDeliveries.clear();
375
380
  inFlightIpcTurnEnds.clear();
376
381
  }
382
+ /**
383
+ * Test hook — read the `runTurnEndForIpc` in-flight map directly (#1968's
384
+ * ABA regression: a second writer replacing an entry mid-flight, then a
385
+ * late-settling first run evicting it with a bare delete-by-key).
386
+ */
387
+ export function _peekInFlightIpcTurnEnds() {
388
+ return inFlightIpcTurnEnds;
389
+ }
@@ -29,9 +29,11 @@
29
29
  * `arrayBuffers` is used as the process-wide proxy instead — WASM linear
30
30
  * memory backs an ArrayBuffer, so it is already included there.
31
31
  */
32
+ import { countPostingEntries, estimateWordIndexStoreBytes, } from "./word-index-store.js";
32
33
  import { getSharedTreeSitterClient } from "./tree-sitter-shared.js";
33
34
  import { getReviewGraphWorkspaceCacheSnapshot } from "./review-graph/builder.js";
34
35
  import { getDispatchCascadeCacheStats } from "./dispatch/integration.js";
36
+ import { getLspDocumentTextRetentionSnapshot } from "./lsp/client.js";
35
37
  /** Every N turns, emit one `memory_sample` latency.log line (#1123 item 2). */
36
38
  export const MEMORY_SAMPLE_TURN_INTERVAL = 10;
37
39
  /** True on turn 10, 20, 30, ... — never on turn 0 (nothing meaningful is
@@ -121,16 +123,36 @@ export function collectMemorySampleSubsystems(wordIndex) {
121
123
  : null;
122
124
  const dispatchCaches = getDispatchCascadeCacheStats();
123
125
  return {
126
+ lsp: (() => {
127
+ const retention = getLspDocumentTextRetentionSnapshot();
128
+ return {
129
+ clients: retention.clients,
130
+ clientRoots: retention.roots,
131
+ incrementalTextEntries: retention.entries,
132
+ incrementalTextBytes: retention.bytes,
133
+ };
134
+ })(),
124
135
  reviewGraph,
125
136
  wordIndex: wordIndex
126
137
  ? {
127
138
  docs: wordIndex.docLengths.size,
139
+ fileTable: wordIndex.fileTable.size,
128
140
  postings: wordIndex.postings.size,
141
+ // O(distinct tokens), reading only `.length`/`.byteLength` per
142
+ // list — no posting ELEMENT is touched, so this stays inside the
143
+ // module docstring’s bounded-read constraint.
144
+ postingEntries: countPostingEntries(wordIndex),
145
+ residentBytes: estimateWordIndexStoreBytes(wordIndex),
129
146
  forwardEntries: wordIndex.forward?.size ?? 0,
130
147
  }
131
148
  : null,
132
149
  treeSitter,
133
- dispatchCaches,
150
+ dispatchCaches: {
151
+ ...dispatchCaches,
152
+ // Three isolated-store runs measured 320 bytes per production entry.
153
+ // Keep this O(1); the cache key and Map bookkeeping are included.
154
+ estimatedBytes: dispatchCaches.recentlyCleanNeighborCacheSize * 320,
155
+ },
134
156
  };
135
157
  }
136
158
  /** Assemble one full sample. `mem` is injectable for tests; defaults to a
@@ -0,0 +1,30 @@
1
+ /** Session-local attribution captured before a stale message_end can drain. */
2
+ let lastStableSessionId;
3
+ let previousSessionId;
4
+ /** Remember the stable id used by a live message_end's own cache row. */
5
+ export function noteLiveMessageEndSessionId(sessionId) {
6
+ lastStableSessionId = sessionId;
7
+ }
8
+ /** Return the newest usable id for a stale message_end attribution. */
9
+ export function getLastLiveMessageEndSessionId() {
10
+ return lastStableSessionId ?? previousSessionId;
11
+ }
12
+ /**
13
+ * Rotate the attribution anchor at the real session boundary. A queued stale
14
+ * message_end from the session being replaced may drain after this boundary,
15
+ * so retain exactly one prior anchor while making room for the replacement.
16
+ */
17
+ export function rotateMessageEndAttribution() {
18
+ // Keep the prior anchor when no live message_end arrived this session:
19
+ // two back-to-back boundaries (reload/resume re-announcing) must not
20
+ // erase the only id a still-queued stale drain can use (#1956 R10).
21
+ if (lastStableSessionId !== undefined) {
22
+ previousSessionId = lastStableSessionId;
23
+ }
24
+ lastStableSessionId = undefined;
25
+ }
26
+ /** Full reset seam used by the session-state registry and test isolation. */
27
+ export function resetMessageEndAttribution() {
28
+ lastStableSessionId = undefined;
29
+ previousSessionId = undefined;
30
+ }
@@ -34,14 +34,31 @@ function isInsideGitRepo(startDir) {
34
34
  /**
35
35
  * Get current git commit hash (short)
36
36
  */
37
- function getCurrentCommit() {
38
- if (!isInsideGitRepo(process.cwd())) {
37
+ function getCurrentCommit(startDir) {
38
+ const repoStartDir = path.resolve(startDir);
39
+ if (!isInsideGitRepo(repoStartDir)) {
39
40
  return "unknown";
40
41
  }
42
+ let spawnDir = repoStartDir;
43
+ while (!fs.existsSync(spawnDir)) {
44
+ const parent = path.dirname(spawnDir);
45
+ if (parent === spawnDir)
46
+ return "unknown";
47
+ spawnDir = parent;
48
+ }
41
49
  try {
50
+ // #2095: execSync inherits the child's stderr to THIS process by
51
+ // default (only stdout is captured for the return value), so a
52
+ // failing `git rev-parse` prints its raw "fatal: ..." line straight
53
+ // into the pi TUI — the catch below only ever sees the thrown
54
+ // non-zero-exit error, never the already-inherited stream. The
55
+ // explicit stdio array pipes stderr instead, so a failure is fully
56
+ // contained: silent here, same as the "unknown" fallback it feeds.
42
57
  return execSync("git rev-parse --short HEAD", {
43
58
  encoding: "utf-8",
44
59
  timeout: 5000,
60
+ stdio: ["ignore", "pipe", "ignore"],
61
+ cwd: spawnDir,
45
62
  }).trim();
46
63
  }
47
64
  catch {
@@ -99,7 +116,7 @@ export function captureSnapshot(filePath, metrics) {
99
116
  pendingHistory = loadHistory();
100
117
  }
101
118
  const relativePath = path.relative(process.cwd(), filePath);
102
- const commit = getCurrentCommit();
119
+ const commit = getCurrentCommit(path.dirname(filePath));
103
120
  const snapshot = {
104
121
  commit,
105
122
  timestamp: new Date().toISOString(),
@@ -152,7 +169,7 @@ export function captureSnapshots(files) {
152
169
  const history = loadHistory();
153
170
  for (const file of files) {
154
171
  const relativePath = path.relative(process.cwd(), file.filePath);
155
- const commit = getCurrentCommit();
172
+ const commit = getCurrentCommit(path.dirname(file.filePath));
156
173
  const snapshot = {
157
174
  commit,
158
175
  timestamp: new Date().toISOString(),