sensemaking 0.14.0 → 0.15.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.
Files changed (40) hide show
  1. package/dist/cjs/commands/peek.js +3 -3
  2. package/dist/cjs/commands/peek.js.map +1 -1
  3. package/dist/cjs/commands/related.js +2 -2
  4. package/dist/cjs/commands/related.js.map +1 -1
  5. package/dist/cjs/db/open.d.cts +1 -1
  6. package/dist/cjs/db/open.d.ts +1 -1
  7. package/dist/cjs/db/open.js +1 -1
  8. package/dist/cjs/db/open.js.map +1 -1
  9. package/dist/cjs/features/embed.js +4 -6
  10. package/dist/cjs/features/embed.js.map +1 -1
  11. package/dist/cjs/features/links.js +100 -16
  12. package/dist/cjs/features/links.js.map +1 -1
  13. package/dist/cjs/features/sections.js +4 -6
  14. package/dist/cjs/features/sections.js.map +1 -1
  15. package/dist/cjs/features/tags.js +129 -11
  16. package/dist/cjs/features/tags.js.map +1 -1
  17. package/dist/cjs/fences.d.cts +12 -0
  18. package/dist/cjs/fences.d.ts +12 -0
  19. package/dist/cjs/fences.js +157 -0
  20. package/dist/cjs/fences.js.map +1 -0
  21. package/dist/esm/commands/peek.js +3 -3
  22. package/dist/esm/commands/peek.js.map +1 -1
  23. package/dist/esm/commands/related.js +2 -2
  24. package/dist/esm/commands/related.js.map +1 -1
  25. package/dist/esm/db/open.d.ts +1 -1
  26. package/dist/esm/db/open.js +1 -1
  27. package/dist/esm/db/open.js.map +1 -1
  28. package/dist/esm/features/embed.js +4 -6
  29. package/dist/esm/features/embed.js.map +1 -1
  30. package/dist/esm/features/links.js +63 -15
  31. package/dist/esm/features/links.js.map +1 -1
  32. package/dist/esm/features/sections.js +4 -6
  33. package/dist/esm/features/sections.js.map +1 -1
  34. package/dist/esm/features/tags.js +127 -9
  35. package/dist/esm/features/tags.js.map +1 -1
  36. package/dist/esm/fences.d.ts +12 -0
  37. package/dist/esm/fences.js +138 -0
  38. package/dist/esm/fences.js.map +1 -0
  39. package/package.json +1 -1
  40. package/skills/sense/SKILL.md +1 -1
@@ -135,7 +135,7 @@ function peek(db, cfg, pathArg) {
135
135
  var backlinksTotal = 0;
136
136
  var _allowed = (0, _scopets.scopedPaths)(db, cfg, overrides);
137
137
  if ((0, _indexts.featureEnabled)(cfg, 'links')) {
138
- var out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path);
138
+ var out = db.prepare('SELECT target, dst FROM links WHERE src = ? AND (dst IS NULL OR dst != src) ORDER BY target').all(path);
139
139
  outbound = _to_consumable_array(new Set(out.filter(function(l) {
140
140
  return l.dst !== null;
141
141
  }).map(function(l) {
@@ -146,8 +146,8 @@ function peek(db, cfg, pathArg) {
146
146
  }).map(function(l) {
147
147
  return l.target;
148
148
  });
149
- backlinksTotal = db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path).n;
150
- backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT).map(function(r) {
149
+ backlinksTotal = db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ? AND src != dst').get(path).n;
150
+ backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT).map(function(r) {
151
151
  return r.src;
152
152
  });
153
153
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Row } from '../output.ts';\nimport { INTERNAL_COLUMNS, scopedPaths } from './scope.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n // Set when the note's frontmatter was refused, so an empty frontmatter block reads as \"did\n // not parse\" rather than \"has none\". Same reason `_parse_error` sits in the row.\n parseError: string | null;\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 // Bounded k-hop expansion beyond the immediate ring already shown by outbound/backlinks\n // (depth starts at 2).\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, overrides: SearchOverrides = {}): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\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 const _allowed = scopedPaths(db, cfg, overrides);\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 parseError,\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":["peek","resolveNote","paths","arg","exact","find","p","base","posix","basename","replace","toLowerCase","matches","filter","length","SenseError","join","PEEK_LIST_LIMIT","db","cfg","pathArg","overrides","row","prepare","all","map","r","path","get","parseError","_parse_error","frontmatter","Object","entries","key","value","INTERNAL_COLUMNS","has","sectionsTotal","featureEnabled","n","sections","outbound","backlinks","unresolved","backlinksTotal","_allowed","scopedPaths","out","Set","l","dst","target","src","tokens","Math","ceil","_size","slice","outboundTotal","unresolvedTotal","off","name"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Row } from '../output.ts';\nimport { INTERNAL_COLUMNS, scopedPaths } from './scope.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n // Set when the note's frontmatter was refused, so an empty frontmatter block reads as \"did\n // not parse\" rather than \"has none\". Same reason `_parse_error` sits in the row.\n parseError: string | null;\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 // Bounded k-hop expansion beyond the immediate ring already shown by outbound/backlinks\n // (depth starts at 2).\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, overrides: SearchOverrides = {}): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\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 const _allowed = scopedPaths(db, cfg, overrides);\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? AND (dst IS NULL OR dst != 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 = ? AND src != dst').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != 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 parseError,\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":["peek","resolveNote","paths","arg","exact","find","p","base","posix","basename","replace","toLowerCase","matches","filter","length","SenseError","join","PEEK_LIST_LIMIT","db","cfg","pathArg","overrides","row","prepare","all","map","r","path","get","parseError","_parse_error","frontmatter","Object","entries","key","value","INTERNAL_COLUMNS","has","sectionsTotal","featureEnabled","n","sections","outbound","backlinks","unresolved","backlinksTotal","_allowed","scopedPaths","out","Set","l","dst","target","src","tokens","Math","ceil","_size","slice","outboundTotal","unresolvedTotal","off","name"],"mappings":";;;;;;;;;;;QA+CgBA;eAAAA;;QArCAC;eAAAA;;;4DAVE;uBAGa;wBACJ;uBAEmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIvC,SAASA,YAAYC,KAAe,EAAEC,GAAW;IACtD,IAAMC,QAAQF,MAAMG,IAAI,CAAC,SAACC;eAAMA,MAAMH;;IACtC,IAAIC,OAAO,OAAOA;IAClB,IAAMG,OAAOC,cAAK,CAACC,QAAQ,CAACN,KAAKO,OAAO,CAAC,UAAU,IAAIC,WAAW;IAClE,IAAMC,UAAUV,MAAMW,MAAM,CAAC,SAACP;eAAME,cAAK,CAACC,QAAQ,CAACH,GAAGI,OAAO,CAAC,UAAU,IAAIC,WAAW,OAAOJ;;IAC9F,IAAIK,QAAQE,MAAM,KAAK,GAAG,OAAOF,OAAO,CAAC,EAAE;IAC3C,IAAIA,QAAQE,MAAM,GAAG,GAAG,MAAM,IAAIC,oBAAU,CAAC,kBAAkB,AAAC,IAAyBH,OAAtBT,KAAI,oBAAqC,OAAnBS,QAAQI,IAAI,CAAC;IACtG,MAAM,IAAID,oBAAU,CAAC,kBAAkB,AAAC,oBAAuB,OAAJZ,KAAI;AACjE;AAyBA,IAAMc,kBAAkB;AAIjB,SAASjB,KAAKkB,EAAgB,EAAEC,GAAmB,EAAEC,OAAe;QAAEC,YAAAA,iEAA6B,CAAC;QAKrFC,mBAwBEA;IA5BtB,IAAMpB,QAAQ,AAACgB,GAAGK,OAAO,CAAC,kCAAkCC,GAAG,GAA+BC,GAAG,CAAC,SAACC;eAAMA,EAAEC,IAAI;;IAC/G,IAAMA,OAAO1B,YAAYC,OAAOkB;IAEhC,IAAME,MAAMJ,GAAGK,OAAO,CAAC,8CAA8CK,GAAG,CAACD;IACzE,IAAME,cAAcP,oBAAAA,IAAIQ,YAAY,cAAhBR,+BAAAA,oBAAsC;IAC1D,IAAMS,cAAmB,CAAC;QACrB,kCAAA,2BAAA;;QAAL,QAAK,YAAsBC,OAAOC,OAAO,CAACX,yBAArC,SAAA,6BAAA,QAAA,yBAAA,iCAA2C;YAA3C,mCAAA,iBAAOY,sBAAKC;YACf,IAAI,CAACC,yBAAgB,CAACC,GAAG,CAACH,QAAQC,UAAU,MAAMJ,WAAW,CAACG,IAAI,GAAGC;QACvE;;QAFK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAIL,IAAMG,gBAAgBC,IAAAA,uBAAc,EAACpB,KAAK,cAAc,AAACD,GAAGK,OAAO,CAAC,uDAAuDK,GAAG,CAACD,MAAwBa,CAAC,GAAG;IAC3J,IAAMC,WAAWF,IAAAA,uBAAc,EAACpB,KAAK,cAAeD,GAAGK,OAAO,CAAC,2GAA2GC,GAAG,CAACG,MAAMV,mBAA6B,EAAE;IAEnN,IAAIyB,WAAqB,EAAE;IAC3B,IAAIC,YAAsB,EAAE;IAC5B,IAAIC,aAAuB,EAAE;IAC7B,IAAIC,iBAAiB;IACrB,IAAMC,WAAWC,IAAAA,oBAAW,EAAC7B,IAAIC,KAAKE;IACtC,IAAIkB,IAAAA,uBAAc,EAACpB,KAAK,UAAU;QAChC,IAAM6B,MAAM9B,GAAGK,OAAO,CAAC,+FAA+FC,GAAG,CAACG;QAC1He,WAAY,qBAAG,IAAIO,IAAID,IAAInC,MAAM,CAAC,SAACqC;mBAAMA,EAAEC,GAAG,KAAK;WAAM1B,GAAG,CAAC,SAACyB;mBAAMA,EAAEC,GAAG;;QACzEP,aAAaI,IAAInC,MAAM,CAAC,SAACqC;mBAAMA,EAAEC,GAAG,KAAK;WAAM1B,GAAG,CAAC,SAACyB;mBAAMA,EAAEE,MAAM;;QAClEP,iBAAiB,AAAC3B,GAAGK,OAAO,CAAC,2EAA2EK,GAAG,CAACD,MAAwBa,CAAC;QACrIG,YAAY,AAACzB,GAAGK,OAAO,CAAC,oFAAoFC,GAAG,CAACG,MAAMV,iBAA4CQ,GAAG,CAAC,SAACC;mBAAMA,EAAE2B,GAAG;;IACpL;IAEA,OAAO;QACL1B,MAAAA;QACA2B,QAAQC,KAAKC,IAAI,CAAC,EAAElC,aAAAA,IAAImC,KAAK,cAATnC,wBAAAA,aAAwB,KAAK;QACjDS,aAAAA;QACAF,YAAAA;QACAY,UAAAA;QACAC,UAAUA,SAASgB,KAAK,CAAC,GAAGzC;QAC5B0B,WAAAA;QACAC,YAAYA,WAAWc,KAAK,CAAC,GAAGzC;QAChCqB,eAAAA;QACAqB,eAAejB,SAAS5B,MAAM;QAC9B+B,gBAAAA;QACAe,iBAAiBhB,WAAW9B,MAAM;QAClC+C,KAAK,AAAC;YAAC;YAAY;SAAQ,CAAmBhD,MAAM,CAAC,SAACiD;mBAAS,CAACvB,IAAAA,uBAAc,EAACpB,KAAK2C;;IACtF;AACF"}
@@ -176,10 +176,10 @@ function relatedNotes(db, cfg, pathArg, overrides, k) {
176
176
  return r.path;
177
177
  });
178
178
  path = (0, _peekts.resolveNote)(paths, pathArg);
179
- outbound = db.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL').all(path).map(function(r) {
179
+ outbound = db.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src').all(path).map(function(r) {
180
180
  return r.dst;
181
181
  });
182
- backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ?').all(path).map(function(r) {
182
+ backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst').all(path).map(function(r) {
183
183
  return r.src;
184
184
  });
