@herbertgao/pi-extensions 2026.9.0 → 2026.9.2

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 (360) hide show
  1. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +13 -1
  2. package/node_modules/@czottmann/pi-automode/README.md +20 -0
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +1 -1
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +2 -1
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +3 -2
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +38 -2
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +5 -3
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +11 -3
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  12. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  13. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  14. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  15. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  17. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  20. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  21. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  22. package/node_modules/@pi-plugins/fast-mode/README.md +5 -3
  23. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  24. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +20 -55
  25. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  26. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  27. package/node_modules/pi-lens/CHANGELOG.md +1072 -0
  28. package/node_modules/pi-lens/README.md +3 -0
  29. package/node_modules/pi-lens/config/biome/core.jsonc +11 -2
  30. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +13 -1
  31. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +927 -80
  33. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  34. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +13 -4
  35. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +123 -3
  36. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +60 -5
  37. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +2 -9
  38. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  39. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -3
  40. package/node_modules/pi-lens/dist/clients/biome-client.js +9 -2
  41. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +14 -6
  42. package/node_modules/pi-lens/dist/clients/bootstrap.js +509 -73
  43. package/node_modules/pi-lens/dist/clients/bounded-cache.js +152 -12
  44. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +59 -9
  46. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +113 -0
  47. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +3 -11
  48. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +31 -4
  49. package/node_modules/pi-lens/dist/clients/cache-manager.js +105 -6
  50. package/node_modules/pi-lens/dist/clients/cache-observability.js +40 -19
  51. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +422 -0
  52. package/node_modules/pi-lens/dist/clients/cascade-logger.js +2 -9
  53. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  54. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +13 -3
  55. package/node_modules/pi-lens/dist/clients/complexity-client.js +16 -5
  56. package/node_modules/pi-lens/dist/clients/config-core/deny.js +221 -0
  57. package/node_modules/pi-lens/dist/clients/config-core/index.js +50 -0
  58. package/node_modules/pi-lens/dist/clients/config-core/merge.js +357 -0
  59. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +340 -0
  60. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +248 -0
  61. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +164 -0
  62. package/node_modules/pi-lens/dist/clients/config-core/records.js +218 -0
  63. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +125 -0
  64. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +78 -0
  65. package/node_modules/pi-lens/dist/clients/config-core/schema.js +165 -0
  66. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +281 -0
  67. package/node_modules/pi-lens/dist/clients/config-locations.js +160 -0
  68. package/node_modules/pi-lens/dist/clients/config-resolve.js +789 -0
  69. package/node_modules/pi-lens/dist/clients/config-schema.js +234 -0
  70. package/node_modules/pi-lens/dist/clients/config-warn.js +407 -0
  71. package/node_modules/pi-lens/dist/clients/dead-code-client.js +19 -14
  72. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +2 -6
  73. package/node_modules/pi-lens/dist/clients/deadline-utils.js +178 -0
  74. package/node_modules/pi-lens/dist/clients/debug-handles.js +2 -2
  75. package/node_modules/pi-lens/dist/clients/debug-heap.js +3 -3
  76. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +106 -0
  77. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +113 -7
  78. package/node_modules/pi-lens/dist/clients/dependency-checker.js +8 -19
  79. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  80. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +12 -9
  81. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +15 -5
  82. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  83. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +162 -44
  84. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +97 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +27 -8
  86. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  87. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  89. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  90. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +383 -27
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -1
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -1
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +1 -3
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -2
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -1
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +14 -18
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -3
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +7 -4
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -2
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +1 -3
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -2
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -3
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -1
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -1
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -1
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -67
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +194 -80
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +84 -15
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -1
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  144. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -1
  145. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -1
  146. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  147. package/node_modules/pi-lens/dist/clients/disposition-logger.js +3 -10
  148. package/node_modules/pi-lens/dist/clients/effective-config.js +403 -0
  149. package/node_modules/pi-lens/dist/clients/error-class.js +23 -0
  150. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +274 -0
  151. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  152. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -2
  153. package/node_modules/pi-lens/dist/clients/feature-hints.js +2 -1
  154. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  155. package/node_modules/pi-lens/dist/clients/file-time.js +7 -2
  156. package/node_modules/pi-lens/dist/clients/file-utils.js +117 -10
  157. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +50 -13
  158. package/node_modules/pi-lens/dist/clients/format-service.js +6 -2
  159. package/node_modules/pi-lens/dist/clients/formatters.js +385 -104
  160. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +17 -0
  161. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +116 -22
  162. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -9
  163. package/node_modules/pi-lens/dist/clients/git-guard.js +6 -1
  164. package/node_modules/pi-lens/dist/clients/go-client.js +39 -0
  165. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +252 -0
  166. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  167. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +424 -0
  168. package/node_modules/pi-lens/dist/clients/hook-budgets.js +76 -0
  169. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +5 -2
  170. package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
  171. package/node_modules/pi-lens/dist/clients/installer/index.js +289 -59
  172. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  173. package/node_modules/pi-lens/dist/clients/instance-reaper.js +110 -236
  174. package/node_modules/pi-lens/dist/clients/instance-registry.js +6 -3
  175. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  176. package/node_modules/pi-lens/dist/clients/language-registry.js +599 -0
  177. package/node_modules/pi-lens/dist/clients/latency-logger.js +165 -9
  178. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +34 -1
  179. package/node_modules/pi-lens/dist/clients/lens-config.js +160 -30
  180. package/node_modules/pi-lens/dist/clients/lens-engine.js +33 -41
  181. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  182. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +77 -6
  183. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -38
  185. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +3 -1
  186. package/node_modules/pi-lens/dist/clients/lsp/client.js +172 -33
  187. package/node_modules/pi-lens/dist/clients/lsp/config.js +484 -158
  188. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +1 -1
  189. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  190. package/node_modules/pi-lens/dist/clients/lsp/edits.js +18 -5
  191. package/node_modules/pi-lens/dist/clients/lsp/index.js +892 -96
  192. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  193. package/node_modules/pi-lens/dist/clients/lsp/language.js +18 -177
  194. package/node_modules/pi-lens/dist/clients/lsp/launch.js +2 -1
  195. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  196. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +5 -18
  197. package/node_modules/pi-lens/dist/clients/lsp/server.js +284 -147
  198. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +116 -23
  199. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +2 -7
  200. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  201. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +14 -7
  202. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  203. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  204. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +1 -1
  205. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  206. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +163 -21
  207. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +36 -0
  208. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +33 -4
  209. package/node_modules/pi-lens/dist/clients/mcp/session.js +5 -16
  210. package/node_modules/pi-lens/dist/clients/memory-sampler.js +8 -3
  211. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  212. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +3 -5
  213. package/node_modules/pi-lens/dist/clients/module-report.js +21 -46
  214. package/node_modules/pi-lens/dist/clients/mutating-tool.js +651 -0
  215. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +368 -0
  216. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +240 -0
  217. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +247 -24
  218. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +101 -0
  219. package/node_modules/pi-lens/dist/clients/observed-mutation.js +1215 -0
  220. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +70 -28
  221. package/node_modules/pi-lens/dist/clients/opengrep-config.js +5 -1
  222. package/node_modules/pi-lens/dist/clients/package-manager.js +195 -26
  223. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +359 -80
  224. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +23 -7
  225. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +21 -2
  226. package/node_modules/pi-lens/dist/clients/path-utils.js +451 -15
  227. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  228. package/node_modules/pi-lens/dist/clients/persist-debounce.js +8 -1
  229. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +114 -0
  230. package/node_modules/pi-lens/dist/clients/pipeline.js +79 -18
  231. package/node_modules/pi-lens/dist/clients/probe-home-state.js +228 -0
  232. package/node_modules/pi-lens/dist/clients/process-bridge.js +66 -0
  233. package/node_modules/pi-lens/dist/clients/process-snapshot.js +68 -0
  234. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  235. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  236. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  237. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  238. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  239. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  240. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  241. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +28 -4
  242. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +51 -20
  243. package/node_modules/pi-lens/dist/clients/project-lens-config.js +438 -122
  244. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  245. package/node_modules/pi-lens/dist/clients/project-snapshot.js +52 -24
  246. package/node_modules/pi-lens/dist/clients/python-environment.js +217 -0
  247. package/node_modules/pi-lens/dist/clients/python-provenance.js +639 -0
  248. package/node_modules/pi-lens/dist/clients/quiet-window.js +6 -1
  249. package/node_modules/pi-lens/dist/clients/read-bridge.js +13 -21
  250. package/node_modules/pi-lens/dist/clients/read-expansion.js +9 -46
  251. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +16 -5
  252. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +364 -212
  253. package/node_modules/pi-lens/dist/clients/read-guard.js +26 -2
  254. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  255. package/node_modules/pi-lens/dist/clients/resource-sampler.js +195 -96
  256. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +155 -114
  257. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  258. package/node_modules/pi-lens/dist/clients/review-graph/service.js +55 -3
  259. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  260. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +10 -74
  261. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +9 -3
  262. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +19 -3
  263. package/node_modules/pi-lens/dist/clients/runtime-context.js +49 -16
  264. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +25 -6
  265. package/node_modules/pi-lens/dist/clients/runtime-session.js +184 -32
  266. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +179 -36
  267. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +797 -243
  268. package/node_modules/pi-lens/dist/clients/runtime-turn.js +1028 -53
  269. package/node_modules/pi-lens/dist/clients/rust-client.js +31 -0
  270. package/node_modules/pi-lens/dist/clients/safe-spawn.js +33 -10
  271. package/node_modules/pi-lens/dist/clients/sanitize.js +7 -12
  272. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  273. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +1 -3
  274. package/node_modules/pi-lens/dist/clients/security-scan-client.js +3 -0
  275. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +1 -1
  276. package/node_modules/pi-lens/dist/clients/session-start-observability.js +79 -0
  277. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  278. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +12 -3
  279. package/node_modules/pi-lens/dist/clients/sgconfig.js +7 -6
  280. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  281. package/node_modules/pi-lens/dist/clients/skills-resolver.js +105 -0
  282. package/node_modules/pi-lens/dist/clients/smells-rollup.js +4 -4
  283. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  284. package/node_modules/pi-lens/dist/clients/string-utils.js +13 -0
  285. package/node_modules/pi-lens/dist/clients/subagent-mode.js +17 -4
  286. package/node_modules/pi-lens/dist/clients/test-runner-client.js +176 -39
  287. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +244 -0
  288. package/node_modules/pi-lens/dist/clients/tool-definition.js +41 -1
  289. package/node_modules/pi-lens/dist/clients/tool-policy.js +92 -22
  290. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +40 -40
  291. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +87 -100
  292. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +2 -2
  293. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +81 -1
  294. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +51 -46
  295. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  296. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  297. package/node_modules/pi-lens/dist/clients/typos-config.js +5 -0
  298. package/node_modules/pi-lens/dist/clients/user-notify.js +6 -6
  299. package/node_modules/pi-lens/dist/clients/widget-state.js +191 -26
  300. package/node_modules/pi-lens/dist/clients/word-index-logger.js +2 -9
  301. package/node_modules/pi-lens/dist/clients/word-index-store.js +23 -12
  302. package/node_modules/pi-lens/dist/clients/word-index.js +252 -47
  303. package/node_modules/pi-lens/dist/clients/workspace-topology.js +8 -1
  304. package/node_modules/pi-lens/dist/clients/zizmor-config.js +4 -1
  305. package/node_modules/pi-lens/dist/index.js +65899 -55340
  306. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -1
  307. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  308. package/node_modules/pi-lens/dist/mcp/server.js +127 -5
  309. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +583 -0
  310. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +129 -0
  311. package/node_modules/pi-lens/dist/tools/effective-config.js +89 -0
  312. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +96 -25
  313. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +104 -86
  314. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +37 -16
  315. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  316. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  317. package/node_modules/pi-lens/dist/tools/shared.js +0 -1
  318. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  319. package/node_modules/pi-lens/docs/agent-guide.md +64 -1
  320. package/node_modules/pi-lens/docs/configuration.md +222 -0
  321. package/node_modules/pi-lens/docs/dependencies.md +3 -3
  322. package/node_modules/pi-lens/docs/features.md +44 -5
  323. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  324. package/node_modules/pi-lens/docs/language-coverage.md +2 -2
  325. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  326. package/node_modules/pi-lens/docs/pi-lens-fixer.md +25 -0
  327. package/node_modules/pi-lens/docs/pi-lens-investigator.md +25 -0
  328. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +33 -0
  329. package/node_modules/pi-lens/docs/pi-lens-subagent.md +49 -0
  330. package/node_modules/pi-lens/docs/pi-lens-warden.md +55 -0
  331. package/node_modules/pi-lens/docs/public-api-stability.md +359 -0
  332. package/node_modules/pi-lens/docs/release-qa-baseline.md +190 -0
  333. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  334. package/node_modules/pi-lens/docs/subagent-compat.md +110 -30
  335. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +2 -2
  336. package/node_modules/pi-lens/docs/word-index.md +1 -1
  337. package/node_modules/pi-lens/package.json +18 -35
  338. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  339. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  340. package/node_modules/pi-lens/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  341. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  342. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +192 -1
  343. package/node_modules/pi-lens/scripts/install-selftest.mjs +58 -2
  344. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +129 -0
  345. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  346. package/node_modules/pi-lens/scripts/rpc-load-check.mjs +3 -1
  347. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  348. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  349. package/node_modules/pi-web-access/README.md +62 -15
  350. package/node_modules/pi-web-access/curator-page.ts +3 -1
  351. package/node_modules/pi-web-access/curator-server.ts +5 -1
  352. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  353. package/node_modules/pi-web-access/github-extract.ts +242 -1
  354. package/node_modules/pi-web-access/index.ts +118 -64
  355. package/node_modules/pi-web-access/mistral-search.ts +281 -0
  356. package/node_modules/pi-web-access/package.json +2 -2
  357. package/node_modules/pi-web-access/perplexity.ts +14 -1
  358. package/node_modules/pi-web-access/utils.ts +23 -6
  359. package/node_modules/pi-web-access/xai-search.ts +96 -33
  360. package/package.json +6 -6
