sensemaking 0.9.5 → 0.11.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 (177) hide show
  1. package/README.md +27 -22
  2. package/dist/cjs/cli/download.d.cts +3 -0
  3. package/dist/cjs/cli/download.d.ts +3 -0
  4. package/dist/cjs/cli/download.js +214 -0
  5. package/dist/cjs/cli/download.js.map +1 -0
  6. package/dist/cjs/cli/index.d.cts +6 -5
  7. package/dist/cjs/cli/index.d.ts +6 -5
  8. package/dist/cjs/cli/index.js +20 -14
  9. package/dist/cjs/cli/index.js.map +1 -1
  10. package/dist/cjs/cli/init.js +4 -1
  11. package/dist/cjs/cli/init.js.map +1 -1
  12. package/dist/cjs/cli/map.js +2 -2
  13. package/dist/cjs/cli/map.js.map +1 -1
  14. package/dist/cjs/cli/named.js +13 -19
  15. package/dist/cjs/cli/named.js.map +1 -1
  16. package/dist/cjs/cli/path.d.cts +3 -0
  17. package/dist/cjs/cli/path.d.ts +3 -0
  18. package/dist/cjs/cli/path.js +161 -0
  19. package/dist/cjs/cli/path.js.map +1 -0
  20. package/dist/cjs/cli/peek.js +3 -2
  21. package/dist/cjs/cli/peek.js.map +1 -1
  22. package/dist/cjs/cli/related.d.cts +3 -0
  23. package/dist/cjs/cli/related.d.ts +3 -0
  24. package/dist/cjs/cli/related.js +279 -0
  25. package/dist/cjs/cli/related.js.map +1 -0
  26. package/dist/cjs/cli/search.js +3 -7
  27. package/dist/cjs/cli/search.js.map +1 -1
  28. package/dist/cjs/cli/shared.d.cts +4 -2
  29. package/dist/cjs/cli/shared.d.ts +4 -2
  30. package/dist/cjs/cli/shared.js +49 -6
  31. package/dist/cjs/cli/shared.js.map +1 -1
  32. package/dist/cjs/cli/sql.d.cts +3 -0
  33. package/dist/cjs/cli/sql.d.ts +3 -0
  34. package/dist/cjs/cli/{query.js → sql.js} +33 -5
  35. package/dist/cjs/cli/sql.js.map +1 -0
  36. package/dist/cjs/cli/status.d.cts +1 -0
  37. package/dist/cjs/cli/status.d.ts +1 -0
  38. package/dist/cjs/cli/status.js +80 -10
  39. package/dist/cjs/cli/status.js.map +1 -1
  40. package/dist/cjs/cli.js +14 -15
  41. package/dist/cjs/cli.js.map +1 -1
  42. package/dist/cjs/commands.d.cts +13 -4
  43. package/dist/cjs/commands.d.ts +13 -4
  44. package/dist/cjs/commands.js +161 -61
  45. package/dist/cjs/commands.js.map +1 -1
  46. package/dist/cjs/config.d.cts +6 -4
  47. package/dist/cjs/config.d.ts +6 -4
  48. package/dist/cjs/config.js +137 -58
  49. package/dist/cjs/config.js.map +1 -1
  50. package/dist/cjs/db.d.cts +2 -2
  51. package/dist/cjs/db.d.ts +2 -2
  52. package/dist/cjs/db.js +25 -46
  53. package/dist/cjs/db.js.map +1 -1
  54. package/dist/cjs/errors.d.cts +1 -1
  55. package/dist/cjs/errors.d.ts +1 -1
  56. package/dist/cjs/errors.js.map +1 -1
  57. package/dist/cjs/features/embed.d.cts +15 -0
  58. package/dist/cjs/features/embed.d.ts +15 -0
  59. package/dist/cjs/features/embed.js +277 -61
  60. package/dist/cjs/features/embed.js.map +1 -1
  61. package/dist/cjs/features/links.js +7 -16
  62. package/dist/cjs/features/links.js.map +1 -1
  63. package/dist/cjs/features/rank.js +2 -6
  64. package/dist/cjs/features/rank.js.map +1 -1
  65. package/dist/cjs/index.d.cts +2 -2
  66. package/dist/cjs/index.d.ts +2 -2
  67. package/dist/cjs/index.js +3 -3
  68. package/dist/cjs/index.js.map +1 -1
  69. package/dist/cjs/output.d.cts +3 -0
  70. package/dist/cjs/output.d.ts +3 -0
  71. package/dist/cjs/output.js +17 -15
  72. package/dist/cjs/output.js.map +1 -1
  73. package/dist/cjs/progress.js +2 -4
  74. package/dist/cjs/progress.js.map +1 -1
  75. package/dist/cjs/scan.d.cts +2 -0
  76. package/dist/cjs/scan.d.ts +2 -0
  77. package/dist/cjs/scan.js +150 -44
  78. package/dist/cjs/scan.js.map +1 -1
  79. package/dist/cjs/search-error.js +4 -6
  80. package/dist/cjs/search-error.js.map +1 -1
  81. package/dist/cjs/traverse.d.cts +8 -0
  82. package/dist/cjs/traverse.d.ts +8 -0
  83. package/dist/cjs/traverse.js +97 -0
  84. package/dist/cjs/traverse.js.map +1 -0
  85. package/dist/esm/cli/download.d.ts +3 -0
  86. package/dist/esm/cli/download.js +27 -0
  87. package/dist/esm/cli/download.js.map +1 -0
  88. package/dist/esm/cli/index.d.ts +6 -5
  89. package/dist/esm/cli/index.js +13 -11
  90. package/dist/esm/cli/index.js.map +1 -1
  91. package/dist/esm/cli/init.js +4 -1
  92. package/dist/esm/cli/init.js.map +1 -1
  93. package/dist/esm/cli/map.js +3 -2
  94. package/dist/esm/cli/map.js.map +1 -1
  95. package/dist/esm/cli/named.js +13 -17
  96. package/dist/esm/cli/named.js.map +1 -1
  97. package/dist/esm/cli/path.d.ts +3 -0
  98. package/dist/esm/cli/path.js +51 -0
  99. package/dist/esm/cli/path.js.map +1 -0
  100. package/dist/esm/cli/peek.js +4 -2
  101. package/dist/esm/cli/peek.js.map +1 -1
  102. package/dist/esm/cli/related.d.ts +3 -0
  103. package/dist/esm/cli/related.js +26 -0
  104. package/dist/esm/cli/related.js.map +1 -0
  105. package/dist/esm/cli/search.js +2 -5
  106. package/dist/esm/cli/search.js.map +1 -1
  107. package/dist/esm/cli/shared.d.ts +4 -2
  108. package/dist/esm/cli/shared.js +49 -6
  109. package/dist/esm/cli/shared.js.map +1 -1
  110. package/dist/esm/cli/sql.d.ts +3 -0
  111. package/dist/esm/cli/{query.js → sql.js} +8 -5
  112. package/dist/esm/cli/sql.js.map +1 -0
  113. package/dist/esm/cli/status.d.ts +1 -0
  114. package/dist/esm/cli/status.js +72 -8
  115. package/dist/esm/cli/status.js.map +1 -1
  116. package/dist/esm/cli.js +13 -10
  117. package/dist/esm/cli.js.map +1 -1
  118. package/dist/esm/commands.d.ts +13 -4
  119. package/dist/esm/commands.js +135 -72
  120. package/dist/esm/commands.js.map +1 -1
  121. package/dist/esm/config.d.ts +6 -4
  122. package/dist/esm/config.js +126 -75
  123. package/dist/esm/config.js.map +1 -1
  124. package/dist/esm/db.d.ts +2 -2
  125. package/dist/esm/db.js +28 -48
  126. package/dist/esm/db.js.map +1 -1
  127. package/dist/esm/errors.d.ts +1 -1
  128. package/dist/esm/errors.js.map +1 -1
  129. package/dist/esm/features/embed.d.ts +15 -0
  130. package/dist/esm/features/embed.js +137 -42
  131. package/dist/esm/features/embed.js.map +1 -1
  132. package/dist/esm/features/links.js +8 -17
  133. package/dist/esm/features/links.js.map +1 -1
  134. package/dist/esm/features/rank.js +2 -6
  135. package/dist/esm/features/rank.js.map +1 -1
  136. package/dist/esm/features/types.js.map +1 -1
  137. package/dist/esm/index.d.ts +2 -2
  138. package/dist/esm/index.js +1 -1
  139. package/dist/esm/index.js.map +1 -1
  140. package/dist/esm/output.d.ts +3 -0
  141. package/dist/esm/output.js +19 -17
  142. package/dist/esm/output.js.map +1 -1
  143. package/dist/esm/progress.js +2 -4
  144. package/dist/esm/progress.js.map +1 -1
  145. package/dist/esm/scan.d.ts +2 -0
  146. package/dist/esm/scan.js +123 -47
  147. package/dist/esm/scan.js.map +1 -1
  148. package/dist/esm/search-error.js +4 -6
  149. package/dist/esm/search-error.js.map +1 -1
  150. package/dist/esm/traverse.d.ts +8 -0
  151. package/dist/esm/traverse.js +71 -0
  152. package/dist/esm/traverse.js.map +1 -0
  153. package/package.json +7 -3
  154. package/schema.json +12 -16
  155. package/skills/sense/EXAMPLES.md +8 -8
  156. package/skills/sense/SKILL.md +51 -23
  157. package/skills/sense-setup/EXAMPLES.md +24 -21
  158. package/skills/sense-setup/SKILL.md +9 -9
  159. package/dist/cjs/cli/check.d.cts +0 -3
  160. package/dist/cjs/cli/check.d.ts +0 -3
  161. package/dist/cjs/cli/check.js +0 -398
  162. package/dist/cjs/cli/check.js.map +0 -1
  163. package/dist/cjs/cli/query.d.cts +0 -3
  164. package/dist/cjs/cli/query.d.ts +0 -3
  165. package/dist/cjs/cli/query.js.map +0 -1
  166. package/dist/cjs/cli/rebuild.d.cts +0 -3
  167. package/dist/cjs/cli/rebuild.d.ts +0 -3
  168. package/dist/cjs/cli/rebuild.js +0 -50
  169. package/dist/cjs/cli/rebuild.js.map +0 -1
  170. package/dist/esm/cli/check.d.ts +0 -3
  171. package/dist/esm/cli/check.js +0 -94
  172. package/dist/esm/cli/check.js.map +0 -1
  173. package/dist/esm/cli/query.d.ts +0 -3
  174. package/dist/esm/cli/query.js.map +0 -1
  175. package/dist/esm/cli/rebuild.d.ts +0 -3
  176. package/dist/esm/cli/rebuild.js +0 -13
  177. package/dist/esm/cli/rebuild.js.map +0 -1
