sensemaking 0.7.2 → 0.9.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 (207) hide show
  1. package/README.md +90 -61
  2. package/dist/cjs/cli/check.js +367 -0
  3. package/dist/cjs/cli/check.js.map +1 -0
  4. package/dist/cjs/{commands → cli}/index.js +2 -2
  5. package/dist/cjs/cli/index.js.map +1 -0
  6. package/dist/cjs/{commands → cli}/init.js +3 -0
  7. package/dist/cjs/cli/init.js.map +1 -0
  8. package/dist/cjs/{commands → cli}/map.js +2 -2
  9. package/dist/cjs/cli/map.js.map +1 -0
  10. package/dist/cjs/{commands → cli}/named.d.cts +1 -1
  11. package/dist/cjs/{commands → cli}/named.d.ts +1 -1
  12. package/dist/cjs/cli/named.js +219 -0
  13. package/dist/cjs/cli/named.js.map +1 -0
  14. package/dist/cjs/{commands → cli}/peek.js +2 -2
  15. package/dist/cjs/cli/peek.js.map +1 -0
  16. package/dist/cjs/cli/query.js.map +1 -0
  17. package/dist/cjs/cli/rebuild.js.map +1 -0
  18. package/dist/cjs/cli/search.d.cts +3 -0
  19. package/dist/cjs/cli/search.d.ts +3 -0
  20. package/dist/cjs/{commands/find.js → cli/search.js} +9 -8
  21. package/dist/cjs/cli/search.js.map +1 -0
  22. package/dist/cjs/{commands → cli}/shared.d.cts +1 -0
  23. package/dist/{esm/commands → cjs/cli}/shared.d.ts +1 -0
  24. package/dist/cjs/{commands → cli}/shared.js +9 -0
  25. package/dist/cjs/cli/shared.js.map +1 -0
  26. package/dist/cjs/{commands → cli}/status.js +36 -13
  27. package/dist/cjs/cli/status.js.map +1 -0
  28. package/dist/cjs/{commands → cli}/types.d.cts +3 -1
  29. package/dist/{esm/commands → cjs/cli}/types.d.ts +3 -1
  30. package/dist/cjs/cli/watch.js.map +1 -0
  31. package/dist/cjs/cli.js +42 -17
  32. package/dist/cjs/cli.js.map +1 -1
  33. package/dist/{esm/verbs.d.ts → cjs/commands.d.cts} +15 -5
  34. package/dist/cjs/{verbs.d.cts → commands.d.ts} +15 -5
  35. package/dist/cjs/{verbs.js → commands.js} +329 -39
  36. package/dist/cjs/commands.js.map +1 -0
  37. package/dist/cjs/config.d.cts +45 -12
  38. package/dist/cjs/config.d.ts +45 -12
  39. package/dist/cjs/config.js +464 -125
  40. package/dist/cjs/config.js.map +1 -1
  41. package/dist/cjs/db.d.cts +1 -1
  42. package/dist/cjs/db.d.ts +1 -1
  43. package/dist/cjs/db.js +293 -76
  44. package/dist/cjs/db.js.map +1 -1
  45. package/dist/cjs/errors.d.cts +1 -1
  46. package/dist/cjs/errors.d.ts +1 -1
  47. package/dist/cjs/errors.js.map +1 -1
  48. package/dist/cjs/features/embed.js +19 -3
  49. package/dist/cjs/features/embed.js.map +1 -1
  50. package/dist/cjs/features/links.js +201 -24
  51. package/dist/cjs/features/links.js.map +1 -1
  52. package/dist/cjs/features/rank.js +8 -1
  53. package/dist/cjs/features/rank.js.map +1 -1
  54. package/dist/cjs/features/types.d.cts +12 -4
  55. package/dist/cjs/features/types.d.ts +12 -4
  56. package/dist/cjs/index.d.cts +3 -3
  57. package/dist/cjs/index.d.ts +3 -3
  58. package/dist/cjs/index.js +9 -6
  59. package/dist/cjs/index.js.map +1 -1
  60. package/dist/cjs/output.d.cts +16 -0
  61. package/dist/cjs/output.d.ts +16 -0
  62. package/dist/cjs/output.js +27 -5
  63. package/dist/cjs/output.js.map +1 -1
  64. package/dist/cjs/progress.d.cts +5 -0
  65. package/dist/cjs/progress.d.ts +5 -0
  66. package/dist/cjs/progress.js +54 -0
  67. package/dist/cjs/progress.js.map +1 -0
  68. package/dist/cjs/scan.d.cts +4 -0
  69. package/dist/cjs/scan.d.ts +4 -0
  70. package/dist/cjs/scan.js +87 -6
  71. package/dist/cjs/scan.js.map +1 -1
  72. package/dist/esm/{commands → cli}/check.js +41 -22
  73. package/dist/esm/cli/check.js.map +1 -0
  74. package/dist/esm/{commands → cli}/index.js +1 -1
  75. package/dist/esm/cli/index.js.map +1 -0
  76. package/dist/esm/{commands → cli}/init.js +3 -0
  77. package/dist/esm/cli/init.js.map +1 -0
  78. package/dist/esm/{commands → cli}/map.js +1 -1
  79. package/dist/esm/cli/map.js.map +1 -0
  80. package/dist/esm/{commands → cli}/named.d.ts +1 -1
  81. package/dist/esm/cli/named.js +41 -0
  82. package/dist/esm/cli/named.js.map +1 -0
  83. package/dist/esm/{commands → cli}/peek.js +1 -1
  84. package/dist/esm/cli/peek.js.map +1 -0
  85. package/dist/esm/cli/query.js.map +1 -0
  86. package/dist/esm/cli/rebuild.js.map +1 -0
  87. package/dist/esm/cli/search.d.ts +3 -0
  88. package/dist/esm/cli/search.js +16 -0
  89. package/dist/esm/cli/search.js.map +1 -0
  90. package/dist/{cjs/commands → esm/cli}/shared.d.ts +1 -0
  91. package/dist/esm/{commands → cli}/shared.js +9 -0
  92. package/dist/esm/cli/shared.js.map +1 -0
  93. package/dist/esm/{commands → cli}/status.js +17 -11
  94. package/dist/esm/cli/status.js.map +1 -0
  95. package/dist/{cjs/commands → esm/cli}/types.d.ts +3 -1
  96. package/dist/esm/cli/types.js.map +1 -0
  97. package/dist/esm/cli/watch.js.map +1 -0
  98. package/dist/esm/cli.js +24 -6
  99. package/dist/esm/cli.js.map +1 -1
  100. package/dist/{cjs/verbs.d.ts → esm/commands.d.ts} +15 -5
  101. package/dist/esm/commands.js +386 -0
  102. package/dist/esm/commands.js.map +1 -0
  103. package/dist/esm/config.d.ts +45 -12
  104. package/dist/esm/config.js +367 -93
  105. package/dist/esm/config.js.map +1 -1
  106. package/dist/esm/db.d.ts +1 -1
  107. package/dist/esm/db.js +126 -17
  108. package/dist/esm/db.js.map +1 -1
  109. package/dist/esm/errors.d.ts +1 -1
  110. package/dist/esm/errors.js.map +1 -1
  111. package/dist/esm/features/embed.js +18 -2
  112. package/dist/esm/features/embed.js.map +1 -1
  113. package/dist/esm/features/links.js +130 -20
  114. package/dist/esm/features/links.js.map +1 -1
  115. package/dist/esm/features/rank.js +8 -1
  116. package/dist/esm/features/rank.js.map +1 -1
  117. package/dist/esm/features/types.d.ts +12 -4
  118. package/dist/esm/features/types.js.map +1 -1
  119. package/dist/esm/index.d.ts +3 -3
  120. package/dist/esm/index.js +1 -1
  121. package/dist/esm/index.js.map +1 -1
  122. package/dist/esm/output.d.ts +16 -0
  123. package/dist/esm/output.js +20 -5
  124. package/dist/esm/output.js.map +1 -1
  125. package/dist/esm/progress.d.ts +5 -0
  126. package/dist/esm/progress.js +44 -0
  127. package/dist/esm/progress.js.map +1 -0
  128. package/dist/esm/scan.d.ts +4 -0
  129. package/dist/esm/scan.js +37 -6
  130. package/dist/esm/scan.js.map +1 -1
  131. package/package.json +12 -2
  132. package/schema.json +85 -50
  133. package/skills/sense/EXAMPLES.md +16 -12
  134. package/skills/sense/SKILL.md +74 -55
  135. package/skills/sense-setup/EXAMPLES.md +121 -0
  136. package/skills/sense-setup/SKILL.md +73 -54
  137. package/dist/cjs/commands/check.js +0 -143
  138. package/dist/cjs/commands/check.js.map +0 -1
  139. package/dist/cjs/commands/find.d.cts +0 -3
  140. package/dist/cjs/commands/find.d.ts +0 -3
  141. package/dist/cjs/commands/find.js.map +0 -1
  142. package/dist/cjs/commands/index.js.map +0 -1
  143. package/dist/cjs/commands/init.js.map +0 -1
  144. package/dist/cjs/commands/map.js.map +0 -1
  145. package/dist/cjs/commands/named.js +0 -23
  146. package/dist/cjs/commands/named.js.map +0 -1
  147. package/dist/cjs/commands/peek.js.map +0 -1
  148. package/dist/cjs/commands/query.js.map +0 -1
  149. package/dist/cjs/commands/rebuild.js.map +0 -1
  150. package/dist/cjs/commands/shared.js.map +0 -1
  151. package/dist/cjs/commands/status.js.map +0 -1
  152. package/dist/cjs/commands/watch.js.map +0 -1
  153. package/dist/cjs/verbs.js.map +0 -1
  154. package/dist/esm/commands/check.js.map +0 -1
  155. package/dist/esm/commands/find.d.ts +0 -3
  156. package/dist/esm/commands/find.js +0 -15
  157. package/dist/esm/commands/find.js.map +0 -1
  158. package/dist/esm/commands/index.js.map +0 -1
  159. package/dist/esm/commands/init.js.map +0 -1
  160. package/dist/esm/commands/map.js.map +0 -1
  161. package/dist/esm/commands/named.js +0 -12
  162. package/dist/esm/commands/named.js.map +0 -1
  163. package/dist/esm/commands/peek.js.map +0 -1
  164. package/dist/esm/commands/query.js.map +0 -1
  165. package/dist/esm/commands/rebuild.js.map +0 -1
  166. package/dist/esm/commands/shared.js.map +0 -1
  167. package/dist/esm/commands/status.js.map +0 -1
  168. package/dist/esm/commands/types.js.map +0 -1
  169. package/dist/esm/commands/watch.js.map +0 -1
  170. package/dist/esm/verbs.js +0 -190
  171. package/dist/esm/verbs.js.map +0 -1
  172. /package/dist/cjs/{commands → cli}/check.d.cts +0 -0
  173. /package/dist/cjs/{commands → cli}/check.d.ts +0 -0
  174. /package/dist/cjs/{commands → cli}/index.d.cts +0 -0
  175. /package/dist/cjs/{commands → cli}/index.d.ts +0 -0
  176. /package/dist/cjs/{commands → cli}/init.d.cts +0 -0
  177. /package/dist/cjs/{commands → cli}/init.d.ts +0 -0
  178. /package/dist/cjs/{commands → cli}/map.d.cts +0 -0
  179. /package/dist/cjs/{commands → cli}/map.d.ts +0 -0
  180. /package/dist/cjs/{commands → cli}/peek.d.cts +0 -0
  181. /package/dist/cjs/{commands → cli}/peek.d.ts +0 -0
  182. /package/dist/cjs/{commands → cli}/query.d.cts +0 -0
  183. /package/dist/cjs/{commands → cli}/query.d.ts +0 -0
  184. /package/dist/cjs/{commands → cli}/query.js +0 -0
  185. /package/dist/cjs/{commands → cli}/rebuild.d.cts +0 -0
  186. /package/dist/cjs/{commands → cli}/rebuild.d.ts +0 -0
  187. /package/dist/cjs/{commands → cli}/rebuild.js +0 -0
  188. /package/dist/cjs/{commands → cli}/status.d.cts +0 -0
  189. /package/dist/cjs/{commands → cli}/status.d.ts +0 -0
  190. /package/dist/cjs/{commands → cli}/types.js +0 -0
  191. /package/dist/cjs/{commands → cli}/types.js.map +0 -0
  192. /package/dist/cjs/{commands → cli}/watch.d.cts +0 -0
  193. /package/dist/cjs/{commands → cli}/watch.d.ts +0 -0
  194. /package/dist/cjs/{commands → cli}/watch.js +0 -0
  195. /package/dist/esm/{commands → cli}/check.d.ts +0 -0
  196. /package/dist/esm/{commands → cli}/index.d.ts +0 -0
  197. /package/dist/esm/{commands → cli}/init.d.ts +0 -0
  198. /package/dist/esm/{commands → cli}/map.d.ts +0 -0
  199. /package/dist/esm/{commands → cli}/peek.d.ts +0 -0
  200. /package/dist/esm/{commands → cli}/query.d.ts +0 -0
  201. /package/dist/esm/{commands → cli}/query.js +0 -0
  202. /package/dist/esm/{commands → cli}/rebuild.d.ts +0 -0
  203. /package/dist/esm/{commands → cli}/rebuild.js +0 -0
  204. /package/dist/esm/{commands → cli}/status.d.ts +0 -0
  205. /package/dist/esm/{commands → cli}/types.js +0 -0
  206. /package/dist/esm/{commands → cli}/watch.d.ts +0 -0
  207. /package/dist/esm/{commands → cli}/watch.js +0 -0
