sensemaking 0.24.6 → 0.25.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 (249) hide show
  1. package/README.md +20 -11
  2. package/dist/cjs/cli/build.d.cts +3 -0
  3. package/dist/cjs/cli/build.d.ts +3 -0
  4. package/dist/cjs/cli/build.js +195 -0
  5. package/dist/cjs/cli/build.js.map +1 -0
  6. package/dist/cjs/cli/index.d.cts +7 -6
  7. package/dist/cjs/cli/index.d.ts +7 -6
  8. package/dist/cjs/cli/index.js +12 -6
  9. package/dist/cjs/cli/index.js.map +1 -1
  10. package/dist/cjs/cli/init.js +2 -2
  11. package/dist/cjs/cli/init.js.map +1 -1
  12. package/dist/cjs/cli/map.js +7 -2
  13. package/dist/cjs/cli/map.js.map +1 -1
  14. package/dist/cjs/cli/named.js +63 -21
  15. package/dist/cjs/cli/named.js.map +1 -1
  16. package/dist/cjs/cli/path.js +7 -2
  17. package/dist/cjs/cli/path.js.map +1 -1
  18. package/dist/cjs/cli/peek.js +7 -2
  19. package/dist/cjs/cli/peek.js.map +1 -1
  20. package/dist/cjs/cli/related.js +55 -6
  21. package/dist/cjs/cli/related.js.map +1 -1
  22. package/dist/cjs/cli/search.js +54 -15
  23. package/dist/cjs/cli/search.js.map +1 -1
  24. package/dist/cjs/cli/shared.d.cts +9 -3
  25. package/dist/cjs/cli/shared.d.ts +9 -3
  26. package/dist/cjs/cli/shared.js +109 -74
  27. package/dist/cjs/cli/shared.js.map +1 -1
  28. package/dist/cjs/cli/sql.js +8 -3
  29. package/dist/cjs/cli/sql.js.map +1 -1
  30. package/dist/cjs/cli/status.js +142 -50
  31. package/dist/cjs/cli/status.js.map +1 -1
  32. package/dist/cjs/cli/types.d.cts +4 -2
  33. package/dist/cjs/cli/types.d.ts +4 -2
  34. package/dist/cjs/cli.js +10 -4
  35. package/dist/cjs/cli.js.map +1 -1
  36. package/dist/cjs/commands/map.js +10 -6
  37. package/dist/cjs/commands/map.js.map +1 -1
  38. package/dist/cjs/commands/peek.js +10 -6
  39. package/dist/cjs/commands/peek.js.map +1 -1
  40. package/dist/cjs/commands/related.d.cts +1 -0
  41. package/dist/cjs/commands/related.d.ts +1 -0
  42. package/dist/cjs/commands/related.js +56 -21
  43. package/dist/cjs/commands/related.js.map +1 -1
  44. package/dist/cjs/commands/scope.js +1 -1
  45. package/dist/cjs/commands/scope.js.map +1 -1
  46. package/dist/cjs/commands/search.js +102 -84
  47. package/dist/cjs/commands/search.js.map +1 -1
  48. package/dist/cjs/config/index.d.cts +1 -1
  49. package/dist/cjs/config/index.d.ts +1 -1
  50. package/dist/cjs/config/index.js.map +1 -1
  51. package/dist/cjs/config/load.d.cts +6 -1
  52. package/dist/cjs/config/load.d.ts +6 -1
  53. package/dist/cjs/config/load.js +38 -1
  54. package/dist/cjs/config/load.js.map +1 -1
  55. package/dist/cjs/config/types.d.cts +2 -1
  56. package/dist/cjs/config/types.d.ts +2 -1
  57. package/dist/cjs/config/types.js +1 -1
  58. package/dist/cjs/config/types.js.map +1 -1
  59. package/dist/cjs/config/validate.js +20 -0
  60. package/dist/cjs/config/validate.js.map +1 -1
  61. package/dist/cjs/embed/query.d.cts +2 -1
  62. package/dist/cjs/embed/query.d.ts +2 -1
  63. package/dist/cjs/embed/query.js +186 -109
  64. package/dist/cjs/embed/query.js.map +1 -1
  65. package/dist/cjs/errors.d.cts +1 -1
  66. package/dist/cjs/errors.d.ts +1 -1
  67. package/dist/cjs/errors.js.map +1 -1
  68. package/dist/cjs/features/embed.js.map +1 -1
  69. package/dist/cjs/graph/traverse.js +11 -6
  70. package/dist/cjs/graph/traverse.js.map +1 -1
  71. package/dist/cjs/index.d.cts +3 -3
  72. package/dist/cjs/index.d.ts +3 -3
  73. package/dist/cjs/index.js +3 -0
  74. package/dist/cjs/index.js.map +1 -1
  75. package/dist/cjs/lib/atomic-write.js +20 -6
  76. package/dist/cjs/lib/atomic-write.js.map +1 -1
  77. package/dist/cjs/lib/serial-query.d.cts +1 -0
  78. package/dist/cjs/lib/serial-query.d.ts +1 -0
  79. package/dist/cjs/lib/serial-query.js +22 -0
  80. package/dist/cjs/lib/serial-query.js.map +1 -0
  81. package/dist/cjs/scan/index.d.cts +5 -0
  82. package/dist/cjs/scan/index.d.ts +5 -0
  83. package/dist/cjs/scan/index.js +12 -4
  84. package/dist/cjs/scan/index.js.map +1 -1
  85. package/dist/cjs/scan/pool.d.cts +5 -0
  86. package/dist/cjs/scan/pool.d.ts +5 -0
  87. package/dist/cjs/scan/pool.js +167 -55
  88. package/dist/cjs/scan/pool.js.map +1 -1
  89. package/dist/cjs/store/builder.d.cts +6 -1
  90. package/dist/cjs/store/builder.d.ts +6 -1
  91. package/dist/cjs/store/builder.js +135 -5
  92. package/dist/cjs/store/builder.js.map +1 -1
  93. package/dist/cjs/store/duckdb/lexical.d.cts +2 -0
  94. package/dist/cjs/store/duckdb/lexical.d.ts +2 -0
  95. package/dist/cjs/store/duckdb/lexical.js +139 -7
  96. package/dist/cjs/store/duckdb/lexical.js.map +1 -1
  97. package/dist/cjs/store/duckdb/open.d.cts +3 -3
  98. package/dist/cjs/store/duckdb/open.d.ts +3 -3
  99. package/dist/cjs/store/duckdb/open.js +61 -42
  100. package/dist/cjs/store/duckdb/open.js.map +1 -1
  101. package/dist/cjs/store/duckdb/reconcile.js +2 -2
  102. package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
  103. package/dist/cjs/store/duckdb/vectors.d.cts +1 -0
  104. package/dist/cjs/store/duckdb/vectors.d.ts +1 -0
  105. package/dist/cjs/store/duckdb/vectors.js +55 -43
  106. package/dist/cjs/store/duckdb/vectors.js.map +1 -1
  107. package/dist/cjs/store/index.d.cts +23 -4
  108. package/dist/cjs/store/index.d.ts +23 -4
  109. package/dist/cjs/store/index.js +224 -15
  110. package/dist/cjs/store/index.js.map +1 -1
  111. package/dist/cjs/store/open.d.cts +9 -1
  112. package/dist/cjs/store/open.d.ts +9 -1
  113. package/dist/cjs/store/open.js +479 -128
  114. package/dist/cjs/store/open.js.map +1 -1
  115. package/dist/cjs/store/reconcile.js +93 -63
  116. package/dist/cjs/store/reconcile.js.map +1 -1
  117. package/dist/cjs/store/sqlite/open.d.cts +3 -3
  118. package/dist/cjs/store/sqlite/open.d.ts +3 -3
  119. package/dist/cjs/store/sqlite/open.js +44 -36
  120. package/dist/cjs/store/sqlite/open.js.map +1 -1
  121. package/dist/cjs/store/turso/open.d.cts +3 -3
  122. package/dist/cjs/store/turso/open.d.ts +3 -3
  123. package/dist/cjs/store/turso/open.js +83 -66
  124. package/dist/cjs/store/turso/open.js.map +1 -1
  125. package/dist/cjs/store/turso/store.d.cts +3 -1
  126. package/dist/cjs/store/turso/store.d.ts +3 -1
  127. package/dist/cjs/store/turso/store.js +46 -31
  128. package/dist/cjs/store/turso/store.js.map +1 -1
  129. package/dist/cjs/store/types.d.cts +12 -3
  130. package/dist/cjs/store/types.d.ts +12 -3
  131. package/dist/cjs/store/types.js.map +1 -1
  132. package/dist/cjs/watch.js +155 -35
  133. package/dist/cjs/watch.js.map +1 -1
  134. package/dist/esm/cli/build.d.ts +3 -0
  135. package/dist/esm/cli/build.js +18 -0
  136. package/dist/esm/cli/build.js.map +1 -0
  137. package/dist/esm/cli/index.d.ts +7 -6
  138. package/dist/esm/cli/index.js +8 -6
  139. package/dist/esm/cli/index.js.map +1 -1
  140. package/dist/esm/cli/init.js +2 -2
  141. package/dist/esm/cli/init.js.map +1 -1
  142. package/dist/esm/cli/map.js +8 -2
  143. package/dist/esm/cli/map.js.map +1 -1
  144. package/dist/esm/cli/named.js +38 -14
  145. package/dist/esm/cli/named.js.map +1 -1
  146. package/dist/esm/cli/path.js +8 -2
  147. package/dist/esm/cli/path.js.map +1 -1
  148. package/dist/esm/cli/peek.js +8 -2
  149. package/dist/esm/cli/peek.js.map +1 -1
  150. package/dist/esm/cli/related.js +21 -3
  151. package/dist/esm/cli/related.js.map +1 -1
  152. package/dist/esm/cli/search.js +28 -7
  153. package/dist/esm/cli/search.js.map +1 -1
  154. package/dist/esm/cli/shared.d.ts +9 -3
  155. package/dist/esm/cli/shared.js +44 -24
  156. package/dist/esm/cli/shared.js.map +1 -1
  157. package/dist/esm/cli/sql.js +8 -2
  158. package/dist/esm/cli/sql.js.map +1 -1
  159. package/dist/esm/cli/status.js +166 -111
  160. package/dist/esm/cli/status.js.map +1 -1
  161. package/dist/esm/cli/types.d.ts +4 -2
  162. package/dist/esm/cli/types.js.map +1 -1
  163. package/dist/esm/cli.js +9 -4
  164. package/dist/esm/cli.js.map +1 -1
  165. package/dist/esm/commands/map.js +5 -1
  166. package/dist/esm/commands/map.js.map +1 -1
  167. package/dist/esm/commands/peek.js +5 -1
  168. package/dist/esm/commands/peek.js.map +1 -1
  169. package/dist/esm/commands/related.d.ts +1 -0
  170. package/dist/esm/commands/related.js +19 -10
  171. package/dist/esm/commands/related.js.map +1 -1
  172. package/dist/esm/commands/scope.js +1 -1
  173. package/dist/esm/commands/scope.js.map +1 -1
  174. package/dist/esm/commands/search.js +11 -10
  175. package/dist/esm/commands/search.js.map +1 -1
  176. package/dist/esm/config/index.d.ts +1 -1
  177. package/dist/esm/config/index.js.map +1 -1
  178. package/dist/esm/config/load.d.ts +6 -1
  179. package/dist/esm/config/load.js +32 -3
  180. package/dist/esm/config/load.js.map +1 -1
  181. package/dist/esm/config/types.d.ts +2 -1
  182. package/dist/esm/config/types.js +1 -1
  183. package/dist/esm/config/types.js.map +1 -1
  184. package/dist/esm/config/validate.js +20 -0
  185. package/dist/esm/config/validate.js.map +1 -1
  186. package/dist/esm/embed/query.d.ts +2 -1
  187. package/dist/esm/embed/query.js +40 -34
  188. package/dist/esm/embed/query.js.map +1 -1
  189. package/dist/esm/errors.d.ts +1 -1
  190. package/dist/esm/errors.js.map +1 -1
  191. package/dist/esm/features/embed.js.map +1 -1
  192. package/dist/esm/graph/traverse.js +5 -1
  193. package/dist/esm/graph/traverse.js.map +1 -1
  194. package/dist/esm/index.d.ts +3 -3
  195. package/dist/esm/index.js +1 -1
  196. package/dist/esm/index.js.map +1 -1
  197. package/dist/esm/lib/atomic-write.js +14 -6
  198. package/dist/esm/lib/atomic-write.js.map +1 -1
  199. package/dist/esm/lib/serial-query.d.ts +1 -0
  200. package/dist/esm/lib/serial-query.js +13 -0
  201. package/dist/esm/lib/serial-query.js.map +1 -0
  202. package/dist/esm/scan/index.d.ts +5 -0
  203. package/dist/esm/scan/index.js +7 -3
  204. package/dist/esm/scan/index.js.map +1 -1
  205. package/dist/esm/scan/pool.d.ts +5 -0
  206. package/dist/esm/scan/pool.js +41 -16
  207. package/dist/esm/scan/pool.js.map +1 -1
  208. package/dist/esm/store/builder.d.ts +6 -1
  209. package/dist/esm/store/builder.js +19 -0
  210. package/dist/esm/store/builder.js.map +1 -1
  211. package/dist/esm/store/duckdb/lexical.d.ts +2 -0
  212. package/dist/esm/store/duckdb/lexical.js +32 -4
  213. package/dist/esm/store/duckdb/lexical.js.map +1 -1
  214. package/dist/esm/store/duckdb/open.d.ts +3 -3
  215. package/dist/esm/store/duckdb/open.js +16 -7
  216. package/dist/esm/store/duckdb/open.js.map +1 -1
  217. package/dist/esm/store/duckdb/reconcile.js +2 -2
  218. package/dist/esm/store/duckdb/reconcile.js.map +1 -1
  219. package/dist/esm/store/duckdb/vectors.d.ts +1 -0
  220. package/dist/esm/store/duckdb/vectors.js +18 -14
  221. package/dist/esm/store/duckdb/vectors.js.map +1 -1
  222. package/dist/esm/store/index.d.ts +23 -4
  223. package/dist/esm/store/index.js +80 -15
  224. package/dist/esm/store/index.js.map +1 -1
  225. package/dist/esm/store/open.d.ts +9 -1
  226. package/dist/esm/store/open.js +175 -84
  227. package/dist/esm/store/open.js.map +1 -1
  228. package/dist/esm/store/reconcile.js +12 -0
  229. package/dist/esm/store/reconcile.js.map +1 -1
  230. package/dist/esm/store/sqlite/open.d.ts +3 -3
  231. package/dist/esm/store/sqlite/open.js +9 -6
  232. package/dist/esm/store/sqlite/open.js.map +1 -1
  233. package/dist/esm/store/turso/open.d.ts +3 -3
  234. package/dist/esm/store/turso/open.js +14 -10
  235. package/dist/esm/store/turso/open.js.map +1 -1
  236. package/dist/esm/store/turso/store.d.ts +3 -1
  237. package/dist/esm/store/turso/store.js +5 -1
  238. package/dist/esm/store/turso/store.js.map +1 -1
  239. package/dist/esm/store/types.d.ts +12 -3
  240. package/dist/esm/store/types.js.map +1 -1
  241. package/dist/esm/watch.js +57 -19
  242. package/dist/esm/watch.js.map +1 -1
  243. package/package.json +1 -1
  244. package/schema.json +7 -2
  245. package/skills/sense/SKILL.md +3 -3
  246. package/skills/sense-bases/SKILL.md +1 -2
  247. package/skills/sense-setup/SKILL.md +1 -1
  248. package/skills/sense-setup/references/embeddings.md +1 -1
  249. package/skills/sense-setup/references/store-benchmarks.md +6 -6
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport { type Config, STORE_NAMES, type StoreName } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'root', 'presets', 'features', 'embed', 'store', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// The one source of truth for what the embed block accepts. schema.json carries an editor-facing\n// copy for autocomplete; nothing at runtime reads it.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\nfunction validateStoreKey(value: unknown, configPath: string): void {\n if (!STORE_NAMES.includes(value as StoreName)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: store must be ${STORE_NAMES.map((name) => `\"${name}\"`).join(' or ')}`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that doesn't\n// need the top-level embed block: links is seeded by word-match rows, so declaring it without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string is rejected rather than inferred to be SQL: an entry says which of the two\n // verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n if (cfg.root !== undefined && (typeof cfg.root !== 'string' || cfg.root.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: root must be a non-empty path string`);\n }\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n // `content` (content.tokenize) is rejected by name for the same reason: every outcome it\n // selected (unspaced-script search, stemming, quoted-phrase substring matching) is now\n // automatic on every store, so a config that still sets it would silently get nothing back.\n if (cfg.content !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize was removed -- every outcome it selected is now automatic on every store; remove the \"content\" block`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.store !== undefined) {\n validateStoreKey(cfg.store, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["SenseError","SIGNAL_NAMES","SIGNAL_PREREQUISITES","STORE_NAMES","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","SAVED_SEARCH_KEYS","unknownConfigKeys","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","validateLegacyScan","parsed","configPath","scan","include","validateStoreKey","includes","map","name","join","validateFeaturesBlock","block","unknown","v","entries","validateEmbedBlock","embed","model","undefined","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","isSignalWeight","isFinite","validatePresetSignals","signal","weight","names","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","trim","search","validateConfig","root","checks","content","presets","default","queries","features","store"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport { type Config, STORE_NAMES, type StoreName } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'root', 'build', 'presets', 'features', 'embed', 'store', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// The one source of truth for what the embed block accepts. schema.json carries an editor-facing\n// copy for autocomplete; nothing at runtime reads it.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\nconst RESERVED_QUERY_NAMES = new Set(['init', 'build', 'watch', 'status', 'download', 'sql', 'search', 'map', 'peek', 'path', 'related']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\nfunction validateStoreKey(value: unknown, configPath: string): void {\n if (!STORE_NAMES.includes(value as StoreName)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: store must be ${STORE_NAMES.map((name) => `\"${name}\"`).join(' or ')}`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that doesn't\n// need the top-level embed block: links is seeded by word-match rows, so declaring it without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n if (RESERVED_QUERY_NAMES.has(name)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} uses a reserved command name and can never be run as a saved query`);\n }\n // A bare string is rejected rather than inferred to be SQL: an entry says which of the two\n // verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n if (cfg.root !== undefined && (typeof cfg.root !== 'string' || cfg.root.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: root must be a non-empty path string`);\n }\n if (cfg.build !== undefined && typeof cfg.build !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: build must be a boolean`);\n }\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n // `content` (content.tokenize) is rejected by name for the same reason: every outcome it\n // selected (unspaced-script search, stemming, quoted-phrase substring matching) is now\n // automatic on every store, so a config that still sets it would silently get nothing back.\n if (cfg.content !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize was removed -- every outcome it selected is now automatic on every store; remove the \"content\" block`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.store !== undefined) {\n validateStoreKey(cfg.store, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["SenseError","SIGNAL_NAMES","SIGNAL_PREREQUISITES","STORE_NAMES","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","SAVED_SEARCH_KEYS","RESERVED_QUERY_NAMES","unknownConfigKeys","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","validateLegacyScan","parsed","configPath","scan","include","validateStoreKey","includes","map","name","join","validateFeaturesBlock","block","unknown","v","entries","validateEmbedBlock","embed","model","undefined","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","isSignalWeight","isFinite","validatePresetSignals","signal","weight","names","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","trim","search","validateConfig","root","build","checks","content","presets","default","queries","features","store"],"mappings":"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"}
@@ -4,7 +4,8 @@ export declare function toStore(full: Float32Array, dims: number, int8: boolean)
4
4
  v: Float32Array;
