@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
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { compareOrdinal } from "../../string-utils.js";
2
3
  function resolveCyclePath(cwd, file) {
3
4
  return path.isAbsolute(file) ? file : path.resolve(cwd, file);
4
5
  }
@@ -36,7 +37,7 @@ export function circularDepsToProjectDiagnostics(cwd, circular) {
36
37
  for (const dep of circular) {
37
38
  const key = cycleMembers(dep)
38
39
  .map((f) => resolveCyclePath(cwd, f))
39
- .sort((a, b) => a.localeCompare(b))
40
+ .sort(compareOrdinal)
40
41
  .join("|");
41
42
  if (seen.has(key))
42
43
  continue;
@@ -5,6 +5,7 @@ import { evaluateRules } from "../dispatch/fact-rule-runner.js";
5
5
  import { runProviders } from "../dispatch/fact-runner.js";
6
6
  import { FactStore } from "../dispatch/fact-store.js";
7
7
  import { canHandle as astGrepCanHandle, getLang as astGrepGetLang, evaluateAstGrepRules, loadSg, } from "../dispatch/runners/ast-grep-napi.js";
8
+ import { detectFileKind } from "../file-kinds.js";
8
9
  import { isTestFile } from "../file-utils.js";
9
10
  import { isAtOrAboveHomeDir } from "../path-utils.js";
10
11
  import { getProjectDiagnosticsScannerMaxFiles } from "../project-scale.js";
@@ -100,7 +101,9 @@ async function scanFileMajorRules(cwd, files, signal) {
100
101
  const queryMap = treeSitterReady
101
102
  ? await queryLoader.loadQueries(cwd)
102
103
  : undefined;
103
- const facts = new FactStore();
104
+ // Subject labels this store's capacity-eviction telemetry distinctly from
105
+ // the other five production FactStore instances (#2243 review round 3, F1).
106
+ const facts = new FactStore("project-diagnostics-scanner");
104
107
  const pi = { getFlag: () => undefined };
105
108
  const treeSitter = [];
106
109
  const factRules = [];
@@ -141,7 +144,7 @@ async function scanFileMajorRules(cwd, files, signal) {
141
144
  }
142
145
  };
143
146
  const scanFactRulesFile = async (filePath, content) => {
144
- facts.clearFileFactsFor(filePath);
147
+ facts.dropFileFacts(filePath);
145
148
  // Seed the exact bytes already supplied to tree-sitter so the file-content
146
149
  // provider is skipped by runProviders.
147
150
  facts.setFileFact(filePath, "file.content", content);
@@ -160,7 +163,7 @@ async function scanFileMajorRules(cwd, files, signal) {
160
163
  const startedAt = Date.now();
161
164
  try {
162
165
  const rootNode = lang.parse(content).root();
163
- const fileDiagnostics = evaluateAstGrepRules(filePath, rootNode, cwd, "jsts", {
166
+ const fileDiagnostics = evaluateAstGrepRules(filePath, rootNode, cwd, detectFileKind(filePath), {
164
167
  maxMatchesPerRule: AST_GREP_SCAN_MAX_MATCHES_PER_RULE,
165
168
  maxTotalDiagnostics: AST_GREP_SCAN_MAX_DIAGNOSTICS_PER_FILE,
166
169
  });
@@ -264,7 +267,10 @@ async function scanFileMajorRules(cwd, files, signal) {
264
267
  finally {
265
268
  // This store belongs to the scan, and every consumer of this file's
266
269
  // content and derived facts has completed by the end of the iteration.
267
- facts.clearFileFactsFor(filePath);
270
+ // Drop without pinning: the scan is sequential and owns this store, so
271
+ // there is no concurrent walk to guard against, and pinning every
272
+ // scanned file would exempt it from the capacity cap (#2243).
273
+ facts.dropFileFacts(filePath);
268
274
  }
269
275
  }
270
276
  if (wasmAborted) {
@@ -533,11 +533,22 @@ export function loadProjectSnapshotExportsAndRules(cwd) {
533
533
  const body = resolveSnapshotBodyPath(cwd);
534
534
  const authoritative = authoritativeSnapshots.get(key);
535
535
  if (authoritative) {
536
- const diskMtime = body ? body.mtimeMs : Number.NEGATIVE_INFINITY;
537
- if (diskMtime <= authoritative.knownMtime) {
536
+ // `body === null` means nothing is on disk — that is never "the body
537
+ // changed size", so it must not be read as superseding our own write.
538
+ // See the matching comment in loadProjectSnapshotInternal.
539
+ const notSuperseded = body === null ||
540
+ (body.mtimeMs <= authoritative.knownMtime &&
541
+ body.size === authoritative.knownSize);
542
+ if (notSuperseded) {
538
543
  const { version, seq, cachedExports, projectRulesScan } = authoritative.snapshot;
539
544
  return { version, seq, cachedExports, projectRulesScan };
540
545
  }
546
+ // On mismatch, unlike the full loader, this narrow loader neither
547
+ // deletes the entry nor touches its idle timer (it never calls
548
+ // touchAuthoritativeSnapshot even on a hit). The stale entry is safe to
549
+ // leave in place: nothing here re-arms its eviction, so it is still
550
+ // bounded by whatever idle window the full loader (or the original
551
+ // write) last set, not left to live indefinitely.
541
552
  }
542
553
  if (!body)
543
554
  return null;
@@ -547,18 +558,22 @@ function loadProjectSnapshotInternal(cwd, requireWordIndex) {
547
558
  const key = normalizeMapKey(cwd);
548
559
  const body = resolveSnapshotBodyPath(cwd);
549
560
  // Authoritative in-process write wins while our own (possibly still
550
- // in-flight) write has not been superseded on disk by a newer external
551
- // mtime. `body === null` means nothing is on disk yet — our just-scheduled
552
- // write is the only truth, so serve it.
561
+ // in-flight) write has not been superseded on disk by a newer external mtime
562
+ // or a different-size body in the same/coarser bucket. `body === null` means
563
+ // nothing is on disk — either our just-scheduled write hasn't landed yet, or
564
+ // something removed the body after we wrote it (e.g. a cleared cache dir).
565
+ // Neither case is "the body changed size", so serve the in-process object.
553
566
  const authoritative = authoritativeSnapshots.get(key);
554
567
  if (authoritative) {
555
- const diskMtime = body ? body.mtimeMs : Number.NEGATIVE_INFINITY;
556
- if (diskMtime <= authoritative.knownMtime) {
568
+ const notSuperseded = body === null ||
569
+ (body.mtimeMs <= authoritative.knownMtime &&
570
+ body.size === authoritative.knownSize);
571
+ if (notSuperseded) {
557
572
  touchAuthoritativeSnapshot(key, authoritative);
558
573
  return authoritative.snapshot;
559
574
  }
560
- // An external writer moved past our write — honor disk and stop
561
- // serving the now-stale in-memory object.
575
+ // An external writer changed the body beyond our metadata stamp — honor
576
+ // disk and stop serving the now-stale in-memory object.
562
577
  deleteAuthoritativeSnapshot(key);
563
578
  }
564
579
  if (!body) {
@@ -907,10 +922,12 @@ function reconcileAuthoritativeAfterWrite(pending, rawBytes) {
907
922
  return;
908
923
  }
909
924
  try {
910
- entry.knownMtime = fs.statSync(pending.gzPath).mtimeMs;
925
+ const stat = fs.statSync(pending.gzPath);
926
+ entry.knownMtime = stat.mtimeMs;
927
+ entry.knownSize = stat.size;
911
928
  }
912
929
  catch {
913
- // If we can't stat our own write, leave knownMtime as-is; the worst case
930
+ // If we can't stat our own write, leave the metadata as-is; the worst case
914
931
  // is one extra disk re-parse on the next load.
915
932
  }
916
933
  }
@@ -1368,11 +1385,16 @@ export function saveProjectSnapshot(cwd, snapshot) {
1368
1385
  // legacy body to a merge-consumer, silently dropping this snapshot's fields.
1369
1386
  const priorBody = resolveSnapshotBodyPath(cwd);
1370
1387
  const knownMtime = priorBody ? priorBody.mtimeMs : Number.NEGATIVE_INFINITY;
1388
+ const knownSize = priorBody ? priorBody.size : Number.NEGATIVE_INFINITY;
1371
1389
  const authoritativeEntry = {
1372
1390
  snapshot,
1373
1391
  knownMtime,
1392
+ knownSize,
1374
1393
  lastUsedAt: Date.now(),
1375
1394
  };
1395
+ const previousAuthoritativeEntry = authoritativeSnapshots.get(key);
1396
+ if (previousAuthoritativeEntry)
1397
+ clearAuthoritativeSnapshotTimer(previousAuthoritativeEntry);
1376
1398
  authoritativeSnapshots.set(key, authoritativeEntry);
1377
1399
  scheduleAuthoritativeSnapshotEviction(key, authoritativeEntry);
1378
1400
  enforceAuthoritativeSnapshotCap();
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import { isTestMode } from "./env-utils.js";
3
3
  import { getGlobalPiLensDir } from "./file-utils.js";
4
4
  import { createNdjsonLogger } from "./ndjson-logger.js";
5
+ import { normalizeFilePath } from "./path-utils.js";
5
6
  const READ_GUARD_LOG_DIR = getGlobalPiLensDir();
6
7
  const READ_GUARD_LOG_FILE = path.join(READ_GUARD_LOG_DIR, "read-guard.log");
7
8
  const READ_GUARD_LOG_BACKUP_FILE = path.join(READ_GUARD_LOG_DIR, "read-guard.log.1");
@@ -140,7 +141,18 @@ export function shouldLogEvent(event) {
140
141
  // READ_GUARD_MAX_RECORDS_PER_FILE overflow triggers one), so this trim
141
142
  // record bypasses the per-read verbosity gate — a live eviction
142
143
  // regression must be visible without PI_LENS_READ_GUARD_VERBOSE=1.
143
- event === "read_cap_trimmed");
144
+ event === "read_cap_trimmed" ||
145
+ // #1918: the record-cap trim's population siblings — whole-file
146
+ // eviction under real pressure (file cap or an external delete) and
147
+ // the per-file edits-cap trim. Idle-timeout eviction is deliberately
148
+ // EXCLUDED from this event (see read-guard.ts's evictFile doc
149
+ // comment) — it's routine housekeeping, not a fault, and its
150
+ // per-file cardinality is unbounded in a healthy session. Same
151
+ // rarity argument as #1913 for the reasons that DO fire: each is
152
+ // rising-edge gated once per file per session, so always-on
153
+ // visibility costs nothing.
154
+ event === "read_file_evicted" ||
155
+ event === "edits_cap_trimmed");
144
156
  }
145
157
  const MAX_TELEMETRY_STRING = 512;
146
158
  const MAX_TELEMETRY_KEYS = 64;
@@ -205,7 +217,17 @@ export function logReadGuardEvent(entry) {
205
217
  ? bounded.value
206
218
  : undefined;
207
219
  const { correlationId: _correlationId, ...logEntry } = entry;
208
- writer.log({ ts: new Date().toISOString(), ...logEntry, metadata });
220
+ // #2219 (the #2141 class): `filePath` reaches here from
221
+ // `runtime-tool-result.ts`'s raw `path.resolve()`/`path.isAbsolute()`
222
+ // arithmetic (never `normalizeFilePath`-passed), always a genuine file —
223
+ // this field carries no non-path sentinel. Normalize once at this single
224
+ // emit seam, matching `review-graph-logger.ts`'s `logReviewGraph`.
225
+ writer.log({
226
+ ts: new Date().toISOString(),
227
+ ...logEntry,
228
+ filePath: normalizeFilePath(logEntry.filePath),
229
+ metadata,
230
+ });
209
231
  }
210
232
  export function getReadGuardLogPath() {
211
233
  return READ_GUARD_LOG_FILE;
@@ -318,7 +318,33 @@ export class ReadGuard {
318
318
  clearTimeout(timer);
319
319
  this.fileIdleTimers.delete(filePath);
320
320
  }
321
- evictFile(filePath) {
321
+ /**
322
+ * #1918: `enforceRecordCapForFile` (the per-file record cap) is the only
323
+ * `read-guard.ts` eviction path #1913 gave a bounded record. This whole-file
324
+ * evictor has four call sites — `enforceFileCap`'s consumed-file cap, its
325
+ * unconsumed-file cap, `touchFile`'s idle timer, and `forgetPath`'s
326
+ * external-delete cleanup — and none of them left any trace. A live
327
+ * regression in any of the four (e.g. a cap set too low) dropped a file's
328
+ * read/edit history with nothing in `~/.pi-lens/read-guard.log` to show it
329
+ * happened.
330
+ *
331
+ * Follows #1913's aggregate-after-first pattern for the three PRESSURE
332
+ * reasons (cap exhaustion, external delete): `incrementDegradationCount`
333
+ * keeps the exact per-file eviction tally (and its own power-of-two
334
+ * milestone rows in latency.log) while the read-guard.log line fires only
335
+ * on the rising edge, so a file evicted repeatedly across a session can't
336
+ * flood the log.
337
+ *
338
+ * `idle-timeout` is deliberately excluded from both the ledger tally and
339
+ * the log line (#1918 review F2): idle eviction is designed housekeeping,
340
+ * not a fault signal — a read-only session that idles out N files is
341
+ * healthy, ordinary behavior, and N is unbounded (every distinct file path
342
+ * touched that session is its own ledger subject). Recording it here would
343
+ * grow the ledger's tally map without bound and spam `pilens_health` in
344
+ * every healthy session, unlike the other three reasons, which are rare by
345
+ * construction (only real pressure or an explicit delete reaches them).
346
+ */
347
+ evictFile(filePath, reason) {
322
348
  this.clearFileTimer(filePath);
323
349
  this.reads.delete(filePath);
324
350
  this.edits.delete(filePath);
@@ -331,6 +357,21 @@ export class ReadGuard {
331
357
  if (stored === filePath)
332
358
  this.knownPathIndex.delete(syntacticKey);
333
359
  }
360
+ if (reason === "idle-timeout")
361
+ return;
362
+ const isRisingEdge = incrementDegradationCount({
363
+ kind: "read-guard-file-evicted",
364
+ subject: filePath,
365
+ reason: `evicted (${reason})`,
366
+ });
367
+ if (isRisingEdge) {
368
+ logReadGuardEvent({
369
+ event: "read_file_evicted",
370
+ sessionId: this.sessionId,
371
+ filePath,
372
+ metadata: { reason },
373
+ });
374
+ }
334
375
  }
335
376
  touchFile(filePath) {
336
377
  const now = Date.now();
@@ -348,7 +389,12 @@ export class ReadGuard {
348
389
  // eviction. Only consumed reads and rebuildable edit history may expire.
349
390
  if (this.reads.has(filePath) && !this.consumedReadFiles.has(filePath))
350
391
  return;
351
- this.evictFile(filePath);
392
+ // #1918 review F2: this reason is intentionally silent inside
393
+ // evictFile — idle eviction is routine housekeeping, not a fault, and
394
+ // every distinct file idling out this session is its own ledger
395
+ // subject, so recording it would grow unbounded and flood
396
+ // pilens_health on every healthy session. See evictFile's doc comment.
397
+ this.evictFile(filePath, "idle-timeout");
352
398
  }, this.idleEvictMs());
353
399
  timer.unref?.();
354
400
  this.fileIdleTimers.set(filePath, timer);
@@ -360,7 +406,7 @@ export class ReadGuard {
360
406
  .sort((a, b) => (this.fileLastUsed.get(a) ?? 0) - (this.fileLastUsed.get(b) ?? 0))[0];
361
407
  if (!victim)
362
408
  break;
363
- this.evictFile(victim);
409
+ this.evictFile(victim, "file-cap-consumed");
364
410
  }
365
411
  while (this.reads.size > READ_GUARD_MAX_UNCONSUMED_FILES) {
366
412
  const victim = [...this.reads.keys()]
@@ -370,7 +416,7 @@ export class ReadGuard {
370
416
  break;
371
417
  // This is a normal read miss: a later edit must require a fresh read,
372
418
  // never silently allow and never become a permanent hard-block.
373
- this.evictFile(victim);
419
+ this.evictFile(victim, "file-cap-unconsumed");
374
420
  }
375
421
  }
376
422
  // --- Public API ---
@@ -575,7 +621,7 @@ export class ReadGuard {
575
621
  });
576
622
  return verdict;
577
623
  }
578
- const verdict = this.blockOrWarn("zero-read", `🔄 RETRYABLE — Edit without read\n\nYou are trying to edit \`${filePath}\` but have not read it in this conversation.\n\nRead the file first, then retry the edit: \`read path="${filePath}"\``, undefined, effectiveMode);
624
+ const verdict = this.blockOrWarn("zero-read", `🔄 RETRYABLE — Edit without read: you have not read \`${filePath}\` in this conversation. Read it first, then retry: \`read path="${filePath}"\`.`, undefined, effectiveMode);
579
625
  this.recordVerdict(filePath, "edit", touchedLines, verdict, {
580
626
  reasonKind: "zero_read",
581
627
  });
@@ -791,7 +837,7 @@ export class ReadGuard {
791
837
  */
792
838
  forgetPath(filePath) {
793
839
  const stored = this.knownPathIndex.get(normalizeEphemeralMapKey(filePath));
794
- this.evictFile(stored ?? this.key(filePath));
840
+ this.evictFile(stored ?? this.key(filePath), "external-delete");
795
841
  }
796
842
  /**
797
843
  * Mark a file as pending creation (Write tool to a non-existing file).
@@ -1358,7 +1404,28 @@ export class ReadGuard {
1358
1404
  timestamp: Date.now(),
1359
1405
  });
1360
1406
  if (arr.length > READ_GUARD_MAX_EDITS_PER_FILE) {
1361
- arr.splice(0, arr.length - READ_GUARD_MAX_EDITS_PER_FILE);
1407
+ const trimmedCount = arr.length - READ_GUARD_MAX_EDITS_PER_FILE;
1408
+ arr.splice(0, trimmedCount);
1409
+ // #1918: the #282-289 doc comment argues this trim is inert in
1410
+ // practice (every consumer reads only the last record or a
1411
+ // bounded relocation window), but an argument is not a record —
1412
+ // if that assumption ever breaks, this is the only signal. Same
1413
+ // aggregate-after-first shape as `read_cap_trimmed` (#1913):
1414
+ // `incrementDegradationCount` keeps the exact per-file tally,
1415
+ // the read-guard.log line fires once on the rising edge.
1416
+ const isRisingEdge = incrementDegradationCount({
1417
+ kind: "read-guard-edits-cap-trim",
1418
+ subject: filePath,
1419
+ reason: `trimmed ${trimmedCount} edit record(s) past cap`,
1420
+ });
1421
+ if (isRisingEdge) {
1422
+ logReadGuardEvent({
1423
+ event: "edits_cap_trimmed",
1424
+ sessionId: this.sessionId,
1425
+ filePath,
1426
+ metadata: { trimmedCount, cappedLength: arr.length },
1427
+ });
1428
+ }
1362
1429
  }
1363
1430
  this.edits.set(filePath, arr);
1364
1431
  }
@@ -56,6 +56,7 @@ export function isRecentTouchesEnabled() {
56
56
  export function _resetRecentTouchesForTests() {
57
57
  _enabledCache = undefined;
58
58
  _lastSeenMtimeMs = undefined;
59
+ _lastSeenSizeBytes = undefined;
59
60
  _lastConsumedCursor.clear();
60
61
  }
61
62
  // --- Read (never throws — missing file, corrupt JSON, or wrong shape ⇒ empty) ---
@@ -170,10 +171,12 @@ export async function readCrossProcessTouchesForSessionStart(args) {
170
171
  }
171
172
  }
172
173
  // --- Consumer: parent at turn_start (mtime-gated hot path) ---
173
- // Module state: the last mtime we've already consumed, and a per-cwd cursor
174
- // (max ts already surfaced) so repeated reads of an unchanged-since-last-
175
- // check file never re-emit the same entries into the accumulator twice.
174
+ // Module state: the last mtime (+ size, #2300) we've already consumed, and a
175
+ // per-cwd cursor (max ts already surfaced) so repeated reads of an
176
+ // unchanged-since-last-check file never re-emit the same entries into the
177
+ // accumulator twice.
176
178
  let _lastSeenMtimeMs;
179
+ let _lastSeenSizeBytes;
177
180
  const _lastConsumedCursor = new Map();
178
181
  /**
179
182
  * Parent-at-turn_start consumer. Hot path: ONE `fs.stat` per call; when the
@@ -198,17 +201,26 @@ export async function readCrossProcessTouchesForTurnStart(args) {
198
201
  try {
199
202
  const target = recentTouchesPath(args.cwd);
200
203
  let mtimeMs;
204
+ let sizeBytes;
201
205
  try {
202
- mtimeMs = (await fs.promises.stat(target)).mtimeMs;
206
+ const stat = await fs.promises.stat(target);
207
+ mtimeMs = stat.mtimeMs;
208
+ sizeBytes = stat.size;
203
209
  }
204
210
  catch {
205
211
  // No record file yet (ENOENT) or inaccessible (EACCES) — nothing to
206
212
  // report; do not disturb the mtime watermark.
207
213
  return [];
208
214
  }
209
- if (_lastSeenMtimeMs === mtimeMs)
215
+ // #2300: size alongside mtime — a same-mtime-bucket rewrite (a sibling
216
+ // process's write landing in the same coarse-granularity mtime tick)
217
+ // changes the file's length, so mtime equality alone would wrongly
218
+ // short-circuit to "nothing new" and drop its entries.
219
+ if (_lastSeenMtimeMs === mtimeMs && _lastSeenSizeBytes === sizeBytes) {
210
220
  return [];
221
+ }
211
222
  _lastSeenMtimeMs = mtimeMs;
223
+ _lastSeenSizeBytes = sizeBytes;
212
224
  const selfPid = args.selfPid ?? process.pid;
213
225
  const now = args.now ?? Date.now();
214
226
  const freshnessMs = args.freshnessMs ?? RECENT_TOUCHES_FRESHNESS_MS;