185
185
  exclude = new Set([
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedEnabled, resolveSearch } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { embedPending, hasEmbedding, modelPresent, similarNotes } from '../features/embed.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\n// Notes most similar by cosine, excluding self and everything already linked either way.\n// Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.\nexport async function relatedNotes(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const outbound = (db.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL').all(path) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ?').all(path) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause. An empty table then means one thing: nothing near in meaning that this\n // note does not already link to, which is a real answer.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json, then run `sense download` (search works without it, on words and links)');\n }\n // search gates on the same flag (see wantsVectors above); reading it here too keeps\n // `semantic: false` meaning one thing. Without this, an overlapping semantic-on preset's\n // vectors would answer for a scope that declined them.\n if (!effective.semantic) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" sets \"semantic\": false, so this scope has no vectors and related has no other signal; search it instead (words and links), or set semantic back on for that preset`);\n }\n if (!modelPresent(cfg)) {\n throw new SenseError('EMBED_MODEL_MISSING', 'related ranks notes by meaning, so it needs the embedding model, which is not downloaded; run `sense download` (search still works without it, on words and links)');\n }\n const allowed = scopedPaths(db, cfg, overrides);\n // Top up pending rows before the seed check, or a fresh index reports every note as\n // having no indexed text until some search has run.\n await embedPending(db, cfg, cfg.baseDir);\n if (!hasEmbedding(db, path)) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!scopeHasEmbeddings(db, cfg, allowed)) return [];\n return similarNotes(db, cfg, path, { exclude, allowed, k });\n}\n"],"names":["relatedNotes","db","cfg","pathArg","overrides","k","paths","path","outbound","backlinks","exclude","effective","allowed","prepare","all","map","r","resolveNote","dst","src","Set","resolveSearch","embedEnabled","SenseError","semantic","presetName","modelPresent","scopedPaths","embedPending","baseDir","hasEmbedding","scopeHasEmbeddings","similarNotes"],"mappings":";;;;+BAUsBA;;;eAAAA;;;uBARsB;wBACjB;uBAC4C;sBAC3C;uBACoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIzC,SAAeA,aAAaC,EAAgB,EAAEC,GAAmB,EAAEC,OAAe,EAAEC,SAA0B,EAAEC,CAAS;;YACxHC,OACAC,MAEAC,UACAC,WACAC,SAKAC,WAaAC;;;;oBAvBAN,QAAQ,AAACL,GAAGY,OAAO,CAAC,kCAAkCC,GAAG,GAA+BC,GAAG,CAAC,SAACC;+BAAMA,EAAET,IAAI;;oBACzGA,OAAOU,IAAAA,mBAAW,EAACX,OAAOH;oBAE1BK,WAAW,AAACP,GAAGY,OAAO,CAAC,oEAAoEC,GAAG,CAACP,MAAiCQ,GAAG,CAAC,SAACC;+BAAMA,EAAEE,GAAG;;oBAChJT,YAAY,AAACR,GAAGY,OAAO,CAAC,gDAAgDC,GAAG,CAACP,MAAiCQ,GAAG,CAAC,SAACC;+BAAMA,EAAEG,GAAG;;oBAC7HT,UAAU,IAAIU,IAAI;wBAACb;sBAAD,OAAO,qBAAGC,WAAU,qBAAGC;oBAE/C,yFAAyF;oBACzF,2FAA2F;oBAC3F,yDAAyD;oBACnDE,YAAYU,IAAAA,sBAAa,EAACnB,KAAKE;oBACrC,IAAI,CAACkB,IAAAA,qBAAY,EAACpB,MAAM;wBACtB,MAAM,IAAIqB,oBAAU,CAAC,kBAAkB;oBACzC;oBACA,oFAAoF;oBACpF,yFAAyF;oBACzF,uDAAuD;oBACvD,IAAI,CAACZ,UAAUa,QAAQ,EAAE;wBACvB,MAAM,IAAID,oBAAU,CAAC,uBAAuB,AAAC,WAA+B,OAArBZ,UAAUc,UAAU,EAAC;oBAC9E;oBACA,IAAI,CAACC,IAAAA,qBAAY,EAACxB,MAAM;wBACtB,MAAM,IAAIqB,oBAAU,CAAC,uBAAuB;oBAC9C;oBACMX,UAAUe,IAAAA,oBAAW,EAAC1B,IAAIC,KAAKE;oBACrC,oFAAoF;oBACpF,oDAAoD;oBACpD;;wBAAMwB,IAAAA,qBAAY,EAAC3B,IAAIC,KAAKA,IAAI2B,OAAO;;;oBAAvC;oBACA,IAAI,CAACC,IAAAA,qBAAY,EAAC7B,IAAIM,OAAO;wBAC3B,MAAM,IAAIgB,oBAAU,CAAC,qBAAqB,AAAC,GAAO,OAALhB,MAAK;oBACpD;oBACA,IAAI,CAACwB,IAAAA,2BAAkB,EAAC9B,IAAIC,KAAKU,UAAU;;;;oBAC3C;;wBAAOoB,IAAAA,qBAAY,EAAC/B,IAAIC,KAAKK,MAAM;4BAAEG,SAAAA;4BAASE,SAAAA;4BAASP,GAAAA;wBAAE;;;;IAC3D"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedEnabled, resolveSearch } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { embedPending, hasEmbedding, modelPresent, similarNotes } from '../features/embed.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\n// Notes most similar by cosine, excluding self and everything already linked either way.\n// Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.\nexport async function relatedNotes(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const outbound = (db.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src').all(path) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst').all(path) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause. An empty table then means one thing: nothing near in meaning that this\n // note does not already link to, which is a real answer.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json, then run `sense download` (search works without it, on words and links)');\n }\n // search gates on the same flag (see wantsVectors above); reading it here too keeps\n // `semantic: false` meaning one thing. Without this, an overlapping semantic-on preset's\n // vectors would answer for a scope that declined them.\n if (!effective.semantic) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" sets \"semantic\": false, so this scope has no vectors and related has no other signal; search it instead (words and links), or set semantic back on for that preset`);\n }\n if (!modelPresent(cfg)) {\n throw new SenseError('EMBED_MODEL_MISSING', 'related ranks notes by meaning, so it needs the embedding model, which is not downloaded; run `sense download` (search still works without it, on words and links)');\n }\n const allowed = scopedPaths(db, cfg, overrides);\n // Top up pending rows before the seed check, or a fresh index reports every note as\n // having no indexed text until some search has run.\n await embedPending(db, cfg, cfg.baseDir);\n if (!hasEmbedding(db, path)) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!scopeHasEmbeddings(db, cfg, allowed)) return [];\n return similarNotes(db, cfg, path, { exclude, allowed, k });\n}\n"],"names":["relatedNotes","db","cfg","pathArg","overrides","k","paths","path","outbound","backlinks","exclude","effective","allowed","prepare","all","map","r","resolveNote","dst","src","Set","resolveSearch","embedEnabled","SenseError","semantic","presetName","modelPresent","scopedPaths","embedPending","baseDir","hasEmbedding","scopeHasEmbeddings","similarNotes"],"mappings":";;;;+BAUsBA;;;eAAAA;;;uBARsB;wBACjB;uBAC4C;sBAC3C;uBACoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIzC,SAAeA,aAAaC,EAAgB,EAAEC,GAAmB,EAAEC,OAAe,EAAEC,SAA0B,EAAEC,CAAS;;YACxHC,OACAC,MAEAC,UACAC,WACAC,SAKAC,WAaAC;;;;oBAvBAN,QAAQ,AAACL,GAAGY,OAAO,CAAC,kCAAkCC,GAAG,GAA+BC,GAAG,CAAC,SAACC;+BAAMA,EAAET,IAAI;;oBACzGA,OAAOU,IAAAA,mBAAW,EAACX,OAAOH;oBAE1BK,WAAW,AAACP,GAAGY,OAAO,CAAC,mFAAmFC,GAAG,CAACP,MAAiCQ,GAAG,CAAC,SAACC;+BAAMA,EAAEE,GAAG;;oBAC/JT,YAAY,AAACR,GAAGY,OAAO,CAAC,+DAA+DC,GAAG,CAACP,MAAiCQ,GAAG,CAAC,SAACC;+BAAMA,EAAEG,GAAG;;oBAC5IT,UAAU,IAAIU,IAAI;wBAACb;sBAAD,OAAO,qBAAGC,WAAU,qBAAGC;oBAE/C,yFAAyF;oBACzF,2FAA2F;oBAC3F,yDAAyD;oBACnDE,YAAYU,IAAAA,sBAAa,EAACnB,KAAKE;oBACrC,IAAI,CAACkB,IAAAA,qBAAY,EAACpB,MAAM;wBACtB,MAAM,IAAIqB,oBAAU,CAAC,kBAAkB;oBACzC;oBACA,oFAAoF;oBACpF,yFAAyF;oBACzF,uDAAuD;oBACvD,IAAI,CAACZ,UAAUa,QAAQ,EAAE;wBACvB,MAAM,IAAID,oBAAU,CAAC,uBAAuB,AAAC,WAA+B,OAArBZ,UAAUc,UAAU,EAAC;oBAC9E;oBACA,IAAI,CAACC,IAAAA,qBAAY,EAACxB,MAAM;wBACtB,MAAM,IAAIqB,oBAAU,CAAC,uBAAuB;oBAC9C;oBACMX,UAAUe,IAAAA,oBAAW,EAAC1B,IAAIC,KAAKE;oBACrC,oFAAoF;oBACpF,oDAAoD;oBACpD;;wBAAMwB,IAAAA,qBAAY,EAAC3B,IAAIC,KAAKA,IAAI2B,OAAO;;;oBAAvC;oBACA,IAAI,CAACC,IAAAA,qBAAY,EAAC7B,IAAIM,OAAO;wBAC3B,MAAM,IAAIgB,oBAAU,CAAC,qBAAqB,AAAC,GAAO,OAALhB,MAAK;oBACpD;oBACA,IAAI,CAACwB,IAAAA,2BAAkB,EAAC9B,IAAIC,KAAKU,UAAU;;;;oBAC3C;;wBAAOoB,IAAAA,qBAAY,EAAC/B,IAAIC,KAAKK,MAAM;4BAAEG,SAAAA;4BAASE,SAAAA;4BAASP,GAAAA;wBAAE;;;;IAC3D"}
@@ -1,7 +1,7 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
2
  import type { ResolvedConfig } from '../config/index.js';
3
3
  export declare const DB_FILENAME = "cache.db";