@@ -21,11 +21,11 @@
21
21
  */
22
22
  import * as path from "node:path";
23
23
  import { isTestMode } from "./env-utils.js";
24
- import { getGlobalPiLensDir } from "./file-utils.js";
24
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
25
25
  import { getMaxLogSizeMB } from "./log-cleanup.js";
26
26
  import { createNdjsonLogger } from "./ndjson-logger.js";
27
27
  import { normalizeFilePath } from "./path-utils.js";
28
- const WORD_INDEX_LOG_FILE = path.join(getGlobalPiLensDir(), "word-index.log");
28
+ const WORD_INDEX_LOG_FILE = path.join(getGlobalPiLensLogDir(), "word-index.log");
29
29
  const writer = createNdjsonLogger({
30
30
  filePath: WORD_INDEX_LOG_FILE,
31
31
  maxBytes: getMaxLogSizeMB() * 1024 * 1024,
@@ -47,10 +47,3 @@ export function logWordIndex(entry) {
47
47
  cwd: normalizeFilePath(entry.cwd),
48
48
  });
49
49
  }
50
- export function getWordIndexLogPath() {
51
- return WORD_INDEX_LOG_FILE;
52
- }
53
- /** Resolve once all enqueued word-index writes are on disk (tests/shutdown). */
54
- export function flushWordIndexLog() {
55
- return writer.flush();
56
- }
@@ -1,4 +1,4 @@
1
- import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
1
+ import { createDeadline, yieldIfOverBudget, } from "./cooperative-budget.js";
2
2
  /**
3
3
  * Packed backing store for the word index's inverted postings (#2069).
4
4
  *
@@ -30,8 +30,8 @@ import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
30
30
  /**
31
31
  * Bytes of backing store per posting entry: one `fileId` lane and one `line`
32
32
  * lane, both `Int32`. This is the constant #2069's acceptance criterion is
33
- * written against, and {@link estimatePostingListBytes} is the arithmetic the
34
- * memory-sample record and the regression test both read.
33
+ * written against, and the arithmetic the memory-sample record and the
34
+ * regression test both read.
35
35
  */
