@herbertgao/pi-extensions 2026.10.1 → 2026.10.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 (707) hide show
  1. package/README.md +13 -9
  2. package/THIRD_PARTY_NOTICES.md +2 -2
  3. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
  4. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
  5. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
  6. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
  7. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
  8. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
  9. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
  10. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  11. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
  12. package/node_modules/@dietrichgebert/ponytail/README.md +68 -72
  13. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +7 -0
  14. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
  15. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
  16. package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
  17. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
  18. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -32
  19. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
  20. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +17 -16
  21. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +9 -10
  22. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -57
  23. package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
  24. package/node_modules/@herbertgao/pi-agency/README.md +40 -0
  25. package/node_modules/@herbertgao/pi-agency/package.json +53 -0
  26. package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
  32. package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
  33. package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
  34. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  35. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  36. package/node_modules/@tifan/pi-recap/package.json +1 -1
  37. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  38. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  39. package/node_modules/pi-antigravity/README.md +23 -2
  40. package/node_modules/pi-antigravity/package.json +5 -3
  41. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  42. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  43. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  44. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  45. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  46. package/node_modules/pi-antigravity/src/index.ts +52 -137
  47. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  48. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  49. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  50. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  51. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  52. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  53. package/node_modules/pi-lens/CHANGELOG.md +1064 -0
  54. package/node_modules/pi-lens/README.md +115 -25
  55. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  56. package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
  57. package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
  58. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  59. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  60. package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
  61. package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
  62. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  63. package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
  64. package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
  65. package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
  66. package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
  67. package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
  68. package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
  69. package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
  70. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  71. package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
  72. package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
  73. package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
  74. package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
  75. package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
  76. package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
  77. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  78. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  79. package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
  80. package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
  81. package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
  82. package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
  83. package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
  84. package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
  85. package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
  86. package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
  87. package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
  88. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  89. package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
  90. package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
  91. package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
  92. package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
  93. package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
  94. package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
  95. package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
  96. package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
  97. package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
  98. package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
  99. package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
  100. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  101. package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
  102. package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
  103. package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
  104. package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
  105. package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
  106. package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
  107. package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
  108. package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
  109. package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
  110. package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
  111. package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
  112. package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
  113. package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
  114. package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
  115. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  116. package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
  117. package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
  118. package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
  119. package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
  120. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  121. package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
  122. package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
  123. package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
  124. package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
  125. package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
  126. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  127. package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
  128. package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
  129. package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
  130. package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
  131. package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
  132. package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
  133. package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
  134. package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
  135. package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
  136. package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
  137. package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
  138. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  139. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  140. package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
  141. package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
  142. package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
  143. package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
  144. package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
  145. package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
  146. package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
  147. package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
  148. package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
  149. package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
  150. package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
  151. package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
  152. package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
  153. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  154. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  155. package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
  156. package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
  157. package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
  158. package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
  159. package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
  160. package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
  161. package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
  162. package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
  163. package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
  164. package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
  165. package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
  166. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  167. package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
  168. package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
  169. package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
  170. package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
  171. package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
  172. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  173. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  174. package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
  175. package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
  176. package/node_modules/pi-lens/dist/index.js +16456 -8335
  177. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  178. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  179. package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
  180. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  181. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  182. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  183. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  184. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  185. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  186. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
  187. package/node_modules/pi-lens/docs/configuration.md +20 -1
  188. package/node_modules/pi-lens/docs/environment-variables.md +52 -2
  189. package/node_modules/pi-lens/docs/features.md +8 -4
  190. package/node_modules/pi-lens/docs/globalconfig.md +1 -0
  191. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  192. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
  193. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  194. package/node_modules/pi-lens/docs/mcp.md +21 -5
  195. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  196. package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
  197. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  198. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
  199. package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
  200. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  201. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
  202. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  203. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  204. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  205. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  206. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  207. package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
  208. package/node_modules/pi-lens/docs/settings.md +1 -0
  209. package/node_modules/pi-lens/docs/usage.md +5 -1
  210. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  211. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  212. package/node_modules/pi-lens/package.json +18 -6
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  215. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  216. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  217. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  218. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  219. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  220. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  221. package/package.json +15 -10
  222. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
  223. package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
  224. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
  225. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
  226. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  227. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  228. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  229. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  230. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  231. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  232. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  233. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  234. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  235. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  236. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  237. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  238. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  239. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  240. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  241. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  242. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  243. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  244. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  245. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  246. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  247. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  248. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  249. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  250. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  251. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  252. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  253. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  254. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  255. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  256. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  257. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  258. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  259. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  260. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  261. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  262. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  263. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  264. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  265. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  266. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  267. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  268. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  269. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  270. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  271. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  272. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  273. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  274. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  275. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  276. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  277. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  278. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  279. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  280. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  281. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  282. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  283. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  284. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  285. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  286. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  287. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  288. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  289. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  290. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  291. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  292. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  293. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  294. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  295. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  296. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  297. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  298. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  299. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  300. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  301. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  302. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  303. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  304. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  305. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  306. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  307. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  308. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  309. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  310. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  311. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  312. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  313. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  314. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  315. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  316. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  317. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  318. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  319. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  320. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  321. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  322. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  323. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  324. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  325. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  326. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  327. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  328. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  329. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  330. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  331. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  332. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  333. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  334. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  335. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  336. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  337. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  338. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  339. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  340. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  341. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  342. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  343. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  344. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  345. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  346. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  347. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  348. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  349. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  350. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  351. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  352. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  353. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  354. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  355. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  356. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  357. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  358. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  359. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  360. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  361. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  362. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  363. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  364. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  365. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  366. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  367. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  368. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  369. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  370. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  371. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  372. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  373. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  374. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  375. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  376. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  377. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  378. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  379. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  380. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  381. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  382. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  383. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  384. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  385. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  386. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  387. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  388. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  389. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  390. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  391. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  392. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  393. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  394. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  395. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  396. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  397. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  398. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  399. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  400. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  401. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  402. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  403. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  404. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  405. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  406. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  407. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  408. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  409. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  410. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  411. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  412. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  413. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  414. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  415. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  416. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  417. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  418. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  419. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  420. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  421. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  422. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  423. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  424. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  425. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  426. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  427. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  428. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  429. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  430. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  431. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  432. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  433. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  434. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  435. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  436. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  437. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  438. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  439. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  440. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  441. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  442. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  443. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  444. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  445. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  446. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  447. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  448. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  449. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  450. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  451. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  452. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  453. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  454. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  455. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  456. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  457. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  458. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  459. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  460. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  461. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  462. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  463. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  464. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  465. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  466. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  467. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  468. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  469. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  470. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  471. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  472. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  473. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  474. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  475. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  476. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  477. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  478. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  479. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  480. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  481. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  482. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  483. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  484. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  485. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  486. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  487. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  488. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  489. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  490. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  491. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  492. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  493. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  494. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  495. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  496. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  497. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  498. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  499. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  500. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  501. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  502. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  503. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  504. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  505. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  506. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  507. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  508. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  509. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  510. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  511. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  512. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  513. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  514. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  515. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  516. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  517. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  518. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  519. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  520. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  521. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  522. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  523. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  524. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  525. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  526. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  527. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  528. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  529. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  530. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  531. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  532. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  533. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  534. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  535. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  536. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  537. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  538. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  539. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  540. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  541. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  542. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  543. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  544. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  545. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  546. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  547. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  548. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  549. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  550. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  551. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  552. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  553. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  554. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  555. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  556. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  557. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  558. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  559. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  560. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  561. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  562. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  563. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  564. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  565. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  566. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  567. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  568. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  569. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  570. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  571. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  572. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  573. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  574. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  575. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  576. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  577. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  578. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  579. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  580. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  581. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  582. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  583. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  584. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  585. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  586. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  587. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  588. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  589. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  590. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  591. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  592. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  593. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  594. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  595. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  596. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  597. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  598. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  599. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  600. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  601. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  602. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  603. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  604. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  605. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  606. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  607. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  608. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  609. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  610. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  611. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  612. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  613. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  614. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  615. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  616. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  617. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  618. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  619. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  620. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  621. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  622. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  623. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  624. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  625. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  626. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  627. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  628. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  629. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  630. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  631. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  632. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  633. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  634. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  635. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  636. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  637. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  638. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  639. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  640. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  641. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  642. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  643. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  644. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  645. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  646. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  647. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  648. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  649. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  650. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  651. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  652. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  653. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  654. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  655. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  656. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  657. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  658. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  659. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  660. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  661. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  662. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  663. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  664. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  665. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  666. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  667. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  668. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  669. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  670. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  671. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  672. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  673. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  674. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  675. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  676. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  677. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  678. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  679. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  680. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  681. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  682. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  683. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  684. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  685. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  686. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  687. package/node_modules/pi-lens/dist/i18n.js +0 -69
  688. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  689. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  690. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  691. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  692. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  693. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  694. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  695. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  696. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  697. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  698. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  699. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  700. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  701. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  702. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  703. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  704. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  705. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  706. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  707. package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
