sensemaking 0.7.0 → 0.7.1
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/dist/cjs/commands/check.js +19 -6
- package/dist/cjs/commands/check.js.map +1 -1
- package/dist/cjs/commands/shared.js +14 -2
- package/dist/cjs/commands/shared.js.map +1 -1
- package/dist/cjs/config.d.cts +1 -0
- package/dist/cjs/config.d.ts +1 -0
- package/dist/cjs/config.js +40 -9
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/search-error.d.cts +1 -0
- package/dist/cjs/search-error.d.ts +1 -0
- package/dist/cjs/search-error.js +42 -0
- package/dist/cjs/search-error.js.map +1 -0
- package/dist/cjs/verbs.js +2 -31
- package/dist/cjs/verbs.js.map +1 -1
- package/dist/esm/commands/check.js +19 -6
- package/dist/esm/commands/check.js.map +1 -1
- package/dist/esm/commands/shared.js +13 -1
- package/dist/esm/commands/shared.js.map +1 -1
- package/dist/esm/config.d.ts +1 -0
- package/dist/esm/config.js +12 -0
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/search-error.d.ts +1 -0
- package/dist/esm/search-error.js +25 -0
- package/dist/esm/search-error.js.map +1 -0
- package/dist/esm/verbs.js +1 -24
- package/dist/esm/verbs.js.map +1 -1
- package/package.json +1 -1
- package/schema.json +5 -0
- package/skills/sense/SKILL.md +13 -6
|
@@ -75,7 +75,9 @@ var check = function check(ctx) {
|
|
|
75
75
|
for(var _iterator = Object.entries((_cfg_queries = cfg.queries) !== null && _cfg_queries !== void 0 ? _cfg_queries : {})[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
76
76
|
var _step_value = _sliced_to_array(_step.value, 2), name = _step_value[0], sql = _step_value[1];
|
|
77
77
|
var _sql_match;
|
|
78
|
+
var _cfg_checks;
|
|
78
79
|
var params = ((_sql_match = sql.match(/\?/g)) !== null && _sql_match !== void 0 ? _sql_match : []).length;
|
|
80
|
+
var expectEmpty = ((_cfg_checks = cfg.checks) === null || _cfg_checks === void 0 ? void 0 : _cfg_checks[name]) === 'empty';
|
|
79
81
|
try {
|
|
80
82
|
var statement = db.prepare(sql);
|
|
81
83
|
if (params > 0) {
|
|
@@ -88,12 +90,23 @@ var check = function check(ctx) {
|
|
|
88
90
|
continue;
|
|
89
91
|
}
|
|
90
92
|
var count = statement.all().length;
|
|
91
|
-
|
|
92
|
-
query:
|
|
93
|
-
|
|
94
|
-
rows
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
if (expectEmpty) {
|
|
94
|
+
// An invariant query: rows are violations, so 0 is the pass, anything else fails.
|
|
95
|
+
if (count > 0) failed++;
|
|
96
|
+
rows.push({
|
|
97
|
+
query: name,
|
|
98
|
+
params: params,
|
|
99
|
+
rows: count,
|
|
100
|
+
status: count === 0 ? 'ok (empty, as asserted)' : "FAILED: expected empty, returned ".concat(count, " row(s)")
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
rows.push({
|
|
104
|
+
query: name,
|
|
105
|
+
params: params,
|
|
106
|
+
rows: count,
|
|
107
|
+
status: count === 0 ? 'ok, but returns 0 rows' : 'ok'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
97
110
|
} catch (err) {
|
|
98
111
|
failed++;
|
|
99
112
|
rows.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/check.ts"],"sourcesContent":["import { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// A saved query that returns zero rows looks identical to a true empty result, so a broken\n// one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric\n// column reading as \"nothing tagged yet\" for hours. Preparing each query catches syntax and\n// unknown-column errors without executing it; queries that take no parameters are also run\n// for a row count. Parameterised queries are validated but not counted: inventing arguments\n// would report a count for a query nobody ran.\nconst check: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n\n const rows: Row[] = [];\n let failed = 0;\n for (const [name, sql] of Object.entries(cfg.queries ?? {})) {\n const params = (sql.match(/\\?/g) ?? []).length;\n try {\n const statement = db.prepare(sql);\n if (params > 0) {\n rows.push({ query: name, params, rows: '—', status: 'ok (not run: needs parameters)' });\n continue;\n }\n const count = (statement.all() as unknown[]).length;\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok, but returns 0 rows' : 'ok' });\n } catch (err) {\n failed++;\n rows.push({ query: name, params, rows: '—', status: `FAILED: ${(err as Error).message}` });\n }\n }\n\n db.close();\n if (rows.length === 0) {\n console.log('no saved queries in config');\n return;\n }\n printRows(rows, ctx.format);\n if (failed > 0) process.exit(1);\n};\nexport default check;\n"],"names":["check","ctx","cfg","resolveConfig","open","db","warnings","printWarnings","rows","failed","Object","entries","queries","name","sql","params","match","length","statement","prepare","push","query","status","count","all","err","message","close","console","log","printRows","format","process","exit"],"mappings":";;;;+
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/check.ts"],"sourcesContent":["import { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// A saved query that returns zero rows looks identical to a true empty result, so a broken\n// one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric\n// column reading as \"nothing tagged yet\" for hours. Preparing each query catches syntax and\n// unknown-column errors without executing it; queries that take no parameters are also run\n// for a row count. Parameterised queries are validated but not counted: inventing arguments\n// would report a count for a query nobody ran.\nconst check: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n\n const rows: Row[] = [];\n let failed = 0;\n for (const [name, sql] of Object.entries(cfg.queries ?? {})) {\n const params = (sql.match(/\\?/g) ?? []).length;\n const expectEmpty = cfg.checks?.[name] === 'empty';\n try {\n const statement = db.prepare(sql);\n if (params > 0) {\n rows.push({ query: name, params, rows: '—', status: 'ok (not run: needs parameters)' });\n continue;\n }\n const count = (statement.all() as unknown[]).length;\n if (expectEmpty) {\n // An invariant query: rows are violations, so 0 is the pass, anything else fails.\n if (count > 0) failed++;\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok (empty, as asserted)' : `FAILED: expected empty, returned ${count} row(s)` });\n } else {\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok, but returns 0 rows' : 'ok' });\n }\n } catch (err) {\n failed++;\n rows.push({ query: name, params, rows: '—', status: `FAILED: ${(err as Error).message}` });\n }\n }\n\n db.close();\n if (rows.length === 0) {\n console.log('no saved queries in config');\n return;\n }\n printRows(rows, ctx.format);\n if (failed > 0) process.exit(1);\n};\nexport default check;\n"],"names":["check","ctx","cfg","resolveConfig","open","db","warnings","printWarnings","rows","failed","Object","entries","queries","name","sql","params","match","length","expectEmpty","checks","statement","prepare","push","query","status","count","all","err","message","close","console","log","printRows","format","process","exit"],"mappings":";;;;+BAkDA;;;eAAA;;;oBAlDqB;wBAEK;wBACI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG9B,2FAA2F;AAC3F,wFAAwF;AACxF,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,+CAA+C;AAC/C,IAAMA,QAAiB,eAACC;QAOmBC;IANzC,IAAMA,MAAMD,IAAIE,aAAa;IAC7B,IAAyBC,QAAAA,IAAAA,UAAI,EAACF,MAAtBG,KAAiBD,MAAjBC,IAAIC,WAAaF,MAAbE;IACZC,IAAAA,uBAAa,EAACD;IAEd,IAAME,OAAc,EAAE;IACtB,IAAIC,SAAS;QACR,kCAAA,2BAAA;;QAAL,QAAK,YAAqBC,OAAOC,OAAO,EAACT,eAAAA,IAAIU,OAAO,cAAXV,0BAAAA,eAAe,CAAC,uBAApD,SAAA,6BAAA,QAAA,yBAAA,iCAAwD;YAAxD,mCAAA,iBAAOW,uBAAMC;gBACAA;gBACIZ;YADpB,IAAMa,SAAS,EAACD,aAAAA,IAAIE,KAAK,CAAC,oBAAVF,wBAAAA,aAAoB,EAAE,EAAEG,MAAM;YAC9C,IAAMC,cAAchB,EAAAA,cAAAA,IAAIiB,MAAM,cAAVjB,kCAAAA,WAAY,CAACW,KAAK,MAAK;YAC3C,IAAI;gBACF,IAAMO,YAAYf,GAAGgB,OAAO,CAACP;gBAC7B,IAAIC,SAAS,GAAG;oBACdP,KAAKc,IAAI,CAAC;wBAAEC,OAAOV;wBAAME,QAAAA;wBAAQP,MAAM;wBAAKgB,QAAQ;oBAAiC;oBACrF;gBACF;gBACA,IAAMC,QAAQ,AAACL,UAAUM,GAAG,GAAiBT,MAAM;gBACnD,IAAIC,aAAa;oBACf,kFAAkF;oBAClF,IAAIO,QAAQ,GAAGhB;oBACfD,KAAKc,IAAI,CAAC;wBAAEC,OAAOV;wBAAME,QAAAA;wBAAQP,MAAMiB;wBAAOD,QAAQC,UAAU,IAAI,4BAA4B,AAAC,oCAAyC,OAANA,OAAM;oBAAS;gBACrJ,OAAO;oBACLjB,KAAKc,IAAI,CAAC;wBAAEC,OAAOV;wBAAME,QAAAA;wBAAQP,MAAMiB;wBAAOD,QAAQC,UAAU,IAAI,2BAA2B;oBAAK;gBACtG;YACF,EAAE,OAAOE,KAAK;gBACZlB;gBACAD,KAAKc,IAAI,CAAC;oBAAEC,OAAOV;oBAAME,QAAAA;oBAAQP,MAAM;oBAAKgB,QAAQ,AAAC,WAAiC,OAAvB,AAACG,IAAcC,OAAO;gBAAG;YAC1F;QACF;;QArBK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAuBLvB,GAAGwB,KAAK;IACR,IAAIrB,KAAKS,MAAM,KAAK,GAAG;QACrBa,QAAQC,GAAG,CAAC;QACZ;IACF;IACAC,IAAAA,mBAAS,EAACxB,MAAMP,IAAIgC,MAAM;IAC1B,IAAIxB,SAAS,GAAGyB,QAAQC,IAAI,CAAC;AAC/B;IACA,WAAenC"}
|
|
@@ -21,6 +21,7 @@ _export(exports, {
|
|
|
21
21
|
});
|
|
22
22
|
var _dbts = require("../db.js");
|
|
23
23
|
var _outputts = require("../output.js");
|
|
24
|
+
var _searcherrorts = require("../search-error.js");
|
|
24
25
|
function _array_like_to_array(arr, len) {
|
|
25
26
|
if (len == null || len > arr.length) len = arr.length;
|
|
26
27
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -237,7 +238,6 @@ function withDb(ctx, fn) {
|
|
|
237
238
|
})();
|
|
238
239
|
}
|
|
239
240
|
function runSql(cfg, sql, params, format, label) {
|
|
240
|
-
var _db_prepare;
|
|
241
241
|
var _sql_match;
|
|
242
242
|
var placeholderCount = ((_sql_match = sql.match(/\?/g)) !== null && _sql_match !== void 0 ? _sql_match : []).length;
|
|
243
243
|
if (params.length !== placeholderCount) {
|
|
@@ -246,7 +246,19 @@ function runSql(cfg, sql, params, format, label) {
|
|
|
246
246
|
}
|
|
247
247
|
var _open = (0, _dbts.open)(cfg), db = _open.db, warnings = _open.warnings;
|
|
248
248
|
printWarnings(warnings);
|
|
249
|
-
|
|
249
|
+
var rows;
|
|
250
|
+
try {
|
|
251
|
+
var _db_prepare;
|
|
252
|
+
rows = (_db_prepare = db.prepare(sql)).all.apply(_db_prepare, _to_consumable_array(params));
|
|
253
|
+
} catch (err) {
|
|
254
|
+
db.close();
|
|
255
|
+
// A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5
|
|
256
|
+
// punctuation trap applies -- the bound parameters are the search terms there. SQL
|
|
257
|
+
// without MATCH gets its error verbatim; search advice on a plain typo would mislead.
|
|
258
|
+
if (/\bMATCH\b/i.test(sql)) throw (0, _searcherrorts.searchError)(err, params.join(' '));
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
(0, _outputts.printRows)(rows, format);
|
|
250
262
|
db.close();
|
|
251
263
|
}
|
|
252
264
|
/* 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/commands/shared.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/shared.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { searchError } from '../search-error.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n let rows: Row[];\n try {\n rows = db.prepare(sql).all(...params) as Row[];\n } catch (err) {\n db.close();\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5\n // punctuation trap applies -- the bound parameters are the search terms there. SQL\n // without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(err as Error, params.join(' '));\n throw err;\n }\n printRows(rows, format);\n db.close();\n}\n"],"names":["printWarnings","runSql","withDb","warnings","w","console","warn","ctx","fn","cfg","open","db","resolveConfig","close","sql","params","format","label","placeholderCount","match","length","error","process","exit","rows","prepare","all","err","test","searchError","join","printRows"],"mappings":";;;;;;;;;;;QAUgBA;eAAAA;;QAgBAC;eAAAA;;QAZMC;eAAAA;;;oBAZD;wBAEK;6BACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKrB,SAASF,cAAcG,QAAkB;QACzC,kCAAA,2BAAA;;QAAL,QAAK,YAAWA,6BAAX,SAAA,6BAAA,QAAA,yBAAA;YAAA,IAAMC,IAAN;YAAqBC,QAAQC,IAAI,CAACF;;;QAAlC;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;AACP;AAEO,SAAeF,OAAOK,GAAQ,EAAEC,EAAuE;;YACtGC,KACmBC,OAAjBC,IAAIR;;;;oBADNM,MAAMF,IAAIK,aAAa;oBACJF,QAAAA,IAAAA,UAAI,EAACD,MAAtBE,KAAiBD,MAAjBC,IAAIR,WAAaO,MAAbP;oBACZH,cAAcG;;;;;;;;;oBAEZ;;wBAAMK,GAAGG,IAAIF;;;oBAAb;;;;;;oBAEAE,GAAGE,KAAK;;;;;;;;;;IAEZ;;AAGO,SAASZ,OAAOQ,GAAmB,EAAEK,GAAW,EAAEC,MAAgB,EAAEC,MAAwB,EAAEC,KAAa;QACtFH;IAA1B,IAAMI,mBAAmB,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;IACxD,IAAIL,OAAOK,MAAM,KAAKF,kBAAkB;QACtCb,QAAQgB,KAAK,CAAC,AAAC,GAAmBH,OAAjBD,OAAM,aAAiDF,OAAtCG,kBAAiB,uBAAmC,OAAdH,OAAOK,MAAM;QACrFE,QAAQC,IAAI,CAAC;IACf;IACA,IAAyBb,QAAAA,IAAAA,UAAI,EAACD,MAAtBE,KAAiBD,MAAjBC,IAAIR,WAAaO,MAAbP;IACZH,cAAcG;IACd,IAAIqB;IACJ,IAAI;YACKb;QAAPa,OAAOb,CAAAA,cAAAA,GAAGc,OAAO,CAACX,MAAKY,GAAG,OAAnBf,aAAoB,qBAAGI;IAChC,EAAE,OAAOY,KAAK;QACZhB,GAAGE,KAAK;QACR,gFAAgF;QAChF,mFAAmF;QACnF,sFAAsF;QACtF,IAAI,aAAae,IAAI,CAACd,MAAM,MAAMe,IAAAA,0BAAW,EAACF,KAAcZ,OAAOe,IAAI,CAAC;QACxE,MAAMH;IACR;IACAI,IAAAA,mBAAS,EAACP,MAAMR;IAChBL,GAAGE,KAAK;AACV"}
|
package/dist/cjs/config.d.cts
CHANGED
package/dist/cjs/config.d.ts
CHANGED
package/dist/cjs/config.js
CHANGED
|
@@ -304,6 +304,7 @@ var KNOWN_KEYS = new Set([
|
|
|
304
304
|
'scan',
|
|
305
305
|
'features',
|
|
306
306
|
'defaults',
|
|
307
|
+
'checks',
|
|
307
308
|
'queries'
|
|
308
309
|
]);
|
|
309
310
|
var KNOWN_SCAN_KEYS = new Set([
|
|
@@ -358,13 +359,43 @@ function validateConfig(parsed, configPath) {
|
|
|
358
359
|
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')) {
|
|
359
360
|
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": defaults.find.where must be a SQL condition string"));
|
|
360
361
|
}
|
|
362
|
+
var checks = cfg.checks;
|
|
363
|
+
if (checks !== undefined) {
|
|
364
|
+
var _cfg_queries;
|
|
365
|
+
if ((typeof checks === "undefined" ? "undefined" : _type_of(checks)) !== 'object' || checks === null || Array.isArray(checks) || !Object.values(checks).every(function(v) {
|
|
366
|
+
return v === 'empty';
|
|
367
|
+
})) {
|
|
368
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ': checks must be an object of query name -> "empty"'));
|
|
369
|
+
}
|
|
370
|
+
var queries = (_cfg_queries = cfg.queries) !== null && _cfg_queries !== void 0 ? _cfg_queries : {};
|
|
371
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
372
|
+
try {
|
|
373
|
+
for(var _iterator = Object.keys(checks)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
374
|
+
var name = _step.value;
|
|
375
|
+
if (queries[name] === undefined) throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ': checks names "').concat(name, '", which is not a saved query'));
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
_didIteratorError = true;
|
|
379
|
+
_iteratorError = err;
|
|
380
|
+
} finally{
|
|
381
|
+
try {
|
|
382
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
383
|
+
_iterator.return();
|
|
384
|
+
}
|
|
385
|
+
} finally{
|
|
386
|
+
if (_didIteratorError) {
|
|
387
|
+
throw _iteratorError;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
361
392
|
if (cfg.features !== undefined && (_type_of(cfg.features) !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {
|
|
362
393
|
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": features must be an object of name -> boolean"));
|
|
363
394
|
}
|
|
364
|
-
var
|
|
395
|
+
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
365
396
|
try {
|
|
366
397
|
var _loop = function() {
|
|
367
|
-
var _step_value = _sliced_to_array(
|
|
398
|
+
var _step_value = _sliced_to_array(_step1.value, 2), name = _step_value[0], value = _step_value[1];
|
|
368
399
|
if (typeof value === 'boolean') return "continue";
|
|
369
400
|
// embed alone takes an object form: { model?, type?: static|api, url?, key? }.
|
|
370
401
|
var embed = value;
|
|
@@ -379,18 +410,18 @@ function validateConfig(parsed, configPath) {
|
|
|
379
410
|
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? }' : ''));
|
|
380
411
|
}
|
|
381
412
|
};
|
|
382
|
-
for(var
|
|
413
|
+
for(var _iterator1 = Object.entries((_cfg_features = cfg.features) !== null && _cfg_features !== void 0 ? _cfg_features : {})[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true)_loop();
|
|
383
414
|
} catch (err) {
|
|
384
|
-
|
|
385
|
-
|
|
415
|
+
_didIteratorError1 = true;
|
|
416
|
+
_iteratorError1 = err;
|
|
386
417
|
} finally{
|
|
387
418
|
try {
|
|
388
|
-
if (!
|
|
389
|
-
|
|
419
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
420
|
+
_iterator1.return();
|
|
390
421
|
}
|
|
391
422
|
} finally{
|
|
392
|
-
if (
|
|
393
|
-
throw
|
|
423
|
+
if (_didIteratorError1) {
|
|
424
|
+
throw _iteratorError1;
|
|
394
425
|
}
|
|
395
426
|
}
|
|
396
427
|
}
|
package/dist/cjs/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n // Tree-declared default scope for `find`. A --where on the invocation replaces it, so a\n // caller can always reach the whole tree (`--where \"1=1\"`).\n defaults?: { find?: { where?: string } };\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; verbs surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\n// Keys each block actually reads. A key outside these sets is accepted and ignored, which\n// looks identical to working -- one field report set `scan.exclude`, saw no change in doc\n// count and no warning, and reasonably concluded the filter had applied. Reported as a\n// warning rather than an error so a config carrying an unknown key still runs.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'scan', 'features', 'defaults', 'queries']);\nconst KNOWN_SCAN_KEYS = new Set(['include']);\n\nfunction unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n const unknown = Object.keys(cfg)\n .filter((k) => !KNOWN_KEYS.has(k))\n .map((k) => k);\n const scan = cfg.scan as Record<string, unknown> | undefined;\n if (scan && typeof scan === 'object' && !Array.isArray(scan)) {\n unknown.push(\n ...Object.keys(scan)\n .filter((k) => !KNOWN_SCAN_KEYS.has(k))\n .map((k) => `scan.${k}`)\n );\n }\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features && typeof features === 'object' && !Array.isArray(features)) {\n unknown.push(\n ...Object.keys(features)\n .filter((k) => !(FEATURE_NAMES as readonly string[]).includes(k))\n .map((k) => `features.${k}`)\n );\n }\n return unknown;\n}\n\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n const defaults = cfg.defaults as { find?: { where?: unknown } } | undefined;\n if (defaults !== undefined && (typeof defaults !== 'object' || defaults === null || Array.isArray(defaults) || (defaults.find !== undefined && typeof defaults.find?.where !== 'string'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n const unknownKeys = unknownConfigKeys(cfg as unknown as Record<string, unknown>);\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom, unknownKeys: unknownKeys.length > 0 ? unknownKeys : undefined };\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","STATE_DIR","SUPPORTED_CONFIG_VERSION","embedConfig","enabledFeatures","featureEnabled","featureSignature","featureStates","findConfigPath","initConfig","loadConfig","migrateConfig","FEATURE_NAMES","OPT_OUT_NAMES","cfg","name","Boolean","features","embed","enabled","filter","on","off","o","e","model","type","url","key","map","join","MIGRATIONS","version","Object","fromEntries","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","KNOWN_KEYS","Set","KNOWN_SCAN_KEYS","unknownConfigKeys","unknown","keys","k","has","Array","isArray","push","includes","validateConfig","parsed","defaults","length","every","g","undefined","values","q","find","where","value","shapeOk","f","entries","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","unknownKeys","baseDir"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n // Tree-declared default scope for `find`. A --where on the invocation replaces it, so a\n // caller can always reach the whole tree (`--where \"1=1\"`).\n defaults?: { find?: { where?: string } };\n // Assertions over saved queries: `checks: { \"dead-links\": \"empty\" }` makes `sense check`\n // fail when that query returns rows -- for invariant queries where 0 rows is the pass.\n checks?: Record<string, 'empty'>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; verbs surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\n// Keys each block actually reads. A key outside these sets is accepted and ignored, which\n// looks identical to working -- one field report set `scan.exclude`, saw no change in doc\n// count and no warning, and reasonably concluded the filter had applied. Reported as a\n// warning rather than an error so a config carrying an unknown key still runs.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'scan', 'features', 'defaults', 'checks', 'queries']);\nconst KNOWN_SCAN_KEYS = new Set(['include']);\n\nfunction unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n const unknown = Object.keys(cfg)\n .filter((k) => !KNOWN_KEYS.has(k))\n .map((k) => k);\n const scan = cfg.scan as Record<string, unknown> | undefined;\n if (scan && typeof scan === 'object' && !Array.isArray(scan)) {\n unknown.push(\n ...Object.keys(scan)\n .filter((k) => !KNOWN_SCAN_KEYS.has(k))\n .map((k) => `scan.${k}`)\n );\n }\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features && typeof features === 'object' && !Array.isArray(features)) {\n unknown.push(\n ...Object.keys(features)\n .filter((k) => !(FEATURE_NAMES as readonly string[]).includes(k))\n .map((k) => `features.${k}`)\n );\n }\n return unknown;\n}\n\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n const defaults = cfg.defaults as { find?: { where?: unknown } } | undefined;\n if (defaults !== undefined && (typeof defaults !== 'object' || defaults === null || Array.isArray(defaults) || (defaults.find !== undefined && typeof defaults.find?.where !== 'string'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);\n }\n const checks = cfg.checks as Record<string, unknown> | undefined;\n if (checks !== undefined) {\n if (typeof checks !== 'object' || checks === null || Array.isArray(checks) || !Object.values(checks).every((v) => v === 'empty')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks must be an object of query name -> \"empty\"`);\n }\n const queries = (cfg.queries ?? {}) as Record<string, unknown>;\n for (const name of Object.keys(checks)) {\n if (queries[name] === undefined) throw new SenseError('CONFIG_INVALID', `${configPath}: checks names \"${name}\", which is not a saved query`);\n }\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n const unknownKeys = unknownConfigKeys(cfg as unknown as Record<string, unknown>);\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom, unknownKeys: unknownKeys.length > 0 ? unknownKeys : undefined };\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","STATE_DIR","SUPPORTED_CONFIG_VERSION","embedConfig","enabledFeatures","featureEnabled","featureSignature","featureStates","findConfigPath","initConfig","loadConfig","migrateConfig","FEATURE_NAMES","OPT_OUT_NAMES","cfg","name","Boolean","features","embed","enabled","filter","on","off","o","e","model","type","url","key","map","join","MIGRATIONS","version","Object","fromEntries","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","KNOWN_KEYS","Set","KNOWN_SCAN_KEYS","unknownConfigKeys","unknown","keys","k","has","Array","isArray","push","includes","validateConfig","parsed","defaults","length","every","g","undefined","values","q","find","where","checks","value","shapeOk","f","entries","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","unknownKeys","baseDir"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function searchError(err: Error, terms: string, scope?: string): Error;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function searchError(err: Error, terms: string, scope?: string): Error;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "searchError", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return searchError;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
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.
|
|
16
|
+
var FTS5_OPERATORS = /[-'"/.:^*()]/;
|
|
17
|
+
function searchError(err, terms, scope) {
|
|
18
|
+
var _terms_match;
|
|
19
|
+
var _exec;
|
|
20
|
+
var message = err.message;
|
|
21
|
+
if (!/no such column|fts5: syntax error|malformed MATCH/.test(message)) return err;
|
|
22
|
+
var suspects = ((_terms_match = terms.match(/\S+/g)) !== null && _terms_match !== void 0 ? _terms_match : []).filter(function(t) {
|
|
23
|
+
return !t.startsWith('"') && FTS5_OPERATORS.test(t);
|
|
24
|
+
});
|
|
25
|
+
// Blame the terms only when the failing token actually came from one -- a typo'd column
|
|
26
|
+
// in --where (or the tree's default scope) raises "no such column" through this same
|
|
27
|
+
// statement, and naming a term for it would state a false fact about the input.
|
|
28
|
+
var col = (_exec = /no such column: (\S+)/.exec(message)) === null || _exec === void 0 ? void 0 : _exec[1];
|
|
29
|
+
var fromTerms = col === undefined ? suspects.length > 0 : suspects.some(function(t) {
|
|
30
|
+
return t.split(RegExp("[^\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}]+", "u")).includes(col);
|
|
31
|
+
});
|
|
32
|
+
if (fromTerms && suspects.length > 0) {
|
|
33
|
+
return new _errorsts.SenseError('SEARCH_SYNTAX', "".concat(message, " -- the punctuation in ").concat(suspects.map(function(t) {
|
|
34
|
+
return "`".concat(t, "`");
|
|
35
|
+
}).join(', '), " is FTS5 syntax, not literal text; search for it literally by double-quoting: '\"").concat(suspects[0], "\"'. Searchable columns are title, summary, text."));
|
|
36
|
+
}
|
|
37
|
+
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')\"."));
|
|
39
|
+
}
|
|
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'))."));
|
|
41
|
+
}
|
|
42
|
+
/* 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/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"}
|
package/dist/cjs/verbs.js
CHANGED
|
@@ -25,6 +25,7 @@ var _errorsts = require("./errors.js");
|
|
|
25
25
|
var _embedts = require("./features/embed.js");
|
|
26
26
|
var _indexts = require("./features/index.js");
|
|
27
27
|
var _graphts = require("./graph.js");
|
|
28
|
+
var _searcherrorts = require("./search-error.js");
|
|
28
29
|
function _array_like_to_array(arr, len) {
|
|
29
30
|
if (len == null || len > arr.length) len = arr.length;
|
|
30
31
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -220,36 +221,6 @@ function _ts_generator(thisArg, body) {
|
|
|
220
221
|
// Each returns data; cli.ts renders. All of them degrade when a feature is off.
|
|
221
222
|
var WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';
|
|
222
223
|
var RRF_K = 60;
|
|
223
|
-
// FTS5 reads these as operators, so a bare term containing one is a syntax error or --
|
|
224
|
-
// worse -- a column filter: `end-to-end` parses as a filter on column `to`, and SQLite
|
|
225
|
-
// reports `no such column: to`, which is a true statement about the parse and a false
|
|
226
|
-
// one about the input. Both field reports on 0.6.0 misdiagnosed that message.
|
|
227
|
-
var FTS5_OPERATORS = /[-'"/.:^*()]/;
|
|
228
|
-
function searchError(err, terms, scope) {
|
|
229
|
-
var _terms_match;
|
|
230
|
-
var _exec;
|
|
231
|
-
var message = err.message;
|
|
232
|
-
if (!/no such column|fts5: syntax error|malformed MATCH/.test(message)) return err;
|
|
233
|
-
var suspects = ((_terms_match = terms.match(/\S+/g)) !== null && _terms_match !== void 0 ? _terms_match : []).filter(function(t) {
|
|
234
|
-
return !t.startsWith('"') && FTS5_OPERATORS.test(t);
|
|
235
|
-
});
|
|
236
|
-
// Blame the terms only when the failing token actually came from one -- a typo'd column
|
|
237
|
-
// in --where (or the tree's default scope) raises "no such column" through this same
|
|
238
|
-
// statement, and naming a term for it would state a false fact about the input.
|
|
239
|
-
var col = (_exec = /no such column: (\S+)/.exec(message)) === null || _exec === void 0 ? void 0 : _exec[1];
|
|
240
|
-
var fromTerms = col === undefined ? suspects.length > 0 : suspects.some(function(t) {
|
|
241
|
-
return t.split(RegExp("[^\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}\\u0030-\\u0039\\u00B2-\\u00B3\\u00B9\\u00BC-\\u00BE\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u09F4-\\u09F9\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0B72-\\u0B77\\u0BE6-\\u0BF2\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0CE6-\\u0CEF\\u0D58-\\u0D5E\\u0D66-\\u0D78\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F33\\u1040-\\u1049\\u1090-\\u1099\\u1369-\\u137C\\u16EE-\\u16F0\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19DA\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\u2070\\u2074-\\u2079\\u2080-\\u2089\\u2150-\\u2182\\u2185-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2CFD\\u3007\\u3021-\\u3029\\u3038-\\u303A\\u3192-\\u3195\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\uA620-\\uA629\\uA6E6-\\uA6EF\\uA830-\\uA835\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}-\\u{1018B}\\u{102E1}-\\u{102FB}\\u{10320}-\\u{10323}\\u{10341}\\u{1034A}\\u{103D1}-\\u{103D5}\\u{104A0}-\\u{104A9}\\u{10858}-\\u{1085F}\\u{10879}-\\u{1087F}\\u{108A7}-\\u{108AF}\\u{108FB}-\\u{108FF}\\u{10916}-\\u{1091B}\\u{109BC}-\\u{109BD}\\u{109C0}-\\u{109CF}\\u{109D2}-\\u{109FF}\\u{10A40}-\\u{10A48}\\u{10A7D}-\\u{10A7E}\\u{10A9D}-\\u{10A9F}\\u{10AEB}-\\u{10AEF}\\u{10B58}-\\u{10B5F}\\u{10B78}-\\u{10B7F}\\u{10BA9}-\\u{10BAF}\\u{10CFA}-\\u{10CFF}\\u{10D30}-\\u{10D39}\\u{10D40}-\\u{10D49}\\u{10E60}-\\u{10E7E}\\u{10F1D}-\\u{10F26}\\u{10F51}-\\u{10F54}\\u{10FC5}-\\u{10FCB}\\u{11052}-\\u{1106F}\\u{110F0}-\\u{110F9}\\u{11136}-\\u{1113F}\\u{111D0}-\\u{111D9}\\u{111E1}-\\u{111F4}\\u{112F0}-\\u{112F9}\\u{11450}-\\u{11459}\\u{114D0}-\\u{114D9}\\u{11650}-\\u{11659}\\u{116C0}-\\u{116C9}\\u{116D0}-\\u{116E3}\\u{11730}-\\u{1173B}\\u{118E0}-\\u{118F2}\\u{11950}-\\u{11959}\\u{11BF0}-\\u{11BF9}\\u{11C50}-\\u{11C6C}\\u{11D50}-\\u{11D59}\\u{11DA0}-\\u{11DA9}\\u{11DE0}-\\u{11DE9}\\u{11F50}-\\u{11F59}\\u{11FC0}-\\u{11FD4}\\u{12400}-\\u{1246E}\\u{16130}-\\u{16139}\\u{16A60}-\\u{16A69}\\u{16AC0}-\\u{16AC9}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16D70}-\\u{16D79}\\u{16E80}-\\u{16E96}\\u{16FF4}-\\u{16FF6}\\u{1CCF0}-\\u{1CCF9}\\u{1D2C0}-\\u{1D2D3}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D7CE}-\\u{1D7FF}\\u{1E140}-\\u{1E149}\\u{1E2F0}-\\u{1E2F9}\\u{1E4F0}-\\u{1E4F9}\\u{1E5F1}-\\u{1E5FA}\\u{1E8C7}-\\u{1E8CF}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1F100}-\\u{1F10C}\\u{1FBF0}-\\u{1FBF9}]+", "u")).includes(col);
|
|
242
|
-
});
|
|
243
|
-
if (fromTerms && suspects.length > 0) {
|
|
244
|
-
return new _errorsts.SenseError('SEARCH_SYNTAX', "".concat(message, " -- the punctuation in ").concat(suspects.map(function(t) {
|
|
245
|
-
return "`".concat(t, "`");
|
|
246
|
-
}).join(', '), " is FTS5 syntax, not literal text; search for it literally by double-quoting: '\"").concat(suspects[0], "\"'. Searchable columns are title, summary, text."));
|
|
247
|
-
}
|
|
248
|
-
if (col !== undefined && scope !== undefined) {
|
|
249
|
-
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')\"."));
|
|
250
|
-
}
|
|
251
|
-
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'))."));
|
|
252
|
-
}
|
|
253
224
|
function find(_0, _1, _2) {
|
|
254
225
|
return _async_to_generator(function(db, cfg, terms) {
|
|
255
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, linesCol;
|
|
@@ -273,7 +244,7 @@ function find(_0, _1, _2) {
|
|
|
273
244
|
try {
|
|
274
245
|
matchRows = db.prepare(matchSql).all(terms);
|
|
275
246
|
} catch (err) {
|
|
276
|
-
throw searchError(err, terms, scope);
|
|
247
|
+
throw (0, _searcherrorts.searchError)(err, terms, scope);
|
|
277
248
|
}
|
|
278
249
|
hits = new Map(matchRows.map(function(r) {
|
|
279
250
|
return [
|
package/dist/cjs/verbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, FeatureName } 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';\n\n// The three layer verbs: 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 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\nfunction 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\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: Config, 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 const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\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 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 const linesCol = opts.semantic ? ', _find.lines, _find.similarity' : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score${linesCol}\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\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// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): 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 const allFields = columns\n .map((name) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\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 const { n, types } = db.prepare(`SELECT COUNT(${quoted}) AS n, GROUP_CONCAT(DISTINCT typeof(${quoted})) AS types FROM frontmatter WHERE ${quoted} IS NOT NULL`).get() as { n: number; types: string | null };\n return { field: name, coverage: n, type: types ?? '' };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\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, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LINK_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: Config, 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 sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) 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_LINK_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_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\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","FTS5_OPERATORS","searchError","err","terms","scope","message","test","suspects","match","filter","t","startsWith","col","exec","fromTerms","undefined","length","some","split","includes","SenseError","map","join","db","cfg","opts","hits","chunkLines","chunkSimilarity","k","fetch","whereJoin","whereCond","matchSql","matchRows","candidates","edges","linked","nodes","seeds","ranked","vec","insert","path","c","where","linesCol","Math","max","defaults","prepare","all","Map","r","hit","forEach","i","set","score","via","featureEnabled","linkEdges","Set","flat","personalizedRank","sort","a","b","slice","existing","get","has","semantic","semanticCandidates","lines","similarity","run","INTERNAL_COLUMNS","docs","columns","name","allFields","quoted","n","types","field","coverage","type","fields","hubs","recent","fieldsTotal","features","featureStates","PEEK_LINK_LIMIT","pathArg","row","paths","p","base","posix","basename","replace","toLowerCase","matches","frontmatter","Object","entries","key","value","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, FeatureName } 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 verbs: 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\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: Config, 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 const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\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 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 const linesCol = opts.semantic ? ', _find.lines, _find.similarity' : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score${linesCol}\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\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// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): 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 const allFields = columns\n .map((name) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\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 const { n, types } = db.prepare(`SELECT COUNT(${quoted}) AS n, GROUP_CONCAT(DISTINCT typeof(${quoted})) AS types FROM frontmatter WHERE ${quoted} IS NOT NULL`).get() as { n: number; types: string | null };\n return { field: name, coverage: n, type: types ?? '' };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\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, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LINK_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: Config, 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 sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) 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_LINK_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_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\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","db","cfg","terms","opts","hits","chunkLines","chunkSimilarity","k","fetch","scope","whereJoin","whereCond","matchSql","matchRows","candidates","edges","linked","nodes","seeds","ranked","vec","insert","path","c","where","linesCol","Math","max","defaults","prepare","all","err","searchError","Map","map","r","hit","forEach","i","set","score","via","featureEnabled","length","linkEdges","Set","flat","personalizedRank","filter","sort","a","b","slice","existing","get","has","semantic","semanticCandidates","lines","similarity","exec","run","INTERNAL_COLUMNS","docs","columns","name","allFields","quoted","split","join","n","types","field","coverage","type","fields","hubs","recent","fieldsTotal","features","featureStates","PEEK_LINK_LIMIT","pathArg","row","paths","p","base","posix","basename","replace","toLowerCase","matches","SenseError","frontmatter","Object","entries","key","value","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}
|
|
@@ -16,7 +16,9 @@ const check = (ctx)=>{
|
|
|
16
16
|
let failed = 0;
|
|
17
17
|
for (const [name, sql] of Object.entries((_cfg_queries = cfg.queries) !== null && _cfg_queries !== void 0 ? _cfg_queries : {})){
|
|
18
18
|
var _sql_match;
|
|
19
|
+
var _cfg_checks;
|
|
19
20
|
const params = ((_sql_match = sql.match(/\?/g)) !== null && _sql_match !== void 0 ? _sql_match : []).length;
|
|
21
|
+
const expectEmpty = ((_cfg_checks = cfg.checks) === null || _cfg_checks === void 0 ? void 0 : _cfg_checks[name]) === 'empty';
|
|
20
22
|
try {
|
|
21
23
|
const statement = db.prepare(sql);
|
|
22
24
|
if (params > 0) {
|
|
@@ -29,12 +31,23 @@ const check = (ctx)=>{
|
|
|
29
31
|
continue;
|
|
30
32
|
}
|
|
31
33
|
const count = statement.all().length;
|
|
32
|
-
|
|
33
|
-
query:
|
|
34
|
-
|
|
35
|
-
rows
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (expectEmpty) {
|
|
35
|
+
// An invariant query: rows are violations, so 0 is the pass, anything else fails.
|
|
36
|
+
if (count > 0) failed++;
|
|
37
|
+
rows.push({
|
|
38
|
+
query: name,
|
|
39
|
+
params,
|
|
40
|
+
rows: count,
|
|
41
|
+
status: count === 0 ? 'ok (empty, as asserted)' : `FAILED: expected empty, returned ${count} row(s)`
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
rows.push({
|
|
45
|
+
query: name,
|
|
46
|
+
params,
|
|
47
|
+
rows: count,
|
|
48
|
+
status: count === 0 ? 'ok, but returns 0 rows' : 'ok'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
38
51
|
} catch (err) {
|
|
39
52
|
failed++;
|
|
40
53
|
rows.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/check.ts"],"sourcesContent":["import { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// A saved query that returns zero rows looks identical to a true empty result, so a broken\n// one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric\n// column reading as \"nothing tagged yet\" for hours. Preparing each query catches syntax and\n// unknown-column errors without executing it; queries that take no parameters are also run\n// for a row count. Parameterised queries are validated but not counted: inventing arguments\n// would report a count for a query nobody ran.\nconst check: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n\n const rows: Row[] = [];\n let failed = 0;\n for (const [name, sql] of Object.entries(cfg.queries ?? {})) {\n const params = (sql.match(/\\?/g) ?? []).length;\n try {\n const statement = db.prepare(sql);\n if (params > 0) {\n rows.push({ query: name, params, rows: '—', status: 'ok (not run: needs parameters)' });\n continue;\n }\n const count = (statement.all() as unknown[]).length;\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok, but returns 0 rows' : 'ok' });\n } catch (err) {\n failed++;\n rows.push({ query: name, params, rows: '—', status: `FAILED: ${(err as Error).message}` });\n }\n }\n\n db.close();\n if (rows.length === 0) {\n console.log('no saved queries in config');\n return;\n }\n printRows(rows, ctx.format);\n if (failed > 0) process.exit(1);\n};\nexport default check;\n"],"names":["open","printRows","printWarnings","check","ctx","cfg","resolveConfig","db","warnings","rows","failed","name","sql","Object","entries","queries","params","match","length","statement","prepare","push","query","status","count","all","err","message","close","console","log","format","process","exit"],"mappings":"AAAA,SAASA,IAAI,QAAQ,WAAW;AAEhC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,aAAa,QAAQ,cAAc;AAG5C,2FAA2F;AAC3F,wFAAwF;AACxF,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,+CAA+C;AAC/C,MAAMC,QAAiB,CAACC;QAOmBC;IANzC,MAAMA,MAAMD,IAAIE,aAAa;IAC7B,MAAM,EAAEC,EAAE,EAAEC,QAAQ,EAAE,GAAGR,KAAKK;IAC9BH,cAAcM;IAEd,MAAMC,OAAc,EAAE;IACtB,IAAIC,SAAS;IACb,KAAK,MAAM,CAACC,MAAMC,IAAI,IAAIC,OAAOC,OAAO,EAACT,eAAAA,IAAIU,OAAO,cAAXV,0BAAAA,eAAe,CAAC,GAAI;YAC3CO;
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/check.ts"],"sourcesContent":["import { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// A saved query that returns zero rows looks identical to a true empty result, so a broken\n// one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric\n// column reading as \"nothing tagged yet\" for hours. Preparing each query catches syntax and\n// unknown-column errors without executing it; queries that take no parameters are also run\n// for a row count. Parameterised queries are validated but not counted: inventing arguments\n// would report a count for a query nobody ran.\nconst check: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n\n const rows: Row[] = [];\n let failed = 0;\n for (const [name, sql] of Object.entries(cfg.queries ?? {})) {\n const params = (sql.match(/\\?/g) ?? []).length;\n const expectEmpty = cfg.checks?.[name] === 'empty';\n try {\n const statement = db.prepare(sql);\n if (params > 0) {\n rows.push({ query: name, params, rows: '—', status: 'ok (not run: needs parameters)' });\n continue;\n }\n const count = (statement.all() as unknown[]).length;\n if (expectEmpty) {\n // An invariant query: rows are violations, so 0 is the pass, anything else fails.\n if (count > 0) failed++;\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok (empty, as asserted)' : `FAILED: expected empty, returned ${count} row(s)` });\n } else {\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok, but returns 0 rows' : 'ok' });\n }\n } catch (err) {\n failed++;\n rows.push({ query: name, params, rows: '—', status: `FAILED: ${(err as Error).message}` });\n }\n }\n\n db.close();\n if (rows.length === 0) {\n console.log('no saved queries in config');\n return;\n }\n printRows(rows, ctx.format);\n if (failed > 0) process.exit(1);\n};\nexport default check;\n"],"names":["open","printRows","printWarnings","check","ctx","cfg","resolveConfig","db","warnings","rows","failed","name","sql","Object","entries","queries","params","match","length","expectEmpty","checks","statement","prepare","push","query","status","count","all","err","message","close","console","log","format","process","exit"],"mappings":"AAAA,SAASA,IAAI,QAAQ,WAAW;AAEhC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,aAAa,QAAQ,cAAc;AAG5C,2FAA2F;AAC3F,wFAAwF;AACxF,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,+CAA+C;AAC/C,MAAMC,QAAiB,CAACC;QAOmBC;IANzC,MAAMA,MAAMD,IAAIE,aAAa;IAC7B,MAAM,EAAEC,EAAE,EAAEC,QAAQ,EAAE,GAAGR,KAAKK;IAC9BH,cAAcM;IAEd,MAAMC,OAAc,EAAE;IACtB,IAAIC,SAAS;IACb,KAAK,MAAM,CAACC,MAAMC,IAAI,IAAIC,OAAOC,OAAO,EAACT,eAAAA,IAAIU,OAAO,cAAXV,0BAAAA,eAAe,CAAC,GAAI;YAC3CO;YACIP;QADpB,MAAMW,SAAS,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;QAC9C,MAAMC,cAAcd,EAAAA,cAAAA,IAAIe,MAAM,cAAVf,kCAAAA,WAAY,CAACM,KAAK,MAAK;QAC3C,IAAI;YACF,MAAMU,YAAYd,GAAGe,OAAO,CAACV;YAC7B,IAAII,SAAS,GAAG;gBACdP,KAAKc,IAAI,CAAC;oBAAEC,OAAOb;oBAAMK;oBAAQP,MAAM;oBAAKgB,QAAQ;gBAAiC;gBACrF;YACF;YACA,MAAMC,QAAQ,AAACL,UAAUM,GAAG,GAAiBT,MAAM;YACnD,IAAIC,aAAa;gBACf,kFAAkF;gBAClF,IAAIO,QAAQ,GAAGhB;gBACfD,KAAKc,IAAI,CAAC;oBAAEC,OAAOb;oBAAMK;oBAAQP,MAAMiB;oBAAOD,QAAQC,UAAU,IAAI,4BAA4B,CAAC,iCAAiC,EAAEA,MAAM,OAAO,CAAC;gBAAC;YACrJ,OAAO;gBACLjB,KAAKc,IAAI,CAAC;oBAAEC,OAAOb;oBAAMK;oBAAQP,MAAMiB;oBAAOD,QAAQC,UAAU,IAAI,2BAA2B;gBAAK;YACtG;QACF,EAAE,OAAOE,KAAK;YACZlB;YACAD,KAAKc,IAAI,CAAC;gBAAEC,OAAOb;gBAAMK;gBAAQP,MAAM;gBAAKgB,QAAQ,CAAC,QAAQ,EAAE,AAACG,IAAcC,OAAO,EAAE;YAAC;QAC1F;IACF;IAEAtB,GAAGuB,KAAK;IACR,IAAIrB,KAAKS,MAAM,KAAK,GAAG;QACrBa,QAAQC,GAAG,CAAC;QACZ;IACF;IACA/B,UAAUQ,MAAML,IAAI6B,MAAM;IAC1B,IAAIvB,SAAS,GAAGwB,QAAQC,IAAI,CAAC;AAC/B;AACA,eAAehC,MAAM"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { open } from '../db.js';
|
|
2
2
|
import { printRows } from '../output.js';
|
|
3
|
+
import { searchError } from '../search-error.js';
|
|
3
4
|
// Shared open-query-close envelope for commands that touch the tree.
|
|
4
5
|
export function printWarnings(warnings) {
|
|
5
6
|
for (const w of warnings)console.warn(w);
|
|
@@ -24,6 +25,17 @@ export function runSql(cfg, sql, params, format, label) {
|
|
|
24
25
|
}
|
|
25
26
|
const { db, warnings } = open(cfg);
|
|
26
27
|
printWarnings(warnings);
|
|
27
|
-
|
|
28
|
+
let rows;
|
|
29
|
+
try {
|
|
30
|
+
rows = db.prepare(sql).all(...params);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
db.close();
|
|
33
|
+
// A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5
|
|
34
|
+
// punctuation trap applies -- the bound parameters are the search terms there. SQL
|
|
35
|
+
// without MATCH gets its error verbatim; search advice on a plain typo would mislead.
|
|
36
|
+
if (/\bMATCH\b/i.test(sql)) throw searchError(err, params.join(' '));
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
printRows(rows, format);
|
|
28
40
|
db.close();
|
|
29
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/shared.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/shared.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { searchError } from '../search-error.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n let rows: Row[];\n try {\n rows = db.prepare(sql).all(...params) as Row[];\n } catch (err) {\n db.close();\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5\n // punctuation trap applies -- the bound parameters are the search terms there. SQL\n // without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(err as Error, params.join(' '));\n throw err;\n }\n printRows(rows, format);\n db.close();\n}\n"],"names":["open","printRows","searchError","printWarnings","warnings","w","console","warn","withDb","ctx","fn","cfg","resolveConfig","db","close","runSql","sql","params","format","label","placeholderCount","match","length","error","process","exit","rows","prepare","all","err","test","join"],"mappings":"AAEA,SAASA,IAAI,QAAQ,WAAW;AAEhC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,WAAW,QAAQ,qBAAqB;AAGjD,qEAAqE;AAErE,OAAO,SAASC,cAAcC,QAAkB;IAC9C,KAAK,MAAMC,KAAKD,SAAUE,QAAQC,IAAI,CAACF;AACzC;AAEA,OAAO,eAAeG,OAAOC,GAAQ,EAAEC,EAAuE;IAC5G,MAAMC,MAAMF,IAAIG,aAAa;IAC7B,MAAM,EAAEC,EAAE,EAAET,QAAQ,EAAE,GAAGJ,KAAKW;IAC9BR,cAAcC;IACd,IAAI;QACF,MAAMM,GAAGG,IAAIF;IACf,SAAU;QACRE,GAAGC,KAAK;IACV;AACF;AAEA,sFAAsF;AACtF,OAAO,SAASC,OAAOJ,GAAmB,EAAEK,GAAW,EAAEC,MAAgB,EAAEC,MAAwB,EAAEC,KAAa;QACtFH;IAA1B,MAAMI,mBAAmB,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;IACxD,IAAIL,OAAOK,MAAM,KAAKF,kBAAkB;QACtCd,QAAQiB,KAAK,CAAC,GAAGJ,MAAM,SAAS,EAAEC,iBAAiB,mBAAmB,EAAEH,OAAOK,MAAM,EAAE;QACvFE,QAAQC,IAAI,CAAC;IACf;IACA,MAAM,EAAEZ,EAAE,EAAET,QAAQ,EAAE,GAAGJ,KAAKW;IAC9BR,cAAcC;IACd,IAAIsB;IACJ,IAAI;QACFA,OAAOb,GAAGc,OAAO,CAACX,KAAKY,GAAG,IAAIX;IAChC,EAAE,OAAOY,KAAK;QACZhB,GAAGC,KAAK;QACR,gFAAgF;QAChF,mFAAmF;QACnF,sFAAsF;QACtF,IAAI,aAAagB,IAAI,CAACd,MAAM,MAAMd,YAAY2B,KAAcZ,OAAOc,IAAI,CAAC;QACxE,MAAMF;IACR;IACA5B,UAAUyB,MAAMR;IAChBL,GAAGC,KAAK;AACV"}
|
package/dist/esm/config.d.ts
CHANGED
package/dist/esm/config.js
CHANGED
|
@@ -136,6 +136,7 @@ const KNOWN_KEYS = new Set([
|
|
|
136
136
|
'scan',
|
|
137
137
|
'features',
|
|
138
138
|
'defaults',
|
|
139
|
+
'checks',
|
|
139
140
|
'queries'
|
|
140
141
|
]);
|
|
141
142
|
const KNOWN_SCAN_KEYS = new Set([
|
|
@@ -172,6 +173,17 @@ function validateConfig(parsed, configPath) {
|
|
|
172
173
|
if (defaults !== undefined && (typeof 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')) {
|
|
173
174
|
throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);
|
|
174
175
|
}
|
|
176
|
+
const checks = cfg.checks;
|
|
177
|
+
if (checks !== undefined) {
|
|
178
|
+
var _cfg_queries;
|
|
179
|
+
if (typeof checks !== 'object' || checks === null || Array.isArray(checks) || !Object.values(checks).every((v)=>v === 'empty')) {
|
|
180
|
+
throw new SenseError('CONFIG_INVALID', `${configPath}: checks must be an object of query name -> "empty"`);
|
|
181
|
+
}
|
|
182
|
+
const queries = (_cfg_queries = cfg.queries) !== null && _cfg_queries !== void 0 ? _cfg_queries : {};
|
|
183
|
+
for (const name of Object.keys(checks)){
|
|
184
|
+
if (queries[name] === undefined) throw new SenseError('CONFIG_INVALID', `${configPath}: checks names "${name}", which is not a saved query`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
175
187
|
if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {
|
|
176
188
|
throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);
|
|
177
189
|
}
|
package/dist/esm/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n // Tree-declared default scope for `find`. A --where on the invocation replaces it, so a\n // caller can always reach the whole tree (`--where \"1=1\"`).\n defaults?: { find?: { where?: string } };\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; verbs surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\n// Keys each block actually reads. A key outside these sets is accepted and ignored, which\n// looks identical to working -- one field report set `scan.exclude`, saw no change in doc\n// count and no warning, and reasonably concluded the filter had applied. Reported as a\n// warning rather than an error so a config carrying an unknown key still runs.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'scan', 'features', 'defaults', 'queries']);\nconst KNOWN_SCAN_KEYS = new Set(['include']);\n\nfunction unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n const unknown = Object.keys(cfg)\n .filter((k) => !KNOWN_KEYS.has(k))\n .map((k) => k);\n const scan = cfg.scan as Record<string, unknown> | undefined;\n if (scan && typeof scan === 'object' && !Array.isArray(scan)) {\n unknown.push(\n ...Object.keys(scan)\n .filter((k) => !KNOWN_SCAN_KEYS.has(k))\n .map((k) => `scan.${k}`)\n );\n }\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features && typeof features === 'object' && !Array.isArray(features)) {\n unknown.push(\n ...Object.keys(features)\n .filter((k) => !(FEATURE_NAMES as readonly string[]).includes(k))\n .map((k) => `features.${k}`)\n );\n }\n return unknown;\n}\n\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n const defaults = cfg.defaults as { find?: { where?: unknown } } | undefined;\n if (defaults !== undefined && (typeof defaults !== 'object' || defaults === null || Array.isArray(defaults) || (defaults.find !== undefined && typeof defaults.find?.where !== 'string'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n const unknownKeys = unknownConfigKeys(cfg as unknown as Record<string, unknown>);\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom, unknownKeys: unknownKeys.length > 0 ? unknownKeys : undefined };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","OPT_OUT_NAMES","DEFAULT_EMBED_MODEL","featureEnabled","cfg","name","Boolean","features","embed","enabled","enabledFeatures","filter","featureStates","on","off","embedConfig","o","e","model","type","url","key","featureSignature","map","MIGRATIONS","version","Object","fromEntries","migrateConfig","from","current","v","step","starterConfig","$schema","scan","include","queries","initConfig","dir","configPath","JSON","stringify","findConfigPath","startDir","candidate","parent","KNOWN_KEYS","Set","KNOWN_SCAN_KEYS","unknownConfigKeys","unknown","keys","k","has","Array","isArray","push","includes","validateConfig","parsed","defaults","length","every","g","undefined","values","q","find","where","value","entries","shapeOk","f","loadConfig","explicitPath","process","cwd","found","raw","parse","migratedFrom","result","unknownKeys","baseDir"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n // Tree-declared default scope for `find`. A --where on the invocation replaces it, so a\n // caller can always reach the whole tree (`--where \"1=1\"`).\n defaults?: { find?: { where?: string } };\n // Assertions over saved queries: `checks: { \"dead-links\": \"empty\" }` makes `sense check`\n // fail when that query returns rows -- for invariant queries where 0 rows is the pass.\n checks?: Record<string, 'empty'>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; verbs surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\n// Keys each block actually reads. A key outside these sets is accepted and ignored, which\n// looks identical to working -- one field report set `scan.exclude`, saw no change in doc\n// count and no warning, and reasonably concluded the filter had applied. Reported as a\n// warning rather than an error so a config carrying an unknown key still runs.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'scan', 'features', 'defaults', 'checks', 'queries']);\nconst KNOWN_SCAN_KEYS = new Set(['include']);\n\nfunction unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n const unknown = Object.keys(cfg)\n .filter((k) => !KNOWN_KEYS.has(k))\n .map((k) => k);\n const scan = cfg.scan as Record<string, unknown> | undefined;\n if (scan && typeof scan === 'object' && !Array.isArray(scan)) {\n unknown.push(\n ...Object.keys(scan)\n .filter((k) => !KNOWN_SCAN_KEYS.has(k))\n .map((k) => `scan.${k}`)\n );\n }\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features && typeof features === 'object' && !Array.isArray(features)) {\n unknown.push(\n ...Object.keys(features)\n .filter((k) => !(FEATURE_NAMES as readonly string[]).includes(k))\n .map((k) => `features.${k}`)\n );\n }\n return unknown;\n}\n\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n const defaults = cfg.defaults as { find?: { where?: unknown } } | undefined;\n if (defaults !== undefined && (typeof defaults !== 'object' || defaults === null || Array.isArray(defaults) || (defaults.find !== undefined && typeof defaults.find?.where !== 'string'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);\n }\n const checks = cfg.checks as Record<string, unknown> | undefined;\n if (checks !== undefined) {\n if (typeof checks !== 'object' || checks === null || Array.isArray(checks) || !Object.values(checks).every((v) => v === 'empty')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks must be an object of query name -> \"empty\"`);\n }\n const queries = (cfg.queries ?? {}) as Record<string, unknown>;\n for (const name of Object.keys(checks)) {\n if (queries[name] === undefined) throw new SenseError('CONFIG_INVALID', `${configPath}: checks names \"${name}\", which is not a saved query`);\n }\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n const unknownKeys = unknownConfigKeys(cfg as unknown as Record<string, unknown>);\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom, unknownKeys: unknownKeys.length > 0 ? unknownKeys : undefined };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","OPT_OUT_NAMES","DEFAULT_EMBED_MODEL","featureEnabled","cfg","name","Boolean","features","embed","enabled","enabledFeatures","filter","featureStates","on","off","embedConfig","o","e","model","type","url","key","featureSignature","map","MIGRATIONS","version","Object","fromEntries","migrateConfig","from","current","v","step","starterConfig","$schema","scan","include","queries","initConfig","dir","configPath","JSON","stringify","findConfigPath","startDir","candidate","parent","KNOWN_KEYS","Set","KNOWN_SCAN_KEYS","unknownConfigKeys","unknown","keys","k","has","Array","isArray","push","includes","validateConfig","parsed","defaults","length","every","g","undefined","values","q","find","where","checks","value","entries","shapeOk","f","loadConfig","explicitPath","process","cwd","found","raw","parse","migratedFrom","result","unknownKeys","baseDir"],"mappings":"AAAA,SAASA,UAAU,EAAEC,YAAY,EAAEC,aAAa,QAAQ,UAAU;AAClE,SAASC,OAAO,EAAEC,IAAI,EAAEC,OAAO,QAAQ,YAAY;AACnD,SAASC,UAAU,QAAQ,cAAc;AAEzC,OAAO,MAAMC,kBAAkB,oBAAoB;AACnD,OAAO,MAAMC,YAAY,SAAS;AAElC,mGAAmG;AACnG,OAAO,MAAMC,2BAA2B,EAAE;AAE1C,0GAA0G;AAC1G,kHAAkH;AAClH,MAAMC,gBAAgB;IAAC;IAAS;IAAY;IAAQ;CAAQ;AAC5D,MAAMC,gBAAgB;IAAC;IAAS;IAAY;CAAO;AAWnD,OAAO,MAAMC,sBAAsB,iCAAiC;AA0BpE,6FAA6F;AAC7F,iEAAiE;AACjE,OAAO,SAASC,eAAeC,GAAW,EAAEC,IAAiB;QACtBD,eACrBA;IADhB,IAAIC,SAAS,SAAS,OAAOC,SAAQF,gBAAAA,IAAIG,QAAQ,cAAZH,oCAAAA,cAAcI,KAAK;IACxD,MAAMC,UAAUL,EAAAA,iBAAAA,IAAIG,QAAQ,cAAZH,qCAAAA,cAAc,CAACC,KAAK,MAAK;IACzC,IAAIA,SAAS,QAAQ,OAAOI,WAAWN,eAAeC,KAAK;IAC3D,OAAOK;AACT;AAEA,OAAO,SAASC,gBAAgBN,GAAW;IACzC,OAAOJ,cAAcW,MAAM,CAAC,CAACN,OAASF,eAAeC,KAAKC;AAC5D;AAEA,qFAAqF;AACrF,6BAA6B;AAC7B,OAAO,SAASO,cAAcR,GAAW;IACvC,OAAO;QACLS,IAAIb,cAAcW,MAAM,CAAC,CAACN,OAASF,eAAeC,KAAKC;QACvDS,KAAKd,cAAcW,MAAM,CAAC,CAACN,OAAS,CAACF,eAAeC,KAAKC;IAC3D;AACF;AAEA,4DAA4D;AAC5D,OAAO,SAASU,YAAYX,GAAW;QAIrBY,UAAsCA;QAH5CZ;IAAV,MAAMa,KAAIb,gBAAAA,IAAIG,QAAQ,cAAZH,oCAAAA,cAAcI,KAAK;IAC7B,IAAI,CAACS,GAAG,OAAO;IACf,MAAMD,IAAIC,MAAM,OAAO,CAAC,IAAIA;IAC5B,OAAO;QAAEC,KAAK,GAAEF,WAAAA,EAAEE,KAAK,cAAPF,sBAAAA,WAAWd;QAAqBiB,IAAI,GAAEH,UAAAA,EAAEG,IAAI,cAANH,qBAAAA,UAAU;QAAUI,KAAKJ,EAAEI,GAAG;QAAEC,KAAKL,EAAEK,GAAG;IAAC;AACnG;AAEA,6FAA6F;AAC7F,OAAO,SAASC,iBAAiBlB,GAAW;IAC1C,OAAOM,gBAAgBN,KACpBmB,GAAG,CAAC,CAAClB;QACJ,IAAIA,SAAS,SAAS,OAAOA;QAC7B,MAAMY,IAAIF,YAAYX;QACtB,OAAO,CAAC,MAAM,EAAEa,cAAAA,wBAAAA,EAAGE,IAAI,CAAC,CAAC,EAAEF,cAAAA,wBAAAA,EAAGC,KAAK,EAAE;IACvC,GACCxB,IAAI,CAAC;AACV;AAEA,yGAAyG;AACzG,MAAM8B,aAAsD;IAC1D,0FAA0F;IAC1F,wEAAwE;IACxE,GAAG,CAACpB,MAAS,CAAA;YAAE,GAAGA,GAAG;YAAEqB,SAAS;YAAGlB,UAAUmB,OAAOC,WAAW,CAAC1B,cAAcsB,GAAG,CAAC,CAAClB,OAAS;oBAACA;oBAAM;iBAAK;QAAyB,CAAA;AACnI;AAEA,OAAO,SAASuB,cAAcxB,GAAW;QAC1BA;IAAb,MAAMyB,QAAOzB,eAAAA,IAAIqB,OAAO,cAAXrB,0BAAAA,eAAe;IAC5B,IAAI0B,UAAU1B;IACd,IAAK,IAAI2B,IAAIF,MAAME,IAAIhC,0BAA0BgC,IAAK;QACpD,MAAMC,OAAOR,UAAU,CAACO,EAAE;QAC1B,IAAI,CAACC,MAAM,MAAM,IAAIpC,WAAW,8BAA8B,CAAC,iCAAiC,EAAEmC,GAAG;QACrGD,UAAUE,KAAKF;IACjB;IACA,OAAO;QAAE1B,KAAK0B;QAASD;IAAK;AAC9B;AAEA,SAASI;IACP,OAAO;QACLC,SAAS;QACTT,SAAS1B;QACToC,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7B7B,UAAUmB,OAAOC,WAAW,CAAC1B,cAAcsB,GAAG,CAAC,CAAClB,OAAS;gBAACA;gBAAM;aAAK;QACrEgC,SAAS,CAAC;IACZ;AACF;AAEA,2CAA2C;AAC3C,OAAO,SAASC,WAAWC,GAAW;IACpC,MAAMC,aAAa9C,KAAK6C,KAAK1C;IAC7B,IAAIP,WAAWkD,aAAa;QAC1B,MAAM,IAAI5C,WAAW,iBAAiB,GAAGC,gBAAgB,mBAAmB,EAAE0C,KAAK;IACrF;IACA/C,cAAcgD,YAAY,GAAGC,KAAKC,SAAS,CAACT,iBAAiB,MAAM,GAAG,EAAE,CAAC;IACzE,OAAOO;AACT;AAEA,OAAO,SAASG,eAAeC,QAAgB;IAC7C,IAAIL,MAAM5C,QAAQiD;IAClB,OAAS;QACP,MAAMC,YAAYnD,KAAK6C,KAAK1C;QAC5B,IAAIP,WAAWuD,YAAY,OAAOA;QAClC,MAAMC,SAASrD,QAAQ8C;QACvB,IAAIO,WAAWP,KAAK,OAAO;QAC3BA,MAAMO;IACR;AACF;AAEA,qFAAqF;AACrF,sFAAsF;AACtF,iEAAiE;AACjE,0FAA0F;AAC1F,0FAA0F;AAC1F,uFAAuF;AACvF,+EAA+E;AAC/E,MAAMC,aAAa,IAAIC,IAAI;IAAC;IAAW;IAAW;IAAQ;IAAY;IAAY;IAAU;CAAU;AACtG,MAAMC,kBAAkB,IAAID,IAAI;IAAC;CAAU;AAE3C,SAASE,kBAAkB9C,GAA4B;IACrD,MAAM+C,UAAUzB,OAAO0B,IAAI,CAAChD,KACzBO,MAAM,CAAC,CAAC0C,IAAM,CAACN,WAAWO,GAAG,CAACD,IAC9B9B,GAAG,CAAC,CAAC8B,IAAMA;IACd,MAAMlB,OAAO/B,IAAI+B,IAAI;IACrB,IAAIA,QAAQ,OAAOA,SAAS,YAAY,CAACoB,MAAMC,OAAO,CAACrB,OAAO;QAC5DgB,QAAQM,IAAI,IACP/B,OAAO0B,IAAI,CAACjB,MACZxB,MAAM,CAAC,CAAC0C,IAAM,CAACJ,gBAAgBK,GAAG,CAACD,IACnC9B,GAAG,CAAC,CAAC8B,IAAM,CAAC,KAAK,EAAEA,GAAG;IAE7B;IACA,MAAM9C,WAAWH,IAAIG,QAAQ;IAC7B,IAAIA,YAAY,OAAOA,aAAa,YAAY,CAACgD,MAAMC,OAAO,CAACjD,WAAW;QACxE4C,QAAQM,IAAI,IACP/B,OAAO0B,IAAI,CAAC7C,UACZI,MAAM,CAAC,CAAC0C,IAAM,CAAC,AAACrD,cAAoC0D,QAAQ,CAACL,IAC7D9B,GAAG,CAAC,CAAC8B,IAAM,CAAC,SAAS,EAAEA,GAAG;IAEjC;IACA,OAAOF;AACT;AAEA,SAASQ,eAAeC,MAAe,EAAEpB,UAAkB;QA8BdpC;QAhB2GyD;IAbtJ,IAAI,OAAOD,WAAW,YAAYA,WAAW,QAAQL,MAAMC,OAAO,CAACI,SAAS;QAC1E,MAAM,IAAIhE,WAAW,kBAAkB,GAAG4C,WAAW,8BAA8B,CAAC;IACtF;IACA,MAAMpC,MAAMwD;IACZ,MAAMzB,OAAO/B,IAAI+B,IAAI;IACrB,IAAI,CAACA,QAAQ,CAACoB,MAAMC,OAAO,CAACrB,KAAKC,OAAO,KAAKD,KAAKC,OAAO,CAAC0B,MAAM,KAAK,KAAK,CAAC3B,KAAKC,OAAO,CAAC2B,KAAK,CAAC,CAACC,IAAM,OAAOA,MAAM,WAAW;QAC3H,MAAM,IAAIpE,WAAW,kBAAkB,GAAG4C,WAAW,wDAAwD,CAAC;IAChH;IACA,IAAIpC,IAAIiC,OAAO,KAAK4B,WAAW7D,IAAIiC,OAAO,GAAG,CAAC;IAC9C,IAAI,OAAOjC,IAAIiC,OAAO,KAAK,YAAYjC,IAAIiC,OAAO,KAAK,QAAQkB,MAAMC,OAAO,CAACpD,IAAIiC,OAAO,KAAK,CAACX,OAAOwC,MAAM,CAAC9D,IAAIiC,OAAO,EAAE0B,KAAK,CAAC,CAACI,IAAM,OAAOA,MAAM,WAAW;QAC5J,MAAM,IAAIvE,WAAW,kBAAkB,GAAG4C,WAAW,iDAAiD,CAAC;IACzG;IACA,MAAMqB,WAAWzD,IAAIyD,QAAQ;IAC7B,IAAIA,aAAaI,aAAc,CAAA,OAAOJ,aAAa,YAAYA,aAAa,QAAQN,MAAMC,OAAO,CAACK,aAAcA,SAASO,IAAI,KAAKH,aAAa,SAAOJ,iBAAAA,SAASO,IAAI,cAAbP,qCAAAA,eAAeQ,KAAK,MAAK,QAAQ,GAAI;QACzL,MAAM,IAAIzE,WAAW,kBAAkB,GAAG4C,WAAW,oDAAoD,CAAC;IAC5G;IACA,MAAM8B,SAASlE,IAAIkE,MAAM;IACzB,IAAIA,WAAWL,WAAW;YAIP7D;QAHjB,IAAI,OAAOkE,WAAW,YAAYA,WAAW,QAAQf,MAAMC,OAAO,CAACc,WAAW,CAAC5C,OAAOwC,MAAM,CAACI,QAAQP,KAAK,CAAC,CAAChC,IAAMA,MAAM,UAAU;YAChI,MAAM,IAAInC,WAAW,kBAAkB,GAAG4C,WAAW,mDAAmD,CAAC;QAC3G;QACA,MAAMH,WAAWjC,eAAAA,IAAIiC,OAAO,cAAXjC,0BAAAA,eAAe,CAAC;QACjC,KAAK,MAAMC,QAAQqB,OAAO0B,IAAI,CAACkB,QAAS;YACtC,IAAIjC,OAAO,CAAChC,KAAK,KAAK4D,WAAW,MAAM,IAAIrE,WAAW,kBAAkB,GAAG4C,WAAW,gBAAgB,EAAEnC,KAAK,6BAA6B,CAAC;QAC7I;IACF;IACA,IAAID,IAAIG,QAAQ,KAAK0D,aAAc,CAAA,OAAO7D,IAAIG,QAAQ,KAAK,YAAYH,IAAIG,QAAQ,KAAK,QAAQgD,MAAMC,OAAO,CAACpD,IAAIG,QAAQ,CAAA,GAAI;QAC5H,MAAM,IAAIX,WAAW,kBAAkB,GAAG4C,WAAW,+CAA+C,CAAC;IACvG;IACA,KAAK,MAAM,CAACnC,MAAMkE,MAAM,IAAI7C,OAAO8C,OAAO,EAACpE,gBAAAA,IAAIG,QAAQ,cAAZH,2BAAAA,gBAAgB,CAAC,GAAI;QAC9D,IAAI,OAAOmE,UAAU,WAAW;QAChC,+EAA+E;QAC/E,MAAM/D,QAAQ+D;QACd,MAAME,UAAUpE,SAAS,WAAW,OAAOkE,UAAU,YAAYA,UAAU,QAAQ,CAAChB,MAAMC,OAAO,CAACe,UAAU;YAAC;YAAS;YAAO;SAAM,CAACR,KAAK,CAAC,CAACW,IAAMlE,KAAK,CAACkE,EAAE,KAAKT,aAAa,OAAOzD,KAAK,CAACkE,EAAE,KAAK,aAAclE,CAAAA,MAAMW,IAAI,KAAK8C,aAAazD,MAAMW,IAAI,KAAK,YAAYX,MAAMW,IAAI,KAAK,KAAI;QACvR,IAAI,CAACsD,SAAS;YACZ,MAAM,IAAI7E,WAAW,kBAAkB,GAAG4C,WAAW,WAAW,EAAEnC,KAAK,kBAAkB,EAAEA,SAAS,UAAU,sDAAsD,IAAI;QAC1K;IACF;IACA,OAAOD;AACT;AAEA,OAAO,SAASuE,WAAWC,YAAqB;IAC9C,IAAIpC;IACJ,IAAIoC,cAAc;QAChBpC,aAAa7C,QAAQkF,QAAQC,GAAG,IAAIF;QACpC,IAAI,CAACtF,WAAWkD,aAAa,MAAM,IAAI5C,WAAW,oBAAoB,CAAC,kBAAkB,EAAE4C,YAAY;IACzG,OAAO;QACL,MAAMuC,QAAQpC,eAAekC,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAInF,WAAW,oBAAoB,CAAC,eAAe,EAAEC,gBAAgB,IAAI,EAAEgF,QAAQC,GAAG,GAAG,wBAAwB,CAAC;QAC1H;QACAtC,aAAauC;IACf;IAEA,MAAMC,MAAMzF,aAAaiD,YAAY;IACrC,MAAMoB,SAAkBnB,KAAKwC,KAAK,CAACD;IAEnC,sFAAsF;IACtF,qFAAqF;IACrF,MAAMvD,UAAU,OAAOmC,WAAW,YAAYA,WAAW,QAAQ,OAAO,AAACA,OAAiCnC,OAAO,KAAK,WAAW,AAACmC,OAA+BnC,OAAO,GAAG;IAC3K,IAAIA,UAAU1B,0BAA0B;QACtC,MAAM,IAAIH,WAAW,8BAA8B,CAAC,eAAe,EAAE6B,QAAQ,uBAAuB,CAAC;IACvG;IAEA,IAAIrB,MAAMuD,eAAeC,QAAQpB;IAEjC,IAAI0C;IACJ,IAAIzD,UAAU1B,0BAA0B;QACtC,MAAMoF,SAASvD,cAAcxB;QAC7BA,MAAM+E,OAAO/E,GAAG;QAChB8E,eAAeC,OAAOtD,IAAI;QAC1BrC,cAAcgD,YAAY,GAAGC,KAAKC,SAAS,CAACtC,KAAK,MAAM,GAAG,EAAE,CAAC;IAC/D;IAEA,MAAMgF,cAAclC,kBAAkB9C;IACtC,OAAO;QAAE,GAAGA,GAAG;QAAEiF,SAAS5F,QAAQ+C;QAAaA;QAAY0C;QAAcE,aAAaA,YAAYtB,MAAM,GAAG,IAAIsB,cAAcnB;IAAU;AACzI"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function searchError(err: Error, terms: string, scope?: string): Error;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SenseError } from './errors.js';
|
|
2
|
+
// FTS5 reads these as operators, so a bare term containing one is a syntax error or --
|
|
3
|
+
// worse -- a column filter: `end-to-end` parses as a filter on column `to`, and SQLite
|
|
4
|
+
// reports `no such column: to`, which is a true statement about the parse and a false
|
|
5
|
+
// one about the input. Both field reports on 0.6.0 misdiagnosed that message.
|
|
6
|
+
const FTS5_OPERATORS = /[-'"/.:^*()]/;
|
|
7
|
+
export function searchError(err, terms, scope) {
|
|
8
|
+
var _terms_match;
|
|
9
|
+
var _exec;
|
|
10
|
+
const message = err.message;
|
|
11
|
+
if (!/no such column|fts5: syntax error|malformed MATCH/.test(message)) return err;
|
|
12
|
+
const suspects = ((_terms_match = terms.match(/\S+/g)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>!t.startsWith('"') && FTS5_OPERATORS.test(t));
|
|
13
|
+
// Blame the terms only when the failing token actually came from one -- a typo'd column
|
|
14
|
+
// in --where (or the tree's default scope) raises "no such column" through this same
|
|
15
|
+
// statement, and naming a term for it would state a false fact about the input.
|
|
16
|
+
const col = (_exec = /no such column: (\S+)/.exec(message)) === null || _exec === void 0 ? void 0 : _exec[1];
|
|
17
|
+
const fromTerms = col === undefined ? suspects.length > 0 : suspects.some((t)=>t.split(/[^\p{L}\p{N}]+/u).includes(col));
|
|
18
|
+
if (fromTerms && suspects.length > 0) {
|
|
19
|
+
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.`);
|
|
20
|
+
}
|
|
21
|
+
if (col !== undefined && scope !== undefined) {
|
|
22
|
+
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')".`);
|
|
23
|
+
}
|
|
24
|
+
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')).`);
|
|
25
|
+
}
|
|
@@ -0,0 +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":["SenseError","FTS5_OPERATORS","searchError","err","terms","scope","message","test","suspects","match","filter","t","startsWith","col","exec","fromTerms","undefined","length","some","split","includes","map","join"],"mappings":"AAAA,SAASA,UAAU,QAAQ,cAAc;AAEzC,uFAAuF;AACvF,uFAAuF;AACvF,sFAAsF;AACtF,8EAA8E;AAC9E,MAAMC,iBAAiB;AAEvB,OAAO,SAASC,YAAYC,GAAU,EAAEC,KAAa,EAAEC,KAAc;QAGjDD;QAIN;IANZ,MAAME,UAAUH,IAAIG,OAAO;IAC3B,IAAI,CAAC,oDAAoDC,IAAI,CAACD,UAAU,OAAOH;IAC/E,MAAMK,WAAW,EAACJ,eAAAA,MAAMK,KAAK,CAAC,qBAAZL,0BAAAA,eAAuB,EAAE,EAAEM,MAAM,CAAC,CAACC,IAAM,CAACA,EAAEC,UAAU,CAAC,QAAQX,eAAeM,IAAI,CAACI;IACrG,wFAAwF;IACxF,qFAAqF;IACrF,gFAAgF;IAChF,MAAME,OAAM,QAAA,wBAAwBC,IAAI,CAACR,sBAA7B,4BAAA,KAAuC,CAAC,EAAE;IACtD,MAAMS,YAAYF,QAAQG,YAAYR,SAASS,MAAM,GAAG,IAAIT,SAASU,IAAI,CAAC,CAACP,IAAMA,EAAEQ,KAAK,CAAC,mBAAmBC,QAAQ,CAACP;IACrH,IAAIE,aAAaP,SAASS,MAAM,GAAG,GAAG;QACpC,OAAO,IAAIjB,WAAW,iBAAiB,GAAGM,QAAQ,uBAAuB,EAAEE,SAASa,GAAG,CAAC,CAACV,IAAM,CAAC,EAAE,EAAEA,EAAE,EAAE,CAAC,EAAEW,IAAI,CAAC,MAAM,gFAAgF,EAAEd,QAAQ,CAAC,EAAE,CAAC,gDAAgD,CAAC;IACvQ;IACA,IAAIK,QAAQG,aAAaX,UAAUW,WAAW;QAC5C,OAAO,IAAIhB,WAAW,iBAAiB,GAAGM,QAAQ,yBAAyB,EAAED,MAAM,mHAAmH,CAAC;IACzM;IACA,OAAO,IAAIL,WAAW,iBAAiB,GAAGM,QAAQ,8JAA8J,CAAC;AACnN"}
|
package/dist/esm/verbs.js
CHANGED
|
@@ -4,34 +4,11 @@ import { SenseError } from './errors.js';
|
|
|
4
4
|
import { semanticCandidates } from './features/embed.js';
|
|
5
5
|
import { linkEdges } from './features/index.js';
|
|
6
6
|
import { personalizedRank } from './graph.js';
|
|
7
|
+
import { searchError } from './search-error.js';
|
|
7
8
|
// The three layer verbs: mapTree (orient), find (locate), peek (structure).
|
|
8
9
|
// Each returns data; cli.ts renders. All of them degrade when a feature is off.
|
|
9
10
|
const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';
|
|
10
11
|
const RRF_K = 60;
|
|
11
|
-
// FTS5 reads these as operators, so a bare term containing one is a syntax error or --
|
|
12
|
-
// worse -- a column filter: `end-to-end` parses as a filter on column `to`, and SQLite
|
|
13
|
-
// reports `no such column: to`, which is a true statement about the parse and a false
|
|
14
|
-
// one about the input. Both field reports on 0.6.0 misdiagnosed that message.
|
|
15
|
-
const FTS5_OPERATORS = /[-'"/.:^*()]/;
|
|
16
|
-
function searchError(err, terms, scope) {
|
|
17
|
-
var _terms_match;
|
|
18
|
-
var _exec;
|
|
19
|
-
const message = err.message;
|
|
20
|
-
if (!/no such column|fts5: syntax error|malformed MATCH/.test(message)) return err;
|
|
21
|
-
const suspects = ((_terms_match = terms.match(/\S+/g)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>!t.startsWith('"') && FTS5_OPERATORS.test(t));
|
|
22
|
-
// Blame the terms only when the failing token actually came from one -- a typo'd column
|
|
23
|
-
// in --where (or the tree's default scope) raises "no such column" through this same
|
|
24
|
-
// statement, and naming a term for it would state a false fact about the input.
|
|
25
|
-
const col = (_exec = /no such column: (\S+)/.exec(message)) === null || _exec === void 0 ? void 0 : _exec[1];
|
|
26
|
-
const fromTerms = col === undefined ? suspects.length > 0 : suspects.some((t)=>t.split(/[^\p{L}\p{N}]+/u).includes(col));
|
|
27
|
-
if (fromTerms && suspects.length > 0) {
|
|
28
|
-
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.`);
|
|
29
|
-
}
|
|
30
|
-
if (col !== undefined && scope !== undefined) {
|
|
31
|
-
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')".`);
|
|
32
|
-
}
|
|
33
|
-
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')).`);
|
|
34
|
-
}
|
|
35
12
|
// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which
|
|
36
13
|
// signal produced each row so the agent knows what evidence it is trusting.
|
|
37
14
|
// Semantic expansion is per-query opt-in: without opts.semantic the result is
|
package/dist/esm/verbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, FeatureName } 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';\n\n// The three layer verbs: 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 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\nfunction 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\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: Config, 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 const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\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 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 const linesCol = opts.semantic ? ', _find.lines, _find.similarity' : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score${linesCol}\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\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// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): 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 const allFields = columns\n .map((name) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\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 const { n, types } = db.prepare(`SELECT COUNT(${quoted}) AS n, GROUP_CONCAT(DISTINCT typeof(${quoted})) AS types FROM frontmatter WHERE ${quoted} IS NOT NULL`).get() as { n: number; types: string | null };\n return { field: name, coverage: n, type: types ?? '' };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\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, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LINK_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: Config, 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 sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) 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_LINK_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_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\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":["posix","featureEnabled","featureStates","SenseError","semanticCandidates","linkEdges","personalizedRank","WEIGHTED_BM25","RRF_K","FTS5_OPERATORS","searchError","err","terms","scope","message","test","suspects","match","filter","t","startsWith","col","exec","fromTerms","undefined","length","some","split","includes","map","join","find","db","cfg","opts","hits","chunkLines","chunkSimilarity","k","fetch","Math","max","where","defaults","whereJoin","whereCond","matchSql","matchRows","prepare","all","Map","r","path","hit","candidates","forEach","i","set","score","via","edges","linked","Set","flat","nodes","seeds","ranked","sort","a","b","slice","existing","get","has","semantic","vec","lines","similarity","insert","c","run","linesCol","INTERNAL_COLUMNS","mapTree","docs","columns","name","allFields","quoted","n","types","field","coverage","type","fields","hubs","recent","fieldsTotal","features","PEEK_LINK_LIMIT","peek","pathArg","row","paths","p","base","basename","replace","toLowerCase","matches","frontmatter","key","value","Object","entries","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, FeatureName } 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 verbs: 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\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: Config, 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 const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\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 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 const linesCol = opts.semantic ? ', _find.lines, _find.similarity' : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score${linesCol}\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\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// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): 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 const allFields = columns\n .map((name) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\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 const { n, types } = db.prepare(`SELECT COUNT(${quoted}) AS n, GROUP_CONCAT(DISTINCT typeof(${quoted})) AS types FROM frontmatter WHERE ${quoted} IS NOT NULL`).get() as { n: number; types: string | null };\n return { field: name, coverage: n, type: types ?? '' };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\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, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LINK_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: Config, 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 sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) 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_LINK_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_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\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":["posix","featureEnabled","featureStates","SenseError","semanticCandidates","linkEdges","personalizedRank","searchError","WEIGHTED_BM25","RRF_K","find","db","cfg","terms","opts","hits","chunkLines","chunkSimilarity","k","fetch","Math","max","scope","where","defaults","whereJoin","whereCond","matchSql","matchRows","prepare","all","err","Map","map","r","path","hit","candidates","forEach","i","set","score","via","edges","length","linked","Set","flat","nodes","seeds","ranked","filter","sort","a","b","slice","existing","get","has","semantic","vec","lines","similarity","exec","insert","c","run","linesCol","INTERNAL_COLUMNS","mapTree","docs","columns","name","allFields","quoted","split","join","n","types","field","coverage","type","fields","hubs","recent","fieldsTotal","features","PEEK_LINK_LIMIT","peek","pathArg","row","paths","p","base","basename","replace","toLowerCase","matches","frontmatter","key","value","Object","entries","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -82,6 +82,11 @@
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
|
+
"checks": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"description": "Assertions over saved queries, for trees whose queries encode invariants: `\"checks\": { \"dead-links\": \"empty\" }` makes `sense check` fail (exit 1) when that query returns rows. Without an entry, `check` reports zero-row queries as `ok, but returns 0 rows` -- informational, since an ordinary query returning nothing is sometimes a bug and sometimes just an empty result. Keys must name saved queries.",
|
|
88
|
+
"additionalProperties": { "type": "string", "enum": ["empty"] }
|
|
89
|
+
},
|
|
85
90
|
"queries": {
|
|
86
91
|
"type": "object",
|
|
87
92
|
"description": "Named SQL queries runnable as `sense <name> [params...]`. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `?` placeholders bind to CLI positional args in order. `has(field, value)`: array membership on a JSON-array field, substring match on a string (so has(f.status, 'active') also matches 'inactive'), false on NULL. Exact matches: `=` for scalars, `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)` for array members. Canonical query: `SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`, `check`.",
|
package/skills/sense/SKILL.md
CHANGED
|
@@ -71,16 +71,23 @@ sense --list | status | rebuild | check
|
|
|
71
71
|
- `score` is a rank-fusion value: it ranks rows within one result set and is not comparable
|
|
72
72
|
across queries, not a relevance magnitude — a perfect lexical hit and a weak vector-only
|
|
73
73
|
hit can both read ~0.017, because the number encodes how many signals fired and at what
|
|
74
|
-
rank. With `--semantic`, rows carry `similarity
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
rank. With `--semantic`, rows carry `similarity`: the cosine (-1 to 1) of the query
|
|
75
|
+
against that file's best-matching chunk — the same chunk the `lines` range points at.
|
|
76
|
+
It orders vector evidence within a result set; its absolute range depends on the corpus.
|
|
77
|
+
Measured: on thousands of notes, unrelated queries ~0.2 and genuine matches ~0.6; on a
|
|
78
|
+
few dozen notes the ranges compress and can overlap, because even a nonsense query has a
|
|
79
|
+
moderately near neighbour somewhere. Compare similarities within a result set rather than
|
|
80
|
+
against a fixed cutoff carried between trees.
|
|
77
81
|
- Lexical `find` returns 0 rows when nothing matches, so it answers "is this in the tree at
|
|
78
82
|
all". `--semantic` always returns up to `k` rows — nearest-neighbour search has a nearest
|
|
79
|
-
neighbour for any input — so absence is a lexical question;
|
|
80
|
-
|
|
83
|
+
neighbour for any input — so absence is a lexical question; `similarity` and the snippet
|
|
84
|
+
are the evidence for judging whether a vector row is a real hit.
|
|
81
85
|
- `sense check` prepares every saved query (catching syntax and unknown-column errors), runs
|
|
82
86
|
the ones taking no parameters, and prints row counts: a saved query returning 0 rows looks
|
|
83
|
-
the same as a true empty result until something distinguishes them.
|
|
87
|
+
the same as a true empty result until something distinguishes them. For queries that
|
|
88
|
+
encode invariants (a dead-link list, an unsupported-claims list — rows are violations),
|
|
89
|
+
`checks: { "<name>": "empty" }` in the config inverts the meaning: `check` fails when the
|
|
90
|
+
query returns rows, making it usable as a test suite rather than a linter.
|
|
84
91
|
|
|
85
92
|
## SQL
|
|
86
93
|
|