sensemaking 0.7.2 → 0.8.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.
- package/README.md +61 -41
- package/dist/cjs/cli/check.js +386 -0
- package/dist/cjs/cli/check.js.map +1 -0
- package/dist/cjs/{commands → cli}/find.js +3 -4
- package/dist/cjs/cli/find.js.map +1 -0
- package/dist/cjs/cli/index.js.map +1 -0
- package/dist/cjs/{commands → cli}/init.js +3 -0
- package/dist/cjs/cli/init.js.map +1 -0
- package/dist/cjs/{commands → cli}/map.js +2 -2
- package/dist/cjs/cli/map.js.map +1 -0
- package/dist/cjs/{commands → cli}/named.d.cts +1 -1
- package/dist/cjs/{commands → cli}/named.d.ts +1 -1
- package/dist/cjs/cli/named.js +209 -0
- package/dist/cjs/cli/named.js.map +1 -0
- package/dist/cjs/{commands → cli}/peek.js +2 -2
- package/dist/cjs/cli/peek.js.map +1 -0
- package/dist/cjs/cli/query.js.map +1 -0
- package/dist/cjs/cli/rebuild.js.map +1 -0
- package/dist/cjs/{commands → cli}/shared.d.cts +1 -0
- package/dist/{esm/commands → cjs/cli}/shared.d.ts +1 -0
- package/dist/cjs/{commands → cli}/shared.js +9 -0
- package/dist/cjs/cli/shared.js.map +1 -0
- package/dist/cjs/{commands → cli}/status.js +7 -1
- package/dist/cjs/cli/status.js.map +1 -0
- package/dist/cjs/cli/watch.js.map +1 -0
- package/dist/cjs/cli.js +22 -17
- package/dist/cjs/cli.js.map +1 -1
- package/dist/{esm/verbs.d.ts → cjs/commands.d.cts} +5 -4
- package/dist/cjs/{verbs.d.cts → commands.d.ts} +5 -4
- package/dist/cjs/{verbs.js → commands.js} +246 -21
- package/dist/cjs/commands.js.map +1 -0
- package/dist/cjs/config.d.cts +7 -1
- package/dist/cjs/config.d.ts +7 -1
- package/dist/cjs/config.js +75 -24
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/db.d.cts +1 -1
- package/dist/cjs/db.d.ts +1 -1
- package/dist/cjs/db.js +88 -17
- package/dist/cjs/db.js.map +1 -1
- package/dist/cjs/errors.d.cts +1 -1
- package/dist/cjs/errors.d.ts +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/features/embed.js +7 -1
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/features/links.js +201 -24
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/features/rank.js +8 -1
- package/dist/cjs/features/rank.js.map +1 -1
- package/dist/cjs/features/types.d.cts +10 -3
- package/dist/cjs/features/types.d.ts +10 -3
- package/dist/cjs/index.d.cts +3 -3
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output.d.cts +1 -0
- package/dist/cjs/output.d.ts +1 -0
- package/dist/cjs/output.js +2 -1
- package/dist/cjs/output.js.map +1 -1
- package/dist/cjs/progress.d.cts +5 -0
- package/dist/cjs/progress.d.ts +5 -0
- package/dist/cjs/progress.js +54 -0
- package/dist/cjs/progress.js.map +1 -0
- package/dist/cjs/scan.d.cts +1 -0
- package/dist/cjs/scan.d.ts +1 -0
- package/dist/cjs/scan.js +3 -2
- package/dist/cjs/scan.js.map +1 -1
- package/dist/esm/{commands → cli}/check.js +43 -3
- package/dist/esm/cli/check.js.map +1 -0
- package/dist/esm/{commands → cli}/find.js +3 -4
- package/dist/esm/cli/find.js.map +1 -0
- package/dist/esm/cli/index.js.map +1 -0
- package/dist/esm/{commands → cli}/init.js +3 -0
- package/dist/esm/cli/init.js.map +1 -0
- package/dist/esm/{commands → cli}/map.js +1 -1
- package/dist/esm/cli/map.js.map +1 -0
- package/dist/esm/{commands → cli}/named.d.ts +1 -1
- package/dist/esm/cli/named.js +33 -0
- package/dist/esm/cli/named.js.map +1 -0
- package/dist/esm/{commands → cli}/peek.js +1 -1
- package/dist/esm/cli/peek.js.map +1 -0
- package/dist/esm/cli/query.js.map +1 -0
- package/dist/esm/cli/rebuild.js.map +1 -0
- package/dist/{cjs/commands → esm/cli}/shared.d.ts +1 -0
- package/dist/esm/{commands → cli}/shared.js +9 -0
- package/dist/esm/cli/shared.js.map +1 -0
- package/dist/esm/{commands → cli}/status.js +7 -1
- package/dist/esm/cli/status.js.map +1 -0
- package/dist/esm/cli/types.js.map +1 -0
- package/dist/esm/cli/watch.js.map +1 -0
- package/dist/esm/cli.js +8 -6
- package/dist/esm/cli.js.map +1 -1
- package/dist/{cjs/verbs.d.ts → esm/commands.d.ts} +5 -4
- package/dist/esm/{verbs.js → commands.js} +165 -21
- package/dist/esm/commands.js.map +1 -0
- package/dist/esm/config.d.ts +7 -1
- package/dist/esm/config.js +38 -4
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/db.d.ts +1 -1
- package/dist/esm/db.js +78 -16
- package/dist/esm/db.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/features/embed.js +6 -0
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/features/links.js +130 -20
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/rank.js +8 -1
- package/dist/esm/features/rank.js.map +1 -1
- package/dist/esm/features/types.d.ts +10 -3
- package/dist/esm/features/types.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output.d.ts +1 -0
- package/dist/esm/output.js +2 -1
- package/dist/esm/output.js.map +1 -1
- package/dist/esm/progress.d.ts +5 -0
- package/dist/esm/progress.js +44 -0
- package/dist/esm/progress.js.map +1 -0
- package/dist/esm/scan.d.ts +1 -0
- package/dist/esm/scan.js +5 -2
- package/dist/esm/scan.js.map +1 -1
- package/package.json +6 -2
- package/schema.json +19 -4
- package/skills/sense/EXAMPLES.md +9 -7
- package/skills/sense/SKILL.md +43 -27
- package/skills/sense-setup/SKILL.md +16 -2
- package/dist/cjs/commands/check.js +0 -143
- package/dist/cjs/commands/check.js.map +0 -1
- package/dist/cjs/commands/find.js.map +0 -1
- package/dist/cjs/commands/index.js.map +0 -1
- package/dist/cjs/commands/init.js.map +0 -1
- package/dist/cjs/commands/map.js.map +0 -1
- package/dist/cjs/commands/named.js +0 -23
- package/dist/cjs/commands/named.js.map +0 -1
- package/dist/cjs/commands/peek.js.map +0 -1
- package/dist/cjs/commands/query.js.map +0 -1
- package/dist/cjs/commands/rebuild.js.map +0 -1
- package/dist/cjs/commands/shared.js.map +0 -1
- package/dist/cjs/commands/status.js.map +0 -1
- package/dist/cjs/commands/watch.js.map +0 -1
- package/dist/cjs/verbs.js.map +0 -1
- package/dist/esm/commands/check.js.map +0 -1
- package/dist/esm/commands/find.js.map +0 -1
- package/dist/esm/commands/index.js.map +0 -1
- package/dist/esm/commands/init.js.map +0 -1
- package/dist/esm/commands/map.js.map +0 -1
- package/dist/esm/commands/named.js +0 -12
- package/dist/esm/commands/named.js.map +0 -1
- package/dist/esm/commands/peek.js.map +0 -1
- package/dist/esm/commands/query.js.map +0 -1
- package/dist/esm/commands/rebuild.js.map +0 -1
- package/dist/esm/commands/shared.js.map +0 -1
- package/dist/esm/commands/status.js.map +0 -1
- package/dist/esm/commands/types.js.map +0 -1
- package/dist/esm/commands/watch.js.map +0 -1
- package/dist/esm/verbs.js.map +0 -1
- /package/dist/cjs/{commands → cli}/check.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/check.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/find.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/find.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/index.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/index.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/index.js +0 -0
- /package/dist/cjs/{commands → cli}/init.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/init.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/map.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/map.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/peek.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/peek.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/query.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/query.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/query.js +0 -0
- /package/dist/cjs/{commands → cli}/rebuild.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/rebuild.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/rebuild.js +0 -0
- /package/dist/cjs/{commands → cli}/status.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/status.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/types.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/types.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/types.js +0 -0
- /package/dist/cjs/{commands → cli}/types.js.map +0 -0
- /package/dist/cjs/{commands → cli}/watch.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/watch.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/watch.js +0 -0
- /package/dist/esm/{commands → cli}/check.d.ts +0 -0
- /package/dist/esm/{commands → cli}/find.d.ts +0 -0
- /package/dist/esm/{commands → cli}/index.d.ts +0 -0
- /package/dist/esm/{commands → cli}/index.js +0 -0
- /package/dist/esm/{commands → cli}/init.d.ts +0 -0
- /package/dist/esm/{commands → cli}/map.d.ts +0 -0
- /package/dist/esm/{commands → cli}/peek.d.ts +0 -0
- /package/dist/esm/{commands → cli}/query.d.ts +0 -0
- /package/dist/esm/{commands → cli}/query.js +0 -0
- /package/dist/esm/{commands → cli}/rebuild.d.ts +0 -0
- /package/dist/esm/{commands → cli}/rebuild.js +0 -0
- /package/dist/esm/{commands → cli}/status.d.ts +0 -0
- /package/dist/esm/{commands → cli}/types.d.ts +0 -0
- /package/dist/esm/{commands → cli}/types.js +0 -0
- /package/dist/esm/{commands → cli}/watch.d.ts +0 -0
- /package/dist/esm/{commands → cli}/watch.js +0 -0
|
@@ -19,6 +19,8 @@ _export(exports, {
|
|
|
19
19
|
return peek;
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
|
+
var _nodefs = require("node:fs");
|
|
23
|
+
var _nodepath = require("node:path");
|
|
22
24
|
var _posix = /*#__PURE__*/ _interop_require_default(require("node:path/posix"));
|
|
23
25
|
var _configts = require("./config.js");
|
|
24
26
|
var _errorsts = require("./errors.js");
|
|
@@ -217,13 +219,153 @@ function _ts_generator(thisArg, body) {
|
|
|
217
219
|
};
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
|
-
// The three layer
|
|
222
|
+
// The three layer commands: mapTree (orient), find (locate), peek (structure).
|
|
221
223
|
// Each returns data; cli.ts renders. All of them degrade when a feature is off.
|
|
222
224
|
var WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';
|
|
223
225
|
var RRF_K = 60;
|
|
226
|
+
// FTS5 snippet() re-tokenizes the whole doc per candidate row, superlinearly: measured
|
|
227
|
+
// 2ms at 4KB docs but 189ms at 64KB for one query's pool, and ~10s for a single 1MB doc
|
|
228
|
+
// (plans/performance-findings.md finding A). 16KB keeps the whole pool's snippet cost in
|
|
229
|
+
// the low-ms range; bigger docs get the JS-computed excerpt, linear and only for the rows
|
|
230
|
+
// actually returned.
|
|
231
|
+
var SNIPPET_BOUND = 16384;
|
|
232
|
+
var EXCERPT_WINDOW = 160;
|
|
233
|
+
// Bare terms from an FTS5 query string: strips operators/quoting so the oversized-doc excerpt
|
|
234
|
+
// scan matches the same words the query matched on, not FTS5 syntax.
|
|
235
|
+
function extractBareTerms(query) {
|
|
236
|
+
var cleaned = query.replace(/"/g, ' ').replace(/[()*]/g, ' ').replace(/\b(AND|OR|NOT|NEAR)\b(\/\d+)?/gi, ' ');
|
|
237
|
+
return cleaned.split(/\s+/).map(function(tok) {
|
|
238
|
+
return tok.replace(/^[A-Za-z_]\w*:/, '');
|
|
239
|
+
}) // column filter, e.g. title:term
|
|
240
|
+
.map(function(tok) {
|
|
241
|
+
return tok.toLowerCase().trim();
|
|
242
|
+
}).filter(function(tok) {
|
|
243
|
+
return tok.length > 0;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function findOccurrences(haystackLower, terms) {
|
|
247
|
+
var occ = [];
|
|
248
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
249
|
+
try {
|
|
250
|
+
for(var _iterator = terms[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
251
|
+
var term = _step.value;
|
|
252
|
+
var idx = 0;
|
|
253
|
+
for(;;){
|
|
254
|
+
var found = haystackLower.indexOf(term, idx);
|
|
255
|
+
if (found === -1) break;
|
|
256
|
+
occ.push({
|
|
257
|
+
start: found,
|
|
258
|
+
end: found + term.length,
|
|
259
|
+
term: term
|
|
260
|
+
});
|
|
261
|
+
idx = found + term.length;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (err) {
|
|
265
|
+
_didIteratorError = true;
|
|
266
|
+
_iteratorError = err;
|
|
267
|
+
} finally{
|
|
268
|
+
try {
|
|
269
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
270
|
+
_iterator.return();
|
|
271
|
+
}
|
|
272
|
+
} finally{
|
|
273
|
+
if (_didIteratorError) {
|
|
274
|
+
throw _iteratorError;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Longest span first at equal start, so "tests" beats its substring "test" and the
|
|
279
|
+
// whole word gets highlighted; the emit loop then absorbs the shorter overlap.
|
|
280
|
+
return occ.sort(function(a, b) {
|
|
281
|
+
return a.start - b.start || b.end - a.end;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// Slides a window over the sorted occurrences, same semantics as FTS5's own best-window
|
|
285
|
+
// pick: most distinct terms, ties broken by most total hits.
|
|
286
|
+
function bestWindowStart(occ, windowSize) {
|
|
287
|
+
var best = occ[0].start;
|
|
288
|
+
var bestDistinct = 0;
|
|
289
|
+
var bestCount = 0;
|
|
290
|
+
for(var i = 0; i < occ.length; i++){
|
|
291
|
+
var winEnd = occ[i].start + windowSize;
|
|
292
|
+
var seen = new Set();
|
|
293
|
+
var count = 0;
|
|
294
|
+
for(var j = i; j < occ.length && occ[j].start < winEnd; j++){
|
|
295
|
+
seen.add(occ[j].term);
|
|
296
|
+
count++;
|
|
297
|
+
}
|
|
298
|
+
if (seen.size > bestDistinct || seen.size === bestDistinct && count > bestCount) {
|
|
299
|
+
best = occ[i].start;
|
|
300
|
+
bestDistinct = seen.size;
|
|
301
|
+
bestCount = count;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return best;
|
|
305
|
+
}
|
|
306
|
+
// snippet()-shaped excerpt: matched terms wrapped in «», … at cut edges. Linear in doc
|
|
307
|
+
// length, only run for rows actually returned (<= k), unlike snippet()'s per-row cost.
|
|
308
|
+
function computeExcerpt(text, terms) {
|
|
309
|
+
var occ = findOccurrences(text.toLowerCase(), terms);
|
|
310
|
+
if (occ.length === 0) {
|
|
311
|
+
// This is a raw substring scan, not porter-stemmed: a doc matched only through a
|
|
312
|
+
// stemmed variant (query "negotiate" vs doc "negotiating") finds no occurrence here.
|
|
313
|
+
// Fall back to the doc's start, unmarked, rather than claim a match that isn't there.
|
|
314
|
+
var end = Math.min(text.length, EXCERPT_WINDOW);
|
|
315
|
+
return {
|
|
316
|
+
excerpt: "".concat(text.slice(0, end).replace(/\s+/g, ' ').trim()).concat(end < text.length ? '…' : ''),
|
|
317
|
+
offset: 0
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
var start = bestWindowStart(occ, EXCERPT_WINDOW);
|
|
321
|
+
var end1 = Math.min(text.length, start + EXCERPT_WINDOW);
|
|
322
|
+
var out = '';
|
|
323
|
+
var cursor = start;
|
|
324
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
325
|
+
try {
|
|
326
|
+
for(var _iterator = occ[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
327
|
+
var o = _step.value;
|
|
328
|
+
if (o.start < start || o.end > end1) continue;
|
|
329
|
+
// Occurrences of duplicate or substring-overlapping terms ("test tests") can overlap;
|
|
330
|
+
// emitting each would duplicate document text. Keep the first, absorb the rest.
|
|
331
|
+
if (o.start < cursor) continue;
|
|
332
|
+
out += "".concat(text.slice(cursor, o.start), "\xab").concat(text.slice(o.start, o.end), "\xbb");
|
|
333
|
+
cursor = o.end;
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
_didIteratorError = true;
|
|
337
|
+
_iteratorError = err;
|
|
338
|
+
} finally{
|
|
339
|
+
try {
|
|
340
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
341
|
+
_iterator.return();
|
|
342
|
+
}
|
|
343
|
+
} finally{
|
|
344
|
+
if (_didIteratorError) {
|
|
345
|
+
throw _iteratorError;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
out += text.slice(cursor, end1);
|
|
350
|
+
var prefix = start > 0 ? '…' : '';
|
|
351
|
+
var suffix = end1 < text.length ? '…' : '';
|
|
352
|
+
return {
|
|
353
|
+
excerpt: "".concat(prefix).concat(out.replace(/\s+/g, ' ')).concat(suffix),
|
|
354
|
+
offset: start
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function lineNumberAt(text, offset) {
|
|
358
|
+
var line = 1;
|
|
359
|
+
for(var i = 0; i < offset; i++)if (text.charCodeAt(i) === 10) line++;
|
|
360
|
+
return line;
|
|
361
|
+
}
|
|
362
|
+
function lineRangeFor(db, path, line) {
|
|
363
|
+
var row = db.prepare('SELECT start_line, end_line FROM sections WHERE "path" = ? AND start_line <= ? AND end_line >= ? ORDER BY start_line DESC LIMIT 1').get(path, line, line);
|
|
364
|
+
return row ? "L".concat(row.start_line, "-").concat(row.end_line) : null;
|
|
365
|
+
}
|
|
224
366
|
function find(_0, _1, _2) {
|
|
225
367
|
return _async_to_generator(function(db, cfg, terms) {
|
|
226
|
-
var opts, _opts_k, _opts_where, _hits_get, _chunkLines_get, _chunkSimilarity_get, _cfg_defaults_find, _cfg_defaults, k, fetch, scope, whereJoin, whereCond, matchSql, matchRows, hits, candidates, edges, linked, nodes, seeds, ranked, chunkLines, chunkSimilarity, vec, insert, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _step_value, path, c, where,
|
|
368
|
+
var opts, _opts_k, _opts_where, _hits_get, _chunkLines_get, _chunkSimilarity_get, _cfg_defaults_find, _cfg_defaults, k, fetch, scope, whereJoin, whereCond, matchSql, matchRows, hits, oversized, candidates, edges, linked, nodes, seeds, ranked, chunkLines, chunkSimilarity, vec, insert, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _step_value, path, c, where, similarityCol, rows, bareTerms, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, row, text, _computeExcerpt, excerpt, offset;
|
|
227
369
|
var _arguments = arguments;
|
|
228
370
|
return _ts_generator(this, function(_state) {
|
|
229
371
|
switch(_state.label){
|
|
@@ -240,7 +382,9 @@ function find(_0, _1, _2) {
|
|
|
240
382
|
scope = (_opts_where = opts.where) !== null && _opts_where !== void 0 ? _opts_where : (_cfg_defaults = cfg.defaults) === null || _cfg_defaults === void 0 ? void 0 : (_cfg_defaults_find = _cfg_defaults.find) === null || _cfg_defaults_find === void 0 ? void 0 : _cfg_defaults_find.where;
|
|
241
383
|
whereJoin = scope ? 'JOIN frontmatter f ON f."path" = content.path' : '';
|
|
242
384
|
whereCond = scope ? "AND (".concat(scope, ")") : '';
|
|
243
|
-
|
|
385
|
+
// Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite
|
|
386
|
+
// short-circuits the untaken CASE branch, so it's never invoked on them.
|
|
387
|
+
matchSql = "SELECT content.path AS path, CASE WHEN length(content.text) <= ".concat(SNIPPET_BOUND, " THEN snippet(content, -1, '\xab', '\xbb', '…', 10) ELSE NULL END AS hit FROM content ").concat(whereJoin, " WHERE content MATCH ? ").concat(whereCond, " ORDER BY ").concat(WEIGHTED_BM25, " LIMIT ").concat(fetch);
|
|
244
388
|
try {
|
|
245
389
|
matchRows = db.prepare(matchSql).all(terms);
|
|
246
390
|
} catch (err) {
|
|
@@ -252,6 +396,13 @@ function find(_0, _1, _2) {
|
|
|
252
396
|
r.hit
|
|
253
397
|
];
|
|
254
398
|
}));
|
|
399
|
+
// hit === null here means the bound suppressed snippet(), not "no match" -- distinguish
|
|
400
|
+
// from via='link' rows (never in matchRows, so absent from this set) below.
|
|
401
|
+
oversized = new Set(matchRows.filter(function(r) {
|
|
402
|
+
return r.hit === null;
|
|
403
|
+
}).map(function(r) {
|
|
404
|
+
return r.path;
|
|
405
|
+
}));
|
|
255
406
|
candidates = new Map();
|
|
256
407
|
matchRows.forEach(function(r, i) {
|
|
257
408
|
candidates.set(r.path, {
|
|
@@ -350,10 +501,45 @@ function find(_0, _1, _2) {
|
|
|
350
501
|
}
|
|
351
502
|
}
|
|
352
503
|
where = scope ? "WHERE ".concat(scope) : '';
|
|
353
|
-
|
|
504
|
+
// lines is always present now: semantic rows carry their chunk's range, oversized-doc
|
|
505
|
+
// lexical rows gain one below, everything else stays null. similarity stays semantic-only.
|
|
506
|
+
similarityCol = opts.semantic ? ', _find.similarity' : '';
|
|
507
|
+
rows = db.prepare('SELECT f."path" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score, _find.lines'.concat(similarityCol, '\n FROM _find JOIN frontmatter f ON f."path" = _find."path" JOIN content ON content.path = _find."path"\n ').concat(where, " ORDER BY _find.score DESC LIMIT ?")).all(k);
|
|
508
|
+
if (oversized.size > 0) {
|
|
509
|
+
bareTerms = extractBareTerms(terms);
|
|
510
|
+
_iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
511
|
+
try {
|
|
512
|
+
for(_iterator1 = rows[Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
513
|
+
row = _step1.value;
|
|
514
|
+
if (row.hit !== null || !oversized.has(row.path)) continue;
|
|
515
|
+
text = void 0;
|
|
516
|
+
try {
|
|
517
|
+
text = (0, _nodefs.readFileSync)((0, _nodepath.join)(cfg.baseDir, row.path), 'utf8');
|
|
518
|
+
} catch (unused) {
|
|
519
|
+
continue; // vanished since the match; leave hit/lines null rather than throw
|
|
520
|
+
}
|
|
521
|
+
_computeExcerpt = computeExcerpt(text, bareTerms), excerpt = _computeExcerpt.excerpt, offset = _computeExcerpt.offset;
|
|
522
|
+
row.hit = excerpt;
|
|
523
|
+
if (row.lines == null) row.lines = (0, _configts.featureEnabled)(cfg, 'sections') ? lineRangeFor(db, row.path, lineNumberAt(text, offset)) : null;
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
_didIteratorError1 = true;
|
|
527
|
+
_iteratorError1 = err;
|
|
528
|
+
} finally{
|
|
529
|
+
try {
|
|
530
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
531
|
+
_iterator1.return();
|
|
532
|
+
}
|
|
533
|
+
} finally{
|
|
534
|
+
if (_didIteratorError1) {
|
|
535
|
+
throw _iteratorError1;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
354
540
|
return [
|
|
355
541
|
2,
|
|
356
|
-
|
|
542
|
+
rows
|
|
357
543
|
];
|
|
358
544
|
}
|
|
359
545
|
});
|
|
@@ -365,6 +551,14 @@ var INTERNAL_COLUMNS = new Set([
|
|
|
365
551
|
'_size',
|
|
366
552
|
'_rank'
|
|
367
553
|
]);
|
|
554
|
+
// A result row is capped at SQLITE_MAX_COLUMN (2000, default); two aggregate expressions
|
|
555
|
+
// per field keeps a chunk's row safely under that regardless of how many fields the tree has.
|
|
556
|
+
var MAP_COLUMN_CHUNK = 300;
|
|
557
|
+
function chunk(items, size) {
|
|
558
|
+
var out = [];
|
|
559
|
+
for(var i = 0; i < items.length; i += size)out.push(items.slice(i, i + size));
|
|
560
|
+
return out;
|
|
561
|
+
}
|
|
368
562
|
function mapTree(db, cfg) {
|
|
369
563
|
var docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM("_size"), 0) AS bytes FROM frontmatter').get();
|
|
370
564
|
var columns = db.prepare('PRAGMA table_info(frontmatter)').all().map(function(r) {
|
|
@@ -372,18 +566,47 @@ function mapTree(db, cfg) {
|
|
|
372
566
|
}).filter(function(name) {
|
|
373
567
|
return !INTERNAL_COLUMNS.has(name);
|
|
374
568
|
});
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
569
|
+
// Observed storage class, not a declared one: these columns are added dynamically and
|
|
570
|
+
// SQLite types per value, so a field can be text in most notes and numeric in a few.
|
|
571
|
+
// Listing every distinct type makes both the type and any drift visible.
|
|
572
|
+
// One aggregate scan per chunk of columns rather than one scan per column: COUNT already
|
|
573
|
+
// ignores NULL, and FILTER keeps typeof() looking only at the non-null rows COUNT counted,
|
|
574
|
+
// matching the old per-column `WHERE ... IS NOT NULL` exactly.
|
|
575
|
+
var allFields = [];
|
|
576
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
577
|
+
try {
|
|
578
|
+
var _loop = function() {
|
|
579
|
+
var group = _step.value;
|
|
580
|
+
var exprs = group.map(function(name, i) {
|
|
581
|
+
var quoted = '"'.concat(name.split('"').join('""'), '"');
|
|
582
|
+
return "COUNT(".concat(quoted, ") AS n").concat(i, ", GROUP_CONCAT(DISTINCT typeof(").concat(quoted, ")) FILTER (WHERE ").concat(quoted, " IS NOT NULL) AS t").concat(i);
|
|
583
|
+
});
|
|
584
|
+
var result = db.prepare("SELECT ".concat(exprs.join(', '), " FROM frontmatter")).get();
|
|
585
|
+
group.forEach(function(name, i) {
|
|
586
|
+
var _result_;
|
|
587
|
+
return allFields.push({
|
|
588
|
+
field: name,
|
|
589
|
+
coverage: result["n".concat(i)],
|
|
590
|
+
type: (_result_ = result["t".concat(i)]) !== null && _result_ !== void 0 ? _result_ : ''
|
|
591
|
+
});
|
|
592
|
+
});
|
|
385
593
|
};
|
|
386
|
-
|
|
594
|
+
for(var _iterator = chunk(columns, MAP_COLUMN_CHUNK)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
|
|
595
|
+
} catch (err) {
|
|
596
|
+
_didIteratorError = true;
|
|
597
|
+
_iteratorError = err;
|
|
598
|
+
} finally{
|
|
599
|
+
try {
|
|
600
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
601
|
+
_iterator.return();
|
|
602
|
+
}
|
|
603
|
+
} finally{
|
|
604
|
+
if (_didIteratorError) {
|
|
605
|
+
throw _iteratorError;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
allFields.sort(function(a, b) {
|
|
387
610
|
return b.coverage - a.coverage;
|
|
388
611
|
});
|
|
389
612
|
var fields = allFields.slice(0, 20);
|
|
@@ -398,7 +621,7 @@ function mapTree(db, cfg) {
|
|
|
398
621
|
recent: recent
|
|
399
622
|
};
|
|
400
623
|
}
|
|
401
|
-
var
|
|
624
|
+
var PEEK_LIST_LIMIT = 20;
|
|
402
625
|
function peek(db, cfg, pathArg) {
|
|
403
626
|
var _row__size;
|
|
404
627
|
var paths = db.prepare('SELECT "path" FROM frontmatter').all().map(function(r) {
|
|
@@ -438,7 +661,8 @@ function peek(db, cfg, pathArg) {
|
|
|
438
661
|
}
|
|
439
662
|
}
|
|
440
663
|
}
|
|
441
|
-
var
|
|
664
|
+
var sectionsTotal = (0, _configts.featureEnabled)(cfg, 'sections') ? db.prepare('SELECT COUNT(*) AS n FROM sections WHERE "path" = ?').get(path).n : 0;
|
|
665
|
+
var sections = (0, _configts.featureEnabled)(cfg, 'sections') ? db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE "path" = ? ORDER BY idx LIMIT ?').all(path, PEEK_LIST_LIMIT) : [];
|
|
442
666
|
var outbound = [];
|
|
443
667
|
var backlinks = [];
|
|
444
668
|
var unresolved = [];
|
|
@@ -456,7 +680,7 @@ function peek(db, cfg, pathArg) {
|
|
|
456
680
|
return l.target;
|
|
457
681
|
});
|
|
458
682
|
backlinksTotal = db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path).n;
|
|
459
|
-
backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path,
|
|
683
|
+
backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT).map(function(r) {
|
|
460
684
|
return r.src;
|
|
461
685
|
});
|
|
462
686
|
}
|
|
@@ -465,9 +689,10 @@ function peek(db, cfg, pathArg) {
|
|
|
465
689
|
tokens: Math.ceil(((_row__size = row._size) !== null && _row__size !== void 0 ? _row__size : 0) / 4),
|
|
466
690
|
frontmatter: frontmatter,
|
|
467
691
|
sections: sections,
|
|
468
|
-
outbound: outbound.slice(0,
|
|
692
|
+
outbound: outbound.slice(0, PEEK_LIST_LIMIT),
|
|
469
693
|
backlinks: backlinks,
|
|
470
|
-
unresolved: unresolved.slice(0,
|
|
694
|
+
unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),
|
|
695
|
+
sectionsTotal: sectionsTotal,
|
|
471
696
|
outboundTotal: outbound.length,
|
|
472
697
|
backlinksTotal: backlinksTotal,
|
|
473
698
|
unresolvedTotal: unresolved.length,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig } from './config.ts';\nimport { featureEnabled, featureStates } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { semanticCandidates } from './features/embed.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\nimport { searchError } from './search-error.ts';\n\n// The three layer commands: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\n// FTS5 snippet() re-tokenizes the whole doc per candidate row, superlinearly: measured\n// 2ms at 4KB docs but 189ms at 64KB for one query's pool, and ~10s for a single 1MB doc\n// (plans/performance-findings.md finding A). 16KB keeps the whole pool's snippet cost in\n// the low-ms range; bigger docs get the JS-computed excerpt, linear and only for the rows\n// actually returned.\nconst SNIPPET_BOUND = 16_384;\nconst EXCERPT_WINDOW = 160;\n\n// Bare terms from an FTS5 query string: strips operators/quoting so the oversized-doc excerpt\n// scan matches the same words the query matched on, not FTS5 syntax.\nfunction extractBareTerms(query: string): string[] {\n const cleaned = query\n .replace(/\"/g, ' ')\n .replace(/[()*]/g, ' ')\n .replace(/\\b(AND|OR|NOT|NEAR)\\b(\\/\\d+)?/gi, ' ');\n return cleaned\n .split(/\\s+/)\n .map((tok) => tok.replace(/^[A-Za-z_]\\w*:/, '')) // column filter, e.g. title:term\n .map((tok) => tok.toLowerCase().trim())\n .filter((tok) => tok.length > 0);\n}\n\nfunction findOccurrences(haystackLower: string, terms: string[]): Array<{ start: number; end: number; term: string }> {\n const occ: Array<{ start: number; end: number; term: string }> = [];\n for (const term of terms) {\n let idx = 0;\n for (;;) {\n const found = haystackLower.indexOf(term, idx);\n if (found === -1) break;\n occ.push({ start: found, end: found + term.length, term });\n idx = found + term.length;\n }\n }\n // Longest span first at equal start, so \"tests\" beats its substring \"test\" and the\n // whole word gets highlighted; the emit loop then absorbs the shorter overlap.\n return occ.sort((a, b) => a.start - b.start || b.end - a.end);\n}\n\n// Slides a window over the sorted occurrences, same semantics as FTS5's own best-window\n// pick: most distinct terms, ties broken by most total hits.\nfunction bestWindowStart(occ: Array<{ start: number; end: number; term: string }>, windowSize: number): number {\n let best = occ[0].start;\n let bestDistinct = 0;\n let bestCount = 0;\n for (let i = 0; i < occ.length; i++) {\n const winEnd = occ[i].start + windowSize;\n const seen = new Set<string>();\n let count = 0;\n for (let j = i; j < occ.length && occ[j].start < winEnd; j++) {\n seen.add(occ[j].term);\n count++;\n }\n if (seen.size > bestDistinct || (seen.size === bestDistinct && count > bestCount)) {\n best = occ[i].start;\n bestDistinct = seen.size;\n bestCount = count;\n }\n }\n return best;\n}\n\n// snippet()-shaped excerpt: matched terms wrapped in «», … at cut edges. Linear in doc\n// length, only run for rows actually returned (<= k), unlike snippet()'s per-row cost.\nfunction computeExcerpt(text: string, terms: string[]): { excerpt: string; offset: number } {\n const occ = findOccurrences(text.toLowerCase(), terms);\n if (occ.length === 0) {\n // This is a raw substring scan, not porter-stemmed: a doc matched only through a\n // stemmed variant (query \"negotiate\" vs doc \"negotiating\") finds no occurrence here.\n // Fall back to the doc's start, unmarked, rather than claim a match that isn't there.\n const end = Math.min(text.length, EXCERPT_WINDOW);\n return { excerpt: `${text.slice(0, end).replace(/\\s+/g, ' ').trim()}${end < text.length ? '…' : ''}`, offset: 0 };\n }\n const start = bestWindowStart(occ, EXCERPT_WINDOW);\n const end = Math.min(text.length, start + EXCERPT_WINDOW);\n let out = '';\n let cursor = start;\n for (const o of occ) {\n if (o.start < start || o.end > end) continue;\n // Occurrences of duplicate or substring-overlapping terms (\"test tests\") can overlap;\n // emitting each would duplicate document text. Keep the first, absorb the rest.\n if (o.start < cursor) continue;\n out += `${text.slice(cursor, o.start)}«${text.slice(o.start, o.end)}»`;\n cursor = o.end;\n }\n out += text.slice(cursor, end);\n const prefix = start > 0 ? '…' : '';\n const suffix = end < text.length ? '…' : '';\n return { excerpt: `${prefix}${out.replace(/\\s+/g, ' ')}${suffix}`, offset: start };\n}\n\nfunction lineNumberAt(text: string, offset: number): number {\n let line = 1;\n for (let i = 0; i < offset; i++) if (text.charCodeAt(i) === 10) line++;\n return line;\n}\n\nfunction lineRangeFor(db: DatabaseSync, path: string, line: number): string | null {\n const row = db.prepare('SELECT start_line, end_line FROM sections WHERE \"path\" = ? AND start_line <= ? AND end_line >= ? ORDER BY start_line DESC LIMIT 1').get(path, line, line) as { start_line: number; end_line: number } | undefined;\n return row ? `L${row.start_line}-${row.end_line}` : null;\n}\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n semantic?: boolean; // invoke vector expansion (requires features.embed); rows gain via 'vector' and a lines column\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\n// Semantic expansion is per-query opt-in: without opts.semantic the result is\n// byte-for-byte independent of the embed feature.\nexport async function find(db: DatabaseSync, cfg: ResolvedConfig, terms: string, opts: FindOptions = {}): Promise<Row[]> {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n // --where applies inside the candidate query (a post-filter over the top-N would drop\n // matches ranked past the pool) and again on the final select for link-derived rows.\n // An explicit --where replaces the tree's declared default rather than ANDing with it,\n // so a caller can always widen back to the whole tree.\n const scope = opts.where ?? cfg.defaults?.find?.where;\n const whereJoin = scope ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = scope ? `AND (${scope})` : '';\n // Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite\n // short-circuits the untaken CASE branch, so it's never invoked on them.\n const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, -1, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string | null }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string | null }>;\n } catch (err) {\n throw searchError(err as Error, terms, scope);\n }\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n // hit === null here means the bound suppressed snippet(), not \"no match\" -- distinguish\n // from via='link' rows (never in matchRows, so absent from this set) below.\n const oversized = new Set(matchRows.filter((r) => r.hit === null).map((r) => r.path));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n const edges = featureEnabled(cfg, 'links') && matchRows.length > 0 ? linkEdges(db) : [];\n if (edges.length > 0) {\n // `linked` gates the label only, not the score: PPR restart mass gives every seed a\n // nonzero rank even without an incident edge, which is not link evidence — but dropping\n // that mass from the score list reweights fusion toward connectivity and measurably\n // wrecks ranking on link-dense corpora (FEVER hit@10 0.997 -> 0.907; fusion-tuning.md).\n const linked = new Set(edges.flat());\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, edges, seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n if (linked.has(path)) existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n // Vector expansion, invoked only: a third RRF list at the swept flat-region constants\n // (weight 1, pool = fetch). Each row carries its best chunk's line range.\n const chunkLines = new Map<string, string>();\n const chunkSimilarity = new Map<string, number>();\n if (opts.semantic) {\n const vec = await semanticCandidates(db, cfg, terms, fetch);\n vec.forEach(({ path, lines, similarity }, i) => {\n chunkLines.set(path, lines);\n chunkSimilarity.set(path, similarity);\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = `${existing.via}+vector`;\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'vector' });\n }\n });\n }\n\n db.exec('DROP TABLE IF EXISTS _find');\n db.exec('CREATE TEMP TABLE _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT, lines TEXT, similarity REAL)');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit, lines, similarity) VALUES (?, ?, ?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null, chunkLines.get(path) ?? null, chunkSimilarity.get(path) ?? null);\n\n const where = scope ? `WHERE ${scope}` : '';\n // lines is always present now: semantic rows carry their chunk's range, oversized-doc\n // lexical rows gain one below, everything else stays null. similarity stays semantic-only.\n const similarityCol = opts.semantic ? ', _find.similarity' : '';\n const rows = db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score, _find.lines${similarityCol}\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n\n if (oversized.size > 0) {\n const bareTerms = extractBareTerms(terms);\n for (const row of rows) {\n if (row.hit !== null || !oversized.has(row.path as string)) continue;\n let text: string;\n try {\n text = readFileSync(join(cfg.baseDir, row.path as string), 'utf8');\n } catch {\n continue; // vanished since the match; leave hit/lines null rather than throw\n }\n const { excerpt, offset } = computeExcerpt(text, bareTerms);\n row.hit = excerpt;\n if (row.lines == null) row.lines = featureEnabled(cfg, 'sections') ? lineRangeFor(db, row.path as string, lineNumberAt(text, offset)) : null;\n }\n }\n\n return rows;\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n features: { on: FeatureName[]; off: FeatureName[] };\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// A result row is capped at SQLITE_MAX_COLUMN (2000, default); two aggregate expressions\n// per field keeps a chunk's row safely under that regardless of how many fields the tree has.\nconst MAP_COLUMN_CHUNK = 300;\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: ResolvedConfig): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n // Observed storage class, not a declared one: these columns are added dynamically and\n // SQLite types per value, so a field can be text in most notes and numeric in a few.\n // Listing every distinct type makes both the type and any drift visible.\n // One aggregate scan per chunk of columns rather than one scan per column: COUNT already\n // ignores NULL, and FILTER keeps typeof() looking only at the non-null rows COUNT counted,\n // matching the old per-column `WHERE ... IS NOT NULL` exactly.\n const allFields: Row[] = [];\n for (const group of chunk(columns, MAP_COLUMN_CHUNK)) {\n const exprs = group.map((name, i) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\n return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const result = db.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter`).get() as Record<string, number | string | null>;\n group.forEach((name, i) => allFields.push({ field: name, coverage: result[`n${i}`] as number, type: (result[`t${i}`] as string) ?? '' }));\n }\n allFields.sort((a, b) => (b.coverage as number) - (a.coverage as number));\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, features: featureStates(cfg), hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks (or a note thousands of\n // headings), and peek's whole point is bounded output. Query the sections/links tables\n // directly for the full list.\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sectionsTotal = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?').get(path) as { n: number }).n : 0;\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?').all(path, PEEK_LIST_LIMIT) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n}\n"],"names":["find","mapTree","peek","WEIGHTED_BM25","RRF_K","SNIPPET_BOUND","EXCERPT_WINDOW","extractBareTerms","query","cleaned","replace","split","map","tok","toLowerCase","trim","filter","length","findOccurrences","haystackLower","terms","occ","term","idx","found","indexOf","push","start","end","sort","a","b","bestWindowStart","windowSize","best","bestDistinct","bestCount","i","winEnd","seen","Set","count","j","add","size","computeExcerpt","text","Math","min","excerpt","slice","offset","out","cursor","o","prefix","suffix","lineNumberAt","line","charCodeAt","lineRangeFor","db","path","row","prepare","get","start_line","end_line","cfg","opts","hits","chunkLines","chunkSimilarity","k","fetch","scope","whereJoin","whereCond","matchSql","matchRows","oversized","candidates","edges","linked","nodes","seeds","ranked","vec","insert","c","where","similarityCol","rows","bareTerms","max","defaults","all","err","searchError","Map","r","hit","forEach","set","score","via","featureEnabled","linkEdges","flat","personalizedRank","existing","has","semantic","semanticCandidates","lines","similarity","exec","run","readFileSync","join","baseDir","INTERNAL_COLUMNS","MAP_COLUMN_CHUNK","chunk","items","docs","columns","name","allFields","group","exprs","quoted","result","field","coverage","type","fields","hubs","recent","fieldsTotal","features","featureStates","PEEK_LIST_LIMIT","pathArg","paths","p","base","posix","basename","matches","SenseError","frontmatter","Object","entries","key","value","sectionsTotal","n","sections","outbound","backlinks","unresolved","backlinksTotal","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}
|
package/dist/cjs/config.d.cts
CHANGED
|
@@ -10,6 +10,12 @@ export interface EmbedConfig {
|
|
|
10
10
|
key?: string;
|
|
11
11
|
}
|
|
12
12
|
export declare const DEFAULT_EMBED_MODEL = "minishlab/potion-retrieval-32M";
|
|
13
|
+
export interface SavedFind {
|
|
14
|
+
find: string;
|
|
15
|
+
k?: number;
|
|
16
|
+
where?: string;
|
|
17
|
+
semantic?: boolean;
|
|
18
|
+
}
|
|
13
19
|
export interface Config {
|
|
14
20
|
$schema?: string;
|
|
15
21
|
version?: number;
|
|
@@ -28,7 +34,7 @@ export interface Config {
|
|
|
28
34
|
};
|
|
29
35
|
};
|
|
30
36
|
checks?: Record<string, 'empty'>;
|
|
31
|
-
queries: Record<string, string>;
|
|
37
|
+
queries: Record<string, string | SavedFind>;
|
|
32
38
|
}
|
|
33
39
|
export interface ResolvedConfig extends Config {
|
|
34
40
|
baseDir: string;
|
package/dist/cjs/config.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface EmbedConfig {
|
|
|
10
10
|
key?: string;
|
|
11
11
|
}
|
|
12
12
|
export declare const DEFAULT_EMBED_MODEL = "minishlab/potion-retrieval-32M";
|
|
13
|
+
export interface SavedFind {
|
|
14
|
+
find: string;
|
|
15
|
+
k?: number;
|
|
16
|
+
where?: string;
|
|
17
|
+
semantic?: boolean;
|
|
18
|
+
}
|
|
13
19
|
export interface Config {
|
|
14
20
|
$schema?: string;
|
|
15
21
|
version?: number;
|
|
@@ -28,7 +34,7 @@ export interface Config {
|
|
|
28
34
|
};
|
|
29
35
|
};
|
|
30
36
|
checks?: Record<string, 'empty'>;
|
|
31
|
-
queries: Record<string, string>;
|
|
37
|
+
queries: Record<string, string | SavedFind>;
|
|
32
38
|
}
|
|
33
39
|
export interface ResolvedConfig extends Config {
|
|
34
40
|
baseDir: string;
|
package/dist/cjs/config.js
CHANGED
|
@@ -169,7 +169,7 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
169
169
|
var CONFIG_FILENAME = 'sense.config.json';
|
|
170
170
|
var STATE_DIR = '.sense';
|
|
171
171
|
var SUPPORTED_CONFIG_VERSION = 2;
|
|
172
|
-
// Each feature owns its tables, parse-time extraction, and reconcile step;
|
|
172
|
+
// Each feature owns its tables, parse-time extraction, and reconcile step; commands degrade when one is off.
|
|
173
173
|
// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.
|
|
174
174
|
var FEATURE_NAMES = [
|
|
175
175
|
'links',
|
|
@@ -336,6 +336,37 @@ function unknownConfigKeys(cfg) {
|
|
|
336
336
|
}
|
|
337
337
|
return unknown;
|
|
338
338
|
}
|
|
339
|
+
var SAVED_FIND_KEYS = new Set([
|
|
340
|
+
'find',
|
|
341
|
+
'k',
|
|
342
|
+
'where',
|
|
343
|
+
'semantic'
|
|
344
|
+
]);
|
|
345
|
+
// A queries.<name> entry that isn't a SQL string must be a saved find: { find, k?, where?, semantic? }.
|
|
346
|
+
function validateSavedFind(name, value, configPath) {
|
|
347
|
+
if ((typeof value === "undefined" ? "undefined" : _type_of(value)) !== 'object' || value === null || Array.isArray(value)) {
|
|
348
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries.").concat(name, " must be a SQL string or { find, k?, where?, semantic? }"));
|
|
349
|
+
}
|
|
350
|
+
var entry = value;
|
|
351
|
+
var unknown = Object.keys(entry).filter(function(k) {
|
|
352
|
+
return !SAVED_FIND_KEYS.has(k);
|
|
353
|
+
});
|
|
354
|
+
if (unknown.length > 0) {
|
|
355
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries.").concat(name, " has unknown key(s) ").concat(unknown.join(', '), "; a saved find takes find, k, where, semantic"));
|
|
356
|
+
}
|
|
357
|
+
if (typeof entry.find !== 'string') {
|
|
358
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries.").concat(name, ".find must be a string"));
|
|
359
|
+
}
|
|
360
|
+
if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {
|
|
361
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries.").concat(name, ".k must be a positive integer"));
|
|
362
|
+
}
|
|
363
|
+
if (entry.where !== undefined && typeof entry.where !== 'string') {
|
|
364
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries.").concat(name, ".where must be a SQL condition string"));
|
|
365
|
+
}
|
|
366
|
+
if (entry.semantic !== undefined && typeof entry.semantic !== 'boolean') {
|
|
367
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries.").concat(name, ".semantic must be a boolean"));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
339
370
|
function validateConfig(parsed, configPath) {
|
|
340
371
|
var _cfg_features;
|
|
341
372
|
var _defaults_find;
|
|
@@ -350,10 +381,29 @@ function validateConfig(parsed, configPath) {
|
|
|
350
381
|
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": scan.include must be a non-empty array of glob strings"));
|
|
351
382
|
}
|
|
352
383
|
if (cfg.queries === undefined) cfg.queries = {};
|
|
353
|
-
if (_type_of(cfg.queries) !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
384
|
+
if (_type_of(cfg.queries) !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {
|
|
385
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries must be an object of name -> SQL string or saved find"));
|
|
386
|
+
}
|
|
387
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
388
|
+
try {
|
|
389
|
+
for(var _iterator = Object.entries(cfg.queries)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
390
|
+
var _step_value = _sliced_to_array(_step.value, 2), name = _step_value[0], value = _step_value[1];
|
|
391
|
+
if (typeof value === 'string') continue;
|
|
392
|
+
validateSavedFind(name, value, configPath);
|
|
393
|
+
}
|
|
394
|
+
} catch (err) {
|
|
395
|
+
_didIteratorError = true;
|
|
396
|
+
_iteratorError = err;
|
|
397
|
+
} finally{
|
|
398
|
+
try {
|
|
399
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
400
|
+
_iterator.return();
|
|
401
|
+
}
|
|
402
|
+
} finally{
|
|
403
|
+
if (_didIteratorError) {
|
|
404
|
+
throw _iteratorError;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
357
407
|
}
|
|
358
408
|
var defaults = cfg.defaults;
|
|
359
409
|
if (defaults !== undefined && ((typeof defaults === "undefined" ? "undefined" : _type_of(defaults)) !== 'object' || defaults === null || Array.isArray(defaults) || defaults.find !== undefined && typeof ((_defaults_find = defaults.find) === null || _defaults_find === void 0 ? void 0 : _defaults_find.where) !== 'string')) {
|
|
@@ -368,23 +418,24 @@ function validateConfig(parsed, configPath) {
|
|
|
368
418
|
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ': checks must be an object of query name -> "empty"'));
|
|
369
419
|
}
|
|
370
420
|
var queries = (_cfg_queries = cfg.queries) !== null && _cfg_queries !== void 0 ? _cfg_queries : {};
|
|
371
|
-
var
|
|
421
|
+
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
372
422
|
try {
|
|
373
|
-
for(var
|
|
374
|
-
var
|
|
375
|
-
if (queries[
|
|
423
|
+
for(var _iterator1 = Object.keys(checks)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
424
|
+
var name1 = _step1.value;
|
|
425
|
+
if (queries[name1] === undefined) throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ': checks names "').concat(name1, '", which is not a saved query'));
|
|
426
|
+
if (typeof queries[name1] !== 'string') throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ': checks names "').concat(name1, '", which is a saved find, not a SQL query'));
|
|
376
427
|
}
|
|
377
428
|
} catch (err) {
|
|
378
|
-
|
|
379
|
-
|
|
429
|
+
_didIteratorError1 = true;
|
|
430
|
+
_iteratorError1 = err;
|
|
380
431
|
} finally{
|
|
381
432
|
try {
|
|
382
|
-
if (!
|
|
383
|
-
|
|
433
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
434
|
+
_iterator1.return();
|
|
384
435
|
}
|
|
385
436
|
} finally{
|
|
386
|
-
if (
|
|
387
|
-
throw
|
|
437
|
+
if (_didIteratorError1) {
|
|
438
|
+
throw _iteratorError1;
|
|
388
439
|
}
|
|
389
440
|
}
|
|
390
441
|
}
|
|
@@ -392,10 +443,10 @@ function validateConfig(parsed, configPath) {
|
|
|
392
443
|
if (cfg.features !== undefined && (_type_of(cfg.features) !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {
|
|
393
444
|
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": features must be an object of name -> boolean"));
|
|
394
445
|
}
|
|
395
|
-
var
|
|
446
|
+
var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
|
|
396
447
|
try {
|
|
397
448
|
var _loop = function() {
|
|
398
|
-
var _step_value = _sliced_to_array(
|
|
449
|
+
var _step_value = _sliced_to_array(_step2.value, 2), name = _step_value[0], value = _step_value[1];
|
|
399
450
|
if (typeof value === 'boolean') return "continue";
|
|
400
451
|
// embed alone takes an object form: { model?, type?: static|api, url?, key? }.
|
|
401
452
|
var embed = value;
|
|
@@ -410,18 +461,18 @@ function validateConfig(parsed, configPath) {
|
|
|
410
461
|
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": features.").concat(name, " must be a boolean").concat(name === 'embed' ? ' or { model?, type?: "static"|"api", url?, key? }' : ''));
|
|
411
462
|
}
|
|
412
463
|
};
|
|
413
|
-
for(var
|
|
464
|
+
for(var _iterator2 = Object.entries((_cfg_features = cfg.features) !== null && _cfg_features !== void 0 ? _cfg_features : {})[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true)_loop();
|
|
414
465
|
} catch (err) {
|
|
415
|
-
|
|
416
|
-
|
|
466
|
+
_didIteratorError2 = true;
|
|
467
|
+
_iteratorError2 = err;
|
|
417
468
|
} finally{
|
|
418
469
|
try {
|
|
419
|
-
if (!
|
|
420
|
-
|
|
470
|
+
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
|
|
471
|
+
_iterator2.return();
|
|
421
472
|
}
|
|
422
473
|
} finally{
|
|
423
|
-
if (
|
|
424
|
-
throw
|
|
474
|
+
if (_didIteratorError2) {
|
|
475
|
+
throw _iteratorError2;
|
|
425
476
|
}
|
|
426
477
|
}
|
|
427
478
|
}
|