sensemaking 0.4.0 → 0.6.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 (93) hide show
  1. package/README.md +16 -7
  2. package/dist/cjs/cli.js +5 -1
  3. package/dist/cjs/cli.js.map +1 -1
  4. package/dist/cjs/commands/find.js +173 -10
  5. package/dist/cjs/commands/find.js.map +1 -1
  6. package/dist/cjs/commands/map.js +1 -1
  7. package/dist/cjs/commands/map.js.map +1 -1
  8. package/dist/cjs/commands/peek.js +1 -1
  9. package/dist/cjs/commands/peek.js.map +1 -1
  10. package/dist/cjs/commands/shared.d.cts +1 -1
  11. package/dist/cjs/commands/shared.d.ts +1 -1
  12. package/dist/cjs/commands/shared.js +166 -8
  13. package/dist/cjs/commands/shared.js.map +1 -1
  14. package/dist/cjs/commands/status.js +10 -1
  15. package/dist/cjs/commands/status.js.map +1 -1
  16. package/dist/cjs/commands/types.d.cts +1 -0
  17. package/dist/cjs/commands/types.d.ts +1 -0
  18. package/dist/cjs/config.d.cts +26 -2
  19. package/dist/cjs/config.d.ts +26 -2
  20. package/dist/cjs/config.js +171 -10
  21. package/dist/cjs/config.js.map +1 -1
  22. package/dist/cjs/db.js +46 -16
  23. package/dist/cjs/db.js.map +1 -1
  24. package/dist/cjs/errors.d.cts +1 -1
  25. package/dist/cjs/errors.d.ts +1 -1
  26. package/dist/cjs/errors.js.map +1 -1
  27. package/dist/cjs/features/embed.d.cts +14 -0
  28. package/dist/cjs/features/embed.d.ts +14 -0
  29. package/dist/cjs/features/embed.js +840 -0
  30. package/dist/cjs/features/embed.js.map +1 -0
  31. package/dist/cjs/features/index.js +3 -1
  32. package/dist/cjs/features/index.js.map +1 -1
  33. package/dist/cjs/features/types.d.cts +4 -1
  34. package/dist/cjs/features/types.d.ts +4 -1
  35. package/dist/cjs/index.d.cts +3 -10
  36. package/dist/cjs/index.d.ts +3 -10
  37. package/dist/cjs/index.js +2 -52
  38. package/dist/cjs/index.js.map +1 -1
  39. package/dist/cjs/output.d.cts +5 -0
  40. package/dist/cjs/output.d.ts +5 -0
  41. package/dist/cjs/output.js +14 -1
  42. package/dist/cjs/output.js.map +1 -1
  43. package/dist/cjs/scan.js +9 -8
  44. package/dist/cjs/scan.js.map +1 -1
  45. package/dist/cjs/verbs.d.cts +8 -2
  46. package/dist/cjs/verbs.d.ts +8 -2
  47. package/dist/cjs/verbs.js +263 -76
  48. package/dist/cjs/verbs.js.map +1 -1
  49. package/dist/esm/cli.js +5 -1
  50. package/dist/esm/cli.js.map +1 -1
  51. package/dist/esm/commands/find.js +5 -4
  52. package/dist/esm/commands/find.js.map +1 -1
  53. package/dist/esm/commands/map.js +1 -3
  54. package/dist/esm/commands/map.js.map +1 -1
  55. package/dist/esm/commands/peek.js +1 -1
  56. package/dist/esm/commands/peek.js.map +1 -1
  57. package/dist/esm/commands/shared.d.ts +1 -1
  58. package/dist/esm/commands/shared.js +2 -2
  59. package/dist/esm/commands/shared.js.map +1 -1
  60. package/dist/esm/commands/status.js +8 -1
  61. package/dist/esm/commands/status.js.map +1 -1
  62. package/dist/esm/commands/types.d.ts +1 -0
  63. package/dist/esm/commands/types.js.map +1 -1
  64. package/dist/esm/config.d.ts +26 -2
  65. package/dist/esm/config.js +88 -10
  66. package/dist/esm/config.js.map +1 -1
  67. package/dist/esm/db.js +19 -10
  68. package/dist/esm/db.js.map +1 -1
  69. package/dist/esm/errors.d.ts +1 -1
  70. package/dist/esm/errors.js.map +1 -1
  71. package/dist/esm/features/embed.d.ts +14 -0
  72. package/dist/esm/features/embed.js +290 -0
  73. package/dist/esm/features/embed.js.map +1 -0
  74. package/dist/esm/features/index.js +3 -1
  75. package/dist/esm/features/index.js.map +1 -1
  76. package/dist/esm/features/types.d.ts +4 -1
  77. package/dist/esm/features/types.js.map +1 -1
  78. package/dist/esm/index.d.ts +3 -10
  79. package/dist/esm/index.js +4 -6
  80. package/dist/esm/index.js.map +1 -1
  81. package/dist/esm/output.d.ts +5 -0
  82. package/dist/esm/output.js +11 -1
  83. package/dist/esm/output.js.map +1 -1
  84. package/dist/esm/scan.js +9 -8
  85. package/dist/esm/scan.js.map +1 -1
  86. package/dist/esm/verbs.d.ts +8 -2
  87. package/dist/esm/verbs.js +51 -13
  88. package/dist/esm/verbs.js.map +1 -1
  89. package/package.json +2 -1
  90. package/schema.json +17 -1
  91. package/skills/sense/EXAMPLES.md +16 -0
  92. package/skills/sense/SKILL.md +41 -19
  93. package/skills/sense-setup/SKILL.md +73 -0
@@ -8,6 +8,7 @@ export interface Ctx {
8
8
  where?: string;
9
9
  k?: string;
10
10
  force: boolean;
11
+ semantic: boolean;
11
12
  };
12
13
  resolveConfig(): ResolvedConfig;
13
14
  usageError(message: string): never;
@@ -8,6 +8,7 @@ export interface Ctx {
8
8
  where?: string;
9
9
  k?: string;
10
10
  force: boolean;
11
+ semantic: boolean;
11
12
  };
12
13
  resolveConfig(): ResolvedConfig;
13
14
  usageError(message: string): never;
@@ -1,15 +1,27 @@
1
1
  export declare const CONFIG_FILENAME = "sense.config.json";
2
2
  export declare const STATE_DIR = ".sense";
3
3
  export declare const SUPPORTED_CONFIG_VERSION = 2;
4
- export declare const FEATURE_NAMES: readonly ["links", "sections", "rank"];
4
+ declare const FEATURE_NAMES: readonly ["links", "sections", "rank", "embed"];
5
5
  export type FeatureName = (typeof FEATURE_NAMES)[number];
