sensemaking 0.10.0 → 0.11.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 (95) hide show
  1. package/README.md +6 -7
  2. package/dist/cjs/cli/index.d.cts +1 -3
  3. package/dist/cjs/cli/index.d.ts +1 -3
  4. package/dist/cjs/cli/index.js +1 -13
  5. package/dist/cjs/cli/index.js.map +1 -1
  6. package/dist/cjs/cli/named.js +3 -1
  7. package/dist/cjs/cli/named.js.map +1 -1
  8. package/dist/cjs/cli/shared.d.cts +1 -1
  9. package/dist/cjs/cli/shared.d.ts +1 -1
  10. package/dist/cjs/cli/shared.js +21 -1
  11. package/dist/cjs/cli/shared.js.map +1 -1
  12. package/dist/cjs/cli/sql.js +30 -2
  13. package/dist/cjs/cli/sql.js.map +1 -1
  14. package/dist/cjs/cli/status.d.cts +1 -0
  15. package/dist/cjs/cli/status.d.ts +1 -0
  16. package/dist/cjs/cli/status.js +76 -13
  17. package/dist/cjs/cli/status.js.map +1 -1
  18. package/dist/cjs/commands.d.cts +2 -1
  19. package/dist/cjs/commands.d.ts +2 -1
  20. package/dist/cjs/commands.js +32 -12
  21. package/dist/cjs/commands.js.map +1 -1
  22. package/dist/cjs/db.d.cts +2 -2
  23. package/dist/cjs/db.d.ts +2 -2
  24. package/dist/cjs/db.js +11 -12
  25. package/dist/cjs/db.js.map +1 -1
  26. package/dist/cjs/errors.d.cts +1 -1
  27. package/dist/cjs/errors.d.ts +1 -1
  28. package/dist/cjs/errors.js.map +1 -1
  29. package/dist/cjs/features/embed.d.cts +1 -0
  30. package/dist/cjs/features/embed.d.ts +1 -0
  31. package/dist/cjs/features/embed.js +31 -9
  32. package/dist/cjs/features/embed.js.map +1 -1
  33. package/dist/cjs/index.d.cts +1 -1
  34. package/dist/cjs/index.d.ts +1 -1
  35. package/dist/cjs/index.js +3 -3
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/output.d.cts +3 -0
  38. package/dist/cjs/output.d.ts +3 -0
  39. package/dist/cjs/output.js +6 -1
  40. package/dist/cjs/output.js.map +1 -1
  41. package/dist/cjs/scan.d.cts +1 -0
  42. package/dist/cjs/scan.d.ts +1 -0
  43. package/dist/cjs/scan.js +83 -12
  44. package/dist/cjs/scan.js.map +1 -1
  45. package/dist/esm/cli/index.d.ts +1 -3
  46. package/dist/esm/cli/index.js +1 -5
  47. package/dist/esm/cli/index.js.map +1 -1
  48. package/dist/esm/cli/named.js +3 -1
  49. package/dist/esm/cli/named.js.map +1 -1
  50. package/dist/esm/cli/shared.d.ts +1 -1
  51. package/dist/esm/cli/shared.js +21 -1
  52. package/dist/esm/cli/shared.js.map +1 -1
  53. package/dist/esm/cli/sql.js +5 -2
  54. package/dist/esm/cli/sql.js.map +1 -1
  55. package/dist/esm/cli/status.d.ts +1 -0
  56. package/dist/esm/cli/status.js +70 -13
  57. package/dist/esm/cli/status.js.map +1 -1
  58. package/dist/esm/commands.d.ts +2 -1
  59. package/dist/esm/commands.js +30 -10
  60. package/dist/esm/commands.js.map +1 -1
  61. package/dist/esm/db.d.ts +2 -2
  62. package/dist/esm/db.js +12 -10
  63. package/dist/esm/db.js.map +1 -1
  64. package/dist/esm/errors.d.ts +1 -1
  65. package/dist/esm/errors.js.map +1 -1
  66. package/dist/esm/features/embed.d.ts +1 -0
  67. package/dist/esm/features/embed.js +29 -9
  68. package/dist/esm/features/embed.js.map +1 -1
  69. package/dist/esm/index.d.ts +1 -1
  70. package/dist/esm/index.js +1 -1
  71. package/dist/esm/index.js.map +1 -1
  72. package/dist/esm/output.d.ts +3 -0
  73. package/dist/esm/output.js +6 -1
  74. package/dist/esm/output.js.map +1 -1
  75. package/dist/esm/scan.d.ts +1 -0
  76. package/dist/esm/scan.js +82 -13
  77. package/dist/esm/scan.js.map +1 -1
  78. package/package.json +1 -1
  79. package/schema.json +1 -1
  80. package/skills/sense/SKILL.md +16 -10
  81. package/skills/sense-setup/SKILL.md +2 -2
  82. package/dist/cjs/cli/check.d.cts +0 -3
  83. package/dist/cjs/cli/check.d.ts +0 -3
  84. package/dist/cjs/cli/check.js +0 -392
  85. package/dist/cjs/cli/check.js.map +0 -1
  86. package/dist/cjs/cli/rebuild.d.cts +0 -3
  87. package/dist/cjs/cli/rebuild.d.ts +0 -3
  88. package/dist/cjs/cli/rebuild.js +0 -50
  89. package/dist/cjs/cli/rebuild.js.map +0 -1
  90. package/dist/esm/cli/check.d.ts +0 -3
  91. package/dist/esm/cli/check.js +0 -88
  92. package/dist/esm/cli/check.js.map +0 -1
  93. package/dist/esm/cli/rebuild.d.ts +0 -3
  94. package/dist/esm/cli/rebuild.js +0 -13
  95. package/dist/esm/cli/rebuild.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.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.ts';\nimport { featureSignature, STATE_DIR } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { activeFeatures } from './features/index.ts';\nimport type { ReconcileDelta } from './features/types.ts';\nimport { progress } from './progress.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile, RESERVED_COLUMNS } from './scan.ts';\n\n// Feature-owned columns (`_rank`) must stay out of the upsert: a reparse would null the last\n// computed value on every touch, not just the reconciles that recompute it.\nconst CORE_FRONTMATTER_COLUMNS = new Set(['path', '_mtime', '_size']);\n\n// Rows -> SQLite: core schema, reconcile loop, has(). Parsing lives in scan.ts;\n// everything beyond frontmatter + content lives in src/features/.\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 = '8';\n\n// SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n return String(field).includes(needle) ? 1 : 0;\n });\n}\n\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\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): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\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 getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\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 reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const features = activeFeatures(cfg);\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n // Bulk reparses (a sync, a cold build) are the long silences a query can hit; short\n // reconciles stay silent (progress() has a threshold).\n const report = progress('reparsing files', toReparse.length);\n let parsedCount = 0;\n for (const file of toReparse) {\n // A doc only gets extract/store from features that apply to it (currently: embed, via\n // FileStat.embed -- true iff the config names an embedding model).\n const fileFeatures = features.filter((feature) => !feature.enabledForFile || feature.enabledForFile(cfg, file));\n const { doc, warnings: fileWarnings } = parseFile(file, fileFeatures);\n report.tick(++parsedCount);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n report.finish();\n\n const allColumns = [...seenColumns];\n // Fence before ALTERing: SQLite's own failure past this point is a raw\n // \"too many columns on sqlite_altertab_frontmatter\" with no indication of the boundary or the levers.\n if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${allColumns.length} columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ${MAX_FRONTMATTER_COLUMNS}; see https://www.sqlite.org/limits.html). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a\n // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).\n const writableColumns = allColumns.filter((c) => CORE_FRONTMATTER_COLUMNS.has(c) || !RESERVED_COLUMNS.has(c));\n // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --\n // content rows are coupled to that rowid below.\n const insertSql = `INSERT INTO frontmatter (${writableColumns.map(quoteIdent).join(', ')}) VALUES (${writableColumns.map(() => '?').join(', ')}) ON CONFLICT(\"path\") DO UPDATE SET ${writableColumns\n .filter((c) => c !== 'path')\n .map((c) => `${quoteIdent(c)} = excluded.${quoteIdent(c)}`)\n .join(', ')}`;\n\n const added = toReparse.filter((f) => !existing.has(f.relPath)).map((f) => f.relPath);\n const delta: ReconcileDelta = { files, reparsed: parsedDocs.map((d) => d.relPath), added, vanished };\n\n const txStart = Date.now();\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`; coupled to the\n // frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`\n // column, which a per-row DELETE would otherwise scan the whole table to find.\n const delBody = db.prepare(`DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE \"path\" = ?)`);\n const delPresetFiles = db.prepare(`DELETE FROM preset_files WHERE \"path\" = ?`);\n const insertPresetFile = db.prepare(`INSERT INTO preset_files (\"path\", preset) VALUES (?, ?)`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n // content delete must run first: it looks up the frontmatter rowid by path, which\n // the frontmatter delete below would otherwise have already removed.\n delBody.run(path);\n del.run(path);\n delPresetFiles.run(path);\n for (const feature of features) feature.remove?.(db, path, delta);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (rowid, title, summary, text, \"path\") VALUES ((SELECT rowid FROM frontmatter WHERE \"path\" = ?), ?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = writableColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n // Frontmatter upsert first: content's rowid lookup below depends on this row existing.\n insert.run(...values);\n // Only for docs that have rows: an unconditional delete made cold crawls quadratic.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath, delta);\n }\n insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n // A preset edit forces a full rebuild, so an unchanged doc's coverage is already\n // correct; new docs have nothing to clear, which keeps cold builds linear.\n if (existing.has(doc.relPath)) delPresetFiles.run(doc.relPath);\n for (const presetName of doc.presets) insertPresetFile.run(doc.relPath, presetName);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name], delta);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, delta);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n // Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):\n // keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.\n const durationMs = Date.now() - txStart;\n const prevRaw = getMeta(db, 'reconcile_max_ms');\n // -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)\n // still gets recorded instead of losing to the \"nothing recorded yet\" default.\n const prevMax = prevRaw === null ? -1 : Number(prevRaw);\n if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));\n\n return { parsed: parsedDocs.length, warnings };\n}\n\n// Names what moved between two feature signatures (see config.featureSignature's format:\n// global features, embed provider, then one segment per preset) for the rebuild notice.\nfunction signatureDiff(before: string, after: string): string {\n // Segment keys: `features`, `embed`, `preset:<name>` (config.featureSignature's format).\n const keyOf = (part: string) => (part.startsWith('preset:') ? part.split(':').slice(0, 2).join(':') : part.split(':')[0]);\n const parse = (sig: string) => new Map(sig.split('|').map((part) => [keyOf(part), part]));\n const a = parse(before);\n const b = parse(after);\n const changed = new Set<string>();\n for (const [key, val] of b) if (a.get(key) !== val) changed.add(key);\n for (const key of a.keys()) if (!b.has(key)) changed.add(key);\n const label = (key: string) => (key === 'embed' ? 'embed settings' : key.startsWith('preset:') ? `preset \"${key.slice(7)}\"` : 'features');\n return changed.size === 0 ? 'features' : [...changed].map(label).join(', ');\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);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\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 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 } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n }\n db.close();\n rmSync(stateDir, { recursive: true, force: true });\n return open(cfg);\n }\n\n ensureSchema(db, cfg);\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 };\n}\n\n// Manual reset for a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["mkdirSync","rmSync","join","DatabaseSync","featureSignature","STATE_DIR","SenseError","activeFeatures","progress","listFiles","parseFile","RESERVED_COLUMNS","CORE_FRONTMATTER_COLUMNS","Set","DB_FILENAME","SCHEMA_VERSION","MAX_FRONTMATTER_COLUMNS","quoteIdent","name","split","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","map","r","ensureSchema","cfg","exec","feature","schema","getMeta","setMeta","key","row","get","run","docCount","n","reconcile","baseDir","files","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","features","seenColumns","newColumns","parsedDocs","report","parsedCount","file","fileFeatures","enabledForFile","doc","fileWarnings","tick","push","Object","keys","data","add","finish","allColumns","writableColumns","c","insertSql","added","delta","reparsed","d","txStart","Date","now","col","delBody","delPresetFiles","insertPresetFile","del","remove","insert","insertBody","values","search","title","summary","text","presetName","presets","store","extracted","afterReconcile","err","durationMs","prevRaw","prevMax","Number","signatureDiff","before","after","keyOf","part","slice","sig","a","b","changed","val","label","open","stateDir","recursive","dbPath","version","wantFeatures","console","error","close","force","recordedMaxMs","Math","min","max","rebuild"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.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.ts';\nimport { featureSignature, STATE_DIR } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { activeFeatures } from './features/index.ts';\nimport type { ReconcileDelta } from './features/types.ts';\nimport { progress } from './progress.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile, RESERVED_COLUMNS } from './scan.ts';\n\n// Feature-owned columns (`_rank`) must stay out of the upsert: a reparse would null the last\n// computed value on every touch, not just the reconciles that recompute it.\nconst CORE_FRONTMATTER_COLUMNS = new Set(['path', '_mtime', '_size', '_parse_error']);\n\n// Rows -> SQLite: core schema, reconcile loop, has(). Parsing lives in scan.ts;\n// everything beyond frontmatter + content lives in src/features/.\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 = '9';\n\n// SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n return String(field).includes(needle) ? 1 : 0;\n });\n}\n\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\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): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\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 getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\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 reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const features = activeFeatures(cfg);\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n // Bulk reparses (a sync, a cold build) are the long silences a query can hit; short\n // reconciles stay silent (progress() has a threshold).\n const report = progress('reparsing files', toReparse.length);\n let parsedCount = 0;\n for (const file of toReparse) {\n // A doc only gets extract/store from features that apply to it (currently: embed, via\n // FileStat.embed -- true iff the config names an embedding model).\n const fileFeatures = features.filter((feature) => !feature.enabledForFile || feature.enabledForFile(cfg, file));\n const { doc, warnings: fileWarnings } = parseFile(file, fileFeatures);\n report.tick(++parsedCount);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n report.finish();\n\n const allColumns = [...seenColumns];\n // Fence before ALTERing: SQLite's own failure past this point is a raw\n // \"too many columns on sqlite_altertab_frontmatter\" with no indication of the boundary or the levers.\n if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${allColumns.length} columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ${MAX_FRONTMATTER_COLUMNS}; see https://www.sqlite.org/limits.html). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a\n // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).\n const writableColumns = allColumns.filter((c) => CORE_FRONTMATTER_COLUMNS.has(c) || !RESERVED_COLUMNS.has(c));\n // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --\n // content rows are coupled to that rowid below.\n const insertSql = `INSERT INTO frontmatter (${writableColumns.map(quoteIdent).join(', ')}) VALUES (${writableColumns.map(() => '?').join(', ')}) ON CONFLICT(\"path\") DO UPDATE SET ${writableColumns\n .filter((c) => c !== 'path')\n .map((c) => `${quoteIdent(c)} = excluded.${quoteIdent(c)}`)\n .join(', ')}`;\n\n const added = toReparse.filter((f) => !existing.has(f.relPath)).map((f) => f.relPath);\n const delta: ReconcileDelta = { files, reparsed: parsedDocs.map((d) => d.relPath), added, vanished };\n\n const txStart = Date.now();\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`; coupled to the\n // frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`\n // column, which a per-row DELETE would otherwise scan the whole table to find.\n const delBody = db.prepare(`DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE \"path\" = ?)`);\n const delPresetFiles = db.prepare(`DELETE FROM preset_files WHERE \"path\" = ?`);\n const insertPresetFile = db.prepare(`INSERT INTO preset_files (\"path\", preset) VALUES (?, ?)`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n // content delete must run first: it looks up the frontmatter rowid by path, which\n // the frontmatter delete below would otherwise have already removed.\n delBody.run(path);\n del.run(path);\n delPresetFiles.run(path);\n for (const feature of features) feature.remove?.(db, path, delta);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (rowid, title, summary, text, \"path\") VALUES ((SELECT rowid FROM frontmatter WHERE \"path\" = ?), ?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = writableColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n // Written per parse, unlike _rank, which a feature pass owns and the upsert skips.\n if (col === '_parse_error') return doc.parseError;\n return doc.data[col] ?? null;\n });\n // Frontmatter upsert first: content's rowid lookup below depends on this row existing.\n insert.run(...values);\n // Only for docs that have rows: an unconditional delete made cold crawls quadratic.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath, delta);\n }\n insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n // A preset edit forces a full rebuild, so an unchanged doc's coverage is already\n // correct; new docs have nothing to clear, which keeps cold builds linear.\n if (existing.has(doc.relPath)) delPresetFiles.run(doc.relPath);\n for (const presetName of doc.presets) insertPresetFile.run(doc.relPath, presetName);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name], delta);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, delta);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n // Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):\n // keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.\n const durationMs = Date.now() - txStart;\n const prevRaw = getMeta(db, 'reconcile_max_ms');\n // -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)\n // still gets recorded instead of losing to the \"nothing recorded yet\" default.\n const prevMax = prevRaw === null ? -1 : Number(prevRaw);\n if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));\n\n return { parsed: parsedDocs.length, warnings };\n}\n\n// Names what moved between two feature signatures (see config.featureSignature's format:\n// global features, embed provider, then one segment per preset) for the rebuild notice.\nfunction signatureDiff(before: string, after: string): string {\n // Segment keys: `features`, `embed`, `preset:<name>` (config.featureSignature's format).\n const keyOf = (part: string) => (part.startsWith('preset:') ? part.split(':').slice(0, 2).join(':') : part.split(':')[0]);\n const parse = (sig: string) => new Map(sig.split('|').map((part) => [keyOf(part), part]));\n const a = parse(before);\n const b = parse(after);\n const changed = new Set<string>();\n for (const [key, val] of b) if (a.get(key) !== val) changed.add(key);\n for (const key of a.keys()) if (!b.has(key)) changed.add(key);\n const label = (key: string) => (key === 'embed' ? 'embed settings' : key.startsWith('preset:') ? `preset \"${key.slice(7)}\"` : 'features');\n return changed.size === 0 ? 'features' : [...changed].map(label).join(', ');\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);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\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 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 } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n }\n db.close();\n clearCache(cfg);\n return open(cfg);\n }\n\n ensureSchema(db, cfg);\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 };\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":["mkdirSync","rmSync","join","DatabaseSync","featureSignature","STATE_DIR","SenseError","activeFeatures","progress","listFiles","parseFile","RESERVED_COLUMNS","CORE_FRONTMATTER_COLUMNS","Set","DB_FILENAME","SCHEMA_VERSION","MAX_FRONTMATTER_COLUMNS","quoteIdent","name","split","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","map","r","ensureSchema","cfg","exec","feature","schema","getMeta","setMeta","key","row","get","run","docCount","n","reconcile","baseDir","files","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","features","seenColumns","newColumns","parsedDocs","report","parsedCount","file","fileFeatures","enabledForFile","doc","fileWarnings","tick","push","Object","keys","data","add","finish","allColumns","writableColumns","c","insertSql","added","delta","reparsed","d","txStart","Date","now","col","delBody","delPresetFiles","insertPresetFile","del","remove","insert","insertBody","values","parseError","search","title","summary","text","presetName","presets","store","extracted","afterReconcile","err","durationMs","prevRaw","prevMax","Number","signatureDiff","before","after","keyOf","part","slice","sig","a","b","changed","val","label","open","stateDir","recursive","dbPath","version","wantFeatures","console","error","close","clearCache","recordedMaxMs","Math","min","max","force"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'EMBED_MODEL_MISSING' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT' | 'PRESET_UNKNOWN';