package/dist/cjs/scan.js CHANGED
@@ -17,11 +17,13 @@ _export(exports, {
17
17
  },
18
18
  get parseFile () {
19
19
  return parseFile;
20
+ },
21
+ get toPosixPath () {
22
+ return toPosixPath;
20
23
  }
21
24
  });
22
25
  var _nodefs = require("node:fs");
23
26
  var _nodepath = require("node:path");
24
- var _fastglob = /*#__PURE__*/ _interop_require_default(require("fast-glob"));
25
27
  var _removemarkdown = /*#__PURE__*/ _interop_require_default(require("remove-markdown"));
26
28
  var _yaml = require("yaml");
27
29
  var _configts = require("./config.js");
@@ -64,39 +66,59 @@ var RESERVED_COLUMNS = new Set([
64
66
  '_mtime',
65
67
  '_size',
66
68
  '_rank',
69
+ '_parse_error',
67
70
  'content',
68
71
  'links',
69
72
  'sections'
70
73
  ]);
74
+ // YAML error codes whose recovery is unambiguous, so the parse is accepted rather than
75
+ // quarantined. Only one qualifies: YAML 1.2 reserves `@` and `` ` `` at the start of a plain
76
+ // scalar for future use, so they can never be valid and the text can only be what was typed
77
+ // (`aliases: [@handle]` -> ["@handle"]). Every other code has a second reading -- an unquoted
78
+ // `:` swallows the keys after it, an unquoted `[..](..)` drops the URL, a duplicate key picks
79
+ // one value in silence -- so it writes values nobody wrote. See plans/frontmatter-parse-policy.md.
80
+ var ACCEPTED_YAML_CODES = new Set([
81
+ 'BAD_SCALAR_START'
82
+ ]);
71
83
  function normalizeText(value) {
72
84
  if (value === null || value === undefined) return '';
73
85
  return String(value).replace(/\s+/g, ' ').trim();
74
86
  }
75
- // Markdown is stripped at index time so snippets read as clean prose (also improves matching, e.g. `**bold**` indexes as `bold`).
76
- // remove-markdown doesn't cover Obsidian wikilinks or table scaffolding, so those are handled first/after.
87
+ // Keeps URL query strings, asset filenames, and HTML attributes out of the index: rare terms
88
+ // carry high IDF, so they outrank prose. remove-markdown misses wikilinks and tables.
77
89
  function stripText(value) {
78
90
  var withoutWikilinks = value.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, '$2').replace(/\[\[([^\]]+)\]\]/g, '$1');
79
91
  var withoutMarkdown = (0, _removemarkdown.default)(withoutWikilinks);
80
92
  var withoutTables = withoutMarkdown.replace(/^\s*\|?[-\s|:]+\|\s*$/gm, '').replace(/\|/g, ' ');
81
93
  return normalizeText(withoutTables);
82
94
  }
