sensemaking 0.25.0 → 0.26.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 (316) hide show
  1. package/README.md +14 -10
  2. package/dist/cjs/cli/index.d.cts +1 -1
  3. package/dist/cjs/cli/index.d.ts +1 -1
  4. package/dist/cjs/cli/index.js +1 -1
  5. package/dist/cjs/cli/index.js.map +1 -1
  6. package/dist/cjs/cli/path.js +5 -29
  7. package/dist/cjs/cli/path.js.map +1 -1
  8. package/dist/cjs/cli/related.js +4 -3
  9. package/dist/cjs/cli/related.js.map +1 -1
  10. package/dist/cjs/commands/index.d.cts +6 -3
  11. package/dist/cjs/commands/index.d.ts +6 -3
  12. package/dist/cjs/commands/index.js +4 -0
  13. package/dist/cjs/commands/index.js.map +1 -1
  14. package/dist/cjs/commands/map.d.cts +14 -5
  15. package/dist/cjs/commands/map.d.ts +14 -5
  16. package/dist/cjs/commands/map.js +42 -35
  17. package/dist/cjs/commands/map.js.map +1 -1
  18. package/dist/cjs/commands/path.d.cts +14 -0
  19. package/dist/cjs/commands/path.d.ts +14 -0
  20. package/dist/cjs/commands/path.js +209 -0
  21. package/dist/cjs/commands/path.js.map +1 -0
  22. package/dist/cjs/commands/peek.d.cts +16 -1
  23. package/dist/cjs/commands/peek.d.ts +16 -1
  24. package/dist/cjs/commands/peek.js +71 -33
  25. package/dist/cjs/commands/peek.js.map +1 -1
  26. package/dist/cjs/commands/related.d.cts +16 -3
  27. package/dist/cjs/commands/related.d.ts +16 -3
  28. package/dist/cjs/commands/related.js +131 -98
  29. package/dist/cjs/commands/related.js.map +1 -1
  30. package/dist/cjs/commands/scope.d.cts +1 -0
  31. package/dist/cjs/commands/scope.d.ts +1 -0
  32. package/dist/cjs/commands/scope.js +149 -0
  33. package/dist/cjs/commands/scope.js.map +1 -1
  34. package/dist/cjs/commands/search.d.cts +26 -3
  35. package/dist/cjs/commands/search.d.ts +26 -3
  36. package/dist/cjs/commands/search.js +211 -71
  37. package/dist/cjs/commands/search.js.map +1 -1
  38. package/dist/cjs/commands/signals.d.cts +4 -3
  39. package/dist/cjs/commands/signals.d.ts +4 -3
  40. package/dist/cjs/commands/signals.js +13 -4
  41. package/dist/cjs/commands/signals.js.map +1 -1
  42. package/dist/cjs/commands/snapshot.d.cts +3 -0
  43. package/dist/cjs/commands/snapshot.d.ts +3 -0
  44. package/dist/cjs/commands/snapshot.js +180 -0
  45. package/dist/cjs/commands/snapshot.js.map +1 -0
  46. package/dist/cjs/embed/cohere.js +41 -16
  47. package/dist/cjs/embed/cohere.js.map +1 -1
  48. package/dist/cjs/embed/endpoint.d.cts +2 -0
  49. package/dist/cjs/embed/endpoint.d.ts +2 -0
  50. package/dist/cjs/embed/endpoint.js +30 -0
  51. package/dist/cjs/embed/endpoint.js.map +1 -0
  52. package/dist/cjs/embed/handoff.d.cts +7 -2
  53. package/dist/cjs/embed/handoff.d.ts +7 -2
  54. package/dist/cjs/embed/handoff.js +10 -0
  55. package/dist/cjs/embed/handoff.js.map +1 -1
  56. package/dist/cjs/embed/http.d.cts +18 -1
  57. package/dist/cjs/embed/http.d.ts +18 -1
  58. package/dist/cjs/embed/http.js +239 -40
  59. package/dist/cjs/embed/http.js.map +1 -1
  60. package/dist/cjs/embed/openai.js +39 -17
  61. package/dist/cjs/embed/openai.js.map +1 -1
  62. package/dist/cjs/embed/query.d.cts +9 -3
  63. package/dist/cjs/embed/query.d.ts +9 -3
  64. package/dist/cjs/embed/query.js +257 -90
  65. package/dist/cjs/embed/query.js.map +1 -1
  66. package/dist/cjs/embed/registry.d.cts +2 -2
  67. package/dist/cjs/embed/registry.d.ts +2 -2
  68. package/dist/cjs/embed/registry.js +36 -4
  69. package/dist/cjs/embed/registry.js.map +1 -1
  70. package/dist/cjs/embed/static.js +6 -2
  71. package/dist/cjs/embed/static.js.map +1 -1
  72. package/dist/cjs/embed/store.js +42 -77
  73. package/dist/cjs/embed/store.js.map +1 -1
  74. package/dist/cjs/embed/types.d.cts +5 -2
  75. package/dist/cjs/embed/types.d.ts +5 -2
  76. package/dist/cjs/embed/types.js.map +1 -1
  77. package/dist/cjs/features/embed.js +290 -43
  78. package/dist/cjs/features/embed.js.map +1 -1
  79. package/dist/cjs/graph/traverse.d.cts +2 -2
  80. package/dist/cjs/graph/traverse.d.ts +2 -2
  81. package/dist/cjs/graph/traverse.js +15 -5
  82. package/dist/cjs/graph/traverse.js.map +1 -1
  83. package/dist/cjs/index.d.cts +2 -2
  84. package/dist/cjs/index.d.ts +2 -2
  85. package/dist/cjs/index.js +6 -0
  86. package/dist/cjs/index.js.map +1 -1
  87. package/dist/cjs/output/output.d.cts +4 -36
  88. package/dist/cjs/output/output.d.ts +4 -36
  89. package/dist/cjs/output/output.js +1 -2
  90. package/dist/cjs/output/output.js.map +1 -1
  91. package/dist/cjs/scan/pool.d.cts +2 -1
  92. package/dist/cjs/scan/pool.d.ts +2 -1
  93. package/dist/cjs/scan/pool.js +116 -27
  94. package/dist/cjs/scan/pool.js.map +1 -1
  95. package/dist/cjs/scan/reparse.d.cts +1 -0
  96. package/dist/cjs/scan/reparse.d.ts +1 -0
  97. package/dist/cjs/scan/reparse.js +75 -21
  98. package/dist/cjs/scan/reparse.js.map +1 -1
  99. package/dist/cjs/store/builder.d.cts +11 -21
  100. package/dist/cjs/store/builder.d.ts +11 -21
  101. package/dist/cjs/store/builder.js +1077 -438
  102. package/dist/cjs/store/builder.js.map +1 -1
  103. package/dist/cjs/store/duckdb/connection.js +16 -2
  104. package/dist/cjs/store/duckdb/connection.js.map +1 -1
  105. package/dist/cjs/store/duckdb/open.d.cts +1 -1
  106. package/dist/cjs/store/duckdb/open.d.ts +1 -1
  107. package/dist/cjs/store/duckdb/open.js +73 -65
  108. package/dist/cjs/store/duckdb/open.js.map +1 -1
  109. package/dist/cjs/store/duckdb/reconcile.js +533 -15
  110. package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
  111. package/dist/cjs/store/embed-scope.js +19 -9
  112. package/dist/cjs/store/embed-scope.js.map +1 -1
  113. package/dist/cjs/store/index.d.cts +6 -0
  114. package/dist/cjs/store/index.d.ts +6 -0
  115. package/dist/cjs/store/index.js +8 -4
  116. package/dist/cjs/store/index.js.map +1 -1
  117. package/dist/cjs/store/open.d.cts +6 -1
  118. package/dist/cjs/store/open.d.ts +6 -1
  119. package/dist/cjs/store/open.js +230 -307
  120. package/dist/cjs/store/open.js.map +1 -1
  121. package/dist/cjs/store/readiness.d.cts +5 -0
  122. package/dist/cjs/store/readiness.d.ts +5 -0
  123. package/dist/cjs/store/readiness.js +39 -0
  124. package/dist/cjs/store/readiness.js.map +1 -0
  125. package/dist/cjs/store/reconcile.d.cts +26 -1
  126. package/dist/cjs/store/reconcile.d.ts +26 -1
  127. package/dist/cjs/store/reconcile.js +670 -576
  128. package/dist/cjs/store/reconcile.js.map +1 -1
  129. package/dist/cjs/store/signature.d.cts +1 -0
  130. package/dist/cjs/store/signature.d.ts +1 -0
  131. package/dist/cjs/store/signature.js +9 -0
  132. package/dist/cjs/store/signature.js.map +1 -1
  133. package/dist/cjs/store/sqlite/open.d.cts +1 -1
  134. package/dist/cjs/store/sqlite/open.d.ts +1 -1
  135. package/dist/cjs/store/sqlite/open.js +84 -90
  136. package/dist/cjs/store/sqlite/open.js.map +1 -1
  137. package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
  138. package/dist/cjs/store/sqlite/store.js +63 -60
  139. package/dist/cjs/store/sqlite/store.js.map +1 -1
  140. package/dist/cjs/store/sqlite/vectors.d.cts +2 -2
  141. package/dist/cjs/store/sqlite/vectors.d.ts +2 -2
  142. package/dist/cjs/store/sqlite/vectors.js +8 -4
  143. package/dist/cjs/store/sqlite/vectors.js.map +1 -1
  144. package/dist/cjs/store/stages.js.map +1 -1
  145. package/dist/cjs/store/turso/connection.d.cts +1 -0
  146. package/dist/cjs/store/turso/connection.d.ts +1 -0
  147. package/dist/cjs/store/turso/connection.js +319 -94
  148. package/dist/cjs/store/turso/connection.js.map +1 -1
  149. package/dist/cjs/store/turso/open.d.cts +1 -1
  150. package/dist/cjs/store/turso/open.d.ts +1 -1
  151. package/dist/cjs/store/turso/open.js +70 -74
  152. package/dist/cjs/store/turso/open.js.map +1 -1
  153. package/dist/cjs/store/turso/store.js +25 -23
  154. package/dist/cjs/store/turso/store.js.map +1 -1
  155. package/dist/cjs/store/turso/vectors.d.cts +2 -2
  156. package/dist/cjs/store/turso/vectors.d.ts +2 -2
  157. package/dist/cjs/store/turso/vectors.js +26 -16
  158. package/dist/cjs/store/turso/vectors.js.map +1 -1
  159. package/dist/cjs/store/types.d.cts +4 -1
  160. package/dist/cjs/store/types.d.ts +4 -1
  161. package/dist/cjs/store/types.js.map +1 -1
  162. package/dist/cjs/store/vectors.d.cts +14 -1
  163. package/dist/cjs/store/vectors.d.ts +14 -1
  164. package/dist/cjs/store/vectors.js +61 -0
  165. package/dist/cjs/store/vectors.js.map +1 -1
  166. package/dist/cjs/watch.d.cts +6 -0
  167. package/dist/cjs/watch.d.ts +6 -0
  168. package/dist/cjs/watch.js +42 -10
  169. package/dist/cjs/watch.js.map +1 -1
  170. package/dist/cjs/workers/parse.d.cts +4 -3
  171. package/dist/cjs/workers/parse.d.ts +4 -3
  172. package/dist/cjs/workers/parse.js +19 -17
  173. package/dist/cjs/workers/parse.js.map +1 -1
  174. package/dist/esm/cli/index.d.ts +1 -1
  175. package/dist/esm/cli/index.js +1 -1
  176. package/dist/esm/cli/index.js.map +1 -1
  177. package/dist/esm/cli/path.js +4 -10
  178. package/dist/esm/cli/path.js.map +1 -1
  179. package/dist/esm/cli/related.js +5 -3
  180. package/dist/esm/cli/related.js.map +1 -1
  181. package/dist/esm/commands/index.d.ts +6 -3
  182. package/dist/esm/commands/index.js +1 -0
  183. package/dist/esm/commands/index.js.map +1 -1
  184. package/dist/esm/commands/map.d.ts +14 -5
  185. package/dist/esm/commands/map.js +2 -0
  186. package/dist/esm/commands/map.js.map +1 -1
  187. package/dist/esm/commands/path.d.ts +14 -0
  188. package/dist/esm/commands/path.js +33 -0
  189. package/dist/esm/commands/path.js.map +1 -0
  190. package/dist/esm/commands/peek.d.ts +16 -1
  191. package/dist/esm/commands/peek.js +35 -9
  192. package/dist/esm/commands/peek.js.map +1 -1
  193. package/dist/esm/commands/related.d.ts +16 -3
  194. package/dist/esm/commands/related.js +45 -28
  195. package/dist/esm/commands/related.js.map +1 -1
  196. package/dist/esm/commands/scope.d.ts +1 -0
  197. package/dist/esm/commands/scope.js +58 -0
  198. package/dist/esm/commands/scope.js.map +1 -1
  199. package/dist/esm/commands/search.d.ts +26 -3
  200. package/dist/esm/commands/search.js +85 -18
  201. package/dist/esm/commands/search.js.map +1 -1
  202. package/dist/esm/commands/signals.d.ts +4 -3
  203. package/dist/esm/commands/signals.js +11 -4
  204. package/dist/esm/commands/signals.js.map +1 -1
  205. package/dist/esm/commands/snapshot.d.ts +3 -0
  206. package/dist/esm/commands/snapshot.js +25 -0
  207. package/dist/esm/commands/snapshot.js.map +1 -0
  208. package/dist/esm/embed/cohere.js +35 -10
  209. package/dist/esm/embed/cohere.js.map +1 -1
  210. package/dist/esm/embed/endpoint.d.ts +2 -0
  211. package/dist/esm/embed/endpoint.js +11 -0
  212. package/dist/esm/embed/endpoint.js.map +1 -0
  213. package/dist/esm/embed/handoff.d.ts +7 -2
  214. package/dist/esm/embed/handoff.js +7 -0
  215. package/dist/esm/embed/handoff.js.map +1 -1
  216. package/dist/esm/embed/http.d.ts +18 -1
  217. package/dist/esm/embed/http.js +137 -17
  218. package/dist/esm/embed/http.js.map +1 -1
  219. package/dist/esm/embed/openai.js +34 -12
  220. package/dist/esm/embed/openai.js.map +1 -1
  221. package/dist/esm/embed/query.d.ts +9 -3
  222. package/dist/esm/embed/query.js +98 -26
  223. package/dist/esm/embed/query.js.map +1 -1
  224. package/dist/esm/embed/registry.d.ts +2 -2
  225. package/dist/esm/embed/registry.js +34 -5
  226. package/dist/esm/embed/registry.js.map +1 -1
  227. package/dist/esm/embed/static.js +10 -2
  228. package/dist/esm/embed/static.js.map +1 -1
  229. package/dist/esm/embed/store.js +30 -21
  230. package/dist/esm/embed/store.js.map +1 -1
  231. package/dist/esm/embed/types.d.ts +5 -2
  232. package/dist/esm/embed/types.js.map +1 -1
  233. package/dist/esm/features/embed.js +82 -21
  234. package/dist/esm/features/embed.js.map +1 -1
  235. package/dist/esm/graph/traverse.d.ts +2 -2
  236. package/dist/esm/graph/traverse.js +4 -2
  237. package/dist/esm/graph/traverse.js.map +1 -1
  238. package/dist/esm/index.d.ts +2 -2
  239. package/dist/esm/index.js +1 -1
  240. package/dist/esm/index.js.map +1 -1
  241. package/dist/esm/output/output.d.ts +4 -36
  242. package/dist/esm/output/output.js +1 -2
  243. package/dist/esm/output/output.js.map +1 -1
  244. package/dist/esm/scan/pool.d.ts +2 -1
  245. package/dist/esm/scan/pool.js +80 -23
  246. package/dist/esm/scan/pool.js.map +1 -1
  247. package/dist/esm/scan/reparse.d.ts +1 -0
  248. package/dist/esm/scan/reparse.js +40 -12
  249. package/dist/esm/scan/reparse.js.map +1 -1
  250. package/dist/esm/store/builder.d.ts +11 -21
  251. package/dist/esm/store/builder.js +337 -133
  252. package/dist/esm/store/builder.js.map +1 -1
  253. package/dist/esm/store/duckdb/connection.js +9 -1
  254. package/dist/esm/store/duckdb/connection.js.map +1 -1
  255. package/dist/esm/store/duckdb/open.d.ts +1 -1
  256. package/dist/esm/store/duckdb/open.js +13 -9
  257. package/dist/esm/store/duckdb/open.js.map +1 -1
  258. package/dist/esm/store/duckdb/reconcile.js +145 -10
  259. package/dist/esm/store/duckdb/reconcile.js.map +1 -1
  260. package/dist/esm/store/embed-scope.js +16 -6
  261. package/dist/esm/store/embed-scope.js.map +1 -1
  262. package/dist/esm/store/index.d.ts +6 -0
  263. package/dist/esm/store/index.js +10 -4
  264. package/dist/esm/store/index.js.map +1 -1
  265. package/dist/esm/store/open.d.ts +6 -1
  266. package/dist/esm/store/open.js +77 -87
  267. package/dist/esm/store/open.js.map +1 -1
  268. package/dist/esm/store/readiness.d.ts +5 -0
  269. package/dist/esm/store/readiness.js +11 -0
  270. package/dist/esm/store/readiness.js.map +1 -0
  271. package/dist/esm/store/reconcile.d.ts +26 -1
  272. package/dist/esm/store/reconcile.js +142 -120
  273. package/dist/esm/store/reconcile.js.map +1 -1
  274. package/dist/esm/store/signature.d.ts +1 -0
  275. package/dist/esm/store/signature.js +8 -0
  276. package/dist/esm/store/signature.js.map +1 -1
  277. package/dist/esm/store/sqlite/open.d.ts +1 -1
  278. package/dist/esm/store/sqlite/open.js +12 -12
  279. package/dist/esm/store/sqlite/open.js.map +1 -1
  280. package/dist/esm/store/sqlite/reconcile.js +2 -2
  281. package/dist/esm/store/sqlite/reconcile.js.map +1 -1
  282. package/dist/esm/store/sqlite/store.js +21 -18
  283. package/dist/esm/store/sqlite/store.js.map +1 -1
  284. package/dist/esm/store/sqlite/vectors.d.ts +2 -2
  285. package/dist/esm/store/sqlite/vectors.js +7 -5
  286. package/dist/esm/store/sqlite/vectors.js.map +1 -1
  287. package/dist/esm/store/stages.js.map +1 -1
  288. package/dist/esm/store/turso/connection.d.ts +1 -0
  289. package/dist/esm/store/turso/connection.js +97 -28
  290. package/dist/esm/store/turso/connection.js.map +1 -1
  291. package/dist/esm/store/turso/open.d.ts +1 -1
  292. package/dist/esm/store/turso/open.js +9 -8
  293. package/dist/esm/store/turso/open.js.map +1 -1
  294. package/dist/esm/store/turso/store.js +16 -14
  295. package/dist/esm/store/turso/store.js.map +1 -1
  296. package/dist/esm/store/turso/vectors.d.ts +2 -2
  297. package/dist/esm/store/turso/vectors.js +27 -17
  298. package/dist/esm/store/turso/vectors.js.map +1 -1
  299. package/dist/esm/store/types.d.ts +4 -1
  300. package/dist/esm/store/types.js.map +1 -1
  301. package/dist/esm/store/vectors.d.ts +14 -1
  302. package/dist/esm/store/vectors.js +55 -0
  303. package/dist/esm/store/vectors.js.map +1 -1
  304. package/dist/esm/watch.d.ts +6 -0
  305. package/dist/esm/watch.js +40 -14
  306. package/dist/esm/watch.js.map +1 -1
  307. package/dist/esm/workers/parse.d.ts +4 -3
  308. package/dist/esm/workers/parse.js +19 -17
  309. package/dist/esm/workers/parse.js.map +1 -1
  310. package/package.json +1 -1
  311. package/skills/sense/SKILL.md +4 -2
  312. package/skills/sense/references/stores/duckdb.md +1 -1
  313. package/skills/sense-setup/SKILL.md +2 -2
  314. package/skills/sense-setup/references/embeddings.md +1 -1
  315. package/skills/sense-setup/references/store-benchmarks.md +5 -5
  316. package/skills/sense-setup/references/stores/duckdb.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport { estimateTokens } from '../chunk/index.ts';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Row } from '../output/output.ts';\nimport type { Store } from '../store/types.ts';\nimport { INTERNAL_COLUMNS, scopedPaths } from './scope.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n // Set when the note's frontmatter was refused, so an empty frontmatter block reads as \"did\n // not parse\" rather than \"has none\". Same reason `_parse_error` sits in the row.\n parseError: string | null;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks (or a note thousands of\n // headings), and peek's whole point is bounded output.\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n // Bounded k-hop expansion beyond the immediate ring already shown by outbound/backlinks\n // (depth starts at 2).\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n// peek: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides = {}): Promise<Peek> {\n return serialQuery(store, () => peekIndexed(store, cfg, pathArg, overrides));\n}\n\nasync function peekIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides): Promise<Peek> {\n return store.transaction(async () => {\n const pathsStmt = await store.prepare('SELECT \"path\" FROM frontmatter');\n const paths = ((await pathsStmt.all()) as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const rowStmt = await store.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?');\n const row = (await rowStmt.get(path)) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n let sectionsTotal = 0;\n let sections: Row[] = [];\n if (featureEnabled(cfg, 'sections')) {\n sectionsTotal = ((await (await store.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?')).get(path)) as { n: number }).n;\n sections = (await (await store.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?')).all(path, PEEK_LIST_LIMIT)) as Row[];\n }\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n await scopedPaths(store, cfg, overrides); // validates --preset/--where/--include/--exclude even though peek's own output isn't scope-filtered\n if (featureEnabled(cfg, 'links')) {\n const out = (await (await store.prepare('SELECT target, dst FROM links WHERE src = ? AND (dst IS NULL OR dst != src) ORDER BY target')).all(path)) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = ((await (await store.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ? AND src != dst')).get(path)) as { n: number }).n;\n backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst ORDER BY src LIMIT ?')).all(path, PEEK_LIST_LIMIT)) as Array<{ src: string }>).map((r) => r.src);\n }\n\n // content.text is the stripped body already computed at reconcile time (no extra file read),\n // floored by the byte/4 estimate so stripped-away syntax and frontmatter can't vanish from the price.\n const body = ((await (await store.prepare('SELECT text FROM content WHERE \"path\" = ?')).get(path)) as { text: string } | undefined)?.text;\n const byteTokens = Math.ceil(((row._size as number) ?? 0) / 4);\n const tokens = body !== undefined ? Math.max(Math.ceil(estimateTokens(body)), byteTokens) : byteTokens;\n\n return {\n path,\n tokens,\n frontmatter,\n parseError,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n });\n}\n"],"names":["peek","resolveNote","paths","arg","exact","find","p","base","posix","basename","replace","toLowerCase","matches","filter","length","SenseError","join","PEEK_LIST_LIMIT","store","cfg","pathArg","overrides","serialQuery","peekIndexed","transaction","row","pathsStmt","path","rowStmt","parseError","frontmatter","key","value","sectionsTotal","sections","outbound","backlinks","unresolved","backlinksTotal","out","body","byteTokens","tokens","prepare","all","map","r","get","_parse_error","Object","entries","INTERNAL_COLUMNS","has","featureEnabled","n","scopedPaths","Set","l","dst","target","src","text","Math","ceil","_size","undefined","max","estimateTokens","slice","outboundTotal","unresolvedTotal","off","name"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport { estimateTokens } from '../chunk/index.ts';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Row } from '../output/output.ts';\nimport type { Store } from '../store/types.ts';\nimport { INTERNAL_COLUMNS, materializeScope, scopedPaths } from './scope.ts';\nimport { assertQuerySnapshot } from './snapshot.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\n/** Structured, bounded metadata returned by the root {@link peek} API. */\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n /** Set when frontmatter parsing failed, distinguishing that case from an empty block. */\n parseError: string | null;\n sections: PeekSection[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n /** Totals before each bounded list is truncated. */\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n /** Disabled features whose result blocks are omitted rather than returned empty. */\n off: FeatureName[];\n}\n\nexport interface PeekSection {\n level: number;\n heading: string;\n start_line: number;\n end_line: number;\n tokens: number;\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n/**\n * Returns indexed metadata for a note in scope. Exact excluded paths fail before scoped basename\n * resolution; resolved neighbors stay in scope, while unresolved written targets remain visible.\n */\nexport function peek(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides = {}): Promise<Peek> {\n return serialQuery(store, () => peekIndexed(store, cfg, pathArg, overrides));\n}\n\nasync function peekIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides): Promise<Peek> {\n return store.transaction(async () => {\n await assertQuerySnapshot(store, cfg);\n const pathsStmt = await store.prepare('SELECT \"path\" FROM frontmatter');\n const paths = ((await pathsStmt.all()) as Array<{ path: string }>).map((r) => r.path);\n const allowed = await scopedPaths(store, cfg, overrides);\n if (paths.includes(pathArg) && !allowed.has(pathArg)) {\n throw new SenseError('NOTE_NOT_FOUND', `note \"${pathArg}\" exists but is outside the current scope; change the preset, include/exclude patterns, or where condition`);\n }\n let path: string;\n try {\n path = resolveNote([...allowed], pathArg);\n } catch (err) {\n if (err instanceof SenseError && err.code === 'NOTE_NOT_FOUND') {\n let excludedPath: string;\n try {\n excludedPath = resolveNote(paths, pathArg);\n } catch (unscopedErr) {\n if (unscopedErr instanceof SenseError && unscopedErr.code === 'NOTE_AMBIGUOUS') {\n throw new SenseError('NOTE_NOT_FOUND', `no note in the current scope matches \"${pathArg}\"; indexed matches exist outside the scope, so change the preset, include/exclude patterns, or where condition`);\n }\n throw err;\n }\n throw new SenseError('NOTE_NOT_FOUND', `note \"${excludedPath}\" exists but is outside the current scope; change the preset, include/exclude patterns, or where condition`);\n }\n throw err;\n }\n\n const rowStmt = await store.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?');\n const row = (await rowStmt.get(path)) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n let sectionsTotal = 0;\n let sections: PeekSection[] = [];\n if (featureEnabled(cfg, 'sections')) {\n sectionsTotal = ((await (await store.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?')).get(path)) as { n: number }).n;\n sections = (await (await store.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?')).all(path, PEEK_LIST_LIMIT)) as PeekSection[];\n }\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n await materializeScope(store, '_peek_scope', allowed);\n const out = (await (await store.prepare('SELECT target, dst FROM links WHERE src = ? AND (dst IS NULL OR (dst != src AND dst IN (SELECT \"path\" FROM _peek_scope))) ORDER BY target')).all(path)) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l): l is { target: string; dst: string } => l.dst !== null).map((l) => l.dst))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = ((await (await store.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ? AND src != dst AND src IN (SELECT \"path\" FROM _peek_scope)')).get(path)) as { n: number }).n;\n backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst AND src IN (SELECT \"path\" FROM _peek_scope) ORDER BY src LIMIT ?')).all(path, PEEK_LIST_LIMIT)) as Array<{ src: string }>).map((r) => r.src);\n }\n\n // content.text is the stripped body already computed at reconcile time (no extra file read),\n // floored by the byte/4 estimate so stripped-away syntax and frontmatter can't vanish from the price.\n const body = ((await (await store.prepare('SELECT text FROM content WHERE \"path\" = ?')).get(path)) as { text: string } | undefined)?.text;\n const byteTokens = Math.ceil(((row._size as number) ?? 0) / 4);\n const tokens = body !== undefined ? Math.max(Math.ceil(estimateTokens(body)), byteTokens) : byteTokens;\n\n return {\n path,\n tokens,\n frontmatter,\n parseError,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n });\n}\n"],"names":["peek","resolveNote","paths","arg","exact","find","p","base","posix","basename","replace","toLowerCase","matches","filter","length","SenseError","join","PEEK_LIST_LIMIT","store","cfg","pathArg","overrides","serialQuery","peekIndexed","transaction","row","pathsStmt","allowed","path","excludedPath","rowStmt","parseError","frontmatter","key","value","sectionsTotal","sections","outbound","backlinks","unresolved","backlinksTotal","out","body","byteTokens","tokens","assertQuerySnapshot","prepare","all","map","r","scopedPaths","includes","has","err","code","unscopedErr","get","_parse_error","Object","entries","INTERNAL_COLUMNS","featureEnabled","n","materializeScope","Set","l","dst","target","src","text","Math","ceil","_size","undefined","max","estimateTokens","slice","outboundTotal","unresolvedTotal","off","name"],"mappings":"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"}