36
36
  export const WORD_POSTING_ENTRY_BYTES = 8;
37
37
  /**
@@ -189,10 +189,6 @@ export class WordPostingList {
189
189
  return list;
190
190
  }
191
191
  }
192
- /** Estimated bytes one posting list holds, backing store plus fixed headers. */
193
- export function estimatePostingListBytes(list) {
194
- return list.byteLength + WORD_POSTING_LIST_OVERHEAD_BYTES;
195
- }
196
192
  /**
197
193
  * Re-home every posting list into ONE shared `ArrayBuffer`, exactly sized.
198
194
  *
@@ -248,8 +244,20 @@ export function compactPostingsIntoArena(postings) {
248
244
  *
249
245
  * Each `adoptArena` copies and re-homes one list within a single synchronous
250
246
  * step, so readers never observe a partially written list.
247
+ *
248
+ * `options.deadline` and `options.beforeYield` are a test seam only (#2293):
249
+ * production always takes the default 8 ms wall-clock deadline. The #2117
250
+ * regression test used to prove the "grew during a yield" invariant by racing
251
+ * a real timer against a 40,000-list copy, which made the yield itself a
252
+ * scheduling accident — the copy sometimes finished inside the 8 ms budget
253
+ * and never yielded at all, so the test's own precondition went unmet under
254
+ * load. Injecting a deadline lets a test force a yield deterministically
255
+ * instead of hoping wall-clock crosses the budget; `beforeYield` runs
256
+ * in-line, immediately before the real cooperative pause, so a test can
257
+ * mutate a not-yet-adopted list at that exact point with no reliance on
258
+ * event-loop scheduling order.
251
259
  */
