sensemaking 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +48 -39
  2. package/dist/cjs/cli/check.js +24 -43
  3. package/dist/cjs/cli/check.js.map +1 -1
  4. package/dist/cjs/cli/index.js +2 -2
  5. package/dist/cjs/cli/index.js.map +1 -1
  6. package/dist/cjs/cli/named.js +18 -8
  7. package/dist/cjs/cli/named.js.map +1 -1
  8. package/dist/cjs/cli/search.d.cts +3 -0
  9. package/dist/cjs/cli/search.d.ts +3 -0
  10. package/dist/cjs/cli/{find.js → search.js} +7 -5
  11. package/dist/cjs/cli/search.js.map +1 -0
  12. package/dist/cjs/cli/shared.js.map +1 -1
  13. package/dist/cjs/cli/status.js +29 -12
  14. package/dist/cjs/cli/status.js.map +1 -1
  15. package/dist/cjs/cli/types.d.cts +3 -1
  16. package/dist/cjs/cli/types.d.ts +3 -1
  17. package/dist/cjs/cli.js +25 -5
  18. package/dist/cjs/cli.js.map +1 -1
  19. package/dist/cjs/commands.d.cts +11 -2
  20. package/dist/cjs/commands.d.ts +11 -2
  21. package/dist/cjs/commands.js +86 -21
  22. package/dist/cjs/commands.js.map +1 -1
  23. package/dist/cjs/config.d.cts +41 -14
  24. package/dist/cjs/config.d.ts +41 -14
  25. package/dist/cjs/config.js +448 -160
  26. package/dist/cjs/config.js.map +1 -1
  27. package/dist/cjs/db.d.cts +1 -1
  28. package/dist/cjs/db.d.ts +1 -1
  29. package/dist/cjs/db.js +208 -62
  30. package/dist/cjs/db.js.map +1 -1
  31. package/dist/cjs/errors.d.cts +1 -1
  32. package/dist/cjs/errors.d.ts +1 -1
  33. package/dist/cjs/errors.js.map +1 -1
  34. package/dist/cjs/features/embed.js +12 -2
  35. package/dist/cjs/features/embed.js.map +1 -1
  36. package/dist/cjs/features/types.d.cts +2 -1
  37. package/dist/cjs/features/types.d.ts +2 -1
  38. package/dist/cjs/index.d.cts +3 -3
  39. package/dist/cjs/index.d.ts +3 -3
  40. package/dist/cjs/index.js +6 -3
  41. package/dist/cjs/index.js.map +1 -1
  42. package/dist/cjs/output.d.cts +15 -0
  43. package/dist/cjs/output.d.ts +15 -0
  44. package/dist/cjs/output.js +26 -5
  45. package/dist/cjs/output.js.map +1 -1
  46. package/dist/cjs/scan.d.cts +3 -0
  47. package/dist/cjs/scan.d.ts +3 -0
  48. package/dist/cjs/scan.js +84 -4
  49. package/dist/cjs/scan.js.map +1 -1
  50. package/dist/esm/cli/check.js +20 -41
  51. package/dist/esm/cli/check.js.map +1 -1
  52. package/dist/esm/cli/index.js +1 -1
  53. package/dist/esm/cli/index.js.map +1 -1
  54. package/dist/esm/cli/named.js +17 -9
  55. package/dist/esm/cli/named.js.map +1 -1
  56. package/dist/esm/cli/search.d.ts +3 -0
  57. package/dist/esm/cli/search.js +16 -0
  58. package/dist/esm/cli/search.js.map +1 -0
  59. package/dist/esm/cli/shared.js +1 -1
  60. package/dist/esm/cli/shared.js.map +1 -1
  61. package/dist/esm/cli/status.js +10 -10
  62. package/dist/esm/cli/status.js.map +1 -1
  63. package/dist/esm/cli/types.d.ts +3 -1
  64. package/dist/esm/cli/types.js.map +1 -1
  65. package/dist/esm/cli.js +20 -4
  66. package/dist/esm/cli.js.map +1 -1
  67. package/dist/esm/commands.d.ts +11 -2
  68. package/dist/esm/commands.js +76 -24
  69. package/dist/esm/commands.js.map +1 -1
  70. package/dist/esm/config.d.ts +41 -14
  71. package/dist/esm/config.js +354 -114
  72. package/dist/esm/config.js.map +1 -1
  73. package/dist/esm/db.d.ts +1 -1
  74. package/dist/esm/db.js +53 -6
  75. package/dist/esm/db.js.map +1 -1
  76. package/dist/esm/errors.d.ts +1 -1
  77. package/dist/esm/errors.js.map +1 -1
  78. package/dist/esm/features/embed.js +12 -2
  79. package/dist/esm/features/embed.js.map +1 -1
  80. package/dist/esm/features/types.d.ts +2 -1
  81. package/dist/esm/features/types.js.map +1 -1
  82. package/dist/esm/index.d.ts +3 -3
  83. package/dist/esm/index.js +1 -1
  84. package/dist/esm/index.js.map +1 -1
  85. package/dist/esm/output.d.ts +15 -0
  86. package/dist/esm/output.js +19 -5
  87. package/dist/esm/output.js.map +1 -1
  88. package/dist/esm/scan.d.ts +3 -0
  89. package/dist/esm/scan.js +32 -4
  90. package/dist/esm/scan.js.map +1 -1
  91. package/package.json +9 -3
  92. package/schema.json +74 -54
  93. package/skills/sense/EXAMPLES.md +8 -6
  94. package/skills/sense/SKILL.md +51 -48
  95. package/skills/sense-setup/EXAMPLES.md +121 -0
  96. package/skills/sense-setup/SKILL.md +71 -66
  97. package/dist/cjs/cli/find.d.cts +0 -3
  98. package/dist/cjs/cli/find.d.ts +0 -3
  99. package/dist/cjs/cli/find.js.map +0 -1
  100. package/dist/esm/cli/find.d.ts +0 -3
  101. package/dist/esm/cli/find.js +0 -14
  102. package/dist/esm/cli/find.js.map +0 -1
@@ -1,9 +1,11 @@
1
+ import { presetCoverage } from '../commands.js';
1
2
  import { embedConfig, featureStates } from '../config.js';
2
3
  import { docCount, getMeta, open } from '../db.js';
4
+ import { featuresLine, presetsLines } from '../output.js';
3
5
  import { printWarnings } from './shared.js';
