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.
Files changed (79) hide show
  1. package/README.md +3 -1
  2. package/dist/cjs/cli.js +1 -1
  3. package/dist/cjs/cli.js.map +1 -1
  4. package/dist/cjs/commands/find.js +2 -2
  5. package/dist/cjs/commands/find.js.map +1 -1
  6. package/dist/cjs/commands/map.js +3 -3
  7. package/dist/cjs/commands/map.js.map +1 -1
  8. package/dist/cjs/commands/named.js +2 -2
  9. package/dist/cjs/commands/named.js.map +1 -1
  10. package/dist/cjs/commands/peek.js +2 -2
  11. package/dist/cjs/commands/peek.js.map +1 -1
  12. package/dist/cjs/commands/query.js +2 -2
  13. package/dist/cjs/commands/query.js.map +1 -1
  14. package/dist/cjs/commands/rebuild.js +2 -2
  15. package/dist/cjs/commands/rebuild.js.map +1 -1
  16. package/dist/cjs/commands/{vault.d.cts → shared.d.cts} +1 -1
  17. package/dist/cjs/commands/{vault.d.ts → shared.d.ts} +1 -1
  18. package/dist/cjs/commands/{vault.js → shared.js} +3 -3
  19. package/dist/cjs/commands/shared.js.map +1 -0
  20. package/dist/cjs/commands/status.js +2 -2
  21. package/dist/cjs/commands/status.js.map +1 -1
  22. package/dist/cjs/commands/watch.js +2 -2
  23. package/dist/cjs/commands/watch.js.map +1 -1
  24. package/dist/cjs/config.js +36 -3
  25. package/dist/cjs/config.js.map +1 -1
  26. package/dist/cjs/errors.d.cts +1 -1
  27. package/dist/cjs/errors.d.ts +1 -1
  28. package/dist/cjs/errors.js.map +1 -1
  29. package/dist/cjs/features/links.js +1 -1
  30. package/dist/cjs/features/links.js.map +1 -1
  31. package/dist/cjs/index.d.cts +4 -11
  32. package/dist/cjs/index.d.ts +4 -11
  33. package/dist/cjs/index.js +5 -55
  34. package/dist/cjs/index.js.map +1 -1
  35. package/dist/cjs/verbs.d.cts +2 -2
  36. package/dist/cjs/verbs.d.ts +2 -2
  37. package/dist/cjs/verbs.js +10 -6
  38. package/dist/cjs/verbs.js.map +1 -1
  39. package/dist/esm/cli.js +1 -1
  40. package/dist/esm/cli.js.map +1 -1
  41. package/dist/esm/commands/find.js +2 -2
  42. package/dist/esm/commands/find.js.map +1 -1
  43. package/dist/esm/commands/map.js +4 -4
  44. package/dist/esm/commands/map.js.map +1 -1
  45. package/dist/esm/commands/named.js +1 -1
  46. package/dist/esm/commands/named.js.map +1 -1
  47. package/dist/esm/commands/peek.js +2 -2
  48. package/dist/esm/commands/peek.js.map +1 -1
  49. package/dist/esm/commands/query.js +1 -1
  50. package/dist/esm/commands/query.js.map +1 -1
  51. package/dist/esm/commands/rebuild.js +1 -1
  52. package/dist/esm/commands/rebuild.js.map +1 -1
  53. package/dist/esm/commands/{vault.d.ts → shared.d.ts} +1 -1
  54. package/dist/esm/commands/{vault.js → shared.js} +2 -2
  55. package/dist/esm/commands/shared.js.map +1 -0
  56. package/dist/esm/commands/status.js +1 -1
  57. package/dist/esm/commands/status.js.map +1 -1
  58. package/dist/esm/commands/watch.js +1 -1
  59. package/dist/esm/commands/watch.js.map +1 -1
  60. package/dist/esm/config.js +26 -3
  61. package/dist/esm/config.js.map +1 -1
  62. package/dist/esm/errors.d.ts +1 -1
  63. package/dist/esm/errors.js.map +1 -1
  64. package/dist/esm/features/links.js +1 -1
  65. package/dist/esm/features/links.js.map +1 -1
  66. package/dist/esm/features/types.js +1 -1
  67. package/dist/esm/features/types.js.map +1 -1
  68. package/dist/esm/index.d.ts +4 -11
  69. package/dist/esm/index.js +5 -7
  70. package/dist/esm/index.js.map +1 -1
  71. package/dist/esm/verbs.d.ts +2 -2
  72. package/dist/esm/verbs.js +8 -4
  73. package/dist/esm/verbs.js.map +1 -1
  74. package/package.json +1 -1
  75. package/schema.json +1 -1
  76. package/skills/sense/EXAMPLES.md +12 -13
  77. package/skills/sense/SKILL.md +28 -16
  78. package/dist/cjs/commands/vault.js.map +0 -1
  79. package/dist/esm/commands/vault.js.map +0 -1