252
- export async function compactPostingsIntoArenaCooperatively(postings) {
260
+ export async function compactPostingsIntoArenaCooperatively(postings, options) {
253
261
  // Snapshot (token, list, width) in one synchronous pass; the arena is sized
254
262
  // from these widths and only unchanged lists are adopted, so the sum of the
255
263
  // widths actually written can never exceed the arena length.
@@ -264,15 +272,17 @@ export async function compactPostingsIntoArenaCooperatively(postings) {
264
272
  return;
265
273
  const arena = new Int32Array(lanes);
266
274
  let offset = 0;
267
- const deadline = createDeadline(8);
275
+ const deadline = options?.deadline ?? createDeadline(8);
268
276
  for (const plan of planned) {
269
277
  if (postings.get(plan.token) === plan.list &&
270
278
  plan.list.length * 2 === plan.width &&
271
279
  offset + plan.width <= arena.length) {
272
280
  offset = plan.list.adoptArena(arena, offset);
273
281
  }
274
- if (deadline.expired())
282
+ if (deadline.expired()) {
283
+ await options?.beforeYield?.();
275
284
  await yieldIfOverBudget(deadline);
285
+ }
276
286
  }
277
287
  }
278
288
  /**
@@ -375,7 +385,8 @@ export class WordForwardEntry {
375
385
  }
376
386
  /** Pack a tokenizer tally. Insertion order is preserved. */
377
387
  static fromTally(tally) {
378
- const tokenNames = new Array(tally.size);
388
+ const tokenNames = [];
389
+ tokenNames.length = tally.size;
379
390
  const lineCounts = new Int32Array(tally.size);
380
391
  let i = 0;
381
392
  for (const [token, count] of tally) {
@@ -390,7 +401,7 @@ export class WordForwardEntry {
390
401
  * Bytes per forward entry: one pointer-compressed slot for the shared token
391
402
  * string and one `Int32` count lane.
392
403
  */
393
- export const WORD_FORWARD_ENTRY_BYTES = 8;
404
+ const WORD_FORWARD_ENTRY_BYTES = 8;
394
405
  /**
395
406
  * The word index's dense file-id space: canonical path key → integer id →
396
407
  * display path.
@@ -725,17 +725,6 @@ async function updateWordIndexDocumentAsyncUnsafe(index, doc, shouldContinue) {
725
725
  commitWordIndexDocumentReplacement(index, doc, perTokenHits, docLength, startedAt);
726
726
  return true;
727
727
  }
728
- /** Bounds shared by every word-index build path — keep the walk off the
729
- * critical path on large repos: cap the file count, and skip files too large
730
- * to be hand-written source (generated/bundled output the source filter
731
- * didn't already exclude).
732
- *
733
- * Deprecated (#776): `collectWordIndexDocs` below no longer reads this
734
- * constant directly — it derives its cap from `getWordIndexMaxFilesDerived`
735
- * (project-scale.ts's `maxProjectFiles` knob), which reproduces this same
736
- * 6,000 default at the default base. Kept exported for tests/callers that
737
- * still reference the literal. */
738
- export const WORD_INDEX_MAX_FILES = 6000;
739
728
  export const WORD_INDEX_MAX_BYTES = 512 * 1024;
740
729
  /**
741
730
  * Collect the bounded `{path, content}` doc set `buildWordIndex` consumes —
@@ -748,9 +737,9 @@ export const WORD_INDEX_MAX_BYTES = 512 * 1024;
748
737
  export async function collectWordIndexDocs(root, shouldContinue = () => true, preflightFiles) {
749
738
  const { collectSourceFilesAsync } = await import("./source-filter.js");
750
739
  // #747 hardening: pass the cap INTO the walk — without it,
751
- // `collectSourceFilesAsync` defaults to an unbounded traversal and the
752
- // `WORD_INDEX_MAX_FILES` slice below only trims the result AFTER the whole
753
- // tree (all of $HOME, on a misrooted cwd) has already been enumerated.
740
+ // `collectSourceFilesAsync` defaults to an unbounded traversal and any
741
+ // slice below would only trim the result AFTER the whole tree (all of
742
+ // $HOME, on a misrooted cwd) has already been enumerated.
754
743
  // #760: the walk is additionally bounded by source-filter's default
755
744
  // visited-entry budget (DEFAULT_MAX_SCAN_ENTRIES), so a mixed tree with few
756
745
  // source files among a huge pile of non-source files can't force a
@@ -811,7 +800,7 @@ export async function collectWordIndexDocs(root, shouldContinue = () => true, pr
811
800
  // Node's fs.promises.stat runs on libuv's threadpool (default 4 slots), so
812
801
  // real parallelism tops out there; the surplus workers are queue depth that
813
802
  // keeps the pool saturated without starving other threadpool consumers.
814
- export const WORD_INDEX_STAT_CONCURRENCY = 8;
803
+ const WORD_INDEX_STAT_CONCURRENCY = 8;
815
804
  const WORD_INDEX_INCREMENTAL_CHURN_THRESHOLD = 0.3;
816
805
  // A ratio alone misclassifies one stale file in a three-file project as dense.
817
806
  // Below this absolute floor, per-document replacement is bounded and cheaper
@@ -888,7 +877,8 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
888
877
  const statConcurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0
889
878
  ? Math.max(1, Math.floor(requestedConcurrency))
890
879
  : WORD_INDEX_STAT_CONCURRENCY;
891
- const statResults = new Array(walked.length);
880
+ const statResults = [];
881
+ statResults.length = walked.length;
892
882
  let cursor = 0;
893
883
  let superseded = false;
894
884
  const worker = async () => {
@@ -1091,7 +1081,11 @@ export async function refreshWordIndexIncrementally(index, root, shouldContinue
1091
1081
  timings,
1092
1082
  };
1093
1083
  }
1094
- export const WORD_INDEX_QUERY_FILTER_KEYS = ["lang", "file", "ext"];
1084
+ const WORD_INDEX_QUERY_FILTER_KEYS = [
1085
+ "lang",
1086
+ "file",
1087
+ "ext",
1088
+ ];
1095
1089
  /** Thrown by {@link parseWordIndexQuery}/{@link buildWordIndexQueryFilter} for
1096
1090
  * an unrecognized `key:` prefix or an unrecognized `lang:` value — a query
1097
1091
  * typo fails loudly with the supported list instead of silently degrading
@@ -1391,6 +1385,22 @@ export function centralityFromReverseDeps(index, reverseDeps, normalizeKey = (fi
1391
1385
  }
1392
1386
  /** Persisted word-index serialization format version. Bump on breaking format changes. */
1393
1387
  export const WORD_INDEX_FORMAT_VERSION = 2;
1388
+ const WORD_INDEX_INT32_MAX = 2_147_483_647;
1389
+ // A file id and line fit in one exact Number key through this boundary.
1390
+ const WORD_INDEX_PACKED_FILE_ID_MAX = 4_194_303;
1391
+ function isCanonicalWordIndexNumber(value) {
1392
+ return (typeof value === "number" &&
1393
+ Number.isFinite(value) &&
1394
+ Number.isSafeInteger(value) &&
1395
+ !Object.is(value, -0));
1396
+ }
1397
+ function isCanonicalWordIndexToken(token) {
1398
+ return (typeof token === "string" &&
1399
+ token.length >= 2 &&
1400
+ token === token.toLowerCase() &&
1401
+ /^[a-z0-9_$]+$/.test(token) &&
1402
+ !STOPWORDS.has(token));
1403
+ }
1394
1404
  const serializedWordIndexCaches = new WeakMap();
1395
1405
  let _lastSerializeWork;
1396
1406
  /** Test-only: work stats for the most recent {@link serializeWordIndex} call. */
@@ -1585,10 +1595,25 @@ export function deserializeWordIndex(data) {
1585
1595
  !Array.isArray(data.files) ||
1586
1596
  !Array.isArray(data.postings) ||
1587
1597
  !Array.isArray(data.docLengths) ||
1588
- !Array.isArray(data.fileMtimes) ||
1589
- data.fileMtimes.length !== data.files.length) {
1598
+ !Array.isArray(data.fileMtimes)) {
1590
1599
  return null;
1591
1600
  }
1601
+ let canonical = data.docLengths.length === data.files.length &&
1602
+ data.fileMtimes.length === data.files.length;
1603
+ let needsDeepSanitize = !Array.isArray(data.forward);
1604
+ if (needsDeepSanitize)
1605
+ canonical = false;
1606
+ const fileKeys = new Set();
1607
+ for (const file of data.files) {
1608
+ if (typeof file !== "string")
1609
+ return null;
1610
+ const key = wordIndexKey(file);
1611
+ if (fileKeys.has(key)) {
1612
+ canonical = false;
1613
+ needsDeepSanitize = true;
1614
+ }
1615
+ fileKeys.add(key);
1616
+ }
1592
1617
  const docLengths = new PathKeyedMap(wordIndexKey);
1593
1618
  // Slot i of the wire `files` array becomes file id `fileIdBySlot[i]`. Two
1594
1619
  // slots whose spellings fold to one key share one id, exactly as a build
@@ -1596,33 +1621,94 @@ export function deserializeWordIndex(data) {
1596
1621
  // posting identities for one document.
1597
1622
  const fileTable = new WordIndexFileTable();
1598
1623
  const fileIdBySlot = data.files.map((file) => fileTable.intern(wordIndexKey(file), file));
1624
+ const slotByFileId = new Map();
1625
+ fileIdBySlot.forEach((fileId, slot) => {
1626
+ if (!slotByFileId.has(fileId))
1627
+ slotByFileId.set(fileId, slot);
1628
+ });
1599
1629
  const fileMtimes = new PathKeyedMap(wordIndexKey);
1600
1630
  const fileSizes = new PathKeyedMap(wordIndexKey);
1601
- data.files.forEach((file, i) => docLengths.set(file, data.docLengths[i] ?? 0));
1602
- data.files.forEach((file, i) => fileMtimes.set(file, data.fileMtimes[i] ?? 0));
1631
+ data.files.forEach((file, i) => {
1632
+ const value = data.docLengths[i];
1633
+ if (!isCanonicalWordIndexNumber(value) || value < 0)
1634
+ canonical = false;
1635
+ docLengths.set(file, isCanonicalWordIndexNumber(value) && value >= 0 ? value : 0);
1636
+ });
1637
+ data.files.forEach((file, i) => {
1638
+ const value = data.fileMtimes[i];
1639
+ if (typeof value !== "number" || !Number.isFinite(value))
1640
+ canonical = false;
1641
+ fileMtimes.set(file, typeof value === "number" && Number.isFinite(value) ? value : 0);
1642
+ });
1603
1643
  // #1105: `fileSizes` is optional on the wire (pre-#1105 snapshots omit it).
1604
1644
  // Only populate when the array is present AND parallel to `files`; otherwise
1605
1645
  // leave it empty so the refresh gate re-reads every file once to repopulate,
1606
1646
  // rather than trusting a bogus/misaligned size. Never treated as "current".
1607
1647
  if (Array.isArray(data.fileSizes) &&
1608
1648
  data.fileSizes.length === data.files.length) {
1609
- data.files.forEach((file, i) => fileSizes.set(file, data.fileSizes?.[i] ?? 0));
1649
+ data.files.forEach((file, i) => {
1650
+ const value = data.fileSizes?.[i];
1651
+ if (!isCanonicalWordIndexNumber(value) || value < 0)
1652
+ canonical = false;
1653
+ fileSizes.set(file, isCanonicalWordIndexNumber(value) && value >= 0 ? value : 0);
1654
+ });
1655
+ }
1656
+ else {
1657
+ canonical = false;
1610
1658
  }
1611
1659
  const postings = new Map();
1612
- for (const [token, flat] of data.postings) {
1613
- if (typeof token !== "string" || !Array.isArray(flat))
1660
+ const postingTokens = new Set();
1661
+ const postingFileCounts = new Map();
1662
+ for (const posting of data.postings) {
1663
+ if (!Array.isArray(posting) || posting.length !== 2) {
1664
+ canonical = false;
1665
+ needsDeepSanitize = true;
1666
+ continue;
1667
+ }
1668
+ const [token, flat] = posting;
1669
+ if (!isCanonicalWordIndexToken(token) ||
1670
+ postingTokens.has(token) ||
1671
+ !Array.isArray(flat) ||
1672
+ flat.length === 0 ||
1673
+ flat.length % 2 !== 0) {
1674
+ canonical = false;
1675
+ needsDeepSanitize = true;
1614
1676
  continue;
1677
+ }
1678
+ postingTokens.add(token);
1615
1679
  const lanes = [];
1616
- for (let i = 0; i + 1 < flat.length; i += 2) {
1617
- const fileId = fileIdBySlot[flat[i]];
1680
+ const seenPairs = new Set();
1681
+ const observedCounts = new Map();
1682
+ for (let i = 0; i < flat.length; i += 2) {
1683
+ const slot = flat[i];
1618
1684
  const line = flat[i + 1];
1619
- if (typeof fileId === "number" && typeof line === "number") {
1620
- lanes.push(fileId, line);
1685
+ if (!isCanonicalWordIndexNumber(slot) ||
1686
+ slot < 0 ||
1687
+ slot >= fileIdBySlot.length ||
1688
+ !isCanonicalWordIndexNumber(line) ||
1689
+ line < 1 ||
1690
+ line > WORD_INDEX_INT32_MAX) {
1691
+ canonical = false;
1692
+ needsDeepSanitize = true;
1693
+ continue;
1621
1694
  }
1695
+ const fileId = fileIdBySlot[slot];
1696
+ const pairKey = fileId <= WORD_INDEX_PACKED_FILE_ID_MAX
1697
+ ? fileId * (WORD_INDEX_INT32_MAX + 1) + line
1698
+ : `${fileId}:${line}`;
1699
+ if (seenPairs.has(pairKey)) {
1700
+ canonical = false;
1701
+ needsDeepSanitize = true;
1702
+ }
1703
+ seenPairs.add(pairKey);
1704
+ lanes.push(fileId, line);
1705
+ observedCounts.set(fileId, (observedCounts.get(fileId) ?? 0) + 1);
1622
1706
  }
1623
- if (lanes.length > 0) {
1624
- postings.set(token, WordPostingList.fromLanes(token, lanes));
1625
- }
1707
+ if (lanes.length === 0)
1708
+ continue;
1709
+ const list = WordPostingList.fromLanes(token, lanes);
1710
+ postings.set(token, list);
1711
+ postingFileCounts.set(token, observedCounts);
1626
1712
  }
1627
1713
  // Every list above was sized exactly from the wire payload, so there is no
1628
1714
  // growth slack to release — but the per-list `ArrayBuffer` headers are the
@@ -1631,34 +1717,128 @@ export function deserializeWordIndex(data) {
1631
1717
  compactPostingsIntoArena(postings);
1632
1718
  let forward;
1633
1719
  if (Array.isArray(data.forward)) {
1634
- forward = new PathKeyedMap(wordIndexKey);
1635
- for (const entry of data.forward) {
1636
- if (!Array.isArray(entry) || entry.length !== 2)
1720
+ const candidate = data.files.map(() => new Map());
1721
+ const forwardTotals = new Map();
1722
+ if (data.forward.length !== data.files.length)
1723
+ canonical = false;
1724
+ if (data.forward.length !== data.files.length)
1725
+ needsDeepSanitize = true;
1726
+ for (const [entryIndex, entry] of data.forward.entries()) {
1727
+ if (!Array.isArray(entry) || entry.length !== 2) {
1728
+ canonical = false;
1729
+ needsDeepSanitize = true;
1637
1730
  continue;
1731
+ }
1638
1732
  const [fileIdx, tokenCounts] = entry;
1639
- const file = data.files[fileIdx];
1640
- if (typeof file !== "string" || !Array.isArray(tokenCounts))
1733
+ if (!isCanonicalWordIndexNumber(fileIdx) ||
1734
+ fileIdx !== entryIndex ||
1735
+ fileIdx < 0 ||
1736
+ fileIdx >= data.files.length ||
1737
+ !Array.isArray(tokenCounts)) {
1738
+ canonical = false;
1739
+ needsDeepSanitize = true;
1641
1740
  continue;
1642
- const perToken = new Map();
1741
+ }
1643
1742
  for (const pair of tokenCounts) {
1644
- if (!Array.isArray(pair) || pair.length !== 2)
1743
+ if (!Array.isArray(pair) ||
1744
+ pair.length !== 2 ||
1745
+ !isCanonicalWordIndexToken(pair[0]) ||
1746
+ !isCanonicalWordIndexNumber(pair[1]) ||
1747
+ pair[1] < 1 ||
1748
+ pair[1] > WORD_INDEX_INT32_MAX ||
1749
+ candidate[fileIdx].has(pair[0])) {
1750
+ canonical = false;
1751
+ needsDeepSanitize = true;
1645
1752
  continue;
1646
- const [token, count] = pair;
1647
- if (typeof token === "string" && typeof count === "number") {
1648
- // Point at the canonical instance the postings map holds, not
1649
- // this document’s own JSON.parse allocation (#2069).
1650
- perToken.set(postings.get(token)?.token ?? token, count);
1651
1753
  }
1754
+ candidate[fileIdx].set(pair[0], pair[1]);
1755
+ forwardTotals.set(pair[0], (forwardTotals.get(pair[0]) ?? 0) + pair[1]);
1652
1756
  }
1653
- forward.set(file, WordForwardEntry.fromTally(perToken));
1757
+ }
1758
+ let forwardMatchesPostings = true;
1759
+ for (const [token, list] of postings) {
1760
+ if (forwardTotals.get(token) !== list.length) {
1761
+ forwardMatchesPostings = false;
1762
+ }
1763
+ const observed = postingFileCounts.get(token);
1764
+ if (!observed) {
1765
+ forwardMatchesPostings = false;
1766
+ continue;
1767
+ }
1768
+ for (const [fileId, count] of observed) {
1769
+ const slot = slotByFileId.get(fileId);
1770
+ if (slot === undefined || candidate[slot].get(token) !== count) {
1771
+ forwardMatchesPostings = false;
1772
+ }
1773
+ }
1774
+ }
1775
+ for (const token of forwardTotals.keys()) {
1776
+ if (!postingTokens.has(token))
1777
+ forwardMatchesPostings = false;
1778
+ }
1779
+ if (!forwardMatchesPostings)
1780
+ canonical = false;
1781
+ if (!forwardMatchesPostings)
1782
+ needsDeepSanitize = true;
1783
+ forward = new PathKeyedMap(wordIndexKey);
1784
+ for (let i = 0; i < data.files.length; i += 1) {
1785
+ forward.set(data.files[i], WordForwardEntry.fromTally(candidate[i]));
1654
1786
  }
1655
1787
  }
1656
- return {
1788
+ else if (Object.prototype.hasOwnProperty.call(data, "forward")) {
1789
+ canonical = false;
1790
+ needsDeepSanitize = true;
1791
+ }
1792
+ if (needsDeepSanitize) {
1793
+ const actualForwardCounts = data.files.map(() => new Map());
1794
+ for (const [token, list] of postings) {
1795
+ const seenPairs = new Set();
1796
+ const sanitized = new WordPostingList(token, list.length);
1797
+ for (let i = 0; i < list.length; i += 1) {
1798
+ const fileId = list.fileIdAt(i);
1799
+ const line = list.lineAt(i);
1800
+ const pairKey = `${fileId}:${line}`;
1801
+ if (seenPairs.has(pairKey))
1802
+ continue;
1803
+ seenPairs.add(pairKey);
1804
+ sanitized.push(fileId, line);
1805
+ const slot = slotByFileId.get(fileId);
1806
+ if (slot !== undefined) {
1807
+ const counts = actualForwardCounts[slot];
1808
+ counts.set(token, (counts.get(token) ?? 0) + 1);
1809
+ }
1810
+ }
1811
+ postings.set(token, sanitized);
1812
+ }
1813
+ compactPostingsIntoArena(postings);
1814
+ if (Array.isArray(data.forward)) {
1815
+ forward = new PathKeyedMap(wordIndexKey);
1816
+ for (let i = 0; i < data.files.length; i += 1) {
1817
+ const canonicalSlot = slotByFileId.get(fileIdBySlot[i]) ?? i;
1818
+ forward.set(data.files[i], WordForwardEntry.fromTally(actualForwardCounts[canonicalSlot]));
1819
+ }
1820
+ }
1821
+ }
1822
+ const expectedTotalTokens = [...docLengths.values()].reduce((total, length) => total + length, 0);
1823
+ const totalTokens = isCanonicalWordIndexNumber(expectedTotalTokens)
1824
+ ? expectedTotalTokens
1825
+ : 0;
1826
+ if (!isCanonicalWordIndexNumber(data.totalTokens) ||
1827
+ data.totalTokens !== totalTokens) {
1828
+ canonical = false;
1829
+ }
1830
+ if (typeof data.indexedFileCount !== "number" ||
1831
+ data.indexedFileCount !== docLengths.size) {
1832
+ canonical = false;
1833
+ }
1834
+ if (typeof data.truncated !== "boolean")
1835
+ canonical = false;
1836
+ const index = {
1657
1837
  postings,
1658
1838
  fileTable,
1659
1839
  docLengths,
1660
- totalTokens: typeof data.totalTokens === "number" ? data.totalTokens : 0,
1661
- docCount: data.files.length,
1840
+ totalTokens,
1841
+ docCount: docLengths.size,
1662
1842
  truncated: data.truncated === true,
1663
1843
  forward,
1664
1844
  fileMtimes,
@@ -1667,7 +1847,32 @@ export function deserializeWordIndex(data) {
1667
1847
  // starts from the exact post-compaction store count (#2117).
1668
1848
  postingStoreCount: countPostingBackingStores(postings),
1669
1849
  recompactFlight: createSingleFlight(),
1850
+ // A deserialized index is a fresh object distinct from whichever index
1851
+ // produced `data`. Without this, every per-edit dirty mark on a
1852
+ // session-start reload (#2068) is a no-op against `undefined`, and
1853
+ // serializeWordIndexIncrementally's `!dirty` branch then returns the
1854
+ // stale cached wire view forever, silently dropping every later edit
1855
+ // from the persisted snapshot.
1856
+ dirtyFiles: new Set(),
1670
1857
  };
1858
+ // Seed the wire cache from the snapshot just loaded so the FIRST persist
1859
+ // of a reloaded session (session-start's `snapshotSaveSyncMs`, #2068) also
1860
+ // takes the bounded incremental path instead of paying a full rebuild —
1861
+ // mirrors exactly what one priming `serializeWordIndex` call would cache.
1862
+ const tokensByFile = new Map();
1863
+ if (forward) {
1864
+ for (const file of data.files) {
1865
+ tokensByFile.set(wordIndexKey(file), new Set(forward.get(file)?.keys() ?? []));
1866
+ }
1867
+ }
1868
+ if (canonical) {
1869
+ serializedWordIndexCaches.set(index, {
1870
+ serialized: data,
1871
+ slotByFileId,
1872
+ tokensByFile,
1873
+ });
1874
+ }
1875
+ return index;
1671
1876
  }
1672
1877
  const buildStatuses = new Map();
1673
1878
  export function getWordIndexBuildStatus(cwd) {
@@ -39,11 +39,18 @@
39
39
  import * as fs from "node:fs";
40
40
  import * as os from "node:os";
41
41
  import * as path from "node:path";
42
+ import { PROJECT_CONFIG_BASENAMES } from "./config-locations.js";
42
43
  import { logLatency } from "./latency-logger.js";
43
44
  import { isAtOrAboveHomeDir, nameMatchesMarkerGlob, walkUpDirs, } from "./path-utils.js";
44
45
  /** Depth cap on any upward marker walk — mirrors `findNearestMarkerRoot`'s bound. */
45
46
  const MAX_WALK_DEPTH = 64;
46
- const PI_LENS_CONFIG_BASENAMES = [".pi-lens.json", "pi-lens.json"];
47
+ /**
48
+ * The project config basenames, canonical first — the SHARED table, not a
49
+ * fourth copy of the pair (#2426 review round 2). `config-locations.ts` derives
50
+ * it from the one location table the resolver walks, so a change there cannot
51
+ * leave this marker index preferring a different file than the loaders do.
52
+ */
53
+ const PI_LENS_CONFIG_BASENAMES = PROJECT_CONFIG_BASENAMES;
47
54
  /** Per-directory marker cache — the shared seam every consumer reads through. */
48
55
  const dirMarkerCache = new Map();
49
56
  /** Downstream memo clears that must follow the topology index reset. */
@@ -15,12 +15,15 @@ import { classifyProbeFailure, createAvailabilityLatch, logAvailabilityDecision,
15
15
  // presence of one is the repo's deliberate opt-in: it carries the author's
16
16
  // chosen severities/ignores, so we let zizmor findings BLOCK in that workspace
17
17
  // (advisory-only otherwise, like Opengrep's local-rules gate).
18
- export const LOCAL_ZIZMOR_CONFIG_NAMES = [
18
+ const LOCAL_ZIZMOR_CONFIG_NAMES = [
19
19
  path.join(".github", "zizmor.yml"),
20
20
  path.join(".github", "zizmor.yaml"),
21
21
  "zizmor.yml",
22
22
  "zizmor.yaml",
23
23
  ];
24
+ // Deliberately UNCEILINGED at $HOME (refs #2472 review round 3, F1): a
25
+ // user-level `~/zizmor.yml` (or `~/.github/zizmor.yml`) is a legitimate
26
+ // global config, not an escaped-workspace accident.
24
27
  export function findLocalZizmorConfig(startDir) {
25
28
  return findLocalToolConfig(startDir, LOCAL_ZIZMOR_CONFIG_NAMES);
26
29
  }