95
+ function toPosixPath(relPath) {
96
+ var separator = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : _nodepath.sep;
97
+ return separator === '\\' ? relPath.split(separator).join('/') : relPath;
98
+ }
99
+ // Every command pays listFiles before it answers (the freshness check stats each file), so
100
+ // per-file work here is the hottest path in the package. Everything derivable from the config
101
+ // alone is computed once, above the loop.
102
+ var NO_THROW = {
103
+ throwIfNoEntry: false
104
+ };
83
105
  function listFiles(cfg, baseDir) {
84
106
  var coverage = new Map();
107
+ var posixNeeded = _nodepath.sep === '\\';
85
108
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
86
109
  try {
87
110
  for(var _iterator = (0, _configts.presetNames)(cfg)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
88
111
  var name = _step.value;
89
- var _preset_exclude;
90
112
  var preset = cfg.presets[name];
91
- var matched = _fastglob.default.sync(preset.include, {
92
- cwd: baseDir,
93
- ignore: (_preset_exclude = preset.exclude) !== null && _preset_exclude !== void 0 ? _preset_exclude : []
94
- });
95
113
  var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
96
114
  try {
97
- for(var _iterator1 = matched[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
98
- var relPath = _step1.value;
115
+ for(var _iterator1 = (0, _nodefs.globSync)(preset.include, {
116
+ cwd: baseDir,
117
+ exclude: preset.exclude
118
+ })[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
119
+ var matched = _step1.value;
99
120
  var _coverage_get;
121
+ var relPath = posixNeeded ? toPosixPath(matched) : matched;
100
122
  var set = (_coverage_get = coverage.get(relPath)) !== null && _coverage_get !== void 0 ? _coverage_get : new Set();
101
123
  set.add(name);
102
124
  coverage.set(relPath, set);
@@ -130,29 +152,52 @@ function listFiles(cfg, baseDir) {
130
152
  }
131
153
  }
132
154
  }
133
- var relPaths = _to_consumable_array(coverage.keys()).sort();
134
- return relPaths.map(function(relPath) {
135
- var absPath = (0, _nodepath.join)(baseDir, relPath);
136
- var st = (0, _nodefs.statSync)(absPath);
137
- var presets = _to_consumable_array(coverage.get(relPath)).sort();
138
- var embed = presets.some(function(name) {
139
- return (0, _configts.presetSemanticEnabled)(cfg, name);
140
- });
141
- return {
142
- relPath: relPath,
143
- absPath: absPath,
144
- mtimeMs: st.mtimeMs,
145
- size: st.size,
146
- presets: presets,
147
- embed: embed
148
- };
149
- });
155
+ // Which presets want vectors is a property of the config, not of any file.
156
+ var embedding = (0, _configts.embedEnabled)(cfg);
157
+ var semanticPresets = embedding ? new Set((0, _configts.presetNames)(cfg).filter(function(name) {
158
+ return (0, _configts.presetSemanticEnabled)(cfg, name);
159
+ })) : null;
160
+ var files = [];
161
+ var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
162
+ try {
163
+ for(var _iterator2 = _to_consumable_array(coverage.keys()).sort()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
164
+ var relPath1 = _step2.value;
165
+ var absPath = (0, _nodepath.join)(baseDir, relPath1); // join re-applies the platform separator for fs calls
166
+ // node:fs glob matches directories and dangling symlinks; fast-glob returned neither, so
167
+ // one stat filters both back out (throwIfNoEntry keeps a dangling link from throwing).
168
+ var st = (0, _nodefs.statSync)(absPath, NO_THROW);
169
+ if (!(st === null || st === void 0 ? void 0 : st.isFile())) continue;
170
+ var presets = _to_consumable_array(coverage.get(relPath1)).sort();
171
+ var embed = semanticPresets !== null && presets.some(function(name) {
172
+ return semanticPresets.has(name);
173
+ });
174
+ files.push({
175
+ relPath: relPath1,
176
+ absPath: absPath,
177
+ mtimeMs: st.mtimeMs,
178
+ size: st.size,
179
+ presets: presets,
180
+ embed: embed
181
+ });
182
+ }
183
+ } catch (err) {
184
+ _didIteratorError2 = true;
185
+ _iteratorError2 = err;
186
+ } finally{
187
+ try {
188
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
189
+ _iterator2.return();
190
+ }
191
+ } finally{
192
+ if (_didIteratorError2) {
193
+ throw _iteratorError2;
194
+ }
195
+ }
196
+ }
197
+ return files;
150
198
  }
151
- // SQLite storage classes follow the YAML scalar: strings -> TEXT, booleans and whole
152
- // numbers -> INTEGER, fractions -> REAL, everything else (lists, maps) -> JSON TEXT.
153
- // Booleans have no SQLite type of their own; 1/0 is the convention, so `WHERE flag = 1`
154
- // matches and `WHERE flag = 'true'` cannot -- `map` prints the observed type per field so
155
- // that mismatch is visible rather than a silent zero-row answer.
199
+ // Storage class follows the YAML scalar. Booleans store as 1/0, so `WHERE flag = 1` matches
200
+ // and `WHERE flag = 'true'` cannot; `map` prints observed types so the mismatch is visible.
156
201
  function mapValue(value) {
157
202
  if (value === null || value === undefined) return null;
158
203
  if (typeof value === 'boolean') return BigInt(value ? 1 : 0);
@@ -178,33 +223,93 @@ function splitFrontmatter(raw) {
178
223
  body: rest.slice(close.index + close[0].length)
179
224
  };
180
225
  }
181
- // Lenient by design: parseDocument collects syntax errors as data and still yields values,
182
- // so Obsidian-style frontmatter (e.g. an alias starting with @) survives with a warning.
226
+ // A well-formed document can still hold a value nobody meant: `created: {{date}}` is valid
227
+ // YAML for a flow map used as a mapping key, so it raises no error and stores
228
+ // {"{ date }": null}. No error code can catch that, but yaml notices the stringified key, so
229
+ // this reports it with the path instead (yaml's own warning has none, fires once per document,
230
+ // and is what trains readers to discard stderr).
231
+ function warnStringifiedKeys(relPath, doc, warnings) {
232
+ var found = false;
233
+ // Nested, not top level: `created: {{date}}` puts the collection key one level down, inside
234
+ // the flow map that `{{...}}` parses as.
235
+ (0, _yaml.visit)(doc, {
236
+ Pair: function Pair(_key, pair) {
237
+ if (!(0, _yaml.isCollection)(pair.key)) return undefined;
238
+ found = true;
239
+ return _yaml.visit.BREAK;
240
+ }
241
+ });
242
+ // One per file: a template repeats the same mistake on every field it stamps.
243
+ if (found) warnings.push("warning: ".concat(relPath, " frontmatter has a key that is itself a list or mapping, stored as text; this is usually an unrendered template placeholder like {{date}}"));
244
+ }
245
+ // Accept a clean parse, and one whose every error is unambiguous (ACCEPTED_YAML_CODES).
246
+ // Anything else is quarantined: no frontmatter columns at all, and `_parse_error` carries the
247
+ // reason. Recovering it would write values nobody wrote, which is worse than absence because
248
+ // no query can see it. The file is still indexed -- content, links and sections never touch
249
+ // frontmatter -- so a broken note stays searchable while it is being hunted for.
250
+ // yaml's message continues onto a source excerpt, so the first line is the sentence -- minus
251
+ // the colon that introduced the part being dropped.
252
+ function firstLine(message) {
253
+ return message.split('\n')[0].replace(/:\s*$/, '');
254
+ }
183
255
  function parseFrontmatter(relPath, fm, warnings) {
184
- var doc = (0, _yaml.parseDocument)(fm);
185
- if (doc.errors.length > 0) {
186
- warnings.push("warning: ".concat(relPath, " frontmatter has ").concat(doc.errors.length, " syntax error(s) (").concat(doc.errors[0].message.split('\n')[0], "); parsed leniently"));
256
+ // logLevel silences yaml's own pathless warnings; warnStringifiedKeys re-reports the one
257
+ // that carries information, with the file it came from.
258
+ var doc = (0, _yaml.parseDocument)(fm, {
259
+ logLevel: 'silent'
260
+ });
261
+ var refused = doc.errors.filter(function(err) {
262
+ return !ACCEPTED_YAML_CODES.has(err.code);
263
+ });
264
+ if (refused.length > 0) {
265
+ var detail = refused.length > 1 ? " (and ".concat(refused.length - 1, " more)") : '';
266
+ var parseError = "".concat(firstLine(refused[0].message)).concat(detail);
267
+ warnings.push("warning: ".concat(relPath, " frontmatter did not parse, so none of it is indexed: ").concat(parseError));
268
+ return {
269
+ data: {},
270
+ parseError: parseError
271
+ };
187
272
  }
188
273
  var data;
189
274
  try {
190
275
  data = doc.toJS();
191
276
  } catch (err) {
192
- warnings.push("warning: ".concat(relPath, " has unparseable frontmatter (").concat(err.message.split('\n')[0], "); indexing without it"));
193
- return {};
277
+ // Reaches here with doc.errors empty: `title: **Bold**` parses, then opens an alias on
278
+ // materialisation. An empty error list is not a successful parse.
279
+ var parseError1 = firstLine(err.message);
280
+ warnings.push("warning: ".concat(relPath, " frontmatter did not parse, so none of it is indexed: ").concat(parseError1));
281
+ return {
282
+ data: {},
283
+ parseError: parseError1
284
+ };
194
285
  }
195
- if (data === null || data === undefined) return {};
286
+ if (data === null || data === undefined) return {
287
+ data: {},
288
+ parseError: null
289
+ };
196
290
  if ((typeof data === "undefined" ? "undefined" : _type_of(data)) !== 'object' || Array.isArray(data)) {
197
- warnings.push("warning: ".concat(relPath, " frontmatter is not a key-value mapping; ignoring it"));
198
- return {};
291
+ var parseError2 = 'frontmatter is not a key-value mapping';
292
+ warnings.push("warning: ".concat(relPath, " ").concat(parseError2, "; none of it is indexed"));
293
+ return {
294
+ data: {},
295
+ parseError: parseError2
296
+ };
199
297
  }
200
- return data;
298
+ warnStringifiedKeys(relPath, doc, warnings);
299
+ return {
300
+ data: data,
301
+ parseError: null
302
+ };
201
303
  }
202
304
  function parseFile(file) {
203
305
  var extractors = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : [];
204
306
  var raw = (0, _nodefs.readFileSync)(file.absPath, 'utf8');
205
307
  var warnings = [];
206
308
  var _splitFrontmatter = splitFrontmatter(raw), fm = _splitFrontmatter.fm, content = _splitFrontmatter.body;
207
- var data = fm === null ? {} : parseFrontmatter(file.relPath, fm, warnings);
309
+ var _ref = fm === null ? {
310
+ data: {},
311
+ parseError: null
312
+ } : parseFrontmatter(file.relPath, fm, warnings), data = _ref.data, parseError = _ref.parseError;
208
313
  var mapped = {};
209
314
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
210
315
  try {
@@ -244,6 +349,7 @@ function parseFile(file) {
244
349
  size: file.size,
245
350
  presets: file.presets,
246
351
  data: mapped,
352
+ parseError: parseError,
247
353
  search: search,
248
354
  extracted: Object.fromEntries(extractors.filter(function(f) {
249
355
  return f.extract;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan.ts"],"sourcesContent":["import { readFileSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport fastGlob from 'fast-glob';\nimport removeMarkdown from 'remove-markdown';\nimport { parseDocument } from 'yaml';\nimport type { Config } from './config.ts';\nimport { presetNames, presetSemanticEnabled } from './config.ts';\nimport type { Feature } from './features/types.ts';\n\n// Filesystem -> rows. Pure data in, data + warnings out; db.ts does the SQL.\n\n// Reserved: colliding with these would clash with the `frontmatter` table's own columns or\n// the other tables (`content`, `links`, `sections`). Exported so db.ts's frontmatter upsert\n// can tell a feature-owned column (e.g. rank's `_rank`) apart from a parsed frontmatter\n// column and leave the former alone on reparse.\nexport const RESERVED_COLUMNS = new Set(['path', '_mtime', '_size', '_rank', 'content', 'links', 'sections']);\n\nfunction normalizeText(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value).replace(/\\s+/g, ' ').trim();\n}\n\n// Markdown is stripped at index time so snippets read as clean prose (also improves matching, e.g. `**bold**` indexes as `bold`).\n// remove-markdown doesn't cover Obsidian wikilinks or table scaffolding, so those are handled first/after.\nfunction stripText(value: string): string {\n const withoutWikilinks = value.replace(/\\[\\[([^\\]|]+)\\|([^\\]]+)\\]\\]/g, '$2').replace(/\\[\\[([^\\]]+)\\]\\]/g, '$1');\n const withoutMarkdown = removeMarkdown(withoutWikilinks);\n const withoutTables = withoutMarkdown.replace(/^\\s*\\|?[-\\s|:]+\\|\\s*$/gm, '').replace(/\\|/g, ' ');\n return normalizeText(withoutTables);\n}\n\nexport interface FileStat {\n relPath: string;\n absPath: string;\n mtimeMs: number;\n size: number;\n presets: string[]; // every declared preset covering this file (>= 1; union, overlap allowed)\n embed: boolean; // true iff any covering preset has semantic !== false\n}\n\n// Presets are the file selection: each preset's include/exclude picks its files (fast-glob,\n// tsconfig-standard semantics, resolved relative to baseDir). Unlike the old layer model,\n// presets are views, not partitions -- they may overlap freely, and a file's set of covering\n// presets (not a single owner) is what indexing and embedding derive from. A preset whose\n// globs match nothing is valid. Files matched by no preset at all are simply not indexed.\nexport function listFiles(cfg: Config, baseDir: string): FileStat[] {\n const coverage = new Map<string, Set<string>>();\n for (const name of presetNames(cfg)) {\n const preset = cfg.presets[name];\n const matched = fastGlob.sync(preset.include, { cwd: baseDir, ignore: preset.exclude ?? [] });\n for (const relPath of matched) {\n const set = coverage.get(relPath) ?? new Set<string>();\n set.add(name);\n coverage.set(relPath, set);\n }\n }\n\n const relPaths = [...coverage.keys()].sort();\n return relPaths.map((relPath) => {\n const absPath = join(baseDir, relPath);\n const st = statSync(absPath);\n const presets = [...(coverage.get(relPath) as Set<string>)].sort();\n const embed = presets.some((name) => presetSemanticEnabled(cfg, name));\n return { relPath, absPath, mtimeMs: st.mtimeMs, size: st.size, presets, embed };\n });\n}\n\nexport interface ParsedDoc {\n relPath: string;\n mtimeMs: number;\n size: number;\n presets: string[];\n data: Record<string, string | number | bigint | null>;\n // title/summary are duplicated from frontmatter so bm25() can weight them above the body text.\n search: { title: string; summary: string; text: string };\n // Per-feature extraction results, keyed by feature name; features store them at reconcile.\n extracted: Record<string, unknown>;\n}\n\n// SQLite storage classes follow the YAML scalar: strings -> TEXT, booleans and whole\n// numbers -> INTEGER, fractions -> REAL, everything else (lists, maps) -> JSON TEXT.\n// Booleans have no SQLite type of their own; 1/0 is the convention, so `WHERE flag = 1`\n// matches and `WHERE flag = 'true'` cannot -- `map` prints the observed type per field so\n// that mismatch is visible rather than a silent zero-row answer.\nfunction mapValue(value: unknown): string | number | bigint | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'boolean') return BigInt(value ? 1 : 0);\n if (typeof value === 'number') return Number.isSafeInteger(value) ? BigInt(value) : value;\n if (typeof value === 'string') return value;\n return JSON.stringify(value);\n}\n\n// The delimiter split is all this package used gray-matter for.\nfunction splitFrontmatter(raw: string): { fm: string | null; body: string } {\n const open = raw.match(/^---\\r?\\n/);\n if (!open) return { fm: null, body: raw };\n const rest = raw.slice(open[0].length);\n const close = rest.match(/^---\\r?(\\n|$)/m);\n if (!close || close.index === undefined) return { fm: null, body: raw };\n return { fm: rest.slice(0, close.index), body: rest.slice(close.index + close[0].length) };\n}\n\n// Lenient by design: parseDocument collects syntax errors as data and still yields values,\n// so Obsidian-style frontmatter (e.g. an alias starting with @) survives with a warning.\nfunction parseFrontmatter(relPath: string, fm: string, warnings: string[]): Record<string, unknown> {\n const doc = parseDocument(fm);\n if (doc.errors.length > 0) {\n warnings.push(`warning: ${relPath} frontmatter has ${doc.errors.length} syntax error(s) (${doc.errors[0].message.split('\\n')[0]}); parsed leniently`);\n }\n let data: unknown;\n try {\n data = doc.toJS();\n } catch (err) {\n warnings.push(`warning: ${relPath} has unparseable frontmatter (${(err as Error).message.split('\\n')[0]}); indexing without it`);\n return {};\n }\n if (data === null || data === undefined) return {};\n if (typeof data !== 'object' || Array.isArray(data)) {\n warnings.push(`warning: ${relPath} frontmatter is not a key-value mapping; ignoring it`);\n return {};\n }\n return data as Record<string, unknown>;\n}\n\nexport function parseFile(file: FileStat, extractors: Feature[] = []): { doc: ParsedDoc; warnings: string[] } {\n const raw = readFileSync(file.absPath, 'utf8');\n const warnings: string[] = [];\n\n const { fm, body: content } = splitFrontmatter(raw);\n const data = fm === null ? {} : parseFrontmatter(file.relPath, fm, warnings);\n const mapped: Record<string, string | number | bigint | null> = {};\n\n for (const key of Object.keys(data)) {\n if (RESERVED_COLUMNS.has(key)) {\n warnings.push(`warning: ${file.relPath} has a frontmatter key named \"${key}\", which is reserved; ignoring it`);\n continue;\n }\n mapped[key] = mapValue(data[key]);\n }\n\n // title/summary are plain YAML strings -- whitespace-collapse only;\n // the prose gets the full markdown strip.\n const search = { title: normalizeText(data.title), summary: normalizeText(data.summary), text: stripText(content) };\n\n return {\n doc: {\n relPath: file.relPath,\n mtimeMs: file.mtimeMs,\n size: file.size,\n presets: file.presets,\n data: mapped,\n search,\n extracted: Object.fromEntries(extractors.filter((f) => f.extract).map((f) => [f.name, f.extract?.(raw, content, search)])),\n },\n warnings,\n };\n}\n"],"names":["RESERVED_COLUMNS","listFiles","parseFile","Set","normalizeText","value","undefined","String","replace","trim","stripText","withoutWikilinks","withoutMarkdown","removeMarkdown","withoutTables","cfg","baseDir","coverage","Map","presetNames","name","preset","presets","matched","fastGlob","sync","include","cwd","ignore","exclude","relPath","set","get","add","relPaths","keys","sort","map","absPath","join","st","statSync","embed","some","presetSemanticEnabled","mtimeMs","size","mapValue","BigInt","Number","isSafeInteger","JSON","stringify","splitFrontmatter","raw","open","match","fm","body","rest","slice","length","close","index","parseFrontmatter","warnings","doc","parseDocument","errors","push","message","split","data","toJS","err","Array","isArray","file","extractors","readFileSync","content","mapped","Object","key","has","search","title","summary","text","extracted","fromEntries","filter","f","extract"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan.ts"],"sourcesContent":["import { globSync, readFileSync, statSync } from 'node:fs';\nimport { join, sep } from 'node:path';\nimport removeMarkdown from 'remove-markdown';\nimport { isCollection, parseDocument, visit } from 'yaml';\nimport type { Config } from './config.ts';\nimport { embedEnabled, presetNames, presetSemanticEnabled } from './config.ts';\nimport type { Feature } from './features/types.ts';\n\n// Filesystem -> rows. Pure data in, data + warnings out; db.ts does the SQL.\n\n// Frontmatter keys that would collide with table columns. Exported so db.ts's upsert can tell\n// a feature-owned column (`_rank`) from a parsed one and leave it alone on reparse.\nexport const RESERVED_COLUMNS = new Set(['path', '_mtime', '_size', '_rank', '_parse_error', 'content', 'links', 'sections']);\n\n// YAML error codes whose recovery is unambiguous, so the parse is accepted rather than\n// quarantined. Only one qualifies: YAML 1.2 reserves `@` and `` ` `` at the start of a plain\n// scalar for future use, so they can never be valid and the text can only be what was typed\n// (`aliases: [@handle]` -> [\"@handle\"]). Every other code has a second reading -- an unquoted\n// `:` swallows the keys after it, an unquoted `[..](..)` drops the URL, a duplicate key picks\n// one value in silence -- so it writes values nobody wrote. See plans/frontmatter-parse-policy.md.\nconst ACCEPTED_YAML_CODES = new Set(['BAD_SCALAR_START']);\n\nfunction normalizeText(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value).replace(/\\s+/g, ' ').trim();\n}\n\n// Keeps URL query strings, asset filenames, and HTML attributes out of the index: rare terms\n// carry high IDF, so they outrank prose. remove-markdown misses wikilinks and tables.\nfunction stripText(value: string): string {\n const withoutWikilinks = value.replace(/\\[\\[([^\\]|]+)\\|([^\\]]+)\\]\\]/g, '$2').replace(/\\[\\[([^\\]]+)\\]\\]/g, '$1');\n const withoutMarkdown = removeMarkdown(withoutWikilinks);\n const withoutTables = withoutMarkdown.replace(/^\\s*\\|?[-\\s|:]+\\|\\s*$/gm, '').replace(/\\|/g, ' ');\n return normalizeText(withoutTables);\n}\n\nexport interface FileStat {\n relPath: string;\n absPath: string;\n mtimeMs: number;\n size: number;\n presets: string[]; // every declared preset covering this file (>= 1; union, overlap allowed)\n embed: boolean; // true iff a model is named and some covering preset has semantic on\n}\n\n// Presets are views, not partitions: they overlap freely, and a file's covering set (not one\n// owner) drives indexing. Globs resolve relative to baseDir; unmatched files are not indexed.\nexport function toPosixPath(relPath: string, separator: string = sep): string {\n return separator === '\\\\' ? relPath.split(separator).join('/') : relPath;\n}\n\n// Every command pays listFiles before it answers (the freshness check stats each file), so\n// per-file work here is the hottest path in the package. Everything derivable from the config\n// alone is computed once, above the loop.\nconst NO_THROW = { throwIfNoEntry: false } as const;\n\nexport function listFiles(cfg: Config, baseDir: string): FileStat[] {\n const coverage = new Map<string, Set<string>>();\n const posixNeeded = sep === '\\\\';\n for (const name of presetNames(cfg)) {\n const preset = cfg.presets[name];\n for (const matched of globSync(preset.include, { cwd: baseDir, exclude: preset.exclude })) {\n const relPath = posixNeeded ? toPosixPath(matched) : matched;\n const set = coverage.get(relPath) ?? new Set<string>();\n set.add(name);\n coverage.set(relPath, set);\n }\n }\n\n // Which presets want vectors is a property of the config, not of any file.\n const embedding = embedEnabled(cfg);\n const semanticPresets = embedding ? new Set(presetNames(cfg).filter((name) => presetSemanticEnabled(cfg, name))) : null;\n\n const files: FileStat[] = [];\n for (const relPath of [...coverage.keys()].sort()) {\n const absPath = join(baseDir, relPath); // join re-applies the platform separator for fs calls\n // node:fs glob matches directories and dangling symlinks; fast-glob returned neither, so\n // one stat filters both back out (throwIfNoEntry keeps a dangling link from throwing).\n const st = statSync(absPath, NO_THROW);\n if (!st?.isFile()) continue;\n const presets = [...(coverage.get(relPath) as Set<string>)].sort();\n const embed = semanticPresets !== null && presets.some((name) => semanticPresets.has(name));\n files.push({ relPath, absPath, mtimeMs: st.mtimeMs, size: st.size, presets, embed });\n }\n return files;\n}\n\nexport interface ParsedDoc {\n relPath: string;\n mtimeMs: number;\n size: number;\n presets: string[];\n data: Record<string, string | number | bigint | null>;\n // NULL when the frontmatter parsed, the first YAML message otherwise. In the row rather than\n // a side table so `SELECT *` and any `IS NULL` investigation trip over it without being asked.\n parseError: string | null;\n // title/summary are duplicated from frontmatter so bm25() can weight them above the body text.\n search: { title: string; summary: string; text: string };\n // Per-feature extraction results, keyed by feature name; features store them at reconcile.\n extracted: Record<string, unknown>;\n}\n\n// Storage class follows the YAML scalar. Booleans store as 1/0, so `WHERE flag = 1` matches\n// and `WHERE flag = 'true'` cannot; `map` prints observed types so the mismatch is visible.\nfunction mapValue(value: unknown): string | number | bigint | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'boolean') return BigInt(value ? 1 : 0);\n if (typeof value === 'number') return Number.isSafeInteger(value) ? BigInt(value) : value;\n if (typeof value === 'string') return value;\n return JSON.stringify(value);\n}\n\n// The delimiter split is all this package used gray-matter for.\nfunction splitFrontmatter(raw: string): { fm: string | null; body: string } {\n const open = raw.match(/^---\\r?\\n/);\n if (!open) return { fm: null, body: raw };\n const rest = raw.slice(open[0].length);\n const close = rest.match(/^---\\r?(\\n|$)/m);\n if (!close || close.index === undefined) return { fm: null, body: raw };\n return { fm: rest.slice(0, close.index), body: rest.slice(close.index + close[0].length) };\n}\n\n// A well-formed document can still hold a value nobody meant: `created: {{date}}` is valid\n// YAML for a flow map used as a mapping key, so it raises no error and stores\n// {\"{ date }\": null}. No error code can catch that, but yaml notices the stringified key, so\n// this reports it with the path instead (yaml's own warning has none, fires once per document,\n// and is what trains readers to discard stderr).\nfunction warnStringifiedKeys(relPath: string, doc: ReturnType<typeof parseDocument>, warnings: string[]): void {\n let found = false;\n // Nested, not top level: `created: {{date}}` puts the collection key one level down, inside\n // the flow map that `{{...}}` parses as.\n visit(doc, {\n Pair(_key, pair) {\n if (!isCollection(pair.key)) return undefined;\n found = true;\n return visit.BREAK;\n },\n });\n // One per file: a template repeats the same mistake on every field it stamps.\n if (found) warnings.push(`warning: ${relPath} frontmatter has a key that is itself a list or mapping, stored as text; this is usually an unrendered template placeholder like {{date}}`);\n}\n\n// Accept a clean parse, and one whose every error is unambiguous (ACCEPTED_YAML_CODES).\n// Anything else is quarantined: no frontmatter columns at all, and `_parse_error` carries the\n// reason. Recovering it would write values nobody wrote, which is worse than absence because\n// no query can see it. The file is still indexed -- content, links and sections never touch\n// frontmatter -- so a broken note stays searchable while it is being hunted for.\n// yaml's message continues onto a source excerpt, so the first line is the sentence -- minus\n// the colon that introduced the part being dropped.\nfunction firstLine(message: string): string {\n return message.split('\\n')[0].replace(/:\\s*$/, '');\n}\n\nfunction parseFrontmatter(relPath: string, fm: string, warnings: string[]): { data: Record<string, unknown>; parseError: string | null } {\n // logLevel silences yaml's own pathless warnings; warnStringifiedKeys re-reports the one\n // that carries information, with the file it came from.\n const doc = parseDocument(fm, { logLevel: 'silent' });\n const refused = doc.errors.filter((err) => !ACCEPTED_YAML_CODES.has(err.code));\n if (refused.length > 0) {\n const detail = refused.length > 1 ? ` (and ${refused.length - 1} more)` : '';\n const parseError = `${firstLine(refused[0].message)}${detail}`;\n warnings.push(`warning: ${relPath} frontmatter did not parse, so none of it is indexed: ${parseError}`);\n return { data: {}, parseError };\n }\n\n let data: unknown;\n try {\n data = doc.toJS();\n } catch (err) {\n // Reaches here with doc.errors empty: `title: **Bold**` parses, then opens an alias on\n // materialisation. An empty error list is not a successful parse.\n const parseError = firstLine((err as Error).message);\n warnings.push(`warning: ${relPath} frontmatter did not parse, so none of it is indexed: ${parseError}`);\n return { data: {}, parseError };\n }\n\n if (data === null || data === undefined) return { data: {}, parseError: null };\n if (typeof data !== 'object' || Array.isArray(data)) {\n const parseError = 'frontmatter is not a key-value mapping';\n warnings.push(`warning: ${relPath} ${parseError}; none of it is indexed`);\n return { data: {}, parseError };\n }\n warnStringifiedKeys(relPath, doc, warnings);\n return { data: data as Record<string, unknown>, parseError: null };\n}\n\nexport function parseFile(file: FileStat, extractors: Feature[] = []): { doc: ParsedDoc; warnings: string[] } {\n const raw = readFileSync(file.absPath, 'utf8');\n const warnings: string[] = [];\n\n const { fm, body: content } = splitFrontmatter(raw);\n const { data, parseError } = fm === null ? { data: {} as Record<string, unknown>, parseError: null } : parseFrontmatter(file.relPath, fm, warnings);\n const mapped: Record<string, string | number | bigint | null> = {};\n\n for (const key of Object.keys(data)) {\n if (RESERVED_COLUMNS.has(key)) {\n warnings.push(`warning: ${file.relPath} has a frontmatter key named \"${key}\", which is reserved; ignoring it`);\n continue;\n }\n mapped[key] = mapValue(data[key]);\n }\n\n // title/summary are plain YAML strings -- whitespace-collapse only;\n // the prose gets the full markdown strip.\n const search = { title: normalizeText(data.title), summary: normalizeText(data.summary), text: stripText(content) };\n\n return {\n doc: {\n relPath: file.relPath,\n mtimeMs: file.mtimeMs,\n size: file.size,\n presets: file.presets,\n data: mapped,\n parseError,\n search,\n extracted: Object.fromEntries(extractors.filter((f) => f.extract).map((f) => [f.name, f.extract?.(raw, content, search)])),\n },\n warnings,\n };\n}\n"],"names":["RESERVED_COLUMNS","listFiles","parseFile","toPosixPath","Set","ACCEPTED_YAML_CODES","normalizeText","value","undefined","String","replace","trim","stripText","withoutWikilinks","withoutMarkdown","removeMarkdown","withoutTables","relPath","separator","sep","split","join","NO_THROW","throwIfNoEntry","cfg","baseDir","coverage","Map","posixNeeded","presetNames","name","preset","presets","globSync","include","cwd","exclude","matched","set","get","add","embedding","embedEnabled","semanticPresets","filter","presetSemanticEnabled","files","keys","sort","absPath","st","statSync","isFile","embed","some","has","push","mtimeMs","size","mapValue","BigInt","Number","isSafeInteger","JSON","stringify","splitFrontmatter","raw","open","match","fm","body","rest","slice","length","close","index","warnStringifiedKeys","doc","warnings","found","visit","Pair","_key","pair","isCollection","key","BREAK","firstLine","message","parseFrontmatter","parseDocument","logLevel","refused","errors","err","code","detail","parseError","data","toJS","Array","isArray","file","extractors","readFileSync","content","mapped","Object","search","title","summary","text","extracted","fromEntries","f","extract","map"],"mappings":"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"}
@@ -9,10 +9,8 @@ Object.defineProperty(exports, "searchError", {
9
9
  }
10
10
  });
11
11
  var _errorsts = require("./errors.js");
12
- // FTS5 reads these as operators, so a bare term containing one is a syntax error or --
13
- // worse -- a column filter: `end-to-end` parses as a filter on column `to`, and SQLite
14
- // reports `no such column: to`, which is a true statement about the parse and a false
15
- // one about the input. Both field reports on 0.6.0 misdiagnosed that message.
12
+ // FTS5 reads these as operators, so `end-to-end` parses as a filter on column `to` and errors
13
+ // `no such column: to` -- true about the parse, misleading about the input.
16
14
  var FTS5_OPERATORS = /[-'"/.:^*()]/;
17
15
  function searchError(err, terms, scope) {
18
16
  var _terms_match;
@@ -35,8 +33,8 @@ function searchError(err, terms, scope) {
35
33
  }).join(', '), " is FTS5 syntax, not literal text; search for it literally by double-quoting: '\"").concat(suspects[0], "\"'. Searchable columns are title, summary, text."));
36
34
  }
37
35
  if (col !== undefined && scope !== undefined) {
38
- return new _errorsts.SenseError('SEARCH_SYNTAX', "".concat(message, " -- the where condition (").concat(scope, ") references it; frontmatter columns are listed by sense query \"SELECT name FROM pragma_table_info('frontmatter')\"."));
36
+ return new _errorsts.SenseError('SEARCH_SYNTAX', "".concat(message, " -- the where condition (").concat(scope, ") references it; frontmatter columns are listed by sense sql \"SELECT name FROM pragma_table_info('frontmatter')\"."));
39
37
  }
40
- return new _errorsts.SenseError('SEARCH_SYNTAX', "".concat(message, " -- searchable columns are title, summary, text; frontmatter fields are queried with --where or sense query (list them with pragma_table_info('frontmatter'))."));
38
+ return new _errorsts.SenseError('SEARCH_SYNTAX', "".concat(message, " -- searchable columns are title, summary, text; frontmatter fields are queried with --where or sense sql (list them with pragma_table_info('frontmatter'))."));
41
39
  }
42
40
  /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/search-error.ts"],"sourcesContent":["import { SenseError } from './errors.ts';\n\n// FTS5 reads these as operators, so a bare term containing one is a syntax error or --\n// worse -- a column filter: `end-to-end` parses as a filter on column `to`, and SQLite\n// reports `no such column: to`, which is a true statement about the parse and a false\n// one about the input. Both field reports on 0.6.0 misdiagnosed that message.\nconst FTS5_OPERATORS = /[-'\"/.:^*()]/;\n\nexport function searchError(err: Error, terms: string, scope?: string): Error {\n const message = err.message;\n if (!/no such column|fts5: syntax error|malformed MATCH/.test(message)) return err;\n const suspects = (terms.match(/\\S+/g) ?? []).filter((t) => !t.startsWith('\"') && FTS5_OPERATORS.test(t));\n // Blame the terms only when the failing token actually came from one -- a typo'd column\n // in --where (or the tree's default scope) raises \"no such column\" through this same\n // statement, and naming a term for it would state a false fact about the input.\n const col = /no such column: (\\S+)/.exec(message)?.[1];\n const fromTerms = col === undefined ? suspects.length > 0 : suspects.some((t) => t.split(/[^\\p{L}\\p{N}]+/u).includes(col));\n if (fromTerms && suspects.length > 0) {\n return new SenseError('SEARCH_SYNTAX', `${message} -- the punctuation in ${suspects.map((t) => `\\`${t}\\``).join(', ')} is FTS5 syntax, not literal text; search for it literally by double-quoting: '\"${suspects[0]}\"'. Searchable columns are title, summary, text.`);\n }\n if (col !== undefined && scope !== undefined) {\n return new SenseError('SEARCH_SYNTAX', `${message} -- the where condition (${scope}) references it; frontmatter columns are listed by sense query \"SELECT name FROM pragma_table_info('frontmatter')\".`);\n }\n return new SenseError('SEARCH_SYNTAX', `${message} -- searchable columns are title, summary, text; frontmatter fields are queried with --where or sense query (list them with pragma_table_info('frontmatter')).`);\n}\n"],"names":["searchError","FTS5_OPERATORS","err","terms","scope","message","test","suspects","match","filter","t","startsWith","col","exec","fromTerms","undefined","length","some","split","includes","SenseError","map","join"],"mappings":";;;;+BAQgBA;;;eAAAA;;;wBARW;AAE3B,uFAAuF;AACvF,uFAAuF;AACvF,sFAAsF;AACtF,8EAA8E;AAC9E,IAAMC,iBAAiB;AAEhB,SAASD,YAAYE,GAAU,EAAEC,KAAa,EAAEC,KAAc;QAGjDD;QAIN;IANZ,IAAME,UAAUH,IAAIG,OAAO;IAC3B,IAAI,CAAC,oDAAoDC,IAAI,CAACD,UAAU,OAAOH;IAC/E,IAAMK,WAAW,EAACJ,eAAAA,MAAMK,KAAK,CAAC,qBAAZL,0BAAAA,eAAuB,EAAE,EAAEM,MAAM,CAAC,SAACC;eAAM,CAACA,EAAEC,UAAU,CAAC,QAAQV,eAAeK,IAAI,CAACI;;IACrG,wFAAwF;IACxF,qFAAqF;IACrF,gFAAgF;IAChF,IAAME,OAAM,QAAA,wBAAwBC,IAAI,CAACR,sBAA7B,4BAAA,KAAuC,CAAC,EAAE;IACtD,IAAMS,YAAYF,QAAQG,YAAYR,SAASS,MAAM,GAAG,IAAIT,SAASU,IAAI,CAAC,SAACP;eAAMA,EAAEQ,KAAK,CAAC,8maAAmBC,QAAQ,CAACP;;IACrH,IAAIE,aAAaP,SAASS,MAAM,GAAG,GAAG;QACpC,OAAO,IAAII,oBAAU,CAAC,iBAAiB,AAAC,GAAmCb,OAAjCF,SAAQ,2BAAsJE,OAA7HA,SAASc,GAAG,CAAC,SAACX;mBAAM,AAAC,IAAM,OAAFA,GAAE;WAAKY,IAAI,CAAC,OAAM,qFAA8F,OAAZf,QAAQ,CAAC,EAAE,EAAC;IACtN;IACA,IAAIK,QAAQG,aAAaX,UAAUW,WAAW;QAC5C,OAAO,IAAIK,oBAAU,CAAC,iBAAiB,AAAC,GAAqChB,OAAnCC,SAAQ,6BAAiC,OAAND,OAAM;IACrF;IACA,OAAO,IAAIgB,oBAAU,CAAC,iBAAiB,AAAC,GAAU,OAARf,SAAQ;AACpD"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/search-error.ts"],"sourcesContent":["import { SenseError } from './errors.ts';\n\n// FTS5 reads these as operators, so `end-to-end` parses as a filter on column `to` and errors\n// `no such column: to` -- true about the parse, misleading about the input.\nconst FTS5_OPERATORS = /[-'\"/.:^*()]/;\n\nexport function searchError(err: Error, terms: string, scope?: string): Error {\n const message = err.message;\n if (!/no such column|fts5: syntax error|malformed MATCH/.test(message)) return err;\n const suspects = (terms.match(/\\S+/g) ?? []).filter((t) => !t.startsWith('\"') && FTS5_OPERATORS.test(t));\n // Blame the terms only when the failing token actually came from one -- a typo'd column\n // in --where (or the tree's default scope) raises \"no such column\" through this same\n // statement, and naming a term for it would state a false fact about the input.\n const col = /no such column: (\\S+)/.exec(message)?.[1];\n const fromTerms = col === undefined ? suspects.length > 0 : suspects.some((t) => t.split(/[^\\p{L}\\p{N}]+/u).includes(col));\n if (fromTerms && suspects.length > 0) {\n return new SenseError('SEARCH_SYNTAX', `${message} -- the punctuation in ${suspects.map((t) => `\\`${t}\\``).join(', ')} is FTS5 syntax, not literal text; search for it literally by double-quoting: '\"${suspects[0]}\"'. Searchable columns are title, summary, text.`);\n }\n if (col !== undefined && scope !== undefined) {\n return new SenseError('SEARCH_SYNTAX', `${message} -- the where condition (${scope}) references it; frontmatter columns are listed by sense sql \"SELECT name FROM pragma_table_info('frontmatter')\".`);\n }\n return new SenseError('SEARCH_SYNTAX', `${message} -- searchable columns are title, summary, text; frontmatter fields are queried with --where or sense sql (list them with pragma_table_info('frontmatter')).`);\n}\n"],"names":["searchError","FTS5_OPERATORS","err","terms","scope","message","test","suspects","match","filter","t","startsWith","col","exec","fromTerms","undefined","length","some","split","includes","SenseError","map","join"],"mappings":";;;;+BAMgBA;;;eAAAA;;;wBANW;AAE3B,8FAA8F;AAC9F,4EAA4E;AAC5E,IAAMC,iBAAiB;AAEhB,SAASD,YAAYE,GAAU,EAAEC,KAAa,EAAEC,KAAc;QAGjDD;QAIN;IANZ,IAAME,UAAUH,IAAIG,OAAO;IAC3B,IAAI,CAAC,oDAAoDC,IAAI,CAACD,UAAU,OAAOH;IAC/E,IAAMK,WAAW,EAACJ,eAAAA,MAAMK,KAAK,CAAC,qBAAZL,0BAAAA,eAAuB,EAAE,EAAEM,MAAM,CAAC,SAACC;eAAM,CAACA,EAAEC,UAAU,CAAC,QAAQV,eAAeK,IAAI,CAACI;;IACrG,wFAAwF;IACxF,qFAAqF;IACrF,gFAAgF;IAChF,IAAME,OAAM,QAAA,wBAAwBC,IAAI,CAACR,sBAA7B,4BAAA,KAAuC,CAAC,EAAE;IACtD,IAAMS,YAAYF,QAAQG,YAAYR,SAASS,MAAM,GAAG,IAAIT,SAASU,IAAI,CAAC,SAACP;eAAMA,EAAEQ,KAAK,CAAC,8maAAmBC,QAAQ,CAACP;;IACrH,IAAIE,aAAaP,SAASS,MAAM,GAAG,GAAG;QACpC,OAAO,IAAII,oBAAU,CAAC,iBAAiB,AAAC,GAAmCb,OAAjCF,SAAQ,2BAAsJE,OAA7HA,SAASc,GAAG,CAAC,SAACX;mBAAM,AAAC,IAAM,OAAFA,GAAE;WAAKY,IAAI,CAAC,OAAM,qFAA8F,OAAZf,QAAQ,CAAC,EAAE,EAAC;IACtN;IACA,IAAIK,QAAQG,aAAaX,UAAUW,WAAW;QAC5C,OAAO,IAAIK,oBAAU,CAAC,iBAAiB,AAAC,GAAqChB,OAAnCC,SAAQ,6BAAiC,OAAND,OAAM;IACrF;IACA,OAAO,IAAIgB,oBAAU,CAAC,iBAAiB,AAAC,GAAU,OAARf,SAAQ;AACpD"}
@@ -0,0 +1,8 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ interface FindPathOptions {
3
+ directed?: boolean;
4
+ maxDepth?: number;
5
+ allowed?: Set<string>;
6
+ }
7
+ export declare function findPath(db: DatabaseSync, from: string, to: string, opts?: FindPathOptions): string[] | null;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ interface FindPathOptions {
3
+ directed?: boolean;
4
+ maxDepth?: number;
5
+ allowed?: Set<string>;
6
+ }
7
+ export declare function findPath(db: DatabaseSync, from: string, to: string, opts?: FindPathOptions): string[] | null;
8
+ export {};
@@ -0,0 +1,97 @@
1
+ // Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a
2
+ // local question touches a neighborhood, never the whole edge list.
3
+ "use strict";
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ Object.defineProperty(exports, "findPath", {
8
+ enumerable: true,
9
+ get: function() {
10
+ return findPath;
11
+ }
12
+ });
13
+ function _array_like_to_array(arr, len) {
14
+ if (len == null || len > arr.length) len = arr.length;
15
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
16
+ return arr2;
17
+ }
18
+ function _array_without_holes(arr) {
19
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
20
+ }
21
+ function _iterable_to_array(iter) {
22
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
23
+ }
24
+ function _non_iterable_spread() {
25
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
26
+ }
27
+ function _to_consumable_array(arr) {
28
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
29
+ }
30
+ function _unsupported_iterable_to_array(o, minLen) {
31
+ if (!o) return;
32
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
33
+ var n = Object.prototype.toString.call(o).slice(8, -1);
34
+ if (n === "Object" && o.constructor) n = o.constructor.name;
35
+ if (n === "Map" || n === "Set") return Array.from(n);
36
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
37
+ }
38
+ function resetVisited(db) {
39
+ db.exec('DROP TABLE IF EXISTS temp.visited');
40
+ db.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');
41
+ }
42
+ // Materializes the caller-resolved scope Set into a temp table so it joins like any other
43
+ // indexed column. `extra`, when given, is always in scope -- findPath's `to` endpoint.
44
+ function setupAllowed(db, allowed, extra) {
45
+ db.exec('DROP TABLE IF EXISTS temp.allowed_nodes');
46
+ db.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');
47
+ db.prepare('INSERT INTO allowed_nodes SELECT DISTINCT value FROM json_each(?1)').run(JSON.stringify(_to_consumable_array(allowed)));
48
+ if (extra !== undefined) db.prepare('INSERT OR IGNORE INTO allowed_nodes VALUES (?)').run(extra);
49
+ }
50
+ // Only resolved links (dst IS NOT NULL) are followed, in either direction.
51
+ function candidateSelect(direction) {
52
+ var forward = 'SELECT l.dst AS path, l.src AS pred FROM links l JOIN visited v ON v.path = l.src AND v.depth = ?1 WHERE l.dst IS NOT NULL';
53
+ var reverse = 'SELECT l.src AS path, l.dst AS pred FROM links l JOIN visited v ON v.path = l.dst AND v.depth = ?1 WHERE l.dst IS NOT NULL';
54
+ if (direction === 'forward') return forward;
55
+ if (direction === 'reverse') return reverse;
56
+ return "".concat(forward, " UNION ALL ").concat(reverse);
57
+ }
58
+ // One ring: candidates adjacent to the previous ring's frontier (?1), anti-joined against
59
+ // everything already visited, optionally filtered to the allowed set, capped (?3) and
60
+ // inserted at the new depth (?2). RETURNING hands back exactly what this ring newly reached.
61
+ function ringSql(direction, hasAllowed) {
62
+ return "\nINSERT INTO visited (path, depth, pred)\nSELECT cand.path, ?2, MIN(cand.pred)\nFROM (".concat(candidateSelect(direction), ") cand\nWHERE NOT EXISTS (SELECT 1 FROM visited v2 WHERE v2.path = cand.path)\n").concat(hasAllowed ? 'AND EXISTS (SELECT 1 FROM allowed_nodes a WHERE a.path = cand.path)' : '', "\nGROUP BY cand.path\nORDER BY cand.path\nLIMIT ?3\nRETURNING path");
63
+ }
64
+ function reconstructPath(db, to) {
65
+ var lookup = db.prepare('SELECT pred FROM visited WHERE path = ?');
66
+ var path = [];
67
+ var cur = to;
68
+ while(cur !== null){
69
+ path.push(cur);
70
+ var row = lookup.get(cur);
71
+ cur = row ? row.pred : null;
72
+ }
73
+ return path.reverse();
74
+ }
75
+ function findPath(db, from, to) {
76
+ var opts = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : {};
77
+ var _opts_maxDepth;
78
+ if (from === to) return [
79
+ from
80
+ ];
81
+ var direction = opts.directed ? 'forward' : 'both';
82
+ var maxDepth = (_opts_maxDepth = opts.maxDepth) !== null && _opts_maxDepth !== void 0 ? _opts_maxDepth : Infinity;
83
+ if (maxDepth <= 0) return null;
84
+ resetVisited(db);
85
+ db.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)').run(from);
86
+ if (opts.allowed) setupAllowed(db, opts.allowed, to);
87
+ var stmt = db.prepare(ringSql(direction, !!opts.allowed));
88
+ for(var ring = 1; ring <= maxDepth; ring++){
89
+ var rows = stmt.all(ring - 1, ring, -1);
90
+ if (rows.length === 0) break;
91
+ if (rows.some(function(row) {
92
+ return row.path === to;
93
+ })) return reconstructPath(db, to);
94
+ }
95
+ return null;
96
+ }
97
+ /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/traverse.ts"],"sourcesContent":["// Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a\n// local question touches a neighborhood, never the whole edge list.\nimport type { DatabaseSync } from 'node:sqlite';\n\ntype Direction = 'forward' | 'reverse' | 'both';\n\ninterface FindPathOptions {\n directed?: boolean;\n maxDepth?: number;\n allowed?: Set<string>;\n}\n\nfunction resetVisited(db: DatabaseSync): void {\n db.exec('DROP TABLE IF EXISTS temp.visited');\n db.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');\n}\n\n// Materializes the caller-resolved scope Set into a temp table so it joins like any other\n// indexed column. `extra`, when given, is always in scope -- findPath's `to` endpoint.\nfunction setupAllowed(db: DatabaseSync, allowed: Set<string>, extra?: string): void {\n db.exec('DROP TABLE IF EXISTS temp.allowed_nodes');\n db.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');\n db.prepare('INSERT INTO allowed_nodes SELECT DISTINCT value FROM json_each(?1)').run(JSON.stringify([...allowed]));\n if (extra !== undefined) db.prepare('INSERT OR IGNORE INTO allowed_nodes VALUES (?)').run(extra);\n}\n\n// Only resolved links (dst IS NOT NULL) are followed, in either direction.\nfunction candidateSelect(direction: Direction): string {\n const forward = 'SELECT l.dst AS path, l.src AS pred FROM links l JOIN visited v ON v.path = l.src AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n const reverse = 'SELECT l.src AS path, l.dst AS pred FROM links l JOIN visited v ON v.path = l.dst AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n if (direction === 'forward') return forward;\n if (direction === 'reverse') return reverse;\n return `${forward} UNION ALL ${reverse}`;\n}\n\n// One ring: candidates adjacent to the previous ring's frontier (?1), anti-joined against\n// everything already visited, optionally filtered to the allowed set, capped (?3) and\n// inserted at the new depth (?2). RETURNING hands back exactly what this ring newly reached.\nfunction ringSql(direction: Direction, hasAllowed: boolean): string {\n return `\nINSERT INTO visited (path, depth, pred)\nSELECT cand.path, ?2, MIN(cand.pred)\nFROM (${candidateSelect(direction)}) cand\nWHERE NOT EXISTS (SELECT 1 FROM visited v2 WHERE v2.path = cand.path)\n${hasAllowed ? 'AND EXISTS (SELECT 1 FROM allowed_nodes a WHERE a.path = cand.path)' : ''}\nGROUP BY cand.path\nORDER BY cand.path\nLIMIT ?3\nRETURNING path`;\n}\n\nfunction reconstructPath(db: DatabaseSync, to: string): string[] {\n const lookup = db.prepare('SELECT pred FROM visited WHERE path = ?');\n const path: string[] = [];\n let cur: string | null = to;\n while (cur !== null) {\n path.push(cur);\n const row = lookup.get(cur) as { pred: string | null } | undefined;\n cur = row ? row.pred : null;\n }\n return path.reverse();\n}\n\n// One BFS from `from`, tracking a predecessor per node. Undirected by default, matching\n// personalizedRank; null when `to` is unreached within `maxDepth`.\nexport function findPath(db: DatabaseSync, from: string, to: string, opts: FindPathOptions = {}): string[] | null {\n if (from === to) return [from];\n\n const direction: Direction = opts.directed ? 'forward' : 'both';\n const maxDepth = opts.maxDepth ?? Infinity;\n if (maxDepth <= 0) return null;\n\n resetVisited(db);\n db.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)').run(from);\n if (opts.allowed) setupAllowed(db, opts.allowed, to);\n\n const stmt = db.prepare(ringSql(direction, !!opts.allowed));\n for (let ring = 1; ring <= maxDepth; ring++) {\n const rows = stmt.all(ring - 1, ring, -1) as Array<{ path: string }>;\n if (rows.length === 0) break;\n if (rows.some((row) => row.path === to)) return reconstructPath(db, to);\n }\n return null;\n}\n"],"names":["findPath","resetVisited","db","exec","setupAllowed","allowed","extra","prepare","run","JSON","stringify","undefined","candidateSelect","direction","forward","reverse","ringSql","hasAllowed","reconstructPath","to","lookup","path","cur","push","row","get","pred","from","opts","directed","maxDepth","Infinity","stmt","ring","rows","all","length","some"],"mappings":"AAAA,2FAA2F;AAC3F,oEAAoE;;;;;+BAgEpDA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AArDhB,SAASC,aAAaC,EAAgB;IACpCA,GAAGC,IAAI,CAAC;IACRD,GAAGC,IAAI,CAAC;AACV;AAEA,0FAA0F;AAC1F,uFAAuF;AACvF,SAASC,aAAaF,EAAgB,EAAEG,OAAoB,EAAEC,KAAc;IAC1EJ,GAAGC,IAAI,CAAC;IACRD,GAAGC,IAAI,CAAC;IACRD,GAAGK,OAAO,CAAC,sEAAsEC,GAAG,CAACC,KAAKC,SAAS,CAAE,qBAAGL;IACxG,IAAIC,UAAUK,WAAWT,GAAGK,OAAO,CAAC,kDAAkDC,GAAG,CAACF;AAC5F;AAEA,2EAA2E;AAC3E,SAASM,gBAAgBC,SAAoB;IAC3C,IAAMC,UAAU;IAChB,IAAMC,UAAU;IAChB,IAAIF,cAAc,WAAW,OAAOC;IACpC,IAAID,cAAc,WAAW,OAAOE;IACpC,OAAO,AAAC,GAAuBA,OAArBD,SAAQ,eAAqB,OAARC;AACjC;AAEA,0FAA0F;AAC1F,sFAAsF;AACtF,6FAA6F;AAC7F,SAASC,QAAQH,SAAoB,EAAEI,UAAmB;IACxD,OAAO,AAAC,0FAKRA,OAFML,gBAAgBC,YAAW,mFAEuD,OAAxFI,aAAa,wEAAwE,IAAG;AAK1F;AAEA,SAASC,gBAAgBhB,EAAgB,EAAEiB,EAAU;IACnD,IAAMC,SAASlB,GAAGK,OAAO,CAAC;IAC1B,IAAMc,OAAiB,EAAE;IACzB,IAAIC,MAAqBH;IACzB,MAAOG,QAAQ,KAAM;QACnBD,KAAKE,IAAI,CAACD;QACV,IAAME,MAAMJ,OAAOK,GAAG,CAACH;QACvBA,MAAME,MAAMA,IAAIE,IAAI,GAAG;IACzB;IACA,OAAOL,KAAKN,OAAO;AACrB;AAIO,SAASf,SAASE,EAAgB,EAAEyB,IAAY,EAAER,EAAU;QAAES,OAAAA,iEAAwB,CAAC;QAI3EA;IAHjB,IAAID,SAASR,IAAI,OAAO;QAACQ;KAAK;IAE9B,IAAMd,YAAuBe,KAAKC,QAAQ,GAAG,YAAY;IACzD,IAAMC,YAAWF,iBAAAA,KAAKE,QAAQ,cAAbF,4BAAAA,iBAAiBG;IAClC,IAAID,YAAY,GAAG,OAAO;IAE1B7B,aAAaC;IACbA,GAAGK,OAAO,CAAC,+DAA+DC,GAAG,CAACmB;IAC9E,IAAIC,KAAKvB,OAAO,EAAED,aAAaF,IAAI0B,KAAKvB,OAAO,EAAEc;IAEjD,IAAMa,OAAO9B,GAAGK,OAAO,CAACS,QAAQH,WAAW,CAAC,CAACe,KAAKvB,OAAO;IACzD,IAAK,IAAI4B,OAAO,GAAGA,QAAQH,UAAUG,OAAQ;QAC3C,IAAMC,OAAOF,KAAKG,GAAG,CAACF,OAAO,GAAGA,MAAM,CAAC;QACvC,IAAIC,KAAKE,MAAM,KAAK,GAAG;QACvB,IAAIF,KAAKG,IAAI,CAAC,SAACb;mBAAQA,IAAIH,IAAI,KAAKF;YAAK,OAAOD,gBAAgBhB,IAAIiB;IACtE;IACA,OAAO;AACT"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from './types.js';
2
+ declare const download: Command;
3
+ export default download;
@@ -0,0 +1,27 @@
1
+ import { embedConfig } from '../config.js';
2
+ import { SenseError } from '../errors.js';
3
+ import { downloadModel, modelDir, modelPresent } from '../features/embed.js';
4
+ import { USAGE } from './index.js';
5
+ import { CONFIG, parse } from './shared.js';
6
+ // The one command that fetches weights. Nothing else touches the network for them, so a
7
+ // query that reads like a query never spends minutes pulling 124 MB. Asks nothing, so there
8
+ // is no prompt for CI to bypass; running it twice is a no-op.
9
+ const download = async (ctx)=>{
10
+ const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.download}`, {
11
+ ...CONFIG
12
+ });
13
+ const cfg = ctx.resolveConfig(values.config);
14
+ const e = embedConfig(cfg);
15
+ if (!e) throw new SenseError('EMBED_DISABLED', 'no preset asks for vectors, so there is no model to download');
16
+ if (e.type === 'api') {
17
+ console.log(`embed type is "api" (${e.url}), so there is nothing to download; ${ctx.name} search reaches it over the network`);
18
+ return;
19
+ }
20
+ if (modelPresent(cfg)) {
21
+ console.log(`${e.model} is already available in ${modelDir(e.model)}`);
22
+ return;
23
+ }
24
+ const dir = await downloadModel(e.model, (file, into)=>console.error(`fetching ${e.model}/${file} into ${into}`));
25
+ console.log(`downloaded ${e.model} into ${dir}`);
26
+ };
27
+ export default download;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/download.ts"],"sourcesContent":["import { embedConfig } from '../config.ts';\nimport { SenseError } from '../errors.ts';\nimport { downloadModel, modelDir, modelPresent } from '../features/embed.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, parse } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// The one command that fetches weights. Nothing else touches the network for them, so a\n// query that reads like a query never spends minutes pulling 124 MB. Asks nothing, so there\n// is no prompt for CI to bypass; running it twice is a no-op.\nconst download: Command = async (ctx) => {\n const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.download}`, { ...CONFIG });\n const cfg = ctx.resolveConfig(values.config as string | undefined);\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'no preset asks for vectors, so there is no model to download');\n\n if (e.type === 'api') {\n console.log(`embed type is \"api\" (${e.url}), so there is nothing to download; ${ctx.name} search reaches it over the network`);\n return;\n }\n if (modelPresent(cfg)) {\n console.log(`${e.model} is already available in ${modelDir(e.model)}`);\n return;\n }\n const dir = await downloadModel(e.model, (file, into) => console.error(`fetching ${e.model}/${file} into ${into}`));\n console.log(`downloaded ${e.model} into ${dir}`);\n};\nexport default download;\n"],"names":["embedConfig","SenseError","downloadModel","modelDir","modelPresent","USAGE","CONFIG","parse","download","ctx","values","argv","name","cfg","resolveConfig","config","e","type","console","log","url","model","dir","file","into","error"],"mappings":"AAAA,SAASA,WAAW,QAAQ,eAAe;AAC3C,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,aAAa,EAAEC,QAAQ,EAAEC,YAAY,QAAQ,uBAAuB;AAC7E,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,KAAK,QAAQ,cAAc;AAG5C,wFAAwF;AACxF,4FAA4F;AAC5F,8DAA8D;AAC9D,MAAMC,WAAoB,OAAOC;IAC/B,MAAM,EAAEC,MAAM,EAAE,GAAGH,MAAME,IAAIE,IAAI,EAAE,CAAC,OAAO,EAAEF,IAAIG,IAAI,CAAC,CAAC,EAAEP,MAAMG,QAAQ,EAAE,EAAE;QAAE,GAAGF,MAAM;IAAC;IACvF,MAAMO,MAAMJ,IAAIK,aAAa,CAACJ,OAAOK,MAAM;IAC3C,MAAMC,IAAIhB,YAAYa;IACtB,IAAI,CAACG,GAAG,MAAM,IAAIf,WAAW,kBAAkB;IAE/C,IAAIe,EAAEC,IAAI,KAAK,OAAO;QACpBC,QAAQC,GAAG,CAAC,CAAC,qBAAqB,EAAEH,EAAEI,GAAG,CAAC,oCAAoC,EAAEX,IAAIG,IAAI,CAAC,mCAAmC,CAAC;QAC7H;IACF;IACA,IAAIR,aAAaS,MAAM;QACrBK,QAAQC,GAAG,CAAC,GAAGH,EAAEK,KAAK,CAAC,yBAAyB,EAAElB,SAASa,EAAEK,KAAK,GAAG;QACrE;IACF;IACA,MAAMC,MAAM,MAAMpB,cAAcc,EAAEK,KAAK,EAAE,CAACE,MAAMC,OAASN,QAAQO,KAAK,CAAC,CAAC,SAAS,EAAET,EAAEK,KAAK,CAAC,CAAC,EAAEE,KAAK,MAAM,EAAEC,MAAM;IACjHN,QAAQC,GAAG,CAAC,CAAC,WAAW,EAAEH,EAAEK,KAAK,CAAC,MAAM,EAAEC,KAAK;AACjD;AACA,eAAed,SAAS"}
@@ -3,12 +3,13 @@ export declare const USAGE: {
3
3
  readonly init: "init";
4
4
  readonly watch: "watch [--force] [--config path]";
5
5
  readonly status: "status [--format table|json] [--config path]";
6
- readonly check: "check [--format table|json] [--config path]";
7
- readonly rebuild: "rebuild [--config path]";
8
- readonly query: "query \"<sql>\" [params...] [--format table|json] [--config path]";
9
- readonly search: "search \"<terms>\" [--preset name] [--include glob ...] [--where \"<sql>\"] [--k n] [--lexical] [--format table|json] [--config path]";
6
+ readonly download: "download [--config path]";
7
+ readonly sql: "sql \"<statement>\" [params...] [--preset name] [--format table|json] [--config path]";
8
+ readonly search: "search \"<terms>\" [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json] [--config path]";
10
9
  readonly map: "map [--format table|json] [--config path]";
11
- readonly peek: "peek <path> [--format table|json] [--config path]";
10
+ readonly peek: "peek <path> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--format table|json] [--config path]";
11
+ readonly path: "path <a> <b> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--max-depth n] [--format table|json] [--config path]";
12
+ readonly related: "related <note> [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--where \"<sql>\"] [--k n] [--format table|json] [--config path]";
12
13
  };
13
14
  export declare const COMMANDS: Record<string, () => Promise<{
14
15
  default: Command;