@herbertgao/pi-extensions 2026.10.1 → 2026.10.3

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 (934) hide show
  1. package/README.md +14 -17
  2. package/THIRD_PARTY_NOTICES.md +2 -2
  3. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
  4. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
  5. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
  6. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
  7. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
  8. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
  9. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
  10. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
  11. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
  12. package/node_modules/@dietrichgebert/ponytail/README.md +68 -72
  13. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +7 -0
  14. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
  15. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
  16. package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
  17. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
  18. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -32
  19. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
  20. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +17 -16
  21. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +9 -10
  22. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -57
  23. package/node_modules/{remote-pi → @herbertgao/pi-agency}/LICENSE +1 -1
  24. package/node_modules/@herbertgao/pi-agency/README.md +40 -0
  25. package/node_modules/@herbertgao/pi-agency/package.json +53 -0
  26. package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
  32. package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
  33. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  34. package/node_modules/@herbertgao/pi-subagents/README.md +1 -1
  35. package/node_modules/@herbertgao/pi-subagents/package.json +4 -5
  36. package/node_modules/@herbertgao/pi-subagents/src/index.ts +1 -1
  37. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +1 -1
  38. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +48 -72
  39. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +1 -1
  40. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +15 -6
  41. package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +4 -1
  42. package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +9 -8
  43. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +6 -1
  44. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
  45. package/node_modules/@narumitw/pi-caffeinate/package.json +3 -3
  46. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +13 -1
  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 +1159 -0
  67. package/node_modules/pi-lens/README.md +116 -25
  68. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
  69. package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
  70. package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
  71. package/node_modules/pi-lens/dist/chunk-2FQ62SPM.js +83 -0
  72. package/node_modules/pi-lens/dist/chunk-2UMYMRI3.js +118 -0
  73. package/node_modules/pi-lens/dist/chunk-34OCDWOS.js +48 -0
  74. package/node_modules/pi-lens/dist/chunk-3FERWMCH.js +144 -0
  75. package/node_modules/pi-lens/dist/chunk-3JL6IFD7.js +97 -0
  76. package/node_modules/pi-lens/dist/chunk-3JSEWUWI.js +327 -0
  77. package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
  78. package/node_modules/pi-lens/dist/chunk-3MRXIJ6I.js +79 -0
  79. package/node_modules/pi-lens/dist/chunk-3P4PF2BX.js +2250 -0
  80. package/node_modules/pi-lens/dist/chunk-3PNFWVHT.js +201 -0
  81. package/node_modules/pi-lens/dist/chunk-3TTPKMEF.js +162 -0
  82. package/node_modules/pi-lens/dist/chunk-5JHXUETW.js +326 -0
  83. package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
  84. package/node_modules/pi-lens/dist/chunk-64LU2OVH.js +341 -0
  85. package/node_modules/pi-lens/dist/chunk-6B5I6ICN.js +44 -0
  86. package/node_modules/pi-lens/dist/chunk-6P54TMWL.js +274 -0
  87. package/node_modules/pi-lens/dist/chunk-6UYA642J.js +145 -0
  88. package/node_modules/pi-lens/dist/chunk-6XX5HCLK.js +398 -0
  89. package/node_modules/pi-lens/dist/chunk-7AXO3ZDF.js +30 -0
  90. package/node_modules/pi-lens/dist/chunk-7BX5DWSA.js +1907 -0
  91. package/node_modules/pi-lens/dist/chunk-7ELY2CSM.js +902 -0
  92. package/node_modules/pi-lens/dist/chunk-7M4T5N3V.js +38 -0
  93. package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
  94. package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
  95. package/node_modules/pi-lens/dist/chunk-AFZYMAIV.js +756 -0
  96. package/node_modules/pi-lens/dist/chunk-ALQRYH6O.js +112 -0
  97. package/node_modules/pi-lens/dist/chunk-AXKPQ6ZI.js +1610 -0
  98. package/node_modules/pi-lens/dist/chunk-BHWHXMQ2.js +62 -0
  99. package/node_modules/pi-lens/dist/chunk-BJF25SUG.js +22 -0
  100. package/node_modules/pi-lens/dist/chunk-BNKLYOOO.js +18 -0
  101. package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
  102. package/node_modules/pi-lens/dist/chunk-CBD2B5CP.js +599 -0
  103. package/node_modules/pi-lens/dist/chunk-CFYISHUW.js +42 -0
  104. package/node_modules/pi-lens/dist/chunk-CMU4RMJB.js +30 -0
  105. package/node_modules/pi-lens/dist/chunk-CULQYFLU.js +14 -0
  106. package/node_modules/pi-lens/dist/chunk-D65GOCJB.js +289 -0
  107. package/node_modules/pi-lens/dist/chunk-D6YF5HKX.js +59 -0
  108. package/node_modules/pi-lens/dist/chunk-DMS52BEP.js +342 -0
  109. package/node_modules/pi-lens/dist/chunk-ELVWJYRV.js +211 -0
  110. package/node_modules/pi-lens/dist/chunk-EUT3B22Y.js +27 -0
  111. package/node_modules/pi-lens/dist/chunk-F5VLZHUW.js +146 -0
  112. package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
  113. package/node_modules/pi-lens/dist/chunk-FGALODG3.js +55 -0
  114. package/node_modules/pi-lens/dist/chunk-G3RLJYA2.js +2358 -0
  115. package/node_modules/pi-lens/dist/chunk-GAYGGICG.js +32 -0
  116. package/node_modules/pi-lens/dist/chunk-H2W2AXD3.js +419 -0
  117. package/node_modules/pi-lens/dist/chunk-H5EV3VMF.js +114 -0
  118. package/node_modules/pi-lens/dist/chunk-HREZQCT3.js +106 -0
  119. package/node_modules/pi-lens/dist/chunk-HSIRMK2D.js +517 -0
  120. package/node_modules/pi-lens/dist/chunk-IMSQR4WO.js +184 -0
  121. package/node_modules/pi-lens/dist/chunk-IQZXOZ7S.js +4816 -0
  122. package/node_modules/pi-lens/dist/chunk-IR6XFA5P.js +37 -0
  123. package/node_modules/pi-lens/dist/chunk-IUKPRZER.js +78 -0
  124. package/node_modules/pi-lens/dist/chunk-IUR76K6A.js +262 -0
  125. package/node_modules/pi-lens/dist/chunk-J3FZR3YY.js +98 -0
  126. package/node_modules/pi-lens/dist/chunk-J6DFZDFA.js +562 -0
  127. package/node_modules/pi-lens/dist/chunk-J7WJZ6W7.js +50 -0
  128. package/node_modules/pi-lens/dist/chunk-JONGANYH.js +202 -0
  129. package/node_modules/pi-lens/dist/chunk-KJCWXUUS.js +425 -0
  130. package/node_modules/pi-lens/dist/chunk-KJN4IOEJ.js +17 -0
  131. package/node_modules/pi-lens/dist/chunk-KMGPUKFD.js +25 -0
  132. package/node_modules/pi-lens/dist/chunk-LD7WFTRX.js +4533 -0
  133. package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
  134. package/node_modules/pi-lens/dist/chunk-LNOMA3MP.js +169 -0
  135. package/node_modules/pi-lens/dist/chunk-MGIWSROG.js +2334 -0
  136. package/node_modules/pi-lens/dist/chunk-MQA2YBAH.js +308 -0
  137. package/node_modules/pi-lens/dist/chunk-MTDRLSI7.js +21720 -0
  138. package/node_modules/pi-lens/dist/chunk-N2IRDBZI.js +2355 -0
  139. package/node_modules/pi-lens/dist/chunk-N77WFUL7.js +50 -0
  140. package/node_modules/pi-lens/dist/chunk-N7YNJE5R.js +466 -0
  141. package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
  142. package/node_modules/pi-lens/dist/chunk-NFXP47RV.js +999 -0
  143. package/node_modules/pi-lens/dist/chunk-NJF2X5XF.js +45 -0
  144. package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
  145. package/node_modules/pi-lens/dist/chunk-O7HZX7CA.js +309 -0
  146. package/node_modules/pi-lens/dist/chunk-OC7LYAU5.js +708 -0
  147. package/node_modules/pi-lens/dist/chunk-OEX24ECI.js +348 -0
  148. package/node_modules/pi-lens/dist/chunk-OYOB3ZFG.js +78 -0
  149. package/node_modules/pi-lens/dist/chunk-OYTMZXRF.js +41 -0
  150. package/node_modules/pi-lens/dist/chunk-OZJINN4O.js +84 -0
  151. package/node_modules/pi-lens/dist/chunk-PCHDGUKT.js +23 -0
  152. package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
  153. package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
  154. package/node_modules/pi-lens/dist/chunk-RJFED73B.js +79 -0
  155. package/node_modules/pi-lens/dist/chunk-RTQNRM5X.js +27 -0
  156. package/node_modules/pi-lens/dist/chunk-S6AMWKPC.js +189 -0
  157. package/node_modules/pi-lens/dist/chunk-T4QZ6FEH.js +308 -0
  158. package/node_modules/pi-lens/dist/chunk-T54VJRAD.js +5456 -0
  159. package/node_modules/pi-lens/dist/chunk-TA5L5KWQ.js +167 -0
  160. package/node_modules/pi-lens/dist/chunk-TQFBZVCG.js +27 -0
  161. package/node_modules/pi-lens/dist/chunk-U52SAGRM.js +62 -0
  162. package/node_modules/pi-lens/dist/chunk-UEAHAMEI.js +5814 -0
  163. package/node_modules/pi-lens/dist/chunk-UGSMCQ4D.js +327 -0
  164. package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
  165. package/node_modules/pi-lens/dist/chunk-USHG4DLH.js +459 -0
  166. package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
  167. package/node_modules/pi-lens/dist/chunk-V5MEBST3.js +4139 -0
  168. package/node_modules/pi-lens/dist/chunk-VGEVL5EQ.js +29 -0
  169. package/node_modules/pi-lens/dist/chunk-VPWKF3GN.js +87 -0
  170. package/node_modules/pi-lens/dist/chunk-VSMJK3ZJ.js +1250 -0
  171. package/node_modules/pi-lens/dist/chunk-VYKKMYVT.js +560 -0
  172. package/node_modules/pi-lens/dist/chunk-VZHJS6DR.js +442 -0
  173. package/node_modules/pi-lens/dist/chunk-WATC3IQR.js +213 -0
  174. package/node_modules/pi-lens/dist/chunk-WPE5FPGR.js +54 -0
  175. package/node_modules/pi-lens/dist/chunk-WSEWKCXU.js +188 -0
  176. package/node_modules/pi-lens/dist/chunk-WW5BLB74.js +659 -0
  177. package/node_modules/pi-lens/dist/chunk-XGUL4SLQ.js +406 -0
  178. package/node_modules/pi-lens/dist/chunk-XHSCVSOL.js +170 -0
  179. package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
  180. package/node_modules/pi-lens/dist/chunk-YLN4K6WD.js +385 -0
  181. package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
  182. package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
  183. package/node_modules/pi-lens/dist/chunk-Z6BB4JLQ.js +478 -0
  184. package/node_modules/pi-lens/dist/chunk-Z7QQA2KT.js +27 -0
  185. package/node_modules/pi-lens/dist/chunk-ZETYB3XG.js +2626 -0
  186. package/node_modules/pi-lens/dist/chunk-ZMOMPX2Z.js +87 -0
  187. package/node_modules/pi-lens/dist/chunk-ZOEYVXSV.js +149 -0
  188. package/node_modules/pi-lens/dist/chunk-ZZUFZUQZ.js +12120 -0
  189. package/node_modules/pi-lens/dist/index.js +39712 -29876
  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 +22688 -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 +5 -4
  200. package/node_modules/pi-lens/docs/configuration.md +28 -2
  201. package/node_modules/pi-lens/docs/custom-rules.md +25 -2
  202. package/node_modules/pi-lens/docs/environment-variables.md +86 -8
  203. package/node_modules/pi-lens/docs/features.md +45 -5
  204. package/node_modules/pi-lens/docs/globalconfig.md +2 -1
  205. package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
  206. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +37 -4
  207. package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
  208. package/node_modules/pi-lens/docs/mcp.md +21 -5
  209. package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
  210. package/node_modules/pi-lens/docs/pi-lens-fixer.md +160 -88
  211. package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
  212. package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +736 -0
  213. package/node_modules/pi-lens/docs/pi-lens-monitor.md +75 -7
  214. package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
  215. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +189 -48
  216. package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
  217. package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
  218. package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
  219. package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
  220. package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
  221. package/node_modules/pi-lens/docs/schema/pi-lens-config-v1.json +530 -0
  222. package/node_modules/pi-lens/docs/servercapabilities.md +56 -49
  223. package/node_modules/pi-lens/docs/settings.md +1 -0
  224. package/node_modules/pi-lens/docs/usage.md +5 -1
  225. package/node_modules/pi-lens/docs/word-index.md +6 -0
  226. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
  227. package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
  228. package/node_modules/pi-lens/package.json +18 -6
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-identity-operator-on-literals-test.yml +12 -2
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-js-test.yml +2 -0
  232. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-test.yml +2 -0
  233. package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  234. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-identity-operator-on-literals.yml +20 -12
  235. package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error-js.yml +2 -2
  236. package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error.yml +2 -2
  237. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
  238. package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
  239. package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
  240. package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
  241. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
  242. package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
  243. package/node_modules/pi-web-access/CHANGELOG.md +28 -0
  244. package/node_modules/pi-web-access/README.md +10 -6
  245. package/node_modules/pi-web-access/ceramic.ts +219 -0
  246. package/node_modules/pi-web-access/curator-page.ts +3 -1
  247. package/node_modules/pi-web-access/curator-server.ts +1 -0
  248. package/node_modules/pi-web-access/dist/index.js +846 -588
  249. package/node_modules/pi-web-access/dist/mcp-cli.js +725 -516
  250. package/node_modules/pi-web-access/gemini-search.ts +8 -4
  251. package/node_modules/pi-web-access/github-extract.ts +34 -2
  252. package/node_modules/pi-web-access/index.ts +72 -30
  253. package/node_modules/pi-web-access/openai-search.ts +3 -3
  254. package/node_modules/pi-web-access/package.json +2 -2
  255. package/node_modules/pi-web-access/page-query.ts +6 -9
  256. package/node_modules/pi-web-access/parallel-mcp.ts +2 -0
  257. package/node_modules/pi-web-access/query-rewrite.ts +2 -2
  258. package/node_modules/pi-web-access/storage.ts +49 -0
  259. package/node_modules/pi-web-access/summary-review.ts +7 -6
  260. package/node_modules/pi-web-access/utils.ts +21 -6
  261. package/node_modules/pi-web-access/web-tool-core.ts +1 -0
  262. package/package.json +19 -19
  263. package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -212
  264. package/node_modules/@dietrichgebert/ponytail/README.ja.md +0 -212
  265. package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -212
  266. package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +0 -212
  267. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
  268. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
  269. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
  270. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
  271. package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
  272. package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
  273. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
  274. package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
  275. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
  276. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
  277. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
  278. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
  279. package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
  280. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
  281. package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
  282. package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
  283. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
  284. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
  285. package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
  286. package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
  287. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
  288. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
  289. package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
  290. package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
  291. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
  292. package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
  293. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
  294. package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
  295. package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
  296. package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
  297. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
  298. package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
  299. package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
  300. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
  301. package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
  302. package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
  303. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
  304. package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
  305. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
  306. package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
  307. package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
  308. package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
  309. package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
  310. package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
  311. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
  312. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
  313. package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
  314. package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
  315. package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
  316. package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
  317. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
  318. package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
  319. package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
  320. package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
  321. package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
  322. package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
  323. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
  324. package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
  325. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
  326. package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
  327. package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
  328. package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
  329. package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
  330. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
  331. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
  332. package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
  333. package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
  334. package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
  335. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
  336. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
  337. package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
  338. package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
  339. package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
  340. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
  341. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
  342. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
  343. package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
  344. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
  345. package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
  346. package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
  347. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
  348. package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
  349. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
  350. package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
  351. package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
  352. package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
  353. package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
  354. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
  355. package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
  356. package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
  357. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
  358. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
  359. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
  360. package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
  361. package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
  362. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
  363. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
  364. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
  365. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
  366. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
  367. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
  368. package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
  369. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
  370. package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
  371. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
  372. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
  373. package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
  374. package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
  375. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
  376. package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
  377. package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
  378. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
  379. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
  380. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
  381. package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
  382. package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
  383. package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
  384. package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
  385. package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
  386. package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
  387. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
  388. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
  389. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
  390. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
  391. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
  392. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
  393. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
  394. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
  395. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
  396. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
  397. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
  398. package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
  399. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
  400. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
  401. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
  402. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
  403. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
  404. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
  405. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
  406. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
  407. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
  408. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
  409. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
  410. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
  411. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
  412. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
  413. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
  414. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
  415. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
  416. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
  417. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
  418. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
  419. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
  420. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
  421. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
  422. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
  423. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
  424. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
  425. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
  426. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
  427. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
  428. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
  429. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
  430. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
  431. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
  432. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
  433. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
  434. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
  435. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
  436. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
  437. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
  438. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
  439. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
  440. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
  441. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
  442. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
  443. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
  444. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
  445. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
  446. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
  447. package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
  448. package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
  449. package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
  450. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
  451. package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
  452. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
  453. package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
  454. package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
  455. package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
  456. package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
  457. package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
  458. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
  459. package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
  460. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
  461. package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
  462. package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
  463. package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
  464. package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
  465. package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
  466. package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
  467. package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
  468. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
  469. package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
  470. package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
  471. package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
  472. package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
  473. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
  474. package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
  475. package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
  476. package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
  477. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
  478. package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
  479. package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
  480. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
  481. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
  482. package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
  483. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
  484. package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
  485. package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
  486. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
  487. package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
  488. package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
  489. package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
  490. package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
  491. package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
  492. package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
  493. package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
  494. package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
  495. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
  496. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
  497. package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
  498. package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
  499. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
  500. package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
  501. package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
  502. package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
  503. package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
  504. package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
  505. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
  506. package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
  507. package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
  508. package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
  509. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
  510. package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
  511. package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
  512. package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
  513. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
  514. package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
  515. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
  516. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
  517. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
  518. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
  519. package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
  520. package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
  521. package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
  522. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
  523. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
  524. package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
  525. package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
  526. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
  527. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
  528. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
  529. package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
  530. package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
  531. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
  532. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
  533. package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
  534. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
  535. package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
  536. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
  537. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
  538. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
  539. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
  540. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
  541. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
  542. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
  543. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
  544. package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
  545. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
  546. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
  547. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
  548. package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
  549. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
  550. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
  551. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
  552. package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
  553. package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
  554. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
  555. package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
  556. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
  557. package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
  558. package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
  559. package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
  560. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
  561. package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
  562. package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
  563. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
  564. package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
  565. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
  566. package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
  567. package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
  568. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
  569. package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
  570. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
  571. package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
  572. package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
  573. package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
  574. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
  575. package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
  576. package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
  577. package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
  578. package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
  579. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
  580. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
  581. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
  582. package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
  583. package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
  584. package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
  585. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
  586. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
  587. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
  588. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
  589. package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
  590. package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
  591. package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
  592. package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
  593. package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
  594. package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
  595. package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
  596. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
  597. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
  598. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
  599. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
  600. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
  601. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
  602. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
  603. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
  604. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
  605. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
  606. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
  607. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
  608. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
  609. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
  610. package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
  611. package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
  612. package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
  613. package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
  614. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
  615. package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
  616. package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
  617. package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
  618. package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
  619. package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
  620. package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
  621. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
  622. package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
  623. package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
  624. package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
  625. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
  626. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
  627. package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
  628. package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
  629. package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
  630. package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
  631. package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
  632. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
  633. package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
  634. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
  635. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
  636. package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
  637. package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
  638. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
  639. package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
  640. package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
  641. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
  642. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
  643. package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
  644. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
  645. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
  646. package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
  647. package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
  648. package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
  649. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
  650. package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
  651. package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
  652. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
  653. package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
  654. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
  655. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
  656. package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
  657. package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
  658. package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
  659. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
  660. package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
  661. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
  662. package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
  663. package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
  664. package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
  665. package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
  666. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
  667. package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
  668. package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
  669. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
  670. package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
  671. package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
  672. package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
  673. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
  674. package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
  675. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
  676. package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
  677. package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
  678. package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
  679. package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
  680. package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
  681. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
  682. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
  683. package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
  684. package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
  685. package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
  686. package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
  687. package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
  688. package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
  689. package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
  690. package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
  691. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
  692. package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
  693. package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
  694. package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
  695. package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
  696. package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
  697. package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
  698. package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
  699. package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
  700. package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
  701. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
  702. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
  703. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
  704. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
  705. package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
  706. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
  707. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
  708. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
  709. package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
  710. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
  711. package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
  712. package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
  713. package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
  714. package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
  715. package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
  716. package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
  717. package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
  718. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
  719. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
  720. package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
  721. package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
  722. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
  723. package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
  724. package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
  725. package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
  726. package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
  727. package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
  728. package/node_modules/pi-lens/dist/i18n.js +0 -69
  729. package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
  730. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
  731. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
  732. package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
  733. package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
  734. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
  735. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
  736. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
  737. package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
  738. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
  739. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
  740. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
  741. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
  742. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
  743. package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
  744. package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
  745. package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
  746. package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
  747. package/node_modules/pi-lens/dist/tools/shared.js +0 -29
  748. package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
  749. package/node_modules/pi-web-access/opencode-session-headers.ts +0 -24
  750. package/node_modules/remote-pi/README.md +0 -675
  751. package/node_modules/remote-pi/dist/actions/handlers.d.ts +0 -122
  752. package/node_modules/remote-pi/dist/actions/handlers.js +0 -160
  753. package/node_modules/remote-pi/dist/actions/handlers.js.map +0 -1
  754. package/node_modules/remote-pi/dist/actions/registry.d.ts +0 -39
  755. package/node_modules/remote-pi/dist/actions/registry.js +0 -58
  756. package/node_modules/remote-pi/dist/actions/registry.js.map +0 -1
  757. package/node_modules/remote-pi/dist/bin/supervisord.d.ts +0 -2
  758. package/node_modules/remote-pi/dist/bin/supervisord.js +0 -86
  759. package/node_modules/remote-pi/dist/bin/supervisord.js.map +0 -1
  760. package/node_modules/remote-pi/dist/commands/builtin_mirror.d.ts +0 -58
  761. package/node_modules/remote-pi/dist/commands/builtin_mirror.js +0 -71
  762. package/node_modules/remote-pi/dist/commands/builtin_mirror.js.map +0 -1
  763. package/node_modules/remote-pi/dist/commands/list_commands.d.ts +0 -60
  764. package/node_modules/remote-pi/dist/commands/list_commands.js +0 -73
  765. package/node_modules/remote-pi/dist/commands/list_commands.js.map +0 -1
  766. package/node_modules/remote-pi/dist/config.d.ts +0 -62
  767. package/node_modules/remote-pi/dist/config.js +0 -115
  768. package/node_modules/remote-pi/dist/config.js.map +0 -1
  769. package/node_modules/remote-pi/dist/daemon/client.d.ts +0 -20
  770. package/node_modules/remote-pi/dist/daemon/client.js +0 -131
  771. package/node_modules/remote-pi/dist/daemon/client.js.map +0 -1
  772. package/node_modules/remote-pi/dist/daemon/control_protocol.d.ts +0 -176
  773. package/node_modules/remote-pi/dist/daemon/control_protocol.js +0 -63
  774. package/node_modules/remote-pi/dist/daemon/control_protocol.js.map +0 -1
  775. package/node_modules/remote-pi/dist/daemon/cron_log.d.ts +0 -45
  776. package/node_modules/remote-pi/dist/daemon/cron_log.js +0 -72
  777. package/node_modules/remote-pi/dist/daemon/cron_log.js.map +0 -1
  778. package/node_modules/remote-pi/dist/daemon/cron_registry.d.ts +0 -80
  779. package/node_modules/remote-pi/dist/daemon/cron_registry.js +0 -194
  780. package/node_modules/remote-pi/dist/daemon/cron_registry.js.map +0 -1
  781. package/node_modules/remote-pi/dist/daemon/id.d.ts +0 -24
  782. package/node_modules/remote-pi/dist/daemon/id.js +0 -36
  783. package/node_modules/remote-pi/dist/daemon/id.js.map +0 -1
  784. package/node_modules/remote-pi/dist/daemon/install.d.ts +0 -170
  785. package/node_modules/remote-pi/dist/daemon/install.js +0 -547
  786. package/node_modules/remote-pi/dist/daemon/install.js.map +0 -1
  787. package/node_modules/remote-pi/dist/daemon/registry.d.ts +0 -63
  788. package/node_modules/remote-pi/dist/daemon/registry.js +0 -153
  789. package/node_modules/remote-pi/dist/daemon/registry.js.map +0 -1
  790. package/node_modules/remote-pi/dist/daemon/rpc_child.d.ts +0 -194
  791. package/node_modules/remote-pi/dist/daemon/rpc_child.js +0 -389
  792. package/node_modules/remote-pi/dist/daemon/rpc_child.js.map +0 -1
  793. package/node_modules/remote-pi/dist/daemon/supervisor.d.ts +0 -93
  794. package/node_modules/remote-pi/dist/daemon/supervisor.js +0 -596
  795. package/node_modules/remote-pi/dist/daemon/supervisor.js.map +0 -1
  796. package/node_modules/remote-pi/dist/extension_ui_bridge.d.ts +0 -26
  797. package/node_modules/remote-pi/dist/extension_ui_bridge.js +0 -341
  798. package/node_modules/remote-pi/dist/extension_ui_bridge.js.map +0 -1
  799. package/node_modules/remote-pi/dist/index.d.ts +0 -213
  800. package/node_modules/remote-pi/dist/index.js +0 -4996
  801. package/node_modules/remote-pi/dist/index.js.map +0 -1
  802. package/node_modules/remote-pi/dist/mcp/mesh_result.d.ts +0 -10
  803. package/node_modules/remote-pi/dist/mcp/mesh_result.js +0 -42
  804. package/node_modules/remote-pi/dist/mcp/mesh_result.js.map +0 -1
  805. package/node_modules/remote-pi/dist/mcp/mesh_server.d.ts +0 -16
  806. package/node_modules/remote-pi/dist/mcp/mesh_server.js +0 -327
  807. package/node_modules/remote-pi/dist/mcp/mesh_server.js.map +0 -1
  808. package/node_modules/remote-pi/dist/mesh/canonical.d.ts +0 -30
  809. package/node_modules/remote-pi/dist/mesh/canonical.js +0 -61
  810. package/node_modules/remote-pi/dist/mesh/canonical.js.map +0 -1
  811. package/node_modules/remote-pi/dist/mesh/client.d.ts +0 -18
  812. package/node_modules/remote-pi/dist/mesh/client.js +0 -125
  813. package/node_modules/remote-pi/dist/mesh/client.js.map +0 -1
  814. package/node_modules/remote-pi/dist/mesh/encoding.d.ts +0 -38
  815. package/node_modules/remote-pi/dist/mesh/encoding.js +0 -216
  816. package/node_modules/remote-pi/dist/mesh/encoding.js.map +0 -1
  817. package/node_modules/remote-pi/dist/mesh/self_revoke.d.ts +0 -73
  818. package/node_modules/remote-pi/dist/mesh/self_revoke.js +0 -401
  819. package/node_modules/remote-pi/dist/mesh/self_revoke.js.map +0 -1
  820. package/node_modules/remote-pi/dist/mesh/siblings.d.ts +0 -49
  821. package/node_modules/remote-pi/dist/mesh/siblings.js +0 -159
  822. package/node_modules/remote-pi/dist/mesh/siblings.js.map +0 -1
  823. package/node_modules/remote-pi/dist/mesh/types.d.ts +0 -34
  824. package/node_modules/remote-pi/dist/mesh/types.js +0 -11
  825. package/node_modules/remote-pi/dist/mesh/types.js.map +0 -1
  826. package/node_modules/remote-pi/dist/mesh/verify.d.ts +0 -17
  827. package/node_modules/remote-pi/dist/mesh/verify.js +0 -86
  828. package/node_modules/remote-pi/dist/mesh/verify.js.map +0 -1
  829. package/node_modules/remote-pi/dist/pairing/crypto.d.ts +0 -8
  830. package/node_modules/remote-pi/dist/pairing/crypto.js +0 -22
  831. package/node_modules/remote-pi/dist/pairing/crypto.js.map +0 -1
  832. package/node_modules/remote-pi/dist/pairing/handshake.d.ts +0 -28
  833. package/node_modules/remote-pi/dist/pairing/handshake.js +0 -113
  834. package/node_modules/remote-pi/dist/pairing/handshake.js.map +0 -1
  835. package/node_modules/remote-pi/dist/pairing/noise-sha256.d.ts +0 -16
  836. package/node_modules/remote-pi/dist/pairing/noise-sha256.js +0 -103
  837. package/node_modules/remote-pi/dist/pairing/noise-sha256.js.map +0 -1
  838. package/node_modules/remote-pi/dist/pairing/qr.d.ts +0 -59
  839. package/node_modules/remote-pi/dist/pairing/qr.js +0 -125
  840. package/node_modules/remote-pi/dist/pairing/qr.js.map +0 -1
  841. package/node_modules/remote-pi/dist/pairing/storage.d.ts +0 -120
  842. package/node_modules/remote-pi/dist/pairing/storage.js +0 -604
  843. package/node_modules/remote-pi/dist/pairing/storage.js.map +0 -1
  844. package/node_modules/remote-pi/dist/protocol/codec.d.ts +0 -7
  845. package/node_modules/remote-pi/dist/protocol/codec.js +0 -49
  846. package/node_modules/remote-pi/dist/protocol/codec.js.map +0 -1
  847. package/node_modules/remote-pi/dist/protocol/types.d.ts +0 -393
  848. package/node_modules/remote-pi/dist/protocol/types.js +0 -2
  849. package/node_modules/remote-pi/dist/protocol/types.js.map +0 -1
  850. package/node_modules/remote-pi/dist/rooms.d.ts +0 -29
  851. package/node_modules/remote-pi/dist/rooms.js +0 -57
  852. package/node_modules/remote-pi/dist/rooms.js.map +0 -1
  853. package/node_modules/remote-pi/dist/session/address.d.ts +0 -49
  854. package/node_modules/remote-pi/dist/session/address.js +0 -58
  855. package/node_modules/remote-pi/dist/session/address.js.map +0 -1
  856. package/node_modules/remote-pi/dist/session/agent_bridge.d.ts +0 -55
  857. package/node_modules/remote-pi/dist/session/agent_bridge.js +0 -146
  858. package/node_modules/remote-pi/dist/session/agent_bridge.js.map +0 -1
  859. package/node_modules/remote-pi/dist/session/bridge.d.ts +0 -37
  860. package/node_modules/remote-pi/dist/session/bridge.js +0 -130
  861. package/node_modules/remote-pi/dist/session/bridge.js.map +0 -1
  862. package/node_modules/remote-pi/dist/session/broker.d.ts +0 -189
  863. package/node_modules/remote-pi/dist/session/broker.js +0 -507
  864. package/node_modules/remote-pi/dist/session/broker.js.map +0 -1
  865. package/node_modules/remote-pi/dist/session/broker_remote.d.ts +0 -74
  866. package/node_modules/remote-pi/dist/session/broker_remote.js +0 -583
  867. package/node_modules/remote-pi/dist/session/broker_remote.js.map +0 -1
  868. package/node_modules/remote-pi/dist/session/cwd_lock.d.ts +0 -33
  869. package/node_modules/remote-pi/dist/session/cwd_lock.js +0 -126
  870. package/node_modules/remote-pi/dist/session/cwd_lock.js.map +0 -1
  871. package/node_modules/remote-pi/dist/session/envelope.d.ts +0 -33
  872. package/node_modules/remote-pi/dist/session/envelope.js +0 -117
  873. package/node_modules/remote-pi/dist/session/envelope.js.map +0 -1
  874. package/node_modules/remote-pi/dist/session/global_config.d.ts +0 -33
  875. package/node_modules/remote-pi/dist/session/global_config.js +0 -73
  876. package/node_modules/remote-pi/dist/session/global_config.js.map +0 -1
  877. package/node_modules/remote-pi/dist/session/ipc.d.ts +0 -27
  878. package/node_modules/remote-pi/dist/session/ipc.js +0 -22
  879. package/node_modules/remote-pi/dist/session/ipc.js.map +0 -1
  880. package/node_modules/remote-pi/dist/session/leader_election.d.ts +0 -32
  881. package/node_modules/remote-pi/dist/session/leader_election.js +0 -106
  882. package/node_modules/remote-pi/dist/session/leader_election.js.map +0 -1
  883. package/node_modules/remote-pi/dist/session/local_config.d.ts +0 -55
  884. package/node_modules/remote-pi/dist/session/local_config.js +0 -157
  885. package/node_modules/remote-pi/dist/session/local_config.js.map +0 -1
  886. package/node_modules/remote-pi/dist/session/mesh_message.d.ts +0 -20
  887. package/node_modules/remote-pi/dist/session/mesh_message.js +0 -28
  888. package/node_modules/remote-pi/dist/session/mesh_message.js.map +0 -1
  889. package/node_modules/remote-pi/dist/session/mesh_node.d.ts +0 -116
  890. package/node_modules/remote-pi/dist/session/mesh_node.js +0 -573
  891. package/node_modules/remote-pi/dist/session/mesh_node.js.map +0 -1
  892. package/node_modules/remote-pi/dist/session/peer.d.ts +0 -139
  893. package/node_modules/remote-pi/dist/session/peer.js +0 -451
  894. package/node_modules/remote-pi/dist/session/peer.js.map +0 -1
  895. package/node_modules/remote-pi/dist/session/peer_inventory.d.ts +0 -13
  896. package/node_modules/remote-pi/dist/session/peer_inventory.js +0 -48
  897. package/node_modules/remote-pi/dist/session/peer_inventory.js.map +0 -1
  898. package/node_modules/remote-pi/dist/session/peer_limits.d.ts +0 -9
  899. package/node_modules/remote-pi/dist/session/peer_limits.js +0 -21
  900. package/node_modules/remote-pi/dist/session/peer_limits.js.map +0 -1
  901. package/node_modules/remote-pi/dist/session/setup_wizard.d.ts +0 -39
  902. package/node_modules/remote-pi/dist/session/setup_wizard.js +0 -63
  903. package/node_modules/remote-pi/dist/session/setup_wizard.js.map +0 -1
  904. package/node_modules/remote-pi/dist/session/tool_gate.d.ts +0 -5
  905. package/node_modules/remote-pi/dist/session/tool_gate.js +0 -11
  906. package/node_modules/remote-pi/dist/session/tool_gate.js.map +0 -1
  907. package/node_modules/remote-pi/dist/session/tools.d.ts +0 -24
  908. package/node_modules/remote-pi/dist/session/tools.js +0 -252
  909. package/node_modules/remote-pi/dist/session/tools.js.map +0 -1
  910. package/node_modules/remote-pi/dist/session/wizard.d.ts +0 -13
  911. package/node_modules/remote-pi/dist/session/wizard.js +0 -20
  912. package/node_modules/remote-pi/dist/session/wizard.js.map +0 -1
  913. package/node_modules/remote-pi/dist/settings.d.ts +0 -15
  914. package/node_modules/remote-pi/dist/settings.js +0 -52
  915. package/node_modules/remote-pi/dist/settings.js.map +0 -1
  916. package/node_modules/remote-pi/dist/transport/peer_channel.d.ts +0 -37
  917. package/node_modules/remote-pi/dist/transport/peer_channel.js +0 -98
  918. package/node_modules/remote-pi/dist/transport/peer_channel.js.map +0 -1
  919. package/node_modules/remote-pi/dist/transport/pi_forward_client.d.ts +0 -29
  920. package/node_modules/remote-pi/dist/transport/pi_forward_client.js +0 -62
  921. package/node_modules/remote-pi/dist/transport/pi_forward_client.js.map +0 -1
  922. package/node_modules/remote-pi/dist/transport/relay_client.d.ts +0 -91
  923. package/node_modules/remote-pi/dist/transport/relay_client.js +0 -206
  924. package/node_modules/remote-pi/dist/transport/relay_client.js.map +0 -1
  925. package/node_modules/remote-pi/dist/ui/footer.d.ts +0 -32
  926. package/node_modules/remote-pi/dist/ui/footer.js +0 -34
  927. package/node_modules/remote-pi/dist/ui/footer.js.map +0 -1
  928. package/node_modules/remote-pi/docs/daemon.md +0 -289
  929. package/node_modules/remote-pi/package.json +0 -92
  930. package/node_modules/remote-pi/service-templates/launchd.plist.template +0 -35
  931. package/node_modules/remote-pi/service-templates/systemd.service.template +0 -19
  932. package/node_modules/remote-pi/service-templates/task-launcher.vbs.template +0 -10
  933. package/node_modules/remote-pi/service-templates/task-scheduler.xml.template +0 -38
  934. package/node_modules/remote-pi/skills/agent-network/SKILL.md +0 -310