package/dist/cjs/db.js CHANGED
@@ -38,22 +38,57 @@ var _nodefs = require("node:fs");
38
38
  var _nodepath = require("node:path");
39
39
  var _nodesqlite = require("node:sqlite");
40
40
  var _configts = require("./config.js");
41
+ var _errorsts = require("./errors.js");
41
42
  var _indexts = require("./features/index.js");
43
+ var _progressts = require("./progress.js");
42
44
  var _scants = require("./scan.js");
43
45
  function _array_like_to_array(arr, len) {
44
46
  if (len == null || len > arr.length) len = arr.length;
45
47
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
46
48
  return arr2;
47
49
  }
50
+ function _array_with_holes(arr) {
51
+ if (Array.isArray(arr)) return arr;
52
+ }
48
53
  function _array_without_holes(arr) {
49
54
  if (Array.isArray(arr)) return _array_like_to_array(arr);
50
55
  }
51
56
  function _iterable_to_array(iter) {
52
57
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
53
58
  }
59
+ function _iterable_to_array_limit(arr, i) {
60
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
61
+ if (_i == null) return;
62
+ var _arr = [];
63
+ var _n = true;
64
+ var _d = false;
65
+ var _s, _e;
66
+ try {
67
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
68
+ _arr.push(_s.value);
69
+ if (i && _arr.length === i) break;
70
+ }
71
+ } catch (err) {
72
+ _d = true;
73
+ _e = err;
74
+ } finally{
75
+ try {
76
+ if (!_n && _i["return"] != null) _i["return"]();
77
+ } finally{
78
+ if (_d) throw _e;
79
+ }
80
+ }
81
+ return _arr;
82
+ }
83
+ function _non_iterable_rest() {
84
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
85
+ }
54
86
  function _non_iterable_spread() {
55
87
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
56
88
  }
