sensemaking 0.27.0 → 0.28.0

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 (364) hide show
  1. package/bin/cli.js +16 -7
  2. package/dist/cjs/chunk/extract.js.map +1 -1
  3. package/dist/cjs/chunk/group.js.map +1 -1
  4. package/dist/cjs/chunk/index.js.map +1 -1
  5. package/dist/cjs/chunk/parse.js.map +1 -1
  6. package/dist/cjs/chunk/parser.js.map +1 -1
  7. package/dist/cjs/chunk/tokens.js.map +1 -1
  8. package/dist/cjs/chunk/version.js.map +1 -1
  9. package/dist/cjs/cli/build.js.map +1 -1
  10. package/dist/cjs/cli/constants.d.cts +2 -0
  11. package/dist/cjs/cli/constants.d.ts +2 -0
  12. package/dist/cjs/cli/constants.js +21 -0
  13. package/dist/cjs/cli/constants.js.map +1 -0
  14. package/dist/cjs/cli/download.js.map +1 -1
  15. package/dist/cjs/cli/index.js.map +1 -1
  16. package/dist/cjs/cli/init.js.map +1 -1
  17. package/dist/cjs/cli/map.js.map +1 -1
  18. package/dist/cjs/cli/named.js +2 -1
  19. package/dist/cjs/cli/named.js.map +1 -1
  20. package/dist/cjs/cli/path.js.map +1 -1
  21. package/dist/cjs/cli/peek.js.map +1 -1
  22. package/dist/cjs/cli/related.js.map +1 -1
  23. package/dist/cjs/cli/search.js.map +1 -1
  24. package/dist/cjs/cli/shared.js +5 -4
  25. package/dist/cjs/cli/shared.js.map +1 -1
  26. package/dist/cjs/cli/sql.js.map +1 -1
  27. package/dist/cjs/cli/status.js.map +1 -1
  28. package/dist/cjs/cli/watch.js.map +1 -1
  29. package/dist/cjs/cli.js +10 -9
  30. package/dist/cjs/cli.js.map +1 -1
  31. package/dist/cjs/commands/index.js.map +1 -1
  32. package/dist/cjs/commands/map.js.map +1 -1
  33. package/dist/cjs/commands/path.js.map +1 -1
  34. package/dist/cjs/commands/peek.js.map +1 -1
  35. package/dist/cjs/commands/related.js.map +1 -1
  36. package/dist/cjs/commands/scope.js.map +1 -1
  37. package/dist/cjs/commands/search.js.map +1 -1
  38. package/dist/cjs/commands/signals.js.map +1 -1
  39. package/dist/cjs/commands/snapshot.js.map +1 -1
  40. package/dist/cjs/commands/status.js.map +1 -1
  41. package/dist/cjs/config/access.js.map +1 -1
  42. package/dist/cjs/config/index.js.map +1 -1
  43. package/dist/cjs/config/load.js.map +1 -1
  44. package/dist/cjs/config/resolve.js.map +1 -1
  45. package/dist/cjs/config/signals.js.map +1 -1
  46. package/dist/cjs/config/types.js.map +1 -1
  47. package/dist/cjs/config/validate.js.map +1 -1
  48. package/dist/cjs/embed/cohere.js.map +1 -1
  49. package/dist/cjs/embed/distribution.js.map +1 -1
  50. package/dist/cjs/embed/endpoint.js.map +1 -1
  51. package/dist/cjs/embed/handoff.js.map +1 -1
  52. package/dist/cjs/embed/http.js.map +1 -1
  53. package/dist/cjs/embed/identity.js.map +1 -1
  54. package/dist/cjs/embed/langfit.js.map +1 -1
  55. package/dist/cjs/embed/languages.js.map +1 -1
  56. package/dist/cjs/embed/openai.js.map +1 -1
  57. package/dist/cjs/embed/query.js.map +1 -1
  58. package/dist/cjs/embed/registry.js.map +1 -1
  59. package/dist/cjs/embed/static.js.map +1 -1
  60. package/dist/cjs/embed/store.js.map +1 -1
  61. package/dist/cjs/embed/types.js.map +1 -1
  62. package/dist/cjs/errors.js.map +1 -1
  63. package/dist/cjs/features/embed.js.map +1 -1
  64. package/dist/cjs/features/fences.js.map +1 -1
  65. package/dist/cjs/features/index.js.map +1 -1
  66. package/dist/cjs/features/links.js.map +1 -1
  67. package/dist/cjs/features/rank.js.map +1 -1
  68. package/dist/cjs/features/sections.js.map +1 -1
  69. package/dist/cjs/features/tags.js.map +1 -1
  70. package/dist/cjs/graph/graph.js.map +1 -1
  71. package/dist/cjs/graph/traverse.js.map +1 -1
  72. package/dist/cjs/index.js.map +1 -1
  73. package/dist/cjs/lib/atomic-write.js.map +1 -1
  74. package/dist/cjs/lib/guarded-tick.js.map +1 -1
  75. package/dist/cjs/lib/serial-query.js.map +1 -1
  76. package/dist/cjs/lib/watch-notification.js.map +1 -1
  77. package/dist/cjs/lib/worker-file.js.map +1 -1
  78. package/dist/cjs/output/column-hint.js.map +1 -1
  79. package/dist/cjs/output/output.js.map +1 -1
  80. package/dist/cjs/output/progress.js.map +1 -1
  81. package/dist/cjs/output/search-error.js.map +1 -1
  82. package/dist/cjs/scan/frontmatter.js.map +1 -1
  83. package/dist/cjs/scan/index.js.map +1 -1
  84. package/dist/cjs/scan/list.js.map +1 -1
  85. package/dist/cjs/scan/pool.js.map +1 -1
  86. package/dist/cjs/scan/reparse.js.map +1 -1
  87. package/dist/cjs/scan/worker-error.js.map +1 -1
  88. package/dist/cjs/store/batch.js.map +1 -1
  89. package/dist/cjs/store/builder.js.map +1 -1
  90. package/dist/cjs/store/cache.js.map +1 -1
  91. package/dist/cjs/store/duckdb/batch.js.map +1 -1
  92. package/dist/cjs/store/duckdb/connection.js.map +1 -1
  93. package/dist/cjs/store/duckdb/engine.d.cts +11 -0
  94. package/dist/cjs/store/duckdb/engine.d.ts +11 -0
  95. package/dist/cjs/store/duckdb/engine.js +193 -0
  96. package/dist/cjs/store/duckdb/engine.js.map +1 -0
  97. package/dist/cjs/store/duckdb/fieldStats.js.map +1 -1
  98. package/dist/cjs/store/duckdb/lexical.d.cts +1 -6
  99. package/dist/cjs/store/duckdb/lexical.d.ts +1 -6
  100. package/dist/cjs/store/duckdb/lexical.js +48 -45
  101. package/dist/cjs/store/duckdb/lexical.js.map +1 -1
  102. package/dist/cjs/store/duckdb/native.d.cts +5 -3
  103. package/dist/cjs/store/duckdb/native.d.ts +5 -3
  104. package/dist/cjs/store/duckdb/native.js +157 -18
  105. package/dist/cjs/store/duckdb/native.js.map +1 -1
  106. package/dist/cjs/store/duckdb/open.js +151 -3
  107. package/dist/cjs/store/duckdb/open.js.map +1 -1
  108. package/dist/cjs/store/duckdb/ordered-bm25.d.cts +3 -16
  109. package/dist/cjs/store/duckdb/ordered-bm25.d.ts +3 -16
  110. package/dist/cjs/store/duckdb/ordered-bm25.js +24 -80
  111. package/dist/cjs/store/duckdb/ordered-bm25.js.map +1 -1
  112. package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
  113. package/dist/cjs/store/duckdb/sql-functions.js.map +1 -1
  114. package/dist/cjs/store/duckdb/store.js.map +1 -1
  115. package/dist/cjs/store/duckdb/vectors.js.map +1 -1
  116. package/dist/cjs/store/embed-scope.js.map +1 -1
  117. package/dist/cjs/store/engine.d.cts +28 -0
  118. package/dist/cjs/store/engine.d.ts +28 -0
  119. package/dist/cjs/store/engine.js +362 -0
  120. package/dist/cjs/store/engine.js.map +1 -0
  121. package/dist/cjs/store/feature-scope.js.map +1 -1
  122. package/dist/cjs/store/index.js.map +1 -1
  123. package/dist/cjs/store/lock-wait.js.map +1 -1
  124. package/dist/cjs/store/native.d.cts +8 -5
  125. package/dist/cjs/store/native.d.ts +8 -5
  126. package/dist/cjs/store/native.js +78 -73
  127. package/dist/cjs/store/native.js.map +1 -1
  128. package/dist/cjs/store/open.js +100 -40
  129. package/dist/cjs/store/open.js.map +1 -1
  130. package/dist/cjs/store/preset-scope.js.map +1 -1
  131. package/dist/cjs/store/readiness.js.map +1 -1
  132. package/dist/cjs/store/reconcile.js.map +1 -1
  133. package/dist/cjs/store/shared.js.map +1 -1
  134. package/dist/cjs/store/signature.js.map +1 -1
  135. package/dist/cjs/store/sql-functions.js.map +1 -1
  136. package/dist/cjs/store/sqlite/connection.js.map +1 -1
  137. package/dist/cjs/store/sqlite/engine.d.cts +12 -0
  138. package/dist/cjs/store/sqlite/engine.d.ts +12 -0
  139. package/dist/cjs/store/sqlite/engine.js +53 -0
  140. package/dist/cjs/store/sqlite/engine.js.map +1 -0
  141. package/dist/cjs/store/sqlite/fieldStats.js.map +1 -1
  142. package/dist/cjs/store/sqlite/lexical.js +3 -4
  143. package/dist/cjs/store/sqlite/lexical.js.map +1 -1
  144. package/dist/cjs/store/sqlite/open.js +119 -6
  145. package/dist/cjs/store/sqlite/open.js.map +1 -1
  146. package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
  147. package/dist/cjs/store/sqlite/sql-functions.js.map +1 -1
  148. package/dist/cjs/store/sqlite/store.js.map +1 -1
  149. package/dist/cjs/store/sqlite/vectors.js.map +1 -1
  150. package/dist/cjs/store/stages.js.map +1 -1
  151. package/dist/cjs/store/transaction.js.map +1 -1
  152. package/dist/cjs/store/turso/connection.js.map +1 -1
  153. package/dist/cjs/store/turso/engine.d.cts +9 -0
  154. package/dist/cjs/store/turso/engine.d.ts +9 -0
  155. package/dist/cjs/store/turso/engine.js +189 -0
  156. package/dist/cjs/store/turso/engine.js.map +1 -0
  157. package/dist/cjs/store/turso/fieldStats.js.map +1 -1
  158. package/dist/cjs/store/turso/lexical-text.js.map +1 -1
  159. package/dist/cjs/store/turso/lexical.js +16 -11
  160. package/dist/cjs/store/turso/lexical.js.map +1 -1
  161. package/dist/cjs/store/turso/native.d.cts +5 -1
  162. package/dist/cjs/store/turso/native.d.ts +5 -1
  163. package/dist/cjs/store/turso/native.js +157 -8
  164. package/dist/cjs/store/turso/native.js.map +1 -1
  165. package/dist/cjs/store/turso/open.js +172 -27
  166. package/dist/cjs/store/turso/open.js.map +1 -1
  167. package/dist/cjs/store/turso/reconcile.d.cts +0 -1
  168. package/dist/cjs/store/turso/reconcile.d.ts +0 -1
  169. package/dist/cjs/store/turso/reconcile.js +22 -23
  170. package/dist/cjs/store/turso/reconcile.js.map +1 -1
  171. package/dist/cjs/store/turso/sql-functions.js.map +1 -1
  172. package/dist/cjs/store/turso/store.js.map +1 -1
  173. package/dist/cjs/store/turso/vectors.js.map +1 -1
  174. package/dist/cjs/store/types.d.cts +2 -0
  175. package/dist/cjs/store/types.d.ts +2 -0
  176. package/dist/cjs/store/types.js.map +1 -1
  177. package/dist/cjs/store/vectors.js.map +1 -1
  178. package/dist/cjs/text/segment.js.map +1 -1
  179. package/dist/cjs/text/strip.js.map +1 -1
  180. package/dist/cjs/watch-claim.js.map +1 -1
  181. package/dist/cjs/watch.js.map +1 -1
  182. package/dist/cjs/workers/parse.js.map +1 -1
  183. package/dist/cjs/workers/watch-heartbeat.js.map +1 -1
  184. package/dist/esm/chunk/extract.js.map +1 -1
  185. package/dist/esm/chunk/group.js.map +1 -1
  186. package/dist/esm/chunk/index.js.map +1 -1
  187. package/dist/esm/chunk/parse.js.map +1 -1
  188. package/dist/esm/chunk/parser.js.map +1 -1
  189. package/dist/esm/chunk/tokens.js.map +1 -1
  190. package/dist/esm/chunk/types.js.map +1 -1
  191. package/dist/esm/chunk/version.js.map +1 -1
  192. package/dist/esm/cli/build.js.map +1 -1
  193. package/dist/esm/cli/constants.d.ts +2 -0
  194. package/dist/esm/cli/constants.js +2 -0
  195. package/dist/esm/cli/constants.js.map +1 -0
  196. package/dist/esm/cli/download.js.map +1 -1
  197. package/dist/esm/cli/index.js.map +1 -1
  198. package/dist/esm/cli/init.js.map +1 -1
  199. package/dist/esm/cli/map.js.map +1 -1
  200. package/dist/esm/cli/named.js +2 -1
  201. package/dist/esm/cli/named.js.map +1 -1
  202. package/dist/esm/cli/path.js.map +1 -1
  203. package/dist/esm/cli/peek.js.map +1 -1
  204. package/dist/esm/cli/related.js.map +1 -1
  205. package/dist/esm/cli/search.js.map +1 -1
  206. package/dist/esm/cli/shared.js +6 -5
  207. package/dist/esm/cli/shared.js.map +1 -1
  208. package/dist/esm/cli/sql.js.map +1 -1
  209. package/dist/esm/cli/status.js.map +1 -1
  210. package/dist/esm/cli/types.js.map +1 -1
  211. package/dist/esm/cli/watch.js.map +1 -1
  212. package/dist/esm/cli.js +10 -9
  213. package/dist/esm/cli.js.map +1 -1
  214. package/dist/esm/commands/index.js.map +1 -1
  215. package/dist/esm/commands/map.js.map +1 -1
  216. package/dist/esm/commands/path.js.map +1 -1
  217. package/dist/esm/commands/peek.js.map +1 -1
  218. package/dist/esm/commands/related.js.map +1 -1
  219. package/dist/esm/commands/scope.js.map +1 -1
  220. package/dist/esm/commands/search.js.map +1 -1
  221. package/dist/esm/commands/signals.js.map +1 -1
  222. package/dist/esm/commands/snapshot.js.map +1 -1
  223. package/dist/esm/commands/status.js.map +1 -1
  224. package/dist/esm/config/access.js.map +1 -1
  225. package/dist/esm/config/index.js.map +1 -1
  226. package/dist/esm/config/load.js.map +1 -1
  227. package/dist/esm/config/resolve.js.map +1 -1
  228. package/dist/esm/config/signals.js.map +1 -1
  229. package/dist/esm/config/types.js.map +1 -1
  230. package/dist/esm/config/validate.js.map +1 -1
  231. package/dist/esm/embed/cohere.js.map +1 -1
  232. package/dist/esm/embed/distribution.js.map +1 -1
  233. package/dist/esm/embed/endpoint.js.map +1 -1
  234. package/dist/esm/embed/handoff.js.map +1 -1
  235. package/dist/esm/embed/http.js.map +1 -1
  236. package/dist/esm/embed/identity.js.map +1 -1
  237. package/dist/esm/embed/langfit.js.map +1 -1
  238. package/dist/esm/embed/languages.js.map +1 -1
  239. package/dist/esm/embed/openai.js.map +1 -1
  240. package/dist/esm/embed/query.js +1 -1
  241. package/dist/esm/embed/query.js.map +1 -1
  242. package/dist/esm/embed/registry.js.map +1 -1
  243. package/dist/esm/embed/static.js.map +1 -1
  244. package/dist/esm/embed/store.js.map +1 -1
  245. package/dist/esm/embed/types.js.map +1 -1
  246. package/dist/esm/errors.js.map +1 -1
  247. package/dist/esm/features/embed.js.map +1 -1
  248. package/dist/esm/features/fences.js.map +1 -1
  249. package/dist/esm/features/index.js.map +1 -1
  250. package/dist/esm/features/links.js.map +1 -1
  251. package/dist/esm/features/rank.js.map +1 -1
  252. package/dist/esm/features/sections.js.map +1 -1
  253. package/dist/esm/features/tags.js.map +1 -1
  254. package/dist/esm/features/types.js.map +1 -1
  255. package/dist/esm/graph/graph.js.map +1 -1
  256. package/dist/esm/graph/traverse.js.map +1 -1
  257. package/dist/esm/index.js.map +1 -1
  258. package/dist/esm/lib/atomic-write.js.map +1 -1
  259. package/dist/esm/lib/guarded-tick.js.map +1 -1
  260. package/dist/esm/lib/serial-query.js.map +1 -1
  261. package/dist/esm/lib/watch-notification.js.map +1 -1
  262. package/dist/esm/lib/worker-file.js.map +1 -1
  263. package/dist/esm/output/column-hint.js.map +1 -1
  264. package/dist/esm/output/output.js.map +1 -1
  265. package/dist/esm/output/progress.js.map +1 -1
  266. package/dist/esm/output/search-error.js.map +1 -1
  267. package/dist/esm/scan/frontmatter.js.map +1 -1
  268. package/dist/esm/scan/index.js.map +1 -1
  269. package/dist/esm/scan/list.js.map +1 -1
  270. package/dist/esm/scan/pool.js.map +1 -1
  271. package/dist/esm/scan/reparse.js.map +1 -1
  272. package/dist/esm/scan/worker-error.js.map +1 -1
  273. package/dist/esm/store/batch.js.map +1 -1
  274. package/dist/esm/store/builder.js.map +1 -1
  275. package/dist/esm/store/cache.js.map +1 -1
  276. package/dist/esm/store/duckdb/batch.js.map +1 -1
  277. package/dist/esm/store/duckdb/connection.js.map +1 -1
  278. package/dist/esm/store/duckdb/engine.d.ts +11 -0
  279. package/dist/esm/store/duckdb/engine.js +18 -0
  280. package/dist/esm/store/duckdb/engine.js.map +1 -0
  281. package/dist/esm/store/duckdb/fieldStats.js.map +1 -1
  282. package/dist/esm/store/duckdb/lexical.d.ts +1 -6
  283. package/dist/esm/store/duckdb/lexical.js +16 -20
  284. package/dist/esm/store/duckdb/lexical.js.map +1 -1
  285. package/dist/esm/store/duckdb/native.d.ts +5 -3
  286. package/dist/esm/store/duckdb/native.js +16 -14
  287. package/dist/esm/store/duckdb/native.js.map +1 -1
  288. package/dist/esm/store/duckdb/open.js +40 -2
  289. package/dist/esm/store/duckdb/open.js.map +1 -1
  290. package/dist/esm/store/duckdb/ordered-bm25.d.ts +3 -16
  291. package/dist/esm/store/duckdb/ordered-bm25.js +14 -55
  292. package/dist/esm/store/duckdb/ordered-bm25.js.map +1 -1
  293. package/dist/esm/store/duckdb/reconcile.js.map +1 -1
  294. package/dist/esm/store/duckdb/sql-functions.js.map +1 -1
  295. package/dist/esm/store/duckdb/store.js.map +1 -1
  296. package/dist/esm/store/duckdb/vectors.js.map +1 -1
  297. package/dist/esm/store/embed-scope.js.map +1 -1
  298. package/dist/esm/store/engine.d.ts +28 -0
  299. package/dist/esm/store/engine.js +85 -0
  300. package/dist/esm/store/engine.js.map +1 -0
  301. package/dist/esm/store/feature-scope.js.map +1 -1
  302. package/dist/esm/store/index.js.map +1 -1
  303. package/dist/esm/store/lock-wait.js.map +1 -1
  304. package/dist/esm/store/native.d.ts +8 -5
  305. package/dist/esm/store/native.js +59 -57
  306. package/dist/esm/store/native.js.map +1 -1
  307. package/dist/esm/store/open.js +24 -7
  308. package/dist/esm/store/open.js.map +1 -1
  309. package/dist/esm/store/preset-scope.js.map +1 -1
  310. package/dist/esm/store/readiness.js.map +1 -1
  311. package/dist/esm/store/reconcile.js.map +1 -1
  312. package/dist/esm/store/shared.js.map +1 -1
  313. package/dist/esm/store/signature.js.map +1 -1
  314. package/dist/esm/store/sql-functions.js.map +1 -1
  315. package/dist/esm/store/sqlite/connection.js.map +1 -1
  316. package/dist/esm/store/sqlite/engine.d.ts +12 -0
  317. package/dist/esm/store/sqlite/engine.js +16 -0
  318. package/dist/esm/store/sqlite/engine.js.map +1 -0
  319. package/dist/esm/store/sqlite/fieldStats.js.map +1 -1
  320. package/dist/esm/store/sqlite/lexical.js +2 -3
  321. package/dist/esm/store/sqlite/lexical.js.map +1 -1
  322. package/dist/esm/store/sqlite/open.js +38 -7
  323. package/dist/esm/store/sqlite/open.js.map +1 -1
  324. package/dist/esm/store/sqlite/reconcile.js.map +1 -1
  325. package/dist/esm/store/sqlite/sql-functions.js.map +1 -1
  326. package/dist/esm/store/sqlite/store.js.map +1 -1
  327. package/dist/esm/store/sqlite/vectors.js.map +1 -1
  328. package/dist/esm/store/stages.js.map +1 -1
  329. package/dist/esm/store/transaction.js.map +1 -1
  330. package/dist/esm/store/turso/connection.js.map +1 -1
  331. package/dist/esm/store/turso/engine.d.ts +9 -0
  332. package/dist/esm/store/turso/engine.js +14 -0
  333. package/dist/esm/store/turso/engine.js.map +1 -0
  334. package/dist/esm/store/turso/fieldStats.js.map +1 -1
  335. package/dist/esm/store/turso/lexical-text.js.map +1 -1
  336. package/dist/esm/store/turso/lexical.js +9 -9
  337. package/dist/esm/store/turso/lexical.js.map +1 -1
  338. package/dist/esm/store/turso/native.d.ts +5 -1
  339. package/dist/esm/store/turso/native.js +16 -10
  340. package/dist/esm/store/turso/native.js.map +1 -1
  341. package/dist/esm/store/turso/open.js +44 -3
  342. package/dist/esm/store/turso/open.js.map +1 -1
  343. package/dist/esm/store/turso/reconcile.d.ts +0 -1
  344. package/dist/esm/store/turso/reconcile.js +3 -8
  345. package/dist/esm/store/turso/reconcile.js.map +1 -1
  346. package/dist/esm/store/turso/sql-functions.js.map +1 -1
  347. package/dist/esm/store/turso/store.js.map +1 -1
  348. package/dist/esm/store/turso/vectors.js.map +1 -1
  349. package/dist/esm/store/types.d.ts +2 -0
  350. package/dist/esm/store/types.js.map +1 -1
  351. package/dist/esm/store/vectors.js.map +1 -1
  352. package/dist/esm/text/segment.js.map +1 -1
  353. package/dist/esm/text/strip.js.map +1 -1
  354. package/dist/esm/watch-claim.js.map +1 -1
  355. package/dist/esm/watch.js.map +1 -1
  356. package/dist/esm/workers/parse.js.map +1 -1
  357. package/dist/esm/workers/watch-heartbeat.js.map +1 -1
  358. package/package.json +21 -6
  359. package/skills/sense/SKILL.md +1 -1
  360. package/skills/sense/references/stores/duckdb.md +1 -1
  361. package/skills/sense/references/stores/sqlite.md +1 -1
  362. package/skills/sense/references/stores/turso.md +1 -1
  363. package/skills/sense-setup/references/stores/duckdb.md +1 -1
  364. package/skills/sense-setup/references/stores/sqlite.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/turso/store.ts"],"sourcesContent":["import type { Database } from '@tursodatabase/database';\nimport { STORE_DIMS } from '../../embed/types.ts';\nimport { getColumns, getMeta, setMeta } from '../shared.ts';\nimport { withTransaction } from '../transaction.ts';\nimport type { Capability, Connection, Statement, Store, VectorStore } from '../types.ts';\nimport { createNativeVectorScopeOwner, hasVectorRow, pendingRows } from '../vectors.ts';\nimport { cacheFilePath, checkpointWal, fileSize, reclaimSpace } from './connection.ts';\nimport { fieldStats } from './fieldStats.ts';\nimport { queryLexical } from './lexical.ts';\nimport { scanCandidates, scanSimilar, writeVectorBatch } from './vectors.ts';\n\n// No 'segment': has()/basename() resolve via SQL rewrite below (sql-functions.ts), but segment() has no SQL form and the client cannot register it as a UDF.\nexport const CAPABILITIES: ReadonlySet<Capability> = new Set(['vectors']);\n\n// close() reclaims once the cache file outgrows its compact size by this factor: the disk\n// overhead a user should accept (PLAN 3.52), in postgres autovacuum scale-factor shape.\nconst BLOAT_FACTOR = 1.5;\n\n// Shares one Connection instance (conn) with the builder's own reconcile call so transaction depth\n// (see transaction.ts) is tracked against the same object everywhere.\nexport function createStore(db: Database, conn: Connection, options: { observational?: boolean } = {}): Store {\n const vectorScope = createNativeVectorScopeOwner();\n // The column's fixed DDL width (STORE_DIMS) is what every scan binds against, not the\n // interface's per-call storeDims -- see vectors.ts's padded() for why a shorter vector is still correct against a wider column.\n const vectors: VectorStore = {\n pending: () => pendingRows(conn),\n writeVectors: (rows) => writeVectorBatch(conn, STORE_DIMS, rows),\n candidates: (qv, _storeDims, fetch, allowed) => scanCandidates(conn, qv, STORE_DIMS, fetch, allowed, vectorScope.activeFor(allowed)),\n similar: (path, opts) => scanSimilar(conn, STORE_DIMS, path, opts, vectorScope.activeFor(opts.allowed)),\n hasVector: (path) => hasVectorRow(conn, path),\n };\n vectorScope.bind(vectors);\n\n return {\n name: 'turso',\n capabilities: CAPABILITIES,\n async exec(sql: string): Promise<void> {\n await conn.exec(sql);\n },\n async prepare(sql: string): Promise<Statement> {\n return conn.prepare(sql);\n },\n async runBatch(sql: string, paramRows: unknown[][]): Promise<void> {\n await conn.runBatch(sql, paramRows);\n },\n async transaction<T>(fn: () => Promise<T>): Promise<T> {\n return withTransaction(conn, fn);\n },\n docs: {\n async columns() {\n return [...(await getColumns(conn))];\n },\n fieldStats: (columns, scopeWhere) => fieldStats(conn, columns, scopeWhere),\n },\n lexical: {\n query: (terms, opts) => queryLexical(conn, terms, opts),\n },\n vectors,\n async engineStatus() {\n // Read back rather than recomputed: this is what open() actually set (3x the largest\n // recorded reconcile, floored at 30s, capped at 10min). Turso's PRAGMA busy_timeout names its column \"busy_timeout\" (spike-verified), not \"timeout\" like real SQLite.\n const row = (await (await conn.prepare('PRAGMA busy_timeout')).get()) as { busy_timeout: number };\n return { busy_timeout: `${row.busy_timeout}ms (derived: 3x the largest reconcile this cache has recorded, floored at 30000ms)` };\n },\n raw: {\n async prepare(sql: string) {\n const stmt = await conn.prepare(sql);\n stmt.setReadBigInts(true); // int64 past 2^53 arrives as BigInt instead of losing precision\n return {\n columns: () => stmt.columns(),\n // sense sql streams through the client's own async generator.\n iterate: async function* (...params: unknown[]) {\n yield* stmt.iterate(...params);\n },\n };\n },\n },\n async close() {\n if (options.observational) {\n await db.close();\n return;\n }\n // Checkpoint before measuring: the last reconcile's pages sit in the WAL, not the file yet.\n await checkpointWal(db);\n let reclaimPath: string | null = null;\n try {\n const path = await cacheFilePath(conn);\n const base = Number((await getMeta(conn, 'compact_size')) ?? '0');\n const size = path === null ? null : await fileSize(path);\n if (size !== null) {\n // No baseline yet (cold build or an upgraded cache): record it, never reclaim here, so a\n // cold build pays no VACUUM (PLAN 3.49 Track A) and an upgraded cache heals on a crossing.\n if (base === 0) {\n await setMeta(conn, 'compact_size', String(size));\n // The meta write landed past the checkpoint; leave no WAL for the next opener.\n await checkpointWal(db);\n } else if (size > BLOAT_FACTOR * base) {\n reclaimPath = path;\n }\n }\n } catch (err) {\n console.error(`sense: turso cache bloat check failed, the cache will keep its space until one succeeds: ${(err as Error).message}`);\n }\n await db.close();\n // After this store's connection is closed, never before: turso hands the file to one\n // connection at a time, and reclaimSpace opens its own to carry the `vacuum` flag (PLAN 3.54).\n if (reclaimPath !== null) await reclaimSpace(reclaimPath);\n },\n };\n}\n"],"names":["CAPABILITIES","createStore","Set","BLOAT_FACTOR","db","conn","options","vectorScope","createNativeVectorScopeOwner","vectors","pending","pendingRows","writeVectors","rows","writeVectorBatch","STORE_DIMS","candidates","qv","_storeDims","fetch","allowed","scanCandidates","activeFor","similar","path","opts","scanSimilar","hasVector","hasVectorRow","bind","name","capabilities","exec","sql","prepare","runBatch","paramRows","transaction","fn","withTransaction","docs","columns","getColumns","fieldStats","scopeWhere","lexical","query","terms","queryLexical","engineStatus","row","get","busy_timeout","raw","stmt","setReadBigInts","iterate","params","close","reclaimPath","base","size","err","observational","checkpointWal","cacheFilePath","getMeta","Number","fileSize","setMeta","String","console","error","message","reclaimSpace"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/turso/store.ts"],"sourcesContent":["import type { Database } from '@tursodatabase/database';\nimport { STORE_DIMS } from '../../embed/types.ts';\nimport { getColumns, getMeta, setMeta } from '../shared.ts';\nimport { withTransaction } from '../transaction.ts';\nimport type { Capability, Connection, Statement, Store, VectorStore } from '../types.ts';\nimport { createNativeVectorScopeOwner, hasVectorRow, pendingRows } from '../vectors.ts';\nimport { cacheFilePath, checkpointWal, fileSize, reclaimSpace } from './connection.ts';\nimport { fieldStats } from './fieldStats.ts';\nimport { queryLexical } from './lexical.ts';\nimport { scanCandidates, scanSimilar, writeVectorBatch } from './vectors.ts';\n\n// No 'segment': has()/basename() resolve via SQL rewrite below (sql-functions.ts), but segment() has no SQL form and the client cannot register it as a UDF.\nexport const CAPABILITIES: ReadonlySet<Capability> = new Set(['vectors']);\n\n// close() reclaims once the cache file outgrows its compact size by this factor: the disk\n// overhead a user should accept (PLAN 3.52), in postgres autovacuum scale-factor shape.\nconst BLOAT_FACTOR = 1.5;\n\n// Shares one Connection instance (conn) with the builder's own reconcile call so transaction depth\n// (see transaction.ts) is tracked against the same object everywhere.\nexport function createStore(db: Database, conn: Connection, options: { observational?: boolean } = {}): Store {\n const vectorScope = createNativeVectorScopeOwner();\n // The column's fixed DDL width (STORE_DIMS) is what every scan binds against, not the\n // interface's per-call storeDims -- see vectors.ts's padded() for why a shorter vector is still correct against a wider column.\n const vectors: VectorStore = {\n pending: () => pendingRows(conn),\n writeVectors: (rows) => writeVectorBatch(conn, STORE_DIMS, rows),\n candidates: (qv, _storeDims, fetch, allowed) => scanCandidates(conn, qv, STORE_DIMS, fetch, allowed, vectorScope.activeFor(allowed)),\n similar: (path, opts) => scanSimilar(conn, STORE_DIMS, path, opts, vectorScope.activeFor(opts.allowed)),\n hasVector: (path) => hasVectorRow(conn, path),\n };\n vectorScope.bind(vectors);\n\n return {\n name: 'turso',\n capabilities: CAPABILITIES,\n async exec(sql: string): Promise<void> {\n await conn.exec(sql);\n },\n async prepare(sql: string): Promise<Statement> {\n return conn.prepare(sql);\n },\n async runBatch(sql: string, paramRows: unknown[][]): Promise<void> {\n await conn.runBatch(sql, paramRows);\n },\n async transaction<T>(fn: () => Promise<T>): Promise<T> {\n return withTransaction(conn, fn);\n },\n docs: {\n async columns() {\n return [...(await getColumns(conn))];\n },\n fieldStats: (columns, scopeWhere) => fieldStats(conn, columns, scopeWhere),\n },\n lexical: {\n query: (terms, opts) => queryLexical(conn, terms, opts),\n },\n vectors,\n async engineStatus() {\n // Read back rather than recomputed: this is what open() actually set (3x the largest\n // recorded reconcile, floored at 30s, capped at 10min). Turso's PRAGMA busy_timeout names its column \"busy_timeout\" (spike-verified), not \"timeout\" like real SQLite.\n const row = (await (await conn.prepare('PRAGMA busy_timeout')).get()) as { busy_timeout: number };\n return { busy_timeout: `${row.busy_timeout}ms (derived: 3x the largest reconcile this cache has recorded, floored at 30000ms)` };\n },\n raw: {\n async prepare(sql: string) {\n const stmt = await conn.prepare(sql);\n stmt.setReadBigInts(true); // int64 past 2^53 arrives as BigInt instead of losing precision\n return {\n columns: () => stmt.columns(),\n // sense sql streams through the client's own async generator.\n iterate: async function* (...params: unknown[]) {\n yield* stmt.iterate(...params);\n },\n };\n },\n },\n async close() {\n if (options.observational) {\n await db.close();\n return;\n }\n // Checkpoint before measuring: the last reconcile's pages sit in the WAL, not the file yet.\n await checkpointWal(db);\n let reclaimPath: string | null = null;\n try {\n const path = await cacheFilePath(conn);\n const base = Number((await getMeta(conn, 'compact_size')) ?? '0');\n const size = path === null ? null : await fileSize(path);\n if (size !== null) {\n // No baseline yet (cold build or an upgraded cache): record it, never reclaim here, so a\n // cold build pays no VACUUM (PLAN 3.49 Track A) and an upgraded cache heals on a crossing.\n if (base === 0) {\n await setMeta(conn, 'compact_size', String(size));\n // The meta write landed past the checkpoint; leave no WAL for the next opener.\n await checkpointWal(db);\n } else if (size > BLOAT_FACTOR * base) {\n reclaimPath = path;\n }\n }\n } catch (err) {\n console.error(`sense: turso cache bloat check failed, the cache will keep its space until one succeeds: ${(err as Error).message}`);\n }\n await db.close();\n // After this store's connection is closed, never before: turso hands the file to one\n // connection at a time, and reclaimSpace opens its own to carry the `vacuum` flag (PLAN 3.54).\n if (reclaimPath !== null) await reclaimSpace(reclaimPath);\n },\n };\n}\n"],"names":["CAPABILITIES","createStore","Set","BLOAT_FACTOR","db","conn","options","vectorScope","createNativeVectorScopeOwner","vectors","pending","pendingRows","writeVectors","rows","writeVectorBatch","STORE_DIMS","candidates","qv","_storeDims","fetch","allowed","scanCandidates","activeFor","similar","path","opts","scanSimilar","hasVector","hasVectorRow","bind","name","capabilities","exec","sql","prepare","runBatch","paramRows","transaction","fn","withTransaction","docs","columns","getColumns","fieldStats","scopeWhere","lexical","query","terms","queryLexical","engineStatus","row","get","busy_timeout","raw","stmt","setReadBigInts","iterate","params","close","reclaimPath","base","size","err","observational","checkpointWal","cacheFilePath","getMeta","Number","fileSize","setMeta","String","console","error","message","reclaimSpace"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/turso/vectors.ts"],"sourcesContent":["import type { Connection, VectorCandidate, VectorSimilar, VectorWriteRow } from '../types.ts';\nimport { asCosine, compareVectorScores, NATIVE_VECTOR_SCOPE_TABLE, sampleEvenly } from '../vectors.ts';\n\n// Native F32_BLOB(dims) columns, dims fixed at DDL time (open.ts, STORE_DIMS). Both scans\n// score in JS: a per-row vector_distance_cos measured 3.5-8x slower on this row engine.\n\n// A model narrower than the column zero-pads: the added dimensions contribute nothing to\n// either dot product or norm, so cosine is unchanged.\nfunction padded(values: ArrayLike<number>, dims: number): Float32Array {\n const out = new Float32Array(dims);\n for (let d = 0; d < Math.min(values.length, dims); d++) out[d] = values[d];\n return out;\n}\n\n// A bound Float32Array stores truncated bytes with no error; a Buffer view over the same\n// bytes round-trips exact (measured 0.7.2).\nfunction toBlob(v: Float32Array): Buffer {\n return Buffer.from(v.buffer, v.byteOffset, v.byteLength);\n}\n\nfunction decode(vector: Buffer, dims: number): Float32Array {\n return new Float32Array(vector.buffer, vector.byteOffset, dims);\n}\n\n// Rows arrive int8-quantized with a per-vector scale (embed/query.ts's toStore); this store\n// dequantizes to floats, as duckdb does.\nfunction dequantize(row: VectorWriteRow, dims: number): Float32Array {\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n const out = new Float32Array(dims);\n for (let d = 0; d < Math.min(q.length, dims); d++) out[d] = q[d] * row.scale;\n return out;\n}\n\nfunction vectorNorm(vector: Float32Array): number {\n let norm = 0;\n for (let d = 0; d < vector.length; d++) norm += vector[d] * vector[d];\n return Math.sqrt(norm);\n}\n\nfunction cosineSimilarity(a: Float32Array, b: Float32Array, aNorm: number, bNorm: number): number {\n let dot = 0;\n for (let d = 0; d < a.length; d++) dot += a[d] * b[d];\n return dot / (aNorm * bNorm + 1e-32);\n}\n\n// `scale` stays NULL: the column exists so the row shape matches the other stores, and\n// cosine is scale-invariant once the value is dequantized here.\nexport async function writeVectorBatch(conn: Connection, dims: number, rows: VectorWriteRow[]): Promise<void> {\n if (rows.length === 0) return;\n await conn.runBatch(\n 'UPDATE embeddings SET vector = ? WHERE \"path\" = ? AND chunk = ?',\n rows.map((row) => [toBlob(dequantize(row, dims)), row.path, row.chunk])\n );\n}\n\n// Best chunk per file by cosine. GROUP BY with MIN() cannot do this on 0.7.2: the aggregate\n// is right but the bare start_line/end_line come from an arbitrary row in the group.\nexport async function scanCandidates(conn: Connection, qv: Float32Array, dims: number, fetch: number, allowed?: Set<string>, nativeScope = false): Promise<VectorCandidate[]> {\n const q = padded(qv, dims);\n const queryNorm = vectorNorm(q);\n const scope = nativeScope ? ` AND \"path\" IN (SELECT \"path\" FROM ${NATIVE_VECTOR_SCOPE_TABLE})` : '';\n const stmt = await conn.prepare(`SELECT \"path\", chunk, start_line, end_line, vector FROM embeddings WHERE vector IS NOT NULL${scope} ORDER BY \"path\", chunk`);\n const rows = (await stmt.all()) as Array<{ path: string; chunk: number; start_line: number; end_line: number; vector: Buffer }>;\n\n const best = new Map<string, { score: number; chunk: number; lines: string }>();\n for (const row of rows) {\n if (allowed && !allowed.has(row.path)) continue;\n const vector = decode(row.vector, dims);\n const score = cosineSimilarity(q, vector, queryNorm, vectorNorm(vector));\n const existing = best.get(row.path);\n if (!existing || score > existing.score || (score === existing.score && row.chunk < existing.chunk)) {\n best.set(row.path, { score, chunk: row.chunk, lines: `L${row.start_line}-${row.end_line}` });\n }\n }\n return [...best.entries()]\n .map(([path, value]) => ({ path, ...value }))\n .sort(compareVectorScores)\n .slice(0, fetch)\n .map(({ path, lines, score }) => ({ path, lines, similarity: asCosine(score) }));\n}\n\n// Max cosine over (target chunk, other chunk) pairs. The SQL cross-join duckdb uses measured\n// 6-8x slower here at 26k chunks (2026-08-30).\nexport async function scanSimilar(conn: Connection, dims: number, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }, nativeScope = false): Promise<VectorSimilar[]> {\n if (opts.allowed && opts.allowed.size === 0) return [];\n const targetStmt = await conn.prepare('SELECT vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk');\n const targetRows = (await targetStmt.all(path)) as Array<{ vector: Buffer }>;\n if (targetRows.length === 0) return [];\n const targets = sampleEvenly(targetRows).map((row) => {\n const vector = decode(row.vector, dims);\n return { vector, norm: vectorNorm(vector) };\n });\n\n const scope = nativeScope ? ` AND \"path\" IN (SELECT \"path\" FROM ${NATIVE_VECTOR_SCOPE_TABLE})` : '';\n const stmt = await conn.prepare(`SELECT \"path\", vector FROM embeddings WHERE vector IS NOT NULL${scope}`);\n const rows = (await stmt.all()) as Array<{ path: string; vector: Buffer }>;\n const best = new Map<string, number>();\n for (const row of rows) {\n if (row.path === path || opts.exclude.has(row.path) || (opts.allowed && !opts.allowed.has(row.path))) continue;\n const other = decode(row.vector, dims);\n const otherNorm = vectorNorm(other);\n for (const t of targets) {\n const score = cosineSimilarity(t.vector, other, t.norm, otherNorm);\n const existing = best.get(row.path);\n if (existing === undefined || score > existing) best.set(row.path, score);\n }\n }\n\n return [...best.entries()]\n .map(([p, score]) => ({ path: p, score }))\n .sort(compareVectorScores)\n .slice(0, opts.k)\n .map(({ path: p, score }) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["scanCandidates","scanSimilar","writeVectorBatch","padded","values","dims","out","Float32Array","d","Math","min","length","toBlob","v","Buffer","from","buffer","byteOffset","byteLength","decode","vector","dequantize","row","q","Int8Array","scale","vectorNorm","norm","sqrt","cosineSimilarity","a","b","aNorm","bNorm","dot","conn","rows","runBatch","map","path","chunk","qv","fetch","allowed","nativeScope","queryNorm","scope","stmt","best","score","existing","NATIVE_VECTOR_SCOPE_TABLE","prepare","all","Map","has","get","set","lines","start_line","end_line","entries","value","sort","compareVectorScores","slice","similarity","asCosine","opts","targetStmt","targetRows","targets","other","otherNorm","t","size","sampleEvenly","exclude","undefined","p","k"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/turso/vectors.ts"],"sourcesContent":["import type { Connection, VectorCandidate, VectorSimilar, VectorWriteRow } from '../types.ts';\nimport { asCosine, compareVectorScores, NATIVE_VECTOR_SCOPE_TABLE, sampleEvenly } from '../vectors.ts';\n\n// Native F32_BLOB(dims) columns, dims fixed at DDL time (open.ts, STORE_DIMS). Both scans\n// score in JS: a per-row vector_distance_cos measured 3.5-8x slower on this row engine.\n\n// A model narrower than the column zero-pads: the added dimensions contribute nothing to\n// either dot product or norm, so cosine is unchanged.\nfunction padded(values: ArrayLike<number>, dims: number): Float32Array {\n const out = new Float32Array(dims);\n for (let d = 0; d < Math.min(values.length, dims); d++) out[d] = values[d];\n return out;\n}\n\n// A bound Float32Array stores truncated bytes with no error; a Buffer view over the same\n// bytes round-trips exact (measured 0.7.2).\nfunction toBlob(v: Float32Array): Buffer {\n return Buffer.from(v.buffer, v.byteOffset, v.byteLength);\n}\n\nfunction decode(vector: Buffer, dims: number): Float32Array {\n return new Float32Array(vector.buffer, vector.byteOffset, dims);\n}\n\n// Rows arrive int8-quantized with a per-vector scale (embed/query.ts's toStore); this store\n// dequantizes to floats, as duckdb does.\nfunction dequantize(row: VectorWriteRow, dims: number): Float32Array {\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n const out = new Float32Array(dims);\n for (let d = 0; d < Math.min(q.length, dims); d++) out[d] = q[d] * row.scale;\n return out;\n}\n\nfunction vectorNorm(vector: Float32Array): number {\n let norm = 0;\n for (let d = 0; d < vector.length; d++) norm += vector[d] * vector[d];\n return Math.sqrt(norm);\n}\n\nfunction cosineSimilarity(a: Float32Array, b: Float32Array, aNorm: number, bNorm: number): number {\n let dot = 0;\n for (let d = 0; d < a.length; d++) dot += a[d] * b[d];\n return dot / (aNorm * bNorm + 1e-32);\n}\n\n// `scale` stays NULL: the column exists so the row shape matches the other stores, and\n// cosine is scale-invariant once the value is dequantized here.\nexport async function writeVectorBatch(conn: Connection, dims: number, rows: VectorWriteRow[]): Promise<void> {\n if (rows.length === 0) return;\n await conn.runBatch(\n 'UPDATE embeddings SET vector = ? WHERE \"path\" = ? AND chunk = ?',\n rows.map((row) => [toBlob(dequantize(row, dims)), row.path, row.chunk])\n );\n}\n\n// Best chunk per file by cosine. GROUP BY with MIN() cannot do this on 0.7.2: the aggregate\n// is right but the bare start_line/end_line come from an arbitrary row in the group.\nexport async function scanCandidates(conn: Connection, qv: Float32Array, dims: number, fetch: number, allowed?: Set<string>, nativeScope = false): Promise<VectorCandidate[]> {\n const q = padded(qv, dims);\n const queryNorm = vectorNorm(q);\n const scope = nativeScope ? ` AND \"path\" IN (SELECT \"path\" FROM ${NATIVE_VECTOR_SCOPE_TABLE})` : '';\n const stmt = await conn.prepare(`SELECT \"path\", chunk, start_line, end_line, vector FROM embeddings WHERE vector IS NOT NULL${scope} ORDER BY \"path\", chunk`);\n const rows = (await stmt.all()) as Array<{ path: string; chunk: number; start_line: number; end_line: number; vector: Buffer }>;\n\n const best = new Map<string, { score: number; chunk: number; lines: string }>();\n for (const row of rows) {\n if (allowed && !allowed.has(row.path)) continue;\n const vector = decode(row.vector, dims);\n const score = cosineSimilarity(q, vector, queryNorm, vectorNorm(vector));\n const existing = best.get(row.path);\n if (!existing || score > existing.score || (score === existing.score && row.chunk < existing.chunk)) {\n best.set(row.path, { score, chunk: row.chunk, lines: `L${row.start_line}-${row.end_line}` });\n }\n }\n return [...best.entries()]\n .map(([path, value]) => ({ path, ...value }))\n .sort(compareVectorScores)\n .slice(0, fetch)\n .map(({ path, lines, score }) => ({ path, lines, similarity: asCosine(score) }));\n}\n\n// Max cosine over (target chunk, other chunk) pairs. The SQL cross-join duckdb uses measured\n// 6-8x slower here at 26k chunks (2026-08-30).\nexport async function scanSimilar(conn: Connection, dims: number, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }, nativeScope = false): Promise<VectorSimilar[]> {\n if (opts.allowed && opts.allowed.size === 0) return [];\n const targetStmt = await conn.prepare('SELECT vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk');\n const targetRows = (await targetStmt.all(path)) as Array<{ vector: Buffer }>;\n if (targetRows.length === 0) return [];\n const targets = sampleEvenly(targetRows).map((row) => {\n const vector = decode(row.vector, dims);\n return { vector, norm: vectorNorm(vector) };\n });\n\n const scope = nativeScope ? ` AND \"path\" IN (SELECT \"path\" FROM ${NATIVE_VECTOR_SCOPE_TABLE})` : '';\n const stmt = await conn.prepare(`SELECT \"path\", vector FROM embeddings WHERE vector IS NOT NULL${scope}`);\n const rows = (await stmt.all()) as Array<{ path: string; vector: Buffer }>;\n const best = new Map<string, number>();\n for (const row of rows) {\n if (row.path === path || opts.exclude.has(row.path) || (opts.allowed && !opts.allowed.has(row.path))) continue;\n const other = decode(row.vector, dims);\n const otherNorm = vectorNorm(other);\n for (const t of targets) {\n const score = cosineSimilarity(t.vector, other, t.norm, otherNorm);\n const existing = best.get(row.path);\n if (existing === undefined || score > existing) best.set(row.path, score);\n }\n }\n\n return [...best.entries()]\n .map(([p, score]) => ({ path: p, score }))\n .sort(compareVectorScores)\n .slice(0, opts.k)\n .map(({ path: p, score }) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["scanCandidates","scanSimilar","writeVectorBatch","padded","values","dims","out","Float32Array","d","Math","min","length","toBlob","v","Buffer","from","buffer","byteOffset","byteLength","decode","vector","dequantize","row","q","Int8Array","scale","vectorNorm","norm","sqrt","cosineSimilarity","a","b","aNorm","bNorm","dot","conn","rows","runBatch","map","path","chunk","qv","fetch","allowed","nativeScope","queryNorm","scope","stmt","best","score","existing","NATIVE_VECTOR_SCOPE_TABLE","prepare","all","Map","has","get","set","lines","start_line","end_line","entries","value","sort","compareVectorScores","slice","similarity","asCosine","opts","targetStmt","targetRows","targets","other","otherNorm","t","size","sampleEvenly","exclude","undefined","p","k"],"mappings":"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"}