@@ -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":"AAAA,SAASA,UAAU,EAAEC,YAAY,EAAEC,aAAa,QAAQ,UAAU;AAClE,SAASC,OAAO,EAAEC,IAAI,EAAEC,OAAO,QAAQ,YAAY;AACnD,SAASC,UAAU,QAAQ,cAAc;AAEzC,OAAO,MAAMC,kBAAkB,oBAAoB;AACnD,OAAO,MAAMC,YAAY,SAAS;AAElC,mGAAmG;AACnG,OAAO,MAAMC,2BAA2B,EAAE;AAE1C,0GAA0G;AAC1G,OAAO,MAAMC,gBAAgB;IAAC;IAAS;IAAY;CAAO,CAAU;AAmBpE,mGAAmG;AACnG,OAAO,SAASC,eAAeC,GAAW,EAAEC,IAAiB;QAC3CD;IAAhB,MAAME,UAAUF,EAAAA,gBAAAA,IAAIG,QAAQ,cAAZH,oCAAAA,aAAc,CAACC,KAAK,MAAK;IACzC,IAAIA,SAAS,QAAQ,OAAOC,WAAWH,eAAeC,KAAK;IAC3D,OAAOE;AACT;AAEA,OAAO,SAASE,gBAAgBJ,GAAW;IACzC,OAAOF,cAAcO,MAAM,CAAC,CAACJ,OAASF,eAAeC,KAAKC;AAC5D;AAEA,yGAAyG;AACzG,MAAMK,aAAsD;IAC1D,4HAA4H;IAC5H,GAAG,CAACN,MAAS,CAAA;YAAE,GAAGA,GAAG;YAAEO,SAAS;YAAGJ,UAAUK,OAAOC,WAAW,CAACX,cAAcY,GAAG,CAAC,CAACT,OAAS;oBAACA;oBAAM;iBAAK;QAAyB,CAAA;AACnI;AAEA,OAAO,SAASU,cAAcX,GAAW;QAC1BA;IAAb,MAAMY,QAAOZ,eAAAA,IAAIO,OAAO,cAAXP,0BAAAA,eAAe;IAC5B,IAAIa,UAAUb;IACd,IAAK,IAAIc,IAAIF,MAAME,IAAIjB,0BAA0BiB,IAAK;QACpD,MAAMC,OAAOT,UAAU,CAACQ,EAAE;QAC1B,IAAI,CAACC,MAAM,MAAM,IAAIrB,WAAW,8BAA8B,CAAC,iCAAiC,EAAEoB,GAAG;QACrGD,UAAUE,KAAKF;IACjB;IACA,OAAO;QAAEb,KAAKa;QAASD;IAAK;AAC9B;AAEA,SAASI;IACP,OAAO;QACLC,SAAS;QACTV,SAASV;QACTqB,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7BhB,UAAUK,OAAOC,WAAW,CAACX,cAAcY,GAAG,CAAC,CAACT,OAAS;gBAACA;gBAAM;aAAK;QACrEmB,SAAS,CAAC;IACZ;AACF;AAEA,2CAA2C;AAC3C,OAAO,SAASC,WAAWC,GAAW;IACpC,MAAMC,aAAa/B,KAAK8B,KAAK3B;IAC7B,IAAIP,WAAWmC,aAAa;QAC1B,MAAM,IAAI7B,WAAW,iBAAiB,GAAGC,gBAAgB,mBAAmB,EAAE2B,KAAK;IACrF;IACAhC,cAAciC,YAAY,GAAGC,KAAKC,SAAS,CAACT,iBAAiB,MAAM,GAAG,EAAE,CAAC;IACzE,OAAOO;AACT;AAEA,OAAO,SAASG,eAAeC,QAAgB;IAC7C,IAAIL,MAAM7B,QAAQkC;IAClB,OAAS;QACP,MAAMC,YAAYpC,KAAK8B,KAAK3B;QAC5B,IAAIP,WAAWwC,YAAY,OAAOA;QAClC,MAAMC,SAAStC,QAAQ+B;QACvB,IAAIO,WAAWP,KAAK,OAAO;QAC3BA,MAAMO;IACR;AACF;AAEA,OAAO,SAASC,WAAWC,YAAqB;QAgB9B/B;IAfhB,IAAIuB;IACJ,IAAIQ,cAAc;QAChBR,aAAa9B,QAAQuC,QAAQC,GAAG,IAAIF;QACpC,IAAI,CAAC3C,WAAWmC,aAAa,MAAM,IAAI7B,WAAW,oBAAoB,CAAC,kBAAkB,EAAE6B,YAAY;IACzG,OAAO;QACL,MAAMW,QAAQR,eAAeM,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAIxC,WAAW,oBAAoB,CAAC,eAAe,EAAEC,gBAAgB,IAAI,EAAEqC,QAAQC,GAAG,GAAG,wBAAwB,CAAC;QAC1H;QACAV,aAAaW;IACf;IAEA,MAAMC,MAAM9C,aAAakC,YAAY;IACrC,IAAIvB,MAAMwB,KAAKY,KAAK,CAACD;IAErB,MAAM5B,WAAUP,eAAAA,IAAIO,OAAO,cAAXP,0BAAAA,eAAe;IAC/B,IAAIO,UAAUV,0BAA0B;QACtC,MAAM,IAAIH,WAAW,8BAA8B,CAAC,eAAe,EAAEa,QAAQ,uBAAuB,CAAC;IACvG;IAEA,IAAI8B;IACJ,IAAI9B,UAAUV,0BAA0B;QACtC,MAAMyC,SAAS3B,cAAcX;QAC7BA,MAAMsC,OAAOtC,GAAG;QAChBqC,eAAeC,OAAO1B,IAAI;QAC1BtB,cAAciC,YAAY,GAAGC,KAAKC,SAAS,CAACzB,KAAK,MAAM,GAAG,EAAE,CAAC;IAC/D;IAEA,OAAO;QAAE,GAAGA,GAAG;QAAEuC,SAAShD,QAAQgC;QAAaA;QAAYc;IAAa;AAC1E"}
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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"}
@@ -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);
@@ -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 vault changes. An in-memory probe per row; writes only on change.
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 vault 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":"AAAA,OAAOA,WAAW,kBAAkB;AAKpC,qGAAqG;AAErG,qGAAqG;AACrG,SAASC,QAAQC,IAAY,EAAEC,IAAY;IACzC,MAAMC,UAAU,IAAIC;IACpB,KAAK,MAAMC,KAAKH,KAAKI,QAAQ,CAAC,iDAAkD;QAC9E,MAAMC,SAASF,CAAC,CAAC,EAAE,CAACG,IAAI;QACxB,IAAID,QAAQJ,QAAQM,GAAG,CAACF;IAC1B;IACA,KAAK,MAAMF,KAAKH,KAAKI,QAAQ,CAAC,iCAAkC;QAC9D,MAAMC,SAASF,CAAC,CAAC,EAAE,CAACG,IAAI;QACxB,IAAID,UAAU,CAAC,gBAAgBG,IAAI,CAACH,SAASJ,QAAQM,GAAG,CAACF;IAC3D;IACA,OAAO;WAAIJ;KAAQ;AACrB;AAEA,SAASQ,QAAQC,IAAY;IAC3B,OAAOb,MAAMc,QAAQ,CAACD,MAAME,OAAO,CAAC,UAAU,IAAIC,WAAW;AAC/D;AAEA,uFAAuF;AACvF,2EAA2E;AAC3E,SAASC,cAAcC,GAAW,EAAEV,MAAc,EAAEW,OAAoB,EAAEC,MAA6B;;QAM9FA;IALP,MAAMC,QAAQb,OAAOO,OAAO,CAAC,OAAO,KAAKA,OAAO,CAAC,SAAS;IAC1D,MAAMO,UAAUtB,MAAMuB,SAAS,CAACvB,MAAMwB,IAAI,CAACxB,MAAMyB,OAAO,CAACP,MAAMG;IAC/D,KAAK,MAAMK,aAAa;QAACL;QAAO,GAAGA,MAAM,GAAG,CAAC;QAAEC;QAAS,GAAGA,QAAQ,GAAG,CAAC;KAAC,CAAE;QACxE,IAAIH,QAAQQ,GAAG,CAACD,YAAY,OAAOA;IACrC;IACA,gBAAON,cAAAA,OAAOQ,GAAG,CAAChB,QAAQS,qBAAnBD,kCAAAA,WAA4B,CAAC,EAAE,uCAAI;AAC5C;AAEA,wFAAwF;AACxF,iFAAiF;AACjF,SAASS,WAAWC,EAAgB,EAAEC,KAAiB;IACrD,MAAMZ,UAAU,IAAId,IAAI0B,MAAMC,GAAG,CAAC,CAACC,IAAMA,EAAEC,OAAO;IAClD,MAAMd,SAAS,IAAIe;IACnB,KAAK,MAAMtB,QAAQ;WAAIM;KAAQ,CAACiB,IAAI,GAAI;QACtC,MAAMC,OAAOjB,OAAOQ,GAAG,CAAChB,QAAQC;QAChC,IAAIwB,MAAMA,KAAKC,IAAI,CAACzB;aACfO,OAAOmB,GAAG,CAAC3B,QAAQC,OAAO;YAACA;SAAK;IACvC;IACA,MAAM2B,OAAOV,GAAGW,OAAO,CAAC,sCAAsCC,GAAG;IACjE,MAAMC,SAASb,GAAGW,OAAO,CAAC;IAC1B,KAAK,MAAMG,OAAOJ,KAAM;QACtB,MAAMK,MAAM5B,cAAc2B,IAAI1B,GAAG,EAAE0B,IAAIpC,MAAM,EAAEW,SAASC;QACxD,IAAIyB,QAAQD,IAAIC,GAAG,EAAEF,OAAOG,GAAG,CAACD,KAAKD,IAAI1B,GAAG,EAAE0B,IAAIpC,MAAM;IAC1D;AACF;AAEA,2DAA2D;AAC3D,OAAO,SAASuC,UAAUjB,EAAgB;IACxC,OAAO,AAACA,GAAGW,OAAO,CAAC,oDAAoDC,GAAG,GAA2CV,GAAG,CAAC,CAACgB,IAAM;YAACA,EAAE9B,GAAG;YAAE8B,EAAEH,GAAG;SAAC;AAChJ;AAEA,OAAO,MAAMI,QAAiB;IAC5BC,MAAM;IACNC,QAAOrB,EAAE;QACPA,GAAGsB,IAAI,CAAC;QACRtB,GAAGsB,IAAI,CAAC;IACV;IACAnD;IACAoD,QAAOvB,EAAE,EAAEjB,IAAI;QACbiB,GAAGW,OAAO,CAAC,mCAAmCK,GAAG,CAACjC;IACpD;IACAyC,OAAMxB,EAAE,EAAEjB,IAAI,EAAE0C,SAAS;QACvB,MAAMC,SAAS1B,GAAGW,OAAO,CAAC;QAC1B,KAAK,MAAMjC,UAAU+C,UAAuBC,OAAOV,GAAG,CAACjC,MAAML;IAC/D;IACAiD,gBAAgB5B;AAClB,EAAE"}
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,3 +1,3 @@
1
- // A feature owns its schema, per-file extraction/storage, and any whole-vault pass.
1
+ // A feature owns its schema, per-file extraction/storage, and any whole-tree pass.
2
2
  // Adding a feature = one module here + one entry in the registry (index.ts).
