sensemaking 0.3.2 → 0.5.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 +3 -1
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/find.js +2 -2
- package/dist/cjs/commands/find.js.map +1 -1
- package/dist/cjs/commands/map.js +3 -3
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/named.js +2 -2
- package/dist/cjs/commands/named.js.map +1 -1
- package/dist/cjs/commands/peek.js +2 -2
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/query.js +2 -2
- package/dist/cjs/commands/query.js.map +1 -1
- package/dist/cjs/commands/rebuild.js +2 -2
- package/dist/cjs/commands/rebuild.js.map +1 -1
- package/dist/cjs/commands/{vault.d.cts → shared.d.cts} +1 -1
- package/dist/cjs/commands/{vault.d.ts → shared.d.ts} +1 -1
- package/dist/cjs/commands/{vault.js → shared.js} +3 -3
- package/dist/cjs/commands/shared.js.map +1 -0
- package/dist/cjs/commands/status.js +2 -2
- package/dist/cjs/commands/status.js.map +1 -1
- package/dist/cjs/commands/watch.js +2 -2
- package/dist/cjs/commands/watch.js.map +1 -1
- package/dist/cjs/config.js +36 -3
- package/dist/cjs/config.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/links.js +1 -1
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/index.d.cts +4 -11
- package/dist/cjs/index.d.ts +4 -11
- package/dist/cjs/index.js +5 -55
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/verbs.d.cts +2 -2
- package/dist/cjs/verbs.d.ts +2 -2
- package/dist/cjs/verbs.js +10 -6
- package/dist/cjs/verbs.js.map +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/commands/find.js +2 -2
- package/dist/esm/commands/find.js.map +1 -1
- package/dist/esm/commands/map.js +4 -4
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/named.js +1 -1
- package/dist/esm/commands/named.js.map +1 -1
- package/dist/esm/commands/peek.js +2 -2
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/query.js +1 -1
- package/dist/esm/commands/query.js.map +1 -1
- package/dist/esm/commands/rebuild.js +1 -1
- package/dist/esm/commands/rebuild.js.map +1 -1
- package/dist/esm/commands/{vault.d.ts → shared.d.ts} +1 -1
- package/dist/esm/commands/{vault.js → shared.js} +2 -2
- package/dist/esm/commands/shared.js.map +1 -0
- package/dist/esm/commands/status.js +1 -1
- package/dist/esm/commands/status.js.map +1 -1
- package/dist/esm/commands/watch.js +1 -1
- package/dist/esm/commands/watch.js.map +1 -1
- package/dist/esm/config.js +26 -3
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/features/links.js +1 -1
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/types.js +1 -1
- package/dist/esm/features/types.js.map +1 -1
- package/dist/esm/index.d.ts +4 -11
- package/dist/esm/index.js +5 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/verbs.d.ts +2 -2
- package/dist/esm/verbs.js +8 -4
- package/dist/esm/verbs.js.map +1 -1
- package/package.json +1 -1
- package/schema.json +1 -1
- package/skills/sense/EXAMPLES.md +12 -13
- package/skills/sense/SKILL.md +28 -16
- package/dist/cjs/commands/vault.js.map +0 -1
- package/dist/esm/commands/vault.js.map +0 -1
package/dist/esm/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.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\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?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\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// 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, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_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(FEATURE_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\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 let cfg = JSON.parse(raw) as Config;\n\n const version = cfg.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 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 return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","featureEnabled","cfg","name","enabled","features","enabledFeatures","filter","MIGRATIONS","version","Object","fromEntries","map","migrateConfig","from","current","v","step","starterConfig","$schema","scan","include","queries","initConfig","dir","configPath","JSON","stringify","findConfigPath","startDir","candidate","parent","loadConfig","explicitPath","process","cwd","found","raw","parse","migratedFrom","result","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; verbs degrade when one is off.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\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?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\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// 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, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_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(FEATURE_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.\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 if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features) || !Object.values(cfg.features).every((v) => typeof v === 'boolean'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\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 return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","featureEnabled","cfg","name","enabled","features","enabledFeatures","filter","MIGRATIONS","version","Object","fromEntries","map","migrateConfig","from","current","v","step","starterConfig","$schema","scan","include","queries","initConfig","dir","configPath","JSON","stringify","findConfigPath","startDir","candidate","parent","validateConfig","parsed","Array","isArray","length","every","g","undefined","values","q","loadConfig","explicitPath","process","cwd","found","raw","parse","migratedFrom","result","baseDir"],"mappings":"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"}
|
package/dist/esm/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
1
|
+
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
2
2
|
export declare class SenseError extends Error {
|
|
3
3
|
code: SenseErrorCode;
|
|
4
4
|
constructor(code: SenseErrorCode, message: string);
|
package/dist/esm/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_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
|
|
@@ -36,7 +36,7 @@ function resolveTarget(src, target, pathSet, byBase) {
|
|
|
36
36
|
return (_ref = (_byBase_get = byBase.get(baseKey(clean))) === null || _byBase_get === void 0 ? void 0 : _byBase_get[0]) !== null && _ref !== void 0 ? _ref : null;
|
|
37
37
|
}
|
|
38
38
|
// A new or deleted file can change any note's resolution, so re-resolve the whole table
|
|
39
|
-
// whenever the
|
|
39
|
+
// whenever the tree changes. An in-memory probe per row; writes only on change.
|
|
40
40
|
function resolveAll(db, files) {
|
|
41
41
|
const pathSet = new Set(files.map((f)=>f.relPath));
|
|
42
42
|
const byBase = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/links.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FileStat } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// links(src, target, dst): target as written, dst the resolved path or NULL (a queryable dead link).\n\n// Wikilinks ([[target]], [[target#anchor|alias]], embeds) plus relative markdown links to .md files.\nfunction extract(_raw: string, body: string): string[] {\n const targets = new Set<string>();\n for (const m of body.matchAll(/\\[\\[([^\\]|#]+)(?:#[^\\]|]*)?(?:\\|[^\\]]*)?\\]\\]/g)) {\n const target = m[1].trim();\n if (target) targets.add(target);\n }\n for (const m of body.matchAll(/\\]\\(([^)]+\\.md)(?:#[^)]*)?\\)/g)) {\n const target = m[1].trim();\n if (target && !/^[a-z]+:\\/\\//i.test(target)) targets.add(target);\n }\n return [...targets];\n}\n\nfunction baseKey(path: string): string {\n return posix.basename(path).replace(/\\.md$/i, '').toLowerCase();\n}\n\n// Obsidian-style: exact relative path (with/without .md), path relative to the linking\n// note's directory, then basename match (lexicographically first on ties).\nfunction resolveTarget(src: string, target: string, pathSet: Set<string>, byBase: Map<string, string[]>): string | null {\n const clean = target.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n const fromSrc = posix.normalize(posix.join(posix.dirname(src), clean));\n for (const candidate of [clean, `${clean}.md`, fromSrc, `${fromSrc}.md`]) {\n if (pathSet.has(candidate)) return candidate;\n }\n return byBase.get(baseKey(clean))?.[0] ?? null;\n}\n\n// A new or deleted file can change any note's resolution, so re-resolve the whole table\n// whenever the
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/links.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FileStat } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// links(src, target, dst): target as written, dst the resolved path or NULL (a queryable dead link).\n\n// Wikilinks ([[target]], [[target#anchor|alias]], embeds) plus relative markdown links to .md files.\nfunction extract(_raw: string, body: string): string[] {\n const targets = new Set<string>();\n for (const m of body.matchAll(/\\[\\[([^\\]|#]+)(?:#[^\\]|]*)?(?:\\|[^\\]]*)?\\]\\]/g)) {\n const target = m[1].trim();\n if (target) targets.add(target);\n }\n for (const m of body.matchAll(/\\]\\(([^)]+\\.md)(?:#[^)]*)?\\)/g)) {\n const target = m[1].trim();\n if (target && !/^[a-z]+:\\/\\//i.test(target)) targets.add(target);\n }\n return [...targets];\n}\n\nfunction baseKey(path: string): string {\n return posix.basename(path).replace(/\\.md$/i, '').toLowerCase();\n}\n\n// Obsidian-style: exact relative path (with/without .md), path relative to the linking\n// note's directory, then basename match (lexicographically first on ties).\nfunction resolveTarget(src: string, target: string, pathSet: Set<string>, byBase: Map<string, string[]>): string | null {\n const clean = target.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n const fromSrc = posix.normalize(posix.join(posix.dirname(src), clean));\n for (const candidate of [clean, `${clean}.md`, fromSrc, `${fromSrc}.md`]) {\n if (pathSet.has(candidate)) return candidate;\n }\n return byBase.get(baseKey(clean))?.[0] ?? null;\n}\n\n// A new or deleted file can change any note's resolution, so re-resolve the whole table\n// whenever the tree changes. An in-memory probe per row; writes only on change.\nfunction resolveAll(db: DatabaseSync, files: FileStat[]): void {\n const pathSet = new Set(files.map((f) => f.relPath));\n const byBase = new Map<string, string[]>();\n for (const path of [...pathSet].sort()) {\n const list = byBase.get(baseKey(path));\n if (list) list.push(path);\n else byBase.set(baseKey(path), [path]);\n }\n const rows = db.prepare('SELECT src, target, dst FROM links').all() as Array<{ src: string; target: string; dst: string | null }>;\n const update = db.prepare('UPDATE links SET dst = ? WHERE src = ? AND target = ?');\n for (const row of rows) {\n const dst = resolveTarget(row.src, row.target, pathSet, byBase);\n if (dst !== row.dst) update.run(dst, row.src, row.target);\n }\n}\n\n// Resolved edges, for rank and for find's graph expansion.\nexport function linkEdges(db: DatabaseSync): [string, string][] {\n return (db.prepare('SELECT src, dst FROM links WHERE dst IS NOT NULL').all() as Array<{ src: string; dst: string }>).map((r) => [r.src, r.dst]);\n}\n\nexport const links: Feature = {\n name: 'links',\n schema(db) {\n db.exec('CREATE TABLE IF NOT EXISTS links (src TEXT, target TEXT, dst TEXT, PRIMARY KEY (src, target))');\n db.exec('CREATE INDEX IF NOT EXISTS links_dst ON links(dst)');\n },\n extract,\n remove(db, path) {\n db.prepare('DELETE FROM links WHERE src = ?').run(path);\n },\n store(db, path, extracted) {\n const insert = db.prepare('INSERT OR REPLACE INTO links (src, target, dst) VALUES (?, ?, NULL)');\n for (const target of extracted as string[]) insert.run(path, target);\n },\n afterReconcile: resolveAll,\n};\n"],"names":["posix","extract","_raw","body","targets","Set","m","matchAll","target","trim","add","test","baseKey","path","basename","replace","toLowerCase","resolveTarget","src","pathSet","byBase","clean","fromSrc","normalize","join","dirname","candidate","has","get","resolveAll","db","files","map","f","relPath","Map","sort","list","push","set","rows","prepare","all","update","row","dst","run","linkEdges","r","links","name","schema","exec","remove","store","extracted","insert","afterReconcile"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/types.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName } from '../config.ts';\nimport type { FileStat } from '../scan.ts';\n\n// A feature owns its schema, per-file extraction/storage, and any whole-
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/types.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName } from '../config.ts';\nimport type { FileStat } from '../scan.ts';\n\n// A feature owns its schema, per-file extraction/storage, and any whole-tree pass.\n// Adding a feature = one module here + one entry in the registry (index.ts).\nexport interface Feature {\n name: FeatureName;\n schema(db: DatabaseSync): void;\n // Pure, per file: raw is the full file, body the prose after frontmatter.\n extract?(raw: string, body: string): unknown;\n remove?(db: DatabaseSync, path: string): void;\n store?(db: DatabaseSync, path: string, extracted: unknown): void;\n // After all rows are current, inside the reconcile transaction (resolution, rank).\n afterReconcile?(db: DatabaseSync, files: FileStat[]): void;\n}\n"],"names":[],"mappings":"AAIA,mFAAmF;AACnF,6EAA6E;AAC7E,WASC"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
export type { Config, FeatureName, ResolvedConfig } from './config.js';
|
|
2
|
-
export { CONFIG_FILENAME,
|
|
2
|
+
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
3
3
|
export type { OpenResult } from './db.js';
|
|
4
|
-
export {
|
|
4
|
+
export { open, rebuild } from './db.js';
|
|
5
5
|
export type { SenseErrorCode } from './errors.js';
|
|
6
6
|
export { SenseError } from './errors.js';
|
|
7
|
-
export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
|
|
8
|
-
export type { Section } from './features/sections.js';
|
|
9
|
-
export type { Feature } from './features/types.js';
|
|
10
|
-
export type { Edge } from './graph.js';
|
|
11
|
-
export { pagerank, personalizedRank } from './graph.js';
|
|
12
7
|
export type { Row } from './output.js';
|
|
13
8
|
export { printRows } from './output.js';
|
|
14
|
-
export type {
|
|
15
|
-
export {
|
|
16
|
-
export type { FindOptions, Peek, VaultMap } from './verbs.js';
|
|
17
|
-
export { find, peek, vaultMap } from './verbs.js';
|
|
9
|
+
export type { FindOptions, Peek, TreeMap } from './verbs.js';
|
|
10
|
+
export { find, mapTree, peek } from './verbs.js';
|
|
18
11
|
export type { WatchEvent, WatchOptions } from './watch.js';
|
|
19
12
|
export { runWatch } from './watch.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// Public library API.
|
|
2
|
-
|
|
3
|
-
export {
|
|
1
|
+
// Public library API. Deliberately small: every export is a stability promise;
|
|
2
|
+
// internals (feature registry, graph, scan, meta) stay module-private.
|
|
3
|
+
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
4
|
+
export { open, rebuild } from './db.js';
|
|
4
5
|
export { SenseError } from './errors.js';
|
|
5
|
-
export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
|
|
6
|
-
export { pagerank, personalizedRank } from './graph.js';
|
|
7
6
|
export { printRows } from './output.js';
|
|
8
|
-
export {
|
|
9
|
-
export { find, peek, vaultMap } from './verbs.js';
|
|
7
|
+
export { find, mapTree, peek } from './verbs.js';
|
|
10
8
|
export { runWatch } from './watch.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME,
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { open, rebuild } from './db.ts';\n\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\n\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { FindOptions, Peek, TreeMap } from './verbs.ts';\nexport { find, mapTree, peek } from './verbs.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","open","rebuild","SenseError","printRows","find","mapTree","peek","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAG1H,SAASC,IAAI,EAAEC,OAAO,QAAQ,UAAU;AAGxC,SAASC,UAAU,QAAQ,cAAc;AAGzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,IAAI,EAAEC,OAAO,EAAEC,IAAI,QAAQ,aAAa;AAGjD,SAASC,QAAQ,QAAQ,aAAa"}
|
package/dist/esm/verbs.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface FindOptions {
|
|
|
6
6
|
where?: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function find(db: DatabaseSync, cfg: Config, terms: string, opts?: FindOptions): Row[];
|
|
9
|
-
export interface
|
|
9
|
+
export interface TreeMap {
|
|
10
10
|
docs: {
|
|
11
11
|
count: number;
|
|
12
12
|
bytes: number;
|
|
@@ -16,7 +16,7 @@ export interface VaultMap {
|
|
|
16
16
|
hubs: Row[];
|
|
17
17
|
recent: Row[];
|
|
18
18
|
}
|
|
19
|
-
export declare function
|
|
19
|
+
export declare function mapTree(db: DatabaseSync, cfg: Config): TreeMap;
|
|
20
20
|
export interface Peek {
|
|
21
21
|
path: string;
|
|
22
22
|
tokens: number;
|
package/dist/esm/verbs.js
CHANGED
|
@@ -3,7 +3,7 @@ import { featureEnabled } from './config.js';
|
|
|
3
3
|
import { SenseError } from './errors.js';
|
|
4
4
|
import { linkEdges } from './features/index.js';
|
|
5
5
|
import { personalizedRank } from './graph.js';
|
|
6
|
-
// The three layer verbs:
|
|
6
|
+
// The three layer verbs: mapTree (orient), find (locate), peek (structure).
|
|
7
7
|
// Each returns data; cli.ts renders. All of them degrade when a feature is off.
|
|
8
8
|
const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';
|
|
9
9
|
const RRF_K = 60;
|
|
@@ -15,7 +15,11 @@ export function find(db, cfg, terms, opts = {}) {
|
|
|
15
15
|
const fetch = Math.max(k * 3, 30);
|
|
16
16
|
// Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.
|
|
17
17
|
// Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.
|
|
18
|
-
|
|
18
|
+
// --where applies inside the candidate query (a post-filter over the top-N would drop
|
|
19
|
+
// matches ranked past the pool) and again on the final select for link-derived rows.
|
|
20
|
+
const whereJoin = opts.where ? `JOIN frontmatter f ON f."path" = content.path` : '';
|
|
21
|
+
const whereCond = opts.where ? `AND (${opts.where})` : '';
|
|
22
|
+
const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
|
|
19
23
|
const matchRows = db.prepare(matchSql).all(terms);
|
|
20
24
|
const hits = new Map(matchRows.map((r)=>[
|
|
21
25
|
r.path,
|
|
@@ -65,8 +69,8 @@ const INTERNAL_COLUMNS = new Set([
|
|
|
65
69
|
'_size',
|
|
66
70
|
'_rank'
|
|
67
71
|
]);
|
|
68
|
-
// Layer 0: what is this
|
|
69
|
-
export function
|
|
72
|
+
// Layer 0: what is this tree. Fixed-size output regardless of tree size.
|
|
73
|
+
export function mapTree(db, cfg) {
|
|
70
74
|
const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM("_size"), 0) AS bytes FROM frontmatter').get();
|
|
71
75
|
const columns = db.prepare('PRAGMA table_info(frontmatter)').all().map((r)=>r.name).filter((name)=>!INTERNAL_COLUMNS.has(name));
|
|
72
76
|
const allFields = columns.map((name)=>{
|
package/dist/esm/verbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config } from './config.ts';\nimport { featureEnabled } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\n\n// The three layer verbs: vaultMap (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\nexport function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Row[] {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content WHERE content MATCH ? ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n const matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n if (featureEnabled(cfg, 'links') && matchRows.length > 0) {\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, linkEdges(db), seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n db.exec('CREATE TEMP TABLE IF NOT EXISTS _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT)');\n db.exec('DELETE FROM _find');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit) VALUES (?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null);\n\n const where = opts.where ? `WHERE ${opts.where}` : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface VaultMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this vault. Fixed-size output regardless of vault size.\nexport function vaultMap(db: DatabaseSync, cfg: Config): VaultMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const { n } = db.prepare(`SELECT COUNT(\"${name.split('\"').join('\"\"')}\") AS n FROM frontmatter`).get() as { n: number };\n return { field: name, coverage: n };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n };\n}\n"],"names":["posix","featureEnabled","SenseError","linkEdges","personalizedRank","WEIGHTED_BM25","RRF_K","find","db","cfg","terms","opts","hits","k","fetch","Math","max","matchSql","matchRows","prepare","all","Map","map","r","path","hit","candidates","forEach","i","set","score","via","length","nodes","seeds","ranked","filter","sort","a","b","slice","existing","get","exec","insert","c","run","where","INTERNAL_COLUMNS","Set","vaultMap","docs","columns","name","has","allFields","n","split","join","field","coverage","fields","hubs","recent","fieldsTotal","PEEK_LINK_LIMIT","peek","pathArg","row","paths","p","base","basename","replace","toLowerCase","matches","frontmatter","key","value","Object","entries","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config } from './config.ts';\nimport { featureEnabled } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\n\n// The three layer verbs: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\nexport function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Row[] {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n // --where applies inside the candidate query (a post-filter over the top-N would drop\n // matches ranked past the pool) and again on the final select for link-derived rows.\n const whereJoin = opts.where ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = opts.where ? `AND (${opts.where})` : '';\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n const matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n if (featureEnabled(cfg, 'links') && matchRows.length > 0) {\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, linkEdges(db), seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n db.exec('CREATE TEMP TABLE IF NOT EXISTS _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT)');\n db.exec('DELETE FROM _find');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit) VALUES (?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null);\n\n const where = opts.where ? `WHERE ${opts.where}` : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const { n } = db.prepare(`SELECT COUNT(\"${name.split('\"').join('\"\"')}\") AS n FROM frontmatter`).get() as { n: number };\n return { field: name, coverage: n };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n };\n}\n"],"names":["posix","featureEnabled","SenseError","linkEdges","personalizedRank","WEIGHTED_BM25","RRF_K","find","db","cfg","terms","opts","hits","k","fetch","Math","max","whereJoin","where","whereCond","matchSql","matchRows","prepare","all","Map","map","r","path","hit","candidates","forEach","i","set","score","via","length","nodes","seeds","ranked","filter","sort","a","b","slice","existing","get","exec","insert","c","run","INTERNAL_COLUMNS","Set","mapTree","docs","columns","name","has","allFields","n","split","join","field","coverage","fields","hubs","recent","fieldsTotal","PEEK_LINK_LIMIT","peek","pathArg","row","paths","p","base","basename","replace","toLowerCase","matches","frontmatter","key","value","Object","entries","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal"],"mappings":"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"}
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"queries": {
|
|
52
52
|
"type": "object",
|
|
53
|
-
"description": "Named SQL queries runnable as `sense <name> [params...]`. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `?` placeholders bind to CLI positional args in order. `has(field, value)`: array membership on a JSON-array field, substring match on a string, false on NULL. Canonical query: `SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`.",
|
|
53
|
+
"description": "Named SQL queries runnable as `sense <name> [params...]`. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `?` placeholders bind to CLI positional args in order. `has(field, value)`: array membership on a JSON-array field, substring match on a string (so has(f.status, 'active') also matches 'inactive'), false on NULL. Exact matches: `=` for scalars, `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)` for array members. Canonical query: `SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`.",
|
|
54
54
|
"additionalProperties": { "type": "string" }
|
|
55
55
|
}
|
|
56
56
|
}
|
package/skills/sense/EXAMPLES.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Outputs below are illustrative. Every result is a reference; reading happens afterward, through
|
|
4
4
|
the filesystem, on the paths that earned it.
|
|
5
5
|
|
|
6
|
-
## A. "
|
|
6
|
+
## A. "Do the notes say anything about X?"
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
sense find "pricing OR billing OR invoicing" --k 10 --format json
|
|
@@ -19,8 +19,7 @@ sense find "pricing OR billing OR invoicing" --k 10 --format json
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
~30 tokens per row; often the `summary` answers the question with no read at all. A row with
|
|
22
|
-
`via: "link"` never contained the terms — it is linked from notes that did
|
|
23
|
-
worth following, not noise.
|
|
22
|
+
`via: "link"` never contained the terms — it is linked from notes that did.
|
|
24
23
|
|
|
25
24
|
## B. Known-field filtering (no search)
|
|
26
25
|
|
|
@@ -68,7 +67,7 @@ sense query "SELECT path, round(_rank*100,2) r FROM frontmatter ORDER BY _rank D
|
|
|
68
67
|
|
|
69
68
|
`sense map` prints the hub list without SQL; use these when you need it filtered or joined.
|
|
70
69
|
|
|
71
|
-
## E. Cold start on an unknown
|
|
70
|
+
## E. Cold start on an unknown notes tree
|
|
72
71
|
|
|
73
72
|
```
|
|
74
73
|
sense map # fields, hubs, recent — read this first
|
|
@@ -76,14 +75,14 @@ sense --list # named queries someone already saved
|
|
|
76
75
|
sense query "SELECT DISTINCT type FROM frontmatter" # what a field's values are
|
|
77
76
|
```
|
|
78
77
|
|
|
79
|
-
##
|
|
78
|
+
## Consequences
|
|
80
79
|
|
|
81
|
-
|
|
|
80
|
+
| query | what happens | bounded alternative |
|
|
82
81
|
|---|---|---|
|
|
83
|
-
| `SELECT text FROM content` |
|
|
84
|
-
| `sense find "pricing"` |
|
|
85
|
-
| `sense find "pricing model details"` | bare words AND-join
|
|
86
|
-
| `sense find "customer-facing OR on-site"` | bare punctuation is FTS5 syntax (`-`
|
|
87
|
-
| `Read` a
|
|
88
|
-
| queries without `LIMIT` | unbounded output
|
|
89
|
-
|
|
|
82
|
+
| `SELECT text FROM content` | returns the tree's entire prose | `snippet(content, -1, '«', '»', '…', 10)` excerpts the match |
|
|
83
|
+
| `sense find "pricing"` | matches only that word's stem | OR-in synonyms and instances: `"pricing OR billing OR invoicing"` |
|
|
84
|
+
| `sense find "pricing model details"` | bare words AND-join; one absent word = zero rows | OR the words, or quote an exact phrase |
|
|
85
|
+
| `sense find "customer-facing OR on-site"` | bare punctuation is FTS5 syntax (`-` reads as a column filter) | double-quote: `"customer-facing" OR "on-site"` |
|
|
86
|
+
| `Read` of a large file for one section | costs the whole file | `peek`, then `Read` the line range |
|
|
87
|
+
| row queries without `LIMIT` | unbounded output (aggregates are already bounded) | `LIMIT n` |
|
|
88
|
+
| saving one-off queries to config | config churn | ad-hoc `sense query`; save reusable views |
|
package/skills/sense/SKILL.md
CHANGED
|
@@ -21,9 +21,11 @@ only as deep as the question needs.
|
|
|
21
21
|
`a OR b OR c` for any-word matching.
|
|
22
22
|
3. `sense peek <path>` — structure before reading: outline with `[L143-162, ~380t]` ranges,
|
|
23
23
|
links both ways. ~17% the cost of reading the file.
|
|
24
|
-
4. `Read` the
|
|
24
|
+
4. `Read` — the payload. On large files, peek's line ranges let you read just the section
|
|
25
|
+
you need; small files are often cheaper to read whole.
|
|
25
26
|
|
|
26
|
-
Every result is a reference, never file contents.
|
|
27
|
+
Every result is a reference, never file contents. Output defaults to a table, built for
|
|
28
|
+
humans; `--format json` returns the same rows machine-parseable.
|
|
27
29
|
|
|
28
30
|
## Verbs
|
|
29
31
|
|
|
@@ -36,13 +38,16 @@ sense <name> [params...] # named query from sense.config.
|
|
|
36
38
|
sense --list | status | rebuild
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
- **Expand terms before searching.** Write `pricing OR billing OR invoicing`, not one word — you
|
|
40
|
-
know the synonyms; the index only knows the words in the files. Expand categories into their
|
|
41
|
-
likely members too: a note about TypeScript never says "programming language".
|
|
42
41
|
- Terms pass verbatim to FTS5 MATCH. Bare words AND-join — one absent word means zero rows —
|
|
43
|
-
so write `OR` yourself when you want any-word matching;
|
|
44
|
-
rewrite.
|
|
45
|
-
|
|
42
|
+
so write `OR` yourself when you want any-word matching; double-quote punctuated terms
|
|
43
|
+
(`"customer-facing"`, `"founder's"`); invalid syntax is an error, not a rewrite. The same
|
|
44
|
+
rules apply to search commands you write into subagent briefs.
|
|
45
|
+
- When a search misses, the recall levers are: OR-in synonyms and concrete instances (the
|
|
46
|
+
index only knows the words in the files — a note about a specific tool rarely names its
|
|
47
|
+
category), and raise `--k` (a row costs ~30 tokens). Each widening adds candidates and
|
|
48
|
+
dilutes ranking, so the noise trade-off runs both ways.
|
|
49
|
+
- A frontmatter query enumerates its matches deterministically; search ranks by term overlap,
|
|
50
|
+
so results shift as phrasing shifts. Trade-off: a query needs a known field, search doesn't.
|
|
46
51
|
- `find` fuses BM25 with link-graph expansion; the `via` column says what produced each row —
|
|
47
52
|
`match` (terms hit), `link` (connected to notes that hit), `match+link` (both).
|
|
48
53
|
- `--where` takes a frontmatter condition against alias `f`, e.g. `"f.status = 'active' AND has(f.tags, 'x')"`.
|
|
@@ -66,15 +71,21 @@ sense query "SELECT j.value, COUNT(*) n FROM frontmatter, json_each(frontmatter.
|
|
|
66
71
|
- Rank with `ORDER BY bm25(content, 10.0, 5.0, 1.0)` (title > summary > body); excerpt with
|
|
67
72
|
`snippet(content, -1, '«', '»', '…', 10)`.
|
|
68
73
|
- Select `content.title`/`content.summary` (always exist, empty when absent) rather than
|
|
69
|
-
`f.title`/`f.summary` (discovered columns — error on
|
|
70
|
-
- `has(field, value)`: array membership on JSON-array fields, substring on strings, false on NULL
|
|
74
|
+
`f.title`/`f.summary` (discovered columns — error on trees that never declare them).
|
|
75
|
+
- `has(field, value)`: array membership on JSON-array fields, substring on strings, false on NULL
|
|
76
|
+
— the `includes()` convention. Substring means `has(f.status, 'active')` also matches
|
|
77
|
+
`inactive`; exact scalar match is `f.status = ?`, deliberate substring is `LIKE`, exact array
|
|
78
|
+
membership is `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)`.
|
|
71
79
|
To aggregate per member instead, use `json_each(frontmatter.<field>)` (above) -- GROUP BY on the
|
|
72
80
|
raw column splits `["a","b"]` and `["b","a"]` into separate buckets.
|
|
73
81
|
- Date fields are stored as written. Compare through `datetime()`, which normalizes ISO 8601
|
|
74
|
-
timezone offsets to UTC: `WHERE datetime(
|
|
82
|
+
timezone offsets to UTC: `WHERE datetime(created) >= datetime(?)`. Bare string comparison
|
|
75
83
|
is only safe when every note uses the same offset.
|
|
76
|
-
-
|
|
77
|
-
|
|
84
|
+
- To bound what a query puts into context: `snippet()` excerpts just the matching text,
|
|
85
|
+
`LIMIT` caps row counts, and selecting `path`/`title`/`summary` keeps rows small.
|
|
86
|
+
`SELECT text FROM content` returns the tree's entire prose (sense warns past 50 KB).
|
|
87
|
+
Aggregates (`COUNT`, `GROUP BY`) are already bounded. `SELECT * FROM frontmatter` is always
|
|
88
|
+
safe — prose is not a frontmatter column.
|
|
78
89
|
|
|
79
90
|
Worked traces: [EXAMPLES.md](EXAMPLES.md).
|
|
80
91
|
|
|
@@ -83,9 +94,10 @@ Worked traces: [EXAMPLES.md](EXAMPLES.md).
|
|
|
83
94
|
- Missing CLI: `npm install -g sensemaking`. Missing config: `sense init` at the tree root.
|
|
84
95
|
Discovery walks up from cwd; `--config <path>` overrides.
|
|
85
96
|
- Save a query into `sense.config.json` only when it will be reused; run ad-hoc otherwise.
|
|
86
|
-
-
|
|
87
|
-
weighted search field.
|
|
88
|
-
the only format
|
|
97
|
+
- A one-line `summary:` per note is optional and pays twice: it appears in result rows and is a
|
|
98
|
+
weighted search field. Date comparisons work for dates written as ISO 8601 (`2026-08-12`, or
|
|
99
|
+
with time and offset) — the only format `datetime()` parses. Field names in examples
|
|
100
|
+
(`status`, `tags`, `created`) are illustrative; your tree defines its own.
|
|
89
101
|
- Reserved frontmatter keys (dropped with a warning): `path`, `_mtime`, `_size`, `_rank`,
|
|
90
102
|
`content`, `links`, `sections`.
|
|
91
103
|
- Exit codes: `0` ok, `1` error (SQLite message verbatim), `2` usage (unknown query, wrong
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/vault.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the vault.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport function withVault(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void): void {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n printRows(db.prepare(sql).all(...params) as Row[], format);\n db.close();\n}\n"],"names":["printWarnings","runSql","withVault","warnings","w","console","warn","ctx","fn","cfg","resolveConfig","open","db","close","sql","params","format","label","placeholderCount","match","length","error","process","exit","printRows","prepare","all"],"mappings":";;;;;;;;;;;QASgBA;eAAAA;;QAgBAC;eAAAA;;QAZAC;eAAAA;;;oBAXK;wBAEK;;;;;;;;;;;;;;;;;;;;;;;;;;AAKnB,SAASF,cAAcG,QAAkB;QACzC,kCAAA,2BAAA;;QAAL,QAAK,YAAWA,6BAAX,SAAA,6BAAA,QAAA,yBAAA;YAAA,IAAMC,IAAN;YAAqBC,QAAQC,IAAI,CAACF;;;QAAlC;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;AACP;AAEO,SAASF,UAAUK,GAAQ,EAAEC,EAAuD;IACzF,IAAMC,MAAMF,IAAIG,aAAa;IAC7B,IAAyBC,QAAAA,IAAAA,UAAI,EAACF,MAAtBG,KAAiBD,MAAjBC,IAAIT,WAAaQ,MAAbR;IACZH,cAAcG;IACd,IAAI;QACFK,GAAGI,IAAIH;IACT,SAAU;QACRG,GAAGC,KAAK;IACV;AACF;AAGO,SAASZ,OAAOQ,GAAmB,EAAEK,GAAW,EAAEC,MAAgB,EAAEC,MAAwB,EAAEC,KAAa;QAQtGL;QAPgBE;IAA1B,IAAMI,mBAAmB,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;IACxD,IAAIL,OAAOK,MAAM,KAAKF,kBAAkB;QACtCb,QAAQgB,KAAK,CAAC,AAAC,GAAmBH,OAAjBD,OAAM,aAAiDF,OAAtCG,kBAAiB,uBAAmC,OAAdH,OAAOK,MAAM;QACrFE,QAAQC,IAAI,CAAC;IACf;IACA,IAAyBZ,QAAAA,IAAAA,UAAI,EAACF,MAAtBG,KAAiBD,MAAjBC,IAAIT,WAAaQ,MAAbR;IACZH,cAAcG;IACdqB,IAAAA,mBAAS,EAACZ,CAAAA,cAAAA,GAAGa,OAAO,CAACX,MAAKY,GAAG,OAAnBd,aAAoB,qBAAGG,UAAkBC;IACnDJ,GAAGC,KAAK;AACV"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/vault.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the vault.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport function withVault(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void): void {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n printRows(db.prepare(sql).all(...params) as Row[], format);\n db.close();\n}\n"],"names":["open","printRows","printWarnings","warnings","w","console","warn","withVault","ctx","fn","cfg","resolveConfig","db","close","runSql","sql","params","format","label","placeholderCount","match","length","error","process","exit","prepare","all"],"mappings":"AAEA,SAASA,IAAI,QAAQ,WAAW;AAEhC,SAASC,SAAS,QAAQ,eAAe;AAGzC,sEAAsE;AAEtE,OAAO,SAASC,cAAcC,QAAkB;IAC9C,KAAK,MAAMC,KAAKD,SAAUE,QAAQC,IAAI,CAACF;AACzC;AAEA,OAAO,SAASG,UAAUC,GAAQ,EAAEC,EAAuD;IACzF,MAAMC,MAAMF,IAAIG,aAAa;IAC7B,MAAM,EAAEC,EAAE,EAAET,QAAQ,EAAE,GAAGH,KAAKU;IAC9BR,cAAcC;IACd,IAAI;QACFM,GAAGG,IAAIF;IACT,SAAU;QACRE,GAAGC,KAAK;IACV;AACF;AAEA,sFAAsF;AACtF,OAAO,SAASC,OAAOJ,GAAmB,EAAEK,GAAW,EAAEC,MAAgB,EAAEC,MAAwB,EAAEC,KAAa;QACtFH;IAA1B,MAAMI,mBAAmB,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;IACxD,IAAIL,OAAOK,MAAM,KAAKF,kBAAkB;QACtCd,QAAQiB,KAAK,CAAC,GAAGJ,MAAM,SAAS,EAAEC,iBAAiB,mBAAmB,EAAEH,OAAOK,MAAM,EAAE;QACvFE,QAAQC,IAAI,CAAC;IACf;IACA,MAAM,EAAEZ,EAAE,EAAET,QAAQ,EAAE,GAAGH,KAAKU;IAC9BR,cAAcC;IACdF,UAAUW,GAAGa,OAAO,CAACV,KAAKW,GAAG,IAAIV,SAAkBC;IACnDL,GAAGC,KAAK;AACV"}
|