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/features/rank.ts"],"sourcesContent":["import { pagerank } from '../graph/graph.ts';\nimport { linkEdges } from './links.ts';\nimport type { Feature } from './types.ts';\n\n// frontmatter._rank: PageRank over resolved links -- a static \"how load-bearing is this\n// note\" prior. Depends on links (enforced in config.featureEnabled).\n\nexport const rank: Feature = {\n name: 'rank',\n schema(db) {\n const columns = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n if (!columns.some((c) => c.name === '_rank')) db.exec('ALTER TABLE frontmatter ADD COLUMN \"_rank\" REAL');\n },\n afterReconcile(db, delta) {\n // Only an explicit linksChanged:false skips. Rank also depends on the node set, so any\n // add or vanish recomputes -- otherwise a new linkless note keeps _rank NULL.\n if (delta.linksChanged === false && delta.added.length === 0 && delta.vanished.length === 0) return;\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const ranks = pagerank(nodes, linkEdges(db));\n const update = db.prepare('UPDATE frontmatter SET \"_rank\" = ? WHERE \"path\" = ?');\n for (const [path, value] of ranks) update.run(value, path);\n },\n};\n"],"names":["pagerank","linkEdges","rank","name","schema","db","columns","prepare","all","some","c","exec","afterReconcile","delta","linksChanged","added","length","vanished","nodes","map","r","path","ranks","update","value","run"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,oBAAoB;AAC7C,SAASC,SAAS,QAAQ,aAAa;AAGvC,wFAAwF;AACxF,qEAAqE;AAErE,OAAO,MAAMC,OAAgB;IAC3BC,MAAM;IACNC,QAAOC,EAAE;QACP,MAAMC,UAAUD,GAAGE,OAAO,CAAC,kCAAkCC,GAAG;QAChE,IAAI,CAACF,QAAQG,IAAI,CAAC,CAACC,IAAMA,EAAEP,IAAI,KAAK,UAAUE,GAAGM,IAAI,CAAC;IACxD;IACAC,gBAAeP,EAAE,EAAEQ,KAAK;QACtB,uFAAuF;QACvF,8EAA8E;QAC9E,IAAIA,MAAMC,YAAY,KAAK,SAASD,MAAME,KAAK,CAACC,MAAM,KAAK,KAAKH,MAAMI,QAAQ,CAACD,MAAM,KAAK,GAAG;QAC7F,MAAME,QAAQ,AAACb,GAAGE,OAAO,CAAC,kCAAkCC,GAAG,GAA+BW,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;QAC/G,MAAMC,QAAQtB,SAASkB,OAAOjB,UAAUI;QACxC,MAAMkB,SAASlB,GAAGE,OAAO,CAAC;QAC1B,KAAK,MAAM,CAACc,MAAMG,MAAM,IAAIF,MAAOC,OAAOE,GAAG,CAACD,OAAOH;IACvD;AACF,EAAE"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/rank.ts"],"sourcesContent":["import { pagerank } from '../graph/graph.ts';\nimport { linkEdges } from './links.ts';\nimport type { Feature } from './types.ts';\n\n// frontmatter._rank: PageRank over resolved links -- a static \"how load-bearing is this\n// note\" prior. Depends on links (enforced in config.featureEnabled).\n\nexport const rank: Feature = {\n name: 'rank',\n async schema(db) {\n const stmt = await db.prepare('PRAGMA table_info(frontmatter)');\n const columns = (await stmt.all()) as Array<{ name: string }>;\n if (!columns.some((c) => c.name === '_rank')) await db.exec('ALTER TABLE frontmatter ADD COLUMN \"_rank\" REAL');\n },\n async afterReconcile(db, delta) {\n // Only an explicit linksChanged:false skips. Rank also depends on the node set, so any\n // add or vanish recomputes -- otherwise a new linkless note keeps _rank NULL.\n if (delta.linksChanged === false && delta.added.length === 0 && delta.vanished.length === 0) return;\n const pathsStmt = await db.prepare('SELECT \"path\" FROM frontmatter');\n const nodes = ((await pathsStmt.all()) as Array<{ path: string }>).map((r) => r.path);\n const ranks = pagerank(nodes, await linkEdges(db));\n if (ranks.size === 0) return;\n await db.runBatch(\n 'UPDATE frontmatter SET \"_rank\" = ? WHERE \"path\" = ?',\n [...ranks].map(([path, value]) => [value, path])\n );\n },\n};\n"],"names":["pagerank","linkEdges","rank","name","schema","db","stmt","prepare","columns","all","some","c","exec","afterReconcile","delta","linksChanged","added","length","vanished","pathsStmt","nodes","map","r","path","ranks","size","runBatch","value"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,oBAAoB;AAC7C,SAASC,SAAS,QAAQ,aAAa;AAGvC,wFAAwF;AACxF,qEAAqE;AAErE,OAAO,MAAMC,OAAgB;IAC3BC,MAAM;IACN,MAAMC,QAAOC,EAAE;QACb,MAAMC,OAAO,MAAMD,GAAGE,OAAO,CAAC;QAC9B,MAAMC,UAAW,MAAMF,KAAKG,GAAG;QAC/B,IAAI,CAACD,QAAQE,IAAI,CAAC,CAACC,IAAMA,EAAER,IAAI,KAAK,UAAU,MAAME,GAAGO,IAAI,CAAC;IAC9D;IACA,MAAMC,gBAAeR,EAAE,EAAES,KAAK;QAC5B,uFAAuF;QACvF,8EAA8E;QAC9E,IAAIA,MAAMC,YAAY,KAAK,SAASD,MAAME,KAAK,CAACC,MAAM,KAAK,KAAKH,MAAMI,QAAQ,CAACD,MAAM,KAAK,GAAG;QAC7F,MAAME,YAAY,MAAMd,GAAGE,OAAO,CAAC;QACnC,MAAMa,QAAQ,AAAE,CAAA,MAAMD,UAAUV,GAAG,EAAC,EAA+BY,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;QACpF,MAAMC,QAAQxB,SAASoB,OAAO,MAAMnB,UAAUI;QAC9C,IAAImB,MAAMC,IAAI,KAAK,GAAG;QACtB,MAAMpB,GAAGqB,QAAQ,CACf,uDACA;eAAIF;SAAM,CAACH,GAAG,CAAC,CAAC,CAACE,MAAMI,MAAM,GAAK;gBAACA;gBAAOJ;aAAK;IAEnD;AACF,EAAE"}
@@ -26,15 +26,28 @@ function extract(raw) {
26
26
  }
