sensemaking 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -41
- package/dist/cjs/cli/check.js +386 -0
- package/dist/cjs/cli/check.js.map +1 -0
- package/dist/cjs/{commands → cli}/find.js +3 -4
- package/dist/cjs/cli/find.js.map +1 -0
- package/dist/cjs/cli/index.js.map +1 -0
- package/dist/cjs/{commands → cli}/init.js +3 -0
- package/dist/cjs/cli/init.js.map +1 -0
- package/dist/cjs/{commands → cli}/map.js +2 -2
- package/dist/cjs/cli/map.js.map +1 -0
- package/dist/cjs/{commands → cli}/named.d.cts +1 -1
- package/dist/cjs/{commands → cli}/named.d.ts +1 -1
- package/dist/cjs/cli/named.js +209 -0
- package/dist/cjs/cli/named.js.map +1 -0
- package/dist/cjs/{commands → cli}/peek.js +2 -2
- package/dist/cjs/cli/peek.js.map +1 -0
- package/dist/cjs/cli/query.js.map +1 -0
- package/dist/cjs/cli/rebuild.js.map +1 -0
- package/dist/cjs/{commands → cli}/shared.d.cts +1 -0
- package/dist/{esm/commands → cjs/cli}/shared.d.ts +1 -0
- package/dist/cjs/{commands → cli}/shared.js +9 -0
- package/dist/cjs/cli/shared.js.map +1 -0
- package/dist/cjs/{commands → cli}/status.js +7 -1
- package/dist/cjs/cli/status.js.map +1 -0
- package/dist/cjs/cli/watch.js.map +1 -0
- package/dist/cjs/cli.js +22 -17
- package/dist/cjs/cli.js.map +1 -1
- package/dist/{esm/verbs.d.ts → cjs/commands.d.cts} +5 -4
- package/dist/cjs/{verbs.d.cts → commands.d.ts} +5 -4
- package/dist/cjs/{verbs.js → commands.js} +246 -21
- package/dist/cjs/commands.js.map +1 -0
- package/dist/cjs/config.d.cts +7 -1
- package/dist/cjs/config.d.ts +7 -1
- package/dist/cjs/config.js +75 -24
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/db.d.cts +1 -1
- package/dist/cjs/db.d.ts +1 -1
- package/dist/cjs/db.js +88 -17
- package/dist/cjs/db.js.map +1 -1
- package/dist/cjs/errors.d.cts +1 -1
- package/dist/cjs/errors.d.ts +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/features/embed.js +7 -1
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/features/links.js +201 -24
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/features/rank.js +8 -1
- package/dist/cjs/features/rank.js.map +1 -1
- package/dist/cjs/features/types.d.cts +10 -3
- package/dist/cjs/features/types.d.ts +10 -3
- package/dist/cjs/index.d.cts +3 -3
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output.d.cts +1 -0
- package/dist/cjs/output.d.ts +1 -0
- package/dist/cjs/output.js +2 -1
- package/dist/cjs/output.js.map +1 -1
- package/dist/cjs/progress.d.cts +5 -0
- package/dist/cjs/progress.d.ts +5 -0
- package/dist/cjs/progress.js +54 -0
- package/dist/cjs/progress.js.map +1 -0
- package/dist/cjs/scan.d.cts +1 -0
- package/dist/cjs/scan.d.ts +1 -0
- package/dist/cjs/scan.js +3 -2
- package/dist/cjs/scan.js.map +1 -1
- package/dist/esm/{commands → cli}/check.js +43 -3
- package/dist/esm/cli/check.js.map +1 -0
- package/dist/esm/{commands → cli}/find.js +3 -4
- package/dist/esm/cli/find.js.map +1 -0
- package/dist/esm/cli/index.js.map +1 -0
- package/dist/esm/{commands → cli}/init.js +3 -0
- package/dist/esm/cli/init.js.map +1 -0
- package/dist/esm/{commands → cli}/map.js +1 -1
- package/dist/esm/cli/map.js.map +1 -0
- package/dist/esm/{commands → cli}/named.d.ts +1 -1
- package/dist/esm/cli/named.js +33 -0
- package/dist/esm/cli/named.js.map +1 -0
- package/dist/esm/{commands → cli}/peek.js +1 -1
- package/dist/esm/cli/peek.js.map +1 -0
- package/dist/esm/cli/query.js.map +1 -0
- package/dist/esm/cli/rebuild.js.map +1 -0
- package/dist/{cjs/commands → esm/cli}/shared.d.ts +1 -0
- package/dist/esm/{commands → cli}/shared.js +9 -0
- package/dist/esm/cli/shared.js.map +1 -0
- package/dist/esm/{commands → cli}/status.js +7 -1
- package/dist/esm/cli/status.js.map +1 -0
- package/dist/esm/cli/types.js.map +1 -0
- package/dist/esm/cli/watch.js.map +1 -0
- package/dist/esm/cli.js +8 -6
- package/dist/esm/cli.js.map +1 -1
- package/dist/{cjs/verbs.d.ts → esm/commands.d.ts} +5 -4
- package/dist/esm/{verbs.js → commands.js} +165 -21
- package/dist/esm/commands.js.map +1 -0
- package/dist/esm/config.d.ts +7 -1
- package/dist/esm/config.js +38 -4
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/db.d.ts +1 -1
- package/dist/esm/db.js +78 -16
- package/dist/esm/db.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/features/embed.js +6 -0
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/features/links.js +130 -20
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/rank.js +8 -1
- package/dist/esm/features/rank.js.map +1 -1
- package/dist/esm/features/types.d.ts +10 -3
- package/dist/esm/features/types.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output.d.ts +1 -0
- package/dist/esm/output.js +2 -1
- package/dist/esm/output.js.map +1 -1
- package/dist/esm/progress.d.ts +5 -0
- package/dist/esm/progress.js +44 -0
- package/dist/esm/progress.js.map +1 -0
- package/dist/esm/scan.d.ts +1 -0
- package/dist/esm/scan.js +5 -2
- package/dist/esm/scan.js.map +1 -1
- package/package.json +6 -2
- package/schema.json +19 -4
- package/skills/sense/EXAMPLES.md +9 -7
- package/skills/sense/SKILL.md +43 -27
- package/skills/sense-setup/SKILL.md +16 -2
- package/dist/cjs/commands/check.js +0 -143
- package/dist/cjs/commands/check.js.map +0 -1
- package/dist/cjs/commands/find.js.map +0 -1
- package/dist/cjs/commands/index.js.map +0 -1
- package/dist/cjs/commands/init.js.map +0 -1
- package/dist/cjs/commands/map.js.map +0 -1
- package/dist/cjs/commands/named.js +0 -23
- package/dist/cjs/commands/named.js.map +0 -1
- package/dist/cjs/commands/peek.js.map +0 -1
- package/dist/cjs/commands/query.js.map +0 -1
- package/dist/cjs/commands/rebuild.js.map +0 -1
- package/dist/cjs/commands/shared.js.map +0 -1
- package/dist/cjs/commands/status.js.map +0 -1
- package/dist/cjs/commands/watch.js.map +0 -1
- package/dist/cjs/verbs.js.map +0 -1
- package/dist/esm/commands/check.js.map +0 -1
- package/dist/esm/commands/find.js.map +0 -1
- package/dist/esm/commands/index.js.map +0 -1
- package/dist/esm/commands/init.js.map +0 -1
- package/dist/esm/commands/map.js.map +0 -1
- package/dist/esm/commands/named.js +0 -12
- package/dist/esm/commands/named.js.map +0 -1
- package/dist/esm/commands/peek.js.map +0 -1
- package/dist/esm/commands/query.js.map +0 -1
- package/dist/esm/commands/rebuild.js.map +0 -1
- package/dist/esm/commands/shared.js.map +0 -1
- package/dist/esm/commands/status.js.map +0 -1
- package/dist/esm/commands/types.js.map +0 -1
- package/dist/esm/commands/watch.js.map +0 -1
- package/dist/esm/verbs.js.map +0 -1
- /package/dist/cjs/{commands → cli}/check.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/check.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/find.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/find.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/index.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/index.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/index.js +0 -0
- /package/dist/cjs/{commands → cli}/init.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/init.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/map.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/map.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/peek.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/peek.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/query.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/query.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/query.js +0 -0
- /package/dist/cjs/{commands → cli}/rebuild.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/rebuild.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/rebuild.js +0 -0
- /package/dist/cjs/{commands → cli}/status.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/status.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/types.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/types.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/types.js +0 -0
- /package/dist/cjs/{commands → cli}/types.js.map +0 -0
- /package/dist/cjs/{commands → cli}/watch.d.cts +0 -0
- /package/dist/cjs/{commands → cli}/watch.d.ts +0 -0
- /package/dist/cjs/{commands → cli}/watch.js +0 -0
- /package/dist/esm/{commands → cli}/check.d.ts +0 -0
- /package/dist/esm/{commands → cli}/find.d.ts +0 -0
- /package/dist/esm/{commands → cli}/index.d.ts +0 -0
- /package/dist/esm/{commands → cli}/index.js +0 -0
- /package/dist/esm/{commands → cli}/init.d.ts +0 -0
- /package/dist/esm/{commands → cli}/map.d.ts +0 -0
- /package/dist/esm/{commands → cli}/peek.d.ts +0 -0
- /package/dist/esm/{commands → cli}/query.d.ts +0 -0
- /package/dist/esm/{commands → cli}/query.js +0 -0
- /package/dist/esm/{commands → cli}/rebuild.d.ts +0 -0
- /package/dist/esm/{commands → cli}/rebuild.js +0 -0
- /package/dist/esm/{commands → cli}/status.d.ts +0 -0
- /package/dist/esm/{commands → cli}/types.d.ts +0 -0
- /package/dist/esm/{commands → cli}/types.js +0 -0
- /package/dist/esm/{commands → cli}/watch.d.ts +0 -0
- /package/dist/esm/{commands → cli}/watch.js +0 -0
package/dist/cjs/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n // Tree-declared default scope for `find`. A --where on the invocation replaces it, so a\n // caller can always reach the whole tree (`--where \"1=1\"`).\n defaults?: { find?: { where?: string } };\n // Assertions over saved queries: `checks: { \"dead-links\": \"empty\" }` makes `sense check`\n // fail when that query returns rows -- for invariant queries where 0 rows is the pass.\n checks?: Record<string, 'empty'>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; verbs surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\n// Keys each block actually reads. A key outside these sets is accepted and ignored, which\n// looks identical to working -- one field report set `scan.exclude`, saw no change in doc\n// count and no warning, and reasonably concluded the filter had applied. Reported as a\n// warning rather than an error so a config carrying an unknown key still runs.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'scan', 'features', 'defaults', 'checks', 'queries']);\nconst KNOWN_SCAN_KEYS = new Set(['include']);\n\nfunction unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n const unknown = Object.keys(cfg)\n .filter((k) => !KNOWN_KEYS.has(k))\n .map((k) => k);\n const scan = cfg.scan as Record<string, unknown> | undefined;\n if (scan && typeof scan === 'object' && !Array.isArray(scan)) {\n unknown.push(\n ...Object.keys(scan)\n .filter((k) => !KNOWN_SCAN_KEYS.has(k))\n .map((k) => `scan.${k}`)\n );\n }\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features && typeof features === 'object' && !Array.isArray(features)) {\n unknown.push(\n ...Object.keys(features)\n .filter((k) => !(FEATURE_NAMES as readonly string[]).includes(k))\n .map((k) => `features.${k}`)\n );\n }\n return unknown;\n}\n\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n const defaults = cfg.defaults as { find?: { where?: unknown } } | undefined;\n if (defaults !== undefined && (typeof defaults !== 'object' || defaults === null || Array.isArray(defaults) || (defaults.find !== undefined && typeof defaults.find?.where !== 'string'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);\n }\n const checks = cfg.checks as Record<string, unknown> | undefined;\n if (checks !== undefined) {\n if (typeof checks !== 'object' || checks === null || Array.isArray(checks) || !Object.values(checks).every((v) => v === 'empty')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks must be an object of query name -> \"empty\"`);\n }\n const queries = (cfg.queries ?? {}) as Record<string, unknown>;\n for (const name of Object.keys(checks)) {\n if (queries[name] === undefined) throw new SenseError('CONFIG_INVALID', `${configPath}: checks names \"${name}\", which is not a saved query`);\n }\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n const unknownKeys = unknownConfigKeys(cfg as unknown as Record<string, unknown>);\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom, unknownKeys: unknownKeys.length > 0 ? unknownKeys : undefined };\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","STATE_DIR","SUPPORTED_CONFIG_VERSION","embedConfig","enabledFeatures","featureEnabled","featureSignature","featureStates","findConfigPath","initConfig","loadConfig","migrateConfig","FEATURE_NAMES","OPT_OUT_NAMES","cfg","name","Boolean","features","embed","enabled","filter","on","off","o","e","model","type","url","key","map","join","MIGRATIONS","version","Object","fromEntries","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","KNOWN_KEYS","Set","KNOWN_SCAN_KEYS","unknownConfigKeys","unknown","keys","k","has","Array","isArray","push","includes","validateConfig","parsed","defaults","length","every","g","undefined","values","q","find","where","checks","value","shapeOk","f","entries","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","unknownKeys","baseDir"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; commands degrade when one is off.\n// links/sections/rank are opt-out (absent = on); embed is opt-in (absent = off) -- most trees don't need vectors.\nconst FEATURE_NAMES = ['links', 'sections', 'rank', 'embed'] as const;\nconst OPT_OUT_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `true` = all defaults (static type, default model). A local `model` path skips the network.\nexport interface EmbedConfig {\n model?: string;\n type?: 'static' | 'api';\n url?: string; // api type: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // api type: name of the env var holding the bearer token, if any\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\n// A saved `find` invocation: `sense <name>` runs like `sense find <find> [--k] [--where] [--semantic]`.\n// All keys but `find` are optional; a runtime --where/--k on the invocation overrides the saved value.\nexport interface SavedFind {\n find: string;\n k?: number;\n where?: string;\n semantic?: boolean;\n}\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n features?: { links?: boolean; sections?: boolean; rank?: boolean; embed?: boolean | EmbedConfig };\n // Tree-declared default scope for `find`. A --where on the invocation replaces it, so a\n // caller can always reach the whole tree (`--where \"1=1\"`).\n defaults?: { find?: { where?: string } };\n // Assertions over saved queries: `checks: { \"dead-links\": \"empty\" }` makes `sense check`\n // fail when that query returns rows -- for invariant queries where 0 rows is the pass.\n // Applies only to SQL queries; a saved find under checks is a config error.\n checks?: Record<string, 'empty'>;\n queries: Record<string, string | SavedFind>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Opt-out features: absent block or key means enabled. `rank` additionally requires `links`.\n// `embed` is the opposite: enabled only when the config says so.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n if (name === 'embed') return Boolean(cfg.features?.embed);\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Every feature with its current state; commands surface this so \"off\" and \"empty\" stay\n// distinguishable in output.\nexport function featureStates(cfg: Config): { on: FeatureName[]; off: FeatureName[] } {\n return {\n on: FEATURE_NAMES.filter((name) => featureEnabled(cfg, name)),\n off: FEATURE_NAMES.filter((name) => !featureEnabled(cfg, name)),\n };\n}\n\n// Resolved embed settings, or null when the feature is off.\nexport function embedConfig(cfg: Config): { model: string; type: 'static' | 'api'; url?: string; key?: string } | null {\n const e = cfg.features?.embed;\n if (!e) return null;\n const o = e === true ? {} : e;\n return { model: o.model ?? DEFAULT_EMBED_MODEL, type: o.type ?? 'static', url: o.url, key: o.key };\n}\n\n// Cache-key string: embed carries its type + model so a model change rebuilds like a toggle.\nexport function featureSignature(cfg: Config): string {\n return enabledFeatures(cfg)\n .map((name) => {\n if (name !== 'embed') return name;\n const e = embedConfig(cfg);\n return `embed:${e?.type}:${e?.model}`;\n })\n .join(',');\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, opt-out features enabled (matches the old implicit\n // behavior of `links` etc. not existing). embed stays absent -- opt-in.\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(OPT_OUT_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n writeFileSync(configPath, `${JSON.stringify(starterConfig(), null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\n// Keys each block actually reads. A key outside these sets is accepted and ignored, which\n// looks identical to working -- one field report set `scan.exclude`, saw no change in doc\n// count and no warning, and reasonably concluded the filter had applied. Reported as a\n// warning rather than an error so a config carrying an unknown key still runs.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'scan', 'features', 'defaults', 'checks', 'queries']);\nconst KNOWN_SCAN_KEYS = new Set(['include']);\n\nfunction unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n const unknown = Object.keys(cfg)\n .filter((k) => !KNOWN_KEYS.has(k))\n .map((k) => k);\n const scan = cfg.scan as Record<string, unknown> | undefined;\n if (scan && typeof scan === 'object' && !Array.isArray(scan)) {\n unknown.push(\n ...Object.keys(scan)\n .filter((k) => !KNOWN_SCAN_KEYS.has(k))\n .map((k) => `scan.${k}`)\n );\n }\n const features = cfg.features as Record<string, unknown> | undefined;\n if (features && typeof features === 'object' && !Array.isArray(features)) {\n unknown.push(\n ...Object.keys(features)\n .filter((k) => !(FEATURE_NAMES as readonly string[]).includes(k))\n .map((k) => `features.${k}`)\n );\n }\n return unknown;\n}\n\nconst SAVED_FIND_KEYS = new Set(['find', 'k', 'where', 'semantic']);\n\n// A queries.<name> entry that isn't a SQL string must be a saved find: { find, k?, where?, semantic? }.\nfunction validateSavedFind(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be a SQL string or { find, k?, where?, semantic? }`);\n }\n const entry = value as Record<string, unknown>;\n const unknown = Object.keys(entry).filter((k) => !SAVED_FIND_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved find takes find, k, where, semantic`);\n }\n if (typeof entry.find !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.find must be a string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.semantic !== undefined && typeof entry.semantic !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.semantic must be a boolean`);\n }\n}\n\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string or saved find`);\n }\n for (const [name, value] of Object.entries(cfg.queries as Record<string, unknown>)) {\n if (typeof value === 'string') continue;\n validateSavedFind(name, value, configPath);\n }\n const defaults = cfg.defaults as { find?: { where?: unknown } } | undefined;\n if (defaults !== undefined && (typeof defaults !== 'object' || defaults === null || Array.isArray(defaults) || (defaults.find !== undefined && typeof defaults.find?.where !== 'string'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: defaults.find.where must be a SQL condition string`);\n }\n const checks = cfg.checks as Record<string, unknown> | undefined;\n if (checks !== undefined) {\n if (typeof checks !== 'object' || checks === null || Array.isArray(checks) || !Object.values(checks).every((v) => v === 'empty')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks must be an object of query name -> \"empty\"`);\n }\n const queries = (cfg.queries ?? {}) as Record<string, unknown>;\n for (const name of Object.keys(checks)) {\n if (queries[name] === undefined) throw new SenseError('CONFIG_INVALID', `${configPath}: checks names \"${name}\", which is not a saved query`);\n if (typeof queries[name] !== 'string') throw new SenseError('CONFIG_INVALID', `${configPath}: checks names \"${name}\", which is a saved find, not a SQL query`);\n }\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n for (const [name, value] of Object.entries(cfg.features ?? {})) {\n if (typeof value === 'boolean') continue;\n // embed alone takes an object form: { model?, type?: static|api, url?, key? }.\n const embed = value as Record<string, unknown>;\n const shapeOk = name === 'embed' && typeof value === 'object' && value !== null && !Array.isArray(value) && ['model', 'url', 'key'].every((f) => embed[f] === undefined || typeof embed[f] === 'string') && (embed.type === undefined || embed.type === 'static' || embed.type === 'api');\n if (!shapeOk) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean${name === 'embed' ? ' or { model?, type?: \"static\"|\"api\", url?, key? }' : ''}`);\n }\n }\n return cfg as unknown as Config;\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).version : 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n const unknownKeys = unknownConfigKeys(cfg as unknown as Record<string, unknown>);\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom, unknownKeys: unknownKeys.length > 0 ? unknownKeys : undefined };\n}\n"],"names":["CONFIG_FILENAME","DEFAULT_EMBED_MODEL","STATE_DIR","SUPPORTED_CONFIG_VERSION","embedConfig","enabledFeatures","featureEnabled","featureSignature","featureStates","findConfigPath","initConfig","loadConfig","migrateConfig","FEATURE_NAMES","OPT_OUT_NAMES","cfg","name","Boolean","features","embed","enabled","filter","on","off","o","e","model","type","url","key","map","join","MIGRATIONS","version","Object","fromEntries","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","KNOWN_KEYS","Set","KNOWN_SCAN_KEYS","unknownConfigKeys","unknown","keys","k","has","Array","isArray","push","includes","SAVED_FIND_KEYS","validateSavedFind","value","entry","length","find","undefined","Number","isInteger","where","semantic","validateConfig","parsed","defaults","every","g","entries","checks","values","shapeOk","f","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","unknownKeys","baseDir"],"mappings":";;;;;;;;;;;QAIaA;eAAAA;;QAoBAC;eAAAA;;QAnBAC;eAAAA;;QAGAC;eAAAA;;QA2EGC;eAAAA;;QAdAC;eAAAA;;QAPAC;eAAAA;;QA6BAC;eAAAA;;QAhBAC;eAAAA;;QAgEAC;eAAAA;;QATAC;eAAAA;;QA8HAC;eAAAA;;QApJAC;eAAAA;;;sBA5GwC;wBACjB;wBACZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEpB,IAAMZ,kBAAkB;AACxB,IAAME,YAAY;AAGlB,IAAMC,2BAA2B;AAExC,6GAA6G;AAC7G,kHAAkH;AAClH,IAAMU,gBAAgB;IAAC;IAAS;IAAY;IAAQ;CAAQ;AAC5D,IAAMC,gBAAgB;IAAC;IAAS;IAAY;CAAO;AAW5C,IAAMb,sBAAsB;AAsC5B,SAASK,eAAeS,GAAW,EAAEC,IAAiB;QACtBD,eACrBA;IADhB,IAAIC,SAAS,SAAS,OAAOC,SAAQF,gBAAAA,IAAIG,QAAQ,cAAZH,oCAAAA,cAAcI,KAAK;IACxD,IAAMC,UAAUL,EAAAA,iBAAAA,IAAIG,QAAQ,cAAZH,qCAAAA,cAAc,CAACC,KAAK,MAAK;IACzC,IAAIA,SAAS,QAAQ,OAAOI,WAAWd,eAAeS,KAAK;IAC3D,OAAOK;AACT;AAEO,SAASf,gBAAgBU,GAAW;IACzC,OAAOF,cAAcQ,MAAM,CAAC,SAACL;eAASV,eAAeS,KAAKC;;AAC5D;AAIO,SAASR,cAAcO,GAAW;IACvC,OAAO;QACLO,IAAIT,cAAcQ,MAAM,CAAC,SAACL;mBAASV,eAAeS,KAAKC;;QACvDO,KAAKV,cAAcQ,MAAM,CAAC,SAACL;mBAAS,CAACV,eAAeS,KAAKC;;IAC3D;AACF;AAGO,SAASZ,YAAYW,GAAW;QAIrBS,UAAsCA;QAH5CT;IAAV,IAAMU,KAAIV,gBAAAA,IAAIG,QAAQ,cAAZH,oCAAAA,cAAcI,KAAK;IAC7B,IAAI,CAACM,GAAG,OAAO;IACf,IAAMD,IAAIC,MAAM,OAAO,CAAC,IAAIA;IAC5B,OAAO;QAAEC,KAAK,GAAEF,WAAAA,EAAEE,KAAK,cAAPF,sBAAAA,WAAWvB;QAAqB0B,IAAI,GAAEH,UAAAA,EAAEG,IAAI,cAANH,qBAAAA,UAAU;QAAUI,KAAKJ,EAAEI,GAAG;QAAEC,KAAKL,EAAEK,GAAG;IAAC;AACnG;AAGO,SAAStB,iBAAiBQ,GAAW;IAC1C,OAAOV,gBAAgBU,KACpBe,GAAG,CAAC,SAACd;QACJ,IAAIA,SAAS,SAAS,OAAOA;QAC7B,IAAMS,IAAIrB,YAAYW;QACtB,OAAO,AAhGb,AAgGc,gBAAQU,cAAAA,wBAAAA,EAAGE,IAAI,EAAC,KAAY,OAATF,cAAAA,wBAAAA,EAAGC,KAAK;IACrC,GACCK,IAAI,CAAC;AACV;AAEA,yGAAyG;AACzG,IAAMC,aAAsD;IAC1D,0FAA0F;IAC1F,wEAAwE;IACxE,GAAG,SAACjB;eAAS,wCAAKA;YAAKkB,SAAS;YAAGf,UAAUgB,OAAOC,WAAW,CAACrB,cAAcgB,GAAG,CAAC,SAACd;uBAAS;oBAACA;oBAAM;iBAAK;;;;AAC1G;AAEO,SAASJ,cAAcG,GAAW;QAC1BA;IAAb,IAAMqB,QAAOrB,eAAAA,IAAIkB,OAAO,cAAXlB,0BAAAA,eAAe;IAC5B,IAAIsB,UAAUtB;IACd,IAAK,IAAIuB,IAAIF,MAAME,IAAInC,0BAA0BmC,IAAK;QACpD,IAAMC,OAAOP,UAAU,CAACM,EAAE;QAC1B,IAAI,CAACC,MAAM,MAAM,IAAIC,oBAAU,CAAC,8BAA8B,AAAC,oCAAqC,OAAFF;QAClGD,UAAUE,KAAKF;IACjB;IACA,OAAO;QAAEtB,KAAKsB;QAASD,MAAAA;IAAK;AAC9B;AAEA,SAASK;IACP,OAAO;QACLC,SAAS;QACTT,SAAS9B;QACTwC,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7B1B,UAAUgB,OAAOC,WAAW,CAACrB,cAAcgB,GAAG,CAAC,SAACd;mBAAS;gBAACA;gBAAM;aAAK;;QACrE6B,SAAS,CAAC;IACZ;AACF;AAGO,SAASnC,WAAWoC,GAAW;IACpC,IAAMC,aAAahB,IAAAA,cAAI,EAACe,KAAK9C;IAC7B,IAAIgD,IAAAA,kBAAU,EAACD,aAAa;QAC1B,MAAM,IAAIP,oBAAU,CAAC,iBAAiB,AAAC,GAAuCM,OAArC9C,iBAAgB,uBAAyB,OAAJ8C;IAChF;IACAG,IAAAA,qBAAa,EAACF,YAAY,AAAC,GAA2C,OAAzCG,KAAKC,SAAS,CAACV,iBAAiB,MAAM,IAAG;IACtE,OAAOM;AACT;AAEO,SAAStC,eAAe2C,QAAgB;IAC7C,IAAIN,MAAMO,IAAAA,iBAAO,EAACD;IAClB,OAAS;QACP,IAAME,YAAYvB,IAAAA,cAAI,EAACe,KAAK9C;QAC5B,IAAIgD,IAAAA,kBAAU,EAACM,YAAY,OAAOA;QAClC,IAAMC,SAASC,IAAAA,iBAAO,EAACV;QACvB,IAAIS,WAAWT,KAAK,OAAO;QAC3BA,MAAMS;IACR;AACF;AAEA,qFAAqF;AACrF,sFAAsF;AACtF,iEAAiE;AACjE,0FAA0F;AAC1F,0FAA0F;AAC1F,uFAAuF;AACvF,+EAA+E;AAC/E,IAAME,aAAa,IAAIC,IAAI;IAAC;IAAW;IAAW;IAAQ;IAAY;IAAY;IAAU;CAAU;AACtG,IAAMC,kBAAkB,IAAID,IAAI;IAAC;CAAU;AAE3C,SAASE,kBAAkB7C,GAA4B;IACrD,IAAM8C,UAAU3B,OAAO4B,IAAI,CAAC/C,KACzBM,MAAM,CAAC,SAAC0C;eAAM,CAACN,WAAWO,GAAG,CAACD;OAC9BjC,GAAG,CAAC,SAACiC;eAAMA;;IACd,IAAMpB,OAAO5B,IAAI4B,IAAI;IACrB,IAAIA,QAAQ,CAAA,OAAOA,qCAAP,SAAOA,KAAG,MAAM,YAAY,CAACsB,MAAMC,OAAO,CAACvB,OAAO;YAC5DkB;QAAAA,CAAAA,WAAAA,SAAQM,IAAI,OAAZN,UACE,qBAAG3B,OAAO4B,IAAI,CAACnB,MACZtB,MAAM,CAAC,SAAC0C;mBAAM,CAACJ,gBAAgBK,GAAG,CAACD;WACnCjC,GAAG,CAAC,SAACiC;mBAAM,AAAC,QAAS,OAAFA;;IAE1B;IACA,IAAM7C,WAAWH,IAAIG,QAAQ;IAC7B,IAAIA,YAAY,CAAA,OAAOA,yCAAP,SAAOA,SAAO,MAAM,YAAY,CAAC+C,MAAMC,OAAO,CAAChD,WAAW;YACxE2C;QAAAA,CAAAA,YAAAA,SAAQM,IAAI,OAAZN,WACE,qBAAG3B,OAAO4B,IAAI,CAAC5C,UACZG,MAAM,CAAC,SAAC0C;mBAAM,CAAC,AAAClD,cAAoCuD,QAAQ,CAACL;WAC7DjC,GAAG,CAAC,SAACiC;mBAAM,AAAC,YAAa,OAAFA;;IAE9B;IACA,OAAOF;AACT;AAEA,IAAMQ,kBAAkB,IAAIX,IAAI;IAAC;IAAQ;IAAK;IAAS;CAAW;AAElE,wGAAwG;AACxG,SAASY,kBAAkBtD,IAAY,EAAEuD,KAAc,EAAExB,UAAkB;IACzE,IAAI,CAAA,OAAOwB,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQN,MAAMC,OAAO,CAACK,QAAQ;QACvE,MAAM,IAAI/B,oBAAU,CAAC,kBAAkB,AAAC,GAAyBxB,OAAvB+B,YAAW,cAAiB,OAAL/B,MAAK;IACxE;IACA,IAAMwD,QAAQD;IACd,IAAMV,UAAU3B,OAAO4B,IAAI,CAACU,OAAOnD,MAAM,CAAC,SAAC0C;eAAM,CAACM,gBAAgBL,GAAG,CAACD;;IACtE,IAAIF,QAAQY,MAAM,GAAG,GAAG;QACtB,MAAM,IAAIjC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBxB,OAAvB+B,YAAW,cAAuCc,OAA3B7C,MAAK,wBAAyC,OAAnB6C,QAAQ9B,IAAI,CAAC,OAAM;IACjH;IACA,IAAI,OAAOyC,MAAME,IAAI,KAAK,UAAU;QAClC,MAAM,IAAIlC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBxB,OAAvB+B,YAAW,cAAiB,OAAL/B,MAAK;IACxE;IACA,IAAIwD,MAAMT,CAAC,KAAKY,aAAc,CAAA,OAAOH,MAAMT,CAAC,KAAK,YAAY,CAACa,OAAOC,SAAS,CAACL,MAAMT,CAAC,KAAKS,MAAMT,CAAC,IAAI,CAAA,GAAI;QACxG,MAAM,IAAIvB,oBAAU,CAAC,kBAAkB,AAAC,GAAyBxB,OAAvB+B,YAAW,cAAiB,OAAL/B,MAAK;IACxE;IACA,IAAIwD,MAAMM,KAAK,KAAKH,aAAa,OAAOH,MAAMM,KAAK,KAAK,UAAU;QAChE,MAAM,IAAItC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBxB,OAAvB+B,YAAW,cAAiB,OAAL/B,MAAK;IACxE;IACA,IAAIwD,MAAMO,QAAQ,KAAKJ,aAAa,OAAOH,MAAMO,QAAQ,KAAK,WAAW;QACvE,MAAM,IAAIvC,oBAAU,CAAC,kBAAkB,AAAC,GAAyBxB,OAAvB+B,YAAW,cAAiB,OAAL/B,MAAK;IACxE;AACF;AAEA,SAASgE,eAAeC,MAAe,EAAElC,UAAkB;QAmCdhC;QAjB2GmE;IAjBtJ,IAAI,CAAA,OAAOD,uCAAP,SAAOA,OAAK,MAAM,YAAYA,WAAW,QAAQhB,MAAMC,OAAO,CAACe,SAAS;QAC1E,MAAM,IAAIzC,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXO,YAAW;IACvD;IACA,IAAMhC,MAAMkE;IACZ,IAAMtC,OAAO5B,IAAI4B,IAAI;IACrB,IAAI,CAACA,QAAQ,CAACsB,MAAMC,OAAO,CAACvB,KAAKC,OAAO,KAAKD,KAAKC,OAAO,CAAC6B,MAAM,KAAK,KAAK,CAAC9B,KAAKC,OAAO,CAACuC,KAAK,CAAC,SAACC;eAAM,OAAOA,MAAM;QAAW;QAC3H,MAAM,IAAI5C,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXO,YAAW;IACvD;IACA,IAAIhC,IAAI8B,OAAO,KAAK8B,WAAW5D,IAAI8B,OAAO,GAAG,CAAC;IAC9C,IAAI,SAAO9B,IAAI8B,OAAO,MAAK,YAAY9B,IAAI8B,OAAO,KAAK,QAAQoB,MAAMC,OAAO,CAACnD,IAAI8B,OAAO,GAAG;QACzF,MAAM,IAAIL,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXO,YAAW;IACvD;QACK,kCAAA,2BAAA;;QAAL,QAAK,YAAuBb,OAAOmD,OAAO,CAACtE,IAAI8B,OAAO,sBAAjD,SAAA,6BAAA,QAAA,yBAAA,iCAA+E;YAA/E,mCAAA,iBAAO7B,uBAAMuD;YAChB,IAAI,OAAOA,UAAU,UAAU;YAC/BD,kBAAkBtD,MAAMuD,OAAOxB;QACjC;;QAHK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAIL,IAAMmC,WAAWnE,IAAImE,QAAQ;IAC7B,IAAIA,aAAaP,aAAc,CAAA,CAAA,OAAOO,yCAAP,SAAOA,SAAO,MAAM,YAAYA,aAAa,QAAQjB,MAAMC,OAAO,CAACgB,aAAcA,SAASR,IAAI,KAAKC,aAAa,SAAOO,iBAAAA,SAASR,IAAI,cAAbQ,qCAAAA,eAAeJ,KAAK,MAAK,QAAQ,GAAI;QACzL,MAAM,IAAItC,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXO,YAAW;IACvD;IACA,IAAMuC,SAASvE,IAAIuE,MAAM;IACzB,IAAIA,WAAWX,WAAW;YAIP5D;QAHjB,IAAI,CAAA,OAAOuE,uCAAP,SAAOA,OAAK,MAAM,YAAYA,WAAW,QAAQrB,MAAMC,OAAO,CAACoB,WAAW,CAACpD,OAAOqD,MAAM,CAACD,QAAQH,KAAK,CAAC,SAAC7C;mBAAMA,MAAM;YAAU;YAChI,MAAM,IAAIE,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXO,YAAW;QACvD;QACA,IAAMF,WAAW9B,eAAAA,IAAI8B,OAAO,cAAX9B,0BAAAA,eAAe,CAAC;YAC5B,mCAAA,4BAAA;;YAAL,QAAK,aAAcmB,OAAO4B,IAAI,CAACwB,4BAA1B,UAAA,8BAAA,SAAA,0BAAA,kCAAmC;gBAAnC,IAAMtE,QAAN;gBACH,IAAI6B,OAAO,CAAC7B,MAAK,KAAK2D,WAAW,MAAM,IAAInC,oBAAU,CAAC,kBAAkB,AAAC,GAA+BxB,OAA7B+B,YAAW,oBAAuB,OAAL/B,OAAK;gBAC7G,IAAI,OAAO6B,OAAO,CAAC7B,MAAK,KAAK,UAAU,MAAM,IAAIwB,oBAAU,CAAC,kBAAkB,AAAC,GAA+BxB,OAA7B+B,YAAW,oBAAuB,OAAL/B,OAAK;YACrH;;YAHK;YAAA;;;qBAAA,8BAAA;oBAAA;;;oBAAA;0BAAA;;;;IAIP;IACA,IAAID,IAAIG,QAAQ,KAAKyD,aAAc,CAAA,SAAO5D,IAAIG,QAAQ,MAAK,YAAYH,IAAIG,QAAQ,KAAK,QAAQ+C,MAAMC,OAAO,CAACnD,IAAIG,QAAQ,CAAA,GAAI;QAC5H,MAAM,IAAIsB,oBAAU,CAAC,kBAAkB,AAAC,GAAa,OAAXO,YAAW;IACvD;QACK,mCAAA,4BAAA;;;YAAA,mCAAA,kBAAO/B,uBAAMuD;YAChB,IAAI,OAAOA,UAAU,WAAW,OAAA;YAChC,+EAA+E;YAC/E,IAAMpD,QAAQoD;YACd,IAAMiB,UAAUxE,SAAS,WAAW,CAAA,OAAOuD,sCAAP,SAAOA,MAAI,MAAM,YAAYA,UAAU,QAAQ,CAACN,MAAMC,OAAO,CAACK,UAAU;gBAAC;gBAAS;gBAAO;aAAM,CAACY,KAAK,CAAC,SAACM;uBAAMtE,KAAK,CAACsE,EAAE,KAAKd,aAAa,OAAOxD,KAAK,CAACsE,EAAE,KAAK;kBAActE,CAAAA,MAAMQ,IAAI,KAAKgD,aAAaxD,MAAMQ,IAAI,KAAK,YAAYR,MAAMQ,IAAI,KAAK,KAAI;YACvR,IAAI,CAAC6D,SAAS;gBACZ,MAAM,IAAIhD,oBAAU,CAAC,kBAAkB,AAAC,GAA0BxB,OAAxB+B,YAAW,eAAsC/B,OAAzBA,MAAK,sBAAgG,OAA5EA,SAAS,UAAU,sDAAsD;YACtK;QACF;QARA,QAAK,aAAuBkB,OAAOmD,OAAO,EAACtE,gBAAAA,IAAIG,QAAQ,cAAZH,2BAAAA,gBAAgB,CAAC,uBAAvD,UAAA,8BAAA,SAAA,0BAAA;;QAAA;QAAA;;;iBAAA,8BAAA;gBAAA;;;gBAAA;sBAAA;;;;IASL,OAAOA;AACT;AAEO,SAASJ,WAAW+E,YAAqB;IAC9C,IAAI3C;IACJ,IAAI2C,cAAc;QAChB3C,aAAaM,IAAAA,iBAAO,EAACsC,QAAQC,GAAG,IAAIF;QACpC,IAAI,CAAC1C,IAAAA,kBAAU,EAACD,aAAa,MAAM,IAAIP,oBAAU,CAAC,oBAAoB,AAAC,qBAA+B,OAAXO;IAC7F,OAAO;QACL,IAAM8C,QAAQpF,eAAekF,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAIrD,oBAAU,CAAC,oBAAoB,AAAC,kBAAuCmD,OAAtB3F,iBAAgB,QAAoB,OAAd2F,QAAQC,GAAG,IAAG;QACjG;QACA7C,aAAa8C;IACf;IAEA,IAAMC,MAAMC,IAAAA,oBAAY,EAAChD,YAAY;IACrC,IAAMkC,SAAkB/B,KAAK8C,KAAK,CAACF;IAEnC,sFAAsF;IACtF,qFAAqF;IACrF,IAAM7D,UAAU,CAAA,OAAOgD,uCAAP,SAAOA,OAAK,MAAM,YAAYA,WAAW,QAAQ,OAAO,AAACA,OAAiChD,OAAO,KAAK,WAAW,AAACgD,OAA+BhD,OAAO,GAAG;IAC3K,IAAIA,UAAU9B,0BAA0B;QACtC,MAAM,IAAIqC,oBAAU,CAAC,8BAA8B,AAAC,kBAAyB,OAARP,SAAQ;IAC/E;IAEA,IAAIlB,MAAMiE,eAAeC,QAAQlC;IAEjC,IAAIkD;IACJ,IAAIhE,UAAU9B,0BAA0B;QACtC,IAAM+F,SAAStF,cAAcG;QAC7BA,MAAMmF,OAAOnF,GAAG;QAChBkF,eAAeC,OAAO9D,IAAI;QAC1Ba,IAAAA,qBAAa,EAACF,YAAY,AAAC,GAA+B,OAA7BG,KAAKC,SAAS,CAACpC,KAAK,MAAM,IAAG;IAC5D;IAEA,IAAMoF,cAAcvC,kBAAkB7C;IACtC,OAAO,wCAAKA;QAAKqF,SAAS5C,IAAAA,iBAAO,EAACT;QAAaA,YAAAA;QAAYkD,cAAAA;QAAcE,aAAaA,YAAY1B,MAAM,GAAG,IAAI0B,cAAcxB;;AAC/H"}
|
package/dist/cjs/db.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { Config, ResolvedConfig } from './config.js';
|
|
3
3
|
export declare const DB_FILENAME = "cache.db";
|
|
4
|
-
export declare const SCHEMA_VERSION = "
|
|
4
|
+
export declare const SCHEMA_VERSION = "5";
|
|
5
5
|
export interface OpenResult {
|
|
6
6
|
db: DatabaseSync;
|
|
7
7
|
cfg: ResolvedConfig;
|
package/dist/cjs/db.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { Config, ResolvedConfig } from './config.js';
|
|
3
3
|
export declare const DB_FILENAME = "cache.db";
|
|
4
|
-
export declare const SCHEMA_VERSION = "
|
|
4
|
+
export declare const SCHEMA_VERSION = "5";
|
|
5
5
|
export interface OpenResult {
|
|
6
6
|
db: DatabaseSync;
|
|
7
7
|
cfg: ResolvedConfig;
|
package/dist/cjs/db.js
CHANGED
|
@@ -38,7 +38,9 @@ var _nodefs = require("node:fs");
|
|
|
38
38
|
var _nodepath = require("node:path");
|
|
39
39
|
var _nodesqlite = require("node:sqlite");
|
|
40
40
|
var _configts = require("./config.js");
|
|
41
|
+
var _errorsts = require("./errors.js");
|
|
41
42
|
var _indexts = require("./features/index.js");
|
|
43
|
+
var _progressts = require("./progress.js");
|
|
42
44
|
var _scants = require("./scan.js");
|
|
43
45
|
function _array_like_to_array(arr, len) {
|
|
44
46
|
if (len == null || len > arr.length) len = arr.length;
|
|
@@ -65,8 +67,20 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
65
67
|
if (n === "Map" || n === "Set") return Array.from(n);
|
|
66
68
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
67
69
|
}
|
|
70
|
+
// path/_mtime/_size are core: every reparse legitimately rewrites them. Every other
|
|
71
|
+
// RESERVED_COLUMNS entry that shows up as a real frontmatter column (currently only
|
|
72
|
+
// rank's `_rank`) is feature-owned -- scan.ts already refuses to let frontmatter set it,
|
|
73
|
+
// so it must never appear in the upsert below, or a reparse would blow its last computed
|
|
74
|
+
// value away with NULL on every touch, not just the reconciles that recompute it.
|
|
75
|
+
var CORE_FRONTMATTER_COLUMNS = new Set([
|
|
76
|
+
'path',
|
|
77
|
+
'_mtime',
|
|
78
|
+
'_size'
|
|
79
|
+
]);
|
|
68
80
|
var DB_FILENAME = 'cache.db';
|
|
69
|
-
var SCHEMA_VERSION = '
|
|
81
|
+
var SCHEMA_VERSION = '5';
|
|
82
|
+
// SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).
|
|
83
|
+
var MAX_FRONTMATTER_COLUMNS = 2000;
|
|
70
84
|
function quoteIdent(name) {
|
|
71
85
|
return '"'.concat(name.split('"').join('""'), '"');
|
|
72
86
|
}
|
|
@@ -173,12 +187,17 @@ function reconcile(db, cfg, baseDir) {
|
|
|
173
187
|
var newColumns = [];
|
|
174
188
|
var parsedDocs = [];
|
|
175
189
|
var warnings = [];
|
|
190
|
+
// Bulk reparses (a sync, a cold build) are the long silences a query can hit; short
|
|
191
|
+
// reconciles stay silent (progress() has a threshold).
|
|
192
|
+
var report = (0, _progressts.progress)('reparsing files', toReparse.length);
|
|
193
|
+
var parsedCount = 0;
|
|
176
194
|
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
177
195
|
try {
|
|
178
196
|
for(var _iterator = toReparse[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
179
197
|
var file = _step.value;
|
|
180
198
|
var _warnings;
|
|
181
199
|
var _parseFile = (0, _scants.parseFile)(file, features), doc = _parseFile.doc, fileWarnings = _parseFile.warnings;
|
|
200
|
+
report.tick(++parsedCount);
|
|
182
201
|
(_warnings = warnings).push.apply(_warnings, _to_consumable_array(fileWarnings));
|
|
183
202
|
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
184
203
|
try {
|
|
@@ -219,10 +238,41 @@ function reconcile(db, cfg, baseDir) {
|
|
|
219
238
|
}
|
|
220
239
|
}
|
|
221
240
|
}
|
|
241
|
+
report.finish();
|
|
222
242
|
var allColumns = _to_consumable_array(seenColumns);
|
|
223
|
-
|
|
243
|
+
// Fence before ALTERing: SQLite's own failure past this point is a raw
|
|
244
|
+
// "too many columns on sqlite_altertab_frontmatter" with no indication of the boundary or the levers.
|
|
245
|
+
if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {
|
|
246
|
+
throw new _errorsts.SenseError('COLUMN_LIMIT', "frontmatter would need ".concat(allColumns.length, " columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ").concat(MAX_FRONTMATTER_COLUMNS, "; see https://www.sqlite.org/limits.html). Narrow scan.include so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys."));
|
|
247
|
+
}
|
|
248
|
+
// Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a
|
|
249
|
+
// feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).
|
|
250
|
+
var writableColumns = allColumns.filter(function(c) {
|
|
251
|
+
return CORE_FRONTMATTER_COLUMNS.has(c) || !_scants.RESERVED_COLUMNS.has(c);
|
|
252
|
+
});
|
|
253
|
+
// ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --
|
|
254
|
+
// content rows are coupled to that rowid below.
|
|
255
|
+
var insertSql = "INSERT INTO frontmatter (".concat(writableColumns.map(quoteIdent).join(', '), ") VALUES (").concat(writableColumns.map(function() {
|
|
224
256
|
return '?';
|
|
225
|
-
}).join(', '),
|
|
257
|
+
}).join(', '), ') ON CONFLICT("path") DO UPDATE SET ').concat(writableColumns.filter(function(c) {
|
|
258
|
+
return c !== 'path';
|
|
259
|
+
}).map(function(c) {
|
|
260
|
+
return "".concat(quoteIdent(c), " = excluded.").concat(quoteIdent(c));
|
|
261
|
+
}).join(', '));
|
|
262
|
+
var added = toReparse.filter(function(f) {
|
|
263
|
+
return !existing.has(f.relPath);
|
|
264
|
+
}).map(function(f) {
|
|
265
|
+
return f.relPath;
|
|
266
|
+
});
|
|
267
|
+
var delta = {
|
|
268
|
+
files: files,
|
|
269
|
+
reparsed: parsedDocs.map(function(d) {
|
|
270
|
+
return d.relPath;
|
|
271
|
+
}),
|
|
272
|
+
added: added,
|
|
273
|
+
vanished: vanished
|
|
274
|
+
};
|
|
275
|
+
var txStart = Date.now();
|
|
226
276
|
db.exec('BEGIN');
|
|
227
277
|
try {
|
|
228
278
|
var _feature_afterReconcile;
|
|
@@ -246,8 +296,10 @@ function reconcile(db, cfg, baseDir) {
|
|
|
246
296
|
}
|
|
247
297
|
}
|
|
248
298
|
}
|
|
249
|
-
// FTS5 has no upsert, so delete-before-insert into `content
|
|
250
|
-
|
|
299
|
+
// FTS5 has no upsert, so delete-before-insert into `content`; coupled to the
|
|
300
|
+
// frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`
|
|
301
|
+
// column, which a per-row DELETE would otherwise scan the whole table to find.
|
|
302
|
+
var delBody = db.prepare('DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE "path" = ?)');
|
|
251
303
|
if (vanished.length > 0) {
|
|
252
304
|
var del = db.prepare('DELETE FROM frontmatter WHERE "path" = ?');
|
|
253
305
|
var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = undefined;
|
|
@@ -255,13 +307,15 @@ function reconcile(db, cfg, baseDir) {
|
|
|
255
307
|
for(var _iterator3 = vanished[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true){
|
|
256
308
|
var path = _step3.value;
|
|
257
309
|
var _feature_remove;
|
|
258
|
-
|
|
310
|
+
// content delete must run first: it looks up the frontmatter rowid by path, which
|
|
311
|
+
// the frontmatter delete below would otherwise have already removed.
|
|
259
312
|
delBody.run(path);
|
|
313
|
+
del.run(path);
|
|
260
314
|
var _iteratorNormalCompletion4 = true, _didIteratorError4 = false, _iteratorError4 = undefined;
|
|
261
315
|
try {
|
|
262
316
|
for(var _iterator4 = features[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true){
|
|
263
317
|
var feature = _step4.value;
|
|
264
|
-
(_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, path);
|
|
318
|
+
(_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, path, delta);
|
|
265
319
|
}
|
|
266
320
|
} catch (err) {
|
|
267
321
|
_didIteratorError4 = true;
|
|
@@ -295,14 +349,15 @@ function reconcile(db, cfg, baseDir) {
|
|
|
295
349
|
}
|
|
296
350
|
if (parsedDocs.length > 0) {
|
|
297
351
|
var insert = db.prepare(insertSql);
|
|
298
|
-
var insertBody = db.prepare('INSERT INTO content (title, summary, text, "path") VALUES (?, ?, ?, ?)');
|
|
352
|
+
var insertBody = db.prepare('INSERT INTO content (rowid, title, summary, text, "path") VALUES ((SELECT rowid FROM frontmatter WHERE "path" = ?), ?, ?, ?, ?)');
|
|
299
353
|
var _iteratorNormalCompletion5 = true, _didIteratorError5 = false, _iteratorError5 = undefined;
|
|
300
354
|
try {
|
|
301
355
|
var _loop = function() {
|
|
302
356
|
var doc = _step5.value;
|
|
303
|
-
var
|
|
357
|
+
var // Frontmatter upsert first: content's rowid lookup below depends on this row existing.
|
|
358
|
+
_insert;
|
|
304
359
|
var _feature_store;
|
|
305
|
-
var values =
|
|
360
|
+
var values = writableColumns.map(function(col) {
|
|
306
361
|
var _doc_data_col;
|
|
307
362
|
if (col === 'path') return doc.relPath;
|
|
308
363
|
if (col === '_mtime') return doc.mtimeMs;
|
|
@@ -310,9 +365,9 @@ function reconcile(db, cfg, baseDir) {
|
|
|
310
365
|
return (_doc_data_col = doc.data[col]) !== null && _doc_data_col !== void 0 ? _doc_data_col : null;
|
|
311
366
|
});
|
|
312
367
|
(_insert = insert).run.apply(_insert, _to_consumable_array(values));
|
|
313
|
-
// Delete-before-insert only for docs that have rows: an FTS5 DELETE by
|
|
314
|
-
//
|
|
315
|
-
//
|
|
368
|
+
// Delete-before-insert only for docs that have rows: an FTS5 DELETE by rowid on a
|
|
369
|
+
// cold build (empty table, nothing to delete) is wasted work, and doing it
|
|
370
|
+
// unconditionally previously made the crawl quadratic when it scanned by column --
|
|
316
371
|
// measured 4x time per note-count doubling at 13k/26k notes.
|
|
317
372
|
if (existing.has(doc.relPath)) {
|
|
318
373
|
var _feature_remove;
|
|
@@ -321,7 +376,7 @@ function reconcile(db, cfg, baseDir) {
|
|
|
321
376
|
try {
|
|
322
377
|
for(var _iterator = features[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
323
378
|
var feature = _step.value;
|
|
324
|
-
(_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, doc.relPath);
|
|
379
|
+
(_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, db, doc.relPath, delta);
|
|
325
380
|
}
|
|
326
381
|
} catch (err) {
|
|
327
382
|
_didIteratorError = true;
|
|
@@ -338,12 +393,12 @@ function reconcile(db, cfg, baseDir) {
|
|
|
338
393
|
}
|
|
339
394
|
}
|
|
340
395
|
}
|
|
341
|
-
insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
|
|
396
|
+
insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
|
|
342
397
|
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
343
398
|
try {
|
|
344
399
|
for(var _iterator1 = features[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
345
400
|
var feature1 = _step1.value;
|
|
346
|
-
(_feature_store = feature1.store) === null || _feature_store === void 0 ? void 0 : _feature_store.call(feature1, db, doc.relPath, doc.extracted[feature1.name]);
|
|
401
|
+
(_feature_store = feature1.store) === null || _feature_store === void 0 ? void 0 : _feature_store.call(feature1, db, doc.relPath, doc.extracted[feature1.name], delta);
|
|
347
402
|
}
|
|
348
403
|
} catch (err) {
|
|
349
404
|
_didIteratorError1 = true;
|
|
@@ -380,7 +435,7 @@ function reconcile(db, cfg, baseDir) {
|
|
|
380
435
|
try {
|
|
381
436
|
for(var _iterator6 = features[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true){
|
|
382
437
|
var feature1 = _step6.value;
|
|
383
|
-
(_feature_afterReconcile = feature1.afterReconcile) === null || _feature_afterReconcile === void 0 ? void 0 : _feature_afterReconcile.call(feature1, db,
|
|
438
|
+
(_feature_afterReconcile = feature1.afterReconcile) === null || _feature_afterReconcile === void 0 ? void 0 : _feature_afterReconcile.call(feature1, db, delta);
|
|
384
439
|
}
|
|
385
440
|
} catch (err) {
|
|
386
441
|
_didIteratorError6 = true;
|
|
@@ -401,12 +456,21 @@ function reconcile(db, cfg, baseDir) {
|
|
|
401
456
|
db.exec('ROLLBACK');
|
|
402
457
|
throw err;
|
|
403
458
|
}
|
|
459
|
+
// Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):
|
|
460
|
+
// keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.
|
|
461
|
+
var durationMs = Date.now() - txStart;
|
|
462
|
+
var prevRaw = getMeta(db, 'reconcile_max_ms');
|
|
463
|
+
// -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)
|
|
464
|
+
// still gets recorded instead of losing to the "nothing recorded yet" default.
|
|
465
|
+
var prevMax = prevRaw === null ? -1 : Number(prevRaw);
|
|
466
|
+
if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));
|
|
404
467
|
return {
|
|
405
468
|
parsed: parsedDocs.length,
|
|
406
469
|
warnings: warnings
|
|
407
470
|
};
|
|
408
471
|
}
|
|
409
472
|
function open(cfg) {
|
|
473
|
+
var _getMeta;
|
|
410
474
|
var stateDir = (0, _nodepath.join)(cfg.baseDir, _configts.STATE_DIR);
|
|
411
475
|
(0, _nodefs.mkdirSync)(stateDir, {
|
|
412
476
|
recursive: true
|
|
@@ -435,6 +499,13 @@ function open(cfg) {
|
|
|
435
499
|
return open(cfg);
|
|
436
500
|
}
|
|
437
501
|
ensureSchema(db, cfg);
|
|
502
|
+
// Derived from reconcile's own recorded max (F): 3x the largest reconcile this cache has
|
|
503
|
+
// ever held its write transaction for, floored at the 30s default and capped at 10min so
|
|
504
|
+
// one pathological build can't pin every later open to an unbounded wait. Installed
|
|
505
|
+
// before reconcile() below -- this open's own reconcile is exactly the operation that
|
|
506
|
+
// races a concurrent watcher's transaction and needs the derived wait.
|
|
507
|
+
var recordedMaxMs = Number((_getMeta = getMeta(db, 'reconcile_max_ms')) !== null && _getMeta !== void 0 ? _getMeta : '0');
|
|
508
|
+
db.exec("PRAGMA busy_timeout = ".concat(Math.min(Math.max(30000, 3 * recordedMaxMs), 600000)));
|
|
438
509
|
var _reconcile = reconcile(db, cfg, cfg.baseDir), parsed = _reconcile.parsed, warnings = _reconcile.warnings;
|
|
439
510
|
return {
|
|
440
511
|
db: db,
|
package/dist/cjs/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.ts"],"sourcesContent":["import { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from './config.ts';\nimport { featureSignature, STATE_DIR } from './config.ts';\nimport { activeFeatures } from './features/index.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile } from './scan.ts';\n\n// Rows -> SQLite: core schema, reconcile loop, has(). Parsing lives in scan.ts;\n// everything beyond frontmatter + content lives in src/features/.\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`.\nexport const SCHEMA_VERSION = '4'; // 4: frontmatter scalars store as INTEGER/REAL/TEXT by YAML type (was: all numbers REAL)\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n return String(field).includes(needle) ? 1 : 0;\n });\n}\n\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter`\n// can't dump file text into context. Features add their own tables after the core ones.\nfunction ensureSchema(db: DatabaseSync, cfg: Config): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\n for (const feature of activeFeatures(cfg)) feature.schema(db);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n if (getMeta(db, 'features') === null) setMeta(db, 'features', featureSignature(cfg));\n}\n\nexport function getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const features = activeFeatures(cfg);\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n for (const file of toReparse) {\n const { doc, warnings: fileWarnings } = parseFile(file, features);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n\n const allColumns = [...seenColumns];\n const insertSql = `INSERT OR REPLACE INTO frontmatter (${allColumns.map(quoteIdent).join(', ')}) VALUES (${allColumns.map(() => '?').join(', ')})`;\n\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`.\n const delBody = db.prepare(`DELETE FROM content WHERE \"path\" = ?`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n del.run(path);\n delBody.run(path);\n for (const feature of features) feature.remove?.(db, path);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (title, summary, text, \"path\") VALUES (?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = allColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n insert.run(...values);\n // Delete-before-insert only for docs that have rows: an FTS5 DELETE by column\n // cannot use an index and scans the whole table, so running it per doc on a\n // cold build (empty table, nothing to delete) made the crawl quadratic --\n // measured 4x time per note-count doubling at 13k/26k notes.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath);\n }\n insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name]);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, files);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n return { parsed: parsedDocs.length, warnings };\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n // Covers a concurrent watcher's bulk reconcile: the write transaction for 500 changed\n // files measures ~5s at 26k notes, so 5s expired exactly at the boundary and queries\n // racing the watcher got SQLITE_BUSY. 30s bounds the wait at ~3x the largest measured\n // reconcile; a query that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an\n // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).\n const version = getMeta(db, 'schema_version');\n const features = getMeta(db, 'features');\n const wantFeatures = featureSignature(cfg);\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n db.close();\n rmSync(stateDir, { recursive: true, force: true });\n return open(cfg);\n }\n\n ensureSchema(db, cfg);\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings };\n}\n\n// Manual reset for a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","docCount","getMeta","open","rebuild","reconcile","setMeta","quoteIdent","name","split","join","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","Set","map","r","ensureSchema","cfg","exec","activeFeatures","feature","schema","featureSignature","key","row","get","run","n","baseDir","files","listFiles","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","features","seenColumns","newColumns","parsedDocs","file","parseFile","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","delBody","del","remove","insert","insertBody","values","search","title","summary","text","store","extracted","afterReconcile","err","stateDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","version","wantFeatures","close","rmSync","force"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.ts"],"sourcesContent":["import { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from './config.ts';\nimport { featureSignature, STATE_DIR } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { activeFeatures } from './features/index.ts';\nimport type { ReconcileDelta } from './features/types.ts';\nimport { progress } from './progress.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile, RESERVED_COLUMNS } from './scan.ts';\n\n// path/_mtime/_size are core: every reparse legitimately rewrites them. Every other\n// RESERVED_COLUMNS entry that shows up as a real frontmatter column (currently only\n// rank's `_rank`) is feature-owned -- scan.ts already refuses to let frontmatter set it,\n// so it must never appear in the upsert below, or a reparse would blow its last computed\n// value away with NULL on every touch, not just the reconciles that recompute it.\nconst CORE_FRONTMATTER_COLUMNS = new Set(['path', '_mtime', '_size']);\n\n// Rows -> SQLite: core schema, reconcile loop, has(). Parsing lives in scan.ts;\n// everything beyond frontmatter + content lives in src/features/.\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`.\n// 5: frontmatter upsert is ON CONFLICT UPDATE (was OR REPLACE) so rowid is stable, and\n// content is coupled to that rowid instead of an UNINDEXED path column (was: 4 -- scalars\n// store as INTEGER/REAL/TEXT by YAML type).\nexport const SCHEMA_VERSION = '5';\n\n// SQLite's compile-time SQLITE_MAX_COLUMN, default 2000 (https://www.sqlite.org/limits.html).\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n return String(field).includes(needle) ? 1 : 0;\n });\n}\n\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter`\n// can't dump file text into context. Features add their own tables after the core ones.\nfunction ensureSchema(db: DatabaseSync, cfg: Config): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\n for (const feature of activeFeatures(cfg)) feature.schema(db);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n if (getMeta(db, 'features') === null) setMeta(db, 'features', featureSignature(cfg));\n}\n\nexport function getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const features = activeFeatures(cfg);\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n // Bulk reparses (a sync, a cold build) are the long silences a query can hit; short\n // reconciles stay silent (progress() has a threshold).\n const report = progress('reparsing files', toReparse.length);\n let parsedCount = 0;\n for (const file of toReparse) {\n const { doc, warnings: fileWarnings } = parseFile(file, features);\n report.tick(++parsedCount);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n report.finish();\n\n const allColumns = [...seenColumns];\n // Fence before ALTERing: SQLite's own failure past this point is a raw\n // \"too many columns on sqlite_altertab_frontmatter\" with no indication of the boundary or the levers.\n if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${allColumns.length} columns, crossing SQLite's compile-time SQLITE_MAX_COLUMN limit (default ${MAX_FRONTMATTER_COLUMNS}; see https://www.sqlite.org/limits.html). Narrow scan.include so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n // Columns the frontmatter upsert actually writes: core + parsed frontmatter keys, never a\n // feature-owned reserved column (see CORE_FRONTMATTER_COLUMNS above).\n const writableColumns = allColumns.filter((c) => CORE_FRONTMATTER_COLUMNS.has(c) || !RESERVED_COLUMNS.has(c));\n // ON CONFLICT UPDATE (not OR REPLACE) keeps the row's rowid stable across reparses --\n // content rows are coupled to that rowid below.\n const insertSql = `INSERT INTO frontmatter (${writableColumns.map(quoteIdent).join(', ')}) VALUES (${writableColumns.map(() => '?').join(', ')}) ON CONFLICT(\"path\") DO UPDATE SET ${writableColumns\n .filter((c) => c !== 'path')\n .map((c) => `${quoteIdent(c)} = excluded.${quoteIdent(c)}`)\n .join(', ')}`;\n\n const added = toReparse.filter((f) => !existing.has(f.relPath)).map((f) => f.relPath);\n const delta: ReconcileDelta = { files, reparsed: parsedDocs.map((d) => d.relPath), added, vanished };\n\n const txStart = Date.now();\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`; coupled to the\n // frontmatter rowid (indexed via its PRIMARY KEY) instead of the UNINDEXED `path`\n // column, which a per-row DELETE would otherwise scan the whole table to find.\n const delBody = db.prepare(`DELETE FROM content WHERE rowid = (SELECT rowid FROM frontmatter WHERE \"path\" = ?)`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n // content delete must run first: it looks up the frontmatter rowid by path, which\n // the frontmatter delete below would otherwise have already removed.\n delBody.run(path);\n del.run(path);\n for (const feature of features) feature.remove?.(db, path, delta);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (rowid, title, summary, text, \"path\") VALUES ((SELECT rowid FROM frontmatter WHERE \"path\" = ?), ?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = writableColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n // Frontmatter upsert first: content's rowid lookup below depends on this row existing.\n insert.run(...values);\n // Delete-before-insert only for docs that have rows: an FTS5 DELETE by rowid on a\n // cold build (empty table, nothing to delete) is wasted work, and doing it\n // unconditionally previously made the crawl quadratic when it scanned by column --\n // measured 4x time per note-count doubling at 13k/26k notes.\n if (existing.has(doc.relPath)) {\n delBody.run(doc.relPath);\n for (const feature of features) feature.remove?.(db, doc.relPath, delta);\n }\n insertBody.run(doc.relPath, doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n for (const feature of features) feature.store?.(db, doc.relPath, doc.extracted[feature.name], delta);\n }\n }\n for (const feature of features) feature.afterReconcile?.(db, delta);\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n // Reconcile's own write-transaction duration, for open()'s derived busy_timeout (F):\n // keep the observed max so a big watcher reconcile's lock hold is what the next open bounds its wait against.\n const durationMs = Date.now() - txStart;\n const prevRaw = getMeta(db, 'reconcile_max_ms');\n // -1, not 0, so a genuinely 0ms first reconcile (sub-millisecond, common on a tiny tree)\n // still gets recorded instead of losing to the \"nothing recorded yet\" default.\n const prevMax = prevRaw === null ? -1 : Number(prevRaw);\n if (durationMs > prevMax) setMeta(db, 'reconcile_max_ms', String(durationMs));\n\n return { parsed: parsedDocs.length, warnings };\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n // Covers a concurrent watcher's bulk reconcile: the write transaction for 500 changed\n // files measures ~5s at 26k notes, so 5s expired exactly at the boundary and queries\n // racing the watcher got SQLITE_BUSY. 30s bounds the wait at ~3x the largest measured\n // reconcile; a query that outwaits it still fails loudly.\n db.exec('PRAGMA busy_timeout = 30000');\n registerFunctions(db);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Schema-version or feature-set mismatch: reconcile only reparses changed files, so an\n // old cache can't be patched incrementally -- rebuild instead (cheap: nothing expensive lives here).\n const version = getMeta(db, 'schema_version');\n const features = getMeta(db, 'features');\n const wantFeatures = featureSignature(cfg);\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n db.close();\n rmSync(stateDir, { recursive: true, force: true });\n return open(cfg);\n }\n\n ensureSchema(db, cfg);\n\n // Derived from reconcile's own recorded max (F): 3x the largest reconcile this cache has\n // ever held its write transaction for, floored at the 30s default and capped at 10min so\n // one pathological build can't pin every later open to an unbounded wait. Installed\n // before reconcile() below -- this open's own reconcile is exactly the operation that\n // races a concurrent watcher's transaction and needs the derived wait.\n const recordedMaxMs = Number(getMeta(db, 'reconcile_max_ms') ?? '0');\n db.exec(`PRAGMA busy_timeout = ${Math.min(Math.max(30000, 3 * recordedMaxMs), 600_000)}`);\n\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings };\n}\n\n// Manual reset for a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","docCount","getMeta","open","rebuild","reconcile","setMeta","CORE_FRONTMATTER_COLUMNS","Set","MAX_FRONTMATTER_COLUMNS","quoteIdent","name","split","join","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","map","r","ensureSchema","cfg","exec","activeFeatures","feature","schema","featureSignature","key","row","get","run","n","baseDir","files","listFiles","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","features","seenColumns","newColumns","parsedDocs","report","progress","parsedCount","file","parseFile","doc","fileWarnings","tick","push","Object","keys","data","add","finish","allColumns","SenseError","writableColumns","c","RESERVED_COLUMNS","insertSql","added","delta","reparsed","d","txStart","Date","now","col","delBody","del","remove","insert","insertBody","values","search","title","summary","text","store","extracted","afterReconcile","err","durationMs","prevRaw","prevMax","Number","stateDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","version","wantFeatures","close","rmSync","force","recordedMaxMs","Math","min","max"],"mappings":"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"}
|
package/dist/cjs/errors.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'SEARCH_SYNTAX';
|
|
1
|
+
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT';
|
|
2
2
|
export declare class SenseError extends Error {
|
|
3
3
|
code: SenseErrorCode;
|
|
4
4
|
constructor(code: SenseErrorCode, message: string);
|
package/dist/cjs/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'SEARCH_SYNTAX';
|
|
1
|
+
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT';
|
|
2
2
|
export declare class SenseError extends Error {
|
|
3
3
|
code: SenseErrorCode;
|
|
4
4
|
constructor(code: SenseErrorCode, message: string);
|
package/dist/cjs/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'SEARCH_SYNTAX';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","code","message","name","Error"],"mappings":"AAAA,qDAAqD;;;;;+BAGxCA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAN,IAAA,AAAMA,2BAAN;;cAAMA;aAAAA,WAGCC,IAAoB,EAAEC,OAAe;gCAHtCF;;gBAIT,kBAJSA;YAIHE;;QACN,MAAKC,IAAI,GAAG;QACZ,MAAKF,IAAI,GAAGA;;;WANHD;qBAAmBI"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS' | 'EMBED_DISABLED' | 'EMBED_MODEL' | 'SEARCH_SYNTAX' | 'COLUMN_LIMIT';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","code","message","name","Error"],"mappings":"AAAA,qDAAqD;;;;;+BAGxCA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAN,IAAA,AAAMA,2BAAN;;cAAMA;aAAAA,WAGCC,IAAoB,EAAEC,OAAe;gCAHtCF;;gBAIT,kBAJSA;YAIHE;;QACN,MAAKC,IAAI,GAAG;QACZ,MAAKF,IAAI,GAAGA;;;WANHD;qBAAmBI"}
|
|
@@ -24,6 +24,7 @@ var _nodeos = require("node:os");
|
|
|
24
24
|
var _nodepath = require("node:path");
|
|
25
25
|
var _configts = require("../config.js");
|
|
26
26
|
var _errorsts = require("../errors.js");
|
|
27
|
+
var _progressts = require("../progress.js");
|
|
27
28
|
var _scants = require("../scan.js");
|
|
28
29
|
function _array_like_to_array(arr, len) {
|
|
29
30
|
if (len == null || len > arr.length) len = arr.length;
|
|
@@ -595,7 +596,7 @@ function toStore(full, dims, int8) {
|
|
|
595
596
|
}
|
|
596
597
|
function embedPending(db, cfg, baseDir) {
|
|
597
598
|
return _async_to_generator(function() {
|
|
598
|
-
var _loop, provider, dirty, storeDims, byPath, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, row, _byPath_get, list, jobs, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, path, chunkIdxs, chunks, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, idx, update, i;
|
|
599
|
+
var _loop, provider, dirty, storeDims, byPath, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, row, _byPath_get, list, jobs, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, path, chunkIdxs, chunks, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, idx, update, report, i;
|
|
599
600
|
return _ts_generator(this, function(_state) {
|
|
600
601
|
switch(_state.label){
|
|
601
602
|
case 0:
|
|
@@ -626,6 +627,7 @@ function embedPending(db, cfg, baseDir) {
|
|
|
626
627
|
db.exec('ROLLBACK');
|
|
627
628
|
throw err;
|
|
628
629
|
}
|
|
630
|
+
report.tick(Math.min(i + BATCH, jobs.length));
|
|
629
631
|
return [
|
|
630
632
|
2
|
|
631
633
|
];
|
|
@@ -725,6 +727,9 @@ function embedPending(db, cfg, baseDir) {
|
|
|
725
727
|
}
|
|
726
728
|
}
|
|
727
729
|
update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE "path" = ? AND chunk = ?');
|
|
730
|
+
// The lazy build is the one long silence a first --semantic query hits (measured 23s at
|
|
731
|
+
// 26k notes); progress makes it distinguishable from a hang.
|
|
732
|
+
report = (0, _progressts.progress)('embedding chunks', jobs.length);
|
|
728
733
|
i = 0;
|
|
729
734
|
_state.label = 2;
|
|
730
735
|
case 2:
|
|
@@ -746,6 +751,7 @@ function embedPending(db, cfg, baseDir) {
|
|
|
746
751
|
2
|
|
747
752
|
];
|
|
748
753
|
case 5:
|
|
754
|
+
report.finish();
|
|
749
755
|
return [
|
|
750
756
|
2
|
|
751
757
|
];
|