sensemaking 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +13 -9
  2. package/dist/cjs/cli/index.d.cts +1 -1
  3. package/dist/cjs/cli/index.d.ts +1 -1
  4. package/dist/cjs/cli/index.js +1 -1
  5. package/dist/cjs/cli/index.js.map +1 -1
  6. package/dist/cjs/cli/named.js +6 -2
  7. package/dist/cjs/cli/named.js.map +1 -1
  8. package/dist/cjs/cli/search.js +6 -2
  9. package/dist/cjs/cli/search.js.map +1 -1
  10. package/dist/cjs/cli/shared.d.cts +2 -0
  11. package/dist/cjs/cli/shared.d.ts +2 -0
  12. package/dist/cjs/cli/shared.js +24 -0
  13. package/dist/cjs/cli/shared.js.map +1 -1
  14. package/dist/cjs/commands/map.js +2 -2
  15. package/dist/cjs/commands/map.js.map +1 -1
  16. package/dist/cjs/commands/search.d.cts +10 -0
  17. package/dist/cjs/commands/search.d.ts +10 -0
  18. package/dist/cjs/commands/search.js +488 -180
  19. package/dist/cjs/commands/search.js.map +1 -1
  20. package/dist/cjs/output/output.js +45 -8
  21. package/dist/cjs/output/output.js.map +1 -1
  22. package/dist/cjs/output/search-error.d.cts +1 -0
  23. package/dist/cjs/output/search-error.d.ts +1 -0
  24. package/dist/cjs/output/search-error.js +28 -4
  25. package/dist/cjs/output/search-error.js.map +1 -1
  26. package/dist/cjs/scan/frontmatter.js +1 -1
  27. package/dist/cjs/scan/frontmatter.js.map +1 -1
  28. package/dist/cjs/scan/index.js +1 -0
  29. package/dist/cjs/scan/index.js.map +1 -1
  30. package/dist/cjs/store/duckdb/fieldStats.js +4 -7
  31. package/dist/cjs/store/duckdb/fieldStats.js.map +1 -1
  32. package/dist/cjs/store/duckdb/lexical.d.cts +2 -2
  33. package/dist/cjs/store/duckdb/lexical.d.ts +2 -2
  34. package/dist/cjs/store/duckdb/lexical.js +215 -64
  35. package/dist/cjs/store/duckdb/lexical.js.map +1 -1
  36. package/dist/cjs/store/duckdb/reconcile.js +7 -2
  37. package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
  38. package/dist/cjs/store/duckdb/store.js +1 -3
  39. package/dist/cjs/store/duckdb/store.js.map +1 -1
  40. package/dist/cjs/store/duckdb/vectors.js +13 -8
  41. package/dist/cjs/store/duckdb/vectors.js.map +1 -1
  42. package/dist/cjs/store/native.js +5 -4
  43. package/dist/cjs/store/native.js.map +1 -1
  44. package/dist/cjs/store/open.js +7 -0
  45. package/dist/cjs/store/open.js.map +1 -1
  46. package/dist/cjs/store/shared.js.map +1 -1
  47. package/dist/cjs/store/sqlite/lexical.js +1 -4
  48. package/dist/cjs/store/sqlite/lexical.js.map +1 -1
  49. package/dist/cjs/store/sqlite/store.js +1 -4
  50. package/dist/cjs/store/sqlite/store.js.map +1 -1
  51. package/dist/cjs/store/sqlite/vectors.js +78 -24
  52. package/dist/cjs/store/sqlite/vectors.js.map +1 -1
  53. package/dist/cjs/store/turso/connection.js +2 -1
  54. package/dist/cjs/store/turso/connection.js.map +1 -1
  55. package/dist/cjs/store/turso/lexical-text.d.cts +1 -0
  56. package/dist/cjs/store/turso/lexical-text.d.ts +1 -0
  57. package/dist/cjs/store/turso/lexical-text.js +40 -0
  58. package/dist/cjs/store/turso/lexical-text.js.map +1 -0
  59. package/dist/cjs/store/turso/lexical.js +89 -16
  60. package/dist/cjs/store/turso/lexical.js.map +1 -1
  61. package/dist/cjs/store/turso/open.d.cts +1 -1
  62. package/dist/cjs/store/turso/open.d.ts +1 -1
  63. package/dist/cjs/store/turso/open.js +2 -2
  64. package/dist/cjs/store/turso/open.js.map +1 -1
  65. package/dist/cjs/store/turso/reconcile.d.cts +1 -1
  66. package/dist/cjs/store/turso/reconcile.d.ts +1 -1
  67. package/dist/cjs/store/turso/reconcile.js +6 -2
  68. package/dist/cjs/store/turso/reconcile.js.map +1 -1
  69. package/dist/cjs/store/turso/sql-functions.d.cts +1 -0
  70. package/dist/cjs/store/turso/sql-functions.d.ts +1 -0
  71. package/dist/cjs/store/turso/sql-functions.js +174 -0
  72. package/dist/cjs/store/turso/sql-functions.js.map +1 -0
  73. package/dist/cjs/store/turso/store.js +2 -3
  74. package/dist/cjs/store/turso/store.js.map +1 -1
  75. package/dist/cjs/store/turso/vectors.js +50 -14
  76. package/dist/cjs/store/turso/vectors.js.map +1 -1
  77. package/dist/cjs/store/types.d.cts +1 -2
  78. package/dist/cjs/store/types.d.ts +1 -2
  79. package/dist/cjs/store/types.js +1 -4
  80. package/dist/cjs/store/types.js.map +1 -1
  81. package/dist/cjs/store/vectors.d.cts +7 -0
  82. package/dist/cjs/store/vectors.d.ts +7 -0
  83. package/dist/cjs/store/vectors.js +8 -0
  84. package/dist/cjs/store/vectors.js.map +1 -1
  85. package/dist/cjs/text/segment.d.cts +13 -0
  86. package/dist/cjs/text/segment.d.ts +13 -0
  87. package/dist/cjs/text/segment.js +109 -4
  88. package/dist/cjs/text/segment.js.map +1 -1
  89. package/dist/esm/cli/index.d.ts +1 -1
  90. package/dist/esm/cli/index.js +1 -1
  91. package/dist/esm/cli/index.js.map +1 -1
  92. package/dist/esm/cli/named.js +6 -2
  93. package/dist/esm/cli/named.js.map +1 -1
  94. package/dist/esm/cli/search.js +5 -1
  95. package/dist/esm/cli/search.js.map +1 -1
  96. package/dist/esm/cli/shared.d.ts +2 -0
  97. package/dist/esm/cli/shared.js +19 -0
  98. package/dist/esm/cli/shared.js.map +1 -1
  99. package/dist/esm/commands/map.js +2 -2
  100. package/dist/esm/commands/map.js.map +1 -1
  101. package/dist/esm/commands/search.d.ts +10 -0
  102. package/dist/esm/commands/search.js +271 -91
  103. package/dist/esm/commands/search.js.map +1 -1
  104. package/dist/esm/output/output.js +27 -4
  105. package/dist/esm/output/output.js.map +1 -1
  106. package/dist/esm/output/search-error.d.ts +1 -0
  107. package/dist/esm/output/search-error.js +17 -1
  108. package/dist/esm/output/search-error.js.map +1 -1
  109. package/dist/esm/scan/frontmatter.js +1 -1
  110. package/dist/esm/scan/frontmatter.js.map +1 -1
  111. package/dist/esm/scan/index.js +1 -0
  112. package/dist/esm/scan/index.js.map +1 -1
  113. package/dist/esm/store/duckdb/fieldStats.js +4 -7
  114. package/dist/esm/store/duckdb/fieldStats.js.map +1 -1
  115. package/dist/esm/store/duckdb/lexical.d.ts +2 -2
  116. package/dist/esm/store/duckdb/lexical.js +94 -40
  117. package/dist/esm/store/duckdb/lexical.js.map +1 -1
  118. package/dist/esm/store/duckdb/reconcile.js +2 -2
  119. package/dist/esm/store/duckdb/reconcile.js.map +1 -1
  120. package/dist/esm/store/duckdb/store.js +2 -4
  121. package/dist/esm/store/duckdb/store.js.map +1 -1
  122. package/dist/esm/store/duckdb/vectors.js +26 -13
  123. package/dist/esm/store/duckdb/vectors.js.map +1 -1
  124. package/dist/esm/store/native.js +5 -4
  125. package/dist/esm/store/native.js.map +1 -1
  126. package/dist/esm/store/open.js +7 -0
  127. package/dist/esm/store/open.js.map +1 -1
  128. package/dist/esm/store/shared.js +1 -2
  129. package/dist/esm/store/shared.js.map +1 -1
  130. package/dist/esm/store/sqlite/lexical.js +2 -5
  131. package/dist/esm/store/sqlite/lexical.js.map +1 -1
  132. package/dist/esm/store/sqlite/store.js +1 -4
  133. package/dist/esm/store/sqlite/store.js.map +1 -1
  134. package/dist/esm/store/sqlite/vectors.js +48 -19
  135. package/dist/esm/store/sqlite/vectors.js.map +1 -1
  136. package/dist/esm/store/turso/connection.js +3 -2
  137. package/dist/esm/store/turso/connection.js.map +1 -1
  138. package/dist/esm/store/turso/lexical-text.d.ts +1 -0
  139. package/dist/esm/store/turso/lexical-text.js +12 -0
  140. package/dist/esm/store/turso/lexical-text.js.map +1 -0
  141. package/dist/esm/store/turso/lexical.js +56 -21
  142. package/dist/esm/store/turso/lexical.js.map +1 -1
  143. package/dist/esm/store/turso/open.d.ts +1 -1
  144. package/dist/esm/store/turso/open.js +2 -2
  145. package/dist/esm/store/turso/open.js.map +1 -1
  146. package/dist/esm/store/turso/reconcile.d.ts +1 -1
  147. package/dist/esm/store/turso/reconcile.js +6 -2
  148. package/dist/esm/store/turso/reconcile.js.map +1 -1
  149. package/dist/esm/store/turso/sql-functions.d.ts +1 -0
  150. package/dist/esm/store/turso/sql-functions.js +179 -0
  151. package/dist/esm/store/turso/sql-functions.js.map +1 -0
  152. package/dist/esm/store/turso/store.js +3 -5
  153. package/dist/esm/store/turso/store.js.map +1 -1
  154. package/dist/esm/store/turso/vectors.js +19 -10
  155. package/dist/esm/store/turso/vectors.js.map +1 -1
  156. package/dist/esm/store/types.d.ts +1 -2
  157. package/dist/esm/store/types.js +6 -8
  158. package/dist/esm/store/types.js.map +1 -1
  159. package/dist/esm/store/vectors.d.ts +7 -0
  160. package/dist/esm/store/vectors.js +8 -2
  161. package/dist/esm/store/vectors.js.map +1 -1
  162. package/dist/esm/text/segment.d.ts +13 -0
  163. package/dist/esm/text/segment.js +70 -0
  164. package/dist/esm/text/segment.js.map +1 -1
  165. package/package.json +4 -3
  166. package/schema.json +1 -1
  167. package/skills/sense/EXAMPLES.md +3 -3
  168. package/skills/sense/SKILL.md +6 -6
  169. package/skills/sense-setup/SKILL.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/frontmatter.ts"],"sourcesContent":["import { isCollection, parseDocument, visit } from 'yaml';\n\n// Frontmatter keys that would collide with table columns. Exported so reconcile.ts's upsert can tell\n// a feature-owned column (`_rank`) from a parsed one and leave it alone on reparse.\nexport const RESERVED_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_rank', '_parse_error', 'content', 'links', 'sections']);\n\n// Only BAD_SCALAR_START qualifies: YAML 1.2 reserves `@` and `` ` `` at the start of a plain scalar, so `aliases: [@handle]` can only mean [\"@handle\"].\n// Every other code has a second reading (a swallowed key, a dropped URL, a silently overwritten duplicate) that would write values nobody wrote.\nconst ACCEPTED_YAML_CODES = new Set(['BAD_SCALAR_START']);\n\nexport function normalizeText(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value).replace(/\\s+/g, ' ').trim();\n}\n\n// SQLite's datetime() rejects a colonless offset (`-0800`) and a space separator, which ISO 8601\n// allows and producers emit. A rejected date is invisible, not excluded: every comparison is NULL.\nconst ISO_DATETIME = /^(\\d{4}-\\d{2}-\\d{2})[T ](\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?)(Z|[+-]\\d{2}(?::?\\d{2})?)?$/;\n\n// A value opening `YYYY-MM-DDT` was meant to be a datetime; prose never is. Reported when it\n// cannot be normalized, so a typo surfaces on the next crawl instead of at some later audit.\nconst MEANT_AS_DATETIME = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d/;\n\nexport function looksLikeDatetime(value: string): boolean {\n return MEANT_AS_DATETIME.test(value);\n}\n\n// Punctuation only, never a timezone conversion: the offset survives, so substr(d,1,10) is still\n// the local date. A shape that is not a real instant is left as written, and stays auditable.\nexport function normalizeDate(value: string): string {\n const m = ISO_DATETIME.exec(value);\n if (m === null) return value;\n const [, date, time, zone] = m;\n const digits = zone === undefined || zone === 'Z' ? '' : zone.replace(':', '');\n const offset = digits === '' ? (zone ?? '') : digits.length === 3 ? `${digits}:00` : `${digits.slice(0, 3)}:${digits.slice(3)}`;\n const normalized = `${date}T${time}${offset}`;\n return Number.isNaN(Date.parse(normalized)) ? value : normalized;\n}\n\n// Storage class follows the YAML scalar. Booleans store as 1/0, so `WHERE flag = 1` matches\n// and `WHERE flag = 'true'` cannot; `map` prints observed types so the mismatch is visible.\nexport function mapValue(value: unknown): string | number | bigint | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'boolean') return BigInt(value ? 1 : 0);\n if (typeof value === 'number') return Number.isSafeInteger(value) ? BigInt(value) : value;\n if (typeof value === 'string') return normalizeDate(value);\n return JSON.stringify(value);\n}\n\n// Exactly `s.split('\\n').length` without allocating the array: callers that need only the count\n// run it per file per feature on the parse hot path.\nexport function countLines(s: string): number {\n let n = 1;\n for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 10) n++;\n return n;\n}\n\n// The delimiter split is all this package used gray-matter for.\nexport function splitFrontmatter(raw: string): { fm: string | null; body: string } {\n const open = raw.match(/^---\\r?\\n/);\n if (!open) return { fm: null, body: raw };\n const rest = raw.slice(open[0].length);\n const close = rest.match(/^---\\r?(\\n|$)/m);\n if (!close || close.index === undefined) return { fm: null, body: raw };\n return { fm: rest.slice(0, close.index), body: rest.slice(close.index + close[0].length) };\n}\n\n// A well-formed document can hold a value nobody meant: `created: {{date}}` is valid YAML for a flow map used as a mapping key, so it raises no error and stores {\"{ date }\": null}.\n// No error code catches that; yaml's own warning lacks a path and fires once per document, so this reports it here instead, with the path.\nfunction warnStringifiedKeys(relPath: string, doc: ReturnType<typeof parseDocument>, warnings: string[]): void {\n let found = false;\n // Nested, not top level: `created: {{date}}` puts the collection key one level down, inside\n // the flow map that `{{...}}` parses as.\n visit(doc, {\n Pair(_key, pair) {\n if (!isCollection(pair.key)) return undefined;\n found = true;\n return visit.BREAK;\n },\n });\n // One per file: a template repeats the same mistake on every field it stamps.\n if (found) warnings.push(`warning: ${relPath} frontmatter has a key that is itself a list or mapping, stored as text; this is usually an unrendered template placeholder like {{date}}`);\n}\n\n// Anything past ACCEPTED_YAML_CODES is quarantined: no frontmatter columns, `_parse_error` carries the reason. Recovering it would write values nobody wrote, worse than absence since no query can see it; content, links and sections never touch frontmatter, so a broken note stays searchable.\n// yaml's message continues onto a source excerpt, so the first line is the sentence, minus the colon that introduced the part being dropped.\nfunction firstLine(message: string): string {\n return message.split('\\n')[0].replace(/:\\s*$/, '');\n}\n\nexport function parseFrontmatter(relPath: string, fm: string, warnings: string[]): { data: Record<string, unknown>; parseError: string | null } {\n // logLevel silences yaml's own pathless warnings; warnStringifiedKeys re-reports the one\n // that carries information, with the file it came from.\n const doc = parseDocument(fm, { logLevel: 'silent' });\n const refused = doc.errors.filter((err) => !ACCEPTED_YAML_CODES.has(err.code));\n if (refused.length > 0) {\n const detail = refused.length > 1 ? ` (and ${refused.length - 1} more)` : '';\n const parseError = `${firstLine(refused[0].message)}${detail}`;\n warnings.push(`warning: ${relPath} frontmatter did not parse, so none of it is indexed: ${parseError}`);\n return { data: {}, parseError };\n }\n\n let data: unknown;\n try {\n data = doc.toJS();\n } catch (err) {\n // Reaches here with doc.errors empty: `title: **Bold**` parses, then opens an alias on\n // materialisation. An empty error list is not a successful parse.\n const parseError = firstLine((err as Error).message);\n warnings.push(`warning: ${relPath} frontmatter did not parse, so none of it is indexed: ${parseError}`);\n return { data: {}, parseError };\n }\n\n if (data === null || data === undefined) return { data: {}, parseError: null };\n if (typeof data !== 'object' || Array.isArray(data)) {\n const parseError = 'frontmatter is not a key-value mapping';\n warnings.push(`warning: ${relPath} ${parseError}; none of it is indexed`);\n return { data: {}, parseError };\n }\n warnStringifiedKeys(relPath, doc, warnings);\n return { data: data as Record<string, unknown>, parseError: null };\n}\n"],"names":["RESERVED_COLUMNS","countLines","looksLikeDatetime","mapValue","normalizeDate","normalizeText","parseFrontmatter","splitFrontmatter","Set","ACCEPTED_YAML_CODES","value","undefined","String","replace","trim","ISO_DATETIME","MEANT_AS_DATETIME","test","m","exec","date","time","zone","digits","offset","length","slice","normalized","Number","isNaN","Date","parse","BigInt","isSafeInteger","JSON","stringify","s","n","i","charCodeAt","raw","open","match","fm","body","rest","close","index","warnStringifiedKeys","relPath","doc","warnings","found","visit","Pair","_key","pair","isCollection","key","BREAK","push","firstLine","message","split","parseDocument","logLevel","refused","errors","filter","err","has","code","detail","parseError","data","toJS","Array","isArray"],"mappings":";;;;;;;;;;;QAIaA;eAAAA;;QA+CGC;eAAAA;;QA5BAC;eAAAA;;QAkBAC;eAAAA;;QAZAC;eAAAA;;QAnBAC;eAAAA;;QAgFAC;eAAAA;;QAhCAC;eAAAA;;;oBA1DmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAI5C,IAAMP,mBAAmB,IAAIQ,IAAI;IAAC;IAAQ;IAAU;IAAU;IAAS;IAAS;IAAgB;IAAW;IAAS;CAAW;AAEtI,wJAAwJ;AACxJ,iJAAiJ;AACjJ,IAAMC,sBAAsB,IAAID,IAAI;IAAC;CAAmB;AAEjD,SAASH,cAAcK,KAAc;IAC1C,IAAIA,UAAU,QAAQA,UAAUC,WAAW,OAAO;IAClD,OAAOC,OAAOF,OAAOG,OAAO,CAAC,QAAQ,KAAKC,IAAI;AAChD;AAEA,iGAAiG;AACjG,mGAAmG;AACnG,IAAMC,eAAe;AAErB,6FAA6F;AAC7F,6FAA6F;AAC7F,IAAMC,oBAAoB;AAEnB,SAASd,kBAAkBQ,KAAa;IAC7C,OAAOM,kBAAkBC,IAAI,CAACP;AAChC;AAIO,SAASN,cAAcM,KAAa;IACzC,IAAMQ,IAAIH,aAAaI,IAAI,CAACT;IAC5B,IAAIQ,MAAM,MAAM,OAAOR;IACvB,IAA6BQ,sBAAAA,OAApBE,OAAoBF,OAAdG,OAAcH,OAARI,OAAQJ;IAC7B,IAAMK,SAASD,SAASX,aAAaW,SAAS,MAAM,KAAKA,KAAKT,OAAO,CAAC,KAAK;IAC3E,IAAMW,SAASD,WAAW,KAAMD,iBAAAA,kBAAAA,OAAQ,KAAMC,OAAOE,MAAM,KAAK,IAAI,AAAC,GAAS,OAAPF,QAAO,SAAO,AAAC,GAAwBA,OAAtBA,OAAOG,KAAK,CAAC,GAAG,IAAG,KAAmB,OAAhBH,OAAOG,KAAK,CAAC;IAC3H,IAAMC,aAAa,AAAC,GAAUN,OAARD,MAAK,KAAUI,OAAPH,MAAc,OAAPG;IACrC,OAAOI,OAAOC,KAAK,CAACC,KAAKC,KAAK,CAACJ,eAAejB,QAAQiB;AACxD;AAIO,SAASxB,SAASO,KAAc;IACrC,IAAIA,UAAU,QAAQA,UAAUC,WAAW,OAAO;IAClD,IAAI,OAAOD,UAAU,WAAW,OAAOsB,OAAOtB,QAAQ,IAAI;IAC1D,IAAI,OAAOA,UAAU,UAAU,OAAOkB,OAAOK,aAAa,CAACvB,SAASsB,OAAOtB,SAASA;IACpF,IAAI,OAAOA,UAAU,UAAU,OAAON,cAAcM;IACpD,OAAOwB,KAAKC,SAAS,CAACzB;AACxB;AAIO,SAAST,WAAWmC,CAAS;IAClC,IAAIC,IAAI;IACR,IAAK,IAAIC,IAAI,GAAGA,IAAIF,EAAEX,MAAM,EAAEa,IAAK,IAAIF,EAAEG,UAAU,CAACD,OAAO,IAAID;IAC/D,OAAOA;AACT;AAGO,SAAS9B,iBAAiBiC,GAAW;IAC1C,IAAMC,OAAOD,IAAIE,KAAK,CAAC;IACvB,IAAI,CAACD,MAAM,OAAO;QAAEE,IAAI;QAAMC,MAAMJ;IAAI;IACxC,IAAMK,OAAOL,IAAId,KAAK,CAACe,IAAI,CAAC,EAAE,CAAChB,MAAM;IACrC,IAAMqB,QAAQD,KAAKH,KAAK,CAAC;IACzB,IAAI,CAACI,SAASA,MAAMC,KAAK,KAAKpC,WAAW,OAAO;QAAEgC,IAAI;QAAMC,MAAMJ;IAAI;IACtE,OAAO;QAAEG,IAAIE,KAAKnB,KAAK,CAAC,GAAGoB,MAAMC,KAAK;QAAGH,MAAMC,KAAKnB,KAAK,CAACoB,MAAMC,KAAK,GAAGD,KAAK,CAAC,EAAE,CAACrB,MAAM;IAAE;AAC3F;AAEA,qLAAqL;AACrL,2IAA2I;AAC3I,SAASuB,oBAAoBC,OAAe,EAAEC,GAAqC,EAAEC,QAAkB;IACrG,IAAIC,QAAQ;IACZ,4FAA4F;IAC5F,yCAAyC;IACzCC,IAAAA,WAAK,EAACH,KAAK;QACTI,MAAAA,SAAAA,KAAKC,IAAI,EAAEC,IAAI;YACb,IAAI,CAACC,IAAAA,kBAAY,EAACD,KAAKE,GAAG,GAAG,OAAO/C;YACpCyC,QAAQ;YACR,OAAOC,WAAK,CAACM,KAAK;QACpB;IACF;IACA,8EAA8E;IAC9E,IAAIP,OAAOD,SAASS,IAAI,CAAC,AAAC,YAAmB,OAARX,SAAQ;AAC/C;AAEA,oSAAoS;AACpS,6IAA6I;AAC7I,SAASY,UAAUC,OAAe;IAChC,OAAOA,QAAQC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAClD,OAAO,CAAC,SAAS;AACjD;AAEO,SAASP,iBAAiB2C,OAAe,EAAEN,EAAU,EAAEQ,QAAkB;IAC9E,yFAAyF;IACzF,wDAAwD;IACxD,IAAMD,MAAMc,IAAAA,mBAAa,EAACrB,IAAI;QAAEsB,UAAU;IAAS;IACnD,IAAMC,UAAUhB,IAAIiB,MAAM,CAACC,MAAM,CAAC,SAACC;eAAQ,CAAC5D,oBAAoB6D,GAAG,CAACD,IAAIE,IAAI;;IAC5E,IAAIL,QAAQzC,MAAM,GAAG,GAAG;QACtB,IAAM+C,SAASN,QAAQzC,MAAM,GAAG,IAAI,AAAC,SAA2B,OAAnByC,QAAQzC,MAAM,GAAG,GAAE,YAAU;QAC1E,IAAMgD,aAAa,AAAC,GAAkCD,OAAhCX,UAAUK,OAAO,CAAC,EAAE,CAACJ,OAAO,GAAW,OAAPU;QACtDrB,SAASS,IAAI,CAAC,AAAC,YAA2Ea,OAAhExB,SAAQ,0DAAmE,OAAXwB;QAC1F,OAAO;YAAEC,MAAM,CAAC;YAAGD,YAAAA;QAAW;IAChC;IAEA,IAAIC;IACJ,IAAI;QACFA,OAAOxB,IAAIyB,IAAI;IACjB,EAAE,OAAON,KAAK;QACZ,uFAAuF;QACvF,kEAAkE;QAClE,IAAMI,cAAaZ,UAAU,AAACQ,IAAcP,OAAO;QACnDX,SAASS,IAAI,CAAC,AAAC,YAA2Ea,OAAhExB,SAAQ,0DAAmE,OAAXwB;QAC1F,OAAO;YAAEC,MAAM,CAAC;YAAGD,YAAAA;QAAW;IAChC;IAEA,IAAIC,SAAS,QAAQA,SAAS/D,WAAW,OAAO;QAAE+D,MAAM,CAAC;QAAGD,YAAY;IAAK;IAC7E,IAAI,CAAA,OAAOC,qCAAP,SAAOA,KAAG,MAAM,YAAYE,MAAMC,OAAO,CAACH,OAAO;QACnD,IAAMD,cAAa;QACnBtB,SAASS,IAAI,CAAC,AAAC,YAAsBa,OAAXxB,SAAQ,KAAc,OAAXwB,aAAW;QAChD,OAAO;YAAEC,MAAM,CAAC;YAAGD,YAAAA;QAAW;IAChC;IACAzB,oBAAoBC,SAASC,KAAKC;IAClC,OAAO;QAAEuB,MAAMA;QAAiCD,YAAY;IAAK;AACnE"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/frontmatter.ts"],"sourcesContent":["import { isCollection, parseDocument, visit } from 'yaml';\n\n// Frontmatter keys that would collide with table columns. Exported so reconcile.ts's upsert can tell\n// a feature-owned column (`_rank`) from a parsed one and leave it alone on reparse.\nexport const RESERVED_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_rank', '_parse_error', 'content', 'links', 'sections']);\n\n// Only BAD_SCALAR_START qualifies: YAML 1.2 reserves `@` and `` ` `` at the start of a plain scalar, so `aliases: [@handle]` can only mean [\"@handle\"].\n// Every other code has a second reading (a swallowed key, a dropped URL, a silently overwritten duplicate) that would write values nobody wrote.\nconst ACCEPTED_YAML_CODES = new Set(['BAD_SCALAR_START']);\n\nexport function normalizeText(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value).replace(/\\s+/g, ' ').trim();\n}\n\n// SQLite's datetime() rejects a colonless offset (`-0800`) and a space separator, which ISO 8601\n// allows and producers emit. A rejected date is invisible, not excluded: every comparison is NULL.\nconst ISO_DATETIME = /^(\\d{4}-\\d{2}-\\d{2})[T ](\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?)(Z|[+-]\\d{2}(?::?\\d{2})?)?$/;\n\n// A value opening `YYYY-MM-DDT` was meant to be a datetime; prose never is. Reported when it\n// cannot be normalized, so a typo surfaces on the next crawl instead of at some later audit.\nconst MEANT_AS_DATETIME = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d/;\n\nexport function looksLikeDatetime(value: string): boolean {\n return MEANT_AS_DATETIME.test(value);\n}\n\n// Punctuation only, never a timezone conversion: the offset survives, so substr(d,1,10) is still\n// the local date. A shape that is not a real instant is left as written, and stays auditable.\nexport function normalizeDate(value: string): string {\n const m = ISO_DATETIME.exec(value);\n if (m === null) return value;\n const [, date, time, zone] = m;\n const digits = zone === undefined || zone === 'Z' ? '' : zone.replace(':', '');\n const offset = digits === '' ? (zone ?? '') : digits.length === 3 ? `${digits}:00` : `${digits.slice(0, 3)}:${digits.slice(3)}`;\n const normalized = `${date}T${time}${offset}`;\n return Number.isNaN(Date.parse(normalized)) ? value : normalized;\n}\n\n// Storage class follows the YAML scalar. Booleans store as 1/0, so `WHERE flag = 1` matches\n// and `WHERE flag = 'true'` cannot; `map` prints observed types so the mismatch is visible.\nexport function mapValue(value: unknown): string | number | bigint | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'boolean') return BigInt(value ? 1 : 0);\n if (typeof value === 'number') return Number.isSafeInteger(value) ? BigInt(value) : value;\n if (typeof value === 'string') return normalizeDate(value);\n return JSON.stringify(value);\n}\n\n// Exactly `s.split('\\n').length` without allocating the array: callers that need only the count\n// run it per file per feature on the parse hot path.\nexport function countLines(s: string): number {\n let n = 1;\n for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 10) n++;\n return n;\n}\n\n// The delimiter split is all this package used gray-matter for.\nexport function splitFrontmatter(raw: string): { fm: string | null; body: string } {\n const open = raw.match(/^---\\r?\\n/);\n if (!open) return { fm: null, body: raw };\n const rest = raw.slice(open[0].length);\n const close = rest.match(/^---\\r?(\\n|$)/m);\n if (!close || close.index === undefined) return { fm: null, body: raw };\n return { fm: rest.slice(0, close.index), body: rest.slice(close.index + close[0].length) };\n}\n\n// A well-formed document can hold a value nobody meant: `created: {{date}}` is valid YAML for a flow map used as a mapping key, so it raises no error and stores {\"{ date }\": null}.\n// No error code catches that; yaml's own warning lacks a path and fires once per document, so this reports it here instead, with the path.\nfunction warnStringifiedKeys(relPath: string, doc: ReturnType<typeof parseDocument>, warnings: string[]): void {\n let found = false;\n // Nested, not top level: `created: {{date}}` puts the collection key one level down, inside\n // the flow map that `{{...}}` parses as.\n visit(doc, {\n Pair(_key, pair) {\n if (!isCollection(pair.key)) return undefined;\n found = true;\n return visit.BREAK;\n },\n });\n // One per file: a template repeats the same mistake on every field it stamps.\n if (found) warnings.push(`warning: ${relPath} frontmatter has a key that is itself a list or mapping, stored as text; this is usually an unrendered template placeholder like {{date}}`);\n}\n\n// Anything past ACCEPTED_YAML_CODES is quarantined: no frontmatter columns, `_parse_error` carries the reason. Recovering it would write values nobody wrote, worse than absence since no query can see it; content, links and sections never touch frontmatter, so a broken note stays searchable.\n// yaml's message continues onto a source snippet, so the first line is the sentence, minus the colon that introduced the part being dropped.\nfunction firstLine(message: string): string {\n return message.split('\\n')[0].replace(/:\\s*$/, '');\n}\n\nexport function parseFrontmatter(relPath: string, fm: string, warnings: string[]): { data: Record<string, unknown>; parseError: string | null } {\n // logLevel silences yaml's own pathless warnings; warnStringifiedKeys re-reports the one\n // that carries information, with the file it came from.\n const doc = parseDocument(fm, { logLevel: 'silent' });\n const refused = doc.errors.filter((err) => !ACCEPTED_YAML_CODES.has(err.code));\n if (refused.length > 0) {\n const detail = refused.length > 1 ? ` (and ${refused.length - 1} more)` : '';\n const parseError = `${firstLine(refused[0].message)}${detail}`;\n warnings.push(`warning: ${relPath} frontmatter did not parse, so none of it is indexed: ${parseError}`);\n return { data: {}, parseError };\n }\n\n let data: unknown;\n try {\n data = doc.toJS();\n } catch (err) {\n // Reaches here with doc.errors empty: `title: **Bold**` parses, then opens an alias on\n // materialisation. An empty error list is not a successful parse.\n const parseError = firstLine((err as Error).message);\n warnings.push(`warning: ${relPath} frontmatter did not parse, so none of it is indexed: ${parseError}`);\n return { data: {}, parseError };\n }\n\n if (data === null || data === undefined) return { data: {}, parseError: null };\n if (typeof data !== 'object' || Array.isArray(data)) {\n const parseError = 'frontmatter is not a key-value mapping';\n warnings.push(`warning: ${relPath} ${parseError}; none of it is indexed`);\n return { data: {}, parseError };\n }\n warnStringifiedKeys(relPath, doc, warnings);\n return { data: data as Record<string, unknown>, parseError: null };\n}\n"],"names":["RESERVED_COLUMNS","countLines","looksLikeDatetime","mapValue","normalizeDate","normalizeText","parseFrontmatter","splitFrontmatter","Set","ACCEPTED_YAML_CODES","value","undefined","String","replace","trim","ISO_DATETIME","MEANT_AS_DATETIME","test","m","exec","date","time","zone","digits","offset","length","slice","normalized","Number","isNaN","Date","parse","BigInt","isSafeInteger","JSON","stringify","s","n","i","charCodeAt","raw","open","match","fm","body","rest","close","index","warnStringifiedKeys","relPath","doc","warnings","found","visit","Pair","_key","pair","isCollection","key","BREAK","push","firstLine","message","split","parseDocument","logLevel","refused","errors","filter","err","has","code","detail","parseError","data","toJS","Array","isArray"],"mappings":"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"}