@@ -1,7 +1,20 @@
1
1
  import type { ResolvedConfig, SearchOverrides } from '../config/index.js';
2
2
  import type { Store } from '../store/types.js';
3
- export declare function resolveRelatedSeed(store: Store, pathArg: string): Promise<string>;
4
- export declare function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{
3
+ export declare const RELATED_DEFAULT_K = 5;
4
+ /**
5
+ * Options for {@link relatedNotes}. Scope fields filter result candidates, not the seed note;
6
+ * `k` defaults to 5, matching the CLI.
7
+ */
8
+ export interface RelatedOptions extends SearchOverrides {
9
+ k?: number;
10
+ }
11
+ export type RelatedResult = {
5
12
  path: string;
6
13
  similarity: number;
7
- }>>;
14
+ };
15
+ export declare function resolveRelatedSeed(store: Store, pathArg: string): Promise<string>;
16
+ /**
17
+ * Returns the notes most similar to an indexed seed, excluding the seed and notes linked either
18
+ * way. The seed resolves across the indexed tree even when it is outside the candidate scope.
19
+ */
20
+ export declare function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, options?: RelatedOptions): Promise<RelatedResult[]>;
@@ -1,7 +1,20 @@
1
1
  import type { ResolvedConfig, SearchOverrides } from '../config/index.js';