4
- export declare const SCHEMA_VERSION = "14";
4
+ export declare const SCHEMA_VERSION = "16";
5
5
  export interface OpenResult {
6
6
  db: DatabaseSync;
7
7
  cfg: ResolvedConfig;
@@ -1,7 +1,7 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
2
  import type { ResolvedConfig } from '../config/index.js';
3
3
  export declare const DB_FILENAME = "cache.db";
4
- export declare const SCHEMA_VERSION = "14";
4
+ export declare const SCHEMA_VERSION = "16";
5
5
  export interface OpenResult {
6
6
  db: DatabaseSync;
7
7
  cfg: ResolvedConfig;
@@ -62,7 +62,7 @@ function _unsupported_iterable_to_array(o, minLen) {
62
62
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
63
63
  }
64
64
  var DB_FILENAME = 'cache.db';
65
- var SCHEMA_VERSION = '14';
65
+ var SCHEMA_VERSION = '16';
66
66
  // Stemming is English-only, but the segmentation underneath it is what decides coverage:
67
67
  // unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)
68
68
  // indexes a whole run as one token and word search finds nothing. `content.tokenize` is how
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/open.ts"],"sourcesContent":["// The Node floor (>=22.20) is explained here and nowhere else: 22.20 is the first release with\n// both FTS5 and row-returning INSERT ... RETURNING. Raise it only for a load-bearing capability.\nimport { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { contentTokenize, featureSignature, STATE_DIR } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { activeFeatures } from '../features/index.ts';\nimport { registerFunctions } from '../sql-functions.ts';\nimport { changedSignatureKeys, rebuildContentTable, reconcile, signatureDiff } from './reconcile.ts';\nimport { getMeta, setMeta } from './shared.ts';\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`. Bumping it rebuilds\n// existing trees on first query.\nexport const SCHEMA_VERSION = '14';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// Stemming is English-only, but the segmentation underneath it is what decides coverage:\n// unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)\n// indexes a whole run as one token and word search finds nothing. `content.tokenize` is how\n// such a tree picks trigram instead.\nconst DEFAULT_TOKENIZE = 'porter unicode61';\n\n// FTS5 takes its tokenizer as a string literal inside DDL, where nothing can bind, so a\n// configured value has to be concatenated. Probing a throwaway table is what makes that safe\n// and is also the whole validation: anything the linked SQLite accepts passes, anything else\n// fails here with SQLite's own message rather than against the real table. It means no table\n// of which version added which tokenizer has to be maintained.\nfunction resolveTokenize(db: DatabaseSync, cfg: Config): string {\n const configured = contentTokenize(cfg);\n if (configured === undefined) return DEFAULT_TOKENIZE;\n const literal = configured.replace(/'/g, \"''\");\n try {\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n db.exec(`CREATE VIRTUAL TABLE temp.sense_tokenize_probe USING fts5(x, tokenize = '${literal}')`);\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n } catch (err) {\n throw new SenseError('CONFIG_INVALID', `content.tokenize \"${configured}\" is not a tokenizer this SQLite accepts (${(err as Error).message}); the built-in choices are unicode61, ascii, porter, and trigram, each with their own options`);\n }\n return literal;\n}\n\n// The tokenizer the content table was actually built with, from its own DDL -- the one\n// record that cannot desynchronize from the table. NULL when the table does not exist yet.\nfunction storedTokenize(db: DatabaseSync): string | null {\n const row = db.prepare(`SELECT sql FROM sqlite_master WHERE name = 'content'`).get() as { sql: string } | undefined;\n if (!row) return null;\n const m = row.sql.match(/tokenize = '((?:[^']|'')*)'/);\n return m ? m[1] : null;\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter`\n// can't dump file text into context. Features add their own tables after the core ones.\nfunction ensureSchema(db: DatabaseSync, cfg: Config, tokenize: string): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_ctime\" REAL, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n // IF NOT EXISTS is safe against a tokenizer change: open() compares the table's own DDL\n // against the resolved tokenizer before this runs, so a stale table is already gone by now.\n // The three `_seg` sidecars are appended after path, never inserted: bm25(content, ...) and\n // snippet(content, 2, ...) are documented against the first three columns and keep working\n // (FTS5 defaults the weights it was not given). Each carries its field's exploded unspaced\n // runs for text that needs it, and an empty string for text that does not.\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${tokenize}')`);\n // Coverage, not ownership: a path can appear under several presets. path leads the PK so the\n // per-doc delete is an index hit -- keyed the other way, cold builds went quadratic.\n db.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n db.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) feature.schema(db);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n if (getMeta(db, 'features') === null) setMeta(db, 'features', featureSignature(cfg));\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n // Covers a concurrent watcher's bulk reconcile (~5s for 500 files at 26k notes). A query\n // that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db, contentTokenize(cfg) === undefined);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Before the rebuild branch below, never after: that branch deletes the cache, so a typo'd\n // tokenizer validated later would cost a full re-index (and re-embed) to reach its own error.\n const tokenize = resolveTokenize(db, cfg);\n\n // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an\n // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).\n const version = getMeta(db, 'schema_version');\n const features = getMeta(db, 'features');\n const wantFeatures = featureSignature(cfg);\n let tokenizeOnlyRebuild = false;\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Indexing derives from presets, so a config edit rebuilding the cache must say so and\n // name what changed -- silent rebuilds make derived indexing look like a hang or a bug.\n if (version !== null && version !== SCHEMA_VERSION) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n const changedKeys = changedSignatureKeys(features ?? '', wantFeatures);\n // Only the tokenizer moved: frontmatter, links, sections, and embeddings are file-derived\n // and tokenizer-independent, so they don't need re-deriving. Everything else -- a preset\n // edit, an embed model change -- still takes the full clear/reopen below.\n if (changedKeys.size === 1 && changedKeys.has('tokenize')) {\n console.error('sense: config change (content tokenizer) rebuilds the text index; vectors, links, and sections are kept');\n db.exec('DROP TABLE content');\n tokenizeOnlyRebuild = true;\n } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n }\n\n // Meta can lie after a crash between table creation and the signature write; the table's\n // own DDL cannot. A mismatch here rebuilds no matter what meta says. A tokenize-only rebuild\n // just dropped content above, so storedTokenize sees no table and this guard is skipped\n // naturally rather than needing its own case.\n const stored = storedTokenize(db);\n if (stored !== null && stored !== tokenize) {\n console.error('sense: cache was built with a different content tokenizer; rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n\n ensureSchema(db, cfg, tokenize);\n\n let rebuildWarnings: string[] = [];\n if (tokenizeOnlyRebuild) {\n rebuildWarnings = rebuildContentTable(db, cfg, cfg.baseDir);\n setMeta(db, 'features', wantFeatures);\n }\n\n // 3x the largest reconcile this cache has recorded, floored at 30s and capped at 10min.\n // Installed before reconcile() -- that call is the one that races a watcher's transaction.\n const recordedMaxMs = Number(getMeta(db, 'reconcile_max_ms') ?? '0');\n db.exec(`PRAGMA busy_timeout = ${Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000)}`);\n\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings: [...rebuildWarnings, ...warnings] };\n}\n\n// Deletes the cache directory, and only that. The rebuild is not this function's job: the next\n// open() reconciles, which is what running any command already does, so a verb that bundled the\n// two described the half that was not its own. Manual reset for a doubted cache; the schema and\n// config-signature mismatches below reset themselves.\nexport function clearCache(cfg: ResolvedConfig): void {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","clearCache","docCount","open","DEFAULT_TOKENIZE","resolveTokenize","db","cfg","configured","contentTokenize","undefined","literal","replace","exec","err","SenseError","message","storedTokenize","row","prepare","get","m","sql","match","ensureSchema","tokenize","activeFeatures","feature","schema","getMeta","setMeta","featureSignature","n","stateDir","join","baseDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","registerFunctions","version","features","wantFeatures","tokenizeOnlyRebuild","console","error","close","changedKeys","changedSignatureKeys","size","has","changed","signatureDiff","stored","rebuildWarnings","rebuildContentTable","recordedMaxMs","Number","Math","min","max","reconcile","parsed","warnings","rmSync","force"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db/open.ts"],"sourcesContent":["// The Node floor (>=22.20) is explained here and nowhere else: 22.20 is the first release with\n// both FTS5 and row-returning INSERT ... RETURNING. Raise it only for a load-bearing capability.\nimport { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { contentTokenize, featureSignature, STATE_DIR } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { activeFeatures } from '../features/index.ts';\nimport { registerFunctions } from '../sql-functions.ts';\nimport { changedSignatureKeys, rebuildContentTable, reconcile, signatureDiff } from './reconcile.ts';\nimport { getMeta, setMeta } from './shared.ts';\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`. Bumping it rebuilds\n// existing trees on first query.\nexport const SCHEMA_VERSION = '16';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// Stemming is English-only, but the segmentation underneath it is what decides coverage:\n// unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)\n// indexes a whole run as one token and word search finds nothing. `content.tokenize` is how\n// such a tree picks trigram instead.\nconst DEFAULT_TOKENIZE = 'porter unicode61';\n\n// FTS5 takes its tokenizer as a string literal inside DDL, where nothing can bind, so a\n// configured value has to be concatenated. Probing a throwaway table is what makes that safe\n// and is also the whole validation: anything the linked SQLite accepts passes, anything else\n// fails here with SQLite's own message rather than against the real table. It means no table\n// of which version added which tokenizer has to be maintained.\nfunction resolveTokenize(db: DatabaseSync, cfg: Config): string {\n const configured = contentTokenize(cfg);\n if (configured === undefined) return DEFAULT_TOKENIZE;\n const literal = configured.replace(/'/g, \"''\");\n try {\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n db.exec(`CREATE VIRTUAL TABLE temp.sense_tokenize_probe USING fts5(x, tokenize = '${literal}')`);\n db.exec('DROP TABLE IF EXISTS temp.sense_tokenize_probe');\n } catch (err) {\n throw new SenseError('CONFIG_INVALID', `content.tokenize \"${configured}\" is not a tokenizer this SQLite accepts (${(err as Error).message}); the built-in choices are unicode61, ascii, porter, and trigram, each with their own options`);\n }\n return literal;\n}\n\n// The tokenizer the content table was actually built with, from its own DDL -- the one\n// record that cannot desynchronize from the table. NULL when the table does not exist yet.\nfunction storedTokenize(db: DatabaseSync): string | null {\n const row = db.prepare(`SELECT sql FROM sqlite_master WHERE name = 'content'`).get() as { sql: string } | undefined;\n if (!row) return null;\n const m = row.sql.match(/tokenize = '((?:[^']|'')*)'/);\n return m ? m[1] : null;\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter`\n// can't dump file text into context. Features add their own tables after the core ones.\nfunction ensureSchema(db: DatabaseSync, cfg: Config, tokenize: string): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_ctime\" REAL, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n // IF NOT EXISTS is safe against a tokenizer change: open() compares the table's own DDL\n // against the resolved tokenizer before this runs, so a stale table is already gone by now.\n // The three `_seg` sidecars are appended after path, never inserted: bm25(content, ...) and\n // snippet(content, 2, ...) are documented against the first three columns and keep working\n // (FTS5 defaults the weights it was not given). Each carries its field's exploded unspaced\n // runs for text that needs it, and an empty string for text that does not.\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${tokenize}')`);\n // Coverage, not ownership: a path can appear under several presets. path leads the PK so the\n // per-doc delete is an index hit -- keyed the other way, cold builds went quadratic.\n db.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n db.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) feature.schema(db);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n if (getMeta(db, 'features') === null) setMeta(db, 'features', featureSignature(cfg));\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n // Covers a concurrent watcher's bulk reconcile (~5s for 500 files at 26k notes). A query\n // that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db, contentTokenize(cfg) === undefined);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Before the rebuild branch below, never after: that branch deletes the cache, so a typo'd\n // tokenizer validated later would cost a full re-index (and re-embed) to reach its own error.\n const tokenize = resolveTokenize(db, cfg);\n\n // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an\n // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).\n const version = getMeta(db, 'schema_version');\n const features = getMeta(db, 'features');\n const wantFeatures = featureSignature(cfg);\n let tokenizeOnlyRebuild = false;\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Indexing derives from presets, so a config edit rebuilding the cache must say so and\n // name what changed -- silent rebuilds make derived indexing look like a hang or a bug.\n if (version !== null && version !== SCHEMA_VERSION) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n const changedKeys = changedSignatureKeys(features ?? '', wantFeatures);\n // Only the tokenizer moved: frontmatter, links, sections, and embeddings are file-derived\n // and tokenizer-independent, so they don't need re-deriving. Everything else -- a preset\n // edit, an embed model change -- still takes the full clear/reopen below.\n if (changedKeys.size === 1 && changedKeys.has('tokenize')) {\n console.error('sense: config change (content tokenizer) rebuilds the text index; vectors, links, and sections are kept');\n db.exec('DROP TABLE content');\n tokenizeOnlyRebuild = true;\n } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n }\n\n // Meta can lie after a crash between table creation and the signature write; the table's\n // own DDL cannot. A mismatch here rebuilds no matter what meta says. A tokenize-only rebuild\n // just dropped content above, so storedTokenize sees no table and this guard is skipped\n // naturally rather than needing its own case.\n const stored = storedTokenize(db);\n if (stored !== null && stored !== tokenize) {\n console.error('sense: cache was built with a different content tokenizer; rebuilding the index');\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n\n ensureSchema(db, cfg, tokenize);\n\n let rebuildWarnings: string[] = [];\n if (tokenizeOnlyRebuild) {\n rebuildWarnings = rebuildContentTable(db, cfg, cfg.baseDir);\n setMeta(db, 'features', wantFeatures);\n }\n\n // 3x the largest reconcile this cache has recorded, floored at 30s and capped at 10min.\n // Installed before reconcile() -- that call is the one that races a watcher's transaction.\n const recordedMaxMs = Number(getMeta(db, 'reconcile_max_ms') ?? '0');\n db.exec(`PRAGMA busy_timeout = ${Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000)}`);\n\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings: [...rebuildWarnings, ...warnings] };\n}\n\n// Deletes the cache directory, and only that. The rebuild is not this function's job: the next\n// open() reconciles, which is what running any command already does, so a verb that bundled the\n// two described the half that was not its own. Manual reset for a doubted cache; the schema and\n// config-signature mismatches below reset themselves.\nexport function clearCache(cfg: ResolvedConfig): void {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","clearCache","docCount","open","DEFAULT_TOKENIZE","resolveTokenize","db","cfg","configured","contentTokenize","undefined","literal","replace","exec","err","SenseError","message","storedTokenize","row","prepare","get","m","sql","match","ensureSchema","tokenize","activeFeatures","feature","schema","getMeta","setMeta","featureSignature","n","stateDir","join","baseDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","registerFunctions","version","features","wantFeatures","tokenizeOnlyRebuild","console","error","close","changedKeys","changedSignatureKeys","size","has","changed","signatureDiff","stored","rebuildWarnings","rebuildContentTable","recordedMaxMs","Number","Math","min","max","reconcile","parsed","warnings","rmSync","force"],"mappings":"AAAA,+FAA+F;AAC/F,iGAAiG;;;;;;;;;;;;QAYpFA;eAAAA;;QAGAC;eAAAA;;QAyJGC;eAAAA;;QAzFAC;eAAAA;;QAKAC;eAAAA;;;sBAnFkB;wBACb;0BACQ;uBAEgC;wBAClC;wBACI;8BACG;2BACkD;wBACnD;;;;;;;;;;;;;;;;;;;;;;;;;;AAE1B,IAAMJ,cAAc;AAGpB,IAAMC,iBAAiB;AAU9B,yFAAyF;AACzF,2FAA2F;AAC3F,4FAA4F;AAC5F,qCAAqC;AACrC,IAAMI,mBAAmB;AAEzB,wFAAwF;AACxF,6FAA6F;AAC7F,6FAA6F;AAC7F,6FAA6F;AAC7F,+DAA+D;AAC/D,SAASC,gBAAgBC,EAAgB,EAAEC,GAAW;IACpD,IAAMC,aAAaC,IAAAA,wBAAe,EAACF;IACnC,IAAIC,eAAeE,WAAW,OAAON;IACrC,IAAMO,UAAUH,WAAWI,OAAO,CAAC,MAAM;IACzC,IAAI;QACFN,GAAGO,IAAI,CAAC;QACRP,GAAGO,IAAI,CAAC,AAAC,4EAAmF,OAARF,SAAQ;QAC5FL,GAAGO,IAAI,CAAC;IACV,EAAE,OAAOC,KAAK;QACZ,MAAM,IAAIC,oBAAU,CAAC,kBAAkB,AAAC,qBAA2E,OAAvDP,YAAW,8CAAmE,OAAvB,AAACM,IAAcE,OAAO,EAAC;IAC5I;IACA,OAAOL;AACT;AAEA,uFAAuF;AACvF,2FAA2F;AAC3F,SAASM,eAAeX,EAAgB;IACtC,IAAMY,MAAMZ,GAAGa,OAAO,CAAC,wDAAwDC,GAAG;IAClF,IAAI,CAACF,KAAK,OAAO;IACjB,IAAMG,IAAIH,IAAII,GAAG,CAACC,KAAK,CAAC;IACxB,OAAOF,IAAIA,CAAC,CAAC,EAAE,GAAG;AACpB;AAEA,2FAA2F;AAC3F,wFAAwF;AACxF,SAASG,aAAalB,EAAgB,EAAEC,GAAW,EAAEkB,QAAgB;IACnEnB,GAAGO,IAAI,CAAC;IACR,wFAAwF;IACxF,4FAA4F;IAC5F,4FAA4F;IAC5F,2FAA2F;IAC3F,2FAA2F;IAC3F,2EAA2E;IAC3EP,GAAGO,IAAI,CAAC,AAAC,6IAAqJ,OAATY,UAAS;IAC9J,6FAA6F;IAC7F,qFAAqF;IACrFnB,GAAGO,IAAI,CAAC;IACRP,GAAGO,IAAI,CAAC;QACH,kCAAA,2BAAA;;QAAL,QAAK,YAAiBa,IAAAA,wBAAc,EAACnB,yBAAhC,SAAA,6BAAA,QAAA,yBAAA;YAAA,IAAMoB,UAAN;YAAsCA,QAAQC,MAAM,CAACtB;;;QAArD;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IACL,IAAIuB,IAAAA,iBAAO,EAACvB,IAAI,sBAAsB,MAAMwB,IAAAA,iBAAO,EAACxB,IAAI,kBAAkBN;IAC1E,IAAI6B,IAAAA,iBAAO,EAACvB,IAAI,gBAAgB,MAAMwB,IAAAA,iBAAO,EAACxB,IAAI,YAAYyB,IAAAA,yBAAgB,EAACxB;AACjF;AAEO,SAASL,SAASI,EAAgB;IACvC,IAAMY,MAAMZ,GAAGa,OAAO,CAAC,yCAAyCC,GAAG;IACnE,OAAOF,IAAIc,CAAC;AACd;AAEO,SAAS7B,KAAKI,GAAmB;QAwETsB;IAvE7B,IAAMI,WAAWC,IAAAA,cAAI,EAAC3B,IAAI4B,OAAO,EAAEC,kBAAS;IAC5CC,IAAAA,iBAAS,EAACJ,UAAU;QAAEK,WAAW;IAAK;IACtC,IAAMC,SAASL,IAAAA,cAAI,EAACD,UAAUlC;IAE9B,IAAMO,KAAK,IAAIkC,wBAAY,CAACD;IAC5BjC,GAAGO,IAAI,CAAC;IACR,yFAAyF;IACzF,uCAAuC;IACvCP,GAAGO,IAAI,CAAC;IACR4B,IAAAA,iCAAiB,EAACnC,IAAIG,IAAAA,wBAAe,EAACF,SAASG;IAE/CJ,GAAGO,IAAI,CAAC;IAER,2FAA2F;IAC3F,8FAA8F;IAC9F,IAAMY,WAAWpB,gBAAgBC,IAAIC;IAErC,uFAAuF;IACvF,qGAAqG;IACrG,IAAMmC,UAAUb,IAAAA,iBAAO,EAACvB,IAAI;IAC5B,IAAMqC,WAAWd,IAAAA,iBAAO,EAACvB,IAAI;IAC7B,IAAMsC,eAAeb,IAAAA,yBAAgB,EAACxB;IACtC,IAAIsC,sBAAsB;IAC1B,IAAI,AAACH,YAAY,QAAQA,YAAY1C,kBAAoB2C,aAAa,QAAQA,aAAaC,cAAe;QACxG,uFAAuF;QACvF,wFAAwF;QACxF,IAAIF,YAAY,QAAQA,YAAY1C,gBAAgB;YAClD8C,QAAQC,KAAK,CAAC;YACdzC,GAAG0C,KAAK;YACR/C,WAAWM;YACX,OAAOJ,KAAKI;QACd;QACA,IAAM0C,cAAcC,IAAAA,iCAAoB,EAACP,qBAAAA,sBAAAA,WAAY,IAAIC;QACzD,0FAA0F;QAC1F,yFAAyF;QACzF,0EAA0E;QAC1E,IAAIK,YAAYE,IAAI,KAAK,KAAKF,YAAYG,GAAG,CAAC,aAAa;YACzDN,QAAQC,KAAK,CAAC;YACdzC,GAAGO,IAAI,CAAC;YACRgC,sBAAsB;QACxB,OAAO;YACL,IAAMQ,UAAUC,IAAAA,0BAAa,EAACX,qBAAAA,sBAAAA,WAAY,IAAIC;YAC9CE,QAAQC,KAAK,CAAC,AAAC,yBAAgC,OAARM,SAAQ;YAC/C/C,GAAG0C,KAAK;YACR/C,WAAWM;YACX,OAAOJ,KAAKI;QACd;IACF;IAEA,yFAAyF;IACzF,6FAA6F;IAC7F,wFAAwF;IACxF,8CAA8C;IAC9C,IAAMgD,SAAStC,eAAeX;IAC9B,IAAIiD,WAAW,QAAQA,WAAW9B,UAAU;QAC1CqB,QAAQC,KAAK,CAAC;QACdzC,GAAG0C,KAAK;QACR/C,WAAWM;QACX,OAAOJ,KAAKI;IACd;IAEAiB,aAAalB,IAAIC,KAAKkB;IAEtB,IAAI+B,kBAA4B,EAAE;IAClC,IAAIX,qBAAqB;QACvBW,kBAAkBC,IAAAA,gCAAmB,EAACnD,IAAIC,KAAKA,IAAI4B,OAAO;QAC1DL,IAAAA,iBAAO,EAACxB,IAAI,YAAYsC;IAC1B;IAEA,wFAAwF;IACxF,2FAA2F;IAC3F,IAAMc,gBAAgBC,QAAO9B,WAAAA,IAAAA,iBAAO,EAACvB,IAAI,iCAAZuB,sBAAAA,WAAmC;IAChEvB,GAAGO,IAAI,CAAC,AAAC,yBAA8E,OAAtD+C,KAAKC,GAAG,CAACD,KAAKE,GAAG,CAAC,OAAO,IAAIJ,gBAAgB;IAE9E,IAA6BK,aAAAA,IAAAA,sBAAS,EAACzD,IAAIC,KAAKA,IAAI4B,OAAO,GAAnD6B,SAAqBD,WAArBC,QAAQC,WAAaF,WAAbE;IAEhB,OAAO;QAAE3D,IAAAA;QAAIC,KAAAA;QAAKgC,QAAAA;QAAQyB,QAAAA;QAAQC,UAAU,AAAC,qBAAGT,wBAAiB,qBAAGS;IAAU;AAChF;AAMO,SAAShE,WAAWM,GAAmB;IAC5C2D,IAAAA,cAAM,EAAChC,IAAAA,cAAI,EAAC3B,IAAI4B,OAAO,EAAEC,kBAAS,GAAG;QAAEE,WAAW;QAAM6B,OAAO;IAAK;AACtE"}
@@ -48,6 +48,7 @@ var _nodeos = require("node:os");
48
48
  var _nodepath = require("node:path");
49
49
  var _indexts = require("../config/index.js");
50
50
  var _errorsts = require("../errors.js");
51
+ var _fencests = require("../fences.js");
51
52
  var _progressts = require("../progress.js");
52
53
  var _scants = require("../scan.js");
53
54
  function _array_like_to_array(arr, len) {
@@ -311,13 +312,10 @@ function chunksOf(body, search) {
311
312
  var offset = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : 0;
312
313
  var lines = body.split('\n');
313
314
  var starts = [];
314
- var inFence = false;
315
+ var fence = (0, _fencests.fenceTracker)();
315
316
  for(var i = 0; i < lines.length; i++){
316
- if (/^(```|~~~)/.test(lines[i])) {
317
- inFence = !inFence;
318
- continue;
319
- }
320
- if (!inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);
317
+ if (fence.feed(lines[i])) continue;
318
+ if (!fence.inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);
321
319
  }
322
320
  var bounds = starts.length === 0 ? [
323
321
  1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { progress } from '../progress.ts';\nimport { parseFile } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// Heading-based chunks, int8 vectors with a per-vector scale, NULL vector = not yet embedded.\n// Reconcile writes dirty rows; embedding tops up on the next search, so staleness costs recall.\n\n// Storage lever fixed by the bake-off (BENCHMARKING.md): int8 at 256 dims is\n// quality-free vs f32-512 when fused. Queries stay f32 at the same dims.\nconst STORE_DIMS = 256;\n// Seed chunks that participate in a `related` scan; see similarNotes for the measurement.\nconst TARGET_CHUNK_CAP = 16;\nconst BATCH = 64;\n\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\ninterface Chunk {\n startLine: number;\n endLine: number;\n text: string;\n}\n\n// Deterministic, so embed time can re-derive text from stored line ranges. Heading-delimited,\n// preamble kept, whole body when no headings; the title/summary prefix mirrors bm25 weighting.\n//\n// Chunks the body, not the raw file, with `offset` shifting line numbers back onto the raw\n// file so a range stays a direct Read range (sections is 1-indexed over raw too). Chunking raw\n// put the frontmatter block in its own leading chunk on every note with a heading, which made\n// `lines` point at YAML and made frontmatter-only notes near-identical to each other. It also\n// disagreed with FTS, which indexes the body alone.\nfunction chunksOf(body: string, search?: { title: string; summary: string }, offset = 0): Chunk[] {\n const lines = body.split('\\n');\n const starts: number[] = [];\n let inFence = false;\n for (let i = 0; i < lines.length; i++) {\n if (/^(```|~~~)/.test(lines[i])) {\n inFence = !inFence;\n continue;\n }\n if (!inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);\n }\n const bounds = starts.length === 0 ? [1] : starts[0] > 1 ? [1, ...starts] : starts;\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n const chunks: Chunk[] = [];\n bounds.forEach((start, i) => {\n const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;\n const text = lines\n .slice(start - 1, end)\n .join('\\n')\n .trim();\n // A body with nothing in it yields no chunks at all, so a frontmatter-only note has no\n // vectors rather than a vector of its own YAML.\n if (text.length > 0) chunks.push({ startLine: start + offset, endLine: end + offset, text: prefix ? `${prefix}\\n${text}` : text });\n });\n return chunks;\n}\n\nexport const embed: Feature = {\n name: 'embed',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n return chunksOf(body, search, raw.split('\\n').length - body.split('\\n').length);\n },\n remove(db, path) {\n db.prepare('DELETE FROM embeddings WHERE \"path\" = ?').run(path);\n },\n // A tree with no embedding model never had extract() run for the doc (db.ts's per-file\n // filter skips it), so extracted is undefined here -- store nothing, i.e. no rows.\n store(db, path, extracted) {\n if (!extracted) return;\n const insert = db.prepare('INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)');\n (extracted as Chunk[]).forEach((c, idx) => insert.run(path, idx, c.startLine, c.endLine));\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n};\n\n// --- static type: Model2Vec safetensors + pure-JS tokenizer, model cached in ~/.cache ---\n// Encode convention from model2vec/model.py: no special tokens, drop unk ids, mean-pool,\n// L2-normalize.\n\nconst MODEL_FILES = ['model.safetensors', 'tokenizer.json'];\n// The pair, for messages that name what a local model directory must contain.\nexport const MODEL_FILENAMES = MODEL_FILES.join(' and ');\n\n// A Hugging Face repo id: one slash, HF's charset. Anything else is a path, used as one rather\n// than flattened into a cache key Windows would reject.\nconst HF_MODEL_ID = /^[A-Za-z0-9][\\w.-]*\\/[A-Za-z0-9][\\w.-]*$/;\n\n// Downloadable iff it names a Hugging Face repo; a local path is the caller's to populate.\nexport function isDownloadable(model: string): boolean {\n return HF_MODEL_ID.test(model);\n}\n\n// Machine-wide, not per-tree: `.sense/` is the index a rebuild throws away, while a 124 MB\n// model is shared by every tree on the machine. Honors XDG_CACHE_HOME where it is set;\n// elsewhere ~/.cache, which is also where Hugging Face's own libraries keep theirs.\nfunction cacheRoot(): string {\n const xdg = process.env.XDG_CACHE_HOME;\n return join(xdg && xdg.length > 0 ? xdg : join(homedir(), '.cache'), 'sensemaking', 'models');\n}\n\n// A model is either a local directory the caller pointed at, or the cache `sense download`\n// fills. Nothing here fetches: an absent model degrades search to its other signals rather\n// than pulling 124 MB out of a command that reads like a query.\nexport function modelDir(model: string): string {\n if (!HF_MODEL_ID.test(model)) return model;\n return join(cacheRoot(), model.replace(/\\//g, '--'));\n}\n\n// Both files, so an interrupted download reads as absent and the next one resumes it.\nexport function hasModelFiles(model: string): boolean {\n const dir = modelDir(model);\n return MODEL_FILES.every((file) => existsSync(join(dir, file)));\n}\n\n// api providers have nothing to download, so they are never \"missing\" here; an unreachable\n// endpoint surfaces as an EMBED_MODEL error at call time instead.\nexport function modelPresent(cfg: Config): boolean {\n const e = embedConfig(cfg);\n if (!e) return false;\n return e.type === 'api' || hasModelFiles(e.model);\n}\n\nfunction fetchToFile(url: string, dest: string): Promise<void> {\n return fetch(url).then(async (res) => {\n if (!res.ok) throw new SenseError('EMBED_MODEL', `model download failed: ${url} -> HTTP ${res.status}`);\n writeFileSync(`${dest}.part`, Buffer.from(await res.arrayBuffer()));\n renameSync(`${dest}.part`, dest);\n });\n}\n\n// The only code path that touches the network for weights. `sense download` calls it; no\n// query ever does. Idempotent: a file already on disk is left alone.\nexport async function downloadModel(model: string, onFile?: (file: string, dir: string) => void): Promise<string> {\n if (!isDownloadable(model)) {\n throw new SenseError('EMBED_MODEL', `embed.model \"${model}\" is a local path, not a Hugging Face model id, so there is nothing to download; put model.safetensors and tokenizer.json in that directory, or name a model id like \"minishlab/potion-retrieval-32M\"`);\n }\n const dir = modelDir(model);\n mkdirSync(dir, { recursive: true });\n for (const file of MODEL_FILES) {\n if (existsSync(join(dir, file))) continue;\n onFile?.(file, dir);\n await fetchToFile(`https://huggingface.co/${model}/resolve/main/${file}`, join(dir, file));\n }\n return dir;\n}\n\nasync function staticProvider(model: string): Promise<EmbedProvider> {\n const dir = modelDir(model);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n // A local path the caller controls gets told what is missing where; only a repo id can\n // be fixed by downloading.\n const fix = isDownloadable(model) ? 'run `sense download`' : `expected ${MODEL_FILENAMES} in that directory`;\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); ${fix}`);\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n // Lazy import: the tokenizer loads only on the semantic path, never at CLI startup.\n const { Tokenizer } = await import('@huggingface/tokenizers');\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n // The tokenizer yields undefined (not the unk id) for tokens outside the vocab; an\n // undefined id would index the matrix at NaN and poison the whole mean-pool -- and the\n // int8 conversion then stores the NaN vector as all zeros, silently. Keep integers only.\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => Number.isInteger(id) && id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n return { id: `static:${model}`, dims, embed: async (texts) => texts.map(one) };\n}\n\n// --- api type: one POST against any OpenAI-compatible /embeddings endpoint ---\n\nasync function apiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!url) throw new SenseError('EMBED_MODEL', 'features.embed.type \"api\" requires a url');\n const base = url.replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[]): Promise<Float32Array[]> {\n const res = await fetch(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);\n const body = (await res.json()) as { data: Array<{ embedding: number[] }> };\n return body.data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n return { id: `api:${base}:${model}`, dims, embed: post };\n}\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nfunction getProvider(cfg: Config): Promise<EmbedProvider> {\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an \"embed\" block naming one to sense.config.json, then run `sense download`');\n const sig = `${e.type}:${e.model}:${e.url ?? ''}`;\n let p = providers.get(sig);\n if (!p) {\n p = e.type === 'api' ? apiProvider(e.model, e.url, e.key) : staticProvider(e.model);\n providers.set(sig, p);\n }\n return p;\n}\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage.\nfunction toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = db.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk').all() as Array<{ path: string; chunk: number }>;\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n // presets/embed are irrelevant here -- re-deriving chunk text for a doc that already\n // has embeddings rows means the tree had an embedding model at reconcile time.\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, ctimeMs: 0, size: 0, presets: [], embed: true }, [embed]).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE \"path\" = ? AND chunk = ?');\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += BATCH) {\n const batch = jobs.slice(i, i + BATCH);\n const vectors = await provider.embed(batch.map((j) => j.text));\n db.exec('BEGIN');\n try {\n batch.forEach((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);\n });\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n report.tick(Math.min(i + BATCH, jobs.length));\n }\n report.finish();\n}\n\n// Dequantised int8 dot products land a little either side of a true cosine, so an identical\n// pair prints 1.001 and undermines a column whose whole job is being a bounded number.\nfunction asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality,\n// and nearest-neighbour search always returns a neighbour however far away.\nexport async function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<Array<{ path: string; lines: string; similarity: number }>> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(db, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore((await provider.embed([text]))[0], storeDims, false);\n\n const rows = db.prepare('SELECT \"path\", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{\n path: string;\n start_line: number;\n end_line: number;\n scale: number;\n vector: Uint8Array;\n }>;\n\n const best = new Map<string, { score: number; lines: string }>();\n for (const row of rows) {\n if (allowed && !allowed.has(row.path)) continue;\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));\n let dot = 0;\n for (let d = 0; d < q.length; d++) dot += q[d] * qv[d];\n const score = dot * row.scale;\n const existing = best.get(row.path);\n if (!existing || score > existing.score) best.set(row.path, { score, lines: `L${row.start_line}-${row.end_line}` });\n }\n return [...best.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .slice(0, fetch)\n .map(([path, b]) => ({ path, lines: b.lines, similarity: asCosine(b.score) }));\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport function hasEmbedding(db: DatabaseSync, path: string): boolean {\n const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1').get(path) as { ok: number } | undefined;\n return row !== undefined;\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors. Reads only what is stored, so it stays sync.\nexport function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Array<{ path: string; similarity: number }> {\n // Cost is target_chunks x stored_chunks, so a heading-dense seed multiplies a full-corpus\n // scan (12.7s at 201 chunks/note). Sample evenly, so late sections still get a vote.\n const targetRows = db.prepare('SELECT scale, vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk').all(path) as Array<{ scale: number; vector: Uint8Array }>;\n if (targetRows.length === 0) return [];\n const step = Math.max(1, Math.ceil(targetRows.length / TARGET_CHUNK_CAP));\n const target = targetRows.filter((_, i) => i % step === 0).map((row) => ({ v: new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength), scale: row.scale }));\n\n const rows = db.prepare('SELECT \"path\", scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{ path: string; scale: number; vector: Uint8Array }>;\n const best = new Map<string, number>();\n for (const row of rows) {\n if (row.path === path || opts.exclude.has(row.path) || (opts.allowed && !opts.allowed.has(row.path))) continue;\n const other = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n for (const t of target) {\n let dot = 0;\n const len = Math.min(t.v.length, other.length);\n for (let d = 0; d < len; d++) dot += t.v[d] * other[d];\n const score = dot * t.scale * row.scale;\n const existing = best.get(row.path);\n if (existing === undefined || score > existing) best.set(row.path, score);\n }\n }\n\n return [...best.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, opts.k)\n .map(([p, score]) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["MODEL_FILENAMES","downloadModel","embed","embedPending","hasEmbedding","hasModelFiles","isDownloadable","modelDir","modelPresent","semanticCandidates","similarNotes","STORE_DIMS","TARGET_CHUNK_CAP","BATCH","chunksOf","body","search","offset","lines","split","starts","inFence","i","length","test","push","bounds","prefix","title","summary","filter","Boolean","join","chunks","forEach","start","end","text","slice","trim","startLine","endLine","name","schema","db","exec","extract","raw","remove","path","prepare","run","store","extracted","insert","c","idx","enabledForFile","_cfg","file","MODEL_FILES","HF_MODEL_ID","model","cacheRoot","xdg","process","env","XDG_CACHE_HOME","homedir","replace","dir","every","existsSync","cfg","e","embedConfig","type","fetchToFile","url","dest","fetch","then","res","ok","SenseError","status","Buffer","from","arrayBuffer","writeFileSync","renameSync","onFile","mkdirSync","recursive","staticProvider","tokenizerJson","fix","headerLen","header","entry","spec","dims","dataStart","matrix","Tokenizer","tok","unkId","one","ids","encode","add_special_tokens","id","Number","isInteger","v","Float32Array","off","d","norm","Math","sqrt","readFileSync","readBigUInt64LE","JSON","parse","subarray","toString","Object","entries","find","k","dtype","shape","byteOffset","data_offsets","buffer","vocab","unk_token","texts","map","apiProvider","keyEnv","base","headers","key","post","method","stringify","input","json","data","embedding","undefined","authorization","providers","Map","getProvider","sig","p","get","set","toStore","full","int8","scale","max","abs","baseDir","provider","dirty","storeDims","byPath","row","list","jobs","chunkIdxs","update","report","batch","vectors","j","job","q","Int8Array","round","chunk","err","tick","min","all","parseFile","relPath","absPath","mtimeMs","ctimeMs","size","presets","doc","progress","finish","asCosine","score","terms","allowed","qv","rows","best","dot","existing","match","t","includes","has","vector","byteLength","start_line","end_line","sort","a","b","similarity","opts","targetRows","step","ceil","target","_","exclude","other","len"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { fenceTracker } from '../fences.ts';\nimport { progress } from '../progress.ts';\nimport { parseFile } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// Heading-based chunks, int8 vectors with a per-vector scale, NULL vector = not yet embedded.\n// Reconcile writes dirty rows; embedding tops up on the next search, so staleness costs recall.\n\n// Storage lever fixed by the bake-off (BENCHMARKING.md): int8 at 256 dims is\n// quality-free vs f32-512 when fused. Queries stay f32 at the same dims.\nconst STORE_DIMS = 256;\n// Seed chunks that participate in a `related` scan; see similarNotes for the measurement.\nconst TARGET_CHUNK_CAP = 16;\nconst BATCH = 64;\n\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\ninterface Chunk {\n startLine: number;\n endLine: number;\n text: string;\n}\n\n// Deterministic, so embed time can re-derive text from stored line ranges. Heading-delimited,\n// preamble kept, whole body when no headings; the title/summary prefix mirrors bm25 weighting.\n//\n// Chunks the body, not the raw file, with `offset` shifting line numbers back onto the raw\n// file so a range stays a direct Read range (sections is 1-indexed over raw too). Chunking raw\n// put the frontmatter block in its own leading chunk on every note with a heading, which made\n// `lines` point at YAML and made frontmatter-only notes near-identical to each other. It also\n// disagreed with FTS, which indexes the body alone.\nfunction chunksOf(body: string, search?: { title: string; summary: string }, offset = 0): Chunk[] {\n const lines = body.split('\\n');\n const starts: number[] = [];\n const fence = fenceTracker();\n for (let i = 0; i < lines.length; i++) {\n if (fence.feed(lines[i])) continue;\n if (!fence.inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);\n }\n const bounds = starts.length === 0 ? [1] : starts[0] > 1 ? [1, ...starts] : starts;\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n const chunks: Chunk[] = [];\n bounds.forEach((start, i) => {\n const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;\n const text = lines\n .slice(start - 1, end)\n .join('\\n')\n .trim();\n // A body with nothing in it yields no chunks at all, so a frontmatter-only note has no\n // vectors rather than a vector of its own YAML.\n if (text.length > 0) chunks.push({ startLine: start + offset, endLine: end + offset, text: prefix ? `${prefix}\\n${text}` : text });\n });\n return chunks;\n}\n\nexport const embed: Feature = {\n name: 'embed',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n return chunksOf(body, search, raw.split('\\n').length - body.split('\\n').length);\n },\n remove(db, path) {\n db.prepare('DELETE FROM embeddings WHERE \"path\" = ?').run(path);\n },\n // A tree with no embedding model never had extract() run for the doc (db.ts's per-file\n // filter skips it), so extracted is undefined here -- store nothing, i.e. no rows.\n store(db, path, extracted) {\n if (!extracted) return;\n const insert = db.prepare('INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)');\n (extracted as Chunk[]).forEach((c, idx) => insert.run(path, idx, c.startLine, c.endLine));\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n};\n\n// --- static type: Model2Vec safetensors + pure-JS tokenizer, model cached in ~/.cache ---\n// Encode convention from model2vec/model.py: no special tokens, drop unk ids, mean-pool,\n// L2-normalize.\n\nconst MODEL_FILES = ['model.safetensors', 'tokenizer.json'];\n// The pair, for messages that name what a local model directory must contain.\nexport const MODEL_FILENAMES = MODEL_FILES.join(' and ');\n\n// A Hugging Face repo id: one slash, HF's charset. Anything else is a path, used as one rather\n// than flattened into a cache key Windows would reject.\nconst HF_MODEL_ID = /^[A-Za-z0-9][\\w.-]*\\/[A-Za-z0-9][\\w.-]*$/;\n\n// Downloadable iff it names a Hugging Face repo; a local path is the caller's to populate.\nexport function isDownloadable(model: string): boolean {\n return HF_MODEL_ID.test(model);\n}\n\n// Machine-wide, not per-tree: `.sense/` is the index a rebuild throws away, while a 124 MB\n// model is shared by every tree on the machine. Honors XDG_CACHE_HOME where it is set;\n// elsewhere ~/.cache, which is also where Hugging Face's own libraries keep theirs.\nfunction cacheRoot(): string {\n const xdg = process.env.XDG_CACHE_HOME;\n return join(xdg && xdg.length > 0 ? xdg : join(homedir(), '.cache'), 'sensemaking', 'models');\n}\n\n// A model is either a local directory the caller pointed at, or the cache `sense download`\n// fills. Nothing here fetches: an absent model degrades search to its other signals rather\n// than pulling 124 MB out of a command that reads like a query.\nexport function modelDir(model: string): string {\n if (!HF_MODEL_ID.test(model)) return model;\n return join(cacheRoot(), model.replace(/\\//g, '--'));\n}\n\n// Both files, so an interrupted download reads as absent and the next one resumes it.\nexport function hasModelFiles(model: string): boolean {\n const dir = modelDir(model);\n return MODEL_FILES.every((file) => existsSync(join(dir, file)));\n}\n\n// api providers have nothing to download, so they are never \"missing\" here; an unreachable\n// endpoint surfaces as an EMBED_MODEL error at call time instead.\nexport function modelPresent(cfg: Config): boolean {\n const e = embedConfig(cfg);\n if (!e) return false;\n return e.type === 'api' || hasModelFiles(e.model);\n}\n\nfunction fetchToFile(url: string, dest: string): Promise<void> {\n return fetch(url).then(async (res) => {\n if (!res.ok) throw new SenseError('EMBED_MODEL', `model download failed: ${url} -> HTTP ${res.status}`);\n writeFileSync(`${dest}.part`, Buffer.from(await res.arrayBuffer()));\n renameSync(`${dest}.part`, dest);\n });\n}\n\n// The only code path that touches the network for weights. `sense download` calls it; no\n// query ever does. Idempotent: a file already on disk is left alone.\nexport async function downloadModel(model: string, onFile?: (file: string, dir: string) => void): Promise<string> {\n if (!isDownloadable(model)) {\n throw new SenseError('EMBED_MODEL', `embed.model \"${model}\" is a local path, not a Hugging Face model id, so there is nothing to download; put model.safetensors and tokenizer.json in that directory, or name a model id like \"minishlab/potion-retrieval-32M\"`);\n }\n const dir = modelDir(model);\n mkdirSync(dir, { recursive: true });\n for (const file of MODEL_FILES) {\n if (existsSync(join(dir, file))) continue;\n onFile?.(file, dir);\n await fetchToFile(`https://huggingface.co/${model}/resolve/main/${file}`, join(dir, file));\n }\n return dir;\n}\n\nasync function staticProvider(model: string): Promise<EmbedProvider> {\n const dir = modelDir(model);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n // A local path the caller controls gets told what is missing where; only a repo id can\n // be fixed by downloading.\n const fix = isDownloadable(model) ? 'run `sense download`' : `expected ${MODEL_FILENAMES} in that directory`;\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); ${fix}`);\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n // Lazy import: the tokenizer loads only on the semantic path, never at CLI startup.\n const { Tokenizer } = await import('@huggingface/tokenizers');\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n // The tokenizer yields undefined (not the unk id) for tokens outside the vocab; an\n // undefined id would index the matrix at NaN and poison the whole mean-pool -- and the\n // int8 conversion then stores the NaN vector as all zeros, silently. Keep integers only.\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => Number.isInteger(id) && id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n return { id: `static:${model}`, dims, embed: async (texts) => texts.map(one) };\n}\n\n// --- api type: one POST against any OpenAI-compatible /embeddings endpoint ---\n\nasync function apiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!url) throw new SenseError('EMBED_MODEL', 'features.embed.type \"api\" requires a url');\n const base = url.replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[]): Promise<Float32Array[]> {\n const res = await fetch(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);\n const body = (await res.json()) as { data: Array<{ embedding: number[] }> };\n return body.data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n return { id: `api:${base}:${model}`, dims, embed: post };\n}\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nfunction getProvider(cfg: Config): Promise<EmbedProvider> {\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an \"embed\" block naming one to sense.config.json, then run `sense download`');\n const sig = `${e.type}:${e.model}:${e.url ?? ''}`;\n let p = providers.get(sig);\n if (!p) {\n p = e.type === 'api' ? apiProvider(e.model, e.url, e.key) : staticProvider(e.model);\n providers.set(sig, p);\n }\n return p;\n}\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage.\nfunction toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = db.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk').all() as Array<{ path: string; chunk: number }>;\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n // presets/embed are irrelevant here -- re-deriving chunk text for a doc that already\n // has embeddings rows means the tree had an embedding model at reconcile time.\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, ctimeMs: 0, size: 0, presets: [], embed: true }, [embed]).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE \"path\" = ? AND chunk = ?');\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += BATCH) {\n const batch = jobs.slice(i, i + BATCH);\n const vectors = await provider.embed(batch.map((j) => j.text));\n db.exec('BEGIN');\n try {\n batch.forEach((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);\n });\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n report.tick(Math.min(i + BATCH, jobs.length));\n }\n report.finish();\n}\n\n// Dequantised int8 dot products land a little either side of a true cosine, so an identical\n// pair prints 1.001 and undermines a column whose whole job is being a bounded number.\nfunction asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality,\n// and nearest-neighbour search always returns a neighbour however far away.\nexport async function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<Array<{ path: string; lines: string; similarity: number }>> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(db, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore((await provider.embed([text]))[0], storeDims, false);\n\n const rows = db.prepare('SELECT \"path\", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{\n path: string;\n start_line: number;\n end_line: number;\n scale: number;\n vector: Uint8Array;\n }>;\n\n const best = new Map<string, { score: number; lines: string }>();\n for (const row of rows) {\n if (allowed && !allowed.has(row.path)) continue;\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));\n let dot = 0;\n for (let d = 0; d < q.length; d++) dot += q[d] * qv[d];\n const score = dot * row.scale;\n const existing = best.get(row.path);\n if (!existing || score > existing.score) best.set(row.path, { score, lines: `L${row.start_line}-${row.end_line}` });\n }\n return [...best.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .slice(0, fetch)\n .map(([path, b]) => ({ path, lines: b.lines, similarity: asCosine(b.score) }));\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport function hasEmbedding(db: DatabaseSync, path: string): boolean {\n const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1').get(path) as { ok: number } | undefined;\n return row !== undefined;\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors. Reads only what is stored, so it stays sync.\nexport function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Array<{ path: string; similarity: number }> {\n // Cost is target_chunks x stored_chunks, so a heading-dense seed multiplies a full-corpus\n // scan (12.7s at 201 chunks/note). Sample evenly, so late sections still get a vote.\n const targetRows = db.prepare('SELECT scale, vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk').all(path) as Array<{ scale: number; vector: Uint8Array }>;\n if (targetRows.length === 0) return [];\n const step = Math.max(1, Math.ceil(targetRows.length / TARGET_CHUNK_CAP));\n const target = targetRows.filter((_, i) => i % step === 0).map((row) => ({ v: new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength), scale: row.scale }));\n\n const rows = db.prepare('SELECT \"path\", scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{ path: string; scale: number; vector: Uint8Array }>;\n const best = new Map<string, number>();\n for (const row of rows) {\n if (row.path === path || opts.exclude.has(row.path) || (opts.allowed && !opts.allowed.has(row.path))) continue;\n const other = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n for (const t of target) {\n let dot = 0;\n const len = Math.min(t.v.length, other.length);\n for (let d = 0; d < len; d++) dot += t.v[d] * other[d];\n const score = dot * t.scale * row.scale;\n const existing = best.get(row.path);\n if (existing === undefined || score > existing) best.set(row.path, score);\n }\n }\n\n return [...best.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, opts.k)\n .map(([p, score]) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["MODEL_FILENAMES","downloadModel","embed","embedPending","hasEmbedding","hasModelFiles","isDownloadable","modelDir","modelPresent","semanticCandidates","similarNotes","STORE_DIMS","TARGET_CHUNK_CAP","BATCH","chunksOf","body","search","offset","lines","split","starts","fence","fenceTracker","i","length","feed","inFence","test","push","bounds","prefix","title","summary","filter","Boolean","join","chunks","forEach","start","end","text","slice","trim","startLine","endLine","name","schema","db","exec","extract","raw","remove","path","prepare","run","store","extracted","insert","c","idx","enabledForFile","_cfg","file","MODEL_FILES","HF_MODEL_ID","model","cacheRoot","xdg","process","env","XDG_CACHE_HOME","homedir","replace","dir","every","existsSync","cfg","e","embedConfig","type","fetchToFile","url","dest","fetch","then","res","ok","SenseError","status","Buffer","from","arrayBuffer","writeFileSync","renameSync","onFile","mkdirSync","recursive","staticProvider","tokenizerJson","fix","headerLen","header","entry","spec","dims","dataStart","matrix","Tokenizer","tok","unkId","one","ids","encode","add_special_tokens","id","Number","isInteger","v","Float32Array","off","d","norm","Math","sqrt","readFileSync","readBigUInt64LE","JSON","parse","subarray","toString","Object","entries","find","k","dtype","shape","byteOffset","data_offsets","buffer","vocab","unk_token","texts","map","apiProvider","keyEnv","base","headers","key","post","method","stringify","input","json","data","embedding","undefined","authorization","providers","Map","getProvider","sig","p","get","set","toStore","full","int8","scale","max","abs","baseDir","provider","dirty","storeDims","byPath","row","list","jobs","chunkIdxs","update","report","batch","vectors","j","job","q","Int8Array","round","chunk","err","tick","min","all","parseFile","relPath","absPath","mtimeMs","ctimeMs","size","presets","doc","progress","finish","asCosine","score","terms","allowed","qv","rows","best","dot","existing","match","t","includes","has","vector","byteLength","start_line","end_line","sort","a","b","similarity","opts","targetRows","step","ceil","target","_","exclude","other","len"],"mappings":";;;;;;;;;;;QAgGaA;eAAAA;;QAmDSC;eAAAA;;QAjFTC;eAAAA;;QAmMSC;eAAAA;;QAgGNC;eAAAA;;QAzOAC;eAAAA;;QArBAC;eAAAA;;QAeAC;eAAAA;;QAaAC;eAAAA;;QA8LMC;eAAAA;;QA2CNC;eAAAA;;;sBA5W+D;sBACvD;wBACH;uBAGO;wBACD;wBACE;0BACJ;sBACC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG1B,8FAA8F;AAC9F,gGAAgG;AAEhG,6EAA6E;AAC7E,yEAAyE;AACzE,IAAMC,aAAa;AACnB,0FAA0F;AAC1F,IAAMC,mBAAmB;AACzB,IAAMC,QAAQ;AAcd,8FAA8F;AAC9F,+FAA+F;AAC/F,EAAE;AACF,2FAA2F;AAC3F,+FAA+F;AAC/F,8FAA8F;AAC9F,8FAA8F;AAC9F,oDAAoD;AACpD,SAASC,SAASC,IAAY,EAAEC,MAA2C;QAAEC,SAAAA,iEAAS;IACpF,IAAMC,QAAQH,KAAKI,KAAK,CAAC;IACzB,IAAMC,SAAmB,EAAE;IAC3B,IAAMC,QAAQC,IAAAA,sBAAY;IAC1B,IAAK,IAAIC,IAAI,GAAGA,IAAIL,MAAMM,MAAM,EAAED,IAAK;QACrC,IAAIF,MAAMI,IAAI,CAACP,KAAK,CAACK,EAAE,GAAG;QAC1B,IAAI,CAACF,MAAMK,OAAO,IAAI,YAAYC,IAAI,CAACT,KAAK,CAACK,EAAE,GAAGH,OAAOQ,IAAI,CAACL,IAAI;IACpE;IACA,IAAMM,SAAST,OAAOI,MAAM,KAAK,IAAI;QAAC;KAAE,GAAGJ,MAAM,CAAC,EAAE,GAAG,IAAI;QAAC;KAAa,CAAd,OAAI,qBAAGA,WAAUA;IAC5E,IAAMU,SAAS;QAACd,mBAAAA,6BAAAA,OAAQe,KAAK;QAAEf,mBAAAA,6BAAAA,OAAQgB,OAAO;KAAC,CAACC,MAAM,CAACC,SAASC,IAAI,CAAC;IACrE,IAAMC,SAAkB,EAAE;IAC1BP,OAAOQ,OAAO,CAAC,SAACC,OAAOf;QACrB,IAAMgB,MAAMhB,IAAI,IAAIM,OAAOL,MAAM,GAAGK,MAAM,CAACN,IAAI,EAAE,GAAG,IAAIL,MAAMM,MAAM;QACpE,IAAMgB,OAAOtB,MACVuB,KAAK,CAACH,QAAQ,GAAGC,KACjBJ,IAAI,CAAC,MACLO,IAAI;QACP,uFAAuF;QACvF,gDAAgD;QAChD,IAAIF,KAAKhB,MAAM,GAAG,GAAGY,OAAOR,IAAI,CAAC;YAAEe,WAAWL,QAAQrB;YAAQ2B,SAASL,MAAMtB;YAAQuB,MAAMV,SAAS,AAAC,GAAaU,OAAXV,QAAO,MAAS,OAALU,QAASA;QAAK;IAClI;IACA,OAAOJ;AACT;AAEO,IAAMlC,QAAiB;IAC5B2C,MAAM;IACNC,QAAAA,SAAAA,OAAOC,EAAE;QACPA,GAAGC,IAAI,CAAC;IACV;IACAC,SAAAA,SAAAA,QAAQC,GAAG,EAAEnC,IAAI,EAAEC,MAAM;QACvB,mFAAmF;QACnF,OAAOF,SAASC,MAAMC,QAAQkC,IAAI/B,KAAK,CAAC,MAAMK,MAAM,GAAGT,KAAKI,KAAK,CAAC,MAAMK,MAAM;IAChF;IACA2B,QAAAA,SAAAA,OAAOJ,EAAE,EAAEK,IAAI;QACbL,GAAGM,OAAO,CAAC,2CAA2CC,GAAG,CAACF;IAC5D;IACA,uFAAuF;IACvF,mFAAmF;IACnFG,OAAAA,SAAAA,MAAMR,EAAE,EAAEK,IAAI,EAAEI,SAAS;QACvB,IAAI,CAACA,WAAW;QAChB,IAAMC,SAASV,GAAGM,OAAO,CAAC;QACzBG,UAAsBnB,OAAO,CAAC,SAACqB,GAAGC;mBAAQF,OAAOH,GAAG,CAACF,MAAMO,KAAKD,EAAEf,SAAS,EAAEe,EAAEd,OAAO;;IACzF;IACAgB,gBAAAA,SAAAA,eAAeC,IAAI,EAAEC,IAAI;QACvB,OAAOA,KAAK5D,KAAK;IACnB;AACF;AAEA,2FAA2F;AAC3F,yFAAyF;AACzF,gBAAgB;AAEhB,IAAM6D,cAAc;IAAC;IAAqB;CAAiB;AAEpD,IAAM/D,kBAAkB+D,YAAY5B,IAAI,CAAC;AAEhD,+FAA+F;AAC/F,wDAAwD;AACxD,IAAM6B,cAAc;AAGb,SAAS1D,eAAe2D,KAAa;IAC1C,OAAOD,YAAYrC,IAAI,CAACsC;AAC1B;AAEA,2FAA2F;AAC3F,uFAAuF;AACvF,oFAAoF;AACpF,SAASC;IACP,IAAMC,MAAMC,QAAQC,GAAG,CAACC,cAAc;IACtC,OAAOnC,IAAAA,cAAI,EAACgC,OAAOA,IAAI3C,MAAM,GAAG,IAAI2C,MAAMhC,IAAAA,cAAI,EAACoC,IAAAA,eAAO,KAAI,WAAW,eAAe;AACtF;AAKO,SAAShE,SAAS0D,KAAa;IACpC,IAAI,CAACD,YAAYrC,IAAI,CAACsC,QAAQ,OAAOA;IACrC,OAAO9B,IAAAA,cAAI,EAAC+B,aAAaD,MAAMO,OAAO,CAAC,OAAO;AAChD;AAGO,SAASnE,cAAc4D,KAAa;IACzC,IAAMQ,MAAMlE,SAAS0D;IACrB,OAAOF,YAAYW,KAAK,CAAC,SAACZ;eAASa,IAAAA,kBAAU,EAACxC,IAAAA,cAAI,EAACsC,KAAKX;;AAC1D;AAIO,SAAStD,aAAaoE,GAAW;IACtC,IAAMC,IAAIC,IAAAA,oBAAW,EAACF;IACtB,IAAI,CAACC,GAAG,OAAO;IACf,OAAOA,EAAEE,IAAI,KAAK,SAAS1E,cAAcwE,EAAEZ,KAAK;AAClD;AAEA,SAASe,YAAYC,GAAW,EAAEC,IAAY;IAC5C,OAAOC,MAAMF,KAAKG,IAAI,CAAC,SAAOC;;;;;;wBAC5B,IAAI,CAACA,IAAIC,EAAE,EAAE,MAAM,IAAIC,oBAAU,CAAC,eAAe,AAAC,0BAAwCF,OAAfJ,KAAI,aAAsB,OAAXI,IAAIG,MAAM;;4BACrF,GAAO,OAALN,MAAK;;4BAAQO,OAAOC,IAAI;wBAAC;;4BAAML,IAAIM,WAAW;;;wBAA/DC,qBAAa;4BAAiBH,QAAAA;gCAAY;;;wBAC1CI,IAAAA,kBAAU,EAAC,AAAC,GAAO,OAALX,MAAK,UAAQA;;;;;;QAC7B;;AACF;AAIO,SAAejF,cAAcgE,KAAa,EAAE6B,MAA4C;;YAIvFrB,KAED,2BAAA,mBAAA,gBAAA,WAAA,OAAMX;;;;oBALX,IAAI,CAACxD,eAAe2D,QAAQ;wBAC1B,MAAM,IAAIsB,oBAAU,CAAC,eAAe,AAAC,gBAAqB,OAANtB,OAAM;oBAC5D;oBACMQ,MAAMlE,SAAS0D;oBACrB8B,IAAAA,iBAAS,EAACtB,KAAK;wBAAEuB,WAAW;oBAAK;oBAC5B,kCAAA,2BAAA;;;;;;;;;oBAAA,YAAcjC;;;2BAAd,6BAAA,QAAA;;;;oBAAMD,OAAN;oBACH,IAAIa,IAAAA,kBAAU,EAACxC,IAAAA,cAAI,EAACsC,KAAKX,QAAQ;;;;oBACjCgC,mBAAAA,6BAAAA,OAAShC,MAAMW;oBACf;;wBAAMO,YAAY,AAAC,0BAA+ClB,OAAtBG,OAAM,kBAAqB,OAALH,OAAQ3B,IAAAA,cAAI,EAACsC,KAAKX;;;oBAApF;;;oBAHG;;;;;;;;;;;;oBAAA;oBAAA;;;;;;;6BAAA,6BAAA;4BAAA;;;4BAAA;kCAAA;;;;;;;oBAKL;;wBAAOW;;;;IACT;;AAEA,SAAewB,eAAehC,KAAa;;kBAyBEiC,sBAA7BA,4BAAAA,uBAxBRzB,KACD,2BAAA,mBAAA,gBAAA,WAAA,OAAMX,MAIDqC,KAKJjD,KACAkD,WACAC,QACAC,OAEAC,MACAC,MACAC,WACAC,QAEAR,eAEES,WACFC,KACAC;QAEN,SAASC,IAAItE,IAAY;YACvB,mFAAmF;YACnF,uFAAuF;YACvF,yFAAyF;YACzF,IAAMuE,MAAM,AAACH,IAAII,MAAM,CAACxE,MAAM;gBAAEyE,oBAAoB;YAAM,GAAGF,GAAG,CAAc9E,MAAM,CAAC,SAACiF;uBAAOC,OAAOC,SAAS,CAACF,OAAOA,OAAOL;;YAC5H,IAAMQ,IAAI,IAAIC,aAAad;YAC3B,IAAIO,IAAIvF,MAAM,KAAK,GAAG,OAAO6F;gBACxB,kCAAA,2BAAA;;gBAAL,QAAK,YAAYN,wBAAZ,SAAA,6BAAA,QAAA,yBAAA,iCAAiB;oBAAjB,IAAMG,KAAN;oBACH,IAAMK,MAAML,KAAKV;oBACjB,IAAK,IAAIgB,IAAI,GAAGA,IAAIhB,MAAMgB,IAAKH,CAAC,CAACG,EAAE,IAAId,MAAM,CAACa,MAAMC,EAAE;gBACxD;;gBAHK;gBAAA;;;yBAAA,6BAAA;wBAAA;;;wBAAA;8BAAA;;;;YAIL,IAAIC,OAAO;YACX,IAAK,IAAID,KAAI,GAAGA,KAAIhB,MAAMgB,KAAK;gBAC7BH,CAAC,CAACG,GAAE,IAAIT,IAAIvF,MAAM;gBAClBiG,QAAQJ,CAAC,CAACG,GAAE,GAAGH,CAAC,CAACG,GAAE;YACrB;YACAC,OAAOC,KAAKC,IAAI,CAACF,QAAQ;YACzB,IAAK,IAAID,KAAI,GAAGA,KAAIhB,MAAMgB,KAAKH,CAAC,CAACG,GAAE,IAAIC;YACvC,OAAOJ;QACT;;;;oBA7CM5C,MAAMlE,SAAS0D;oBAChB,kCAAA,2BAAA;;wBAAL,IAAK,YAAcF,kCAAd,6BAAA,QAAA,yBAAA,iCAA2B;4BAArBD,OAAN;4BACH,IAAI,CAACa,IAAAA,kBAAU,EAACxC,IAAAA,cAAI,EAACsC,KAAKX,QAAQ;gCAChC,uFAAuF;gCACvF,2BAA2B;gCACrBqC,MAAM7F,eAAe2D,SAAS,yBAAyB,AAAC,YAA2B,OAAhBjE,iBAAgB;gCACzF,MAAM,IAAIuF,oBAAU,CAAC,uBAAuB,AAAC,eAAmDd,OAArCR,OAAM,iCAAwCkC,OAAT1B,KAAI,OAAS,OAAJ0B;4BAC3G;wBACF;;wBAPK;wBAAA;;;iCAAA,6BAAA;gCAAA;;;gCAAA;sCAAA;;;;oBASCjD,MAAM0E,IAAAA,oBAAY,EAACzF,IAAAA,cAAI,EAACsC,KAAK;oBAC7B2B,YAAYe,OAAOjE,IAAI2E,eAAe,CAAC;oBACvCxB,SAASyB,KAAKC,KAAK,CAAC7E,IAAI8E,QAAQ,CAAC,GAAG,IAAI5B,WAAW6B,QAAQ,CAAC;oBAC5D3B,QAAQ4B,OAAOC,OAAO,CAAC9B,QAAQ+B,IAAI,CAAC;iEAAEC;+BAAOA,MAAM;;oBACzD,IAAI,CAAC/B,SAASA,KAAK,CAAC,EAAE,CAACgC,KAAK,KAAK,OAAO,MAAM,IAAI/C,oBAAU,CAAC,eAAe,AAAC,GAAQ,OAANtB,OAAM;oBAC/EsC,OAAOD,KAAK,CAAC,EAAE;oBACfE,OAAOD,KAAKgC,KAAK,CAAC,EAAE;oBACpB9B,YAAYvD,IAAIsF,UAAU,GAAG,IAAIpC,YAAYG,KAAKkC,YAAY,CAAC,EAAE;oBACjE/B,SAASD,YAAY,MAAM,IAAI,IAAIa,aAAapE,IAAIwF,MAAM,EAAEjC,WAAWF,KAAKgC,KAAK,CAAC,EAAE,GAAG/B,QAAQ,IAAIc,aAAapE,IAAIwF,MAAM,CAACjG,KAAK,CAACgE,WAAWA,YAAYF,KAAKgC,KAAK,CAAC,EAAE,GAAG/B,OAAO;oBAE/KN,gBAAgB4B,KAAKC,KAAK,CAACH,IAAAA,oBAAY,EAACzF,IAAAA,cAAI,EAACsC,KAAK,mBAAmB;oBAErD;;wBAAM;2EAAA,QAAO;;;;oBAA3BkC,YAAc,cAAdA;oBACFC,MAAM,IAAID,UAAUT,eAAe,CAAC;oBACpCW,iBAAQX,wBAAAA,cAAcjC,KAAK,cAAnBiC,6CAAAA,6BAAAA,sBAAqByC,KAAK,cAA1BzC,iDAAAA,0BAA4B,EAACA,uBAAAA,cAAcjC,KAAK,cAAnBiC,2CAAAA,qBAAqB0C,SAAS,CAAC,uCAAI,CAAC;oBAuB/E;;wBAAO;4BAAE1B,IAAI,AAAC,UAAe,OAANjD;4BAASuC,MAAAA;4BAAMtG,OAAO,SAAPA,MAAc2I;;;;;4CAAUA,MAAMC,GAAG,CAAChC;;;;;wBAAK;;;;IAC/E;;AAEA,gFAAgF;AAEhF,SAAeiC,YAAY9E,KAAa,EAAEgB,GAAuB,EAAE+D,MAA0B;;YAErFC,MACAC,SACAC,KAUA3C;QAPN,SAAe4C,KAAKP,KAAe;;oBAC3BxD,KAEAtE;;;;4BAFM;;gCAAMoE,MAAM,AAAC,GAAO,OAAL8D,MAAK,gBAAc;oCAAEI,QAAQ;oCAAQH,SAAAA;oCAASnI,MAAM+G,KAAKwB,SAAS,CAAC;wCAAErF,OAAAA;wCAAOsF,OAAOV;oCAAM;gCAAG;;;4BAAjHxD,MAAM;4BACZ,IAAI,CAACA,IAAIC,EAAE,EAAE,MAAM,IAAIC,oBAAU,CAAC,eAAe,AAAC,GAA6BF,OAA3B4D,MAAK,wBAAiC,OAAX5D,IAAIG,MAAM;4BAC3E;;gCAAMH,IAAImE,IAAI;;;4BAAtBzI,OAAQ;4BACd;;gCAAOA,KAAK0I,IAAI,CAACX,GAAG,CAAC,SAACtB;2CAAMF,aAAa5B,IAAI,CAAC8B,EAAEkC,SAAS;;;;;YAC3D;;;;;oBAXA,IAAI,CAACzE,KAAK,MAAM,IAAIM,oBAAU,CAAC,eAAe;oBACxC0D,OAAOhE,IAAIT,OAAO,CAAC,QAAQ;oBAC3B0E,UAAkC;wBAAE,gBAAgB;oBAAmB;oBACvEC,MAAMH,SAAS5E,QAAQC,GAAG,CAAC2E,OAAO,GAAGW;oBAC3C,IAAIR,KAAKD,QAAQU,aAAa,GAAG,AAAC,UAAa,OAAJT;oBAS7B;;wBAAMC;4BAAM;;;;oBAApB5C,OAAO,AAAC,aAAgC,CAAC,EAAE,CAAChF,MAAM;oBACxD;;wBAAO;4BAAE0F,IAAI,AAAC,OAAcjD,OAARgF,MAAK,KAAS,OAANhF;4BAASuC,MAAAA;4BAAMtG,OAAOkJ;wBAAK;;;;IACzD;;AAEA,IAAMS,YAAY,IAAIC;AAEtB,SAASC,YAAYnF,GAAW;QAGMC;IAFpC,IAAMA,IAAIC,IAAAA,oBAAW,EAACF;IACtB,IAAI,CAACC,GAAG,MAAM,IAAIU,oBAAU,CAAC,kBAAkB;IAC/C,IAAMyE,MAAM,AA7Od,AA6Oe,GAAYnF,OAAVA,EAAEE,IAAI,EAAC,YAAGF,EAAEZ,KAAK,EAAC,KAAe,QAAZY,SAAAA,EAAEI,GAAG,cAALJ,oBAAAA,SAAS;IAC7C,IAAIoF,IAAIJ,UAAUK,GAAG,CAACF;IACtB,IAAI,CAACC,GAAG;QACNA,IAAIpF,EAAEE,IAAI,KAAK,QAAQgE,YAAYlE,EAAEZ,KAAK,EAAEY,EAAEI,GAAG,EAAEJ,EAAEsE,GAAG,IAAIlD,eAAepB,EAAEZ,KAAK;QAClF4F,UAAUM,GAAG,CAACH,KAAKC;IACrB;IACA,OAAOA;AACT;AAEA,4EAA4E;AAC5E,SAASG,QAAQC,IAAkB,EAAE7D,IAAY,EAAE8D,IAAa;IAC9D,IAAMjD,IAAI,IAAIC,aAAad;IAC3B,IAAIiB,OAAO;IACX,IAAK,IAAID,IAAI,GAAGA,IAAIhB,MAAMgB,IAAKC,QAAQ4C,IAAI,CAAC7C,EAAE,GAAG6C,IAAI,CAAC7C,EAAE;IACxDC,OAAOC,KAAKC,IAAI,CAACF,QAAQ;IACzB,IAAK,IAAID,KAAI,GAAGA,KAAIhB,MAAMgB,KAAKH,CAAC,CAACG,GAAE,GAAG6C,IAAI,CAAC7C,GAAE,GAAGC;IAChD,IAAI,CAAC6C,MAAM,OAAO;QAAEjD,GAAAA;QAAGkD,OAAO;IAAE;IAChC,IAAIC,MAAM;IACV,IAAK,IAAIhD,KAAI,GAAGA,KAAIhB,MAAMgB,KAAKgD,MAAM9C,KAAK8C,GAAG,CAACA,KAAK9C,KAAK+C,GAAG,CAACpD,CAAC,CAACG,GAAE;IAChE,OAAO;QAAEH,GAAAA;QAAGkD,OAAOC,MAAM,OAAO;IAAE;AACpC;AAIO,SAAerK,aAAa4C,EAAgB,EAAE6B,GAAW,EAAE8F,OAAe;;mBACzEC,UACAC,OAEAC,WAEAC,QACD,2BAAA,mBAAA,gBAAA,WAAA,OAAMC,KACID,aAAPE,MAKFC,MACD,4BAAA,oBAAA,iBAAA,YAAA,qBAAO7H,MAAM8H,WACZ9I,QAQC,4BAAA,oBAAA,iBAAA,YAAA,QAAMuB,KAGPwH,QAGAC,QACG7J;;;;;4BACD8J,OACAC;;;;oCADAD,QAAQJ,KAAKxI,KAAK,CAAClB,GAAGA,IAAIV;oCAChB;;wCAAM8J,SAASzK,KAAK,CAACmL,MAAMvC,GAAG,CAAC,SAACyC;mDAAMA,EAAE/I,IAAI;;;;oCAAtD8I,UAAU;oCAChBvI,GAAGC,IAAI,CAAC;oCACR,IAAI;wCACFqI,MAAMhJ,OAAO,CAAC,SAACmJ,KAAKD;4CAClB,IAAqBnB,WAAAA,QAAQkB,OAAO,CAACC,EAAE,EAAEV,WAAW,OAA5CxD,IAAa+C,SAAb/C,GAAGkD,QAAUH,SAAVG;4CACX,IAAMkB,IAAI,IAAIC,UAAUb;4CACxB,IAAK,IAAIrD,IAAI,GAAGA,IAAIqD,WAAWrD,IAAKiE,CAAC,CAACjE,EAAE,GAAGE,KAAKiE,KAAK,CAACtE,CAAC,CAACG,EAAE,GAAG+C;4CAC7DY,OAAO7H,GAAG,CAACiH,OAAO9E,OAAOC,IAAI,CAAC+F,EAAE/C,MAAM,GAAG8C,IAAIpI,IAAI,EAAEoI,IAAII,KAAK;wCAC9D;wCACA7I,GAAGC,IAAI,CAAC;oCACV,EAAE,OAAO6I,KAAK;wCACZ9I,GAAGC,IAAI,CAAC;wCACR,MAAM6I;oCACR;oCACAT,OAAOU,IAAI,CAACpE,KAAKqE,GAAG,CAACxK,IAAIV,OAAOoK,KAAKzJ,MAAM;;;;;;oBAC7C;oBA9CiB;;wBAAMuI,YAAYnF;;;oBAA7B+F,WAAW;oBACXC,QAAQ7H,GAAGM,OAAO,CAAC,oFAAoF2I,GAAG;oBAChH,IAAIpB,MAAMpJ,MAAM,KAAK,GAAG;;;oBAClBqJ,YAAYnD,KAAKqE,GAAG,CAACpL,YAAYgK,SAASnE,IAAI;oBAE9CsE,SAAS,IAAIhB;oBACd,kCAAA,2BAAA;;wBAAL,IAAK,YAAac,4BAAb,6BAAA,QAAA,yBAAA,iCAAoB;4BAAdG,MAAN;;4BACGC,QAAOF,cAAAA,OAAOZ,GAAG,CAACa,IAAI3H,IAAI,eAAnB0H,yBAAAA;4BACbE,KAAKpJ,IAAI,CAACmJ,IAAIa,KAAK;4BACnBd,OAAOX,GAAG,CAACY,IAAI3H,IAAI,EAAE4H;wBACvB;;wBAJK;wBAAA;;;iCAAA,6BAAA;gCAAA;;;gCAAA;sCAAA;;;;oBAMCC;oBACD,mCAAA,4BAAA;;wBAAL,IAAK,aAA2BH,6BAA3B,8BAAA,SAAA,0BAAA,kCAAmC;2DAAnC,kBAAO1H,uBAAM8H;4BACZ9I,SAAAA,KAAAA;4BACJ,IAAI;gCACF,qFAAqF;gCACrF,+EAA+E;gCAC/EA,SAAS6J,IAAAA,iBAAS,EAAC;oCAAEC,SAAS9I;oCAAM+I,SAAShK,IAAAA,cAAI,EAACuI,SAAStH;oCAAOgJ,SAAS;oCAAGC,SAAS;oCAAGC,MAAM;oCAAGC,OAAO;oCAAMrM,OAAO;gCAAK;oCAAIA;mCAAQsM,GAAG,CAAChJ,SAAS,CAACtD,KAAK;4BAC7J,EAAE,eAAM;gCACN,UAAU,gEAAgE;4BAC5E;4BACK,mCAAA,4BAAA;;gCAAL,IAAK,aAAagL,gCAAb,8BAAA,SAAA,0BAAA;oCAAMvH,MAAN;oCAAwB,IAAIvB,MAAM,CAACuB,IAAI,EAAEsH,KAAKrJ,IAAI,CAAC;wCAAEwB,MAAAA;wCAAMwI,OAAOjI;wCAAKnB,MAAMJ,MAAM,CAACuB,IAAI,CAACnB,IAAI;oCAAC;;;gCAA9F;gCAAA;;;yCAAA,8BAAA;wCAAA;;;wCAAA;8CAAA;;;;wBACP;;wBAVK;wBAAA;;;iCAAA,8BAAA;gCAAA;;;gCAAA;sCAAA;;;;oBAYC2I,SAASpI,GAAGM,OAAO,CAAC;oBAC1B,8EAA8E;oBAC9E,6DAA6D;oBACvD+H,SAASqB,IAAAA,oBAAQ,EAAC,oBAAoBxB,KAAKzJ,MAAM;oBAC9CD,IAAI;;;yBAAGA,CAAAA,IAAI0J,KAAKzJ,MAAM,AAAD;;;;;;;;;;;;oBAAGD,KAAKV;;;;;;oBAkBtCuK,OAAOsB,MAAM;;;;;;IACf;;AAEA,4FAA4F;AAC5F,uFAAuF;AACvF,SAASC,SAASC,KAAa;IAC7B,OAAOlF,KAAKiE,KAAK,CAACjE,KAAKqE,GAAG,CAAC,GAAGrE,KAAK8C,GAAG,CAAC,CAAC,GAAGoC,UAAU,QAAQ;AAC/D;AAKO,SAAenM,mBAAmBsC,EAAgB,EAAE6B,GAAW,EAAEiI,KAAa,EAAE1H,MAAa,EAAE2H,OAAqB;;YAO3GD,cANRnC,SAIAC,UACAE,WACArI,MACY4H,UAAP2C,IAELC,MAQAC,MACD,2BAAA,mBAAA,gBAAA,WAAA,OAAMlC,KAEHU,GACFyB,KACK1F,GACHoF,OACAO;;;;oBAxBFzC,UAAU,AAAC9F,IAAgC8F,OAAO;oBACxD,IAAI,CAACA,SAAS,MAAM,IAAInF,oBAAU,CAAC,eAAe;oBAClD;;wBAAMpF,aAAa4C,IAAI6B,KAAK8F;;;oBAA5B;oBAEiB;;wBAAMX,YAAYnF;;;oBAA7B+F,WAAW;oBACXE,YAAYnD,KAAKqE,GAAG,CAACpL,YAAYgK,SAASnE,IAAI;oBAC9ChE,OAAO,EAACqK,eAAAA,MAAMO,KAAK,CAAC,2naAAZP,0BAAAA,mBAAsC5K,MAAM,CAAC,SAACoL;+BAAM,CAAC;4BAAC;4BAAO;4BAAM;4BAAO;yBAAO,CAACC,QAAQ,CAACD;uBAAIlL,IAAI,CAAC;oBACvF;;wBAAMwI,SAASzK,KAAK;4BAAEsC;;;;oBAA/B4H,WAAAA;wBAAS,aAA6B,CAAC,EAAE;wBAAES;wBAAW;wBAA7DkC,KAAO3C,SAAV/C;oBAEF2F,OAAOjK,GAAGM,OAAO,CAAC,+FAA+F2I,GAAG;oBAQpHiB,OAAO,IAAInD;oBACZ,kCAAA,2BAAA;;wBAAL,IAAK,YAAakD,2BAAb,6BAAA,QAAA,yBAAA,iCAAmB;4BAAbjC,MAAN;4BACH,IAAI+B,WAAW,CAACA,QAAQS,GAAG,CAACxC,IAAI3H,IAAI,GAAG;4BACjCqI,IAAI,IAAIC,UAAUX,IAAIyC,MAAM,CAAC9E,MAAM,EAAEqC,IAAIyC,MAAM,CAAChF,UAAU,EAAEd,KAAKqE,GAAG,CAAClB,WAAWE,IAAIyC,MAAM,CAACC,UAAU;4BACvGP,MAAM;4BACV,IAAS1F,IAAI,GAAGA,IAAIiE,EAAEjK,MAAM,EAAEgG,IAAK0F,OAAOzB,CAAC,CAACjE,EAAE,GAAGuF,EAAE,CAACvF,EAAE;4BAChDoF,QAAQM,MAAMnC,IAAIR,KAAK;4BACvB4C,WAAWF,KAAK/C,GAAG,CAACa,IAAI3H,IAAI;4BAClC,IAAI,CAAC+J,YAAYP,QAAQO,SAASP,KAAK,EAAEK,KAAK9C,GAAG,CAACY,IAAI3H,IAAI,EAAE;gCAAEwJ,OAAAA;gCAAO1L,OAAO,AAAC,IAAqB6J,OAAlBA,IAAI2C,UAAU,EAAC,KAAgB,OAAb3C,IAAI4C,QAAQ;4BAAG;wBACnH;;wBARK;wBAAA;;;iCAAA,6BAAA;gCAAA;;;gCAAA;sCAAA;;;;oBASL;;wBAAQ,qBAAGV,KAAK9E,OAAO,IACpByF,IAAI,CAAC,SAACC,GAAGC;mCAAMA,CAAC,CAAC,EAAE,CAAClB,KAAK,GAAGiB,CAAC,CAAC,EAAE,CAACjB,KAAK;2BACtCnK,KAAK,CAAC,GAAG0C,QACT2D,GAAG,CAAC;qEAAE1F,kBAAM0K;mCAAQ;gCAAE1K,MAAAA;gCAAMlC,OAAO4M,EAAE5M,KAAK;gCAAE6M,YAAYpB,SAASmB,EAAElB,KAAK;4BAAE;;;;;IAC/E;;AAIO,SAASxM,aAAa2C,EAAgB,EAAEK,IAAY;IACzD,IAAM2H,MAAMhI,GAAGM,OAAO,CAAC,kFAAkF6G,GAAG,CAAC9G;IAC7G,OAAO2H,QAAQpB;AACjB;AAIO,SAASjJ,aAAaqC,EAAgB,EAAEc,IAAY,EAAET,IAAY,EAAE4K,IAAgE;IACzI,0FAA0F;IAC1F,qFAAqF;IACrF,IAAMC,aAAalL,GAAGM,OAAO,CAAC,+FAA+F2I,GAAG,CAAC5I;IACjI,IAAI6K,WAAWzM,MAAM,KAAK,GAAG,OAAO,EAAE;IACtC,IAAM0M,OAAOxG,KAAK8C,GAAG,CAAC,GAAG9C,KAAKyG,IAAI,CAACF,WAAWzM,MAAM,GAAGZ;IACvD,IAAMwN,SAASH,WAAWhM,MAAM,CAAC,SAACoM,GAAG9M;eAAMA,IAAI2M,SAAS;OAAGpF,GAAG,CAAC,SAACiC;eAAS;YAAE1D,GAAG,IAAIqE,UAAUX,IAAIyC,MAAM,CAAC9E,MAAM,EAAEqC,IAAIyC,MAAM,CAAChF,UAAU,EAAEuC,IAAIyC,MAAM,CAACC,UAAU;YAAGlD,OAAOQ,IAAIR,KAAK;QAAC;;IAE/K,IAAMyC,OAAOjK,GAAGM,OAAO,CAAC,yEAAyE2I,GAAG;IACpG,IAAMiB,OAAO,IAAInD;QACZ,kCAAA,2BAAA;;QAAL,QAAK,YAAakD,yBAAb,SAAA,6BAAA,QAAA,yBAAA,iCAAmB;YAAnB,IAAMjC,MAAN;YACH,IAAIA,IAAI3H,IAAI,KAAKA,QAAQ4K,KAAKM,OAAO,CAACf,GAAG,CAACxC,IAAI3H,IAAI,KAAM4K,KAAKlB,OAAO,IAAI,CAACkB,KAAKlB,OAAO,CAACS,GAAG,CAACxC,IAAI3H,IAAI,GAAI;YACtG,IAAMmL,QAAQ,IAAI7C,UAAUX,IAAIyC,MAAM,CAAC9E,MAAM,EAAEqC,IAAIyC,MAAM,CAAChF,UAAU,EAAEuC,IAAIyC,MAAM,CAACC,UAAU;gBACtF,mCAAA,4BAAA;;gBAAL,QAAK,aAAWW,2BAAX,UAAA,8BAAA,SAAA,0BAAA,kCAAmB;oBAAnB,IAAMf,IAAN;oBACH,IAAIH,MAAM;oBACV,IAAMsB,MAAM9G,KAAKqE,GAAG,CAACsB,EAAEhG,CAAC,CAAC7F,MAAM,EAAE+M,MAAM/M,MAAM;oBAC7C,IAAK,IAAIgG,IAAI,GAAGA,IAAIgH,KAAKhH,IAAK0F,OAAOG,EAAEhG,CAAC,CAACG,EAAE,GAAG+G,KAAK,CAAC/G,EAAE;oBACtD,IAAMoF,QAAQM,MAAMG,EAAE9C,KAAK,GAAGQ,IAAIR,KAAK;oBACvC,IAAM4C,WAAWF,KAAK/C,GAAG,CAACa,IAAI3H,IAAI;oBAClC,IAAI+J,aAAaxD,aAAaiD,QAAQO,UAAUF,KAAK9C,GAAG,CAACY,IAAI3H,IAAI,EAAEwJ;gBACrE;;gBAPK;gBAAA;;;yBAAA,8BAAA;wBAAA;;;wBAAA;8BAAA;;;;QAQP;;QAXK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAaL,OAAO,AAAC,qBAAGK,KAAK9E,OAAO,IACpByF,IAAI,CAAC,SAACC,GAAGC;eAAMA,CAAC,CAAC,EAAE,GAAGD,CAAC,CAAC,EAAE;OAC1BpL,KAAK,CAAC,GAAGuL,KAAK3F,CAAC,EACfS,GAAG,CAAC;iDAAEmB,eAAG2C;eAAY;YAAExJ,MAAM6G;YAAG8D,YAAYpB,SAASC;QAAO;;AACjE"}