@@ -35,6 +35,7 @@ var _frontmatterts = require("./frontmatter.js");
35
35
  var _listts = require("./list.js");
36
36
  function parseFile(file) {
37
37
  var extractors = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : [], cfg = arguments.length > 2 ? arguments[2] : void 0;
38
+ // Preserve the authored bytes: feature extractors and section offsets use the raw document.
38
39
  var raw = (0, _nodefs.readFileSync)(file.absPath, 'utf8');
39
40
  var warnings = [];
40
41
  var _splitFrontmatter = (0, _frontmatterts.splitFrontmatter)(raw), fm = _splitFrontmatter.fm, content = _splitFrontmatter.body;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { parse } from '../chunk/index.ts';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport { textFromBlocks } from '../text/strip.ts';\nimport { looksLikeDatetime, mapValue, normalizeText, parseFrontmatter, RESERVED_COLUMNS, splitFrontmatter } from './frontmatter.ts';\nimport type { FileStat } from './list.ts';\n\n// Filesystem -> rows. Pure data in, data + warnings out; store/sqlite/reconcile.ts does the SQL.\n\nexport { looksLikeDatetime, normalizeDate, RESERVED_COLUMNS } from './frontmatter.ts';\nexport type { FileStat } from './list.ts';\nexport { listFiles, toPosixPath } from './list.ts';\n\nexport interface ParsedDoc {\n relPath: string;\n mtimeMs: number;\n ctimeMs: number;\n size: number;\n presets: string[];\n data: Record<string, string | number | bigint | null>;\n // NULL when the frontmatter parsed, the first YAML message otherwise. In the row rather than\n // a side table so `SELECT *` and any `IS NULL` investigation trip over it without being asked.\n parseError: string | null;\n // title/summary are duplicated from frontmatter so bm25() can weight them above the body text.\n search: { title: string; summary: string; text: string };\n // Per-feature extraction results, keyed by feature name; features store them at reconcile.\n extracted: Record<string, unknown>;\n}\n\nexport function parseFile(file: FileStat, extractors: Feature[] = [], cfg?: Config): { doc: ParsedDoc; warnings: string[] } {\n const raw = readFileSync(file.absPath, 'utf8');\n const warnings: string[] = [];\n\n const { fm, body: content } = splitFrontmatter(raw);\n const { data, parseError } = fm === null ? { data: {} as Record<string, unknown>, parseError: null } : parseFrontmatter(file.relPath, fm, warnings);\n const mapped: Record<string, string | number | bigint | null> = {};\n\n for (const key of Object.keys(data)) {\n if (RESERVED_COLUMNS.has(key)) {\n warnings.push(`warning: ${file.relPath} has a frontmatter key named \"${key}\", which is reserved; ignoring it`);\n continue;\n }\n const value = mapValue(data[key]);\n if (typeof value === 'string' && looksLikeDatetime(value) && Number.isNaN(Date.parse(value))) {\n warnings.push(`warning: ${file.relPath}: ${key} is not a valid date (${value}), so it is invisible to every date comparison`);\n }\n mapped[key] = value;\n }\n\n // Parsed once and shared: the FTS text path (textFromBlocks, ~ strip.ts) and any feature\n // that needs a parse tree (embed's chunker) both read this same block list.\n const blocks = parse(content);\n\n // title/summary are plain YAML strings -- whitespace-collapse only;\n // the prose gets the full markdown strip.\n const search = { title: normalizeText(data.title), summary: normalizeText(data.summary), text: textFromBlocks(blocks) };\n\n return {\n doc: {\n relPath: file.relPath,\n mtimeMs: file.mtimeMs,\n ctimeMs: file.ctimeMs,\n size: file.size,\n presets: file.presets,\n data: mapped,\n parseError,\n search,\n extracted: Object.fromEntries(extractors.filter((f) => f.extract).map((f) => [f.name, f.extract?.(raw, content, search, data, cfg, blocks)])),\n },\n warnings,\n };\n}\n"],"names":["RESERVED_COLUMNS","listFiles","looksLikeDatetime","normalizeDate","parseFile","toPosixPath","file","extractors","cfg","raw","readFileSync","absPath","warnings","splitFrontmatter","fm","body","content","data","parseError","parseFrontmatter","relPath","mapped","Object","keys","key","has","push","value","mapValue","Number","isNaN","Date","parse","blocks","search","title","normalizeText","summary","text","textFromBlocks","doc","mtimeMs","ctimeMs","size","presets","extracted","fromEntries","filter","f","extract","map","name"],"mappings":";;;;;;;;;;;QAU2CA;eAAAA,+BAAgB;;QAElDC;eAAAA,iBAAS;;QAFTC;eAAAA,gCAAiB;;QAAEC;eAAAA,4BAAa;;QAoBzBC;eAAAA;;QAlBIC;eAAAA,mBAAW;;;sBAZF;uBACP;uBAGS;6BACkF;sBAO1E;AAkBhC,SAASD,UAAUE,IAAc;QAAEC,aAAAA,iEAAwB,EAAE,EAAEC;IACpE,IAAMC,MAAMC,IAAAA,oBAAY,EAACJ,KAAKK,OAAO,EAAE;IACvC,IAAMC,WAAqB,EAAE;IAE7B,IAA8BC,oBAAAA,IAAAA,+BAAgB,EAACJ,MAAvCK,KAAsBD,kBAAtBC,IAAIC,AAAMC,UAAYH,kBAAlBE;IACZ,IAA6BD,OAAAA,OAAO,OAAO;QAAEG,MAAM,CAAC;QAA8BC,YAAY;IAAK,IAAIC,IAAAA,+BAAgB,EAACb,KAAKc,OAAO,EAAEN,IAAIF,WAAlIK,OAAqBH,KAArBG,MAAMC,aAAeJ,KAAfI;IACd,IAAMG,SAA0D,CAAC;QAE5D,kCAAA,2BAAA;;QAAL,QAAK,YAAaC,OAAOC,IAAI,CAACN,0BAAzB,SAAA,6BAAA,QAAA,yBAAA,iCAAgC;YAAhC,IAAMO,MAAN;YACH,IAAIxB,+BAAgB,CAACyB,GAAG,CAACD,MAAM;gBAC7BZ,SAASc,IAAI,CAAC,AAAC,YAAwDF,OAA7ClB,KAAKc,OAAO,EAAC,kCAAoC,OAAJI,KAAI;gBAC3E;YACF;YACA,IAAMG,QAAQC,IAAAA,uBAAQ,EAACX,IAAI,CAACO,IAAI;YAChC,IAAI,OAAOG,UAAU,YAAYzB,IAAAA,gCAAiB,EAACyB,UAAUE,OAAOC,KAAK,CAACC,KAAKC,KAAK,CAACL,SAAS;gBAC5Ff,SAASc,IAAI,CAAC,AAAC,YAA4BF,OAAjBlB,KAAKc,OAAO,EAAC,MAAgCO,OAA5BH,KAAI,0BAA8B,OAANG,OAAM;YAC/E;YACAN,MAAM,CAACG,IAAI,GAAGG;QAChB;;QAVK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAYL,yFAAyF;IACzF,4EAA4E;IAC5E,IAAMM,SAASD,IAAAA,cAAK,EAAChB;IAErB,oEAAoE;IACpE,0CAA0C;IAC1C,IAAMkB,SAAS;QAAEC,OAAOC,IAAAA,4BAAa,EAACnB,KAAKkB,KAAK;QAAGE,SAASD,IAAAA,4BAAa,EAACnB,KAAKoB,OAAO;QAAGC,MAAMC,IAAAA,uBAAc,EAACN;IAAQ;IAEtH,OAAO;QACLO,KAAK;YACHpB,SAASd,KAAKc,OAAO;YACrBqB,SAASnC,KAAKmC,OAAO;YACrBC,SAASpC,KAAKoC,OAAO;YACrBC,MAAMrC,KAAKqC,IAAI;YACfC,SAAStC,KAAKsC,OAAO;YACrB3B,MAAMI;YACNH,YAAAA;YACAgB,QAAAA;YACAW,WAAWvB,OAAOwB,WAAW,CAACvC,WAAWwC,MAAM,CAAC,SAACC;uBAAMA,EAAEC,OAAO;eAAEC,GAAG,CAAC,SAACF;oBAAeA;uBAAT;oBAACA,EAAEG,IAAI;qBAAEH,aAAAA,EAAEC,OAAO,cAATD,iCAAAA,gBAAAA,GAAYvC,KAAKO,SAASkB,QAAQjB,MAAMT,KAAKyB;iBAAQ;;QAC7I;QACArB,UAAAA;IACF;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { parse } from '../chunk/index.ts';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport { textFromBlocks } from '../text/strip.ts';\nimport { looksLikeDatetime, mapValue, normalizeText, parseFrontmatter, RESERVED_COLUMNS, splitFrontmatter } from './frontmatter.ts';\nimport type { FileStat } from './list.ts';\n\n// Filesystem -> rows. Pure data in, data + warnings out; store/sqlite/reconcile.ts does the SQL.\n\nexport { looksLikeDatetime, normalizeDate, RESERVED_COLUMNS } from './frontmatter.ts';\nexport type { FileStat } from './list.ts';\nexport { listFiles, toPosixPath } from './list.ts';\n\nexport interface ParsedDoc {\n relPath: string;\n mtimeMs: number;\n ctimeMs: number;\n size: number;\n presets: string[];\n data: Record<string, string | number | bigint | null>;\n // NULL when the frontmatter parsed, the first YAML message otherwise. In the row rather than\n // a side table so `SELECT *` and any `IS NULL` investigation trip over it without being asked.\n parseError: string | null;\n // title/summary are duplicated from frontmatter so bm25() can weight them above the body text.\n search: { title: string; summary: string; text: string };\n // Per-feature extraction results, keyed by feature name; features store them at reconcile.\n extracted: Record<string, unknown>;\n}\n\nexport function parseFile(file: FileStat, extractors: Feature[] = [], cfg?: Config): { doc: ParsedDoc; warnings: string[] } {\n // Preserve the authored bytes: feature extractors and section offsets use the raw document.\n const raw = readFileSync(file.absPath, 'utf8');\n const warnings: string[] = [];\n\n const { fm, body: content } = splitFrontmatter(raw);\n const { data, parseError } = fm === null ? { data: {} as Record<string, unknown>, parseError: null } : parseFrontmatter(file.relPath, fm, warnings);\n const mapped: Record<string, string | number | bigint | null> = {};\n\n for (const key of Object.keys(data)) {\n if (RESERVED_COLUMNS.has(key)) {\n warnings.push(`warning: ${file.relPath} has a frontmatter key named \"${key}\", which is reserved; ignoring it`);\n continue;\n }\n const value = mapValue(data[key]);\n if (typeof value === 'string' && looksLikeDatetime(value) && Number.isNaN(Date.parse(value))) {\n warnings.push(`warning: ${file.relPath}: ${key} is not a valid date (${value}), so it is invisible to every date comparison`);\n }\n mapped[key] = value;\n }\n\n // Parsed once and shared: the FTS text path (textFromBlocks, ~ strip.ts) and any feature\n // that needs a parse tree (embed's chunker) both read this same block list.\n const blocks = parse(content);\n\n // title/summary are plain YAML strings -- whitespace-collapse only;\n // the prose gets the full markdown strip.\n const search = { title: normalizeText(data.title), summary: normalizeText(data.summary), text: textFromBlocks(blocks) };\n\n return {\n doc: {\n relPath: file.relPath,\n mtimeMs: file.mtimeMs,\n ctimeMs: file.ctimeMs,\n size: file.size,\n presets: file.presets,\n data: mapped,\n parseError,\n search,\n extracted: Object.fromEntries(extractors.filter((f) => f.extract).map((f) => [f.name, f.extract?.(raw, content, search, data, cfg, blocks)])),\n },\n warnings,\n };\n}\n"],"names":["RESERVED_COLUMNS","listFiles","looksLikeDatetime","normalizeDate","parseFile","toPosixPath","file","extractors","cfg","raw","readFileSync","absPath","warnings","splitFrontmatter","fm","body","content","data","parseError","parseFrontmatter","relPath","mapped","Object","keys","key","has","push","value","mapValue","Number","isNaN","Date","parse","blocks","search","title","normalizeText","summary","text","textFromBlocks","doc","mtimeMs","ctimeMs","size","presets","extracted","fromEntries","filter","f","extract","map","name"],"mappings":";;;;;;;;;;;QAU2CA;eAAAA,+BAAgB;;QAElDC;eAAAA,iBAAS;;QAFTC;eAAAA,gCAAiB;;QAAEC;eAAAA,4BAAa;;QAoBzBC;eAAAA;;QAlBIC;eAAAA,mBAAW;;;sBAZF;uBACP;uBAGS;6BACkF;sBAO1E;AAkBhC,SAASD,UAAUE,IAAc;QAAEC,aAAAA,iEAAwB,EAAE,EAAEC;IACpE,4FAA4F;IAC5F,IAAMC,MAAMC,IAAAA,oBAAY,EAACJ,KAAKK,OAAO,EAAE;IACvC,IAAMC,WAAqB,EAAE;IAE7B,IAA8BC,oBAAAA,IAAAA,+BAAgB,EAACJ,MAAvCK,KAAsBD,kBAAtBC,IAAIC,AAAMC,UAAYH,kBAAlBE;IACZ,IAA6BD,OAAAA,OAAO,OAAO;QAAEG,MAAM,CAAC;QAA8BC,YAAY;IAAK,IAAIC,IAAAA,+BAAgB,EAACb,KAAKc,OAAO,EAAEN,IAAIF,WAAlIK,OAAqBH,KAArBG,MAAMC,aAAeJ,KAAfI;IACd,IAAMG,SAA0D,CAAC;QAE5D,kCAAA,2BAAA;;QAAL,QAAK,YAAaC,OAAOC,IAAI,CAACN,0BAAzB,SAAA,6BAAA,QAAA,yBAAA,iCAAgC;YAAhC,IAAMO,MAAN;YACH,IAAIxB,+BAAgB,CAACyB,GAAG,CAACD,MAAM;gBAC7BZ,SAASc,IAAI,CAAC,AAAC,YAAwDF,OAA7ClB,KAAKc,OAAO,EAAC,kCAAoC,OAAJI,KAAI;gBAC3E;YACF;YACA,IAAMG,QAAQC,IAAAA,uBAAQ,EAACX,IAAI,CAACO,IAAI;YAChC,IAAI,OAAOG,UAAU,YAAYzB,IAAAA,gCAAiB,EAACyB,UAAUE,OAAOC,KAAK,CAACC,KAAKC,KAAK,CAACL,SAAS;gBAC5Ff,SAASc,IAAI,CAAC,AAAC,YAA4BF,OAAjBlB,KAAKc,OAAO,EAAC,MAAgCO,OAA5BH,KAAI,0BAA8B,OAANG,OAAM;YAC/E;YACAN,MAAM,CAACG,IAAI,GAAGG;QAChB;;QAVK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAYL,yFAAyF;IACzF,4EAA4E;IAC5E,IAAMM,SAASD,IAAAA,cAAK,EAAChB;IAErB,oEAAoE;IACpE,0CAA0C;IAC1C,IAAMkB,SAAS;QAAEC,OAAOC,IAAAA,4BAAa,EAACnB,KAAKkB,KAAK;QAAGE,SAASD,IAAAA,4BAAa,EAACnB,KAAKoB,OAAO;QAAGC,MAAMC,IAAAA,uBAAc,EAACN;IAAQ;IAEtH,OAAO;QACLO,KAAK;YACHpB,SAASd,KAAKc,OAAO;YACrBqB,SAASnC,KAAKmC,OAAO;YACrBC,SAASpC,KAAKoC,OAAO;YACrBC,MAAMrC,KAAKqC,IAAI;YACfC,SAAStC,KAAKsC,OAAO;YACrB3B,MAAMI;YACNH,YAAAA;YACAgB,QAAAA;YACAW,WAAWvB,OAAOwB,WAAW,CAACvC,WAAWwC,MAAM,CAAC,SAACC;uBAAMA,EAAEC,OAAO;eAAEC,GAAG,CAAC,SAACF;oBAAeA;uBAAT;oBAACA,EAAEG,IAAI;qBAAEH,aAAAA,EAAEC,OAAO,cAATD,iCAAAA,gBAAAA,GAAYvC,KAAKO,SAASkB,QAAQjB,MAAMT,KAAKyB;iBAAQ;;QAC7I;QACArB,UAAAA;IACF;AACF"}
@@ -202,13 +202,10 @@ function classifyVariantType(name, field) {
202
202
  if (name === 'VARCHAR') return 'text';
203
203
  throw new Error('duckdb: variant_typeof() returned unrecognized type "'.concat(name, '" for column "').concat(field, '"'));
204
204
  }
205
- // duckdb degrades superlinearly in the number of string_agg(DISTINCT variant_typeof()) aggregates in
206
- // one projection: 301 columns cost 1148ms in one query against 10ms for COUNT alone over the same
207
- // columns, so it is the aggregate count, not the width. Measured chunk sizes at 301 columns:
208
- // 10 -> 71ms, 16 -> 81ms, 25 -> 88ms, 40 -> 110ms, 64 -> 158ms, flat between 10 and 25. Chunking
209
- // also wins on an ordinary tree (31 columns: 17ms in one query, 11ms chunked), so there is no
210
- // small-tree cost to trade against. sqlite and turso need none of this: GROUP_CONCAT over 300
211
- // columns runs in 7ms.
205
+ // duckdb degrades superlinearly in the number of string_agg(DISTINCT variant_typeof()) aggregates
206
+ // per projection, not the column width (301 columns: 1148ms unchunked vs 88ms at chunk size 25);
207
+ // 16 sits inside the measured flat 10-25 range. sqlite/turso need none of this: GROUP_CONCAT over
208
+ // 300 columns runs in 7ms.
212
209
  var AGGREGATES_PER_QUERY = 16;
213
210
  function fieldStats(conn, columns, scopeWhere) {
214
211
  return _async_to_generator(function() {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/fieldStats.ts"],"sourcesContent":["import { quoteIdent } from '../shared.ts';\nimport type { Connection, FieldStat } from '../types.ts';\n\n// DuckDB's frontmatter columns are VARIANT (reconcile.ts), so typeof() always reports VARIANT; variant_typeof() reports the boxed native\n// type instead -- INT128, DOUBLE, VARCHAR, or a literal's narrower type like DECIMAL(p, s) -- mapped onto sqlite/turso's integer/real/text vocabulary; an unrecognized name throws (no-silent-modes).\nfunction classifyVariantType(name: string, field: string): 'integer' | 'real' | 'text' {\n if (/^U?INT\\d+$/.test(name)) return 'integer';\n if (name === 'FLOAT' || name === 'DOUBLE' || name.startsWith('DECIMAL(')) return 'real';\n if (name === 'VARCHAR') return 'text';\n throw new Error(`duckdb: variant_typeof() returned unrecognized type \"${name}\" for column \"${field}\"`);\n}\n\n// duckdb degrades superlinearly in the number of string_agg(DISTINCT variant_typeof()) aggregates in\n// one projection: 301 columns cost 1148ms in one query against 10ms for COUNT alone over the same\n// columns, so it is the aggregate count, not the width. Measured chunk sizes at 301 columns:\n// 10 -> 71ms, 16 -> 81ms, 25 -> 88ms, 40 -> 110ms, 64 -> 158ms, flat between 10 and 25. Chunking\n// also wins on an ordinary tree (31 columns: 17ms in one query, 11ms chunked), so there is no\n// small-tree cost to trade against. sqlite and turso need none of this: GROUP_CONCAT over 300\n// columns runs in 7ms.\nconst AGGREGATES_PER_QUERY = 16;\n\n// COUNT + string_agg(DISTINCT variant_typeof()) per column keeps this O(columns), not O(rows x columns).\n// '|' is the separator, not ',': DECIMAL's display name (\"DECIMAL(4, 2)\") contains a comma.\nexport async function fieldStats(conn: Connection, columns: string[], scopeWhere: string): Promise<FieldStat[]> {\n const stats: FieldStat[] = [];\n for (let start = 0; start < columns.length; start += AGGREGATES_PER_QUERY) {\n const chunk = columns.slice(start, start + AGGREGATES_PER_QUERY);\n const exprs = chunk.map((name, i) => {\n const quoted = quoteIdent(name);\n return `COUNT(${quoted}) AS n${i}, string_agg(DISTINCT variant_typeof(${quoted}), '|') FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const stmt = await conn.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`);\n const row = (await stmt.get()) as Record<string, number | string | null>;\n for (const [i, name] of chunk.entries()) {\n const raw = ((row[`t${i}`] as string) ?? '').split('|').filter(Boolean);\n const types = [...new Set(raw.map((t) => classifyVariantType(t, name)))].sort();\n stats.push({ field: name, coverage: Number(row[`n${i}`] ?? 0), type: types.join(',') });\n }\n }\n return stats;\n}\n"],"names":["fieldStats","classifyVariantType","name","field","test","startsWith","Error","AGGREGATES_PER_QUERY","conn","columns","scopeWhere","stats","start","chunk","exprs","stmt","row","length","slice","map","i","quoted","quoteIdent","prepare","join","get","raw","split","filter","Boolean","types","Set","t","sort","push","coverage","Number","type","entries"],"mappings":";;;;+BAuBsBA;;;eAAAA;;;wBAvBK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG3B,yIAAyI;AACzI,sMAAsM;AACtM,SAASC,oBAAoBC,IAAY,EAAEC,KAAa;IACtD,IAAI,aAAaC,IAAI,CAACF,OAAO,OAAO;IACpC,IAAIA,SAAS,WAAWA,SAAS,YAAYA,KAAKG,UAAU,CAAC,aAAa,OAAO;IACjF,IAAIH,SAAS,WAAW,OAAO;IAC/B,MAAM,IAAII,MAAM,AAAC,wDAA4EH,OAArBD,MAAK,kBAAsB,OAANC,OAAM;AACrG;AAEA,qGAAqG;AACrG,kGAAkG;AAClG,6FAA6F;AAC7F,iGAAiG;AACjG,8FAA8F;AAC9F,8FAA8F;AAC9F,uBAAuB;AACvB,IAAMI,uBAAuB;AAItB,SAAeP,WAAWQ,IAAgB,EAAEC,OAAiB,EAAEC,UAAkB;;YAChFC,OACGC,OACDC,OACAC,OAIAC,MACAC,KACD,2BAAA,mBAAA,uBAAA,WAAA;;;;oBATDL;oBACGC,QAAQ;;;yBAAGA,CAAAA,QAAQH,QAAQQ,MAAM,AAAD;;;;oBACjCJ,QAAQJ,QAAQS,KAAK,CAACN,OAAOA,QAAQL;oBACrCO,QAAQD,MAAMM,GAAG,CAAC,SAACjB,MAAMkB;wBAC7B,IAAMC,SAASC,IAAAA,oBAAU,EAACpB;wBAC1B,OAAO,AAAC,SAAuBkB,OAAfC,QAAO,UAAiDA,OAAzCD,GAAE,yCAAsEC,OAA/BA,QAAO,0BAAmDD,OAA3BC,QAAO,sBAAsB,OAAFD;oBACpI;oBACa;;wBAAMZ,KAAKe,OAAO,CAAC,AAAC,UAA8Cb,OAArCI,MAAMU,IAAI,CAAC,OAAM,sBAA+B,OAAXd;;;oBAAzEK,OAAO;oBACA;;wBAAMA,KAAKU,GAAG;;;oBAArBT,MAAO;oBACR,kCAAA,2BAAA;;;4BAAA,mCAAA,iBAAOI,oBAAGlB;gCACCc,OAE6BA;4BAF3C,IAAMU,MAAM,EAAEV,QAAAA,GAAG,CAAC,AAAC,IAAK,OAAFI,GAAI,cAAZJ,mBAAAA,QAA2B,IAAIW,KAAK,CAAC,KAAKC,MAAM,CAACC;4BAC/D,IAAMC,QAAQ,AAAC,qBAAG,IAAIC,IAAIL,IAAIP,GAAG,CAAC,SAACa;uCAAM/B,oBAAoB+B,GAAG9B;iCAAS+B,IAAI;4BAC7EtB,MAAMuB,IAAI,CAAC;gCAAE/B,OAAOD;gCAAMiC,UAAUC,QAAOpB,SAAAA,GAAG,CAAC,AAAC,IAAK,OAAFI,GAAI,cAAZJ,oBAAAA,SAAgB;gCAAIqB,MAAMP,MAAMN,IAAI,CAAC;4BAAK;wBACvF;wBAJA,IAAK,YAAmBX,MAAMyB,OAAO,yBAAhC,6BAAA,QAAA,yBAAA;;wBAAA;wBAAA;;;iCAAA,6BAAA;gCAAA;;;gCAAA;sCAAA;;;;;;oBARqC1B,SAASL;;;;;;oBAcrD;;wBAAOI;;;;IACT"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/fieldStats.ts"],"sourcesContent":["import { quoteIdent } from '../shared.ts';\nimport type { Connection, FieldStat } from '../types.ts';\n\n// DuckDB's frontmatter columns are VARIANT (reconcile.ts), so typeof() always reports VARIANT; variant_typeof() reports the boxed native\n// type instead -- INT128, DOUBLE, VARCHAR, or a literal's narrower type like DECIMAL(p, s) -- mapped onto sqlite/turso's integer/real/text vocabulary; an unrecognized name throws (no-silent-modes).\nfunction classifyVariantType(name: string, field: string): 'integer' | 'real' | 'text' {\n if (/^U?INT\\d+$/.test(name)) return 'integer';\n if (name === 'FLOAT' || name === 'DOUBLE' || name.startsWith('DECIMAL(')) return 'real';\n if (name === 'VARCHAR') return 'text';\n throw new Error(`duckdb: variant_typeof() returned unrecognized type \"${name}\" for column \"${field}\"`);\n}\n\n// duckdb degrades superlinearly in the number of string_agg(DISTINCT variant_typeof()) aggregates\n// per projection, not the column width (301 columns: 1148ms unchunked vs 88ms at chunk size 25);\n// 16 sits inside the measured flat 10-25 range. sqlite/turso need none of this: GROUP_CONCAT over\n// 300 columns runs in 7ms.\nconst AGGREGATES_PER_QUERY = 16;\n\n// COUNT + string_agg(DISTINCT variant_typeof()) per column keeps this O(columns), not O(rows x columns).\n// '|' is the separator, not ',': DECIMAL's display name (\"DECIMAL(4, 2)\") contains a comma.\nexport async function fieldStats(conn: Connection, columns: string[], scopeWhere: string): Promise<FieldStat[]> {\n const stats: FieldStat[] = [];\n for (let start = 0; start < columns.length; start += AGGREGATES_PER_QUERY) {\n const chunk = columns.slice(start, start + AGGREGATES_PER_QUERY);\n const exprs = chunk.map((name, i) => {\n const quoted = quoteIdent(name);\n return `COUNT(${quoted}) AS n${i}, string_agg(DISTINCT variant_typeof(${quoted}), '|') FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const stmt = await conn.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`);\n const row = (await stmt.get()) as Record<string, number | string | null>;\n for (const [i, name] of chunk.entries()) {\n const raw = ((row[`t${i}`] as string) ?? '').split('|').filter(Boolean);\n const types = [...new Set(raw.map((t) => classifyVariantType(t, name)))].sort();\n stats.push({ field: name, coverage: Number(row[`n${i}`] ?? 0), type: types.join(',') });\n }\n }\n return stats;\n}\n"],"names":["fieldStats","classifyVariantType","name","field","test","startsWith","Error","AGGREGATES_PER_QUERY","conn","columns","scopeWhere","stats","start","chunk","exprs","stmt","row","length","slice","map","i","quoted","quoteIdent","prepare","join","get","raw","split","filter","Boolean","types","Set","t","sort","push","coverage","Number","type","entries"],"mappings":";;;;+BAoBsBA;;;eAAAA;;;wBApBK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG3B,yIAAyI;AACzI,sMAAsM;AACtM,SAASC,oBAAoBC,IAAY,EAAEC,KAAa;IACtD,IAAI,aAAaC,IAAI,CAACF,OAAO,OAAO;IACpC,IAAIA,SAAS,WAAWA,SAAS,YAAYA,KAAKG,UAAU,CAAC,aAAa,OAAO;IACjF,IAAIH,SAAS,WAAW,OAAO;IAC/B,MAAM,IAAII,MAAM,AAAC,wDAA4EH,OAArBD,MAAK,kBAAsB,OAANC,OAAM;AACrG;AAEA,kGAAkG;AAClG,iGAAiG;AACjG,kGAAkG;AAClG,2BAA2B;AAC3B,IAAMI,uBAAuB;AAItB,SAAeP,WAAWQ,IAAgB,EAAEC,OAAiB,EAAEC,UAAkB;;YAChFC,OACGC,OACDC,OACAC,OAIAC,MACAC,KACD,2BAAA,mBAAA,uBAAA,WAAA;;;;oBATDL;oBACGC,QAAQ;;;yBAAGA,CAAAA,QAAQH,QAAQQ,MAAM,AAAD;;;;oBACjCJ,QAAQJ,QAAQS,KAAK,CAACN,OAAOA,QAAQL;oBACrCO,QAAQD,MAAMM,GAAG,CAAC,SAACjB,MAAMkB;wBAC7B,IAAMC,SAASC,IAAAA,oBAAU,EAACpB;wBAC1B,OAAO,AAAC,SAAuBkB,OAAfC,QAAO,UAAiDA,OAAzCD,GAAE,yCAAsEC,OAA/BA,QAAO,0BAAmDD,OAA3BC,QAAO,sBAAsB,OAAFD;oBACpI;oBACa;;wBAAMZ,KAAKe,OAAO,CAAC,AAAC,UAA8Cb,OAArCI,MAAMU,IAAI,CAAC,OAAM,sBAA+B,OAAXd;;;oBAAzEK,OAAO;oBACA;;wBAAMA,KAAKU,GAAG;;;oBAArBT,MAAO;oBACR,kCAAA,2BAAA;;;4BAAA,mCAAA,iBAAOI,oBAAGlB;gCACCc,OAE6BA;4BAF3C,IAAMU,MAAM,EAAEV,QAAAA,GAAG,CAAC,AAAC,IAAK,OAAFI,GAAI,cAAZJ,mBAAAA,QAA2B,IAAIW,KAAK,CAAC,KAAKC,MAAM,CAACC;4BAC/D,IAAMC,QAAQ,AAAC,qBAAG,IAAIC,IAAIL,IAAIP,GAAG,CAAC,SAACa;uCAAM/B,oBAAoB+B,GAAG9B;iCAAS+B,IAAI;4BAC7EtB,MAAMuB,IAAI,CAAC;gCAAE/B,OAAOD;gCAAMiC,UAAUC,QAAOpB,SAAAA,GAAG,CAAC,AAAC,IAAK,OAAFI,GAAI,cAAZJ,oBAAAA,SAAgB;gCAAIqB,MAAMP,MAAMN,IAAI,CAAC;4BAAK;wBACvF;wBAJA,IAAK,YAAmBX,MAAMyB,OAAO,yBAAhC,6BAAA,QAAA,yBAAA;;wBAAA;wBAAA;;;iCAAA,6BAAA;gCAAA;;;gCAAA;sCAAA;;;;;;oBARqC1B,SAASL;;;;;;oBAcrD;;wBAAOI;;;;IACT"}
@@ -2,10 +2,10 @@ import type { Connection, LexicalHit, LexicalQueryOptions } from '../types.js';
2
2
  interface FtsIndexState {
3
3
  stale: boolean;
4
4
  }
5
- export declare function markContentStale(conn: Connection): void;
5
+ export declare function markContentStale(conn: Connection): Promise<void>;
6
6
  export declare function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, state: FtsIndexState): Promise<LexicalHit[]>;
7
7
  export declare function createLexicalIndex(conn: Connection): {
8
8
  query: (terms: string, opts: LexicalQueryOptions) => Promise<LexicalHit[]>;
9
- markStale: () => void;
9
+ markStale: () => Promise<void>;
10
10
  };
11
11
  export {};
@@ -2,10 +2,10 @@ import type { Connection, LexicalHit, LexicalQueryOptions } from '../types.js';
2
2
  interface FtsIndexState {
3
3
  stale: boolean;
4
4
  }
5
- export declare function markContentStale(conn: Connection): void;
5
+ export declare function markContentStale(conn: Connection): Promise<void>;
6
6
  export declare function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, state: FtsIndexState): Promise<LexicalHit[]>;
7
7
  export declare function createLexicalIndex(conn: Connection): {
8
8
  query: (terms: string, opts: LexicalQueryOptions) => Promise<LexicalHit[]>;
9
- markStale: () => void;
9
+ markStale: () => Promise<void>;
10
10
  };
11
11
  export {};
@@ -1,5 +1,5 @@
1
1
  // D1: fts BM25 for ranking, contains() scans for exact substring / phrase verification /
2
- // unspaced scripts, JS excerpts for every doc (no snippet()).
2
+ // unspaced scripts, JS snippets for every doc (no snippet()).
3
3
  "use strict";
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
@@ -23,6 +23,7 @@ _export(exports, {
23
23
  });
24
24
  var _errorsts = require("../../errors.js");
25
25
  var _segmentts = require("../../text/segment.js");
26
+ var _sharedts = require("../shared.js");
26
27
  var _transactionts = require("../transaction.js");
27
28
  function _array_like_to_array(arr, len) {
28
29
  if (len == null || len > arr.length) len = arr.length;
@@ -240,50 +241,102 @@ function unsupportedOperator(terms) {
240
241
  }
241
242
  return null;
242
243
  }
244
+ // Durable across connections/processes (meta table, shared.ts); `stale` is the in-memory cache of
245
+ // it for this connection's lifetime, so a watcher's repeat queries don't re-read meta every time.
246
+ var FTS_STALE_META_KEY = 'fts_stale';
243
247
  // One state per connection, not per store instance: reconcileContent (reconcile.ts) marks it
244
248
  // stale from outside createLexicalIndex's closure, whether a one-shot open or a watcher's builder ran it.
245
249
  var ftsState = new WeakMap();
246
250
  function stateFor(conn) {
247
- var state = ftsState.get(conn);
248
- if (!state) {
249
- state = {
250
- stale: true
251
- };
252
- ftsState.set(conn, state);
253
- }
254
- return state;
251
+ return _async_to_generator(function() {
252
+ var state, _tmp;
253
+ return _ts_generator(this, function(_state) {
254
+ switch(_state.label){
255
+ case 0:
256
+ state = ftsState.get(conn);
257
+ if (!!state) return [
258
+ 3,
259
+ 2
260
+ ];
261
+ _tmp = {};
262
+ return [
263
+ 4,
264
+ (0, _sharedts.getMeta)(conn, FTS_STALE_META_KEY)
265
+ ];
266
+ case 1:
267
+ // Anything but a persisted '0' (missing key, or '1') means a rebuild is owed: a cache that
268
+ // never built an index, or one a prior process marked stale and never got to clear.
269
+ state = (_tmp.stale = _state.sent() !== '0', _tmp);
270
+ ftsState.set(conn, state);
271
+ _state.label = 2;
272
+ case 2:
273
+ return [
274
+ 2,
275
+ state
276
+ ];
277
+ }
278
+ });
279
+ })();
255
280
  }
256
281
  function markContentStale(conn) {
257
- stateFor(conn).stale = true;
282
+ return _async_to_generator(function() {
283
+ return _ts_generator(this, function(_state) {
284
+ switch(_state.label){
285
+ case 0:
286
+ ftsState.set(conn, {
287
+ stale: true
288
+ });
289
+ return [
290
+ 4,
291
+ (0, _sharedts.setMeta)(conn, FTS_STALE_META_KEY, '1')
292
+ ];
293
+ case 1:
294
+ _state.sent();
295
+ return [
296
+ 2
297
+ ];
298
+ }
299
+ });
300
+ })();
258
301
  }
259
- // A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index the whole run as one token
260
- // (same gap FTS5 has without the `_seg` sidecar), so such runs -- and any author-quoted phrase, any script -- go through contains() instead.
302
+ // A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index the whole run as one token,
303
+ // so unspaced terms use contains() and quoted phrases use the shared authored-field verifier.
261
304
  function splitTerms(terms) {
262
305
  var words = [];
306
+ var bareWords = [];
307
+ var phraseWords = [];
263
308
  var substrings = [];
309
+ var phrases = [];
310
+ var emptyPhrase = false;
264
311
  var withoutPhrases = terms.replace(/"([^"]*)"/g, function(_m, inner) {
265
312
  var phrase = inner.trim();
266
- if (phrase.length === 0) return ' ';
267
- substrings.push(phrase);
268
- if (!(0, _segmentts.hasUnspacedRun)(phrase)) {
269
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
313
+ var phraseTokens = (0, _segmentts.searchTokens)(phrase);
314
+ if (phraseTokens.length === 0) {
315
+ emptyPhrase = true;
316
+ return ' ';
317
+ }
318
+ phrases.push(phraseTokens);
319
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
320
+ try {
321
+ for(var _iterator = phraseTokens[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
322
+ var token = _step.value;
323
+ if (token.unspaced) substrings.push(token.text);
324
+ else {
325
+ words.push(token.text);
326
+ phraseWords.push(token.text);
327
+ }
328
+ }
329
+ } catch (err) {
330
+ _didIteratorError = true;
331
+ _iteratorError = err;
332
+ } finally{
270
333
  try {
271
- for(var _iterator = phrase.split(/\s+/)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
272
- var w = _step.value;
273
- if (w.length > 0) words.push(w);
334
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
335
+ _iterator.return();
274
336
  }
275
- } catch (err) {
276
- _didIteratorError = true;
277
- _iteratorError = err;
278
337
  } finally{
279
- try {
280
- if (!_iteratorNormalCompletion && _iterator.return != null) {
281
- _iterator.return();
282
- }
283
- } finally{
284
- if (_didIteratorError) {
285
- throw _iteratorError;
286
- }
338
+ if (_didIteratorError) {
339
+ throw _iteratorError;
287
340
  }
288
341
  }
289
342
  }
@@ -294,8 +347,30 @@ function splitTerms(terms) {
294
347
  for(var _iterator = withoutPhrases.split(/\s+/)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
295
348
  var tok = _step.value;
296
349
  if (tok.length === 0) continue;
297
- if ((0, _segmentts.hasUnspacedRun)(tok)) substrings.push(tok);
298
- else words.push(tok);
350
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
351
+ try {
352
+ for(var _iterator1 = (0, _segmentts.searchTokens)(tok)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
353
+ var token = _step1.value;
354
+ if (token.unspaced) substrings.push(token.text);
355
+ else {
356
+ words.push(token.text);
357
+ bareWords.push(token.text);
358
+ }
359
+ }
360
+ } catch (err) {
361
+ _didIteratorError1 = true;
362
+ _iteratorError1 = err;
363
+ } finally{
364
+ try {
365
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
366
+ _iterator1.return();
367
+ }
368
+ } finally{
369
+ if (_didIteratorError1) {
370
+ throw _iteratorError1;
371
+ }
372
+ }
373
+ }
299
374
  }
300
375
  } catch (err) {
301
376
  _didIteratorError = true;
@@ -313,11 +388,15 @@ function splitTerms(terms) {
313
388
  }
314
389
  return {
315
390
  words: words,
316
- substrings: substrings
391
+ bareWords: bareWords,
392
+ phraseWords: phraseWords,
393
+ substrings: substrings,
394
+ phrases: phrases,
395
+ emptyPhrase: emptyPhrase
317
396
  };
318
397
  }
319
- // No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this pays the full rebuild -- but only
320
- // once per store instance's first lexical query, and only when content actually changed since (FtsIndexState / store.ts's markStale()).
398
+ // No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this pays
399
+ // the full rebuild whenever `state.stale`, which meta.fts_stale keeps true across processes until cleared below.
321
400
  function ensureFtsFresh(conn, state) {
322
401
  return _async_to_generator(function() {
323
402
  return _ts_generator(this, function(_state) {
@@ -346,6 +425,14 @@ function ensureFtsFresh(conn, state) {
346
425
  conn.exec("PRAGMA create_fts_index('content', 'path', 'title', 'summary', 'text', stopwords='none', overwrite=1)")
347
426
  ];
348
427
  case 1:
428
+ _state.sent();
429
+ // Same transaction as the rebuild: a crash before commit leaves the mark set (a redundant
430
+ // rebuild next time), never a cleared mark serving a half-built index as fresh.
431
+ return [
432
+ 4,
433
+ (0, _sharedts.setMeta)(conn, FTS_STALE_META_KEY, '0')
434
+ ];
435
+ case 2:
349
436
  _state.sent();
350
437
  return [
351
438
  2
@@ -367,13 +454,13 @@ function ensureFtsFresh(conn, state) {
367
454
  }
368
455
  // Field-weighted fragments (title 10 / summary 5 / text 1), with params emitted in the exact left-to-right order the assembled SQL needs for positional `?` binding.
369
456
  // score/gate params stay in separate arrays until joined at the end, since the SQL puts every score part before every gate part; sharing one array would shift binds once a query has both words and substrings (PRINCIPLES: no-silent-modes).
370
- function buildScoreAndGate(words, substrings) {
457
+ function buildScoreAndGate(words, bareWords, phraseWords, substrings) {
371
458
  var scoreParts = [];
372
459
  var gateParts = [];
373
460
  var scoreParams = [];
374
461
  var gateParams = [];
375
462
  if (words.length > 0) {
376
- var wordQuery = words.join(' ');
463
+ var wordQuery = words.map(_segmentts.foldForSearch).join(' ');
377
464
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
378
465
  try {
379
466
  for(var _iterator = FIELDS[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -395,38 +482,63 @@ function buildScoreAndGate(words, substrings) {
395
482
  }
396
483
  }
397
484
  }
398
- gateParts.push('fts_main_content.match_bm25(content."path", ?, conjunctive := true) IS NOT NULL');
399
- gateParams.push(wordQuery);
485
+ if (bareWords.length > 0) {
486
+ var nativeGate = 'fts_main_content.match_bm25(content."path", ?, conjunctive := true) IS NOT NULL';
487
+ gateParts.push(nativeGate);
488
+ gateParams.push(bareWords.map(_segmentts.foldForSearch).join(' '));
489
+ }
490
+ if (phraseWords.length > 0) {
491
+ var _gateParams;
492
+ var nativeGate1 = 'fts_main_content.match_bm25(content."path", ?, conjunctive := true) IS NOT NULL';
493
+ var fallback = phraseWords.map(function(_word) {
494
+ return "(".concat(FIELDS.map(function(field) {
495
+ return "contains(strip_accents(lower(content.".concat(field, ")), ?)");
496
+ }).join(' OR '), ")");
497
+ }).join(' AND ');
498
+ gateParts.push("(".concat(nativeGate1, " OR (").concat(fallback, "))"));
499
+ gateParams.push(phraseWords.map(_segmentts.foldForSearch).join(' '));
500
+ (_gateParams = gateParams).push.apply(_gateParams, _to_consumable_array(phraseWords.flatMap(function(word) {
501
+ return FIELDS.map(function() {
502
+ return (0, _segmentts.foldForSearch)(word);
503
+ });
504
+ })));
505
+ }
400
506
  }
401
507
  var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
402
508
  try {
403
- for(var _iterator1 = substrings[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
509
+ var _loop = function() {
404
510
  var raw = _step1.value;
511
+ var _gateParams;
405
512
  var needle = raw.toLowerCase();
406
- var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
513
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
407
514
  try {
408
- for(var _iterator2 = FIELDS[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
409
- var field1 = _step2.value;
410
- scoreParts.push("".concat(FIELD_WEIGHT[field1], ".0 * CAST(contains(lower(content.").concat(field1, "), ?) AS INTEGER)"));
411
- scoreParams.push(needle);
515
+ for(var _iterator = FIELDS[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
516
+ var field = _step.value;
517
+ scoreParts.push("".concat(FIELD_WEIGHT[field], ".0 * CAST(contains(strip_accents(lower(content.").concat(field, ")), ?) AS INTEGER)"));
518
+ scoreParams.push((0, _segmentts.foldForSearch)(needle));
412
519
  }
413
520
  } catch (err) {
414
- _didIteratorError2 = true;
415
- _iteratorError2 = err;
521
+ _didIteratorError = true;
522
+ _iteratorError = err;
416
523
  } finally{
417
524
  try {
418
- if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
419
- _iterator2.return();
525
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
526
+ _iterator.return();
420
527
  }
421
528
  } finally{
422
- if (_didIteratorError2) {
423
- throw _iteratorError2;
529
+ if (_didIteratorError) {
530
+ throw _iteratorError;
424
531
  }
425
532
  }
426
533
  }
427
- gateParts.push("contains(lower(content.title) || ' ' || lower(content.summary) || ' ' || lower(content.text), ?)");
428
- gateParams.push(needle);
429
- }
534
+ gateParts.push("(".concat(FIELDS.map(function(field) {
535
+ return "contains(strip_accents(lower(content.".concat(field, ")), ?)");
536
+ }).join(' OR '), ")"));
537
+ (_gateParams = gateParams).push.apply(_gateParams, _to_consumable_array(FIELDS.map(function() {
538
+ return (0, _segmentts.foldForSearch)(needle);
539
+ })));
540
+ };
541
+ for(var _iterator1 = substrings[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true)_loop();
430
542
  } catch (err) {
431
543
  _didIteratorError1 = true;
432
544
  _iteratorError1 = err;
@@ -443,13 +555,13 @@ function buildScoreAndGate(words, substrings) {
443
555
  }
444
556
  return {
445
557
  scoreSql: scoreParts.join(' + '),
446
- gateSql: gateParts.join(' AND '),
558
+ gateSql: gateParts.join(' AND ') || 'TRUE',
447
559
  params: _to_consumable_array(scoreParams).concat(_to_consumable_array(gateParams))
448
560
  };
449
561
  }
450
562
  function queryLexical(conn, terms, opts, state) {
451
563
  return _async_to_generator(function() {
452
- var _stmt, unsupported, whereJoin, whereCond, scopeCond, limit, _splitTerms, words, substrings, _buildScoreAndGate, scoreSql, gateSql, params, sql, stmt;
564
+ var _stmt, unsupported, whereJoin, whereCond, scopeCond, limit, _splitTerms, words, bareWords, phraseWords, substrings, phrases, emptyPhrase, _buildScoreAndGate, scoreSql, gateSql, params, selectFields, sqlLimit, sql, stmt, rows, matches;
453
565
  return _ts_generator(this, function(_state) {
454
566
  switch(_state.label){
455
567
  case 0:
@@ -458,8 +570,8 @@ function queryLexical(conn, terms, opts, state) {
458
570
  throw new _errorsts.SenseError('STORE_CAPABILITY_MISSING', 'store "duckdb" does not implement FTS5\'s '.concat(unsupported.label, ' ("').concat(unsupported.token, '") in this build; rephrase "').concat(terms.trim(), '" without it, or set "store" to "sqlite" in this tree\'s config to search it as written'));
459
571
  }
460
572
  whereJoin = opts.whereJoin, whereCond = opts.whereCond, scopeCond = opts.scopeCond, limit = opts.limit;
461
- _splitTerms = splitTerms(terms), words = _splitTerms.words, substrings = _splitTerms.substrings;
462
- if (words.length === 0 && substrings.length === 0) return [
573
+ _splitTerms = splitTerms(terms), words = _splitTerms.words, bareWords = _splitTerms.bareWords, phraseWords = _splitTerms.phraseWords, substrings = _splitTerms.substrings, phrases = _splitTerms.phrases, emptyPhrase = _splitTerms.emptyPhrase;
574
+ if (emptyPhrase || words.length === 0 && substrings.length === 0) return [
463
575
  2,
464
576
  []
465
577
  ];
@@ -475,8 +587,10 @@ function queryLexical(conn, terms, opts, state) {
475
587
  _state.sent();
476
588
  _state.label = 2;
477
589
  case 2:
478
- _buildScoreAndGate = buildScoreAndGate(words, substrings), scoreSql = _buildScoreAndGate.scoreSql, gateSql = _buildScoreAndGate.gateSql, params = _buildScoreAndGate.params;
479
- sql = 'SELECT path, NULL AS hit FROM (\n SELECT content."path" AS path, ('.concat(scoreSql, ") AS score\n FROM content\n ").concat(whereJoin, "\n WHERE ").concat(gateSql, "\n ").concat(whereCond, " ").concat(scopeCond, "\n ) sq ORDER BY score DESC, path LIMIT ?");
590
+ _buildScoreAndGate = buildScoreAndGate(words, bareWords, phraseWords, substrings), scoreSql = _buildScoreAndGate.scoreSql, gateSql = _buildScoreAndGate.gateSql, params = _buildScoreAndGate.params;
591
+ selectFields = phrases.length > 0 ? ', content.title, content.summary, content.text' : '';
592
+ sqlLimit = phrases.length > 0 ? '' : ' LIMIT ?';
593
+ sql = "SELECT path, score".concat(phrases.length > 0 ? ', title, summary, text' : '', ' FROM (\n SELECT content."path" AS path, (').concat(scoreSql, ") AS score").concat(selectFields, "\n FROM content\n ").concat(whereJoin, "\n WHERE ").concat(gateSql, "\n ").concat(whereCond, " ").concat(scopeCond, "\n ) sq ORDER BY score DESC, path").concat(sqlLimit);
480
594
  return [
481
595
  4,
482
596
  conn.prepare(sql)
@@ -485,14 +599,28 @@ function queryLexical(conn, terms, opts, state) {
485
599
  stmt = _state.sent();
486
600
  return [
487
601
  4,
488
- (_stmt = stmt).all.apply(_stmt, _to_consumable_array(params).concat([
602
+ (_stmt = stmt).all.apply(_stmt, _to_consumable_array(params).concat(_to_consumable_array(phrases.length > 0 ? [] : [
489
603
  limit
490
- ]))
604
+ ])))
491
605
  ];
492
606
  case 4:
607
+ rows = _state.sent();
608
+ matches = phrases.length === 0 ? rows : rows.filter(function(row) {
609
+ return phrases.every(function(phrase) {
610
+ return FIELDS.some(function(field) {
611
+ var _row_field;
612
+ return (0, _segmentts.matchesSearchPhrase)((_row_field = row[field]) !== null && _row_field !== void 0 ? _row_field : '', phrase);
613
+ });
614
+ });
615
+ });
493
616
  return [
494
617
  2,
495
- _state.sent()
618
+ matches.slice(0, limit).map(function(param) {
619
+ var path = param.path;
620
+ return {
621
+ path: path
622
+ };
623
+ })
496
624
  ];
497
625
  }
498
626
  });
@@ -501,7 +629,30 @@ function queryLexical(conn, terms, opts, state) {
501
629
  function createLexicalIndex(conn) {
502
630
  return {
503
631
  query: function query(terms, opts) {
504
- return queryLexical(conn, terms, opts, stateFor(conn));
632
+ return _async_to_generator(function() {
633
+ var _tmp;
634
+ return _ts_generator(this, function(_state) {
635
+ switch(_state.label){
636
+ case 0:
637
+ _tmp = [
638
+ conn,
639
+ terms,
640
+ opts
641
+ ];
642
+ return [
643
+ 4,
644
+ stateFor(conn)
645
+ ];
646
+ case 1:
647
+ return [
648
+ 2,
649
+ queryLexical.apply(void 0, _tmp.concat([
650
+ _state.sent()
651
+ ]))
652
+ ];
653
+ }
654
+ });
655
+ })();
505
656
  },
506
657
  markStale: function markStale() {
507
658
  return markContentStale(conn);