@herbertgao/pi-extensions 2026.8.11 → 2026.8.13

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 (476) hide show
  1. package/README.md +23 -10
  2. package/{node_modules/@herbertgao/pi-mermaid-open/LICENSE → THIRD_PARTY_NOTICES.md} +19 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +15 -0
  4. package/node_modules/@czottmann/pi-automode/README.md +5 -3
  5. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +2 -0
  6. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
  7. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +1 -0
  8. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +4 -4
  9. package/node_modules/@czottmann/pi-automode/docs/configuration.md +20 -4
  10. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +1 -1
  11. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/bash.ts +692 -0
  12. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +181 -3
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +4 -1
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +143 -36
  15. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +103 -148
  16. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +32 -6
  17. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +344 -22
  18. package/node_modules/@czottmann/pi-automode/extensions/auto-mode.ts +1 -0
  19. package/node_modules/@czottmann/pi-automode/package.json +4 -1
  20. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -1
  21. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -1
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +4 -0
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +21 -1
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +1 -1
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +19 -19
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +4 -2
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +2 -1
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +10 -2
  29. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  30. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +10 -0
  31. package/node_modules/@herbertgao/pi-subagents/README.md +21 -15
  32. package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
  33. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +11 -8
  34. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +184 -106
  35. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +31 -11
  36. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +9 -1
  37. package/node_modules/@herbertgao/pi-subagents/src/index.ts +156 -31
  38. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +17 -0
  39. package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +14 -0
  40. package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +8 -0
  41. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +32 -1
  42. package/node_modules/@herbertgao/pi-subagents/src/types.ts +17 -2
  43. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +23 -2
  44. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +259 -24
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  46. package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +376 -0
  47. package/node_modules/@luxusai/pi-hindsight/README.md +92 -0
  48. package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +56 -0
  49. package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +169 -0
  50. package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +107 -0
  51. package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +89 -0
  52. package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +189 -0
  53. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
  54. package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
  55. package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +54 -0
  56. package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +75 -0
  57. package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +300 -0
  58. package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +158 -0
  59. package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +164 -0
  60. package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +139 -0
  61. package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +74 -0
  62. package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +261 -0
  63. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +151 -0
  64. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +18 -0
  65. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +46 -0
  66. package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +383 -0
  67. package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +240 -0
  68. package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +176 -0
  69. package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +178 -0
  70. package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +41 -0
  71. package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +386 -0
  72. package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +39 -0
  73. package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +34 -0
  74. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +140 -0
  75. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +61 -0
  76. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +925 -0
  77. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +247 -0
  78. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +547 -0
  79. package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +480 -0
  80. package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +182 -0
  81. package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +86 -0
  82. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +1010 -0
  83. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +175 -0
  84. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +425 -0
  85. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +210 -0
  86. package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +817 -0
  87. package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +25 -0
  88. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +319 -0
  89. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +196 -0
  90. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +189 -0
  91. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +65 -0
  92. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +260 -0
  93. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +244 -0
  94. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +59 -0
  95. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +82 -0
  96. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +45 -0
  97. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +331 -0
  98. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +354 -0
  99. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +66 -0
  100. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +132 -0
  101. package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +228 -0
  102. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +74 -0
  103. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +16 -0
  104. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +648 -0
  105. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +105 -0
  106. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +68 -0
  107. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +71 -0
  108. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +13 -0
  109. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +162 -0
  110. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +131 -0
  111. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +104 -0
  112. package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +37 -0
  113. package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +893 -0
  114. package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +35 -0
  115. package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +177 -0
  116. package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +7 -0
  117. package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +21 -0
  118. package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +112 -0
  119. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +111 -0
  120. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +214 -0
  121. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +70 -0
  122. package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +392 -0
  123. package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +45 -0
  124. package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +9 -0
  125. package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +860 -0
  126. package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +105 -0
  127. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +189 -0
  128. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +299 -0
  129. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +201 -0
  130. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +47 -0
  131. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +239 -0
  132. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +50 -0
  133. package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +259 -0
  134. package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +77 -0
  135. package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +534 -0
  136. package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +319 -0
  137. package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +325 -0
  138. package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +42 -0
  139. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +244 -0
  140. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +56 -0
  141. package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +50 -0
  142. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +167 -0
  143. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +207 -0
  144. package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +120 -0
  145. package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +8 -0
  146. package/node_modules/@luxusai/pi-hindsight/package.json +118 -0
  147. package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +75 -0
  148. package/node_modules/@narumitw/pi-btw/dist/index.ts +143 -46
  149. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  150. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  151. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +136 -22
  152. package/node_modules/{@herbertgao → @tifan}/pi-copy-response/README.md +2 -4
  153. package/node_modules/{@herbertgao → @tifan}/pi-copy-response/package.json +5 -15
  154. package/node_modules/{@herbertgao → @tifan}/pi-handoff/README.md +4 -6
  155. package/node_modules/@tifan/pi-handoff/package.json +43 -0
  156. package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/index.ts +1 -1
  157. package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/README.md +2 -4
  158. package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/package.json +5 -15
  159. package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/README.md +5 -7
  160. package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/package.json +5 -19
  161. package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/README.md +4 -6
  162. package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/package.json +6 -16
  163. package/node_modules/{@herbertgao → @tifan}/pi-preferred-thinking/src/index.ts +1 -6
  164. package/node_modules/{@herbertgao → @tifan}/pi-recap/README.md +4 -6
  165. package/node_modules/@tifan/pi-recap/package.json +42 -0
  166. package/node_modules/{@herbertgao → @tifan}/pi-recap/src/models.ts +1 -6
  167. package/node_modules/{@herbertgao → @tifan}/pi-rename/README.md +13 -8
  168. package/node_modules/@tifan/pi-rename/package.json +47 -0
  169. package/node_modules/@tifan/pi-rename/src/herdr-label.ts +4 -0
  170. package/node_modules/{@herbertgao → @tifan}/pi-rename/src/index.ts +4 -16
  171. package/node_modules/{@herbertgao → @tifan}/pi-rename/src/models.ts +1 -6
  172. package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/README.md +2 -4
  173. package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/package.json +5 -15
  174. package/node_modules/pi-lens/CHANGELOG.md +96 -1
  175. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -1
  176. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +14 -5
  177. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +48 -18
  178. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -3
  179. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +16 -4
  180. package/node_modules/pi-lens/dist/clients/bash-file-access.js +3 -1
  181. package/node_modules/pi-lens/dist/clients/biome-client.js +1 -3
  182. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +21 -8
  183. package/node_modules/pi-lens/dist/clients/bounded-cache.js +24 -8
  184. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +4 -3
  185. package/node_modules/pi-lens/dist/clients/cache-manager.js +6 -7
  186. package/node_modules/pi-lens/dist/clients/cache-observability.js +461 -66
  187. package/node_modules/pi-lens/dist/clients/call-graph.js +238 -63
  188. package/node_modules/pi-lens/dist/clients/codebase-model.js +13 -5
  189. package/node_modules/pi-lens/dist/clients/complexity-client.js +1 -3
  190. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -8
  191. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +1 -2
  192. package/node_modules/pi-lens/dist/clients/deadline-utils.js +3 -1
  193. package/node_modules/pi-lens/dist/clients/debug-heap.js +8 -1
  194. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +1 -1
  195. package/node_modules/pi-lens/dist/clients/dependency-checker.js +11 -7
  196. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -2
  197. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +5 -1
  198. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +2 -3
  199. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +13 -1
  200. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +3 -1
  201. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +2 -1
  202. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +9 -4
  204. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +3 -1
  205. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +6 -2
  206. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +31 -7
  207. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +5 -2
  208. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +10 -14
  209. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +5 -1
  210. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +1 -1
  211. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +3 -1
  212. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +25 -6
  213. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +1 -1
  214. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +1 -1
  215. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +3 -1
  216. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +13 -8
  217. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +4 -1
  218. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +25 -10
  219. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +1 -1
  220. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +1 -1
  221. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +1 -1
  222. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +28 -4
  223. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +6 -8
  224. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +212 -11
  225. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +1 -1
  226. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +8 -4
  227. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -6
  228. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +23 -14
  229. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -1
  230. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +7 -9
  231. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +7 -7
  233. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +6 -8
  234. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +13 -8
  235. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +13 -8
  236. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +17 -3
  237. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +7 -1
  238. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +24 -1
  239. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +35 -0
  240. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +1 -3
  241. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +6 -8
  242. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +1 -1
  243. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +2 -1
  244. package/node_modules/pi-lens/dist/clients/dispatch/types.js +6 -1
  245. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +6 -2
  246. package/node_modules/pi-lens/dist/clients/extension-log.js +5 -3
  247. package/node_modules/pi-lens/dist/clients/feature-hints.js +8 -1
  248. package/node_modules/pi-lens/dist/clients/file-kinds.js +34 -129
  249. package/node_modules/pi-lens/dist/clients/file-utils.js +17 -1
  250. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +57 -2
  251. package/node_modules/pi-lens/dist/clients/formatters.js +144 -27
  252. package/node_modules/pi-lens/dist/clients/freshness.js +38 -0
  253. package/node_modules/pi-lens/dist/clients/git-guard.js +43 -13
  254. package/node_modules/pi-lens/dist/clients/go-client.js +1 -3
  255. package/node_modules/pi-lens/dist/clients/grammar-source.js +1 -1
  256. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +30 -4
  257. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +1 -3
  258. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +4 -2
  259. package/node_modules/pi-lens/dist/clients/installer/index.js +86 -77
  260. package/node_modules/pi-lens/dist/clients/instance-reaper.js +5 -2
  261. package/node_modules/pi-lens/dist/clients/instance-registry.js +4 -2
  262. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  263. package/node_modules/pi-lens/dist/clients/knip-client.js +29 -15
  264. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -1
  265. package/node_modules/pi-lens/dist/clients/latency-logger.js +13 -2
  266. package/node_modules/pi-lens/dist/clients/lens-config.js +1 -1
  267. package/node_modules/pi-lens/dist/clients/lens-map.js +8 -3
  268. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +1 -1
  269. package/node_modules/pi-lens/dist/clients/lsp/config.js +4 -6
  270. package/node_modules/pi-lens/dist/clients/lsp/edits.js +119 -32
  271. package/node_modules/pi-lens/dist/clients/lsp/index.js +94 -35
  272. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +2 -1
  273. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +4 -1
  274. package/node_modules/pi-lens/dist/clients/lsp/launch.js +11 -3
  275. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +152 -0
  276. package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -7
  277. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +10 -2
  278. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +9 -4
  279. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -3
  280. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +10 -20
  281. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +1 -1
  282. package/node_modules/pi-lens/dist/clients/memory-sampler.js +73 -7
  283. package/node_modules/pi-lens/dist/clients/metrics-client.js +1 -3
  284. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +8 -3
  285. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +7 -1
  286. package/node_modules/pi-lens/dist/clients/module-report.js +14 -17
  287. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +3 -5
  288. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +220 -0
  289. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -1
  290. package/node_modules/pi-lens/dist/clients/pipeline.js +57 -9
  291. package/node_modules/pi-lens/dist/clients/project-conventions.js +2 -8
  292. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +10 -7
  293. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +2 -1
  294. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +3 -1
  295. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +13 -9
  296. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +3 -1
  297. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -2
  298. package/node_modules/pi-lens/dist/clients/project-lens-config.js +12 -5
  299. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +48 -0
  300. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +15 -1
  301. package/node_modules/pi-lens/dist/clients/project-snapshot.js +472 -66
  302. package/node_modules/pi-lens/dist/clients/read-expansion.js +12 -2
  303. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +5 -4
  304. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +5 -2
  305. package/node_modules/pi-lens/dist/clients/read-guard.js +1 -1
  306. package/node_modules/pi-lens/dist/clients/resource-sampler.js +1 -1
  307. package/node_modules/pi-lens/dist/clients/reverse-deps.js +6 -5
  308. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +42 -17
  309. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +8 -2
  310. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +10 -1
  311. package/node_modules/pi-lens/dist/clients/review-graph/query.js +2 -1
  312. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +4 -2
  313. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +2 -2
  314. package/node_modules/pi-lens/dist/clients/ruff-client.js +6 -5
  315. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +63 -31
  316. package/node_modules/pi-lens/dist/clients/runtime-context.js +12 -4
  317. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +83 -4
  318. package/node_modules/pi-lens/dist/clients/runtime-session.js +14 -0
  319. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +59 -3
  320. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +148 -34
  321. package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -6
  322. package/node_modules/pi-lens/dist/clients/rust-client.js +1 -3
  323. package/node_modules/pi-lens/dist/clients/safe-spawn.js +150 -11
  324. package/node_modules/pi-lens/dist/clients/security-scan-client.js +1 -3
  325. package/node_modules/pi-lens/dist/clients/session-summary.js +3 -1
  326. package/node_modules/pi-lens/dist/clients/sg-runner.js +1 -3
  327. package/node_modules/pi-lens/dist/clients/source-filter.js +10 -6
  328. package/node_modules/pi-lens/dist/clients/source-walker.js +1 -1
  329. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +96 -0
  330. package/node_modules/pi-lens/dist/clients/startup-scan.js +4 -1
  331. package/node_modules/pi-lens/dist/clients/tool-definition.js +7 -0
  332. package/node_modules/pi-lens/dist/clients/tool-policy.js +26 -11
  333. package/node_modules/pi-lens/dist/clients/tool-render.js +2 -1
  334. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +10 -4
  335. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  336. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +12 -5
  337. package/node_modules/pi-lens/dist/clients/warm-attach.js +12 -5
  338. package/node_modules/pi-lens/dist/clients/widget-state.js +27 -11
  339. package/node_modules/pi-lens/dist/clients/word-index.js +3 -1
  340. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -2
  341. package/node_modules/pi-lens/dist/clients/zizmor-config.js +9 -1
  342. package/node_modules/pi-lens/dist/i18n.js +13 -5
  343. package/node_modules/pi-lens/dist/index.js +10797 -8627
  344. package/node_modules/pi-lens/dist/mcp/server.js +3 -1
  345. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +68 -12
  346. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +21 -7
  347. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +164 -35
  348. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +24 -7
  349. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  350. package/node_modules/pi-lens/dist/tools/module-report.js +1 -1
  351. package/node_modules/pi-lens/dist/tools/project-report.js +1 -1
  352. package/node_modules/pi-lens/dist/tools/symbol-search.js +5 -2
  353. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +1 -1
  354. package/node_modules/pi-lens/docs/servercapabilities.md +1 -1
  355. package/node_modules/pi-lens/package.json +160 -156
  356. package/node_modules/pi-lens/rules/typos/_typos.toml +2 -2
  357. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +31 -13
  358. package/node_modules/pi-lens/scripts/download-grammars.js +9 -3
  359. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +30 -9
  360. package/node_modules/pi-mcp-adapter/CHANGELOG.md +47 -0
  361. package/node_modules/pi-mcp-adapter/README.md +42 -13
  362. package/node_modules/pi-mcp-adapter/commands.ts +4 -2
  363. package/node_modules/pi-mcp-adapter/config.ts +45 -5
  364. package/node_modules/pi-mcp-adapter/direct-tools.ts +103 -92
  365. package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +10 -0
  366. package/node_modules/pi-mcp-adapter/dist/agent-dir.js +63 -0
  367. package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +1 -0
  368. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +8 -0
  369. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +356 -0
  370. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +1 -0
  371. package/node_modules/pi-mcp-adapter/dist/config.d.ts +123 -0
  372. package/node_modules/pi-mcp-adapter/dist/config.js +1087 -0
  373. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -0
  374. package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +19 -0
  375. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +319 -0
  376. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -0
  377. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +2 -0
  378. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +156 -0
  379. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +1 -0
  380. package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +1 -0
  381. package/node_modules/pi-mcp-adapter/dist/resource-tools.js +15 -0
  382. package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +1 -0
  383. package/node_modules/pi-mcp-adapter/dist/types.d.ts +564 -0
  384. package/node_modules/pi-mcp-adapter/dist/types.js +287 -0
  385. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -0
  386. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +6 -0
  387. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +37 -0
  388. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +1 -0
  389. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +104 -0
  390. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +59 -0
  391. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +1 -0
  392. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +4 -0
  393. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +27 -0
  394. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +1 -0
  395. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +32 -0
  396. package/node_modules/pi-mcp-adapter/dist/utils.js +338 -0
  397. package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -0
  398. package/node_modules/pi-mcp-adapter/failure-backoff.ts +23 -0
  399. package/node_modules/pi-mcp-adapter/index.ts +204 -82
  400. package/node_modules/pi-mcp-adapter/init.ts +41 -30
  401. package/node_modules/pi-mcp-adapter/lifecycle.ts +16 -8
  402. package/node_modules/pi-mcp-adapter/mcp-code.ts +2 -0
  403. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +66 -16
  404. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +95 -7
  405. package/node_modules/pi-mcp-adapter/mcp-probe.ts +18 -4
  406. package/node_modules/pi-mcp-adapter/mcp-references.ts +368 -0
  407. package/node_modules/pi-mcp-adapter/mcp-status.ts +8 -19
  408. package/node_modules/pi-mcp-adapter/metadata-cache.ts +20 -12
  409. package/node_modules/pi-mcp-adapter/namespace-tools.ts +276 -0
  410. package/node_modules/pi-mcp-adapter/package.json +24 -6
  411. package/node_modules/pi-mcp-adapter/proxy-modes.ts +66 -10
  412. package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -0
  413. package/node_modules/pi-mcp-adapter/server-manager.ts +63 -12
  414. package/node_modules/pi-mcp-adapter/state.ts +1 -3
  415. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +16 -2
  416. package/node_modules/pi-mcp-adapter/types.ts +48 -1
  417. package/node_modules/pi-mcp-adapter/ui-server.ts +3 -19
  418. package/node_modules/pi-mcp-adapter/ui-session.ts +2 -1
  419. package/node_modules/pi-mcp-adapter/utils.ts +21 -19
  420. package/node_modules/pi-web-access/CHANGELOG.md +26 -0
  421. package/node_modules/pi-web-access/README.md +71 -14
  422. package/node_modules/pi-web-access/chrome-cookies.ts +115 -35
  423. package/node_modules/pi-web-access/credential-source.ts +6 -2
  424. package/node_modules/pi-web-access/curator-page.ts +5 -3
  425. package/node_modules/pi-web-access/curator-server.ts +2 -1
  426. package/node_modules/pi-web-access/extract.ts +89 -9
  427. package/node_modules/pi-web-access/fetch-params.ts +13 -0
  428. package/node_modules/pi-web-access/gemini-adc.ts +298 -0
  429. package/node_modules/pi-web-access/gemini-api.ts +59 -15
  430. package/node_modules/pi-web-access/gemini-search.ts +58 -18
  431. package/node_modules/pi-web-access/gemini-url-context.ts +3 -2
  432. package/node_modules/pi-web-access/gemini-web-config.ts +57 -7
  433. package/node_modules/pi-web-access/gemini-web.ts +9 -3
  434. package/node_modules/pi-web-access/github-api.ts +5 -4
  435. package/node_modules/pi-web-access/github-issue-pr.ts +700 -0
  436. package/node_modules/pi-web-access/index.ts +403 -297
  437. package/node_modules/pi-web-access/kimi-search.ts +234 -0
  438. package/node_modules/pi-web-access/openai-search.ts +118 -21
  439. package/node_modules/pi-web-access/package.json +3 -2
  440. package/node_modules/pi-web-access/ssrf-protection.ts +4 -1
  441. package/node_modules/pi-web-access/utils.ts +307 -1
  442. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  443. package/package.json +46 -37
  444. package/node_modules/@herbertgao/pi-copy-response/CHANGELOG.md +0 -49
  445. package/node_modules/@herbertgao/pi-copy-response/LICENSE +0 -22
  446. package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +0 -83
  447. package/node_modules/@herbertgao/pi-handoff/LICENSE +0 -22
  448. package/node_modules/@herbertgao/pi-handoff/package.json +0 -60
  449. package/node_modules/@herbertgao/pi-inline-skills/CHANGELOG.md +0 -57
  450. package/node_modules/@herbertgao/pi-inline-skills/LICENSE +0 -22
  451. package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +0 -39
  452. package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +0 -63
  453. package/node_modules/@herbertgao/pi-preferred-thinking/LICENSE +0 -22
  454. package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +0 -101
  455. package/node_modules/@herbertgao/pi-recap/LICENSE +0 -22
  456. package/node_modules/@herbertgao/pi-recap/package.json +0 -55
  457. package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +0 -49
  458. package/node_modules/@herbertgao/pi-rename/LICENSE +0 -22
  459. package/node_modules/@herbertgao/pi-rename/package.json +0 -63
  460. package/node_modules/@herbertgao/pi-titlebar-spinner/CHANGELOG.md +0 -25
  461. package/node_modules/@herbertgao/pi-titlebar-spinner/LICENSE +0 -22
  462. /package/node_modules/{@herbertgao → @tifan}/pi-copy-response/src/index.ts +0 -0
  463. /package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/complete-text.ts +0 -0
  464. /package/node_modules/{@herbertgao → @tifan}/pi-handoff/src/session-query.ts +0 -0
  465. /package/node_modules/{@herbertgao → @tifan}/pi-inline-skills/src/index.ts +0 -0
  466. /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +0 -0
  467. /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/herdr-plugin/viewer.mjs +0 -0
  468. /package/node_modules/{@herbertgao → @tifan}/pi-mermaid-open/src/index.ts +0 -0
  469. /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/index.ts +0 -0
  470. /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/model-picker.ts +0 -0
  471. /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/sanitize.ts +0 -0
  472. /package/node_modules/{@herbertgao → @tifan}/pi-recap/src/tui.ts +0 -0
  473. /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/model-picker.ts +0 -0
  474. /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/naming.ts +0 -0
  475. /package/node_modules/{@herbertgao → @tifan}/pi-rename/src/sanitize.ts +0 -0
  476. /package/node_modules/{@herbertgao → @tifan}/pi-titlebar-spinner/src/index.ts +0 -0
