@herbertgao/pi-extensions 2026.10.0 → 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 (769) hide show
  1. package/README.md +16 -12
  2. package/THIRD_PARTY_NOTICES.md +3 -3
  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/.opencode/plugins/index.js +4 -0
  10. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +43 -3
  11. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +142 -59
  12. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
  13. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  14. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
  15. package/node_modules/@dietrichgebert/ponytail/README.md +52 -242
  16. package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +3 -3
  17. package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +4 -4
  18. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +70 -16
  19. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +8 -7
  20. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
  21. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
  22. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +46 -23
  23. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +39 -7
  24. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +20 -3
  25. package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +3 -3
  26. package/node_modules/@dietrichgebert/ponytail/package.json +3 -3
  27. package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +47 -17
  28. package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -7
  29. package/node_modules/@dietrichgebert/ponytail/skills/plugin.json +5 -0
  30. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
  31. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -27
  32. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
  33. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +21 -18
  34. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +12 -13
  35. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -52
  36. package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
  37. package/node_modules/@herbertgao/pi-agency/README.md +40 -0
  38. package/node_modules/@herbertgao/pi-agency/package.json +53 -0
  39. package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
  45. package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
  46. package/node_modules/@narumitw/pi-caffeinate/package.json +4 -4
  47. package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
  48. package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
  49. package/node_modules/@tifan/pi-recap/package.json +1 -1
  50. package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
  51. package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
  52. package/node_modules/pi-antigravity/README.md +23 -2
  53. package/node_modules/pi-antigravity/package.json +5 -3
  54. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
  55. package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
  56. package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +86 -47
  58. package/node_modules/pi-antigravity/src/image/native.ts +69 -0
  59. package/node_modules/pi-antigravity/src/index.ts +52 -137
  60. package/node_modules/pi-antigravity/src/search/search.ts +130 -31
  61. package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
  62. package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
  63. package/node_modules/pi-antigravity/src/types/types.ts +1 -1
  64. package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
  65. package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
  66. package/node_modules/pi-lens/CHANGELOG.md +1064 -0
  67. package/node_modules/pi-lens/README.md +115 -25
  68. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  69. package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
  70. package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
  71. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  72. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  73. package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
  74. package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
  75. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  76. package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
  77. package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
  78. package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
  79. package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
  80. package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
  81. package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
  82. package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
  83. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  84. package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
  85. package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
  86. package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
  87. package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
  88. package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
  89. package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
  90. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  91. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  92. package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
  93. package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
  94. package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
  95. package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
  96. package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
  97. package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
  98. package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
  99. package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
  100. package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
  101. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  102. package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
  103. package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
  104. package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
  105. package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
  106. package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
  107. package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
  108. package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
  109. package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
  110. package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
  111. package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
  112. package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
  113. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  114. package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
  115. package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
  116. package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
  117. package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
  118. package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
  119. package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
  120. package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
  121. package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
  122. package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
  123. package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
  124. package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
  125. package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
  126. package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
  127. package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
  128. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  129. package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
  130. package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
  131. package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
  132. package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
  133. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  134. package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
  135. package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
  136. package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
  137. package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
  138. package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
  139. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  140. package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
  141. package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
  142. package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
  143. package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
  144. package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
  145. package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
  146. package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
  147. package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
  148. package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
  149. package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
  150. package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
  151. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  152. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  153. package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
  154. package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
  155. package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
  156. package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
  157. package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
  158. package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
  159. package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
  160. package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
  161. package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
  162. package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
  163. package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
  164. package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
  165. package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
  166. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  167. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  168. package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
  169. package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
  170. package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
  171. package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
  172. package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
  173. package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
  174. package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
  175. package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
  176. package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
  177. package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
  178. package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
  179. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  180. package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
  181. package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
  182. package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
  183. package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
  184. package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
  185. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  186. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  187. package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
  188. package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
  189. package/node_modules/pi-lens/dist/index.js +16456 -8335
  190. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
  191. package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
  192. package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
  193. package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
  194. package/node_modules/pi-lens/dist/probes/installer.js +135 -0
  195. package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
  196. package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
  197. package/node_modules/pi-lens/docs/agent-guide.md +13 -4
  198. package/node_modules/pi-lens/docs/agent-tools.md +42 -0
  199. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
  200. package/node_modules/pi-lens/docs/configuration.md +20 -1
  201. package/node_modules/pi-lens/docs/environment-variables.md +52 -2
  202. package/node_modules/pi-lens/docs/features.md +8 -4
  203. package/node_modules/pi-lens/docs/globalconfig.md +1 -0
  204. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  205. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
  206. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  207. package/node_modules/pi-lens/docs/mcp.md +21 -5
  208. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  209. package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
  210. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  211. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
  212. package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
  213. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  214. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
  215. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  216. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  217. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  218. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  219. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  220. package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
  221. package/node_modules/pi-lens/docs/settings.md +1 -0
  222. package/node_modules/pi-lens/docs/usage.md +5 -1
  223. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  224. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  225. package/node_modules/pi-lens/package.json +18 -6
  226. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  227. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  228. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  229. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  230. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  231. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  232. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  233. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  234. package/node_modules/pi-typesafe/README.md +1 -1
  235. package/node_modules/pi-typesafe/dist/backends.d.ts +15 -5
  236. package/node_modules/pi-typesafe/dist/backends.js +18 -0
  237. package/node_modules/pi-typesafe/dist/client.d.ts +4 -3
  238. package/node_modules/pi-typesafe/dist/client.js +22 -7
  239. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  240. package/node_modules/pi-typesafe/dist/index.js +1 -1
  241. package/node_modules/pi-typesafe/dist/usage.d.ts +14 -4
  242. package/node_modules/pi-typesafe/dist/usage.js +147 -42
  243. package/node_modules/pi-typesafe/package.json +1 -1
  244. package/node_modules/pi-web-access/CHANGELOG.md +48 -0
  245. package/node_modules/pi-web-access/README.md +57 -11
  246. package/node_modules/pi-web-access/brave-rate-limit.ts +8 -2
  247. package/node_modules/pi-web-access/curator-page.ts +5 -1
  248. package/node_modules/pi-web-access/curator-server.ts +2 -0
  249. package/node_modules/pi-web-access/declared-web-links.ts +2 -2
  250. package/node_modules/pi-web-access/degoog.ts +323 -0
  251. package/node_modules/pi-web-access/dist/index.js +3483 -2745
  252. package/node_modules/pi-web-access/dist/mcp-cli.js +27443 -0
  253. package/node_modules/pi-web-access/exa.ts +5 -1
  254. package/node_modules/pi-web-access/extract.ts +41 -3
  255. package/node_modules/pi-web-access/firecrawl.ts +35 -4
  256. package/node_modules/pi-web-access/gemini-search.ts +34 -11
  257. package/node_modules/pi-web-access/gemini-web.ts +14 -9
  258. package/node_modules/pi-web-access/index.ts +153 -1202
  259. package/node_modules/pi-web-access/keenable.ts +221 -0
  260. package/node_modules/pi-web-access/mcp-cli.ts +40 -0
  261. package/node_modules/pi-web-access/mcp-server.ts +81 -0
  262. package/node_modules/pi-web-access/mcp-tool-definitions.ts +117 -0
  263. package/node_modules/pi-web-access/openai-search.ts +34 -1
  264. package/node_modules/pi-web-access/package.json +23 -5
  265. package/node_modules/pi-web-access/perplexity.ts +64 -56
  266. package/node_modules/pi-web-access/tool-arguments.ts +20 -0
  267. package/node_modules/pi-web-access/video-extract.ts +1 -1
  268. package/node_modules/pi-web-access/web-tool-contract.ts +86 -0
  269. package/node_modules/pi-web-access/web-tool-core.ts +1502 -0
  270. package/node_modules/pi-web-access/youtube-extract.ts +2 -5
  271. package/package.json +17 -12
  272. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -329
  273. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -346
  274. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +0 -21
  275. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +0 -62
  276. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
  277. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +0 -115
  278. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +0 -27
  279. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +0 -27
  280. package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
  281. package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
  282. package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +0 -4
  283. package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +0 -4
  284. package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
  285. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
  286. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
  287. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
  288. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  289. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  290. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  291. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  292. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  293. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  294. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  295. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  296. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  297. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  298. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  299. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  300. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  301. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  302. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  303. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  304. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  305. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  306. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  307. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  308. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  309. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  310. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  311. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  312. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  313. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  314. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  315. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  316. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  317. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  318. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  319. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  320. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  321. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  322. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  323. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  324. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  325. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  326. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  327. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  328. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  329. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  330. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  331. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  332. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  333. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  334. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  335. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  336. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  337. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  338. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  339. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  340. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  341. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  342. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  343. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  344. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  345. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  346. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  347. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  348. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  349. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  350. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  351. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  352. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  353. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  354. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  355. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  356. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  357. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  358. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  359. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  360. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  361. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  362. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  363. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  364. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  365. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  366. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  367. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  368. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  369. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  370. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  371. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  372. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  373. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  374. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  375. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  376. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  377. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  378. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  379. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  380. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  381. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  382. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  383. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  384. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  385. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  386. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  387. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  388. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  389. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  390. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  391. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  392. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  393. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  394. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  395. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  396. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  397. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  398. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  399. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  400. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  401. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  402. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  403. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  404. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  405. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  406. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  407. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  408. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  409. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  410. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  411. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  412. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  413. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  414. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  415. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  416. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  417. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  418. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  419. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  420. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  421. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  422. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  423. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  424. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  425. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  426. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  427. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  428. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  429. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  430. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  431. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  432. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  433. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  434. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  435. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  436. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  437. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  438. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  439. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  440. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  441. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  442. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  443. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  444. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  445. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  446. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  447. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  448. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  449. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  450. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  451. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  452. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  453. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  454. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  455. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  456. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  457. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  458. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  459. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  460. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  461. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  462. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  463. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  464. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  465. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  466. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  467. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  468. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  469. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  470. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  471. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  472. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  473. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  474. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  475. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  476. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  477. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  478. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  479. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  480. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  481. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  482. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  483. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  484. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  485. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  486. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  487. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  488. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  489. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  490. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  491. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  492. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  493. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  494. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  495. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  496. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  497. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  498. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  499. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  500. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  501. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  502. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  503. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  504. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  505. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  506. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  507. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  508. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  509. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  510. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  511. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  512. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  513. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  514. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  515. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  516. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  517. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  518. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  519. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  520. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  521. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  522. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  523. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  524. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  525. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  526. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  527. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  528. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  529. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  530. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  531. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  532. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  533. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  534. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  535. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  536. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  537. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  538. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  539. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  540. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  541. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  542. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  543. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  544. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  545. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  546. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  547. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  548. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  549. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  550. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  551. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  552. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  553. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  554. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  555. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  556. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  557. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  558. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  559. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  560. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  561. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  562. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  563. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  564. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  565. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  566. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  567. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  568. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  569. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  570. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  571. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  572. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  573. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  574. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  575. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  576. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  577. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  578. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  579. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  580. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  581. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  582. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  583. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  584. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  585. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  586. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  587. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  588. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  589. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  590. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  591. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  592. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  593. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  594. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  595. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  596. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  597. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  598. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  599. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  600. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  601. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  602. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  603. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  604. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  605. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  606. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  607. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  608. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  609. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  610. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  611. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  612. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  613. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  614. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  615. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  616. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  617. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  618. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  619. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  620. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  621. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  622. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  623. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  624. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  625. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  626. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  627. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  628. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  629. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  630. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  631. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  632. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  633. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  634. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  635. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  636. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  637. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  638. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  639. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  640. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  641. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  642. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  643. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  644. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  645. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  646. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  647. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  648. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  649. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  650. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  651. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  652. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  653. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  654. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  655. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  656. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  657. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  658. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  659. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  660. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  661. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  662. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  663. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  664. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  665. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  666. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  667. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  668. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  669. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  670. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  671. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  672. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  673. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  674. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  675. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  676. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  677. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  678. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  679. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  680. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  681. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  682. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  683. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  684. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  685. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  686. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  687. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  688. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  689. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  690. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  691. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  692. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  693. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  694. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  695. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  696. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  697. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  698. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  699. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  700. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  701. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  702. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  703. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  704. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  705. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  706. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  707. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  708. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  709. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  710. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  711. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  712. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  713. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  714. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  715. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  716. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  717. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  718. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  719. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  720. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  721. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  722. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  723. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  724. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  725. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  726. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  727. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  728. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  729. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  730. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  731. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  732. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  733. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  734. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  735. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  736. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  737. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  738. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  739. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  740. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  741. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  742. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  743. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  744. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  745. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  746. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  747. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  748. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  749. package/node_modules/pi-lens/dist/i18n.js +0 -69
  750. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  751. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  752. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  753. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  754. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  755. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  756. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  757. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  758. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  759. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  760. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  761. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  762. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  763. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  764. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  765. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  766. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  767. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  768. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  769. 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
- }