sensemaking 0.16.0 → 0.17.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 (312) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/chunk/extract.d.cts +2 -0
  3. package/dist/cjs/chunk/extract.d.ts +2 -0
  4. package/dist/cjs/chunk/extract.js +139 -0
  5. package/dist/cjs/chunk/extract.js.map +1 -0
  6. package/dist/cjs/chunk/group.d.cts +4 -0
  7. package/dist/cjs/chunk/group.d.ts +4 -0
  8. package/dist/cjs/chunk/group.js +489 -0
  9. package/dist/cjs/chunk/group.js.map +1 -0
  10. package/dist/cjs/chunk/index.d.cts +8 -0
  11. package/dist/cjs/chunk/index.d.ts +8 -0
  12. package/dist/cjs/chunk/index.js +44 -0
  13. package/dist/cjs/chunk/index.js.map +1 -0
  14. package/dist/cjs/chunk/parse.d.cts +2 -0
  15. package/dist/cjs/chunk/parse.d.ts +2 -0
  16. package/dist/cjs/chunk/parse.js +71 -0
  17. package/dist/cjs/chunk/parse.js.map +1 -0
  18. package/dist/cjs/chunk/types.d.cts +19 -0
  19. package/dist/cjs/chunk/types.d.ts +19 -0
  20. package/dist/cjs/chunk/types.js +5 -0
  21. package/dist/cjs/chunk/types.js.map +1 -0
  22. package/dist/cjs/chunk/version.d.cts +1 -0
  23. package/dist/cjs/chunk/version.d.ts +1 -0
  24. package/dist/cjs/chunk/version.js +14 -0
  25. package/dist/cjs/chunk/version.js.map +1 -0
  26. package/dist/cjs/cli/map.js +1 -1
  27. package/dist/cjs/cli/map.js.map +1 -1
  28. package/dist/cjs/cli/named.js +1 -1
  29. package/dist/cjs/cli/named.js.map +1 -1
  30. package/dist/cjs/cli/path.js +2 -2
  31. package/dist/cjs/cli/path.js.map +1 -1
  32. package/dist/cjs/cli/peek.js +1 -1
  33. package/dist/cjs/cli/peek.js.map +1 -1
  34. package/dist/cjs/cli/related.js +1 -1
  35. package/dist/cjs/cli/related.js.map +1 -1
  36. package/dist/cjs/cli/search.js +1 -1
  37. package/dist/cjs/cli/search.js.map +1 -1
  38. package/dist/cjs/cli/shared.d.cts +1 -1
  39. package/dist/cjs/cli/shared.d.ts +1 -1
  40. package/dist/cjs/cli/shared.js +3 -3
  41. package/dist/cjs/cli/shared.js.map +1 -1
  42. package/dist/cjs/cli/status.js +1 -1
  43. package/dist/cjs/cli/status.js.map +1 -1
  44. package/dist/cjs/commands/map.d.cts +1 -1
  45. package/dist/cjs/commands/map.d.ts +1 -1
  46. package/dist/cjs/commands/map.js.map +1 -1
  47. package/dist/cjs/commands/peek.d.cts +1 -1
  48. package/dist/cjs/commands/peek.d.ts +1 -1
  49. package/dist/cjs/commands/peek.js +13 -6
  50. package/dist/cjs/commands/peek.js.map +1 -1
  51. package/dist/cjs/commands/scope.js +1 -1
  52. package/dist/cjs/commands/scope.js.map +1 -1
  53. package/dist/cjs/commands/search.d.cts +1 -1
  54. package/dist/cjs/commands/search.d.ts +1 -1
  55. package/dist/cjs/commands/search.js +3 -3
  56. package/dist/cjs/commands/search.js.map +1 -1
  57. package/dist/cjs/commands/signals.js +1 -1
  58. package/dist/cjs/commands/signals.js.map +1 -1
  59. package/dist/cjs/config/access.d.cts +3 -1
  60. package/dist/cjs/config/access.d.ts +3 -1
  61. package/dist/cjs/config/access.js +11 -11
  62. package/dist/cjs/config/access.js.map +1 -1
  63. package/dist/cjs/config/index.d.cts +1 -0
  64. package/dist/cjs/config/index.d.ts +1 -0
  65. package/dist/cjs/config/index.js +4 -0
  66. package/dist/cjs/config/index.js.map +1 -1
  67. package/dist/cjs/config/types.d.cts +1 -0
  68. package/dist/cjs/config/types.d.ts +1 -0
  69. package/dist/cjs/config/types.js.map +1 -1
  70. package/dist/cjs/config/validate.d.cts +1 -0
  71. package/dist/cjs/config/validate.d.ts +1 -0
  72. package/dist/cjs/config/validate.js +8 -1
  73. package/dist/cjs/config/validate.js.map +1 -1
  74. package/dist/cjs/db/open.d.cts +1 -1
  75. package/dist/cjs/db/open.d.ts +1 -1
  76. package/dist/cjs/db/open.js +4 -4
  77. package/dist/cjs/db/open.js.map +1 -1
  78. package/dist/cjs/db/reconcile.js +8 -8
  79. package/dist/cjs/db/reconcile.js.map +1 -1
  80. package/dist/cjs/{sql-functions.js → db/sql-functions.js} +1 -1
  81. package/dist/cjs/db/sql-functions.js.map +1 -0
  82. package/dist/cjs/embed/langfit.js +45 -85
  83. package/dist/cjs/embed/langfit.js.map +1 -1
  84. package/dist/cjs/embed/languages.js.map +1 -1
  85. package/dist/cjs/embed/query.js +6 -6
  86. package/dist/cjs/embed/query.js.map +1 -1
  87. package/dist/cjs/embed/static.js +3 -40
  88. package/dist/cjs/embed/static.js.map +1 -1
  89. package/dist/cjs/features/embed.d.cts +2 -5
  90. package/dist/cjs/features/embed.d.ts +2 -5
  91. package/dist/cjs/features/embed.js +36 -58
  92. package/dist/cjs/features/embed.js.map +1 -1
  93. package/dist/cjs/{fences.js → features/fences.js} +1 -1
  94. package/dist/cjs/features/fences.js.map +1 -0
  95. package/dist/cjs/features/links.js +1 -1
  96. package/dist/cjs/features/links.js.map +1 -1
  97. package/dist/cjs/features/rank.js +1 -1
  98. package/dist/cjs/features/rank.js.map +1 -1
  99. package/dist/cjs/features/sections.js +4 -3
  100. package/dist/cjs/features/sections.js.map +1 -1
  101. package/dist/cjs/features/tags.js +1 -1
  102. package/dist/cjs/features/tags.js.map +1 -1
  103. package/dist/cjs/features/types.d.cts +4 -2
  104. package/dist/cjs/features/types.d.ts +4 -2
  105. package/dist/cjs/graph/graph.js.map +1 -0
  106. package/dist/cjs/graph/traverse.js.map +1 -0
  107. package/dist/cjs/index.d.cts +4 -4
  108. package/dist/cjs/index.d.ts +4 -4
  109. package/dist/cjs/index.js +4 -1
  110. package/dist/cjs/index.js.map +1 -1
  111. package/dist/cjs/output/column-hint.js.map +1 -0
  112. package/dist/cjs/output/output.js.map +1 -0
  113. package/dist/cjs/output/progress.js.map +1 -0
  114. package/dist/cjs/{search-error.js → output/search-error.js} +1 -1
  115. package/dist/cjs/output/search-error.js.map +1 -0
  116. package/dist/cjs/scan/frontmatter.d.cts +13 -0
  117. package/dist/cjs/scan/frontmatter.d.ts +13 -0
  118. package/dist/cjs/scan/frontmatter.js +229 -0
  119. package/dist/cjs/scan/frontmatter.js.map +1 -0
  120. package/dist/cjs/scan/index.d.cts +25 -0
  121. package/dist/cjs/scan/index.d.ts +25 -0
  122. package/dist/cjs/scan/index.js +107 -0
  123. package/dist/cjs/scan/index.js.map +1 -0
  124. package/dist/cjs/scan/list.d.cts +12 -0
  125. package/dist/cjs/scan/list.d.ts +12 -0
  126. package/dist/cjs/scan/list.js +154 -0
  127. package/dist/cjs/scan/list.js.map +1 -0
  128. package/dist/cjs/text/segment.d.cts +3 -0
  129. package/dist/cjs/text/segment.d.ts +3 -0
  130. package/dist/cjs/{segment.js → text/segment.js} +17 -14
  131. package/dist/cjs/text/segment.js.map +1 -0
  132. package/dist/cjs/text/strip.d.cts +3 -0
  133. package/dist/cjs/text/strip.d.ts +3 -0
  134. package/dist/cjs/text/strip.js +32 -0
  135. package/dist/cjs/text/strip.js.map +1 -0
  136. package/dist/esm/chunk/extract.d.ts +2 -0
  137. package/dist/esm/chunk/extract.js +114 -0
  138. package/dist/esm/chunk/extract.js.map +1 -0
  139. package/dist/esm/chunk/group.d.ts +4 -0
  140. package/dist/esm/chunk/group.js +253 -0
  141. package/dist/esm/chunk/group.js.map +1 -0
  142. package/dist/esm/chunk/index.d.ts +8 -0
  143. package/dist/esm/chunk/index.js +16 -0
  144. package/dist/esm/chunk/index.js.map +1 -0
  145. package/dist/esm/chunk/parse.d.ts +2 -0
  146. package/dist/esm/chunk/parse.js +63 -0
  147. package/dist/esm/chunk/parse.js.map +1 -0
  148. package/dist/esm/chunk/types.d.ts +19 -0
  149. package/dist/esm/chunk/types.js +2 -0
  150. package/dist/esm/chunk/types.js.map +1 -0
  151. package/dist/esm/chunk/version.d.ts +1 -0
  152. package/dist/esm/chunk/version.js +3 -0
  153. package/dist/esm/chunk/version.js.map +1 -0
  154. package/dist/esm/cli/map.js +1 -1
  155. package/dist/esm/cli/map.js.map +1 -1
  156. package/dist/esm/cli/named.js +1 -1
  157. package/dist/esm/cli/named.js.map +1 -1
  158. package/dist/esm/cli/path.js +2 -2
  159. package/dist/esm/cli/path.js.map +1 -1
  160. package/dist/esm/cli/peek.js +1 -1
  161. package/dist/esm/cli/peek.js.map +1 -1
  162. package/dist/esm/cli/related.js +1 -1
  163. package/dist/esm/cli/related.js.map +1 -1
  164. package/dist/esm/cli/search.js +1 -1
  165. package/dist/esm/cli/search.js.map +1 -1
  166. package/dist/esm/cli/shared.d.ts +1 -1
  167. package/dist/esm/cli/shared.js +3 -3
  168. package/dist/esm/cli/shared.js.map +1 -1
  169. package/dist/esm/cli/status.js +1 -1
  170. package/dist/esm/cli/status.js.map +1 -1
  171. package/dist/esm/commands/map.d.ts +1 -1
  172. package/dist/esm/commands/map.js.map +1 -1
  173. package/dist/esm/commands/peek.d.ts +1 -1
  174. package/dist/esm/commands/peek.js +8 -1
  175. package/dist/esm/commands/peek.js.map +1 -1
  176. package/dist/esm/commands/scope.js +1 -1
  177. package/dist/esm/commands/scope.js.map +1 -1
  178. package/dist/esm/commands/search.d.ts +1 -1
  179. package/dist/esm/commands/search.js +3 -3
  180. package/dist/esm/commands/search.js.map +1 -1
  181. package/dist/esm/commands/signals.js +1 -1
  182. package/dist/esm/commands/signals.js.map +1 -1
  183. package/dist/esm/config/access.d.ts +3 -1
  184. package/dist/esm/config/access.js +9 -9
  185. package/dist/esm/config/access.js.map +1 -1
  186. package/dist/esm/config/index.d.ts +1 -0
  187. package/dist/esm/config/index.js +1 -0
  188. package/dist/esm/config/index.js.map +1 -1
  189. package/dist/esm/config/types.d.ts +1 -0
  190. package/dist/esm/config/types.js.map +1 -1
  191. package/dist/esm/config/validate.d.ts +1 -0
  192. package/dist/esm/config/validate.js +8 -2
  193. package/dist/esm/config/validate.js.map +1 -1
  194. package/dist/esm/db/open.d.ts +1 -1
  195. package/dist/esm/db/open.js +5 -5
  196. package/dist/esm/db/open.js.map +1 -1
  197. package/dist/esm/db/reconcile.js +4 -4
  198. package/dist/esm/db/reconcile.js.map +1 -1
  199. package/dist/esm/{sql-functions.js → db/sql-functions.js} +1 -1
  200. package/dist/esm/db/sql-functions.js.map +1 -0
  201. package/dist/esm/embed/langfit.js +3 -3
  202. package/dist/esm/embed/langfit.js.map +1 -1
  203. package/dist/esm/embed/languages.js.map +1 -1
  204. package/dist/esm/embed/query.js +5 -5
  205. package/dist/esm/embed/query.js.map +1 -1
  206. package/dist/esm/embed/static.js +1 -2
  207. package/dist/esm/embed/static.js.map +1 -1
  208. package/dist/esm/features/embed.d.ts +2 -5
  209. package/dist/esm/features/embed.js +34 -32
  210. package/dist/esm/features/embed.js.map +1 -1
  211. package/dist/esm/{fences.js → features/fences.js} +1 -1
  212. package/dist/esm/features/fences.js.map +1 -0
  213. package/dist/esm/features/links.js +1 -1
  214. package/dist/esm/features/links.js.map +1 -1
  215. package/dist/esm/features/rank.js +1 -1
  216. package/dist/esm/features/rank.js.map +1 -1
  217. package/dist/esm/features/sections.js +4 -3
  218. package/dist/esm/features/sections.js.map +1 -1
  219. package/dist/esm/features/tags.js +1 -1
  220. package/dist/esm/features/tags.js.map +1 -1
  221. package/dist/esm/features/types.d.ts +4 -2
  222. package/dist/esm/features/types.js.map +1 -1
  223. package/dist/esm/graph/graph.js.map +1 -0
  224. package/dist/esm/graph/traverse.js.map +1 -0
  225. package/dist/esm/index.d.ts +4 -4
  226. package/dist/esm/index.js +2 -2
  227. package/dist/esm/index.js.map +1 -1
  228. package/dist/esm/output/column-hint.js.map +1 -0
  229. package/dist/esm/output/output.js.map +1 -0
  230. package/dist/esm/output/progress.js.map +1 -0
  231. package/dist/esm/{search-error.js → output/search-error.js} +1 -1
  232. package/dist/esm/output/search-error.js.map +1 -0
  233. package/dist/esm/scan/frontmatter.d.ts +13 -0
  234. package/dist/esm/{scan.js → scan/frontmatter.js} +4 -123
  235. package/dist/esm/scan/frontmatter.js.map +1 -0
  236. package/dist/esm/scan/index.d.ts +25 -0
  237. package/dist/esm/scan/index.js +58 -0
  238. package/dist/esm/scan/index.js.map +1 -0
  239. package/dist/esm/scan/list.d.ts +12 -0
  240. package/dist/esm/scan/list.js +58 -0
  241. package/dist/esm/scan/list.js.map +1 -0
  242. package/dist/esm/text/segment.d.ts +3 -0
  243. package/dist/esm/{segment.js → text/segment.js} +3 -3
  244. package/dist/esm/text/segment.js.map +1 -0
  245. package/dist/esm/text/strip.d.ts +3 -0
  246. package/dist/esm/text/strip.js +12 -0
  247. package/dist/esm/text/strip.js.map +1 -0
  248. package/package.json +15 -2
  249. package/schema.json +8 -1
  250. package/skills/sense/SKILL.md +1 -1
  251. package/skills/sense-setup/SKILL.md +3 -3
  252. package/dist/cjs/column-hint.js.map +0 -1
  253. package/dist/cjs/fences.js.map +0 -1
  254. package/dist/cjs/graph.js.map +0 -1
  255. package/dist/cjs/output.js.map +0 -1
  256. package/dist/cjs/progress.js.map +0 -1
  257. package/dist/cjs/scan.d.cts +0 -35
  258. package/dist/cjs/scan.d.ts +0 -35
  259. package/dist/cjs/scan.js +0 -436
  260. package/dist/cjs/scan.js.map +0 -1
  261. package/dist/cjs/search-error.js.map +0 -1
  262. package/dist/cjs/segment.d.cts +0 -2
  263. package/dist/cjs/segment.d.ts +0 -2
  264. package/dist/cjs/segment.js.map +0 -1
  265. package/dist/cjs/sql-functions.js.map +0 -1
  266. package/dist/cjs/traverse.js.map +0 -1
  267. package/dist/esm/column-hint.js.map +0 -1
  268. package/dist/esm/fences.js.map +0 -1
  269. package/dist/esm/graph.js.map +0 -1
  270. package/dist/esm/output.js.map +0 -1
  271. package/dist/esm/progress.js.map +0 -1
  272. package/dist/esm/scan.d.ts +0 -35
  273. package/dist/esm/scan.js.map +0 -1
  274. package/dist/esm/search-error.js.map +0 -1
  275. package/dist/esm/segment.d.ts +0 -2
  276. package/dist/esm/segment.js.map +0 -1
  277. package/dist/esm/sql-functions.js.map +0 -1
  278. package/dist/esm/traverse.js.map +0 -1
  279. /package/dist/cjs/{sql-functions.d.cts → db/sql-functions.d.cts} +0 -0
  280. /package/dist/cjs/{sql-functions.d.ts → db/sql-functions.d.ts} +0 -0
  281. /package/dist/cjs/{fences.d.cts → features/fences.d.cts} +0 -0
  282. /package/dist/cjs/{fences.d.ts → features/fences.d.ts} +0 -0
  283. /package/dist/cjs/{graph.d.cts → graph/graph.d.cts} +0 -0
  284. /package/dist/cjs/{graph.d.ts → graph/graph.d.ts} +0 -0
  285. /package/dist/cjs/{graph.js → graph/graph.js} +0 -0
  286. /package/dist/cjs/{traverse.d.cts → graph/traverse.d.cts} +0 -0
  287. /package/dist/cjs/{traverse.d.ts → graph/traverse.d.ts} +0 -0
  288. /package/dist/cjs/{traverse.js → graph/traverse.js} +0 -0
  289. /package/dist/cjs/{column-hint.d.cts → output/column-hint.d.cts} +0 -0
  290. /package/dist/cjs/{column-hint.d.ts → output/column-hint.d.ts} +0 -0
  291. /package/dist/cjs/{column-hint.js → output/column-hint.js} +0 -0
  292. /package/dist/cjs/{output.d.cts → output/output.d.cts} +0 -0
  293. /package/dist/cjs/{output.d.ts → output/output.d.ts} +0 -0
  294. /package/dist/cjs/{output.js → output/output.js} +0 -0
  295. /package/dist/cjs/{progress.d.cts → output/progress.d.cts} +0 -0
  296. /package/dist/cjs/{progress.d.ts → output/progress.d.ts} +0 -0
  297. /package/dist/cjs/{progress.js → output/progress.js} +0 -0
  298. /package/dist/cjs/{search-error.d.cts → output/search-error.d.cts} +0 -0
  299. /package/dist/cjs/{search-error.d.ts → output/search-error.d.ts} +0 -0
  300. /package/dist/esm/{sql-functions.d.ts → db/sql-functions.d.ts} +0 -0
  301. /package/dist/esm/{fences.d.ts → features/fences.d.ts} +0 -0
  302. /package/dist/esm/{graph.d.ts → graph/graph.d.ts} +0 -0
  303. /package/dist/esm/{graph.js → graph/graph.js} +0 -0
  304. /package/dist/esm/{traverse.d.ts → graph/traverse.d.ts} +0 -0
  305. /package/dist/esm/{traverse.js → graph/traverse.js} +0 -0
  306. /package/dist/esm/{column-hint.d.ts → output/column-hint.d.ts} +0 -0
  307. /package/dist/esm/{column-hint.js → output/column-hint.js} +0 -0
  308. /package/dist/esm/{output.d.ts → output/output.d.ts} +0 -0
  309. /package/dist/esm/{output.js → output/output.js} +0 -0
  310. /package/dist/esm/{progress.d.ts → output/progress.d.ts} +0 -0
  311. /package/dist/esm/{progress.js → output/progress.js} +0 -0
  312. /package/dist/esm/{search-error.d.ts → output/search-error.d.ts} +0 -0
