sensemaking 0.24.0 → 0.24.2

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 (64) hide show
  1. package/README.md +2 -1
  2. package/dist/cjs/cli/status.js +6 -3
  3. package/dist/cjs/cli/status.js.map +1 -1
  4. package/dist/cjs/cli/watch.js +1 -1
  5. package/dist/cjs/cli/watch.js.map +1 -1
  6. package/dist/cjs/commands/related.js +2 -2
  7. package/dist/cjs/commands/related.js.map +1 -1
  8. package/dist/cjs/commands/search.js +3 -2
  9. package/dist/cjs/commands/search.js.map +1 -1
  10. package/dist/cjs/config/access.js +7 -1
  11. package/dist/cjs/config/access.js.map +1 -1
  12. package/dist/cjs/config/load.js +6 -1
  13. package/dist/cjs/config/load.js.map +1 -1
  14. package/dist/cjs/config/types.d.cts +3 -0
  15. package/dist/cjs/config/types.d.ts +3 -0
  16. package/dist/cjs/config/types.js.map +1 -1
  17. package/dist/cjs/config/validate.js +4 -0
  18. package/dist/cjs/config/validate.js.map +1 -1
  19. package/dist/cjs/embed/query.js +4 -4
  20. package/dist/cjs/embed/query.js.map +1 -1
  21. package/dist/cjs/store/cache.js +2 -1
  22. package/dist/cjs/store/cache.js.map +1 -1
  23. package/dist/cjs/store/open.js +13 -6
  24. package/dist/cjs/store/open.js.map +1 -1
  25. package/dist/cjs/store/turso/reconcile.d.cts +6 -1
  26. package/dist/cjs/store/turso/reconcile.d.ts +6 -1
  27. package/dist/cjs/store/turso/reconcile.js +32 -5
  28. package/dist/cjs/store/turso/reconcile.js.map +1 -1
  29. package/dist/cjs/watch.d.cts +1 -1
  30. package/dist/cjs/watch.d.ts +1 -1
  31. package/dist/cjs/watch.js +5 -4
  32. package/dist/cjs/watch.js.map +1 -1
  33. package/dist/esm/cli/status.js +6 -3
  34. package/dist/esm/cli/status.js.map +1 -1
  35. package/dist/esm/cli/watch.js +1 -1
  36. package/dist/esm/cli/watch.js.map +1 -1
  37. package/dist/esm/commands/related.js +2 -1
  38. package/dist/esm/commands/related.js.map +1 -1
  39. package/dist/esm/commands/search.js +2 -1
  40. package/dist/esm/commands/search.js.map +1 -1
  41. package/dist/esm/config/access.js +7 -0
  42. package/dist/esm/config/access.js.map +1 -1
  43. package/dist/esm/config/load.js +6 -1
  44. package/dist/esm/config/load.js.map +1 -1
  45. package/dist/esm/config/types.d.ts +3 -0
  46. package/dist/esm/config/types.js.map +1 -1
  47. package/dist/esm/config/validate.js +4 -0
  48. package/dist/esm/config/validate.js.map +1 -1
  49. package/dist/esm/embed/query.js +4 -4
  50. package/dist/esm/embed/query.js.map +1 -1
  51. package/dist/esm/store/cache.js +2 -1
  52. package/dist/esm/store/cache.js.map +1 -1
  53. package/dist/esm/store/open.js +14 -4
  54. package/dist/esm/store/open.js.map +1 -1
  55. package/dist/esm/store/turso/reconcile.d.ts +6 -1
  56. package/dist/esm/store/turso/reconcile.js +11 -3
  57. package/dist/esm/store/turso/reconcile.js.map +1 -1
  58. package/dist/esm/watch.d.ts +1 -1
  59. package/dist/esm/watch.js +5 -4
  60. package/dist/esm/watch.js.map +1 -1
  61. package/package.json +1 -1
  62. package/schema.json +6 -1
  63. package/skills/sense-setup/EXAMPLES.md +21 -0
  64. package/skills/sense-setup/SKILL.md +2 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/open.ts"],"sourcesContent":["import { channel } from 'node:diagnostics_channel';\nimport { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from '../config/index.ts';\nimport { featureSignature, STATE_DIR } from '../config/index.ts';\nimport { rekeyChunkText } from '../embed/handoff.ts';\nimport { SenseError } from '../errors.ts';\nimport { FEATURES } from '../features/index.ts';\nimport { createBuilder } from './builder.ts';\nimport { clearCache } from './cache.ts';\nimport type { EmbedChangeKind } from './embed-scope.ts';\nimport { classifyEmbedChange } from './embed-scope.ts';\nimport type { FeatureToggle } from './feature-scope.ts';\nimport { classifyFeatureToggles, isFeatureOnlyChange } from './feature-scope.ts';\nimport { lockWaitBudgetMs } from './lock-wait.ts';\nimport { forcedPresetPaths, isPresetOnlyChange } from './preset-scope.ts';\nimport { getMeta, setMeta } from './shared.ts';\nimport { changedSignatureKeys, embedIdentityAdopted, signatureDiff } from './signature.ts';\nimport type { Stages } from './stages.ts';\nimport type { Connection, OpenDialect, Store } from './types.ts';\n\n// One open algorithm shared by every store, parameterised by a per-engine OpenDialect (types.ts).\n// Ordering is universal, not a dialect concern: connect, meta table, schema-version and\n// feature-signature comparison (rebuild, adopt, or partial rebuild), any extra rebuild trigger,\n// ensureSchema, the derived busy_timeout PRAGMA, reconcile, then the store itself.\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n // Per-stage wall time for this open's build pass (stages.ts), not for any narrow embed or\n // feature invalidation that ran beside it -- on a cold build there is none, so it is the whole cost.\n stages: Stages;\n}\n\ninterface ConnectResult<Handle> {\n handle: Handle;\n conn: Connection;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n stages: Stages;\n}\n\n// duckdb/turso hold the cache file for their connection's whole life, so a second command waits.\n// Bounded by this tree's recorded reconcile time (lock-wait.ts), not a fixed guess.\nconst LOCK_POLL_MS = 50;\nconst lockWait = channel('sensemaking.store.lock-wait');\n\nasync function connectUnlocked<Handle>(dbPath: string, cfg: ResolvedConfig, dialect: OpenDialect<Handle>): Promise<{ handle: Handle; conn: Connection }> {\n const budgetMs = lockWaitBudgetMs(cfg.baseDir);\n const deadline = Date.now() + budgetMs;\n for (;;) {\n try {\n return await dialect.connect(dbPath, cfg);\n } catch (err) {\n if (!dialect.isLocked?.(err as Error)) throw err;\n // Observe a real rejected native connection, never merely the start of an async open.\n if (lockWait.hasSubscribers) lockWait.publish({ store: cfg.store, dbPath });\n if (Date.now() >= deadline) {\n throw new SenseError('STORE_BUSY', `another sense process is using this tree's ${cfg.store} cache (${dbPath}) and did not release it within ${Math.round(budgetMs / 1000)}s; wait for that command to finish, or set \"store\" to \"sqlite\" in sense.config.json, which serves concurrent commands`);\n }\n await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));\n }\n }\n}\n\nasync function connectWithDialect<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>): Promise<ConnectResult<Handle>> {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, dialect.filename);\n\n const { handle, conn } = await connectUnlocked(dbPath, cfg, dialect);\n // A throw below must release this handle, or the leaked WAL makes the cache file undeletable on\n // Windows. `closed` keeps the catch from double-closing a handle a rebuild branch already closed.\n let closed = false;\n // Created here, not inside reconcile(): invalidate()/invalidateFeatures() below share its pool\n // with the build() call that follows them.\n const builder = createBuilder(conn, cfg, cfg.baseDir, dialect.reconcileDialect);\n try {\n await conn.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 = await getMeta(conn, 'schema_version');\n const features = await getMeta(conn, 'features');\n const wantFeatures = featureSignature(cfg, FEATURES);\n // Set only by the preset-membership branch below; reconcile treats these paths as touched\n // despite an unchanged stamp, for this one build() call.\n let forcedPaths: Set<string> | undefined;\n // Set only by the embed-narrow branch below; applied via builder.invalidate() after ensureSchema.\n let embedInvalidate: EmbedChangeKind | undefined;\n // Set only by the feature-toggle branch below; applied via builder.invalidateFeatures() after ensureSchema.\n let featureToggles: FeatureToggle[] | undefined;\n if ((version !== null && version !== dialect.schemaVersion) || (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 !== dialect.schemaVersion) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n // Close before clearCache deletes the files out from under the still-open handle.\n closed = true;\n await dialect.close(handle);\n clearCache(cfg);\n return connectWithDialect(cfg, dialect);\n }\n const changedKeys = changedSignatureKeys(features ?? '', wantFeatures);\n // Null (rather than the empty set) when changedKeys isn't preset-only, or a changed\n // preset's stored segment can't be parsed back into old include/exclude/vectors -- either\n // way the branch below is skipped and the full rebuild stays the fallback.\n const presetForced = isPresetOnlyChange(changedKeys) ? forcedPresetPaths(cfg, cfg.baseDir, features ?? '', changedKeys) : null;\n // Null unless exactly one segment changed and it decomposes into a recognised embed-only\n // case (embed-scope.ts): anything else, including embed toggled on/off, stays null.\n const embedKind = changedKeys.size === 1 && changedKeys.has('embed') ? classifyEmbedChange(features ?? '', wantFeatures) : null;\n // Null (rather than the empty set) when changedKeys isn't feature-toggle-only, or a changed\n // feature's stored segment can't be parsed back into old/new on-off -- either way the branch\n // below is skipped and the full rebuild stays the fallback.\n const toggles = isFeatureOnlyChange(changedKeys) ? classifyFeatureToggles(features ?? '', wantFeatures, changedKeys) : null;\n if (changedKeys.size === 1 && changedKeys.has('embed') && embedIdentityAdopted(features ?? '', wantFeatures)) {\n // First sight of a resolved weight identity: the model itself hasn't changed, so\n // adopt it into meta with no rebuild and no re-embed.\n console.error(\"sense: recorded the embedding model's resolved identity; vectors are unaffected\");\n await setMeta(conn, 'features', wantFeatures);\n } else if (embedKind !== null) {\n // Chunk boundaries are file-derived, not model-derived (embed.ts's chunksOf), so neither\n // case needs any file reparsed for any feature but embed itself.\n embedInvalidate = embedKind;\n console.error(embedKind === 'model' ? 'sense: config change (embed settings) invalidates only the stale vectors' : 'sense: config change (embed settings) rebuilds only the embeddings it affects');\n await setMeta(conn, 'features', wantFeatures);\n } else if (presetForced !== null) {\n // Membership only: which files a preset covers moved, not a file's own content, a\n // global feature, or the embed model. reconcile() already knows how to\n // add, update, and remove a file correctly (every cross-feature cascade included); it\n // just needs telling which unchanged files to treat as touched (forcedPaths, above).\n forcedPaths = presetForced;\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) reparses only the files it affects`);\n await setMeta(conn, 'features', wantFeatures);\n } else if (toggles !== null) {\n // A feature turned off or on: reconcile.ts's activeFeatures skips a disabled feature's\n // hooks entirely, so its rows rot while off -- invalidateFeatures drops them and, for a\n // feature turning back on, fully re-derives across every indexed file rather than\n // trusting rows written before or during that window.\n featureToggles = toggles;\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) reparses only the feature it affects`);\n await setMeta(conn, 'features', wantFeatures);\n } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n closed = true;\n await dialect.close(handle);\n clearCache(cfg);\n return connectWithDialect(cfg, dialect);\n }\n }\n\n await dialect.ensureSchema(handle, conn, cfg);\n\n // 3x the largest reconcile this cache has recorded, floored at 30s and capped at 10min.\n // Installed before build() -- that call is the one that races a watcher's transaction.\n if (dialect.setDerivedBusyTimeout) {\n const recordedMaxMs = Number((await getMeta(conn, 'reconcile_max_ms')) ?? '0');\n await dialect.setDerivedBusyTimeout(handle, conn, Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000));\n }\n\n // Runs before build(): forcedPaths, embedInvalidate and featureToggles never target the same\n // call (each comes from its own mutually exclusive branch above: a preset-only, embed-only, or\n // feature-toggle-only changed-key set).\n const embedParsed = embedInvalidate ? (await builder.invalidate(embedInvalidate)).parsed : 0;\n const featureParsed = featureToggles ? (await builder.invalidateFeatures(featureToggles)).parsed : 0;\n\n const { parsed, warnings, stages } = await builder.build(forcedPaths);\n // Every open reconciles once and is done with it -- a watcher opens fresh per event instead of holding this connection (src/watch.ts).\n await builder.close();\n\n return { handle, conn, cfg, dbPath, parsed: parsed + embedParsed + featureParsed, warnings, stages };\n } catch (err) {\n await builder.close();\n if (!closed) await dialect.close(handle);\n throw err;\n }\n}\n\n// A store's open(): connects (see connectWithDialect above), then wraps the resulting connection\n// in the Store interface. The builder's pool closes right after the initial build.\nexport async function openWithDialect<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>): Promise<OpenResult> {\n const { handle, conn, cfg: resolvedCfg, dbPath, parsed, warnings, stages } = await connectWithDialect(cfg, dialect);\n const store = dialect.createStore(handle, conn, resolvedCfg);\n // reconcile ran before this object existed, so its chunk text is keyed by the connection.\n rekeyChunkText(conn, store);\n return { store, cfg: resolvedCfg, dbPath, parsed, warnings, stages };\n}\n\nexport async function docCount(store: Store): Promise<number> {\n const stmt = await store.prepare('SELECT COUNT(*) AS n FROM frontmatter');\n return ((await stmt.get()) as { n: number }).n;\n}\n"],"names":["docCount","openWithDialect","LOCK_POLL_MS","lockWait","channel","connectUnlocked","dbPath","cfg","dialect","budgetMs","deadline","err","lockWaitBudgetMs","baseDir","Date","now","connect","isLocked","hasSubscribers","publish","store","SenseError","Math","round","Promise","resolve","setTimeout","connectWithDialect","stateDir","handle","conn","closed","builder","version","features","wantFeatures","forcedPaths","embedInvalidate","featureToggles","changedKeys","presetForced","embedKind","toggles","changed","recordedMaxMs","embedParsed","featureParsed","parsed","warnings","stages","join","STATE_DIR","mkdirSync","recursive","filename","createBuilder","reconcileDialect","exec","getMeta","featureSignature","FEATURES","schemaVersion","console","error","close","clearCache","changedSignatureKeys","isPresetOnlyChange","forcedPresetPaths","size","has","classifyEmbedChange","isFeatureOnlyChange","classifyFeatureToggles","embedIdentityAdopted","setMeta","signatureDiff","ensureSchema","setDerivedBusyTimeout","Number","min","max","invalidate","invalidateFeatures","build","resolvedCfg","createStore","rekeyChunkText","stmt","prepare","get","n"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/open.ts"],"sourcesContent":["import { channel } from 'node:diagnostics_channel';\nimport { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ResolvedConfig } from '../config/index.ts';\nimport { featureSignature, STATE_DIR } from '../config/index.ts';\nimport { rekeyChunkText } from '../embed/handoff.ts';\nimport { SenseError } from '../errors.ts';\nimport { FEATURES } from '../features/index.ts';\nimport { createBuilder } from './builder.ts';\nimport { clearCache } from './cache.ts';\nimport type { EmbedChangeKind } from './embed-scope.ts';\nimport { classifyEmbedChange } from './embed-scope.ts';\nimport type { FeatureToggle } from './feature-scope.ts';\nimport { classifyFeatureToggles, isFeatureOnlyChange } from './feature-scope.ts';\nimport { lockWaitBudgetMs } from './lock-wait.ts';\nimport { forcedPresetPaths, isPresetOnlyChange } from './preset-scope.ts';\nimport { getMeta, setMeta } from './shared.ts';\nimport { changedSignatureKeys, embedIdentityAdopted, signatureDiff } from './signature.ts';\nimport type { Stages } from './stages.ts';\nimport type { Connection, OpenDialect, Store } from './types.ts';\n\n// One open algorithm shared by every store, parameterised by a per-engine OpenDialect (types.ts).\n// Ordering is universal, not a dialect concern: connect, meta table, schema-version and\n// feature-signature comparison (rebuild, adopt, or partial rebuild), any extra rebuild trigger,\n// ensureSchema, the derived busy_timeout PRAGMA, reconcile, then the store itself.\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n // Per-stage wall time for this open's build pass (stages.ts), not for any narrow embed or\n // feature invalidation that ran beside it -- on a cold build there is none, so it is the whole cost.\n stages: Stages;\n}\n\ninterface ConnectResult<Handle> {\n handle: Handle;\n conn: Connection;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n stages: Stages;\n}\n\n// duckdb/turso hold the cache file for their connection's whole life, so a second command waits.\n// Bounded by this tree's recorded reconcile time (lock-wait.ts), not a fixed guess.\nconst LOCK_POLL_MS = 50;\nconst lockWait = channel('sensemaking.store.lock-wait');\n\nasync function connectUnlocked<Handle>(dbPath: string, cfg: ResolvedConfig, dialect: OpenDialect<Handle>): Promise<{ handle: Handle; conn: Connection }> {\n const budgetMs = lockWaitBudgetMs(cfg.configDir ?? cfg.baseDir);\n const deadline = Date.now() + budgetMs;\n for (;;) {\n try {\n return await dialect.connect(dbPath, cfg);\n } catch (err) {\n if (!dialect.isLocked?.(err as Error)) throw err;\n // Observe a real rejected native connection, never merely the start of an async open.\n if (lockWait.hasSubscribers) lockWait.publish({ store: cfg.store, dbPath });\n if (Date.now() >= deadline) {\n throw new SenseError('STORE_BUSY', `another sense process is using this tree's ${cfg.store} cache (${dbPath}) and did not release it within ${Math.round(budgetMs / 1000)}s; wait for that command to finish, or set \"store\" to \"sqlite\" in sense.config.json, which serves concurrent commands`);\n }\n await new Promise((resolve) => setTimeout(resolve, LOCK_POLL_MS));\n }\n }\n}\n\nasync function connectWithDialect<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>): Promise<ConnectResult<Handle>> {\n const stateDir = join(cfg.configDir ?? cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, dialect.filename);\n\n const { handle, conn } = await connectUnlocked(dbPath, cfg, dialect);\n // A throw below must release this handle, or the leaked WAL makes the cache file undeletable on\n // Windows. `closed` keeps the catch from double-closing a handle a rebuild branch already closed.\n let closed = false;\n // Created here, not inside reconcile(): invalidate()/invalidateFeatures() below share its pool\n // with the build() call that follows them.\n const builder = createBuilder(conn, cfg, cfg.rootDir ?? cfg.baseDir, dialect.reconcileDialect);\n try {\n await conn.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 = await getMeta(conn, 'schema_version');\n const features = await getMeta(conn, 'features');\n const wantFeatures = featureSignature(cfg, FEATURES);\n // Set only by the preset-membership branch below; reconcile treats these paths as touched\n // despite an unchanged stamp, for this one build() call.\n let forcedPaths: Set<string> | undefined;\n // Set only by the embed-narrow branch below; applied via builder.invalidate() after ensureSchema.\n let embedInvalidate: EmbedChangeKind | undefined;\n // Set only by the feature-toggle branch below; applied via builder.invalidateFeatures() after ensureSchema.\n let featureToggles: FeatureToggle[] | undefined;\n if ((version !== null && version !== dialect.schemaVersion) || (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 !== dialect.schemaVersion) {\n console.error('sense: cache format changed (new sensemaking version); rebuilding the index');\n // Close before clearCache deletes the files out from under the still-open handle.\n closed = true;\n await dialect.close(handle);\n clearCache(cfg);\n return connectWithDialect(cfg, dialect);\n }\n const changedKeys = changedSignatureKeys(features ?? '', wantFeatures);\n // Null (rather than the empty set) when changedKeys isn't preset-only, or a changed\n // preset's stored segment can't be parsed back into old include/exclude/vectors -- either\n // way the branch below is skipped and the full rebuild stays the fallback.\n const presetForced = isPresetOnlyChange(changedKeys) ? forcedPresetPaths(cfg, cfg.rootDir ?? cfg.baseDir, features ?? '', changedKeys) : null;\n // Null unless exactly one segment changed and it decomposes into a recognised embed-only\n // case (embed-scope.ts): anything else, including embed toggled on/off, stays null.\n const embedKind = changedKeys.size === 1 && changedKeys.has('embed') ? classifyEmbedChange(features ?? '', wantFeatures) : null;\n // Null (rather than the empty set) when changedKeys isn't feature-toggle-only, or a changed\n // feature's stored segment can't be parsed back into old/new on-off -- either way the branch\n // below is skipped and the full rebuild stays the fallback.\n const toggles = isFeatureOnlyChange(changedKeys) ? classifyFeatureToggles(features ?? '', wantFeatures, changedKeys) : null;\n if (changedKeys.size === 1 && changedKeys.has('embed') && embedIdentityAdopted(features ?? '', wantFeatures)) {\n // First sight of a resolved weight identity: the model itself hasn't changed, so\n // adopt it into meta with no rebuild and no re-embed.\n console.error(\"sense: recorded the embedding model's resolved identity; vectors are unaffected\");\n await setMeta(conn, 'features', wantFeatures);\n } else if (embedKind !== null) {\n // Chunk boundaries are file-derived, not model-derived (embed.ts's chunksOf), so neither\n // case needs any file reparsed for any feature but embed itself.\n embedInvalidate = embedKind;\n console.error(embedKind === 'model' ? 'sense: config change (embed settings) invalidates only the stale vectors' : 'sense: config change (embed settings) rebuilds only the embeddings it affects');\n await setMeta(conn, 'features', wantFeatures);\n } else if (presetForced !== null) {\n // Membership only: which files a preset covers moved, not a file's own content, a\n // global feature, or the embed model. reconcile() already knows how to\n // add, update, and remove a file correctly (every cross-feature cascade included); it\n // just needs telling which unchanged files to treat as touched (forcedPaths, above).\n forcedPaths = presetForced;\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) reparses only the files it affects`);\n await setMeta(conn, 'features', wantFeatures);\n } else if (toggles !== null) {\n // A feature turned off or on: reconcile.ts's activeFeatures skips a disabled feature's\n // hooks entirely, so its rows rot while off -- invalidateFeatures drops them and, for a\n // feature turning back on, fully re-derives across every indexed file rather than\n // trusting rows written before or during that window.\n featureToggles = toggles;\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) reparses only the feature it affects`);\n await setMeta(conn, 'features', wantFeatures);\n } else {\n const changed = signatureDiff(features ?? '', wantFeatures);\n console.error(`sense: config change (${changed}) rebuilds the index`);\n closed = true;\n await dialect.close(handle);\n clearCache(cfg);\n return connectWithDialect(cfg, dialect);\n }\n }\n\n await dialect.ensureSchema(handle, conn, cfg);\n\n // 3x the largest reconcile this cache has recorded, floored at 30s and capped at 10min.\n // Installed before build() -- that call is the one that races a watcher's transaction.\n if (dialect.setDerivedBusyTimeout) {\n const recordedMaxMs = Number((await getMeta(conn, 'reconcile_max_ms')) ?? '0');\n await dialect.setDerivedBusyTimeout(handle, conn, Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000));\n }\n\n // Runs before build(): forcedPaths, embedInvalidate and featureToggles never target the same\n // call (each comes from its own mutually exclusive branch above: a preset-only, embed-only, or\n // feature-toggle-only changed-key set).\n const embedParsed = embedInvalidate ? (await builder.invalidate(embedInvalidate)).parsed : 0;\n const featureParsed = featureToggles ? (await builder.invalidateFeatures(featureToggles)).parsed : 0;\n\n const { parsed, warnings, stages } = await builder.build(forcedPaths);\n // Every open reconciles once and is done with it -- a watcher opens fresh per event instead of holding this connection (src/watch.ts).\n await builder.close();\n\n return { handle, conn, cfg, dbPath, parsed: parsed + embedParsed + featureParsed, warnings, stages };\n } catch (err) {\n await builder.close();\n if (!closed) await dialect.close(handle);\n throw err;\n }\n}\n\n// A store's open(): connects (see connectWithDialect above), then wraps the resulting connection\n// in the Store interface. The builder's pool closes right after the initial build.\nexport async function openWithDialect<Handle>(cfg: ResolvedConfig, dialect: OpenDialect<Handle>): Promise<OpenResult> {\n // `baseDir` was the original public open() input. Keep direct API callers working while\n // making the two ownership paths explicit for every internal operation.\n if (!cfg.rootDir) {\n if (!cfg.baseDir) throw new SenseError('CONFIG_INVALID', 'open requires rootDir (or legacy baseDir)');\n cfg.rootDir = cfg.baseDir;\n }\n if (!cfg.configDir) cfg.configDir = cfg.rootDir;\n const { handle, conn, cfg: resolvedCfg, dbPath, parsed, warnings, stages } = await connectWithDialect(cfg, dialect);\n const store = dialect.createStore(handle, conn, resolvedCfg);\n // reconcile ran before this object existed, so its chunk text is keyed by the connection.\n rekeyChunkText(conn, store);\n return { store, cfg: resolvedCfg, dbPath, parsed, warnings, stages };\n}\n\nexport async function docCount(store: Store): Promise<number> {\n const stmt = await store.prepare('SELECT COUNT(*) AS n FROM frontmatter');\n return ((await stmt.get()) as { n: number }).n;\n}\n"],"names":["docCount","openWithDialect","LOCK_POLL_MS","lockWait","channel","connectUnlocked","dbPath","cfg","dialect","budgetMs","deadline","err","lockWaitBudgetMs","configDir","baseDir","Date","now","connect","isLocked","hasSubscribers","publish","store","SenseError","Math","round","Promise","resolve","setTimeout","connectWithDialect","stateDir","handle","conn","closed","builder","version","features","wantFeatures","forcedPaths","embedInvalidate","featureToggles","changedKeys","presetForced","embedKind","toggles","changed","recordedMaxMs","embedParsed","featureParsed","parsed","warnings","stages","join","STATE_DIR","mkdirSync","recursive","filename","createBuilder","rootDir","reconcileDialect","exec","getMeta","featureSignature","FEATURES","schemaVersion","console","error","close","clearCache","changedSignatureKeys","isPresetOnlyChange","forcedPresetPaths","size","has","classifyEmbedChange","isFeatureOnlyChange","classifyFeatureToggles","embedIdentityAdopted","setMeta","signatureDiff","ensureSchema","setDerivedBusyTimeout","Number","min","max","invalidate","invalidateFeatures","build","resolvedCfg","createStore","rekeyChunkText","stmt","prepare","get","n"],"mappings":"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"}
@@ -1,5 +1,10 @@
1
- import type { ReconcileDialect } from '../types.js';
1
+ import type { ReconcileDelta } from '../../features/types.js';
2
+ import type { ParsedDoc } from '../../scan/index.js';
3
+ import type { Connection, ReconcileDialect } from '../types.js';
2
4
  export declare const CONTENT_FTS_DDL: readonly ["CREATE INDEX IF NOT EXISTS content_fts ON content USING fts (title_stem, summary_stem, text_stem) WITH (weights = 'title_stem=10.0,summary_stem=5.0,text_stem=1.0')", "CREATE INDEX IF NOT EXISTS content_fts_ngram ON content USING fts (title_ngram, summary_ngram, text_ngram) WITH (tokenizer='ngram', weights='title_ngram=10.0,summary_ngram=5.0,text_ngram=1.0')"];
3
5
  export declare const CONTENT_FTS_NAMES: readonly ["content_fts", "content_fts_ngram"];
4
6
  export declare const FTS_REBUILD_THRESHOLD = 250;
7
+ export type TursoFtsStrategy = 'incremental' | 'rebuild';
8
+ export declare function tursoFtsStrategy(delta: ReconcileDelta): TursoFtsStrategy;
9
+ export declare function reconcileTursoContentWithStrategy(conn: Connection, touched: string[], docs: ParsedDoc[], strategy: TursoFtsStrategy): Promise<void>;
5
10
  export declare const tursoDialect: ReconcileDialect;
@@ -1,5 +1,10 @@
1
- import type { ReconcileDialect } from '../types.js';
1
+ import type { ReconcileDelta } from '../../features/types.js';
2
+ import type { ParsedDoc } from '../../scan/index.js';
3
+ import type { Connection, ReconcileDialect } from '../types.js';
2
4
  export declare const CONTENT_FTS_DDL: readonly ["CREATE INDEX IF NOT EXISTS content_fts ON content USING fts (title_stem, summary_stem, text_stem) WITH (weights = 'title_stem=10.0,summary_stem=5.0,text_stem=1.0')", "CREATE INDEX IF NOT EXISTS content_fts_ngram ON content USING fts (title_ngram, summary_ngram, text_ngram) WITH (tokenizer='ngram', weights='title_ngram=10.0,summary_ngram=5.0,text_ngram=1.0')"];
3
5
  export declare const CONTENT_FTS_NAMES: readonly ["content_fts", "content_fts_ngram"];
4
6
  export declare const FTS_REBUILD_THRESHOLD = 250;
7
+ export type TursoFtsStrategy = 'incremental' | 'rebuild';
8
+ export declare function tursoFtsStrategy(delta: ReconcileDelta): TursoFtsStrategy;
9
+ export declare function reconcileTursoContentWithStrategy(conn: Connection, touched: string[], docs: ParsedDoc[], strategy: TursoFtsStrategy): Promise<void>;
5
10
  export declare const tursoDialect: ReconcileDialect;
@@ -18,8 +18,14 @@ _export(exports, {
18
18
  get FTS_REBUILD_THRESHOLD () {
19
19
  return FTS_REBUILD_THRESHOLD;
20
20
  },
21
+ get reconcileTursoContentWithStrategy () {
22
+ return reconcileTursoContentWithStrategy;
23
+ },
21
24
  get tursoDialect () {
22
25
  return tursoDialect;
26
+ },
27
+ get tursoFtsStrategy () {
28
+ return tursoFtsStrategy;
23
29
  }
24
30
  });
25
31
  var _errorsts = require("../../errors.js");
@@ -186,16 +192,19 @@ function contentRow(doc) {
186
192
  ngramSidecar(text)
187
193
  ];
188
194
  }
