sensemaking 0.17.0 → 0.18.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 (429) hide show
  1. package/README.md +15 -13
  2. package/dist/cjs/cli/map.js +147 -6
  3. package/dist/cjs/cli/map.js.map +1 -1
  4. package/dist/cjs/cli/named.js +20 -12
  5. package/dist/cjs/cli/named.js.map +1 -1
  6. package/dist/cjs/cli/path.js +190 -29
  7. package/dist/cjs/cli/path.js.map +1 -1
  8. package/dist/cjs/cli/peek.js +148 -7
  9. package/dist/cjs/cli/peek.js.map +1 -1
  10. package/dist/cjs/cli/related.js +5 -5
  11. package/dist/cjs/cli/related.js.map +1 -1
  12. package/dist/cjs/cli/search.js +5 -5
  13. package/dist/cjs/cli/search.js.map +1 -1
  14. package/dist/cjs/cli/shared.d.cts +3 -3
  15. package/dist/cjs/cli/shared.d.ts +3 -3
  16. package/dist/cjs/cli/shared.js +156 -58
  17. package/dist/cjs/cli/shared.js.map +1 -1
  18. package/dist/cjs/cli/sql.js +151 -10
  19. package/dist/cjs/cli/sql.js.map +1 -1
  20. package/dist/cjs/cli/status.js +187 -62
  21. package/dist/cjs/cli/status.js.map +1 -1
  22. package/dist/cjs/commands/map.d.cts +2 -2
  23. package/dist/cjs/commands/map.d.ts +2 -2
  24. package/dist/cjs/commands/map.js +373 -67
  25. package/dist/cjs/commands/map.js.map +1 -1
  26. package/dist/cjs/commands/peek.d.cts +2 -2
  27. package/dist/cjs/commands/peek.d.ts +2 -2
  28. package/dist/cjs/commands/peek.js +321 -79
  29. package/dist/cjs/commands/peek.js.map +1 -1
  30. package/dist/cjs/commands/related.d.cts +2 -2
  31. package/dist/cjs/commands/related.d.ts +2 -2
  32. package/dist/cjs/commands/related.js +55 -10
  33. package/dist/cjs/commands/related.js.map +1 -1
  34. package/dist/cjs/commands/scope.d.cts +7 -7
  35. package/dist/cjs/commands/scope.d.ts +7 -7
  36. package/dist/cjs/commands/scope.js +382 -45
  37. package/dist/cjs/commands/scope.js.map +1 -1
  38. package/dist/cjs/commands/search.d.cts +2 -2
  39. package/dist/cjs/commands/search.d.ts +2 -2
  40. package/dist/cjs/commands/search.js +292 -99
  41. package/dist/cjs/commands/search.js.map +1 -1
  42. package/dist/cjs/commands/signals.d.cts +4 -10
  43. package/dist/cjs/commands/signals.d.ts +4 -10
  44. package/dist/cjs/commands/signals.js +91 -45
  45. package/dist/cjs/commands/signals.js.map +1 -1
  46. package/dist/cjs/commands/status.d.cts +2 -2
  47. package/dist/cjs/commands/status.d.ts +2 -2
  48. package/dist/cjs/commands/status.js +247 -10
  49. package/dist/cjs/commands/status.js.map +1 -1
  50. package/dist/cjs/config/access.d.cts +1 -0
  51. package/dist/cjs/config/access.d.ts +1 -0
  52. package/dist/cjs/config/access.js +7 -0
  53. package/dist/cjs/config/access.js.map +1 -1
  54. package/dist/cjs/config/index.d.cts +1 -1
  55. package/dist/cjs/config/index.d.ts +1 -1
  56. package/dist/cjs/config/index.js +3 -0
  57. package/dist/cjs/config/index.js.map +1 -1
  58. package/dist/cjs/config/types.d.cts +1 -0
  59. package/dist/cjs/config/types.d.ts +1 -0
  60. package/dist/cjs/config/types.js.map +1 -1
  61. package/dist/cjs/config/validate.js +10 -1
  62. package/dist/cjs/config/validate.js.map +1 -1
  63. package/dist/cjs/embed/distribution.d.cts +4 -0
  64. package/dist/cjs/embed/distribution.d.ts +4 -0
  65. package/dist/cjs/embed/distribution.js +186 -0
  66. package/dist/cjs/embed/distribution.js.map +1 -0
  67. package/dist/cjs/embed/langfit.d.cts +2 -5
  68. package/dist/cjs/embed/langfit.d.ts +2 -5
  69. package/dist/cjs/embed/langfit.js +73 -63
  70. package/dist/cjs/embed/langfit.js.map +1 -1
  71. package/dist/cjs/embed/query.d.cts +6 -13
  72. package/dist/cjs/embed/query.d.ts +6 -13
  73. package/dist/cjs/embed/query.js +56 -168
  74. package/dist/cjs/embed/query.js.map +1 -1
  75. package/dist/cjs/embed/static.js +13 -3
  76. package/dist/cjs/embed/static.js.map +1 -1
  77. package/dist/cjs/embed/types.d.cts +1 -0
  78. package/dist/cjs/embed/types.d.ts +1 -0
  79. package/dist/cjs/embed/types.js +11 -2
  80. package/dist/cjs/embed/types.js.map +1 -1
  81. package/dist/cjs/errors.d.cts +1 -1
  82. package/dist/cjs/errors.d.ts +1 -1
  83. package/dist/cjs/errors.js.map +1 -1
  84. package/dist/cjs/features/embed.js +218 -11
  85. package/dist/cjs/features/embed.js.map +1 -1
  86. package/dist/cjs/features/index.d.cts +1 -1
  87. package/dist/cjs/features/index.d.ts +1 -1
  88. package/dist/cjs/features/index.js +6 -0
  89. package/dist/cjs/features/index.js.map +1 -1
  90. package/dist/cjs/features/links.d.cts +7 -2
  91. package/dist/cjs/features/links.d.ts +7 -2
  92. package/dist/cjs/features/links.js +710 -198
  93. package/dist/cjs/features/links.js.map +1 -1
  94. package/dist/cjs/features/rank.js +231 -31
  95. package/dist/cjs/features/rank.js.map +1 -1
  96. package/dist/cjs/features/sections.js +218 -8
  97. package/dist/cjs/features/sections.js.map +1 -1
  98. package/dist/cjs/features/tags.js +236 -40
  99. package/dist/cjs/features/tags.js.map +1 -1
  100. package/dist/cjs/features/types.d.cts +9 -5
  101. package/dist/cjs/features/types.d.ts +9 -5
  102. package/dist/cjs/graph/traverse.d.cts +2 -2
  103. package/dist/cjs/graph/traverse.d.ts +2 -2
  104. package/dist/cjs/graph/traverse.js +321 -42
  105. package/dist/cjs/graph/traverse.js.map +1 -1
  106. package/dist/cjs/index.d.cts +3 -2
  107. package/dist/cjs/index.d.ts +3 -2
  108. package/dist/cjs/index.js +2 -2
  109. package/dist/cjs/index.js.map +1 -1
  110. package/dist/cjs/lib/guarded-tick.d.cts +1 -0
  111. package/dist/cjs/lib/guarded-tick.d.ts +1 -0
  112. package/dist/cjs/lib/guarded-tick.js +19 -0
  113. package/dist/cjs/lib/guarded-tick.js.map +1 -0
  114. package/dist/cjs/output/column-hint.d.cts +1 -2
  115. package/dist/cjs/output/column-hint.d.ts +1 -2
  116. package/dist/cjs/output/column-hint.js +1 -4
  117. package/dist/cjs/output/column-hint.js.map +1 -1
  118. package/dist/cjs/output/output.d.cts +2 -1
  119. package/dist/cjs/output/output.d.ts +2 -1
  120. package/dist/cjs/output/output.js +515 -97
  121. package/dist/cjs/output/output.js.map +1 -1
  122. package/dist/cjs/scan/frontmatter.js.map +1 -1
  123. package/dist/cjs/scan/index.js.map +1 -1
  124. package/dist/cjs/scan/reparse.d.cts +10 -0
  125. package/dist/cjs/scan/reparse.d.ts +10 -0
  126. package/dist/cjs/scan/reparse.js +104 -0
  127. package/dist/cjs/scan/reparse.js.map +1 -0
  128. package/dist/cjs/store/duckdb/batch.d.cts +16 -0
  129. package/dist/cjs/store/duckdb/batch.d.ts +16 -0
  130. package/dist/cjs/store/duckdb/batch.js +208 -0
  131. package/dist/cjs/store/duckdb/batch.js.map +1 -0
  132. package/dist/cjs/store/duckdb/connection.d.cts +5 -0
  133. package/dist/cjs/store/duckdb/connection.d.ts +5 -0
  134. package/dist/cjs/store/duckdb/connection.js +772 -0
  135. package/dist/cjs/store/duckdb/connection.js.map +1 -0
  136. package/dist/cjs/store/duckdb/lexical.d.cts +10 -0
  137. package/dist/cjs/store/duckdb/lexical.d.ts +10 -0
  138. package/dist/cjs/store/duckdb/lexical.js +502 -0
  139. package/dist/cjs/store/duckdb/lexical.js.map +1 -0
  140. package/dist/cjs/store/duckdb/native.d.cts +3 -0
  141. package/dist/cjs/store/duckdb/native.d.ts +3 -0
  142. package/dist/cjs/store/duckdb/native.js +360 -0
  143. package/dist/cjs/store/duckdb/native.js.map +1 -0
  144. package/dist/cjs/store/duckdb/open.d.cts +12 -0
  145. package/dist/cjs/store/duckdb/open.d.ts +12 -0
  146. package/dist/cjs/store/duckdb/open.js +501 -0
  147. package/dist/cjs/store/duckdb/open.js.map +1 -0
  148. package/dist/cjs/store/duckdb/reconcile.d.cts +6 -0
  149. package/dist/cjs/store/duckdb/reconcile.d.ts +6 -0
  150. package/dist/cjs/store/duckdb/reconcile.js +753 -0
  151. package/dist/cjs/store/duckdb/reconcile.js.map +1 -0
  152. package/dist/cjs/store/duckdb/sql-functions.d.cts +2 -0
  153. package/dist/cjs/store/duckdb/sql-functions.d.ts +2 -0
  154. package/dist/cjs/store/duckdb/sql-functions.js +203 -0
  155. package/dist/cjs/store/duckdb/sql-functions.js.map +1 -0
  156. package/dist/cjs/store/duckdb/store.d.cts +5 -0
  157. package/dist/cjs/store/duckdb/store.d.ts +5 -0
  158. package/dist/cjs/store/duckdb/store.js +504 -0
  159. package/dist/cjs/store/duckdb/store.js.map +1 -0
  160. package/dist/cjs/store/duckdb/vectors.d.cts +9 -0
  161. package/dist/cjs/store/duckdb/vectors.d.ts +9 -0
  162. package/dist/cjs/store/duckdb/vectors.js +502 -0
  163. package/dist/cjs/store/duckdb/vectors.js.map +1 -0
  164. package/dist/cjs/store/index.d.cts +7 -0
  165. package/dist/cjs/store/index.d.ts +7 -0
  166. package/dist/cjs/store/index.js +201 -0
  167. package/dist/cjs/store/index.js.map +1 -0
  168. package/dist/cjs/store/meta.d.cts +3 -0
  169. package/dist/cjs/store/meta.d.ts +3 -0
  170. package/dist/cjs/store/meta.js +215 -0
  171. package/dist/cjs/store/meta.js.map +1 -0
  172. package/dist/cjs/store/shared.d.cts +5 -0
  173. package/dist/cjs/store/shared.d.ts +5 -0
  174. package/dist/cjs/store/shared.js +252 -0
  175. package/dist/cjs/store/shared.js.map +1 -0
  176. package/dist/cjs/store/sql-functions.d.cts +2 -0
  177. package/dist/cjs/store/sql-functions.d.ts +2 -0
  178. package/dist/cjs/store/sql-functions.js +48 -0
  179. package/dist/cjs/store/sql-functions.js.map +1 -0
  180. package/dist/cjs/store/sqlite/connection.d.cts +3 -0
  181. package/dist/cjs/store/sqlite/connection.d.ts +3 -0
  182. package/dist/cjs/store/sqlite/connection.js +516 -0
  183. package/dist/cjs/store/sqlite/connection.js.map +1 -0
  184. package/dist/cjs/store/sqlite/lexical.d.cts +2 -0
  185. package/dist/cjs/store/sqlite/lexical.d.ts +2 -0
  186. package/dist/cjs/store/sqlite/lexical.js +177 -0
  187. package/dist/cjs/store/sqlite/lexical.js.map +1 -0
  188. package/dist/cjs/{db → store/sqlite}/open.d.cts +5 -5
  189. package/dist/cjs/{db → store/sqlite}/open.d.ts +5 -5
  190. package/dist/cjs/store/sqlite/open.js +604 -0
  191. package/dist/cjs/store/sqlite/open.js.map +1 -0
  192. package/dist/cjs/store/sqlite/reconcile.d.cts +10 -0
  193. package/dist/cjs/store/sqlite/reconcile.d.ts +10 -0
  194. package/dist/cjs/store/sqlite/reconcile.js +1020 -0
  195. package/dist/cjs/store/sqlite/reconcile.js.map +1 -0
  196. package/dist/cjs/store/sqlite/sql-functions.js +34 -0
  197. package/dist/cjs/store/sqlite/sql-functions.js.map +1 -0
  198. package/dist/cjs/store/sqlite/store.d.cts +5 -0
  199. package/dist/cjs/store/sqlite/store.d.ts +5 -0
  200. package/dist/cjs/store/sqlite/store.js +588 -0
  201. package/dist/cjs/store/sqlite/store.js.map +1 -0
  202. package/dist/cjs/store/sqlite/vectors.d.cts +13 -0
  203. package/dist/cjs/store/sqlite/vectors.d.ts +13 -0
  204. package/dist/cjs/store/sqlite/vectors.js +470 -0
  205. package/dist/cjs/store/sqlite/vectors.js.map +1 -0
  206. package/dist/cjs/store/transaction.d.cts +5 -0
  207. package/dist/cjs/store/transaction.d.ts +5 -0
  208. package/dist/cjs/store/transaction.js +297 -0
  209. package/dist/cjs/store/transaction.js.map +1 -0
  210. package/dist/cjs/store/types.d.cts +91 -0
  211. package/dist/cjs/store/types.d.ts +91 -0
  212. package/dist/cjs/store/types.js +15 -0
  213. package/dist/cjs/store/types.js.map +1 -0
  214. package/dist/cjs/watch.d.cts +3 -0
  215. package/dist/cjs/watch.d.ts +3 -0
  216. package/dist/cjs/watch.js +215 -73
  217. package/dist/cjs/watch.js.map +1 -1
  218. package/dist/esm/cli/map.js +5 -5
  219. package/dist/esm/cli/map.js.map +1 -1
  220. package/dist/esm/cli/named.js +3 -3
  221. package/dist/esm/cli/named.js.map +1 -1
  222. package/dist/esm/cli/path.js +6 -5
  223. package/dist/esm/cli/path.js.map +1 -1
  224. package/dist/esm/cli/peek.js +5 -5
  225. package/dist/esm/cli/peek.js.map +1 -1
  226. package/dist/esm/cli/related.js +3 -3
  227. package/dist/esm/cli/related.js.map +1 -1
  228. package/dist/esm/cli/search.js +2 -2
  229. package/dist/esm/cli/search.js.map +1 -1
  230. package/dist/esm/cli/shared.d.ts +3 -3
  231. package/dist/esm/cli/shared.js +17 -17
  232. package/dist/esm/cli/shared.js.map +1 -1
  233. package/dist/esm/cli/sql.js +2 -2
  234. package/dist/esm/cli/sql.js.map +1 -1
  235. package/dist/esm/cli/status.js +25 -24
  236. package/dist/esm/cli/status.js.map +1 -1
  237. package/dist/esm/commands/map.d.ts +2 -2
  238. package/dist/esm/commands/map.js +51 -41
  239. package/dist/esm/commands/map.js.map +1 -1
  240. package/dist/esm/commands/peek.d.ts +2 -2
  241. package/dist/esm/commands/peek.js +58 -50
  242. package/dist/esm/commands/peek.js.map +1 -1
  243. package/dist/esm/commands/related.d.ts +2 -2
  244. package/dist/esm/commands/related.js +9 -9
  245. package/dist/esm/commands/related.js.map +1 -1
  246. package/dist/esm/commands/scope.d.ts +7 -7
  247. package/dist/esm/commands/scope.js +28 -17
  248. package/dist/esm/commands/scope.js.map +1 -1
  249. package/dist/esm/commands/search.d.ts +2 -2
  250. package/dist/esm/commands/search.js +42 -35
  251. package/dist/esm/commands/search.js.map +1 -1
  252. package/dist/esm/commands/signals.d.ts +4 -10
  253. package/dist/esm/commands/signals.js +17 -13
  254. package/dist/esm/commands/signals.js.map +1 -1
  255. package/dist/esm/commands/status.d.ts +2 -2
  256. package/dist/esm/commands/status.js +13 -7
  257. package/dist/esm/commands/status.js.map +1 -1
  258. package/dist/esm/config/access.d.ts +1 -0
  259. package/dist/esm/config/access.js +5 -0
  260. package/dist/esm/config/access.js.map +1 -1
  261. package/dist/esm/config/index.d.ts +1 -1
  262. package/dist/esm/config/index.js +1 -1
  263. package/dist/esm/config/index.js.map +1 -1
  264. package/dist/esm/config/types.d.ts +1 -0
  265. package/dist/esm/config/types.js.map +1 -1
  266. package/dist/esm/config/validate.js +10 -1
  267. package/dist/esm/config/validate.js.map +1 -1
  268. package/dist/esm/embed/distribution.d.ts +4 -0
  269. package/dist/esm/embed/distribution.js +11 -0
  270. package/dist/esm/embed/distribution.js.map +1 -0
  271. package/dist/esm/embed/langfit.d.ts +2 -5
  272. package/dist/esm/embed/langfit.js +13 -13
  273. package/dist/esm/embed/langfit.js.map +1 -1
  274. package/dist/esm/embed/query.d.ts +6 -13
  275. package/dist/esm/embed/query.js +23 -84
  276. package/dist/esm/embed/query.js.map +1 -1
  277. package/dist/esm/embed/static.js +6 -1
  278. package/dist/esm/embed/static.js.map +1 -1
  279. package/dist/esm/embed/types.d.ts +1 -0
  280. package/dist/esm/embed/types.js +5 -3
  281. package/dist/esm/embed/types.js.map +1 -1
  282. package/dist/esm/errors.d.ts +1 -1
  283. package/dist/esm/errors.js.map +1 -1
  284. package/dist/esm/features/embed.js +22 -10
  285. package/dist/esm/features/embed.js.map +1 -1
  286. package/dist/esm/features/index.d.ts +1 -1
  287. package/dist/esm/features/index.js +1 -1
  288. package/dist/esm/features/index.js.map +1 -1
  289. package/dist/esm/features/links.d.ts +7 -2
  290. package/dist/esm/features/links.js +124 -97
  291. package/dist/esm/features/links.js.map +1 -1
  292. package/dist/esm/features/rank.js +15 -8
  293. package/dist/esm/features/rank.js.map +1 -1
  294. package/dist/esm/features/sections.js +20 -7
  295. package/dist/esm/features/sections.js.map +1 -1
  296. package/dist/esm/features/tags.js +19 -18
  297. package/dist/esm/features/tags.js.map +1 -1
  298. package/dist/esm/features/types.d.ts +9 -5
  299. package/dist/esm/features/types.js +1 -1
  300. package/dist/esm/features/types.js.map +1 -1
  301. package/dist/esm/graph/traverse.d.ts +2 -2
  302. package/dist/esm/graph/traverse.js +26 -23
  303. package/dist/esm/graph/traverse.js.map +1 -1
  304. package/dist/esm/index.d.ts +3 -2
  305. package/dist/esm/index.js +1 -1
  306. package/dist/esm/index.js.map +1 -1
  307. package/dist/esm/lib/guarded-tick.d.ts +1 -0
  308. package/dist/esm/lib/guarded-tick.js +8 -0
  309. package/dist/esm/lib/guarded-tick.js.map +1 -0
  310. package/dist/esm/output/column-hint.d.ts +1 -2
  311. package/dist/esm/output/column-hint.js +3 -2
  312. package/dist/esm/output/column-hint.js.map +1 -1
  313. package/dist/esm/output/output.d.ts +2 -1
  314. package/dist/esm/output/output.js +27 -23
  315. package/dist/esm/output/output.js.map +1 -1
  316. package/dist/esm/scan/frontmatter.js +1 -1
  317. package/dist/esm/scan/frontmatter.js.map +1 -1
  318. package/dist/esm/scan/index.js +1 -1
  319. package/dist/esm/scan/index.js.map +1 -1
  320. package/dist/esm/scan/reparse.d.ts +10 -0
  321. package/dist/esm/scan/reparse.js +30 -0
  322. package/dist/esm/scan/reparse.js.map +1 -0
  323. package/dist/esm/store/duckdb/batch.d.ts +16 -0
  324. package/dist/esm/store/duckdb/batch.js +96 -0
  325. package/dist/esm/store/duckdb/batch.js.map +1 -0
  326. package/dist/esm/store/duckdb/connection.d.ts +5 -0
  327. package/dist/esm/store/duckdb/connection.js +97 -0
  328. package/dist/esm/store/duckdb/connection.js.map +1 -0
  329. package/dist/esm/store/duckdb/lexical.d.ts +10 -0
  330. package/dist/esm/store/duckdb/lexical.js +163 -0
  331. package/dist/esm/store/duckdb/lexical.js.map +1 -0
  332. package/dist/esm/store/duckdb/native.d.ts +3 -0
  333. package/dist/esm/store/duckdb/native.js +96 -0
  334. package/dist/esm/store/duckdb/native.js.map +1 -0
  335. package/dist/esm/store/duckdb/open.d.ts +12 -0
  336. package/dist/esm/store/duckdb/open.js +127 -0
  337. package/dist/esm/store/duckdb/open.js.map +1 -0
  338. package/dist/esm/store/duckdb/reconcile.d.ts +6 -0
  339. package/dist/esm/store/duckdb/reconcile.js +141 -0
  340. package/dist/esm/store/duckdb/reconcile.js.map +1 -0
  341. package/dist/esm/store/duckdb/sql-functions.d.ts +2 -0
  342. package/dist/esm/store/duckdb/sql-functions.js +56 -0
  343. package/dist/esm/store/duckdb/sql-functions.js.map +1 -0
  344. package/dist/esm/store/duckdb/store.d.ts +5 -0
  345. package/dist/esm/store/duckdb/store.js +104 -0
  346. package/dist/esm/store/duckdb/store.js.map +1 -0
  347. package/dist/esm/store/duckdb/vectors.d.ts +9 -0
  348. package/dist/esm/store/duckdb/vectors.js +171 -0
  349. package/dist/esm/store/duckdb/vectors.js.map +1 -0
  350. package/dist/esm/store/index.d.ts +7 -0
  351. package/dist/esm/store/index.js +35 -0
  352. package/dist/esm/store/index.js.map +1 -0
  353. package/dist/esm/store/meta.d.ts +3 -0
  354. package/dist/esm/store/meta.js +17 -0
  355. package/dist/esm/store/meta.js.map +1 -0
  356. package/dist/esm/store/shared.d.ts +5 -0
  357. package/dist/esm/store/shared.js +24 -0
  358. package/dist/esm/store/shared.js.map +1 -0
  359. package/dist/esm/store/sql-functions.d.ts +2 -0
  360. package/dist/esm/store/sql-functions.js +29 -0
  361. package/dist/esm/store/sql-functions.js.map +1 -0
  362. package/dist/esm/store/sqlite/connection.d.ts +3 -0
  363. package/dist/esm/store/sqlite/connection.js +50 -0
  364. package/dist/esm/store/sqlite/connection.js.map +1 -0
  365. package/dist/esm/store/sqlite/lexical.d.ts +2 -0
  366. package/dist/esm/store/sqlite/lexical.js +21 -0
  367. package/dist/esm/store/sqlite/lexical.js.map +1 -0
  368. package/dist/esm/{db → store/sqlite}/open.d.ts +5 -5
  369. package/dist/esm/store/sqlite/open.js +193 -0
  370. package/dist/esm/store/sqlite/open.js.map +1 -0
  371. package/dist/esm/store/sqlite/reconcile.d.ts +10 -0
  372. package/dist/esm/{db → store/sqlite}/reconcile.js +101 -99
  373. package/dist/esm/store/sqlite/reconcile.js.map +1 -0
  374. package/dist/esm/store/sqlite/sql-functions.js +23 -0
  375. package/dist/esm/store/sqlite/sql-functions.js.map +1 -0
  376. package/dist/esm/store/sqlite/store.d.ts +5 -0
  377. package/dist/esm/store/sqlite/store.js +82 -0
  378. package/dist/esm/store/sqlite/store.js.map +1 -0
  379. package/dist/esm/store/sqlite/vectors.d.ts +13 -0
  380. package/dist/esm/store/sqlite/vectors.js +90 -0
  381. package/dist/esm/store/sqlite/vectors.js.map +1 -0
  382. package/dist/esm/store/transaction.d.ts +5 -0
  383. package/dist/esm/store/transaction.js +57 -0
  384. package/dist/esm/store/transaction.js.map +1 -0
  385. package/dist/esm/store/types.d.ts +91 -0
  386. package/dist/esm/store/types.js +11 -0
  387. package/dist/esm/store/types.js.map +1 -0
  388. package/dist/esm/watch.d.ts +3 -0
  389. package/dist/esm/watch.js +39 -22
  390. package/dist/esm/watch.js.map +1 -1
  391. package/package.json +3 -1
  392. package/schema.json +5 -0
  393. package/skills/sense/EXAMPLES.md +3 -3
  394. package/skills/sense/SKILL.md +16 -14
  395. package/skills/sense-bases/EXAMPLES.md +2 -2
  396. package/skills/sense-bases/SKILL.md +6 -6
  397. package/skills/sense-setup/EXAMPLES.md +1 -1
  398. package/skills/sense-setup/SKILL.md +5 -4
  399. package/dist/cjs/db/index.d.cts +0 -4
  400. package/dist/cjs/db/index.d.ts +0 -4
  401. package/dist/cjs/db/index.js +0 -42
  402. package/dist/cjs/db/index.js.map +0 -1
  403. package/dist/cjs/db/open.js +0 -230
  404. package/dist/cjs/db/open.js.map +0 -1
  405. package/dist/cjs/db/reconcile.d.cts +0 -10
  406. package/dist/cjs/db/reconcile.d.ts +0 -10
  407. package/dist/cjs/db/reconcile.js +0 -579
  408. package/dist/cjs/db/reconcile.js.map +0 -1
  409. package/dist/cjs/db/shared.d.cts +0 -5
  410. package/dist/cjs/db/shared.d.ts +0 -5
  411. package/dist/cjs/db/shared.js +0 -45
  412. package/dist/cjs/db/shared.js.map +0 -1
  413. package/dist/cjs/db/sql-functions.js +0 -64
  414. package/dist/cjs/db/sql-functions.js.map +0 -1
  415. package/dist/esm/db/index.d.ts +0 -4
  416. package/dist/esm/db/index.js +0 -5
  417. package/dist/esm/db/index.js.map +0 -1
  418. package/dist/esm/db/open.js +0 -165
  419. package/dist/esm/db/open.js.map +0 -1
  420. package/dist/esm/db/reconcile.d.ts +0 -10
  421. package/dist/esm/db/reconcile.js.map +0 -1
  422. package/dist/esm/db/shared.d.ts +0 -5
  423. package/dist/esm/db/shared.js +0 -20
  424. package/dist/esm/db/shared.js.map +0 -1
  425. package/dist/esm/db/sql-functions.js +0 -47
  426. package/dist/esm/db/sql-functions.js.map +0 -1
  427. /package/dist/cjs/{db → store/sqlite}/sql-functions.d.cts +0 -0
  428. /package/dist/cjs/{db → store/sqlite}/sql-functions.d.ts +0 -0
  429. /package/dist/esm/{db → store/sqlite}/sql-functions.d.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport type { Config } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'presets', 'features', 'embed', 'content', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// Exported so a docs test can assert every key here has a schema.json property -- one source\n// of truth for what the embed block accepts, checked against the other for drift.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst KNOWN_CONTENT_KEYS = new Set(['tokenize']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\n// Only the shape is checked here. Whether the linked SQLite accepts the tokenizer is settled\n// by probing it in db.ts, so this never has to carry a table of which version added what.\nfunction validateContentBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content must be an object of { tokenize?: string }`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_CONTENT_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content has unknown key(s) ${unknown.join(', ')}; content takes tokenize`);\n }\n if (block.tokenize !== undefined && (typeof block.tokenize !== 'string' || block.tokenize.trim() === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. \"trigram\" or \"unicode61 tokenchars '-_'\"`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that\n// doesn't need the top-level embed block: links is seeded by word-match rows, so declaring it\n// without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string used to mean SQL. It now fails rather than being inferred: an entry says\n // which of the two verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.content !== undefined) {\n validateContentBlock(cfg.content, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["SenseError","SIGNAL_NAMES","SIGNAL_PREREQUISITES","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","KNOWN_CONTENT_KEYS","SAVED_SEARCH_KEYS","unknownConfigKeys","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","validateLegacyScan","parsed","configPath","scan","include","validateContentBlock","block","unknown","join","tokenize","undefined","trim","validateFeaturesBlock","name","v","entries","validateEmbedBlock","embed","model","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","includes","isSignalWeight","isFinite","validatePresetSignals","map","signal","weight","names","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","search","validateConfig","checks","presets","default","queries","features","content"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport type { Config } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'presets', 'features', 'embed', 'content', 'store', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// Exported so a docs test can assert every key here has a schema.json property -- one source\n// of truth for what the embed block accepts, checked against the other for drift.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst KNOWN_CONTENT_KEYS = new Set(['tokenize']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\n// Only the shape is checked here. Whether the linked SQLite accepts the tokenizer is settled\n// by probing it in store/sqlite/open.ts, so this never has to carry a table of which version added what.\nfunction validateContentBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content must be an object of { tokenize?: string }`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_CONTENT_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content has unknown key(s) ${unknown.join(', ')}; content takes tokenize`);\n }\n if (block.tokenize !== undefined && (typeof block.tokenize !== 'string' || block.tokenize.trim() === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. \"trigram\" or \"unicode61 tokenchars '-_'\"`);\n }\n}\n\nfunction validateStoreKey(value: unknown, configPath: string): void {\n if (value !== 'sqlite' && value !== 'duckdb') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: store must be \"sqlite\" or \"duckdb\"`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that\n// doesn't need the top-level embed block: links is seeded by word-match rows, so declaring it\n// without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string used to mean SQL. It now fails rather than being inferred: an entry says\n // which of the two verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.content !== undefined) {\n validateContentBlock(cfg.content, configPath);\n }\n if (cfg.store !== undefined) {\n validateStoreKey(cfg.store, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["SenseError","SIGNAL_NAMES","SIGNAL_PREREQUISITES","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","KNOWN_CONTENT_KEYS","SAVED_SEARCH_KEYS","unknownConfigKeys","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","validateLegacyScan","parsed","configPath","scan","include","validateContentBlock","block","unknown","join","tokenize","undefined","trim","validateStoreKey","validateFeaturesBlock","name","v","entries","validateEmbedBlock","embed","model","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","includes","isSignalWeight","isFinite","validatePresetSignals","map","signal","weight","names","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","search","validateConfig","checks","presets","default","queries","features","content","store"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Store } from '../store/types.js';