@@ -10,6 +10,7 @@ export interface EmbedConfig {
10
10
  url?: string;
11
11
  key?: string;
12
12
  languages?: string[];
13
+ chunkTokens?: number;
13
14
  }
14
15
  export declare const DEFAULT_EMBED_MODEL = "minishlab/potion-retrieval-32M";
15
16
  export interface Preset {
@@ -10,6 +10,7 @@ export interface EmbedConfig {
10
10
  url?: string;
11
11
  key?: string;
12
12
  languages?: string[];
13
+ chunkTokens?: number;
13
14
  }
14
15
  export declare const DEFAULT_EMBED_MODEL = "minishlab/potion-retrieval-32M";
15
16
  export interface Preset {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/types.ts"],"sourcesContent":["import type { SignalWeights } from './signals.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 5;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; commands degrade when one is off.\n// links/sections/tags/rank are opt-out toggles in the top-level `features` block; embed is not a\n// member of that block -- it is on exactly when an `embed` block names a model.\nexport const FEATURE_NAMES = ['links', 'sections', 'tags', 'rank', 'embed'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `embed` names the model and gives the tree vectors at all; a preset's `signals` says which\n// engines that scope's searches use.\nexport interface EmbedConfig {\n model?: string;\n provider?: 'static' | 'openai' | 'cohere';\n url?: string; // openai provider: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // openai/cohere provider: name of the env var holding the bearer token, if any\n languages?: string[]; // owner-declared model languages; enables the language-fit check where no card exists\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\n// A named, self-contained file-selection scope. include/exclude are globby patterns resolved\n// relative to the config file. No inheritance between presets -- every field a preset wants\n// it declares itself. Presets may overlap freely; they are views, not partitions.\nexport interface Preset {\n include: string[];\n exclude?: string[];\n k?: number; // result count for `search` scoped to this preset; default 10\n // Exhaustive when present: every signal this preset's searches compose, each mapped to\n // its RRF weight (a finite number > 0; presence is enablement). Absent means every signal\n // whose prerequisite holds, each at weight 1 -- words always, links when the feature is on,\n // vectors when the tree names a model. A layer searched for exact wording (ingested sources,\n // archives, generated output) declares {\"words\":1,\"links\":1} and costs no embedding at all.\n signals?: SignalWeights;\n where?: string; // standing SQL condition against frontmatter alias `f`\n}\n\n// A saved `search` invocation: `sense <name>` runs like\n// `sense search <search> [--preset] [--include] [--exclude] [--where] [--k]`.\nexport interface SavedSearch {\n search: string;\n preset?: string;\n include?: string[];\n exclude?: string[];\n where?: string;\n k?: number;\n}\n\n// An entry names the verb it runs, one to one with the commands: { sql } runs like\n// `sense sql`, { search } like `sense search`.\nexport type SavedQuery = { sql: string } | SavedSearch;\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n // File selection, index-time, and per-preset search defaults. A file is indexed iff any\n // preset's include/exclude covers it (union). `default` is used when a command names no preset.\n presets: Record<string, Preset>;\n // Global feature defaults; absent block or key means enabled. embed is not a member here --\n // see the `embed` block.\n features?: { links?: boolean; sections?: boolean; tags?: boolean; rank?: boolean };\n // Names the embedding model. Present means every indexed file gets vectors; absent means none do.\n embed?: EmbedConfig;\n // Settings for the `content` FTS5 table. `tokenize` decides which languages the tree can be\n // word-searched in at all; changing it rebuilds the index.\n content?: { tokenize?: string };\n queries: Record<string, SavedQuery>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\nexport interface SearchOverrides {\n preset?: string;\n k?: number;\n where?: string;\n include?: string[];\n exclude?: string[];\n noExclude?: boolean; // drop the preset's exclude for this command; --exclude still applies\n}\n\nexport interface EffectiveSearch {\n presetName: string;\n k: number;\n where?: string;\n include: string[];\n exclude?: string[];\n signals: SignalWeights; // this preset's effective signal weights\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","FEATURE_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION"],"mappings":";;;;;;;;;;;QAEaA;eAAAA;;QAsBAC;eAAAA;;QAbAC;eAAAA;;QARAC;eAAAA;;QAGAC;eAAAA;;;AAJN,IAAMJ,kBAAkB;AACxB,IAAMG,YAAY;AAGlB,IAAMC,2BAA2B;AAKjC,IAAMF,gBAAgB;IAAC;IAAS;IAAY;IAAQ;IAAQ;CAAQ;AAapE,IAAMD,sBAAsB"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/types.ts"],"sourcesContent":["import type { SignalWeights } from './signals.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 5;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; commands degrade when one is off.\n// links/sections/tags/rank are opt-out toggles in the top-level `features` block; embed is not a\n// member of that block -- it is on exactly when an `embed` block names a model.\nexport const FEATURE_NAMES = ['links', 'sections', 'tags', 'rank', 'embed'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `embed` names the model and gives the tree vectors at all; a preset's `signals` says which\n// engines that scope's searches use.\nexport interface EmbedConfig {\n model?: string;\n provider?: 'static' | 'openai' | 'cohere';\n url?: string; // openai provider: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // openai/cohere provider: name of the env var holding the bearer token, if any\n languages?: string[]; // owner-declared model languages; enables the language-fit check where no card exists\n chunkTokens?: number; // chunk size ceiling in estimated tokens for small-context models; default 500\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\n// A named, self-contained file-selection scope. include/exclude are globby patterns resolved\n// relative to the config file. No inheritance between presets -- every field a preset wants\n// it declares itself. Presets may overlap freely; they are views, not partitions.\nexport interface Preset {\n include: string[];\n exclude?: string[];\n k?: number; // result count for `search` scoped to this preset; default 10\n // Exhaustive when present: every signal this preset's searches compose, each mapped to\n // its RRF weight (a finite number > 0; presence is enablement). Absent means every signal\n // whose prerequisite holds, each at weight 1 -- words always, links when the feature is on,\n // vectors when the tree names a model. A layer searched for exact wording (ingested sources,\n // archives, generated output) declares {\"words\":1,\"links\":1} and costs no embedding at all.\n signals?: SignalWeights;\n where?: string; // standing SQL condition against frontmatter alias `f`\n}\n\n// A saved `search` invocation: `sense <name>` runs like\n// `sense search <search> [--preset] [--include] [--exclude] [--where] [--k]`.\nexport interface SavedSearch {\n search: string;\n preset?: string;\n include?: string[];\n exclude?: string[];\n where?: string;\n k?: number;\n}\n\n// An entry names the verb it runs, one to one with the commands: { sql } runs like\n// `sense sql`, { search } like `sense search`.\nexport type SavedQuery = { sql: string } | SavedSearch;\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n // File selection, index-time, and per-preset search defaults. A file is indexed iff any\n // preset's include/exclude covers it (union). `default` is used when a command names no preset.\n presets: Record<string, Preset>;\n // Global feature defaults; absent block or key means enabled. embed is not a member here --\n // see the `embed` block.\n features?: { links?: boolean; sections?: boolean; tags?: boolean; rank?: boolean };\n // Names the embedding model. Present means every indexed file gets vectors; absent means none do.\n embed?: EmbedConfig;\n // Settings for the `content` FTS5 table. `tokenize` decides which languages the tree can be\n // word-searched in at all; changing it rebuilds the index.\n content?: { tokenize?: string };\n queries: Record<string, SavedQuery>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\nexport interface SearchOverrides {\n preset?: string;\n k?: number;\n where?: string;\n include?: string[];\n exclude?: string[];\n noExclude?: boolean; // drop the preset's exclude for this command; --exclude still applies\n}\n\nexport interface EffectiveSearch {\n presetName: string;\n k: number;\n where?: string;\n include: string[];\n exclude?: string[];\n signals: SignalWeights; // this preset's effective signal weights\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","FEATURE_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION"],"mappings":";;;;;;;;;;;QAEaA;eAAAA;;QAuBAC;eAAAA;;QAdAC;eAAAA;;QARAC;eAAAA;;QAGAC;eAAAA;;;AAJN,IAAMJ,kBAAkB;AACxB,IAAMG,YAAY;AAGlB,IAAMC,2BAA2B;AAKjC,IAAMF,gBAAgB;IAAC;IAAS;IAAY;IAAQ;IAAQ;CAAQ;AAcpE,IAAMD,sBAAsB"}
@@ -1,4 +1,5 @@
1
1
  import type { Config } from './types.js';
2
+ export declare const KNOWN_EMBED_KEYS: Set<string>;
2
3
  export declare function unknownConfigKeys(cfg: Record<string, unknown>): string[];
3
4
  export declare function validateLegacyScan(parsed: unknown, configPath: string): void;
4
5
  export declare function validateConfig(parsed: unknown, configPath: string): Config;
@@ -1,4 +1,5 @@
1
1
  import type { Config } from './types.js';
2
+ export declare const KNOWN_EMBED_KEYS: Set<string>;
2
3
  export declare function unknownConfigKeys(cfg: Record<string, unknown>): string[];
3
4
  export declare function validateLegacyScan(parsed: unknown, configPath: string): void;
4
5
  export declare function validateConfig(parsed: unknown, configPath: string): Config;
@@ -9,6 +9,9 @@ function _export(target, all) {
9
9
  });
10
10
  }