89
+ function _sliced_to_array(arr, i) {
90
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
91
+ }
57
92
  function _to_consumable_array(arr) {
58
93
  return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
59
94
  }
@@ -65,8 +100,20 @@ function _unsupported_iterable_to_array(o, minLen) {
65
100
  if (n === "Map" || n === "Set") return Array.from(n);
66
101
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
67
102
  }
103
+ // path/_mtime/_size are core: every reparse legitimately rewrites them. Every other
104
+ // RESERVED_COLUMNS entry that shows up as a real frontmatter column (currently only
105
+ // rank's `_rank`) is feature-owned -- scan.ts already refuses to let frontmatter set it,
106
+ // so it must never appear in the upsert below, or a reparse would blow its last computed
107
+ // value away with NULL on every touch, not just the reconciles that recompute it.
108
+ var CORE_FRONTMATTER_COLUMNS = new Set([
109
+ 'path',
110
+ '_mtime',
111
+ '_size'
112
+ ]);
68
113
  var DB_FILENAME = 'cache.db';
69
- var SCHEMA_VERSION = '4'; // 4: frontmatter scalars store as INTEGER/REAL/TEXT by YAML type (was: all numbers REAL)
114
+ var SCHEMA_VERSION = '8';
115
+ // SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).
116
+ var MAX_FRONTMATTER_COLUMNS = 2000;
70
117
  function quoteIdent(name) {
71
118
  return '"'.concat(name.split('"').join('""'), '"');
72
119
  }