2
+ export type LangCounts = Record<string, number>;
3
+ export declare function languageDistribution(store: Store): Promise<LangCounts | undefined>;
4
+ export declare function saveLanguageDistribution(store: Store, counts: LangCounts): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { getMeta, setMeta } from '../store/meta.js';
2
+ // Split out of langfit.ts so reading the persisted counts (status.ts's only need) never pulls in
3
+ // franc-min; only checkLanguageFit's classify path needs the classifier itself.
4
+ const META_KEY = 'embed_languages';
5
+ export async function languageDistribution(store) {
6
+ const raw = await getMeta(store, META_KEY);
7
+ return raw ? JSON.parse(raw) : undefined;
8
+ }
9
+ export async function saveLanguageDistribution(store, counts) {
10
+ await setMeta(store, META_KEY, JSON.stringify(counts));
11
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/distribution.ts"],"sourcesContent":["import { getMeta, setMeta } from '../store/meta.ts';\nimport type { Store } from '../store/types.ts';\n\n// Split out of langfit.ts so reading the persisted counts (status.ts's only need) never pulls in\n// franc-min; only checkLanguageFit's classify path needs the classifier itself.\nconst META_KEY = 'embed_languages';\n\nexport type LangCounts = Record<string, number>;\n\nexport async function languageDistribution(store: Store): Promise<LangCounts | undefined> {\n const raw = await getMeta(store, META_KEY);\n return raw ? JSON.parse(raw) : undefined;\n}\n\nexport async function saveLanguageDistribution(store: Store, counts: LangCounts): Promise<void> {\n await setMeta(store, META_KEY, JSON.stringify(counts));\n}\n"],"names":["getMeta","setMeta","META_KEY","languageDistribution","store","raw","JSON","parse","undefined","saveLanguageDistribution","counts","stringify"],"mappings":"AAAA,SAASA,OAAO,EAAEC,OAAO,QAAQ,mBAAmB;AAGpD,iGAAiG;AACjG,gFAAgF;AAChF,MAAMC,WAAW;AAIjB,OAAO,eAAeC,qBAAqBC,KAAY;IACrD,MAAMC,MAAM,MAAML,QAAQI,OAAOF;IACjC,OAAOG,MAAMC,KAAKC,KAAK,CAACF,OAAOG;AACjC;AAEA,OAAO,eAAeC,yBAAyBL,KAAY,EAAEM,MAAkB;IAC7E,MAAMT,QAAQG,OAAOF,UAAUI,KAAKK,SAAS,CAACD;AAChD"}
@@ -1,6 +1,3 @@
1
- import type { DatabaseSync } from 'node:sqlite';
1
+ import type { Store } from '../store/types.js';
2
2
  import type { EmbedProvider } from './types.js';
3
- type LangCounts = Record<string, number>;
4
- export declare function languageDistribution(db: DatabaseSync): LangCounts | undefined;
5
- export declare function checkLanguageFit(db: DatabaseSync, provider: EmbedProvider, texts: string[]): Promise<void>;
6
- export {};
3
+ export declare function checkLanguageFit(store: Store, provider: EmbedProvider, texts: string[]): Promise<void>;
@@ -1,34 +1,34 @@
1
- import { franc } from 'franc-min';
2
- import { getMeta, setMeta } from '../db/shared.js';
1
+ import Module from 'node:module';
3
2
  import { SenseError } from '../errors.js';
3
+ import { languageDistribution, saveLanguageDistribution } from './distribution.js';
4
4
  import { toIso3 } from './languages.js';
5
- const META_KEY = 'embed_languages';
6
5
  // franc's own reliability floor is a handful of characters; this is a cheap upper bound, not a
7
6
  // per-chunk minimum -- a short chunk is simply more likely to come back 'und' (unclassified).
8
7
  const SAMPLE_CHARS = 300;
9
8
  // A majority computed over a handful of chunks is noise, not a corpus signal; the decision rule
10
9
  // never fires below this many classified chunks, declared languages or not.
11
10
  const MIN_CLASSIFIED = 10;
12
- export function languageDistribution(db) {
13
- const raw = getMeta(db, META_KEY);
14
- return raw ? JSON.parse(raw) : undefined;
15
- }
16
- function classify(text) {
11
+ // franc-min is a 148K static import; a top-level import put it on every command's module graph
12
+ // regardless of whether the semantic path ran, shipped as the 0.17.0 regression. Deferred to a
13
+ // synchronous require inside checkLanguageFit so only the embed path pays for it.
14
+ const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
15
+ function classify(franc, text) {
17
16
  const code = franc(text.slice(0, SAMPLE_CHARS));
18
17
  return code === 'und' ? undefined : code;
19
18
  }
20
19
  // Merges chunk texts into the persisted distribution; throws (without persisting) if the model
21
20
  // declares languages and a clear majority classified so far is not among them.
22
- export async function checkLanguageFit(db, provider, texts) {
23
- var _languageDistribution;
21
+ export async function checkLanguageFit(store, provider, texts) {
22
+ var _ref;
24
23
  if (texts.length === 0) return;
25
- const persisted = (_languageDistribution = languageDistribution(db)) !== null && _languageDistribution !== void 0 ? _languageDistribution : {};
24
+ const { franc } = _require('franc-min');
25
+ const persisted = (_ref = await languageDistribution(store)) !== null && _ref !== void 0 ? _ref : {};
26
26
  const merged = {
27
27
  ...persisted
28
28
  };
29
29
  for (const text of texts){
30
30
  var _merged_code;
31
- const code = classify(text);
31
+ const code = classify(franc, text);
32
32
  if (code) merged[code] = ((_merged_code = merged[code]) !== null && _merged_code !== void 0 ? _merged_code : 0) + 1;
33
33
  }
34
34
  const total = Object.values(merged).reduce((a, b)=>a + b, 0);
@@ -40,5 +40,5 @@ export async function checkLanguageFit(db, provider, texts) {
40
40
  throw new SenseError('EMBED_MODEL_MISMATCH', `embed model ${provider.id} declares languages [${provider.languages.join(', ')}], but ${pct}% of this tree's classified text is "${majorityLang}" (ISO 639-3), which is not among them; choose a model for this tree's languages: \`sense init --model ...\`, see the sense-setup skill or INTEGRATIONS.md`);
41
41
  }
42
42
  }
43
- setMeta(db, META_KEY, JSON.stringify(merged));
43
+ await saveLanguageDistribution(store, merged);
44
44
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/langfit.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport { franc } from 'franc-min';\nimport { getMeta, setMeta } from '../db/shared.ts';\nimport { SenseError } from '../errors.ts';\nimport { toIso3 } from './languages.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst META_KEY = 'embed_languages';\n// franc's own reliability floor is a handful of characters; this is a cheap upper bound, not a\n// per-chunk minimum -- a short chunk is simply more likely to come back 'und' (unclassified).\nconst SAMPLE_CHARS = 300;\n// A majority computed over a handful of chunks is noise, not a corpus signal; the decision rule\n// never fires below this many classified chunks, declared languages or not.\nconst MIN_CLASSIFIED = 10;\n\ntype LangCounts = Record<string, number>;\n\nexport function languageDistribution(db: DatabaseSync): LangCounts | undefined {\n const raw = getMeta(db, META_KEY);\n return raw ? JSON.parse(raw) : undefined;\n}\n\nfunction classify(text: string): string | undefined {\n const code = franc(text.slice(0, SAMPLE_CHARS));\n return code === 'und' ? undefined : code;\n}\n\n// Merges chunk texts into the persisted distribution; throws (without persisting) if the model\n// declares languages and a clear majority classified so far is not among them.\nexport async function checkLanguageFit(db: DatabaseSync, provider: EmbedProvider, texts: string[]): Promise<void> {\n if (texts.length === 0) return;\n const persisted = languageDistribution(db) ?? {};\n const merged = { ...persisted };\n for (const text of texts) {\n const code = classify(text);\n if (code) merged[code] = (merged[code] ?? 0) + 1;\n }\n\n const total = Object.values(merged).reduce((a, b) => a + b, 0);\n if (provider.languages && provider.languages.length > 0 && total >= MIN_CLASSIFIED) {\n const declared = new Set(provider.languages.map(toIso3));\n const [majorityLang, majorityCount] = Object.entries(merged).sort((a, b) => b[1] - a[1])[0];\n if (majorityCount / total >= 0.5 && !declared.has(majorityLang)) {\n const pct = Math.round((majorityCount / total) * 100);\n throw new SenseError(\n 'EMBED_MODEL_MISMATCH',\n `embed model ${provider.id} declares languages [${provider.languages.join(', ')}], but ${pct}% of this tree's classified text is \"${majorityLang}\" (ISO 639-3), which is not among them; choose a model for this tree's languages: \\`sense init --model ...\\`, see the sense-setup skill or INTEGRATIONS.md`\n );\n }\n }\n setMeta(db, META_KEY, JSON.stringify(merged));\n}\n"],"names":["franc","getMeta","setMeta","SenseError","toIso3","META_KEY","SAMPLE_CHARS","MIN_CLASSIFIED","languageDistribution","db","raw","JSON","parse","undefined","classify","text","code","slice","checkLanguageFit","provider","texts","length","persisted","merged","total","Object","values","reduce","a","b","languages","declared","Set","map","majorityLang","majorityCount","entries","sort","has","pct","Math","round","id","join","stringify"],"mappings":"AACA,SAASA,KAAK,QAAQ,YAAY;AAClC,SAASC,OAAO,EAAEC,OAAO,QAAQ,kBAAkB;AACnD,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,MAAM,QAAQ,iBAAiB;AAGxC,MAAMC,WAAW;AACjB,+FAA+F;AAC/F,8FAA8F;AAC9F,MAAMC,eAAe;AACrB,gGAAgG;AAChG,4EAA4E;AAC5E,MAAMC,iBAAiB;AAIvB,OAAO,SAASC,qBAAqBC,EAAgB;IACnD,MAAMC,MAAMT,QAAQQ,IAAIJ;IACxB,OAAOK,MAAMC,KAAKC,KAAK,CAACF,OAAOG;AACjC;AAEA,SAASC,SAASC,IAAY;IAC5B,MAAMC,OAAOhB,MAAMe,KAAKE,KAAK,CAAC,GAAGX;IACjC,OAAOU,SAAS,QAAQH,YAAYG;AACtC;AAEA,+FAA+F;AAC/F,+EAA+E;AAC/E,OAAO,eAAeE,iBAAiBT,EAAgB,EAAEU,QAAuB,EAAEC,KAAe;QAE7EZ;IADlB,IAAIY,MAAMC,MAAM,KAAK,GAAG;IACxB,MAAMC,aAAYd,wBAAAA,qBAAqBC,iBAArBD,mCAAAA,wBAA4B,CAAC;IAC/C,MAAMe,SAAS;QAAE,GAAGD,SAAS;IAAC;IAC9B,KAAK,MAAMP,QAAQK,MAAO;YAEEG;QAD1B,MAAMP,OAAOF,SAASC;QACtB,IAAIC,MAAMO,MAAM,CAACP,KAAK,GAAG,EAACO,eAAAA,MAAM,CAACP,KAAK,cAAZO,0BAAAA,eAAgB,KAAK;IACjD;IAEA,MAAMC,QAAQC,OAAOC,MAAM,CAACH,QAAQI,MAAM,CAAC,CAACC,GAAGC,IAAMD,IAAIC,GAAG;IAC5D,IAAIV,SAASW,SAAS,IAAIX,SAASW,SAAS,CAACT,MAAM,GAAG,KAAKG,SAASjB,gBAAgB;QAClF,MAAMwB,WAAW,IAAIC,IAAIb,SAASW,SAAS,CAACG,GAAG,CAAC7B;QAChD,MAAM,CAAC8B,cAAcC,cAAc,GAAGV,OAAOW,OAAO,CAACb,QAAQc,IAAI,CAAC,CAACT,GAAGC,IAAMA,CAAC,CAAC,EAAE,GAAGD,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QAC3F,IAAIO,gBAAgBX,SAAS,OAAO,CAACO,SAASO,GAAG,CAACJ,eAAe;YAC/D,MAAMK,MAAMC,KAAKC,KAAK,CAAC,AAACN,gBAAgBX,QAAS;YACjD,MAAM,IAAIrB,WACR,wBACA,CAAC,YAAY,EAAEgB,SAASuB,EAAE,CAAC,qBAAqB,EAAEvB,SAASW,SAAS,CAACa,IAAI,CAAC,MAAM,OAAO,EAAEJ,IAAI,qCAAqC,EAAEL,aAAa,0JAA0J,CAAC;QAEhT;IACF;IACAhC,QAAQO,IAAIJ,UAAUM,KAAKiC,SAAS,CAACrB;AACvC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/langfit.ts"],"sourcesContent":["import Module from 'node:module';\nimport { SenseError } from '../errors.ts';\nimport type { Store } from '../store/types.ts';\nimport { languageDistribution, saveLanguageDistribution } from './distribution.ts';\nimport { toIso3 } from './languages.ts';\nimport type { EmbedProvider } from './types.ts';\n\n// franc's own reliability floor is a handful of characters; this is a cheap upper bound, not a\n// per-chunk minimum -- a short chunk is simply more likely to come back 'und' (unclassified).\nconst SAMPLE_CHARS = 300;\n// A majority computed over a handful of chunks is noise, not a corpus signal; the decision rule\n// never fires below this many classified chunks, declared languages or not.\nconst MIN_CLASSIFIED = 10;\n\n// franc-min is a 148K static import; a top-level import put it on every command's module graph\n// regardless of whether the semantic path ran, shipped as the 0.17.0 regression. Deferred to a\n// synchronous require inside checkLanguageFit so only the embed path pays for it.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\nfunction classify(franc: typeof import('franc-min')['franc'], text: string): string | undefined {\n const code = franc(text.slice(0, SAMPLE_CHARS));\n return code === 'und' ? undefined : code;\n}\n\n// Merges chunk texts into the persisted distribution; throws (without persisting) if the model\n// declares languages and a clear majority classified so far is not among them.\nexport async function checkLanguageFit(store: Store, provider: EmbedProvider, texts: string[]): Promise<void> {\n if (texts.length === 0) return;\n const { franc } = _require('franc-min') as typeof import('franc-min');\n const persisted = (await languageDistribution(store)) ?? {};\n const merged = { ...persisted };\n for (const text of texts) {\n const code = classify(franc, text);\n if (code) merged[code] = (merged[code] ?? 0) + 1;\n }\n\n const total = Object.values(merged).reduce((a, b) => a + b, 0);\n if (provider.languages && provider.languages.length > 0 && total >= MIN_CLASSIFIED) {\n const declared = new Set(provider.languages.map(toIso3));\n const [majorityLang, majorityCount] = Object.entries(merged).sort((a, b) => b[1] - a[1])[0];\n if (majorityCount / total >= 0.5 && !declared.has(majorityLang)) {\n const pct = Math.round((majorityCount / total) * 100);\n throw new SenseError(\n 'EMBED_MODEL_MISMATCH',\n `embed model ${provider.id} declares languages [${provider.languages.join(', ')}], but ${pct}% of this tree's classified text is \"${majorityLang}\" (ISO 639-3), which is not among them; choose a model for this tree's languages: \\`sense init --model ...\\`, see the sense-setup skill or INTEGRATIONS.md`\n );\n }\n }\n await saveLanguageDistribution(store, merged);\n}\n"],"names":["Module","SenseError","languageDistribution","saveLanguageDistribution","toIso3","SAMPLE_CHARS","MIN_CLASSIFIED","_require","require","createRequire","url","classify","franc","text","code","slice","undefined","checkLanguageFit","store","provider","texts","length","persisted","merged","total","Object","values","reduce","a","b","languages","declared","Set","map","majorityLang","majorityCount","entries","sort","has","pct","Math","round","id","join"],"mappings":"AAAA,OAAOA,YAAY,cAAc;AACjC,SAASC,UAAU,QAAQ,eAAe;AAE1C,SAASC,oBAAoB,EAAEC,wBAAwB,QAAQ,oBAAoB;AACnF,SAASC,MAAM,QAAQ,iBAAiB;AAGxC,+FAA+F;AAC/F,8FAA8F;AAC9F,MAAMC,eAAe;AACrB,gGAAgG;AAChG,4EAA4E;AAC5E,MAAMC,iBAAiB;AAEvB,+FAA+F;AAC/F,+FAA+F;AAC/F,kFAAkF;AAClF,MAAMC,WAAW,OAAOC,YAAY,cAAcR,OAAOS,aAAa,CAAC,YAAYC,GAAG,IAAIF;AAE1F,SAASG,SAASC,KAA0C,EAAEC,IAAY;IACxE,MAAMC,OAAOF,MAAMC,KAAKE,KAAK,CAAC,GAAGV;IACjC,OAAOS,SAAS,QAAQE,YAAYF;AACtC;AAEA,+FAA+F;AAC/F,+EAA+E;AAC/E,OAAO,eAAeG,iBAAiBC,KAAY,EAAEC,QAAuB,EAAEC,KAAe;QAGxE;IAFnB,IAAIA,MAAMC,MAAM,KAAK,GAAG;IACxB,MAAM,EAAET,KAAK,EAAE,GAAGL,SAAS;IAC3B,MAAMe,aAAa,OAAA,MAAMpB,qBAAqBgB,oBAA3B,kBAAA,OAAsC,CAAC;IAC1D,MAAMK,SAAS;QAAE,GAAGD,SAAS;IAAC;IAC9B,KAAK,MAAMT,QAAQO,MAAO;YAEEG;QAD1B,MAAMT,OAAOH,SAASC,OAAOC;QAC7B,IAAIC,MAAMS,MAAM,CAACT,KAAK,GAAG,EAACS,eAAAA,MAAM,CAACT,KAAK,cAAZS,0BAAAA,eAAgB,KAAK;IACjD;IAEA,MAAMC,QAAQC,OAAOC,MAAM,CAACH,QAAQI,MAAM,CAAC,CAACC,GAAGC,IAAMD,IAAIC,GAAG;IAC5D,IAAIV,SAASW,SAAS,IAAIX,SAASW,SAAS,CAACT,MAAM,GAAG,KAAKG,SAASlB,gBAAgB;QAClF,MAAMyB,WAAW,IAAIC,IAAIb,SAASW,SAAS,CAACG,GAAG,CAAC7B;QAChD,MAAM,CAAC8B,cAAcC,cAAc,GAAGV,OAAOW,OAAO,CAACb,QAAQc,IAAI,CAAC,CAACT,GAAGC,IAAMA,CAAC,CAAC,EAAE,GAAGD,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QAC3F,IAAIO,gBAAgBX,SAAS,OAAO,CAACO,SAASO,GAAG,CAACJ,eAAe;YAC/D,MAAMK,MAAMC,KAAKC,KAAK,CAAC,AAACN,gBAAgBX,QAAS;YACjD,MAAM,IAAIvB,WACR,wBACA,CAAC,YAAY,EAAEkB,SAASuB,EAAE,CAAC,qBAAqB,EAAEvB,SAASW,SAAS,CAACa,IAAI,CAAC,MAAM,OAAO,EAAEJ,IAAI,qCAAqC,EAAEL,aAAa,0JAA0J,CAAC;QAEhT;IACF;IACA,MAAM/B,yBAAyBe,OAAOK;AACxC"}
@@ -1,21 +1,14 @@
1
- import type { DatabaseSync } from 'node:sqlite';
2
1
  import type { Config } from '../config/index.js';
2
+ import type { Store, VectorCandidate, VectorSimilar } from '../store/types.js';
3
3
  export declare function toStore(full: Float32Array, dims: number, int8: boolean): {
4
4
  v: Float32Array;
5
5
  scale: number;
6
6
  };
7
- export declare function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void>;
8
- export declare function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<Array<{
9
- path: string;
10
- lines: string;
11
- similarity: number;
12
- }>>;
13
- export declare function hasEmbedding(db: DatabaseSync, path: string): boolean;
14
- export declare function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: {
7
+ export declare function embedPending(store: Store, cfg: Config, baseDir: string): Promise<void>;
8
+ export declare function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;
9
+ export declare function hasEmbedding(store: Store, path: string): Promise<boolean>;
10
+ export declare function similarNotes(store: Store, _cfg: Config, path: string, opts: {
15
11
  exclude: Set<string>;
16
12
  allowed?: Set<string>;
17
13
  k: number;
18
- }): Array<{
19
- path: string;
20
- similarity: number;
21
- }>;
14
+ }): Promise<VectorSimilar[]>;
@@ -5,11 +5,7 @@ import { progress } from '../output/progress.js';
5
5
  import { parseFile } from '../scan/index.js';
6
6
  import { checkLanguageFit } from './langfit.js';
7
7
  import { getProvider } from './registry.js';
8
- // Storage lever fixed by the bake-off (benchmark/reports/2026-08-13-static-model-bakeoff.md):
9
- // int8 at 256 dims is quality-free vs f32-512 when fused. Queries stay f32 at the same dims.
10
- const STORE_DIMS = 256;
11
- // Seed chunks that participate in a `related` scan; see similarNotes for the measurement.
12
- const TARGET_CHUNK_CAP = 16;
8
+ import { STORE_DIMS } from './types.js';
13
9
  // Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for
14
10
  // benchmark/lib/embed.mjs, which scores the same lever math offline.
15
11
  export function toStore(full, dims, int8) {
@@ -31,9 +27,9 @@ export function toStore(full, dims, int8) {
31
27
  }
32
28
  // Embed rows whose vector is NULL, re-deriving chunk text from the files through the
33
29
  // same parse + chunker that stored the rows.
34
- export async function embedPending(db, cfg, baseDir) {
30
+ export async function embedPending(store, cfg, baseDir) {
35
31
  const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table
36
- const dirty = db.prepare('SELECT "path", chunk FROM embeddings WHERE vector IS NULL ORDER BY "path", chunk').all();
32
+ const dirty = await store.vectors.pending();
37
33
  if (dirty.length === 0) return;
38
34
  const storeDims = Math.min(STORE_DIMS, provider.dims);
39
35
  const byPath = new Map();
@@ -71,44 +67,36 @@ export async function embedPending(db, cfg, baseDir) {
71
67
  }
72
68
  // Over the exact texts about to be embedded, before any of them are: a mismatch fails
73
69
  // this run loudly instead of quietly storing vectors from the wrong model.
74
- await checkLanguageFit(db, provider, jobs.map((j)=>j.text));
75
- const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE "path" = ? AND chunk = ?');
70
+ await checkLanguageFit(store, provider, jobs.map((j)=>j.text));
76
71
  // The lazy build is the one long silence a first search hits (measured 23s at
77
72
  // 26k notes); progress makes it distinguishable from a hang.
78
73
  const report = progress('embedding chunks', jobs.length);
79
74
  for(let i = 0; i < jobs.length; i += provider.batchCap){
80
75
  const batch = jobs.slice(i, i + provider.batchCap);
81
76
  const vectors = await provider.embedDocuments(batch.map((j)=>j.text));
82
- db.exec('BEGIN');
83
- try {
84
- batch.forEach((job, j)=>{
85
- const { v, scale } = toStore(vectors[j], storeDims, true);
86
- const q = new Int8Array(storeDims);
87
- for(let d = 0; d < storeDims; d++)q[d] = Math.round(v[d] / scale);
88
- update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);
89
- });
90
- db.exec('COMMIT');
91
- } catch (err) {
92
- db.exec('ROLLBACK');
93
- throw err;
94
- }
77
+ await store.vectors.writeVectors(batch.map((job, j)=>{
78
+ const { v, scale } = toStore(vectors[j], storeDims, true);
79
+ const q = new Int8Array(storeDims);
80
+ for(let d = 0; d < storeDims; d++)q[d] = Math.round(v[d] / scale);
81
+ return {
82
+ path: job.path,
83
+ chunk: job.chunk,
84
+ scale,
85
+ vector: Buffer.from(q.buffer)
86
+ };
87
+ }));
95
88
  report.tick(Math.min(i + provider.batchCap, jobs.length));
96
89
  }
97
90
  report.finish();
98
91
  }
99
- // Dequantised int8 dot products land a little either side of a true cosine, so an identical
100
- // pair prints 1.001 and undermines a column whose whole job is being a bounded number.
101
- function asCosine(score) {
102
- return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;
103
- }
104
92
  // Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as
105
93
  // lexical syntax. Similarity comes back because the fused score cannot express match quality,
106
94
  // and nearest-neighbour search always returns a neighbour however far away.
107
- export async function semanticCandidates(db, cfg, terms, fetch, allowed) {
95
+ export async function semanticCandidates(store, cfg, terms, fetch, allowed) {
108
96
  var _terms_match;
109
97
  const baseDir = cfg.baseDir;
110
98
  if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');
111
- await embedPending(db, cfg, baseDir);
99
+ await embedPending(store, cfg, baseDir);
112
100
  const provider = await getProvider(cfg);
113
101
  const storeDims = Math.min(STORE_DIMS, provider.dims);
114
102
  const text = ((_terms_match = terms.match(/[\p{L}\p{N}]+/gu)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>![
@@ -118,64 +106,15 @@ export async function semanticCandidates(db, cfg, terms, fetch, allowed) {
118
106
  'NEAR'
119
107
  ].includes(t)).join(' ');
120
108
  const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);
121
- const rows = db.prepare('SELECT "path", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all();
122
- const best = new Map();
123
- for (const row of rows){
124
- if (allowed && !allowed.has(row.path)) continue;
125
- const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));
126
- let dot = 0;
127
- for(let d = 0; d < q.length; d++)dot += q[d] * qv[d];
128
- const score = dot * row.scale;
129
- const existing = best.get(row.path);
130
- if (!existing || score > existing.score) best.set(row.path, {
131
- score,
132
- lines: `L${row.start_line}-${row.end_line}`
133
- });
134
- }
135
- return [
136
- ...best.entries()
137
- ].sort((a, b)=>b[1].score - a[1].score).slice(0, fetch).map(([path, b])=>({
138
- path,
139
- lines: b.lines,
140
- similarity: asCosine(b.score)
141
- }));
109
+ return store.vectors.candidates(qv, storeDims, fetch, allowed);
142
110
  }
143
111
  // Whether a note has any chunk with a vector. Distinguishes "nothing is near this note" from
144
112
  // "this note has no text", which look the same in an empty result.
145
- export function hasEmbedding(db, path) {
146
- const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE "path" = ? AND vector IS NOT NULL LIMIT 1').get(path);
147
- return row !== undefined;
113
+ export async function hasEmbedding(store, path) {
114
+ return store.vectors.hasVector(path);
148
115
  }
149
116
  // Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear
150
- // scan of stored vectors. Reads only what is stored, so it stays sync.
151
- export function similarNotes(db, _cfg, path, opts) {
152
- // Cost is target_chunks x stored_chunks, so a heading-dense seed multiplies a full-corpus
153
- // scan (12.7s at 201 chunks/note). Sample evenly, so late sections still get a vote.
154
- const targetRows = db.prepare('SELECT scale, vector FROM embeddings WHERE "path" = ? AND vector IS NOT NULL ORDER BY chunk').all(path);
155
- if (targetRows.length === 0) return [];
156
- const step = Math.max(1, Math.ceil(targetRows.length / TARGET_CHUNK_CAP));
157
- const target = targetRows.filter((_, i)=>i % step === 0).map((row)=>({
158
- v: new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength),
159
- scale: row.scale
160
- }));
161
- const rows = db.prepare('SELECT "path", scale, vector FROM embeddings WHERE vector IS NOT NULL').all();
162
- const best = new Map();
163
- for (const row of rows){
164
- if (row.path === path || opts.exclude.has(row.path) || opts.allowed && !opts.allowed.has(row.path)) continue;
165
- const other = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);
166
- for (const t of target){
167
- let dot = 0;
168
- const len = Math.min(t.v.length, other.length);
169
- for(let d = 0; d < len; d++)dot += t.v[d] * other[d];
170
- const score = dot * t.scale * row.scale;
171
- const existing = best.get(row.path);
172
- if (existing === undefined || score > existing) best.set(row.path, score);
173
- }
174
- }
175
- return [
176
- ...best.entries()
177
- ].sort((a, b)=>b[1] - a[1]).slice(0, opts.k).map(([p, score])=>({
178
- path: p,
179
- similarity: asCosine(score)
180
- }));
117
+ // scan of stored vectors.
118
+ export async function similarNotes(store, _cfg, path, opts) {
119
+ return store.vectors.similar(path, opts);
181
120
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/query.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Chunk } from '../features/embed.ts';\nimport { embed } from '../features/embed.ts';\nimport { progress } from '../output/progress.ts';\nimport { parseFile } from '../scan/index.ts';\nimport { checkLanguageFit } from './langfit.ts';\nimport { getProvider } from './registry.ts';\n\n// Storage lever fixed by the bake-off (benchmark/reports/2026-08-13-static-model-bakeoff.md):\n// int8 at 256 dims is quality-free vs f32-512 when fused. Queries stay f32 at the same dims.\nconst STORE_DIMS = 256;\n// Seed chunks that participate in a `related` scan; see similarNotes for the measurement.\nconst TARGET_CHUNK_CAP = 16;\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for\n// benchmark/lib/embed.mjs, which scores the same lever math offline.\nexport function toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = db.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk').all() as Array<{ path: string; chunk: number }>;\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n // presets/embed are irrelevant here -- re-deriving chunk text for a doc that already\n // has embeddings rows means the tree had an embedding model at reconcile time.\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, ctimeMs: 0, size: 0, presets: [], embed: true }, [embed], cfg).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n // Over the exact texts about to be embedded, before any of them are: a mismatch fails\n // this run loudly instead of quietly storing vectors from the wrong model.\n await checkLanguageFit(\n db,\n provider,\n jobs.map((j) => j.text)\n );\n\n const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE \"path\" = ? AND chunk = ?');\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += provider.batchCap) {\n const batch = jobs.slice(i, i + provider.batchCap);\n const vectors = await provider.embedDocuments(batch.map((j) => j.text));\n db.exec('BEGIN');\n try {\n batch.forEach((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);\n });\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n report.tick(Math.min(i + provider.batchCap, jobs.length));\n }\n report.finish();\n}\n\n// Dequantised int8 dot products land a little either side of a true cosine, so an identical\n// pair prints 1.001 and undermines a column whose whole job is being a bounded number.\nfunction asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality,\n// and nearest-neighbour search always returns a neighbour however far away.\nexport async function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<Array<{ path: string; lines: string; similarity: number }>> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(db, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);\n\n const rows = db.prepare('SELECT \"path\", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{\n path: string;\n start_line: number;\n end_line: number;\n scale: number;\n vector: Uint8Array;\n }>;\n\n const best = new Map<string, { score: number; lines: string }>();\n for (const row of rows) {\n if (allowed && !allowed.has(row.path)) continue;\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));\n let dot = 0;\n for (let d = 0; d < q.length; d++) dot += q[d] * qv[d];\n const score = dot * row.scale;\n const existing = best.get(row.path);\n if (!existing || score > existing.score) best.set(row.path, { score, lines: `L${row.start_line}-${row.end_line}` });\n }\n return [...best.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .slice(0, fetch)\n .map(([path, b]) => ({ path, lines: b.lines, similarity: asCosine(b.score) }));\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 function hasEmbedding(db: DatabaseSync, path: string): boolean {\n const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1').get(path) as { ok: number } | undefined;\n return row !== undefined;\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors. Reads only what is stored, so it stays sync.\nexport function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Array<{ path: string; similarity: number }> {\n // Cost is target_chunks x stored_chunks, so a heading-dense seed multiplies a full-corpus\n // scan (12.7s at 201 chunks/note). Sample evenly, so late sections still get a vote.\n const targetRows = db.prepare('SELECT scale, vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk').all(path) as Array<{ scale: number; vector: Uint8Array }>;\n if (targetRows.length === 0) return [];\n const step = Math.max(1, Math.ceil(targetRows.length / TARGET_CHUNK_CAP));\n const target = targetRows.filter((_, i) => i % step === 0).map((row) => ({ v: new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength), scale: row.scale }));\n\n const rows = db.prepare('SELECT \"path\", scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{ path: string; scale: number; vector: Uint8Array }>;\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 = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n for (const t of target) {\n let dot = 0;\n const len = Math.min(t.v.length, other.length);\n for (let d = 0; d < len; d++) dot += t.v[d] * other[d];\n const score = dot * t.scale * row.scale;\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 .sort((a, b) => b[1] - a[1])\n .slice(0, opts.k)\n .map(([p, score]) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["join","SenseError","embed","progress","parseFile","checkLanguageFit","getProvider","STORE_DIMS","TARGET_CHUNK_CAP","toStore","full","dims","int8","v","Float32Array","norm","d","Math","sqrt","scale","max","abs","embedPending","db","cfg","baseDir","provider","dirty","prepare","all","length","storeDims","min","byPath","Map","row","list","get","path","push","chunk","set","jobs","chunkIdxs","chunks","relPath","absPath","mtimeMs","ctimeMs","size","presets","doc","extracted","idx","text","map","j","update","report","i","batchCap","batch","slice","vectors","embedDocuments","exec","forEach","job","q","Int8Array","round","run","Buffer","from","buffer","err","tick","finish","asCosine","score","semanticCandidates","terms","fetch","allowed","match","filter","t","includes","qv","embedQuery","rows","best","has","vector","byteOffset","byteLength","dot","existing","lines","start_line","end_line","entries","sort","a","b","similarity","hasEmbedding","undefined","similarNotes","_cfg","opts","targetRows","step","ceil","target","_","exclude","other","len","k","p"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/query.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Chunk } from '../features/embed.ts';\nimport { embed } from '../features/embed.ts';\nimport { progress } from '../output/progress.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { Store, VectorCandidate, VectorSimilar } from '../store/types.ts';\nimport { checkLanguageFit } from './langfit.ts';\nimport { getProvider } from './registry.ts';\nimport { STORE_DIMS } from './types.ts';\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for\n// benchmark/lib/embed.mjs, which scores the same lever math offline.\nexport function toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(store: Store, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = await store.vectors.pending();\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n // presets/embed are irrelevant here -- re-deriving chunk text for a doc that already\n // has embeddings rows means the tree had an embedding model at reconcile time.\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, ctimeMs: 0, size: 0, presets: [], embed: true }, [embed], cfg).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n // Over the exact texts about to be embedded, before any of them are: a mismatch fails\n // this run loudly instead of quietly storing vectors from the wrong model.\n await checkLanguageFit(\n store,\n provider,\n jobs.map((j) => j.text)\n );\n\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += provider.batchCap) {\n const batch = jobs.slice(i, i + provider.batchCap);\n const vectors = await provider.embedDocuments(batch.map((j) => j.text));\n await store.vectors.writeVectors(\n batch.map((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n return { path: job.path, chunk: job.chunk, scale, vector: Buffer.from(q.buffer) };\n })\n );\n report.tick(Math.min(i + provider.batchCap, jobs.length));\n }\n report.finish();\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality,\n// and nearest-neighbour search always returns a neighbour however far away.\nexport async function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(store, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);\n\n return store.vectors.candidates(qv, storeDims, fetch, allowed);\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 hasEmbedding(store: Store, path: string): Promise<boolean> {\n return store.vectors.hasVector(path);\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors.\nexport async function similarNotes(store: Store, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]> {\n return store.vectors.similar(path, opts);\n}\n"],"names":["join","SenseError","embed","progress","parseFile","checkLanguageFit","getProvider","STORE_DIMS","toStore","full","dims","int8","v","Float32Array","norm","d","Math","sqrt","scale","max","abs","embedPending","store","cfg","baseDir","provider","dirty","vectors","pending","length","storeDims","min","byPath","Map","row","list","get","path","push","chunk","set","jobs","chunkIdxs","chunks","relPath","absPath","mtimeMs","ctimeMs","size","presets","doc","extracted","idx","text","map","j","report","i","batchCap","batch","slice","embedDocuments","writeVectors","job","q","Int8Array","round","vector","Buffer","from","buffer","tick","finish","semanticCandidates","terms","fetch","allowed","match","filter","t","includes","qv","embedQuery","candidates","hasEmbedding","hasVector","similarNotes","_cfg","opts","similar"],"mappings":"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"}
@@ -1,9 +1,13 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
+ import Module from 'node:module';
2
3
  import { join } from 'node:path';
3
- import { Tokenizer } from '@huggingface/tokenizers';
4
4
  import { SenseError } from '../errors.js';
5
5
  import { downloadModel, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages } from './store.js';
6
6
  const BATCH_CAP = 64;
7
+ // @huggingface/tokenizers is a 540K static import; a top-level import put it on every command's
8
+ // module graph regardless of whether the semantic path ran, shipped as the 0.17.0 regression.
9
+ // Deferred to a synchronous require inside staticProvider so only the embed path pays for it.
10
+ const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
7
11
  // Model2Vec safetensors + pure-JS tokenizer. Encode convention from model2vec/model.py: no
8
12
  // special tokens, drop unk ids, mean-pool, L2-normalize.
9
13
  export async function staticProvider(model) {
@@ -35,6 +39,7 @@ export async function staticProvider(model) {
35
39
  const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];
36
40
  const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));
37
41
  const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));
42
+ const { Tokenizer } = _require('@huggingface/tokenizers');
38
43
  const tok = new Tokenizer(tokenizerJson, {});
39
44
  const unkId = (_ref = (_tokenizerJson_model1 = tokenizerJson.model) === null || _tokenizerJson_model1 === void 0 ? void 0 : (_tokenizerJson_model_vocab = _tokenizerJson_model1.vocab) === null || _tokenizerJson_model_vocab === void 0 ? void 0 : _tokenizerJson_model_vocab[(_tokenizerJson_model = tokenizerJson.model) === null || _tokenizerJson_model === void 0 ? void 0 : _tokenizerJson_model.unk_token]) !== null && _ref !== void 0 ? _ref : -1;
40
45
  function one(text) {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/static.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { Tokenizer } from '@huggingface/tokenizers';\nimport { SenseError } from '../errors.ts';\nimport { downloadModel, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages } from './store.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\n// Model2Vec safetensors + pure-JS tokenizer. Encode convention from model2vec/model.py: no\n// special tokens, drop unk ids, mean-pool, L2-normalize.\nexport async function staticProvider(model: string): Promise<EmbedProvider> {\n let dir: string;\n let languages: string[] | undefined;\n if (isDownloadable(model)) {\n // Naming a HF id is consent: fetch whatever is missing now, announcing it on stderr.\n dir = await downloadModel(model, (file, into) => console.error(`sense: fetching ${model}/${file} into ${into}`));\n // Cached beside the repo; a local model directory has no card to read, so it has none.\n const cached = readLanguages(model);\n languages = cached && cached.length > 0 ? cached : undefined;\n } else {\n dir = modelDir(model);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); expected ${MODEL_FILENAMES} in that directory`);\n }\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n // The tokenizer yields undefined (not the unk id) for tokens outside the vocab; an\n // undefined id would index the matrix at NaN and poison the whole mean-pool -- and the\n // int8 conversion then stores the NaN vector as all zeros, silently. Keep integers only.\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => Number.isInteger(id) && id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n // Symmetric model: document and query embedding are the same call.\n return { id: `static:${model}`, dims, batchCap: BATCH_CAP, languages, embedDocuments: async (texts) => texts.map(one), embedQuery: async (text) => one(text) };\n}\n"],"names":["existsSync","readFileSync","join","Tokenizer","SenseError","downloadModel","isDownloadable","MODEL_FILENAMES","MODEL_FILES","modelDir","readLanguages","BATCH_CAP","staticProvider","model","tokenizerJson","dir","languages","file","into","console","error","cached","length","undefined","raw","headerLen","Number","readBigUInt64LE","header","JSON","parse","subarray","toString","entry","Object","entries","find","k","dtype","spec","dims","shape","dataStart","byteOffset","data_offsets","matrix","Float32Array","buffer","slice","tok","unkId","vocab","unk_token","one","text","ids","encode","add_special_tokens","filter","id","isInteger","v","off","d","norm","Math","sqrt","batchCap","embedDocuments","texts","map","embedQuery"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/static.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { join } from 'node:path';\nimport { SenseError } from '../errors.ts';\nimport { downloadModel, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages } from './store.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\n// @huggingface/tokenizers is a 540K static import; a top-level import put it on every command's\n// module graph regardless of whether the semantic path ran, shipped as the 0.17.0 regression.\n// Deferred to a synchronous require inside staticProvider so only the embed path pays for it.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Model2Vec safetensors + pure-JS tokenizer. Encode convention from model2vec/model.py: no\n// special tokens, drop unk ids, mean-pool, L2-normalize.\nexport async function staticProvider(model: string): Promise<EmbedProvider> {\n let dir: string;\n let languages: string[] | undefined;\n if (isDownloadable(model)) {\n // Naming a HF id is consent: fetch whatever is missing now, announcing it on stderr.\n dir = await downloadModel(model, (file, into) => console.error(`sense: fetching ${model}/${file} into ${into}`));\n // Cached beside the repo; a local model directory has no card to read, so it has none.\n const cached = readLanguages(model);\n languages = cached && cached.length > 0 ? cached : undefined;\n } else {\n dir = modelDir(model);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); expected ${MODEL_FILENAMES} in that directory`);\n }\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n const { Tokenizer } = _require('@huggingface/tokenizers') as typeof import('@huggingface/tokenizers');\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n // The tokenizer yields undefined (not the unk id) for tokens outside the vocab; an\n // undefined id would index the matrix at NaN and poison the whole mean-pool -- and the\n // int8 conversion then stores the NaN vector as all zeros, silently. Keep integers only.\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => Number.isInteger(id) && id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n // Symmetric model: document and query embedding are the same call.\n return { id: `static:${model}`, dims, batchCap: BATCH_CAP, languages, embedDocuments: async (texts) => texts.map(one), embedQuery: async (text) => one(text) };\n}\n"],"names":["existsSync","readFileSync","Module","join","SenseError","downloadModel","isDownloadable","MODEL_FILENAMES","MODEL_FILES","modelDir","readLanguages","BATCH_CAP","_require","require","createRequire","url","staticProvider","model","tokenizerJson","dir","languages","file","into","console","error","cached","length","undefined","raw","headerLen","Number","readBigUInt64LE","header","JSON","parse","subarray","toString","entry","Object","entries","find","k","dtype","spec","dims","shape","dataStart","byteOffset","data_offsets","matrix","Float32Array","buffer","slice","Tokenizer","tok","unkId","vocab","unk_token","one","text","ids","encode","add_special_tokens","filter","id","isInteger","v","off","d","norm","Math","sqrt","batchCap","embedDocuments","texts","map","embedQuery"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ export declare const STORE_DIMS = 256;
1
2
  export interface EmbedProvider {
2
3
  id: string;
3
4
  dims: number;