11
11
  _export(exports, {
12
+ get KNOWN_EMBED_KEYS () {
13
+ return KNOWN_EMBED_KEYS;
14
+ },
12
15
  get unknownConfigKeys () {
13
16
  return unknownConfigKeys;
14
17
  },
@@ -100,7 +103,8 @@ var KNOWN_EMBED_KEYS = new Set([
100
103
  'provider',
101
104
  'url',
102
105
  'key',
103
- 'languages'
106
+ 'languages',
107
+ 'chunkTokens'
104
108
  ]);
105
109
  var KNOWN_CONTENT_KEYS = new Set([
106
110
  'tokenize'
@@ -219,6 +223,9 @@ function validateEmbedBlock(value, configPath) {
219
223
  }))) {
220
224
  throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ': embed.languages must be a non-empty array of language tags (e.g. ["en", "zh"])'));
221
225
  }
226
+ if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {
227
+ throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": embed.chunkTokens must be a positive integer"));
228
+ }
222
229
  }
223
230
  function isSignalName(value) {
224
231
  return typeof value === 'string' && _signalsts.SIGNAL_NAMES.includes(value);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport type { Config } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'presets', 'features', 'embed', 'content', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\nconst KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages']);\nconst KNOWN_CONTENT_KEYS = new Set(['tokenize']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\n// Only the shape is checked here. Whether the linked SQLite accepts the tokenizer is settled\n// by probing it in db.ts, so this never has to carry a table of which version added what.\nfunction validateContentBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content must be an object of { tokenize?: string }`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_CONTENT_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content has unknown key(s) ${unknown.join(', ')}; content takes tokenize`);\n }\n if (block.tokenize !== undefined && (typeof block.tokenize !== 'string' || block.tokenize.trim() === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. \"trigram\" or \"unicode61 tokenchars '-_'\"`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that\n// doesn't need the top-level embed block: links is seeded by word-match rows, so declaring it\n// without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string used to mean SQL. It now fails rather than being inferred: an entry says\n // which of the two verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.content !== undefined) {\n validateContentBlock(cfg.content, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["unknownConfigKeys","validateConfig","validateLegacyScan","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","KNOWN_CONTENT_KEYS","SAVED_SEARCH_KEYS","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","parsed","configPath","scan","include","SenseError","validateContentBlock","block","unknown","join","tokenize","undefined","trim","validateFeaturesBlock","entries","name","v","validateEmbedBlock","embed","model","provider","url","key","languages","some","l","isSignalName","SIGNAL_NAMES","includes","isSignalWeight","Number","isFinite","validatePresetSignals","map","signal","weight","names","SIGNAL_PREREQUISITES","requires","validatePreset","preset","exclude","signals","isInteger","where","validateSavedQuery","JSON","stringify","slice","entry","sql","search","checks","presets","default","queries","features","content"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport type { Config } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'presets', 'features', 'embed', 'content', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// Exported so a docs test can assert every key here has a schema.json property -- one source\n// of truth for what the embed block accepts, checked against the other for drift.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst KNOWN_CONTENT_KEYS = new Set(['tokenize']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\n// Only the shape is checked here. Whether the linked SQLite accepts the tokenizer is settled\n// by probing it in db.ts, so this never has to carry a table of which version added what.\nfunction validateContentBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content must be an object of { tokenize?: string }`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_CONTENT_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content has unknown key(s) ${unknown.join(', ')}; content takes tokenize`);\n }\n if (block.tokenize !== undefined && (typeof block.tokenize !== 'string' || block.tokenize.trim() === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. \"trigram\" or \"unicode61 tokenchars '-_'\"`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that\n// doesn't need the top-level embed block: links is seeded by word-match rows, so declaring it\n// without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string used to mean SQL. It now fails rather than being inferred: an entry says\n // which of the two verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.content !== undefined) {\n validateContentBlock(cfg.content, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["KNOWN_EMBED_KEYS","unknownConfigKeys","validateConfig","validateLegacyScan","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_CONTENT_KEYS","SAVED_SEARCH_KEYS","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","parsed","configPath","scan","include","SenseError","validateContentBlock","block","unknown","join","tokenize","undefined","trim","validateFeaturesBlock","entries","name","v","validateEmbedBlock","embed","model","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","SIGNAL_NAMES","includes","isSignalWeight","isFinite","validatePresetSignals","map","signal","weight","names","SIGNAL_PREREQUISITES","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","search","checks","presets","default","queries","features","content"],"mappings":";;;;;;;;;;;QAWaA;eAAAA;;QAIGC;eAAAA;;QAuMAC;eAAAA;;QA7LAC;eAAAA;;;wBAzBW;yBACyC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGpE,sFAAsF;AACtF,8FAA8F;AAC9F,IAAMC,aAAa,IAAIC,IAAI;IAAC;IAAW;IAAW;IAAW;IAAY;IAAS;IAAW;CAAU;AACvG,IAAMC,oBAAoB,IAAID,IAAI;IAAC;IAAW;IAAW;IAAK;IAAW;CAAQ;AACjF,IAAME,qBAAqB,IAAIF,IAAI;IAAC;IAAS;IAAY;IAAQ;CAAO;AAGjE,IAAML,mBAAmB,IAAIK,IAAI;IAAC;IAAS;IAAY;IAAO;IAAO;IAAa;CAAc;AACvG,IAAMG,qBAAqB,IAAIH,IAAI;IAAC;CAAW;AAC/C,IAAMI,oBAAoB,IAAIJ,IAAI;IAAC;IAAU;IAAU;IAAW;IAAW;IAAS;CAAI;AAEnF,SAASJ,kBAAkBS,GAA4B;IAC5D,OAAOC,OAAOC,IAAI,CAACF,KAAKG,MAAM,CAAC,SAACC;eAAM,CAACV,WAAWW,GAAG,CAACD;;AACxD;AAEA,SAASE,sBAAsBC,KAAc;IAC3C,OAAOC,MAAMC,OAAO,CAACF,UAAUA,MAAMG,MAAM,GAAG,KAAKH,MAAMI,KAAK,CAAC,SAACC;eAAM,OAAOA,MAAM,YAAYA,EAAEF,MAAM,GAAG;;AAC5G;AAIO,SAASjB,mBAAmBoB,MAAe,EAAEC,UAAkB;IACpE,IAAMd,MAAM,CAAA,OAAOa,uCAAP,SAAOA,OAAK,MAAM,YAAYA,WAAW,QAAQ,CAACL,MAAMC,OAAO,CAACI,UAAWA,SAAqC;IAC5H,IAAME,OAAOf,gBAAAA,0BAAAA,IAAKe,IAAI;IACtB,IAAI,CAACf,OAAO,CAACe,QAAQ,CAACT,sBAAsBS,KAAKC,OAAO,GAAG;QACzD,MAAM,IAAIC,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;AACF;AAEA,6FAA6F;AAC7F,0FAA0F;AAC1F,SAASI,qBAAqBX,KAAc,EAAEO,UAAkB;IAC9D,IAAI,CAAA,OAAOP,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,QAAQ;QACvE,MAAM,IAAIU,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAMK,QAAQZ;IACd,IAAMa,UAAUnB,OAAOC,IAAI,CAACiB,OAAOhB,MAAM,CAAC,SAACC;eAAM,CAACN,mBAAmBO,GAAG,CAACD;;IACzE,IAAIgB,QAAQV,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAA4CG,OAA1CN,YAAW,iCAAkD,OAAnBM,QAAQC,IAAI,CAAC,OAAM;IACzG;IACA,IAAIF,MAAMG,QAAQ,KAAKC,aAAc,CAAA,OAAOJ,MAAMG,QAAQ,KAAK,YAAYH,MAAMG,QAAQ,CAACE,IAAI,OAAO,EAAC,GAAI;QACxG,MAAM,IAAIP,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;AACF;AAEA,SAASW,sBAAsBlB,KAAc,EAAEO,UAAkB;IAC/D,IAAI,CAAA,OAAOP,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,QAAQ;QACvE,MAAM,IAAIU,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAMK,QAAQZ;IACd,IAAMa,UAAUnB,OAAOC,IAAI,CAACiB,OAAOhB,MAAM,CAAC,SAACC;eAAM,CAACP,mBAAmBQ,GAAG,CAACD;;IACzE,IAAIgB,QAAQV,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAA6CG,OAA3CN,YAAW,kCAAmD,OAAnBM,QAAQC,IAAI,CAAC,OAAM;IAC1G;QACK,kCAAA,2BAAA;;QAAL,QAAK,YAAmBpB,OAAOyB,OAAO,CAACP,2BAAlC,SAAA,6BAAA,QAAA,yBAAA,iCAA0C;YAA1C,mCAAA,iBAAOQ,uBAAMC;YAChB,IAAI,OAAOA,MAAM,WAAW;gBAC1B,MAAM,IAAIX,oBAAU,CAAC,kBAAkB,AAAC,GAA0BU,OAAxBb,YAAW,eAAkB,OAALa,MAAK;YACzE;QACF;;QAJK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;AAKP;AAEA,SAASE,mBAAmBtB,KAAc,EAAEO,UAAkB;IAC5D,IAAI,CAAA,OAAOP,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,QAAQ;QACvE,MAAM,IAAIU,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAMgB,QAAQvB;IACd,IAAMa,UAAUnB,OAAOC,IAAI,CAAC4B,OAAO3B,MAAM,CAAC,SAACC;eAAM,CAACd,iBAAiBe,GAAG,CAACD;;IACvE,IAAIgB,QAAQV,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAA0CG,OAAxCN,YAAW,+BAAgD,OAAnBM,QAAQC,IAAI,CAAC;IACjG;IACA,IAAIS,MAAMC,KAAK,KAAKR,aAAa,OAAOO,MAAMC,KAAK,KAAK,UAAU;QAChE,MAAM,IAAId,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,yFAAyF;IACzF,4EAA4E;IAC5E,IAAIgB,MAAMC,KAAK,KAAKR,aAAaO,MAAMC,KAAK,KAAK,IAAI;QACnD,MAAM,IAAId,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAIgB,MAAME,QAAQ,KAAKT,aAAaO,MAAME,QAAQ,KAAK,YAAYF,MAAME,QAAQ,KAAK,YAAYF,MAAME,QAAQ,KAAK,UAAU;QAC7H,MAAM,IAAIf,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAIgB,MAAMG,GAAG,KAAKV,aAAa,OAAOO,MAAMG,GAAG,KAAK,UAAU;QAC5D,MAAM,IAAIhB,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAIgB,MAAMI,GAAG,KAAKX,aAAa,OAAOO,MAAMI,GAAG,KAAK,UAAU;QAC5D,MAAM,IAAIjB,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,yFAAyF;IACzF,wDAAwD;IACxD,IAAIgB,MAAME,QAAQ,KAAK,YAAaF,CAAAA,MAAMG,GAAG,KAAKV,aAAaO,MAAMG,GAAG,KAAK,EAAC,GAAI;QAChF,MAAM,IAAIhB,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAIgB,MAAMK,SAAS,KAAKZ,aAAc,CAAA,CAACf,MAAMC,OAAO,CAACqB,MAAMK,SAAS,KAAKL,MAAMK,SAAS,CAACzB,MAAM,KAAK,KAAKoB,MAAMK,SAAS,CAACC,IAAI,CAAC,SAACC;eAAM,OAAOA,MAAM,YAAYA,MAAM;MAAE,GAAI;QACxK,MAAM,IAAIpB,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAIgB,MAAMQ,WAAW,KAAKf,aAAc,CAAA,OAAOO,MAAMQ,WAAW,KAAK,YAAY,CAACC,OAAOC,SAAS,CAACV,MAAMQ,WAAW,KAAKR,MAAMQ,WAAW,IAAI,CAAA,GAAI;QAChJ,MAAM,IAAIrB,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;AACF;AAEA,SAAS2B,aAAalC,KAAc;IAClC,OAAO,OAAOA,UAAU,YAAY,AAACmC,uBAAY,CAAuBC,QAAQ,CAACpC;AACnF;AAEA,SAASqC,eAAerC,KAAc;IACpC,OAAO,OAAOA,UAAU,YAAYgC,OAAOM,QAAQ,CAACtC,UAAUA,QAAQ;AACxE;AAEA,yFAAyF;AACzF,8FAA8F;AAC9F,iCAAiC;AACjC,SAASuC,sBAAsBnB,IAAY,EAAEpB,KAAc,EAAEO,UAAkB;IAC7E,IAAI,CAAA,OAAOP,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,UAAUN,OAAOC,IAAI,CAACK,OAAOG,MAAM,KAAK,GAAG;QAC1G,MAAM,IAAIO,oBAAU,CAClB,kBACA,AAAC,GAAyBU,OAAvBb,YAAW,cAA8G4B,OAAlGf,MAAK,+FAAqH,OAAxBe,uBAAY,CAACrB,IAAI,CAAC,OAAM;IAExJ;IACA,IAAMK,UAAUzB,OAAOyB,OAAO,CAACnB;IAC/B,IAAMa,UAAUM,QAAQvB,MAAM,CAAC;iDAAEC;eAAO,CAACqC,aAAarC;OAAI2C,GAAG,CAAC;iDAAE3C;eAAOA;;IACvE,IAAIgB,QAAQV,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAoDM,OAAxCO,MAAK,qCAA4Ee,OAAzCtB,QAAQC,IAAI,CAAC,OAAM,wBAA8C,OAAxBqB,uBAAY,CAACrB,IAAI,CAAC;IACtK;QACK,kCAAA,2BAAA;;QAAL,QAAK,YAA0BK,4BAA1B,SAAA,6BAAA,QAAA,yBAAA,iCAAmC;YAAnC,mCAAA,iBAAOsB,yBAAQC;YAClB,IAAI,CAACL,eAAeK,SAAS;gBAC3B,MAAM,IAAIhC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAA4BkC,OAAhBrB,MAAK,aAAkB,OAAPqB,QAAO;YAC1F;QACF;;QAJK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAKL,IAAME,QAAQ,IAAIvD,IAAI+B,QAAQqB,GAAG,CAAC;iDAAE3C;eAAOA;;QACtC,mCAAA,4BAAA;;QAAL,QAAK,aAA4BH,OAAOyB,OAAO,CAACyB,+BAAoB,sBAA/D,UAAA,8BAAA,SAAA,0BAAA,kCAAqG;YAArG,oCAAA,kBAAOH,2BAAQI;YAClB,IAAIF,MAAM7C,GAAG,CAAC2C,YAAW,CAACE,MAAM7C,GAAG,CAAC+C,WAAW;gBAC7C,MAAM,IAAInC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiCkC,OAArBrB,MAAK,kBAAoCyB,OAApBJ,SAAO,eAAgCI,OAAnBA,UAAS,YAAkCzB,OAAxByB,UAAS,iBAAyCJ,OAA1BrB,MAAK,uBAA4B,OAAPqB,SAAO;YACxL;QACF;;QAJK;QAAA;;;iBAAA,8BAAA;gBAAA;;;gBAAA;sBAAA;;;;AAKP;AAEA,SAASK,eAAe1B,IAAY,EAAEpB,KAAc,EAAEO,UAAkB;IACtE,IAAI,CAAA,OAAOP,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,QAAQ;QACvE,MAAM,IAAIU,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;IACxE;IACA,IAAM2B,SAAS/C;IACf,IAAMa,UAAUnB,OAAOC,IAAI,CAACoD,QAAQnD,MAAM,CAAC,SAACC;eAAM,CAACR,kBAAkBS,GAAG,CAACD;;IACzE,IAAIgB,QAAQV,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAuCM,OAA3BO,MAAK,wBAAyC,OAAnBP,QAAQC,IAAI,CAAC,OAAM;IACjH;IACA,IAAI,CAACf,sBAAsBgD,OAAOtC,OAAO,GAAG;QAC1C,MAAM,IAAIC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;IACxE;IACA,IAAI2B,OAAOC,OAAO,KAAKhC,aAAa,CAACjB,sBAAsBgD,OAAOC,OAAO,GAAG;QAC1E,MAAM,IAAItC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;IACxE;IACA,IAAI2B,OAAOE,OAAO,KAAKjC,WAAWuB,sBAAsBnB,MAAM2B,OAAOE,OAAO,EAAE1C;IAC9E,IAAIwC,OAAOlD,CAAC,KAAKmB,aAAc,CAAA,OAAO+B,OAAOlD,CAAC,KAAK,YAAY,CAACmC,OAAOC,SAAS,CAACc,OAAOlD,CAAC,KAAKkD,OAAOlD,CAAC,IAAI,CAAA,GAAI;QAC5G,MAAM,IAAIa,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;IACxE;IACA,IAAI2B,OAAOG,KAAK,KAAKlC,aAAa,OAAO+B,OAAOG,KAAK,KAAK,UAAU;QAClE,MAAM,IAAIxC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;IACxE;AACF;AAEA,yGAAyG;AACzG,SAAS+B,mBAAmB/B,IAAY,EAAEpB,KAAc,EAAEO,UAAkB;IAC1E,yFAAyF;IACzF,0EAA0E;IAC1E,IAAI,OAAOP,UAAU,UAAU;QAC7B,MAAM,IAAIU,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAA0D6C,OAA9ChC,MAAK,2CAAgH,OAAvEgC,KAAKC,SAAS,CAACrD,MAAMG,MAAM,GAAG,KAAK,AAAC,GAAqB,OAAnBH,MAAMsD,KAAK,CAAC,GAAG,KAAI,SAAOtD,QAAO;IACxL;IACA,IAAI,CAAA,OAAOA,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQC,MAAMC,OAAO,CAACF,QAAQ;QACvE,MAAM,IAAIU,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;IACxE;IACA,IAAMmC,QAAQvD;IACd,IAAI,SAASuD,OAAO;QAClB,IAAM1C,UAAUnB,OAAOC,IAAI,CAAC4D,OAAO3D,MAAM,CAAC,SAACC;mBAAMA,MAAM;;QACvD,IAAIgB,QAAQV,MAAM,GAAG,GAAG;YACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAuCM,OAA3BO,MAAK,wBAAyC,OAAnBP,QAAQC,IAAI,CAAC,OAAM;QACjH;QACA,IAAI,OAAOyC,MAAMC,GAAG,KAAK,YAAYD,MAAMC,GAAG,CAACvC,IAAI,OAAO,IAAI;YAC5D,MAAM,IAAIP,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA;IACF;IACA,IAAI,YAAYmC,OAAO;QACrB,IAAM1C,WAAUnB,OAAOC,IAAI,CAAC4D,OAAO3D,MAAM,CAAC,SAACC;mBAAM,CAACL,kBAAkBM,GAAG,CAACD;;QACxE,IAAIgB,SAAQV,MAAM,GAAG,GAAG;YACtB,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAuCM,OAA3BO,MAAK,wBAAyC,OAAnBP,SAAQC,IAAI,CAAC,OAAM;QACjH;QACA,4EAA4E;QAC5E,IAAI,OAAOyC,MAAME,MAAM,KAAK,YAAYF,MAAME,MAAM,CAACxC,IAAI,OAAO,IAAI;YAClE,MAAM,IAAIP,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA,IAAImC,MAAMR,MAAM,KAAK/B,aAAc,CAAA,OAAOuC,MAAMR,MAAM,KAAK,YAAYQ,MAAMR,MAAM,CAAC5C,MAAM,KAAK,CAAA,GAAI;YACjG,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA,IAAImC,MAAM9C,OAAO,KAAKO,aAAa,CAACjB,sBAAsBwD,MAAM9C,OAAO,GAAG;YACxE,MAAM,IAAIC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA,IAAImC,MAAMP,OAAO,KAAKhC,aAAa,CAACjB,sBAAsBwD,MAAMP,OAAO,GAAG;YACxE,MAAM,IAAItC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA,IAAImC,MAAML,KAAK,KAAKlC,aAAa,OAAOuC,MAAML,KAAK,KAAK,UAAU;YAChE,MAAM,IAAIxC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA,IAAImC,MAAM1D,CAAC,KAAKmB,aAAc,CAAA,OAAOuC,MAAM1D,CAAC,KAAK,YAAY,CAACmC,OAAOC,SAAS,CAACsB,MAAM1D,CAAC,KAAK0D,MAAM1D,CAAC,IAAI,CAAA,GAAI;YACxG,MAAM,IAAIa,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;QACxE;QACA;IACF;IACA,MAAM,IAAIV,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAAiB,OAALa,MAAK;AACxE;AAEO,SAASnC,eAAeqB,MAAe,EAAEC,UAAkB;IAChE,IAAI,CAAA,OAAOD,uCAAP,SAAOA,OAAK,MAAM,YAAYA,WAAW,QAAQL,MAAMC,OAAO,CAACI,SAAS;QAC1E,MAAM,IAAII,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAMd,MAAMa;IAEZ,yFAAyF;IACzF,IAAIb,IAAIiE,MAAM,KAAK1C,WAAW;QAC5B,MAAM,IAAIN,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IAEA,IAAMoD,UAAUlE,IAAIkE,OAAO;IAC3B,IAAI,CAACA,WAAW,CAAA,OAAOA,wCAAP,SAAOA,QAAM,MAAM,YAAY1D,MAAMC,OAAO,CAACyD,YAAYjE,OAAOC,IAAI,CAACgE,SAASxD,MAAM,KAAK,GAAG;QAC1G,MAAM,IAAIO,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;QACK,kCAAA,2BAAA;;QAAL,QAAK,YAAuBb,OAAOyB,OAAO,CAACwC,6BAAtC,SAAA,6BAAA,QAAA,yBAAA,iCAAgD;YAAhD,mCAAA,iBAAOvC,uBAAMpB;YAChB8C,eAAe1B,MAAMpB,OAAOO;QAC9B;;QAFK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAGL,IAAIoD,QAAQC,OAAO,KAAK5C,WAAW;QACjC,MAAM,IAAIN,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IAEA,IAAId,IAAIoE,OAAO,KAAK7C,WAAWvB,IAAIoE,OAAO,GAAG,CAAC;IAC9C,IAAI,SAAOpE,IAAIoE,OAAO,MAAK,YAAYpE,IAAIoE,OAAO,KAAK,QAAQ5D,MAAMC,OAAO,CAACT,IAAIoE,OAAO,GAAG;QACzF,MAAM,IAAInD,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXH,YAAW;IACvD;IACA,IAAMsD,UAAUpE,IAAIoE,OAAO;QACtB,mCAAA,4BAAA;;QAAL,QAAK,aAAuBnE,OAAOyB,OAAO,CAAC0C,6BAAtC,UAAA,8BAAA,SAAA,0BAAA,kCAAgD;YAAhD,oCAAA,kBAAOzC,yBAAMpB;YAChBmD,mBAAmB/B,OAAMpB,QAAOO;QAClC;;QAFK;QAAA;;;iBAAA,8BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAIL,IAAId,IAAIqE,QAAQ,KAAK9C,WAAW;QAC9BE,sBAAsBzB,IAAIqE,QAAQ,EAAEvD;IACtC;IACA,IAAId,IAAI8B,KAAK,KAAKP,WAAW;QAC3BM,mBAAmB7B,IAAI8B,KAAK,EAAEhB;IAChC;IACA,IAAId,IAAIsE,OAAO,KAAK/C,WAAW;QAC7BL,qBAAqBlB,IAAIsE,OAAO,EAAExD;IACpC;IAEA,8FAA8F;IAC9F,IAAId,IAAI8B,KAAK,KAAKP,WAAW;YACtB,mCAAA,4BAAA;;YAAL,QAAK,aAAuBtB,OAAOyB,OAAO,CAACwC,6BAAtC,UAAA,8BAAA,SAAA,0BAAA,kCAAgD;gBAAhD,oCAAA,kBAAOvC,yBAAMpB;gBAChB,IAAMiD,UAAU,AAACjD,OAAkCiD,OAAO;gBAC1D,IAAIA,WAAW,aAAaA,SAAS;oBACnC,MAAM,IAAIvC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBU,OAAvBb,YAAW,cAA4Ha,OAAhHA,OAAK,6GAAgH,OAALA,OAAK;gBACxL;YACF;;YALK;YAAA;;;qBAAA,8BAAA;oBAAA;;;oBAAA;0BAAA;;;;IAMP;IAEA,OAAO3B;AACT"}
@@ -1,7 +1,7 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
2
  import type { ResolvedConfig } from '../config/index.js';
3
3
  export declare const DB_FILENAME = "cache.db";
4
- export declare const SCHEMA_VERSION = "17";
4
+ export declare const SCHEMA_VERSION = "18";
5
5
  export interface OpenResult {
6
6
  db: DatabaseSync;
7
7
  cfg: ResolvedConfig;
@@ -1,7 +1,7 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
2
  import type { ResolvedConfig } from '../config/index.js';
3
3
  export declare const DB_FILENAME = "cache.db";
4
- export declare const SCHEMA_VERSION = "17";
4
+ export declare const SCHEMA_VERSION = "18";
5
5
  export interface OpenResult {
6
6
  db: DatabaseSync;
7
7
  cfg: ResolvedConfig;
@@ -33,9 +33,9 @@ var _nodesqlite = require("node:sqlite");
33
33
  var _indexts = require("../config/index.js");
34
34
  var _errorsts = require("../errors.js");
35
35
  var _indexts1 = require("../features/index.js");
36
- var _sqlfunctionsts = require("../sql-functions.js");
37
36
  var _reconcilets = require("./reconcile.js");
38
37
  var _sharedts = require("./shared.js");
38
+ var _sqlfunctionsts = require("./sql-functions.js");
39
39
  function _array_like_to_array(arr, len) {
40
40
  if (len == null || len > arr.length) len = arr.length;
41
41
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -64,7 +64,7 @@ function _unsupported_iterable_to_array(o, minLen) {
64
64
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
65
65
  }
66
66
  var DB_FILENAME = 'cache.db';
67
- var SCHEMA_VERSION = '17';
67
+ var SCHEMA_VERSION = '18';
68
68
  // Stemming is English-only, but the segmentation underneath it is what decides coverage:
69
69
  // unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)
70
70
  // indexes a whole run as one token and word search finds nothing. `content.tokenize` is how
@@ -132,7 +132,7 @@ function ensureSchema(db, cfg, tokenize) {
132
132
  }
133
133
  }
134
134
  if ((0, _sharedts.getMeta)(db, 'schema_version') === null) (0, _sharedts.setMeta)(db, 'schema_version', SCHEMA_VERSION);
135
- if ((0, _sharedts.getMeta)(db, 'features') === null) (0, _sharedts.setMeta)(db, 'features', (0, _indexts.featureSignature)(cfg));
135
+ if ((0, _sharedts.getMeta)(db, 'features') === null) (0, _sharedts.setMeta)(db, 'features', (0, _indexts.featureSignature)(cfg, _indexts1.FEATURES));
136
136
  }
137
137
  function docCount(db) {
138
138
  var row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get();
@@ -159,7 +159,7 @@ function open(cfg) {
159
159
  // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).
160
160
  var version = (0, _sharedts.getMeta)(db, 'schema_version');
161
161
  var features = (0, _sharedts.getMeta)(db, 'features');
162
- var wantFeatures = (0, _indexts.featureSignature)(cfg);
162
+ var wantFeatures = (0, _indexts.featureSignature)(cfg, _indexts1.FEATURES);
163
163
  var tokenizeOnlyRebuild = false;
164
164
  if (version !== null && version !== SCHEMA_VERSION || features !== null && features !== wantFeatures) {
165
165
  // Indexing derives from presets, so a config edit rebuilding the cache must say so and
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/open.ts"],"sourcesContent":["// The Node floor (>=22.20) is explained here and nowhere else: 22.20 is the first release with\n// both FTS5 and row-returning INSERT ... RETURNING. Raise it only for a load-bearing capability.\nimport { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { contentTokenize, featureSignature, STATE_DIR } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { activeFeatures } from '../features/index.ts';\nimport { registerFunctions } from '../sql-functions.ts';\nimport { changedSignatureKeys, embedIdentityAdopted, rebuildContentTable, reconcile, signatureDiff } from './reconcile.ts';\nimport { getMeta, setMeta } from './shared.ts';\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`. Bumping it rebuilds\n// existing trees on first query.\nexport const SCHEMA_VERSION = '17';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// Stemming is English-only, but the segmentation underneath it is what decides coverage:\n// unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)\n// indexes a whole run as one token and word search finds nothing. `content.tokenize` is how\n// such a tree picks trigram instead.\nconst DEFAULT_TOKENIZE = 'porter unicode61';\n\n// FTS5 takes its tokenizer as a string literal inside DDL, where nothing can bind, so a\n// configured value has to be concatenated. Probing a throwaway table is what makes that safe\n// and is also the whole validation: anything the linked SQLite accepts passes, anything else\n// fails here with SQLite's own message rather than against the real table. It means no table\n// of which version added which tokenizer has to be maintained.\nfunction resolveTokenize(db: DatabaseSync, cfg: Config): string {\n const configured = contentTokenize(cfg);\n if (configured === undefined) return DEFAULT_TOKENIZE;\n const literal = configured.replace(/'/g, \"''\");\n try {\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n db.exec(`CREATE VIRTUAL TABLE temp.sense_tokenize_probe USING fts5(x, tokenize = '${literal}')`);\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n } catch (err) {\n throw new SenseError('CONFIG_INVALID', `content.tokenize \"${configured}\" is not a tokenizer this SQLite accepts (${(err as Error).message}); the built-in choices are unicode61, ascii, porter, and trigram, each with their own options`);\n }\n return literal;\n}\n\n// The tokenizer the content table was actually built with, from its own DDL -- the one\n// record that cannot desynchronize from the table. NULL when the table does not exist yet.\nfunction storedTokenize(db: DatabaseSync): string | null {\n const row = db.prepare(`SELECT sql FROM sqlite_master WHERE name = 'content'`).get() as { sql: string } | undefined;\n if (!row) return null;\n const m = row.sql.match(/tokenize = '((?:[^']|'')*)'/);\n return m ? m[1] : null;\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter`\n// can't dump file text into context. Features add their own tables after the core ones.\nfunction ensureSchema(db: DatabaseSync, cfg: Config, tokenize: string): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_ctime\" REAL, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n // IF NOT EXISTS is safe against a tokenizer change: open() compares the table's own DDL\n // against the resolved tokenizer before this runs, so a stale table is already gone by now.\n // The three `_seg` sidecars are appended after path, never inserted: bm25(content, ...) and\n // snippet(content, 2, ...) are documented against the first three columns and keep working\n // (FTS5 defaults the weights it was not given). Each carries its field's exploded unspaced\n // runs for text that needs it, and an empty string for text that does not.\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${tokenize}')`);\n // Coverage, not ownership: a path can appear under several presets. path leads the PK so the\n // per-doc delete is an index hit -- keyed the other way, cold builds went quadratic.\n db.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n db.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) feature.schema(db);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n if (getMeta(db, 'features') === null) setMeta(db, 'features', featureSignature(cfg));\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n // Covers a concurrent watcher's bulk reconcile (~5s for 500 files at 26k notes). A query\n // that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db, contentTokenize(cfg) === undefined);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Before the rebuild branch below, never after: that branch deletes the cache, so a typo'd\n // tokenizer validated later would cost a full re-index (and re-embed) to reach its own error.\n const tokenize = resolveTokenize(db, cfg);\n\n // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an\n // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).\n const version = getMeta(db, 'schema_version');\n const features = getMeta(db, 'features');\n const wantFeatures = featureSignature(cfg);\n let tokenizeOnlyRebuild = false;\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Indexing derives from presets, so a config edit rebuilding the cache must say so and\n // name what changed -- silent rebuilds make derived indexing look like a hang or a bug.\n if (version !== null && version !== SCHEMA_VERSION) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n const changedKeys = changedSignatureKeys(features ?? '', wantFeatures);\n // Only the tokenizer moved: frontmatter, links, sections, and embeddings are file-derived\n // and tokenizer-independent, so they don't need re-deriving. Everything else -- a preset\n // edit, an embed model change -- still takes the full clear/reopen below.\n if (changedKeys.size === 1 && changedKeys.has('tokenize')) {\n console.error('sense: config change (content tokenizer) rebuilds the text index; vectors, links, and sections are kept');\n db.exec('DROP TABLE content');\n tokenizeOnlyRebuild = true;\n } else if (changedKeys.size === 1 && changedKeys.has('embed') && embedIdentityAdopted(features ?? '', wantFeatures)) {\n // First sight of a resolved weight identity: the model itself hasn't changed, so\n // adopt it into meta with no rebuild and no re-embed, mirroring the tokenize precedent.\n console.error(\"sense: recorded the embedding model's resolved identity; vectors are unaffected\");\n setMeta(db, 'features', wantFeatures);\n } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n }\n\n // Meta can lie after a crash between table creation and the signature write; the table's\n // own DDL cannot. A mismatch here rebuilds no matter what meta says. A tokenize-only rebuild\n // just dropped content above, so storedTokenize sees no table and this guard is skipped\n // naturally rather than needing its own case.\n const stored = storedTokenize(db);\n if (stored !== null && stored !== tokenize) {\n console.error('sense: cache was built with a different content tokenizer; rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n\n ensureSchema(db, cfg, tokenize);\n\n let rebuildWarnings: string[] = [];\n if (tokenizeOnlyRebuild) {\n rebuildWarnings = rebuildContentTable(db, cfg, cfg.baseDir);\n setMeta(db, 'features', wantFeatures);\n }\n\n // 3x the largest reconcile this cache has recorded, floored at 30s and capped at 10min.\n // Installed before reconcile() -- that call is the one that races a watcher's transaction.\n const recordedMaxMs = Number(getMeta(db, 'reconcile_max_ms') ?? '0');\n db.exec(`PRAGMA busy_timeout = ${Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000)}`);\n\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings: [...rebuildWarnings, ...warnings] };\n}\n\n// Deletes the cache directory, and only that. The rebuild is not this function's job: the next\n// open() reconciles, which is what running any command already does, so a verb that bundled the\n// two described the half that was not its own. Manual reset for a doubted cache; the schema and\n// config-signature mismatches below reset themselves.\nexport function clearCache(cfg: ResolvedConfig): void {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","clearCache","docCount","open","DEFAULT_TOKENIZE","resolveTokenize","db","cfg","configured","contentTokenize","undefined","literal","replace","exec","err","SenseError","message","storedTokenize","row","prepare","get","m","sql","match","ensureSchema","tokenize","activeFeatures","feature","schema","getMeta","setMeta","featureSignature","n","stateDir","join","baseDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","registerFunctions","version","features","wantFeatures","tokenizeOnlyRebuild","console","error","close","changedKeys","changedSignatureKeys","size","has","embedIdentityAdopted","changed","signatureDiff","stored","rebuildWarnings","rebuildContentTable","recordedMaxMs","Number","Math","min","max","reconcile","parsed","warnings","rmSync","force"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/open.ts"],"sourcesContent":["// The Node floor (>=22.20) is explained here and nowhere else: 22.20 is the first release with\n// both FTS5 and row-returning INSERT ... RETURNING. Raise it only for a load-bearing capability.\nimport { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { contentTokenize, featureSignature, STATE_DIR } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { activeFeatures, FEATURES } from '../features/index.ts';\nimport { changedSignatureKeys, embedIdentityAdopted, rebuildContentTable, reconcile, signatureDiff } from './reconcile.ts';\nimport { getMeta, setMeta } from './shared.ts';\nimport { registerFunctions } from './sql-functions.ts';\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`. Bumping it rebuilds\n// existing trees on first query.\nexport const SCHEMA_VERSION = '18';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// Stemming is English-only, but the segmentation underneath it is what decides coverage:\n// unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)\n// indexes a whole run as one token and word search finds nothing. `content.tokenize` is how\n// such a tree picks trigram instead.\nconst DEFAULT_TOKENIZE = 'porter unicode61';\n\n// FTS5 takes its tokenizer as a string literal inside DDL, where nothing can bind, so a\n// configured value has to be concatenated. Probing a throwaway table is what makes that safe\n// and is also the whole validation: anything the linked SQLite accepts passes, anything else\n// fails here with SQLite's own message rather than against the real table. It means no table\n// of which version added which tokenizer has to be maintained.\nfunction resolveTokenize(db: DatabaseSync, cfg: Config): string {\n const configured = contentTokenize(cfg);\n if (configured === undefined) return DEFAULT_TOKENIZE;\n const literal = configured.replace(/'/g, \"''\");\n try {\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n db.exec(`CREATE VIRTUAL TABLE temp.sense_tokenize_probe USING fts5(x, tokenize = '${literal}')`);\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n } catch (err) {\n throw new SenseError('CONFIG_INVALID', `content.tokenize \"${configured}\" is not a tokenizer this SQLite accepts (${(err as Error).message}); the built-in choices are unicode61, ascii, porter, and trigram, each with their own options`);\n }\n return literal;\n}\n\n// The tokenizer the content table was actually built with, from its own DDL -- the one\n// record that cannot desynchronize from the table. NULL when the table does not exist yet.\nfunction storedTokenize(db: DatabaseSync): string | null {\n const row = db.prepare(`SELECT sql FROM sqlite_master WHERE name = 'content'`).get() as { sql: string } | undefined;\n if (!row) return null;\n const m = row.sql.match(/tokenize = '((?:[^']|'')*)'/);\n return m ? m[1] : null;\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter`\n// can't dump file text into context. Features add their own tables after the core ones.\nfunction ensureSchema(db: DatabaseSync, cfg: Config, tokenize: string): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_ctime\" REAL, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n // IF NOT EXISTS is safe against a tokenizer change: open() compares the table's own DDL\n // against the resolved tokenizer before this runs, so a stale table is already gone by now.\n // The three `_seg` sidecars are appended after path, never inserted: bm25(content, ...) and\n // snippet(content, 2, ...) are documented against the first three columns and keep working\n // (FTS5 defaults the weights it was not given). Each carries its field's exploded unspaced\n // runs for text that needs it, and an empty string for text that does not.\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${tokenize}')`);\n // Coverage, not ownership: a path can appear under several presets. path leads the PK so the\n // per-doc delete is an index hit -- keyed the other way, cold builds went quadratic.\n db.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n db.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) feature.schema(db);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n if (getMeta(db, 'features') === null) setMeta(db, 'features', featureSignature(cfg, FEATURES));\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n // Covers a concurrent watcher's bulk reconcile (~5s for 500 files at 26k notes). A query\n // that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db, contentTokenize(cfg) === undefined);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Before the rebuild branch below, never after: that branch deletes the cache, so a typo'd\n // tokenizer validated later would cost a full re-index (and re-embed) to reach its own error.\n const tokenize = resolveTokenize(db, cfg);\n\n // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an\n // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).\n const version = getMeta(db, 'schema_version');\n const features = getMeta(db, 'features');\n const wantFeatures = featureSignature(cfg, FEATURES);\n let tokenizeOnlyRebuild = false;\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Indexing derives from presets, so a config edit rebuilding the cache must say so and\n // name what changed -- silent rebuilds make derived indexing look like a hang or a bug.\n if (version !== null && version !== SCHEMA_VERSION) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n const changedKeys = changedSignatureKeys(features ?? '', wantFeatures);\n // Only the tokenizer moved: frontmatter, links, sections, and embeddings are file-derived\n // and tokenizer-independent, so they don't need re-deriving. Everything else -- a preset\n // edit, an embed model change -- still takes the full clear/reopen below.\n if (changedKeys.size === 1 && changedKeys.has('tokenize')) {\n console.error('sense: config change (content tokenizer) rebuilds the text index; vectors, links, and sections are kept');\n db.exec('DROP TABLE content');\n tokenizeOnlyRebuild = true;\n } else if (changedKeys.size === 1 && changedKeys.has('embed') && embedIdentityAdopted(features ?? '', wantFeatures)) {\n // First sight of a resolved weight identity: the model itself hasn't changed, so\n // adopt it into meta with no rebuild and no re-embed, mirroring the tokenize precedent.\n console.error(\"sense: recorded the embedding model's resolved identity; vectors are unaffected\");\n setMeta(db, 'features', wantFeatures);\n } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n }\n\n // Meta can lie after a crash between table creation and the signature write; the table's\n // own DDL cannot. A mismatch here rebuilds no matter what meta says. A tokenize-only rebuild\n // just dropped content above, so storedTokenize sees no table and this guard is skipped\n // naturally rather than needing its own case.\n const stored = storedTokenize(db);\n if (stored !== null && stored !== tokenize) {\n console.error('sense: cache was built with a different content tokenizer; rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n\n ensureSchema(db, cfg, tokenize);\n\n let rebuildWarnings: string[] = [];\n if (tokenizeOnlyRebuild) {\n rebuildWarnings = rebuildContentTable(db, cfg, cfg.baseDir);\n setMeta(db, 'features', wantFeatures);\n }\n\n // 3x the largest reconcile this cache has recorded, floored at 30s and capped at 10min.\n // Installed before reconcile() -- that call is the one that races a watcher's transaction.\n const recordedMaxMs = Number(getMeta(db, 'reconcile_max_ms') ?? '0');\n db.exec(`PRAGMA busy_timeout = ${Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000)}`);\n\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings: [...rebuildWarnings, ...warnings] };\n}\n\n// Deletes the cache directory, and only that. The rebuild is not this function's job: the next\n// open() reconciles, which is what running any command already does, so a verb that bundled the\n// two described the half that was not its own. Manual reset for a doubted cache; the schema and\n// config-signature mismatches below reset themselves.\nexport function clearCache(cfg: ResolvedConfig): void {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","clearCache","docCount","open","DEFAULT_TOKENIZE","resolveTokenize","db","cfg","configured","contentTokenize","undefined","literal","replace","exec","err","SenseError","message","storedTokenize","row","prepare","get","m","sql","match","ensureSchema","tokenize","activeFeatures","feature","schema","getMeta","setMeta","featureSignature","FEATURES","n","stateDir","join","baseDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","registerFunctions","version","features","wantFeatures","tokenizeOnlyRebuild","console","error","close","changedKeys","changedSignatureKeys","size","has","embedIdentityAdopted","changed","signatureDiff","stored","rebuildWarnings","rebuildContentTable","recordedMaxMs","Number","Math","min","max","reconcile","parsed","warnings","rmSync","force"],"mappings":"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"}
@@ -28,9 +28,9 @@ _export(exports, {
28
28
  var _indexts = require("../config/index.js");
29
29
  var _errorsts = require("../errors.js");
30
30
  var _indexts1 = require("../features/index.js");
31
- var _progressts = require("../progress.js");
32
- var _scants = require("../scan.js");
33
- var _segmentts = require("../segment.js");
31
+ var _progressts = require("../output/progress.js");
32
+ var _indexts2 = require("../scan/index.js");
33
+ var _segmentts = require("../text/segment.js");
34
34
  var _sharedts = require("./shared.js");
35
35
  function _array_like_to_array(arr, len) {
36
36
  if (len == null || len > arr.length) len = arr.length;
@@ -112,7 +112,7 @@ function insertContentRow(insertBody, doc, segmenting) {
112
112
  insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath, segmenting ? (0, _segmentts.segmentField)(doc.search.title) : '', segmenting ? (0, _segmentts.segmentField)(doc.search.summary) : '', segmenting ? (0, _segmentts.segmentField)(doc.search.text) : '');
113
113
  }
114
114
  function reconcile(db, cfg, baseDir) {
115
- var files = (0, _scants.listFiles)(cfg, baseDir);
115
+ var files = (0, _indexts2.listFiles)(cfg, baseDir);
116
116
  var currentSet = new Set(files.map(function(f) {
117
117
  return f.relPath;
118
118
  }));
@@ -155,7 +155,7 @@ function reconcile(db, cfg, baseDir) {
155
155
  var fileFeatures = features.filter(function(feature) {
156
156
  return !feature.enabledForFile || feature.enabledForFile(cfg, file);
157
157
  });
158
- var _parseFile = (0, _scants.parseFile)(file, fileFeatures), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
158
+ var _parseFile = (0, _indexts2.parseFile)(file, fileFeatures, cfg), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
159
159
  report.tick(++parsedCount);
160
160
  (_warnings = warnings).push.apply(_warnings, _to_consumable_array(fileWarnings));
161
161
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
@@ -208,7 +208,7 @@ function reconcile(db, cfg, baseDir) {
208
208
  // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a
209
209
  // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).
210
210
  var writableColumns = allColumns.filter(function(c) {
211
- return CORE_FRONTMATTER_COLUMNS.has(c) || !_scants.RESERVED_COLUMNS.has(c);
211
+ return CORE_FRONTMATTER_COLUMNS.has(c) || !_indexts2.RESERVED_COLUMNS.has(c);
212
212
  });
213
213
  // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --
214
214
  // content rows are coupled to that rowid below.
@@ -538,7 +538,7 @@ function rebuildContentTable(db, cfg, baseDir) {
538
538
  var known = new Set(db.prepare('SELECT "path" FROM frontmatter').all().map(function(r) {
539
539
  return r.path;
540
540
  }));
541
- var files = (0, _scants.listFiles)(cfg, baseDir).filter(function(f) {
541
+ var files = (0, _indexts2.listFiles)(cfg, baseDir).filter(function(f) {
542
542
  return known.has(f.relPath);
543
543
  });
544
544
  var segmenting = (0, _indexts.contentTokenize)(cfg) === undefined;
@@ -551,7 +551,7 @@ function rebuildContentTable(db, cfg, baseDir) {
551
551
  for(var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
552
552
  var file = _step.value;
553
553
  var _warnings;
554
- var _parseFile = (0, _scants.parseFile)(file), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
554
+ var _parseFile = (0, _indexts2.parseFile)(file), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
555
555
  (_warnings = warnings).push.apply(_warnings, _to_consumable_array(fileWarnings));
556
556
  insertContentRow(insertBody, doc, segmenting);
557
557
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/reconcile.ts"],"sourcesContent":["import type { DatabaseSync, StatementSync } from 'node:sqlite';\nimport type { Config } from '../config/index.ts';\nimport { contentTokenize } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { activeFeatures } from '../features/index.ts';\nimport type { ReconcileDelta } from '../features/types.ts';\nimport { progress } from '../progress.ts';\nimport type { ParsedDoc } from '../scan.ts';\nimport { listFiles, parseFile, RESERVED_COLUMNS } from '../scan.ts';\nimport { segmentField } from '../segment.ts';\nimport { getColumns, getMeta, quoteIdent, setMeta } from './shared.ts';\n\n// Feature-owned columns (`_rank`) must stay out of the upsert: a reparse would null the last\n// computed value on every touch, not just the reconciles that recompute it.\nconst CORE_FRONTMATTER_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_parse_error']);\n\n// SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\n// Shared by reconcile() and the tokenize-only rebuild in open(), so both prepare the same\n// literal instead of two copies drifting apart.\nconst INSERT_CONTENT_SQL = `INSERT INTO content (rowid, title, summary, text, \"path\", title_seg, summary_seg, text_seg) VALUES ((SELECT rowid FROM frontmatter WHERE \"path\" = ?), ?, ?, ?, ?, ?, ?, ?)`;\n\n// Shared by reconcile() and the tokenize-only rebuild in open(), so content population is\n// defined once. Assumes the frontmatter row for doc.relPath already exists (rowid lookup).\nfunction insertContentRow(insertBody: StatementSync, doc: ParsedDoc, segmenting: boolean): void {\n insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath, segmenting ? segmentField(doc.search.title) : '', segmenting ? segmentField(doc.search.summary) : '', segmenting ? segmentField(doc.search.text) : '');\n}\n\nexport function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const features = activeFeatures(cfg);\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n // Bulk reparses (a sync, a cold build) are the long silences a query can hit; short\n // reconciles stay silent (progress() has a threshold).\n const report = progress('reparsing files', toReparse.length);\n let parsedCount = 0;\n for (const file of toReparse) {\n // A doc only gets extract/store from features that apply to it (currently: embed, via\n // FileStat.embed -- true iff the config names an embedding model).\n const fileFeatures = features.filter((feature) => !feature.enabledForFile || feature.enabledForFile(cfg, file));\n const { doc, warnings: fileWarnings } = parseFile(file, fileFeatures);\n report.tick(++parsedCount);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n report.finish();\n\n const allColumns = [...seenColumns];\n // Fence before ALTERing: SQLite's own failure past this point is a raw\n // \"too many columns on sqlite_altertab_frontmatter\" with no indication of the boundary or the levers.\n if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${allColumns.length} columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ${MAX_FRONTMATTER_COLUMNS}; see https://www.sqlite.org/limits.html). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a\n // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).\n const writableColumns = allColumns.filter((c) => CORE_FRONTMATTER_COLUMNS.has(c) || !RESERVED_COLUMNS.has(c));\n // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --\n // content rows are coupled to that rowid below.\n const insertSql = `INSERT INTO frontmatter (${writableColumns.map(quoteIdent).join(', ')}) VALUES (${writableColumns.map(() => '?').join(', ')}) ON CONFLICT(\"path\") DO UPDATE SET ${writableColumns\n .filter((c) => c !== 'path')\n .map((c) => `${quoteIdent(c)} = excluded.${quoteIdent(c)}`)\n .join(', ')}`;\n\n const added = toReparse.filter((f) => !existing.has(f.relPath)).map((f) => f.relPath);\n const delta: ReconcileDelta = { files, reparsed: parsedDocs.map((d) => d.relPath), added, vanished };\n\n const txStart = Date.now();\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`; coupled to the\n // frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`\n // column, which a per-row DELETE would otherwise scan the whole table to find.\n const delBody = db.prepare(`DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE \"path\" = ?)`);\n const delPresetFiles = db.prepare(`DELETE FROM preset_files WHERE \"path\" = ?`);\n const insertPresetFile = db.prepare(`INSERT INTO preset_files (\"path\", preset) VALUES (?, ?)`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n // content delete must run first: it looks up the frontmatter rowid by path, which\n // the frontmatter delete below would otherwise have already removed.\n delBody.run(path);\n del.run(path);\n delPresetFiles.run(path);\n for (const feature of features) feature.remove?.(db, path, delta);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(INSERT_CONTENT_SQL);\n // A non-default tokenizer means the tree has chosen its own scheme; a phrase query over\n // grapheme runs would be nonsense against trigram, so the sidecars stay empty.\n const segmenting = contentTokenize(cfg) === undefined;\n for (const doc of parsedDocs) {\n const values = writableColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_ctime') return doc.ctimeMs;\n if (col === '_size') return doc.size;\n // Written per parse, unlike _rank, which a feature pass owns and the upsert skips.\n if (col === '_parse_error') return doc.parseError;\n return doc.data[col] ?? null;\n });\n // Frontmatter upsert first: content's rowid lookup below depends on this row existing.\n insert.run(...values);\n // Only for docs that have rows: an unconditional delete made cold crawls quadratic.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath, delta);\n }\n insertContentRow(insertBody, doc, segmenting);\n // A preset edit forces a full rebuild, so an unchanged doc's coverage is already\n // correct; new docs have nothing to clear, which keeps cold builds linear.\n if (existing.has(doc.relPath)) delPresetFiles.run(doc.relPath);\n for (const presetName of doc.presets) insertPresetFile.run(doc.relPath, presetName);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name], delta);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, delta);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n // Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):\n // keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.\n const durationMs = Date.now() - txStart;\n const prevRaw = getMeta(db, 'reconcile_max_ms');\n // -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)\n // still gets recorded instead of losing to the \"nothing recorded yet\" default.\n const prevMax = prevRaw === null ? -1 : Number(prevRaw);\n if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));\n\n return { parsed: parsedDocs.length, warnings };\n}\n\n// Segment keys that moved between two feature signatures (see config.featureSignature's\n// format: global features, embed provider, tokenize, then one segment per preset).\nexport function changedSignatureKeys(before: string, after: string): Set<string> {\n const keyOf = (part: string) => (part.startsWith('preset:') ? part.split(':').slice(0, 2).join(':') : part.split(':')[0]);\n const parse = (sig: string) => new Map(sig.split('|').map((part) => [keyOf(part), part]));\n const a = parse(before);\n const b = parse(after);\n const changed = new Set<string>();\n for (const [key, val] of b) if (a.get(key) !== val) changed.add(key);\n for (const key of a.keys()) if (!b.has(key)) changed.add(key);\n return changed;\n}\n\n// Whether the embed segment only gained its resolved weight identity: same provider and\n// model, no identity recorded before, one now -- adopted into meta without a rebuild.\nexport function embedIdentityAdopted(before: string, after: string): boolean {\n const embedPart = (sig: string) => sig.split('|').find((p) => p.startsWith('embed:'));\n const b = embedPart(before);\n const a = embedPart(after);\n if (b === undefined || a === undefined) return false;\n const at = a.indexOf('@');\n return b.indexOf('@') === -1 && at !== -1 && a.slice(0, at) === b;\n}\n\n// Names what moved, for the rebuild notice.\nexport function signatureDiff(before: string, after: string): string {\n const changed = changedSignatureKeys(before, after);\n const label = (key: string) => (key === 'embed' ? 'embed settings' : key === 'tokenize' ? 'content tokenizer' : key.startsWith('preset:') ? `preset \"${key.slice(7)}\"` : 'features');\n return changed.size === 0 ? 'features' : [...changed].map(label).join(', ');\n}\n\n// The tokenize-only rebuild in open(): content is dropped and repopulated from files already\n// listed in frontmatter, which itself is untouched. Frontmatter, links, sections, and\n// embeddings are file-derived and tokenizer-independent, so they survive. No feature extractors\n// run here -- doc.search (title/summary/text) is all content population needs. Returns\n// parseFile's per-file warnings (e.g. a bad date) so open() can surface them -- mtimes are\n// untouched, so reconcile() never reparses these files and would otherwise never emit them again.\nexport function rebuildContentTable(db: DatabaseSync, cfg: Config, baseDir: string): string[] {\n const known = new Set((db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path));\n const files = listFiles(cfg, baseDir).filter((f) => known.has(f.relPath));\n const segmenting = contentTokenize(cfg) === undefined;\n const insertBody = db.prepare(INSERT_CONTENT_SQL);\n const warnings: string[] = [];\n db.exec('BEGIN');\n try {\n for (const file of files) {\n const { doc, warnings: fileWarnings } = parseFile(file);\n warnings.push(...fileWarnings);\n insertContentRow(insertBody, doc, segmenting);\n }\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n return warnings;\n}\n"],"names":["changedSignatureKeys","embedIdentityAdopted","rebuildContentTable","reconcile","signatureDiff","CORE_FRONTMATTER_COLUMNS","Set","MAX_FRONTMATTER_COLUMNS","INSERT_CONTENT_SQL","insertContentRow","insertBody","doc","segmenting","run","relPath","search","title","summary","text","segmentField","db","cfg","baseDir","files","listFiles","currentSet","map","f","existingRows","prepare","all","existing","Map","r","path","vanished","filter","has","toReparse","row","get","_mtime","mtimeMs","_size","size","length","parsed","warnings","features","activeFeatures","seenColumns","getColumns","newColumns","parsedDocs","report","progress","parsedCount","file","fileFeatures","feature","enabledForFile","parseFile","fileWarnings","tick","push","Object","keys","data","key","add","finish","allColumns","SenseError","writableColumns","c","RESERVED_COLUMNS","insertSql","quoteIdent","join","added","delta","reparsed","d","txStart","Date","now","exec","col","delBody","delPresetFiles","insertPresetFile","del","remove","insert","contentTokenize","undefined","values","ctimeMs","parseError","presets","presetName","store","extracted","name","afterReconcile","err","durationMs","prevRaw","getMeta","prevMax","Number","setMeta","String","before","after","keyOf","part","startsWith","split","slice","parse","sig","a","b","changed","val","embedPart","find","p","at","indexOf","label","known"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/reconcile.ts"],"sourcesContent":["import type { DatabaseSync, StatementSync } from 'node:sqlite';\nimport type { Config } from '../config/index.ts';\nimport { contentTokenize } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { activeFeatures } from '../features/index.ts';\nimport type { ReconcileDelta } from '../features/types.ts';\nimport { progress } from '../output/progress.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { listFiles, parseFile, RESERVED_COLUMNS } from '../scan/index.ts';\nimport { segmentField } from '../text/segment.ts';\nimport { getColumns, getMeta, quoteIdent, setMeta } from './shared.ts';\n\n// Feature-owned columns (`_rank`) must stay out of the upsert: a reparse would null the last\n// computed value on every touch, not just the reconciles that recompute it.\nconst CORE_FRONTMATTER_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_parse_error']);\n\n// SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\n// Shared by reconcile() and the tokenize-only rebuild in open(), so both prepare the same\n// literal instead of two copies drifting apart.\nconst INSERT_CONTENT_SQL = `INSERT INTO content (rowid, title, summary, text, \"path\", title_seg, summary_seg, text_seg) VALUES ((SELECT rowid FROM frontmatter WHERE \"path\" = ?), ?, ?, ?, ?, ?, ?, ?)`;\n\n// Shared by reconcile() and the tokenize-only rebuild in open(), so content population is\n// defined once. Assumes the frontmatter row for doc.relPath already exists (rowid lookup).\nfunction insertContentRow(insertBody: StatementSync, doc: ParsedDoc, segmenting: boolean): void {\n insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath, segmenting ? segmentField(doc.search.title) : '', segmenting ? segmentField(doc.search.summary) : '', segmenting ? segmentField(doc.search.text) : '');\n}\n\nexport function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const features = activeFeatures(cfg);\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n // Bulk reparses (a sync, a cold build) are the long silences a query can hit; short\n // reconciles stay silent (progress() has a threshold).\n const report = progress('reparsing files', toReparse.length);\n let parsedCount = 0;\n for (const file of toReparse) {\n // A doc only gets extract/store from features that apply to it (currently: embed, via\n // FileStat.embed -- true iff the config names an embedding model).\n const fileFeatures = features.filter((feature) => !feature.enabledForFile || feature.enabledForFile(cfg, file));\n const { doc, warnings: fileWarnings } = parseFile(file, fileFeatures, cfg);\n report.tick(++parsedCount);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n report.finish();\n\n const allColumns = [...seenColumns];\n // Fence before ALTERing: SQLite's own failure past this point is a raw\n // \"too many columns on sqlite_altertab_frontmatter\" with no indication of the boundary or the levers.\n if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${allColumns.length} columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ${MAX_FRONTMATTER_COLUMNS}; see https://www.sqlite.org/limits.html). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a\n // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).\n const writableColumns = allColumns.filter((c) => CORE_FRONTMATTER_COLUMNS.has(c) || !RESERVED_COLUMNS.has(c));\n // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --\n // content rows are coupled to that rowid below.\n const insertSql = `INSERT INTO frontmatter (${writableColumns.map(quoteIdent).join(', ')}) VALUES (${writableColumns.map(() => '?').join(', ')}) ON CONFLICT(\"path\") DO UPDATE SET ${writableColumns\n .filter((c) => c !== 'path')\n .map((c) => `${quoteIdent(c)} = excluded.${quoteIdent(c)}`)\n .join(', ')}`;\n\n const added = toReparse.filter((f) => !existing.has(f.relPath)).map((f) => f.relPath);\n const delta: ReconcileDelta = { files, reparsed: parsedDocs.map((d) => d.relPath), added, vanished };\n\n const txStart = Date.now();\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`; coupled to the\n // frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`\n // column, which a per-row DELETE would otherwise scan the whole table to find.\n const delBody = db.prepare(`DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE \"path\" = ?)`);\n const delPresetFiles = db.prepare(`DELETE FROM preset_files WHERE \"path\" = ?`);\n const insertPresetFile = db.prepare(`INSERT INTO preset_files (\"path\", preset) VALUES (?, ?)`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n // content delete must run first: it looks up the frontmatter rowid by path, which\n // the frontmatter delete below would otherwise have already removed.\n delBody.run(path);\n del.run(path);\n delPresetFiles.run(path);\n for (const feature of features) feature.remove?.(db, path, delta);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(INSERT_CONTENT_SQL);\n // A non-default tokenizer means the tree has chosen its own scheme; a phrase query over\n // grapheme runs would be nonsense against trigram, so the sidecars stay empty.\n const segmenting = contentTokenize(cfg) === undefined;\n for (const doc of parsedDocs) {\n const values = writableColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_ctime') return doc.ctimeMs;\n if (col === '_size') return doc.size;\n // Written per parse, unlike _rank, which a feature pass owns and the upsert skips.\n if (col === '_parse_error') return doc.parseError;\n return doc.data[col] ?? null;\n });\n // Frontmatter upsert first: content's rowid lookup below depends on this row existing.\n insert.run(...values);\n // Only for docs that have rows: an unconditional delete made cold crawls quadratic.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath, delta);\n }\n insertContentRow(insertBody, doc, segmenting);\n // A preset edit forces a full rebuild, so an unchanged doc's coverage is already\n // correct; new docs have nothing to clear, which keeps cold builds linear.\n if (existing.has(doc.relPath)) delPresetFiles.run(doc.relPath);\n for (const presetName of doc.presets) insertPresetFile.run(doc.relPath, presetName);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name], delta);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, delta);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n // Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):\n // keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.\n const durationMs = Date.now() - txStart;\n const prevRaw = getMeta(db, 'reconcile_max_ms');\n // -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)\n // still gets recorded instead of losing to the \"nothing recorded yet\" default.\n const prevMax = prevRaw === null ? -1 : Number(prevRaw);\n if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));\n\n return { parsed: parsedDocs.length, warnings };\n}\n\n// Segment keys that moved between two feature signatures (see config.featureSignature's\n// format: global features, embed provider, tokenize, then one segment per preset).\nexport function changedSignatureKeys(before: string, after: string): Set<string> {\n const keyOf = (part: string) => (part.startsWith('preset:') ? part.split(':').slice(0, 2).join(':') : part.split(':')[0]);\n const parse = (sig: string) => new Map(sig.split('|').map((part) => [keyOf(part), part]));\n const a = parse(before);\n const b = parse(after);\n const changed = new Set<string>();\n for (const [key, val] of b) if (a.get(key) !== val) changed.add(key);\n for (const key of a.keys()) if (!b.has(key)) changed.add(key);\n return changed;\n}\n\n// Whether the embed segment only gained its resolved weight identity: same provider and\n// model, no identity recorded before, one now -- adopted into meta without a rebuild.\nexport function embedIdentityAdopted(before: string, after: string): boolean {\n const embedPart = (sig: string) => sig.split('|').find((p) => p.startsWith('embed:'));\n const b = embedPart(before);\n const a = embedPart(after);\n if (b === undefined || a === undefined) return false;\n const at = a.indexOf('@');\n return b.indexOf('@') === -1 && at !== -1 && a.slice(0, at) === b;\n}\n\n// Names what moved, for the rebuild notice.\nexport function signatureDiff(before: string, after: string): string {\n const changed = changedSignatureKeys(before, after);\n const label = (key: string) => (key === 'embed' ? 'embed settings' : key === 'tokenize' ? 'content tokenizer' : key.startsWith('preset:') ? `preset \"${key.slice(7)}\"` : 'features');\n return changed.size === 0 ? 'features' : [...changed].map(label).join(', ');\n}\n\n// The tokenize-only rebuild in open(): content is dropped and repopulated from files already\n// listed in frontmatter, which itself is untouched. Frontmatter, links, sections, and\n// embeddings are file-derived and tokenizer-independent, so they survive. No feature extractors\n// run here -- doc.search (title/summary/text) is all content population needs. Returns\n// parseFile's per-file warnings (e.g. a bad date) so open() can surface them -- mtimes are\n// untouched, so reconcile() never reparses these files and would otherwise never emit them again.\nexport function rebuildContentTable(db: DatabaseSync, cfg: Config, baseDir: string): string[] {\n const known = new Set((db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path));\n const files = listFiles(cfg, baseDir).filter((f) => known.has(f.relPath));\n const segmenting = contentTokenize(cfg) === undefined;\n const insertBody = db.prepare(INSERT_CONTENT_SQL);\n const warnings: string[] = [];\n db.exec('BEGIN');\n try {\n for (const file of files) {\n const { doc, warnings: fileWarnings } = parseFile(file);\n warnings.push(...fileWarnings);\n insertContentRow(insertBody, doc, segmenting);\n }\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n return warnings;\n}\n"],"names":["changedSignatureKeys","embedIdentityAdopted","rebuildContentTable","reconcile","signatureDiff","CORE_FRONTMATTER_COLUMNS","Set","MAX_FRONTMATTER_COLUMNS","INSERT_CONTENT_SQL","insertContentRow","insertBody","doc","segmenting","run","relPath","search","title","summary","text","segmentField","db","cfg","baseDir","files","listFiles","currentSet","map","f","existingRows","prepare","all","existing","Map","r","path","vanished","filter","has","toReparse","row","get","_mtime","mtimeMs","_size","size","length","parsed","warnings","features","activeFeatures","seenColumns","getColumns","newColumns","parsedDocs","report","progress","parsedCount","file","fileFeatures","feature","enabledForFile","parseFile","fileWarnings","tick","push","Object","keys","data","key","add","finish","allColumns","SenseError","writableColumns","c","RESERVED_COLUMNS","insertSql","quoteIdent","join","added","delta","reparsed","d","txStart","Date","now","exec","col","delBody","delPresetFiles","insertPresetFile","del","remove","insert","contentTokenize","undefined","values","ctimeMs","parseError","presets","presetName","store","extracted","name","afterReconcile","err","durationMs","prevRaw","getMeta","prevMax","Number","setMeta","String","before","after","keyOf","part","startsWith","split","slice","parse","sig","a","b","changed","val","embedPart","find","p","at","indexOf","label","known"],"mappings":"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"}
@@ -9,7 +9,7 @@ Object.defineProperty(exports, "registerFunctions", {
9
9
  }
10
10
  });
11
11
  var _posix = /*#__PURE__*/ _interop_require_default(require("node:path/posix"));
12
- var _segmentts = require("./segment.js");
12
+ var _segmentts = require("../text/segment.js");
13
13
  function _interop_require_default(obj) {
14
14
  return obj && obj.__esModule ? obj : {
15
15
  default: obj
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/sql-functions.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport { segmentMatch } from '../text/segment.ts';\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nexport function registerFunctions(db: DatabaseSync, segmenting: boolean): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n return String(field).includes(needle) ? 1 : 0;\n });\n\n // Unix basename(path, suffix?): the filename, minus suffix when it ends with one. SQLite\n // itself has no filename function, and the LIKE tricks that fake one also match folder names.\n db.function('basename', { deterministic: true, varargs: true }, (path: unknown, suffix?: unknown): string | null => {\n if (path === null || path === undefined) return null;\n const name = posix.basename(String(path));\n const tail = suffix === null || suffix === undefined ? '' : String(suffix);\n // POSIX: a suffix identical to the whole name is not removed (node's own basename strips it).\n return tail !== '' && tail !== name && name.endsWith(tail) ? name.slice(0, -tail.length) : name;\n });\n\n // Raw `content MATCH '<unspaced text>'` cannot be rewritten behind the author's back, and\n // matches nothing, so the same transform is available to hand-written SQL by name. `segmenting`\n // is the same config predicate commands.ts's search() gates on (contentTokenize(cfg) ===\n // undefined), passed in by open() rather than read back from persisted state.\n db.function('segment', { deterministic: true, varargs: false }, (terms: unknown): string => {\n const text = terms === null || terms === undefined ? '' : String(terms);\n return segmenting ? segmentMatch(text) : text;\n });\n}\n"],"names":["registerFunctions","db","segmenting","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","path","suffix","name","posix","basename","tail","endsWith","slice","length","terms","text","segmentMatch"],"mappings":";;;;+BAKgBA;;;eAAAA;;;4DALE;yBAEW;;;;;;AAGtB,SAASA,kBAAkBC,EAAgB,EAAEC,UAAmB;IACrED,GAAGE,QAAQ,CAAC,OAAO;QAAEC,eAAe;QAAMC,SAAS;IAAM,GAAG,SAACC,OAAgBC;QAC3E,IAAID,UAAU,QAAQA,UAAUE,WAAW,OAAO;QAElD,IAAMC,SAASC,OAAOH;QAEtB,IAAI,OAAOD,UAAU,UAAU;YAC7B,IAAIA,MAAMK,UAAU,CAAC,MAAM;gBACzB,IAAI;oBACF,IAAMC,SAASC,KAAKC,KAAK,CAACR;oBAC1B,IAAIS,MAAMC,OAAO,CAACJ,SAAS;wBACzB,OAAOA,OAAOK,IAAI,CAAC,SAACC;mCAASR,OAAOQ,UAAUT;6BAAU,IAAI;oBAC9D;gBACF,EAAE,eAAM,CAAC;YACX;YACA,OAAOH,MAAMa,QAAQ,CAACV,UAAU,IAAI;QACtC;QAEA,OAAOC,OAAOJ,OAAOa,QAAQ,CAACV,UAAU,IAAI;IAC9C;IAEA,yFAAyF;IACzF,8FAA8F;IAC9FR,GAAGE,QAAQ,CAAC,YAAY;QAAEC,eAAe;QAAMC,SAAS;IAAK,GAAG,SAACe,MAAeC;QAC9E,IAAID,SAAS,QAAQA,SAASZ,WAAW,OAAO;QAChD,IAAMc,OAAOC,cAAK,CAACC,QAAQ,CAACd,OAAOU;QACnC,IAAMK,OAAOJ,WAAW,QAAQA,WAAWb,YAAY,KAAKE,OAAOW;QACnE,8FAA8F;QAC9F,OAAOI,SAAS,MAAMA,SAASH,QAAQA,KAAKI,QAAQ,CAACD,QAAQH,KAAKK,KAAK,CAAC,GAAG,CAACF,KAAKG,MAAM,IAAIN;IAC7F;IAEA,0FAA0F;IAC1F,gGAAgG;IAChG,yFAAyF;IACzF,8EAA8E;IAC9ErB,GAAGE,QAAQ,CAAC,WAAW;QAAEC,eAAe;QAAMC,SAAS;IAAM,GAAG,SAACwB;QAC/D,IAAMC,OAAOD,UAAU,QAAQA,UAAUrB,YAAY,KAAKE,OAAOmB;QACjE,OAAO3B,aAAa6B,IAAAA,uBAAY,EAACD,QAAQA;IAC3C;AACF"}