1
+ export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'EMBED_MODEL_MISSING' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT' | 'PRESET_UNKNOWN' | 'PRESET_NOT_SEMANTIC' | 'NOTE_NOT_EMBEDDED';
2
2
  export declare class SenseError extends Error {
3
3
  code: SenseErrorCode;
4
4
  constructor(code: SenseErrorCode, message: string);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'EMBED_MODEL_MISSING' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT' | 'PRESET_UNKNOWN';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode =\n | 'CONFIG_NOT_FOUND'\n | 'CONFIG_EXISTS'\n | 'CONFIG_INVALID'\n | 'CONFIG_VERSION_UNSUPPORTED'\n | 'WATCH_ACTIVE'\n | 'NOTE_NOT_FOUND'\n | 'NOTE_AMBIGUOUS'\n | 'EMBED_DISABLED'\n | 'EMBED_MODEL'\n | 'EMBED_MODEL_MISSING'\n | 'SEARCH_SYNTAX'\n | 'COLUMN_LIMIT'\n | 'PRESET_UNKNOWN'\n | 'PRESET_NOT_SEMANTIC'\n | 'NOTE_NOT_EMBEDDED';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AAkBrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
@@ -19,6 +19,7 @@ export declare function semanticCandidates(db: DatabaseSync, cfg: Config, terms:
19
19
  lines: string;
20
20
  similarity: number;
21
21
  }>>;
22
+ export declare function hasEmbedding(db: DatabaseSync, path: string): boolean;
22
23
  export declare function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: {
23
24
  exclude: Set<string>;
24
25
  allowed?: Set<string>;
@@ -14,9 +14,15 @@ const STORE_DIMS = 256;
14
14
  const TARGET_CHUNK_CAP = 16;
15
15
  const BATCH = 64;
16
16
  // Deterministic, so embed time can re-derive text from stored line ranges. Heading-delimited,
17
- // preamble kept, whole file when no headings; the title/summary prefix mirrors bm25 weighting.
18
- function chunksOf(raw, search) {
19
- const lines = raw.split('\n');
17
+ // preamble kept, whole body when no headings; the title/summary prefix mirrors bm25 weighting.
18
+ //
19
+ // Chunks the body, not the raw file, with `offset` shifting line numbers back onto the raw
20
+ // file so a range stays a direct Read range (sections is 1-indexed over raw too). Chunking raw
21
+ // put the frontmatter block in its own leading chunk on every note with a heading, which made
22
+ // `lines` point at YAML and made frontmatter-only notes near-identical to each other. It also
23
+ // disagreed with FTS, which indexes the body alone.
24
+ function chunksOf(body, search, offset = 0) {
25
+ const lines = body.split('\n');
20
26
  const starts = [];
21
27
  let inFence = false;
22
28
  for(let i = 0; i < lines.length; i++){
@@ -40,9 +46,11 @@ function chunksOf(raw, search) {
40
46
  bounds.forEach((start, i)=>{
41
47
  const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;
42
48
  const text = lines.slice(start - 1, end).join('\n').trim();
49
+ // A body with nothing in it yields no chunks at all, so a frontmatter-only note has no
50
+ // vectors rather than a vector of its own YAML.
43
51
  if (text.length > 0) chunks.push({
44
- startLine: start,
45
- endLine: end,
52
+ startLine: start + offset,
53
+ endLine: end + offset,
46
54
  text: prefix ? `${prefix}\n${text}` : text
47
55
  });
48
56
  });
@@ -53,8 +61,9 @@ export const embed = {
53
61
  schema (db) {
54
62
  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))`);
55
63
  },
56
- extract (raw, _body, search) {
57
- return chunksOf(raw, search);
64
+ extract (raw, body, search) {
65
+ // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.
66
+ return chunksOf(body, search, raw.split('\n').length - body.split('\n').length);
58
67
  },
59
68
  remove (db, path) {
60
69
  db.prepare('DELETE FROM embeddings WHERE "path" = ?').run(path);
@@ -315,6 +324,11 @@ export async function embedPending(db, cfg, baseDir) {
315
324
  }
316
325
  report.finish();
317
326
  }
327
+ // Dequantised int8 dot products land a little either side of a true cosine, so an identical
328
+ // pair prints 1.001 and undermines a column whose whole job is being a bounded number.
329
+ function asCosine(score) {
330
+ return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;
331
+ }
318
332
  // Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as
319
333
  // lexical syntax. Similarity comes back because the fused score cannot express match quality,
320
334
  // and nearest-neighbour search always returns a neighbour however far away.
@@ -352,9 +366,15 @@ export async function semanticCandidates(db, cfg, terms, fetch1) {
352
366
  ].sort((a, b)=>b[1].score - a[1].score).slice(0, fetch1).map(([path, b])=>({
353
367
  path,
354
368
  lines: b.lines,
355
- similarity: Math.round(b.score * 1000) / 1000
369
+ similarity: asCosine(b.score)
356
370
  }));
357
371
  }
372
+ // Whether a note has any chunk with a vector. Distinguishes "nothing is near this note" from
373
+ // "this note has no text", which look the same in an empty result.
374
+ export function hasEmbedding(db, path) {
375
+ const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE "path" = ? AND vector IS NOT NULL LIMIT 1').get(path);
376
+ return row !== undefined;
377
+ }
358
378
  // Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear
359
379
  // scan of stored vectors. Reads only what is stored, so it stays sync.
360
380
  export function similarNotes(db, _cfg, path, opts) {
@@ -385,6 +405,6 @@ export function similarNotes(db, _cfg, path, opts) {
385
405
  ...best.entries()
386
406
  ].sort((a, b)=>b[1] - a[1]).slice(0, opts.k).map(([p, score])=>({
387
407
  path: p,
388
- similarity: Math.round(score * 1000) / 1000
408
+ similarity: asCosine(score)
389
409
  }));
390
410
  }
@@ -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.ts';\nimport { embedConfig } from '../config.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 file when no headings; the title/summary prefix mirrors bm25 weighting.\nfunction chunksOf(raw: string, search?: { title: string; summary: string }): Chunk[] {\n const lines = raw.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 if (text.length > 0) chunks.push({ startLine: start, endLine: end, 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 return chunksOf(raw, search);\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, 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// 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): 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 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: Math.round(b.score * 1000) / 1000 }));\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: Math.round(score * 1000) / 1000 }));\n}\n"],"names":["existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","homedir","join","embedConfig","SenseError","progress","parseFile","STORE_DIMS","TARGET_CHUNK_CAP","BATCH","chunksOf","raw","search","lines","split","starts","inFence","i","length","test","push","bounds","prefix","title","summary","filter","Boolean","chunks","forEach","start","end","text","slice","trim","startLine","endLine","embed","name","schema","db","exec","extract","_body","remove","path","prepare","run","store","extracted","insert","c","idx","enabledForFile","_cfg","file","MODEL_FILES","MODEL_FILENAMES","HF_MODEL_ID","isDownloadable","model","cacheRoot","xdg","process","env","XDG_CACHE_HOME","modelDir","replace","hasModelFiles","dir","every","modelPresent","cfg","e","type","fetchToFile","url","dest","fetch","then","res","ok","status","Buffer","from","arrayBuffer","downloadModel","onFile","recursive","staticProvider","tokenizerJson","fix","headerLen","Number","readBigUInt64LE","header","JSON","parse","subarray","toString","entry","Object","entries","find","k","dtype","spec","dims","shape","dataStart","byteOffset","data_offsets","matrix","Float32Array","buffer","Tokenizer","tok","unkId","vocab","unk_token","one","ids","encode","add_special_tokens","id","isInteger","v","off","d","norm","Math","sqrt","texts","map","apiProvider","keyEnv","base","headers","key","undefined","authorization","post","method","body","stringify","input","json","data","embedding","providers","Map","getProvider","sig","p","get","set","toStore","full","int8","scale","max","abs","embedPending","baseDir","provider","dirty","all","storeDims","min","byPath","row","list","chunk","jobs","chunkIdxs","relPath","absPath","mtimeMs","size","presets","doc","update","report","batch","vectors","j","job","q","Int8Array","round","err","tick","finish","semanticCandidates","terms","match","t","includes","qv","rows","best","vector","byteLength","dot","score","existing","start_line","end_line","sort","a","b","similarity","similarNotes","opts","targetRows","step","ceil","target","_","exclude","has","allowed","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.ts';\nimport { embedConfig } from '../config.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, 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): 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 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":["existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","homedir","join","embedConfig","SenseError","progress","parseFile","STORE_DIMS","TARGET_CHUNK_CAP","BATCH","chunksOf","body","search","offset","lines","split","starts","inFence","i","length","test","push","bounds","prefix","title","summary","filter","Boolean","chunks","forEach","start","end","text","slice","trim","startLine","endLine","embed","name","schema","db","exec","extract","raw","remove","path","prepare","run","store","extracted","insert","c","idx","enabledForFile","_cfg","file","MODEL_FILES","MODEL_FILENAMES","HF_MODEL_ID","isDownloadable","model","cacheRoot","xdg","process","env","XDG_CACHE_HOME","modelDir","replace","hasModelFiles","dir","every","modelPresent","cfg","e","type","fetchToFile","url","dest","fetch","then","res","ok","status","Buffer","from","arrayBuffer","downloadModel","onFile","recursive","staticProvider","tokenizerJson","fix","headerLen","Number","readBigUInt64LE","header","JSON","parse","subarray","toString","entry","Object","entries","find","k","dtype","spec","dims","shape","dataStart","byteOffset","data_offsets","matrix","Float32Array","buffer","Tokenizer","tok","unkId","vocab","unk_token","one","ids","encode","add_special_tokens","id","isInteger","v","off","d","norm","Math","sqrt","texts","map","apiProvider","keyEnv","base","headers","key","undefined","authorization","post","method","stringify","input","json","data","embedding","providers","Map","getProvider","sig","p","get","set","toStore","full","int8","scale","max","abs","embedPending","baseDir","provider","dirty","all","storeDims","min","byPath","row","list","chunk","jobs","chunkIdxs","relPath","absPath","mtimeMs","size","presets","doc","update","report","batch","vectors","j","job","q","Int8Array","round","err","tick","finish","asCosine","score","semanticCandidates","terms","match","t","includes","qv","rows","best","vector","byteLength","dot","existing","start_line","end_line","sort","a","b","similarity","hasEmbedding","similarNotes","opts","targetRows","step","ceil","target","_","exclude","has","allowed","other","len"],"mappings":"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"}
@@ -3,7 +3,7 @@ export { mapTree, peek, presetCoverage, search } from './commands.js';
3
3
  export type { Config, EmbedConfig, FeatureName, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides } from './config.js';
4
4
  export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
5
5
  export type { OpenResult } from './db.js';
6
- export { open, rebuild } from './db.js';
6
+ export { clearCache, open } from './db.js';
7
7
  export type { SenseErrorCode } from './errors.js';
8
8
  export { SenseError } from './errors.js';
9
9
  export type { Row } from './output.js';
package/dist/esm/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // internals (feature registry, graph, scan, meta) stay module-private.
3
3
  export { mapTree, peek, presetCoverage, search } from './commands.js';
4
4
  export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
5
- export { open, rebuild } from './db.js';
5
+ export { clearCache, open } from './db.js';
6
6
  export { SenseError } from './errors.js';
7
7
  export { printRows } from './output.js';
8
8
  export { runWatch } from './watch.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands.ts';\nexport type { Config, EmbedConfig, FeatureName, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\nexport type { OpenResult } from './db.ts';\nexport { open, rebuild } from './db.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["mapTree","peek","presetCoverage","search","CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","open","rebuild","SenseError","printRows","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAEC,MAAM,QAAQ,gBAAgB;AAEtE,SAASC,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAE1H,SAASC,IAAI,EAAEC,OAAO,QAAQ,UAAU;AAExC,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,QAAQ,QAAQ,aAAa"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands.ts';\nexport type { Config, EmbedConfig, FeatureName, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\nexport type { OpenResult } from './db.ts';\nexport { clearCache, open } from './db.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["mapTree","peek","presetCoverage","search","CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","clearCache","open","SenseError","printRows","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAEC,MAAM,QAAQ,gBAAgB;AAEtE,SAASC,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAE1H,SAASC,UAAU,EAAEC,IAAI,QAAQ,UAAU;AAE3C,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,QAAQ,QAAQ,aAAa"}
@@ -9,6 +9,7 @@ export declare function presetsLines(presets: Array<{
9
9
  name: string;
10
10
  files: number;
11
11
  embedded: number;
12
+ semantic?: boolean;
12
13
  }>): string[];
13
14
  export declare function renderMap(result: {
14
15
  docs: {
@@ -25,6 +26,7 @@ export declare function renderMap(result: {
25
26
  name: string;
26
27
  files: number;
27
28
  embedded: number;
29
+ semantic?: boolean;
28
30
  }>;
29
31
  hubs: Row[];
30
32
  recent: Row[];
@@ -33,6 +35,7 @@ export declare function renderPeek(result: {
33
35
  path: string;
34
36
  tokens: number;
35
37
  frontmatter: Row;
38
+ parseError?: string | null;
36
39
  sections: Row[];
37
40
  outbound: string[];
38
41
  backlinks: string[];
@@ -55,9 +55,11 @@ export function featuresLine(features) {
55
55
  return `features: ${features.on.join(', ')}${off}`;
56
56
  }
57
57
  export function presetsLines(presets) {
58
+ // "0 embedded" is ambiguous between a scope that declined vectors and one that has not built
59
+ // them yet, so a semantic-off preset says which.
58
60
  return [
59
61
  'presets:',
60
- ...presets.map((p)=>` ${p.name}: ${p.files} file(s), ${p.embedded} embedded`)
62
+ ...presets.map((p)=>` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.semantic === false ? ' (semantic: false)' : ''}`)
61
63
  ];
62
64
  }
63
65
  export function renderMap(result) {
@@ -79,6 +81,9 @@ export function renderPeek(result) {
79
81
  const lines = [
80
82
  `${result.path} (~${result.tokens} tokens)`
81
83
  ];
84
+ // Otherwise a refused parse is indistinguishable from a note that has no frontmatter, which
85
+ // is the confusion the whole quarantine design exists to remove.
86
+ if (result.parseError) lines.push(` frontmatter: did not parse, so none of it is indexed (${result.parseError})`);
82
87
  for (const [key, value] of Object.entries(result.frontmatter))lines.push(` ${key}: ${value}`);
83
88
  if (result.sections.length > 0) {
84
89
  lines.push('', 'sections:');
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json\n\nexport type Row = Record<string, unknown>;\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\nexport function printRows(rows: Row[], format: 'table' | 'json'): void {\n const rendered = renderRows(rows, format);\n console.log(rendered);\n if (rendered.length > OVERSIZE_BYTES) {\n console.warn(`warning: result is ${Math.round(rendered.length / 1000)} KB across ${rows.length} row(s); consider a LIMIT, fewer columns, or snippet() instead of whole values`);\n }\n}\n\n// Tables are for reading, and a row wider than the terminal wraps unreadably: widest columns\n// give up space first, down to a floor. --format json is never truncated.\nconst MIN_COLUMN = 12;\nconst TRUNCATED = '…';\n\nfunction fitWidths(widths: number[], limit: number): number[] {\n const gaps = (widths.length - 1) * 2;\n const fitted = [...widths];\n for (;;) {\n const total = fitted.reduce((a, b) => a + b, 0) + gaps;\n if (total <= limit) return fitted;\n const widest = fitted.indexOf(Math.max(...fitted));\n if (fitted[widest] <= MIN_COLUMN) return fitted;\n fitted[widest] = Math.max(MIN_COLUMN, fitted[widest] - Math.max(1, total - limit));\n }\n}\n\nfunction renderRows(rows: Row[], format: 'table' | 'json', width = process.stdout.columns): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const natural = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;\n\n const clip = (value: string, w: number) => (value.length <= w ? value.padEnd(w) : `${value.slice(0, w - 1)}${TRUNCATED}`);\n const formatRow = (values: string[]) =>\n values\n .map((value, i) => clip(value, widths[i]))\n .join(' ')\n .trimEnd();\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n\n// Text renderers for the top-level commands; cli.ts prints what these return.\n\n// Names the config key that turns a feature back on. Only the three features-block toggles\n// reach here; `embed` has its own status line.\nexport function featureOffHint(name: string): string {\n return `features.${name}`;\n}\n\nexport function featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (${featureOffHint(name)})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number }>): string[] {\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded`)];\n}\n\nexport function renderMap(result: { docs: { count: number; bytes: number }; fields: Row[]; fieldsTotal: number; features: { on: string[]; off: string[] }; presets: Array<{ name: string; files: number; embedded: number }>; hubs: Row[]; recent: Row[] }): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, featuresLine(result.features), '', ...presetsLines(result.presets), '', renderRows(result.fields, 'table')];\n if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);\n if (result.hubs.length > 0) parts.push('\\nhubs (by link rank):', renderRows(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push(`\\nhubs: off (${featureOffHint('rank')})`);\n parts.push('\\nrecent:', renderRows(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; sectionsTotal: number; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number; off: string[] }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\n for (const [key, value] of Object.entries(result.frontmatter)) lines.push(` ${key}: ${value}`);\n if (result.sections.length > 0) {\n lines.push('', 'sections:');\n for (const s of result.sections) lines.push(` ${'#'.repeat(s.level as number)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);\n if (result.sectionsTotal > result.sections.length) lines.push(` (+${result.sectionsTotal - result.sections.length} more sections -- sections table has all of them)`);\n } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');\n if (result.off.includes('links')) {\n lines.push('', 'links: off (features.links)');\n } else {\n const linkLine = (label: string, shown: string[], total: number) => {\n if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);\n };\n if (result.outboundTotal + result.unresolvedTotal + result.backlinksTotal > 0) lines.push('');\n linkLine('links out', result.outbound, result.outboundTotal);\n linkLine('unresolved', result.unresolved, result.unresolvedTotal);\n linkLine('backlinks', result.backlinks, result.backlinksTotal);\n }\n return lines.join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","printRows","rows","format","rendered","renderRows","console","log","length","warn","Math","round","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","width","process","stdout","columns","JSON","stringify","Object","keys","cells","map","row","col","natural","i","clip","w","padEnd","slice","formatRow","values","join","trimEnd","repeat","featureOffHint","name","featuresLine","features","off","on","presetsLines","presets","p","files","embedded","renderMap","result","parts","docs","count","bytes","fields","fieldsTotal","push","hubs","includes","recent","renderPeek","lines","path","tokens","key","entries","frontmatter","sections","s","level","heading","start_line","end_line","sectionsTotal","linkLine","label","shown","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json\n\nexport type Row = Record<string, unknown>;\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\nexport function printRows(rows: Row[], format: 'table' | 'json'): void {\n const rendered = renderRows(rows, format);\n console.log(rendered);\n if (rendered.length > OVERSIZE_BYTES) {\n console.warn(`warning: result is ${Math.round(rendered.length / 1000)} KB across ${rows.length} row(s); consider a LIMIT, fewer columns, or snippet() instead of whole values`);\n }\n}\n\n// Tables are for reading, and a row wider than the terminal wraps unreadably: widest columns\n// give up space first, down to a floor. --format json is never truncated.\nconst MIN_COLUMN = 12;\nconst TRUNCATED = '…';\n\nfunction fitWidths(widths: number[], limit: number): number[] {\n const gaps = (widths.length - 1) * 2;\n const fitted = [...widths];\n for (;;) {\n const total = fitted.reduce((a, b) => a + b, 0) + gaps;\n if (total <= limit) return fitted;\n const widest = fitted.indexOf(Math.max(...fitted));\n if (fitted[widest] <= MIN_COLUMN) return fitted;\n fitted[widest] = Math.max(MIN_COLUMN, fitted[widest] - Math.max(1, total - limit));\n }\n}\n\nfunction renderRows(rows: Row[], format: 'table' | 'json', width = process.stdout.columns): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const natural = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;\n\n const clip = (value: string, w: number) => (value.length <= w ? value.padEnd(w) : `${value.slice(0, w - 1)}${TRUNCATED}`);\n const formatRow = (values: string[]) =>\n values\n .map((value, i) => clip(value, widths[i]))\n .join(' ')\n .trimEnd();\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n\n// Text renderers for the top-level commands; cli.ts prints what these return.\n\n// Names the config key that turns a feature back on. Only the three features-block toggles\n// reach here; `embed` has its own status line.\nexport function featureOffHint(name: string): string {\n return `features.${name}`;\n}\n\nexport function featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (${featureOffHint(name)})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; semantic?: boolean }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a semantic-off preset says which.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.semantic === false ? ' (semantic: false)' : ''}`)];\n}\n\nexport function renderMap(result: { docs: { count: number; bytes: number }; fields: Row[]; fieldsTotal: number; features: { on: string[]; off: string[] }; presets: Array<{ name: string; files: number; embedded: number; semantic?: boolean }>; hubs: Row[]; recent: Row[] }): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, featuresLine(result.features), '', ...presetsLines(result.presets), '', renderRows(result.fields, 'table')];\n if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);\n if (result.hubs.length > 0) parts.push('\\nhubs (by link rank):', renderRows(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push(`\\nhubs: off (${featureOffHint('rank')})`);\n parts.push('\\nrecent:', renderRows(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; parseError?: string | null; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; sectionsTotal: number; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number; off: string[] }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\n // Otherwise a refused parse is indistinguishable from a note that has no frontmatter, which\n // is the confusion the whole quarantine design exists to remove.\n if (result.parseError) lines.push(` frontmatter: did not parse, so none of it is indexed (${result.parseError})`);\n for (const [key, value] of Object.entries(result.frontmatter)) lines.push(` ${key}: ${value}`);\n if (result.sections.length > 0) {\n lines.push('', 'sections:');\n for (const s of result.sections) lines.push(` ${'#'.repeat(s.level as number)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);\n if (result.sectionsTotal > result.sections.length) lines.push(` (+${result.sectionsTotal - result.sections.length} more sections -- sections table has all of them)`);\n } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');\n if (result.off.includes('links')) {\n lines.push('', 'links: off (features.links)');\n } else {\n const linkLine = (label: string, shown: string[], total: number) => {\n if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);\n };\n if (result.outboundTotal + result.unresolvedTotal + result.backlinksTotal > 0) lines.push('');\n linkLine('links out', result.outbound, result.outboundTotal);\n linkLine('unresolved', result.unresolved, result.unresolvedTotal);\n linkLine('backlinks', result.backlinks, result.backlinksTotal);\n }\n return lines.join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","printRows","rows","format","rendered","renderRows","console","log","length","warn","Math","round","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","width","process","stdout","columns","JSON","stringify","Object","keys","cells","map","row","col","natural","i","clip","w","padEnd","slice","formatRow","values","join","trimEnd","repeat","featureOffHint","name","featuresLine","features","off","on","presetsLines","presets","p","files","embedded","semantic","renderMap","result","parts","docs","count","bytes","fields","fieldsTotal","push","hubs","includes","recent","renderPeek","lines","path","tokens","parseError","key","entries","frontmatter","sections","s","level","heading","start_line","end_line","sectionsTotal","linkLine","label","shown","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
@@ -17,6 +17,7 @@ export interface ParsedDoc {
17
17
  size: number;
18
18
  presets: string[];
19
19
  data: Record<string, string | number | bigint | null>;
20
+ parseError: string | null;
20
21
  search: {
21
22
  title: string;
22
23
  summary: string;