@@ -1,1653 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { Worker } from "node:worker_threads";
5
- import { gunzipSync, gzipSync } from "node:zlib";
6
- import { writeFileAtomic } from "./atomic-write.js";
7
- import { BoundedFifoMap } from "./bounded-cache.js";
8
- import { getProjectDataDir } from "./file-utils.js";
9
- import { incrementDegradationCount } from "./degradation-ledger.js";
10
- import { readJsonCache } from "./json-cache-read.js";
11
- import { logLatency } from "./latency-logger.js";
12
- import { normalizeMapKey } from "./path-utils.js";
13
- import { fingerprintProjectSnapshotJson } from "./project-snapshot-fingerprint.js";
14
- import { detectProjectConventions, } from "./project-conventions.js";
15
- import { deserializeWordIndex, serializeWordIndex, } from "./word-index.js";
16
- // v2: added `wordIndex` (identifier inverted index + BM25, #162). Bumping the
17
- // version invalidates pre-v2 snapshots so they rebuild with the new field.
18
- export const PROJECT_SNAPSHOT_VERSION = 2;
19
- function parseSequenceIndex(value) {
20
- if (!value || typeof value !== "object")
21
- return undefined;
22
- const index = value;
23
- if (typeof index.projectSeq !== "number")
24
- return undefined;
25
- if (!Array.isArray(index.fileSeqByPath))
26
- return undefined;
27
- const fileSeqByPath = index.fileSeqByPath.filter((entry) => Array.isArray(entry) &&
28
- typeof entry[0] === "string" &&
29
- typeof entry[1] === "number");
30
- return { projectSeq: index.projectSeq, fileSeqByPath };
31
- }
32
- // #958 item 2: the canonical snapshot body is now streamed gzip
33
- // (`project-snapshot.json.gz`), written by a worker thread off the save path
34
- // (see the persist plumbing below). The previous uncompressed
35
- // `project-snapshot.json` remains readable for ONE compatibility release so an
36
- // upgrading user doesn't lose their snapshot — see loadSnapshotBody's legacy
37
- // fallback. gzip measured 5-10x on top of the #957 compaction win (the
38
- // review-graph's own measurement was 60MB → 1.4MB).
39
- export function getProjectSnapshotPath(cwd) {
40
- return path.join(getProjectDataDir(cwd), "cache", "project-snapshot.json.gz");
41
- }
42
- /**
43
- * Pre-#958 uncompressed body path. Read as a one-release fallback when no
44
- * `.json.gz` is present; deleted whenever a fresh gz body is promoted so the
45
- * two never coexist.
46
- */
47
- export function getProjectSnapshotLegacyPath(cwd) {
48
- return path.join(getProjectDataDir(cwd), "cache", "project-snapshot.json");
49
- }
50
- export function getProjectSnapshotMetaPath(cwd) {
51
- return path.join(getProjectDataDir(cwd), "cache", "project-snapshot.meta.json");
52
- }
53
- export function isProjectSnapshotFresh(snapshot, currentProjectSeq) {
54
- return (!!snapshot &&
55
- snapshot.version === PROJECT_SNAPSHOT_VERSION &&
56
- snapshot.seq === currentProjectSeq);
57
- }
58
- function parseSnapshot(value) {
59
- if (!value || typeof value !== "object")
60
- return null;
61
- const snapshot = value;
62
- if (snapshot.version !== PROJECT_SNAPSHOT_VERSION)
63
- return null;
64
- if (typeof snapshot.projectRoot !== "string")
65
- return null;
66
- if (typeof snapshot.generatedAt !== "string")
67
- return null;
68
- if (typeof snapshot.seq !== "number")
69
- return null;
70
- if (!Array.isArray(snapshot.cachedExports))
71
- return null;
72
- return {
73
- version: PROJECT_SNAPSHOT_VERSION,
74
- projectRoot: snapshot.projectRoot,
75
- generatedAt: snapshot.generatedAt,
76
- seq: snapshot.seq,
77
- files: snapshot.files ?? {},
78
- symbols: snapshot.symbols ?? {},
79
- reverseDeps: snapshot.reverseDeps ?? {},
80
- cachedExports: snapshot.cachedExports.filter((entry) => Array.isArray(entry) &&
81
- typeof entry[0] === "string" &&
82
- typeof entry[1] === "string"),
83
- sequenceIndex: parseSequenceIndex(snapshot.sequenceIndex),
84
- wordIndex: snapshot.wordIndex,
85
- projectRulesScan: snapshot.projectRulesScan,
86
- startupScan: snapshot.startupScan,
87
- languageProfile: snapshot.languageProfile,
88
- conventions: snapshot.conventions,
89
- };
90
- }
91
- function parseSnapshotMeta(value) {
92
- if (!value || typeof value !== "object")
93
- return null;
94
- const meta = value;
95
- if (typeof meta.version !== "number")
96
- return null;
97
- if (typeof meta.seq !== "number")
98
- return null;
99
- return {
100
- timestamp: typeof meta.timestamp === "string" ? meta.timestamp : "",
101
- version: meta.version,
102
- seq: meta.seq,
103
- fingerprint: typeof meta.fingerprint === "string" &&
104
- /^[a-f0-9]{64}$/.test(meta.fingerprint)
105
- ? meta.fingerprint
106
- : undefined,
107
- gzBytes: typeof meta.gzBytes === "number" &&
108
- Number.isInteger(meta.gzBytes) &&
109
- meta.gzBytes > 0
110
- ? meta.gzBytes
111
- : undefined,
112
- sequenceIndex: parseSequenceIndex(meta.sequenceIndex),
113
- };
114
- }
115
- /**
116
- * Read the tiny meta sidecar (`project-snapshot.meta.json`) WITHOUT parsing
117
- * the (potentially 40-112MB) snapshot body. Written on every save; absent on
118
- * legacy installs — callers must treat a `null` return as "no opinion" and
119
- * fall through to parsing the body. #947.
120
- */
121
- export function readProjectSnapshotMeta(cwd) {
122
- const meta = readJsonCache(getProjectSnapshotMetaPath(cwd), (parsed) => parseSnapshotMeta(parsed) ?? undefined);
123
- return meta ?? null;
124
- }
125
- /**
126
- * Cheap staleness verdict from the meta sidecar alone. When this returns
127
- * true, the snapshot body CANNOT be fresh (isProjectSnapshotFresh would
128
- * reject it on the same two fields), so the expensive body parse can be
129
- * skipped entirely. #947.
130
- */
131
- export function isProjectSnapshotMetaStale(meta, currentProjectSeq) {
132
- return (meta.version !== PROJECT_SNAPSHOT_VERSION || meta.seq !== currentProjectSeq);
133
- }
134
- export const SNAPSHOT_PARSE_CACHE_MAX = 4;
135
- // #957 review: the cache exists to avoid re-parsing NORMAL snapshots within a
136
- // session. A 112MB-class body parses to hundreds of MB of heap — pinning that
137
- // for process lifetime inverts the win, so oversized bodies are simply never
138
- // cached (they re-parse per read, exactly the pre-#947 behavior). Measured
139
- // against the UNCOMPRESSED body size, never the (much smaller) gz file size.
140
- const SNAPSHOT_PARSE_CACHE_MAX_BYTES = 24 * 1024 * 1024;
141
- const snapshotParseCache = new BoundedFifoMap(SNAPSHOT_PARSE_CACHE_MAX);
142
- function withoutWordIndex(snapshot) {
143
- if (!snapshot?.wordIndex)
144
- return snapshot;
145
- const { wordIndex: _releasedPostings, ...stripped } = snapshot;
146
- return stripped;
147
- }
148
- function cacheParsedSnapshot(snapshotPath, entry) {
149
- // Refresh recency (delete then set moves the key to the newest position;
150
- // BoundedFifoMap itself never reorders an already-present key on set()).
151
- snapshotParseCache.delete(snapshotPath);
152
- snapshotParseCache.set(snapshotPath, entry);
153
- }
154
- const authoritativeSnapshots = new Map();
155
- export const PROJECT_SNAPSHOT_MAX_WARM_ROOTS = 8;
156
- const PROJECT_SNAPSHOT_IDLE_EVICT_MS_DEFAULT = 20 * 60_000;
157
- function projectSnapshotIdleEvictMs() {
158
- const value = Number.parseInt(process.env.PI_LENS_PROJECT_SNAPSHOT_IDLE_EVICT_MS ?? "", 10);
159
- return Number.isSafeInteger(value) && value > 0
160
- ? value
161
- : PROJECT_SNAPSHOT_IDLE_EVICT_MS_DEFAULT;
162
- }
163
- function clearAuthoritativeSnapshotTimer(entry) {
164
- if (entry.idleTimer)
165
- clearTimeout(entry.idleTimer);
166
- entry.idleTimer = undefined;
167
- }
168
- function deleteAuthoritativeSnapshot(key) {
169
- const entry = authoritativeSnapshots.get(key);
170
- if (entry)
171
- clearAuthoritativeSnapshotTimer(entry);
172
- authoritativeSnapshots.delete(key);
173
- }
174
- function scheduleAuthoritativeSnapshotEviction(key, entry) {
175
- clearAuthoritativeSnapshotTimer(entry);
176
- const generation = entry.lastUsedAt;
177
- entry.idleTimer = setTimeout(() => {
178
- entry.idleTimer = undefined;
179
- if (authoritativeSnapshots.get(key) !== entry ||
180
- entry.lastUsedAt !== generation)
181
- return;
182
- deleteAuthoritativeSnapshot(key);
183
- }, projectSnapshotIdleEvictMs());
184
- entry.idleTimer.unref?.();
185
- }
186
- function touchAuthoritativeSnapshot(key, entry) {
187
- entry.lastUsedAt = Date.now();
188
- scheduleAuthoritativeSnapshotEviction(key, entry);
189
- }
190
- function enforceAuthoritativeSnapshotCap() {
191
- while (authoritativeSnapshots.size > PROJECT_SNAPSHOT_MAX_WARM_ROOTS) {
192
- const victim = [...authoritativeSnapshots.entries()].sort(([, a], [, b]) => a.lastUsedAt - b.lastUsedAt)[0];
193
- if (!victim)
194
- return;
195
- clearAuthoritativeSnapshotTimer(victim[1]);
196
- deleteAuthoritativeSnapshot(victim[0]);
197
- }
198
- }
199
- /** Test-only cache keys, in LRU order from oldest to newest. */
200
- export function _getAuthoritativeSnapshotCacheKeysForTests() {
201
- return [...authoritativeSnapshots.entries()]
202
- .sort(([, a], [, b]) => a.lastUsedAt - b.lastUsedAt)
203
- .map(([key]) => key);
204
- }
205
- /** Test hook: drop all cached parses + authoritative writes (per-worker isolation). */
206
- export function _resetProjectSnapshotParseCacheForTests() {
207
- snapshotParseCache.clear();
208
- for (const entry of authoritativeSnapshots.values())
209
- clearAuthoritativeSnapshotTimer(entry);
210
- authoritativeSnapshots.clear();
211
- }
212
- /** Test hook: prove the parse-cache tier never owns serialized postings. */
213
- export function _projectSnapshotParseCacheRetainsWordIndexForTests() {
214
- return [...snapshotParseCache.values()].some((entry) => entry.snapshot?.wordIndex !== undefined);
215
- }
216
- /** Resolve which body file is currently on disk: gz canonical, else legacy. */
217
- function resolveSnapshotBodyPath(cwd) {
218
- const gzPath = getProjectSnapshotPath(cwd);
219
- try {
220
- const stat = fs.statSync(gzPath);
221
- return { path: gzPath, gz: true, mtimeMs: stat.mtimeMs, size: stat.size };
222
- }
223
- catch {
224
- /* fall through to the legacy uncompressed body */
225
- }
226
- const legacyPath = getProjectSnapshotLegacyPath(cwd);
227
- try {
228
- const stat = fs.statSync(legacyPath);
229
- return {
230
- path: legacyPath,
231
- gz: false,
232
- mtimeMs: stat.mtimeMs,
233
- size: stat.size,
234
- };
235
- }
236
- catch {
237
- return null;
238
- }
239
- }
240
- /**
241
- * Read + parse the body off disk, transparently gunzipping the `.json.gz`
242
- * canonical form and falling back to the pre-#958 uncompressed `.json` body
243
- * for one compatibility release. Returns the parsed snapshot (or null on any
244
- * failure) plus the UNCOMPRESSED byte length, so the caller can apply the
245
- * heap-bounded cache gate against the real body size rather than the gz size.
246
- */
247
- // Test-observable count of ACTUAL disk body reads (both gz and legacy), so the
248
- // #947 meta-gate tests can assert "body parsed / not parsed" independently of
249
- // the compression format — the pre-#958 tests keyed this off a readJsonCache
250
- // spy, which the gz path (gunzip + JSON.parse) legitimately bypasses.
251
- let _snapshotBodyReadCountForTests = 0;
252
- export function getSnapshotBodyReadCountForTests() {
253
- return _snapshotBodyReadCountForTests;
254
- }
255
- export function resetSnapshotBodyReadCountForTests() {
256
- _snapshotBodyReadCountForTests = 0;
257
- }
258
- function readSnapshotBody(bodyPath, gz) {
259
- _snapshotBodyReadCountForTests++;
260
- if (!gz) {
261
- const snapshot = readJsonCache(bodyPath, (parsed) => parseSnapshot(parsed) ?? undefined) ?? null;
262
- let rawBytes = 0;
263
- try {
264
- rawBytes = fs.statSync(bodyPath).size;
265
- }
266
- catch {
267
- /* best-effort */
268
- }
269
- return { snapshot, rawBytes };
270
- }
271
- try {
272
- const json = gunzipSync(fs.readFileSync(bodyPath)).toString("utf-8");
273
- const snapshot = parseSnapshot(JSON.parse(json)) ?? null;
274
- return { snapshot, rawBytes: Buffer.byteLength(json) };
275
- }
276
- catch (err) {
277
- // Corrupt / truncated gz, or a parse failure: fail open exactly like
278
- // readJsonCache does — a null return rebuilds the snapshot. Log it so a
279
- // corrupt body is diagnosable rather than indistinguishable from "no
280
- // snapshot yet" (both return a null snapshot here).
281
- logLatency({
282
- type: "phase",
283
- phase: "project_snapshot_body_corrupt",
284
- filePath: bodyPath,
285
- durationMs: 0,
286
- metadata: { error: err instanceof Error ? err.message : String(err) },
287
- });
288
- return { snapshot: null, rawBytes: 0 };
289
- }
290
- }
291
- /**
292
- * Scan a JSON value starting at `start` (skipping any leading whitespace)
293
- * and return the index one past its end. Handles strings, objects, arrays,
294
- * and bare scalars (numbers/`true`/`false`/`null`) — correctly skipping
295
- * nested strings/braces/brackets (respecting `\"` escapes) WITHOUT ever
296
- * constructing a JS value for them. This is the primitive
297
- * `stripTopLevelJsonKeys` (#1785 F5) is built on: `JSON.parse` has no way to
298
- * skip a subtree's construction cost — a reviver only prunes the
299
- * already-built result — so avoiding a field's parse cost requires never
300
- * handing its text to `JSON.parse` in the first place.
301
- */
302
- function skipJsonValue(text, start) {
303
- let i = start;
304
- while (i < text.length && /\s/.test(text[i]))
305
- i++;
306
- const ch = text[i];
307
- if (ch === '"') {
308
- i++;
309
- while (i < text.length) {
310
- if (text[i] === "\\") {
311
- i += 2;
312
- continue;
313
- }
314
- if (text[i] === '"') {
315
- i++;
316
- break;
317
- }
318
- i++;
319
- }
320
- return i;
321
- }
322
- if (ch === "{" || ch === "[") {
323
- const open = ch;
324
- const close = open === "{" ? "}" : "]";
325
- let depth = 0;
326
- let inString = false;
327
- for (; i < text.length; i++) {
328
- const c = text[i];
329
- if (inString) {
330
- if (c === "\\") {
331
- i++;
332
- continue;
333
- }
334
- if (c === '"')
335
- inString = false;
336
- continue;
337
- }
338
- if (c === '"') {
339
- inString = true;
340
- continue;
341
- }
342
- if (c === open)
343
- depth++;
344
- else if (c === close) {
345
- depth--;
346
- if (depth === 0) {
347
- i++;
348
- break;
349
- }
350
- }
351
- }
352
- return i;
353
- }
354
- // A bare scalar (number/true/false/null): scan to the next structural
355
- // character. Every snapshot value is one of the five JSON kinds above, so
356
- // this branch only ever fires for those scalars.
357
- while (i < text.length && !",}]".includes(text[i]))
358
- i++;
359
- return i;
360
- }
361
- /**
362
- * Reconstruct a JSON object literal with the given top-level keys removed,
363
- * without ever materializing their values as JS objects (#1785 F5). Used to
364
- * excise the expensive fields — `wordIndex`'s postings graph above all,
365
- * `files`/`symbols`/`reverseDeps` too — before `JSON.parse` ever sees them,
366
- * so a caller that only needs a few small top-level fields (`seq`,
367
- * `cachedExports`, `projectRulesScan`, …) doesn't pay to construct the
368
- * fields it's about to discard. Correctness relies only on the writer
369
- * (`JSON.stringify(snapshot)`, `clients/gzip-stage-write.ts`) never emitting
370
- * pretty-printed/indented output — compact `JSON.stringify` output is what
371
- * every persist path in this file produces.
372
- */
373
- function stripTopLevelJsonKeys(text, keysToStrip) {
374
- const objStart = text.indexOf("{");
375
- if (objStart === -1)
376
- return text;
377
- let out = text.slice(0, objStart + 1);
378
- let i = objStart + 1;
379
- let wroteField = false;
380
- while (i < text.length) {
381
- while (i < text.length && /\s/.test(text[i]))
382
- i++;
383
- if (text[i] === "}" || i >= text.length) {
384
- out += text.slice(i);
385
- break;
386
- }
387
- if (text[i] === ",") {
388
- i++;
389
- continue;
390
- }
391
- const keyStart = i;
392
- const keyTextEnd = skipJsonValue(text, keyStart);
393
- let key;
394
- try {
395
- key = JSON.parse(text.slice(keyStart, keyTextEnd));
396
- }
397
- catch {
398
- // Malformed key text — bail out to the untouched original rather
399
- // than risk producing invalid JSON; the caller's own JSON.parse
400
- // on the result will then fail loudly instead of silently.
401
- return text;
402
- }
403
- let j = keyTextEnd;
404
- while (j < text.length && /\s/.test(text[j]))
405
- j++;
406
- // text[j] is ':' for a well-formed object.
407
- const valueStart = j + 1;
408
- const valueEnd = skipJsonValue(text, valueStart);
409
- if (!keysToStrip.has(key)) {
410
- out += (wroteField ? "," : "") + text.slice(keyStart, valueEnd);
411
- wroteField = true;
412
- }
413
- i = valueEnd;
414
- }
415
- return out;
416
- }
417
- /**
418
- * Test-only direct access to the raw-text stripper (#1785 F5). Unlike
419
- * asserting on `loadProjectSnapshotExportsAndRules`'s RETURN shape — which
420
- * only proves the output omits the heavy fields, not that their parse cost
421
- * was ever avoided (the function could strip nothing and still hand-pick 4
422
- * fields out of a fully-parsed object) — this lets a test inspect the
423
- * INTERMEDIATE text `JSON.parse` actually receives, which is the only way to
424
- * prove the expensive fields' text never reached `JSON.parse` at all.
425
- */
426
- export function _stripTopLevelJsonKeysForTests(text, keysToStrip) {
427
- return stripTopLevelJsonKeys(text, new Set(keysToStrip));
428
- }
429
- // A fixed, 4-entry, import-time constant — not per-session accumulating
430
- // state, so it needs no session_start reset (counted in
431
- // tests/support/session-state-registry.ts's SESSION_STATE_SYMBOL_COUNTS
432
- // pin for "project-snapshot.ts", alongside the bounded digest hook below).
433
- //
434
- // Adding a key here? Add it to `NARROW_DIGEST_HEAVY_KEY_LITERALS` too (~40
435
- // lines down) — it's a DELIBERATELY separate, independent list (#1785 F7: a
436
- // digest that reads its own containsHeavyKey answer off THIS set can't
437
- // detect this set failing/emptying, so it must not).
438
- const HEAVY_SNAPSHOT_KEYS = new Set([
439
- "wordIndex",
440
- "files",
441
- "symbols",
442
- "reverseDeps",
443
- ]);
444
- // #1785 F7 (review round 5): a FIXED LITERAL list, independent of
445
- // `HEAVY_SNAPSHOT_KEYS` — the digest below exists to detect a broken/emptied
446
- // `HEAVY_SNAPSHOT_KEYS`, so deriving `containsHeavyKey` from that same
447
- // constant makes the check vacuous by construction: empty the set and the
448
- // stripper strips nothing AND `[...HEAVY_SNAPSHOT_KEYS].some(...)` iterates
449
- // zero entries and reports `false` — the exact failure mode this hook exists
450
- // to catch, silently passing. Verified: with the shared-constant version,
451
- // emptying `HEAVY_SNAPSHOT_KEYS` left 38 tests green instead of red.
452
- const NARROW_DIGEST_HEAVY_KEY_LITERALS = [
453
- "wordIndex",
454
- "files",
455
- "symbols",
456
- "reverseDeps",
457
- ];
458
- let _lastNarrowParseDigestForTests;
459
- export function getLastNarrowParseDigestForTests() {
460
- return _lastNarrowParseDigestForTests;
461
- }
462
- export function resetLastNarrowParseDigestForTests() {
463
- _lastNarrowParseDigestForTests = undefined;
464
- }
465
- function parseExportsAndRulesOnly(json) {
466
- const narrowed = stripTopLevelJsonKeys(json, HEAVY_SNAPSHOT_KEYS);
467
- _lastNarrowParseDigestForTests = {
468
- length: narrowed.length,
469
- containsHeavyKey: NARROW_DIGEST_HEAVY_KEY_LITERALS.some((key) => narrowed.includes(`"${key}":`)),
470
- };
471
- const parsed = JSON.parse(narrowed);
472
- if (parsed.version !== PROJECT_SNAPSHOT_VERSION)
473
- return null;
474
- if (typeof parsed.seq !== "number")
475
- return null;
476
- if (!Array.isArray(parsed.cachedExports))
477
- return null;
478
- return {
479
- version: PROJECT_SNAPSHOT_VERSION,
480
- seq: parsed.seq,
481
- cachedExports: parsed.cachedExports.filter((entry) => Array.isArray(entry) &&
482
- typeof entry[0] === "string" &&
483
- typeof entry[1] === "string"),
484
- projectRulesScan: parsed.projectRulesScan,
485
- };
486
- }
487
- function readSnapshotExportsAndRulesBody(bodyPath, gz) {
488
- try {
489
- const json = gz
490
- ? gunzipSync(fs.readFileSync(bodyPath)).toString("utf-8")
491
- : fs.readFileSync(bodyPath, "utf-8");
492
- return parseExportsAndRulesOnly(json);
493
- }
494
- catch (err) {
495
- logLatency({
496
- type: "phase",
497
- phase: "project_snapshot_body_corrupt",
498
- filePath: bodyPath,
499
- durationMs: 0,
500
- metadata: {
501
- error: err instanceof Error ? err.message : String(err),
502
- narrow: true,
503
- },
504
- });
505
- return null;
506
- }
507
- }
508
- /**
509
- * #1785 F5: a narrow counterpart to `loadProjectSnapshot` for callers that
510
- * only need `seq`/`version`/`cachedExports`/`projectRulesScan` — critically,
511
- * NOT `wordIndex`, `files`, `symbols`, or `reverseDeps`. The full
512
- * `loadProjectSnapshot` (even `loadProjectSnapshotWithoutWordIndex`, which
513
- * only strips the RETAINED copy AFTER a full parse) pays `gunzip` +
514
- * `JSON.parse` for the ENTIRE body regardless — dominated by `wordIndex`'s
515
- * postings graph (measured: +200ms warm / +700ms cold on a 29MB body,
516
- * 209-278ms on this repo's own 16.7MB snapshot). This function excises the
517
- * heavy fields from the raw text BEFORE parsing (`stripTopLevelJsonKeys`),
518
- * so their construction cost is never paid at all; the remaining cost is
519
- * dominated by `cachedExports` (typically a small array), not the postings.
520
- *
521
- * Consults the same in-process authoritative-write cache
522
- * `loadProjectSnapshotInternal` does, for the same read-your-own-write
523
- * reason — a save's in-memory object is cheap to narrow (no parse needed at
524
- * all) and must win over a possibly-stale on-disk body exactly like the full
525
- * loader.
526
- */
527
- export function loadProjectSnapshotExportsAndRules(cwd) {
528
- const key = normalizeMapKey(cwd);
529
- const body = resolveSnapshotBodyPath(cwd);
530
- const authoritative = authoritativeSnapshots.get(key);
531
- if (authoritative) {
532
- // `body === null` means nothing is on disk — that is never "the body
533
- // changed size", so it must not be read as superseding our own write.
534
- // See the matching comment in loadProjectSnapshotInternal.
535
- const notSuperseded = body === null ||
536
- (body.mtimeMs <= authoritative.knownMtime &&
537
- body.size === authoritative.knownSize);
538
- if (notSuperseded) {
539
- const { version, seq, cachedExports, projectRulesScan } = authoritative.snapshot;
540
- return { version, seq, cachedExports, projectRulesScan };
541
- }
542
- // On mismatch, unlike the full loader, this narrow loader neither
543
- // deletes the entry nor touches its idle timer (it never calls
544
- // touchAuthoritativeSnapshot even on a hit). The stale entry is safe to
545
- // leave in place: nothing here re-arms its eviction, so it is still
546
- // bounded by whatever idle window the full loader (or the original
547
- // write) last set, not left to live indefinitely.
548
- }
549
- if (!body)
550
- return null;
551
- return readSnapshotExportsAndRulesBody(body.path, body.gz);
552
- }
553
- function loadProjectSnapshotInternal(cwd, requireWordIndex) {
554
- const key = normalizeMapKey(cwd);
555
- const body = resolveSnapshotBodyPath(cwd);
556
- // Authoritative in-process write wins while our own (possibly still
557
- // in-flight) write has not been superseded on disk by a newer external mtime
558
- // or a different-size body in the same/coarser bucket. `body === null` means
559
- // nothing is on disk — either our just-scheduled write hasn't landed yet, or
560
- // something removed the body after we wrote it (e.g. a cleared cache dir).
561
- // Neither case is "the body changed size", so serve the in-process object.
562
- const authoritative = authoritativeSnapshots.get(key);
563
- if (authoritative) {
564
- const notSuperseded = body === null ||
565
- (body.mtimeMs <= authoritative.knownMtime &&
566
- body.size === authoritative.knownSize);
567
- if (notSuperseded) {
568
- touchAuthoritativeSnapshot(key, authoritative);
569
- return authoritative.snapshot;
570
- }
571
- // An external writer changed the body beyond our metadata stamp — honor
572
- // disk and stop serving the now-stale in-memory object.
573
- deleteAuthoritativeSnapshot(key);
574
- }
575
- if (!body) {
576
- snapshotParseCache.delete(getProjectSnapshotPath(cwd));
577
- return null;
578
- }
579
- const cacheKey = body.path;
580
- const cached = snapshotParseCache.get(cacheKey);
581
- // Both mtime AND size must match (see the `size` field doc on
582
- // SnapshotParseCacheEntry for why: coarse FAT/exFAT mtime resolution can
583
- // otherwise alias a just-rewritten file onto a stale cache entry).
584
- if (cached && cached.mtimeMs === body.mtimeMs && cached.size === body.size) {
585
- if (!requireWordIndex || !cached.snapshot || cached.snapshot.wordIndex) {
586
- return cached.snapshot;
587
- }
588
- }
589
- const { snapshot, rawBytes } = readSnapshotBody(body.path, body.gz);
590
- // Serialized postings expand into a much larger object graph. Cache only a
591
- // shallow postings-stripped body: metadata/report consumers stay warm while
592
- // the live warm WordIndex remains the sole retained postings graph (#1370).
593
- const cacheSnapshot = withoutWordIndex(snapshot);
594
- if (rawBytes > 0 &&
595
- (rawBytes <= SNAPSHOT_PARSE_CACHE_MAX_BYTES || snapshot?.wordIndex)) {
596
- cacheParsedSnapshot(cacheKey, {
597
- mtimeMs: body.mtimeMs,
598
- size: body.size,
599
- snapshot: cacheSnapshot,
600
- });
601
- }
602
- else {
603
- snapshotParseCache.delete(cacheKey);
604
- }
605
- return snapshot;
606
- }
607
- /** Load the canonical body, including serialized postings when present. */
608
- export function loadProjectSnapshot(cwd) {
609
- return loadProjectSnapshotInternal(cwd, true);
610
- }
611
- /**
612
- * Load snapshot metadata without retaining or re-reading serialized postings.
613
- * After publication this is served by the postings-stripped parse cache.
614
- */
615
- export function loadProjectSnapshotWithoutWordIndex(cwd) {
616
- return loadProjectSnapshotInternal(cwd, false);
617
- }
618
- const _snapshotGenerationStates = new Map();
619
- const _successfulSnapshotPersists = new BoundedFifoMap(PROJECT_SNAPSHOT_MAX_WARM_ROOTS);
620
- const _failedSnapshotPersists = new BoundedFifoMap(PROJECT_SNAPSHOT_MAX_WARM_ROOTS);
621
- const _activeSnapshotPersists = new Map();
622
- const _queuedSnapshotPersists = new Map();
623
- const _snapshotWorkerRequests = new Map();
624
- let _snapshotPersistWorker;
625
- let _snapshotWorkerRequestId = 0;
626
- let _snapshotWorkerDisabled = false;
627
- let _snapshotGenerationGateEnabledForTests = true;
628
- let _snapshotPromotionSeamForTests;
629
- let _lastSnapshotPersistErrorForTests;
630
- let _snapshotExiting = false;
631
- let _snapshotWorkerBodyWritesForTests = 0;
632
- function snapshotWorkerEnabled() {
633
- // The synchronous fallback writer is a legitimate degraded mode (hosts that
634
- // can't spawn a worker); tests also force it so a save→load is fully
635
- // synchronous. Production defaults to the worker.
636
- const raw = process.env.PI_LENS_SNAPSHOT_PERSIST_SYNC;
637
- return !(raw === "1" || raw === "true");
638
- }
639
- function pendingSnapshotIsCurrent(pending) {
640
- return (!_snapshotGenerationGateEnabledForTests ||
641
- _snapshotGenerationStates.get(pending.key)?.generation ===
642
- pending.generation);
643
- }
644
- function rememberSuccessfulSnapshotPersist(key, record) {
645
- _successfulSnapshotPersists.delete(key);
646
- _successfulSnapshotPersists.set(key, record);
647
- }
648
- function assessSnapshotBodyIntegrity(meta, body) {
649
- if (meta.gzBytes === undefined) {
650
- // Legacy meta (pre-#2008): nothing to compare against, so the evidence is
651
- // untrusted until the next successful persist populates gzBytes.
652
- return { outcome: "legacy-meta", actualBytes: body.size };
653
- }
654
- if (!body.gz || body.size !== meta.gzBytes) {
655
- return {
656
- outcome: "size-mismatch",
657
- expectedGzBytes: meta.gzBytes,
658
- actualBytes: body.size,
659
- };
660
- }
661
- return {
662
- outcome: "ok",
663
- expectedGzBytes: meta.gzBytes,
664
- actualBytes: body.size,
665
- };
666
- }
667
- /**
668
- * Bounded observability for #2008: the degradation ledger keeps one entry per
669
- * corrupted subject with the exact detection count for the session; every
670
- * detection also emits one `project_snapshot_body_integrity` latency row
671
- * naming expected vs actual bytes, so an operator can confirm both the
672
- * detection and the forced republish from logs alone.
673
- */
674
- function noteSnapshotBodyIntegrityWithheld(args) {
675
- incrementDegradationCount({
676
- kind: "snapshot-integrity",
677
- subject: args.gzPath,
678
- reason: args.verdict,
679
- });
680
- logLatency({
681
- type: "phase",
682
- phase: "project_snapshot_body_integrity",
683
- filePath: args.gzPath,
684
- durationMs: 0,
685
- metadata: {
686
- outcome: "dedupe_withheld",
687
- reason: args.verdict,
688
- ...(args.seq !== undefined ? { seq: args.seq } : {}),
689
- ...(args.expectedGzBytes === undefined
690
- ? {}
691
- : { expectedGzBytes: args.expectedGzBytes }),
692
- actualBytes: args.actualBytes,
693
- },
694
- });
695
- }
696
- /** Log one row naming a skip that was refused because integrity regressed. */
697
- function noteSnapshotSkipRefused(args) {
698
- logLatency({
699
- type: "phase",
700
- phase: "project_snapshot_body_integrity",
701
- filePath: args.gzPath,
702
- durationMs: 0,
703
- metadata: {
704
- outcome: "skip_refused_rewrite",
705
- reason: args.verdict,
706
- seq: args.seq,
707
- },
708
- });
709
- }
710
- /**
711
- * PURE READ of the durable meta+body evidence for a snapshot key (#2008
712
- * refactor): it must never mutate {@link _successfulSnapshotPersists}. This
713
- * runs up to three times per persist — admission in saveProjectSnapshot, the
714
- * dispatch-time refresh, and the skip-honor re-read — including on results
715
- * whose fingerprints are only partially used, so the seeding write lives in
716
- * {@link seedSnapshotPersistBaselineFromDurable} and is called only by the
717
- * seam that owns the persist lifecycle (dispatchSnapshotPersist).
718
- */
719
- function snapshotPersistBaselinesFor(cwd, key) {
720
- const local = _successfulSnapshotPersists.get(key);
721
- const meta = readProjectSnapshotMeta(cwd);
722
- const body = resolveSnapshotBodyPath(cwd);
723
- // No body means no dedupe evidence. This keeps same-seq deletion repair live.
724
- if (!meta?.fingerprint || !body) {
725
- if (!body)
726
- _successfulSnapshotPersists.delete(key);
727
- return { fingerprints: [] };
728
- }
729
- // #2008: a meta whose recorded gz size no longer matches the on-disk body
730
- // describes evidence we cannot trust — a truncated/torn gzip under an
731
- // intact meta used to win same-fingerprint dedupe forever, keeping the
732
- // corrupt body canonical until seq advanced. Withhold the fingerprints so
733
- // the pending save republishes (and rewrites) the body.
734
- const integrity = assessSnapshotBodyIntegrity(meta, body);
735
- if (integrity.outcome !== "ok") {
736
- noteSnapshotBodyIntegrityWithheld({
737
- gzPath: getProjectSnapshotPath(cwd),
738
- verdict: integrity.outcome,
739
- seq: meta.seq,
740
- expectedGzBytes: integrity.expectedGzBytes,
741
- actualBytes: integrity.actualBytes,
742
- });
743
- return { fingerprints: [] };
744
- }
745
- const durable = {
746
- seq: meta.seq,
747
- fingerprint: meta.fingerprint,
748
- generatedAt: meta.timestamp,
749
- generation: _snapshotGenerationStates.get(key)?.generation ?? 0,
750
- gzBytes: meta.gzBytes,
751
- };
752
- const fingerprints = [durable.fingerprint];
753
- // A sibling process may have advanced durable state from local A to B. An
754
- // unchanged replay of A is stale work and must not overwrite B. A third
755
- // semantic state C matches neither fingerprint and still publishes.
756
- if (local && local.fingerprint !== durable.fingerprint) {
757
- fingerprints.push(local.fingerprint);
758
- }
759
- return { durable, fingerprints, integrity: "ok" };
760
- }
761
- /**
762
- * Seed the in-process baseline for `key` from freshly-read durable state.
763
- * Deliberately separate from {@link snapshotPersistBaselinesFor} so the read
764
- * stays pure; called only at the dispatch seam where the persist lifecycle
765
- * begins.
766
- */
767
- function seedSnapshotPersistBaselineFromDurable(key, durable) {
768
- if (!durable || _successfulSnapshotPersists.has(key))
769
- return;
770
- rememberSuccessfulSnapshotPersist(key, durable);
771
- }
772
- function writeProjectSnapshotMeta(metaPath, snapshot, bodyRecord) {
773
- writeFileAtomic(metaPath, JSON.stringify({
774
- timestamp: bodyRecord?.generatedAt ?? snapshot.generatedAt,
775
- version: snapshot.version,
776
- seq: snapshot.seq,
777
- ...(bodyRecord
778
- ? {
779
- fingerprint: bodyRecord.fingerprint,
780
- ...(bodyRecord.gzBytes === undefined
781
- ? {}
782
- : { gzBytes: bodyRecord.gzBytes }),
783
- }
784
- : {}),
785
- ...(snapshot.sequenceIndex
786
- ? { sequenceIndex: snapshot.sequenceIndex }
787
- : {}),
788
- }), { bestEffort: false });
789
- }
790
- function recordSnapshotPersistFailure(pending, error, bodyPersisted = false) {
791
- // Honesty (#533): a failed async body write must be surfaced, never left to
792
- // masquerade as a saved snapshot. The meta gate is already self-healing (an
793
- // old body under a newer-seq meta is rejected on the body's own embedded
794
- // seq), and dropping the authoritative entry means the next load reflects
795
- // what is ACTUALLY on disk rather than the object we failed to persist.
796
- _lastSnapshotPersistErrorForTests = error;
797
- _failedSnapshotPersists.set(pending.key, {
798
- seq: pending.snapshot.seq,
799
- generation: pending.generation,
800
- });
801
- deleteAuthoritativeSnapshot(pending.key);
802
- logLatency({
803
- type: "phase",
804
- phase: "project_snapshot_persist_failed",
805
- filePath: pending.gzPath,
806
- durationMs: 0,
807
- metadata: {
808
- error,
809
- seq: pending.snapshot.seq,
810
- outcome: bodyPersisted ? "metadata_repair_required" : "failed",
811
- ...(bodyPersisted ? { bodyPersisted: true } : {}),
812
- },
813
- });
814
- // #1333: the logLatency call above already carries this failure to
815
- // latency.log — the console.error was a duplicate RAW write into pi's frame.
816
- }
817
- function logSnapshotPersistSuccess(pending, fingerprint, stats) {
818
- rememberSuccessfulSnapshotPersist(pending.key, {
819
- seq: pending.snapshot.seq,
820
- fingerprint,
821
- generatedAt: pending.snapshot.generatedAt,
822
- generation: pending.generation,
823
- rawBytes: stats.rawBytes,
824
- gzBytes: stats.gzBytes,
825
- });
826
- _failedSnapshotPersists.delete(pending.key);
827
- logLatency({
828
- type: "phase",
829
- phase: "project_snapshot_persist",
830
- filePath: pending.gzPath,
831
- durationMs: stats.serializeMs + stats.writeMs,
832
- metadata: { seq: pending.snapshot.seq, outcome: "executed", ...stats },
833
- });
834
- }
835
- function logSnapshotPersistDecision(args) {
836
- logLatency({
837
- type: "phase",
838
- phase: "project_snapshot_persist_decision",
839
- filePath: getProjectSnapshotPath(args.cwd),
840
- durationMs: 0,
841
- metadata: {
842
- seq: args.seq,
843
- decision: args.decision,
844
- ...(args.fingerprint
845
- ? { fingerprint: args.fingerprint.slice(0, 12) }
846
- : {}),
847
- ...(args.avoidedRawBytes === undefined
848
- ? {}
849
- : { avoidedRawBytes: args.avoidedRawBytes }),
850
- ...(args.avoidedGzipBytes === undefined
851
- ? {}
852
- : { avoidedGzipBytes: args.avoidedGzipBytes }),
853
- },
854
- });
855
- }
856
- /**
857
- * Reconcile the authoritative in-process entry with a body that just landed on
858
- * disk: update its `knownMtime` so a subsequent load keeps serving our own
859
- * object without re-parsing, and DROP oversized bodies (their post-promotion
860
- * disk read is the pre-#947 behavior — we won't pin hundreds of MB of heap).
861
- */
862
- function reconcileAuthoritativeAfterWrite(pending, rawBytes) {
863
- const entry = authoritativeSnapshots.get(pending.key);
864
- // Only reconcile the entry that still belongs to THIS (latest) generation —
865
- // a superseding save already replaced it with a newer object.
866
- if (!entry || entry.snapshot !== pending.snapshot)
867
- return;
868
- // The worker needs the serialized snapshot until promotion completes, but
869
- // retaining it afterward duplicates the mutable warm index's postings. A
870
- // shared reference is unsafe: ProjectSnapshot stores serialized arrays while
871
- // WordIndex owns mutable Map/PathKeyedMap state. Drop the authoritative copy
872
- // after publication; later merge-writers rehydrate the canonical disk body.
873
- if (pending.snapshot.wordIndex) {
874
- try {
875
- const stat = fs.statSync(pending.gzPath);
876
- cacheParsedSnapshot(pending.gzPath, {
877
- mtimeMs: stat.mtimeMs,
878
- size: stat.size,
879
- snapshot: withoutWordIndex(pending.snapshot),
880
- });
881
- }
882
- catch {
883
- // A cache miss is safe: the first metadata consumer reconstructs it.
884
- }
885
- deleteAuthoritativeSnapshot(pending.key);
886
- logLatency({
887
- type: "phase",
888
- phase: "project_snapshot_word_index_released",
889
- filePath: pending.gzPath,
890
- durationMs: 0,
891
- metadata: { rawBytes },
892
- });
893
- return;
894
- }
895
- if (rawBytes > SNAPSHOT_PARSE_CACHE_MAX_BYTES) {
896
- // Benign but invisible otherwise: the next load will re-parse this body
897
- // from disk instead of serving the in-process object.
898
- logLatency({
899
- type: "phase",
900
- phase: "project_snapshot_authoritative_dropped_oversized",
901
- filePath: pending.gzPath,
902
- durationMs: 0,
903
- metadata: { rawBytes, maxBytes: SNAPSHOT_PARSE_CACHE_MAX_BYTES },
904
- });
905
- deleteAuthoritativeSnapshot(pending.key);
906
- return;
907
- }
908
- try {
909
- const stat = fs.statSync(pending.gzPath);
910
- entry.knownMtime = stat.mtimeMs;
911
- entry.knownSize = stat.size;
912
- }
913
- catch {
914
- // If we can't stat our own write, leave the metadata as-is; the worst case
915
- // is one extra disk re-parse on the next load.
916
- }
917
- }
918
- function finalizeProjectSnapshotMeta(pending, record) {
919
- try {
920
- writeProjectSnapshotMeta(getProjectSnapshotMetaPath(pending.cwd), pending.snapshot, record);
921
- return true;
922
- }
923
- catch (err) {
924
- recordSnapshotPersistFailure(pending, err instanceof Error ? err.message : String(err), true);
925
- return false;
926
- }
927
- }
928
- function completeSnapshotPersist(pending) {
929
- if (_activeSnapshotPersists.get(pending.key) !== pending)
930
- return;
931
- _activeSnapshotPersists.delete(pending.key);
932
- if (_snapshotExiting)
933
- return;
934
- const queued = _queuedSnapshotPersists.get(pending.key);
935
- if (!queued)
936
- return;
937
- _queuedSnapshotPersists.delete(pending.key);
938
- dispatchSnapshotPersist(queued);
939
- }
940
- function writeSnapshotBodyOnMainThread(pending, reason) {
941
- if (!pendingSnapshotIsCurrent(pending)) {
942
- completeSnapshotPersist(pending);
943
- return;
944
- }
945
- if (reason) {
946
- // We took the synchronous main-thread gzip path (the +656MB-risk path,
947
- // #950) instead of the worker. Surface it rather than burying it in an
948
- // `offloaded:false` success line below. `degraded` distinguishes a REAL
949
- // degradation (worker died/unavailable/promote-failed) from the benign
950
- // `exit_hook` teardown flush, so an operator triaging worker health isn't
951
- // misled by normal process-exit flushes.
952
- logLatency({
953
- type: "phase",
954
- phase: "project_snapshot_worker_fallback",
955
- filePath: pending.gzPath,
956
- durationMs: 0,
957
- metadata: {
958
- reason,
959
- seq: pending.snapshot.seq,
960
- degraded: reason !== "exit_hook",
961
- },
962
- });
963
- }
964
- try {
965
- const serializeStarted = performance.now();
966
- const json = JSON.stringify(pending.snapshot);
967
- const serializeMs = performance.now() - serializeStarted;
968
- const rawBytes = Buffer.byteLength(json);
969
- const fingerprint = fingerprintProjectSnapshotJson(json, pending.snapshot.generatedAt);
970
- if (pending.dedupeFingerprints.includes(fingerprint)) {
971
- // Re-read the tiny durable sidecar before honoring a fingerprint
972
- // captured at dispatch time (#2008): a body torn between dispatch and
973
- // execution must fall through to the full write, not skip.
974
- const current = snapshotPersistBaselinesFor(pending.cwd, pending.key);
975
- if (current.integrity === "ok") {
976
- const prior = current.durable ?? pending.durablePersist;
977
- if (authoritativeSnapshots.get(pending.key)?.snapshot === pending.snapshot) {
978
- deleteAuthoritativeSnapshot(pending.key);
979
- }
980
- logSnapshotPersistDecision({
981
- cwd: pending.cwd,
982
- seq: pending.snapshot.seq,
983
- fingerprint,
984
- decision: "skipped_unchanged",
985
- avoidedRawBytes: rawBytes,
986
- avoidedGzipBytes: prior?.gzBytes,
987
- });
988
- return;
989
- }
990
- if (current.integrity !== undefined) {
991
- noteSnapshotSkipRefused({
992
- gzPath: pending.gzPath,
993
- verdict: current.integrity,
994
- seq: pending.snapshot.seq,
995
- });
996
- }
997
- // integrity === undefined: the meta/body evidence vanished entirely
998
- // since dispatch — falling through to the full write repairs it, the
999
- // same contract as deletion-before-dispatch.
1000
- }
1001
- const writeStarted = performance.now();
1002
- const gzip = gzipSync(json);
1003
- fs.mkdirSync(path.dirname(pending.gzPath), { recursive: true });
1004
- writeFileAtomic(pending.gzPath, gzip, { bestEffort: false });
1005
- fs.rmSync(pending.legacyPath, { force: true });
1006
- const metadataFinalized = finalizeProjectSnapshotMeta(pending, {
1007
- fingerprint,
1008
- generatedAt: pending.snapshot.generatedAt,
1009
- gzBytes: gzip.byteLength,
1010
- });
1011
- if (!metadataFinalized)
1012
- return;
1013
- reconcileAuthoritativeAfterWrite(pending, rawBytes);
1014
- logSnapshotPersistSuccess(pending, fingerprint, {
1015
- rawBytes,
1016
- gzBytes: gzip.byteLength,
1017
- serializeMs,
1018
- writeMs: performance.now() - writeStarted,
1019
- offloaded: false,
1020
- });
1021
- if (reason)
1022
- _lastSnapshotPersistErrorForTests = reason;
1023
- }
1024
- catch (err) {
1025
- recordSnapshotPersistFailure(pending, err instanceof Error ? err.message : String(err));
1026
- }
1027
- finally {
1028
- completeSnapshotPersist(pending);
1029
- }
1030
- }
1031
- /**
1032
- * The ForTests promotion seam covers worker-message and every main-thread
1033
- * fallback promotion. It stays sync and seam-free in production, identical to
1034
- * calling the writer directly. The process-exit hook is deliberately the sole
1035
- * direct write because an exit handler cannot await this asynchronous seam.
1036
- */
1037
- function dispatchMainThreadWriteThroughSeam(pending, reason) {
1038
- if (_snapshotPromotionSeamForTests) {
1039
- void _snapshotPromotionSeamForTests().then(() => writeSnapshotBodyOnMainThread(pending, reason));
1040
- return;
1041
- }
1042
- writeSnapshotBodyOnMainThread(pending, reason);
1043
- }
1044
- function handleSnapshotWorkerResult(result) {
1045
- const pending = _snapshotWorkerRequests.get(result.id);
1046
- if (!pending) {
1047
- fs.rmSync(result.stagePath, { force: true });
1048
- return;
1049
- }
1050
- _snapshotWorkerRequests.delete(result.id);
1051
- if (result.error ||
1052
- result.rawBytes === undefined ||
1053
- result.gzBytes === undefined ||
1054
- result.serializeMs === undefined ||
1055
- result.writeMs === undefined ||
1056
- result.semanticFingerprint === undefined) {
1057
- fs.rmSync(result.stagePath, { force: true });
1058
- dispatchMainThreadWriteThroughSeam(pending, result.error ?? "invalid worker result");
1059
- return;
1060
- }
1061
- // Generation gate: a newer save already superseded this one — discard the
1062
- // stale stage file rather than promote it over the fresher body.
1063
- if (!result.skippedUnchanged)
1064
- _snapshotWorkerBodyWritesForTests++;
1065
- if (_snapshotGenerationGateEnabledForTests &&
1066
- _snapshotGenerationStates.get(pending.key)?.generation !== result.generation) {
1067
- // The stale stage is part of the promotion transaction: remove it before
1068
- // returning so a superseded save cannot leave an orphan behind.
1069
- fs.rmSync(result.stagePath, { force: true });
1070
- completeSnapshotPersist(pending);
1071
- return;
1072
- }
1073
- if (result.skippedUnchanged) {
1074
- // Re-read the tiny durable sidecar at the decision seam. A sibling process
1075
- // may have advanced B after this request captured A. A stale replay of A
1076
- // must preserve B's body metadata, not restore the captured A sidecar.
1077
- const baselines = snapshotPersistBaselinesFor(pending.cwd, pending.key);
1078
- if (baselines.integrity !== "ok") {
1079
- // The worker skipped WITHOUT staging a body, trusting the durable
1080
- // fingerprint captured at dispatch. The re-read says that evidence no
1081
- // longer passes the #2008 integrity gate — refuse the skip and rewrite
1082
- // synchronously so a torn/missing body cannot outlive this save. The
1083
- // sync writer recomputes the same baselines, finds them untrusted, and
1084
- // performs the full gzip+stage+promote (its finally completes this
1085
- // pending, so do not complete it twice here).
1086
- if (baselines.integrity !== undefined) {
1087
- noteSnapshotSkipRefused({
1088
- gzPath: pending.gzPath,
1089
- verdict: baselines.integrity,
1090
- seq: pending.snapshot.seq,
1091
- });
1092
- }
1093
- dispatchMainThreadWriteThroughSeam(pending, "snapshot body integrity regressed");
1094
- return;
1095
- }
1096
- const prior = baselines.durable ?? pending.durablePersist;
1097
- if (authoritativeSnapshots.get(pending.key)?.snapshot === pending.snapshot) {
1098
- deleteAuthoritativeSnapshot(pending.key);
1099
- }
1100
- logSnapshotPersistDecision({
1101
- cwd: pending.cwd,
1102
- seq: pending.snapshot.seq,
1103
- fingerprint: result.semanticFingerprint,
1104
- decision: "skipped_unchanged",
1105
- avoidedRawBytes: result.rawBytes,
1106
- avoidedGzipBytes: prior?.gzBytes,
1107
- });
1108
- completeSnapshotPersist(pending);
1109
- return;
1110
- }
1111
- try {
1112
- fs.renameSync(result.stagePath, pending.gzPath);
1113
- fs.rmSync(pending.legacyPath, { force: true });
1114
- const metadataFinalized = finalizeProjectSnapshotMeta(pending, {
1115
- fingerprint: result.semanticFingerprint,
1116
- generatedAt: pending.snapshot.generatedAt,
1117
- gzBytes: result.gzBytes,
1118
- });
1119
- if (!metadataFinalized) {
1120
- completeSnapshotPersist(pending);
1121
- return;
1122
- }
1123
- reconcileAuthoritativeAfterWrite(pending, result.rawBytes);
1124
- logSnapshotPersistSuccess(pending, result.semanticFingerprint, {
1125
- rawBytes: result.rawBytes,
1126
- gzBytes: result.gzBytes,
1127
- serializeMs: result.serializeMs,
1128
- writeMs: result.writeMs,
1129
- offloaded: true,
1130
- });
1131
- completeSnapshotPersist(pending);
1132
- }
1133
- catch (err) {
1134
- fs.rm(result.stagePath, { force: true }, () => { });
1135
- dispatchMainThreadWriteThroughSeam(pending, err instanceof Error ? err.message : String(err));
1136
- }
1137
- }
1138
- function dispatchSnapshotPersist(pending) {
1139
- // A queued request may have waited behind the publication that created its
1140
- // best dedupe evidence. Refresh only the tiny sidecar/body stat here; never
1141
- // retain the stale admission-time baseline through dispatch.
1142
- const baselines = snapshotPersistBaselinesFor(pending.cwd, pending.key);
1143
- pending.durablePersist = baselines.durable;
1144
- pending.dedupeFingerprints = baselines.fingerprints;
1145
- seedSnapshotPersistBaselineFromDurable(pending.key, baselines.durable);
1146
- _activeSnapshotPersists.set(pending.key, pending);
1147
- if (!snapshotWorkerEnabled()) {
1148
- dispatchMainThreadWriteThroughSeam(pending, undefined);
1149
- return;
1150
- }
1151
- const worker = getSnapshotPersistWorker();
1152
- if (!worker) {
1153
- dispatchMainThreadWriteThroughSeam(pending, "persist worker unavailable");
1154
- return;
1155
- }
1156
- const id = ++_snapshotWorkerRequestId;
1157
- _snapshotWorkerRequests.set(id, pending);
1158
- const request = {
1159
- id,
1160
- generation: pending.generation,
1161
- stagePath: pending.stagePath,
1162
- data: pending.snapshot,
1163
- priorFingerprints: pending.dedupeFingerprints,
1164
- testDelayMs: process.env.NODE_ENV === "test"
1165
- ? Number(process.env.PI_LENS_TEST_SNAPSHOT_PERSIST_WORKER_DELAY_MS) ||
1166
- undefined
1167
- : undefined,
1168
- };
1169
- try {
1170
- worker.postMessage(request);
1171
- }
1172
- catch (err) {
1173
- _snapshotWorkerRequests.delete(id);
1174
- dispatchMainThreadWriteThroughSeam(pending, err instanceof Error ? err.message : String(err));
1175
- }
1176
- }
1177
- function handleSnapshotWorkerDeath(reason) {
1178
- _snapshotPersistWorker = undefined;
1179
- _snapshotWorkerDisabled = true;
1180
- const requests = [..._snapshotWorkerRequests.values()];
1181
- _snapshotWorkerRequests.clear();
1182
- for (const pending of requests)
1183
- dispatchMainThreadWriteThroughSeam(pending, reason);
1184
- }
1185
- function resolveSnapshotPersistWorkerPath() {
1186
- // esbuild does NOT rewrite new URL(...) asset refs, so from the bundled
1187
- // dist/index.js a sibling ./project-snapshot-persist-worker.js resolves
1188
- // beside the BUNDLE where nothing exists. Try the compiled-sibling layout
1189
- // first (source checkout / unbundled dist/clients tree), then the dist-tree
1190
- // path relative to the bundle entry — same shape as the review graph's
1191
- // resolvePersistWorkerPath (#950 review F1).
1192
- const candidates = [
1193
- new URL("./project-snapshot-persist-worker.js", import.meta.url),
1194
- new URL("./clients/project-snapshot-persist-worker.js", import.meta.url),
1195
- ];
1196
- for (const url of candidates) {
1197
- try {
1198
- const resolved = fileURLToPath(url);
1199
- if (fs.existsSync(resolved))
1200
- return resolved;
1201
- }
1202
- catch {
1203
- /* try next layout */
1204
- }
1205
- }
1206
- return undefined;
1207
- }
1208
- function getSnapshotPersistWorker() {
1209
- if (_snapshotWorkerDisabled)
1210
- return undefined;
1211
- if (_snapshotPersistWorker)
1212
- return _snapshotPersistWorker;
1213
- try {
1214
- const workerPath = resolveSnapshotPersistWorkerPath();
1215
- if (workerPath === undefined) {
1216
- handleSnapshotWorkerDeath("persist worker script not found in any layout");
1217
- return undefined;
1218
- }
1219
- const worker = new Worker(workerPath);
1220
- // The ForTests promotion seam wraps ONLY when set — the production path
1221
- // binds the sync handler directly, so scheduling is byte-identical when
1222
- // no test seam is installed (the async-handler variant of this shifted
1223
- // promotion timing under full-suite load and flaked the round-trip test).
1224
- worker.on("message", (result) => {
1225
- if (_snapshotPromotionSeamForTests) {
1226
- void _snapshotPromotionSeamForTests().then(() => handleSnapshotWorkerResult(result));
1227
- return;
1228
- }
1229
- handleSnapshotWorkerResult(result);
1230
- });
1231
- worker.on("error", (err) => handleSnapshotWorkerDeath(err.message));
1232
- worker.on("exit", (code) => {
1233
- if (_snapshotPersistWorker === worker)
1234
- _snapshotPersistWorker = undefined;
1235
- // Any body still queued when the worker exits was abandoned mid-flight
1236
- // (a crash, a `terminate()`, or host recycling) — it will never be
1237
- // promoted by this worker, so fall it back to the sync writer rather
1238
- // than let it vanish (honesty, #533). `terminate()` can report a 0
1239
- // exit code on some platforms, so this cannot key off `code !== 0`.
1240
- const stranded = [..._snapshotWorkerRequests.values()];
1241
- if (stranded.length > 0) {
1242
- _snapshotWorkerRequests.clear();
1243
- for (const pending of stranded) {
1244
- dispatchMainThreadWriteThroughSeam(pending, `persist worker exited with code ${code}`);
1245
- }
1246
- }
1247
- // Only an ABNORMAL exit disables respawning; a clean idle exit (nothing
1248
- // stranded) just drops the reference so the next persist respawns.
1249
- if (code !== 0)
1250
- _snapshotWorkerDisabled = true;
1251
- });
1252
- // #1148: adding a message listener refs the Worker's public MessagePort.
1253
- // Unref only after every listener is installed so it stays background-only.
1254
- worker.unref();
1255
- _snapshotPersistWorker = worker;
1256
- return worker;
1257
- }
1258
- catch (err) {
1259
- handleSnapshotWorkerDeath(err instanceof Error ? err.message : String(err));
1260
- return undefined;
1261
- }
1262
- }
1263
- // #950 review F3: a process that dies between a worker's staged write and its
1264
- // promotion leaves project-snapshot.json.gz.stage-<pid>-<gen> (and the worker's
1265
- // .tmp-<pid>) behind forever. Sweep leftovers from PRIOR processes once per
1266
- // cache dir; our own live stage files carry this pid and are skipped.
1267
- const _sweptSnapshotStageDirs = new Set();
1268
- function sweepStaleSnapshotStageFiles(cacheDir) {
1269
- if (_sweptSnapshotStageDirs.has(cacheDir))
1270
- return;
1271
- _sweptSnapshotStageDirs.add(cacheDir);
1272
- fs.readdir(cacheDir, (err, entries) => {
1273
- if (err)
1274
- return;
1275
- const ownMarker = `.stage-${process.pid}-`;
1276
- for (const entry of entries) {
1277
- if (!entry.startsWith("project-snapshot.json.gz.stage-"))
1278
- continue;
1279
- if (entry.includes(ownMarker))
1280
- continue;
1281
- fs.rm(path.join(cacheDir, entry), { force: true }, () => { });
1282
- }
1283
- });
1284
- }
1285
- // Flush any in-flight worker writes synchronously at process teardown so a body
1286
- // whose worker hasn't promoted yet isn't lost. Sync writes only (no child
1287
- // spawn — the teardown libuv hazard); best-effort.
1288
- let _snapshotExitHookInstalled = false;
1289
- function ensureSnapshotPersistExitHook() {
1290
- if (_snapshotExitHookInstalled)
1291
- return;
1292
- _snapshotExitHookInstalled = true;
1293
- process.once("exit", () => {
1294
- _snapshotExiting = true;
1295
- const latestByKey = new Map();
1296
- for (const pending of _snapshotWorkerRequests.values()) {
1297
- latestByKey.set(pending.key, pending);
1298
- }
1299
- for (const pending of _queuedSnapshotPersists.values()) {
1300
- const prior = latestByKey.get(pending.key);
1301
- // Queued work is later admission order. Equal-seq requests deliberately
1302
- // share a gate generation, so a tie must still select the queued payload.
1303
- if (!prior || prior.generation <= pending.generation) {
1304
- latestByKey.set(pending.key, pending);
1305
- }
1306
- }
1307
- _snapshotWorkerRequests.clear();
1308
- _queuedSnapshotPersists.clear();
1309
- _activeSnapshotPersists.clear();
1310
- for (const pending of latestByKey.values()) {
1311
- // Only the newest generation per key still matters; older ones are
1312
- // superseded and their stage files are swept on next launch.
1313
- if (_snapshotGenerationStates.get(pending.key)?.generation !==
1314
- pending.generation)
1315
- continue;
1316
- writeSnapshotBodyOnMainThread(pending, "exit_hook");
1317
- }
1318
- void _snapshotPersistWorker?.terminate();
1319
- });
1320
- }
1321
- export function saveProjectSnapshot(cwd, snapshot) {
1322
- const gzPath = getProjectSnapshotPath(cwd);
1323
- const legacyPath = getProjectSnapshotLegacyPath(cwd);
1324
- const metaPath = getProjectSnapshotMetaPath(cwd);
1325
- const cacheDir = path.dirname(gzPath);
1326
- const key = normalizeMapKey(cwd);
1327
- fs.mkdirSync(cacheDir, { recursive: true });
1328
- const baselines = snapshotPersistBaselinesFor(cwd, key);
1329
- const priorPersist = baselines.durable;
1330
- logSnapshotPersistDecision({
1331
- cwd,
1332
- seq: snapshot.seq,
1333
- decision: "requested",
1334
- });
1335
- if (_failedSnapshotPersists.delete(key)) {
1336
- logSnapshotPersistDecision({ cwd, seq: snapshot.seq, decision: "retry" });
1337
- }
1338
- // #958: for a new seq, meta is written FIRST and body SECOND. A crash or
1339
- // failure between the two writes can now only produce
1340
- // "meta already claims the new seq, body hasn't caught up yet" (the meta
1341
- // races ahead). The meta-first gate (isProjectSnapshotMetaStale) reads
1342
- // that as *fresh* and falls through to parsing the body, whose own
1343
- // embedded `seq` is still the old one, so `isProjectSnapshotFresh`
1344
- // correctly rejects it as stale on the body's own merits — one wasted
1345
- // parse, self-healing, no data lost. The OLD body-then-meta order could
1346
- // instead leave an old-seq meta sitting over a freshly written body,
1347
- // which the meta-first gate discards WITHOUT ever reading it — throwing
1348
- // away a genuinely fresh snapshot. That direction is not recoverable
1349
- // until the next save, so it's the one this reorder eliminates.
1350
- //
1351
- // The new-seq meta write stays synchronous and throwing. Same-seq work does
1352
- // not need to advance the freshness gate, so it leaves the current sidecar
1353
- // untouched until the worker has a semantic verdict. This prevents a stale
1354
- // local request from replacing a
1355
- // newer sibling process's same-seq fingerprint before it can coalesce.
1356
- if (priorPersist?.seq !== snapshot.seq) {
1357
- writeProjectSnapshotMeta(metaPath, snapshot);
1358
- }
1359
- // Record the authoritative in-process write BEFORE handing the body off, so
1360
- // a merge-read between now and the worker's promotion sees our own object
1361
- // (the on-disk body still holds the previous generation until promotion). The
1362
- // pre-write mtime is our baseline: while disk stays at it (or below), our
1363
- // object wins; a promotion or an external write moves past it. Baseline off
1364
- // the CURRENTLY-RESOLVED body — which is the legacy uncompressed
1365
- // `project-snapshot.json` in the one-release upgrade window before the first
1366
- // gz promotion — not gz-only: statting only gzPath there would leave the
1367
- // baseline at -Infinity, so the load gate (which resolves the legacy body's
1368
- // real, positive mtime) would reject our own fresh write and serve the stale
1369
- // legacy body to a merge-consumer, silently dropping this snapshot's fields.
1370
- const priorBody = resolveSnapshotBodyPath(cwd);
1371
- const knownMtime = priorBody ? priorBody.mtimeMs : Number.NEGATIVE_INFINITY;
1372
- const knownSize = priorBody ? priorBody.size : Number.NEGATIVE_INFINITY;
1373
- const authoritativeEntry = {
1374
- snapshot,
1375
- knownMtime,
1376
- knownSize,
1377
- lastUsedAt: Date.now(),
1378
- };
1379
- const previousAuthoritativeEntry = authoritativeSnapshots.get(key);
1380
- if (previousAuthoritativeEntry)
1381
- clearAuthoritativeSnapshotTimer(previousAuthoritativeEntry);
1382
- authoritativeSnapshots.set(key, authoritativeEntry);
1383
- scheduleAuthoritativeSnapshotEviction(key, authoritativeEntry);
1384
- enforceAuthoritativeSnapshotCap();
1385
- // A stale disk-parse-cache entry for this path must not out-vote the fresh
1386
- // authoritative write once the latter is dropped (oversized bodies).
1387
- snapshotParseCache.delete(gzPath);
1388
- const generationState = _snapshotGenerationStates.get(key);
1389
- const generation = generationState?.seq === snapshot.seq
1390
- ? generationState.generation
1391
- : (generationState?.generation ?? 0) + 1;
1392
- _snapshotGenerationStates.set(key, { generation, seq: snapshot.seq });
1393
- const stagePath = `${gzPath}.stage-${process.pid}-${generation}`;
1394
- const pending = {
1395
- key,
1396
- cwd,
1397
- gzPath,
1398
- legacyPath,
1399
- stagePath,
1400
- snapshot,
1401
- generation,
1402
- durablePersist: priorPersist,
1403
- dedupeFingerprints: baselines.fingerprints,
1404
- };
1405
- sweepStaleSnapshotStageFiles(cacheDir);
1406
- ensureSnapshotPersistExitHook();
1407
- if (_activeSnapshotPersists.has(key)) {
1408
- _queuedSnapshotPersists.set(key, pending);
1409
- logSnapshotPersistDecision({
1410
- cwd,
1411
- seq: snapshot.seq,
1412
- decision: "coalesced",
1413
- });
1414
- return;
1415
- }
1416
- dispatchSnapshotPersist(pending);
1417
- }
1418
- // --- Test hooks for the worker persist path ---------------------------------
1419
- /** Test-only: wait until worker requests have either landed or degraded. */
1420
- export async function waitForProjectSnapshotPersistsForTests() {
1421
- for (let attempts = 0; attempts < 200 &&
1422
- (_snapshotWorkerRequests.size > 0 ||
1423
- _activeSnapshotPersists.size > 0 ||
1424
- _queuedSnapshotPersists.size > 0); attempts++) {
1425
- await new Promise((resolve) => setTimeout(resolve, 10));
1426
- }
1427
- }
1428
- /** Test-only: force any in-flight worker body write to the sync main-thread path. */
1429
- export function flushProjectSnapshotPersistsForTests() {
1430
- const requests = [..._snapshotWorkerRequests.values()];
1431
- _snapshotWorkerRequests.clear();
1432
- for (const pending of requests) {
1433
- dispatchMainThreadWriteThroughSeam(pending, undefined);
1434
- }
1435
- }
1436
- /** Test-only: exercise the degraded worker-death path. */
1437
- export async function terminateProjectSnapshotPersistWorkerForTests() {
1438
- const worker = _snapshotPersistWorker;
1439
- if (worker)
1440
- await worker.terminate();
1441
- }
1442
- /** Test-only: restore worker creation + clear generation state after a deliberate death. */
1443
- export function resetProjectSnapshotPersistWorkerForTests() {
1444
- _snapshotWorkerDisabled = false;
1445
- _snapshotGenerationGateEnabledForTests = true;
1446
- _snapshotPromotionSeamForTests = undefined;
1447
- _snapshotPersistWorker = undefined;
1448
- _snapshotWorkerRequests.clear();
1449
- _snapshotGenerationStates.clear();
1450
- _successfulSnapshotPersists.clear();
1451
- _failedSnapshotPersists.clear();
1452
- _activeSnapshotPersists.clear();
1453
- _queuedSnapshotPersists.clear();
1454
- _snapshotExiting = false;
1455
- _snapshotWorkerBodyWritesForTests = 0;
1456
- _lastSnapshotPersistErrorForTests = undefined;
1457
- }
1458
- /** Test-only: expose bounded queue state without retaining snapshot bodies. */
1459
- export function getProjectSnapshotPersistStateForTests(cwd) {
1460
- const key = normalizeMapKey(cwd);
1461
- return {
1462
- active: _activeSnapshotPersists.has(key),
1463
- queued: _queuedSnapshotPersists.has(key),
1464
- successfulFingerprint: _successfulSnapshotPersists.get(key)?.fingerprint,
1465
- workerBodyWrites: _snapshotWorkerBodyWritesForTests,
1466
- };
1467
- }
1468
- /** Test-only mutation switch for proving the supersession invariant. */
1469
- export function setProjectSnapshotGenerationGateForTests(enabled) {
1470
- _snapshotGenerationGateEnabledForTests = enabled;
1471
- }
1472
- /** Test-only seam immediately before generation-gated promotion. */
1473
- export function setProjectSnapshotPromotionSeamForTests(seam) {
1474
- _snapshotPromotionSeamForTests = seam;
1475
- }
1476
- export function getProjectSnapshotPersistErrorForTests() {
1477
- return _lastSnapshotPersistErrorForTests;
1478
- }
1479
- // ── #2442 test-only seams: exercise the three BoundedFifoMap-backed caches ──
1480
- // through the exact same write path production uses (cacheParsedSnapshot /
1481
- // rememberSuccessfulSnapshotPersist's delete+set refresh, and a direct write
1482
- // mirroring recordSnapshotPersistFailure's), without the real gzip/worker
1483
- // persistence pipeline or a full ProjectSnapshot fixture.
1484
- /** Test-only: seed the parse cache exactly as `cacheParsedSnapshot` would. */
1485
- export function _seedSnapshotParseCacheForTests(key, entry) {
1486
- cacheParsedSnapshot(key, entry);
1487
- }
1488
- export function _snapshotParseCacheKeysForTests() {
1489
- return [...snapshotParseCache.keys()];
1490
- }
1491
- /**
1492
- * #2442 test-only: the exact `snapshotParseCache.get(key)` the production
1493
- * read at {@link loadProjectSnapshot}'s parse-cache hit performs. A `.has()`
1494
- * would not do — `.has()` reorders nothing on either bounded class, so a
1495
- * has-based test cannot tell FIFO from LRU, which is the axis these bounded
1496
- * tests exist to pin (#2442 review F4).
1497
- */
1498
- export function _snapshotParseCacheGetForTests(key) {
1499
- return snapshotParseCache.get(key) !== undefined;
1500
- }
1501
- /** Test-only: seed the successful-persist cache exactly as production would. */
1502
- export function _seedSuccessfulSnapshotPersistForTests(key, record) {
1503
- rememberSuccessfulSnapshotPersist(key, record);
1504
- }
1505
- export function _successfulSnapshotPersistKeysForTests() {
1506
- return [..._successfulSnapshotPersists.keys()];
1507
- }
1508
- /** #2442 test-only: the same `_successfulSnapshotPersists.get(key)` the
1509
- * persist-dedupe read and `getProjectSnapshotPersistStateForTests` perform.
1510
- * See {@link _snapshotParseCacheGetForTests} for why `.has()` will not do. */
1511
- export function _successfulSnapshotPersistGetForTests(key) {
1512
- return _successfulSnapshotPersists.get(key) !== undefined;
1513
- }
1514
- /** Test-only: seed the failed-persist cache with the same write
1515
- * `recordSnapshotPersistFailure` performs, without its side effects
1516
- * (deleteAuthoritativeSnapshot / logLatency) or a full PendingSnapshotBody. */
1517
- export function _seedFailedSnapshotPersistForTests(key, record) {
1518
- _failedSnapshotPersists.set(key, record);
1519
- }
1520
- export function _failedSnapshotPersistKeysForTests() {
1521
- return [..._failedSnapshotPersists.keys()];
1522
- }
1523
- export function buildProjectSnapshotFromRuntime(args) {
1524
- return {
1525
- version: PROJECT_SNAPSHOT_VERSION,
1526
- projectRoot: normalizeMapKey(path.resolve(args.cwd)),
1527
- generatedAt: new Date().toISOString(),
1528
- seq: args.runtime.projectSeq,
1529
- files: {},
1530
- symbols: {},
1531
- reverseDeps: {},
1532
- cachedExports: [...args.runtime.cachedExports.entries()].sort((a, b) => a[0].localeCompare(b[0])),
1533
- // #1019: capture the runtime's live sequence index AT this seq. The runtime
1534
- // is seeded from the change log at session start and bumped in lockstep with
1535
- // every append, so `getFileSeqEntries()` IS the fold of the log up to
1536
- // `projectSeq` — and its keys are already `normalizeMapKey(path.resolve())`,
1537
- // the exact form the change-log replay produces.
1538
- sequenceIndex: {
1539
- projectSeq: args.runtime.projectSeq,
1540
- fileSeqByPath: args.runtime.getFileSeqEntries(),
1541
- },
1542
- wordIndex: args.runtime.wordIndex
1543
- ? serializeWordIndex(args.runtime.wordIndex)
1544
- : undefined,
1545
- projectRulesScan: args.runtime.projectRulesScan,
1546
- startupScan: args.startupScan,
1547
- languageProfile: args.languageProfile,
1548
- conventions: args.conventions,
1549
- };
1550
- }
1551
- export function hydrateRuntimeFromProjectSnapshot(runtime, snapshot) {
1552
- runtime.cachedExports.clear();
1553
- for (const [name, filePath] of snapshot.cachedExports) {
1554
- runtime.cachedExports.set(name, filePath);
1555
- }
1556
- if (snapshot.projectRulesScan) {
1557
- runtime.projectRulesScan = snapshot.projectRulesScan;
1558
- }
1559
- runtime.wordIndex = deserializeWordIndex(snapshot.wordIndex);
1560
- }
1561
- /**
1562
- * #1785 F2/F3: additive counterpart to `hydrateRuntimeFromProjectSnapshot`
1563
- * for a LATE/retroactive hydration attempt — one that runs after other work
1564
- * may already have populated the runtime for real (e.g. quick mode's
1565
- * background warmup building a genuine `wordIndex`, docCount > 0). The
1566
- * unconditional version above is only safe for the FIRST hydration attempt,
1567
- * made before anything else has run: clearing `cachedExports` is correct
1568
- * there because there is nothing yet to destroy.
1569
- *
1570
- * A late call cannot make that assumption, and `cachedExports` and
1571
- * `projectRulesScan` are populated by INDEPENDENT tasks from whatever else is
1572
- * running (nothing else in quick mode touches either), so this guards each
1573
- * field on its OWN "nothing computed since" check rather than one
1574
- * all-or-nothing bail-out — the field a concurrent task actually populated is
1575
- * protected without needlessly withholding the other, still-idle field the
1576
- * snapshot could still supply. Returns whether it hydrated ANY field, so the
1577
- * caller can log honestly instead of claiming success when nothing changed.
1578
- *
1579
- * #1785 F5: takes ONLY the narrow `{ cachedExports, projectRulesScan }`
1580
- * shape (see `ProjectSnapshotExportsAndRules`) — deliberately no `wordIndex`
1581
- * parameter at all, not even an optional one. The retroactive path's
1582
- * captured/reloaded snapshot always comes from
1583
- * `loadProjectSnapshotExportsAndRules`, which never parses `wordIndex` in
1584
- * the first place (avoiding its dominant share of a full body parse's cost —
1585
- * see that function's doc comment for the measured numbers). This is also
1586
- * the right behavior, not just a performance shortcut: quick mode's warmup
1587
- * is the only thing that ever builds a `wordIndex` in this window, and F2's
1588
- * hazard was this exact late-hydration path nulling that live index from a
1589
- * disk copy that predated it — removing the parameter removes the hazard by
1590
- * construction instead of merely guarding against it at runtime.
1591
- */
1592
- export function hydrateRuntimeFromProjectSnapshotIfIdle(runtime, snapshot) {
1593
- let hydratedAnything = false;
1594
- if (runtime.cachedExports.size === 0 && snapshot.cachedExports.length > 0) {
1595
- runtime.cachedExports.clear();
1596
- for (const [name, filePath] of snapshot.cachedExports) {
1597
- runtime.cachedExports.set(name, filePath);
1598
- }
1599
- hydratedAnything = true;
1600
- }
1601
- if (!runtime.projectRulesScan.hasCustomRules &&
1602
- runtime.projectRulesScan.rules.length === 0 &&
1603
- snapshot.projectRulesScan) {
1604
- runtime.projectRulesScan = snapshot.projectRulesScan;
1605
- hydratedAnything = true;
1606
- }
1607
- return hydratedAnything;
1608
- }
1609
- export function saveRuntimeProjectSnapshot(args) {
1610
- try {
1611
- if (typeof args.runtime.projectSeq !== "number")
1612
- return;
1613
- const existing = loadProjectSnapshot(args.cwd);
1614
- let conventions = args.conventions ?? existing?.conventions;
1615
- if (!conventions) {
1616
- try {
1617
- conventions = detectProjectConventions(args.cwd);
1618
- }
1619
- catch (err) {
1620
- args.dbg?.(`project_snapshot: convention detection failed: ${err}`);
1621
- }
1622
- }
1623
- const snapshot = buildProjectSnapshotFromRuntime({
1624
- ...args,
1625
- startupScan: args.startupScan ?? existing?.startupScan,
1626
- languageProfile: args.languageProfile ?? existing?.languageProfile,
1627
- conventions,
1628
- });
1629
- if (existing) {
1630
- snapshot.files = existing.files ?? {};
1631
- snapshot.symbols = existing.symbols ?? {};
1632
- snapshot.reverseDeps = existing.reverseDeps ?? {};
1633
- // The word index is built by its own session task, which may not have
1634
- // finished when another task triggers a save — keep the prior index
1635
- // rather than clobbering it with undefined. #348: only carry it forward
1636
- // when `existing` was built AT THIS SAME seq — otherwise a stale
1637
- // snapshot's leftover index (already correctly rejected as stale by
1638
- // isProjectSnapshotFresh on load, seq mismatch) would get silently
1639
- // re-stamped with the CURRENT seq by this save, "laundering" a stale
1640
- // index into looking fresh before the word-index task even runs.
1641
- if (!snapshot.wordIndex &&
1642
- existing.wordIndex &&
1643
- existing.seq === snapshot.seq) {
1644
- snapshot.wordIndex = existing.wordIndex;
1645
- }
1646
- }
1647
- saveProjectSnapshot(args.cwd, snapshot);
1648
- args.dbg?.(`project_snapshot: saved seq=${snapshot.seq} exports=${snapshot.cachedExports.length}`);
1649
- }
1650
- catch (err) {
1651
- args.dbg?.(`project_snapshot: save failed: ${err}`);
1652
- }
1653
- }