189
- function reconcileContent(conn, touched, docs, delta) {
195
+ function tursoFtsStrategy(delta) {
196
+ var churn = delta.reparsed.length + delta.vanished.length;
197
+ return delta.files.length === 0 || churn > FTS_REBUILD_THRESHOLD ? 'rebuild' : 'incremental';
198
+ }
199
+ function reconcileTursoContentWithStrategy(conn, touched, docs, strategy) {
190
200
  return _async_to_generator(function() {
191
- var churn, bulk, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, name, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ddl, err;
201
+ var bulk, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, name, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ddl, err;
192
202
  return _ts_generator(this, function(_state) {
193
203
  switch(_state.label){
194
204
  case 0:
195
- churn = delta.reparsed.length + delta.vanished.length;
196
205
  // Tantivy indexes per inserted row at a cost that grows with the batch, so a large insert is
197
206
  // superlinear and a rebuild wins past the threshold.
198
- bulk = delta.files.length === 0 || churn > FTS_REBUILD_THRESHOLD;
207
+ bulk = strategy === 'rebuild';
199
208
  _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
200
209
  if (!bulk) return [
201
210
  3,
@@ -353,6 +362,24 @@ function reconcileContent(conn, touched, docs, delta) {
353
362
  });
354
363
  })();
355
364
  }
365
+ function reconcileTursoContent(conn, touched, docs, delta, _cfg) {
366
+ return _async_to_generator(function() {
367
+ return _ts_generator(this, function(_state) {
368
+ switch(_state.label){
369
+ case 0:
370
+ return [
371
+ 4,
372
+ reconcileTursoContentWithStrategy(conn, touched, docs, tursoFtsStrategy(delta))
373
+ ];
374
+ case 1:
375
+ _state.sent();
376
+ return [
377
+ 2
378
+ ];
379
+ }
380
+ });
381
+ })();
382
+ }
356
383
  // turso's ADD COLUMN is metadata-only, so a loop costs nothing extra over one statement.
357
384
  function addColumns(conn, names) {
358
385
  return _async_to_generator(function() {
@@ -434,7 +461,7 @@ var tursoDialect = {
434
461
  }
435
462
  },
436
463
  addColumns: addColumns,
437
- reconcileContent: reconcileContent,
464
+ reconcileContent: reconcileTursoContent,
438
465
  recordDuration: _sharedts.recordReconcileDuration
439
466
  };
440
467
  /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/turso/reconcile.ts"],"sourcesContent":["import { SenseError } from '../../errors.ts';\nimport type { ReconcileDelta } from '../../features/types.ts';\nimport type { ParsedDoc } from '../../scan/index.ts';\nimport { hasUnspacedRun } from '../../text/segment.ts';\nimport { quoteIdent, recordReconcileDuration } from '../shared.ts';\nimport { BEGIN_WRITE } from '../transaction.ts';\nimport type { Connection, ReconcileDialect } from '../types.ts';\nimport { stemFolded } from './lexical-text.ts';\n\n// This store's dialect (types.ts's ReconcileDialect) for the shared orchestration in\n// store/reconcile.ts. content is a plain table with \"_ngram\" sidecars for lexical.ts.\n\n// The ngram index is scoped to disjoint \"_ngram\" sidecar columns: a second index over the same\n// columns makes a bare substring match a whole word, defeating the prefix-query rejection.\n// The two FTS indexes, named here (not open.ts) because reconcile drops and rebuilds them around\n// a bulk load: Tantivy maintains them per inserted row, which is quadratic in what is already indexed.\nexport const CONTENT_FTS_DDL = [\n `CREATE INDEX IF NOT EXISTS content_fts ON content USING fts (title_stem, summary_stem, text_stem) WITH (weights = 'title_stem=10.0,summary_stem=5.0,text_stem=1.0')`,\n `CREATE INDEX IF NOT EXISTS content_fts_ngram ON content USING fts (title_ngram, summary_ngram, text_ngram) WITH (tokenizer='ngram', weights='title_ngram=10.0,summary_ngram=5.0,text_ngram=1.0')`,\n] as const;\nexport const CONTENT_FTS_NAMES = ['content_fts', 'content_fts_ngram'] as const;\n\n// '' when the field has no unspaced-script run (the common case), so the ngram index carries\n// nothing for it -- same \"pay for nothing when absent\" shape as sqlite's segmentField sidecars.\nfunction ngramSidecar(text: string): string {\n return hasUnspacedRun(text) ? text : '';\n}\n\n// Not a compile-time cap on ALTER TABLE ADD COLUMN (spike-measured: turso accepts 10,000 with\n// no error); the real fence is a SELECT projecting more than this many result columns, which fails to prepare.\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\n// Changed files above which rebuilding the FTS index beats maintaining it per row. Measured\n// 2026-08-30; the derivation and its bounds are pinned in this file's spec.\nexport const FTS_REBUILD_THRESHOLD = 250;\n\n// No rowid coupling (unlike sqlite's FTS5 content): `path` is content's own primary key.\nconst INSERT_CONTENT_SQL = `INSERT INTO content (\"path\", title, summary, text, title_stem, summary_stem, text_stem, title_ngram, summary_ngram, text_ngram) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n\nfunction contentRow(doc: ParsedDoc): unknown[] {\n const { title, summary, text } = doc.search;\n return [doc.relPath, title, summary, text, stemFolded(title), stemFolded(summary), stemFolded(text), ngramSidecar(title), ngramSidecar(summary), ngramSidecar(text)];\n}\n\nasync function reconcileContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta): Promise<void> {\n const churn = delta.reparsed.length + delta.vanished.length;\n\n // Tantivy indexes per inserted row at a cost that grows with the batch, so a large insert is\n // superlinear and a rebuild wins past the threshold.\n const bulk = delta.files.length === 0 || churn > FTS_REBUILD_THRESHOLD;\n if (bulk) for (const name of CONTENT_FTS_NAMES) await conn.exec(`DROP INDEX IF EXISTS ${name}`);\n\n // content is a plain table keyed by its own path (no rowid subquery, unlike sqlite's FTS5\n // content), so vanished and reparsed docs delete in one pass.\n if (touched.length > 0)\n await conn.runBatch(\n 'DELETE FROM content WHERE \"path\" = ?',\n touched.map((p) => [p])\n );\n if (docs.length > 0) await conn.runBatch(INSERT_CONTENT_SQL, docs.map(contentRow));\n if (bulk) for (const ddl of CONTENT_FTS_DDL) await conn.exec(ddl);\n}\n\n// turso's ADD COLUMN is metadata-only, so a loop costs nothing extra over one statement.\nasync function addColumns(conn: Connection, names: string[]): Promise<void> {\n for (const name of names) await conn.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(name)}`);\n}\n\nexport const tursoDialect: ReconcileDialect = {\n beginMode: () => BEGIN_WRITE,\n checkColumnLimit(count) {\n if (count > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${count} columns, crossing turso's SELECT result-set column limit (${MAX_FRONTMATTER_COLUMNS}; ALTER TABLE ADD COLUMN itself accepts far more, but a query projecting past this many columns fails to prepare). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n },\n addColumns,\n reconcileContent,\n recordDuration: recordReconcileDuration,\n};\n"],"names":["CONTENT_FTS_DDL","CONTENT_FTS_NAMES","FTS_REBUILD_THRESHOLD","tursoDialect","ngramSidecar","text","hasUnspacedRun","MAX_FRONTMATTER_COLUMNS","INSERT_CONTENT_SQL","contentRow","doc","search","title","summary","relPath","stemFolded","reconcileContent","conn","touched","docs","delta","churn","bulk","name","ddl","reparsed","length","vanished","files","exec","runBatch","map","p","addColumns","names","quoteIdent","beginMode","BEGIN_WRITE","checkColumnLimit","count","SenseError","recordDuration","recordReconcileDuration"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/turso/reconcile.ts"],"sourcesContent":["import type { Config } from '../../config/index.ts';\nimport { SenseError } from '../../errors.ts';\nimport type { ReconcileDelta } from '../../features/types.ts';\nimport type { ParsedDoc } from '../../scan/index.ts';\nimport { hasUnspacedRun } from '../../text/segment.ts';\nimport { quoteIdent, recordReconcileDuration } from '../shared.ts';\nimport { BEGIN_WRITE } from '../transaction.ts';\nimport type { Connection, ReconcileDialect } from '../types.ts';\nimport { stemFolded } from './lexical-text.ts';\n\n// This store's dialect (types.ts's ReconcileDialect) for the shared orchestration in\n// store/reconcile.ts. content is a plain table with \"_ngram\" sidecars for lexical.ts.\n\n// The ngram index is scoped to disjoint \"_ngram\" sidecar columns: a second index over the same\n// columns makes a bare substring match a whole word, defeating the prefix-query rejection.\n// The two FTS indexes, named here (not open.ts) because reconcile drops and rebuilds them around\n// a bulk load: Tantivy maintains them per inserted row, which is quadratic in what is already indexed.\nexport const CONTENT_FTS_DDL = [\n `CREATE INDEX IF NOT EXISTS content_fts ON content USING fts (title_stem, summary_stem, text_stem) WITH (weights = 'title_stem=10.0,summary_stem=5.0,text_stem=1.0')`,\n `CREATE INDEX IF NOT EXISTS content_fts_ngram ON content USING fts (title_ngram, summary_ngram, text_ngram) WITH (tokenizer='ngram', weights='title_ngram=10.0,summary_ngram=5.0,text_ngram=1.0')`,\n] as const;\nexport const CONTENT_FTS_NAMES = ['content_fts', 'content_fts_ngram'] as const;\n\n// '' when the field has no unspaced-script run (the common case), so the ngram index carries\n// nothing for it -- same \"pay for nothing when absent\" shape as sqlite's segmentField sidecars.\nfunction ngramSidecar(text: string): string {\n return hasUnspacedRun(text) ? text : '';\n}\n\n// Not a compile-time cap on ALTER TABLE ADD COLUMN (spike-measured: turso accepts 10,000 with\n// no error); the real fence is a SELECT projecting more than this many result columns, which fails to prepare.\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\n// Changed files above which rebuilding the FTS index beats maintaining it per row. Measured\n// 2026-08-30; the derivation and its bounds are pinned in this file's spec.\nexport const FTS_REBUILD_THRESHOLD = 250;\n\nexport type TursoFtsStrategy = 'incremental' | 'rebuild';\n\n// No rowid coupling (unlike sqlite's FTS5 content): `path` is content's own primary key.\nconst INSERT_CONTENT_SQL = `INSERT INTO content (\"path\", title, summary, text, title_stem, summary_stem, text_stem, title_ngram, summary_ngram, text_ngram) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n\nfunction contentRow(doc: ParsedDoc): unknown[] {\n const { title, summary, text } = doc.search;\n return [doc.relPath, title, summary, text, stemFolded(title), stemFolded(summary), stemFolded(text), ngramSidecar(title), ngramSidecar(summary), ngramSidecar(text)];\n}\n\nexport function tursoFtsStrategy(delta: ReconcileDelta): TursoFtsStrategy {\n const churn = delta.reparsed.length + delta.vanished.length;\n return delta.files.length === 0 || churn > FTS_REBUILD_THRESHOLD ? 'rebuild' : 'incremental';\n}\n\n// Kept transaction-free because the shared reconcile owns the one write transaction. The explicit\n// strategy is internal composition for real-engine diagnostics; production selects it from delta.\nexport async function reconcileTursoContentWithStrategy(conn: Connection, touched: string[], docs: ParsedDoc[], strategy: TursoFtsStrategy): Promise<void> {\n // Tantivy indexes per inserted row at a cost that grows with the batch, so a large insert is\n // superlinear and a rebuild wins past the threshold.\n const bulk = strategy === 'rebuild';\n if (bulk) for (const name of CONTENT_FTS_NAMES) await conn.exec(`DROP INDEX IF EXISTS ${name}`);\n\n // content is a plain table keyed by its own path (no rowid subquery, unlike sqlite's FTS5\n // content), so vanished and reparsed docs delete in one pass.\n if (touched.length > 0)\n await conn.runBatch(\n 'DELETE FROM content WHERE \"path\" = ?',\n touched.map((p) => [p])\n );\n if (docs.length > 0) await conn.runBatch(INSERT_CONTENT_SQL, docs.map(contentRow));\n if (bulk) for (const ddl of CONTENT_FTS_DDL) await conn.exec(ddl);\n}\n\nasync function reconcileTursoContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, _cfg: Config): Promise<void> {\n await reconcileTursoContentWithStrategy(conn, touched, docs, tursoFtsStrategy(delta));\n}\n\n// turso's ADD COLUMN is metadata-only, so a loop costs nothing extra over one statement.\nasync function addColumns(conn: Connection, names: string[]): Promise<void> {\n for (const name of names) await conn.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(name)}`);\n}\n\nexport const tursoDialect: ReconcileDialect = {\n beginMode: () => BEGIN_WRITE,\n checkColumnLimit(count) {\n if (count > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${count} columns, crossing turso's SELECT result-set column limit (${MAX_FRONTMATTER_COLUMNS}; ALTER TABLE ADD COLUMN itself accepts far more, but a query projecting past this many columns fails to prepare). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n },\n addColumns,\n reconcileContent: reconcileTursoContent,\n recordDuration: recordReconcileDuration,\n};\n"],"names":["CONTENT_FTS_DDL","CONTENT_FTS_NAMES","FTS_REBUILD_THRESHOLD","reconcileTursoContentWithStrategy","tursoDialect","tursoFtsStrategy","ngramSidecar","text","hasUnspacedRun","MAX_FRONTMATTER_COLUMNS","INSERT_CONTENT_SQL","contentRow","doc","search","title","summary","relPath","stemFolded","delta","churn","reparsed","length","vanished","files","conn","touched","docs","strategy","bulk","name","ddl","exec","runBatch","map","p","reconcileTursoContent","_cfg","addColumns","names","quoteIdent","beginMode","BEGIN_WRITE","checkColumnLimit","count","SenseError","reconcileContent","recordDuration","recordReconcileDuration"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedConfig } from './config/index.js';
2
2
  export type WatchEvent = {
3
3
  type: 'started';
4
- baseDir: string;
4
+ rootDir: string;
5
5
  dbPath: string;
6
6
  } | {
7
7
  type: 'reconciled';
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedConfig } from './config/index.js';
2
2
  export type WatchEvent = {
3
3
  type: 'started';
4
- baseDir: string;
4
+ rootDir: string;
5
5
  dbPath: string;
6
6
  } | {
7
7
  type: 'reconciled';
package/dist/cjs/watch.js CHANGED
@@ -144,7 +144,7 @@ var HEARTBEAT_INTERVAL_MS = 5000;
144
144
  var STALE_HEARTBEAT_MS = 15000;
145
145
  function runWatch(_0) {
146
146
  return _async_to_generator(function(cfg) {
147
- var opts, _opts_onEvent, _opts_debounceMs, _opts_heartbeatIntervalMs, onEvent, debounceMs, heartbeatIntervalMs, baseDir, _ref, initialStore, dbPath, initialWarnings, initialParsed, existingHeartbeat, age, initialTotal, _tmp, stopping, running, queuedAlways, tick, startCycle, requestCycle, debounceTimer, scheduleReconcile, watcher, heartbeatTimer;
147
+ var opts, _opts_onEvent, _opts_debounceMs, _opts_heartbeatIntervalMs, _cfg_rootDir, onEvent, debounceMs, heartbeatIntervalMs, _ref, initialStore, dbPath, initialWarnings, initialParsed, rootDir, existingHeartbeat, age, initialTotal, _tmp, stopping, running, queuedAlways, tick, startCycle, requestCycle, debounceTimer, scheduleReconcile, watcher, heartbeatTimer;
148
148
  var _arguments = arguments;
149
149
  return _ts_generator(this, function(_state) {
150
150
  switch(_state.label){
@@ -153,13 +153,14 @@ function runWatch(_0) {
153
153
  onEvent = (_opts_onEvent = opts.onEvent) !== null && _opts_onEvent !== void 0 ? _opts_onEvent : function() {};
154
154
  debounceMs = (_opts_debounceMs = opts.debounceMs) !== null && _opts_debounceMs !== void 0 ? _opts_debounceMs : DEBOUNCE_MS;
155
155
  heartbeatIntervalMs = (_opts_heartbeatIntervalMs = opts.heartbeatIntervalMs) !== null && _opts_heartbeatIntervalMs !== void 0 ? _opts_heartbeatIntervalMs : HEARTBEAT_INTERVAL_MS;
156
- baseDir = cfg.baseDir;
157
156
  return [
158
157
  4,
159
158
  (0, _indexts1.openStore)(cfg)
160
159
  ];
161
160
  case 1:
162
161
  _ref = _state.sent(), initialStore = _ref.store, dbPath = _ref.dbPath, initialWarnings = _ref.warnings, initialParsed = _ref.parsed;
162
+ // openStore normalizes the legacy direct-API baseDir before a watcher touches the filesystem.
163
+ rootDir = (_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir;
163
164
  return [
164
165
  4,
165
166
  (0, _indexts1.getMeta)(initialStore, 'watch_heartbeat')
@@ -222,7 +223,7 @@ function runWatch(_0) {
222
223
  _state.sent();
223
224
  onEvent({
224
225
  type: 'started',
225
- baseDir: baseDir,
226
+ rootDir: rootDir,
226
227
  dbPath: dbPath
227
228
  });
228
229
  if (initialWarnings.length > 0 || initialParsed > 0) {
@@ -359,7 +360,7 @@ function runWatch(_0) {
359
360
  };
360
361
  // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with
361
362
  // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.
362
- watcher = (0, _nodefs.watch)(baseDir, {
363
+ watcher = (0, _nodefs.watch)(rootDir, {
363
364
  recursive: true
364
365
  }, function(_event, filename) {
365
366
  if (typeof filename === 'string' && filename.startsWith(_indexts.STATE_DIR)) return;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, getMeta, openStore, setMeta } from './store/index.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; baseDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. duckdb/turso hand the cache file to\n// one process at a time, so every cycle opens, reconciles, writes the heartbeat/pid rows, and closes.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;\n const baseDir = cfg.baseDir;\n\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n const existingHeartbeat = await getMeta(initialStore, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n await initialStore.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n // total is read before close: an event fires only once nothing is held open again.\n const initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n await setMeta(initialStore, 'watch_heartbeat', new Date().toISOString());\n await setMeta(initialStore, 'watch_pid', String(process.pid));\n await initialStore.close();\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n await setMeta(store, 'watch_heartbeat', new Date().toISOString());\n await setMeta(store, 'watch_pid', String(process.pid));\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with\n // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n heartbeatIntervalMs\n );\n\n return new Promise<void>((resolveShutdown) => {\n // SIGINT/SIGTERM and an aborted signal all run this same path exactly once; each is\n // unregistered here too so a second runWatch call in the same process starts clean.\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n opts.signal?.removeEventListener('abort', shutdown);\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n const { store: finalStore } = await openStore(cfg);\n await setMeta(finalStore, 'watch_heartbeat', null);\n await setMeta(finalStore, 'watch_pid', null);\n await finalStore.close();\n resolveShutdown();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n if (opts.signal?.aborted) shutdown();\n else opts.signal?.addEventListener('abort', shutdown, { once: true });\n });\n}\n"],"names":["runWatch","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","cfg","opts","onEvent","debounceMs","heartbeatIntervalMs","baseDir","initialStore","dbPath","initialWarnings","initialParsed","existingHeartbeat","age","initialTotal","stopping","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","scheduleReconcile","watcher","heartbeatTimer","openStore","store","warnings","parsed","getMeta","force","Date","now","parse","close","SenseError","Math","round","length","docCount","setMeta","toISOString","String","process","pid","type","total","alwaysEmit","err","message","then","again","clearTimeout","setTimeout","fsWatch","recursive","_event","filename","startsWith","STATE_DIR","setInterval","guardedTick","Promise","resolveShutdown","shutdown","finalStore","off","signal","removeEventListener","clearInterval","once","aborted","addEventListener"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, getMeta, openStore, setMeta } from './store/index.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. duckdb/turso hand the cache file to\n// one process at a time, so every cycle opens, reconciles, writes the heartbeat/pid rows, and closes.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n // openStore normalizes the legacy direct-API baseDir before a watcher touches the filesystem.\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const existingHeartbeat = await getMeta(initialStore, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n await initialStore.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n // total is read before close: an event fires only once nothing is held open again.\n const initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n await setMeta(initialStore, 'watch_heartbeat', new Date().toISOString());\n await setMeta(initialStore, 'watch_pid', String(process.pid));\n await initialStore.close();\n\n onEvent({ type: 'started', rootDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n await setMeta(store, 'watch_heartbeat', new Date().toISOString());\n await setMeta(store, 'watch_pid', String(process.pid));\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with\n // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n heartbeatIntervalMs\n );\n\n return new Promise<void>((resolveShutdown) => {\n // SIGINT/SIGTERM and an aborted signal all run this same path exactly once; each is\n // unregistered here too so a second runWatch call in the same process starts clean.\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n opts.signal?.removeEventListener('abort', shutdown);\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n const { store: finalStore } = await openStore(cfg);\n await setMeta(finalStore, 'watch_heartbeat', null);\n await setMeta(finalStore, 'watch_pid', null);\n await finalStore.close();\n resolveShutdown();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n if (opts.signal?.aborted) shutdown();\n else opts.signal?.addEventListener('abort', shutdown, { once: true });\n });\n}\n"],"names":["runWatch","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","cfg","opts","onEvent","debounceMs","heartbeatIntervalMs","initialStore","dbPath","initialWarnings","initialParsed","rootDir","existingHeartbeat","age","initialTotal","stopping","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","scheduleReconcile","watcher","heartbeatTimer","openStore","store","warnings","parsed","baseDir","getMeta","force","Date","now","parse","close","SenseError","Math","round","length","docCount","setMeta","toISOString","String","process","pid","type","total","alwaysEmit","err","message","then","again","clearTimeout","setTimeout","fsWatch","recursive","_event","filename","startsWith","STATE_DIR","setInterval","guardedTick","Promise","resolveShutdown","shutdown","finalStore","off","signal","removeEventListener","clearInterval","once","aborted","addEventListener"],"mappings":"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"}
@@ -76,7 +76,7 @@ function languagesInfo(e) {
76
76
  };
77
77
  }
78
78
  const status = async (ctx)=>{
79
- var _ref, _process_env_e_key, _cfg_migratedFrom, _cfg_unknownKeys, _cfg_queries;
79
+ var _ref, _process_env_e_key, _cfg_migratedFrom, _cfg_unknownKeys, _cfg_rootDir, _cfg_rootDir1, _cfg_configDir, _cfg_queries;
80
80
  const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.status}`, {
81
81
  ...FORMAT,
82
82
  ...CONFIG
@@ -114,7 +114,9 @@ const status = async (ctx)=>{
114
114
  configVersion: SUPPORTED_CONFIG_VERSION,
115
115
  migratedFrom: (_cfg_migratedFrom = cfg.migratedFrom) !== null && _cfg_migratedFrom !== void 0 ? _cfg_migratedFrom : null,
116
116
  unknownConfigKeys: (_cfg_unknownKeys = cfg.unknownKeys) !== null && _cfg_unknownKeys !== void 0 ? _cfg_unknownKeys : null,
117
- tree: cfg.baseDir,
117
+ tree: (_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir,
118
+ treeRoot: (_cfg_rootDir1 = cfg.rootDir) !== null && _cfg_rootDir1 !== void 0 ? _cfg_rootDir1 : cfg.baseDir,
119
+ configDir: (_cfg_configDir = cfg.configDir) !== null && _cfg_configDir !== void 0 ? _cfg_configDir : cfg.baseDir,
118
120
  cache: dirname(dbPath),
119
121
  db: dbPath,
120
122
  cacheSchema,
@@ -148,7 +150,8 @@ const status = async (ctx)=>{
148
150
  console.log(`config: ${result.config} (v${SUPPORTED_CONFIG_VERSION}${migrated})`);
149
151
  // A load-time stderr warning has scrolled away by the time anyone looks here.
150
152
  if (cfg.unknownKeys) console.log(` unknown keys, ignored: ${cfg.unknownKeys.join(', ')}`);
151
- console.log(`tree: ${result.tree}`);
153
+ console.log(`tree root: ${result.treeRoot}`);
154
+ console.log(`configDir: ${result.configDir}`);
152
155
  console.log(`cache: ${result.cache} (schema ${result.cacheSchema}; delete this directory to reset)`);
153
156
  console.log(`docs: ${result.docs}${result.unparseableFrontmatter > 0 ? ` (${result.unparseableFrontmatter} with unparseable frontmatter: WHERE _parse_error IS NOT NULL)` : ''}`);
154
157
  console.log('');
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/status.ts"],"sourcesContent":["import { dirname } from 'node:path';\nimport { presetCoverage } from '../commands/status.ts';\nimport { embedConfig, featureStates, SUPPORTED_CONFIG_VERSION } from '../config/index.ts';\nimport { languageDistribution } from '../embed/distribution.ts';\nimport { probeReachable } from '../embed/http.ts';\nimport { isDownloadable, MODEL_FILENAMES, modelDir, modelPresent, readLanguages } from '../embed/store.ts';\nimport { featuresLine, presetsLines, stringifyJson } from '../output/output.ts';\nimport { docCount, getMeta, openStore } from '../store/index.ts';\nimport type { Store } from '../store/types.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, formatOf, parse, printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// status names what you cannot read off the config file: locations sense chose, values it derived, state it stored. Config content itself is not echoed, since duplicating its globs here would create two places that disagree after an edit.\n// The exception is the embed block's url and key, whose effect is remote and otherwise invisible.\n\n// Vectors are computed on the first semantic query, not at reconcile, so a tree can have a semantic-on preset with nothing embedded yet.\n// Reported here because a fast reconcile on such a tree otherwise reads as \"the feature isn't on\".\nasync function vectorState(store: Store): Promise<{ embedded: number; pending: number } | null> {\n try {\n const stmt = await store.prepare('SELECT COUNT(vector) AS embedded, COUNT(*) - COUNT(vector) AS pending FROM embeddings');\n return (await stmt.get()) as { embedded: number; pending: number };\n } catch {\n return null;\n }\n}\n\nasync function parseErrorCount(store: Store): Promise<number> {\n const stmt = await store.prepare(`SELECT COUNT(*) AS n FROM frontmatter WHERE \"_parse_error\" IS NOT NULL`);\n return ((await stmt.get()) as { n: number }).n;\n}\n\n// A URL can carry credentials in its userinfo (https://user:pass@host/v1). status output gets\n// pasted into issues and transcripts, so it is always safe to paste.\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (!parsed.username && !parsed.password) return url;\n parsed.username = '';\n parsed.password = '';\n return `${parsed.toString().replace(/\\/$/, '')} (credentials redacted)`;\n } catch {\n return url;\n }\n}\n\n// A stopped Ollama/LM Studio/Cohere endpoint prints identically to a running one otherwise; any\n// HTTP response counts as reachable (probeReachable), so this only distinguishes up from down.\nasync function embedReachable(e: { provider: string; url?: string }): Promise<boolean | null> {\n if (e.provider === 'static' || (e.provider === 'openai' && !e.url)) return null;\n const base = (e.url ?? 'https://api.cohere.com').replace(/\\/+$/, '');\n return probeReachable(e.provider === 'openai' ? `${base}/models` : base);\n}\n\ntype LanguagesState = 'declared' | 'none' | 'unresolved' | null;\n\n// Config-declared languages win over the model card, for every provider, as registry.ts does.\n// A local-path model has no card by design, so it is never \"unresolved\": that means an HF id.\nfunction languagesInfo(e: NonNullable<ReturnType<typeof embedConfig>>): { languages: string[] | null; state: LanguagesState; source: 'config' | 'card' | null } {\n if (e.languages) return { languages: e.languages, state: 'declared', source: 'config' };\n if (e.provider !== 'static' || !isDownloadable(e.model)) return { languages: null, state: null, source: null };\n const cardLanguages = readLanguages(e.model);\n if (cardLanguages === undefined) return { languages: null, state: 'unresolved', source: null };\n if (cardLanguages.length === 0) return { languages: null, state: 'none', source: null };\n return { languages: cardLanguages, state: 'declared', source: 'card' };\n}\n\nconst status: Command = async (ctx) => {\n const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.status}`, { ...FORMAT, ...CONFIG });\n const format = formatOf(values);\n const cfg = ctx.resolveConfig(values.config as string | undefined);\n const { store, dbPath, warnings } = await openStore(cfg);\n printWarnings(warnings);\n\n const features = featureStates(cfg);\n const e = embedConfig(cfg);\n const hasModel = e ? modelPresent(cfg) : false;\n const reachable = e ? await embedReachable(e) : null;\n const vectors = e ? await vectorState(store) : null;\n const presets = await presetCoverage(store, cfg);\n const { languages: declaredLanguages, state: languagesState, source: languagesSource } = e ? languagesInfo(e) : { languages: null, state: null, source: null };\n const detectedLanguages = (await languageDistribution(store)) ?? null;\n const heartbeat = await getMeta(store, 'watch_heartbeat');\n // Written by the store's open() on every open, so it is this cache's true version on either\n // store (sqlite 18, duckdb 2); a version mismatch already triggered a rebuild before this read.\n const cacheSchema = await getMeta(store, 'schema_version');\n // Each store owns what it reports and how it is worded (Store.engineStatus); this command\n // prints entries generically, without branching on any store's name.\n const engine = await store.engineStatus();\n // The env var holds the token; only its name and whether it is set are ever reported.\n const keyEnv = e?.key ? { name: e.key, set: (process.env[e.key] ?? '') !== '' } : null;\n const result = {\n config: cfg.configPath,\n configVersion: SUPPORTED_CONFIG_VERSION,\n migratedFrom: cfg.migratedFrom ?? null,\n unknownConfigKeys: cfg.unknownKeys ?? null,\n tree: cfg.baseDir,\n cache: dirname(dbPath),\n db: dbPath,\n cacheSchema,\n docs: await docCount(store),\n unparseableFrontmatter: await parseErrorCount(store),\n features: features.on,\n featuresOff: features.off,\n embed: e ? { provider: e.provider, model: e.model, dir: e.provider === 'static' ? modelDir(e.model) : null, url: e.url ? redactUrl(e.url) : null, reachable, keyEnv, downloaded: hasModel, languages: declaredLanguages, languagesState, detectedLanguages, ...(vectors ?? {}) } : null,\n presets,\n queries: Object.keys(cfg.queries ?? {}).length,\n watcherPid: await getMeta(store, 'watch_pid'),\n watcherHeartbeatSecondsAgo: heartbeat ? Math.round((Date.now() - Date.parse(heartbeat)) / 1000) : null,\n engine,\n };\n\n if (format === 'json') {\n console.log(stringifyJson(result, 2));\n } else {\n const migrated = cfg.migratedFrom !== undefined ? `, migrated from v${cfg.migratedFrom} on this run` : '';\n console.log(`config: ${result.config} (v${SUPPORTED_CONFIG_VERSION}${migrated})`);\n // A load-time stderr warning has scrolled away by the time anyone looks here.\n if (cfg.unknownKeys) console.log(` unknown keys, ignored: ${cfg.unknownKeys.join(', ')}`);\n console.log(`tree: ${result.tree}`);\n console.log(`cache: ${result.cache} (schema ${result.cacheSchema}; delete this directory to reset)`);\n console.log(`docs: ${result.docs}${result.unparseableFrontmatter > 0 ? ` (${result.unparseableFrontmatter} with unparseable frontmatter: WHERE _parse_error IS NOT NULL)` : ''}`);\n console.log('');\n console.log(featuresLine(features));\n // One authoritative block, off states included: an absent model reads as a degraded search,\n // not as an empty corpus.\n if (!e) console.log('embed: off (no preset asks for vectors)');\n else {\n console.log(`embed: ${e.provider} ${e.model}`);\n if (e.provider === 'static') console.log(` model: ${modelDir(e.model)} (${hasModel ? 'present' : `missing, ${isDownloadable(e.model) ? `fetches automatically at first search, or run \\`${ctx.name} download\\` to prefetch` : `no ${MODEL_FILENAMES} in that directory`}`})`);\n if (declaredLanguages && languagesSource === 'config') console.log(` languages: ${declaredLanguages.join(', ')} (declared in embed.languages)`);\n else if (declaredLanguages && languagesSource === 'card') console.log(` languages: ${declaredLanguages.join(', ')} (declared by the model card)`);\n else if (languagesState === 'none') console.log(' languages: none declared by the model card');\n else if (languagesState === 'unresolved') console.log(` languages: unresolved (model card unreachable; run \\`${ctx.name} download\\`)`);\n // Cohere defaults to api.cohere.com with no url in the config; show it anyway so the\n // reachability probe below has something to name.\n if (e.url || reachable !== null) {\n const shown = e.url ? redactUrl(e.url) : 'https://api.cohere.com (default)';\n console.log(` url: ${shown}${reachable === null ? '' : reachable ? ' (reachable)' : ' (unreachable)'}`);\n }\n if (keyEnv) console.log(` key: env ${keyEnv.name} (${keyEnv.set ? 'set' : 'NOT SET'})`);\n if (!hasModel) console.log(' vectors: off until the model is present; search runs on words and links');\n else if (vectors) console.log(` vectors: ${vectors.embedded} embedded, ${vectors.pending} pending (embedded on the first semantic search)`);\n if (detectedLanguages) {\n const total = Object.values(detectedLanguages).reduce((a, b) => a + b, 0);\n const breakdown = Object.entries(detectedLanguages)\n .sort((a, b) => b[1] - a[1])\n .map(([code, n]) => `${code} ${Math.round((n / total) * 100)}%`)\n .join(', ');\n console.log(` detected: ${breakdown} (${total} chunks classified)`);\n }\n }\n console.log('');\n for (const line of presetsLines(presets)) console.log(line);\n console.log('');\n console.log(`queries: ${result.queries} saved (${ctx.name} --list)`);\n const pid = result.watcherPid ? `pid ${result.watcherPid}, ` : '';\n console.log(result.watcherHeartbeatSecondsAgo === null ? 'watcher: none' : `watcher: ${pid}last heartbeat ${result.watcherHeartbeatSecondsAgo}s ago`);\n for (const [key, value] of Object.entries(result.engine)) console.log(`${`${store.name}:`.padEnd(10)}${key} ${value}`);\n }\n await store.close();\n};\nexport default status;\n"],"names":["dirname","presetCoverage","embedConfig","featureStates","SUPPORTED_CONFIG_VERSION","languageDistribution","probeReachable","isDownloadable","MODEL_FILENAMES","modelDir","modelPresent","readLanguages","featuresLine","presetsLines","stringifyJson","docCount","getMeta","openStore","USAGE","CONFIG","FORMAT","formatOf","parse","printWarnings","vectorState","store","stmt","prepare","get","parseErrorCount","n","redactUrl","url","parsed","URL","username","password","toString","replace","embedReachable","e","provider","base","languagesInfo","languages","state","source","model","cardLanguages","undefined","length","status","ctx","process","cfg","values","argv","name","format","resolveConfig","config","dbPath","warnings","features","hasModel","reachable","vectors","presets","declaredLanguages","languagesState","languagesSource","detectedLanguages","heartbeat","cacheSchema","engine","engineStatus","keyEnv","key","set","env","result","configPath","configVersion","migratedFrom","unknownConfigKeys","unknownKeys","tree","baseDir","cache","db","docs","unparseableFrontmatter","on","featuresOff","off","embed","dir","downloaded","queries","Object","keys","watcherPid","watcherHeartbeatSecondsAgo","Math","round","Date","now","console","log","migrated","join","shown","embedded","pending","total","reduce","a","b","breakdown","entries","sort","map","code","line","pid","value","padEnd","close"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/status.ts"],"sourcesContent":["import { dirname } from 'node:path';\nimport { presetCoverage } from '../commands/status.ts';\nimport { embedConfig, featureStates, SUPPORTED_CONFIG_VERSION } from '../config/index.ts';\nimport { languageDistribution } from '../embed/distribution.ts';\nimport { probeReachable } from '../embed/http.ts';\nimport { isDownloadable, MODEL_FILENAMES, modelDir, modelPresent, readLanguages } from '../embed/store.ts';\nimport { featuresLine, presetsLines, stringifyJson } from '../output/output.ts';\nimport { docCount, getMeta, openStore } from '../store/index.ts';\nimport type { Store } from '../store/types.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, FORMAT, formatOf, parse, printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// status names what you cannot read off the config file: locations sense chose, values it derived, state it stored. Config content itself is not echoed, since duplicating its globs here would create two places that disagree after an edit.\n// The exception is the embed block's url and key, whose effect is remote and otherwise invisible.\n\n// Vectors are computed on the first semantic query, not at reconcile, so a tree can have a semantic-on preset with nothing embedded yet.\n// Reported here because a fast reconcile on such a tree otherwise reads as \"the feature isn't on\".\nasync function vectorState(store: Store): Promise<{ embedded: number; pending: number } | null> {\n try {\n const stmt = await store.prepare('SELECT COUNT(vector) AS embedded, COUNT(*) - COUNT(vector) AS pending FROM embeddings');\n return (await stmt.get()) as { embedded: number; pending: number };\n } catch {\n return null;\n }\n}\n\nasync function parseErrorCount(store: Store): Promise<number> {\n const stmt = await store.prepare(`SELECT COUNT(*) AS n FROM frontmatter WHERE \"_parse_error\" IS NOT NULL`);\n return ((await stmt.get()) as { n: number }).n;\n}\n\n// A URL can carry credentials in its userinfo (https://user:pass@host/v1). status output gets\n// pasted into issues and transcripts, so it is always safe to paste.\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n if (!parsed.username && !parsed.password) return url;\n parsed.username = '';\n parsed.password = '';\n return `${parsed.toString().replace(/\\/$/, '')} (credentials redacted)`;\n } catch {\n return url;\n }\n}\n\n// A stopped Ollama/LM Studio/Cohere endpoint prints identically to a running one otherwise; any\n// HTTP response counts as reachable (probeReachable), so this only distinguishes up from down.\nasync function embedReachable(e: { provider: string; url?: string }): Promise<boolean | null> {\n if (e.provider === 'static' || (e.provider === 'openai' && !e.url)) return null;\n const base = (e.url ?? 'https://api.cohere.com').replace(/\\/+$/, '');\n return probeReachable(e.provider === 'openai' ? `${base}/models` : base);\n}\n\ntype LanguagesState = 'declared' | 'none' | 'unresolved' | null;\n\n// Config-declared languages win over the model card, for every provider, as registry.ts does.\n// A local-path model has no card by design, so it is never \"unresolved\": that means an HF id.\nfunction languagesInfo(e: NonNullable<ReturnType<typeof embedConfig>>): { languages: string[] | null; state: LanguagesState; source: 'config' | 'card' | null } {\n if (e.languages) return { languages: e.languages, state: 'declared', source: 'config' };\n if (e.provider !== 'static' || !isDownloadable(e.model)) return { languages: null, state: null, source: null };\n const cardLanguages = readLanguages(e.model);\n if (cardLanguages === undefined) return { languages: null, state: 'unresolved', source: null };\n if (cardLanguages.length === 0) return { languages: null, state: 'none', source: null };\n return { languages: cardLanguages, state: 'declared', source: 'card' };\n}\n\nconst status: Command = async (ctx) => {\n const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.status}`, { ...FORMAT, ...CONFIG });\n const format = formatOf(values);\n const cfg = ctx.resolveConfig(values.config as string | undefined);\n const { store, dbPath, warnings } = await openStore(cfg);\n printWarnings(warnings);\n\n const features = featureStates(cfg);\n const e = embedConfig(cfg);\n const hasModel = e ? modelPresent(cfg) : false;\n const reachable = e ? await embedReachable(e) : null;\n const vectors = e ? await vectorState(store) : null;\n const presets = await presetCoverage(store, cfg);\n const { languages: declaredLanguages, state: languagesState, source: languagesSource } = e ? languagesInfo(e) : { languages: null, state: null, source: null };\n const detectedLanguages = (await languageDistribution(store)) ?? null;\n const heartbeat = await getMeta(store, 'watch_heartbeat');\n // Written by the store's open() on every open, so it is this cache's true version on either\n // store (sqlite 18, duckdb 2); a version mismatch already triggered a rebuild before this read.\n const cacheSchema = await getMeta(store, 'schema_version');\n // Each store owns what it reports and how it is worded (Store.engineStatus); this command\n // prints entries generically, without branching on any store's name.\n const engine = await store.engineStatus();\n // The env var holds the token; only its name and whether it is set are ever reported.\n const keyEnv = e?.key ? { name: e.key, set: (process.env[e.key] ?? '') !== '' } : null;\n const result = {\n config: cfg.configPath,\n configVersion: SUPPORTED_CONFIG_VERSION,\n migratedFrom: cfg.migratedFrom ?? null,\n unknownConfigKeys: cfg.unknownKeys ?? null,\n tree: cfg.rootDir ?? cfg.baseDir,\n treeRoot: cfg.rootDir ?? cfg.baseDir,\n configDir: cfg.configDir ?? cfg.baseDir,\n cache: dirname(dbPath),\n db: dbPath,\n cacheSchema,\n docs: await docCount(store),\n unparseableFrontmatter: await parseErrorCount(store),\n features: features.on,\n featuresOff: features.off,\n embed: e ? { provider: e.provider, model: e.model, dir: e.provider === 'static' ? modelDir(e.model) : null, url: e.url ? redactUrl(e.url) : null, reachable, keyEnv, downloaded: hasModel, languages: declaredLanguages, languagesState, detectedLanguages, ...(vectors ?? {}) } : null,\n presets,\n queries: Object.keys(cfg.queries ?? {}).length,\n watcherPid: await getMeta(store, 'watch_pid'),\n watcherHeartbeatSecondsAgo: heartbeat ? Math.round((Date.now() - Date.parse(heartbeat)) / 1000) : null,\n engine,\n };\n\n if (format === 'json') {\n console.log(stringifyJson(result, 2));\n } else {\n const migrated = cfg.migratedFrom !== undefined ? `, migrated from v${cfg.migratedFrom} on this run` : '';\n console.log(`config: ${result.config} (v${SUPPORTED_CONFIG_VERSION}${migrated})`);\n // A load-time stderr warning has scrolled away by the time anyone looks here.\n if (cfg.unknownKeys) console.log(` unknown keys, ignored: ${cfg.unknownKeys.join(', ')}`);\n console.log(`tree root: ${result.treeRoot}`);\n console.log(`configDir: ${result.configDir}`);\n console.log(`cache: ${result.cache} (schema ${result.cacheSchema}; delete this directory to reset)`);\n console.log(`docs: ${result.docs}${result.unparseableFrontmatter > 0 ? ` (${result.unparseableFrontmatter} with unparseable frontmatter: WHERE _parse_error IS NOT NULL)` : ''}`);\n console.log('');\n console.log(featuresLine(features));\n // One authoritative block, off states included: an absent model reads as a degraded search,\n // not as an empty corpus.\n if (!e) console.log('embed: off (no preset asks for vectors)');\n else {\n console.log(`embed: ${e.provider} ${e.model}`);\n if (e.provider === 'static') console.log(` model: ${modelDir(e.model)} (${hasModel ? 'present' : `missing, ${isDownloadable(e.model) ? `fetches automatically at first search, or run \\`${ctx.name} download\\` to prefetch` : `no ${MODEL_FILENAMES} in that directory`}`})`);\n if (declaredLanguages && languagesSource === 'config') console.log(` languages: ${declaredLanguages.join(', ')} (declared in embed.languages)`);\n else if (declaredLanguages && languagesSource === 'card') console.log(` languages: ${declaredLanguages.join(', ')} (declared by the model card)`);\n else if (languagesState === 'none') console.log(' languages: none declared by the model card');\n else if (languagesState === 'unresolved') console.log(` languages: unresolved (model card unreachable; run \\`${ctx.name} download\\`)`);\n // Cohere defaults to api.cohere.com with no url in the config; show it anyway so the\n // reachability probe below has something to name.\n if (e.url || reachable !== null) {\n const shown = e.url ? redactUrl(e.url) : 'https://api.cohere.com (default)';\n console.log(` url: ${shown}${reachable === null ? '' : reachable ? ' (reachable)' : ' (unreachable)'}`);\n }\n if (keyEnv) console.log(` key: env ${keyEnv.name} (${keyEnv.set ? 'set' : 'NOT SET'})`);\n if (!hasModel) console.log(' vectors: off until the model is present; search runs on words and links');\n else if (vectors) console.log(` vectors: ${vectors.embedded} embedded, ${vectors.pending} pending (embedded on the first semantic search)`);\n if (detectedLanguages) {\n const total = Object.values(detectedLanguages).reduce((a, b) => a + b, 0);\n const breakdown = Object.entries(detectedLanguages)\n .sort((a, b) => b[1] - a[1])\n .map(([code, n]) => `${code} ${Math.round((n / total) * 100)}%`)\n .join(', ');\n console.log(` detected: ${breakdown} (${total} chunks classified)`);\n }\n }\n console.log('');\n for (const line of presetsLines(presets)) console.log(line);\n console.log('');\n console.log(`queries: ${result.queries} saved (${ctx.name} --list)`);\n const pid = result.watcherPid ? `pid ${result.watcherPid}, ` : '';\n console.log(result.watcherHeartbeatSecondsAgo === null ? 'watcher: none' : `watcher: ${pid}last heartbeat ${result.watcherHeartbeatSecondsAgo}s ago`);\n for (const [key, value] of Object.entries(result.engine)) console.log(`${`${store.name}:`.padEnd(10)}${key} ${value}`);\n }\n await store.close();\n};\nexport default status;\n"],"names":["dirname","presetCoverage","embedConfig","featureStates","SUPPORTED_CONFIG_VERSION","languageDistribution","probeReachable","isDownloadable","MODEL_FILENAMES","modelDir","modelPresent","readLanguages","featuresLine","presetsLines","stringifyJson","docCount","getMeta","openStore","USAGE","CONFIG","FORMAT","formatOf","parse","printWarnings","vectorState","store","stmt","prepare","get","parseErrorCount","n","redactUrl","url","parsed","URL","username","password","toString","replace","embedReachable","e","provider","base","languagesInfo","languages","state","source","model","cardLanguages","undefined","length","status","ctx","process","cfg","values","argv","name","format","resolveConfig","config","dbPath","warnings","features","hasModel","reachable","vectors","presets","declaredLanguages","languagesState","languagesSource","detectedLanguages","heartbeat","cacheSchema","engine","engineStatus","keyEnv","key","set","env","result","configPath","configVersion","migratedFrom","unknownConfigKeys","unknownKeys","tree","rootDir","baseDir","treeRoot","configDir","cache","db","docs","unparseableFrontmatter","on","featuresOff","off","embed","dir","downloaded","queries","Object","keys","watcherPid","watcherHeartbeatSecondsAgo","Math","round","Date","now","console","log","migrated","join","shown","embedded","pending","total","reduce","a","b","breakdown","entries","sort","map","code","line","pid","value","padEnd","close"],"mappings":"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"}
@@ -13,7 +13,7 @@ const watch = async (ctx)=>{
13
13
  force: values.force,
14
14
  onEvent: (event)=>{
15
15
  if (event.type === 'started') {
16
- console.log(`${ctx.name} watch: watching ${event.baseDir}`);
16
+ console.log(`${ctx.name} watch: watching ${event.rootDir}`);
17
17
  console.log(`${ctx.name} watch: db ${event.dbPath}`);
18
18
  } else if (event.type === 'reconciled') {
19
19
  printWarnings(event.warnings);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/watch.ts"],"sourcesContent":["import type { WatchEvent } from '../watch.ts';\nimport { runWatch } from '../watch.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, parse, printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst watch: Command = async (ctx) => {\n const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.watch}`, { force: { type: 'boolean', default: false }, ...CONFIG });\n await runWatch(ctx.resolveConfig(values.config as string | undefined), {\n force: values.force as boolean,\n onEvent: (event: WatchEvent) => {\n if (event.type === 'started') {\n console.log(`${ctx.name} watch: watching ${event.baseDir}`);\n console.log(`${ctx.name} watch: db ${event.dbPath}`);\n } else if (event.type === 'reconciled') {\n printWarnings(event.warnings);\n if (event.parsed > 0) console.log(`${ctx.name} watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);\n } else {\n console.error(`${ctx.name} watch: reconcile error: ${event.message}`);\n }\n },\n });\n};\nexport default watch;\n"],"names":["runWatch","USAGE","CONFIG","parse","printWarnings","watch","ctx","values","argv","name","force","type","default","resolveConfig","config","onEvent","event","console","log","baseDir","dbPath","warnings","parsed","total","error","message"],"mappings":"AACA,SAASA,QAAQ,QAAQ,cAAc;AACvC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,KAAK,EAAEC,aAAa,QAAQ,cAAc;AAG3D,MAAMC,QAAiB,OAAOC;IAC5B,MAAM,EAAEC,MAAM,EAAE,GAAGJ,MAAMG,IAAIE,IAAI,EAAE,CAAC,OAAO,EAAEF,IAAIG,IAAI,CAAC,CAAC,EAAER,MAAMI,KAAK,EAAE,EAAE;QAAEK,OAAO;YAAEC,MAAM;YAAWC,SAAS;QAAM;QAAG,GAAGV,MAAM;IAAC;IAChI,MAAMF,SAASM,IAAIO,aAAa,CAACN,OAAOO,MAAM,GAAyB;QACrEJ,OAAOH,OAAOG,KAAK;QACnBK,SAAS,CAACC;YACR,IAAIA,MAAML,IAAI,KAAK,WAAW;gBAC5BM,QAAQC,GAAG,CAAC,GAAGZ,IAAIG,IAAI,CAAC,iBAAiB,EAAEO,MAAMG,OAAO,EAAE;gBAC1DF,QAAQC,GAAG,CAAC,GAAGZ,IAAIG,IAAI,CAAC,WAAW,EAAEO,MAAMI,MAAM,EAAE;YACrD,OAAO,IAAIJ,MAAML,IAAI,KAAK,cAAc;gBACtCP,cAAcY,MAAMK,QAAQ;gBAC5B,IAAIL,MAAMM,MAAM,GAAG,GAAGL,QAAQC,GAAG,CAAC,GAAGZ,IAAIG,IAAI,CAAC,oBAAoB,EAAEO,MAAMM,MAAM,CAAC,mBAAmB,EAAEN,MAAMO,KAAK,CAAC,OAAO,CAAC;YAC5H,OAAO;gBACLN,QAAQO,KAAK,CAAC,GAAGlB,IAAIG,IAAI,CAAC,yBAAyB,EAAEO,MAAMS,OAAO,EAAE;YACtE;QACF;IACF;AACF;AACA,eAAepB,MAAM"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli/watch.ts"],"sourcesContent":["import type { WatchEvent } from '../watch.ts';\nimport { runWatch } from '../watch.ts';\nimport { USAGE } from './index.ts';\nimport { CONFIG, parse, printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst watch: Command = async (ctx) => {\n const { values } = parse(ctx.argv, `usage: ${ctx.name} ${USAGE.watch}`, { force: { type: 'boolean', default: false }, ...CONFIG });\n await runWatch(ctx.resolveConfig(values.config as string | undefined), {\n force: values.force as boolean,\n onEvent: (event: WatchEvent) => {\n if (event.type === 'started') {\n console.log(`${ctx.name} watch: watching ${event.rootDir}`);\n console.log(`${ctx.name} watch: db ${event.dbPath}`);\n } else if (event.type === 'reconciled') {\n printWarnings(event.warnings);\n if (event.parsed > 0) console.log(`${ctx.name} watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);\n } else {\n console.error(`${ctx.name} watch: reconcile error: ${event.message}`);\n }\n },\n });\n};\nexport default watch;\n"],"names":["runWatch","USAGE","CONFIG","parse","printWarnings","watch","ctx","values","argv","name","force","type","default","resolveConfig","config","onEvent","event","console","log","rootDir","dbPath","warnings","parsed","total","error","message"],"mappings":"AACA,SAASA,QAAQ,QAAQ,cAAc;AACvC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,EAAEC,KAAK,EAAEC,aAAa,QAAQ,cAAc;AAG3D,MAAMC,QAAiB,OAAOC;IAC5B,MAAM,EAAEC,MAAM,EAAE,GAAGJ,MAAMG,IAAIE,IAAI,EAAE,CAAC,OAAO,EAAEF,IAAIG,IAAI,CAAC,CAAC,EAAER,MAAMI,KAAK,EAAE,EAAE;QAAEK,OAAO;YAAEC,MAAM;YAAWC,SAAS;QAAM;QAAG,GAAGV,MAAM;IAAC;IAChI,MAAMF,SAASM,IAAIO,aAAa,CAACN,OAAOO,MAAM,GAAyB;QACrEJ,OAAOH,OAAOG,KAAK;QACnBK,SAAS,CAACC;YACR,IAAIA,MAAML,IAAI,KAAK,WAAW;gBAC5BM,QAAQC,GAAG,CAAC,GAAGZ,IAAIG,IAAI,CAAC,iBAAiB,EAAEO,MAAMG,OAAO,EAAE;gBAC1DF,QAAQC,GAAG,CAAC,GAAGZ,IAAIG,IAAI,CAAC,WAAW,EAAEO,MAAMI,MAAM,EAAE;YACrD,OAAO,IAAIJ,MAAML,IAAI,KAAK,cAAc;gBACtCP,cAAcY,MAAMK,QAAQ;gBAC5B,IAAIL,MAAMM,MAAM,GAAG,GAAGL,QAAQC,GAAG,CAAC,GAAGZ,IAAIG,IAAI,CAAC,oBAAoB,EAAEO,MAAMM,MAAM,CAAC,mBAAmB,EAAEN,MAAMO,KAAK,CAAC,OAAO,CAAC;YAC5H,OAAO;gBACLN,QAAQO,KAAK,CAAC,GAAGlB,IAAIG,IAAI,CAAC,yBAAyB,EAAEO,MAAMS,OAAO,EAAE;YACtE;QACF;IACF;AACF;AACA,eAAepB,MAAM"}
@@ -7,6 +7,7 @@ import { scopedPaths, scopeHasEmbeddings } from './scope.js';
7
7
  // Notes most similar by cosine, excluding self and everything already linked either way.
8
8
  // Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.
9
9
  export async function relatedNotes(store, cfg, pathArg, overrides, k) {
10
+ var _cfg_rootDir;
10
11
  const paths = (await (await store.prepare('SELECT "path" FROM frontmatter')).all()).map((r)=>r.path);
11
12
  const path = resolveNote(paths, pathArg);
12
13
  const outbound = (await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)).map((r)=>r.dst);
@@ -36,7 +37,7 @@ export async function relatedNotes(store, cfg, pathArg, overrides, k) {
36
37
  const allowed = await scopedPaths(store, cfg, overrides);
37
38
  // Top up pending rows before the seed check, or a fresh index reports every note as
38
39
  // having no indexed text until some search has run.
39
- await embedPending(store, cfg, cfg.baseDir);
40
+ await embedPending(store, cfg, (_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir);
40
41
  if (!await hasEmbedding(store, path)) {
41
42
  throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);
42
43
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedConfig, embedEnabled, resolveSearch } from '../config/index.ts';\nimport { embedPending, hasEmbedding, similarNotes } from '../embed/query.ts';\nimport { localModelMissing, MODEL_FILENAMES } from '../embed/store.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Store } from '../store/types.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\n// Notes most similar by cosine, excluding self and everything already linked either way.\n// Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.\nexport async function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n const paths = ((await (await store.prepare('SELECT \"path\" FROM frontmatter')).all()) as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const outbound = ((await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')).all(path)) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause; an empty result means one thing, nothing near in meaning.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');\n }\n // search gates on the same signal, so a preset's declared signals mean one thing: without\n // this check an overlapping vectors-on preset would answer for a scope that declined them.\n if (effective.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n // A downloadable HF id proceeds -- embedPending's getProvider call fetches it lazily on\n // consent. Only a local path with missing files can never fetch itself.\n const e = embedConfig(cfg); // embedEnabled(cfg) above guarantees this is set\n if (localModelMissing(e)) {\n throw new SenseError('EMBED_MODEL_MISSING', `related ranks notes by meaning, so it needs the embedding model, but the local model path \"${e.model}\" is missing ${MODEL_FILENAMES}; point embed.model at a directory containing them (search still works without it, on words and links)`);\n }\n const allowed = await scopedPaths(store, cfg, overrides);\n // Top up pending rows before the seed check, or a fresh index reports every note as\n // having no indexed text until some search has run.\n await embedPending(store, cfg, cfg.baseDir);\n if (!(await hasEmbedding(store, path))) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!(await scopeHasEmbeddings(store, cfg, allowed))) return [];\n return similarNotes(store, cfg, path, { exclude, allowed, k });\n}\n"],"names":["embedConfig","embedEnabled","resolveSearch","embedPending","hasEmbedding","similarNotes","localModelMissing","MODEL_FILENAMES","SenseError","resolveNote","scopedPaths","scopeHasEmbeddings","relatedNotes","store","cfg","pathArg","overrides","k","paths","prepare","all","map","r","path","outbound","dst","backlinks","src","exclude","Set","effective","signals","vectors","undefined","presetName","e","model","allowed","baseDir"],"mappings":"AACA,SAASA,WAAW,EAAEC,YAAY,EAAEC,aAAa,QAAQ,qBAAqB;AAC9E,SAASC,YAAY,EAAEC,YAAY,EAAEC,YAAY,QAAQ,oBAAoB;AAC7E,SAASC,iBAAiB,EAAEC,eAAe,QAAQ,oBAAoB;AACvE,SAASC,UAAU,QAAQ,eAAe;AAE1C,SAASC,WAAW,QAAQ,YAAY;AACxC,SAASC,WAAW,EAAEC,kBAAkB,QAAQ,aAAa;AAE7D,yFAAyF;AACzF,sFAAsF;AACtF,OAAO,eAAeC,aAAaC,KAAY,EAAEC,GAAmB,EAAEC,OAAe,EAAEC,SAA0B,EAAEC,CAAS;IAC1H,MAAMC,QAAQ,AAAE,CAAA,MAAM,AAAC,CAAA,MAAML,MAAMM,OAAO,CAAC,iCAAgC,EAAGC,GAAG,EAAC,EAA+BC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAClI,MAAMA,OAAOd,YAAYS,OAAOH;IAEhC,MAAMS,WAAW,AAAE,CAAA,MAAM,AAAC,CAAA,MAAMX,MAAMM,OAAO,CAAC,kFAAiF,EAAGC,GAAG,CAACG,KAAI,EAA8BF,GAAG,CAAC,CAACC,IAAMA,EAAEG,GAAG;IACxL,MAAMC,YAAY,AAAE,CAAA,MAAM,AAAC,CAAA,MAAMb,MAAMM,OAAO,CAAC,8DAA6D,EAAGC,GAAG,CAACG,KAAI,EAA8BF,GAAG,CAAC,CAACC,IAAMA,EAAEK,GAAG;IACrK,MAAMC,UAAU,IAAIC,IAAI;QAACN;WAASC;WAAaE;KAAU;IAEzD,yFAAyF;IACzF,8EAA8E;IAC9E,MAAMI,YAAY5B,cAAcY,KAAKE;IACrC,IAAI,CAACf,aAAaa,MAAM;QACtB,MAAM,IAAIN,WAAW,kBAAkB;IACzC;IACA,0FAA0F;IAC1F,2FAA2F;IAC3F,IAAIsB,UAAUC,OAAO,CAACC,OAAO,KAAKC,WAAW;QAC3C,MAAM,IAAIzB,WAAW,uBAAuB,CAAC,QAAQ,EAAEsB,UAAUI,UAAU,CAAC,uKAAuK,CAAC;IACtP;IACA,wFAAwF;IACxF,wEAAwE;IACxE,MAAMC,IAAInC,YAAYc,MAAM,iDAAiD;IAC7E,IAAIR,kBAAkB6B,IAAI;QACxB,MAAM,IAAI3B,WAAW,uBAAuB,CAAC,2FAA2F,EAAE2B,EAAEC,KAAK,CAAC,aAAa,EAAE7B,gBAAgB,sGAAsG,CAAC;IAC1R;IACA,MAAM8B,UAAU,MAAM3B,YAAYG,OAAOC,KAAKE;IAC9C,oFAAoF;IACpF,oDAAoD;IACpD,MAAMb,aAAaU,OAAOC,KAAKA,IAAIwB,OAAO;IAC1C,IAAI,CAAE,MAAMlC,aAAaS,OAAOU,OAAQ;QACtC,MAAM,IAAIf,WAAW,qBAAqB,GAAGe,KAAK,4GAA4G,CAAC;IACjK;IACA,IAAI,CAAE,MAAMZ,mBAAmBE,OAAOC,KAAKuB,UAAW,OAAO,EAAE;IAC/D,OAAOhC,aAAaQ,OAAOC,KAAKS,MAAM;QAAEK;QAASS;QAASpB;IAAE;AAC9D"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedConfig, embedEnabled, resolveSearch } from '../config/index.ts';\nimport { embedPending, hasEmbedding, similarNotes } from '../embed/query.ts';\nimport { localModelMissing, MODEL_FILENAMES } from '../embed/store.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Store } from '../store/types.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\n// Notes most similar by cosine, excluding self and everything already linked either way.\n// Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.\nexport async function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n const paths = ((await (await store.prepare('SELECT \"path\" FROM frontmatter')).all()) as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const outbound = ((await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')).all(path)) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause; an empty result means one thing, nothing near in meaning.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');\n }\n // search gates on the same signal, so a preset's declared signals mean one thing: without\n // this check an overlapping vectors-on preset would answer for a scope that declined them.\n if (effective.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n // A downloadable HF id proceeds -- embedPending's getProvider call fetches it lazily on\n // consent. Only a local path with missing files can never fetch itself.\n const e = embedConfig(cfg); // embedEnabled(cfg) above guarantees this is set\n if (localModelMissing(e)) {\n throw new SenseError('EMBED_MODEL_MISSING', `related ranks notes by meaning, so it needs the embedding model, but the local model path \"${e.model}\" is missing ${MODEL_FILENAMES}; point embed.model at a directory containing them (search still works without it, on words and links)`);\n }\n const allowed = await scopedPaths(store, cfg, overrides);\n // Top up pending rows before the seed check, or a fresh index reports every note as\n // having no indexed text until some search has run.\n await embedPending(store, cfg, cfg.rootDir ?? cfg.baseDir);\n if (!(await hasEmbedding(store, path))) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!(await scopeHasEmbeddings(store, cfg, allowed))) return [];\n return similarNotes(store, cfg, path, { exclude, allowed, k });\n}\n"],"names":["embedConfig","embedEnabled","resolveSearch","embedPending","hasEmbedding","similarNotes","localModelMissing","MODEL_FILENAMES","SenseError","resolveNote","scopedPaths","scopeHasEmbeddings","relatedNotes","store","cfg","pathArg","overrides","k","paths","prepare","all","map","r","path","outbound","dst","backlinks","src","exclude","Set","effective","signals","vectors","undefined","presetName","e","model","allowed","rootDir","baseDir"],"mappings":"AACA,SAASA,WAAW,EAAEC,YAAY,EAAEC,aAAa,QAAQ,qBAAqB;AAC9E,SAASC,YAAY,EAAEC,YAAY,EAAEC,YAAY,QAAQ,oBAAoB;AAC7E,SAASC,iBAAiB,EAAEC,eAAe,QAAQ,oBAAoB;AACvE,SAASC,UAAU,QAAQ,eAAe;AAE1C,SAASC,WAAW,QAAQ,YAAY;AACxC,SAASC,WAAW,EAAEC,kBAAkB,QAAQ,aAAa;AAE7D,yFAAyF;AACzF,sFAAsF;AACtF,OAAO,eAAeC,aAAaC,KAAY,EAAEC,GAAmB,EAAEC,OAAe,EAAEC,SAA0B,EAAEC,CAAS;QA4B3FH;IA3B/B,MAAMI,QAAQ,AAAE,CAAA,MAAM,AAAC,CAAA,MAAML,MAAMM,OAAO,CAAC,iCAAgC,EAAGC,GAAG,EAAC,EAA+BC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAClI,MAAMA,OAAOd,YAAYS,OAAOH;IAEhC,MAAMS,WAAW,AAAE,CAAA,MAAM,AAAC,CAAA,MAAMX,MAAMM,OAAO,CAAC,kFAAiF,EAAGC,GAAG,CAACG,KAAI,EAA8BF,GAAG,CAAC,CAACC,IAAMA,EAAEG,GAAG;IACxL,MAAMC,YAAY,AAAE,CAAA,MAAM,AAAC,CAAA,MAAMb,MAAMM,OAAO,CAAC,8DAA6D,EAAGC,GAAG,CAACG,KAAI,EAA8BF,GAAG,CAAC,CAACC,IAAMA,EAAEK,GAAG;IACrK,MAAMC,UAAU,IAAIC,IAAI;QAACN;WAASC;WAAaE;KAAU;IAEzD,yFAAyF;IACzF,8EAA8E;IAC9E,MAAMI,YAAY5B,cAAcY,KAAKE;IACrC,IAAI,CAACf,aAAaa,MAAM;QACtB,MAAM,IAAIN,WAAW,kBAAkB;IACzC;IACA,0FAA0F;IAC1F,2FAA2F;IAC3F,IAAIsB,UAAUC,OAAO,CAACC,OAAO,KAAKC,WAAW;QAC3C,MAAM,IAAIzB,WAAW,uBAAuB,CAAC,QAAQ,EAAEsB,UAAUI,UAAU,CAAC,uKAAuK,CAAC;IACtP;IACA,wFAAwF;IACxF,wEAAwE;IACxE,MAAMC,IAAInC,YAAYc,MAAM,iDAAiD;IAC7E,IAAIR,kBAAkB6B,IAAI;QACxB,MAAM,IAAI3B,WAAW,uBAAuB,CAAC,2FAA2F,EAAE2B,EAAEC,KAAK,CAAC,aAAa,EAAE7B,gBAAgB,sGAAsG,CAAC;IAC1R;IACA,MAAM8B,UAAU,MAAM3B,YAAYG,OAAOC,KAAKE;IAC9C,oFAAoF;IACpF,oDAAoD;IACpD,MAAMb,aAAaU,OAAOC,MAAKA,eAAAA,IAAIwB,OAAO,cAAXxB,0BAAAA,eAAeA,IAAIyB,OAAO;IACzD,IAAI,CAAE,MAAMnC,aAAaS,OAAOU,OAAQ;QACtC,MAAM,IAAIf,WAAW,qBAAqB,GAAGe,KAAK,4GAA4G,CAAC;IACjK;IACA,IAAI,CAAE,MAAMZ,mBAAmBE,OAAOC,KAAKuB,UAAW,OAAO,EAAE;IAC/D,OAAOhC,aAAaQ,OAAOC,KAAKS,MAAM;QAAEK;QAASS;QAASpB;IAAE;AAC9D"}
@@ -281,7 +281,8 @@ export async function hydrateSearchRows(store, cfg, rows, matchedPaths, terms, o
281
281
  if (!matchedPaths.has(row.path)) continue;
282
282
  let text;
283
283
  try {
284
- text = readFileSync(join(cfg.baseDir, row.path), 'utf8');
284
+ var _cfg_rootDir;
285
+ text = readFileSync(join((_cfg_rootDir = cfg.rootDir) !== null && _cfg_rootDir !== void 0 ? _cfg_rootDir : cfg.baseDir, row.path), 'utf8');
285
286
  } catch {
286
287
  continue; // vanished since the match; leave snippets/lines empty rather than throw
287
288
  }