@@ -86,14 +86,14 @@ if (isSharedWriterState(existingGlobalState)) {
86
86
  ndjsonGlobalState = existingGlobalState;
87
87
  }
88
88
  else if (existingGlobalState === undefined) {
89
- ndjsonGlobalState = (globalStateHost[NDJSON_GLOBAL_STATE_KEY] = {
89
+ ndjsonGlobalState = globalStateHost[NDJSON_GLOBAL_STATE_KEY] = {
90
90
  schema: NDJSON_GLOBAL_STATE_SCHEMA,
91
91
  version: NDJSON_GLOBAL_STATE_VERSION,
92
92
  writers: new Map(),
93
93
  exitFlushers: new Set(),
94
94
  exitHandlerRegistered: false,
95
95
  registeredLogFiles: new Set(),
96
- });
96
+ };
97
97
  }
98
98
  else if (isLegacyGlobalState(existingGlobalState)) {
99
99
  // Do not mutate or replace this state: its private queues and exit flusher
@@ -363,9 +363,7 @@ async function drainLoop(state) {
363
363
  ? state.queue.findIndex((queued) => queued.kind === "truncate")
364
364
  : 0;
365
365
  const pendingEnd = truncateIndex === -1 ? state.queue.length : truncateIndex;
366
- const pending = item.kind === "truncate"
367
- ? [item]
368
- : state.queue.slice(0, pendingEnd);
366
+ const pending = item.kind === "truncate" ? [item] : state.queue.slice(0, pendingEnd);
369
367
  state.inFlightBatch = pending;
370
368
  const writeBatch = async () => {
371
369
  if (item.kind === "truncate") {
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Opaque-mutation recovery (#2000 phase 2).
3
+ *
4
+ * A bash command whose writes are NOT recognized by
5
+ * `extractWrittenPathsFromCommand` (python/node/perl/PowerShell internal
6
+ * writes, restores) previously bypassed dispatch AND read-guard authorship.
7
+ * This module observes what such a command actually changed.
8
+ *
9
+ * GIT-FIRST STRATEGY: inside a git worktree the pre side records only a
10
+ * TIMESTAMP; the post side asks `git status --porcelain` which files are
11
+ * dirty and keeps those whose mtime falls inside the command's window.
12
+ * This has NO file-universe bound — it works identically on a 10-file site
13
+ * and a 5000-file monorepo, and content-identical rewrites are correctly
14
+ * NOT reported (a false positive the pure stat-diff design produced).
15
+ *
16
+ * NON-GIT FALLBACK: outside git the pre side takes a bounded stat snapshot
17
+ * (cap enforced, cooperative budget) and the post side diffs it — the
18
+ * original design, kept honestly scoped to small non-git trees.
19
+ *
20
+ * Every failure mode yields an explicit UNKNOWN verdict to the caller —
21
+ * never a clean claim (issue invariant 3). All path keys use
22
+ * normalizeMapKey+resolve, joining the mutation-seam's key form.
23
+ *
24
+ * KNOWN LIMITATIONS:
25
+ * - shape 6: stat-diff identity is size+mtimeMs PLUS a budgeted content-hash
26
+ * confirm (withHashes captures sha1 up to OPAQUE_HASH_BUDGET_BYTES); a
27
+ * same-tick/same-size rewrite is detected when both sides hashed the file,
28
+ * and degrades to mtime+size for files past the hash budget. The git path
29
+ * detects any content change regardless.
30
+ * - INVARIANT 4 (per issue): ignored/vendor paths are EXCLUDED. Under the
31
+ * git strategy this means writes landing ONLY in .gitignore'd locations
32
+ * are never reported - an all-ignored write set reads as an empty (clean)
33
+ * recovery BY SPEC, not by oversight. Codegen targeting dist/build-style
34
+ * outputs should use explicit redirect destinations so the extractor
35
+ * recognizes them.
36
+ */
37
+ import * as fs from "node:fs";
38
+ import * as path from "node:path";
39
+ import { collectSourceFilesWithBudgetAsync } from "./source-filter.js";
40
+ import { createHash } from "node:crypto";
41
+ import { normalizeMapKey } from "./path-utils.js";
42
+ import { freshnessFromMtime } from "./freshness.js";
43
+ import { safeSpawnAsync } from "./safe-spawn.js";
44
+ /** Stop hashing once this many cumulative bytes were read (per capture). */
45
+ export const OPAQUE_HASH_BUDGET_BYTES = 8 * 1024 * 1024;
46
+ /** Hard cap on scanned files — beyond it the verdict is coverage-unknown. */
47
+ export const OPAQUE_SCAN_MAX_FILES = 2000;
48
+ /** How far before recorded start an earlier write may still be attributed. */
49
+ export const OPAQUE_MTIME_TOLERANCE_MS = 150;
50
+ export async function captureFileStats(root, options = {}) {
51
+ const budgetMs = options.budgetMs ?? 50;
52
+ try {
53
+ const walk = await collectSourceFilesWithBudgetAsync(root, {
54
+ maxFiles: OPAQUE_SCAN_MAX_FILES + 1,
55
+ budgetMs,
56
+ });
57
+ const files = walk.files;
58
+ if (walk.entryBudgetExceeded || files.length > OPAQUE_SCAN_MAX_FILES) {
59
+ // A PARTIAL universe must never read as a confident diff (invariant
60
+ // 3): writes in the unvisited tail would silently vanish.
61
+ return {
62
+ unknownReason: walk.entryBudgetExceeded
63
+ ? "entry-budget-exceeded"
64
+ : "file-cap-exceeded",
65
+ scannedCount: files.length,
66
+ };
67
+ }
68
+ const snapshot = new Map();
69
+ let hashBytesSpent = 0;
70
+ for (const file of files) {
71
+ try {
72
+ const stat = await fs.promises.stat(file);
73
+ const entry = {
74
+ mtimeMs: stat.mtimeMs,
75
+ size: stat.size,
76
+ };
77
+ if (options.withHashes &&
78
+ hashBytesSpent + stat.size <= OPAQUE_HASH_BUDGET_BYTES) {
79
+ entry.hash = createHash("sha256")
80
+ .update(await fs.promises.readFile(file))
81
+ .digest("hex");
82
+ hashBytesSpent += stat.size;
83
+ }
84
+ snapshot.set(normalizeMapKey(path.resolve(file)), entry);
85
+ }
86
+ catch {
87
+ // Vanished mid-walk: absent from both snapshots = unchanged-by-absence.
88
+ }
89
+ }
90
+ return { snapshot, scannedCount: snapshot.size };
91
+ }
92
+ catch {
93
+ return { unknownReason: "walk-failed", scannedCount: 0 };
94
+ }
95
+ }
96
+ /** Paths added or whose size/mtime changed. Deletions are NOT reported. */
97
+ export function diffFileStats(before, after) {
98
+ const changed = [];
99
+ for (const [key, stat] of after) {
100
+ const prev = before.get(key);
101
+ // Content confirm: same mtime tick + same size but different bytes.
102
+ const contentConfirm = prev?.hash !== undefined &&
103
+ stat.hash !== undefined &&
104
+ prev.hash !== stat.hash;
105
+ if (!prev ||
106
+ prev.mtimeMs !== stat.mtimeMs ||
107
+ prev.size !== stat.size ||
108
+ contentConfirm) {
109
+ changed.push(key);
110
+ }
111
+ }
112
+ return changed;
113
+ }
114
+ export class OpaqueBaselineStore {
115
+ byCwd = new Map();
116
+ evictions = 0;
117
+ record(cwdKey, baseline) {
118
+ if (this.byCwd.has(cwdKey))
119
+ this.evictions += 1;
120
+ this.byCwd.set(cwdKey, baseline);
121
+ }
122
+ take(cwdKey) {
123
+ const baseline = this.byCwd.get(cwdKey);
124
+ this.byCwd.delete(cwdKey);
125
+ return baseline;
126
+ }
127
+ get evictionCount() {
128
+ return this.evictions;
129
+ }
130
+ /** Session-boundary clear - unconsumed baselines are unreachable after reset. */
131
+ takeAllForTest() {
132
+ this.byCwd.clear();
133
+ }
134
+ }
135
+ const globalStoreSymbol = Symbol.for("pi-lens:opaque-snapshot-store");
136
+ const globalSlot = globalThis;
137
+ export function getOpaqueBaselineStore() {
138
+ const existing = globalSlot[globalStoreSymbol]?.store;
139
+ if (existing)
140
+ return existing;
141
+ const created = new OpaqueBaselineStore();
142
+ globalSlot[globalStoreSymbol] = { store: created };
143
+ return created;
144
+ }
145
+ const gitRepoMemo = new Map();
146
+ /**
147
+ * Session-boundary clear (#1635): unconsumed pending baselines are keyed by
148
+ * cwd:generation, so entries from a finished session are unreachable; and a
149
+ * directory that was not a worktree last session may be one now. Without this
150
+ * reset both leak and mis-answer forever.
151
+ */
152
+ export function resetOpaqueMutationState() {
153
+ getOpaqueBaselineStore().takeAllForTest();
154
+ gitRepoMemo.clear();
155
+ }
156
+ /** Cached git-worktree probe (repos don't stop being git mid-session). */
157
+ export async function isGitWorktree(root) {
158
+ const key = normalizeMapKey(path.resolve(root));
159
+ const memo = gitRepoMemo.get(key);
160
+ if (memo !== undefined)
161
+ return memo;
162
+ const result = await safeSpawnAsync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root, timeout: 3000 });
163
+ const isRepo = !result.error && result.status === 0 && result.stdout?.trim() === "true";
164
+ gitRepoMemo.set(key, isRepo === true);
165
+ return isRepo === true;
166
+ }
167
+ export function _resetGitWorktreeMemoForTests() {
168
+ gitRepoMemo.clear();
169
+ }
170
+ /**
171
+ * Files dirty in the working tree whose mtime falls inside
172
+ * [startedAt - tolerance, now]. Porcelain -z parsing handles renames
173
+ * (the NEW path is reported; the old path token is skipped).
174
+ */
175
+ export async function recoverOpaqueChangesViaGit(root, startedAt) {
176
+ const result = await safeSpawnAsync("git", ["status", "--porcelain", "-z", "--untracked-files=all"], { cwd: root, timeout: 5000 });
177
+ if (result.error || (result.status !== 0 && result.status !== null)) {
178
+ return {
179
+ verdict: "unknown",
180
+ paths: [],
181
+ unknownReason: "git-failed",
182
+ scannedCount: 0,
183
+ };
184
+ }
185
+ const floorMs = startedAt - OPAQUE_MTIME_TOLERANCE_MS;
186
+ const raw = result.stdout ?? "";
187
+ const tokens = raw.split("\0");
188
+ const paths = [];
189
+ let skipNext = false; // rename's OLD path follows its NEW path
190
+ for (const token of tokens) {
191
+ if (!token)
192
+ continue;
193
+ if (skipNext) {
194
+ skipNext = false;
195
+ continue;
196
+ }
197
+ // Each entry: two status chars, one space, then the path.
198
+ if (token.length < 4 || token[2] !== " ")
199
+ continue;
200
+ const status = token.slice(0, 2);
201
+ const relPath = token.slice(3);
202
+ if (status.includes("R") || status.includes("C"))
203
+ skipNext = true;
204
+ const abs = path.resolve(root, relPath);
205
+ try {
206
+ const stat = await fs.promises.stat(abs);
207
+ if (stat.isFile() &&
208
+ freshnessFromMtime({ mtimeMs: stat.mtimeMs, referenceMs: floorMs })
209
+ .verdict === "stale") {
210
+ // Kernel "stale" = modified AFTER the window floor - exactly the
211
+ // writes this command may have authored.
212
+ paths.push(normalizeMapKey(abs));
213
+ }
214
+ }
215
+ catch {
216
+ // Deleted or vanished: deletions are deliberately unreported.
217
+ }
218
+ }
219
+ return { verdict: "recovered", paths, scannedCount: paths.length };
220
+ }
@@ -23,7 +23,12 @@ import { safeSpawnAsync } from "./safe-spawn.js";
23
23
  * missing). npm first for maximum compatibility; bun last. A project lockfile
24
24
  * always overrides this order.
25
25
  */
26
- const PREFERENCE = ["npm", "pnpm", "yarn", "bun"];
26
+ const PREFERENCE = [
27
+ "npm",
28
+ "pnpm",
29
+ "yarn",
30
+ "bun",
31
+ ];
27
32
  function onWindows() {
28
33
  return process.platform === "win32";
29
34
  }
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import * as nodeFs from "node:fs";
15
15
  import * as path from "node:path";
16
- import { findNearestContaining, normalizeEphemeralMapKey } from "./path-utils.js";
16
+ import { findNearestContaining, normalizeEphemeralMapKey, } from "./path-utils.js";
17
17
  import { recordFromDispatchDiagnostic, } from "./actionable-warnings.js";
18
18
  import { recordFromCodeQualityDiagnostic, } from "./code-quality-warnings.js";
19
19
  import { recordDiagnostics } from "./widget-state.js";
@@ -25,6 +25,7 @@ import { classifyProbeFailure, createAvailabilityChecker, createAvailabilityLatc
25
25
  import { findDetektConfig } from "./dispatch/runners/detekt.js";
26
26
  import { detectFileKind, getFileKindLabel } from "./file-kinds.js";
27
27
  import { detectFileChangedAfterCommand, getProjectIgnoreMatcher, isExcludedDirName, } from "./file-utils.js";
28
+ import { isInSpawnTimeoutCooldown } from "./spawn-timeout-cooldown.js";
28
29
  import { logLatency } from "./latency-logger.js";
29
30
  import { emitLensAnalysisComplete } from "./lens-events.js";
30
31
  import { publishFilesTouched } from "./bus-publish.js";
@@ -36,6 +37,7 @@ import { RUNTIME_CONFIG } from "./runtime-config.js";
36
37
  import { getAmbientAbortSignal, safeSpawnAsync } from "./safe-spawn.js";
37
38
  import { combineAbortSignals } from "./deadline-utils.js";
38
39
  import { recordDegradationOnce } from "./degradation-ledger.js";
40
+ import { dropFindingsForMissingPaths } from "./advisory-provenance.js";
39
41
  import { getAutofixPolicyForFile, getPreferredAutofixTools, getRubocopCommand, hasBiomeConfig, hasDetektConfig, hasEslintConfig, hasGolangciConfig, hasKtfmtConfig, hasKtlintConfig, hasOxlintConfig, hasRubocopConfig, hasSqlfluffConfig, hasStylelintConfig, markdownlintConfigArgs, } from "./tool-policy.js";
40
42
  const LSP_MAX_FILE_BYTES = RUNTIME_CONFIG.pipeline.lspMaxFileBytes;
41
43
  const LSP_MAX_FILE_LINES = RUNTIME_CONFIG.pipeline.lspMaxFileLines;
@@ -336,10 +338,17 @@ async function tryDetektFix(filePath, cwd) {
336
338
  const absPath = path.resolve(cwd, filePath);
337
339
  return detectFileChangedAfterCommand(filePath, cmd, ["--auto-correct", "--input", absPath, "--config", configPath], cwd, [1, 2]);
338
340
  }
339
- async function tryMarkdownlintFix(filePath, cwd) {
341
+ // Exported for #1995 cooldown wiring tests: the guard on this lane is
342
+ // one of three mutation-proof surfaces.
343
+ export async function tryMarkdownlintFix(filePath, cwd) {
340
344
  const cmd = await resolveToolCommandWithInstallFallback(cwd, "markdownlint");
341
345
  if (!cmd)
342
346
  return 0;
347
+ // #1995: skip the spawn entirely when the command is cooling down after a
348
+ // timeout — detectFileChangedAfterCommand also self-guards, but a wedged
349
+ // command should not even reach a second budget in the hot loop.
350
+ if (isInSpawnTimeoutCooldown(cmd))
351
+ return 0;
343
352
  // Shared config-args seam (#1247): the lint runner consumes the same
344
353
  // builder, so the bare --fix here can never fall back to markdownlint's
345
354
  // default all-rules-on config again (the whole-file CHANGELOG/AGENTS
@@ -838,8 +847,10 @@ export async function runFormatPhase(filePath, getFormatService, dbg) {
838
847
  * L4: 'randomInt' is declared but its value is never read.
839
848
  * → const randomInt = Math.floor(result);
840
849
  */
841
- function buildEnrichedBlockerOutput(blockers, fileContent) {
842
- const fileLines = fileContent.split("\n");
850
+ function buildEnrichedBlockerOutput(blockers, fileContent = "") {
851
+ // Empty fileContent (readback failed, e.g. deleted-file race) still
852
+ // renders the gated blocker list - just without per-line snippets.
853
+ const fileLines = fileContent ? fileContent.split("\n") : [];
843
854
  const MAX_SNIPPET = 120; // chars — keep it tight in context
844
855
  let out = `\n\n🔴 STOP — ${blockers.length} issue(s) must be fixed:\n`;
845
856
  const shown = blockers.slice(0, 10);
@@ -1065,10 +1076,27 @@ export async function runPipeline(ctx, deps) {
1065
1076
  if (dispatchResult.resolvedCount > 0)
1066
1077
  getDiagnosticTracker().trackAgentFixed(dispatchResult.resolvedCount);
1067
1078
  let output = "";
1079
+ // #2028: the 🔴 STOP block is an agent-facing delivery surface
1080
+ // (finding-delivery-gate.ts's `tool-call:stop-blocker`), so it routes through
1081
+ // the shared deleted-path gate before rendering: a blocker whose cited file
1082
+ // no longer exists has no remediation the agent can perform (the finding IS
1083
+ // the deleted file's content), so it is dropped here rather than re-asserted.
1084
+ // One bounded stat per unique cited path, only when blockers exist — zero
1085
+ // cost on the clean/fast paths.
1086
+ const deliverableBlockers = dispatchResult.hasBlockers
1087
+ ? dropFindingsForMissingPaths({
1088
+ store: "stop-blocker",
1089
+ findings: dispatchResult.blockers,
1090
+ cwd,
1091
+ citedPath: (b) => b.filePath || undefined,
1092
+ })
1093
+ : dispatchResult.blockers;
1068
1094
  if (dispatchResult.hasBlockers && fileContent) {
1069
1095
  // Enrich blocker output with a code snippet so the agent can see the
1070
1096
  // exact line it wrote that caused each violation — no re-read needed.
1071
- output += buildEnrichedBlockerOutput(dispatchResult.blockers, fileContent);
1097
+ if (deliverableBlockers.length > 0) {
1098
+ output += buildEnrichedBlockerOutput(deliverableBlockers, fileContent);
1099
+ }
1072
1100
  // Append fixed/coverage parts from the original output (slice off the
1073
1101
  // blocker section we're replacing).
1074
1102
  const rest = dispatchResult.output.slice(dispatchResult.blockerOutput.length);
@@ -1076,7 +1104,20 @@ export async function runPipeline(ctx, deps) {
1076
1104
  output += rest;
1077
1105
  }
1078
1106
  else if (dispatchResult.output) {
1079
- output += `\n\n${dispatchResult.output}`;
1107
+ // #2028 review P3: this path fires when readback FAILED - raw output
1108
+ // still cites possibly-deleted files. Re-render from the gated set
1109
+ // (no snippets without fileContent) and keep the post-blocker slice.
1110
+ if (dispatchResult.hasBlockers &&
1111
+ deliverableBlockers.length !== dispatchResult.blockers.length) {
1112
+ let gatedOut = buildEnrichedBlockerOutput(deliverableBlockers);
1113
+ const rest = dispatchResult.output.slice(dispatchResult.blockerOutput.length);
1114
+ if (rest)
1115
+ gatedOut += rest;
1116
+ output += `\n\n${gatedOut}`;
1117
+ }
1118
+ else {
1119
+ output += `\n\n${dispatchResult.output}`;
1120
+ }
1080
1121
  }
1081
1122
  if (fixedCount > 0) {
1082
1123
  const detail = autofixTools.length > 0 ? ` (${autofixTools.join(", ")})` : "";
@@ -1126,7 +1167,8 @@ export async function runPipeline(ctx, deps) {
1126
1167
  fileContent,
1127
1168
  wordIndex: ctx.wordIndex,
1128
1169
  onWordIndexUpdated: ctx.onWordIndexUpdated,
1129
- }).then((run) => ({ ...run, origin: cascadeOrigin }))
1170
+ })
1171
+ .then((run) => ({ ...run, origin: cascadeOrigin }))
1130
1172
  .catch((err) => {
1131
1173
  dbg(`cascade compute failed for ${filePath}: ${err}`);
1132
1174
  return {
@@ -1233,8 +1275,14 @@ export async function runPipeline(ctx, deps) {
1233
1275
  fixedCount,
1234
1276
  autofixTools,
1235
1277
  postAutofixNotice,
1236
- postMutation: ctx.autofixMode !== "deferred" && autofixChangedFiles.some((f) => path.resolve(f) === path.resolve(filePath)) && fileContent !== undefined
1237
- ? { filePath: path.resolve(filePath), content: fileContent, source: "autofix" }
1278
+ postMutation: ctx.autofixMode !== "deferred" &&
1279
+ autofixChangedFiles.some((f) => path.resolve(f) === path.resolve(filePath)) &&
1280
+ fileContent !== undefined
1281
+ ? {
1282
+ filePath: path.resolve(filePath),
1283
+ content: fileContent,
1284
+ source: "autofix",
1285
+ }
1238
1286
  : undefined,
1239
1287
  };
1240
1288
  }
@@ -72,8 +72,7 @@ function detectNext(cwd, pkg) {
72
72
  signals.push("app/");
73
73
  if (signals.length === 0)
74
74
  return undefined;
75
- const confidence = hasDep(pkg, "next") &&
76
- signals.some((s) => s.startsWith("next.config."))
75
+ const confidence = hasDep(pkg, "next") && signals.some((s) => s.startsWith("next.config."))
77
76
  ? "high"
78
77
  : hasDep(pkg, "next")
79
78
  ? "medium"
@@ -152,12 +151,7 @@ export function detectProjectConventions(cwd) {
152
151
  return { frameworks: [], testRunners: [], buildTools: [], agentDocs: [] };
153
152
  }
154
153
  const pkg = readPackageJson(cwd);
155
- const detectors = [
156
- detectReact,
157
- detectNext,
158
- detectVite,
159
- detectVitest,
160
- ];
154
+ const detectors = [detectReact, detectNext, detectVite, detectVitest];
161
155
  const frameworks = [];
162
156
  for (const detector of detectors) {
163
157
  const detection = detector(cwd, pkg);
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { getProjectDataDir } from "../file-utils.js";
4
4
  import { writeFileAtomic } from "../atomic-write.js";
5
5
  import { readJsonCache } from "../json-cache-read.js";
6
- import { MTIME_DRIFT_TOLERANCE_MS } from "../blocker-freshness.js";
6
+ import { freshnessFromMtime } from "../freshness.js";
7
7
  // v2: cheap-tier scan now also runs ast-grep-napi (#308); invalidate older
8
8
  // snapshots so a pre-ast-grep cache isn't served as complete via refreshRunners=cached.
9
9
  export const PROJECT_DIAGNOSTICS_CACHE_VERSION = 2;
@@ -80,16 +80,19 @@ export function reconcileProjectDiagnosticsSnapshot(snapshot) {
80
80
  if (cached !== undefined)
81
81
  return cached;
82
82
  let stale;
83
+ let mtimeMs;
83
84
  try {
84
- // MTIME_DRIFT_TOLERANCE_MS: a file scanned at scannedAt, or written
85
- // within the tolerance window of the scan timestamp being captured,
86
- // has mtime <= scannedAt + MTIME_DRIFT_TOLERANCE_MS.
87
- stale =
88
- fs.statSync(filePath).mtimeMs > scannedAtMs + MTIME_DRIFT_TOLERANCE_MS;
85
+ mtimeMs = fs.statSync(filePath).mtimeMs;
89
86
  }
90
87
  catch {
91
- stale = true; // deleted / unreadable drop
88
+ mtimeMs = undefined; // deleted / unreadable -> indeterminate -> drop
92
89
  }
90
+ const verdict = freshnessFromMtime({
91
+ mtimeMs,
92
+ referenceMs: scannedAtMs,
93
+ });
94
+ // Pre-kernel policy: an unreadable/missing file is stale (dropped).
95
+ stale = verdict.verdict !== "fresh";
93
96
  staleByFile.set(filePath, stale);
94
97
  return stale;
95
98
  };
@@ -97,7 +97,8 @@ export function callGraphImpactToProjectDiagnostics(cwd, findings) {
97
97
  continue;
98
98
  const dedupeKey = `${resolvedFile}:${result.symbolKey}`;
99
99
  const existing = byKey.get(dedupeKey);
100
- if (existing && SEVERITY_RANK[existing.severity] >= SEVERITY_RANK[mapped.severity]) {
100
+ if (existing &&
101
+ SEVERITY_RANK[existing.severity] >= SEVERITY_RANK[mapped.severity]) {
101
102
  continue;
102
103
  }
103
104
  const callerName = displayName(result.symbolKey);
@@ -14,7 +14,9 @@ export function gitleaksFindingToProjectDiagnostic(cwd, finding) {
14
14
  // #1562 observability criterion: surface the git status right in the
15
15
  // finding's message so a triage is a read, not a re-derivation. Omitted
16
16
  // when git itself degraded (`pathStatus` undefined) rather than guessed.
17
- const statusSuffix = finding.pathStatus ? ` [git: ${finding.pathStatus}]` : "";
17
+ const statusSuffix = finding.pathStatus
18
+ ? ` [git: ${finding.pathStatus}]`
19
+ : "";
18
20
  const isScratch = finding.pathStatus === "scratch";
19
21
  return {
20
22
  filePath: path.isAbsolute(finding.file)
@@ -84,16 +84,20 @@ export function testRunnerFindingsToProjectDiagnostics(cache, cwd, runtime) {
84
84
  const missingKeys = new Set((cache.provenance?.files ?? [])
85
85
  .filter((file) => !fs.existsSync(path.resolve(root, file.path)))
86
86
  .map((file) => advisoryPathKey(file.path, root)));
87
- const historical = cache.superseded === true || cache.stale === true || validation.status !== "current";
87
+ const historical = cache.superseded === true ||
88
+ cache.stale === true ||
89
+ validation.status !== "current";
88
90
  return cache.results
89
91
  .filter((result) => !missingKeys.has(advisoryPathKey(result.file, root)))
90
92
  .flatMap((result) => testResultToProjectDiagnostics(result, historical))
91
- .map((diagnostic) => historical ? {
92
- ...diagnostic,
93
- severity: "info",
94
- semantic: "none",
95
- message: diagnostic.message.startsWith("[stale")
96
- ? diagnostic.message
97
- : `[historical — re-run to confirm] ${diagnostic.message}`,
98
- } : diagnostic);
93
+ .map((diagnostic) => historical
94
+ ? {
95
+ ...diagnostic,
96
+ severity: "info",
97
+ semantic: "none",
98
+ message: diagnostic.message.startsWith("[stale")
99
+ ? diagnostic.message
100
+ : `[historical — re-run to confirm] ${diagnostic.message}`,
101
+ }
102
+ : diagnostic);
99
103
  }
@@ -10,7 +10,9 @@ export function trivyFindingToProjectDiagnostic(cwd, finding) {
10
10
  ? finding.target
11
11
  : path.resolve(cwd, finding.target)
12
12
  : cwd;
13
- const version = finding.installedVersion ? `@${finding.installedVersion}` : "";
13
+ const version = finding.installedVersion
14
+ ? `@${finding.installedVersion}`
15
+ : "";
14
16
  const fix = finding.fixedVersion ? ` (fixed in ${finding.fixedVersion})` : "";
15
17
  return {
16
18
  filePath,
@@ -38,7 +38,7 @@ const FACT_RULE_EXTENSIONS = new Set([
38
38
  // The base is the shared per-edit resolver (EXT_TO_LANG — the single
39
39
  // ext→grammar-id authority), so c/cpp/csharp/php/css and the .tsx→tsx /
40
40
  // .jsx→javascript nuances can never drift from the per-edit path. `.tsx` resolves
41
- // to the tsx grammar (not typescript: the typescript grammar ERRORs on JSX);
41
+ // to the tsx grammar (not typescript: the typescript grammar ERRORs on JSX); // spellchecker:disable-line
42
42
  // typescript RULES still apply because `queriesForLanguage(...)` below merges the
43
43
  // typescript rule set onto tsx.
44
44
  //
@@ -295,7 +295,10 @@ async function scanFileMajorRules(cwd, files, signal) {
295
295
  // so it stays visible SOMEWHERE instead of disappearing when the
296
296
  // vacuous `project_diagnostics_ast_grep_scan` cache_stats record
297
297
  // (below) was removed.
298
- astGrep: { durationMs: astGrepDurationMs, fileCount: astGrepFilesScanned },
298
+ astGrep: {
299
+ durationMs: astGrepDurationMs,
300
+ fileCount: astGrepFilesScanned,
301
+ },
299
302
  });
300
303
  });
301
304
  // #1935: no `project_diagnostics_ast_grep_scan` cache_stats record here.
@@ -194,7 +194,11 @@ export function loadPiLensConfigInDir(dir) {
194
194
  return cached.config;
195
195
  }
196
196
  const config = parseConfigFile(info.path);
197
- configCache.set(info.path, { mtimeMs: info.mtimeMs, size: info.size, config });
197
+ configCache.set(info.path, {
198
+ mtimeMs: info.mtimeMs,
199
+ size: info.size,
200
+ config,
201
+ });
198
202
  return config;
199
203
  }
200
204
  export function findPiLensProjectConfig(startDir) {
@@ -239,7 +243,12 @@ function discoverPiLensProjectConfig(startDir) {
239
243
  const stat = safeFileStat(candidate);
240
244
  if (stat?.isFile()) {
241
245
  return {
242
- info: { path: candidate, dir, mtimeMs: stat.mtimeMs, size: stat.size },
246
+ info: {
247
+ path: candidate,
248
+ dir,
249
+ mtimeMs: stat.mtimeMs,
250
+ size: stat.size,
251
+ },
243
252
  dirMtimes,
244
253
  };
245
254
  }
@@ -351,9 +360,7 @@ function parseConfigFile(configPath) {
351
360
  if (entry.threshold !== undefined || entry.disable || entry.select) {
352
361
  rules[ruleId] = entry;
353
362
  }
354
- else if (!("threshold" in r) &&
355
- !("disable" in r) &&
356
- !("select" in r)) {
363
+ else if (!("threshold" in r) && !("disable" in r) && !("select" in r)) {
357
364
  warnInvalidConfigOnce(configPath, `rules.${ruleId} has no recognized setting (threshold, disable, select); ignored`);
358
365
  }
359
366
  }
@@ -0,0 +1,48 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Hash serialized project-snapshot JSON while replacing only the top-level
4
+ * volatile `generatedAt` value with a stable sentinel. Production calls this
5
+ * on the persistence worker after stringify. The main-thread caller uses it
6
+ * only on the explicit synchronous fallback path, where serialization already
7
+ * happened and no second object walk is added.
8
+ */
9
+ export function fingerprintProjectSnapshotJson(json, generatedAt) {
10
+ const marker = `"generatedAt":${JSON.stringify(generatedAt)}`;
11
+ let markerIndex = -1;
12
+ let depth = 0;
13
+ let inString = false;
14
+ let escaped = false;
15
+ for (let index = 0; index < json.length; index++) {
16
+ const char = json[index];
17
+ if (inString) {
18
+ if (escaped)
19
+ escaped = false;
20
+ else if (char === "\\")
21
+ escaped = true;
22
+ else if (char === '"')
23
+ inString = false;
24
+ continue;
25
+ }
26
+ if (char === '"') {
27
+ if (depth === 1 && json.startsWith(marker, index)) {
28
+ markerIndex = index;
29
+ break;
30
+ }
31
+ inString = true;
32
+ }
33
+ else if (char === "{")
34
+ depth++;
35
+ else if (char === "}")
36
+ depth--;
37
+ }
38
+ const hash = createHash("sha256");
39
+ if (markerIndex < 0) {
40
+ hash.update(json);
41
+ }
42
+ else {
43
+ hash.update(json.slice(0, markerIndex));
44
+ hash.update('"generatedAt":""');
45
+ hash.update(json.slice(markerIndex + marker.length));
46
+ }
47
+ return hash.digest("hex");
48
+ }
@@ -1,6 +1,20 @@
1
1
  import { serveGzipStageWorker, } from "./gzip-stage-write.js";
2
+ import { fingerprintProjectSnapshotJson } from "./project-snapshot-fingerprint.js";
3
+ /**
4
+ * Hash the serialized body while replacing only the top-level volatile
5
+ * `generatedAt` value with a stable sentinel. This runs after the worker has
6
+ * already paid the unavoidable stringify cost. It never walks the snapshot on
7
+ * the extension host's event loop.
8
+ */
9
+ function semanticSnapshotFingerprint(request, json) {
10
+ const generatedAt = request.data.generatedAt;
11
+ return fingerprintProjectSnapshotJson(json, typeof generatedAt === "string" ? generatedAt : "");
12
+ }
2
13
  serveGzipStageWorker((request) => ({
3
14
  id: request.id,
4
15
  generation: request.generation,
5
16
  stagePath: request.stagePath,
6
- }));
17
+ }), {
18
+ semanticFingerprint: semanticSnapshotFingerprint,
19
+ skipIfFingerprints: (request) => request.priorFingerprints,
20
+ });