@@ -105,6 +152,14 @@ function getColumns(db) {
105
152
  function ensureSchema(db, cfg) {
106
153
  db.exec('CREATE TABLE IF NOT EXISTS frontmatter ("path" TEXT PRIMARY KEY, "_mtime" REAL, "_size" INTEGER)');
107
154
  db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')");
155
+ // Coverage, not ownership: a path can appear under several presets. Rebuilt per-file
156
+ // alongside frontmatter/content at reconcile so status/map can report matched/embedded
157
+ // counts per preset without recomputing globs at read time.
158
+ // path leads the PK so the per-doc delete in reconcile is an index hit -- keyed the other
159
+ // way it scans the whole table per doc, which made cold builds quadratic (measured 3x cost
160
+ // per note-count doubling at 13k/26k). Coverage-by-preset reads get their own index.
161
+ db.exec('CREATE TABLE IF NOT EXISTS preset_files ("path" TEXT, preset TEXT, PRIMARY KEY ("path", preset))');
162
+ db.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');
108
163
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
109
164
  try {
110
165
  for(var _iterator = (0, _indexts.activeFeatures)(cfg)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -173,16 +228,26 @@ function reconcile(db, cfg, baseDir) {
173
228
  var newColumns = [];
174
229
  var parsedDocs = [];
175
230
  var warnings = [];
231
+ // Bulk reparses (a sync, a cold build) are the long silences a query can hit; short
232
+ // reconciles stay silent (progress() has a threshold).
233
+ var report = (0, _progressts.progress)('reparsing files', toReparse.length);
234
+ var parsedCount = 0;
176
235
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
177
236
  try {
178
- for(var _iterator = toReparse[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
237
+ var _loop = function() {
179
238
  var file = _step.value;
180
239
  var _warnings;
181
- var _parseFile = (0, _scants.parseFile)(file, features), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
240
+ // A doc only gets extract/store from features that apply to it (currently: embed, via
241
+ // FileStat.embed -- true iff a covering preset has semantic on).
242
+ var fileFeatures = features.filter(function(feature) {
243
+ return !feature.enabledForFile || feature.enabledForFile(cfg, file);
244
+ });
245
+ var _parseFile = (0, _scants.parseFile)(file, fileFeatures), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
246
+ report.tick(++parsedCount);
182
247
  (_warnings = warnings).push.apply(_warnings, _to_consumable_array(fileWarnings));
183
- var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
248
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
184
249
  try {
185
- for(var _iterator1 = Object.keys(doc.data)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
250
+ for(var _iterator = Object.keys(doc.data)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion = (_step1 = _iterator.next()).done); _iteratorNormalCompletion = true){
186
251
  var key = _step1.value;
187
252
  if (!seenColumns.has(key)) {
188
253
  seenColumns.add(key);
@@ -190,21 +255,22 @@ function reconcile(db, cfg, baseDir) {
190
255
  }
191
256
  }
192
257
  } catch (err) {
193
- _didIteratorError1 = true;
194
- _iteratorError1 = err;
258
+ _didIteratorError = true;
259
+ _iteratorError = err;
195
260
  } finally{
196
261
  try {
197
- if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
198
- _iterator1.return();
262
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
263
+ _iterator.return();
199
264
  }
200
265
  } finally{
201
- if (_didIteratorError1) {
202
- throw _iteratorError1;
266
+ if (_didIteratorError) {
267
+ throw _iteratorError;
203
268
  }
204
269
  }
205
270
  }
206
271
  parsedDocs.push(doc);
207
- }
272
+ };
273
+ for(var _iterator = toReparse[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
208
274
  } catch (err) {
209
275
  _didIteratorError = true;
210
276
  _iteratorError = err;
@@ -219,90 +285,129 @@ function reconcile(db, cfg, baseDir) {
219
285
  }
220
286
  }
221
287
  }
288
+ report.finish();
222
289
  var allColumns = _to_consumable_array(seenColumns);
223
- var insertSql = "INSERT OR REPLACE INTO frontmatter (".concat(allColumns.map(quoteIdent).join(', '), ") VALUES (").concat(allColumns.map(function() {
290
+ // Fence before ALTERing: SQLite's own failure past this point is a raw
291
+ // "too many columns on sqlite_altertab_frontmatter" with no indication of the boundary or the levers.
292
+ if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {
293
+ throw new _errorsts.SenseError('COLUMN_LIMIT', "frontmatter would need ".concat(allColumns.length, " columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ").concat(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."));
294
+ }
295
+ // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a
296
+ // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).
297
+ var writableColumns = allColumns.filter(function(c) {
298
+ return CORE_FRONTMATTER_COLUMNS.has(c) || !_scants.RESERVED_COLUMNS.has(c);
299
+ });
300
+ // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --
301
+ // content rows are coupled to that rowid below.
302
+ var insertSql = "INSERT INTO frontmatter (".concat(writableColumns.map(quoteIdent).join(', '), ") VALUES (").concat(writableColumns.map(function() {
224
303
  return '?';
225
- }).join(', '), ")");
304
+ }).join(', '), ') ON CONFLICT("path") DO UPDATE SET ').concat(writableColumns.filter(function(c) {
305
+ return c !== 'path';
306
+ }).map(function(c) {
307
+ return "".concat(quoteIdent(c), " = excluded.").concat(quoteIdent(c));
308
+ }).join(', '));
309
+ var added = toReparse.filter(function(f) {
310
+ return !existing.has(f.relPath);
311
+ }).map(function(f) {
312
+ return f.relPath;
313
+ });
314
+ var delta = {
315
+ files: files,
316
+ reparsed: parsedDocs.map(function(d) {
317
+ return d.relPath;
318
+ }),
319
+ added: added,
320
+ vanished: vanished
321
+ };
322
+ var txStart = Date.now();
226
323
  db.exec('BEGIN');
227
324
  try {
228
325
  var _feature_afterReconcile;
229
- var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
326
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
230
327
  try {
231
- for(var _iterator2 = newColumns[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
232
- var col = _step2.value;
328
+ for(var _iterator1 = newColumns[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
329
+ var col = _step1.value;
233
330
  db.exec("ALTER TABLE frontmatter ADD COLUMN ".concat(quoteIdent(col)));
234
331
  }
235
332
  } catch (err) {
236
- _didIteratorError2 = true;
237
- _iteratorError2 = err;
333
+ _didIteratorError1 = true;
334
+ _iteratorError1 = err;
238
335
  } finally{
239
336
  try {
240
- if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
241
- _iterator2.return();
337
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
338
+ _iterator1.return();
242
339
  }
243
340
  } finally{
244
- if (_didIteratorError2) {
245
- throw _iteratorError2;
341
+ if (_didIteratorError1) {
342
+ throw _iteratorError1;
246
343
  }
247
344
  }
248
345
  }
249
- // FTS5 has no upsert, so delete-before-insert into `content`.
250
- var delBody = db.prepare('DELETE FROM content WHERE "path" = ?');
346
+ // FTS5 has no upsert, so delete-before-insert into `content`; coupled to the
347
+ // frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`
348
+ // column, which a per-row DELETE would otherwise scan the whole table to find.
349
+ var delBody = db.prepare('DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE "path" = ?)');
350
+ var delPresetFiles = db.prepare('DELETE FROM preset_files WHERE "path" = ?');
351
+ var insertPresetFile = db.prepare('INSERT INTO preset_files ("path", preset) VALUES (?, ?)');
251
352
  if (vanished.length > 0) {
252
353
  var del = db.prepare('DELETE FROM frontmatter WHERE "path" = ?');
253
- var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = undefined;
354
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
254
355
  try {
255
- for(var _iterator3 = vanished[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true){
256
- var path = _step3.value;
356
+ for(var _iterator2 = vanished[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
357
+ var path = _step2.value;
257
358
  var _feature_remove;
258
- del.run(path);
359
+ // content delete must run first: it looks up the frontmatter rowid by path, which
360
+ // the frontmatter delete below would otherwise have already removed.
259
361
  delBody.run(path);
260
- var _iteratorNormalCompletion4 = true, _didIteratorError4 = false, _iteratorError4 = undefined;
362
+ del.run(path);
363
+ delPresetFiles.run(path);
364
+ var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = undefined;
261
365
  try {
262
- for(var _iterator4 = features[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true){
263
- var feature = _step4.value;
264
- (_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, path);
366
+ for(var _iterator3 = features[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true){
367
+ var feature = _step3.value;
368
+ (_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, path, delta);
265
369
  }
266
370
  } catch (err) {
267
- _didIteratorError4 = true;
268
- _iteratorError4 = err;
371
+ _didIteratorError3 = true;
372
+ _iteratorError3 = err;
269
373
  } finally{
270
374
  try {
271
- if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
272
- _iterator4.return();
375
+ if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
376
+ _iterator3.return();
273
377
  }
274
378
  } finally{
275
- if (_didIteratorError4) {
276
- throw _iteratorError4;
379
+ if (_didIteratorError3) {
380
+ throw _iteratorError3;
277
381
  }
278
382
  }
279
383
  }
280
384
  }
281
385
  } catch (err) {
282
- _didIteratorError3 = true;
283
- _iteratorError3 = err;
386
+ _didIteratorError2 = true;
387
+ _iteratorError2 = err;
284
388
  } finally{
285
389
  try {
286
- if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
287
- _iterator3.return();
390
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
391
+ _iterator2.return();
288
392
  }
289
393
  } finally{
290
- if (_didIteratorError3) {
291
- throw _iteratorError3;
394
+ if (_didIteratorError2) {
395
+ throw _iteratorError2;
292
396
  }
293
397
  }
294
398
  }
295
399
  }
296
400
  if (parsedDocs.length > 0) {
297
401
  var insert = db.prepare(insertSql);
298
- var insertBody = db.prepare('INSERT INTO content (title, summary, text, "path") VALUES (?, ?, ?, ?)');
299
- var _iteratorNormalCompletion5 = true, _didIteratorError5 = false, _iteratorError5 = undefined;
402
+ var insertBody = db.prepare('INSERT INTO content (rowid, title, summary, text, "path") VALUES ((SELECT rowid FROM frontmatter WHERE "path" = ?), ?, ?, ?, ?)');
403
+ var _iteratorNormalCompletion4 = true, _didIteratorError4 = false, _iteratorError4 = undefined;
300
404
  try {
301
- var _loop = function() {
302
- var doc = _step5.value;
303
- var _insert;
405
+ var _loop1 = function() {
406
+ var doc = _step4.value;
407
+ var // Frontmatter upsert first: content's rowid lookup below depends on this row existing.
408
+ _insert;
304
409
  var _feature_store;
305
- var values = allColumns.map(function(col) {
410
+ var values = writableColumns.map(function(col) {
306
411
  var _doc_data_col;
307
412
  if (col === 'path') return doc.relPath;
308
413
  if (col === '_mtime') return doc.mtimeMs;
@@ -310,9 +415,9 @@ function reconcile(db, cfg, baseDir) {
310
415
  return (_doc_data_col = doc.data[col]) !== null && _doc_data_col !== void 0 ? _doc_data_col : null;
311
416
  });
312
417
  (_insert = insert).run.apply(_insert, _to_consumable_array(values));
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 --
418
+ // Delete-before-insert only for docs that have rows: an FTS5 DELETE by rowid on a
419
+ // cold build (empty table, nothing to delete) is wasted work, and doing it
420
+ // unconditionally previously made the crawl quadratic when it scanned by column --
316
421
  // measured 4x time per note-count doubling at 13k/26k notes.
317
422
  if (existing.has(doc.relPath)) {
318
423
  var _feature_remove;
@@ -321,7 +426,7 @@ function reconcile(db, cfg, baseDir) {
321
426
  try {
322
427
  for(var _iterator = features[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
323
428
  var feature = _step.value;
324
- (_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, doc.relPath);
429
+ (_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, doc.relPath, delta);
325
430
  }
326
431
  } catch (err) {
327
432
  _didIteratorError = true;
@@ -338,12 +443,17 @@ function reconcile(db, cfg, baseDir) {
338
443
  }
339
444
  }
340
445
  }
341
- insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
446
+ insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
447
+ // Coverage is glob-derived, not content-derived, but only reparsed/added docs are
448
+ // touched here: a preset edit changes featureSignature and forces a full rebuild
449
+ // (see open()), so an unchanged doc's coverage is already correct on disk. New docs
450
+ // have no rows to clear -- skipping the delete keeps cold builds linear.
451
+ if (existing.has(doc.relPath)) delPresetFiles.run(doc.relPath);
342
452
  var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
343
453
  try {
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]);
454
+ for(var _iterator1 = doc.presets[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
455
+ var presetName = _step1.value;
456
+ insertPresetFile.run(doc.relPath, presetName);
347
457
  }
348
458
  } catch (err) {
349
459
  _didIteratorError1 = true;
@@ -359,40 +469,60 @@ function reconcile(db, cfg, baseDir) {
359
469
  }
360
470
  }
361
471
  }
472
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
473
+ try {
474
+ for(var _iterator2 = features[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
475
+ var feature1 = _step2.value;
476
+ (_feature_store = feature1.store) === null || _feature_store === void 0 ? void 0 : _feature_store.call(feature1, db, doc.relPath, doc.extracted[feature1.name], delta);
477
+ }
478
+ } catch (err) {
479
+ _didIteratorError2 = true;
480
+ _iteratorError2 = err;
481
+ } finally{
482
+ try {
483
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
484
+ _iterator2.return();
485
+ }
486
+ } finally{
487
+ if (_didIteratorError2) {
488
+ throw _iteratorError2;
489
+ }
490
+ }
491
+ }
362
492
  };
363
- for(var _iterator5 = parsedDocs[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true)_loop();
493
+ for(var _iterator4 = parsedDocs[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true)_loop1();
364
494
  } catch (err) {
365
- _didIteratorError5 = true;
366
- _iteratorError5 = err;
495
+ _didIteratorError4 = true;
496
+ _iteratorError4 = err;
367
497
  } finally{
368
498
  try {
369
- if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
370
- _iterator5.return();
499
+ if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
500
+ _iterator4.return();
371
501
  }
372
502
  } finally{
373
- if (_didIteratorError5) {
374
- throw _iteratorError5;
503
+ if (_didIteratorError4) {
504
+ throw _iteratorError4;
375
505
  }
376
506
  }
377
507
  }
378
508
  }
379
- var _iteratorNormalCompletion6 = true, _didIteratorError6 = false, _iteratorError6 = undefined;
509
+ var _iteratorNormalCompletion5 = true, _didIteratorError5 = false, _iteratorError5 = undefined;
380
510
  try {
381
- for(var _iterator6 = features[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true){
382
- var feature1 = _step6.value;
383
- (_feature_afterReconcile = feature1.afterReconcile) === null || _feature_afterReconcile === void 0 ? void 0 : _feature_afterReconcile.call(feature1, db, files);
511
+ for(var _iterator5 = features[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true){
512
+ var feature1 = _step5.value;
513
+ (_feature_afterReconcile = feature1.afterReconcile) === null || _feature_afterReconcile === void 0 ? void 0 : _feature_afterReconcile.call(feature1, db, delta);
384
514
  }
385
515
  } catch (err) {
386
- _didIteratorError6 = true;
387
- _iteratorError6 = err;
516
+ _didIteratorError5 = true;
517
+ _iteratorError5 = err;
388
518
  } finally{
389
519
  try {
390
- if (!_iteratorNormalCompletion6 && _iterator6.return != null) {
391
- _iterator6.return();
520
+ if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
521
+ _iterator5.return();
392
522
  }
393
523
  } finally{
394
- if (_didIteratorError6) {
395
- throw _iteratorError6;
524
+ if (_didIteratorError5) {
525
+ throw _iteratorError5;
396
526
  }
397
527
  }
398
528
  }
@@ -401,12 +531,84 @@ function reconcile(db, cfg, baseDir) {
401
531
  db.exec('ROLLBACK');
402
532
  throw err;
403
533
  }
534
+ // Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):
535
+ // keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.
536
+ var durationMs = Date.now() - txStart;
537
+ var prevRaw = getMeta(db, 'reconcile_max_ms');
538
+ // -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)
539
+ // still gets recorded instead of losing to the "nothing recorded yet" default.
540
+ var prevMax = prevRaw === null ? -1 : Number(prevRaw);
541
+ if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));
404
542
  return {
405
543
  parsed: parsedDocs.length,
406
544
  warnings: warnings
407
545
  };
408
546
  }
547
+ // Names what moved between two feature signatures (see config.featureSignature's format:
548
+ // global features, embed provider, then one segment per preset) for the rebuild notice.
549
+ function signatureDiff(before, after) {
550
+ // Segment keys: `features`, `embed`, `preset:<name>` (config.featureSignature's format).
551
+ var keyOf = function keyOf(part) {
552
+ return part.startsWith('preset:') ? part.split(':').slice(0, 2).join(':') : part.split(':')[0];
553
+ };
554
+ var parse = function parse(sig) {
555
+ return new Map(sig.split('|').map(function(part) {
556
+ return [
557
+ keyOf(part),
558
+ part
559
+ ];
560
+ }));
561
+ };
562
+ var a = parse(before);
563
+ var b = parse(after);
564
+ var changed = new Set();
565
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
566
+ try {
567
+ for(var _iterator = b[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
568
+ var _step_value = _sliced_to_array(_step.value, 2), key = _step_value[0], val = _step_value[1];
569
+ if (a.get(key) !== val) changed.add(key);
570
+ }
571
+ } catch (err) {
572
+ _didIteratorError = true;
573
+ _iteratorError = err;
574
+ } finally{
575
+ try {
576
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
577
+ _iterator.return();
578
+ }
579
+ } finally{
580
+ if (_didIteratorError) {
581
+ throw _iteratorError;
582
+ }
583
+ }
584
+ }
585
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
586
+ try {
587
+ for(var _iterator1 = a.keys()[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
588
+ var key1 = _step1.value;
589
+ if (!b.has(key1)) changed.add(key1);
590
+ }
591
+ } catch (err) {
592
+ _didIteratorError1 = true;
593
+ _iteratorError1 = err;
594
+ } finally{
595
+ try {
596
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
597
+ _iterator1.return();
598
+ }
599
+ } finally{
600
+ if (_didIteratorError1) {
601
+ throw _iteratorError1;
602
+ }
603
+ }
604
+ }
605
+ var label = function label(key) {
606
+ return key === 'embed' ? 'embed settings' : key.startsWith('preset:') ? 'preset "'.concat(key.slice(7), '"') : 'features';
607
+ };
608
+ return changed.size === 0 ? 'features' : _to_consumable_array(changed).map(label).join(', ');
609
+ }
409
610
  function open(cfg) {
611
+ var _getMeta;
410
612
  var stateDir = (0, _nodepath.join)(cfg.baseDir, _configts.STATE_DIR);
411
613
  (0, _nodefs.mkdirSync)(stateDir, {
412
614
  recursive: true
@@ -427,6 +629,14 @@ function open(cfg) {
427
629
  var features = getMeta(db, 'features');
428
630
  var wantFeatures = (0, _configts.featureSignature)(cfg);
429
631
  if (version !== null && version !== SCHEMA_VERSION || features !== null && features !== wantFeatures) {
632
+ // Indexing derives from presets, so a config edit rebuilding the cache must say so and
633
+ // name what changed -- silent rebuilds make derived indexing look like a hang or a bug.
634
+ if (version !== null && version !== SCHEMA_VERSION) {
635
+ console.error('sense: cache format changed (new sensemaking version); rebuilding the index');
636
+ } else {
637
+ var changed = signatureDiff(features !== null && features !== void 0 ? features : '', wantFeatures);
638
+ console.error("sense: config change (".concat(changed, ") rebuilds the index"));
639
+ }
430
640
  db.close();
431
641
  (0, _nodefs.rmSync)(stateDir, {
432
642
  recursive: true,
@@ -435,6 +645,13 @@ function open(cfg) {
435
645
  return open(cfg);
436
646
  }
437
647
  ensureSchema(db, cfg);
648
+ // Derived from reconcile's own recorded max (F): 3x the largest reconcile this cache has
649
+ // ever held its write transaction for, floored at the 30s default and capped at 10min so
650
+ // one pathological build can't pin every later open to an unbounded wait. Installed
651
+ // before reconcile() below -- this open's own reconcile is exactly the operation that
652
+ // races a concurrent watcher's transaction and needs the derived wait.
653
+ var recordedMaxMs = Number((_getMeta = getMeta(db, 'reconcile_max_ms')) !== null && _getMeta !== void 0 ? _getMeta : '0');
654
+ db.exec("PRAGMA busy_timeout = ".concat(Math.min(Math.max(30000, 3 * recordedMaxMs), 600000)));
438
655
  var _reconcile = reconcile(db, cfg, cfg.baseDir), parsed = _reconcile.parsed, warnings = _reconcile.warnings;
439
656
  return {
440
657
  db: db,