27
27
  export const sections = {
28
28
  name: 'sections',
29
- schema (db) {
30
- db.exec(`CREATE TABLE IF NOT EXISTS sections ("path" TEXT, idx INTEGER, level INTEGER, heading TEXT, start_line INTEGER, end_line INTEGER, tokens INTEGER, PRIMARY KEY ("path", idx))`);
29
+ async schema (db) {
30
+ await db.exec(`CREATE TABLE IF NOT EXISTS sections ("path" TEXT, idx INTEGER, level INTEGER, heading TEXT, start_line INTEGER, end_line INTEGER, tokens INTEGER, PRIMARY KEY ("path", idx))`);
31
31
  },
32
32
  extract,
33
- remove (db, path) {
34
- db.prepare('DELETE FROM sections WHERE "path" = ?').run(path);
33
+ async remove (db, paths) {
34
+ if (paths.length === 0) return;
35
+ await db.runBatch('DELETE FROM sections WHERE "path" = ?', paths.map((p)=>[
36
+ p
37
+ ]));
35
38
  },
36
- store (db, path, extracted) {
37
- const insert = db.prepare('INSERT INTO sections ("path", idx, level, heading, start_line, end_line, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)');
38
- extracted.forEach((s, idx)=>insert.run(path, idx, s.level, s.heading, s.startLine, s.endLine, s.tokens));
39
+ async store (db, docs) {
40
+ const rows = [];
41
+ for (const { path, extracted } of docs)extracted.forEach((s, idx)=>rows.push([
42
+ path,
43
+ idx,
44
+ s.level,
45
+ s.heading,
46
+ s.startLine,
47
+ s.endLine,
48
+ s.tokens
49
+ ]));
50
+ if (rows.length === 0) return;
51
+ await db.runBatch('INSERT INTO sections ("path", idx, level, heading, start_line, end_line, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)', rows);
39
52
  }
40
53
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/sections.ts"],"sourcesContent":["import { estimateTokens } from '../chunk/index.ts';\nimport { fenceTracker } from './fences.ts';\nimport type { Feature } from './types.ts';\n\n// sections(path, idx, level, heading, start_line, end_line, tokens): the heading outline,\n// 1-indexed over the raw file so a row is a direct Read range; tokens is estimateTokens (D5),\n// CJK-aware rather than a flat chars/4.\n\nexport interface Section {\n level: number;\n heading: string;\n startLine: number;\n endLine: number;\n tokens: number;\n}\n\n// Headings outside fenced code blocks.\nfunction extract(raw: string): Section[] {\n const lines = raw.split('\\n');\n const found: Section[] = [];\n const fence = fenceTracker();\n for (let i = 0; i < lines.length; i++) {\n if (fence.feed(lines[i])) continue;\n if (fence.inFence) continue;\n const m = lines[i].match(/^(#{1,6}) +(.*)/);\n if (m) found.push({ level: m[1].length, heading: m[2].trim(), startLine: i + 1, endLine: lines.length, tokens: 0 });\n }\n for (let s = 0; s < found.length; s++) {\n if (s + 1 < found.length) found[s].endLine = found[s + 1].startLine - 1;\n const text = lines.slice(found[s].startLine - 1, found[s].endLine).join('\\n');\n found[s].tokens = Math.ceil(estimateTokens(text));\n }\n return found;\n}\n\nexport const sections: Feature = {\n name: 'sections',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS sections (\"path\" TEXT, idx INTEGER, level INTEGER, heading TEXT, start_line INTEGER, end_line INTEGER, tokens INTEGER, PRIMARY KEY (\"path\", idx))`);\n },\n extract,\n remove(db, path) {\n db.prepare('DELETE FROM sections WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const insert = db.prepare('INSERT INTO sections (\"path\", idx, level, heading, start_line, end_line, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)');\n (extracted as Section[]).forEach((s, idx) => insert.run(path, idx, s.level, s.heading, s.startLine, s.endLine, s.tokens));\n },\n};\n"],"names":["estimateTokens","fenceTracker","extract","raw","lines","split","found","fence","i","length","feed","inFence","m","match","push","level","heading","trim","startLine","endLine","tokens","s","text","slice","join","Math","ceil","sections","name","schema","db","exec","remove","path","prepare","run","store","extracted","insert","forEach","idx"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AACnD,SAASC,YAAY,QAAQ,cAAc;AAe3C,uCAAuC;AACvC,SAASC,QAAQC,GAAW;IAC1B,MAAMC,QAAQD,IAAIE,KAAK,CAAC;IACxB,MAAMC,QAAmB,EAAE;IAC3B,MAAMC,QAAQN;IACd,IAAK,IAAIO,IAAI,GAAGA,IAAIJ,MAAMK,MAAM,EAAED,IAAK;QACrC,IAAID,MAAMG,IAAI,CAACN,KAAK,CAACI,EAAE,GAAG;QAC1B,IAAID,MAAMI,OAAO,EAAE;QACnB,MAAMC,IAAIR,KAAK,CAACI,EAAE,CAACK,KAAK,CAAC;QACzB,IAAID,GAAGN,MAAMQ,IAAI,CAAC;YAAEC,OAAOH,CAAC,CAAC,EAAE,CAACH,MAAM;YAAEO,SAASJ,CAAC,CAAC,EAAE,CAACK,IAAI;YAAIC,WAAWV,IAAI;YAAGW,SAASf,MAAMK,MAAM;YAAEW,QAAQ;QAAE;IACnH;IACA,IAAK,IAAIC,IAAI,GAAGA,IAAIf,MAAMG,MAAM,EAAEY,IAAK;QACrC,IAAIA,IAAI,IAAIf,MAAMG,MAAM,EAAEH,KAAK,CAACe,EAAE,CAACF,OAAO,GAAGb,KAAK,CAACe,IAAI,EAAE,CAACH,SAAS,GAAG;QACtE,MAAMI,OAAOlB,MAAMmB,KAAK,CAACjB,KAAK,CAACe,EAAE,CAACH,SAAS,GAAG,GAAGZ,KAAK,CAACe,EAAE,CAACF,OAAO,EAAEK,IAAI,CAAC;QACxElB,KAAK,CAACe,EAAE,CAACD,MAAM,GAAGK,KAAKC,IAAI,CAAC1B,eAAesB;IAC7C;IACA,OAAOhB;AACT;AAEA,OAAO,MAAMqB,WAAoB;IAC/BC,MAAM;IACNC,QAAOC,EAAE;QACPA,GAAGC,IAAI,CAAC,CAAC,4KAA4K,CAAC;IACxL;IACA7B;IACA8B,QAAOF,EAAE,EAAEG,IAAI;QACbH,GAAGI,OAAO,CAAC,yCAAyCC,GAAG,CAACF;IAC1D;IACAG,OAAMN,EAAE,EAAEG,IAAI,EAAEI,SAAS;QACvB,MAAMC,SAASR,GAAGI,OAAO,CAAC;QACzBG,UAAwBE,OAAO,CAAC,CAAClB,GAAGmB,MAAQF,OAAOH,GAAG,CAACF,MAAMO,KAAKnB,EAAEN,KAAK,EAAEM,EAAEL,OAAO,EAAEK,EAAEH,SAAS,EAAEG,EAAEF,OAAO,EAAEE,EAAED,MAAM;IACzH;AACF,EAAE"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/sections.ts"],"sourcesContent":["import { estimateTokens } from '../chunk/index.ts';\nimport { fenceTracker } from './fences.ts';\nimport type { Feature } from './types.ts';\n\n// sections(path, idx, level, heading, start_line, end_line, tokens): the heading outline,\n// 1-indexed over the raw file so a row is a direct Read range; tokens is estimateTokens (D5),\n// CJK-aware rather than a flat chars/4.\n\nexport interface Section {\n level: number;\n heading: string;\n startLine: number;\n endLine: number;\n tokens: number;\n}\n\n// Headings outside fenced code blocks.\nfunction extract(raw: string): Section[] {\n const lines = raw.split('\\n');\n const found: Section[] = [];\n const fence = fenceTracker();\n for (let i = 0; i < lines.length; i++) {\n if (fence.feed(lines[i])) continue;\n if (fence.inFence) continue;\n const m = lines[i].match(/^(#{1,6}) +(.*)/);\n if (m) found.push({ level: m[1].length, heading: m[2].trim(), startLine: i + 1, endLine: lines.length, tokens: 0 });\n }\n for (let s = 0; s < found.length; s++) {\n if (s + 1 < found.length) found[s].endLine = found[s + 1].startLine - 1;\n const text = lines.slice(found[s].startLine - 1, found[s].endLine).join('\\n');\n found[s].tokens = Math.ceil(estimateTokens(text));\n }\n return found;\n}\n\nexport const sections: Feature = {\n name: 'sections',\n async schema(db) {\n await db.exec(`CREATE TABLE IF NOT EXISTS sections (\"path\" TEXT, idx INTEGER, level INTEGER, heading TEXT, start_line INTEGER, end_line INTEGER, tokens INTEGER, PRIMARY KEY (\"path\", idx))`);\n },\n extract,\n async remove(db, paths) {\n if (paths.length === 0) return;\n await db.runBatch(\n 'DELETE FROM sections WHERE \"path\" = ?',\n paths.map((p) => [p])\n );\n },\n async store(db, docs) {\n const rows: unknown[][] = [];\n for (const { path, extracted } of docs) (extracted as Section[]).forEach((s, idx) => rows.push([path, idx, s.level, s.heading, s.startLine, s.endLine, s.tokens]));\n if (rows.length === 0) return;\n await db.runBatch('INSERT INTO sections (\"path\", idx, level, heading, start_line, end_line, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)', rows);\n },\n};\n"],"names":["estimateTokens","fenceTracker","extract","raw","lines","split","found","fence","i","length","feed","inFence","m","match","push","level","heading","trim","startLine","endLine","tokens","s","text","slice","join","Math","ceil","sections","name","schema","db","exec","remove","paths","runBatch","map","p","store","docs","rows","path","extracted","forEach","idx"],"mappings":"AAAA,SAASA,cAAc,QAAQ,oBAAoB;AACnD,SAASC,YAAY,QAAQ,cAAc;AAe3C,uCAAuC;AACvC,SAASC,QAAQC,GAAW;IAC1B,MAAMC,QAAQD,IAAIE,KAAK,CAAC;IACxB,MAAMC,QAAmB,EAAE;IAC3B,MAAMC,QAAQN;IACd,IAAK,IAAIO,IAAI,GAAGA,IAAIJ,MAAMK,MAAM,EAAED,IAAK;QACrC,IAAID,MAAMG,IAAI,CAACN,KAAK,CAACI,EAAE,GAAG;QAC1B,IAAID,MAAMI,OAAO,EAAE;QACnB,MAAMC,IAAIR,KAAK,CAACI,EAAE,CAACK,KAAK,CAAC;QACzB,IAAID,GAAGN,MAAMQ,IAAI,CAAC;YAAEC,OAAOH,CAAC,CAAC,EAAE,CAACH,MAAM;YAAEO,SAASJ,CAAC,CAAC,EAAE,CAACK,IAAI;YAAIC,WAAWV,IAAI;YAAGW,SAASf,MAAMK,MAAM;YAAEW,QAAQ;QAAE;IACnH;IACA,IAAK,IAAIC,IAAI,GAAGA,IAAIf,MAAMG,MAAM,EAAEY,IAAK;QACrC,IAAIA,IAAI,IAAIf,MAAMG,MAAM,EAAEH,KAAK,CAACe,EAAE,CAACF,OAAO,GAAGb,KAAK,CAACe,IAAI,EAAE,CAACH,SAAS,GAAG;QACtE,MAAMI,OAAOlB,MAAMmB,KAAK,CAACjB,KAAK,CAACe,EAAE,CAACH,SAAS,GAAG,GAAGZ,KAAK,CAACe,EAAE,CAACF,OAAO,EAAEK,IAAI,CAAC;QACxElB,KAAK,CAACe,EAAE,CAACD,MAAM,GAAGK,KAAKC,IAAI,CAAC1B,eAAesB;IAC7C;IACA,OAAOhB;AACT;AAEA,OAAO,MAAMqB,WAAoB;IAC/BC,MAAM;IACN,MAAMC,QAAOC,EAAE;QACb,MAAMA,GAAGC,IAAI,CAAC,CAAC,4KAA4K,CAAC;IAC9L;IACA7B;IACA,MAAM8B,QAAOF,EAAE,EAAEG,KAAK;QACpB,IAAIA,MAAMxB,MAAM,KAAK,GAAG;QACxB,MAAMqB,GAAGI,QAAQ,CACf,yCACAD,MAAME,GAAG,CAAC,CAACC,IAAM;gBAACA;aAAE;IAExB;IACA,MAAMC,OAAMP,EAAE,EAAEQ,IAAI;QAClB,MAAMC,OAAoB,EAAE;QAC5B,KAAK,MAAM,EAAEC,IAAI,EAAEC,SAAS,EAAE,IAAIH,KAAM,AAACG,UAAwBC,OAAO,CAAC,CAACrB,GAAGsB,MAAQJ,KAAKzB,IAAI,CAAC;gBAAC0B;gBAAMG;gBAAKtB,EAAEN,KAAK;gBAAEM,EAAEL,OAAO;gBAAEK,EAAEH,SAAS;gBAAEG,EAAEF,OAAO;gBAAEE,EAAED,MAAM;aAAC;QAChK,IAAImB,KAAK9B,MAAM,KAAK,GAAG;QACvB,MAAMqB,GAAGI,QAAQ,CAAC,iHAAiHK;IACrI;AACF,EAAE"}
@@ -60,26 +60,27 @@ function extract(_raw, body, _search, data) {
60
60
  }
61
61
  export const tags = {
62
62
  name: 'tags',
63
- schema (db) {
64
- db.exec('CREATE TABLE IF NOT EXISTS tags ("path" TEXT, tag TEXT, PRIMARY KEY ("path", tag))');
65
- db.exec('CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag)');
63
+ async schema (db) {
64
+ await db.exec('CREATE TABLE IF NOT EXISTS tags ("path" TEXT, tag TEXT, PRIMARY KEY ("path", tag))');
65
+ await db.exec('CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag)');
66
66
  },
67
67
  extract,
68
- // Per-file rows with nothing else to resolve, so only a vanished file needs a delete here;
69
- // store() below handles a reparse's stale rows itself.
70
- remove (db, path, delta) {
71
- if (!delta.vanished.includes(path)) return;
72
- db.prepare('DELETE FROM tags WHERE "path" = ?').run(path);
68
+ // Blanket clear-then-reinsert (store() below always writes the full found list back), so
69
+ // remove() covers both a vanished file and a reparsed one that already had rows -- no
70
+ // per-file NOT IN diffing, which cannot be expressed as one statement shared across files.
71
+ async remove (db, paths) {
72
+ if (paths.length === 0) return;
73
+ await db.runBatch('DELETE FROM tags WHERE "path" = ?', paths.map((p)=>[
74
+ p
75
+ ]));
73
76
  },
74
- store (db, path, extracted) {
75
- const found = extracted;
76
- if (found.length === 0) {
77
- db.prepare('DELETE FROM tags WHERE "path" = ?').run(path);
78
- } else {
79
- const placeholders = found.map(()=>'?').join(', ');
80
- db.prepare(`DELETE FROM tags WHERE "path" = ? AND tag NOT IN (${placeholders})`).run(path, ...found);
81
- }
82
- const insert = db.prepare('INSERT OR IGNORE INTO tags ("path", tag) VALUES (?, ?)');
83
- for (const tag of found)insert.run(path, tag);
77
+ async store (db, docs) {
78
+ const rows = [];
79
+ for (const { path, extracted } of docs)for (const tag of extracted)rows.push([
80
+ path,
81
+ tag
82
+ ]);
83
+ if (rows.length === 0) return;
84
+ await db.runBatch('INSERT OR IGNORE INTO tags ("path", tag) VALUES (?, ?)', rows);
84
85
  }
85
86
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/tags.ts"],"sourcesContent":["import { maskRegions } from './fences.ts';\nimport type { Feature } from './types.ts';\n\n// tags(path, tag): Obsidian's file.tags grain -- frontmatter list/string tags plus inline\n// #tags from the prose, deduplicated, source not distinguished. Nested tags store full\n// (book/scifi); `tag = 'book' OR tag LIKE 'book/%'` is how a caller matches the parent too.\n\n// Obsidian treats [[#Heading]] as a same-note link, not a tag.\nconst WIKILINK_RE = /\\[\\[.*?\\]\\]/g; // to the first ]], so a heading holding a lone ] still masks\n// Obsidian doesn't read tags inside HTML markup.\nconst HTML_TAG_RE = /<\\/?[a-zA-Z][^>]*>/g; // tag-shaped only: a comparison's `< 5` must not open a span\n// Anchors on start-of-line or a preceding whitespace/(/[ so `a#b` and URL fragments don't count.\nconst INLINE_TAG_RE = /(?:^|[\\s([])#([\\p{L}\\p{N}_/-]+)/gu;\n// A markdown link destination `](...)` -- `[text](#anchor)` is a same-page link, not a tag.\nconst LINK_DEST_RE = /\\]\\((?:[^()]|\\([^()]*\\))*\\)/g; // one paren-nesting level, as CommonMark destinations allow: (https://x/a_(b)#frag)\n\n// Strips a leading # (frontmatter entries may carry one) and a trailing /; rejects an\n// all-digit result -- a tag needs at least one non-digit character.\nfunction normalizeTag(raw: string): string | null {\n const stripped = raw.replace(/^#/, '').replace(/\\/+$/, '');\n if (!stripped || /^\\d+$/.test(stripped)) return null;\n return stripped;\n}\n\n// data.tags: a YAML list (Obsidian also accepts a bare string). Null members and non-string\n// members are skipped rather than throwing -- `tags:\\n -` parses to [null].\nfunction frontmatterTags(data?: Record<string, unknown>): string[] {\n const raw = data?.tags;\n const list = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];\n const found: string[] = [];\n for (const item of list) {\n if (typeof item !== 'string') continue;\n const tag = normalizeTag(item);\n if (tag) found.push(tag);\n }\n return found;\n}\n\n// #tag tokens outside fenced code blocks, inline code spans, wikilinks, HTML tags, HTML blocks,\n// <!-- --> comments, and link destinations. maskRegions() handles all of those region-level\n// concerns (and code spans, masked once there); this loop only does per-line tag extraction.\nfunction inlineTags(body: string): string[] {\n const found: string[] = [];\n for (const line of maskRegions(body).split('\\n')) {\n if (!line.includes('#')) continue; // most lines; skip the regex work\n let cleaned = line;\n if (cleaned.includes('[[')) cleaned = cleaned.replace(WIKILINK_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('](')) cleaned = cleaned.replace(LINK_DEST_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('<')) cleaned = cleaned.replace(HTML_TAG_RE, (m) => ' '.repeat(m.length));\n for (const m of cleaned.matchAll(INLINE_TAG_RE)) {\n const tag = normalizeTag(m[1]);\n if (tag) found.push(tag);\n }\n }\n return found;\n}\n\nfunction extract(_raw: string, body: string, _search?: { title: string; summary: string }, data?: Record<string, unknown>): string[] {\n return [...new Set([...frontmatterTags(data), ...inlineTags(body)])].sort();\n}\n\nexport const tags: Feature = {\n name: 'tags',\n schema(db) {\n db.exec('CREATE TABLE IF NOT EXISTS tags (\"path\" TEXT, tag TEXT, PRIMARY KEY (\"path\", tag))');\n db.exec('CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag)');\n },\n extract,\n // Per-file rows with nothing else to resolve, so only a vanished file needs a delete here;\n // store() below handles a reparse's stale rows itself.\n remove(db, path, delta) {\n if (!delta.vanished.includes(path)) return;\n db.prepare('DELETE FROM tags WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const found = extracted as string[];\n if (found.length === 0) {\n db.prepare('DELETE FROM tags WHERE \"path\" = ?').run(path);\n } else {\n const placeholders = found.map(() => '?').join(', ');\n db.prepare(`DELETE FROM tags WHERE \"path\" = ? AND tag NOT IN (${placeholders})`).run(path, ...found);\n }\n const insert = db.prepare('INSERT OR IGNORE INTO tags (\"path\", tag) VALUES (?, ?)');\n for (const tag of found) insert.run(path, tag);\n },\n};\n"],"names":["maskRegions","WIKILINK_RE","HTML_TAG_RE","INLINE_TAG_RE","LINK_DEST_RE","normalizeTag","raw","stripped","replace","test","frontmatterTags","data","tags","list","Array","isArray","found","item","tag","push","inlineTags","body","line","split","includes","cleaned","m","repeat","length","matchAll","extract","_raw","_search","Set","sort","name","schema","db","exec","remove","path","delta","vanished","prepare","run","store","extracted","placeholders","map","join","insert"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/tags.ts"],"sourcesContent":["import { maskRegions } from './fences.ts';\nimport type { Feature } from './types.ts';\n\n// tags(path, tag): Obsidian's file.tags grain -- frontmatter list/string tags plus inline\n// #tags from the prose, deduplicated, source not distinguished. Nested tags store full\n// (book/scifi); `tag = 'book' OR tag LIKE 'book/%'` is how a caller matches the parent too.\n\n// Obsidian treats [[#Heading]] as a same-note link, not a tag.\nconst WIKILINK_RE = /\\[\\[.*?\\]\\]/g; // to the first ]], so a heading holding a lone ] still masks\n// Obsidian doesn't read tags inside HTML markup.\nconst HTML_TAG_RE = /<\\/?[a-zA-Z][^>]*>/g; // tag-shaped only: a comparison's `< 5` must not open a span\n// Anchors on start-of-line or a preceding whitespace/(/[ so `a#b` and URL fragments don't count.\nconst INLINE_TAG_RE = /(?:^|[\\s([])#([\\p{L}\\p{N}_/-]+)/gu;\n// A markdown link destination `](...)` -- `[text](#anchor)` is a same-page link, not a tag.\nconst LINK_DEST_RE = /\\]\\((?:[^()]|\\([^()]*\\))*\\)/g; // one paren-nesting level, as CommonMark destinations allow: (https://x/a_(b)#frag)\n\n// Strips a leading # (frontmatter entries may carry one) and a trailing /; rejects an\n// all-digit result -- a tag needs at least one non-digit character.\nfunction normalizeTag(raw: string): string | null {\n const stripped = raw.replace(/^#/, '').replace(/\\/+$/, '');\n if (!stripped || /^\\d+$/.test(stripped)) return null;\n return stripped;\n}\n\n// data.tags: a YAML list (Obsidian also accepts a bare string). Null members and non-string\n// members are skipped rather than throwing -- `tags:\\n -` parses to [null].\nfunction frontmatterTags(data?: Record<string, unknown>): string[] {\n const raw = data?.tags;\n const list = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];\n const found: string[] = [];\n for (const item of list) {\n if (typeof item !== 'string') continue;\n const tag = normalizeTag(item);\n if (tag) found.push(tag);\n }\n return found;\n}\n\n// #tag tokens outside fenced code blocks, inline code spans, wikilinks, HTML tags, HTML blocks,\n// <!-- --> comments, and link destinations. maskRegions() handles all of those region-level\n// concerns (and code spans, masked once there); this loop only does per-line tag extraction.\nfunction inlineTags(body: string): string[] {\n const found: string[] = [];\n for (const line of maskRegions(body).split('\\n')) {\n if (!line.includes('#')) continue; // most lines; skip the regex work\n let cleaned = line;\n if (cleaned.includes('[[')) cleaned = cleaned.replace(WIKILINK_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('](')) cleaned = cleaned.replace(LINK_DEST_RE, (m) => ' '.repeat(m.length));\n if (cleaned.includes('<')) cleaned = cleaned.replace(HTML_TAG_RE, (m) => ' '.repeat(m.length));\n for (const m of cleaned.matchAll(INLINE_TAG_RE)) {\n const tag = normalizeTag(m[1]);\n if (tag) found.push(tag);\n }\n }\n return found;\n}\n\nfunction extract(_raw: string, body: string, _search?: { title: string; summary: string }, data?: Record<string, unknown>): string[] {\n return [...new Set([...frontmatterTags(data), ...inlineTags(body)])].sort();\n}\n\nexport const tags: Feature = {\n name: 'tags',\n async schema(db) {\n await db.exec('CREATE TABLE IF NOT EXISTS tags (\"path\" TEXT, tag TEXT, PRIMARY KEY (\"path\", tag))');\n await db.exec('CREATE INDEX IF NOT EXISTS tags_tag ON tags(tag)');\n },\n extract,\n // Blanket clear-then-reinsert (store() below always writes the full found list back), so\n // remove() covers both a vanished file and a reparsed one that already had rows -- no\n // per-file NOT IN diffing, which cannot be expressed as one statement shared across files.\n async remove(db, paths) {\n if (paths.length === 0) return;\n await db.runBatch(\n 'DELETE FROM tags WHERE \"path\" = ?',\n paths.map((p) => [p])\n );\n },\n async store(db, docs) {\n const rows: unknown[][] = [];\n for (const { path, extracted } of docs) for (const tag of extracted as string[]) rows.push([path, tag]);\n if (rows.length === 0) return;\n await db.runBatch('INSERT OR IGNORE INTO tags (\"path\", tag) VALUES (?, ?)', rows);\n },\n};\n"],"names":["maskRegions","WIKILINK_RE","HTML_TAG_RE","INLINE_TAG_RE","LINK_DEST_RE","normalizeTag","raw","stripped","replace","test","frontmatterTags","data","tags","list","Array","isArray","found","item","tag","push","inlineTags","body","line","split","includes","cleaned","m","repeat","length","matchAll","extract","_raw","_search","Set","sort","name","schema","db","exec","remove","paths","runBatch","map","p","store","docs","rows","path","extracted"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import type { DatabaseSync } from 'node:sqlite';
2
1
  import type { Block } from '../chunk/index.js';
3
2
  import type { Config, FeatureName } from '../config/index.js';
4
3
  import type { FileStat } from '../scan/index.js';
4
+ import type { Connection } from '../store/types.js';
5
5
  export interface ReconcileDelta {
6
6
  files: FileStat[];
7
7
  reparsed: string[];
@@ -9,16 +9,20 @@ export interface ReconcileDelta {
9
9
  vanished: string[];
10
10
  linksChanged?: boolean;
11
11
  }
12
+ export interface ExtractedDoc {
13
+ path: string;
14
+ extracted: unknown;
15
+ }
12
16
  export interface Feature {
13
17
  name: FeatureName;
14
- schema(db: DatabaseSync): void;
18
+ schema(db: Connection): Promise<void>;
15
19
  extract?(raw: string, body: string, search?: {
16
20
  title: string;
17
21
  summary: string;
18
22
  }, data?: Record<string, unknown>, cfg?: Config, blocks?: Block[]): unknown;
19
- remove?(db: DatabaseSync, path: string, delta: ReconcileDelta): void;
20
- store?(db: DatabaseSync, path: string, extracted: unknown, delta: ReconcileDelta): void;
21
- afterReconcile?(db: DatabaseSync, delta: ReconcileDelta): void;
23
+ remove?(db: Connection, paths: string[], delta: ReconcileDelta): Promise<void>;
24
+ store?(db: Connection, docs: ExtractedDoc[], delta: ReconcileDelta): Promise<void>;
25
+ afterReconcile?(db: Connection, delta: ReconcileDelta): Promise<void>;
22
26
  enabledForFile?(cfg: Config, file: FileStat): boolean;
23
27
  signature?(cfg: Config): string | undefined;
24
28
  }
@@ -1,3 +1,3 @@
1
- // A feature owns its schema, per-file extraction/storage, and any whole-tree pass.
1
+ // A feature owns its schema, per-file extraction, batched storage, and any whole-tree pass.
2
2
  // Adding a feature = one module here + one entry in the registry (index.ts).
3
3
  export { };
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/types.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { Block } from '../chunk/index.ts';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport type { FileStat } from '../scan/index.ts';\n\n// What changed this reconcile, so hooks act on the delta rather than re-reading the tree.\n// `added` is the brand-new subset of `reparsed`. Hooks communicate through this one object.\nexport interface ReconcileDelta {\n files: FileStat[];\n reparsed: string[];\n added: string[];\n vanished: string[];\n // Set by the links feature: the resolved edge set may differ from before this reconcile.\n linksChanged?: boolean;\n}\n\n// A feature owns its schema, per-file extraction/storage, and any whole-tree pass.\n// Adding a feature = one module here + one entry in the registry (index.ts).\nexport interface Feature {\n name: FeatureName;\n schema(db: DatabaseSync): void;\n // Pure, per file: raw is the full file, body the prose after frontmatter, search the\n // normalized title/summary strings, data the parsed frontmatter, cfg the resolved config\n // (mirrors enabledForFile's own cfg argument, for a feature whose extraction is config-tunable),\n // blocks the body already parsed once by parseFile, shared with a feature needing a parse tree.\n extract?(raw: string, body: string, search?: { title: string; summary: string }, data?: Record<string, unknown>, cfg?: Config, blocks?: Block[]): unknown;\n remove?(db: DatabaseSync, path: string, delta: ReconcileDelta): void;\n store?(db: DatabaseSync, path: string, extracted: unknown, delta: ReconcileDelta): void;\n // After all rows are current, inside the reconcile transaction (resolution, rank).\n // Hooks run in registry order and communicate through fields on `delta`.\n afterReconcile?(db: DatabaseSync, delta: ReconcileDelta): void;\n // Per-file opt-out (currently only embed, via FileStat.embed): absent means the feature\n // applies to every file it's otherwise on for. Checked per file before extract/store so a\n // doc in a tree with no embedding model gets no rows for it at all.\n enabledForFile?(cfg: Config, file: FileStat): boolean;\n // This feature's cache-identity segment for config.featureSignature, or undefined to\n // contribute nothing. Composed by access.ts in registry order (features/index.ts's FEATURES).\n signature?(cfg: Config): string | undefined;\n}\n"],"names":[],"mappings":"AAgBA,mFAAmF;AACnF,6EAA6E;AAC7E,WAoBC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/types.ts"],"sourcesContent":["import type { Block } from '../chunk/index.ts';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport type { FileStat } from '../scan/index.ts';\nimport type { Connection } from '../store/types.ts';\n\n// What changed this reconcile, so hooks act on the delta rather than re-reading the tree.\n// `added` is the brand-new subset of `reparsed`. Hooks communicate through this one object.\nexport interface ReconcileDelta {\n files: FileStat[];\n reparsed: string[];\n added: string[];\n vanished: string[];\n // Set by the links feature: the resolved edge set may differ from before this reconcile.\n linksChanged?: boolean;\n}\n\n// One doc's per-feature extracted value, as store()/remove() see it (reconcile.ts assembles\n// these from ParsedDoc.extracted before calling a feature, once for the whole batch).\nexport interface ExtractedDoc {\n path: string;\n extracted: unknown;\n}\n\n// A feature owns its schema, per-file extraction, batched storage, and any whole-tree pass.\n// Adding a feature = one module here + one entry in the registry (index.ts).\nexport interface Feature {\n name: FeatureName;\n schema(db: Connection): Promise<void>;\n // Pure, per file: raw is the full file, body the prose after frontmatter, search the\n // normalized title/summary strings, data the parsed frontmatter, cfg the resolved config\n // (mirrors enabledForFile's own cfg argument, for a feature whose extraction is config-tunable),\n // blocks the body already parsed once by parseFile, shared with a feature needing a parse tree.\n extract?(raw: string, body: string, search?: { title: string; summary: string }, data?: Record<string, unknown>, cfg?: Config, blocks?: Block[]): unknown;\n // Called once per reconcile (not per file) with every path whose rows need clearing: vanished\n // files, plus reparsed files that already had rows (a feature that diffs its own stale rows\n // in store() instead, e.g. links/tags, filters this list down to just delta.vanished itself).\n remove?(db: Connection, paths: string[], delta: ReconcileDelta): Promise<void>;\n // Called once per reconcile with every freshly parsed doc's extracted value, so a feature\n // writes its rows through one Connection.runBatch call instead of looping per file.\n store?(db: Connection, docs: ExtractedDoc[], delta: ReconcileDelta): Promise<void>;\n // After all rows are current, inside the reconcile transaction (resolution, rank).\n // Hooks run in registry order and communicate through fields on `delta`.\n afterReconcile?(db: Connection, delta: ReconcileDelta): Promise<void>;\n // Per-file opt-out (currently only embed, via FileStat.embed): absent means the feature\n // applies to every file it's otherwise on for. Checked per file before extract/store so a\n // doc in a tree with no embedding model gets no rows for it at all.\n enabledForFile?(cfg: Config, file: FileStat): boolean;\n // This feature's cache-identity segment for config.featureSignature, or undefined to\n // contribute nothing. Composed by access.ts in registry order (features/index.ts's FEATURES).\n signature?(cfg: Config): string | undefined;\n}\n"],"names":[],"mappings":"AAuBA,4FAA4F;AAC5F,6EAA6E;AAC7E,WAyBC"}
@@ -1,8 +1,8 @@
1
- import type { DatabaseSync } from 'node:sqlite';
1
+ import type { Store } from '../store/types.js';
2
2
  interface FindPathOptions {
3
3
  directed?: boolean;
4
4
  maxDepth?: number;
5
5
  allowed?: Set<string>;
6
6
  }
7
- export declare function findPath(db: DatabaseSync, from: string, to: string, opts?: FindPathOptions): string[] | null;
7
+ export declare function findPath(store: Store, from: string, to: string, opts?: FindPathOptions): Promise<string[] | null>;
8
8
  export {};
@@ -1,18 +1,21 @@
1
1
  // Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a
2
2
  // local question touches a neighborhood, never the whole edge list.
3
- function resetVisited(db) {
4
- db.exec('DROP TABLE IF EXISTS temp.visited');
5
- db.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');
3
+ async function resetVisited(store) {
4
+ await store.exec('DROP TABLE IF EXISTS temp.visited');
5
+ await store.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');
6
6
  }
7
7
  // Materializes the caller-resolved scope Set into a temp table so it joins like any other
8
8
  // indexed column. `extra`, when given, is always in scope -- findPath's `to` endpoint.
9
- function setupAllowed(db, allowed, extra) {
10
- db.exec('DROP TABLE IF EXISTS temp.allowed_nodes');
11
- db.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');
12
- db.prepare('INSERT INTO allowed_nodes SELECT DISTINCT value FROM json_each(?1)').run(JSON.stringify([
13
- ...allowed
14
- ]));
15
- if (extra !== undefined) db.prepare('INSERT OR IGNORE INTO allowed_nodes VALUES (?)').run(extra);
9
+ async function setupAllowed(store, allowed, extra) {
10
+ const all = new Set(allowed);
11
+ if (extra !== undefined) all.add(extra);
12
+ await store.exec('DROP TABLE IF EXISTS temp.allowed_nodes');
13
+ await store.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');
14
+ await store.runBatch('INSERT INTO allowed_nodes (path) VALUES (?)', [
15
+ ...all
16
+ ].map((p)=>[
17
+ p
18
+ ]));
16
19
  }
17
20
  // Only resolved links (dst IS NOT NULL) are followed, in either direction.
18
21
  function candidateSelect(direction) {
@@ -23,8 +26,8 @@ function candidateSelect(direction) {
23
26
  return `${forward} UNION ALL ${reverse}`;
24
27
  }
25
28
  // One ring: candidates adjacent to the previous ring's frontier (?1), anti-joined against
26
- // everything already visited, optionally filtered to the allowed set, capped (?3) and
27
- // inserted at the new depth (?2). RETURNING hands back exactly what this ring newly reached.
29
+ // everything already visited, optionally filtered to the allowed set, and inserted at the
30
+ // new depth (?2). RETURNING hands back exactly what this ring newly reached.
28
31
  function ringSql(direction, hasAllowed) {
29
32
  return `
30
33
  INSERT INTO visited (path, depth, pred)
@@ -34,23 +37,22 @@ WHERE NOT EXISTS (SELECT 1 FROM visited v2 WHERE v2.path = cand.path)
34
37
  ${hasAllowed ? 'AND EXISTS (SELECT 1 FROM allowed_nodes a WHERE a.path = cand.path)' : ''}
35
38
  GROUP BY cand.path
36
39
  ORDER BY cand.path
37
- LIMIT ?3
38
40
  RETURNING path`;
39
41
  }
40
- function reconstructPath(db, to) {
41
- const lookup = db.prepare('SELECT pred FROM visited WHERE path = ?');
42
+ async function reconstructPath(store, to) {
43
+ const lookup = await store.prepare('SELECT pred FROM visited WHERE path = ?');
42
44
  const path = [];
43
45
  let cur = to;
44
46
  while(cur !== null){
45
47
  path.push(cur);
46
- const row = lookup.get(cur);
48
+ const row = await lookup.get(cur);
47
49
  cur = row ? row.pred : null;
48
50
  }
49
51
  return path.reverse();
50
52
  }
51
53
  // One BFS from `from`, tracking a predecessor per node. Undirected by default, matching
52
54
  // personalizedRank; null when `to` is unreached within `maxDepth`.
53
- export function findPath(db, from, to, opts = {}) {
55
+ export async function findPath(store, from, to, opts = {}) {
54
56
  var _opts_maxDepth;
55
57
  if (from === to) return [
56
58
  from
@@ -58,14 +60,15 @@ export function findPath(db, from, to, opts = {}) {
58
60
  const direction = opts.directed ? 'forward' : 'both';
59
61
  const maxDepth = (_opts_maxDepth = opts.maxDepth) !== null && _opts_maxDepth !== void 0 ? _opts_maxDepth : Infinity;
60
62
  if (maxDepth <= 0) return null;
61
- resetVisited(db);
62
- db.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)').run(from);
63
- if (opts.allowed) setupAllowed(db, opts.allowed, to);
64
- const stmt = db.prepare(ringSql(direction, !!opts.allowed));
63
+ await resetVisited(store);
64
+ const insertStart = await store.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)');
65
+ await insertStart.run(from);
66
+ if (opts.allowed) await setupAllowed(store, opts.allowed, to);
67
+ const stmt = await store.prepare(ringSql(direction, !!opts.allowed));
65
68
  for(let ring = 1; ring <= maxDepth; ring++){
66
- const rows = stmt.all(ring - 1, ring, -1);
69
+ const rows = await stmt.all(ring - 1, ring);
67
70
  if (rows.length === 0) break;
68
- if (rows.some((row)=>row.path === to)) return reconstructPath(db, to);
71
+ if (rows.some((row)=>row.path === to)) return reconstructPath(store, to);
69
72
  }
70
73
  return null;
71
74
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/graph/traverse.ts"],"sourcesContent":["// Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a\n// local question touches a neighborhood, never the whole edge list.\nimport type { DatabaseSync } from 'node:sqlite';\n\ntype Direction = 'forward' | 'reverse' | 'both';\n\ninterface FindPathOptions {\n directed?: boolean;\n maxDepth?: number;\n allowed?: Set<string>;\n}\n\nfunction resetVisited(db: DatabaseSync): void {\n db.exec('DROP TABLE IF EXISTS temp.visited');\n db.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');\n}\n\n// Materializes the caller-resolved scope Set into a temp table so it joins like any other\n// indexed column. `extra`, when given, is always in scope -- findPath's `to` endpoint.\nfunction setupAllowed(db: DatabaseSync, allowed: Set<string>, extra?: string): void {\n db.exec('DROP TABLE IF EXISTS temp.allowed_nodes');\n db.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');\n db.prepare('INSERT INTO allowed_nodes SELECT DISTINCT value FROM json_each(?1)').run(JSON.stringify([...allowed]));\n if (extra !== undefined) db.prepare('INSERT OR IGNORE INTO allowed_nodes VALUES (?)').run(extra);\n}\n\n// Only resolved links (dst IS NOT NULL) are followed, in either direction.\nfunction candidateSelect(direction: Direction): string {\n const forward = 'SELECT l.dst AS path, l.src AS pred FROM links l JOIN visited v ON v.path = l.src AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n const reverse = 'SELECT l.src AS path, l.dst AS pred FROM links l JOIN visited v ON v.path = l.dst AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n if (direction === 'forward') return forward;\n if (direction === 'reverse') return reverse;\n return `${forward} UNION ALL ${reverse}`;\n}\n\n// One ring: candidates adjacent to the previous ring's frontier (?1), anti-joined against\n// everything already visited, optionally filtered to the allowed set, capped (?3) and\n// inserted at the new depth (?2). RETURNING hands back exactly what this ring newly reached.\nfunction ringSql(direction: Direction, hasAllowed: boolean): string {\n return `\nINSERT INTO visited (path, depth, pred)\nSELECT cand.path, ?2, MIN(cand.pred)\nFROM (${candidateSelect(direction)}) cand\nWHERE NOT EXISTS (SELECT 1 FROM visited v2 WHERE v2.path = cand.path)\n${hasAllowed ? 'AND EXISTS (SELECT 1 FROM allowed_nodes a WHERE a.path = cand.path)' : ''}\nGROUP BY cand.path\nORDER BY cand.path\nLIMIT ?3\nRETURNING path`;\n}\n\nfunction reconstructPath(db: DatabaseSync, to: string): string[] {\n const lookup = db.prepare('SELECT pred FROM visited WHERE path = ?');\n const path: string[] = [];\n let cur: string | null = to;\n while (cur !== null) {\n path.push(cur);\n const row = lookup.get(cur) as { pred: string | null } | undefined;\n cur = row ? row.pred : null;\n }\n return path.reverse();\n}\n\n// One BFS from `from`, tracking a predecessor per node. Undirected by default, matching\n// personalizedRank; null when `to` is unreached within `maxDepth`.\nexport function findPath(db: DatabaseSync, from: string, to: string, opts: FindPathOptions = {}): string[] | null {\n if (from === to) return [from];\n\n const direction: Direction = opts.directed ? 'forward' : 'both';\n const maxDepth = opts.maxDepth ?? Infinity;\n if (maxDepth <= 0) return null;\n\n resetVisited(db);\n db.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)').run(from);\n if (opts.allowed) setupAllowed(db, opts.allowed, to);\n\n const stmt = db.prepare(ringSql(direction, !!opts.allowed));\n for (let ring = 1; ring <= maxDepth; ring++) {\n const rows = stmt.all(ring - 1, ring, -1) as Array<{ path: string }>;\n if (rows.length === 0) break;\n if (rows.some((row) => row.path === to)) return reconstructPath(db, to);\n }\n return null;\n}\n"],"names":["resetVisited","db","exec","setupAllowed","allowed","extra","prepare","run","JSON","stringify","undefined","candidateSelect","direction","forward","reverse","ringSql","hasAllowed","reconstructPath","to","lookup","path","cur","push","row","get","pred","findPath","from","opts","directed","maxDepth","Infinity","stmt","ring","rows","all","length","some"],"mappings":"AAAA,2FAA2F;AAC3F,oEAAoE;AAWpE,SAASA,aAAaC,EAAgB;IACpCA,GAAGC,IAAI,CAAC;IACRD,GAAGC,IAAI,CAAC;AACV;AAEA,0FAA0F;AAC1F,uFAAuF;AACvF,SAASC,aAAaF,EAAgB,EAAEG,OAAoB,EAAEC,KAAc;IAC1EJ,GAAGC,IAAI,CAAC;IACRD,GAAGC,IAAI,CAAC;IACRD,GAAGK,OAAO,CAAC,sEAAsEC,GAAG,CAACC,KAAKC,SAAS,CAAC;WAAIL;KAAQ;IAChH,IAAIC,UAAUK,WAAWT,GAAGK,OAAO,CAAC,kDAAkDC,GAAG,CAACF;AAC5F;AAEA,2EAA2E;AAC3E,SAASM,gBAAgBC,SAAoB;IAC3C,MAAMC,UAAU;IAChB,MAAMC,UAAU;IAChB,IAAIF,cAAc,WAAW,OAAOC;IACpC,IAAID,cAAc,WAAW,OAAOE;IACpC,OAAO,GAAGD,QAAQ,WAAW,EAAEC,SAAS;AAC1C;AAEA,0FAA0F;AAC1F,sFAAsF;AACtF,6FAA6F;AAC7F,SAASC,QAAQH,SAAoB,EAAEI,UAAmB;IACxD,OAAO,CAAC;;;MAGJ,EAAEL,gBAAgBC,WAAW;;AAEnC,EAAEI,aAAa,wEAAwE,GAAG;;;;cAI5E,CAAC;AACf;AAEA,SAASC,gBAAgBhB,EAAgB,EAAEiB,EAAU;IACnD,MAAMC,SAASlB,GAAGK,OAAO,CAAC;IAC1B,MAAMc,OAAiB,EAAE;IACzB,IAAIC,MAAqBH;IACzB,MAAOG,QAAQ,KAAM;QACnBD,KAAKE,IAAI,CAACD;QACV,MAAME,MAAMJ,OAAOK,GAAG,CAACH;QACvBA,MAAME,MAAMA,IAAIE,IAAI,GAAG;IACzB;IACA,OAAOL,KAAKN,OAAO;AACrB;AAEA,wFAAwF;AACxF,mEAAmE;AACnE,OAAO,SAASY,SAASzB,EAAgB,EAAE0B,IAAY,EAAET,EAAU,EAAEU,OAAwB,CAAC,CAAC;QAI5EA;IAHjB,IAAID,SAAST,IAAI,OAAO;QAACS;KAAK;IAE9B,MAAMf,YAAuBgB,KAAKC,QAAQ,GAAG,YAAY;IACzD,MAAMC,YAAWF,iBAAAA,KAAKE,QAAQ,cAAbF,4BAAAA,iBAAiBG;IAClC,IAAID,YAAY,GAAG,OAAO;IAE1B9B,aAAaC;IACbA,GAAGK,OAAO,CAAC,+DAA+DC,GAAG,CAACoB;IAC9E,IAAIC,KAAKxB,OAAO,EAAED,aAAaF,IAAI2B,KAAKxB,OAAO,EAAEc;IAEjD,MAAMc,OAAO/B,GAAGK,OAAO,CAACS,QAAQH,WAAW,CAAC,CAACgB,KAAKxB,OAAO;IACzD,IAAK,IAAI6B,OAAO,GAAGA,QAAQH,UAAUG,OAAQ;QAC3C,MAAMC,OAAOF,KAAKG,GAAG,CAACF,OAAO,GAAGA,MAAM,CAAC;QACvC,IAAIC,KAAKE,MAAM,KAAK,GAAG;QACvB,IAAIF,KAAKG,IAAI,CAAC,CAACd,MAAQA,IAAIH,IAAI,KAAKF,KAAK,OAAOD,gBAAgBhB,IAAIiB;IACtE;IACA,OAAO;AACT"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/graph/traverse.ts"],"sourcesContent":["// Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a\n// local question touches a neighborhood, never the whole edge list.\nimport type { Store } from '../store/types.ts';\n\ntype Direction = 'forward' | 'reverse' | 'both';\n\ninterface FindPathOptions {\n directed?: boolean;\n maxDepth?: number;\n allowed?: Set<string>;\n}\n\nasync function resetVisited(store: Store): Promise<void> {\n await store.exec('DROP TABLE IF EXISTS temp.visited');\n await store.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');\n}\n\n// Materializes the caller-resolved scope Set into a temp table so it joins like any other\n// indexed column. `extra`, when given, is always in scope -- findPath's `to` endpoint.\nasync function setupAllowed(store: Store, allowed: Set<string>, extra?: string): Promise<void> {\n const all = new Set(allowed);\n if (extra !== undefined) all.add(extra);\n await store.exec('DROP TABLE IF EXISTS temp.allowed_nodes');\n await store.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');\n await store.runBatch(\n 'INSERT INTO allowed_nodes (path) VALUES (?)',\n [...all].map((p) => [p])\n );\n}\n\n// Only resolved links (dst IS NOT NULL) are followed, in either direction.\nfunction candidateSelect(direction: Direction): string {\n const forward = 'SELECT l.dst AS path, l.src AS pred FROM links l JOIN visited v ON v.path = l.src AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n const reverse = 'SELECT l.src AS path, l.dst AS pred FROM links l JOIN visited v ON v.path = l.dst AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n if (direction === 'forward') return forward;\n if (direction === 'reverse') return reverse;\n return `${forward} UNION ALL ${reverse}`;\n}\n\n// One ring: candidates adjacent to the previous ring's frontier (?1), anti-joined against\n// everything already visited, optionally filtered to the allowed set, and inserted at the\n// new depth (?2). RETURNING hands back exactly what this ring newly reached.\nfunction ringSql(direction: Direction, hasAllowed: boolean): string {\n return `\nINSERT INTO visited (path, depth, pred)\nSELECT cand.path, ?2, MIN(cand.pred)\nFROM (${candidateSelect(direction)}) cand\nWHERE NOT EXISTS (SELECT 1 FROM visited v2 WHERE v2.path = cand.path)\n${hasAllowed ? 'AND EXISTS (SELECT 1 FROM allowed_nodes a WHERE a.path = cand.path)' : ''}\nGROUP BY cand.path\nORDER BY cand.path\nRETURNING path`;\n}\n\nasync function reconstructPath(store: Store, to: string): Promise<string[]> {\n const lookup = await store.prepare('SELECT pred FROM visited WHERE path = ?');\n const path: string[] = [];\n let cur: string | null = to;\n while (cur !== null) {\n path.push(cur);\n const row = (await lookup.get(cur)) as { pred: string | null } | undefined;\n cur = row ? row.pred : null;\n }\n return path.reverse();\n}\n\n// One BFS from `from`, tracking a predecessor per node. Undirected by default, matching\n// personalizedRank; null when `to` is unreached within `maxDepth`.\nexport async function findPath(store: Store, from: string, to: string, opts: FindPathOptions = {}): Promise<string[] | null> {\n if (from === to) return [from];\n\n const direction: Direction = opts.directed ? 'forward' : 'both';\n const maxDepth = opts.maxDepth ?? Infinity;\n if (maxDepth <= 0) return null;\n\n await resetVisited(store);\n const insertStart = await store.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)');\n await insertStart.run(from);\n if (opts.allowed) await setupAllowed(store, opts.allowed, to);\n\n const stmt = await store.prepare(ringSql(direction, !!opts.allowed));\n for (let ring = 1; ring <= maxDepth; ring++) {\n const rows = (await stmt.all(ring - 1, ring)) as Array<{ path: string }>;\n if (rows.length === 0) break;\n if (rows.some((row) => row.path === to)) return reconstructPath(store, to);\n }\n return null;\n}\n"],"names":["resetVisited","store","exec","setupAllowed","allowed","extra","all","Set","undefined","add","runBatch","map","p","candidateSelect","direction","forward","reverse","ringSql","hasAllowed","reconstructPath","to","lookup","prepare","path","cur","push","row","get","pred","findPath","from","opts","directed","maxDepth","Infinity","insertStart","run","stmt","ring","rows","length","some"],"mappings":"AAAA,2FAA2F;AAC3F,oEAAoE;AAWpE,eAAeA,aAAaC,KAAY;IACtC,MAAMA,MAAMC,IAAI,CAAC;IACjB,MAAMD,MAAMC,IAAI,CAAC;AACnB;AAEA,0FAA0F;AAC1F,uFAAuF;AACvF,eAAeC,aAAaF,KAAY,EAAEG,OAAoB,EAAEC,KAAc;IAC5E,MAAMC,MAAM,IAAIC,IAAIH;IACpB,IAAIC,UAAUG,WAAWF,IAAIG,GAAG,CAACJ;IACjC,MAAMJ,MAAMC,IAAI,CAAC;IACjB,MAAMD,MAAMC,IAAI,CAAC;IACjB,MAAMD,MAAMS,QAAQ,CAClB,+CACA;WAAIJ;KAAI,CAACK,GAAG,CAAC,CAACC,IAAM;YAACA;SAAE;AAE3B;AAEA,2EAA2E;AAC3E,SAASC,gBAAgBC,SAAoB;IAC3C,MAAMC,UAAU;IAChB,MAAMC,UAAU;IAChB,IAAIF,cAAc,WAAW,OAAOC;IACpC,IAAID,cAAc,WAAW,OAAOE;IACpC,OAAO,GAAGD,QAAQ,WAAW,EAAEC,SAAS;AAC1C;AAEA,0FAA0F;AAC1F,0FAA0F;AAC1F,6EAA6E;AAC7E,SAASC,QAAQH,SAAoB,EAAEI,UAAmB;IACxD,OAAO,CAAC;;;MAGJ,EAAEL,gBAAgBC,WAAW;;AAEnC,EAAEI,aAAa,wEAAwE,GAAG;;;cAG5E,CAAC;AACf;AAEA,eAAeC,gBAAgBlB,KAAY,EAAEmB,EAAU;IACrD,MAAMC,SAAS,MAAMpB,MAAMqB,OAAO,CAAC;IACnC,MAAMC,OAAiB,EAAE;IACzB,IAAIC,MAAqBJ;IACzB,MAAOI,QAAQ,KAAM;QACnBD,KAAKE,IAAI,CAACD;QACV,MAAME,MAAO,MAAML,OAAOM,GAAG,CAACH;QAC9BA,MAAME,MAAMA,IAAIE,IAAI,GAAG;IACzB;IACA,OAAOL,KAAKP,OAAO;AACrB;AAEA,wFAAwF;AACxF,mEAAmE;AACnE,OAAO,eAAea,SAAS5B,KAAY,EAAE6B,IAAY,EAAEV,EAAU,EAAEW,OAAwB,CAAC,CAAC;QAI9EA;IAHjB,IAAID,SAASV,IAAI,OAAO;QAACU;KAAK;IAE9B,MAAMhB,YAAuBiB,KAAKC,QAAQ,GAAG,YAAY;IACzD,MAAMC,YAAWF,iBAAAA,KAAKE,QAAQ,cAAbF,4BAAAA,iBAAiBG;IAClC,IAAID,YAAY,GAAG,OAAO;IAE1B,MAAMjC,aAAaC;IACnB,MAAMkC,cAAc,MAAMlC,MAAMqB,OAAO,CAAC;IACxC,MAAMa,YAAYC,GAAG,CAACN;IACtB,IAAIC,KAAK3B,OAAO,EAAE,MAAMD,aAAaF,OAAO8B,KAAK3B,OAAO,EAAEgB;IAE1D,MAAMiB,OAAO,MAAMpC,MAAMqB,OAAO,CAACL,QAAQH,WAAW,CAAC,CAACiB,KAAK3B,OAAO;IAClE,IAAK,IAAIkC,OAAO,GAAGA,QAAQL,UAAUK,OAAQ;QAC3C,MAAMC,OAAQ,MAAMF,KAAK/B,GAAG,CAACgC,OAAO,GAAGA;QACvC,IAAIC,KAAKC,MAAM,KAAK,GAAG;QACvB,IAAID,KAAKE,IAAI,CAAC,CAACf,MAAQA,IAAIH,IAAI,KAAKH,KAAK,OAAOD,gBAAgBlB,OAAOmB;IACzE;IACA,OAAO;AACT"}
@@ -2,11 +2,12 @@ export type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands/in
2
2
  export { mapTree, peek, presetCoverage, search } from './commands/index.js';
3
3
  export type { Config, EmbedConfig, FeatureName, InitOverrides, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights } from './config/index.js';
4
4
  export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config/index.js';
5
- export type { OpenResult } from './db/index.js';
6
- export { clearCache, open } from './db/index.js';
7
5
  export type { SenseErrorCode } from './errors.js';
8
6
  export { SenseError } from './errors.js';
9
7
  export type { Format, Row, RowFormat } from './output/output.js';
10
8
  export { printRows } from './output/output.js';
9
+ export type { OpenResult } from './store/index.js';
10
+ export { clearCache, openStore as open } from './store/index.js';
11
+ export type { Capability, DocumentStore, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './store/types.js';
11
12
  export type { WatchEvent, WatchOptions } from './watch.js';
12
13
  export { runWatch } from './watch.js';
package/dist/esm/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // internals (feature registry, graph, scan, meta) stay module-private.
3
3
  export { mapTree, peek, presetCoverage, search } from './commands/index.js';
4
4
  export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config/index.js';
5
- export { clearCache, open } from './db/index.js';
6
5
  export { SenseError } from './errors.js';
7
6
  export { printRows } from './output/output.js';
7
+ export { clearCache, openStore as open } from './store/index.js';
8
8
  export { runWatch } from './watch.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands/index.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands/index.ts';\nexport type { Config, EmbedConfig, FeatureName, InitOverrides, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights } from './config/index.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config/index.ts';\nexport type { OpenResult } from './db/index.ts';\nexport { clearCache, open } from './db/index.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Format, Row, RowFormat } from './output/output.ts';\nexport { printRows } from './output/output.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["mapTree","peek","presetCoverage","search","CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","SIGNAL_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION","clearCache","open","SenseError","printRows","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAEC,MAAM,QAAQ,sBAAsB;AAE5E,SAASC,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,YAAY,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,oBAAoB;AAE9I,SAASC,UAAU,EAAEC,IAAI,QAAQ,gBAAgB;AAEjD,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,qBAAqB;AAG/C,SAASC,QAAQ,QAAQ,aAAa"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands/index.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands/index.ts';\nexport type { Config, EmbedConfig, FeatureName, InitOverrides, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights } from './config/index.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config/index.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Format, Row, RowFormat } from './output/output.ts';\nexport { printRows } from './output/output.ts';\nexport type { OpenResult } from './store/index.ts';\nexport { clearCache, openStore as open } from './store/index.ts';\nexport type { Capability, DocumentStore, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './store/types.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["mapTree","peek","presetCoverage","search","CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","SIGNAL_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION","SenseError","printRows","clearCache","openStore","open","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAEC,MAAM,QAAQ,sBAAsB;AAE5E,SAASC,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,YAAY,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,oBAAoB;AAE9I,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,qBAAqB;AAE/C,SAASC,UAAU,EAAEC,aAAaC,IAAI,QAAQ,mBAAmB;AAIjE,SAASC,QAAQ,QAAQ,aAAa"}
@@ -0,0 +1 @@
1
+ export declare function guardedTick(fn: () => Promise<void>, skip: () => boolean): () => void;
@@ -0,0 +1,8 @@
1
+ // Wraps an async interval tick so a rejection (e.g. a store closed mid-await) never
2
+ // escapes as an unhandled rejection, and so a skip() guard can suppress the call entirely.
3
+ export function guardedTick(fn, skip) {
4
+ return ()=>{
5
+ if (skip()) return;
6
+ fn().catch(()=>{});
7
+ };
8
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/lib/guarded-tick.ts"],"sourcesContent":["// Wraps an async interval tick so a rejection (e.g. a store closed mid-await) never\n// escapes as an unhandled rejection, and so a skip() guard can suppress the call entirely.\nexport function guardedTick(fn: () => Promise<void>, skip: () => boolean): () => void {\n return () => {\n if (skip()) return;\n fn().catch(() => {});\n };\n}\n"],"names":["guardedTick","fn","skip","catch"],"mappings":"AAAA,oFAAoF;AACpF,2FAA2F;AAC3F,OAAO,SAASA,YAAYC,EAAuB,EAAEC,IAAmB;IACtE,OAAO;QACL,IAAIA,QAAQ;QACZD,KAAKE,KAAK,CAAC,KAAO;IACpB;AACF"}
@@ -1,2 +1 @@
1
- import type { DatabaseSync } from 'node:sqlite';
2
- export declare function columnHint(db: DatabaseSync, err: Error): Error;
1
+ export declare function columnHint(columns: string[], err: Error): Error;
@@ -5,7 +5,9 @@
5
5
  function startsWithBoundary(name, prefix) {
6
6
  return name.length > prefix.length && name.startsWith(prefix) && /\W/.test(name[prefix.length]);
7
7
  }
8
- export function columnHint(db, err) {
8
+ // columns: the tree's frontmatter columns (store.docs.columns()), read by the caller so this
9
+ // stays a pure function rather than a store dependency.
10
+ export function columnHint(columns, err) {
9
11
  const match = /no such column: (\S+)/.exec(err.message);
10
12
  if (!match) return err;
11
13
  const token = match[1];
@@ -17,7 +19,6 @@ export function columnHint(db, err) {
17
19
  token,
18
20
  aliasStripped
19
21
  ]);
20
- const columns = db.prepare('PRAGMA table_info(frontmatter)').all().map((c)=>c.name);
21
22
  const candidates = new Set();
22
23
  for (const name of columns){
23
24
  for (const form of forms){
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output/column-hint.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\n\n// A frontmatter key with punctuation (`plugin-id`, `a.b`) is a real column, but naming it\n// unquoted in SQL parses as an expression: `plugin-id` is `plugin - id`, and SQLite's error\n// names a fragment (`plugin`) the user never wrote. This maps that fragment back to the\n// columns it could have come from, so the error names the actual fix instead.\n\nfunction startsWithBoundary(name: string, prefix: string): boolean {\n return name.length > prefix.length && name.startsWith(prefix) && /\\W/.test(name[prefix.length]);\n}\n\nexport function columnHint(db: DatabaseSync, err: Error): Error {\n const match = /no such column: (\\S+)/.exec(err.message);\n if (!match) return err;\n const token = match[1];\n // `f.plugin` -> `plugin`; left as-is when there's no leading alias segment, which also\n // covers a column literally named `a.b` -- the full-token check below matches that case\n // directly, so stripping here never needs to special-case it.\n const aliasStripped = token.replace(/^[A-Za-z_]\\w*\\./, '');\n const forms = new Set([token, aliasStripped]);\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((c) => c.name);\n const candidates = new Set<string>();\n for (const name of columns) {\n for (const form of forms) {\n if (name === form || startsWithBoundary(name, form)) candidates.add(name);\n }\n }\n if (candidates.size === 0) return err;\n const quoted = [...candidates].map((name) => `\"${name.split('\"').join('\"\"')}\"`).join(', ');\n return new Error(`${err.message} -- ${quoted} needs double quotes in SQL: unquoted, SQLite parses the punctuation as an operator instead of column syntax.`);\n}\n"],"names":["startsWithBoundary","name","prefix","length","startsWith","test","columnHint","db","err","match","exec","message","token","aliasStripped","replace","forms","Set","columns","prepare","all","map","c","candidates","form","add","size","quoted","split","join","Error"],"mappings":"AAEA,0FAA0F;AAC1F,4FAA4F;AAC5F,wFAAwF;AACxF,8EAA8E;AAE9E,SAASA,mBAAmBC,IAAY,EAAEC,MAAc;IACtD,OAAOD,KAAKE,MAAM,GAAGD,OAAOC,MAAM,IAAIF,KAAKG,UAAU,CAACF,WAAW,KAAKG,IAAI,CAACJ,IAAI,CAACC,OAAOC,MAAM,CAAC;AAChG;AAEA,OAAO,SAASG,WAAWC,EAAgB,EAAEC,GAAU;IACrD,MAAMC,QAAQ,wBAAwBC,IAAI,CAACF,IAAIG,OAAO;IACtD,IAAI,CAACF,OAAO,OAAOD;IACnB,MAAMI,QAAQH,KAAK,CAAC,EAAE;IACtB,uFAAuF;IACvF,wFAAwF;IACxF,8DAA8D;IAC9D,MAAMI,gBAAgBD,MAAME,OAAO,CAAC,mBAAmB;IACvD,MAAMC,QAAQ,IAAIC,IAAI;QAACJ;QAAOC;KAAc;IAC5C,MAAMI,UAAU,AAACV,GAAGW,OAAO,CAAC,kCAAkCC,GAAG,GAA+BC,GAAG,CAAC,CAACC,IAAMA,EAAEpB,IAAI;IACjH,MAAMqB,aAAa,IAAIN;IACvB,KAAK,MAAMf,QAAQgB,QAAS;QAC1B,KAAK,MAAMM,QAAQR,MAAO;YACxB,IAAId,SAASsB,QAAQvB,mBAAmBC,MAAMsB,OAAOD,WAAWE,GAAG,CAACvB;QACtE;IACF;IACA,IAAIqB,WAAWG,IAAI,KAAK,GAAG,OAAOjB;IAClC,MAAMkB,SAAS;WAAIJ;KAAW,CAACF,GAAG,CAAC,CAACnB,OAAS,CAAC,CAAC,EAAEA,KAAK0B,KAAK,CAAC,KAAKC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAEA,IAAI,CAAC;IACrF,OAAO,IAAIC,MAAM,GAAGrB,IAAIG,OAAO,CAAC,IAAI,EAAEe,OAAO,6GAA6G,CAAC;AAC7J"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output/column-hint.ts"],"sourcesContent":["// A frontmatter key with punctuation (`plugin-id`, `a.b`) is a real column, but naming it\n// unquoted in SQL parses as an expression: `plugin-id` is `plugin - id`, and SQLite's error\n// names a fragment (`plugin`) the user never wrote. This maps that fragment back to the\n// columns it could have come from, so the error names the actual fix instead.\n\nfunction startsWithBoundary(name: string, prefix: string): boolean {\n return name.length > prefix.length && name.startsWith(prefix) && /\\W/.test(name[prefix.length]);\n}\n\n// columns: the tree's frontmatter columns (store.docs.columns()), read by the caller so this\n// stays a pure function rather than a store dependency.\nexport function columnHint(columns: string[], err: Error): Error {\n const match = /no such column: (\\S+)/.exec(err.message);\n if (!match) return err;\n const token = match[1];\n // `f.plugin` -> `plugin`; left as-is when there's no leading alias segment, which also\n // covers a column literally named `a.b` -- the full-token check below matches that case\n // directly, so stripping here never needs to special-case it.\n const aliasStripped = token.replace(/^[A-Za-z_]\\w*\\./, '');\n const forms = new Set([token, aliasStripped]);\n const candidates = new Set<string>();\n for (const name of columns) {\n for (const form of forms) {\n if (name === form || startsWithBoundary(name, form)) candidates.add(name);\n }\n }\n if (candidates.size === 0) return err;\n const quoted = [...candidates].map((name) => `\"${name.split('\"').join('\"\"')}\"`).join(', ');\n return new Error(`${err.message} -- ${quoted} needs double quotes in SQL: unquoted, SQLite parses the punctuation as an operator instead of column syntax.`);\n}\n"],"names":["startsWithBoundary","name","prefix","length","startsWith","test","columnHint","columns","err","match","exec","message","token","aliasStripped","replace","forms","Set","candidates","form","add","size","quoted","map","split","join","Error"],"mappings":"AAAA,0FAA0F;AAC1F,4FAA4F;AAC5F,wFAAwF;AACxF,8EAA8E;AAE9E,SAASA,mBAAmBC,IAAY,EAAEC,MAAc;IACtD,OAAOD,KAAKE,MAAM,GAAGD,OAAOC,MAAM,IAAIF,KAAKG,UAAU,CAACF,WAAW,KAAKG,IAAI,CAACJ,IAAI,CAACC,OAAOC,MAAM,CAAC;AAChG;AAEA,6FAA6F;AAC7F,wDAAwD;AACxD,OAAO,SAASG,WAAWC,OAAiB,EAAEC,GAAU;IACtD,MAAMC,QAAQ,wBAAwBC,IAAI,CAACF,IAAIG,OAAO;IACtD,IAAI,CAACF,OAAO,OAAOD;IACnB,MAAMI,QAAQH,KAAK,CAAC,EAAE;IACtB,uFAAuF;IACvF,wFAAwF;IACxF,8DAA8D;IAC9D,MAAMI,gBAAgBD,MAAME,OAAO,CAAC,mBAAmB;IACvD,MAAMC,QAAQ,IAAIC,IAAI;QAACJ;QAAOC;KAAc;IAC5C,MAAMI,aAAa,IAAID;IACvB,KAAK,MAAMf,QAAQM,QAAS;QAC1B,KAAK,MAAMW,QAAQH,MAAO;YACxB,IAAId,SAASiB,QAAQlB,mBAAmBC,MAAMiB,OAAOD,WAAWE,GAAG,CAAClB;QACtE;IACF;IACA,IAAIgB,WAAWG,IAAI,KAAK,GAAG,OAAOZ;IAClC,MAAMa,SAAS;WAAIJ;KAAW,CAACK,GAAG,CAAC,CAACrB,OAAS,CAAC,CAAC,EAAEA,KAAKsB,KAAK,CAAC,KAAKC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAEA,IAAI,CAAC;IACrF,OAAO,IAAIC,MAAM,GAAGjB,IAAIG,OAAO,CAAC,IAAI,EAAEU,OAAO,6GAA6G,CAAC;AAC7J"}
@@ -1,8 +1,9 @@
1
1
  export type Row = Record<string, unknown>;
2
2
  export type Format = 'table' | 'json';
3
3
  export type RowFormat = Format | 'csv';
4
+ export declare function stringifyJson(value: unknown, indent?: number): string;
4
5
  export declare function printRows(rows: Row[], format: RowFormat, columns?: string[]): void;
5
- export declare function printRowStream(rows: Iterable<Row>, format: RowFormat, columns: string[]): void;
6
+ export declare function printRowStream(rows: AsyncIterable<Row>, format: RowFormat, columns: string[]): Promise<void>;
6
7
  export declare function featureOffHint(name: string): string;
7
8
  export declare function featuresLine(features: {
8
9
  on: string[];
@@ -24,6 +24,14 @@ function warnOversize(bytes, rowCount) {
24
24
  // b's value under both headers. `columns` is the fallback that labels a 0-row result, and with
25
25
  // no rows and no statement (a bounded command that found nothing) csv writes nothing at all --
26
26
  // a bare newline would read to a csv parser as one empty record.
27
+ // JSON.stringify throws on BigInt, and stores return int64 as BigInt (DuckDB does). A value
28
+ // that fits a safe integer stays a number; a larger one becomes its decimal string, since a
29
+ // JSON number cannot carry it losslessly.
30
+ const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
31
+ const bigintSafe = (_key, value)=>typeof value === 'bigint' ? value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString() : value;
32
+ export function stringifyJson(value, indent) {
33
+ return JSON.stringify(value, bigintSafe, indent);
34
+ }
27
35
  export function printRows(rows, format, columns) {
28
36
  if (format === 'csv') {
29
37
  // Collapsed the same way a row object collapses them, so a statement naming a column
@@ -45,25 +53,26 @@ export function printRows(rows, format, columns) {
45
53
  console.log(rendered);
46
54
  warnOversize(Buffer.byteLength(rendered), rows.length);
47
55
  }
48
- // Streaming counterpart for the one unbounded caller, `sense sql`: rows arrive from an
49
- // iterator, so a large result is never held here as rows and again as a rendered string.
50
- // Table buffers by necessity (a column is as wide as its widest cell, which the last row can
51
- // change); json and csv write row by row. The writes are synchronous, so when stdout is a pipe
52
- // Node buffers what the reader has not taken yet -- the saving is this module's copies, not
53
- // the operating system's.
54
- export function printRowStream(rows, format, columns) {
55
- const iterator = rows[Symbol.iterator]();
56
+ // Streaming counterpart for the one unbounded caller, `sense sql`: rows arrive from an async
57
+ // iterator (raw.prepare().iterate(), one microtask for the whole statement, not per row), so a
58
+ // large result is never held here as rows and again as a rendered string. Table buffers by
59
+ // necessity (a column is as wide as its widest cell, which the last row can change); json and
60
+ // csv write row by row. The writes are synchronous, so when stdout is a pipe Node buffers what
61
+ // the reader has not taken yet -- the saving is this module's copies, not the operating system's.
62
+ export async function printRowStream(rows, format, columns) {
63
+ const iterator = rows[Symbol.asyncIterator]();
56
64
  // One step before any write. FTS5 parses a MATCH expression at step time rather than at
57
65
  // prepare time, so a bad search string throws here, with stdout still clean for the error.
58
- const first = iterator.next();
66
+ const first = await iterator.next();
59
67
  const rest = {
60
- [Symbol.iterator]: ()=>iterator
68
+ [Symbol.asyncIterator]: ()=>iterator
61
69
  };
62
70
  if (format === 'table') {
63
- printRows(first.done ? [] : [
64
- first.value,
65
- ...rest
66
- ], format, columns);
71
+ const collected = first.done ? [] : [
72
+ first.value
73
+ ];
74
+ for await (const row of rest)collected.push(row);
75
+ printRows(collected, format, columns);
67
76
  return;
68
77
  }
69
78
  let bytes = 0;
@@ -76,19 +85,14 @@ export function printRowStream(rows, format, columns) {
76
85
  if (format === 'json') {
77
86
  // Byte-identical to JSON.stringify(rows, null, 2) + newline: each row re-indented one
78
87
  // level, comma-joined, inside the array brackets written here.
79
- // JSON.stringify throws on BigInt, so a row with an int64 past 2^53 (setReadBigInts) is
80
- // converted via the replacer: a value that fits a safe integer stays a number, a larger one
81
- // becomes its decimal string, since a JSON number cannot carry it losslessly.
82
- const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
83
- const bigintSafe = (_key, value)=>typeof value === 'bigint' ? value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString() : value;
84
- const indent = (row)=>JSON.stringify(row, bigintSafe, 2).split('\n').map((line)=>` ${line}`).join('\n');
88
+ const indent = (row)=>stringifyJson(row, 2).split('\n').map((line)=>` ${line}`).join('\n');
85
89
  if (first.done) {
86
90
  write('[]\n');
87
91
  } else {
88
92
  write('[\n');
89
93
  write(indent(first.value));
90
94
  rowCount = 1;
91
- for (const row of rest){
95
+ for await (const row of rest){
92
96
  write(',\n');
93
97
  write(indent(row));
94
98
  rowCount++;
@@ -105,7 +109,7 @@ export function printRowStream(rows, format, columns) {
105
109
  write(`${csvLine(names)}\n`);
106
110
  write(`${csvLine(names.map((name)=>first.value[name]))}\n`);
107
111
  rowCount = 1;
108
- for (const row of rest){
112
+ for await (const row of rest){
109
113
  write(`${csvLine(names.map((name)=>row[name]))}\n`);
110
114
  rowCount++;
111
115
  }
@@ -130,7 +134,7 @@ function fitWidths(widths, limit) {
130
134
  }
131
135
  }
132
136
  function renderRows(rows, format, width = process.stdout.columns) {
133
- if (format === 'json') return JSON.stringify(rows, null, 2);
137
+ if (format === 'json') return stringifyJson(rows, 2);
134
138
  if (rows.length === 0) return '(0 rows)';
135
139
  const columns = Object.keys(rows[0]);
136
140
  const cells = rows.map((row)=>columns.map((col)=>cell(row[col])));