5
5
  scale: number;
6
6
  };
7
- export declare function embedPending(store: Store, cfg: Config, baseDir: string): Promise<void>;
7
+ export declare function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>): Promise<void>;
8
+ export declare function ensureDocumentEmbeddings(store: Store, _cfg: Config, allowed: ReadonlySet<string>): Promise<void>;
8
9
  export declare function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;
9
10
  export declare function hasEmbedding(store: Store, path: string): Promise<boolean>;
10
11
  export declare function similarNotes(store: Store, _cfg: Config, path: string, opts: {
@@ -1,9 +1,7 @@
1
- import { existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
1
  import { SenseError } from '../errors.js';
4
2
  import { embed } from '../features/embed.js';
5
3
  import { progress } from '../output/progress.js';
6
- import { parseFile } from '../scan/index.js';
4
+ import { parseSource } from '../scan/index.js';
7
5
  import { takeChunkText } from './handoff.js';
8
6
  import { checkLanguageFit } from './langfit.js';
9
7
  import { getProvider } from './registry.js';
@@ -28,11 +26,15 @@ export function toStore(full, dims, int8) {
28
26
  };
29
27
  }
30
28
  // Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in
31
- // this process (handoff.ts); a row left pending by an earlier command re-derives from the file.
32
- export async function embedPending(store, cfg, baseDir) {
29
+ // this process (handoff.ts); a row left pending by an earlier command re-derives from the exact
30
+ // source string persisted with that indexed generation.
31
+ export async function embedPending(store, cfg, allowed) {
33
32
  var _takeChunkText;
34
33
  const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table
35
- const dirty = await store.vectors.pending();
34
+ const dirty = (await store.vectors.pending()).filter((row)=>{
35
+ var _ref;
36
+ return (_ref = allowed === null || allowed === void 0 ? void 0 : allowed.has(row.path)) !== null && _ref !== void 0 ? _ref : true;
37
+ });
36
38
  if (dirty.length === 0) return;
37
39
  const storeDims = Math.min(STORE_DIMS, provider.dims);
38
40
  const byPath = new Map();
@@ -43,13 +45,11 @@ export async function embedPending(store, cfg, baseDir) {
43
45
  byPath.set(row.path, list);
44
46
  }
45
47
  const textByPath = (_takeChunkText = takeChunkText(store)) !== null && _takeChunkText !== void 0 ? _takeChunkText : new Map();
48
+ const sourceStmt = await store.prepare('SELECT text FROM indexed_sources WHERE "path" = ?');
46
49
  const jobs = [];
47
50
  for (const [path, chunkIdxs] of byPath){
48
51
  const texts = textByPath.get(path);
49
52
  if (texts && chunkIdxs.every((idx)=>idx < texts.length)) {
50
- // A stat, not a parse: a file that vanished since reconcile is skipped here even though its
51
- // text comes from memory, and the next reconcile removes its rows.
52
- if (!existsSync(join(baseDir, path))) continue;
53
53
  for (const idx of chunkIdxs)jobs.push({
54
54
  path,
55
55
  chunk: idx,
@@ -57,35 +57,36 @@ export async function embedPending(store, cfg, baseDir) {
57
57
  });
58
58
  continue;
59
59
  }
60
- // Reconcile ran in another process (`sense map` earlier, or a background `sense watch`), so
61
- // its chunk text is gone: re-derive this one file.
62
- let chunks;
63
- try {
64
- chunks = parseFile({
65
- relPath: path,
66
- absPath: join(baseDir, path),
67
- mtimeMs: 0,
68
- ctimeMs: 0,
69
- size: 0,
70
- presets: [],
71
- embed: true
72
- }, [
73
- embed
74
- ], cfg).doc.extracted.embed;
75
- } catch {
76
- continue; // vanished since reconcile; the next reconcile removes its rows
60
+ // A prior process may have reconciled the row, so its in-memory handoff is gone. Re-derive
61
+ // from that indexed generation's stored source, never from a newer live file.
62
+ const source = await sourceStmt.get(path);
63
+ if (!source) throw new SenseError('INDEX_NOT_READY', `indexed source text is missing for ${path}; run \`sense build --force\` (or library build(config, { force: true })) before preparing vectors`);
64
+ const chunks = parseSource({
65
+ relPath: path,
66
+ absPath: path,
67
+ mtimeMs: 0,
68
+ ctimeMs: 0,
69
+ size: Buffer.byteLength(source.text),
70
+ presets: [],
71
+ embed: true
72
+ }, source.text, [
73
+ embed
74
+ ], cfg).doc.extracted.embed;
75
+ for (const idx of chunkIdxs){
76
+ const chunk = chunks[idx];
77
+ if (!chunk) throw new SenseError('INDEX_NOT_READY', `indexed vector metadata for ${path} does not match its indexed source; run \`sense build --force\` (or library build(config, { force: true }))`);
78
+ jobs.push({
79
+ path,
80
+ chunk: idx,
81
+ text: chunk.text
82
+ });
77
83
  }
78
- for (const idx of chunkIdxs)if (chunks[idx]) jobs.push({
79
- path,
80
- chunk: idx,
81
- text: chunks[idx].text
82
- });
83
84
  }
84
85
  // Over the exact texts about to be embedded, before any of them are: a mismatch fails
85
86
  // this run loudly instead of quietly storing vectors from the wrong model.
86
87
  await checkLanguageFit(store, provider, jobs.map((j)=>j.text));
87
- // The lazy build is the one long silence a first search hits (measured 23s at
88
- // 26k notes); progress makes it distinguishable from a hang.
88
+ // Preparing a large configured scope can take long enough to look stalled, so report each
89
+ // completed provider batch.
89
90
  const report = progress('embedding chunks', jobs.length);
90
91
  for(let i = 0; i < jobs.length; i += provider.batchCap){
91
92
  const batch = jobs.slice(i, i + provider.batchCap);
@@ -105,13 +106,18 @@ export async function embedPending(store, cfg, baseDir) {
105
106
  }
106
107
  report.finish();
107
108
  }
109
+ export async function ensureDocumentEmbeddings(store, _cfg, allowed) {
110
+ const pending = (await store.vectors.pending()).filter((row)=>allowed.has(row.path));
111
+ if (pending.length > 0) {
112
+ throw new SenseError('INDEX_NOT_READY', `${pending.length} indexed chunk(s) in the requested scope still need vectors; run \`sense build\` (or library build(config)) before querying with build disabled`);
113
+ }
114
+ }
108
115
  // Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as
109
116
  // lexical syntax. Similarity comes back because the fused score cannot express match quality.
110
117
  export async function semanticCandidates(store, cfg, terms, fetch, allowed) {
111
118
  var _cfg_rootDir, _terms_match;
112
119
  const rootDir = (_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir;
113
120
  if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');
114
- await embedPending(store, cfg, rootDir);
115
121
  const provider = await getProvider(cfg);
116
122
  const storeDims = Math.min(STORE_DIMS, provider.dims);
117
123
  const text = ((_terms_match = terms.match(/[\p{L}\p{N}]+/gu)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>![
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/query.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Chunk } from '../features/embed.ts';\nimport { embed } from '../features/embed.ts';\nimport { progress } from '../output/progress.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { Store, VectorCandidate, VectorSimilar } from '../store/types.ts';\nimport { takeChunkText } from './handoff.ts';\nimport { checkLanguageFit } from './langfit.ts';\nimport { getProvider } from './registry.ts';\nimport { STORE_DIMS } from './types.ts';\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for\n// benchmark/lib/embed.mjs, which scores the same lever math offline.\nexport function toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in\n// this process (handoff.ts); a row left pending by an earlier command re-derives from the file.\nexport async function embedPending(store: Store, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = await store.vectors.pending();\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const textByPath = takeChunkText(store) ?? new Map<string, string[]>();\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n const texts = textByPath.get(path);\n if (texts && chunkIdxs.every((idx) => idx < texts.length)) {\n // A stat, not a parse: a file that vanished since reconcile is skipped here even though its\n // text comes from memory, and the next reconcile removes its rows.\n if (!existsSync(join(baseDir, path))) continue;\n for (const idx of chunkIdxs) jobs.push({ path, chunk: idx, text: texts[idx] });\n continue;\n }\n // Reconcile ran in another process (`sense map` earlier, or a background `sense watch`), so\n // its chunk text is gone: re-derive this one file.\n let chunks: Chunk[];\n try {\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, ctimeMs: 0, size: 0, presets: [], embed: true }, [embed], cfg).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n // Over the exact texts about to be embedded, before any of them are: a mismatch fails\n // this run loudly instead of quietly storing vectors from the wrong model.\n await checkLanguageFit(\n store,\n provider,\n jobs.map((j) => j.text)\n );\n\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += provider.batchCap) {\n const batch = jobs.slice(i, i + provider.batchCap);\n const vectors = await provider.embedDocuments(batch.map((j) => j.text));\n await store.vectors.writeVectors(\n batch.map((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n return { path: job.path, chunk: job.chunk, scale, vector: Buffer.from(q.buffer) };\n })\n );\n report.tick(Math.min(i + provider.batchCap, jobs.length));\n }\n report.finish();\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality.\nexport async function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]> {\n const rootDir = (cfg as Partial<ResolvedConfig>).rootDir ?? (cfg as Partial<ResolvedConfig>).baseDir;\n if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');\n await embedPending(store, cfg, rootDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);\n\n return store.vectors.candidates(qv, storeDims, fetch, allowed);\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport async function hasEmbedding(store: Store, path: string): Promise<boolean> {\n return store.vectors.hasVector(path);\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors.\nexport async function similarNotes(store: Store, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]> {\n return store.vectors.similar(path, opts);\n}\n"],"names":["existsSync","join","SenseError","embed","progress","parseFile","takeChunkText","checkLanguageFit","getProvider","STORE_DIMS","toStore","full","dims","int8","v","Float32Array","norm","d","Math","sqrt","scale","max","abs","embedPending","store","cfg","baseDir","provider","dirty","vectors","pending","length","storeDims","min","byPath","Map","row","list","get","path","push","chunk","set","textByPath","jobs","chunkIdxs","texts","every","idx","text","chunks","relPath","absPath","mtimeMs","ctimeMs","size","presets","doc","extracted","map","j","report","i","batchCap","batch","slice","embedDocuments","writeVectors","job","q","Int8Array","round","vector","Buffer","from","buffer","tick","finish","semanticCandidates","terms","fetch","allowed","rootDir","match","filter","t","includes","qv","embedQuery","candidates","hasEmbedding","hasVector","similarNotes","_cfg","opts","similar"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/query.ts"],"sourcesContent":["import type { Config, ResolvedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Chunk } from '../features/embed.ts';\nimport { embed } from '../features/embed.ts';\nimport { progress } from '../output/progress.ts';\nimport { parseSource } from '../scan/index.ts';\nimport type { Store, VectorCandidate, VectorSimilar } from '../store/types.ts';\nimport { takeChunkText } from './handoff.ts';\nimport { checkLanguageFit } from './langfit.ts';\nimport { getProvider } from './registry.ts';\nimport { STORE_DIMS } from './types.ts';\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for\n// benchmark/lib/embed.mjs, which scores the same lever math offline.\nexport function toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL. Reconcile hands its chunk text over in memory when it ran in\n// this process (handoff.ts); a row left pending by an earlier command re-derives from the exact\n// source string persisted with that indexed generation.\nexport async function embedPending(store: Store, cfg: Config, allowed?: ReadonlySet<string>): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = (await store.vectors.pending()).filter((row) => allowed?.has(row.path) ?? true);\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const textByPath = takeChunkText(store) ?? new Map<string, string[]>();\n const sourceStmt = await store.prepare('SELECT text FROM indexed_sources WHERE \"path\" = ?');\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n const texts = textByPath.get(path);\n if (texts && chunkIdxs.every((idx) => idx < texts.length)) {\n for (const idx of chunkIdxs) jobs.push({ path, chunk: idx, text: texts[idx] });\n continue;\n }\n // A prior process may have reconciled the row, so its in-memory handoff is gone. Re-derive\n // from that indexed generation's stored source, never from a newer live file.\n const source = (await sourceStmt.get(path)) as { text: string } | undefined;\n if (!source) throw new SenseError('INDEX_NOT_READY', `indexed source text is missing for ${path}; run \\`sense build --force\\` (or library build(config, { force: true })) before preparing vectors`);\n const chunks = parseSource({ relPath: path, absPath: path, mtimeMs: 0, ctimeMs: 0, size: Buffer.byteLength(source.text), presets: [], embed: true }, source.text, [embed], cfg).doc.extracted.embed as Chunk[];\n for (const idx of chunkIdxs) {\n const chunk = chunks[idx];\n if (!chunk) throw new SenseError('INDEX_NOT_READY', `indexed vector metadata for ${path} does not match its indexed source; run \\`sense build --force\\` (or library build(config, { force: true }))`);\n jobs.push({ path, chunk: idx, text: chunk.text });\n }\n }\n\n // Over the exact texts about to be embedded, before any of them are: a mismatch fails\n // this run loudly instead of quietly storing vectors from the wrong model.\n await checkLanguageFit(\n store,\n provider,\n jobs.map((j) => j.text)\n );\n\n // Preparing a large configured scope can take long enough to look stalled, so report each\n // completed provider batch.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += provider.batchCap) {\n const batch = jobs.slice(i, i + provider.batchCap);\n const vectors = await provider.embedDocuments(batch.map((j) => j.text));\n await store.vectors.writeVectors(\n batch.map((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n return { path: job.path, chunk: job.chunk, scale, vector: Buffer.from(q.buffer) };\n })\n );\n report.tick(Math.min(i + provider.batchCap, jobs.length));\n }\n report.finish();\n}\n\nexport async function ensureDocumentEmbeddings(store: Store, _cfg: Config, allowed: ReadonlySet<string>): Promise<void> {\n const pending = (await store.vectors.pending()).filter((row) => allowed.has(row.path));\n if (pending.length > 0) {\n throw new SenseError('INDEX_NOT_READY', `${pending.length} indexed chunk(s) in the requested scope still need vectors; run \\`sense build\\` (or library build(config)) before querying with build disabled`);\n }\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality.\nexport async function semanticCandidates(store: Store, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]> {\n const rootDir = (cfg as Partial<ResolvedConfig>).rootDir ?? (cfg as Partial<ResolvedConfig>).baseDir;\n if (!rootDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a resolved config (use loadConfig/open)');\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore(await provider.embedQuery(text), storeDims, false);\n\n return store.vectors.candidates(qv, storeDims, fetch, allowed);\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport async function hasEmbedding(store: Store, path: string): Promise<boolean> {\n return store.vectors.hasVector(path);\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors.\nexport async function similarNotes(store: Store, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]> {\n return store.vectors.similar(path, opts);\n}\n"],"names":["SenseError","embed","progress","parseSource","takeChunkText","checkLanguageFit","getProvider","STORE_DIMS","toStore","full","dims","int8","v","Float32Array","norm","d","Math","sqrt","scale","max","abs","embedPending","store","cfg","allowed","provider","dirty","vectors","pending","filter","row","has","path","length","storeDims","min","byPath","Map","list","get","push","chunk","set","textByPath","sourceStmt","prepare","jobs","chunkIdxs","texts","every","idx","text","source","chunks","relPath","absPath","mtimeMs","ctimeMs","size","Buffer","byteLength","presets","doc","extracted","map","j","report","i","batchCap","batch","slice","embedDocuments","writeVectors","job","q","Int8Array","round","vector","from","buffer","tick","finish","ensureDocumentEmbeddings","_cfg","semanticCandidates","terms","fetch","rootDir","baseDir","match","t","includes","join","qv","embedQuery","candidates","hasEmbedding","hasVector","similarNotes","opts","similar"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'EMBED_MODEL_MISSING' | 'EMBED_MODEL_MISMATCH' | 'SEARCH_OPTION_INVALID' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT' | 'PRESET_UNKNOWN' | 'PRESET_NOT_SEMANTIC' | 'NOTE_NOT_EMBEDDED' | 'STORE_UNKNOWN' | 'STORE_DEPENDENCY_MISSING' | 'STORE_CAPABILITY_MISSING' | 'STORE_BUSY' | 'LEXICAL_NULL_PATH';
1
+ export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'EMBED_MODEL_MISSING' | 'EMBED_MODEL_MISMATCH' | 'SEARCH_OPTION_INVALID' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT' | 'PRESET_UNKNOWN' | 'PRESET_NOT_SEMANTIC' | 'NOTE_NOT_EMBEDDED' | 'STORE_UNKNOWN' | 'STORE_DEPENDENCY_MISSING' | 'STORE_CAPABILITY_MISSING' | 'STORE_BUSY' | 'INDEX_NOT_READY' | 'LEXICAL_NULL_PATH';
2
2
  export declare class SenseError extends Error {
3
3
  code: SenseErrorCode;
4
4
  constructor(code: SenseErrorCode, message: string);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode =\n | 'CONFIG_NOT_FOUND'\n | 'CONFIG_EXISTS'\n | 'CONFIG_INVALID'\n | 'CONFIG_VERSION_UNSUPPORTED'\n | 'WATCH_ACTIVE'\n | 'NOTE_NOT_FOUND'\n | 'NOTE_AMBIGUOUS'\n | 'EMBED_DISABLED'\n | 'EMBED_MODEL'\n | 'EMBED_MODEL_MISSING'\n | 'EMBED_MODEL_MISMATCH'\n | 'SEARCH_OPTION_INVALID'\n | 'SEARCH_SYNTAX'\n | 'COLUMN_LIMIT'\n | 'PRESET_UNKNOWN'\n | 'PRESET_NOT_SEMANTIC'\n | 'NOTE_NOT_EMBEDDED'\n | 'STORE_UNKNOWN'\n | 'STORE_DEPENDENCY_MISSING'\n | 'STORE_CAPABILITY_MISSING'\n | 'STORE_BUSY'\n | 'LEXICAL_NULL_PATH';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AAyBrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode =\n | 'CONFIG_NOT_FOUND'\n | 'CONFIG_EXISTS'\n | 'CONFIG_INVALID'\n | 'CONFIG_VERSION_UNSUPPORTED'\n | 'WATCH_ACTIVE'\n | 'NOTE_NOT_FOUND'\n | 'NOTE_AMBIGUOUS'\n | 'EMBED_DISABLED'\n | 'EMBED_MODEL'\n | 'EMBED_MODEL_MISSING'\n | 'EMBED_MODEL_MISMATCH'\n | 'SEARCH_OPTION_INVALID'\n | 'SEARCH_SYNTAX'\n | 'COLUMN_LIMIT'\n | 'PRESET_UNKNOWN'\n | 'PRESET_NOT_SEMANTIC'\n | 'NOTE_NOT_EMBEDDED'\n | 'STORE_UNKNOWN'\n | 'STORE_DEPENDENCY_MISSING'\n | 'STORE_CAPABILITY_MISSING'\n | 'STORE_BUSY'\n | 'INDEX_NOT_READY'\n | 'LEXICAL_NULL_PATH';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AA0BrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import type { Block, Chunk } from '../chunk/index.ts';\nimport { CHUNK_VERSION, chunkFromBlocks, parse } from '../chunk/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { stashChunkText } from '../embed/handoff.ts';\nimport { modelIdentity } from '../embed/identity.ts';\nimport { countLines } from '../scan/frontmatter.ts';\nimport { appendRows } from '../store/shared.ts';\nimport type { Feature } from './types.ts';\n\n// int8 vectors with a per-vector scale, NULL vector = not yet embedded: reconcile writes dirty\n// rows and embedding tops up on the next search, so staleness costs recall.\n\nexport type { Chunk };\n\n// The title/summary prefix mirrors bm25 field weighting. Chunks the body, not the raw file, so\n// `offset` shifts line numbers back onto raw and a range stays a direct Read range.\nfunction chunksOf(blocks: Block[], body: string, search?: { title: string; summary: string }, offset = 0, chunkTokens?: number): Chunk[] {\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n // A hardcoded option change here must bump src/chunk/version.ts's CHUNK_VERSION, the signature\n // for every chunker input not already carried in config.\n return chunkFromBlocks(blocks, body, chunkTokens !== undefined ? { targetTokens: chunkTokens } : undefined).map((c) => ({\n startLine: c.startLine + offset,\n endLine: c.endLine + offset,\n text: prefix ? `${prefix}\\n${c.text}` : c.text,\n }));\n}\n\nexport const embed: Feature = {\n name: 'embed',\n async schema(db) {\n await db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search, _data, cfg, blocks) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n // blocks comes from parseFile's shared parse; falls back to parsing body for a direct call.\n return chunksOf(blocks ?? parse(body), body, search, countLines(raw) - countLines(body), cfg?.embed?.chunkTokens);\n },\n async remove(db, paths) {\n if (paths.length === 0) return;\n await db.runBatch(\n 'DELETE FROM embeddings WHERE \"path\" = ?',\n paths.map((p) => [p])\n );\n },\n // A tree with no embedding model never had extract() run for the doc (reconcile.ts's per-file\n // filter skips it), so extracted is undefined here -- those docs contribute no rows.\n async store(db, docs) {\n const rows: unknown[][] = [];\n const texts = new Map<string, string[]>();\n for (const { path, extracted } of docs) {\n if (!extracted) continue;\n const chunks = extracted as Chunk[];\n chunks.forEach((c, idx) => rows.push([path, idx, c.startLine, c.endLine]));\n texts.set(\n path,\n chunks.map((c) => c.text)\n );\n }\n if (rows.length === 0) return;\n // scale and vector are written by the embed pass, not here, so they are not in the column list\n // and take the table's own default on the append path.\n await appendRows(db, 'embeddings', ['path', 'chunk', 'start_line', 'end_line'], 'INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)', rows);\n // The text extract() just computed, handed to the embed pass that follows in this same\n // process rather than re-derived from the file there (src/embed/handoff.ts).\n stashChunkText(db, texts);\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n // Keyed so a rebuild notice can name it \"embed settings\". Driven by embedConfig(cfg) directly:\n // a model can be configured with no preset yet using vectors.\n signature(cfg) {\n const e = embedConfig(cfg);\n if (!e) return 'embed:off';\n // Weight identity from identity.ts, no network: a static model's resolved sha or local\n // size+mtime, appended once known so changed weights re-embed.\n const identity = e.provider === 'static' ? modelIdentity(e.model) : undefined;\n // W3b: chunkTokens rides the version token, since it's the one owner lever the chunker\n // itself takes -- changing or clearing it must rebuild exactly like a chunker version bump.\n const chunkVersion = e.chunkTokens !== undefined ? `${CHUNK_VERSION}:${e.chunkTokens}` : CHUNK_VERSION;\n return `embed:${e.provider}:${e.model}:${chunkVersion}${identity !== undefined ? `@${identity}` : ''}`;\n },\n};\n"],"names":["CHUNK_VERSION","chunkFromBlocks","parse","embedConfig","stashChunkText","modelIdentity","countLines","appendRows","chunksOf","blocks","body","search","offset","chunkTokens","prefix","title","summary","filter","Boolean","join","undefined","targetTokens","map","c","startLine","endLine","text","embed","name","schema","db","exec","extract","raw","_data","cfg","remove","paths","length","runBatch","p","store","docs","rows","texts","Map","path","extracted","chunks","forEach","idx","push","set","enabledForFile","_cfg","file","signature","e","identity","provider","model","chunkVersion"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import type { Block, Chunk } from '../chunk/index.ts';\nimport { CHUNK_VERSION, chunkFromBlocks, parse } from '../chunk/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { stashChunkText } from '../embed/handoff.ts';\nimport { modelIdentity } from '../embed/identity.ts';\nimport { countLines } from '../scan/frontmatter.ts';\nimport { appendRows } from '../store/shared.ts';\nimport type { Feature } from './types.ts';\n\n// int8 vectors with a per-vector scale, NULL vector = not yet embedded: reconcile writes dirty\n// rows and build (or an authorised scoped CLI query) prepares them before semantic retrieval.\n\nexport type { Chunk };\n\n// The title/summary prefix mirrors bm25 field weighting. Chunks the body, not the raw file, so\n// `offset` shifts line numbers back onto raw and a range stays a direct Read range.\nfunction chunksOf(blocks: Block[], body: string, search?: { title: string; summary: string }, offset = 0, chunkTokens?: number): Chunk[] {\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n // A hardcoded option change here must bump src/chunk/version.ts's CHUNK_VERSION, the signature\n // for every chunker input not already carried in config.\n return chunkFromBlocks(blocks, body, chunkTokens !== undefined ? { targetTokens: chunkTokens } : undefined).map((c) => ({\n startLine: c.startLine + offset,\n endLine: c.endLine + offset,\n text: prefix ? `${prefix}\\n${c.text}` : c.text,\n }));\n}\n\nexport const embed: Feature = {\n name: 'embed',\n async schema(db) {\n await db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search, _data, cfg, blocks) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n // blocks comes from parseFile's shared parse; falls back to parsing body for a direct call.\n return chunksOf(blocks ?? parse(body), body, search, countLines(raw) - countLines(body), cfg?.embed?.chunkTokens);\n },\n async remove(db, paths) {\n if (paths.length === 0) return;\n await db.runBatch(\n 'DELETE FROM embeddings WHERE \"path\" = ?',\n paths.map((p) => [p])\n );\n },\n // A tree with no embedding model never had extract() run for the doc (reconcile.ts's per-file\n // filter skips it), so extracted is undefined here -- those docs contribute no rows.\n async store(db, docs) {\n const rows: unknown[][] = [];\n const texts = new Map<string, string[]>();\n for (const { path, extracted } of docs) {\n if (!extracted) continue;\n const chunks = extracted as Chunk[];\n chunks.forEach((c, idx) => rows.push([path, idx, c.startLine, c.endLine]));\n texts.set(\n path,\n chunks.map((c) => c.text)\n );\n }\n if (rows.length === 0) return;\n // scale and vector are written by the embed pass, not here, so they are not in the column list\n // and take the table's own default on the append path.\n await appendRows(db, 'embeddings', ['path', 'chunk', 'start_line', 'end_line'], 'INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)', rows);\n // The text extract() just computed, handed to the embed pass that follows in this same\n // process rather than re-derived from the file there (src/embed/handoff.ts).\n stashChunkText(db, texts);\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n // Keyed so a rebuild notice can name it \"embed settings\". Driven by embedConfig(cfg) directly:\n // a model can be configured with no preset yet using vectors.\n signature(cfg) {\n const e = embedConfig(cfg);\n if (!e) return 'embed:off';\n // Weight identity from identity.ts, no network: a static model's resolved sha or local\n // size+mtime, appended once known so changed weights re-embed.\n const identity = e.provider === 'static' ? modelIdentity(e.model) : undefined;\n // W3b: chunkTokens rides the version token, since it's the one owner lever the chunker\n // itself takes -- changing or clearing it must rebuild exactly like a chunker version bump.\n const chunkVersion = e.chunkTokens !== undefined ? `${CHUNK_VERSION}:${e.chunkTokens}` : CHUNK_VERSION;\n return `embed:${e.provider}:${e.model}:${chunkVersion}${identity !== undefined ? `@${identity}` : ''}`;\n },\n};\n"],"names":["CHUNK_VERSION","chunkFromBlocks","parse","embedConfig","stashChunkText","modelIdentity","countLines","appendRows","chunksOf","blocks","body","search","offset","chunkTokens","prefix","title","summary","filter","Boolean","join","undefined","targetTokens","map","c","startLine","endLine","text","embed","name","schema","db","exec","extract","raw","_data","cfg","remove","paths","length","runBatch","p","store","docs","rows","texts","Map","path","extracted","chunks","forEach","idx","push","set","enabledForFile","_cfg","file","signature","e","identity","provider","model","chunkVersion"],"mappings":"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"}
@@ -1,5 +1,6 @@
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
+ import { serialQuery } from '../lib/serial-query.js';
3
4
  async function resetVisited(store) {
4
5
  await store.exec('DROP TABLE IF EXISTS temp.visited');
5
6
  await store.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');
@@ -51,7 +52,10 @@ async function reconstructPath(store, to) {
51
52
  }
52
53
  // One BFS from `from`, tracking a predecessor per node. Undirected by default, matching
53
54
  // personalizedRank; null when `to` is unreached within `maxDepth`.
54
- export async function findPath(store, from, to, opts = {}) {
55
+ export function findPath(store, from, to, opts = {}) {
56
+ return serialQuery(store, ()=>findIndexedPath(store, from, to, opts));
57
+ }
58
+ async function findIndexedPath(store, from, to, opts) {
55
59
  var _opts_maxDepth;
56
60
  if (from === to) return [
57
61
  from
@@ -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 { 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, inserted at the new depth (?2); RETURNING gives 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,oJAAoJ;AACpJ,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"}
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.\n\nimport { serialQuery } from '../lib/serial-query.ts';\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, inserted at the new depth (?2); RETURNING gives 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 function findPath(store: Store, from: string, to: string, opts: FindPathOptions = {}): Promise<string[] | null> {\n return serialQuery(store, () => findIndexedPath(store, from, to, opts));\n}\n\nasync function findIndexedPath(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":["serialQuery","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","findIndexedPath","directed","maxDepth","Infinity","insertStart","run","stmt","ring","rows","length","some"],"mappings":"AAAA,2FAA2F;AAC3F,oEAAoE;AAEpE,SAASA,WAAW,QAAQ,yBAAyB;AAWrD,eAAeC,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,oJAAoJ;AACpJ,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,SAASa,SAAS5B,KAAY,EAAE6B,IAAY,EAAEV,EAAU,EAAEW,OAAwB,CAAC,CAAC;IACzF,OAAOhC,YAAYE,OAAO,IAAM+B,gBAAgB/B,OAAO6B,MAAMV,IAAIW;AACnE;AAEA,eAAeC,gBAAgB/B,KAAY,EAAE6B,IAAY,EAAEV,EAAU,EAAEW,IAAqB;QAIzEA;IAHjB,IAAID,SAASV,IAAI,OAAO;QAACU;KAAK;IAE9B,MAAMhB,YAAuBiB,KAAKE,QAAQ,GAAG,YAAY;IACzD,MAAMC,YAAWH,iBAAAA,KAAKG,QAAQ,cAAbH,4BAAAA,iBAAiBI;IAClC,IAAID,YAAY,GAAG,OAAO;IAE1B,MAAMlC,aAAaC;IACnB,MAAMmC,cAAc,MAAMnC,MAAMqB,OAAO,CAAC;IACxC,MAAMc,YAAYC,GAAG,CAACP;IACtB,IAAIC,KAAK3B,OAAO,EAAE,MAAMD,aAAaF,OAAO8B,KAAK3B,OAAO,EAAEgB;IAE1D,MAAMkB,OAAO,MAAMrC,MAAMqB,OAAO,CAACL,QAAQH,WAAW,CAAC,CAACiB,KAAK3B,OAAO;IAClE,IAAK,IAAImC,OAAO,GAAGA,QAAQL,UAAUK,OAAQ;QAC3C,MAAMC,OAAQ,MAAMF,KAAKhC,GAAG,CAACiC,OAAO,GAAGA;QACvC,IAAIC,KAAKC,MAAM,KAAK,GAAG;QACvB,IAAID,KAAKE,IAAI,CAAC,CAAChB,MAAQA,IAAIH,IAAI,KAAKH,KAAK,OAAOD,gBAAgBlB,OAAOmB;IACzE;IACA,OAAO;AACT"}
@@ -1,13 +1,13 @@
1
1
  export type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands/index.js';
2
2
  export { mapTree, peek, presetCoverage, search } from './commands/index.js';
3
- export type { Config, EmbedConfig, FeatureName, InitOverrides, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights, StoreName } from './config/index.js';
3
+ export type { Config, EmbedConfig, FeatureName, InitOverrides, LoadConfigOptions, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights, StoreName } from './config/index.js';
4
4
  export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, SUPPORTED_CONFIG_VERSION } from './config/index.js';
5
5
  export type { SenseErrorCode } from './errors.js';
6
6
  export { SenseError } from './errors.js';
7
7
  export type { Format, Row, RowFormat } from './output/output.js';
8
8
  export { printRows } from './output/output.js';
9
- export type { OpenResult, Stages } from './store/index.js';
10
- export { CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.js';
9
+ export type { BuildOptions, BuildResult, OpenOptions, OpenResult, Stages } from './store/index.js';
10
+ export { buildIndex as build, CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.js';
11
11
  export type { Capability, DocumentStore, FieldStat, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './store/types.js';
12
12
  export type { WatchEvent, WatchOptions } from './watch.js';
13
13
  export { runWatch } from './watch.js';
package/dist/esm/index.js CHANGED
@@ -4,5 +4,5 @@ export { mapTree, peek, presetCoverage, search } from './commands/index.js';
4
4
  export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, SUPPORTED_CONFIG_VERSION } from './config/index.js';
5
5
  export { SenseError } from './errors.js';
6
6
  export { printRows } from './output/output.js';
7
- export { CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.js';
7
+ export { buildIndex as build, CAPABILITY_NAMES, 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, StoreName } from './config/index.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, 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, Stages } from './store/index.ts';\nexport { CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.ts';\nexport type { Capability, DocumentStore, FieldStat, 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","STORE_NAMES","SUPPORTED_CONFIG_VERSION","SenseError","printRows","CAPABILITY_NAMES","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,WAAW,EAAEC,wBAAwB,QAAQ,oBAAoB;AAE3J,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,qBAAqB;AAE/C,SAASC,gBAAgB,EAAEC,UAAU,EAAEC,aAAaC,IAAI,QAAQ,mBAAmB;AAInF,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, LoadConfigOptions, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights, StoreName } from './config/index.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, 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 { BuildOptions, BuildResult, OpenOptions, OpenResult, Stages } from './store/index.ts';\nexport { buildIndex as build, CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.ts';\nexport type { Capability, DocumentStore, FieldStat, 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","STORE_NAMES","SUPPORTED_CONFIG_VERSION","SenseError","printRows","buildIndex","build","CAPABILITY_NAMES","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,WAAW,EAAEC,wBAAwB,QAAQ,oBAAoB;AAE3J,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,qBAAqB;AAE/C,SAASC,cAAcC,KAAK,EAAEC,gBAAgB,EAAEC,UAAU,EAAEC,aAAaC,IAAI,QAAQ,mBAAmB;AAIxG,SAASC,QAAQ,QAAQ,aAAa"}
@@ -1,16 +1,24 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
3
  // Stage-then-swap: write beside the target, then rename over it. A crash or full disk mid-write
3
4
  // leaves the target untouched instead of truncated. Matches embed/store.ts's model-download idiom.
4
5
  export function writeFileAtomic(path, data) {
5
- const tmp = `${path}.part`;
6
- writeFileSync(tmp, data);
6
+ const tmp = `${path}.${randomUUID()}.part`;
7
+ let staged = false;
7
8
  try {
9
+ writeFileSync(tmp, data, {
10
+ flag: 'wx'
11
+ });
12
+ staged = true;
8
13
  renameSync(tmp, path);
9
14
  } catch (err) {
10
- try {
11
- unlinkSync(tmp);
12
- } catch {
13
- // best-effort cleanup; the rename error below is what matters
15
+ // An exclusive-create collision belongs to another writer; never remove its staging file.
16
+ if (staged || !(err instanceof Error && 'code' in err && err.code === 'EEXIST')) {
17
+ try {
18
+ unlinkSync(tmp);
19
+ } catch {
20
+ // best-effort cleanup; the original error below is what matters
21
+ }
14
22
  }
15
23
  throw err;
16
24
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/lib/atomic-write.ts"],"sourcesContent":["import { renameSync, unlinkSync, writeFileSync } from 'node:fs';\n\n// Stage-then-swap: write beside the target, then rename over it. A crash or full disk mid-write\n// leaves the target untouched instead of truncated. Matches embed/store.ts's model-download idiom.\nexport function writeFileAtomic(path: string, data: string | Buffer): void {\n const tmp = `${path}.part`;\n writeFileSync(tmp, data);\n try {\n renameSync(tmp, path);\n } catch (err) {\n try {\n unlinkSync(tmp);\n } catch {\n // best-effort cleanup; the rename error below is what matters\n }\n throw err;\n }\n}\n"],"names":["renameSync","unlinkSync","writeFileSync","writeFileAtomic","path","data","tmp","err"],"mappings":"AAAA,SAASA,UAAU,EAAEC,UAAU,EAAEC,aAAa,QAAQ,UAAU;AAEhE,gGAAgG;AAChG,mGAAmG;AACnG,OAAO,SAASC,gBAAgBC,IAAY,EAAEC,IAAqB;IACjE,MAAMC,MAAM,GAAGF,KAAK,KAAK,CAAC;IAC1BF,cAAcI,KAAKD;IACnB,IAAI;QACFL,WAAWM,KAAKF;IAClB,EAAE,OAAOG,KAAK;QACZ,IAAI;YACFN,WAAWK;QACb,EAAE,OAAM;QACN,8DAA8D;QAChE;QACA,MAAMC;IACR;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/lib/atomic-write.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { renameSync, unlinkSync, writeFileSync } from 'node:fs';\n\n// Stage-then-swap: write beside the target, then rename over it. A crash or full disk mid-write\n// leaves the target untouched instead of truncated. Matches embed/store.ts's model-download idiom.\nexport function writeFileAtomic(path: string, data: string | Buffer): void {\n const tmp = `${path}.${randomUUID()}.part`;\n let staged = false;\n try {\n writeFileSync(tmp, data, { flag: 'wx' });\n staged = true;\n renameSync(tmp, path);\n } catch (err) {\n // An exclusive-create collision belongs to another writer; never remove its staging file.\n if (staged || !(err instanceof Error && 'code' in err && err.code === 'EEXIST')) {\n try {\n unlinkSync(tmp);\n } catch {\n // best-effort cleanup; the original error below is what matters\n }\n }\n throw err;\n }\n}\n"],"names":["randomUUID","renameSync","unlinkSync","writeFileSync","writeFileAtomic","path","data","tmp","staged","flag","err","Error","code"],"mappings":"AAAA,SAASA,UAAU,QAAQ,cAAc;AACzC,SAASC,UAAU,EAAEC,UAAU,EAAEC,aAAa,QAAQ,UAAU;AAEhE,gGAAgG;AAChG,mGAAmG;AACnG,OAAO,SAASC,gBAAgBC,IAAY,EAAEC,IAAqB;IACjE,MAAMC,MAAM,GAAGF,KAAK,CAAC,EAAEL,aAAa,KAAK,CAAC;IAC1C,IAAIQ,SAAS;IACb,IAAI;QACFL,cAAcI,KAAKD,MAAM;YAAEG,MAAM;QAAK;QACtCD,SAAS;QACTP,WAAWM,KAAKF;IAClB,EAAE,OAAOK,KAAK;QACZ,0FAA0F;QAC1F,IAAIF,UAAU,CAAEE,CAAAA,eAAeC,SAAS,UAAUD,OAAOA,IAAIE,IAAI,KAAK,QAAO,GAAI;YAC/E,IAAI;gBACFV,WAAWK;YACb,EAAE,OAAM;YACN,gEAAgE;YAClE;QACF;QACA,MAAMG;IACR;AACF"}
@@ -0,0 +1 @@
1
+ export declare function serialQuery<T>(handle: object, query: () => Promise<T>): Promise<T>;
@@ -0,0 +1,13 @@
1
+ const pending = new WeakMap();
2
+ // High-level queries share temporary tables and transaction state on a handle.
3
+ // Raw SQL and handle closure remain caller-owned and must await these queries.
4
+ export function serialQuery(handle, query) {
5
+ var _pending_get;
6
+ const result = ((_pending_get = pending.get(handle)) !== null && _pending_get !== void 0 ? _pending_get : Promise.resolve()).then(query);
7
+ const settled = result.then(()=>{}, ()=>{});
8
+ pending.set(handle, settled);
9
+ void settled.then(()=>{
10
+ if (pending.get(handle) === settled) pending.delete(handle);
11
+ });
12
+ return result;
13
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/lib/serial-query.ts"],"sourcesContent":["const pending = new WeakMap<object, Promise<void>>();\n\n// High-level queries share temporary tables and transaction state on a handle.\n// Raw SQL and handle closure remain caller-owned and must await these queries.\nexport function serialQuery<T>(handle: object, query: () => Promise<T>): Promise<T> {\n const result = (pending.get(handle) ?? Promise.resolve()).then(query);\n const settled = result.then(\n () => {},\n () => {}\n );\n pending.set(handle, settled);\n void settled.then(() => {\n if (pending.get(handle) === settled) pending.delete(handle);\n });\n return result;\n}\n"],"names":["pending","WeakMap","serialQuery","handle","query","result","get","Promise","resolve","then","settled","set","delete"],"mappings":"AAAA,MAAMA,UAAU,IAAIC;AAEpB,+EAA+E;AAC/E,+EAA+E;AAC/E,OAAO,SAASC,YAAeC,MAAc,EAAEC,KAAuB;QACpDJ;IAAhB,MAAMK,SAAS,EAACL,eAAAA,QAAQM,GAAG,CAACH,qBAAZH,0BAAAA,eAAuBO,QAAQC,OAAO,IAAIC,IAAI,CAACL;IAC/D,MAAMM,UAAUL,OAAOI,IAAI,CACzB,KAAO,GACP,KAAO;IAETT,QAAQW,GAAG,CAACR,QAAQO;IACpB,KAAKA,QAAQD,IAAI,CAAC;QAChB,IAAIT,QAAQM,GAAG,CAACH,YAAYO,SAASV,QAAQY,MAAM,CAACT;IACtD;IACA,OAAOE;AACT"}
@@ -12,6 +12,7 @@ export interface ParsedDoc {
12
12
  presets: string[];
13
13
  data: Record<string, string | number | bigint | null>;
14
14
  parseError: string | null;
15
+ source: string;
15
16
  search: {
16
17
  title: string;
17
18
  summary: string;
@@ -19,6 +20,10 @@ export interface ParsedDoc {
19
20
  };
20
21
  extracted: Record<string, unknown>;
21
22
  }
23
+ export declare function parseSource(file: FileStat, raw: string, extractors?: Feature[], cfg?: Config): {
24
+ doc: ParsedDoc;
25
+ warnings: string[];
26
+ };
22
27
  export declare function parseFile(file: FileStat, extractors?: Feature[], cfg?: Config): {
23
28
  doc: ParsedDoc;
24
29
  warnings: string[];