6
+ export interface EmbedConfig {
7
+ model?: string;
8
+ type?: 'static' | 'api';
9
+ url?: string;
10
+ key?: string;
11
+ }
12
+ export declare const DEFAULT_EMBED_MODEL = "minishlab/potion-retrieval-32M";
6
13
  export interface Config {
7
14
  $schema?: string;
8
15
  version?: number;
9
16
  scan: {
10
17
  include: string[];
11
18
  };
12
- features?: Partial<Record<FeatureName, boolean>>;
19
+ features?: {
20
+ links?: boolean;
21
+ sections?: boolean;
22
+ rank?: boolean;
23
+ embed?: boolean | EmbedConfig;
24
+ };
13
25
  queries: Record<string, string>;
14
26
  }
15
27
  export interface ResolvedConfig extends Config {
@@ -19,6 +31,17 @@ export interface ResolvedConfig extends Config {
19
31
  }
20
32
  export declare function featureEnabled(cfg: Config, name: FeatureName): boolean;
21
33
  export declare function enabledFeatures(cfg: Config): FeatureName[];
34
+ export declare function featureStates(cfg: Config): {
35
+ on: FeatureName[];
36
+ off: FeatureName[];
37
+ };
38
+ export declare function embedConfig(cfg: Config): {
39
+ model: string;
40
+ type: 'static' | 'api';
41
+ url?: string;
42
+ key?: string;
43
+ } | null;
44
+ export declare function featureSignature(cfg: Config): string;
22
45
  export declare function migrateConfig(cfg: Config): {
23
46
  cfg: Config;
24
47
  from: number;
@@ -26,3 +49,4 @@ export declare function migrateConfig(cfg: Config): {
26
49
  export declare function initConfig(dir: string): string;
27
50
  export declare function findConfigPath(startDir: string): string | null;
28
51
  export declare function loadConfig(explicitPath?: string): ResolvedConfig;
52
+ export {};
@@ -1,15 +1,27 @@
1
1
  export declare const CONFIG_FILENAME = "sense.config.json";
2
2
  export declare const STATE_DIR = ".sense";
3
3
  export declare const SUPPORTED_CONFIG_VERSION = 2;
4
- export declare const FEATURE_NAMES: readonly ["links", "sections", "rank"];
4
+ declare const FEATURE_NAMES: readonly ["links", "sections", "rank", "embed"];
5
5
  export type FeatureName = (typeof FEATURE_NAMES)[number];
6
+ export interface EmbedConfig {
7
+ model?: string;
8
+ type?: 'static' | 'api';
9
+ url?: string;
10
+ key?: string;
11
+ }
12
+ export declare const DEFAULT_EMBED_MODEL = "minishlab/potion-retrieval-32M";
6
13
  export interface Config {
7
14
  $schema?: string;
8
15
  version?: number;
9
16
  scan: {
10
17
  include: string[];
11
18
  };
12
- features?: Partial<Record<FeatureName, boolean>>;
19
+ features?: {
20
+ links?: boolean;
21
+ sections?: boolean;
22
+ rank?: boolean;
23
+ embed?: boolean | EmbedConfig;
24
+ };
13
25
  queries: Record<string, string>;
14
26
  }
15
27
  export interface ResolvedConfig extends Config {
@@ -19,6 +31,17 @@ export interface ResolvedConfig extends Config {
19
31
  }
20
32
  export declare function featureEnabled(cfg: Config, name: FeatureName): boolean;
21
33
  export declare function enabledFeatures(cfg: Config): FeatureName[];
34
+ export declare function featureStates(cfg: Config): {
35
+ on: FeatureName[];
36
+ off: FeatureName[];
37
+ };
38
+ export declare function embedConfig(cfg: Config): {
39
+ model: string;
40
+ type: 'static' | 'api';
41
+ url?: string;
42
+ key?: string;
43
+ } | null;
44
+ export declare function featureSignature(cfg: Config): string;
22
45
  export declare function migrateConfig(cfg: Config): {
23
46
  cfg: Config;
24
47
  from: number;
@@ -26,3 +49,4 @@ export declare function migrateConfig(cfg: Config): {
26
49
  export declare function initConfig(dir: string): string;
27
50
  export declare function findConfigPath(startDir: string): string | null;
28
51
  export declare function loadConfig(explicitPath?: string): ResolvedConfig;
52
+ export {};
@@ -12,8 +12,8 @@ _export(exports, {
12
12
  get CONFIG_FILENAME () {
13
13
  return CONFIG_FILENAME;
14
14
  },
15
- get FEATURE_NAMES () {
16
- return FEATURE_NAMES;
15
+ get DEFAULT_EMBED_MODEL () {
16
+ return DEFAULT_EMBED_MODEL;
17
17
  },
18
18
  get STATE_DIR () {
19
19
  return STATE_DIR;
@@ -21,12 +21,21 @@ _export(exports, {
21
21
  get SUPPORTED_CONFIG_VERSION () {
22
22
  return SUPPORTED_CONFIG_VERSION;
23
23
  },
24
+ get embedConfig () {
25
+ return embedConfig;
26
+ },
24
27
  get enabledFeatures () {
25
28
  return enabledFeatures;
26
29
  },
27
30
  get featureEnabled () {
28
31
  return featureEnabled;
29
32
  },
33
+ get featureSignature () {
34
+ return featureSignature;
35
+ },
36
+ get featureStates () {
37
+ return featureStates;
38
+ },
30
39
  get findConfigPath () {
31
40
  return findConfigPath;
32
41
  },
@@ -43,6 +52,14 @@ _export(exports, {
43
52
  var _nodefs = require("node:fs");
44
53
  var _nodepath = require("node:path");
45
54
  var _errorsts = require("./errors.js");
55
+ function _array_like_to_array(arr, len) {
56
+ if (len == null || len > arr.length) len = arr.length;
57
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
58
+ return arr2;
59
+ }
60
+ function _array_with_holes(arr) {
61
+ if (Array.isArray(arr)) return arr;
62
+ }
46
63
  function _define_property(obj, key, value) {
47
64
  if (key in obj) {
48
65
  Object.defineProperty(obj, key, {
@@ -56,6 +73,33 @@ function _define_property(obj, key, value) {
56
73
  }
57
74
  return obj;
58
75
  }
76
+ function _iterable_to_array_limit(arr, i) {
77
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
78
+ if (_i == null) return;
79
+ var _arr = [];
80
+ var _n = true;
81
+ var _d = false;
82
+ var _s, _e;
83
+ try {
84
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
85
+ _arr.push(_s.value);
86
+ if (i && _arr.length === i) break;
87
+ }
88
+ } catch (err) {
89
+ _d = true;
90
+ _e = err;
91
+ } finally{
92
+ try {
93
+ if (!_n && _i["return"] != null) _i["return"]();
94
+ } finally{
95
+ if (_d) throw _e;
96
+ }
97
+ }
98
+ return _arr;
99
+ }
100
+ function _non_iterable_rest() {
101
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
102
+ }
59
103
  function _object_spread(target) {
60
104
  for(var i = 1; i < arguments.length; i++){
61
105
  var source = arguments[i] != null ? arguments[i] : {};
@@ -95,17 +139,42 @@ function _object_spread_props(target, source) {
95
139
  }
96
140
  return target;
97
141
  }
142
+ function _sliced_to_array(arr, i) {
143
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
144
+ }
145
+ function _type_of(obj) {
146
+ "@swc/helpers - typeof";
147
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
148
+ }
149
+ function _unsupported_iterable_to_array(o, minLen) {
150
+ if (!o) return;
151
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
152
+ var n = Object.prototype.toString.call(o).slice(8, -1);
153
+ if (n === "Object" && o.constructor) n = o.constructor.name;
154
+ if (n === "Map" || n === "Set") return Array.from(n);
155
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
156
+ }
98
157
  var CONFIG_FILENAME = 'sense.config.json';
99
158
  var STATE_DIR = '.sense';
100
159
  var SUPPORTED_CONFIG_VERSION = 2;
160
+ // Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.
161
+ // links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.
101
162
  var FEATURE_NAMES = [
163
+ 'links',
164
+ 'sections',
165
+ 'rank',
166
+ 'embed'
167
+ ];
168
+ var OPT_OUT_NAMES = [
102
169
  'links',
103
170
  'sections',
104
171
  'rank'
105
172
  ];
173
+ var DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';
106
174
  function featureEnabled(cfg, name) {
107
- var _cfg_features;
108
- var enabled = ((_cfg_features = cfg.features) === null || _cfg_features === void 0 ? void 0 : _cfg_features[name]) !== false;
175
+ var _cfg_features, _cfg_features1;
176
+ if (name === 'embed') return Boolean((_cfg_features = cfg.features) === null || _cfg_features === void 0 ? void 0 : _cfg_features.embed);
177
+ var enabled = ((_cfg_features1 = cfg.features) === null || _cfg_features1 === void 0 ? void 0 : _cfg_features1[name]) !== false;
109
178
  if (name === 'rank') return enabled && featureEnabled(cfg, 'links');
110
179
  return enabled;
111
180
  }
@@ -114,13 +183,44 @@ function enabledFeatures(cfg) {
114
183
  return featureEnabled(cfg, name);
115
184
  });
116
185
  }
186
+ function featureStates(cfg) {
187
+ return {
188
+ on: FEATURE_NAMES.filter(function(name) {
189
+ return featureEnabled(cfg, name);
190
+ }),
191
+ off: FEATURE_NAMES.filter(function(name) {
192
+ return !featureEnabled(cfg, name);
193
+ })
194
+ };
195
+ }
196
+ function embedConfig(cfg) {
197
+ var _o_model, _o_type;
198
+ var _cfg_features;
199
+ var e = (_cfg_features = cfg.features) === null || _cfg_features === void 0 ? void 0 : _cfg_features.embed;
200
+ if (!e) return null;
201
+ var o = e === true ? {} : e;
202
+ return {
203
+ model: (_o_model = o.model) !== null && _o_model !== void 0 ? _o_model : DEFAULT_EMBED_MODEL,
204
+ type: (_o_type = o.type) !== null && _o_type !== void 0 ? _o_type : 'static',
205
+ url: o.url,
206
+ key: o.key
207
+ };
208
+ }
209
+ function featureSignature(cfg) {
210
+ return enabledFeatures(cfg).map(function(name) {
211
+ if (name !== 'embed') return name;
212
+ var e = embedConfig(cfg);
213
+ return "embed:".concat(e === null || e === void 0 ? void 0 : e.type, ":").concat(e === null || e === void 0 ? void 0 : e.model);
214
+ }).join(',');
215
+ }
117
216
  // Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.
118
217
  var MIGRATIONS = {
119
- // v1 -> v2: features block introduced, everything enabled (matches the old implicit behavior of `links` etc. not existing).
218
+ // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit
219
+ // behavior of `links` etc. not existing). embed stays absent -- opt-in.
120
220
  1: function(cfg) {
121
221
  return _object_spread_props(_object_spread({}, cfg), {
122
222
  version: 2,
123
- features: Object.fromEntries(FEATURE_NAMES.map(function(name) {
223
+ features: Object.fromEntries(OPT_OUT_NAMES.map(function(name) {
124
224
  return [
125
225
  name,
126
226
  true
@@ -152,7 +252,7 @@ function starterConfig() {
152
252
  '**/*.md'
153
253
  ]
154
254
  },
155
- features: Object.fromEntries(FEATURE_NAMES.map(function(name) {
255
+ features: Object.fromEntries(OPT_OUT_NAMES.map(function(name) {
156
256
  return [
157
257
  name,
158
258
  true
@@ -179,8 +279,66 @@ function findConfigPath(startDir) {
179
279
  dir = parent;
180
280
  }
181
281
  }
282
+ // Shape check for hand-edited files: a typo'd config fails with a named error, not a
283
+ // TypeError from whatever code touched the missing field first. `queries` is optional
284
+ // on disk (absent = none); `scan.include` has no usable default.
285
+ function validateConfig(parsed, configPath) {
286
+ var _cfg_features;
287
+ if ((typeof parsed === "undefined" ? "undefined" : _type_of(parsed)) !== 'object' || parsed === null || Array.isArray(parsed)) {
288
+ throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": config must be a JSON object"));
289
+ }
290
+ var cfg = parsed;
291
+ var scan = cfg.scan;
292
+ if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every(function(g) {
293
+ return typeof g === 'string';
294
+ })) {
295
+ throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": scan.include must be a non-empty array of glob strings"));
296
+ }
297
+ if (cfg.queries === undefined) cfg.queries = {};
298
+ if (_type_of(cfg.queries) !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every(function(q) {
299
+ return typeof q === 'string';
300
+ })) {
301
+ throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries must be an object of name -> SQL string"));
302
+ }
303
+ if (cfg.features !== undefined && (_type_of(cfg.features) !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {
304
+ throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": features must be an object of name -> boolean"));
305
+ }
306
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
307
+ try {
308
+ var _loop = function() {
309
+ var _step_value = _sliced_to_array(_step.value, 2), name = _step_value[0], value = _step_value[1];
310
+ if (typeof value === 'boolean') return "continue";
311
+ // embed alone takes an object form: { model?, type?: static|api, url?, key? }.
312
+ var embed = value;
313
+ var shapeOk = name === 'embed' && (typeof value === "undefined" ? "undefined" : _type_of(value)) === 'object' && value !== null && !Array.isArray(value) && [
314
+ 'model',
315
+ 'url',
316
+ 'key'
317
+ ].every(function(f) {
318
+ return embed[f] === undefined || typeof embed[f] === 'string';
319
+ }) && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');
320
+ if (!shapeOk) {
321
+ throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": features.").concat(name, " must be a boolean").concat(name === 'embed' ? ' or { model?, type?: "static"|"api", url?, key? }' : ''));
322
+ }
323
+ };
324
+ for(var _iterator = Object.entries((_cfg_features = cfg.features) !== null && _cfg_features !== void 0 ? _cfg_features : {})[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
325
+ } catch (err) {
326
+ _didIteratorError = true;
327
+ _iteratorError = err;
328
+ } finally{
329
+ try {
330
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
331
+ _iterator.return();
332
+ }
333
+ } finally{
334
+ if (_didIteratorError) {
335
+ throw _iteratorError;
336
+ }
337
+ }
338
+ }
339
+ return cfg;
340
+ }
182
341
  function loadConfig(explicitPath) {
183
- var _cfg_version;
184
342
  var configPath;
185
343
  if (explicitPath) {
186
344
  configPath = (0, _nodepath.resolve)(process.cwd(), explicitPath);
@@ -193,11 +351,14 @@ function loadConfig(explicitPath) {
193
351
  configPath = found;
194
352
  }
195
353
  var raw = (0, _nodefs.readFileSync)(configPath, 'utf8');
196
- var cfg = JSON.parse(raw);
197
- var version = (_cfg_version = cfg.version) !== null && _cfg_version !== void 0 ? _cfg_version : 1;
354
+ var parsed = JSON.parse(raw);
355
+ // Version gate before shape validation: a config written by a newer sense should fail
356
+ // with "requires a newer sense", not with shape errors its own version may not have.
357
+ var version = (typeof parsed === "undefined" ? "undefined" : _type_of(parsed)) === 'object' && parsed !== null && typeof parsed.version === 'number' ? parsed.version : 1;
198
358
  if (version > SUPPORTED_CONFIG_VERSION) {
199
359
  throw new _errorsts.SenseError('CONFIG_VERSION_UNSUPPORTED', "config version ".concat(version, " requires a newer sense"));
200
360
  }
361
+ var cfg = validateConfig(parsed, configPath);
201
362
  var migratedFrom;
202
363
  if (version < SUPPORTED_CONFIG_VERSION) {
203
364
  var result = migrateConfig(cfg);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.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 = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n let cfg = JSON.parse(raw) as Config;\n\n const version = cfg.version ?? 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["CONFIG_FILENAME","FEATURE_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION","enabledFeatures","featureEnabled","findConfigPath","initConfig","loadConfig","migrateConfig","cfg","name","enabled","features","filter","MIGRATIONS","version","Object","fromEntries","map","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","join","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","baseDir"],"mappings":";;;;;;;;;;;QAIaA;eAAAA;;QAOAC;eAAAA;;QANAC;eAAAA;;QAGAC;eAAAA;;QA6BGC;eAAAA;;QANAC;eAAAA;;QA+CAC;eAAAA;;QATAC;eAAAA;;QAoBAC;eAAAA;;QA1CAC;eAAAA;;;sBA/CwC;wBACjB;wBACZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEpB,IAAMT,kBAAkB;AACxB,IAAME,YAAY;AAGlB,IAAMC,2BAA2B;AAGjC,IAAMF,gBAAgB;IAAC;IAAS;IAAY;CAAO;AAoBnD,SAASI,eAAeK,GAAW,EAAEC,IAAiB;QAC3CD;IAAhB,IAAME,UAAUF,EAAAA,gBAAAA,IAAIG,QAAQ,cAAZH,oCAAAA,aAAc,CAACC,KAAK,MAAK;IACzC,IAAIA,SAAS,QAAQ,OAAOC,WAAWP,eAAeK,KAAK;IAC3D,OAAOE;AACT;AAEO,SAASR,gBAAgBM,GAAW;IACzC,OAAOT,cAAca,MAAM,CAAC,SAACH;eAASN,eAAeK,KAAKC;;AAC5D;AAEA,yGAAyG;AACzG,IAAMI,aAAsD;IAC1D,4HAA4H;IAC5H,GAAG,SAACL;eAAS,wCAAKA;YAAKM,SAAS;YAAGH,UAAUI,OAAOC,WAAW,CAACjB,cAAckB,GAAG,CAAC,SAACR;uBAAS;oBAACA;oBAAM;iBAAK;;;;AAC1G;AAEO,SAASF,cAAcC,GAAW;QAC1BA;IAAb,IAAMU,QAAOV,eAAAA,IAAIM,OAAO,cAAXN,0BAAAA,eAAe;IAC5B,IAAIW,UAAUX;IACd,IAAK,IAAIY,IAAIF,MAAME,IAAInB,0BAA0BmB,IAAK;QACpD,IAAMC,OAAOR,UAAU,CAACO,EAAE;QAC1B,IAAI,CAACC,MAAM,MAAM,IAAIC,oBAAU,CAAC,8BAA8B,AAAC,oCAAqC,OAAFF;QAClGD,UAAUE,KAAKF;IACjB;IACA,OAAO;QAAEX,KAAKW;QAASD,MAAAA;IAAK;AAC9B;AAEA,SAASK;IACP,OAAO;QACLC,SAAS;QACTV,SAASb;QACTwB,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7Bf,UAAUI,OAAOC,WAAW,CAACjB,cAAckB,GAAG,CAAC,SAACR;mBAAS;gBAACA;gBAAM;aAAK;;QACrEkB,SAAS,CAAC;IACZ;AACF;AAGO,SAAStB,WAAWuB,GAAW;IACpC,IAAMC,aAAaC,IAAAA,cAAI,EAACF,KAAK9B;IAC7B,IAAIiC,IAAAA,kBAAU,EAACF,aAAa;QAC1B,MAAM,IAAIP,oBAAU,CAAC,iBAAiB,AAAC,GAAuCM,OAArC9B,iBAAgB,uBAAyB,OAAJ8B;IAChF;IACAI,IAAAA,qBAAa,EAACH,YAAY,AAAC,GAA2C,OAAzCI,KAAKC,SAAS,CAACX,iBAAiB,MAAM,IAAG;IACtE,OAAOM;AACT;AAEO,SAASzB,eAAe+B,QAAgB;IAC7C,IAAIP,MAAMQ,IAAAA,iBAAO,EAACD;IAClB,OAAS;QACP,IAAME,YAAYP,IAAAA,cAAI,EAACF,KAAK9B;QAC5B,IAAIiC,IAAAA,kBAAU,EAACM,YAAY,OAAOA;QAClC,IAAMC,SAASC,IAAAA,iBAAO,EAACX;QACvB,IAAIU,WAAWV,KAAK,OAAO;QAC3BA,MAAMU;IACR;AACF;AAEO,SAAShC,WAAWkC,YAAqB;QAgB9BhC;IAfhB,IAAIqB;IACJ,IAAIW,cAAc;QAChBX,aAAaO,IAAAA,iBAAO,EAACK,QAAQC,GAAG,IAAIF;QACpC,IAAI,CAACT,IAAAA,kBAAU,EAACF,aAAa,MAAM,IAAIP,oBAAU,CAAC,oBAAoB,AAAC,qBAA+B,OAAXO;IAC7F,OAAO;QACL,IAAMc,QAAQvC,eAAeqC,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAIrB,oBAAU,CAAC,oBAAoB,AAAC,kBAAuCmB,OAAtB3C,iBAAgB,QAAoB,OAAd2C,QAAQC,GAAG,IAAG;QACjG;QACAb,aAAac;IACf;IAEA,IAAMC,MAAMC,IAAAA,oBAAY,EAAChB,YAAY;IACrC,IAAIrB,MAAMyB,KAAKa,KAAK,CAACF;IAErB,IAAM9B,WAAUN,eAAAA,IAAIM,OAAO,cAAXN,0BAAAA,eAAe;IAC/B,IAAIM,UAAUb,0BAA0B;QACtC,MAAM,IAAIqB,oBAAU,CAAC,8BAA8B,AAAC,kBAAyB,OAARR,SAAQ;IAC/E;IAEA,IAAIiC;IACJ,IAAIjC,UAAUb,0BAA0B;QACtC,IAAM+C,SAASzC,cAAcC;QAC7BA,MAAMwC,OAAOxC,GAAG;QAChBuC,eAAeC,OAAO9B,IAAI;QAC1Bc,IAAAA,qBAAa,EAACH,YAAY,AAAC,GAA+B,OAA7BI,KAAKC,SAAS,CAAC1B,KAAK,MAAM,IAAG;IAC5D;IAEA,OAAO,wCAAKA;QAAKyC,SAASV,IAAAA,iBAAO,EAACV;QAAaA,YAAAA;QAAYkB,cAAAA;;AAC7D"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.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 = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; verbs surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\nfunction 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 const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","STATE_DIR","SUPPORTED_CONFIG_VERSION","embedConfig","enabledFeatures","featureEnabled","featureSignature","featureStates","findConfigPath","initConfig","loadConfig","migrateConfig","FEATURE_NAMES","OPT_OUT_NAMES","cfg","name","Boolean","features","embed","enabled","filter","on","off","o","e","model","type","url","key","map","join","MIGRATIONS","version","Object","fromEntries","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","validateConfig","parsed","Array","isArray","length","every","g","undefined","values","q","value","shapeOk","f","entries","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","baseDir"],"mappings":"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"}
package/dist/cjs/db.js CHANGED
@@ -126,7 +126,7 @@ function ensureSchema(db, cfg) {
126
126
  }
127
127
  }
128
128
  if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);
129
- if (getMeta(db, 'features') === null) setMeta(db, 'features', (0, _configts.enabledFeatures)(cfg).join(','));
129
+ if (getMeta(db, 'features') === null) setMeta(db, 'features', (0, _configts.featureSignature)(cfg));
130
130
  }
131
131
  function getMeta(db, key) {
132
132
  var row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key);
@@ -301,6 +301,7 @@ function reconcile(db, cfg, baseDir) {
301
301
  var _loop = function() {
302
302
  var doc = _step5.value;
303
303
  var _insert;
304
+ var _feature_store;
304
305
  var values = allColumns.map(function(col) {
305
306
  var _doc_data_col;
306
307
  if (col === 'path') return doc.relPath;
@@ -309,27 +310,52 @@ function reconcile(db, cfg, baseDir) {
309
310
  return (_doc_data_col = doc.data[col]) !== null && _doc_data_col !== void 0 ? _doc_data_col : null;
310
311
  });
311
312
  (_insert = insert).run.apply(_insert, _to_consumable_array(values));
312
- delBody.run(doc.relPath);
313
+ // Delete-before-insert only for docs that have rows: an FTS5 DELETE by column
314
+ // cannot use an index and scans the whole table, so running it per doc on a
315
+ // cold build (empty table, nothing to delete) made the crawl quadratic --
316
+ // measured 4x time per note-count doubling at 13k/26k notes.
317
+ if (existing.has(doc.relPath)) {
318
+ var _feature_remove;
319
+ delBody.run(doc.relPath);
320
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
321
+ try {
322
+ for(var _iterator = features[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
323
+ var feature = _step.value;
324
+ (_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, doc.relPath);
325
+ }
326
+ } catch (err) {
327
+ _didIteratorError = true;
328
+ _iteratorError = err;
329
+ } finally{
330
+ try {
331
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
332
+ _iterator.return();
333
+ }
334
+ } finally{
335
+ if (_didIteratorError) {
336
+ throw _iteratorError;
337
+ }
338
+ }
339
+ }
340
+ }
313
341
  insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
314
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
342
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
315
343
  try {
316
- for(var _iterator = features[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
317
- var feature = _step.value;
318
- var _feature_remove, _feature_store;
319
- (_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, doc.relPath);
320
- (_feature_store = feature.store) === null || _feature_store === void 0 ? void 0 : _feature_store.call(feature, db, doc.relPath, doc.extracted[feature.name]);
344
+ for(var _iterator1 = features[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
345
+ var feature1 = _step1.value;
346
+ (_feature_store = feature1.store) === null || _feature_store === void 0 ? void 0 : _feature_store.call(feature1, db, doc.relPath, doc.extracted[feature1.name]);
321
347
  }
322
348
  } catch (err) {
323
- _didIteratorError = true;
324
- _iteratorError = err;
349
+ _didIteratorError1 = true;
350
+ _iteratorError1 = err;
325
351
  } finally{
326
352
  try {
327
- if (!_iteratorNormalCompletion && _iterator.return != null) {
328
- _iterator.return();
353
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
354
+ _iterator1.return();
329
355
  }
330
356
  } finally{
331
- if (_didIteratorError) {
332
- throw _iteratorError;
357
+ if (_didIteratorError1) {
358
+ throw _iteratorError1;
333
359
  }
334
360
  }
335
361
  }
@@ -388,14 +414,18 @@ function open(cfg) {
388
414
  var dbPath = (0, _nodepath.join)(stateDir, DB_FILENAME);
389
415
  var db = new _nodesqlite.DatabaseSync(dbPath);
390
416
  db.exec('PRAGMA journal_mode = WAL');
391
- db.exec('PRAGMA busy_timeout = 5000');
417
+ // Covers a concurrent watcher's bulk reconcile: the write transaction for 500 changed
418
+ // files measures ~5s at 26k notes, so 5s expired exactly at the boundary and queries
419
+ // racing the watcher got SQLITE_BUSY. 30s bounds the wait at ~3x the largest measured
420
+ // reconcile; a query that outwaits it still fails loudly.
421
+ db.exec('PRAGMA busy_timeout = 30000');
392
422
  registerFunctions(db);
393
423
  db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');
394
424
  // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an
395
425
  // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).
396
426
  var version = getMeta(db, 'schema_version');
397
427
  var features = getMeta(db, 'features');
398
- var wantFeatures = (0, _configts.enabledFeatures)(cfg).join(',');
428
+ var wantFeatures = (0, _configts.featureSignature)(cfg);
399
429
  if (version !== null && version !== SCHEMA_VERSION || features !== null && features !== wantFeatures) {
400
430
  db.close();
401
431
  (0, _nodefs.rmSync)(stateDir, {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.ts"],"sourcesContent":["import { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from './config.ts';\nimport { enabledFeatures, STATE_DIR } from './config.ts';\nimport { activeFeatures } from './features/index.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile } from './scan.ts';\n\n// Rows -> SQLite: core schema, reconcile loop, has(). Parsing lives in scan.ts;\n// everything beyond frontmatter + content lives in src/features/.\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`.\nexport const SCHEMA_VERSION = '3';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): 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\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\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): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\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', enabledFeatures(cfg).join(','));\n}\n\nexport function getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\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 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 for (const file of toReparse) {\n const { doc, warnings: fileWarnings } = parseFile(file, features);\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\n const allColumns = [...seenColumns];\n const insertSql = `INSERT OR REPLACE INTO frontmatter (${allColumns.map(quoteIdent).join(', ')}) VALUES (${allColumns.map(() => '?').join(', ')})`;\n\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`.\n const delBody = db.prepare(`DELETE FROM content WHERE \"path\" = ?`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n del.run(path);\n delBody.run(path);\n for (const feature of features) feature.remove?.(db, path);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (title, summary, text, \"path\") VALUES (?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = allColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n insert.run(...values);\n delBody.run(doc.relPath);\n insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n for (const feature of features) {\n feature.remove?.(db, doc.relPath);\n feature.store?.(db, doc.relPath, doc.extracted[feature.name]);\n }\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, files);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n return { parsed: parsedDocs.length, warnings };\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 db.exec('PRAGMA busy_timeout = 5000');\n registerFunctions(db);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\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 = enabledFeatures(cfg).join(',');\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n db.close();\n rmSync(stateDir, { recursive: true, force: true });\n return open(cfg);\n }\n\n ensureSchema(db, cfg);\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings };\n}\n\n// Manual reset for a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","docCount","getMeta","open","rebuild","reconcile","setMeta","quoteIdent","name","split","join","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","Set","map","r","ensureSchema","cfg","exec","activeFeatures","feature","schema","enabledFeatures","key","row","get","run","n","baseDir","files","listFiles","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","features","seenColumns","newColumns","parsedDocs","file","parseFile","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","delBody","del","remove","insert","insertBody","values","search","title","summary","text","store","extracted","afterReconcile","err","stateDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","version","wantFeatures","close","rmSync","force"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.ts"],"sourcesContent":["import { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from './config.ts';\nimport { featureSignature, STATE_DIR } from './config.ts';\nimport { activeFeatures } from './features/index.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile } from './scan.ts';\n\n// Rows -> SQLite: core schema, reconcile loop, has(). Parsing lives in scan.ts;\n// everything beyond frontmatter + content lives in src/features/.\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`.\nexport const SCHEMA_VERSION = '3';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): 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\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\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): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\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 getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\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 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 for (const file of toReparse) {\n const { doc, warnings: fileWarnings } = parseFile(file, features);\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\n const allColumns = [...seenColumns];\n const insertSql = `INSERT OR REPLACE INTO frontmatter (${allColumns.map(quoteIdent).join(', ')}) VALUES (${allColumns.map(() => '?').join(', ')})`;\n\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`.\n const delBody = db.prepare(`DELETE FROM content WHERE \"path\" = ?`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n del.run(path);\n delBody.run(path);\n for (const feature of features) feature.remove?.(db, path);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (title, summary, text, \"path\") VALUES (?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = allColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n insert.run(...values);\n // Delete-before-insert only for docs that have rows: an FTS5 DELETE by column\n // cannot use an index and scans the whole table, so running it per doc on a\n // cold build (empty table, nothing to delete) made the crawl quadratic --\n // measured 4x time per note-count doubling at 13k/26k notes.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath);\n }\n insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name]);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, files);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n return { parsed: parsedDocs.length, warnings };\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: the write transaction for 500 changed\n // files measures ~5s at 26k notes, so 5s expired exactly at the boundary and queries\n // racing the watcher got SQLITE_BUSY. 30s bounds the wait at ~3x the largest measured\n // reconcile; a query that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\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 if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n db.close();\n rmSync(stateDir, { recursive: true, force: true });\n return open(cfg);\n }\n\n ensureSchema(db, cfg);\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings };\n}\n\n// Manual reset for a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","docCount","getMeta","open","rebuild","reconcile","setMeta","quoteIdent","name","split","join","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","Set","map","r","ensureSchema","cfg","exec","activeFeatures","feature","schema","featureSignature","key","row","get","run","n","baseDir","files","listFiles","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","features","seenColumns","newColumns","parsedDocs","file","parseFile","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","delBody","del","remove","insert","insertBody","values","search","title","summary","text","store","extracted","afterReconcile","err","stateDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","version","wantFeatures","close","rmSync","force"],"mappings":";;;;;;;;;;;QAYaA;eAAAA;;QAEAC;eAAAA;;QAiEGC;eAAAA;;QAbAC;eAAAA;;QAwGAC;eAAAA;;QAkCAC;eAAAA;;QAxHAC;eAAAA;;QAbAC;eAAAA;;;sBAvEkB;wBACb;0BACQ;wBAEe;uBACb;sBAEM;;;;;;;;;;;;;;;;;;;;;;;;;;AAK9B,IAAMP,cAAc;AAEpB,IAAMC,iBAAiB;AAU9B,SAASO,WAAWC,IAAY;IAC9B,OAAO,AAAC,IAA8B,OAA3BA,KAAKC,KAAK,CAAC,KAAKC,IAAI,CAAC,OAAM;AACxC;AAEA,+FAA+F;AAC/F,SAASC,kBAAkBC,EAAgB;IACzCA,GAAGC,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;AACF;AAEA,SAASW,WAAWlB,EAAgB;IAClC,IAAMmB,OAAOnB,GAAGoB,OAAO,CAAC,kCAAkCC,GAAG;IAC7D,OAAO,IAAIC,IAAIH,KAAKI,GAAG,CAAC,SAACC;eAAMA,EAAE5B,IAAI;;AACvC;AAEA,2FAA2F;AAC3F,wFAAwF;AACxF,SAAS6B,aAAazB,EAAgB,EAAE0B,GAAW;IACjD1B,GAAG2B,IAAI,CAAC;IACR3B,GAAG2B,IAAI,CAAC;QACH,kCAAA,2BAAA;;QAAL,QAAK,YAAiBC,IAAAA,uBAAc,EAACF,yBAAhC,SAAA,6BAAA,QAAA,yBAAA;YAAA,IAAMG,UAAN;YAAsCA,QAAQC,MAAM,CAAC9B;;;QAArD;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IACL,IAAIV,QAAQU,IAAI,sBAAsB,MAAMN,QAAQM,IAAI,kBAAkBZ;IAC1E,IAAIE,QAAQU,IAAI,gBAAgB,MAAMN,QAAQM,IAAI,YAAY+B,IAAAA,0BAAgB,EAACL;AACjF;AAEO,SAASpC,QAAQU,EAAgB,EAAEgC,GAAW;IACnD,IAAMC,MAAMjC,GAAGoB,OAAO,CAAC,wCAAwCc,GAAG,CAACF;IACnE,OAAOC,MAAMA,IAAI5B,KAAK,GAAG;AAC3B;AAEO,SAASX,QAAQM,EAAgB,EAAEgC,GAAW,EAAE3B,KAAoB;IACzE,IAAIA,UAAU,MAAM;QAClBL,GAAGoB,OAAO,CAAC,kCAAkCe,GAAG,CAACH;QACjD;IACF;IACAhC,GAAGoB,OAAO,CAAC,qGAAqGe,GAAG,CAACH,KAAK3B;AAC3H;AAEO,SAAShB,SAASW,EAAgB;IACvC,IAAMiC,MAAMjC,GAAGoB,OAAO,CAAC,yCAAyCc,GAAG;IACnE,OAAOD,IAAIG,CAAC;AACd;AAEO,SAAS3C,UAAUO,EAAgB,EAAE0B,GAAW,EAAEW,OAAe;IACtE,IAAMC,QAAQC,IAAAA,iBAAS,EAACb,KAAKW;IAC7B,IAAMG,aAAa,IAAIlB,IAAIgB,MAAMf,GAAG,CAAC,SAACkB;eAAMA,EAAEC,OAAO;;IAErD,IAAMC,eAAe3C,GAAGoB,OAAO,CAAC,qDAAqDC,GAAG;IAKxF,IAAMuB,WAAW,IAAIC,IAAIF,aAAapB,GAAG,CAAC,SAACC;eAAM;YAACA,EAAEsB,IAAI;YAAEtB;SAAE;;IAC5D,IAAMuB,WAAWJ,aAAaK,MAAM,CAAC,SAACxB;eAAM,CAACgB,WAAWS,GAAG,CAACzB,EAAEsB,IAAI;OAAGvB,GAAG,CAAC,SAACC;eAAMA,EAAEsB,IAAI;;IAEtF,IAAMI,YAAYZ,MAAMU,MAAM,CAAC,SAACP;QAC9B,IAAMR,MAAMW,SAASV,GAAG,CAACO,EAAEC,OAAO;QAClC,OAAO,CAACT,OAAOA,IAAIkB,MAAM,KAAKV,EAAEW,OAAO,IAAInB,IAAIoB,KAAK,KAAKZ,EAAEa,IAAI;IACjE;IAEA,IAAIP,SAASQ,MAAM,KAAK,KAAKL,UAAUK,MAAM,KAAK,GAAG,OAAO;QAAE7C,QAAQ;QAAG8C,UAAU,EAAE;IAAC;IAEtF,IAAMC,WAAW7B,IAAAA,uBAAc,EAACF;IAChC,IAAMgC,cAAcxC,WAAWlB;IAC/B,IAAM2D,aAAuB,EAAE;IAC/B,IAAMC,aAA0B,EAAE;IAClC,IAAMJ,WAAqB,EAAE;QAExB,kCAAA,2BAAA;;QAAL,QAAK,YAAcN,8BAAd,SAAA,6BAAA,QAAA,yBAAA,iCAAyB;YAAzB,IAAMW,OAAN;gBAEHL;YADA,IAAwCM,aAAAA,IAAAA,iBAAS,EAACD,MAAMJ,WAAhDM,MAAgCD,WAAhCC,KAAKP,AAAUQ,eAAiBF,WAA3BN;YACbA,CAAAA,YAAAA,UAASS,IAAI,OAAbT,WAAc,qBAAGQ;gBACZ,mCAAA,4BAAA;;gBAAL,QAAK,aAAaE,OAAOC,IAAI,CAACJ,IAAIK,IAAI,sBAAjC,UAAA,8BAAA,SAAA,0BAAA,kCAAoC;oBAApC,IAAMpC,MAAN;oBACH,IAAI,CAAC0B,YAAYT,GAAG,CAACjB,MAAM;wBACzB0B,YAAYW,GAAG,CAACrC;wBAChB2B,WAAWM,IAAI,CAACjC;oBAClB;gBACF;;gBALK;gBAAA;;;yBAAA,8BAAA;wBAAA;;;wBAAA;8BAAA;;;;YAML4B,WAAWK,IAAI,CAACF;QAClB;;QAVK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAYL,IAAMO,aAAc,qBAAGZ;IACvB,IAAMa,YAAY,AAAC,uCAAwFD,OAAlDA,WAAW/C,GAAG,CAAC5B,YAAYG,IAAI,CAAC,OAAM,cAAiD,OAArCwE,WAAW/C,GAAG,CAAC;eAAM;OAAKzB,IAAI,CAAC,OAAM;IAEhJE,GAAG2B,IAAI,CAAC;IACR,IAAI;YAmC8BE;YAlC3B,mCAAA,4BAAA;;YAAL,QAAK,aAAa8B,+BAAb,UAAA,8BAAA,SAAA,0BAAA;gBAAA,IAAMa,MAAN;gBAAyBxE,GAAG2B,IAAI,CAAC,AAAC,sCAAqD,OAAhBhC,WAAW6E;;;YAAlF;YAAA;;;qBAAA,8BAAA;oBAAA;;;oBAAA;0BAAA;;;;QACL,8DAA8D;QAC9D,IAAMC,UAAUzE,GAAGoB,OAAO,CAAC;QAC3B,IAAI2B,SAASQ,MAAM,GAAG,GAAG;YACvB,IAAMmB,MAAM1E,GAAGoB,OAAO,CAAC;gBAClB,mCAAA,4BAAA;;gBAAL,QAAK,aAAc2B,6BAAd,UAAA,8BAAA,SAAA,0BAAA,kCAAwB;oBAAxB,IAAMD,OAAN;wBAG6BjB;oBAFhC6C,IAAIvC,GAAG,CAACW;oBACR2B,QAAQtC,GAAG,CAACW;wBACP,mCAAA,4BAAA;;wBAAL,QAAK,aAAiBW,6BAAjB,UAAA,8BAAA,SAAA,0BAAA;4BAAA,IAAM5B,UAAN;6BAA2BA,kBAAAA,QAAQ8C,MAAM,cAAd9C,sCAAAA,qBAAAA,SAAiB7B,IAAI8C;;;wBAAhD;wBAAA;;;iCAAA,8BAAA;gCAAA;;;gCAAA;sCAAA;;;;gBACP;;gBAJK;gBAAA;;;yBAAA,8BAAA;wBAAA;;;wBAAA;8BAAA;;;;QAKP;QACA,IAAIc,WAAWL,MAAM,GAAG,GAAG;YACzB,IAAMqB,SAAS5E,GAAGoB,OAAO,CAACmD;YAC1B,IAAMM,aAAa7E,GAAGoB,OAAO,CAAC;gBACzB,mCAAA,4BAAA;;;oBAAA,IAAM2C,MAAN;wBAOHa;wBAUgC/C;oBAhBhC,IAAMiD,SAASR,WAAW/C,GAAG,CAAC,SAACiD;4BAItBT;wBAHP,IAAIS,QAAQ,QAAQ,OAAOT,IAAIrB,OAAO;wBACtC,IAAI8B,QAAQ,UAAU,OAAOT,IAAIX,OAAO;wBACxC,IAAIoB,QAAQ,SAAS,OAAOT,IAAIT,IAAI;wBACpC,QAAOS,gBAAAA,IAAIK,IAAI,CAACI,IAAI,cAAbT,2BAAAA,gBAAiB;oBAC1B;oBACAa,CAAAA,UAAAA,QAAOzC,GAAG,OAAVyC,SAAW,qBAAGE;oBACd,8EAA8E;oBAC9E,4EAA4E;oBAC5E,0EAA0E;oBAC1E,6DAA6D;oBAC7D,IAAIlC,SAASK,GAAG,CAACc,IAAIrB,OAAO,GAAG;4BAEGb;wBADhC4C,QAAQtC,GAAG,CAAC4B,IAAIrB,OAAO;4BAClB,kCAAA,2BAAA;;4BAAL,QAAK,YAAiBe,6BAAjB,SAAA,6BAAA,QAAA,yBAAA;gCAAA,IAAM5B,UAAN;iCAA2BA,kBAAAA,QAAQ8C,MAAM,cAAd9C,sCAAAA,qBAAAA,SAAiB7B,IAAI+D,IAAIrB,OAAO;;;4BAA3D;4BAAA;;;qCAAA,6BAAA;oCAAA;;;oCAAA;0CAAA;;;;oBACP;oBACAmC,WAAW1C,GAAG,CAAC4B,IAAIgB,MAAM,CAACC,KAAK,EAAEjB,IAAIgB,MAAM,CAACE,OAAO,EAAElB,IAAIgB,MAAM,CAACG,IAAI,EAAEnB,IAAIrB,OAAO;wBAC5E,mCAAA,4BAAA;;wBAAL,QAAK,aAAiBe,6BAAjB,UAAA,8BAAA,SAAA,0BAAA;4BAAA,IAAM5B,WAAN;6BAA2BA,iBAAAA,SAAQsD,KAAK,cAAbtD,qCAAAA,oBAAAA,UAAgB7B,IAAI+D,IAAIrB,OAAO,EAAEqB,IAAIqB,SAAS,CAACvD,SAAQjC,IAAI,CAAC;;;wBAAvF;wBAAA;;;iCAAA,8BAAA;gCAAA;;;gCAAA;sCAAA;;;;gBACP;gBAlBA,QAAK,aAAagE,+BAAb,UAAA,8BAAA,SAAA,0BAAA;;gBAAA;gBAAA;;;yBAAA,8BAAA;wBAAA;;;wBAAA;8BAAA;;;;QAmBP;YACK,mCAAA,4BAAA;;YAAL,QAAK,aAAiBH,6BAAjB,UAAA,8BAAA,SAAA,0BAAA;gBAAA,IAAM5B,WAAN;iBAA2BA,0BAAAA,SAAQwD,cAAc,cAAtBxD,8CAAAA,6BAAAA,UAAyB7B,IAAIsC;;;YAAxD;YAAA;;;qBAAA,8BAAA;oBAAA;;;oBAAA;0BAAA;;;;QACLtC,GAAG2B,IAAI,CAAC;IACV,EAAE,OAAO2D,KAAK;QACZtF,GAAG2B,IAAI,CAAC;QACR,MAAM2D;IACR;IAEA,OAAO;QAAE5E,QAAQkD,WAAWL,MAAM;QAAEC,UAAAA;IAAS;AAC/C;AAEO,SAASjE,KAAKmC,GAAmB;IACtC,IAAM6D,WAAWzF,IAAAA,cAAI,EAAC4B,IAAIW,OAAO,EAAEmD,mBAAS;IAC5CC,IAAAA,iBAAS,EAACF,UAAU;QAAEG,WAAW;IAAK;IACtC,IAAMC,SAAS7F,IAAAA,cAAI,EAACyF,UAAUpG;IAE9B,IAAMa,KAAK,IAAI4F,wBAAY,CAACD;IAC5B3F,GAAG2B,IAAI,CAAC;IACR,sFAAsF;IACtF,qFAAqF;IACrF,sFAAsF;IACtF,0DAA0D;IAC1D3B,GAAG2B,IAAI,CAAC;IACR5B,kBAAkBC;IAElBA,GAAG2B,IAAI,CAAC;IAER,uFAAuF;IACvF,qGAAqG;IACrG,IAAMkE,UAAUvG,QAAQU,IAAI;IAC5B,IAAMyD,WAAWnE,QAAQU,IAAI;IAC7B,IAAM8F,eAAe/D,IAAAA,0BAAgB,EAACL;IACtC,IAAI,AAACmE,YAAY,QAAQA,YAAYzG,kBAAoBqE,aAAa,QAAQA,aAAaqC,cAAe;QACxG9F,GAAG+F,KAAK;QACRC,IAAAA,cAAM,EAACT,UAAU;YAAEG,WAAW;YAAMO,OAAO;QAAK;QAChD,OAAO1G,KAAKmC;IACd;IAEAD,aAAazB,IAAI0B;IACjB,IAA6BjC,aAAAA,UAAUO,IAAI0B,KAAKA,IAAIW,OAAO,GAAnD3B,SAAqBjB,WAArBiB,QAAQ8C,WAAa/D,WAAb+D;IAEhB,OAAO;QAAExD,IAAAA;QAAI0B,KAAAA;QAAKiE,QAAAA;QAAQjF,QAAAA;QAAQ8C,UAAAA;IAAS;AAC7C;AAGO,SAAShE,QAAQkC,GAAmB;IACzCsE,IAAAA,cAAM,EAAClG,IAAAA,cAAI,EAAC4B,IAAIW,OAAO,EAAEmD,mBAAS,GAAG;QAAEE,WAAW;QAAMO,OAAO;IAAK;IACpE,OAAO1G,KAAKmC;AACd"}
@@ -1,4 +1,4 @@
1
- export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
1
+ export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL';
2
2
  export declare class SenseError extends Error {
3
3
  code: SenseErrorCode;
4
4
  constructor(code: SenseErrorCode, message: string);