@@ -50,6 +50,8 @@ export interface OpenDialect<Handle> {
50
50
  ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
51
51
  prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
52
52
  setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;
53
+ engineIdentity?(): Promise<string>;
54
+ checkEngine?(): Promise<void>;
53
55
  prepareLexical?(conn: Connection): Promise<void>;
54
56
  assertLexicalReady?(conn: Connection): Promise<void>;
55
57
  createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: {
@@ -50,6 +50,8 @@ export interface OpenDialect<Handle> {
50
50
  ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
51
51
  prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
52
52
  setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;
53
+ engineIdentity?(): Promise<string>;
54
+ checkEngine?(): Promise<void>;
53
55
  prepareLexical?(conn: Connection): Promise<void>;
54
56
  assertLexicalReady?(conn: Connection): Promise<void>;
55
57
  createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/types.ts"],"sourcesContent":["// The backing-store interface: a minimal portable statement surface (exec/prepare), plus\n// dedicated interfaces exactly where engines diverge (lexical index, vector scan, raw sql).\n\nimport type { Config, ResolvedConfig, StoreName } from '../config/index.ts';\nimport type { ReconcileDelta } from '../features/types.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\n\n// 'vectors': the store's VectorStore is functionally implemented, not present-but-inert; checked\n// at open (index.ts) before a config with an \"embed\" block is allowed onto a store lacking it.\n// 'segment': the engine can run segment() (Intl.Segmenter grapheme clustering) as a SQL function;\n// turso's client cannot register it and has no rewrite for it, unlike has()/basename(), which now\n// work everywhere. An array, not a bare union, so a runtime check reads the same list the type does.\nexport const CAPABILITY_NAMES = ['vectors', 'segment'] as const;\nexport type Capability = (typeof CAPABILITY_NAMES)[number];\n\nexport interface RunResult {\n changes: number | bigint;\n lastInsertRowid: number | bigint;\n}\n\n// A prepared statement's async surface: every supported engine crosses a real async boundary\n// for a query (DuckDB has no synchronous client), so run/get/all return Promises.\nexport interface Statement {\n run(...params: unknown[]): Promise<RunResult>;\n get(...params: unknown[]): Promise<unknown>;\n all(...params: unknown[]): Promise<unknown[]>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n columns(): Array<{ name: string }>;\n setReadBigInts(enabled: boolean): void;\n}\n\n// Connection surface feature-owned SQL runs against inside a store's hot loops (schema,\n// reconcile), portable so a feature never imports an engine-specific client type.\nexport interface Connection {\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Bulk-inserts rows that cannot conflict, through a path that binds no per-value parameters.\n // Optional: a store that has nothing faster than its own INSERT omits it and callers fall back\n // (appendRows in shared.ts). `columns` names the values each row carries; a table column the\n // caller does not write takes its default.\n appendRows?(table: string, columns: string[], rows: unknown[][]): Promise<void>;\n}\n\n// One reconcile algorithm (src/store/reconcile.ts), parameterised per engine. reconcileContent is\n// the load-bearing member: it lands every content-table change and owns its own multi-step\n// strategy (sqlite FTS5 incremental, duckdb combined delete/insert, turso incremental-or-rebuild).\nexport interface ReconcileDialect {\n // BEGIN mode for the whole reconcile: sqlite/turso 'BEGIN IMMEDIATE', duckdb 'BEGIN'.\n beginMode(): string;\n // Throws SenseError('COLUMN_LIMIT', ...) past this store's own column ceiling and reasoning.\n checkColumnLimit(count: number): void;\n // Adds `names` to frontmatter, already filtered to columns this connection doesn't have yet.\n // sqlite/turso loop (their ADD COLUMN is metadata-only); duckdb issues one statement per call.\n addColumns(conn: Connection, names: string[]): Promise<void>;\n // Replaces this engine's existing-row frontmatter upsert, inside the caller's\n // publication transaction. Preserve non-writable columns and repair missing rows.\n updateFrontmatter?(conn: Connection, writableColumns: string[], rows: unknown[][]): Promise<void>;\n // Reconciles searchable content for `touched` paths and parsed `docs`; `delta` carries the tree\n // state a strategy may need. Must not open its own transaction, and must not return before its own\n // multi-step strategy (e.g. turso's DROP/rebuild) completes.\n reconcileContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, cfg: Config): Promise<void>;\n // Records this reconcile's write-transaction duration. sqlite/turso use it for open()'s derived\n // busy_timeout; duckdb has no such PRAGMA and omits it.\n recordDuration?(conn: Connection, ms: number): Promise<void>;\n}\n\n// One open algorithm (src/store/open.ts), parameterised per engine. `Handle` is whatever this\n// store needs to close the connection and construct its Store (sqlite: {db}; duckdb:\n// {instance, duckdb}; turso: db) -- opaque to the shared orchestration, threaded through unchanged.\nexport interface OpenDialect<Handle> {\n // Cache filename under STATE_DIR, e.g. 'cache.db'.\n filename: string;\n // Cache shape version, independent of the config's own `version`; bumping it rebuilds an existing tree.\n schemaVersion: string;\n reconcileDialect: ReconcileDialect;\n // Opens the physical connection and applies pragmas due before any SQL runs (sqlite: busy_timeout\n // + WAL; turso: connect-time timeout; duckdb: none).\n connect(dbPath: string, cfg: ResolvedConfig, options: { existingOnly: boolean; observational: boolean }): Promise<{ handle: Handle; conn: Connection }>;\n // Releases the handle, for both the rebuild-and-reopen branch and error cleanup on this attempt.\n close(handle: Handle, options?: { observational?: boolean }): Promise<void>;\n // True when connect() failed because another process holds the cache file, which the orchestration\n // retries. Each engine words it differently, so the match is the dialect's own (`native-not-emulated`).\n // Absent for sqlite, whose file lock is shared and whose concurrent-open failure is a different defect.\n // Matching on message text is forced, not chosen: measured 2026-09-02, duckdb throws a plain Error\n // whose only own properties are stack and message, and turso sets code to the constant\n // 'GenericFailure' on every failure alike with rawCode undefined. Neither exposes anything a\n // predicate could switch on, so each dialect pins its engine's wordings and unit-tests them.\n isLocked?(err: Error): boolean;\n // Minimal, configuration-independent schema needed before the builder can capture a planning\n // snapshot. This bootstrap may commit independently because no feature generation is published.\n ensureCoreSchema(handle: Handle, conn: Connection): Promise<void>;\n // Configuration-dependent persistent DDL. The builder invokes this only inside its fenced write\n // transaction, so a failed feature enable cannot leave catalog changes under the old signature.\n ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Connection-local preparation with no durable cache effect (DuckDB's TEMP vector stage).\n prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Installs the derived busy_timeout PRAGMA right before reconcile (sqlite/turso); absent for\n // duckdb, which has no such PRAGMA.\n setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;\n // Persistent lexical preparation belongs to build, while no-build calls only the readiness\n // check. SQLite and Turso maintain lexical state during reconcile and omit both hooks.\n prepareLexical?(conn: Connection): Promise<void>;\n assertLexicalReady?(conn: Connection): Promise<void>;\n createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: { observational: boolean }): Store;\n}\n\nexport interface FieldStat {\n field: string;\n coverage: number;\n type: string;\n}\n\nexport interface DocumentStore {\n // Frontmatter column names (including internal ones; callers filter).\n columns(): Promise<string[]>;\n // Per-column coverage (non-null count) and observed type set, aggregated in one SQL query,\n // never one row per note. `scopeWhere` is a caller-built WHERE fragment, per LexicalQueryOptions.\n fieldStats(columns: string[], scopeWhere: string): Promise<FieldStat[]>;\n}\n\n// A ranked match, nothing more. Snippets are cut from the document above the store\n// (commands/search.ts), identically for every engine, so no store carries them.\nexport interface LexicalHit {\n path: string;\n}\n\nexport interface LexicalQueryOptions {\n whereJoin: string;\n whereCond: string;\n scopeCond: string;\n limit: number;\n}\n\nexport interface LexicalIndex {\n // Ranked word-match query with snippet, scoped by the caller-built SQL fragments (the same\n // fragments narrowByWhere/materializeScope produce elsewhere).\n query(terms: string, opts: LexicalQueryOptions): Promise<LexicalHit[]>;\n}\n\nexport interface VectorCandidate {\n path: string;\n lines: string;\n similarity: number;\n}\n\nexport interface VectorSimilar {\n path: string;\n similarity: number;\n}\n\nexport interface VectorWriteRow {\n path: string;\n chunk: number;\n scale: number;\n vector: Buffer;\n}\n\nexport interface VectorStore {\n // Rows whose vector is NULL: never embedded, or added since.\n pending(): Promise<Array<{ path: string; chunk: number }>>;\n // One batch write per call (never per row) so a provider's embedding batch stays inside a\n // single store method.\n writeVectors(rows: VectorWriteRow[]): Promise<void>;\n candidates(queryVector: Float32Array, storeDims: number, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;\n similar(path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]>;\n hasVector(path: string): Promise<boolean>;\n}\n\n// The `sense sql` passthrough: string in, streamed rows out. Each store registers the same\n// sense-supplied functions and applies its own read-bigints/error-translation behavior.\nexport interface RawStatement {\n columns(): Array<{ name: string }>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n}\n\nexport interface SqlSession {\n prepare(sql: string): Promise<RawStatement>;\n}\n\nexport interface Store {\n readonly name: StoreName;\n readonly capabilities: ReadonlySet<Capability>;\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n // One crossing, N rows: every external write loop (search's candidate insert, graph ring's\n // temp-table writes) goes through this instead of looping over `run()`. Same contract as Connection.runBatch.\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Pins one snapshot across multi-statement reads; `map` and `peek` use it. A network-bound\n // write (search's embedding top-up) must stay outside it. Nesting joins the enclosing transaction.\n transaction<T>(fn: () => Promise<T>): Promise<T>;\n docs: DocumentStore;\n lexical: LexicalIndex;\n vectors: VectorStore;\n raw: SqlSession;\n // Engine-level facts for `sense status` (e.g. a derived busy_timeout PRAGMA reading); each\n // store owns what it reports and how it is worded. Empty when there is nothing to report.\n engineStatus(): Promise<Record<string, string>>;\n close(): Promise<void>;\n}\n"],"names":["CAPABILITY_NAMES"],"mappings":"AAAA,yFAAyF;AACzF,4FAA4F;;;;;+BAW/EA;;;eAAAA;;;AAAN,IAAMA,mBAAmB;IAAC;IAAW;CAAU"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/types.ts"],"sourcesContent":["// The backing-store interface: a minimal portable statement surface (exec/prepare), plus\n// dedicated interfaces exactly where engines diverge (lexical index, vector scan, raw sql).\n\nimport type { Config, ResolvedConfig, StoreName } from '../config/index.ts';\nimport type { ReconcileDelta } from '../features/types.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\n\n// 'vectors': the store's VectorStore is functionally implemented, not present-but-inert; checked\n// at open (index.ts) before a config with an \"embed\" block is allowed onto a store lacking it.\n// 'segment': the engine can run segment() (Intl.Segmenter grapheme clustering) as a SQL function;\n// turso's client cannot register it and has no rewrite for it, unlike has()/basename(), which now\n// work everywhere. An array, not a bare union, so a runtime check reads the same list the type does.\nexport const CAPABILITY_NAMES = ['vectors', 'segment'] as const;\nexport type Capability = (typeof CAPABILITY_NAMES)[number];\n\nexport interface RunResult {\n changes: number | bigint;\n lastInsertRowid: number | bigint;\n}\n\n// A prepared statement's async surface: every supported engine crosses a real async boundary\n// for a query (DuckDB has no synchronous client), so run/get/all return Promises.\nexport interface Statement {\n run(...params: unknown[]): Promise<RunResult>;\n get(...params: unknown[]): Promise<unknown>;\n all(...params: unknown[]): Promise<unknown[]>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n columns(): Array<{ name: string }>;\n setReadBigInts(enabled: boolean): void;\n}\n\n// Connection surface feature-owned SQL runs against inside a store's hot loops (schema,\n// reconcile), portable so a feature never imports an engine-specific client type.\nexport interface Connection {\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Bulk-inserts rows that cannot conflict, through a path that binds no per-value parameters.\n // Optional: a store that has nothing faster than its own INSERT omits it and callers fall back\n // (appendRows in shared.ts). `columns` names the values each row carries; a table column the\n // caller does not write takes its default.\n appendRows?(table: string, columns: string[], rows: unknown[][]): Promise<void>;\n}\n\n// One reconcile algorithm (src/store/reconcile.ts), parameterised per engine. reconcileContent is\n// the load-bearing member: it lands every content-table change and owns its own multi-step\n// strategy (sqlite FTS5 incremental, duckdb combined delete/insert, turso incremental-or-rebuild).\nexport interface ReconcileDialect {\n // BEGIN mode for the whole reconcile: sqlite/turso 'BEGIN IMMEDIATE', duckdb 'BEGIN'.\n beginMode(): string;\n // Throws SenseError('COLUMN_LIMIT', ...) past this store's own column ceiling and reasoning.\n checkColumnLimit(count: number): void;\n // Adds `names` to frontmatter, already filtered to columns this connection doesn't have yet.\n // sqlite/turso loop (their ADD COLUMN is metadata-only); duckdb issues one statement per call.\n addColumns(conn: Connection, names: string[]): Promise<void>;\n // Replaces this engine's existing-row frontmatter upsert, inside the caller's\n // publication transaction. Preserve non-writable columns and repair missing rows.\n updateFrontmatter?(conn: Connection, writableColumns: string[], rows: unknown[][]): Promise<void>;\n // Reconciles searchable content for `touched` paths and parsed `docs`; `delta` carries the tree\n // state a strategy may need. Must not open its own transaction, and must not return before its own\n // multi-step strategy (e.g. turso's DROP/rebuild) completes.\n reconcileContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, cfg: Config): Promise<void>;\n // Records this reconcile's write-transaction duration. sqlite/turso use it for open()'s derived\n // busy_timeout; duckdb has no such PRAGMA and omits it.\n recordDuration?(conn: Connection, ms: number): Promise<void>;\n}\n\n// One open algorithm (src/store/open.ts), parameterised per engine. `Handle` is whatever this\n// store needs to close the connection and construct its Store (sqlite: {db}; duckdb:\n// {instance, duckdb}; turso: db) -- opaque to the shared orchestration, threaded through unchanged.\nexport interface OpenDialect<Handle> {\n // Cache filename under STATE_DIR, e.g. 'cache.db'.\n filename: string;\n // Cache shape version, independent of the config's own `version`; bumping it rebuilds an existing tree.\n schemaVersion: string;\n reconcileDialect: ReconcileDialect;\n // Opens the physical connection and applies pragmas due before any SQL runs (sqlite: busy_timeout\n // + WAL; turso: connect-time timeout; duckdb: none).\n connect(dbPath: string, cfg: ResolvedConfig, options: { existingOnly: boolean; observational: boolean }): Promise<{ handle: Handle; conn: Connection }>;\n // Releases the handle, for both the rebuild-and-reopen branch and error cleanup on this attempt.\n close(handle: Handle, options?: { observational?: boolean }): Promise<void>;\n // True when connect() failed because another process holds the cache file, which the orchestration\n // retries. Each engine words it differently, so the match is the dialect's own (`native-not-emulated`).\n // Absent for sqlite, whose file lock is shared and whose concurrent-open failure is a different defect.\n // Matching on message text is forced, not chosen: measured 2026-09-02, duckdb throws a plain Error\n // whose only own properties are stack and message, and turso sets code to the constant\n // 'GenericFailure' on every failure alike with rawCode undefined. Neither exposes anything a\n // predicate could switch on, so each dialect pins its engine's wordings and unit-tests them.\n isLocked?(err: Error): boolean;\n // Minimal, configuration-independent schema needed before the builder can capture a planning\n // snapshot. This bootstrap may commit independently because no feature generation is published.\n ensureCoreSchema(handle: Handle, conn: Connection): Promise<void>;\n // Configuration-dependent persistent DDL. The builder invokes this only inside its fenced write\n // transaction, so a failed feature enable cannot leave catalog changes under the old signature.\n ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Connection-local preparation with no durable cache effect (DuckDB's TEMP vector stage).\n prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Installs the derived busy_timeout PRAGMA right before reconcile (sqlite/turso); absent for\n // duckdb, which has no such PRAGMA.\n setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;\n // The engine that builds this store's derived index (store/engine.ts): the npm package for duckdb/turso, Node's\n // bundled SQLite for sqlite. A recorded identity that differs rebuilds the cache; checkEngine runs before any build under a new one.\n engineIdentity?(): Promise<string>;\n checkEngine?(): Promise<void>;\n // Persistent lexical preparation belongs to build, while no-build calls only the readiness\n // check. SQLite and Turso maintain lexical state during reconcile and omit both hooks.\n prepareLexical?(conn: Connection): Promise<void>;\n assertLexicalReady?(conn: Connection): Promise<void>;\n createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: { observational: boolean }): Store;\n}\n\nexport interface FieldStat {\n field: string;\n coverage: number;\n type: string;\n}\n\nexport interface DocumentStore {\n // Frontmatter column names (including internal ones; callers filter).\n columns(): Promise<string[]>;\n // Per-column coverage (non-null count) and observed type set, aggregated in one SQL query,\n // never one row per note. `scopeWhere` is a caller-built WHERE fragment, per LexicalQueryOptions.\n fieldStats(columns: string[], scopeWhere: string): Promise<FieldStat[]>;\n}\n\n// A ranked match, nothing more. Snippets are cut from the document above the store\n// (commands/search.ts), identically for every engine, so no store carries them.\nexport interface LexicalHit {\n path: string;\n}\n\nexport interface LexicalQueryOptions {\n whereJoin: string;\n whereCond: string;\n scopeCond: string;\n limit: number;\n}\n\nexport interface LexicalIndex {\n // Ranked word-match query with snippet, scoped by the caller-built SQL fragments (the same\n // fragments narrowByWhere/materializeScope produce elsewhere).\n query(terms: string, opts: LexicalQueryOptions): Promise<LexicalHit[]>;\n}\n\nexport interface VectorCandidate {\n path: string;\n lines: string;\n similarity: number;\n}\n\nexport interface VectorSimilar {\n path: string;\n similarity: number;\n}\n\nexport interface VectorWriteRow {\n path: string;\n chunk: number;\n scale: number;\n vector: Buffer;\n}\n\nexport interface VectorStore {\n // Rows whose vector is NULL: never embedded, or added since.\n pending(): Promise<Array<{ path: string; chunk: number }>>;\n // One batch write per call (never per row) so a provider's embedding batch stays inside a\n // single store method.\n writeVectors(rows: VectorWriteRow[]): Promise<void>;\n candidates(queryVector: Float32Array, storeDims: number, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;\n similar(path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]>;\n hasVector(path: string): Promise<boolean>;\n}\n\n// The `sense sql` passthrough: string in, streamed rows out. Each store registers the same\n// sense-supplied functions and applies its own read-bigints/error-translation behavior.\nexport interface RawStatement {\n columns(): Array<{ name: string }>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n}\n\nexport interface SqlSession {\n prepare(sql: string): Promise<RawStatement>;\n}\n\nexport interface Store {\n readonly name: StoreName;\n readonly capabilities: ReadonlySet<Capability>;\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n // One crossing, N rows: every external write loop (search's candidate insert, graph ring's\n // temp-table writes) goes through this instead of looping over `run()`. Same contract as Connection.runBatch.\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Pins one snapshot across multi-statement reads; `map` and `peek` use it. A network-bound\n // write (search's embedding top-up) must stay outside it. Nesting joins the enclosing transaction.\n transaction<T>(fn: () => Promise<T>): Promise<T>;\n docs: DocumentStore;\n lexical: LexicalIndex;\n vectors: VectorStore;\n raw: SqlSession;\n // Engine-level facts for `sense status` (e.g. a derived busy_timeout PRAGMA reading); each\n // store owns what it reports and how it is worded. Empty when there is nothing to report.\n engineStatus(): Promise<Record<string, string>>;\n close(): Promise<void>;\n}\n"],"names":["CAPABILITY_NAMES"],"mappings":"AAAA,yFAAyF;AACzF,4FAA4F;;;;;+BAW/EA;;;eAAAA;;;AAAN,IAAMA,mBAAmB;IAAC;IAAW;CAAU"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/vectors.ts"],"sourcesContent":["import type { Connection, VectorStore } from './types.ts';\n\nexport const NATIVE_VECTOR_SCOPE_TABLE = '_vector_scope';\n\ntype NativeVectorScopePhase = 'materializing' | 'active' | 'cleaning';\n\ninterface NativeVectorScopeState {\n token: object;\n allowed: Set<string>;\n phase: NativeVectorScopePhase;\n}\n\ninterface NativeVectorScopeOwnerState {\n vectors?: VectorStore;\n scope?: NativeVectorScopeState;\n}\n\ninterface NativeVectorScopeOwner {\n bind(vectors: VectorStore): void;\n activeFor(allowed: Set<string> | undefined): boolean;\n}\n\ninterface NativeVectorScopeReservation {\n activate(): void;\n deactivate(): void;\n invalidate(): void;\n}\n\nconst nativeVectorScopeOwners = new WeakMap<VectorStore, NativeVectorScopeOwnerState>();\n\n// One owner is constructed beside one native store connection, then bound to that connection's\n// VectorStore wrapper. The adapter asks its own owner for a private boolean; no Set or reservation\n// can activate another Store, even when a caller reuses the same Set object across both.\nexport function createNativeVectorScopeOwner(): NativeVectorScopeOwner {\n const owner: NativeVectorScopeOwnerState = {};\n return {\n bind(vectors) {\n if (owner.vectors) throw new Error('native vector scope owner is already bound');\n if (nativeVectorScopeOwners.has(vectors)) throw new Error('VectorStore is already bound to a native vector scope owner');\n owner.vectors = vectors;\n nativeVectorScopeOwners.set(vectors, owner);\n },\n activeFor(allowed) {\n return allowed !== undefined && owner.scope?.phase === 'active' && owner.scope.allowed === allowed;\n },\n };\n}\n\nexport function reserveNativeVectorScope(vectors: VectorStore, allowed: Set<string>): NativeVectorScopeReservation | null {\n const owner = nativeVectorScopeOwners.get(vectors);\n if (!owner) return null;\n if (owner.scope) throw new Error('native vector scope operation is already active for this Store');\n\n const token = {};\n owner.scope = { token, allowed, phase: 'materializing' };\n let valid = true;\n\n const current = (): NativeVectorScopeState => {\n if (!valid || owner.scope?.token !== token) throw new Error('native vector scope reservation is no longer valid');\n return owner.scope;\n };\n\n return {\n activate() {\n const scope = current();\n if (scope.phase !== 'materializing') throw new Error(`native vector scope cannot activate from ${scope.phase}`);\n scope.phase = 'active';\n },\n deactivate() {\n const scope = current();\n if (scope.phase === 'cleaning') throw new Error('native vector scope cleanup already started');\n scope.phase = 'cleaning';\n },\n invalidate() {\n if (!valid) return;\n if (owner.scope?.token === token) owner.scope = undefined;\n valid = false;\n },\n };\n}\n\n// Seed chunks that participate in a `related` scan. Cost is target_chunks x stored_chunks, so a\n// heading-dense seed multiplies a full-corpus scan (12.7s at 201 chunks/note unsampled).\nexport const TARGET_CHUNK_CAP = 16;\n\n// Evenly samples down to at most `cap` rows, so late sections of a long note still get a vote\n// instead of being cut off by a fixed prefix.\nexport function sampleEvenly<T>(rows: T[], cap: number = TARGET_CHUNK_CAP): T[] {\n const step = Math.max(1, Math.ceil(rows.length / cap));\n return rows.filter((_, i) => i % step === 0);\n}\n\n// Every store computes a real cosine; clamp float error at the ends and round for the public score.\nexport function asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Rank by the computed cosine before rounding for display. Bytewise path order resolves only an\n// exact score tie, so two distinguishable similarities never cross a caller's result boundary.\nexport function compareVectorScores(a: { path: string; score: number }, b: { path: string; score: number }): number {\n const byScore = b.score - a.score;\n if (byScore !== 0) return byScore;\n return Buffer.compare(Buffer.from(a.path), Buffer.from(b.path));\n}\n\nexport async function pendingRows(conn: Connection): Promise<Array<{ path: string; chunk: number }>> {\n const stmt = await conn.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk');\n return (await stmt.all()) as Array<{ path: string; chunk: number }>;\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport async function hasVectorRow(conn: Connection, path: string): Promise<boolean> {\n const stmt = await conn.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1');\n const row = (await stmt.get(path)) as { ok: number } | undefined;\n return row !== undefined;\n}\n"],"names":["NATIVE_VECTOR_SCOPE_TABLE","TARGET_CHUNK_CAP","asCosine","compareVectorScores","createNativeVectorScopeOwner","hasVectorRow","pendingRows","reserveNativeVectorScope","sampleEvenly","nativeVectorScopeOwners","WeakMap","owner","bind","vectors","Error","has","set","activeFor","allowed","undefined","scope","phase","get","token","valid","current","activate","deactivate","invalidate","rows","cap","step","Math","max","ceil","length","filter","_","i","score","round","min","a","b","byScore","Buffer","compare","from","path","conn","stmt","prepare","all","row"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/vectors.ts"],"sourcesContent":["import type { Connection, VectorStore } from './types.ts';\n\nexport const NATIVE_VECTOR_SCOPE_TABLE = '_vector_scope';\n\ntype NativeVectorScopePhase = 'materializing' | 'active' | 'cleaning';\n\ninterface NativeVectorScopeState {\n token: object;\n allowed: Set<string>;\n phase: NativeVectorScopePhase;\n}\n\ninterface NativeVectorScopeOwnerState {\n vectors?: VectorStore;\n scope?: NativeVectorScopeState;\n}\n\ninterface NativeVectorScopeOwner {\n bind(vectors: VectorStore): void;\n activeFor(allowed: Set<string> | undefined): boolean;\n}\n\ninterface NativeVectorScopeReservation {\n activate(): void;\n deactivate(): void;\n invalidate(): void;\n}\n\nconst nativeVectorScopeOwners = new WeakMap<VectorStore, NativeVectorScopeOwnerState>();\n\n// One owner is constructed beside one native store connection, then bound to that connection's\n// VectorStore wrapper. The adapter asks its own owner for a private boolean; no Set or reservation\n// can activate another Store, even when a caller reuses the same Set object across both.\nexport function createNativeVectorScopeOwner(): NativeVectorScopeOwner {\n const owner: NativeVectorScopeOwnerState = {};\n return {\n bind(vectors) {\n if (owner.vectors) throw new Error('native vector scope owner is already bound');\n if (nativeVectorScopeOwners.has(vectors)) throw new Error('VectorStore is already bound to a native vector scope owner');\n owner.vectors = vectors;\n nativeVectorScopeOwners.set(vectors, owner);\n },\n activeFor(allowed) {\n return allowed !== undefined && owner.scope?.phase === 'active' && owner.scope.allowed === allowed;\n },\n };\n}\n\nexport function reserveNativeVectorScope(vectors: VectorStore, allowed: Set<string>): NativeVectorScopeReservation | null {\n const owner = nativeVectorScopeOwners.get(vectors);\n if (!owner) return null;\n if (owner.scope) throw new Error('native vector scope operation is already active for this Store');\n\n const token = {};\n owner.scope = { token, allowed, phase: 'materializing' };\n let valid = true;\n\n const current = (): NativeVectorScopeState => {\n if (!valid || owner.scope?.token !== token) throw new Error('native vector scope reservation is no longer valid');\n return owner.scope;\n };\n\n return {\n activate() {\n const scope = current();\n if (scope.phase !== 'materializing') throw new Error(`native vector scope cannot activate from ${scope.phase}`);\n scope.phase = 'active';\n },\n deactivate() {\n const scope = current();\n if (scope.phase === 'cleaning') throw new Error('native vector scope cleanup already started');\n scope.phase = 'cleaning';\n },\n invalidate() {\n if (!valid) return;\n if (owner.scope?.token === token) owner.scope = undefined;\n valid = false;\n },\n };\n}\n\n// Seed chunks that participate in a `related` scan. Cost is target_chunks x stored_chunks, so a\n// heading-dense seed multiplies a full-corpus scan (12.7s at 201 chunks/note unsampled).\nexport const TARGET_CHUNK_CAP = 16;\n\n// Evenly samples down to at most `cap` rows, so late sections of a long note still get a vote\n// instead of being cut off by a fixed prefix.\nexport function sampleEvenly<T>(rows: T[], cap: number = TARGET_CHUNK_CAP): T[] {\n const step = Math.max(1, Math.ceil(rows.length / cap));\n return rows.filter((_, i) => i % step === 0);\n}\n\n// Every store computes a real cosine; clamp float error at the ends and round for the public score.\nexport function asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Rank by the computed cosine before rounding for display. Bytewise path order resolves only an\n// exact score tie, so two distinguishable similarities never cross a caller's result boundary.\nexport function compareVectorScores(a: { path: string; score: number }, b: { path: string; score: number }): number {\n const byScore = b.score - a.score;\n if (byScore !== 0) return byScore;\n return Buffer.compare(Buffer.from(a.path), Buffer.from(b.path));\n}\n\nexport async function pendingRows(conn: Connection): Promise<Array<{ path: string; chunk: number }>> {\n const stmt = await conn.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk');\n return (await stmt.all()) as Array<{ path: string; chunk: number }>;\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport async function hasVectorRow(conn: Connection, path: string): Promise<boolean> {\n const stmt = await conn.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1');\n const row = (await stmt.get(path)) as { ok: number } | undefined;\n return row !== undefined;\n}\n"],"names":["NATIVE_VECTOR_SCOPE_TABLE","TARGET_CHUNK_CAP","asCosine","compareVectorScores","createNativeVectorScopeOwner","hasVectorRow","pendingRows","reserveNativeVectorScope","sampleEvenly","nativeVectorScopeOwners","WeakMap","owner","bind","vectors","Error","has","set","activeFor","allowed","undefined","scope","phase","get","token","valid","current","activate","deactivate","invalidate","rows","cap","step","Math","max","ceil","length","filter","_","i","score","round","min","a","b","byScore","Buffer","compare","from","path","conn","stmt","prepare","all","row"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/text/segment.ts"],"sourcesContent":["import { stemmer } from 'stemmer';\n\n// Word boundaries FTS5's tokenizers cannot find on their own. Contract: seg(query) must appear in\n// seg(document) for any substring query; word mode is context-dependent and breaks that, so grapheme mode (UAX #29) is used.\n\n// Scripts written without word spaces: a closed set of writing systems.\n// Script_Extensions, not Script: the katakana long-vowel mark ー is Script=Common.\nexport const UNSPACED_SCRIPTS = '\\\\p{scx=Han}\\\\p{scx=Hiragana}\\\\p{scx=Katakana}\\\\p{scx=Thai}\\\\p{scx=Khmer}\\\\p{scx=Lao}\\\\p{scx=Myanmar}';\n// A run is script BASE characters with their combining marks attached; a bare mark after a\n// Latin letter (decomposed é) never starts one.\nconst RUN_BODY = `(?:[${UNSPACED_SCRIPTS}]\\\\p{M}*)+`;\nconst RUN = new RegExp(`(${RUN_BODY})`, 'gu');\nconst HAS_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');\n\n// Whether text holds a script that marks no word boundaries, the predicate every store's\n// sidecar population and query split turns on.\nexport function hasUnspacedRun(text: string): boolean {\n return HAS_RUN.test(text);\n}\n\nexport interface SearchToken {\n text: string;\n start: number;\n end: number;\n unspaced: boolean;\n}\n\n// Search tokens share the native separator contract: letters, marks, and numbers form tokens;\n// underscore, apostrophe, and hyphen separate them. Unspaced-script runs stay whole so callers\n// can apply substring semantics, while adjacent Latin text still gets its own token.\nexport function searchTokens(text: string): SearchToken[] {\n const tokens: SearchToken[] = [];\n for (const match of text.matchAll(/[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N}]*/gu)) {\n const start = match.index;\n const end = start + match[0].length;\n let cursor = start;\n for (const run of text.slice(start, end).matchAll(RUN)) {\n const runStart = start + run.index;\n if (runStart > cursor) tokens.push({ text: text.slice(cursor, runStart), start: cursor, end: runStart, unspaced: false });\n const runEnd = runStart + run[0].length;\n tokens.push({ text: run[0], start: runStart, end: runEnd, unspaced: true });\n cursor = runEnd;\n }\n if (cursor < end) tokens.push({ text: text.slice(cursor, end), start: cursor, end, unspaced: false });\n }\n return tokens;\n}\n\n// Phrase membership is checked after native ranking. A CJK token matches inside one authored\n// run; Latin tokens compare folded Porter stems. Each call scans one field once per candidate.\nexport function matchesSearchPhrase(text: string, phrase: SearchToken[]): boolean {\n const haystack = searchTokens(text);\n if (phrase.length === 0 || haystack.length < phrase.length) return false;\n for (let start = 0; start <= haystack.length - phrase.length; start++) {\n let matches = true;\n for (let i = 0; i < phrase.length; i++) {\n const query = phrase[i];\n const authored = haystack[start + i];\n if (query.unspaced) {\n if (!authored.unspaced || !authored.text.includes(query.text)) matches = false;\n } else if (authored.unspaced || stemmer(foldForSearch(authored.text)) !== stemmer(foldForSearch(query.text))) {\n matches = false;\n }\n if (!matches) break;\n }\n if (matches) return true;\n }\n return false;\n}\n\n// Character spans of unspaced-script runs in text, the same boundary the index segments on --\n// the snippet marker uses this to keep substring semantics inside a run and word/stem matching outside it.\nexport function unspacedRuns(text: string): Array<{ start: number; end: number }> {\n return Array.from(text.matchAll(RUN), (m) => ({ start: m.index, end: m.index + m[0].length }));\n}\n// Grapheme clusters, ECMA-402/UAX #29: base char plus its marks, ZWJ sequences, Hangul jamo.\n// Built on first use and kept: construction is 6.6 ms, and a tree with no unspaced-script run\n// never segments at all, so no command pays it at module load.\nlet graphemeSegmenter: Intl.Segmenter | undefined;\n// unicode61 drops Unicode punctuation as a separator. Some of it (。、「」) has Script_Extensions\n// into an unspaced script, so RUN keeps it -- a grapheme matching this becomes a split point.\nconst PUNCTUATION = /\\p{P}/u;\n// Token barrier between separate runs (or a punctuation split within one) so their graphemes\n// are never phrase-adjacent. U+A7F7: a letter (so FTS5 keeps it as a token) no one types.\nconst BARRIER = 'ꟷ';\n\nfunction graphemes(run: string): string[] {\n graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n return Array.from(graphemeSegmenter.segment(run), (s) => s.segment);\n}\n\n// A run's graphemes, cut into punctuation-free groups at every punctuation grapheme (dropped,\n// matching unicode61). The one place punctuation is classified, so index and query agree.\nfunction splitOnPunctuation(run: string): string[][] {\n const groups: string[][] = [[]];\n for (const g of graphemes(run)) {\n if (PUNCTUATION.test(g)) groups.push([]);\n else groups[groups.length - 1].push(g);\n }\n return groups.filter((g) => g.length > 0);\n}\n\n// Index side: '' when the field has no unspaced-script run. Otherwise each run explodes into\n// its graphemes, barrier-delimited from its neighbors and from a punctuation split within itself.\nexport function segmentField(text: string): string {\n if (!HAS_RUN.test(text)) return '';\n const out = text.replace(RUN, (run) => {\n const body = splitOnPunctuation(run)\n .map((g) => g.join(' '))\n .join(` ${BARRIER} `);\n return ` ${BARRIER} ${body} ${BARRIER} `;\n });\n return out.replace(/\\s+/g, ' ').trim();\n}\n\n// A `title:`/`summary:`/`text:` qualifier directly before a run that is about to become a\n// quoted grapheme phrase, so the rewrite can retarget it at the matching `_seg` column.\nconst QUALIFIER = /(^|[\\s(])(-?)(title|summary|text)\\s*:\\s*$/;\n\n// Raw title/summary/text drop punctuation as unicode61's token separator, so `数数` and `数。数`\n// falsely match there; only the barriered `_seg` columns are safe from it, hence this fallback target.\nconst SIDECAR_COLUMNS = '{title_seg summary_seg text_seg}:';\nconst AUTHORED_COLUMNS = '{title summary text}:';\nconst QUOTED_RUN_TOKEN = new RegExp(`\"(${RUN_BODY})\"`, 'gu');\n\ninterface PositionalSpan {\n start: number;\n end: number;\n qualified: boolean;\n}\n\nconst FTS_WORD = /[\\p{L}\\p{N}_]/u;\n\nfunction isFtsWord(char: string | undefined): boolean {\n return char !== undefined && FTS_WORD.test(char);\n}\n\nfunction quoteEnd(text: string, start: number): number {\n for (let i = start + 1; i < text.length; i++) {\n if (text[i] !== '\"') continue;\n if (text[i + 1] === '\"') {\n i++;\n continue;\n }\n return i + 1;\n }\n return text.length;\n}\n\nfunction nearEnd(text: string, open: number): number {\n let depth = 1;\n let quoted = false;\n for (let i = open + 1; i < text.length; i++) {\n const char = text[i];\n if (char === '\"') {\n if (quoted && text[i + 1] === '\"') {\n i++;\n } else {\n quoted = !quoted;\n }\n } else if (!quoted && char === '(') {\n depth++;\n } else if (!quoted && char === ')' && --depth === 0) {\n return i + 1;\n }\n }\n return text.length;\n}\n\n// Lexical routing only: SQLite remains responsible for validating the expression. The scanner\n// recognizes NEAR groups and the one FTS string governed by ^, while quoted text shields both.\nfunction positionalSpans(terms: string): PositionalSpan[] {\n const spans: PositionalSpan[] = [];\n for (let i = 0; i < terms.length; i++) {\n if (terms[i] === '\"') {\n i = quoteEnd(terms, i) - 1;\n continue;\n }\n if (terms.startsWith('NEAR', i) && !isFtsWord(terms[i - 1])) {\n let open = i + 4;\n while (/\\s/u.test(terms[open] ?? '')) open++;\n if (terms[open] === '(') {\n const end = nearEnd(terms, open);\n if (HAS_RUN.test(terms.slice(open + 1, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = end - 1;\n continue;\n }\n }\n if (terms[i] === '^' && !isFtsWord(terms[i - 1])) {\n let operand = i + 1;\n while (/\\s/u.test(terms[operand] ?? '')) operand++;\n const end =\n terms[operand] === '\"'\n ? quoteEnd(terms, operand)\n : (() => {\n let cursor = operand;\n while (/[\\p{L}\\p{N}_]/u.test(terms[cursor] ?? '')) cursor++;\n return cursor;\n })();\n if (end > operand && HAS_RUN.test(terms.slice(operand, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = Math.max(i, end - 1);\n }\n }\n return spans;\n}\n\nfunction routePositionalSpans(terms: string): string {\n const spans = positionalSpans(terms);\n if (spans.length === 0) return terms;\n let out = '';\n let cursor = 0;\n for (const span of spans) {\n out += terms.slice(cursor, span.start);\n if (!span.qualified) out += AUTHORED_COLUMNS;\n out += terms.slice(span.start, span.end);\n cursor = span.end;\n }\n return out + terms.slice(cursor);\n}\n\nfunction inPositionalSpan(start: number, spans: PositionalSpan[]): boolean {\n return spans.some((span) => start >= span.start && start < span.end);\n}\n\n// A run's punctuation-free groups, each its own quoted phrase (bare token if one grapheme),\n// space-joined -- the same split points segmentField barriers, so query and index agree.\nfunction runQuery(run: string, columnPrefix: string, quoteSingle = false): string {\n return splitOnPunctuation(run)\n .map((g) => `${columnPrefix}${g.length > 1 || quoteSingle ? `\"${g.join(' ')}\"` : g[0]}`)\n .join(' ');\n}\n\n// Retarget complete quoted run tokens without interpreting the expression around them. Positional\n// spans have already been routed to authored columns, so sidecar barriers cannot change meaning.\nfunction quotedRunTokens(terms: string): string {\n const spans = positionalSpans(terms);\n let out = '';\n let cursor = 0;\n for (const match of terms.matchAll(QUOTED_RUN_TOKEN)) {\n const start = match.index;\n const run = match[1];\n if (inPositionalSpan(start, spans) || PUNCTUATION.test(run)) continue;\n out += terms.slice(cursor, start);\n const qualifier = out.match(QUALIFIER);\n if (qualifier) {\n out = `${out.slice(0, qualifier.index)}${qualifier[1]}${runQuery(run, `${qualifier[2]}${qualifier[3]}_seg:`, true)}`;\n } else {\n out += runQuery(run, SIDECAR_COLUMNS, true);\n }\n cursor = start + match[0].length;\n }\n return cursor === 0 ? terms : out + terms.slice(cursor);\n}\n\n// Query side: each unspaced run becomes phrases of its graphemes, matching segmentField. A\n// qualifier maps to its `_seg` column; unqualified maps to all three (SIDECAR_COLUMNS).\nexport function segmentMatch(terms: string): string {\n if (!HAS_RUN.test(terms)) return terms;\n terms = routePositionalSpans(terms);\n terms = quotedRunTokens(terms);\n const spans = positionalSpans(terms);\n let out = '';\n let quoted = false;\n let offset = 0;\n const pieces = terms.split(RUN); // split keeps captured runs at odd indices\n for (let i = 0; i < pieces.length; i++) {\n if (i % 2 === 0) {\n for (const ch of pieces[i]) if (ch === '\"') quoted = !quoted;\n out += pieces[i];\n offset += pieces[i].length;\n continue;\n }\n const protectedRun = inPositionalSpan(offset, spans);\n if (quoted || protectedRun) {\n out += pieces[i]; // an author's phrase is matched as written\n offset += pieces[i].length;\n continue;\n }\n const m = out.match(QUALIFIER);\n if (m) {\n out = `${out.slice(0, m.index)}${m[1]}${runQuery(pieces[i], `${m[2]}${m[3]}_seg:`)}`;\n } else {\n out += runQuery(pieces[i], SIDECAR_COLUMNS);\n }\n offset += pieces[i].length;\n }\n return out;\n}\n\n// FTS tokenizers compare letters without combining marks. Keep the authored string for output and\n// offsets, but use this key when JS needs to compare a matched word to its source segment.\nexport function foldForSearch(text: string): string {\n return text.normalize('NFD').replace(/\\p{M}/gu, '').toLowerCase();\n}\n"],"names":["UNSPACED_SCRIPTS","foldForSearch","hasUnspacedRun","matchesSearchPhrase","searchTokens","segmentField","segmentMatch","unspacedRuns","RUN_BODY","RUN","RegExp","HAS_RUN","text","test","tokens","matchAll","match","start","index","end","length","cursor","slice","run","runStart","push","unspaced","runEnd","phrase","haystack","matches","i","query","authored","includes","stemmer","Array","from","m","graphemeSegmenter","PUNCTUATION","BARRIER","graphemes","Intl","Segmenter","undefined","granularity","segment","s","splitOnPunctuation","groups","g","filter","out","replace","body","map","join","trim","QUALIFIER","SIDECAR_COLUMNS","AUTHORED_COLUMNS","QUOTED_RUN_TOKEN","FTS_WORD","isFtsWord","char","quoteEnd","nearEnd","open","depth","quoted","positionalSpans","terms","startsWith","spans","qualified","operand","Math","max","routePositionalSpans","span","inPositionalSpan","some","runQuery","columnPrefix","quoteSingle","quotedRunTokens","qualifier","offset","pieces","split","ch","protectedRun","normalize","toLowerCase"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/text/segment.ts"],"sourcesContent":["import { stemmer } from 'stemmer';\n\n// Word boundaries FTS5's tokenizers cannot find on their own. Contract: seg(query) must appear in\n// seg(document) for any substring query; word mode is context-dependent and breaks that, so grapheme mode (UAX #29) is used.\n\n// Scripts written without word spaces: a closed set of writing systems.\n// Script_Extensions, not Script: the katakana long-vowel mark ー is Script=Common.\nexport const UNSPACED_SCRIPTS = '\\\\p{scx=Han}\\\\p{scx=Hiragana}\\\\p{scx=Katakana}\\\\p{scx=Thai}\\\\p{scx=Khmer}\\\\p{scx=Lao}\\\\p{scx=Myanmar}';\n// A run is script BASE characters with their combining marks attached; a bare mark after a\n// Latin letter (decomposed é) never starts one.\nconst RUN_BODY = `(?:[${UNSPACED_SCRIPTS}]\\\\p{M}*)+`;\nconst RUN = new RegExp(`(${RUN_BODY})`, 'gu');\nconst HAS_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');\n\n// Whether text holds a script that marks no word boundaries, the predicate every store's\n// sidecar population and query split turns on.\nexport function hasUnspacedRun(text: string): boolean {\n return HAS_RUN.test(text);\n}\n\nexport interface SearchToken {\n text: string;\n start: number;\n end: number;\n unspaced: boolean;\n}\n\n// Search tokens share the native separator contract: letters, marks, and numbers form tokens;\n// underscore, apostrophe, and hyphen separate them. Unspaced-script runs stay whole so callers\n// can apply substring semantics, while adjacent Latin text still gets its own token.\nexport function searchTokens(text: string): SearchToken[] {\n const tokens: SearchToken[] = [];\n for (const match of text.matchAll(/[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N}]*/gu)) {\n const start = match.index;\n const end = start + match[0].length;\n let cursor = start;\n for (const run of text.slice(start, end).matchAll(RUN)) {\n const runStart = start + run.index;\n if (runStart > cursor) tokens.push({ text: text.slice(cursor, runStart), start: cursor, end: runStart, unspaced: false });\n const runEnd = runStart + run[0].length;\n tokens.push({ text: run[0], start: runStart, end: runEnd, unspaced: true });\n cursor = runEnd;\n }\n if (cursor < end) tokens.push({ text: text.slice(cursor, end), start: cursor, end, unspaced: false });\n }\n return tokens;\n}\n\n// Phrase membership is checked after native ranking. A CJK token matches inside one authored\n// run; Latin tokens compare folded Porter stems. Each call scans one field once per candidate.\nexport function matchesSearchPhrase(text: string, phrase: SearchToken[]): boolean {\n const haystack = searchTokens(text);\n if (phrase.length === 0 || haystack.length < phrase.length) return false;\n for (let start = 0; start <= haystack.length - phrase.length; start++) {\n let matches = true;\n for (let i = 0; i < phrase.length; i++) {\n const query = phrase[i];\n const authored = haystack[start + i];\n if (query.unspaced) {\n if (!authored.unspaced || !authored.text.includes(query.text)) matches = false;\n } else if (authored.unspaced || stemmer(foldForSearch(authored.text)) !== stemmer(foldForSearch(query.text))) {\n matches = false;\n }\n if (!matches) break;\n }\n if (matches) return true;\n }\n return false;\n}\n\n// Character spans of unspaced-script runs in text, the same boundary the index segments on --\n// the snippet marker uses this to keep substring semantics inside a run and word/stem matching outside it.\nexport function unspacedRuns(text: string): Array<{ start: number; end: number }> {\n return Array.from(text.matchAll(RUN), (m) => ({ start: m.index, end: m.index + m[0].length }));\n}\n// Grapheme clusters, ECMA-402/UAX #29: base char plus its marks, ZWJ sequences, Hangul jamo.\n// Built on first use and kept: construction is 6.6 ms, and a tree with no unspaced-script run\n// never segments at all, so no command pays it at module load.\nlet graphemeSegmenter: Intl.Segmenter | undefined;\n// unicode61 drops Unicode punctuation as a separator. Some of it (。、「」) has Script_Extensions\n// into an unspaced script, so RUN keeps it -- a grapheme matching this becomes a split point.\nconst PUNCTUATION = /\\p{P}/u;\n// Token barrier between separate runs (or a punctuation split within one) so their graphemes\n// are never phrase-adjacent. U+A7F7: a letter (so FTS5 keeps it as a token) no one types.\nconst BARRIER = 'ꟷ';\n\nfunction graphemes(run: string): string[] {\n graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n return Array.from(graphemeSegmenter.segment(run), (s) => s.segment);\n}\n\n// A run's graphemes, cut into punctuation-free groups at every punctuation grapheme (dropped,\n// matching unicode61). The one place punctuation is classified, so index and query agree.\nfunction splitOnPunctuation(run: string): string[][] {\n const groups: string[][] = [[]];\n for (const g of graphemes(run)) {\n if (PUNCTUATION.test(g)) groups.push([]);\n else groups[groups.length - 1].push(g);\n }\n return groups.filter((g) => g.length > 0);\n}\n\n// Index side: '' when the field has no unspaced-script run. Otherwise each run explodes into\n// its graphemes, barrier-delimited from its neighbors and from a punctuation split within itself.\nexport function segmentField(text: string): string {\n if (!HAS_RUN.test(text)) return '';\n const out = text.replace(RUN, (run) => {\n const body = splitOnPunctuation(run)\n .map((g) => g.join(' '))\n .join(` ${BARRIER} `);\n return ` ${BARRIER} ${body} ${BARRIER} `;\n });\n return out.replace(/\\s+/g, ' ').trim();\n}\n\n// A `title:`/`summary:`/`text:` qualifier directly before a run that is about to become a\n// quoted grapheme phrase, so the rewrite can retarget it at the matching `_seg` column.\nconst QUALIFIER = /(^|[\\s(])(-?)(title|summary|text)\\s*:\\s*$/;\n\n// Raw title/summary/text drop punctuation as unicode61's token separator, so `数数` and `数。数`\n// falsely match there; only the barriered `_seg` columns are safe from it, hence this fallback target.\nconst SIDECAR_COLUMNS = '{title_seg summary_seg text_seg}:';\nconst AUTHORED_COLUMNS = '{title summary text}:';\nconst QUOTED_RUN_TOKEN = new RegExp(`\"(${RUN_BODY})\"`, 'gu');\n\ninterface PositionalSpan {\n start: number;\n end: number;\n qualified: boolean;\n}\n\nconst FTS_WORD = /[\\p{L}\\p{N}_]/u;\n\nfunction isFtsWord(char: string | undefined): boolean {\n return char !== undefined && FTS_WORD.test(char);\n}\n\nfunction quoteEnd(text: string, start: number): number {\n for (let i = start + 1; i < text.length; i++) {\n if (text[i] !== '\"') continue;\n if (text[i + 1] === '\"') {\n i++;\n continue;\n }\n return i + 1;\n }\n return text.length;\n}\n\nfunction nearEnd(text: string, open: number): number {\n let depth = 1;\n let quoted = false;\n for (let i = open + 1; i < text.length; i++) {\n const char = text[i];\n if (char === '\"') {\n if (quoted && text[i + 1] === '\"') {\n i++;\n } else {\n quoted = !quoted;\n }\n } else if (!quoted && char === '(') {\n depth++;\n } else if (!quoted && char === ')' && --depth === 0) {\n return i + 1;\n }\n }\n return text.length;\n}\n\n// Lexical routing only: SQLite remains responsible for validating the expression. The scanner\n// recognizes NEAR groups and the one FTS string governed by ^, while quoted text shields both.\nfunction positionalSpans(terms: string): PositionalSpan[] {\n const spans: PositionalSpan[] = [];\n for (let i = 0; i < terms.length; i++) {\n if (terms[i] === '\"') {\n i = quoteEnd(terms, i) - 1;\n continue;\n }\n if (terms.startsWith('NEAR', i) && !isFtsWord(terms[i - 1])) {\n let open = i + 4;\n while (/\\s/u.test(terms[open] ?? '')) open++;\n if (terms[open] === '(') {\n const end = nearEnd(terms, open);\n if (HAS_RUN.test(terms.slice(open + 1, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = end - 1;\n continue;\n }\n }\n if (terms[i] === '^' && !isFtsWord(terms[i - 1])) {\n let operand = i + 1;\n while (/\\s/u.test(terms[operand] ?? '')) operand++;\n const end =\n terms[operand] === '\"'\n ? quoteEnd(terms, operand)\n : (() => {\n let cursor = operand;\n while (/[\\p{L}\\p{N}_]/u.test(terms[cursor] ?? '')) cursor++;\n return cursor;\n })();\n if (end > operand && HAS_RUN.test(terms.slice(operand, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = Math.max(i, end - 1);\n }\n }\n return spans;\n}\n\nfunction routePositionalSpans(terms: string): string {\n const spans = positionalSpans(terms);\n if (spans.length === 0) return terms;\n let out = '';\n let cursor = 0;\n for (const span of spans) {\n out += terms.slice(cursor, span.start);\n if (!span.qualified) out += AUTHORED_COLUMNS;\n out += terms.slice(span.start, span.end);\n cursor = span.end;\n }\n return out + terms.slice(cursor);\n}\n\nfunction inPositionalSpan(start: number, spans: PositionalSpan[]): boolean {\n return spans.some((span) => start >= span.start && start < span.end);\n}\n\n// A run's punctuation-free groups, each its own quoted phrase (bare token if one grapheme),\n// space-joined -- the same split points segmentField barriers, so query and index agree.\nfunction runQuery(run: string, columnPrefix: string, quoteSingle = false): string {\n return splitOnPunctuation(run)\n .map((g) => `${columnPrefix}${g.length > 1 || quoteSingle ? `\"${g.join(' ')}\"` : g[0]}`)\n .join(' ');\n}\n\n// Retarget complete quoted run tokens without interpreting the expression around them. Positional\n// spans have already been routed to authored columns, so sidecar barriers cannot change meaning.\nfunction quotedRunTokens(terms: string): string {\n const spans = positionalSpans(terms);\n let out = '';\n let cursor = 0;\n for (const match of terms.matchAll(QUOTED_RUN_TOKEN)) {\n const start = match.index;\n const run = match[1];\n if (inPositionalSpan(start, spans) || PUNCTUATION.test(run)) continue;\n out += terms.slice(cursor, start);\n const qualifier = out.match(QUALIFIER);\n if (qualifier) {\n out = `${out.slice(0, qualifier.index)}${qualifier[1]}${runQuery(run, `${qualifier[2]}${qualifier[3]}_seg:`, true)}`;\n } else {\n out += runQuery(run, SIDECAR_COLUMNS, true);\n }\n cursor = start + match[0].length;\n }\n return cursor === 0 ? terms : out + terms.slice(cursor);\n}\n\n// Query side: each unspaced run becomes phrases of its graphemes, matching segmentField. A\n// qualifier maps to its `_seg` column; unqualified maps to all three (SIDECAR_COLUMNS).\nexport function segmentMatch(terms: string): string {\n if (!HAS_RUN.test(terms)) return terms;\n terms = routePositionalSpans(terms);\n terms = quotedRunTokens(terms);\n const spans = positionalSpans(terms);\n let out = '';\n let quoted = false;\n let offset = 0;\n const pieces = terms.split(RUN); // split keeps captured runs at odd indices\n for (let i = 0; i < pieces.length; i++) {\n if (i % 2 === 0) {\n for (const ch of pieces[i]) if (ch === '\"') quoted = !quoted;\n out += pieces[i];\n offset += pieces[i].length;\n continue;\n }\n const protectedRun = inPositionalSpan(offset, spans);\n if (quoted || protectedRun) {\n out += pieces[i]; // an author's phrase is matched as written\n offset += pieces[i].length;\n continue;\n }\n const m = out.match(QUALIFIER);\n if (m) {\n out = `${out.slice(0, m.index)}${m[1]}${runQuery(pieces[i], `${m[2]}${m[3]}_seg:`)}`;\n } else {\n out += runQuery(pieces[i], SIDECAR_COLUMNS);\n }\n offset += pieces[i].length;\n }\n return out;\n}\n\n// FTS tokenizers compare letters without combining marks. Keep the authored string for output and\n// offsets, but use this key when JS needs to compare a matched word to its source segment.\nexport function foldForSearch(text: string): string {\n return text.normalize('NFD').replace(/\\p{M}/gu, '').toLowerCase();\n}\n"],"names":["UNSPACED_SCRIPTS","foldForSearch","hasUnspacedRun","matchesSearchPhrase","searchTokens","segmentField","segmentMatch","unspacedRuns","RUN_BODY","RUN","RegExp","HAS_RUN","text","test","tokens","matchAll","match","start","index","end","length","cursor","slice","run","runStart","push","unspaced","runEnd","phrase","haystack","matches","i","query","authored","includes","stemmer","Array","from","m","graphemeSegmenter","PUNCTUATION","BARRIER","graphemes","Intl","Segmenter","undefined","granularity","segment","s","splitOnPunctuation","groups","g","filter","out","replace","body","map","join","trim","QUALIFIER","SIDECAR_COLUMNS","AUTHORED_COLUMNS","QUOTED_RUN_TOKEN","FTS_WORD","isFtsWord","char","quoteEnd","nearEnd","open","depth","quoted","positionalSpans","terms","startsWith","spans","qualified","operand","Math","max","routePositionalSpans","span","inPositionalSpan","some","runQuery","columnPrefix","quoteSingle","quotedRunTokens","qualifier","offset","pieces","split","ch","protectedRun","normalize","toLowerCase"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/text/strip.ts"],"sourcesContent":["import type { Block } from '../chunk/index.ts';\nimport { extractTexts, parse } from '../chunk/index.ts';\nimport { normalizeText } from '../scan/frontmatter.ts';\n\n// Flat prose from blocks a caller already parsed, via the same extractor the chunker uses\n// (src/chunk) -- words and vectors read one definition of \"the prose of this note\".\nexport function textFromBlocks(blocks: Block[]): string {\n return normalizeText(extractTexts(blocks));\n}\n\n// Thin parse + delegate, for callers with no blocks of their own already.\nexport function stripText(value: string): string {\n return textFromBlocks(parse(value));\n}\n"],"names":["stripText","textFromBlocks","blocks","normalizeText","extractTexts","value","parse"],"mappings":";;;;;;;;;;;QAWgBA;eAAAA;;QALAC;eAAAA;;;uBALoB;6BACN;AAIvB,SAASA,eAAeC,MAAe;IAC5C,OAAOC,IAAAA,4BAAa,EAACC,IAAAA,qBAAY,EAACF;AACpC;AAGO,SAASF,UAAUK,KAAa;IACrC,OAAOJ,eAAeK,IAAAA,cAAK,EAACD;AAC9B"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/text/strip.ts"],"sourcesContent":["import type { Block } from '../chunk/index.ts';\nimport { extractTexts, parse } from '../chunk/index.ts';\nimport { normalizeText } from '../scan/frontmatter.ts';\n\n// Flat prose from blocks a caller already parsed, via the same extractor the chunker uses\n// (src/chunk) -- words and vectors read one definition of \"the prose of this note\".\nexport function textFromBlocks(blocks: Block[]): string {\n return normalizeText(extractTexts(blocks));\n}\n\n// Thin parse + delegate, for callers with no blocks of their own already.\nexport function stripText(value: string): string {\n return textFromBlocks(parse(value));\n}\n"],"names":["stripText","textFromBlocks","blocks","normalizeText","extractTexts","value","parse"],"mappings":";;;;;;;;;;;QAWgBA;eAAAA;;QALAC;eAAAA;;;uBALoB;6BACN;AAIvB,SAASA,eAAeC,MAAe;IAC5C,OAAOC,IAAAA,4BAAa,EAACC,IAAAA,qBAAY,EAACF;AACpC;AAGO,SAASF,UAAUK,KAAa;IACrC,OAAOJ,eAAeK,IAAAA,cAAK,EAACD;AAC9B"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch-claim.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { Worker } from 'node:worker_threads';\nimport { SenseError } from './errors.ts';\nimport { resolveWorkerFile } from './lib/worker-file.ts';\nimport { reviveError, type WorkerErrorPayload } from './scan/worker-error.ts';\nimport { createConnection } from './store/sqlite/connection.ts';\nimport { BEGIN_WRITE, withTransaction } from './store/transaction.ts';\n\nexport const WATCH_CLAIM_FILENAME = '.sense-watch.db';\nexport const WATCH_HEARTBEAT_INTERVAL_MS = 5_000;\nexport const WATCH_STALE_HEARTBEAT_MS = 15_000;\nconst CLAIM_BUSY_TIMEOUT_MS = 30_000;\n\nexport interface WatchClaimRecord {\n token: string;\n pid: number;\n heartbeatMs: number;\n}\n\ninterface ClaimRow {\n token: string;\n pid: number;\n heartbeat_ms: number;\n}\n\nexport interface WatchClaimWorkerData {\n configDir: string;\n force: boolean;\n heartbeatIntervalMs: number;\n pid: number;\n token: string;\n}\n\nexport type WatchClaimWorkerMessage = { type: 'acquired' } | { type: 'stopped' } | { type: 'failure'; error: WorkerErrorPayload };\n\nexport interface WatchClaimController {\n close(): Promise<void>;\n}\n\nfunction claimPath(configDir: string): string {\n return join(configDir, WATCH_CLAIM_FILENAME);\n}\n\nfunction changedOne(changes: number | bigint): boolean {\n return changes === 1 || changes === BigInt(1);\n}\n\n// This database is separate from the selected search store so its heartbeat remains writable while\n// a DuckDB, Turso, or SQLite reconciliation owns the cache connection or blocks the main thread.\nexport class WatchClaimDatabase {\n private readonly db: DatabaseSync;\n private readonly conn: ReturnType<typeof createConnection>;\n\n constructor(configDir: string) {\n const db = new DatabaseSync(claimPath(configDir));\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n db.exec('CREATE TABLE IF NOT EXISTS watch_claim (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), token TEXT NOT NULL, pid INTEGER NOT NULL, heartbeat_ms INTEGER NOT NULL)');\n } catch (err) {\n try {\n db.close();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch claim setup and database cleanup both failed');\n }\n throw err;\n }\n this.db = db;\n this.conn = createConnection(db);\n }\n\n async acquire(token: string, pid: number, force: boolean): Promise<void> {\n await withTransaction(\n this.conn,\n async () => {\n const current = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n if (current && !force) {\n const age = Date.now() - current.heartbeat_ms;\n if (age >= 0 && age < WATCH_STALE_HEARTBEAT_MS) throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n this.db.prepare('INSERT INTO watch_claim (singleton, token, pid, heartbeat_ms) VALUES (1, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET token = excluded.token, pid = excluded.pid, heartbeat_ms = excluded.heartbeat_ms').run(token, pid, Date.now());\n },\n BEGIN_WRITE\n );\n }\n\n renew(token: string): boolean {\n return changedOne(this.db.prepare('UPDATE watch_claim SET heartbeat_ms = ? WHERE singleton = 1 AND token = ?').run(Date.now(), token).changes);\n }\n\n release(token: string): boolean {\n return changedOne(this.db.prepare('DELETE FROM watch_claim WHERE singleton = 1 AND token = ?').run(token).changes);\n }\n\n read(): WatchClaimRecord | null {\n const row = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// Status opens the coordinator read-only and therefore cannot create either the file or its schema.\nexport function readWatchClaim(configDir: string): WatchClaimRecord | null {\n const path = claimPath(configDir);\n if (!existsSync(path)) return null;\n let db: DatabaseSync;\n try {\n db = new DatabaseSync(path, { readOnly: true });\n } catch (err) {\n if (!existsSync(path)) return null;\n throw err;\n }\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n const row = db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n } catch (err) {\n if (/no such table: watch_claim/.test((err as Error).message)) return null;\n throw err;\n } finally {\n db.close();\n }\n}\n\nexport async function startWatchClaim(configDir: string, force: boolean, heartbeatIntervalMs: number, onFailure: (err: Error) => void): Promise<WatchClaimController> {\n const workerData: WatchClaimWorkerData = { configDir, force, heartbeatIntervalMs, pid: process.pid, token: randomUUID() };\n const worker = new Worker(resolveWorkerFile('watch-heartbeat'), { workerData });\n let acquired = false;\n let closing = false;\n let exited = false;\n let failureReported = false;\n let closeError: Error | null = null;\n let closePromise: Promise<void> | undefined;\n let resolveExit!: () => void;\n const exit = new Promise<void>((resolve) => {\n resolveExit = resolve;\n });\n let resolveStart!: () => void;\n let rejectStart!: (err: Error) => void;\n const start = new Promise<void>((resolve, reject) => {\n resolveStart = resolve;\n rejectStart = reject;\n });\n\n const fail = (err: Error): void => {\n if (failureReported) return;\n failureReported = true;\n if (!acquired) rejectStart(err);\n else if (closing) closeError ??= err;\n else onFailure(err);\n };\n worker.on('message', (message: WatchClaimWorkerMessage) => {\n if (message.type === 'acquired') {\n acquired = true;\n resolveStart();\n } else if (message.type === 'failure') {\n fail(reviveError(message.error));\n }\n });\n worker.on('error', fail);\n worker.on('exit', (code) => {\n exited = true;\n resolveExit();\n if (closing && code !== 0) closeError ??= new Error(`watch heartbeat worker stopped with exit code ${code}`);\n else if (!closing && !failureReported) fail(new Error(`watch heartbeat worker stopped unexpectedly with exit code ${code}`));\n });\n\n try {\n await start;\n } catch (err) {\n closing = true;\n if (!exited) await worker.terminate();\n await exit;\n throw err;\n }\n\n return {\n close(): Promise<void> {\n closePromise ??= (async () => {\n closing = true;\n if (!exited) {\n try {\n worker.postMessage({ type: 'stop' });\n } catch (err) {\n closeError ??= err instanceof Error ? err : new Error(String(err));\n await worker.terminate();\n }\n }\n await exit;\n if (closeError) throw closeError;\n })();\n return closePromise;\n },\n };\n}\n"],"names":["WATCH_CLAIM_FILENAME","WATCH_HEARTBEAT_INTERVAL_MS","WATCH_STALE_HEARTBEAT_MS","WatchClaimDatabase","readWatchClaim","startWatchClaim","CLAIM_BUSY_TIMEOUT_MS","claimPath","configDir","join","changedOne","changes","BigInt","db","DatabaseSync","exec","err","close","closeError","AggregateError","conn","createConnection","acquire","token","pid","force","withTransaction","current","age","prepare","get","Date","now","heartbeat_ms","SenseError","Math","round","run","BEGIN_WRITE","renew","release","read","row","heartbeatMs","path","existsSync","readOnly","test","message","heartbeatIntervalMs","onFailure","workerData","worker","acquired","closing","exited","failureReported","closePromise","resolveExit","exit","resolveStart","rejectStart","start","fail","process","randomUUID","Worker","resolveWorkerFile","Promise","resolve","reject","on","type","reviveError","error","code","Error","terminate","postMessage","String"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/watch-claim.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { Worker } from 'node:worker_threads';\nimport { SenseError } from './errors.ts';\nimport { resolveWorkerFile } from './lib/worker-file.ts';\nimport { reviveError, type WorkerErrorPayload } from './scan/worker-error.ts';\nimport { createConnection } from './store/sqlite/connection.ts';\nimport { BEGIN_WRITE, withTransaction } from './store/transaction.ts';\n\nexport const WATCH_CLAIM_FILENAME = '.sense-watch.db';\nexport const WATCH_HEARTBEAT_INTERVAL_MS = 5_000;\nexport const WATCH_STALE_HEARTBEAT_MS = 15_000;\nconst CLAIM_BUSY_TIMEOUT_MS = 30_000;\n\nexport interface WatchClaimRecord {\n token: string;\n pid: number;\n heartbeatMs: number;\n}\n\ninterface ClaimRow {\n token: string;\n pid: number;\n heartbeat_ms: number;\n}\n\nexport interface WatchClaimWorkerData {\n configDir: string;\n force: boolean;\n heartbeatIntervalMs: number;\n pid: number;\n token: string;\n}\n\nexport type WatchClaimWorkerMessage = { type: 'acquired' } | { type: 'stopped' } | { type: 'failure'; error: WorkerErrorPayload };\n\nexport interface WatchClaimController {\n close(): Promise<void>;\n}\n\nfunction claimPath(configDir: string): string {\n return join(configDir, WATCH_CLAIM_FILENAME);\n}\n\nfunction changedOne(changes: number | bigint): boolean {\n return changes === 1 || changes === BigInt(1);\n}\n\n// This database is separate from the selected search store so its heartbeat remains writable while\n// a DuckDB, Turso, or SQLite reconciliation owns the cache connection or blocks the main thread.\nexport class WatchClaimDatabase {\n private readonly db: DatabaseSync;\n private readonly conn: ReturnType<typeof createConnection>;\n\n constructor(configDir: string) {\n const db = new DatabaseSync(claimPath(configDir));\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n db.exec('CREATE TABLE IF NOT EXISTS watch_claim (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), token TEXT NOT NULL, pid INTEGER NOT NULL, heartbeat_ms INTEGER NOT NULL)');\n } catch (err) {\n try {\n db.close();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch claim setup and database cleanup both failed');\n }\n throw err;\n }\n this.db = db;\n this.conn = createConnection(db);\n }\n\n async acquire(token: string, pid: number, force: boolean): Promise<void> {\n await withTransaction(\n this.conn,\n async () => {\n const current = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n if (current && !force) {\n const age = Date.now() - current.heartbeat_ms;\n if (age >= 0 && age < WATCH_STALE_HEARTBEAT_MS) throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n this.db.prepare('INSERT INTO watch_claim (singleton, token, pid, heartbeat_ms) VALUES (1, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET token = excluded.token, pid = excluded.pid, heartbeat_ms = excluded.heartbeat_ms').run(token, pid, Date.now());\n },\n BEGIN_WRITE\n );\n }\n\n renew(token: string): boolean {\n return changedOne(this.db.prepare('UPDATE watch_claim SET heartbeat_ms = ? WHERE singleton = 1 AND token = ?').run(Date.now(), token).changes);\n }\n\n release(token: string): boolean {\n return changedOne(this.db.prepare('DELETE FROM watch_claim WHERE singleton = 1 AND token = ?').run(token).changes);\n }\n\n read(): WatchClaimRecord | null {\n const row = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// Status opens the coordinator read-only and therefore cannot create either the file or its schema.\nexport function readWatchClaim(configDir: string): WatchClaimRecord | null {\n const path = claimPath(configDir);\n if (!existsSync(path)) return null;\n let db: DatabaseSync;\n try {\n db = new DatabaseSync(path, { readOnly: true });\n } catch (err) {\n if (!existsSync(path)) return null;\n throw err;\n }\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n const row = db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n } catch (err) {\n if (/no such table: watch_claim/.test((err as Error).message)) return null;\n throw err;\n } finally {\n db.close();\n }\n}\n\nexport async function startWatchClaim(configDir: string, force: boolean, heartbeatIntervalMs: number, onFailure: (err: Error) => void): Promise<WatchClaimController> {\n const workerData: WatchClaimWorkerData = { configDir, force, heartbeatIntervalMs, pid: process.pid, token: randomUUID() };\n const worker = new Worker(resolveWorkerFile('watch-heartbeat'), { workerData });\n let acquired = false;\n let closing = false;\n let exited = false;\n let failureReported = false;\n let closeError: Error | null = null;\n let closePromise: Promise<void> | undefined;\n let resolveExit!: () => void;\n const exit = new Promise<void>((resolve) => {\n resolveExit = resolve;\n });\n let resolveStart!: () => void;\n let rejectStart!: (err: Error) => void;\n const start = new Promise<void>((resolve, reject) => {\n resolveStart = resolve;\n rejectStart = reject;\n });\n\n const fail = (err: Error): void => {\n if (failureReported) return;\n failureReported = true;\n if (!acquired) rejectStart(err);\n else if (closing) closeError ??= err;\n else onFailure(err);\n };\n worker.on('message', (message: WatchClaimWorkerMessage) => {\n if (message.type === 'acquired') {\n acquired = true;\n resolveStart();\n } else if (message.type === 'failure') {\n fail(reviveError(message.error));\n }\n });\n worker.on('error', fail);\n worker.on('exit', (code) => {\n exited = true;\n resolveExit();\n if (closing && code !== 0) closeError ??= new Error(`watch heartbeat worker stopped with exit code ${code}`);\n else if (!closing && !failureReported) fail(new Error(`watch heartbeat worker stopped unexpectedly with exit code ${code}`));\n });\n\n try {\n await start;\n } catch (err) {\n closing = true;\n if (!exited) await worker.terminate();\n await exit;\n throw err;\n }\n\n return {\n close(): Promise<void> {\n closePromise ??= (async () => {\n closing = true;\n if (!exited) {\n try {\n worker.postMessage({ type: 'stop' });\n } catch (err) {\n closeError ??= err instanceof Error ? err : new Error(String(err));\n await worker.terminate();\n }\n }\n await exit;\n if (closeError) throw closeError;\n })();\n return closePromise;\n },\n };\n}\n"],"names":["WATCH_CLAIM_FILENAME","WATCH_HEARTBEAT_INTERVAL_MS","WATCH_STALE_HEARTBEAT_MS","WatchClaimDatabase","readWatchClaim","startWatchClaim","CLAIM_BUSY_TIMEOUT_MS","claimPath","configDir","join","changedOne","changes","BigInt","db","DatabaseSync","exec","err","close","closeError","AggregateError","conn","createConnection","acquire","token","pid","force","withTransaction","current","age","prepare","get","Date","now","heartbeat_ms","SenseError","Math","round","run","BEGIN_WRITE","renew","release","read","row","heartbeatMs","path","existsSync","readOnly","test","message","heartbeatIntervalMs","onFailure","workerData","worker","acquired","closing","exited","failureReported","closePromise","resolveExit","exit","resolveStart","rejectStart","start","fail","process","randomUUID","Worker","resolveWorkerFile","Promise","resolve","reject","on","type","reviveError","error","code","Error","terminate","postMessage","String"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { shouldReconcileWatchNotification } from './lib/watch-notification.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch prepares the index for snapshot queries; filesystem events request debounced builds\n// and periodic reconciliation recovers missed notifications.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\n/** Options for the root {@link runWatch} API. */\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n /** Caller abort cancels provider work, skips final preparation, and resolves after cleanup. */\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\nfunction isCallerAbort(signal: AbortSignal | undefined, reason: unknown): boolean {\n return signal?.aborted === true && Object.is(reason, signal.reason);\n}\n\nfunction errorMessage(reason: unknown): string {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\n/**\n * Runs in the foreground. Caller abort waits for active native work and cleanup; SIGINT/SIGTERM\n * also run one final freshness pass. Cleanup failures reject either shutdown path.\n */\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n if (opts.signal?.aborted) return;\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n let watcher: ReturnType<typeof fsWatch> | undefined;\n let startup = true;\n let startupNotification = false;\n let scheduleReconcile: (() => void) | undefined;\n let stopping = false;\n let callerCanceled = false;\n try {\n opts.signal?.throwIfAborted();\n // Subscribe before the first build. Native watchers have no ready barrier, so startup also\n // performs an unconditional catch-up pass after the initial build.\n watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (!shouldReconcileWatchNotification(filename)) return;\n if (startup) startupNotification = true;\n else scheduleReconcile?.();\n });\n watcher.on('error', (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n\n const startupEvents: Array<{ parsed: number; total: number; warnings: string[] }> = [];\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg, { signal: opts.signal });\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n\n startupEvents.push({ parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n // Always reopen once after subscribing, then drain notifications observed while startup was\n // in progress. Each pass closes before the next one starts, so no stores overlap.\n do {\n startupNotification = false;\n opts.signal?.throwIfAborted();\n const { store, parsed, warnings } = await openStore(cfg, { signal: opts.signal });\n try {\n if (parsed > 0 || warnings.length > 0) startupEvents.push({ parsed, total: await docCount(store), warnings });\n } finally {\n await store.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n } while (startupNotification && !opts.signal?.aborted);\n startup = false;\n\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg, { signal: opts.signal });\n try {\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n if (isCallerAbort(opts.signal, err)) return;\n onEvent({ type: 'reconcile-error', message: errorMessage(err) });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error, canceledByCaller = false) => {\n shutdownReason ??= reason ?? null;\n callerCanceled ||= canceledByCaller;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopForCaller);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher?.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason && !callerCanceled) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n const stopForCaller = () => shutdown(undefined, true);\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) stopForCaller();\n else {\n opts.signal?.addEventListener('abort', stopForCaller, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping) {\n for (const event of startupEvents) {\n if (stopping) break;\n if (event.warnings.length > 0 || event.parsed > 0) onEvent({ type: 'reconciled', ...event });\n }\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n const canceledByCaller = isCallerAbort(opts.signal, err);\n watcher?.close();\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n if (canceledByCaller) return;\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["runWatch","DEBOUNCE_MS","isCallerAbort","signal","reason","aborted","Object","is","errorMessage","Error","message","String","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","claimIntervalMs","rootDir","configDir","claimFailure","requestShutdown","claim","claimCloseStarted","closeClaim","watcher","startup","startupNotification","scheduleReconcile","stopping","callerCanceled","startupEvents","initialStore","dbPath","initialWarnings","initialParsed","initialTotal","store","parsed","warnings","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","reconcileTimer","err","canceledByCaller","closeError","heartbeatIntervalMs","WATCH_HEARTBEAT_INTERVAL_MS","Number","isFinite","SenseError","Math","min","baseDir","startWatchClaim","force","close","throwIfAborted","fsWatch","recursive","_event","filename","shouldReconcileWatchNotification","on","openStore","length","docCount","push","total","alwaysEmit","type","then","again","clearTimeout","setTimeout","setInterval","guardedTick","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","shutdownTask","finalError","finalStore","process","off","stopNormally","removeEventListener","stopForCaller","clearInterval","AggregateError","undefined","once","addEventListener","event"],"mappings":";;;;+BAoCsBA;;;eAAAA;;;sBApCW;wBAEN;6BACC;mCACqB;uBACb;4BACyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE7D,4FAA4F;AAC5F,6DAA6D;AAC7D,IAAMC,cAAc;AAcpB,SAASC,cAAcC,MAA+B,EAAEC,MAAe;IACrE,OAAOD,CAAAA,mBAAAA,6BAAAA,OAAQE,OAAO,MAAK,QAAQC,OAAOC,EAAE,CAACH,QAAQD,OAAOC,MAAM;AACpE;AAEA,SAASI,aAAaJ,MAAe;IACnC,OAAOA,AAAM,YAANA,QAAkBK,SAAQL,OAAOM,OAAO,GAAGC,OAAOP;AAC3D;AAMO,SAAeJ;wCAASY,GAAmB;YAAEC,MAElCA,eACGA,kBACSA,2BAIZD,cACEA,gBAG6BC,aAX3CA,cACEC,SACAC,YACAC,qBAGAC,iBACAC,SACAC,WACFC,cACAC,iBACEC,OAIFC,mBACEC,YAIFC,SACAC,SACAC,qBACAC,mBACAC,UACAC,gBAEFjB,eAsBAA,eAiBiCA,eA1B3BkB,eACoF,MAA3EC,cAAcC,QAAkBC,iBAAyBC,eACpEC,oBAeFvB,eAOAA,eANoC,OAA5BwB,OAAOC,QAAQC,oBAarBC,SACAC,cAEEC,MAgBAC,YAWAC,cASFC,eAUEC,gBAmECC,KACDC,kBAKGC;;;;;oBAlMuCpC,OAAAA,oEAAqB,CAAC;oBACxE,KAAIA,eAAAA,KAAKV,MAAM,cAAXU,mCAAAA,aAAaR,OAAO,EAAE;;;oBACpBS,WAAUD,gBAAAA,KAAKC,OAAO,cAAZD,2BAAAA,gBAAiB,YAAO;oBAClCE,cAAaF,mBAAAA,KAAKE,UAAU,cAAfF,8BAAAA,mBAAmBZ;oBAChCe,uBAAsBH,4BAAAA,KAAKqC,mBAAmB,cAAxBrC,uCAAAA,4BAA4BsC,yCAA2B;oBACnF,IAAI,CAACC,OAAOC,QAAQ,CAACrC,wBAAwBA,uBAAuB,GAAG,MAAM,IAAIsC,oBAAU,CAAC,kBAAkB;oBAC9G,gGAAgG;oBAC1FrC,kBAAkBsC,KAAKC,GAAG,CAACxC,qBAAqBmC,yCAA2B;oBAC3EjC,WAAUN,eAAAA,IAAIM,OAAO,cAAXN,0BAAAA,eAAeA,IAAI6C,OAAO;oBACpCtC,aAAYP,iBAAAA,IAAIO,SAAS,cAAbP,4BAAAA,iBAAiBM;oBAC/BE,eAA6B;oBAEnB;;wBAAMsC,IAAAA,6BAAe,EAACvC,YAAWN,cAAAA,KAAK8C,KAAK,cAAV9C,yBAAAA,cAAc,OAAOI,iBAAiB,SAAC8B;4BACpF3B,yBAAAA,0BAAAA,eAAAA,eAAiB2B;4BACjB1B,4BAAAA,sCAAAA,gBAAkB0B;wBACpB;;;oBAHMzB,QAAQ;oBAIVC,oBAAoB;oBAClBC,aAAa;wBACjBD,oBAAoB;wBACpB,OAAOD,MAAMsC,KAAK;oBACpB;oBAEIlC,UAAU;oBACVC,sBAAsB;oBAEtBE,WAAW;oBACXC,iBAAiB;;;;;;;;;qBAEnBjB,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBAC3B,2FAA2F;oBAC3F,mEAAmE;oBACnEpC,UAAUqC,IAAAA,aAAO,EAAC5C,SAAS;wBAAE6C,WAAW;oBAAK,GAAG,SAACC,QAAQC;wBACvD,IAAI,CAACC,IAAAA,qDAAgC,EAACD,WAAW;wBACjD,IAAIvC,SAASC,sBAAsB;6BAC9BC,8BAAAA,wCAAAA;oBACP;oBACAH,QAAQ0C,EAAE,CAAC,SAAS,SAACpB;wBACnB3B,yBAAAA,0BAAAA,eAAAA,eAAiB2B;wBACjB1B,4BAAAA,sCAAAA,gBAAkB0B;oBACpB;oBAEMhB;oBACoF;;wBAAMqC,IAAAA,kBAAS,EAACxD,KAAK;4BAAET,QAAQU,KAAKV,MAAM;wBAAC;;;oBAA3C,OAAA,eAA3E6B,eAA2E,KAAlFK,OAAqBJ,SAA6D,KAA7DA,QAAkBC,kBAA2C,KAArDK,UAAmCJ,gBAAkB,KAA1BG;oBAC5DF,eAAe;;;;;;;;;yBAGFF,CAAAA,gBAAgBmC,MAAM,GAAG,KAAKlC,gBAAgB,CAAA,GAA9CD;;;;oBAAkD;;wBAAMoC,IAAAA,iBAAQ,EAACtC;;;2BAAf;;;;;;2BAA+B;;;oBADhG,mFAAmF;oBACnFI;;;;;;oBAEA;;wBAAMJ,aAAa4B,KAAK;;;oBAAxB;;;;;qBAEF/C,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBAC3B,IAAIzC,cAAc,MAAMA;oBAExBW,cAAcwC,IAAI,CAAC;wBAAEjC,QAAQH;wBAAeqC,OAAOpC;wBAAcG,UAAUL;oBAAgB;;;oBAIzFP,sBAAsB;qBACtBd,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBACS;;wBAAMO,IAAAA,kBAAS,EAACxD,KAAK;4BAAET,QAAQU,KAAKV,MAAM;wBAAC;;;oBAA3C,QAAA,eAA5BkC,QAA4B,MAA5BA,OAAOC,SAAqB,MAArBA,QAAQC,WAAa,MAAbA;;;;;;;;;yBAEjBD,CAAAA,SAAS,KAAKC,SAAS8B,MAAM,GAAG,CAAA,GAAhC/B;;;;wBAAmCP,cAAcwC,IAAI;;wBAAGjC,QAAAA;;oBAAe;;wBAAMgC,IAAAA,iBAAQ,EAACjC;;;oBAAnDN,QAAAA;+BAA6ByC,QAAO,qBAAuBjC,WAAAA;;;;;;;;;oBAElG;;wBAAMF,MAAMuB,KAAK;;;oBAAjB;;;;;qBAEF/C,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBAC3B,IAAIzC,cAAc,MAAMA;;;wBACjBO,uBAAuB,GAACd,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAaR,OAAO,GAA5CsB;;;;;;oBACTD,UAAU;oBAEV,6FAA6F;oBAC7F,qFAAqF;oBACjFc,UAAgC;oBAChCC,eAA+B;oBAE7BC,OAAO,cAAO+B;;gCAEoB,MAA5BpC,OAAOC,QAAQC,gBAQhBQ;;;;;;;;;;wCAR6B;;4CAAMqB,IAAAA,kBAAS,EAACxD,KAAK;gDAAET,QAAQU,KAAKV,MAAM;4CAAC;;;wCAA3C,OAAA,eAA5BkC,QAA4B,KAA5BA,OAAOC,SAAqB,KAArBA,QAAQC,WAAa,KAAbA;;;;;;;;;6CAEjBkC,CAAAA,cAAcnC,SAAS,KAAKC,SAAS8B,MAAM,GAAG,CAAA,GAA9CI;;;;;4CACQC,MAAM;4CAAcpC,QAAAA;;wCAAe;;4CAAMgC,IAAAA,iBAAQ,EAACjC;;;wCAA5DvB;kDAAsC0D,QAAO,oBAAuBjC,WAAAA;;;;;;;;;wCAGtE;;4CAAMF,MAAMuB,KAAK;;;wCAAjB;;;;;;;;;;wCAEKb;wCACP,IAAI7C,cAAcW,KAAKV,MAAM,EAAE4C,MAAM;;;wCACrCjC,QAAQ;4CAAE4D,MAAM;4CAAmBhE,SAASF,aAAauC;wCAAK;;;;;;;;;;;wBAElE;;oBAEMJ,aAAa,qBAAC8B;wBAClBjC,UAAUE,KAAK+B,YAAYE,IAAI,CAAC;4BAC9BnC,UAAU;4BACV,IAAIC,iBAAiB,QAAQ,CAACZ,UAAU;gCACtC,IAAM+C,QAAQnC;gCACdA,eAAe;gCACfE,WAAWiC;4BACb;wBACF;oBACF;oBAEMhC,eAAe,sBAAC6B;wBACpB,IAAI5C,UAAU;wBACd,IAAIW,SAAS;4BACXC,eAAeA,gBAAgBgC;4BAC/B;wBACF;wBACA9B,WAAW8B;oBACb;oBAEI5B,gBAAuC;oBAC3C,gGAAgG;oBAChG,mGAAmG;oBACnGjB,oBAAoB;wBAClB,IAAIiB,eAAegC,aAAahC;wBAChCA,gBAAgBiC,WAAW;4BACzBjC,gBAAgB;4BAChBD,aAAa;wBACf,GAAG7B;oBACL;oBACM+B,iBAAiBiC,YACrBC,IAAAA,0BAAW,EACT;;;;;oCAAYpC,aAAa;;;;uBACzB;+BAAMf;wBAERb;oBAGK;;wBAAM,IAAIiE,QAAc,SAACC,iBAAiBC;gCA2CtCtE;4BA1CT,IAAIuE,iBAA+B;4BACnC,IAAMC,WAAW,kBAACjF;oCAAgB4C,oFAAmB;gCACnDoC,2BAAAA,4BAAAA,iBAAAA,iBAAmBhF,mBAAAA,oBAAAA,SAAU;gCAC7B0B,mBAAAA,iBAAmBkB;gCACnB,IAAInB,UAAU;gCACdA,WAAW;gCACX,IAAMyD,eAAe,AAAC;;4CAGpBzE,cAKI0E,YAK8B,MAAfC,YAGVzC,KAKAA;;;;oDApBT0C,QAAQC,GAAG,CAAC,UAAUC;oDACtBF,QAAQC,GAAG,CAAC,WAAWC;qDACvB9E,eAAAA,KAAKV,MAAM,cAAXU,mCAAAA,aAAa+E,mBAAmB,CAAC,SAASC;oDAC1CC,cAAchD;oDACd,IAAID,eAAegC,aAAahC;oDAChCpB,oBAAAA,8BAAAA,QAASmC,KAAK;oDACd;;wDAAMpB;;;oDAAN;;;;;;;;;yDAKM,CAAA,CAAC4C,kBAAkB,CAACtD,cAAa,GAAjC;;;;oDAC4B;;wDAAMsC,IAAAA,kBAAS,EAACxD;;;oDAAhB,OAAA,eAAf4E,aAAe,KAAtBnD;oDACR;;wDAAMmD,WAAW5B,KAAK;;;oDAAtB;;;;;;;;oDAEKb;oDACPwC,aAAaxC;;;;;;;;;;;;oDAGb;;wDAAMvB;;;oDAAN;;;;;;oDACOuB;oDACP,IAAIwC,YAAY,MAAM,IAAIQ;wDAAgBR;wDAAYxC;uDAAM;oDAC5D,MAAMA;;oDAER,IAAIwC,YAAY,MAAMA;oDACtB,IAAIH,gBAAgB,MAAMA;;;;;;oCAC5B;;gCACAE,aAAaX,IAAI,CAACO,iBAAiBC;4BACrC;4BACA,IAAMQ,eAAe;uCAAMN;;4BAC3B,IAAMQ,gBAAgB;uCAAMR,SAASW,WAAW;;4BAChD3E,kBAAkBgE;4BAClBI,QAAQQ,IAAI,CAAC,UAAUN;4BACvBF,QAAQQ,IAAI,CAAC,WAAWN;4BACxB,IAAIvE,cAAciE,SAASjE;iCACtB,KAAIP,eAAAA,KAAKV,MAAM,cAAXU,mCAAAA,aAAaR,OAAO,EAAEwF;iCAC1B;oCACHhF;iCAAAA,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAaqF,gBAAgB,CAAC,SAASL,eAAe;oCAAEI,MAAM;gCAAK;gCACnE,IAAI;oCACFnF,QAAQ;wCAAE4D,MAAM;wCAAWxD,SAAAA;wCAASe,QAAAA;oCAAO;oCAC3C,IAAI,CAACJ,UAAU;4CACR,kCAAA,2BAAA;;4CAAL,QAAK,YAAeE,kCAAf,SAAA,6BAAA,QAAA,yBAAA,iCAA8B;gDAA9B,IAAMoE,QAAN;gDACH,IAAItE,UAAU;gDACd,IAAIsE,MAAM5D,QAAQ,CAAC8B,MAAM,GAAG,KAAK8B,MAAM7D,MAAM,GAAG,GAAGxB,QAAQ;oDAAE4D,MAAM;mDAAiByB;4CACtF;;4CAHK;4CAAA;;;qDAAA,6BAAA;oDAAA;;;oDAAA;0DAAA;;;;oCAIP;gCACF,EAAE,OAAOpD,KAAK;oCACZsC,SAAStC,AAAG,YAAHA,KAAetC,SAAQsC,MAAM,IAAItC,MAAME,OAAOoC;gCACzD;4BACF;wBACF;;;oBA1DA;;wBAAO;;;oBA2DAA;oBACDC,mBAAmB9C,cAAcW,KAAKV,MAAM,EAAE4C;oBACpDtB,oBAAAA,8BAAAA,QAASmC,KAAK;oBACd,IAAIrC,mBAAmB,MAAMwB;;;;;;;;;oBAE3B;;wBAAMvB;;;oBAAN;;;;;;oBACOyB;oBACP,MAAM,IAAI8C;wBAAgBhD;wBAAKE;uBAAa;;oBAE9C,IAAID,kBAAkB;;;oBACtB,MAAMD;;oBAEN1B,kBAAkB2E;;;;;;;;;;IAEtB"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { shouldReconcileWatchNotification } from './lib/watch-notification.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch prepares the index for snapshot queries; filesystem events request debounced builds\n// and periodic reconciliation recovers missed notifications.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\n/** Options for the root {@link runWatch} API. */\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n /** Caller abort cancels provider work, skips final preparation, and resolves after cleanup. */\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\nfunction isCallerAbort(signal: AbortSignal | undefined, reason: unknown): boolean {\n return signal?.aborted === true && Object.is(reason, signal.reason);\n}\n\nfunction errorMessage(reason: unknown): string {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\n/**\n * Runs in the foreground. Caller abort waits for active native work and cleanup; SIGINT/SIGTERM\n * also run one final freshness pass. Cleanup failures reject either shutdown path.\n */\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n if (opts.signal?.aborted) return;\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n let watcher: ReturnType<typeof fsWatch> | undefined;\n let startup = true;\n let startupNotification = false;\n let scheduleReconcile: (() => void) | undefined;\n let stopping = false;\n let callerCanceled = false;\n try {\n opts.signal?.throwIfAborted();\n // Subscribe before the first build. Native watchers have no ready barrier, so startup also\n // performs an unconditional catch-up pass after the initial build.\n watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (!shouldReconcileWatchNotification(filename)) return;\n if (startup) startupNotification = true;\n else scheduleReconcile?.();\n });\n watcher.on('error', (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n\n const startupEvents: Array<{ parsed: number; total: number; warnings: string[] }> = [];\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg, { signal: opts.signal });\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n\n startupEvents.push({ parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n // Always reopen once after subscribing, then drain notifications observed while startup was\n // in progress. Each pass closes before the next one starts, so no stores overlap.\n do {\n startupNotification = false;\n opts.signal?.throwIfAborted();\n const { store, parsed, warnings } = await openStore(cfg, { signal: opts.signal });\n try {\n if (parsed > 0 || warnings.length > 0) startupEvents.push({ parsed, total: await docCount(store), warnings });\n } finally {\n await store.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n } while (startupNotification && !opts.signal?.aborted);\n startup = false;\n\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg, { signal: opts.signal });\n try {\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n if (isCallerAbort(opts.signal, err)) return;\n onEvent({ type: 'reconcile-error', message: errorMessage(err) });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error, canceledByCaller = false) => {\n shutdownReason ??= reason ?? null;\n callerCanceled ||= canceledByCaller;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopForCaller);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher?.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason && !callerCanceled) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n const stopForCaller = () => shutdown(undefined, true);\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) stopForCaller();\n else {\n opts.signal?.addEventListener('abort', stopForCaller, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping) {\n for (const event of startupEvents) {\n if (stopping) break;\n if (event.warnings.length > 0 || event.parsed > 0) onEvent({ type: 'reconciled', ...event });\n }\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n const canceledByCaller = isCallerAbort(opts.signal, err);\n watcher?.close();\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n if (canceledByCaller) return;\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["runWatch","DEBOUNCE_MS","isCallerAbort","signal","reason","aborted","Object","is","errorMessage","Error","message","String","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","claimIntervalMs","rootDir","configDir","claimFailure","requestShutdown","claim","claimCloseStarted","closeClaim","watcher","startup","startupNotification","scheduleReconcile","stopping","callerCanceled","startupEvents","initialStore","dbPath","initialWarnings","initialParsed","initialTotal","store","parsed","warnings","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","reconcileTimer","err","canceledByCaller","closeError","heartbeatIntervalMs","WATCH_HEARTBEAT_INTERVAL_MS","Number","isFinite","SenseError","Math","min","baseDir","startWatchClaim","force","close","throwIfAborted","fsWatch","recursive","_event","filename","shouldReconcileWatchNotification","on","openStore","length","docCount","push","total","alwaysEmit","type","then","again","clearTimeout","setTimeout","setInterval","guardedTick","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","shutdownTask","finalError","finalStore","process","off","stopNormally","removeEventListener","stopForCaller","clearInterval","AggregateError","undefined","once","addEventListener","event"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/workers/parse.ts"],"sourcesContent":["import Tinypool from 'tinypool';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport { FEATURES } from '../features/index.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { FileStat } from '../scan/list.ts';\nimport { featuresForFile } from '../scan/reparse.ts';\nimport type { WorkerErrorPayload } from '../scan/worker-error.ts';\nimport { serializeError } from '../scan/worker-error.ts';\n\n// Constant for the whole dispatch, so it crosses once per worker instead of once per task. A\n// Feature carries closures and cannot cross the thread boundary; its name can, and the registry here resolves it back.\nexport interface ParseWorkerData {\n cfg: Config;\n featureNames: FeatureName[];\n}\n\n// Each task is a small file batch. Results stay per-file so one failure does not stop the\n// remaining files in that batch, and no token tree crosses the thread boundary.\nexport type ParseTask = FileStat[];\n\n// parseMs is the worker's own hrtime for parseFile, excluding dispatch and the return clone --\n// what separates it from the pool's dispatch-to-drain `parse` stage.\nexport type ParseFileResult = { ok: true; doc: ParsedDoc; warnings: string[]; parseMs: number } | { ok: false; error: WorkerErrorPayload };\nexport type ParseTaskResult = ParseFileResult[];\n\n// Read once per worker, not per task.\nconst { cfg, featureNames } = Tinypool.workerData as ParseWorkerData;\n// Filtering the registry (rather than mapping the names) keeps registry order, which is the\n// order `extracted` keys land in on the serial path.\nconst selected = FEATURES.filter((feature) => featureNames.includes(feature.name));\n\nexport default function parseTask(files: ParseTask): ParseTaskResult {\n return files.map((file): ParseFileResult => {\n try {\n const start = process.hrtime.bigint();\n const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);\n const parseMs = Number(process.hrtime.bigint() - start) / 1e6;\n return { ok: true, doc, warnings, parseMs };\n } catch (err) {\n return { ok: false, error: serializeError(err) };\n }\n });\n}\n"],"names":["parseTask","Tinypool","workerData","cfg","featureNames","selected","FEATURES","filter","feature","includes","name","files","map","file","start","process","hrtime","bigint","parseFile","featuresForFile","doc","warnings","parseMs","Number","ok","err","error","serializeError"],"mappings":";;;;+BAgCA;;;eAAwBA;;;+DAhCH;uBAEI;wBAEC;yBAEM;6BAED;;;;;;AAkB/B,sCAAsC;AACtC,IAA8BC,uBAAAA,iBAAQ,CAACC,UAAU,EAAzCC,MAAsBF,qBAAtBE,KAAKC,eAAiBH,qBAAjBG;AACb,4FAA4F;AAC5F,qDAAqD;AACrD,IAAMC,WAAWC,iBAAQ,CAACC,MAAM,CAAC,SAACC;WAAYJ,aAAaK,QAAQ,CAACD,QAAQE,IAAI;;AAEjE,SAASV,UAAUW,KAAgB;IAChD,OAAOA,MAAMC,GAAG,CAAC,SAACC;QAChB,IAAI;YACF,IAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;YACnC,IAA0BC,aAAAA,IAAAA,mBAAS,EAACL,MAAMM,IAAAA,0BAAe,EAACd,UAAUF,KAAKU,OAAOV,MAAxEiB,MAAkBF,WAAlBE,KAAKC,WAAaH,WAAbG;YACb,IAAMC,UAAUC,OAAOR,QAAQC,MAAM,CAACC,MAAM,KAAKH,SAAS;YAC1D,OAAO;gBAAEU,IAAI;gBAAMJ,KAAAA;gBAAKC,UAAAA;gBAAUC,SAAAA;YAAQ;QAC5C,EAAE,OAAOG,KAAK;YACZ,OAAO;gBAAED,IAAI;gBAAOE,OAAOC,IAAAA,6BAAc,EAACF;YAAK;QACjD;IACF;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/workers/parse.ts"],"sourcesContent":["import Tinypool from 'tinypool';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport { FEATURES } from '../features/index.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { FileStat } from '../scan/list.ts';\nimport { featuresForFile } from '../scan/reparse.ts';\nimport type { WorkerErrorPayload } from '../scan/worker-error.ts';\nimport { serializeError } from '../scan/worker-error.ts';\n\n// Constant for the whole dispatch, so it crosses once per worker instead of once per task. A\n// Feature carries closures and cannot cross the thread boundary; its name can, and the registry here resolves it back.\nexport interface ParseWorkerData {\n cfg: Config;\n featureNames: FeatureName[];\n}\n\n// Each task is a small file batch. Results stay per-file so one failure does not stop the\n// remaining files in that batch, and no token tree crosses the thread boundary.\nexport type ParseTask = FileStat[];\n\n// parseMs is the worker's own hrtime for parseFile, excluding dispatch and the return clone --\n// what separates it from the pool's dispatch-to-drain `parse` stage.\nexport type ParseFileResult = { ok: true; doc: ParsedDoc; warnings: string[]; parseMs: number } | { ok: false; error: WorkerErrorPayload };\nexport type ParseTaskResult = ParseFileResult[];\n\n// Read once per worker, not per task.\nconst { cfg, featureNames } = Tinypool.workerData as ParseWorkerData;\n// Filtering the registry (rather than mapping the names) keeps registry order, which is the\n// order `extracted` keys land in on the serial path.\nconst selected = FEATURES.filter((feature) => featureNames.includes(feature.name));\n\nexport default function parseTask(files: ParseTask): ParseTaskResult {\n return files.map((file): ParseFileResult => {\n try {\n const start = process.hrtime.bigint();\n const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);\n const parseMs = Number(process.hrtime.bigint() - start) / 1e6;\n return { ok: true, doc, warnings, parseMs };\n } catch (err) {\n return { ok: false, error: serializeError(err) };\n }\n });\n}\n"],"names":["parseTask","Tinypool","workerData","cfg","featureNames","selected","FEATURES","filter","feature","includes","name","files","map","file","start","process","hrtime","bigint","parseFile","featuresForFile","doc","warnings","parseMs","Number","ok","err","error","serializeError"],"mappings":";;;;+BAgCA;;;eAAwBA;;;+DAhCH;uBAEI;wBAEC;yBAEM;6BAED;;;;;;AAkB/B,sCAAsC;AACtC,IAA8BC,uBAAAA,iBAAQ,CAACC,UAAU,EAAzCC,MAAsBF,qBAAtBE,KAAKC,eAAiBH,qBAAjBG;AACb,4FAA4F;AAC5F,qDAAqD;AACrD,IAAMC,WAAWC,iBAAQ,CAACC,MAAM,CAAC,SAACC;WAAYJ,aAAaK,QAAQ,CAACD,QAAQE,IAAI;;AAEjE,SAASV,UAAUW,KAAgB;IAChD,OAAOA,MAAMC,GAAG,CAAC,SAACC;QAChB,IAAI;YACF,IAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;YACnC,IAA0BC,aAAAA,IAAAA,mBAAS,EAACL,MAAMM,IAAAA,0BAAe,EAACd,UAAUF,KAAKU,OAAOV,MAAxEiB,MAAkBF,WAAlBE,KAAKC,WAAaH,WAAbG;YACb,IAAMC,UAAUC,OAAOR,QAAQC,MAAM,CAACC,MAAM,KAAKH,SAAS;YAC1D,OAAO;gBAAEU,IAAI;gBAAMJ,KAAAA;gBAAKC,UAAAA;gBAAUC,SAAAA;YAAQ;QAC5C,EAAE,OAAOG,KAAK;YACZ,OAAO;gBAAED,IAAI;gBAAOE,OAAOC,IAAAA,6BAAc,EAACF;YAAK;QACjD;IACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/workers/watch-heartbeat.ts"],"sourcesContent":["import { parentPort, workerData } from 'node:worker_threads';\nimport { SenseError } from '../errors.ts';\nimport { serializeError } from '../scan/worker-error.ts';\nimport { WatchClaimDatabase, type WatchClaimWorkerData, type WatchClaimWorkerMessage } from '../watch-claim.ts';\n\nfunction requireParentPort() {\n const port = parentPort;\n if (!port) throw new Error('watch heartbeat worker requires a parent port');\n return port;\n}\n\nconst port = requireParentPort();\nconst data = workerData as WatchClaimWorkerData;\nlet database: WatchClaimDatabase | undefined;\nlet timer: NodeJS.Timeout | undefined;\nlet stopping = false;\nlet renewing = Promise.resolve();\n\nfunction send(message: WatchClaimWorkerMessage): void {\n port.postMessage(message);\n}\n\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction closeDatabase(): Error | undefined {\n if (timer) clearInterval(timer);\n timer = undefined;\n try {\n database?.close();\n database = undefined;\n return undefined;\n } catch (err) {\n return asError(err);\n }\n}\n\nfunction addFailure(primary: Error, secondary: Error | undefined, message: string): Error {\n return secondary ? new AggregateError([primary, secondary], message) : primary;\n}\n\nfunction fail(err: unknown): void {\n if (stopping) return;\n stopping = true;\n let failure = asError(err);\n try {\n database?.release(data.token);\n } catch (releaseError) {\n failure = addFailure(failure, asError(releaseError), 'watch heartbeat and claim release both failed');\n }\n const closeError = closeDatabase();\n failure = addFailure(failure, closeError, 'watch heartbeat and database cleanup both failed');\n try {\n send({ type: 'failure', error: serializeError(failure) });\n } finally {\n port.close();\n }\n}\n\nasync function stop(): Promise<void> {\n if (stopping) return;\n stopping = true;\n if (timer) clearInterval(timer);\n let failure: Error | undefined;\n try {\n await renewing;\n if (!database?.release(data.token)) throw new SenseError('WATCH_ACTIVE', 'watch ownership was replaced by another watcher');\n } catch (err) {\n failure = asError(err);\n } finally {\n const closeError = closeDatabase();\n if (closeError) failure = failure ? addFailure(failure, closeError, 'watch heartbeat shutdown and database cleanup both failed') : closeError;\n try {\n if (failure) send({ type: 'failure', error: serializeError(failure) });\n else send({ type: 'stopped' });\n } finally {\n port.close();\n }\n }\n}\n\nasync function main(): Promise<void> {\n try {\n database = new WatchClaimDatabase(data.configDir);\n await database.acquire(data.token, data.pid, data.force);\n send({ type: 'acquired' });\n timer = setInterval(() => {\n if (stopping) return;\n renewing = renewing.then(() => {\n if (!database?.renew(data.token)) throw new SenseError('WATCH_ACTIVE', 'watch ownership was replaced by another watcher');\n });\n renewing.catch(fail);\n }, data.heartbeatIntervalMs);\n port.on('message', (message: { type?: string }) => {\n if (message.type === 'stop') void stop();\n });\n } catch (err) {\n fail(err);\n }\n}\n\nvoid main();\n"],"names":["requireParentPort","port","parentPort","Error","data","workerData","database","timer","stopping","renewing","Promise","resolve","send","message","postMessage","asError","value","String","closeDatabase","clearInterval","undefined","close","err","addFailure","primary","secondary","AggregateError","fail","failure","release","token","releaseError","closeError","type","error","serializeError","stop","SenseError","main","WatchClaimDatabase","configDir","acquire","pid","force","setInterval","then","renew","catch","heartbeatIntervalMs","on"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/workers/watch-heartbeat.ts"],"sourcesContent":["import { parentPort, workerData } from 'node:worker_threads';\nimport { SenseError } from '../errors.ts';\nimport { serializeError } from '../scan/worker-error.ts';\nimport { WatchClaimDatabase, type WatchClaimWorkerData, type WatchClaimWorkerMessage } from '../watch-claim.ts';\n\nfunction requireParentPort() {\n const port = parentPort;\n if (!port) throw new Error('watch heartbeat worker requires a parent port');\n return port;\n}\n\nconst port = requireParentPort();\nconst data = workerData as WatchClaimWorkerData;\nlet database: WatchClaimDatabase | undefined;\nlet timer: NodeJS.Timeout | undefined;\nlet stopping = false;\nlet renewing = Promise.resolve();\n\nfunction send(message: WatchClaimWorkerMessage): void {\n port.postMessage(message);\n}\n\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction closeDatabase(): Error | undefined {\n if (timer) clearInterval(timer);\n timer = undefined;\n try {\n database?.close();\n database = undefined;\n return undefined;\n } catch (err) {\n return asError(err);\n }\n}\n\nfunction addFailure(primary: Error, secondary: Error | undefined, message: string): Error {\n return secondary ? new AggregateError([primary, secondary], message) : primary;\n}\n\nfunction fail(err: unknown): void {\n if (stopping) return;\n stopping = true;\n let failure = asError(err);\n try {\n database?.release(data.token);\n } catch (releaseError) {\n failure = addFailure(failure, asError(releaseError), 'watch heartbeat and claim release both failed');\n }\n const closeError = closeDatabase();\n failure = addFailure(failure, closeError, 'watch heartbeat and database cleanup both failed');\n try {\n send({ type: 'failure', error: serializeError(failure) });\n } finally {\n port.close();\n }\n}\n\nasync function stop(): Promise<void> {\n if (stopping) return;\n stopping = true;\n if (timer) clearInterval(timer);\n let failure: Error | undefined;\n try {\n await renewing;\n if (!database?.release(data.token)) throw new SenseError('WATCH_ACTIVE', 'watch ownership was replaced by another watcher');\n } catch (err) {\n failure = asError(err);\n } finally {\n const closeError = closeDatabase();\n if (closeError) failure = failure ? addFailure(failure, closeError, 'watch heartbeat shutdown and database cleanup both failed') : closeError;\n try {\n if (failure) send({ type: 'failure', error: serializeError(failure) });\n else send({ type: 'stopped' });\n } finally {\n port.close();\n }\n }\n}\n\nasync function main(): Promise<void> {\n try {\n database = new WatchClaimDatabase(data.configDir);\n await database.acquire(data.token, data.pid, data.force);\n send({ type: 'acquired' });\n timer = setInterval(() => {\n if (stopping) return;\n renewing = renewing.then(() => {\n if (!database?.renew(data.token)) throw new SenseError('WATCH_ACTIVE', 'watch ownership was replaced by another watcher');\n });\n renewing.catch(fail);\n }, data.heartbeatIntervalMs);\n port.on('message', (message: { type?: string }) => {\n if (message.type === 'stop') void stop();\n });\n } catch (err) {\n fail(err);\n }\n}\n\nvoid main();\n"],"names":["requireParentPort","port","parentPort","Error","data","workerData","database","timer","stopping","renewing","Promise","resolve","send","message","postMessage","asError","value","String","closeDatabase","clearInterval","undefined","close","err","addFailure","primary","secondary","AggregateError","fail","failure","release","token","releaseError","closeError","type","error","serializeError","stop","SenseError","main","WatchClaimDatabase","configDir","acquire","pid","force","setInterval","then","renew","catch","heartbeatIntervalMs","on"],"mappings":"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"}