@@ -1,2080 +0,0 @@
1
- /**
2
- * Identifier-aware inverted word index + BM25 ranking.
3
- *
4
- * The lexical half of the "codebase mental model + hybrid ranking" ask (#162):
5
- * a deterministic, zero-dep index over source identifiers that answers
6
- * "which files are most relevant to <query>" with BM25 relevance plus a small
7
- * set of priors (demote tests/vendor and doc files) and an optional graph
8
- * centrality boost (importedBy count from the reverse-dependency index). It
9
- * complements LSP/symbol navigation rather than duplicating the host's grep:
10
- * grep finds raw substrings; this ranks files by identifier relevance.
11
- *
12
- * Built from file contents during the session scan, persisted in the project
13
- * snapshot (serialize/deserialize below), and queried via an MCP tool. No
14
- * embeddings, no native deps, no daemon — pure in-process TypeScript.
15
- */
16
- import * as fs from "node:fs";
17
- import * as path from "node:path";
18
- import { createDeadline, forEachCooperatively, yieldIfOverBudget, } from "./cooperative-budget.js";
19
- import { incrementDegradationCount } from "./degradation-ledger.js";
20
- import { KIND_EXTENSIONS } from "./file-kinds.js";
21
- import { isTestFileName } from "./file-role.js";
22
- import { PathKeyedMap } from "./path-keyed-map.js";
23
- import { isAtOrAboveHomeDir, normalizeEphemeralMapKey } from "./path-utils.js";
24
- import { createSingleFlight } from "./single-flight.js";
25
- import { createDebounceScheduler, } from "./persist-debounce.js";
26
- import { getWordIndexMaxFilesDerived } from "./project-scale.js";
27
- import { compactPostingsIntoArena, compactPostingsIntoArenaCooperatively, countPostingBackingStores, countPostingEntries, estimateWordIndexStoreBytes, WordForwardEntry, WordIndexFileTable, WordPostingList, } from "./word-index-store.js";
28
- import { logWordIndex } from "./word-index-logger.js";
29
- /**
30
- * The single normalizer every word-index path key folds through (#1025). The
31
- * CHEAP form — slash-fold + win32-lowercase, NO filesystem I/O — because the
32
- * index is a hot, single-process, in-memory structure whose keys this process
33
- * produces itself (`collectSourceFilesAsync`'s walk, `path.resolve` at the
34
- * per-edit seams). `normalizeMapKey`'s `realpathSync` per call would be wrong on
35
- * the BM25 path. Applied at BOTH the build seam and the per-edit update seam so
36
- * a file's on-disk casing (walk) and its tool-input casing (edit) can no longer
37
- * diverge into a duplicate doc entry with stale postings (the #1025 item #2
38
- * bug). Kept as a named alias so every seam is grep-visible and provably shares
39
- * ONE normalizer with the {@link PathKeyedMap}s below.
40
- */
41
- export const wordIndexKey = normalizeEphemeralMapKey;
42
- // Common language keywords / boilerplate — indexing them adds noise and bloats
43
- // postings without improving relevance. Kept deliberately small and
44
- // language-agnostic.
45
- const STOPWORDS = new Set([
46
- "the",
47
- "and",
48
- "for",
49
- "let",
50
- "var",
51
- "const",
52
- "function",
53
- "return",
54
- "if",
55
- "else",
56
- "import",
57
- "export",
58
- "from",
59
- "class",
60
- "interface",
61
- "type",
62
- "enum",
63
- "new",
64
- "this",
65
- "self",
66
- "void",
67
- "null",
68
- "true",
69
- "false",
70
- "async",
71
- "await",
72
- "public",
73
- "private",
74
- "protected",
75
- "static",
76
- "def",
77
- "fn",
78
- "func",
79
- "struct",
80
- "impl",
81
- "pub",
82
- "use",
83
- "mod",
84
- "in",
85
- "of",
86
- "as",
87
- "is",
88
- "not",
89
- "with",
90
- ]);
91
- // Retain the old directory alternatives and anchors; filename classification
92
- // belongs to file-role (#2928). The relevance multiplier remains 0.3.
93
- const VENDOR_DIR_RE = /(^|[\\/])(?:tests?|__tests__|spec|specs|__mocks__|vendor|node_modules|examples?|fixtures?|\.git|dist|build|coverage)([\\/]|$)/i;
94
- const DOC_FILE_RE = /\.(?:md|mdx|markdown|json|json5|jsonc|txt|rst|lock|ya?ml|toml|csv)$/i;
95
- const TEST_VENDOR_PENALTY = 0.3;
96
- const DOC_FILE_PENALTY = 0.5;
97
- const BM25_K1 = 1.2;
98
- const BM25_B = 0.75;
99
- function isTestOrVendor(file) {
100
- return isTestFileName(file) || VENDOR_DIR_RE.test(file);
101
- }
102
- function isDocFile(file) {
103
- return DOC_FILE_RE.test(file);
104
- }
105
- /**
106
- * Split an identifier into lowercased sub-tokens across camelCase, PascalCase,
107
- * snake_case, kebab-case, dotted, and digit boundaries — and keep the whole
108
- * lowercased identifier too. `getUserByID` → [getuserbyid, get, user, by, id];
109
- * `MAX_RETRY_2` → [max_retry_2, max, retry, 2] (whole kept, plus parts).
110
- */
111
- export function splitIdentifier(identifier) {
112
- const parts = new Set();
113
- const whole = identifier.toLowerCase();
114
- if (whole.length >= 2 && !STOPWORDS.has(whole))
115
- parts.add(whole);
116
- for (const chunk of identifier.split(/[^A-Za-z0-9]+/)) {
117
- if (!chunk)
118
- continue;
119
- const spaced = chunk
120
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2") // camelCase → camel Case
121
- .replace(/[A-Z](?=[A-Z][a-z])/g, "$& ") // HTTPServer → HTTP Server (linear; lookahead avoids super-linear backtracking, S5852)
122
- .replace(/([A-Za-z])([0-9])/g, "$1 $2") // retry2 → retry 2
123
- .replace(/([0-9])([A-Za-z])/g, "$1 $2"); // 2fa → 2 fa
124
- for (const sub of spaced.split(/\s+/)) {
125
- const token = sub.toLowerCase();
126
- if (token.length >= 2 && !STOPWORDS.has(token))
127
- parts.add(token);
128
- }
129
- }
130
- return [...parts];
131
- }
132
- /** Extract identifier-like tokens from a line and split each into sub-tokens. */
133
- export function tokenizeLine(line) {
134
- const tokens = [];
135
- const matches = line.match(/[A-Za-z_$][A-Za-z0-9_$]*/g);
136
- if (!matches)
137
- return tokens;
138
- for (const match of matches) {
139
- for (const token of splitIdentifier(match))
140
- tokens.push(token);
141
- }
142
- return tokens;
143
- }
144
- const WORD_INDEX_BUILD_YIELD_BUDGET_MS = 8;
145
- /**
146
- * A line at or above this length is treated as a yield checkpoint on its own.
147
- * The per-50-lines cadence is a proxy for work that only holds on hand-written
148
- * source: a minified/bundled single-line file up to {@link WORD_INDEX_MAX_BYTES}
149
- * that the source filter did not exclude would otherwise get ZERO in-document
150
- * yields — one unbroken tokenize+push burst far above the budget (#1197 review
151
- * finding 4). Checking the clock after any long line makes the cooperativeness
152
- * claim true per BYTE, not just per line count.
153
- */
154
- const WORD_INDEX_LONG_LINE_YIELD_CHARS = 4096;
155
- /**
156
- * Shared cooperative time-slicer for every bulk word-index path (#1197).
157
- *
158
- * Time-based, not count-based: "yield every N items" is only a bound when the
159
- * per-item cost is bounded too, and the incremental refresh's per-document cost
160
- * grows with the corpus (each replacement filters shared posting arrays). The
161
- * #1197 outage was exactly that shape — 239 stale documents held pi's event loop
162
- * for seconds between two "every 100 files" checkpoints. Callers combine this
163
- * The shared deadline is checked at every bounded work unit so abort latency
164
- * and occupancy are governed by the same monotonic budget.
165
- */
166
- function createEmptyWordIndex(truncated) {
167
- return {
168
- postings: new Map(),
169
- fileTable: new WordIndexFileTable(),
170
- docLengths: new PathKeyedMap(wordIndexKey),
171
- totalTokens: 0,
172
- docCount: 0,
173
- truncated,
174
- forward: new PathKeyedMap(wordIndexKey),
175
- fileMtimes: new PathKeyedMap(wordIndexKey),
176
- fileSizes: new PathKeyedMap(wordIndexKey),
177
- replacementStats: { count: 0, totalMs: 0, maxMs: 0 },
178
- dirtyFiles: new Set(),
179
- postingStoreCount: 0,
180
- recompactFlight: createSingleFlight(),
181
- };
182
- }
183
- export function countWordIndexPostingEntries(index) {
184
- return countPostingEntries(index);
185
- }
186
- /**
187
- * Estimated resident bytes of the index's two packed stores (#2069).
188
- * Reported on the `full_rebuild`, `incremental_refresh`, and `cold_build`
189
- * word-index log records and on the `memory_sample` word-index subsystem, so
190
- * a heap census can be reconciled against the log without taking a snapshot —
191
- * the gap that let #1999 under-count this subsystem by a factor of sixty.
192
- */
193
- export function estimateWordIndexResidentBytes(index) {
194
- return estimateWordIndexStoreBytes(index);
195
- }
196
- /**
197
- * Record a posting whose file id the file table cannot resolve (#2069).
198
- *
199
- * Unreachable by construction: {@link WordIndexFileTable.release} only runs
200
- * after the forward index has enumerated and removed every posting naming that
201
- * id. If it fires, that invariant broke, and the visible symptom is a query
202
- * returning FEWER results with nothing to distinguish it from a smaller match
203
- * set — the clean-versus-errored ambiguity AGENTS.md catalogs as shape 10.
204
- *
205
- * `incrementDegradationCount` rather than `recordDegradationOnce` because
206
- * ranking is a hot loop: the tally stays exact for every occurrence while only
207
- * the first per (kind, orphaned id) writes a durable row. Subject is the id
208
- * itself, so aggregation keeps the discriminating identity.
209
- */
210
- function recordOrphanWordIndexFileId(fileId, token, seam) {
211
- incrementDegradationCount({
212
- kind: "word-index-orphan-file-id",
213
- subject: `fileId:${fileId}`,
214
- reason: `${seam} dropped a posting for token "${token}": the file table has no path for this id`,
215
- });
216
- }
217
- /**
218
- * Decode one token’s postings into `{ file, line }` objects. Allocates, so it
219
- * is for callers that genuinely need the display form (tests, diagnostics) —
220
- * never for the hot ranking loop, which reads the packed lanes directly.
221
- */
222
- export function wordIndexPostingHits(index, token) {
223
- const list = index.postings.get(token);
224
- if (!list)
225
- return [];
226
- const hits = [];
227
- for (let i = 0; i < list.length; i += 1) {
228
- const fileId = list.fileIdAt(i);
229
- const file = index.fileTable.pathFor(fileId);
230
- if (file === undefined) {
231
- recordOrphanWordIndexFileId(fileId, token, "decode");
232
- continue;
233
- }
234
- hits.push({ file, line: list.lineAt(i) });
235
- }
236
- return hits;
237
- }
238
- /**
239
- * Settle a freshly built index's posting store into its compact resident form
240
- * (#2069): re-home every list into one shared arena, sized from the live entry
241
- * counts. That both releases the doubling growth slack a build leaves behind
242
- * and stops the long tail of rare tokens paying for an `ArrayBuffer` header
243
- * each.
244
- */
245
- function compactWordIndexPostings(index) {
246
- compactPostingsIntoArena(index.postings);
247
- index.postingStoreCount = countPostingBackingStores(index.postings);
248
- }
249
- /** Bump the running distinct-backing-store tally by `count` (#2117). */
250
- function notePostingStoresAllocated(index, count) {
251
- index.postingStoreCount = (index.postingStoreCount ?? 0) + count;
252
- }
253
- /**
254
- * Floor for the recompaction gate: below this many backing stores the arena is
255
- * never repacked, so a small index never pays an O(vocab) walk it cannot amortize
256
- * (#2117). This is also the whole gate for indexes whose vocabulary is under
257
- * `WORD_INDEX_RECOMPACT_STORE_FLOOR / WORD_INDEX_RECOMPACT_STORE_FRACTION`
258
- * tokens, keeping the pre-#2246 behaviour for small corpora bit-identical.
259
- */
260
- const WORD_INDEX_RECOMPACT_STORE_FLOOR = 64;
261
- /**
262
- * Recompaction gate as a share of the live vocabulary (#2246). The flat 64-store
263
- * threshold #2117 shipped was crossed by every single edit: one document
264
- * replacement raises `postingStoreCount` by roughly the edited document's
265
- * distinct-token count (hundreds), so the whole O(vocab) arena was rebuilt after
266
- * every edit. Gating on a fraction of `postings.size` instead lets fragmentation
267
- * accumulate proportionally to the index, so recompaction fires once per many
268
- * edits rather than once per edit.
269
- *
270
- * Memory ceiling: the store count can reach a tenth of the vocabulary in private
271
- * stores before a repack, each carrying a fixed header plus growth slack. #2117
272
- * measured the fully-churned ceiling (every token private, store count =
273
- * vocabulary) at +22% resident. The cost does not scale with the store count
274
- * alone, because the tokens that churn most are also the ones carrying the
275
- * longest posting lists. PR #2246's adversarial hot-token probe measured this
276
- * gate at +13.7% resident at rest and +19.9% peak over base — inside the +22%
277
- * #2117 accepted, but not by the wide margin a naive store-count ratio would
278
- * predict. Raising the fraction further would cross that bound.
279
- */
280
- const WORD_INDEX_RECOMPACT_STORE_FRACTION = 0.1;
281
- /**
282
- * Backing-store count that triggers a repack for `index`: a fixed floor, or a
283
- * share of the live vocabulary once the index is large enough for the share to
284
- * exceed the floor. O(1) — reads `postings.size`, never walks the vocabulary.
285
- */
286
- function wordIndexRecompactThreshold(index) {
287
- return Math.max(WORD_INDEX_RECOMPACT_STORE_FLOOR, Math.floor(index.postings.size * WORD_INDEX_RECOMPACT_STORE_FRACTION));
288
- }
289
- /**
290
- * Pack the arena if churn has spread the postings across more than the
291
- * threshold of backing stores (#2117, #2246). Runs the exact O(vocab) store
292
- * count as the authoritative gate — the per-edit hot path uses the O(1)
293
- * `postingStoreCount` estimate, so this expensive walk only happens inside the
294
- * bounded, off-hot-path recompaction it guards. Resets the estimate to the
295
- * true post-compaction count.
296
- */
297
- async function recompactWordIndexPostingsIfNeeded(index, root) {
298
- const threshold = wordIndexRecompactThreshold(index);
299
- const beforeStores = countPostingBackingStores(index.postings);
300
- if (beforeStores <= threshold) {
301
- index.postingStoreCount = beforeStores;
302
- return;
303
- }
304
- const beforeBytes = estimateWordIndexResidentBytes(index);
305
- await compactPostingsIntoArenaCooperatively(index.postings);
306
- const afterStores = countPostingBackingStores(index.postings);
307
- index.postingStoreCount = afterStores;
308
- const afterBytes = estimateWordIndexResidentBytes(index);
309
- const firstForRoot = incrementDegradationCount({
310
- kind: "word-index-arena-recompact",
311
- subject: path.resolve(root),
312
- reason: `arena store threshold ${threshold} exceeded`,
313
- metadata: { beforeBytes, afterBytes, beforeStores, afterStores },
314
- });
315
- // Keep the detailed record bounded per root. The ledger still counts every
316
- // repeated recompaction and emits its own power-of-two summaries.
317
- if (firstForRoot) {
318
- logWordIndex({
319
- phase: "incremental_refresh",
320
- cwd: path.resolve(root),
321
- trigger: "incremental_refresh",
322
- reason: `arena_recompact beforeBytes=${beforeBytes} afterBytes=${afterBytes} beforeStores=${beforeStores} afterStores=${afterStores}`,
323
- });
324
- }
325
- }
326
- /**
327
- * Serialize a recompaction through the per-index async operation queue so no
328
- * async edit or refresh interleaves it (#2117). The queue plus the corruption-
329
- * proof cooperative compactor together close the review-F2 data race: the queue
330
- * excludes async operations, and the compactor's snapshot-and-skip publish
331
- * excludes the synchronous per-edit path the queue cannot see. The per-index
332
- * `recompactFlight` single-flight (#1753) keeps a burst of edits from stacking
333
- * duplicates: a caller that arrives while one recompaction runs joins it rather
334
- * than starting a second. The single slot uses a constant key.
335
- */
336
- function enqueueWordIndexRecompact(index, root) {
337
- if ((index.postingStoreCount ?? 0) <= wordIndexRecompactThreshold(index)) {
338
- return Promise.resolve();
339
- }
340
- const flight = (index.recompactFlight ??= createSingleFlight());
341
- return flight.run("recompact", () => enqueueAsyncWordIndexOperation(index, () => recompactWordIndexPostingsIfNeeded(index, root)));
342
- }
343
- /**
344
- * O(1) hot-path gate for the synchronous per-edit seam. Reads the running
345
- * store-count estimate, never a fresh `Set` over the vocabulary, then defers
346
- * the actual work onto the async queue so it cannot stall the edit (#2117).
347
- */
348
- function scheduleWordIndexRecompact(index, filePath) {
349
- void enqueueWordIndexRecompact(index, path.dirname(filePath));
350
- }
351
- /**
352
- * Test hook: settle any queued arena recompaction for `index`. Draining the
353
- * index's own async operation queue is enough because the recompaction runs
354
- * through it; there is no module-level scheduler to flush.
355
- */
356
- export async function flushWordIndexRecompactionsForTests(index) {
357
- await (asyncWordIndexOperations.get(index) ?? Promise.resolve());
358
- }
359
- function recordWordIndexReplacement(index, startedAt) {
360
- const stats = index.replacementStats ?? { count: 0, totalMs: 0, maxMs: 0 };
361
- const durationMs = Math.max(0, Date.now() - startedAt);
362
- stats.count += 1;
363
- stats.totalMs += durationMs;
364
- stats.maxMs = Math.max(stats.maxMs, durationMs);
365
- index.replacementStats = stats;
366
- }
367
- /** Intern a document path once per index; posting removal compares this id. */
368
- function internWordIndexFile(index, filePath) {
369
- return index.fileTable.intern(wordIndexKey(filePath), filePath);
370
- }
371
- function appendWordIndexPostings(index, filePath, perTokenHits) {
372
- const fileId = internWordIndexFile(index, filePath);
373
- const tokenLineCounts = new Map();
374
- for (const [token, lineNumbers] of perTokenHits) {
375
- let list = index.postings.get(token);
376
- if (list) {
377
- // A grow allocates a fresh private store; count it so the O(1) gate
378
- // tracks fragmentation without re-walking the vocabulary (#2117).
379
- if (list.reserve(list.length + lineNumbers.length)) {
380
- notePostingStoresAllocated(index, 1);
381
- }
382
- }
383
- else {
384
- list = new WordPostingList(token, lineNumbers.length);
385
- index.postings.set(token, list);
386
- notePostingStoresAllocated(index, 1);
387
- }
388
- // `list.token` is the canonical instance, so the forward entry points at
389
- // the same string the postings map is keyed by instead of retaining this
390
- // document’s own tokenizer allocation (#2069).
391
- tokenLineCounts.set(list.token, lineNumbers.length);
392
- for (const line of lineNumbers)
393
- list.push(fileId, line);
394
- }
395
- return tokenLineCounts;
396
- }
397
- function commitWordIndexDocumentReplacement(index, doc, perTokenHits, docLength, startedAt) {
398
- const tokenLineCounts = appendWordIndexPostings(index, doc.path, perTokenHits);
399
- index.docLengths.set(doc.path, docLength);
400
- index.forward.set(doc.path, WordForwardEntry.fromTally(tokenLineCounts));
401
- index.fileMtimes.set(doc.path, -1);
402
- index.fileSizes.set(doc.path, Buffer.byteLength(doc.content, "utf-8"));
403
- index.dirtyFiles?.add(wordIndexKey(doc.path));
404
- index.totalTokens += docLength;
405
- index.docCount += 1;
406
- recordWordIndexReplacement(index, startedAt);
407
- }
408
- function indexWordLine(index, fileId, line, lineNumber, tokenLineCounts) {
409
- const lineTokens = tokenizeLine(line);
410
- const seenOnLine = new Set();
411
- for (const token of lineTokens) {
412
- if (seenOnLine.has(token))
413
- continue;
414
- seenOnLine.add(token);
415
- let list = index.postings.get(token);
416
- if (!list) {
417
- list = new WordPostingList(token, 1);
418
- index.postings.set(token, list);
419
- }
420
- list.push(fileId, lineNumber);
421
- // Canonical instance, not this line’s fresh tokenizer allocation (#2069).
422
- const canonical = list.token;
423
- tokenLineCounts.set(canonical, (tokenLineCounts.get(canonical) ?? 0) + 1);
424
- }
425
- return lineTokens.length;
426
- }
427
- function finishWordIndexDocument(index, doc, docLength, tokenLineCounts) {
428
- index.docLengths.set(doc.path, docLength);
429
- index.forward?.set(doc.path, WordForwardEntry.fromTally(tokenLineCounts));
430
- index.fileMtimes.set(doc.path, doc.mtimeMs ?? 0);
431
- index.fileSizes.set(doc.path, doc.size ?? Buffer.byteLength(doc.content, "utf-8"));
432
- // A build has no prior wire cache. Keeping the marker makes a future
433
- // serializer cache explicit and gives incremental refresh one uniform seam.
434
- index.dirtyFiles?.add(wordIndexKey(doc.path));
435
- index.totalTokens += docLength;
436
- index.docCount += 1;
437
- }
438
- function indexWordDocument(index, doc) {
439
- const fileId = internWordIndexFile(index, doc.path);
440
- const lines = doc.content.split(/\r?\n/);
441
- const tokenLineCounts = new Map();
442
- let docLength = 0;
443
- for (let i = 0; i < lines.length; i += 1) {
444
- docLength += indexWordLine(index, fileId, lines[i], i + 1, tokenLineCounts);
445
- }
446
- finishWordIndexDocument(index, doc, docLength, tokenLineCounts);
447
- }
448
- export function buildWordIndex(files) {
449
- const index = createEmptyWordIndex(files.truncated ?? false);
450
- for (const doc of files)
451
- indexWordDocument(index, doc);
452
- compactWordIndexPostings(index);
453
- return index;
454
- }
455
- /**
456
- * Cooperative production builder for bulk/cold paths. It produces the exact
457
- * same index as {@link buildWordIndex}, but time-slices both between documents
458
- * and within a large document so startup warmup cannot monopolize pi's TUI
459
- * event loop (#1197). The index is private until this resolves, so a superseded
460
- * build never publishes a partial replacement.
461
- */
462
- export async function buildWordIndexAsync(files, shouldContinue = () => true) {
463
- const index = createEmptyWordIndex(files.truncated ?? false);
464
- const deadline = createDeadline(WORD_INDEX_BUILD_YIELD_BUDGET_MS);
465
- for (const doc of files) {
466
- if (!shouldContinue())
467
- throw new Error("word index build superseded");
468
- const fileId = internWordIndexFile(index, doc.path);
469
- const lines = doc.content.split(/\r?\n/);
470
- const tokenLineCounts = new Map();
471
- let docLength = 0;
472
- for (let i = 0; i < lines.length; i += 1) {
473
- const line = lines[i];
474
- docLength += indexWordLine(index, fileId, line, i + 1, tokenLineCounts);
475
- if (line.length >= WORD_INDEX_LONG_LINE_YIELD_CHARS ||
476
- deadline.expired()) {
477
- await yieldIfOverBudget(deadline);
478
- if (!shouldContinue())
479
- throw new Error("word index build superseded");
480
- }
481
- }
482
- finishWordIndexDocument(index, doc, docLength, tokenLineCounts);
483
- if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
484
- if (!shouldContinue())
485
- throw new Error("word index build superseded");
486
- }
487
- }
488
- compactWordIndexPostings(index);
489
- return index;
490
- }
491
- /**
492
- * Remove `filePath`'s postings/docLength/forward entry from `index` in place,
493
- * using the forward index to know exactly which tokens to touch (no scan of
494
- * unrelated postings). No-op (returns false) if the index has no forward
495
- * index yet (pre-phase-2 / deserialized-old-shape) or the file isn't present —
496
- * callers must treat `false` as "fall back to a full rebuild", never as
497
- * silent success.
498
- */
499
- export function removeWordIndexDocument(index, filePath) {
500
- if (!index.forward)
501
- return false;
502
- const tokenLineCounts = index.forward.get(filePath);
503
- if (!tokenLineCounts)
504
- return false;
505
- // `postings` is token-keyed (not a PathKeyedMap), so the file identity it
506
- // carries must be resolved through the SAME normalizer the path maps use —
507
- // otherwise a build-form hit (`SUB/a.ts`) survives an edit-form removal
508
- // (`sub/a.ts`) on a case-insensitive FS and lingers as a stale posting
509
- // (the #1025 item #2 bug this fix closes). Since #2069 that identity is the
510
- // file table’s integer id, so the comparison below is an int compare rather
511
- // than a per-element string compare.
512
- const removedKey = wordIndexKey(filePath);
513
- const removedId = index.fileTable.idFor(removedKey);
514
- if (removedId === undefined)
515
- return false;
516
- for (const token of tokenLineCounts.keys()) {
517
- const list = index.postings.get(token);
518
- if (!list)
519
- continue;
520
- const next = list.withoutFile(removedId);
521
- // `withoutFile` always returns a freshly allocated private store (#2117).
522
- if (next.length > 0) {
523
- index.postings.set(token, next);
524
- notePostingStoresAllocated(index, 1);
525
- }
526
- else
527
- index.postings.delete(token);
528
- }
529
- const docLength = index.docLengths.get(filePath) ?? 0;
530
- index.docLengths.delete(filePath);
531
- index.forward.delete(filePath);
532
- index.fileMtimes.delete(filePath);
533
- index.fileSizes.delete(filePath);
534
- index.dirtyFiles?.add(wordIndexKey(filePath));
535
- index.fileTable.release(removedKey);
536
- index.totalTokens -= docLength;
537
- index.docCount = Math.max(0, index.docCount - 1);
538
- return true;
539
- }
540
- /**
541
- * Add or replace `filePath`'s document in `index` in place: removes the prior
542
- * postings for this file (if any, via {@link removeWordIndexDocument}'s
543
- * forward-index lookup) then re-tokenizes `content` and adds the new
544
- * postings/docLength/forward entry. df/N/totalTokens (avgdl) are updated as
545
- * running stats — no full recompute over other documents.
546
- *
547
- * Returns `false` (no-op on `index`) when the index has no forward index —
548
- * the caller must fall back to a full {@link buildWordIndex} rebuild in that
549
- * case (documented at the `forward` field and enforced by callers, not
550
- * silently patched here: a partially-forward-consistent index would corrupt
551
- * future incremental updates).
552
- */
553
- export function updateWordIndexDocument(index, doc) {
554
- if (!index.forward)
555
- return false;
556
- const startedAt = Date.now();
557
- // Remove the old contribution first (no-op if this is a brand new doc).
558
- if (index.forward.has(doc.path)) {
559
- removeWordIndexDocument(index, doc.path);
560
- }
561
- // Tokenize with line numbers attached (needed for WordHit.line) — this also
562
- // yields the forward-index entry (distinct-line count per token) so the
563
- // tokenization work happens exactly once for this document.
564
- const lines = doc.content.split(/\r?\n/);
565
- const perTokenHits = new Map();
566
- let docLength = 0;
567
- for (let i = 0; i < lines.length; i += 1) {
568
- const lineTokens = tokenizeLine(lines[i]);
569
- docLength += lineTokens.length;
570
- const seenOnLine = new Set();
571
- for (const token of lineTokens) {
572
- if (seenOnLine.has(token))
573
- continue;
574
- seenOnLine.add(token);
575
- const arr = perTokenHits.get(token);
576
- if (arr)
577
- arr.push(i + 1);
578
- else
579
- perTokenHits.set(token, [i + 1]);
580
- }
581
- }
582
- // Per-edit callers generally already have content but not a stat. -1 is an
583
- // impossible real mtime, so it deliberately makes the document stale at the
584
- // next startup refresh (`-1 !== realMtime` always) — unlike 0, which is a
585
- // legal on-disk mtime (SOURCE_DATE_EPOCH=0, archive extraction) and would
586
- // collide, leaving such a file never re-tokenized (#958 review F2).
587
- commitWordIndexDocumentReplacement(index, doc, perTokenHits, docLength, startedAt);
588
- scheduleWordIndexRecompact(index, doc.path);
589
- return true;
590
- }
591
- const asyncWordIndexOperations = new WeakMap();
592
- function enqueueAsyncWordIndexOperation(index, operation) {
593
- const previous = asyncWordIndexOperations.get(index) ?? Promise.resolve();
594
- const run = previous.catch(() => undefined).then(operation);
595
- const settled = run.then(() => undefined, () => undefined);
596
- asyncWordIndexOperations.set(index, settled);
597
- return run.finally(() => {
598
- if (asyncWordIndexOperations.get(index) === settled) {
599
- asyncWordIndexOperations.delete(index);
600
- }
601
- });
602
- }
603
- async function stageWordIndexDocumentRemoval(index, filePath, shouldContinue) {
604
- if (!index.forward)
605
- return undefined;
606
- const tokenLineCounts = index.forward.get(filePath);
607
- if (!tokenLineCounts)
608
- return undefined;
609
- const removedKey = wordIndexKey(filePath);
610
- const removedId = index.fileTable.idFor(removedKey);
611
- if (removedId === undefined)
612
- return undefined;
613
- const postings = new Map();
614
- const deadline = createDeadline(WORD_INDEX_BUILD_YIELD_BUDGET_MS);
615
- for (const token of tokenLineCounts.keys()) {
616
- if (!shouldContinue())
617
- throw new Error("word index refresh superseded");
618
- const list = index.postings.get(token);
619
- if (!list)
620
- continue;
621
- // One token's postings, filtered by the SAME packed primitive the
622
- // synchronous path uses, so the staged result is identical rather than
623
- // merely equivalent. The work unit is the token, not the posting element:
624
- // a per-element deadline check cost one `performance.now()` per entry —
625
- // on the order of a million per edit on a 2.2M-posting corpus — which is
626
- // the tax that made this path unusable from the per-edit seam (#2067).
627
- // One list's scan is a bounded, non-yieldable unit; the deadline is
628
- // checked between lists.
629
- const survivors = list.withoutFile(removedId);
630
- postings.set(token, survivors.length > 0 ? survivors : undefined);
631
- if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
632
- if (!shouldContinue())
633
- throw new Error("word index refresh superseded");
634
- }
635
- }
636
- return { postings, docLength: index.docLengths.get(filePath) ?? 0 };
637
- }
638
- function commitWordIndexDocumentRemoval(index, filePath, staged) {
639
- for (const [token, list] of staged.postings) {
640
- // Staged survivors come from `WordPostingList.withoutFile` — a fresh
641
- // private store each, so the O(1) fragmentation gate must count them
642
- // (#2117).
643
- if (list) {
644
- index.postings.set(token, list);
645
- notePostingStoresAllocated(index, 1);
646
- }
647
- else
648
- index.postings.delete(token);
649
- }
650
- index.docLengths.delete(filePath);
651
- index.forward?.delete(filePath);
652
- index.fileMtimes.delete(filePath);
653
- index.fileSizes.delete(filePath);
654
- index.dirtyFiles?.add(wordIndexKey(filePath));
655
- index.fileTable.release(wordIndexKey(filePath));
656
- index.totalTokens -= staged.docLength;
657
- index.docCount = Math.max(0, index.docCount - 1);
658
- }
659
- /** Cooperative, atomically-published removal used only by bulk refresh. */
660
- export async function removeWordIndexDocumentAsync(index, filePath, shouldContinue = () => true) {
661
- return enqueueAsyncWordIndexOperation(index, async () => {
662
- const staged = await stageWordIndexDocumentRemoval(index, filePath, shouldContinue);
663
- if (!staged)
664
- return false;
665
- if (!shouldContinue())
666
- throw new Error("word index refresh superseded");
667
- commitWordIndexDocumentRemoval(index, filePath, staged);
668
- return true;
669
- });
670
- }
671
- /**
672
- * The per-edit replacement primitive the cascade seam calls (#2067).
673
- *
674
- * `updateWordIndexDocumentAsync` plus the arena-recompaction gate the
675
- * synchronous variant carries, which is what makes this the per-edit entry
676
- * point rather than a rename. Bulk refresh must NOT use it: one refreshed
677
- * document raises `postingStoreCount` by roughly its distinct-token count, so a
678
- * per-document schedule would drive an O(vocabulary) recompaction between
679
- * nearly every pair of documents. `refreshWordIndexIncrementally` therefore
680
- * drives `updateWordIndexDocumentAsync` directly and recompacts ONCE after its
681
- * loop; the per-edit seam has no such "after the loop" and schedules here.
682
- */
683
- export async function updateWordIndexDocumentForEdit(index, doc) {
684
- const updated = await updateWordIndexDocumentAsync(index, doc);
685
- if (updated)
686
- scheduleWordIndexRecompact(index, doc.path);
687
- return updated;
688
- }
689
- /** Cooperative replacement whose old/new state is committed without an await. */
690
- export async function updateWordIndexDocumentAsync(index, doc, shouldContinue = () => true) {
691
- return enqueueAsyncWordIndexOperation(index, () => updateWordIndexDocumentAsyncUnsafe(index, doc, shouldContinue));
692
- }
693
- async function updateWordIndexDocumentAsyncUnsafe(index, doc, shouldContinue) {
694
- if (!index.forward)
695
- return false;
696
- const startedAt = Date.now();
697
- const removal = index.forward.has(doc.path)
698
- ? await stageWordIndexDocumentRemoval(index, doc.path, shouldContinue)
699
- : undefined;
700
- if (index.forward.has(doc.path) && !removal)
701
- return false;
702
- const perTokenHits = new Map();
703
- let docLength = 0;
704
- const lines = doc.content.split(/\r?\n/);
705
- await forEachCooperatively(lines, (line, i) => {
706
- const lineTokens = tokenizeLine(line);
707
- docLength += lineTokens.length;
708
- const seenOnLine = new Set();
709
- for (const token of lineTokens) {
710
- if (seenOnLine.has(token))
711
- continue;
712
- seenOnLine.add(token);
713
- const arr = perTokenHits.get(token);
714
- if (arr)
715
- arr.push(i + 1);
716
- else
717
- perTokenHits.set(token, [i + 1]);
718
- }
719
- }, {
720
- budgetMs: WORD_INDEX_BUILD_YIELD_BUDGET_MS,
721
- shouldContinue,
722
- abortMessage: "word index refresh superseded",
723
- });
724
- if (!shouldContinue())
725
- throw new Error("word index refresh superseded");
726
- if (removal)
727
- commitWordIndexDocumentRemoval(index, doc.path, removal);
728
- commitWordIndexDocumentReplacement(index, doc, perTokenHits, docLength, startedAt);
729
- return true;
730
- }
731
- export const WORD_INDEX_MAX_BYTES = 512 * 1024;
732
- /**
733
- * Collect the bounded `{path, content}` doc set `buildWordIndex` consumes —
734
- * the ONE file-walk-and-read implementation shared by every build path
735
- * (session-start task, quick-mode warmup, cold-query background trigger),
736
- * so a bound/skip-rule change lands in one place instead of three copies.
737
- * `shouldContinue` lets a session-scoped caller abort early (session
738
- * superseded) without this module knowing about RuntimeCoordinator.
739
- */
740
- export async function collectWordIndexDocs(root, shouldContinue = () => true, preflightFiles) {
741
- const { collectSourceFilesAsync } = await import("./source-filter.js");
742
- // #747 hardening: pass the cap INTO the walk — without it,
743
- // `collectSourceFilesAsync` defaults to an unbounded traversal and any
744
- // slice below would only trim the result AFTER the whole tree (all of
745
- // $HOME, on a misrooted cwd) has already been enumerated.
746
- // #760: the walk is additionally bounded by source-filter's default
747
- // visited-entry budget (DEFAULT_MAX_SCAN_ENTRIES), so a mixed tree with few
748
- // source files among a huge pile of non-source files can't force a
749
- // full-tree walk either; an index over the truncated list is acceptable.
750
- const maxFiles = preflightFiles?.length ?? getWordIndexMaxFilesDerived(root);
751
- // #894 review: prioritize code kinds within the cap — with broadened
752
- // enumeration, thousands of data/doc files (locale JSON, fixtures, …)
753
- // ahead of the code dirs in walk order could exhaust `maxFiles` and evict
754
- // real source files from the index entirely (DOC_FILE_PENALTY can't
755
- // rescue a file that never made the slice).
756
- const files = preflightFiles
757
- ? preflightFiles.map((file) => file.path)
758
- : await collectSourceFilesAsync(root, {
759
- maxFiles,
760
- prioritizeCodeKinds: true,
761
- });
762
- const truncated = preflightFiles?.truncated ?? files.length === maxFiles;
763
- const docs = Object.assign([], { truncated, skipped: 0 });
764
- if (!shouldContinue())
765
- return docs;
766
- const deadline = createDeadline(WORD_INDEX_BUILD_YIELD_BUDGET_MS);
767
- for (const file of files.slice(0, maxFiles)) {
768
- if (!shouldContinue())
769
- return docs;
770
- try {
771
- // A preflight list saves the second full WALK, but its metadata may be
772
- // stale by the time this rebuild reads the file. Re-stat each document at
773
- // content-read time so the stored freshness stamp describes the bytes we
774
- // actually index and the next incremental refresh cannot miss a change in
775
- // the walk-to-read window (#1302).
776
- const stat = fs.statSync(file);
777
- if (stat.size <= WORD_INDEX_MAX_BYTES) {
778
- docs.push({
779
- path: file,
780
- content: fs.readFileSync(file, "utf-8"),
781
- mtimeMs: stat.mtimeMs,
782
- size: stat.size,
783
- });
784
- }
785
- else {
786
- // Over the byte cap — enumerated but deliberately not indexed. Count
787
- // it (L1, #958) so callers can keep coverage honest instead of the
788
- // old silent drop.
789
- docs.skipped += 1;
790
- }
791
- }
792
- catch {
793
- // unreadable / vanished file — skip, but count it (see above).
794
- docs.skipped += 1;
795
- }
796
- if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
797
- if (!shouldContinue())
798
- return docs;
799
- }
800
- }
801
- return docs;
802
- }
803
- // Node's fs.promises.stat runs on libuv's threadpool (default 4 slots), so
804
- // real parallelism tops out there; the surplus workers are queue depth that
805
- // keeps the pool saturated without starving other threadpool consumers.
806
- const WORD_INDEX_STAT_CONCURRENCY = 8;
807
- const WORD_INDEX_INCREMENTAL_CHURN_THRESHOLD = 0.3;
808
- // A ratio alone misclassifies one stale file in a three-file project as dense.
809
- // Below this absolute floor, per-document replacement is bounded and cheaper
810
- // than a second full walk/read even when the percentage is high (#1197).
811
- const WORD_INDEX_DENSE_REFRESH_MIN_DOCUMENTS = 32;
812
- /**
813
- * Cost of one file's stat+read+decode expressed in tokenizer-token units, so
814
- * the work model below can compare a path that re-reads EVERY file (full
815
- * rebuild) against one that re-reads only the stale files (incremental) in a
816
- * single currency. One read ≈ 300 tokens at the ~1 µs/token measured below.
817
- * Order-of-magnitude by design — it only has to keep a tiny-vocabulary corpus
818
- * (where posting scans are nearly free) from being sent to a full rebuild that
819
- * re-reads everything to save a handful of array filters.
820
- */
821
- const WORD_INDEX_FILE_READ_TOKEN_COST = 300;
822
- /**
823
- * Cost of scanning ONE posting-array element (the `wordIndexKey` compare in
824
- * {@link removeWordIndexDocument}'s filter) relative to tokenizing one token,
825
- * so both sides of the model below are denominated in tokens. Measured on this
826
- * repository's own 2,062-document corpus: 213 ns per element scan against 979 ns
827
- * per token — and the resulting predicted crossover (23 stale documents) lands
828
- * on the directly measured one (~21, from 97.8 ms per document replacement
829
- * against a 1,436 ms rebuild + re-read). The synthetic high-df corpus in the
830
- * #1197 probe gives 0.09, so this is the conservative (incremental-favouring)
831
- * end of the observed range.
832
- *
833
- * #2069 made the scan an Int32 compare over packed lanes instead of a string
834
- * compare over boxed objects, so the real per-element cost is now BELOW this
835
- * constant. The constant is left as measured: over-stating incremental cost
836
- * only ever routes work to a full rebuild, which is the bounded side of the
837
- * decision. Re-measuring it is #2067/#2068 territory, not this change.
838
- */
839
- const WORD_INDEX_POSTING_SCAN_TOKEN_COST = 0.2;
840
- /**
841
- * Refresh a serializer-v2 index from the current bounded source-file set.
842
- * The walk/stat pass is cheap; only sparse stale/new documents are read and
843
- * tokenized. Expected dense/legacy states return `full-required` BEFORE any
844
- * mutation; unexpected corruption or supersession still throws.
845
- */
846
- export async function refreshWordIndexIncrementally(index, root, shouldContinue = () => true, options = {}) {
847
- if (!index.forward || !index.fileMtimes || !index.fileSizes) {
848
- return {
849
- mode: "full-required",
850
- reason: "missing-incremental-metadata",
851
- timings: { sourceWalkMs: 0, statWalkMs: 0, refreshReadsMs: 0 },
852
- };
853
- }
854
- const { collectSourceFilesAsync } = await import("./source-filter.js");
855
- const maxFiles = getWordIndexMaxFilesDerived(root);
856
- const sourceWalkStartMs = Date.now();
857
- const walked = await collectSourceFilesAsync(root, {
858
- maxFiles,
859
- prioritizeCodeKinds: true,
860
- });
861
- const sourceWalkMs = Date.now() - sourceWalkStartMs;
862
- if (!shouldContinue())
863
- throw new Error("word index refresh superseded");
864
- // This set-difference must run in the SAME normalized key space the path
865
- // maps use (#1025 review). Otherwise a file whose stored displayPath became
866
- // the edit form (last-writer-wins after a case/separator-divergent per-edit
867
- // update) no longer string-matches its walk form here — even though
868
- // `fileMtimes.get(walkForm)` folds and hits — which would (1) double-count
869
- // churn (the same file scored as both "dropped" and "added"), possibly
870
- // crossing the threshold and forcing a needless full rebuild, and (2) drop
871
- // then re-add an unchanged file, opening a regression window because the drop
872
- // loop runs BEFORE the re-read: a transient read failure (skipped++) would
873
- // strand the file with no postings. Keying `current` and `oldSet` through
874
- // `wordIndexKey` keeps build/edit/refresh convergent; `current`'s value
875
- // retains the raw walk path for the stat/read and for the display key.
876
- const current = new Map();
877
- const statWalkStartMs = Date.now();
878
- const statFile = options.statFile ?? ((file) => fs.promises.stat(file));
879
- const requestedConcurrency = options.statConcurrency ?? WORD_INDEX_STAT_CONCURRENCY;
880
- const statConcurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0
881
- ? Math.max(1, Math.floor(requestedConcurrency))
882
- : WORD_INDEX_STAT_CONCURRENCY;
883
- const statResults = [];
884
- statResults.length = walked.length;
885
- let cursor = 0;
886
- let superseded = false;
887
- const worker = async () => {
888
- while (true) {
889
- if (!shouldContinue()) {
890
- superseded = true;
891
- return;
892
- }
893
- const slot = cursor++;
894
- if (slot >= walked.length)
895
- return;
896
- const file = walked[slot];
897
- try {
898
- const stat = await statFile(file);
899
- if (stat.size <= WORD_INDEX_MAX_BYTES) {
900
- statResults[slot] = {
901
- path: file,
902
- mtimeMs: stat.mtimeMs,
903
- size: stat.size,
904
- };
905
- }
906
- }
907
- catch {
908
- // A rejection has statSync parity: the file is simply absent.
909
- }
910
- }
911
- };
912
- await Promise.all(Array.from({ length: Math.min(statConcurrency, walked.length) }, () => worker()));
913
- if (superseded || !shouldContinue()) {
914
- throw new Error("word index refresh superseded");
915
- }
916
- const statWalkMs = Date.now() - statWalkStartMs;
917
- for (const result of statResults) {
918
- if (result)
919
- current.set(wordIndexKey(result.path), result);
920
- }
921
- const timings = { sourceWalkMs, statWalkMs, refreshReadsMs: 0 };
922
- const preflightFiles = Object.assign([...current.values()], {
923
- truncated: walked.length === maxFiles,
924
- });
925
- const oldSet = new Set([...index.docLengths.keys()].map(wordIndexKey));
926
- let changedSet = 0;
927
- for (const key of oldSet)
928
- if (!current.has(key))
929
- changedSet++;
930
- for (const key of current.keys())
931
- if (!oldSet.has(key))
932
- changedSet++;
933
- const denominator = Math.max(oldSet.size, current.size, 1);
934
- if (changedSet / denominator > WORD_INDEX_INCREMENTAL_CHURN_THRESHOLD) {
935
- return {
936
- mode: "full-required",
937
- reason: "file-set-churn",
938
- preflightFiles,
939
- timings,
940
- };
941
- }
942
- // Replacing one document filters every shared posting array for that
943
- // document's tokens. That is excellent for a sparse edit, but repeating it
944
- // for most of the corpus becomes effectively quadratic. Decide the dense
945
- // transition BEFORE dropping or editing anything so the old index remains a
946
- // valid fallback until a separately-built full replacement is ready (#1197).
947
- let staleDocuments = 0;
948
- for (const { path: file, mtimeMs, size } of current.values()) {
949
- if (index.fileMtimes.get(file) !== mtimeMs ||
950
- (index.fileSizes.get(file) ?? -1) !== size) {
951
- staleDocuments += 1;
952
- }
953
- }
954
- // Density alone is NOT a bound (#1197 review finding 1): the per-document cost
955
- // GROWS with the corpus, so a stale set comfortably under any fixed ratio (or
956
- // any fixed absolute ceiling) still costs orders of magnitude more than a
957
- // rebuild on a big enough repository. 800 documents with 239 stale — 29.875%,
958
- // just under the ratio — measured 90.6 s with a 39.6 s synchronous block
959
- // against 1.3 s / 10.6 ms for a full build, and at the 6,000-file cap up to
960
- // 1,799 stale documents would have stayed on that path.
961
- //
962
- // So compare estimated WORK, both sides denominated in tokenizer tokens:
963
- // incremental ≈ stale x (docTokens x postingLength x scanCost + readCost)
964
- // full ≈ totalTokens + corpusSize x readCost
965
- // This is self-bounding in a way no constant is: the incremental path is taken
966
- // only while its total estimated cost stays under ONE full rebuild, so the
967
- // worst case is "about as expensive as rebuilding", whatever the corpus size.
968
- //
969
- // Both statistics come from cheap metadata passes — one over `postings`
970
- // (distinct tokens, reading only `.length`) and one over `forward` (documents,
971
- // reading only `.size`). Neither touches a posting ELEMENT, which is the work
972
- // this decision exists to avoid.
973
- let postingEntries = 0;
974
- let weightedPostingEntries = 0;
975
- for (const list of index.postings.values()) {
976
- postingEntries += list.length;
977
- weightedPostingEntries += list.length * list.length;
978
- }
979
- // The mean posting-array length weighted by OCCURRENCES, not the plain mean:
980
- // a document's tokens are drawn from the frequency distribution, so the arrays
981
- // a removal actually filters skew hard toward the high-document-frequency
982
- // tokens. The unweighted mean (dominated by the long tail of df=1 tokens)
983
- // underestimates the real cost by an order of magnitude.
984
- const expectedPostingLength = postingEntries > 0 ? weightedPostingEntries / postingEntries : 0;
985
- let distinctTokenEntries = 0;
986
- for (const tokenLineCounts of index.forward.values()) {
987
- distinctTokenEntries += tokenLineCounts.size;
988
- }
989
- const expectedDocumentTokens = index.docCount > 0 ? distinctTokenEntries / index.docCount : 0;
990
- const estimatedIncrementalWork = staleDocuments *
991
- (expectedDocumentTokens *
992
- expectedPostingLength *
993
- WORD_INDEX_POSTING_SCAN_TOKEN_COST +
994
- WORD_INDEX_FILE_READ_TOKEN_COST);
995
- const estimatedFullRebuildWork = index.totalTokens + current.size * WORD_INDEX_FILE_READ_TOKEN_COST;
996
- // The document-count floor guards the RATIO test only — its documented job is
997
- // that "one stale file in a three-file project" is 33% but not dense. It must
998
- // NOT gate the work test: a floor is another constant, and letting one
999
- // suppress the work comparison would reintroduce exactly the unbounded-on-the
1000
- // -other-axis shape (31 documents at 98 ms each on this repository's own
1001
- // corpus, and worse as the corpus grows). The work test needs no floor — on a
1002
- // small project a full rebuild is cheap, so it never fires there.
1003
- if ((staleDocuments >= WORD_INDEX_DENSE_REFRESH_MIN_DOCUMENTS &&
1004
- staleDocuments / Math.max(current.size, 1) >
1005
- WORD_INDEX_INCREMENTAL_CHURN_THRESHOLD) ||
1006
- estimatedIncrementalWork > estimatedFullRebuildWork) {
1007
- return {
1008
- mode: "full-required",
1009
- reason: "stale-document-churn",
1010
- preflightFiles,
1011
- timings,
1012
- };
1013
- }
1014
- const deadline = createDeadline(WORD_INDEX_BUILD_YIELD_BUDGET_MS);
1015
- let dropped = 0;
1016
- for (const key of oldSet) {
1017
- if (!current.has(key)) {
1018
- // `key` is already folded; removeWordIndexDocument re-folds it
1019
- // idempotently via the PathKeyedMap, so the drop hits the right entry.
1020
- if (!(await removeWordIndexDocumentAsync(index, key, shouldContinue))) {
1021
- throw new Error(`failed to drop word-index document: ${key}`);
1022
- }
1023
- dropped++;
1024
- }
1025
- }
1026
- let refreshed = 0;
1027
- let skipped = 0;
1028
- const refreshReadsStartMs = Date.now();
1029
- for (const { path: file, mtimeMs, size } of current.values()) {
1030
- // #1105: mtime-first, size-second freshness — mtime alone is a stale
1031
- // signal (mtime-preserving content changes serve stale identifiers to
1032
- // symbol_search). Size is free (the stat above already read it) and
1033
- // catches every content change that alters byte length. Both come from the
1034
- // SAME stat, so a mismatch on EITHER axis re-reads. `?? -1` makes a file
1035
- // absent from the (possibly legacy, pre-#1105) size map always count as
1036
- // changed → one-time re-read that repopulates the size, the safe direction.
1037
- if (index.fileMtimes.get(file) !== mtimeMs ||
1038
- (index.fileSizes.get(file) ?? -1) !== size) {
1039
- // A file the walk/stat pass saw can still fail to read here — a
1040
- // transient exclusive lock (antivirus, an editor, a build step) or a
1041
- // file that vanished in the interim. Match collectWordIndexDocs'
1042
- // tolerance: skip this one file and leave its existing posting (and
1043
- // its old mtime, so it is retried next session) rather than aborting
1044
- // the whole incremental pass into a full rebuild (#958 review F1).
1045
- let content;
1046
- try {
1047
- content = fs.readFileSync(file, "utf-8");
1048
- }
1049
- catch {
1050
- skipped++;
1051
- continue;
1052
- }
1053
- if (!(await updateWordIndexDocumentAsync(index, { path: file, content }, shouldContinue))) {
1054
- throw new Error(`failed to refresh word-index document: ${file}`);
1055
- }
1056
- // updateWordIndexDocument stamps mtime=-1 (per-edit convention); the
1057
- // refresh path KNOWS the real on-disk stat, so overwrite both axes with
1058
- // the true values so this document is not needlessly re-read next pass.
1059
- index.fileMtimes.set(file, mtimeMs);
1060
- index.fileSizes.set(file, size);
1061
- refreshed++;
1062
- }
1063
- // Same OR as the stat loop. This is the loop the #1197 outage actually
1064
- // blocked in: replacing one document costs more the larger the corpus is,
1065
- // so a count-only checkpoint let 100 replacements run back to back for
1066
- // seconds. The gate above keeps the stale set sparse; this keeps even a
1067
- // sparse-but-expensive set off the event loop.
1068
- if (deadline.expired() && (await yieldIfOverBudget(deadline))) {
1069
- if (!shouldContinue())
1070
- throw new Error("word index refresh superseded");
1071
- }
1072
- }
1073
- timings.refreshReadsMs = Date.now() - refreshReadsStartMs;
1074
- // Route through the same guarded queue the per-edit seam uses, so a refresh
1075
- // and a concurrent cascade edit cannot both drive a recompaction (#2117).
1076
- await enqueueWordIndexRecompact(index, root);
1077
- index.truncated = walked.length === maxFiles;
1078
- return {
1079
- mode: "incremental",
1080
- refreshed,
1081
- dropped,
1082
- skipped,
1083
- reused: current.size - refreshed - skipped,
1084
- timings,
1085
- };
1086
- }
1087
- const WORD_INDEX_QUERY_FILTER_KEYS = [
1088
- "lang",
1089
- "file",
1090
- "ext",
1091
- ];
1092
- /** Thrown by {@link parseWordIndexQuery}/{@link buildWordIndexQueryFilter} for
1093
- * an unrecognized `key:` prefix or an unrecognized `lang:` value — a query
1094
- * typo fails loudly with the supported list instead of silently degrading
1095
- * into a literal search term (#1450 acceptance criterion). */
1096
- export class WordIndexQueryError extends Error {
1097
- constructor(message) {
1098
- super(message);
1099
- this.name = "WordIndexQueryError";
1100
- }
1101
- }
1102
- const WORD_INDEX_FILTER_TOKEN_RE = /^(-)?([A-Za-z][A-Za-z0-9_-]*):(.+)$/;
1103
- /**
1104
- * Split a word-index query string into plain search terms and `key:value`
1105
- * prefix filters (#1450). Whitespace-delimited, one regex per token — no
1106
- * quoting scheme invented here, matching the pre-existing query path (which
1107
- * never supported quoting either). A `-` immediately before a recognized
1108
- * `key:` negates that filter; a bare `-term` (no colon) is left as an
1109
- * ordinary token, unchanged from prior behavior — `tokenizeLine` already
1110
- * strips the leading `-` when it extracts identifiers.
1111
- *
1112
- * An unrecognized `key:` (e.g. `type:foo`) throws {@link WordIndexQueryError}
1113
- * naming the supported prefixes, rather than falling through as a literal
1114
- * term — a typo must fail loudly, never silently rank as if the filter never
1115
- * existed. An empty value (`lang:`) is not treated as a filter token at all
1116
- * (kept as a term) since there is nothing to filter by.
1117
- */
1118
- export function parseWordIndexQuery(query) {
1119
- const filters = [];
1120
- const termParts = [];
1121
- for (const raw of query.split(/\s+/)) {
1122
- if (!raw)
1123
- continue;
1124
- const match = raw.match(WORD_INDEX_FILTER_TOKEN_RE);
1125
- if (!match) {
1126
- termParts.push(raw);
1127
- continue;
1128
- }
1129
- const [, negation, keyRaw, value] = match;
1130
- const key = keyRaw.toLowerCase();
1131
- if (!value) {
1132
- termParts.push(raw);
1133
- continue;
1134
- }
1135
- if (!WORD_INDEX_QUERY_FILTER_KEYS.includes(key)) {
1136
- // Not a recognized filter key. Ordinary search terms legitimately
1137
- // contain colons (std::vector, error:foo, http://…, C:\ paths, a
1138
- // TODO:tag), and the old tokenizer searched them fine — a hard
1139
- // error here is a usability regression, not typo protection. The
1140
- // token passes through as a plain term; the loud-failure contract
1141
- // survives where it is unambiguous: a KNOWN key with a bad value
1142
- // (lang:notalang) still throws.
1143
- termParts.push(raw);
1144
- continue;
1145
- }
1146
- filters.push({
1147
- key: key,
1148
- value,
1149
- negated: negation === "-",
1150
- });
1151
- }
1152
- return { terms: termParts.join(" "), filters };
1153
- }
1154
- /** `lang:X` → the extension set for FileKind X (case-insensitive), drawn
1155
- * exclusively from `KIND_EXTENSIONS` (clients/file-kinds.ts) — the single
1156
- * source of truth (#894 invariant: no second, hand-maintained language list).
1157
- * An unrecognized kind throws {@link WordIndexQueryError} listing the accepted
1158
- * kinds (KIND_EXTENSIONS' own keys), the same loud-failure contract as an
1159
- * unsupported `key:` prefix. */
1160
- function resolveLangExtensions(value) {
1161
- const kind = value.toLowerCase();
1162
- const extensions = KIND_EXTENSIONS[kind];
1163
- if (!extensions) {
1164
- const known = Object.keys(KIND_EXTENSIONS)
1165
- .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
1166
- .join(", ");
1167
- throw new WordIndexQueryError(`Unknown lang: "${value}" in word-index query — supported languages: ${known}.`);
1168
- }
1169
- return extensions;
1170
- }
1171
- /** `ext:ts` / `ext:.ts` → normalized to a single leading-dot, lowercased form for extname comparison. */
1172
- function normalizeExtFilterValue(value) {
1173
- return `.${value.replace(/^\.+/, "").toLowerCase()}`;
1174
- }
1175
- function resolveWordIndexFilter(filter) {
1176
- if (filter.key === "lang") {
1177
- const extensions = resolveLangExtensions(filter.value);
1178
- return {
1179
- key: filter.key,
1180
- negated: filter.negated,
1181
- test: (file) => extensions.includes(path.extname(file).toLowerCase()),
1182
- };
1183
- }
1184
- if (filter.key === "ext") {
1185
- const normalized = normalizeExtFilterValue(filter.value);
1186
- return {
1187
- key: filter.key,
1188
- negated: filter.negated,
1189
- test: (file) => path.extname(file).toLowerCase() === normalized,
1190
- };
1191
- }
1192
- // "file": substring match against the index's own normalized display path
1193
- // (#1450 — see the doc comment on {@link buildWordIndexQueryFilter} for the
1194
- // case-behavior choice). Both sides fold through `wordIndexKey` so the
1195
- // comparison matches the SAME normalization the index's own path keys use.
1196
- const needle = wordIndexKey(filter.value);
1197
- return {
1198
- key: filter.key,
1199
- negated: filter.negated,
1200
- test: (_file, displayPath) => displayPath.includes(needle),
1201
- };
1202
- }
1203
- /**
1204
- * Build a pre-ranking predicate from parsed query filters (#1450). Composes
1205
- * (AND) with `searchWordIndex`'s pre-existing `fileFilter` option (#771) —
1206
- * `symbol_search`'s structured `paths`/`lang` params and this query's inline
1207
- * filters both apply when both are present.
1208
- *
1209
- * Semantics: multiple positive filters of the SAME key OR together
1210
- * (`lang:ts lang:go` matches either); filters of DIFFERENT keys AND
1211
- * (`lang:ts file:clients/` requires both); a negated filter always subtracts,
1212
- * regardless of any positive filter sharing its key.
1213
- *
1214
- * All filters are RESOLVED EAGERLY (extension lookups, `WordIndexQueryError`
1215
- * thrown) here at build time — before any candidate file is tested — so a bad
1216
- * `lang:` value fails once, loudly, rather than per-file during the scoring
1217
- * loop.
1218
- *
1219
- * `file:` case behavior: matched via `wordIndexKey` (this module's single
1220
- * path-key normalizer, `normalizeEphemeralMapKey`) on BOTH the candidate path
1221
- * and the filter value — slash-folded everywhere, and case-folded ONLY on
1222
- * win32 (Windows' case-insensitive filesystem), preserved on POSIX. This
1223
- * mirrors the SAME normalization the word index's own path-keyed maps already
1224
- * apply (#1025), rather than inventing a second, divergent case rule for this
1225
- * one filter. The candidate path matched is the RAW index-stored path (not
1226
- * cwd-relativized) — the index already stores paths in the form the caller
1227
- * built it with (relative in tests, absolute in the real project walk), and
1228
- * an absolute path's tail always contains its project-relative suffix, so
1229
- * `file:clients/word-index.ts` still matches naturally either way.
1230
- */
1231
- export function buildWordIndexQueryFilter(filters) {
1232
- if (filters.length === 0)
1233
- return undefined;
1234
- const resolved = filters.map(resolveWordIndexFilter);
1235
- const positivesByKey = new Map();
1236
- const negatives = [];
1237
- for (const r of resolved) {
1238
- if (r.negated) {
1239
- negatives.push(r);
1240
- continue;
1241
- }
1242
- const group = positivesByKey.get(r.key) ?? [];
1243
- group.push(r);
1244
- positivesByKey.set(r.key, group);
1245
- }
1246
- return (file) => {
1247
- const displayPath = wordIndexKey(file);
1248
- for (const negative of negatives) {
1249
- if (negative.test(file, displayPath))
1250
- return false;
1251
- }
1252
- for (const group of positivesByKey.values()) {
1253
- if (!group.some((r) => r.test(file, displayPath)))
1254
- return false;
1255
- }
1256
- return true;
1257
- };
1258
- }
1259
- function combineFileFilters(a, b) {
1260
- if (!a)
1261
- return b;
1262
- if (!b)
1263
- return a;
1264
- return (file) => a(file) && b(file);
1265
- }
1266
- /**
1267
- * Rank files for a query by BM25 over the query's identifier tokens, then apply
1268
- * priors: demote test/vendor and doc/data files, and boost by graph centrality
1269
- * when supplied. Returns the top {@link RankOptions.limit} files, highest first.
1270
- *
1271
- * The query string may mix plain terms with `lang:`/`file:`/`ext:` prefix
1272
- * filters (+ `-` negation, #1450) — parsed by {@link parseWordIndexQuery} and
1273
- * applied as a `fileFilter` (composed, AND, with any caller-supplied one)
1274
- * BEFORE scoring, same as the pre-existing `paths`/`lang` options (#771): a
1275
- * surviving file's score is unaffected by filtering. A query with no filter
1276
- * tokens reproduces prior output byte-for-byte.
1277
- *
1278
- * DF-normalization note: BM25's per-token `idf` is computed from `docFrequency`
1279
- * over the FULL (unfiltered) postings for that token — `fileFilter` (whether
1280
- * from `options.fileFilter` or from this query's inline filters) is applied
1281
- * AFTER `idf` is computed, per file, inside the same loop. This means a
1282
- * filtered query reuses the GLOBAL, corpus-wide document frequency rather than
1283
- * recomputing it over just the filtered subset. That is the pre-existing #771
1284
- * behavior this change does not alter; it is an acceptable approximation
1285
- * (idf reflects true corpus rarity, not an artifact of the filter) and keeps
1286
- * filtered/unfiltered scores for the SAME file directly comparable, which is
1287
- * the property #1450 asks for ("BM25 and centrality stay comparable within
1288
- * the filtered set").
1289
- */
1290
- export function searchWordIndex(index, query, options = {}) {
1291
- const { demoteTestVendor = true, demoteDocs = true, centrality, limit = 20, fileFilter, } = options;
1292
- const parsedQuery = parseWordIndexQuery(query);
1293
- const queryFilter = buildWordIndexQueryFilter(parsedQuery.filters);
1294
- const combinedFilter = combineFileFilters(fileFilter, queryFilter);
1295
- const queryTokens = [...new Set(tokenizeLine(parsedQuery.terms))];
1296
- if (queryTokens.length === 0)
1297
- return [];
1298
- const docCount = index.docCount || 1;
1299
- const avgDocLength = index.totalTokens / docCount || 1;
1300
- const scores = new Map();
1301
- for (const token of queryTokens) {
1302
- const posting = index.postings.get(token);
1303
- if (!posting)
1304
- continue;
1305
- // Grouped by the packed file id, then resolved to the display path once
1306
- // per (token, file) instead of once per posting (#2069). Insertion order,
1307
- // document frequency, and the per-file line lists are identical to the
1308
- // boxed representation, so scores and ordering are unchanged.
1309
- const linesByFileId = new Map();
1310
- for (let i = 0; i < posting.length; i += 1) {
1311
- const fileId = posting.fileIdAt(i);
1312
- const arr = linesByFileId.get(fileId);
1313
- if (arr)
1314
- arr.push(posting.lineAt(i));
1315
- else
1316
- linesByFileId.set(fileId, [posting.lineAt(i)]);
1317
- }
1318
- const docFrequency = linesByFileId.size;
1319
- const idf = Math.log(1 + (docCount - docFrequency + 0.5) / (docFrequency + 0.5));
1320
- for (const [fileId, lines] of linesByFileId) {
1321
- const file = index.fileTable.pathFor(fileId);
1322
- if (file === undefined) {
1323
- // Dropping this silently would shorten the result list with nothing to
1324
- // tell it apart from a smaller match set. Record, then drop.
1325
- recordOrphanWordIndexFileId(fileId, token, "search");
1326
- continue;
1327
- }
1328
- if (combinedFilter && !combinedFilter(file))
1329
- continue;
1330
- const termFrequency = lines.length;
1331
- const docLength = index.docLengths.get(file) ?? avgDocLength;
1332
- const denominator = termFrequency +
1333
- BM25_K1 * (1 - BM25_B + BM25_B * (docLength / avgDocLength));
1334
- const termScore = idf * ((termFrequency * (BM25_K1 + 1)) / denominator);
1335
- const entry = scores.get(file) ?? {
1336
- score: 0,
1337
- hits: 0,
1338
- lines: new Set(),
1339
- };
1340
- entry.score += termScore;
1341
- entry.hits += termFrequency;
1342
- for (const line of lines)
1343
- entry.lines.add(line);
1344
- scores.set(file, entry);
1345
- }
1346
- }
1347
- const results = [];
1348
- for (const [file, entry] of scores) {
1349
- let score = entry.score;
1350
- if (demoteTestVendor && isTestOrVendor(file))
1351
- score *= TEST_VENDOR_PENALTY;
1352
- if (demoteDocs && isDocFile(file))
1353
- score *= DOC_FILE_PENALTY;
1354
- const connections = centrality?.get(file);
1355
- if (connections && connections > 0) {
1356
- score *= 1 + Math.log(1 + connections) / 4;
1357
- }
1358
- results.push({
1359
- file,
1360
- score,
1361
- hits: entry.hits,
1362
- lines: [...entry.lines].sort((a, b) => a - b),
1363
- });
1364
- }
1365
- results.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
1366
- return results.slice(0, Math.max(0, limit));
1367
- }
1368
- /**
1369
- * Build a centrality map (file → importedBy count) keyed by THIS index's file
1370
- * paths, from the project snapshot's `reverseDeps` (importedBy). The snapshot
1371
- * keys are normalized (`normalizeMapKey(resolve(...))`) while the index keys are
1372
- * the raw scanned paths, so the caller injects a `normalizeKey` bridge; it
1373
- * defaults to identity for testing. Pass the result to {@link searchWordIndex}
1374
- * as `centrality` to boost well-connected files. Kept here (not in the engine)
1375
- * so it stays pure + unit-testable without the normalizer dependency.
1376
- */
1377
- export function centralityFromReverseDeps(index, reverseDeps, normalizeKey = (file) => file) {
1378
- const centrality = new Map();
1379
- if (!reverseDeps)
1380
- return centrality;
1381
- for (const file of index.docLengths.keys()) {
1382
- const importers = reverseDeps[normalizeKey(file)];
1383
- if (importers && importers.length > 0) {
1384
- centrality.set(file, importers.length);
1385
- }
1386
- }
1387
- return centrality;
1388
- }
1389
- /** Persisted word-index serialization format version. Bump on breaking format changes. */
1390
- export const WORD_INDEX_FORMAT_VERSION = 2;
1391
- const WORD_INDEX_INT32_MAX = 2_147_483_647;
1392
- // A file id and line fit in one exact Number key through this boundary.
1393
- const WORD_INDEX_PACKED_FILE_ID_MAX = 4_194_303;
1394
- function isCanonicalWordIndexNumber(value) {
1395
- return (typeof value === "number" &&
1396
- Number.isFinite(value) &&
1397
- Number.isSafeInteger(value) &&
1398
- !Object.is(value, -0));
1399
- }
1400
- function isCanonicalWordIndexToken(token) {
1401
- return (typeof token === "string" &&
1402
- token.length >= 2 &&
1403
- token === token.toLowerCase() &&
1404
- /^[a-z0-9_$]+$/.test(token) &&
1405
- !STOPWORDS.has(token));
1406
- }
1407
- const serializedWordIndexCaches = new WeakMap();
1408
- let _lastSerializeWork;
1409
- /** Test-only: work stats for the most recent {@link serializeWordIndex} call. */
1410
- export function getLastWordIndexSerializeWork() {
1411
- return _lastSerializeWork;
1412
- }
1413
- function serializeWordIndexFull(index) {
1414
- const files = [...index.docLengths.keys()];
1415
- // `files` carries whatever spelling `docLengths` last stored, which can
1416
- // differ from the spelling the file table interned. Both fold through
1417
- // `wordIndexKey`, so the slot lookup runs in the folded key space and a
1418
- // separator- or case-divergent spelling still finds its postings (#1025).
1419
- const slotByFileId = new Map();
1420
- files.forEach((file, i) => {
1421
- const fileId = index.fileTable.idFor(wordIndexKey(file));
1422
- if (fileId !== undefined)
1423
- slotByFileId.set(fileId, i);
1424
- });
1425
- const postings = [];
1426
- for (const [token, list] of index.postings) {
1427
- const flat = [];
1428
- for (let i = 0; i < list.length; i += 1) {
1429
- const slot = slotByFileId.get(list.fileIdAt(i));
1430
- if (slot === undefined)
1431
- continue;
1432
- flat.push(slot, list.lineAt(i));
1433
- }
1434
- if (flat.length > 0)
1435
- postings.push([token, flat]);
1436
- }
1437
- const forward = index.forward
1438
- ? files.map((file, i) => [
1439
- i,
1440
- [...(index.forward.get(file)?.entries() ?? [])],
1441
- ])
1442
- : undefined;
1443
- const serialized = {
1444
- version: WORD_INDEX_FORMAT_VERSION,
1445
- files,
1446
- postings,
1447
- docLengths: files.map((file) => index.docLengths.get(file) ?? 0),
1448
- totalTokens: index.totalTokens,
1449
- indexedFileCount: index.docCount,
1450
- truncated: index.truncated,
1451
- fileMtimes: files.map((file) => index.fileMtimes.get(file) ?? 0),
1452
- fileSizes: files.map((file) => index.fileSizes.get(file) ?? 0),
1453
- forward,
1454
- };
1455
- const tokensByFile = new Map();
1456
- if (index.forward) {
1457
- for (const file of files) {
1458
- tokensByFile.set(wordIndexKey(file), new Set(index.forward.get(file)?.keys() ?? []));
1459
- }
1460
- }
1461
- _lastSerializeWork = {
1462
- affectedTokenCount: postings.length,
1463
- tookFullPath: true,
1464
- };
1465
- return { serialized, slotByFileId, tokensByFile };
1466
- }
1467
- /**
1468
- * Refresh the cached wire view without walking untouched posting lanes.
1469
- * Existing files keep their slots, so a document replacement only rebuilds
1470
- * the token lists named by that document's old or new forward entry. A file
1471
- * removal or reordering changes slot identity and deliberately takes the
1472
- * bounded full path.
1473
- */
1474
- function serializeWordIndexIncrementally(index, cache) {
1475
- const files = [...index.docLengths.keys()];
1476
- const priorFiles = cache.serialized.files;
1477
- const currentKeys = new Set(files.map(wordIndexKey));
1478
- const priorKeys = new Set(priorFiles.map(wordIndexKey));
1479
- if ([...priorKeys].some((key) => !currentKeys.has(key))) {
1480
- return serializeWordIndexFull(index);
1481
- }
1482
- // Re-flattening most lanes costs more than rebuilding the compact wire view.
1483
- // The review fixture crosses over between 10% and 100% dirty, so use the
1484
- // midpoint as a conservative bound until a workload-specific model exists.
1485
- const dirty = index.dirtyFiles;
1486
- if (dirty && dirty.size * 2 > files.length) {
1487
- return serializeWordIndexFull(index);
1488
- }
1489
- // A replacement deletes and re-adds a PathKeyedMap entry, which changes Map
1490
- // insertion order. Keep the cached wire slots stable instead of rewriting
1491
- // every posting just because one document changed order.
1492
- const filesInWireOrder = [
1493
- ...priorFiles,
1494
- ...files.filter((file) => !priorKeys.has(wordIndexKey(file))),
1495
- ];
1496
- if (!dirty || dirty.size === 0) {
1497
- _lastSerializeWork = { affectedTokenCount: 0, tookFullPath: false };
1498
- return cache;
1499
- }
1500
- const slotByFileId = new Map(cache.slotByFileId);
1501
- for (let i = priorFiles.length; i < filesInWireOrder.length; i += 1) {
1502
- const fileId = index.fileTable.idFor(wordIndexKey(filesInWireOrder[i]));
1503
- if (fileId !== undefined)
1504
- slotByFileId.set(fileId, i);
1505
- }
1506
- const postings = cache.serialized.postings.slice();
1507
- const postingAt = new Map();
1508
- postings.forEach(([token], i) => postingAt.set(token, i));
1509
- const removedTokens = new Set();
1510
- const tokensByFile = new Map(cache.tokensByFile);
1511
- const fileByKey = new Map(filesInWireOrder.map((file) => [wordIndexKey(file), file]));
1512
- const affectedTokens = new Set();
1513
- for (const fileKey of dirty) {
1514
- const file = fileByKey.get(fileKey);
1515
- const oldTokens = tokensByFile.get(fileKey) ?? new Set();
1516
- const currentEntry = file === undefined ? undefined : index.forward?.get(file);
1517
- const currentTokens = new Set(currentEntry?.keys() ?? []);
1518
- for (const token of oldTokens)
1519
- affectedTokens.add(token);
1520
- for (const token of currentTokens)
1521
- affectedTokens.add(token);
1522
- if (file === undefined)
1523
- tokensByFile.delete(fileKey);
1524
- else
1525
- tokensByFile.set(fileKey, currentTokens);
1526
- }
1527
- for (const token of affectedTokens) {
1528
- const list = index.postings.get(token);
1529
- const flat = [];
1530
- if (list) {
1531
- for (let i = 0; i < list.length; i += 1) {
1532
- const slot = slotByFileId.get(list.fileIdAt(i));
1533
- if (slot !== undefined)
1534
- flat.push(slot, list.lineAt(i));
1535
- }
1536
- }
1537
- const at = postingAt.get(token);
1538
- if (flat.length === 0) {
1539
- if (at !== undefined)
1540
- removedTokens.add(token);
1541
- }
1542
- else if (at === undefined) {
1543
- postings.push([token, flat]);
1544
- postingAt.set(token, postings.length - 1);
1545
- }
1546
- else {
1547
- postings[at] = [postings[at][0], flat];
1548
- }
1549
- }
1550
- const compactedPostings = removedTokens.size
1551
- ? postings.filter(([token]) => !removedTokens.has(token))
1552
- : postings;
1553
- const forward = index.forward
1554
- ? (cache.serialized.forward
1555
- ?.slice()
1556
- .map((entry) => [entry[0], entry[1]]) ?? [])
1557
- : undefined;
1558
- for (const fileKey of dirty) {
1559
- const file = fileByKey.get(fileKey);
1560
- const fileId = file === undefined
1561
- ? undefined
1562
- : index.fileTable.idFor(wordIndexKey(file));
1563
- const slot = fileId === undefined ? undefined : slotByFileId.get(fileId);
1564
- if (slot === undefined || !forward || file === undefined)
1565
- continue;
1566
- forward[slot] = [slot, [...(index.forward?.get(file)?.entries() ?? [])]];
1567
- }
1568
- const serialized = {
1569
- ...cache.serialized,
1570
- files: filesInWireOrder,
1571
- postings: compactedPostings,
1572
- docLengths: filesInWireOrder.map((file) => index.docLengths.get(file) ?? 0),
1573
- fileMtimes: filesInWireOrder.map((file) => index.fileMtimes.get(file) ?? 0),
1574
- fileSizes: filesInWireOrder.map((file) => index.fileSizes.get(file) ?? 0),
1575
- forward,
1576
- totalTokens: index.totalTokens,
1577
- indexedFileCount: index.docCount,
1578
- truncated: index.truncated,
1579
- };
1580
- _lastSerializeWork = {
1581
- affectedTokenCount: affectedTokens.size,
1582
- tookFullPath: false,
1583
- };
1584
- return { serialized, slotByFileId, tokensByFile };
1585
- }
1586
- export function serializeWordIndex(index) {
1587
- const prior = serializedWordIndexCaches.get(index);
1588
- const cache = prior
1589
- ? serializeWordIndexIncrementally(index, prior)
1590
- : serializeWordIndexFull(index);
1591
- serializedWordIndexCaches.set(index, cache);
1592
- index.dirtyFiles?.clear();
1593
- return cache.serialized;
1594
- }
1595
- export function deserializeWordIndex(data) {
1596
- if (!data ||
1597
- data.version !== WORD_INDEX_FORMAT_VERSION ||
1598
- !Array.isArray(data.files) ||
1599
- !Array.isArray(data.postings) ||
1600
- !Array.isArray(data.docLengths) ||
1601
- !Array.isArray(data.fileMtimes)) {
1602
- return null;
1603
- }
1604
- let canonical = data.docLengths.length === data.files.length &&
1605
- data.fileMtimes.length === data.files.length;
1606
- let needsDeepSanitize = !Array.isArray(data.forward);
1607
- if (needsDeepSanitize)
1608
- canonical = false;
1609
- const fileKeys = new Set();
1610
- for (const file of data.files) {
1611
- if (typeof file !== "string")
1612
- return null;
1613
- const key = wordIndexKey(file);
1614
- if (fileKeys.has(key)) {
1615
- canonical = false;
1616
- needsDeepSanitize = true;
1617
- }
1618
- fileKeys.add(key);
1619
- }
1620
- const docLengths = new PathKeyedMap(wordIndexKey);
1621
- // Slot i of the wire `files` array becomes file id `fileIdBySlot[i]`. Two
1622
- // slots whose spellings fold to one key share one id, exactly as a build
1623
- // would intern them, so a divergent snapshot cannot deserialize into two
1624
- // posting identities for one document.
1625
- const fileTable = new WordIndexFileTable();
1626
- const fileIdBySlot = data.files.map((file) => fileTable.intern(wordIndexKey(file), file));
1627
- const slotByFileId = new Map();
1628
- fileIdBySlot.forEach((fileId, slot) => {
1629
- if (!slotByFileId.has(fileId))
1630
- slotByFileId.set(fileId, slot);
1631
- });
1632
- const fileMtimes = new PathKeyedMap(wordIndexKey);
1633
- const fileSizes = new PathKeyedMap(wordIndexKey);
1634
- data.files.forEach((file, i) => {
1635
- const value = data.docLengths[i];
1636
- if (!isCanonicalWordIndexNumber(value) || value < 0)
1637
- canonical = false;
1638
- docLengths.set(file, isCanonicalWordIndexNumber(value) && value >= 0 ? value : 0);
1639
- });
1640
- data.files.forEach((file, i) => {
1641
- const value = data.fileMtimes[i];
1642
- if (typeof value !== "number" || !Number.isFinite(value))
1643
- canonical = false;
1644
- fileMtimes.set(file, typeof value === "number" && Number.isFinite(value) ? value : 0);
1645
- });
1646
- // #1105: `fileSizes` is optional on the wire (pre-#1105 snapshots omit it).
1647
- // Only populate when the array is present AND parallel to `files`; otherwise
1648
- // leave it empty so the refresh gate re-reads every file once to repopulate,
1649
- // rather than trusting a bogus/misaligned size. Never treated as "current".
1650
- if (Array.isArray(data.fileSizes) &&
1651
- data.fileSizes.length === data.files.length) {
1652
- data.files.forEach((file, i) => {
1653
- const value = data.fileSizes?.[i];
1654
- if (!isCanonicalWordIndexNumber(value) || value < 0)
1655
- canonical = false;
1656
- fileSizes.set(file, isCanonicalWordIndexNumber(value) && value >= 0 ? value : 0);
1657
- });
1658
- }
1659
- else {
1660
- canonical = false;
1661
- }
1662
- const postings = new Map();
1663
- const postingTokens = new Set();
1664
- const postingFileCounts = new Map();
1665
- for (const posting of data.postings) {
1666
- if (!Array.isArray(posting) || posting.length !== 2) {
1667
- canonical = false;
1668
- needsDeepSanitize = true;
1669
- continue;
1670
- }
1671
- const [token, flat] = posting;
1672
- if (!isCanonicalWordIndexToken(token) ||
1673
- postingTokens.has(token) ||
1674
- !Array.isArray(flat) ||
1675
- flat.length === 0 ||
1676
- flat.length % 2 !== 0) {
1677
- canonical = false;
1678
- needsDeepSanitize = true;
1679
- continue;
1680
- }
1681
- postingTokens.add(token);
1682
- const lanes = [];
1683
- const seenPairs = new Set();
1684
- const observedCounts = new Map();
1685
- for (let i = 0; i < flat.length; i += 2) {
1686
- const slot = flat[i];
1687
- const line = flat[i + 1];
1688
- if (!isCanonicalWordIndexNumber(slot) ||
1689
- slot < 0 ||
1690
- slot >= fileIdBySlot.length ||
1691
- !isCanonicalWordIndexNumber(line) ||
1692
- line < 1 ||
1693
- line > WORD_INDEX_INT32_MAX) {
1694
- canonical = false;
1695
- needsDeepSanitize = true;
1696
- continue;
1697
- }
1698
- const fileId = fileIdBySlot[slot];
1699
- const pairKey = fileId <= WORD_INDEX_PACKED_FILE_ID_MAX
1700
- ? fileId * (WORD_INDEX_INT32_MAX + 1) + line
1701
- : `${fileId}:${line}`;
1702
- if (seenPairs.has(pairKey)) {
1703
- canonical = false;
1704
- needsDeepSanitize = true;
1705
- }
1706
- seenPairs.add(pairKey);
1707
- lanes.push(fileId, line);
1708
- observedCounts.set(fileId, (observedCounts.get(fileId) ?? 0) + 1);
1709
- }
1710
- if (lanes.length === 0)
1711
- continue;
1712
- const list = WordPostingList.fromLanes(token, lanes);
1713
- postings.set(token, list);
1714
- postingFileCounts.set(token, observedCounts);
1715
- }
1716
- // Every list above was sized exactly from the wire payload, so there is no
1717
- // growth slack to release — but the per-list `ArrayBuffer` headers are the
1718
- // same third-of-the-store cost a fresh build pays, and a deserialized index
1719
- // is the LONG-lived one (a warm session reuses it across turns).
1720
- compactPostingsIntoArena(postings);
1721
- let forward;
1722
- if (Array.isArray(data.forward)) {
1723
- const candidate = data.files.map(() => new Map());
1724
- const forwardTotals = new Map();
1725
- if (data.forward.length !== data.files.length)
1726
- canonical = false;
1727
- if (data.forward.length !== data.files.length)
1728
- needsDeepSanitize = true;
1729
- for (const [entryIndex, entry] of data.forward.entries()) {
1730
- if (!Array.isArray(entry) || entry.length !== 2) {
1731
- canonical = false;
1732
- needsDeepSanitize = true;
1733
- continue;
1734
- }
1735
- const [fileIdx, tokenCounts] = entry;
1736
- if (!isCanonicalWordIndexNumber(fileIdx) ||
1737
- fileIdx !== entryIndex ||
1738
- fileIdx < 0 ||
1739
- fileIdx >= data.files.length ||
1740
- !Array.isArray(tokenCounts)) {
1741
- canonical = false;
1742
- needsDeepSanitize = true;
1743
- continue;
1744
- }
1745
- for (const pair of tokenCounts) {
1746
- if (!Array.isArray(pair) ||
1747
- pair.length !== 2 ||
1748
- !isCanonicalWordIndexToken(pair[0]) ||
1749
- !isCanonicalWordIndexNumber(pair[1]) ||
1750
- pair[1] < 1 ||
1751
- pair[1] > WORD_INDEX_INT32_MAX ||
1752
- candidate[fileIdx].has(pair[0])) {
1753
- canonical = false;
1754
- needsDeepSanitize = true;
1755
- continue;
1756
- }
1757
- candidate[fileIdx].set(pair[0], pair[1]);
1758
- forwardTotals.set(pair[0], (forwardTotals.get(pair[0]) ?? 0) + pair[1]);
1759
- }
1760
- }
1761
- let forwardMatchesPostings = true;
1762
- for (const [token, list] of postings) {
1763
- if (forwardTotals.get(token) !== list.length) {
1764
- forwardMatchesPostings = false;
1765
- }
1766
- const observed = postingFileCounts.get(token);
1767
- if (!observed) {
1768
- forwardMatchesPostings = false;
1769
- continue;
1770
- }
1771
- for (const [fileId, count] of observed) {
1772
- const slot = slotByFileId.get(fileId);
1773
- if (slot === undefined || candidate[slot].get(token) !== count) {
1774
- forwardMatchesPostings = false;
1775
- }
1776
- }
1777
- }
1778
- for (const token of forwardTotals.keys()) {
1779
- if (!postingTokens.has(token))
1780
- forwardMatchesPostings = false;
1781
- }
1782
- if (!forwardMatchesPostings)
1783
- canonical = false;
1784
- if (!forwardMatchesPostings)
1785
- needsDeepSanitize = true;
1786
- forward = new PathKeyedMap(wordIndexKey);
1787
- for (let i = 0; i < data.files.length; i += 1) {
1788
- forward.set(data.files[i], WordForwardEntry.fromTally(candidate[i]));
1789
- }
1790
- }
1791
- else if (Object.prototype.hasOwnProperty.call(data, "forward")) {
1792
- canonical = false;
1793
- needsDeepSanitize = true;
1794
- }
1795
- if (needsDeepSanitize) {
1796
- const actualForwardCounts = data.files.map(() => new Map());
1797
- for (const [token, list] of postings) {
1798
- const seenPairs = new Set();
1799
- const sanitized = new WordPostingList(token, list.length);
1800
- for (let i = 0; i < list.length; i += 1) {
1801
- const fileId = list.fileIdAt(i);
1802
- const line = list.lineAt(i);
1803
- const pairKey = `${fileId}:${line}`;
1804
- if (seenPairs.has(pairKey))
1805
- continue;
1806
- seenPairs.add(pairKey);
1807
- sanitized.push(fileId, line);
1808
- const slot = slotByFileId.get(fileId);
1809
- if (slot !== undefined) {
1810
- const counts = actualForwardCounts[slot];
1811
- counts.set(token, (counts.get(token) ?? 0) + 1);
1812
- }
1813
- }
1814
- postings.set(token, sanitized);
1815
- }
1816
- compactPostingsIntoArena(postings);
1817
- if (Array.isArray(data.forward)) {
1818
- forward = new PathKeyedMap(wordIndexKey);
1819
- for (let i = 0; i < data.files.length; i += 1) {
1820
- const canonicalSlot = slotByFileId.get(fileIdBySlot[i]) ?? i;
1821
- forward.set(data.files[i], WordForwardEntry.fromTally(actualForwardCounts[canonicalSlot]));
1822
- }
1823
- }
1824
- }
1825
- const expectedTotalTokens = [...docLengths.values()].reduce((total, length) => total + length, 0);
1826
- const totalTokens = isCanonicalWordIndexNumber(expectedTotalTokens)
1827
- ? expectedTotalTokens
1828
- : 0;
1829
- if (!isCanonicalWordIndexNumber(data.totalTokens) ||
1830
- data.totalTokens !== totalTokens) {
1831
- canonical = false;
1832
- }
1833
- if (typeof data.indexedFileCount !== "number" ||
1834
- data.indexedFileCount !== docLengths.size) {
1835
- canonical = false;
1836
- }
1837
- if (typeof data.truncated !== "boolean")
1838
- canonical = false;
1839
- const index = {
1840
- postings,
1841
- fileTable,
1842
- docLengths,
1843
- totalTokens,
1844
- docCount: docLengths.size,
1845
- truncated: data.truncated === true,
1846
- forward,
1847
- fileMtimes,
1848
- fileSizes,
1849
- // Postings were just packed into one arena above, so the running gate
1850
- // starts from the exact post-compaction store count (#2117).
1851
- postingStoreCount: countPostingBackingStores(postings),
1852
- recompactFlight: createSingleFlight(),
1853
- // A deserialized index is a fresh object distinct from whichever index
1854
- // produced `data`. Without this, every per-edit dirty mark on a
1855
- // session-start reload (#2068) is a no-op against `undefined`, and
1856
- // serializeWordIndexIncrementally's `!dirty` branch then returns the
1857
- // stale cached wire view forever, silently dropping every later edit
1858
- // from the persisted snapshot.
1859
- dirtyFiles: new Set(),
1860
- };
1861
- // Seed the wire cache from the snapshot just loaded so the FIRST persist
1862
- // of a reloaded session (session-start's `snapshotSaveSyncMs`, #2068) also
1863
- // takes the bounded incremental path instead of paying a full rebuild —
1864
- // mirrors exactly what one priming `serializeWordIndex` call would cache.
1865
- const tokensByFile = new Map();
1866
- if (forward) {
1867
- for (const file of data.files) {
1868
- tokensByFile.set(wordIndexKey(file), new Set(forward.get(file)?.keys() ?? []));
1869
- }
1870
- }
1871
- if (canonical) {
1872
- serializedWordIndexCaches.set(index, {
1873
- serialized: data,
1874
- slotByFileId,
1875
- tokensByFile,
1876
- });
1877
- }
1878
- return index;
1879
- }
1880
- const buildStatuses = new Map();
1881
- export function getWordIndexBuildStatus(cwd) {
1882
- return buildStatuses.get(path.resolve(cwd));
1883
- }
1884
- /** Test-only: reset the in-flight-build guard between test files/cases. */
1885
- export function _resetWordIndexBuildGuardForTests() {
1886
- buildStatuses.clear();
1887
- }
1888
- /**
1889
- * Fire a one-time bounded background build for `cwd` if one isn't already
1890
- * running. Persists into the existing project snapshot (preserving its other
1891
- * fields) so the next query — or the next real session — picks it up. Errors
1892
- * are swallowed (this is best-effort warmth, not a request the caller is
1893
- * waiting on); the guard always clears in a `finally` so a failed build can be
1894
- * retried by a later query.
1895
- */
1896
- export function triggerBackgroundWordIndexBuild(cwd, dbg, options = {}) {
1897
- const key = path.resolve(cwd);
1898
- // #747 hardening: this trigger is the one word-index build path with NO
1899
- // session lifecycle in front of it (cold `symbol_search` queries, in-process
1900
- // and via MCP where cwd can be a raw tool argument) — the session-start and
1901
- // quick-mode-warmup builds sit behind `canWarmCaches`, which already refuses
1902
- // a home-rooted cwd. Apply the same `isAtOrAboveHomeDir` ceiling here so a
1903
- // cold query from $HOME never starts a whole-home walk-and-read.
1904
- if (isAtOrAboveHomeDir(key, options.homeDir)) {
1905
- const reason = `root at/above home directory (${key})`;
1906
- dbg?.(`word-index cold-build: skipped — ${reason}`);
1907
- logWordIndex({
1908
- phase: "cold_build_refused",
1909
- cwd: key,
1910
- trigger: "cold_query",
1911
- reason,
1912
- });
1913
- const status = { state: "refused", reason };
1914
- buildStatuses.set(key, status);
1915
- return status;
1916
- }
1917
- const current = buildStatuses.get(key);
1918
- if (current?.state === "building")
1919
- return current;
1920
- const status = { state: "building" };
1921
- buildStatuses.set(key, status);
1922
- void (async () => {
1923
- const startMs = Date.now();
1924
- try {
1925
- const { loadProjectSnapshot, saveProjectSnapshot, PROJECT_SNAPSHOT_VERSION, } = await import("./project-snapshot.js");
1926
- const docs = await collectWordIndexDocs(key);
1927
- const index = await buildWordIndexAsync(docs);
1928
- const existing = loadProjectSnapshot(key);
1929
- const snapshot = existing ?? {
1930
- version: PROJECT_SNAPSHOT_VERSION,
1931
- projectRoot: key,
1932
- generatedAt: new Date().toISOString(),
1933
- seq: 0,
1934
- files: {},
1935
- symbols: {},
1936
- reverseDeps: {},
1937
- cachedExports: [],
1938
- };
1939
- snapshot.generatedAt = new Date().toISOString();
1940
- snapshot.wordIndex = serializeWordIndex(index);
1941
- saveProjectSnapshot(key, snapshot);
1942
- dbg?.(`word-index cold-build: ${index.docCount} files, ${index.postings.size} tokens (${Date.now() - startMs}ms)`);
1943
- // M2, #958: durable decision/coverage record for the MCP-critical cold
1944
- // path (this is where symbol_search reads the index and dbg is a no-op).
1945
- logWordIndex({
1946
- phase: "cold_build",
1947
- cwd: key,
1948
- trigger: "cold_query",
1949
- durationMs: Date.now() - startMs,
1950
- indexedFileCount: index.docCount,
1951
- tokens: index.postings.size,
1952
- postingEntries: countWordIndexPostingEntries(index),
1953
- residentBytes: estimateWordIndexResidentBytes(index),
1954
- truncated: index.truncated,
1955
- skipped: docs.skipped,
1956
- });
1957
- buildStatuses.delete(key);
1958
- }
1959
- catch (err) {
1960
- const reason = err instanceof Error ? err.message : String(err);
1961
- buildStatuses.set(key, { state: "failed", reason });
1962
- dbg?.(`word-index cold-build: failed: ${reason}`);
1963
- logWordIndex({
1964
- phase: "cold_build_failed",
1965
- cwd: key,
1966
- trigger: "cold_query",
1967
- durationMs: Date.now() - startMs,
1968
- error: reason,
1969
- });
1970
- }
1971
- })();
1972
- return status;
1973
- }
1974
- // --- Debounced per-edit persist (#348 phase 2) --------------------------------
1975
- //
1976
- // The per-edit seam (dispatch/integration.ts) updates `runtime.wordIndex` in
1977
- // memory on every write, same as the review graph's per-edit rebuild. Without
1978
- // coalescing, persisting that in-memory index on every single edit would mean
1979
- // one full-snapshot JSON.stringify+write per keystroke-adjacent edit — the
1980
- // same OOM-risking spike the graph's #260 circuit-breaker exists to prevent.
1981
- // This reuses `createDebounceScheduler` (persist-debounce.ts) rather than
1982
- // growing a second copy of the graph's bespoke pending-map+timer bookkeeping;
1983
- // only the "write" callback differs, because the target differs: the graph
1984
- // owns its own cache file, but the word index must merge into the SHARED
1985
- // project-snapshot file via `saveRuntimeProjectSnapshot`/`saveProjectSnapshot`
1986
- // (preserving unrelated snapshot fields, and honoring the seq-laundering guard
1987
- // in project-snapshot.ts — see saveRuntimeProjectSnapshot's comment).
1988
- const WORD_INDEX_PERSIST_DEBOUNCE_MS_DEFAULT = 1500;
1989
- function wordIndexPersistDebounceMs() {
1990
- const raw = Number(process.env.PI_LENS_WORD_INDEX_PERSIST_DEBOUNCE_MS);
1991
- return Number.isFinite(raw) && raw >= 0
1992
- ? raw
1993
- : WORD_INDEX_PERSIST_DEBOUNCE_MS_DEFAULT;
1994
- }
1995
- let wordIndexPersistScheduler;
1996
- function getWordIndexPersistScheduler() {
1997
- if (wordIndexPersistScheduler)
1998
- return wordIndexPersistScheduler;
1999
- wordIndexPersistScheduler = createDebounceScheduler({
2000
- debounceMs: wordIndexPersistDebounceMs,
2001
- write(_key, pending) {
2002
- void writeWordIndexSnapshot(pending.cwd, pending.index, pending.dbg);
2003
- },
2004
- });
2005
- return wordIndexPersistScheduler;
2006
- }
2007
- async function writeWordIndexSnapshot(cwd, index, dbg) {
2008
- const persistStartedAt = Date.now();
2009
- try {
2010
- const { loadProjectSnapshot, saveProjectSnapshot, PROJECT_SNAPSHOT_VERSION, } = await import("./project-snapshot.js");
2011
- const existing = loadProjectSnapshot(cwd);
2012
- const snapshot = existing ?? {
2013
- version: PROJECT_SNAPSHOT_VERSION,
2014
- projectRoot: path.resolve(cwd),
2015
- generatedAt: new Date().toISOString(),
2016
- seq: 0,
2017
- files: {},
2018
- symbols: {},
2019
- reverseDeps: {},
2020
- cachedExports: [],
2021
- };
2022
- snapshot.generatedAt = new Date().toISOString();
2023
- const serializeStartedAt = performance.now();
2024
- snapshot.wordIndex = serializeWordIndex(index);
2025
- const serializeMs = performance.now() - serializeStartedAt;
2026
- saveProjectSnapshot(cwd, snapshot);
2027
- const writeMs = performance.now() - serializeStartedAt - serializeMs;
2028
- dbg?.(`word-index persist: ${index.docCount} files, ${index.postings.size} tokens`);
2029
- // #958 review F1: durably record persist SUCCESS too, not just failures —
2030
- // in the MCP host (`dbg` is a no-op) this is the only signal that the index
2031
- // is actually being kept fresh across edits. Debounced (~1.5s), so not
2032
- // spammy. Mirrors the review graph's persist_succeeded.
2033
- logWordIndex({
2034
- phase: "persist_succeeded",
2035
- cwd: path.resolve(cwd),
2036
- trigger: "per_edit",
2037
- durationMs: Date.now() - persistStartedAt,
2038
- serializeMs,
2039
- writeMs,
2040
- indexedFileCount: index.docCount,
2041
- tokens: index.postings.size,
2042
- postingEntries: countWordIndexPostingEntries(index),
2043
- residentBytes: estimateWordIndexResidentBytes(index),
2044
- replacementCount: index.replacementStats?.count ?? 0,
2045
- totalReplacementMs: index.replacementStats?.totalMs ?? 0,
2046
- maxReplacementMs: index.replacementStats?.maxMs ?? 0,
2047
- });
2048
- index.replacementStats = { count: 0, totalMs: 0, maxMs: 0 };
2049
- }
2050
- catch (err) {
2051
- dbg?.(`word-index persist: failed: ${err}`);
2052
- // M3, #958: a swallowed persist means every LATER symbol_search reads a
2053
- // stale index with no trace — the exact silent-failure this durable log
2054
- // exists to surface (dbg is a no-op in the MCP host).
2055
- logWordIndex({
2056
- phase: "persist_failed",
2057
- cwd: path.resolve(cwd),
2058
- trigger: "per_edit",
2059
- indexedFileCount: index.docCount,
2060
- error: err instanceof Error ? err.message : String(err),
2061
- });
2062
- }
2063
- }
2064
- /**
2065
- * Schedule a debounced persist of `index` for `cwd`, coalescing a burst of
2066
- * per-edit updates into one write after a quiet window (default 1500ms,
2067
- * override via `PI_LENS_WORD_INDEX_PERSIST_DEBOUNCE_MS`, mirroring the review
2068
- * graph's `PI_LENS_GRAPH_PERSIST_DEBOUNCE_MS`). Merges through the same
2069
- * `saveProjectSnapshot` path phase 1 uses — preserves unrelated snapshot
2070
- * fields and respects the seq-laundering guard (only ever writes wordIndex
2071
- * for the CURRENT in-memory index, never re-stamps a stale one).
2072
- */
2073
- export function scheduleWordIndexPersist(cwd, index, dbg) {
2074
- const key = path.resolve(cwd);
2075
- getWordIndexPersistScheduler().schedule(key, { cwd: key, index, dbg });
2076
- }
2077
- /** Test hook: force any pending debounced word-index persist to write immediately. */
2078
- export function flushWordIndexPersistsForTests() {
2079
- getWordIndexPersistScheduler().flushAll();
2080
- }