4
- // Vectors are computed on the first semantic query, not at reconcile, so a tree can be
5
- // embed-enabled with nothing embedded yet -- reported here because a fast `rebuild` on an
6
- // embed tree otherwise reads as "the feature isn't on".
6
+ // Vectors are computed on the first semantic query, not at reconcile, so a tree can have a
7
+ // semantic-on preset with nothing embedded yet -- reported here because a fast `rebuild` on
8
+ // such a tree otherwise reads as "the feature isn't on".
7
9
  function vectorState(db) {
8
10
  try {
9
11
  const row = db.prepare('SELECT COUNT(vector) AS embedded, COUNT(*) - COUNT(vector) AS pending FROM embeddings').get();
@@ -13,14 +15,13 @@ function vectorState(db) {
13
15
  }
14
16
  }
15
17
  const status = (ctx)=>{
16
- var _ref;
17
- var _cfg_defaults_find, _cfg_defaults;
18
18
  const cfg = ctx.resolveConfig();
19
19
  const { db, dbPath, warnings } = open(cfg);
20
20
  printWarnings(warnings);
21
21
  const features = featureStates(cfg);
22
22
  const e = embedConfig(cfg);
23
23
  const vectors = e ? vectorState(db) : null;
24
+ const presets = presetCoverage(db, cfg);
24
25
  const heartbeat = getMeta(db, 'watch_heartbeat');
25
26
  // Derived at open() (F): 3x the largest reconcile this cache has ever held its write
26
27
  // transaction for, floored at 30s -- read back from the connection rather than
@@ -36,7 +37,7 @@ const status = (ctx)=>{
36
37
  model: e.model,
37
38
  ...vectors !== null && vectors !== void 0 ? vectors : {}
38
39
  } : null,
39
- findDefaultWhere: (_ref = (_cfg_defaults = cfg.defaults) === null || _cfg_defaults === void 0 ? void 0 : (_cfg_defaults_find = _cfg_defaults.find) === null || _cfg_defaults_find === void 0 ? void 0 : _cfg_defaults_find.where) !== null && _ref !== void 0 ? _ref : null,
40
+ presets,
40
41
  watcherHeartbeatSecondsAgo: heartbeat ? Math.round((Date.now() - Date.parse(heartbeat)) / 1000) : null,
41
42
  busyTimeoutMs
42
43
  };
@@ -45,10 +46,9 @@ const status = (ctx)=>{
45
46
  } else {
46
47
  console.log(`db: ${result.db}`);
47
48
  console.log(`docs: ${result.docs}`);
48
- const off = features.off.length > 0 ? ` · off: ${features.off.map((name)=>`${name} (features.${name})`).join(', ')}` : '';
49
- console.log(`features: ${features.on.join(', ')}${off}`);
50
- if (e) console.log(`embed: ${e.type} ${e.model}${vectors ? ` (vectors: ${vectors.embedded} embedded, ${vectors.pending} pending — embedded on first --semantic query)` : ''}`);
51
- if (result.findDefaultWhere) console.log(`find default scope: ${result.findDefaultWhere} (--where replaces it)`);
49
+ console.log(featuresLine(features));
50
+ if (e) console.log(`embed: ${e.type} ${e.model}${vectors ? ` (vectors: ${vectors.embedded} embedded, ${vectors.pending} pending — embedded on the first semantic search)` : ''}`);
51
+ for (const line of presetsLines(presets))console.log(line);
52
52
  console.log(result.watcherHeartbeatSecondsAgo === null ? 'watcher: no watcher' : `watcher: last heartbeat ${result.watcherHeartbeatSecondsAgo}s ago`);
53
53
  console.log(`busy_timeout: ${result.busyTimeoutMs}ms (derived: 3x the largest reconcile this cache has recorded, floored at 30000ms)`);
54
54
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/status.ts"],"sourcesContent":["import { embedConfig, featureStates } from '../config.ts';\nimport { docCount, getMeta, open } from '../db.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// Vectors are computed on the first semantic query, not at reconcile, so a tree can be\n// embed-enabled with nothing embedded yet -- reported here because a fast `rebuild` on an\n// embed tree otherwise reads as \"the feature isn't on\".\nfunction vectorState(db: ReturnType<typeof open>['db']): { embedded: number; pending: number } | null {\n try {\n const row = db.prepare('SELECT COUNT(vector) AS embedded, COUNT(*) - COUNT(vector) AS pending FROM embeddings').get() as { embedded: number; pending: number };\n return row;\n } catch {\n return null;\n }\n}\n\nconst status: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, dbPath, warnings } = open(cfg);\n printWarnings(warnings);\n\n const features = featureStates(cfg);\n const e = embedConfig(cfg);\n const vectors = e ? vectorState(db) : null;\n const heartbeat = getMeta(db, 'watch_heartbeat');\n // Derived at open() (F): 3x the largest reconcile this cache has ever held its write\n // transaction for, floored at 30s -- read back from the connection rather than\n // recomputed here, so this always reports what open() actually set.\n const busyTimeoutMs = (db.prepare('PRAGMA busy_timeout').get() as { timeout: number }).timeout;\n const result = {\n db: dbPath,\n docs: docCount(db),\n features: features.on,\n featuresOff: features.off,\n embed: e ? { type: e.type, model: e.model, ...(vectors ?? {}) } : null,\n findDefaultWhere: cfg.defaults?.find?.where ?? null,\n watcherHeartbeatSecondsAgo: heartbeat ? Math.round((Date.now() - Date.parse(heartbeat)) / 1000) : null,\n busyTimeoutMs,\n };\n\n if (ctx.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(`db: ${result.db}`);\n console.log(`docs: ${result.docs}`);\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (features.${name})`).join(', ')}` : '';\n console.log(`features: ${features.on.join(', ')}${off}`);\n if (e) console.log(`embed: ${e.type} ${e.model}${vectors ? ` (vectors: ${vectors.embedded} embedded, ${vectors.pending} pending — embedded on first --semantic query)` : ''}`);\n if (result.findDefaultWhere) console.log(`find default scope: ${result.findDefaultWhere} (--where replaces it)`);\n console.log(result.watcherHeartbeatSecondsAgo === null ? 'watcher: no watcher' : `watcher: last heartbeat ${result.watcherHeartbeatSecondsAgo}s ago`);\n console.log(`busy_timeout: ${result.busyTimeoutMs}ms (derived: 3x the largest reconcile this cache has recorded, floored at 30000ms)`);\n }\n db.close();\n};\nexport default status;\n"],"names":["embedConfig","featureStates","docCount","getMeta","open","printWarnings","vectorState","db","row","prepare","get","status","ctx","cfg","resolveConfig","dbPath","warnings","features","e","vectors","heartbeat","busyTimeoutMs","timeout","result","docs","on","featuresOff","off","embed","type","model","findDefaultWhere","defaults","find","where","watcherHeartbeatSecondsAgo","Math","round","Date","now","parse","format","console","log","JSON","stringify","length","map","name","join","embedded","pending","close"],"mappings":"AAAA,SAASA,WAAW,EAAEC,aAAa,QAAQ,eAAe;AAC1D,SAASC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,QAAQ,WAAW;AACnD,SAASC,aAAa,QAAQ,cAAc;AAG5C,uFAAuF;AACvF,0FAA0F;AAC1F,wDAAwD;AACxD,SAASC,YAAYC,EAAiC;IACpD,IAAI;QACF,MAAMC,MAAMD,GAAGE,OAAO,CAAC,yFAAyFC,GAAG;QACnH,OAAOF;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,MAAMG,SAAkB,CAACC;;QAmBHC,oBAAAA;IAlBpB,MAAMA,MAAMD,IAAIE,aAAa;IAC7B,MAAM,EAAEP,EAAE,EAAEQ,MAAM,EAAEC,QAAQ,EAAE,GAAGZ,KAAKS;IACtCR,cAAcW;IAEd,MAAMC,WAAWhB,cAAcY;IAC/B,MAAMK,IAAIlB,YAAYa;IACtB,MAAMM,UAAUD,IAAIZ,YAAYC,MAAM;IACtC,MAAMa,YAAYjB,QAAQI,IAAI;IAC9B,qFAAqF;IACrF,+EAA+E;IAC/E,oEAAoE;IACpE,MAAMc,gBAAgB,AAACd,GAAGE,OAAO,CAAC,uBAAuBC,GAAG,GAA2BY,OAAO;IAC9F,MAAMC,SAAS;QACbhB,IAAIQ;QACJS,MAAMtB,SAASK;QACfU,UAAUA,SAASQ,EAAE;QACrBC,aAAaT,SAASU,GAAG;QACzBC,OAAOV,IAAI;YAAEW,MAAMX,EAAEW,IAAI;YAAEC,OAAOZ,EAAEY,KAAK;eAAMX,oBAAAA,qBAAAA,UAAW,CAAC,CAAhB;QAAmB,IAAI;QAClEY,gBAAgB,WAAElB,gBAAAA,IAAImB,QAAQ,cAAZnB,qCAAAA,qBAAAA,cAAcoB,IAAI,cAAlBpB,yCAAAA,mBAAoBqB,KAAK,uCAAI;QAC/CC,4BAA4Bf,YAAYgB,KAAKC,KAAK,CAAC,AAACC,CAAAA,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAACpB,UAAS,IAAK,QAAQ;QAClGC;IACF;IAEA,IAAIT,IAAI6B,MAAM,KAAK,QAAQ;QACzBC,QAAQC,GAAG,CAACC,KAAKC,SAAS,CAACtB,QAAQ,MAAM;IAC3C,OAAO;QACLmB,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEpB,OAAOhB,EAAE,EAAE;QAC9BmC,QAAQC,GAAG,CAAC,CAAC,MAAM,EAAEpB,OAAOC,IAAI,EAAE;QAClC,MAAMG,MAAMV,SAASU,GAAG,CAACmB,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE7B,SAASU,GAAG,CAACoB,GAAG,CAAC,CAACC,OAAS,GAAGA,KAAK,WAAW,EAAEA,KAAK,CAAC,CAAC,EAAEC,IAAI,CAAC,OAAO,GAAG;QACzHP,QAAQC,GAAG,CAAC,CAAC,UAAU,EAAE1B,SAASQ,EAAE,CAACwB,IAAI,CAAC,QAAQtB,KAAK;QACvD,IAAIT,GAAGwB,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEzB,EAAEW,IAAI,CAAC,CAAC,EAAEX,EAAEY,KAAK,GAAGX,UAAU,CAAC,WAAW,EAAEA,QAAQ+B,QAAQ,CAAC,WAAW,EAAE/B,QAAQgC,OAAO,CAAC,8CAA8C,CAAC,GAAG,IAAI;QAC7K,IAAI5B,OAAOQ,gBAAgB,EAAEW,QAAQC,GAAG,CAAC,CAAC,oBAAoB,EAAEpB,OAAOQ,gBAAgB,CAAC,sBAAsB,CAAC;QAC/GW,QAAQC,GAAG,CAACpB,OAAOY,0BAA0B,KAAK,OAAO,wBAAwB,CAAC,wBAAwB,EAAEZ,OAAOY,0BAA0B,CAAC,KAAK,CAAC;QACpJO,QAAQC,GAAG,CAAC,CAAC,cAAc,EAAEpB,OAAOF,aAAa,CAAC,kFAAkF,CAAC;IACvI;IACAd,GAAG6C,KAAK;AACV;AACA,eAAezC,OAAO"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/status.ts"],"sourcesContent":["import { presetCoverage } from '../commands.ts';\nimport { embedConfig, featureStates } from '../config.ts';\nimport { docCount, getMeta, open } from '../db.ts';\nimport { featuresLine, presetsLines } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// Vectors are computed on the first semantic query, not at reconcile, so a tree can have a\n// semantic-on preset with nothing embedded yet -- reported here because a fast `rebuild` on\n// such a tree otherwise reads as \"the feature isn't on\".\nfunction vectorState(db: ReturnType<typeof open>['db']): { embedded: number; pending: number } | null {\n try {\n const row = db.prepare('SELECT COUNT(vector) AS embedded, COUNT(*) - COUNT(vector) AS pending FROM embeddings').get() as { embedded: number; pending: number };\n return row;\n } catch {\n return null;\n }\n}\n\nconst status: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, dbPath, warnings } = open(cfg);\n printWarnings(warnings);\n\n const features = featureStates(cfg);\n const e = embedConfig(cfg);\n const vectors = e ? vectorState(db) : null;\n const presets = presetCoverage(db, cfg);\n const heartbeat = getMeta(db, 'watch_heartbeat');\n // Derived at open() (F): 3x the largest reconcile this cache has ever held its write\n // transaction for, floored at 30s -- read back from the connection rather than\n // recomputed here, so this always reports what open() actually set.\n const busyTimeoutMs = (db.prepare('PRAGMA busy_timeout').get() as { timeout: number }).timeout;\n const result = {\n db: dbPath,\n docs: docCount(db),\n features: features.on,\n featuresOff: features.off,\n embed: e ? { type: e.type, model: e.model, ...(vectors ?? {}) } : null,\n presets,\n watcherHeartbeatSecondsAgo: heartbeat ? Math.round((Date.now() - Date.parse(heartbeat)) / 1000) : null,\n busyTimeoutMs,\n };\n\n if (ctx.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(`db: ${result.db}`);\n console.log(`docs: ${result.docs}`);\n console.log(featuresLine(features));\n if (e) console.log(`embed: ${e.type} ${e.model}${vectors ? ` (vectors: ${vectors.embedded} embedded, ${vectors.pending} pending — embedded on the first semantic search)` : ''}`);\n for (const line of presetsLines(presets)) console.log(line);\n console.log(result.watcherHeartbeatSecondsAgo === null ? 'watcher: no watcher' : `watcher: last heartbeat ${result.watcherHeartbeatSecondsAgo}s ago`);\n console.log(`busy_timeout: ${result.busyTimeoutMs}ms (derived: 3x the largest reconcile this cache has recorded, floored at 30000ms)`);\n }\n db.close();\n};\nexport default status;\n"],"names":["presetCoverage","embedConfig","featureStates","docCount","getMeta","open","featuresLine","presetsLines","printWarnings","vectorState","db","row","prepare","get","status","ctx","cfg","resolveConfig","dbPath","warnings","features","e","vectors","presets","heartbeat","busyTimeoutMs","timeout","result","docs","on","featuresOff","off","embed","type","model","watcherHeartbeatSecondsAgo","Math","round","Date","now","parse","format","console","log","JSON","stringify","embedded","pending","line","close"],"mappings":"AAAA,SAASA,cAAc,QAAQ,iBAAiB;AAChD,SAASC,WAAW,EAAEC,aAAa,QAAQ,eAAe;AAC1D,SAASC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,QAAQ,WAAW;AACnD,SAASC,YAAY,EAAEC,YAAY,QAAQ,eAAe;AAC1D,SAASC,aAAa,QAAQ,cAAc;AAG5C,2FAA2F;AAC3F,4FAA4F;AAC5F,yDAAyD;AACzD,SAASC,YAAYC,EAAiC;IACpD,IAAI;QACF,MAAMC,MAAMD,GAAGE,OAAO,CAAC,yFAAyFC,GAAG;QACnH,OAAOF;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,MAAMG,SAAkB,CAACC;IACvB,MAAMC,MAAMD,IAAIE,aAAa;IAC7B,MAAM,EAAEP,EAAE,EAAEQ,MAAM,EAAEC,QAAQ,EAAE,GAAGd,KAAKW;IACtCR,cAAcW;IAEd,MAAMC,WAAWlB,cAAcc;IAC/B,MAAMK,IAAIpB,YAAYe;IACtB,MAAMM,UAAUD,IAAIZ,YAAYC,MAAM;IACtC,MAAMa,UAAUvB,eAAeU,IAAIM;IACnC,MAAMQ,YAAYpB,QAAQM,IAAI;IAC9B,qFAAqF;IACrF,+EAA+E;IAC/E,oEAAoE;IACpE,MAAMe,gBAAgB,AAACf,GAAGE,OAAO,CAAC,uBAAuBC,GAAG,GAA2Ba,OAAO;IAC9F,MAAMC,SAAS;QACbjB,IAAIQ;QACJU,MAAMzB,SAASO;QACfU,UAAUA,SAASS,EAAE;QACrBC,aAAaV,SAASW,GAAG;QACzBC,OAAOX,IAAI;YAAEY,MAAMZ,EAAEY,IAAI;YAAEC,OAAOb,EAAEa,KAAK;eAAMZ,oBAAAA,qBAAAA,UAAW,CAAC,CAAhB;QAAmB,IAAI;QAClEC;QACAY,4BAA4BX,YAAYY,KAAKC,KAAK,CAAC,AAACC,CAAAA,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAAChB,UAAS,IAAK,QAAQ;QAClGC;IACF;IAEA,IAAIV,IAAI0B,MAAM,KAAK,QAAQ;QACzBC,QAAQC,GAAG,CAACC,KAAKC,SAAS,CAAClB,QAAQ,MAAM;IAC3C,OAAO;QACLe,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEhB,OAAOjB,EAAE,EAAE;QAC9BgC,QAAQC,GAAG,CAAC,CAAC,MAAM,EAAEhB,OAAOC,IAAI,EAAE;QAClCc,QAAQC,GAAG,CAACrC,aAAac;QACzB,IAAIC,GAAGqB,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEtB,EAAEY,IAAI,CAAC,CAAC,EAAEZ,EAAEa,KAAK,GAAGZ,UAAU,CAAC,WAAW,EAAEA,QAAQwB,QAAQ,CAAC,WAAW,EAAExB,QAAQyB,OAAO,CAAC,iDAAiD,CAAC,GAAG,IAAI;QAChL,KAAK,MAAMC,QAAQzC,aAAagB,SAAUmB,QAAQC,GAAG,CAACK;QACtDN,QAAQC,GAAG,CAAChB,OAAOQ,0BAA0B,KAAK,OAAO,wBAAwB,CAAC,wBAAwB,EAAER,OAAOQ,0BAA0B,CAAC,KAAK,CAAC;QACpJO,QAAQC,GAAG,CAAC,CAAC,cAAc,EAAEhB,OAAOF,aAAa,CAAC,kFAAkF,CAAC;IACvI;IACAf,GAAGuC,KAAK;AACV;AACA,eAAenC,OAAO"}
@@ -7,8 +7,10 @@ export interface Ctx {
7
7
  config?: string;
8
8
  where?: string;
9
9
  k?: string;
10
+ preset?: string;
11
+ include?: string[];
10
12
  force: boolean;
11
- semantic: boolean;
13
+ lexical: boolean;
12
14
  };
13
15
  resolveConfig(): ResolvedConfig;
14
16
  usageError(message: string): never;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/types.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\n\n// Handed to every command; argv parsing stays in cli.ts.\nexport interface Ctx {\n name: string;\n rest: string[]; // positionals after the command word\n format: 'table' | 'json';\n values: { config?: string; where?: string; k?: string; force: boolean; semantic: boolean };\n resolveConfig(): ResolvedConfig;\n usageError(message: string): never;\n}\n\nexport type Command = (ctx: Ctx) => Promise<void> | void;\n"],"names":[],"mappings":"AAYA,WAAyD"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/types.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\n\n// Handed to every command; argv parsing stays in cli.ts.\nexport interface Ctx {\n name: string;\n rest: string[]; // positionals after the command word\n format: 'table' | 'json';\n values: { config?: string; where?: string; k?: string; preset?: string; include?: string[]; force: boolean; lexical: boolean };\n resolveConfig(): ResolvedConfig;\n usageError(message: string): never;\n}\n\nexport type Command = (ctx: Ctx) => Promise<void> | void;\n"],"names":[],"mappings":"AAYA,WAAyD"}
package/dist/esm/cli.js CHANGED
@@ -20,7 +20,7 @@ function packageVersion() {
20
20
  return 'unknown';
21
21
  }
22
22
  function usage(name) {
23
- return `usage: ${name} <name> [params...] [--format table|json] [--config path]\n` + ` ${name} query "<sql>" [params...]\n` + ` ${name} find "<terms>" [--where "<sql>"] [--k n] [--semantic]\n` + ` ${name} map\n` + ` ${name} peek <path>\n` + ` ${name} --list\n` + ` ${name} init\n` + ` ${name} watch [--force]\n` + ` ${name} status\n` + ` ${name} check\n` + ` ${name} rebuild\n` + ` ${name} --version`;
23
+ return `usage: ${name} <name> [params...] [--format table|json] [--config path]\n` + ` ${name} query "<sql>" [params...]\n` + ` ${name} search "<terms>" [--preset name] [--include glob ...] [--where "<sql>"] [--k n] [--lexical]\n` + ` ${name} map\n` + ` ${name} peek <path>\n` + ` ${name} --list\n` + ` ${name} init\n` + ` ${name} watch [--force]\n` + ` ${name} status\n` + ` ${name} check\n` + ` ${name} rebuild\n` + ` ${name} --version`;
24
24
  }
25
25
  // Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.
26
26
  export default async function cli(argv, name) {
@@ -43,8 +43,16 @@ export default async function cli(argv, name) {
43
43
  k: {
44
44
  type: 'string'
45
45
  },
46
- semantic: {
47
- type: 'boolean'
46
+ preset: {
47
+ type: 'string'
48
+ },
49
+ include: {
50
+ type: 'string',
51
+ multiple: true
52
+ },
53
+ lexical: {
54
+ type: 'boolean',
55
+ default: false
48
56
  },
49
57
  list: {
50
58
  type: 'boolean',
@@ -104,7 +112,9 @@ export default async function cli(argv, name) {
104
112
  if (values.list) {
105
113
  const queries = ctx.resolveConfig().queries;
106
114
  for (const queryName of Object.keys(queries).sort()){
107
- console.log(typeof queries[queryName] === 'string' ? queryName : `${queryName} (find)`);
115
+ const entry = queries[queryName];
116
+ const isSavedSearch = typeof entry === 'object' && entry !== null && 'search' in entry;
117
+ console.log(isSavedSearch ? `${queryName} (search)` : queryName);
108
118
  }
109
119
  return;
110
120
  }
@@ -113,6 +123,12 @@ export default async function cli(argv, name) {
113
123
  console.error(usage(name));
114
124
  process.exit(2);
115
125
  }
126
+ // find -> search (breaking rename): one release of a pointer instead of "unknown query".
127
+ if (first === 'find') {
128
+ console.error(`${name}: find is now search`);
129
+ console.error(usage(name));
130
+ process.exit(2);
131
+ }
116
132
  const load = COMMANDS[first];
117
133
  if (load) await (await load()).default(ctx);
118
134
  else await (await import('./cli/named.js')).default(ctx, first, params);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { COMMANDS } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport { loadConfig, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\n// Parsing and dispatch only. Commands live in src/cli/, one file each, lazy-loaded --\n// nothing tree- or dependency-heavy may be imported at the top of this file.\n\n// Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.\nfunction packageVersion(): string {\n const load = createRequire(import.meta.url);\n for (const rel of ['../package.json', '../../package.json', '../../../package.json']) {\n try {\n const pkg = load(rel) as { name?: string; version?: string };\n if (pkg.name === 'sensemaking' && pkg.version) return pkg.version;\n } catch {}\n }\n return 'unknown';\n}\n\nfunction usage(name: string): string {\n return (\n `usage: ${name} <name> [params...] [--format table|json] [--config path]\\n` +\n ` ${name} query \"<sql>\" [params...]\\n` +\n ` ${name} find \"<terms>\" [--where \"<sql>\"] [--k n] [--semantic]\\n` +\n ` ${name} map\\n` +\n ` ${name} peek <path>\\n` +\n ` ${name} --list\\n` +\n ` ${name} init\\n` +\n ` ${name} watch [--force]\\n` +\n ` ${name} status\\n` +\n ` ${name} check\\n` +\n ` ${name} rebuild\\n` +\n ` ${name} --version`\n );\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n let values: Record<string, string | boolean | undefined>;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: {\n format: { type: 'string', default: 'table' },\n config: { type: 'string' },\n where: { type: 'string' },\n k: { type: 'string' },\n semantic: { type: 'boolean' },\n list: { type: 'boolean', default: false },\n force: { type: 'boolean', default: false },\n version: { type: 'boolean', default: false, short: 'v' },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n\n if (values.version) {\n console.log(`v${packageVersion()}`);\n return;\n }\n if (values.help) {\n console.log(usage(name));\n return;\n }\n\n const ctx: Ctx = {\n name,\n rest: positionals.slice(1),\n format: values.format === 'json' ? 'json' : 'table',\n values: values as Ctx['values'],\n resolveConfig() {\n const cfg = loadConfig(values.config as string | undefined);\n if (cfg.migratedFrom !== undefined) {\n console.warn(`${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`);\n }\n if (cfg.unknownKeys !== undefined) {\n console.warn(`${name}: ${cfg.configPath} sets ${cfg.unknownKeys.join(', ')}, which this build does not read (no effect); see schema.json for the keys it does`);\n }\n return cfg;\n },\n usageError(message) {\n console.error(message);\n process.exit(2);\n },\n };\n\n try {\n if (values.list) {\n const queries = ctx.resolveConfig().queries;\n for (const queryName of Object.keys(queries).sort()) {\n console.log(typeof queries[queryName] === 'string' ? queryName : `${queryName} (find)`);\n }\n return;\n }\n\n const [first, ...params] = positionals;\n if (!first) {\n console.error(usage(name));\n process.exit(2);\n }\n\n const load = COMMANDS[first];\n if (load) await (await load()).default(ctx);\n else await (await import('./cli/named.ts')).default(ctx, first, params);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["createRequire","parseArgs","COMMANDS","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","cli","argv","values","positionals","args","options","format","type","default","config","where","k","semantic","list","force","short","help","allowPositionals","err","console","error","message","process","exit","log","ctx","rest","slice","resolveConfig","cfg","migratedFrom","undefined","warn","configPath","unknownKeys","join","usageError","queries","queryName","Object","keys","sort","first","params"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { COMMANDS } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport { loadConfig, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\n// Parsing and dispatch only. Commands live in src/cli/, one file each, lazy-loaded --\n// nothing tree- or dependency-heavy may be imported at the top of this file.\n\n// Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.\nfunction packageVersion(): string {\n const load = createRequire(import.meta.url);\n for (const rel of ['../package.json', '../../package.json', '../../../package.json']) {\n try {\n const pkg = load(rel) as { name?: string; version?: string };\n if (pkg.name === 'sensemaking' && pkg.version) return pkg.version;\n } catch {}\n }\n return 'unknown';\n}\n\nfunction usage(name: string): string {\n return (\n `usage: ${name} <name> [params...] [--format table|json] [--config path]\\n` +\n ` ${name} query \"<sql>\" [params...]\\n` +\n ` ${name} search \"<terms>\" [--preset name] [--include glob ...] [--where \"<sql>\"] [--k n] [--lexical]\\n` +\n ` ${name} map\\n` +\n ` ${name} peek <path>\\n` +\n ` ${name} --list\\n` +\n ` ${name} init\\n` +\n ` ${name} watch [--force]\\n` +\n ` ${name} status\\n` +\n ` ${name} check\\n` +\n ` ${name} rebuild\\n` +\n ` ${name} --version`\n );\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n let values: Record<string, string | boolean | string[] | undefined>;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: {\n format: { type: 'string', default: 'table' },\n config: { type: 'string' },\n where: { type: 'string' },\n k: { type: 'string' },\n preset: { type: 'string' },\n include: { type: 'string', multiple: true },\n lexical: { type: 'boolean', default: false },\n list: { type: 'boolean', default: false },\n force: { type: 'boolean', default: false },\n version: { type: 'boolean', default: false, short: 'v' },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n\n if (values.version) {\n console.log(`v${packageVersion()}`);\n return;\n }\n if (values.help) {\n console.log(usage(name));\n return;\n }\n\n const ctx: Ctx = {\n name,\n rest: positionals.slice(1),\n format: values.format === 'json' ? 'json' : 'table',\n values: values as Ctx['values'],\n resolveConfig() {\n const cfg = loadConfig(values.config as string | undefined);\n if (cfg.migratedFrom !== undefined) {\n console.warn(`${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`);\n }\n if (cfg.unknownKeys !== undefined) {\n console.warn(`${name}: ${cfg.configPath} sets ${cfg.unknownKeys.join(', ')}, which this build does not read (no effect); see schema.json for the keys it does`);\n }\n return cfg;\n },\n usageError(message) {\n console.error(message);\n process.exit(2);\n },\n };\n\n try {\n if (values.list) {\n const queries = ctx.resolveConfig().queries;\n for (const queryName of Object.keys(queries).sort()) {\n const entry = queries[queryName];\n const isSavedSearch = typeof entry === 'object' && entry !== null && 'search' in entry;\n console.log(isSavedSearch ? `${queryName} (search)` : queryName);\n }\n return;\n }\n\n const [first, ...params] = positionals;\n if (!first) {\n console.error(usage(name));\n process.exit(2);\n }\n\n // find -> search (breaking rename): one release of a pointer instead of \"unknown query\".\n if (first === 'find') {\n console.error(`${name}: find is now search`);\n console.error(usage(name));\n process.exit(2);\n }\n\n const load = COMMANDS[first];\n if (load) await (await load()).default(ctx);\n else await (await import('./cli/named.ts')).default(ctx, first, params);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["createRequire","parseArgs","COMMANDS","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","cli","argv","values","positionals","args","options","format","type","default","config","where","k","preset","include","multiple","lexical","list","force","short","help","allowPositionals","err","console","error","message","process","exit","log","ctx","rest","slice","resolveConfig","cfg","migratedFrom","undefined","warn","configPath","unknownKeys","join","usageError","queries","queryName","Object","keys","sort","entry","isSavedSearch","first","params"],"mappings":"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"}
@@ -1,12 +1,20 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
2
  import type { FeatureName, ResolvedConfig } from './config.js';
3
3
  import type { Row } from './output.js';
4
- export interface FindOptions {
4
+ export interface SearchOptions {
5
5
  k?: number;
6
6
  where?: string;
7
+ preset?: string;
8
+ include?: string[];
7
9
  semantic?: boolean;
8
10
  }
9
- export declare function find(db: DatabaseSync, cfg: ResolvedConfig, terms: string, opts?: FindOptions): Promise<Row[]>;
11
+ export declare function search(db: DatabaseSync, cfg: ResolvedConfig, terms: string, opts?: SearchOptions): Promise<Row[]>;
12
+ export interface PresetCoverage {
13
+ name: string;
14
+ files: number;
15
+ embedded: number;
16
+ }
17
+ export declare function presetCoverage(db: DatabaseSync, cfg: ResolvedConfig): PresetCoverage[];
10
18
  export interface TreeMap {
11
19
  docs: {
12
20
  count: number;
@@ -18,6 +26,7 @@ export interface TreeMap {
18
26
  on: FeatureName[];
19
27
  off: FeatureName[];
20
28
  };
29
+ presets: PresetCoverage[];
21
30
  hubs: Row[];
22
31
  recent: Row[];
23
32
  }
@@ -1,13 +1,13 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
2
+ import { join, matchesGlob } from 'node:path';
3
3
  import posix from 'node:path/posix';
4
- import { featureEnabled, featureStates } from './config.js';
4
+ import { anyPresetEmbeds, featureEnabled, featureStates, presetNames, resolveSearch } from './config.js';
5
5
  import { SenseError } from './errors.js';
6
6
  import { semanticCandidates } from './features/embed.js';
7
7
  import { linkEdges } from './features/index.js';
8
8
  import { personalizedRank } from './graph.js';
9
9
  import { searchError } from './search-error.js';
10
- // The three layer commands: mapTree (orient), find (locate), peek (structure).
10
+ // The three commands: mapTree (orient), search (locate), peek (structure).
11
11
  // Each returns data; cli.ts renders. All of them degrade when a feature is off.
12
12
  const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';
13
13
  const RRF_K = 60;
@@ -109,22 +109,50 @@ function lineRangeFor(db, path, line) {
109
109
  const row = db.prepare('SELECT start_line, end_line FROM sections WHERE "path" = ? AND start_line <= ? AND end_line >= ? ORDER BY start_line DESC LIMIT 1').get(path, line, line);
110
110
  return row ? `L${row.start_line}-${row.end_line}` : null;
111
111
  }
112
- // Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which
113
- // signal produced each row so the agent knows what evidence it is trusting.
114
- // Semantic expansion is per-query opt-in: without opts.semantic the result is
115
- // byte-for-byte independent of the embed feature.
116
- export async function find(db, cfg, terms, opts = {}) {
117
- var _opts_k, _opts_where, _hits_get, _chunkLines_get, _chunkSimilarity_get;
118
- var _cfg_defaults_find, _cfg_defaults;
119
- const k = (_opts_k = opts.k) !== null && _opts_k !== void 0 ? _opts_k : 10;
120
- const fetch = Math.max(k * 3, 30);
112
+ // node:path's matchesGlob is experimental (stable behind an unstable-API flag) as of the
113
+ // engines floor (Node >=22.16); scope filtering only ever needs single-pattern matching, so
114
+ // it's used here in JS rather than pulling fast-glob's directory walk into the query path.
115
+ function inScope(path, include, exclude) {
116
+ if (!include.some((g)=>matchesGlob(path, g))) return false;
117
+ if (exclude === null || exclude === void 0 ? void 0 : exclude.some((g)=>matchesGlob(path, g))) return false;
118
+ return true;
119
+ }
120
+ function scopeHasEmbeddings(db, cfg, scopedPaths) {
121
+ if (!anyPresetEmbeds(cfg)) return false; // the embeddings table doesn't exist at all in this case
122
+ const rows = db.prepare('SELECT DISTINCT "path" FROM embeddings').all();
123
+ return rows.some((r)=>scopedPaths.has(r.path));
124
+ }
125
+ // BM25 + link-graph expansion, fused by reciprocal rank. `via` says which signal produced
126
+ // each row so the agent knows what evidence it is trusting.
127
+ // Resolution precedence (built-ins <- preset <- caller overrides) lives in
128
+ // src/config.ts:resolveSearch; `opts` here is whatever the caller (a saved search merged
129
+ // with any CLI flag, or a bare CLI invocation) already resolved.
130
+ export async function search(db, cfg, terms, opts = {}) {
131
+ var _hits_get, _chunkLines_get, _chunkSimilarity_get;
132
+ const effective = resolveSearch(cfg, opts);
133
+ const { k, include, exclude } = effective;
134
+ const allPaths = db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
135
+ // A preset scope reads preset_files -- the coverage reconcile actually indexed with
136
+ // (fast-glob semantics); re-matching globs here with node:path's matchesGlob could
137
+ // silently disagree with it. Ad-hoc --include has no persisted coverage, so only that
138
+ // path pays the JS glob match.
139
+ const scopedPaths = opts.include ? new Set(allPaths.filter((p)=>inScope(p, include, exclude))) : new Set(db.prepare('SELECT "path" FROM preset_files WHERE preset = ?').all(effective.presetName).map((r)=>r.path));
140
+ // A scope narrower than the whole index needs a bigger candidate pool before filtering, or
141
+ // filtering can starve k even though enough in-scope matches exist further down the ranked
142
+ // list that a plain over-fetch wouldn't have reached.
143
+ const scopeActive = scopedPaths.size < allPaths.length;
144
+ const fetch = scopeActive ? Math.max(k * 5, 50) : Math.max(k * 3, 30);
145
+ // Semantic participation: vectors run only when the effective setting allows it AND the
146
+ // scope actually has embedded files -- a scope covered only by semantic-false presets (or
147
+ // by no preset embedding at all) searches lexically, automatically, no error.
148
+ const semanticEnabled = effective.semantic && scopeHasEmbeddings(db, cfg, scopedPaths);
121
149
  // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.
122
150
  // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.
123
151
  // --where applies inside the candidate query (a post-filter over the top-N would drop
124
152
  // matches ranked past the pool) and again on the final select for link-derived rows.
125
- // An explicit --where replaces the tree's declared default rather than ANDing with it,
126
- // so a caller can always widen back to the whole tree.
127
- const scope = (_opts_where = opts.where) !== null && _opts_where !== void 0 ? _opts_where : (_cfg_defaults = cfg.defaults) === null || _cfg_defaults === void 0 ? void 0 : (_cfg_defaults_find = _cfg_defaults.find) === null || _cfg_defaults_find === void 0 ? void 0 : _cfg_defaults_find.where;
153
+ // An explicit --where replaces the preset's declared where rather than ANDing with it,
154
+ // so a caller can always widen back to the whole scope.
155
+ const scope = effective.where;
128
156
  const whereJoin = scope ? `JOIN frontmatter f ON f."path" = content.path` : '';
129
157
  const whereCond = scope ? `AND (${scope})` : '';
130
158
  // Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite
@@ -136,6 +164,10 @@ export async function find(db, cfg, terms, opts = {}) {
136
164
  } catch (err) {
137
165
  throw searchError(err, terms, scope);
138
166
  }
167
+ // Scope filtering happens in JS, on each candidate list, rather than in SQL: FTS5 match,
168
+ // link expansion, and vector search all run unscoped above (cheap to over-fetch), then get
169
+ // filtered here against the effective include/exclude before ranking is finalized.
170
+ matchRows = matchRows.filter((r)=>scopedPaths.has(r.path));
139
171
  const hits = new Map(matchRows.map((r)=>[
140
172
  r.path,
141
173
  r.hit
@@ -157,14 +189,13 @@ export async function find(db, cfg, terms, opts = {}) {
157
189
  // that mass from the score list reweights fusion toward connectivity and measurably
158
190
  // wrecks ranking on link-dense corpora (FEVER hit@10 0.997 -> 0.907; fusion-tuning.md).
159
191
  const linked = new Set(edges.flat());
160
- const nodes = db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
161
192
  const seeds = new Map(matchRows.map((r, i)=>[
162
193
  r.path,
163
194
  1 / (i + 1)
164
195
  ]));
165
196
  const ranked = [
166
- ...personalizedRank(nodes, edges, seeds)
167
- ].filter(([, score])=>score > 1e-9).sort((a, b)=>b[1] - a[1]).slice(0, fetch);
197
+ ...personalizedRank(allPaths, edges, seeds)
198
+ ].filter(([path, score])=>score > 1e-9 && scopedPaths.has(path)).sort((a, b)=>b[1] - a[1]).slice(0, fetch);
168
199
  ranked.forEach(([path], i)=>{
169
200
  const existing = candidates.get(path);
170
201
  if (existing) {
@@ -182,8 +213,8 @@ export async function find(db, cfg, terms, opts = {}) {
182
213
  // (weight 1, pool = fetch). Each row carries its best chunk's line range.
183
214
  const chunkLines = new Map();
184
215
  const chunkSimilarity = new Map();
185
- if (opts.semantic) {
186
- const vec = await semanticCandidates(db, cfg, terms, fetch);
216
+ if (semanticEnabled) {
217
+ const vec = (await semanticCandidates(db, cfg, terms, fetch)).filter((v)=>scopedPaths.has(v.path));
187
218
  vec.forEach(({ path, lines, similarity }, i)=>{
188
219
  chunkLines.set(path, lines);
189
220
  chunkSimilarity.set(path, similarity);
@@ -203,10 +234,14 @@ export async function find(db, cfg, terms, opts = {}) {
203
234
  db.exec('CREATE TEMP TABLE _find ("path" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT, lines TEXT, similarity REAL)');
204
235
  const insert = db.prepare('INSERT INTO _find ("path", score, via, hit, lines, similarity) VALUES (?, ?, ?, ?, ?, ?)');
205
236
  for (const [path, c] of candidates)insert.run(path, c.score, c.via, (_hits_get = hits.get(path)) !== null && _hits_get !== void 0 ? _hits_get : null, (_chunkLines_get = chunkLines.get(path)) !== null && _chunkLines_get !== void 0 ? _chunkLines_get : null, (_chunkSimilarity_get = chunkSimilarity.get(path)) !== null && _chunkSimilarity_get !== void 0 ? _chunkSimilarity_get : null);
206
- const where = scope ? `WHERE ${scope}` : '';
237
+ // Every candidate list was already filtered to scopedPaths above, so _find only ever holds
238
+ // in-scope rows -- the final select just needs --where again, same as before, for rows
239
+ // whichever candidate query didn't itself apply it to (link-derived rows never touched
240
+ // whereCond above).
241
+ const where = scope ? `WHERE (${scope})` : '';
207
242
  // lines is always present now: semantic rows carry their chunk's range, oversized-doc
208
243
  // lexical rows gain one below, everything else stays null. similarity stays semantic-only.
209
- const similarityCol = opts.semantic ? ', _find.similarity' : '';
244
+ const similarityCol = semanticEnabled ? ', _find.similarity' : '';
210
245
  const rows = db.prepare(`SELECT f."path" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score, _find.lines${similarityCol}
211
246
  FROM _find JOIN frontmatter f ON f."path" = _find."path" JOIN content ON content.path = _find."path"
212
247
  ${where} ORDER BY _find.score DESC LIMIT ?`).all(k);
@@ -227,6 +262,22 @@ export async function find(db, cfg, terms, opts = {}) {
227
262
  }
228
263
  return rows;
229
264
  }
265
+ // Indexing and embedding are both derived from presets rather than declared directly, so
266
+ // that derivation must stay visible: how many files each preset actually matched, and how
267
+ // many of those are covered by embedding. Read from preset_files (rebuilt at reconcile), not
268
+ // recomputed from globs, so this always reflects what's actually on disk in the cache.
269
+ export function presetCoverage(db, cfg) {
270
+ const embedActive = anyPresetEmbeds(cfg);
271
+ return presetNames(cfg).map((name)=>{
272
+ const files = db.prepare('SELECT COUNT(*) AS n FROM preset_files WHERE preset = ?').get(name).n;
273
+ const embedded = embedActive ? db.prepare('SELECT COUNT(*) AS n FROM preset_files pf WHERE pf.preset = ? AND EXISTS (SELECT 1 FROM embeddings e WHERE e."path" = pf."path" AND e.vector IS NOT NULL)').get(name).n : 0;
274
+ return {
275
+ name,
276
+ files,
277
+ embedded
278
+ };
279
+ });
280
+ }
230
281
  const INTERNAL_COLUMNS = new Set([
231
282
  'path',
232
283
  '_mtime',
@@ -241,7 +292,7 @@ function chunk(items, size) {
241
292
  for(let i = 0; i < items.length; i += size)out.push(items.slice(i, i + size));
242
293
  return out;
243
294
  }
244
- // Layer 0: what is this tree. Fixed-size output regardless of tree size.
295
+ // mapTree: what is this tree. Fixed-size output regardless of tree size.
245
296
  export function mapTree(db, cfg) {
246
297
  const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM("_size"), 0) AS bytes FROM frontmatter').get();
247
298
  const columns = db.prepare('PRAGMA table_info(frontmatter)').all().map((r)=>r.name).filter((name)=>!INTERNAL_COLUMNS.has(name));
@@ -276,12 +327,13 @@ export function mapTree(db, cfg) {
276
327
  fields,
277
328
  fieldsTotal: allFields.length,
278
329
  features: featureStates(cfg),
330
+ presets: presetCoverage(db, cfg),
279
331
  hubs,
280
332
  recent
281
333
  };
282
334
  }
283
335
  const PEEK_LIST_LIMIT = 20;
284
- // Layer 2: everything about one note except its prose -- frontmatter, outline with line
336
+ // peek: everything about one note except its prose -- frontmatter, outline with line
285
337
  // ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.
286
338
  export function peek(db, cfg, pathArg) {
287
339
  var _row__size;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig } from './config.ts';\nimport { featureEnabled, featureStates } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { semanticCandidates } from './features/embed.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\nimport { searchError } from './search-error.ts';\n\n// The three layer commands: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\n// FTS5 snippet() re-tokenizes the whole doc per candidate row, superlinearly: measured\n// 2ms at 4KB docs but 189ms at 64KB for one query's pool, and ~10s for a single 1MB doc\n// (plans/performance-findings.md finding A). 16KB keeps the whole pool's snippet cost in\n// the low-ms range; bigger docs get the JS-computed excerpt, linear and only for the rows\n// actually returned.\nconst SNIPPET_BOUND = 16_384;\nconst EXCERPT_WINDOW = 160;\n\n// Bare terms from an FTS5 query string: strips operators/quoting so the oversized-doc excerpt\n// scan matches the same words the query matched on, not FTS5 syntax.\nfunction extractBareTerms(query: string): string[] {\n const cleaned = query\n .replace(/\"/g, ' ')\n .replace(/[()*]/g, ' ')\n .replace(/\\b(AND|OR|NOT|NEAR)\\b(\\/\\d+)?/gi, ' ');\n return cleaned\n .split(/\\s+/)\n .map((tok) => tok.replace(/^[A-Za-z_]\\w*:/, '')) // column filter, e.g. title:term\n .map((tok) => tok.toLowerCase().trim())\n .filter((tok) => tok.length > 0);\n}\n\nfunction findOccurrences(haystackLower: string, terms: string[]): Array<{ start: number; end: number; term: string }> {\n const occ: Array<{ start: number; end: number; term: string }> = [];\n for (const term of terms) {\n let idx = 0;\n for (;;) {\n const found = haystackLower.indexOf(term, idx);\n if (found === -1) break;\n occ.push({ start: found, end: found + term.length, term });\n idx = found + term.length;\n }\n }\n // Longest span first at equal start, so \"tests\" beats its substring \"test\" and the\n // whole word gets highlighted; the emit loop then absorbs the shorter overlap.\n return occ.sort((a, b) => a.start - b.start || b.end - a.end);\n}\n\n// Slides a window over the sorted occurrences, same semantics as FTS5's own best-window\n// pick: most distinct terms, ties broken by most total hits.\nfunction bestWindowStart(occ: Array<{ start: number; end: number; term: string }>, windowSize: number): number {\n let best = occ[0].start;\n let bestDistinct = 0;\n let bestCount = 0;\n for (let i = 0; i < occ.length; i++) {\n const winEnd = occ[i].start + windowSize;\n const seen = new Set<string>();\n let count = 0;\n for (let j = i; j < occ.length && occ[j].start < winEnd; j++) {\n seen.add(occ[j].term);\n count++;\n }\n if (seen.size > bestDistinct || (seen.size === bestDistinct && count > bestCount)) {\n best = occ[i].start;\n bestDistinct = seen.size;\n bestCount = count;\n }\n }\n return best;\n}\n\n// snippet()-shaped excerpt: matched terms wrapped in «», … at cut edges. Linear in doc\n// length, only run for rows actually returned (<= k), unlike snippet()'s per-row cost.\nfunction computeExcerpt(text: string, terms: string[]): { excerpt: string; offset: number } {\n const occ = findOccurrences(text.toLowerCase(), terms);\n if (occ.length === 0) {\n // This is a raw substring scan, not porter-stemmed: a doc matched only through a\n // stemmed variant (query \"negotiate\" vs doc \"negotiating\") finds no occurrence here.\n // Fall back to the doc's start, unmarked, rather than claim a match that isn't there.\n const end = Math.min(text.length, EXCERPT_WINDOW);\n return { excerpt: `${text.slice(0, end).replace(/\\s+/g, ' ').trim()}${end < text.length ? '…' : ''}`, offset: 0 };\n }\n const start = bestWindowStart(occ, EXCERPT_WINDOW);\n const end = Math.min(text.length, start + EXCERPT_WINDOW);\n let out = '';\n let cursor = start;\n for (const o of occ) {\n if (o.start < start || o.end > end) continue;\n // Occurrences of duplicate or substring-overlapping terms (\"test tests\") can overlap;\n // emitting each would duplicate document text. Keep the first, absorb the rest.\n if (o.start < cursor) continue;\n out += `${text.slice(cursor, o.start)}«${text.slice(o.start, o.end)}»`;\n cursor = o.end;\n }\n out += text.slice(cursor, end);\n const prefix = start > 0 ? '…' : '';\n const suffix = end < text.length ? '…' : '';\n return { excerpt: `${prefix}${out.replace(/\\s+/g, ' ')}${suffix}`, offset: start };\n}\n\nfunction lineNumberAt(text: string, offset: number): number {\n let line = 1;\n for (let i = 0; i < offset; i++) if (text.charCodeAt(i) === 10) line++;\n return line;\n}\n\nfunction lineRangeFor(db: DatabaseSync, path: string, line: number): string | null {\n const row = db.prepare('SELECT start_line, end_line FROM sections WHERE \"path\" = ? AND start_line <= ? AND end_line >= ? ORDER BY start_line DESC LIMIT 1').get(path, line, line) as { start_line: number; end_line: number } | undefined;\n return row ? `L${row.start_line}-${row.end_line}` : null;\n}\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n semantic?: boolean; // invoke vector expansion (requires features.embed); rows gain via 'vector' and a lines column\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\n// Semantic expansion is per-query opt-in: without opts.semantic the result is\n// byte-for-byte independent of the embed feature.\nexport async function find(db: DatabaseSync, cfg: ResolvedConfig, terms: string, opts: FindOptions = {}): Promise<Row[]> {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n // --where applies inside the candidate query (a post-filter over the top-N would drop\n // matches ranked past the pool) and again on the final select for link-derived rows.\n // An explicit --where replaces the tree's declared default rather than ANDing with it,\n // so a caller can always widen back to the whole tree.\n const scope = opts.where ?? cfg.defaults?.find?.where;\n const whereJoin = scope ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = scope ? `AND (${scope})` : '';\n // Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite\n // short-circuits the untaken CASE branch, so it's never invoked on them.\n const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, -1, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string | null }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string | null }>;\n } catch (err) {\n throw searchError(err as Error, terms, scope);\n }\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n // hit === null here means the bound suppressed snippet(), not \"no match\" -- distinguish\n // from via='link' rows (never in matchRows, so absent from this set) below.\n const oversized = new Set(matchRows.filter((r) => r.hit === null).map((r) => r.path));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n const edges = featureEnabled(cfg, 'links') && matchRows.length > 0 ? linkEdges(db) : [];\n if (edges.length > 0) {\n // `linked` gates the label only, not the score: PPR restart mass gives every seed a\n // nonzero rank even without an incident edge, which is not link evidence — but dropping\n // that mass from the score list reweights fusion toward connectivity and measurably\n // wrecks ranking on link-dense corpora (FEVER hit@10 0.997 -> 0.907; fusion-tuning.md).\n const linked = new Set(edges.flat());\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, edges, seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n if (linked.has(path)) existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n // Vector expansion, invoked only: a third RRF list at the swept flat-region constants\n // (weight 1, pool = fetch). Each row carries its best chunk's line range.\n const chunkLines = new Map<string, string>();\n const chunkSimilarity = new Map<string, number>();\n if (opts.semantic) {\n const vec = await semanticCandidates(db, cfg, terms, fetch);\n vec.forEach(({ path, lines, similarity }, i) => {\n chunkLines.set(path, lines);\n chunkSimilarity.set(path, similarity);\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = `${existing.via}+vector`;\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'vector' });\n }\n });\n }\n\n db.exec('DROP TABLE IF EXISTS _find');\n db.exec('CREATE TEMP TABLE _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT, lines TEXT, similarity REAL)');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit, lines, similarity) VALUES (?, ?, ?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null, chunkLines.get(path) ?? null, chunkSimilarity.get(path) ?? null);\n\n const where = scope ? `WHERE ${scope}` : '';\n // lines is always present now: semantic rows carry their chunk's range, oversized-doc\n // lexical rows gain one below, everything else stays null. similarity stays semantic-only.\n const similarityCol = opts.semantic ? ', _find.similarity' : '';\n const rows = db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score, _find.lines${similarityCol}\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n\n if (oversized.size > 0) {\n const bareTerms = extractBareTerms(terms);\n for (const row of rows) {\n if (row.hit !== null || !oversized.has(row.path as string)) continue;\n let text: string;\n try {\n text = readFileSync(join(cfg.baseDir, row.path as string), 'utf8');\n } catch {\n continue; // vanished since the match; leave hit/lines null rather than throw\n }\n const { excerpt, offset } = computeExcerpt(text, bareTerms);\n row.hit = excerpt;\n if (row.lines == null) row.lines = featureEnabled(cfg, 'sections') ? lineRangeFor(db, row.path as string, lineNumberAt(text, offset)) : null;\n }\n }\n\n return rows;\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n features: { on: FeatureName[]; off: FeatureName[] };\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// A result row is capped at SQLITE_MAX_COLUMN (2000, default); two aggregate expressions\n// per field keeps a chunk's row safely under that regardless of how many fields the tree has.\nconst MAP_COLUMN_CHUNK = 300;\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: ResolvedConfig): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n // Observed storage class, not a declared one: these columns are added dynamically and\n // SQLite types per value, so a field can be text in most notes and numeric in a few.\n // Listing every distinct type makes both the type and any drift visible.\n // One aggregate scan per chunk of columns rather than one scan per column: COUNT already\n // ignores NULL, and FILTER keeps typeof() looking only at the non-null rows COUNT counted,\n // matching the old per-column `WHERE ... IS NOT NULL` exactly.\n const allFields: Row[] = [];\n for (const group of chunk(columns, MAP_COLUMN_CHUNK)) {\n const exprs = group.map((name, i) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\n return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const result = db.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter`).get() as Record<string, number | string | null>;\n group.forEach((name, i) => allFields.push({ field: name, coverage: result[`n${i}`] as number, type: (result[`t${i}`] as string) ?? '' }));\n }\n allFields.sort((a, b) => (b.coverage as number) - (a.coverage as number));\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, features: featureStates(cfg), hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks (or a note thousands of\n // headings), and peek's whole point is bounded output. Query the sections/links tables\n // directly for the full list.\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sectionsTotal = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?').get(path) as { n: number }).n : 0;\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?').all(path, PEEK_LIST_LIMIT) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n}\n"],"names":["readFileSync","join","posix","featureEnabled","featureStates","SenseError","semanticCandidates","linkEdges","personalizedRank","searchError","WEIGHTED_BM25","RRF_K","SNIPPET_BOUND","EXCERPT_WINDOW","extractBareTerms","query","cleaned","replace","split","map","tok","toLowerCase","trim","filter","length","findOccurrences","haystackLower","terms","occ","term","idx","found","indexOf","push","start","end","sort","a","b","bestWindowStart","windowSize","best","bestDistinct","bestCount","i","winEnd","seen","Set","count","j","add","size","computeExcerpt","text","Math","min","excerpt","slice","offset","out","cursor","o","prefix","suffix","lineNumberAt","line","charCodeAt","lineRangeFor","db","path","row","prepare","get","start_line","end_line","find","cfg","opts","hits","chunkLines","chunkSimilarity","k","fetch","max","scope","where","defaults","whereJoin","whereCond","matchSql","matchRows","all","err","Map","r","hit","oversized","candidates","forEach","set","score","via","edges","linked","flat","nodes","seeds","ranked","existing","has","semantic","vec","lines","similarity","exec","insert","c","run","similarityCol","rows","bareTerms","baseDir","INTERNAL_COLUMNS","MAP_COLUMN_CHUNK","chunk","items","mapTree","docs","columns","name","allFields","group","exprs","quoted","result","field","coverage","type","fields","hubs","recent","fieldsTotal","features","PEEK_LIST_LIMIT","peek","pathArg","paths","p","base","basename","matches","frontmatter","key","value","Object","entries","sectionsTotal","n","sections","outbound","backlinks","unresolved","backlinksTotal","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/commands.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join, matchesGlob } from 'node:path';\nimport posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig } from './config.ts';\nimport { anyPresetEmbeds, featureEnabled, featureStates, presetNames, resolveSearch } 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 commands: mapTree (orient), search (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\n// FTS5 snippet() re-tokenizes the whole doc per candidate row, superlinearly: measured\n// 2ms at 4KB docs but 189ms at 64KB for one query's pool, and ~10s for a single 1MB doc\n// (plans/performance-findings.md finding A). 16KB keeps the whole pool's snippet cost in\n// the low-ms range; bigger docs get the JS-computed excerpt, linear and only for the rows\n// actually returned.\nconst SNIPPET_BOUND = 16_384;\nconst EXCERPT_WINDOW = 160;\n\n// Bare terms from an FTS5 query string: strips operators/quoting so the oversized-doc excerpt\n// scan matches the same words the query matched on, not FTS5 syntax.\nfunction extractBareTerms(query: string): string[] {\n const cleaned = query\n .replace(/\"/g, ' ')\n .replace(/[()*]/g, ' ')\n .replace(/\\b(AND|OR|NOT|NEAR)\\b(\\/\\d+)?/gi, ' ');\n return cleaned\n .split(/\\s+/)\n .map((tok) => tok.replace(/^[A-Za-z_]\\w*:/, '')) // column filter, e.g. title:term\n .map((tok) => tok.toLowerCase().trim())\n .filter((tok) => tok.length > 0);\n}\n\nfunction findOccurrences(haystackLower: string, terms: string[]): Array<{ start: number; end: number; term: string }> {\n const occ: Array<{ start: number; end: number; term: string }> = [];\n for (const term of terms) {\n let idx = 0;\n for (;;) {\n const found = haystackLower.indexOf(term, idx);\n if (found === -1) break;\n occ.push({ start: found, end: found + term.length, term });\n idx = found + term.length;\n }\n }\n // Longest span first at equal start, so \"tests\" beats its substring \"test\" and the\n // whole word gets highlighted; the emit loop then absorbs the shorter overlap.\n return occ.sort((a, b) => a.start - b.start || b.end - a.end);\n}\n\n// Slides a window over the sorted occurrences, same semantics as FTS5's own best-window\n// pick: most distinct terms, ties broken by most total hits.\nfunction bestWindowStart(occ: Array<{ start: number; end: number; term: string }>, windowSize: number): number {\n let best = occ[0].start;\n let bestDistinct = 0;\n let bestCount = 0;\n for (let i = 0; i < occ.length; i++) {\n const winEnd = occ[i].start + windowSize;\n const seen = new Set<string>();\n let count = 0;\n for (let j = i; j < occ.length && occ[j].start < winEnd; j++) {\n seen.add(occ[j].term);\n count++;\n }\n if (seen.size > bestDistinct || (seen.size === bestDistinct && count > bestCount)) {\n best = occ[i].start;\n bestDistinct = seen.size;\n bestCount = count;\n }\n }\n return best;\n}\n\n// snippet()-shaped excerpt: matched terms wrapped in «», … at cut edges. Linear in doc\n// length, only run for rows actually returned (<= k), unlike snippet()'s per-row cost.\nfunction computeExcerpt(text: string, terms: string[]): { excerpt: string; offset: number } {\n const occ = findOccurrences(text.toLowerCase(), terms);\n if (occ.length === 0) {\n // This is a raw substring scan, not porter-stemmed: a doc matched only through a\n // stemmed variant (query \"negotiate\" vs doc \"negotiating\") finds no occurrence here.\n // Fall back to the doc's start, unmarked, rather than claim a match that isn't there.\n const end = Math.min(text.length, EXCERPT_WINDOW);\n return { excerpt: `${text.slice(0, end).replace(/\\s+/g, ' ').trim()}${end < text.length ? '…' : ''}`, offset: 0 };\n }\n const start = bestWindowStart(occ, EXCERPT_WINDOW);\n const end = Math.min(text.length, start + EXCERPT_WINDOW);\n let out = '';\n let cursor = start;\n for (const o of occ) {\n if (o.start < start || o.end > end) continue;\n // Occurrences of duplicate or substring-overlapping terms (\"test tests\") can overlap;\n // emitting each would duplicate document text. Keep the first, absorb the rest.\n if (o.start < cursor) continue;\n out += `${text.slice(cursor, o.start)}«${text.slice(o.start, o.end)}»`;\n cursor = o.end;\n }\n out += text.slice(cursor, end);\n const prefix = start > 0 ? '…' : '';\n const suffix = end < text.length ? '…' : '';\n return { excerpt: `${prefix}${out.replace(/\\s+/g, ' ')}${suffix}`, offset: start };\n}\n\nfunction lineNumberAt(text: string, offset: number): number {\n let line = 1;\n for (let i = 0; i < offset; i++) if (text.charCodeAt(i) === 10) line++;\n return line;\n}\n\nfunction lineRangeFor(db: DatabaseSync, path: string, line: number): string | null {\n const row = db.prepare('SELECT start_line, end_line FROM sections WHERE \"path\" = ? AND start_line <= ? AND end_line >= ? ORDER BY start_line DESC LIMIT 1').get(path, line, line) as { start_line: number; end_line: number } | undefined;\n return row ? `L${row.start_line}-${row.end_line}` : null;\n}\n\nexport interface SearchOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n preset?: string; // named preset; unknown name throws listing declared presets, undefined -> \"default\"\n include?: string[]; // ad hoc scope override (repeatable --include); replaces the preset's include/exclude\n semantic?: boolean; // false opts out of vector participation regardless of the preset\n}\n\n// node:path's matchesGlob is experimental (stable behind an unstable-API flag) as of the\n// engines floor (Node >=22.16); scope filtering only ever needs single-pattern matching, so\n// it's used here in JS rather than pulling fast-glob's directory walk into the query path.\nfunction inScope(path: string, include: string[], exclude?: string[]): boolean {\n if (!include.some((g) => matchesGlob(path, g))) return false;\n if (exclude?.some((g) => matchesGlob(path, g))) return false;\n return true;\n}\n\nfunction scopeHasEmbeddings(db: DatabaseSync, cfg: ResolvedConfig, scopedPaths: Set<string>): boolean {\n if (!anyPresetEmbeds(cfg)) return false; // the embeddings table doesn't exist at all in this case\n const rows = db.prepare('SELECT DISTINCT \"path\" FROM embeddings').all() as Array<{ path: string }>;\n return rows.some((r) => scopedPaths.has(r.path));\n}\n\n// BM25 + link-graph expansion, fused by reciprocal rank. `via` says which signal produced\n// each row so the agent knows what evidence it is trusting.\n// Resolution precedence (built-ins <- preset <- caller overrides) lives in\n// src/config.ts:resolveSearch; `opts` here is whatever the caller (a saved search merged\n// with any CLI flag, or a bare CLI invocation) already resolved.\nexport async function search(db: DatabaseSync, cfg: ResolvedConfig, terms: string, opts: SearchOptions = {}): Promise<Row[]> {\n const effective = resolveSearch(cfg, opts);\n const { k, include, exclude } = effective;\n\n const allPaths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n // A preset scope reads preset_files -- the coverage reconcile actually indexed with\n // (fast-glob semantics); re-matching globs here with node:path's matchesGlob could\n // silently disagree with it. Ad-hoc --include has no persisted coverage, so only that\n // path pays the JS glob match.\n const scopedPaths = opts.include ? new Set(allPaths.filter((p) => inScope(p, include, exclude))) : new Set((db.prepare('SELECT \"path\" FROM preset_files WHERE preset = ?').all(effective.presetName) as Array<{ path: string }>).map((r) => r.path));\n // A scope narrower than the whole index needs a bigger candidate pool before filtering, or\n // filtering can starve k even though enough in-scope matches exist further down the ranked\n // list that a plain over-fetch wouldn't have reached.\n const scopeActive = scopedPaths.size < allPaths.length;\n const fetch = scopeActive ? Math.max(k * 5, 50) : Math.max(k * 3, 30);\n\n // Semantic participation: vectors run only when the effective setting allows it AND the\n // scope actually has embedded files -- a scope covered only by semantic-false presets (or\n // by no preset embedding at all) searches lexically, automatically, no error.\n const semanticEnabled = effective.semantic && scopeHasEmbeddings(db, cfg, scopedPaths);\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 preset's declared where rather than ANDing with it,\n // so a caller can always widen back to the whole scope.\n const scope = effective.where;\n const whereJoin = scope ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = scope ? `AND (${scope})` : '';\n // Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite\n // short-circuits the untaken CASE branch, so it's never invoked on them.\n const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, -1, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string | null }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string | null }>;\n } catch (err) {\n throw searchError(err as Error, terms, scope);\n }\n // Scope filtering happens in JS, on each candidate list, rather than in SQL: FTS5 match,\n // link expansion, and vector search all run unscoped above (cheap to over-fetch), then get\n // filtered here against the effective include/exclude before ranking is finalized.\n matchRows = matchRows.filter((r) => scopedPaths.has(r.path));\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n // hit === null here means the bound suppressed snippet(), not \"no match\" -- distinguish\n // from via='link' rows (never in matchRows, so absent from this set) below.\n const oversized = new Set(matchRows.filter((r) => r.hit === null).map((r) => r.path));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n const edges = featureEnabled(cfg, 'links') && matchRows.length > 0 ? linkEdges(db) : [];\n if (edges.length > 0) {\n // `linked` gates the label only, not the score: PPR restart mass gives every seed a\n // nonzero rank even without an incident edge, which is not link evidence — but dropping\n // that mass from the score list reweights fusion toward connectivity and measurably\n // wrecks ranking on link-dense corpora (FEVER hit@10 0.997 -> 0.907; fusion-tuning.md).\n const linked = new Set(edges.flat());\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(allPaths, edges, seeds)]\n .filter(([path, score]) => score > 1e-9 && scopedPaths.has(path))\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 (semanticEnabled) {\n const vec = (await semanticCandidates(db, cfg, terms, fetch)).filter((v) => scopedPaths.has(v.path));\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 // Every candidate list was already filtered to scopedPaths above, so _find only ever holds\n // in-scope rows -- the final select just needs --where again, same as before, for rows\n // whichever candidate query didn't itself apply it to (link-derived rows never touched\n // whereCond above).\n const where = scope ? `WHERE (${scope})` : '';\n // lines is always present now: semantic rows carry their chunk's range, oversized-doc\n // lexical rows gain one below, everything else stays null. similarity stays semantic-only.\n const similarityCol = semanticEnabled ? ', _find.similarity' : '';\n const rows = db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score, _find.lines${similarityCol}\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n\n if (oversized.size > 0) {\n const bareTerms = extractBareTerms(terms);\n for (const row of rows) {\n if (row.hit !== null || !oversized.has(row.path as string)) continue;\n let text: string;\n try {\n text = readFileSync(join(cfg.baseDir, row.path as string), 'utf8');\n } catch {\n continue; // vanished since the match; leave hit/lines null rather than throw\n }\n const { excerpt, offset } = computeExcerpt(text, bareTerms);\n row.hit = excerpt;\n if (row.lines == null) row.lines = featureEnabled(cfg, 'sections') ? lineRangeFor(db, row.path as string, lineNumberAt(text, offset)) : null;\n }\n }\n\n return rows;\n}\n\nexport interface PresetCoverage {\n name: string;\n files: number;\n embedded: number;\n}\n\n// Indexing and embedding are both derived from presets rather than declared directly, so\n// that derivation must stay visible: how many files each preset actually matched, and how\n// many of those are covered by embedding. Read from preset_files (rebuilt at reconcile), not\n// recomputed from globs, so this always reflects what's actually on disk in the cache.\nexport function presetCoverage(db: DatabaseSync, cfg: ResolvedConfig): PresetCoverage[] {\n const embedActive = anyPresetEmbeds(cfg);\n return presetNames(cfg).map((name) => {\n const files = (db.prepare('SELECT COUNT(*) AS n FROM preset_files WHERE preset = ?').get(name) as { n: number }).n;\n const embedded = embedActive ? (db.prepare('SELECT COUNT(*) AS n FROM preset_files pf WHERE pf.preset = ? AND EXISTS (SELECT 1 FROM embeddings e WHERE e.\"path\" = pf.\"path\" AND e.vector IS NOT NULL)').get(name) as { n: number }).n : 0;\n return { name, files, embedded };\n });\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 presets: PresetCoverage[];\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// A result row is capped at SQLITE_MAX_COLUMN (2000, default); two aggregate expressions\n// per field keeps a chunk's row safely under that regardless of how many fields the tree has.\nconst MAP_COLUMN_CHUNK = 300;\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\n// mapTree: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: ResolvedConfig): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n // Observed storage class, not a declared one: these columns are added dynamically and\n // SQLite types per value, so a field can be text in most notes and numeric in a few.\n // Listing every distinct type makes both the type and any drift visible.\n // One aggregate scan per chunk of columns rather than one scan per column: COUNT already\n // ignores NULL, and FILTER keeps typeof() looking only at the non-null rows COUNT counted,\n // matching the old per-column `WHERE ... IS NOT NULL` exactly.\n const allFields: Row[] = [];\n for (const group of chunk(columns, MAP_COLUMN_CHUNK)) {\n const exprs = group.map((name, i) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\n return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const result = db.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter`).get() as Record<string, number | string | null>;\n group.forEach((name, i) => allFields.push({ field: name, coverage: result[`n${i}`] as number, type: (result[`t${i}`] as string) ?? '' }));\n }\n allFields.sort((a, b) => (b.coverage as number) - (a.coverage as number));\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, features: featureStates(cfg), presets: presetCoverage(db, cfg), hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks (or a note thousands of\n // headings), and peek's whole point is bounded output. Query the sections/links tables\n // directly for the full list.\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n// peek: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sectionsTotal = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?').get(path) as { n: number }).n : 0;\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?').all(path, PEEK_LIST_LIMIT) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n}\n"],"names":["readFileSync","join","matchesGlob","posix","anyPresetEmbeds","featureEnabled","featureStates","presetNames","resolveSearch","SenseError","semanticCandidates","linkEdges","personalizedRank","searchError","WEIGHTED_BM25","RRF_K","SNIPPET_BOUND","EXCERPT_WINDOW","extractBareTerms","query","cleaned","replace","split","map","tok","toLowerCase","trim","filter","length","findOccurrences","haystackLower","terms","occ","term","idx","found","indexOf","push","start","end","sort","a","b","bestWindowStart","windowSize","best","bestDistinct","bestCount","i","winEnd","seen","Set","count","j","add","size","computeExcerpt","text","Math","min","excerpt","slice","offset","out","cursor","o","prefix","suffix","lineNumberAt","line","charCodeAt","lineRangeFor","db","path","row","prepare","get","start_line","end_line","inScope","include","exclude","some","g","scopeHasEmbeddings","cfg","scopedPaths","rows","all","r","has","search","opts","hits","chunkLines","chunkSimilarity","effective","k","allPaths","p","presetName","scopeActive","fetch","max","semanticEnabled","semantic","scope","where","whereJoin","whereCond","matchSql","matchRows","err","Map","hit","oversized","candidates","forEach","set","score","via","edges","linked","flat","seeds","ranked","existing","vec","v","lines","similarity","exec","insert","c","run","similarityCol","bareTerms","baseDir","presetCoverage","embedActive","name","files","n","embedded","INTERNAL_COLUMNS","MAP_COLUMN_CHUNK","chunk","items","mapTree","docs","columns","allFields","group","exprs","quoted","result","field","coverage","type","fields","hubs","recent","fieldsTotal","features","presets","PEEK_LIST_LIMIT","peek","pathArg","paths","find","base","basename","matches","frontmatter","key","value","Object","entries","sectionsTotal","sections","outbound","backlinks","unresolved","backlinksTotal","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}