3
3
  export { };
@@ -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-vault 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,oFAAoF;AACpF,6EAA6E;AAC7E,WASC"}
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"}
@@ -1,19 +1,12 @@
1
1
  export type { Config, FeatureName, ResolvedConfig } from './config.js';
2
- export { CONFIG_FILENAME, enabledFeatures, FEATURE_NAMES, featureEnabled, findConfigPath, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
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 { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.js';
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 { FileStat, ParsedDoc } from './scan.js';
15
- export { listFiles, parseFile } from './scan.js';
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
- export { CONFIG_FILENAME, enabledFeatures, FEATURE_NAMES, featureEnabled, findConfigPath, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
3
- export { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.js';
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 { listFiles, parseFile } from './scan.js';
9
- export { find, peek, vaultMap } from './verbs.js';
7
+ export { find, mapTree, peek } from './verbs.js';
10
8
  export { runWatch } from './watch.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, enabledFeatures, FEATURE_NAMES, featureEnabled, findConfigPath, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport { activeFeatures, FEATURES, linkEdges } from './features/index.ts';\nexport type { Section } from './features/sections.ts';\nexport type { Feature } from './features/types.ts';\n\nexport type { Edge } from './graph.ts';\nexport { pagerank, personalizedRank } from './graph.ts';\n\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { FileStat, ParsedDoc } from './scan.ts';\nexport { listFiles, parseFile } from './scan.ts';\n\nexport type { FindOptions, Peek, VaultMap } from './verbs.ts';\nexport { find, peek, vaultMap } from './verbs.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","enabledFeatures","FEATURE_NAMES","featureEnabled","findConfigPath","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","DB_FILENAME","docCount","getMeta","open","rebuild","reconcile","setMeta","SenseError","activeFeatures","FEATURES","linkEdges","pagerank","personalizedRank","printRows","listFiles","parseFile","find","peek","vaultMap","runWatch"],"mappings":"AAAA,sBAAsB;AAGtB,SAASA,eAAe,EAAEC,eAAe,EAAEC,aAAa,EAAEC,cAAc,EAAEC,cAAc,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAG1L,SAASC,WAAW,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,EAAEC,OAAO,EAAEC,SAAS,EAAEC,OAAO,QAAQ,UAAU;AAE5F,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,cAAc,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,sBAAsB;AAK1E,SAASC,QAAQ,EAAEC,gBAAgB,QAAQ,aAAa;AAGxD,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,SAAS,EAAEC,SAAS,QAAQ,YAAY;AAGjD,SAASC,IAAI,EAAEC,IAAI,EAAEC,QAAQ,QAAQ,aAAa;AAGlD,SAASC,QAAQ,QAAQ,aAAa"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { 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"}
@@ -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 VaultMap {
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 vaultMap(db: DatabaseSync, cfg: Config): VaultMap;
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: vaultMap (orient), find (locate), peek (structure).
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
- const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content WHERE content MATCH ? ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
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 vault. Fixed-size output regardless of vault size.
69
- export function vaultMap(db, cfg) {
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)=>{
@@ -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":"AAAA,OAAOA,WAAW,kBAAkB;AAGpC,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,SAAS,QAAQ,sBAAsB;AAChD,SAASC,gBAAgB,QAAQ,aAAa;AAG9C,4EAA4E;AAC5E,gFAAgF;AAEhF,MAAMC,gBAAgB;AACtB,MAAMC,QAAQ;AAOd,mFAAmF;AACnF,4EAA4E;AAC5E,OAAO,SAASC,KAAKC,EAAgB,EAAEC,GAAW,EAAEC,KAAa,EAAEC,OAAoB,CAAC,CAAC;QAC7EA,SAuC2DC;IAvCrE,MAAMC,KAAIF,UAAAA,KAAKE,CAAC,cAANF,qBAAAA,UAAU;IACpB,MAAMG,QAAQC,KAAKC,GAAG,CAACH,IAAI,GAAG;IAE9B,sFAAsF;IACtF,yFAAyF;IACzF,sFAAsF;IACtF,qFAAqF;IACrF,MAAMI,YAAYN,KAAKO,KAAK,GAAG,CAAC,6CAA6C,CAAC,GAAG;IACjF,MAAMC,YAAYR,KAAKO,KAAK,GAAG,CAAC,KAAK,EAAEP,KAAKO,KAAK,CAAC,CAAC,CAAC,GAAG;IACvD,MAAME,WAAW,CAAC,yFAAyF,EAAEH,UAAU,uBAAuB,EAAEE,UAAU,UAAU,EAAEd,cAAc,OAAO,EAAES,OAAO;IACpM,MAAMO,YAAYb,GAAGc,OAAO,CAACF,UAAUG,GAAG,CAACb;IAE3C,MAAME,OAAO,IAAIY,IAAIH,UAAUI,GAAG,CAAC,CAACC,IAAM;YAACA,EAAEC,IAAI;YAAED,EAAEE,GAAG;SAAC;IACzD,MAAMC,aAAa,IAAIL;IACvBH,UAAUS,OAAO,CAAC,CAACJ,GAAGK;QACpBF,WAAWG,GAAG,CAACN,EAAEC,IAAI,EAAE;YAAEM,OAAO,IAAK3B,CAAAA,QAAQyB,CAAAA;YAAIG,KAAK;QAAQ;IAChE;IAEA,IAAIjC,eAAeQ,KAAK,YAAYY,UAAUc,MAAM,GAAG,GAAG;QACxD,MAAMC,QAAQ,AAAC5B,GAAGc,OAAO,CAAC,kCAAkCC,GAAG,GAA+BE,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;QAC/G,MAAMU,QAAQ,IAAIb,IAAIH,UAAUI,GAAG,CAAC,CAACC,GAAGK,IAAM;gBAACL,EAAEC,IAAI;gBAAE,IAAKI,CAAAA,IAAI,CAAA;aAAG;QACnE,MAAMO,SAAS;eAAIlC,iBAAiBgC,OAAOjC,UAAUK,KAAK6B;SAAO,CAC9DE,MAAM,CAAC,CAAC,GAAGN,MAAM,GAAKA,QAAQ,MAC9BO,IAAI,CAAC,CAACC,GAAGC,IAAMA,CAAC,CAAC,EAAE,GAAGD,CAAC,CAAC,EAAE,EAC1BE,KAAK,CAAC,GAAG7B;QACZwB,OAAOR,OAAO,CAAC,CAAC,CAACH,KAAK,EAAEI;YACtB,MAAMa,WAAWf,WAAWgB,GAAG,CAAClB;YAChC,IAAIiB,UAAU;gBACZA,SAASX,KAAK,IAAI,IAAK3B,CAAAA,QAAQyB,CAAAA;gBAC/Ba,SAASV,GAAG,GAAG;YACjB,OAAO;gBACLL,WAAWG,GAAG,CAACL,MAAM;oBAAEM,OAAO,IAAK3B,CAAAA,QAAQyB,CAAAA;oBAAIG,KAAK;gBAAO;YAC7D;QACF;IACF;IAEA1B,GAAGsC,IAAI,CAAC;IACRtC,GAAGsC,IAAI,CAAC;IACR,MAAMC,SAASvC,GAAGc,OAAO,CAAC;IAC1B,KAAK,MAAM,CAACK,MAAMqB,EAAE,IAAInB,WAAYkB,OAAOE,GAAG,CAACtB,MAAMqB,EAAEf,KAAK,EAAEe,EAAEd,GAAG,GAAEtB,YAAAA,KAAKiC,GAAG,CAAClB,mBAATf,uBAAAA,YAAkB;IAEvF,MAAMM,QAAQP,KAAKO,KAAK,GAAG,CAAC,MAAM,EAAEP,KAAKO,KAAK,EAAE,GAAG;IACnD,OAAOV,GACJc,OAAO,CACN,CAAC;;OAEA,EAAEJ,MAAM,kCAAkC,CAAC,EAE7CK,GAAG,CAACV;AACT;AAUA,MAAMqC,mBAAmB,IAAIC,IAAI;IAAC;IAAQ;IAAU;IAAS;CAAQ;AAErE,yEAAyE;AACzE,OAAO,SAASC,QAAQ5C,EAAgB,EAAEC,GAAW;IACnD,MAAM4C,OAAO7C,GAAGc,OAAO,CAAC,iFAAiFuB,GAAG;IAE5G,MAAMS,UAAU,AAAC9C,GAAGc,OAAO,CAAC,kCAAkCC,GAAG,GAA+BE,GAAG,CAAC,CAACC,IAAMA,EAAE6B,IAAI,EAAEhB,MAAM,CAAC,CAACgB,OAAS,CAACL,iBAAiBM,GAAG,CAACD;IAC1J,MAAME,YAAYH,QACf7B,GAAG,CAAC,CAAC8B;QACJ,MAAM,EAAEG,CAAC,EAAE,GAAGlD,GAAGc,OAAO,CAAC,CAAC,cAAc,EAAEiC,KAAKI,KAAK,CAAC,KAAKC,IAAI,CAAC,MAAM,wBAAwB,CAAC,EAAEf,GAAG;QACnG,OAAO;YAAEgB,OAAON;YAAMO,UAAUJ;QAAE;IACpC,GACClB,IAAI,CAAC,CAACC,GAAGC,IAAM,AAACA,EAAEoB,QAAQ,GAAerB,EAAEqB,QAAQ;IACtD,MAAMC,SAASN,UAAUd,KAAK,CAAC,GAAG;IAElC,MAAMqB,OAAO/D,eAAeQ,KAAK,UAAWD,GAAGc,OAAO,CAAC,CAAC,gMAAgM,CAAC,EAAEC,GAAG,KAAe,EAAE;IAE/Q,MAAM0C,SAASzD,GAAGc,OAAO,CAAC,CAAC,iHAAiH,CAAC,EAAEC,GAAG;IAElJ,OAAO;QAAE8B;QAAMU;QAAQG,aAAaT,UAAUtB,MAAM;QAAE6B;QAAMC;IAAO;AACrE;AAiBA,MAAME,kBAAkB;AAExB,wFAAwF;AACxF,8FAA8F;AAC9F,OAAO,SAASC,KAAK5D,EAAgB,EAAEC,GAAW,EAAE4D,OAAe;QAiC3CC;IAhCtB,MAAMC,QAAQ,AAAC/D,GAAGc,OAAO,CAAC,kCAAkCC,GAAG,GAA+BE,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAC/G,IAAIA,OAAO4C,MAAMhE,IAAI,CAAC,CAACiE,IAAMA,MAAMH;IACnC,IAAI,CAAC1C,MAAM;QACT,MAAM8C,OAAOzE,MAAM0E,QAAQ,CAACL,SAASM,OAAO,CAAC,UAAU,IAAIC,WAAW;QACtE,MAAMC,UAAUN,MAAMhC,MAAM,CAAC,CAACiC,IAAMxE,MAAM0E,QAAQ,CAACF,GAAGG,OAAO,CAAC,UAAU,IAAIC,WAAW,OAAOH;QAC9F,IAAII,QAAQ1C,MAAM,KAAK,GAAGR,OAAOkD,OAAO,CAAC,EAAE;aACtC,IAAIA,QAAQ1C,MAAM,GAAG,GAAG,MAAM,IAAIjC,WAAW,kBAAkB,CAAC,CAAC,EAAEmE,QAAQ,gBAAgB,EAAEQ,QAAQjB,IAAI,CAAC,OAAO;aACjH,MAAM,IAAI1D,WAAW,kBAAkB,CAAC,iBAAiB,EAAEmE,QAAQ,CAAC,CAAC;IAC5E;IAEA,MAAMC,MAAM9D,GAAGc,OAAO,CAAC,8CAA8CuB,GAAG,CAAClB;IACzE,MAAMmD,cAAmB,CAAC;IAC1B,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACZ,KAAM;QAC9C,IAAI,CAACpB,iBAAiBM,GAAG,CAACuB,QAAQC,UAAU,MAAMF,WAAW,CAACC,IAAI,GAAGC;IACvE;IAEA,MAAMG,WAAWlF,eAAeQ,KAAK,cAAeD,GAAGc,OAAO,CAAC,mGAAmGC,GAAG,CAACI,QAAkB,EAAE;IAE1L,IAAIyD,WAAqB,EAAE;IAC3B,IAAIC,YAAsB,EAAE;IAC5B,IAAIC,aAAuB,EAAE;IAC7B,IAAIC,iBAAiB;IACrB,IAAItF,eAAeQ,KAAK,UAAU;QAChC,MAAM+E,MAAMhF,GAAGc,OAAO,CAAC,+DAA+DC,GAAG,CAACI;QAC1FyD,WAAW;eAAI,IAAIjC,IAAIqC,IAAIjD,MAAM,CAAC,CAACkD,IAAMA,EAAEC,GAAG,KAAK,MAAMjE,GAAG,CAAC,CAACgE,IAAMA,EAAEC,GAAG;SAAa;QACtFJ,aAAaE,IAAIjD,MAAM,CAAC,CAACkD,IAAMA,EAAEC,GAAG,KAAK,MAAMjE,GAAG,CAAC,CAACgE,IAAMA,EAAEE,MAAM;QAClEJ,iBAAiB,AAAC/E,GAAGc,OAAO,CAAC,4DAA4DuB,GAAG,CAAClB,MAAwB+B,CAAC;QACtH2B,YAAY,AAAC7E,GAAGc,OAAO,CAAC,qEAAqEC,GAAG,CAACI,MAAMwC,iBAA4C1C,GAAG,CAAC,CAACC,IAAMA,EAAEkE,GAAG;IACrK;IAEA,OAAO;QACLjE;QACAkE,QAAQ9E,KAAK+E,IAAI,CAAC,EAAExB,aAAAA,IAAIyB,KAAK,cAATzB,wBAAAA,aAAwB,KAAK;QACjDQ;QACAK;QACAC,UAAUA,SAASzC,KAAK,CAAC,GAAGwB;QAC5BkB;QACAC,YAAYA,WAAW3C,KAAK,CAAC,GAAGwB;QAChC6B,eAAeZ,SAASjD,MAAM;QAC9BoD;QACAU,iBAAiBX,WAAWnD,MAAM;IACpC;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sensemaking",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Query a knowledge base you build with an agent: filter notes by frontmatter, then search inside them",
5
5
  "keywords": [
6
6
  "markdown",
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
  }
@@ -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. "Does the vault say anything about X?"
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, usually a signal
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 vault
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
- ## Anti-patterns
78
+ ## Consequences
80
79
 
81
- | don't | because | instead |
80
+ | query | what happens | bounded alternative |
82
81
  |---|---|---|
83
- | `SELECT text FROM content` | dumps the vault into context | `snippet(content, -1, '«', '»', '…', 10)` |
84
- | `sense find "pricing"` | one word misses synonyms | `"pricing OR billing OR invoicing"` |
85
- | `sense find "pricing model details"` | bare words AND-join in FTS5; one absent word = zero rows | OR the words, or quote an exact phrase |
86
- | `sense find "customer-facing OR on-site"` | bare punctuation is FTS5 syntax (`-` = column filter) | double-quote the terms: `"customer-facing" OR "on-site"` |
87
- | `Read` a 4,000-token file for one section | 10× the tokens needed | `peek` first, `Read` the line range |
88
- | queries without `LIMIT` | unbounded output | `LIMIT 10`, widen only if all rows look wrong |
89
- | save every query to config | config churn for one-offs | ad-hoc `sense query`; save only reusable views |
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 |
@@ -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 line range peek gave you not the whole file.
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. Prefer `--format json` when consuming output.
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; invalid syntax is an error, not a
44
- rewrite.
45
- - **Over-fetch, then choose.** `--k 20`, read the rows, open the 2–3 that matter.
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 vaults that never declare them).
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(dateCreated) >= datetime(?)`. Bare string comparison
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
- - Never `SELECT text FROM content` that is the whole vault into context. `SELECT * FROM
77
- frontmatter` is safe; prose is not a frontmatter column. Always `LIMIT`.
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
- - When writing notes, give each a one-line `summary:` it appears in every result row and is a
87
- weighted search field. Write date fields as ISO 8601 (`2026-08-12`, or with time and offset) —
88
- the only format SQL date comparisons understand.
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"}