2
2
  import type { Store } from '../store/types.js';
3
- export declare function resolveRelatedSeed(store: Store, pathArg: string): Promise<string>;
4
- export declare function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{
3
+ export declare const RELATED_DEFAULT_K = 5;
4
+ /**
5
+ * Options for {@link relatedNotes}. Scope fields filter result candidates, not the seed note;
6
+ * `k` defaults to 5, matching the CLI.
7
+ */
8
+ export interface RelatedOptions extends SearchOverrides {
9
+ k?: number;
10
+ }
11
+ export type RelatedResult = {
5
12
  path: string;
6
13
  similarity: number;
7
- }>>;
14
+ };
15
+ export declare function resolveRelatedSeed(store: Store, pathArg: string): Promise<string>;
16
+ /**
17
+ * Returns the notes most similar to an indexed seed, excluding the seed and notes linked either
18
+ * way. The seed resolves across the indexed tree even when it is outside the candidate scope.
19
+ */
20
+ export declare function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, options?: RelatedOptions): Promise<RelatedResult[]>;
@@ -9,6 +9,9 @@ function _export(target, all) {
9
9
  });
10
10
  }
11
11
  _export(exports, {
12
+ get RELATED_DEFAULT_K () {
13
+ return RELATED_DEFAULT_K;
14
+ },
12
15
  get relatedNotes () {
13
16
  return relatedNotes;
14
17
  },
@@ -23,6 +26,7 @@ var _errorsts = require("../errors.js");
23
26
  var _serialqueryts = require("../lib/serial-query.js");
24
27
  var _peekts = require("./peek.js");
25
28
  var _scopets = require("./scope.js");
29
+ var _snapshotts = require("./snapshot.js");
26
30
  function _array_like_to_array(arr, len) {
27
31
  if (len == null || len > arr.length) len = arr.length;
28
32
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -175,6 +179,7 @@ function _unsupported_iterable_to_array(o, minLen) {
175
179
  if (n === "Map" || n === "Set") return Array.from(n);
176
180
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
177
181
  }
182
+ var RELATED_DEFAULT_K = 5;
178
183
  function resolveRelatedSeed(store, pathArg) {
179
184
  return _async_to_generator(function() {
180
185
  var paths;
@@ -202,111 +207,139 @@ function resolveRelatedSeed(store, pathArg) {
202
207
  });
203
208
  })();
204
209
  }
205
- function relatedNotes(store, cfg, pathArg, overrides, k) {
210
+ function relatedNotes(store, cfg, pathArg) {
211
+ var options = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : {};
206
212
  return (0, _serialqueryts.serialQuery)(store, function() {
207
- return relatedIndexed(store, cfg, pathArg, overrides, k);
213
+ var _options_k;
214
+ return relatedIndexed(store, cfg, pathArg, options, (_options_k = options.k) !== null && _options_k !== void 0 ? _options_k : RELATED_DEFAULT_K);
208
215
  });
209
216
  }
210
217
  function relatedIndexed(store, cfg, pathArg, overrides, k) {
211
218
  return _async_to_generator(function() {
212
- var path, outbound, backlinks, exclude, effective, e, allowed;
219
+ var effective, e;
213
220
  return _ts_generator(this, function(_state) {
214
- switch(_state.label){
215
- case 0:
216
- return [
217
- 4,
218
- resolveRelatedSeed(store, pathArg)
219
- ];
220
- case 1:
221
- path = _state.sent();
222
- return [
223
- 4,
224
- store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')
225
- ];
226
- case 2:
227
- return [
228
- 4,
229
- _state.sent().all(path)
230
- ];
231
- case 3:
232
- outbound = _state.sent().map(function(r) {
233
- return r.dst;
234
- });
235
- return [
236
- 4,
237
- store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')
238
- ];
239
- case 4:
240
- return [
241
- 4,
242
- _state.sent().all(path)
243
- ];
244
- case 5:
245
- backlinks = _state.sent().map(function(r) {
246
- return r.src;
247
- });
248
- exclude = new Set([
249
- path
250
- ].concat(_to_consumable_array(outbound), _to_consumable_array(backlinks)));
251
- // Vectors are the only signal `related` has, so every way of not having them is an error
252
- // naming the cause; an empty result means one thing, nothing near in meaning.
253
- effective = (0, _indexts.resolveSearch)(cfg, overrides);
254
- if (!(0, _indexts.embedEnabled)(cfg)) {
255
- throw new _errorsts.SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an "embed" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');
256
- }
257
- // search gates on the same signal, so a preset's declared signals mean one thing: without
258
- // this check an overlapping vectors-on preset would answer for a scope that declined them.
259
- if (effective.signals.vectors === undefined) {
260
- throw new _errorsts.SenseError('PRESET_NOT_SEMANTIC', 'preset "'.concat(effective.presetName, '" has no "vectors" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add "vectors" to that preset\'s signals'));
261
- }
262
- // A downloadable HF id can be fetched by preparation. Only a local path with missing files
263
- // can never fetch itself.
264
- e = (0, _indexts.embedConfig)(cfg); // embedEnabled(cfg) above guarantees this is set
265
- if ((0, _storets.localModelMissing)(e)) {
266
- throw new _errorsts.SenseError('EMBED_MODEL_MISSING', 'related ranks notes by meaning, so it needs the embedding model, but the local model path "'.concat(e.model, '" is missing ').concat(_storets.MODEL_FILENAMES, "; point embed.model at a directory containing them (search still works without it, on words and links)"));
267
- }
268
- return [
269
- 4,
270
- (0, _scopets.scopedPaths)(store, cfg, overrides)
271
- ];
272
- case 6:
273
- allowed = _state.sent();
274
- // The seed may sit outside a narrowed candidate scope; both it and every eligible result must
275
- // be ready, while pending vectors in unrelated notes do not block this operation.
276
- return [
277
- 4,
278
- (0, _queryts.ensureDocumentEmbeddings)(store, cfg, new Set(_to_consumable_array(allowed).concat([
279
- path
280
- ])))
281
- ];
282
- case 7:
283
- _state.sent();
284
- return [
285
- 4,
286
- (0, _queryts.hasEmbedding)(store, path)
287
- ];
288
- case 8:
289
- if (!_state.sent()) {
290
- throw new _errorsts.SenseError('NOTE_NOT_EMBEDDED', "".concat(path, " has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning"));
291
- }
292
- return [
293
- 4,
294
- (0, _scopets.scopeHasEmbeddings)(store, cfg, allowed)
295
- ];
296
- case 9:
297
- if (!_state.sent()) return [
298
- 2,
299
- []
300
- ];
301
- return [
302
- 2,
303
- (0, _queryts.similarNotes)(store, cfg, path, {
304
- exclude: exclude,
305
- allowed: allowed,
306
- k: k
307
- })
308
- ];
221
+ if (!Number.isFinite(k) || !Number.isInteger(k) || k <= 0) {
222
+ throw new _errorsts.SenseError('SEARCH_OPTION_INVALID', 'related option "k" must be a positive finite integer, got '.concat(String(k)));
223
+ }
224
+ // Vectors are the only signal `related` has, so every way of not having them is an error
225
+ // naming the cause; an empty result means one thing, nothing near in meaning.
226
+ effective = (0, _indexts.resolveSearch)(cfg, overrides);
227
+ if (!(0, _indexts.embedEnabled)(cfg)) {
228
+ throw new _errorsts.SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an "embed" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');
309
229
  }
230
+ // search gates on the same signal, so a preset's declared signals mean one thing: without
231
+ // this check an overlapping vectors-on preset would answer for a scope that declined them.
232
+ if (effective.signals.vectors === undefined) {
233
+ throw new _errorsts.SenseError('PRESET_NOT_SEMANTIC', 'preset "'.concat(effective.presetName, '" has no "vectors" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add "vectors" to that preset\'s signals'));
234
+ }
235
+ // A downloadable HF id can be fetched by preparation. Only a local path with missing files
236
+ // can never fetch itself.
237
+ e = (0, _indexts.embedConfig)(cfg); // embedEnabled(cfg) above guarantees this is set
238
+ if ((0, _storets.localModelMissing)(e)) {
239
+ throw new _errorsts.SenseError('EMBED_MODEL_MISSING', 'related ranks notes by meaning, so it needs the embedding model, but the local model path "'.concat(e.model, '" is missing ').concat(_storets.MODEL_FILENAMES, "; point embed.model at a directory containing them (search still works without it, on words and links)"));
240
+ }
241
+ return [
242
+ 2,
243
+ store.transaction(function() {
244
+ return _async_to_generator(function() {
245
+ var current, path, outbound, backlinks, exclude, allowed;
246
+ return _ts_generator(this, function(_state) {
247
+ switch(_state.label){
248
+ case 0:
249
+ return [
250
+ 4,
251
+ (0, _snapshotts.assertQuerySnapshot)(store, cfg)
252
+ ];
253
+ case 1:
254
+ _state.sent();
255
+ // Repeat effective preset validation inside the snapshot that supplies its durable scope.
256
+ current = (0, _indexts.resolveSearch)(cfg, overrides);
257
+ if (current.signals.vectors === undefined) {
258
+ throw new _errorsts.SenseError('PRESET_NOT_SEMANTIC', 'preset "'.concat(current.presetName, '" has no "vectors" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add "vectors" to that preset\'s signals'));
259
+ }
260
+ return [
261
+ 4,
262
+ resolveRelatedSeed(store, pathArg)
263
+ ];
264
+ case 2:
265
+ path = _state.sent();
266
+ return [
267
+ 4,
268
+ store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')
269
+ ];
270
+ case 3:
271
+ return [
272
+ 4,
273
+ _state.sent().all(path)
274
+ ];
275
+ case 4:
276
+ outbound = _state.sent().map(function(r) {
277
+ return r.dst;
278
+ });
279
+ return [
280
+ 4,
281
+ store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')
282
+ ];
283
+ case 5:
284
+ return [
285
+ 4,
286
+ _state.sent().all(path)
287
+ ];
288
+ case 6:
289
+ backlinks = _state.sent().map(function(r) {
290
+ return r.src;
291
+ });
292
+ exclude = new Set([
293
+ path
294
+ ].concat(_to_consumable_array(outbound), _to_consumable_array(backlinks)));
295
+ return [
296
+ 4,
297
+ (0, _scopets.scopedPaths)(store, cfg, overrides)
298
+ ];
299
+ case 7:
300
+ allowed = _state.sent();
301
+ // The seed may sit outside a narrowed candidate scope; both it and every eligible result must
302
+ // be ready, while pending vectors in unrelated notes do not block this operation.
303
+ return [
304
+ 4,
305
+ (0, _queryts.ensureDocumentEmbeddings)(store, cfg, new Set(_to_consumable_array(allowed).concat([
306
+ path
307
+ ])))
308
+ ];
309
+ case 8:
310
+ _state.sent();
311
+ return [
312
+ 4,
313
+ (0, _queryts.hasEmbedding)(store, path)
314
+ ];
315
+ case 9:
316
+ if (!_state.sent()) {
317
+ throw new _errorsts.SenseError('NOTE_NOT_EMBEDDED', "".concat(path, " has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning"));
318
+ }
319
+ return [
320
+ 4,
321
+ (0, _scopets.scopeHasEmbeddings)(store, cfg, allowed)
322
+ ];
323
+ case 10:
324
+ if (!_state.sent()) return [
325
+ 2,
326
+ []
327
+ ];
328
+ return [
329
+ 2,
330
+ (0, _scopets.withMaterializedVectorScope)(store, allowed, function() {
331
+ return (0, _queryts.similarNotes)(store, cfg, path, {
332
+ exclude: exclude,
333
+ allowed: allowed,
334
+ k: k
335
+ });
336
+ })
337
+ ];
338
+ }
339
+ });
340
+ })();
341
+ })
342
+ ];
310
343
  });
311
344
  })();
312
345
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedConfig, embedEnabled, resolveSearch } from '../config/index.ts';\nimport { ensureDocumentEmbeddings, hasEmbedding, similarNotes } from '../embed/query.ts';\nimport { localModelMissing, MODEL_FILENAMES } from '../embed/store.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Store } from '../store/types.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\nexport async function resolveRelatedSeed(store: Store, pathArg: string): Promise<string> {\n const paths = ((await (await store.prepare('SELECT \"path\" FROM frontmatter')).all()) as Array<{ path: string }>).map((row) => row.path);\n return resolveNote(paths, pathArg);\n}\n\n// Notes most similar by cosine, excluding self and everything already linked either way.\n// Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.\nexport function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n return serialQuery(store, () => relatedIndexed(store, cfg, pathArg, overrides, k));\n}\n\nasync function relatedIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n const path = await resolveRelatedSeed(store, pathArg);\n\n const outbound = ((await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')).all(path)) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause; an empty result means one thing, nothing near in meaning.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');\n }\n // search gates on the same signal, so a preset's declared signals mean one thing: without\n // this check an overlapping vectors-on preset would answer for a scope that declined them.\n if (effective.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n // A downloadable HF id can be fetched by preparation. Only a local path with missing files\n // can never fetch itself.\n const e = embedConfig(cfg); // embedEnabled(cfg) above guarantees this is set\n if (localModelMissing(e)) {\n throw new SenseError('EMBED_MODEL_MISSING', `related ranks notes by meaning, so it needs the embedding model, but the local model path \"${e.model}\" is missing ${MODEL_FILENAMES}; point embed.model at a directory containing them (search still works without it, on words and links)`);\n }\n const allowed = await scopedPaths(store, cfg, overrides);\n // The seed may sit outside a narrowed candidate scope; both it and every eligible result must\n // be ready, while pending vectors in unrelated notes do not block this operation.\n await ensureDocumentEmbeddings(store, cfg, new Set([...allowed, path]));\n if (!(await hasEmbedding(store, path))) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!(await scopeHasEmbeddings(store, cfg, allowed))) return [];\n return similarNotes(store, cfg, path, { exclude, allowed, k });\n}\n"],"names":["relatedNotes","resolveRelatedSeed","store","pathArg","paths","prepare","all","map","row","path","resolveNote","cfg","overrides","k","serialQuery","relatedIndexed","outbound","backlinks","exclude","effective","e","allowed","r","dst","src","Set","resolveSearch","embedEnabled","SenseError","signals","vectors","undefined","presetName","embedConfig","localModelMissing","MODEL_FILENAMES","model","scopedPaths","ensureDocumentEmbeddings","hasEmbedding","scopeHasEmbeddings","similarNotes"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedConfig, embedEnabled, resolveSearch } from '../config/index.ts';\nimport { ensureDocumentEmbeddings, hasEmbedding, similarNotes } from '../embed/query.ts';\nimport { localModelMissing, MODEL_FILENAMES } from '../embed/store.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Store } from '../store/types.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings, withMaterializedVectorScope } from './scope.ts';\nimport { assertQuerySnapshot } from './snapshot.ts';\n\nexport const RELATED_DEFAULT_K = 5;\n\n/**\n * Options for {@link relatedNotes}. Scope fields filter result candidates, not the seed note;\n * `k` defaults to 5, matching the CLI.\n */\nexport interface RelatedOptions extends SearchOverrides {\n k?: number;\n}\n\nexport type RelatedResult = {\n path: string;\n similarity: number;\n};\n\nexport async function resolveRelatedSeed(store: Store, pathArg: string): Promise<string> {\n const paths = ((await (await store.prepare('SELECT \"path\" FROM frontmatter')).all()) as Array<{ path: string }>).map((row) => row.path);\n return resolveNote(paths, pathArg);\n}\n\n/**\n * Returns the notes most similar to an indexed seed, excluding the seed and notes linked either\n * way. The seed resolves across the indexed tree even when it is outside the candidate scope.\n */\nexport function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, options: RelatedOptions = {}): Promise<RelatedResult[]> {\n return serialQuery(store, () => relatedIndexed(store, cfg, pathArg, options, options.k ?? RELATED_DEFAULT_K));\n}\n\nasync function relatedIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: RelatedOptions, k: number): Promise<RelatedResult[]> {\n if (!Number.isFinite(k) || !Number.isInteger(k) || k <= 0) {\n throw new SenseError('SEARCH_OPTION_INVALID', `related option \"k\" must be a positive finite integer, got ${String(k)}`);\n }\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause; an empty result means one thing, nothing near in meaning.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');\n }\n // search gates on the same signal, so a preset's declared signals mean one thing: without\n // this check an overlapping vectors-on preset would answer for a scope that declined them.\n if (effective.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n // A downloadable HF id can be fetched by preparation. Only a local path with missing files\n // can never fetch itself.\n const e = embedConfig(cfg); // embedEnabled(cfg) above guarantees this is set\n if (localModelMissing(e)) {\n throw new SenseError('EMBED_MODEL_MISSING', `related ranks notes by meaning, so it needs the embedding model, but the local model path \"${e.model}\" is missing ${MODEL_FILENAMES}; point embed.model at a directory containing them (search still works without it, on words and links)`);\n }\n\n return store.transaction(async () => {\n await assertQuerySnapshot(store, cfg);\n // Repeat effective preset validation inside the snapshot that supplies its durable scope.\n const current = resolveSearch(cfg, overrides);\n if (current.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${current.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n\n const path = await resolveRelatedSeed(store, pathArg);\n const outbound = ((await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')).all(path)) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n const allowed = await scopedPaths(store, cfg, overrides);\n // The seed may sit outside a narrowed candidate scope; both it and every eligible result must\n // be ready, while pending vectors in unrelated notes do not block this operation.\n await ensureDocumentEmbeddings(store, cfg, new Set([...allowed, path]));\n if (!(await hasEmbedding(store, path))) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!(await scopeHasEmbeddings(store, cfg, allowed))) return [];\n return withMaterializedVectorScope(store, allowed, () => similarNotes(store, cfg, path, { exclude, allowed, k }));\n });\n}\n"],"names":["RELATED_DEFAULT_K","relatedNotes","resolveRelatedSeed","store","pathArg","paths","prepare","all","map","row","path","resolveNote","cfg","options","serialQuery","relatedIndexed","k","overrides","effective","e","Number","isFinite","isInteger","SenseError","String","resolveSearch","embedEnabled","signals","vectors","undefined","presetName","embedConfig","localModelMissing","MODEL_FILENAMES","model","transaction","current","outbound","backlinks","exclude","allowed","assertQuerySnapshot","r","dst","src","Set","scopedPaths","ensureDocumentEmbeddings","hasEmbedding","scopeHasEmbeddings","withMaterializedVectorScope","similarNotes"],"mappings":"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"}
@@ -7,4 +7,5 @@ export declare function rawScope(store: Store, cfg: ResolvedConfig, overrides: S
7
7
  export declare function narrowByWhere(store: Store, paths: Set<string>, where: string | undefined): Promise<Set<string>>;
8
8
  export declare function scopedPaths(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides): Promise<Set<string>>;
9
9
  export declare function materializeScope(store: Store, table: string, paths: Set<string>): Promise<void>;
10
+ export declare function withMaterializedVectorScope<T>(store: Store, allowed: Set<string>, callback: () => Promise<T>, knownTotal?: number): Promise<T>;
10
11
  export declare function setupMapScope(store: Store, paths: Set<string>): Promise<void>;
@@ -7,4 +7,5 @@ export declare function rawScope(store: Store, cfg: ResolvedConfig, overrides: S
7
7
  export declare function narrowByWhere(store: Store, paths: Set<string>, where: string | undefined): Promise<Set<string>>;
8
8
  export declare function scopedPaths(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides): Promise<Set<string>>;
9
9
  export declare function materializeScope(store: Store, table: string, paths: Set<string>): Promise<void>;
10
+ export declare function withMaterializedVectorScope<T>(store: Store, allowed: Set<string>, callback: () => Promise<T>, knownTotal?: number): Promise<T>;
10
11
  export declare function setupMapScope(store: Store, paths: Set<string>): Promise<void>;
@@ -32,11 +32,15 @@ _export(exports, {
32
32
  },
33
33
  get setupMapScope () {
34
34
  return setupMapScope;
35
+ },
36
+ get withMaterializedVectorScope () {
37
+ return withMaterializedVectorScope;
35
38
  }
36
39
  });
37
40
  var _nodepath = require("node:path");
38
41
  var _indexts = require("../config/index.js");
39
42
  var _columnhintts = require("../output/column-hint.js");
43
+ var _vectorsts = require("../store/vectors.js");
40
44
  function _array_like_to_array(arr, len) {
41
45
  if (len == null || len > arr.length) len = arr.length;
42
46
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -448,6 +452,151 @@ function materializeScope(store, table, paths) {
448
452
  });
449
453
  })();
450
454
  }
455
+ function withMaterializedVectorScope(store, allowed, callback, knownTotal) {
456
+ return _async_to_generator(function() {
457
+ var reservation, total, stmt, row, err, operationFailed, operationError, result, err1, cleanupFailed, cleanupError, err2;
458
+ return _ts_generator(this, function(_state) {
459
+ switch(_state.label){
460
+ case 0:
461
+ reservation = (0, _vectorsts.reserveNativeVectorScope)(store.vectors, allowed);
462
+ if (!reservation) return [
463
+ 2,
464
+ callback()
465
+ ];
466
+ if (allowed.size === 0) {
467
+ reservation.invalidate();
468
+ return [
469
+ 2,
470
+ callback()
471
+ ];
472
+ }
473
+ total = knownTotal;
474
+ if (!(total === undefined)) return [
475
+ 3,
476
+ 5
477
+ ];
478
+ _state.label = 1;
479
+ case 1:
480
+ _state.trys.push([
481
+ 1,
482
+ 4,
483
+ ,
484
+ 5
485
+ ]);
486
+ return [
487
+ 4,
488
+ store.prepare('SELECT COUNT(*) AS count FROM frontmatter')
489
+ ];
490
+ case 2:
491
+ stmt = _state.sent();
492
+ return [
493
+ 4,
494
+ stmt.get()
495
+ ];
496
+ case 3:
497
+ row = _state.sent();
498
+ total = Number(row.count);
499
+ return [
500
+ 3,
501
+ 5
502
+ ];
503
+ case 4:
504
+ err = _state.sent();
505
+ reservation.invalidate();
506
+ throw err;
507
+ case 5:
508
+ // Materialization only buys work when this is a nonempty true narrowing and the allowed side
509
+ // is no larger than its complement. Direct adapter calls and broader scopes retain JS filtering.
510
+ if (allowed.size >= total || allowed.size > total - allowed.size) {
511
+ reservation.invalidate();
512
+ return [
513
+ 2,
514
+ callback()
515
+ ];
516
+ }
517
+ operationFailed = false;
518
+ _state.label = 6;
519
+ case 6:
520
+ _state.trys.push([
521
+ 6,
522
+ 9,
523
+ ,
524
+ 10
525
+ ]);
526
+ return [
527
+ 4,
528
+ materializeScope(store, _vectorsts.NATIVE_VECTOR_SCOPE_TABLE, allowed)
529
+ ];
530
+ case 7:
531
+ _state.sent();
532
+ reservation.activate();
533
+ return [
534
+ 4,
535
+ callback()
536
+ ];
537
+ case 8:
538
+ result = _state.sent();
539
+ return [
540
+ 3,
541
+ 10
542
+ ];
543
+ case 9:
544
+ err1 = _state.sent();
545
+ operationFailed = true;
546
+ operationError = err1;
547
+ return [
548
+ 3,
549
+ 10
550
+ ];
551
+ case 10:
552
+ cleanupFailed = false;
553
+ _state.label = 11;
554
+ case 11:
555
+ _state.trys.push([
556
+ 11,
557
+ 13,
558
+ 14,
559
+ 15
560
+ ]);
561
+ reservation.deactivate();
562
+ return [
563
+ 4,
564
+ store.exec("DROP TABLE IF EXISTS ".concat(_vectorsts.NATIVE_VECTOR_SCOPE_TABLE))
565
+ ];
566
+ case 12:
567
+ _state.sent();
568
+ return [
569
+ 3,
570
+ 15
571
+ ];
572
+ case 13:
573
+ err2 = _state.sent();
574
+ cleanupFailed = true;
575
+ cleanupError = err2;
576
+ return [
577
+ 3,
578
+ 15
579
+ ];
580
+ case 14:
581
+ reservation.invalidate();
582
+ return [
583
+ 7
584
+ ];
585
+ case 15:
586
+ if (operationFailed && cleanupFailed) throw new AggregateError([
587
+ operationError,
588
+ cleanupError
589
+ ], 'native vector scope operation and cleanup failed');
590
+ if (operationFailed) throw operationError;
591
+ if (cleanupFailed) throw cleanupError;
592
+ return [
593
+ 2,
594
+ result
595
+ ];
596
+ }
597
+ });
598
+ })();
599
+ }
451
600
  function setupMapScope(store, paths) {
452
601
  return _async_to_generator(function() {
453
602
  return _ts_generator(this, function(_state) {