sensemaking 0.12.2 → 0.13.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 (245) hide show
  1. package/dist/cjs/cli/download.js +4 -4
  2. package/dist/cjs/cli/download.js.map +1 -1
  3. package/dist/cjs/cli/init.js +4 -4
  4. package/dist/cjs/cli/init.js.map +1 -1
  5. package/dist/cjs/cli/map.js +4 -4
  6. package/dist/cjs/cli/map.js.map +1 -1
  7. package/dist/cjs/cli/named.js +2 -2
  8. package/dist/cjs/cli/named.js.map +1 -1
  9. package/dist/cjs/cli/path.js +6 -6
  10. package/dist/cjs/cli/path.js.map +1 -1
  11. package/dist/cjs/cli/peek.js +4 -4
  12. package/dist/cjs/cli/peek.js.map +1 -1
  13. package/dist/cjs/cli/related.js +4 -4
  14. package/dist/cjs/cli/related.js.map +1 -1
  15. package/dist/cjs/cli/search.js +4 -4
  16. package/dist/cjs/cli/search.js.map +1 -1
  17. package/dist/cjs/cli/shared.d.cts +2 -2
  18. package/dist/cjs/cli/shared.d.ts +2 -2
  19. package/dist/cjs/cli/shared.js +5 -5
  20. package/dist/cjs/cli/shared.js.map +1 -1
  21. package/dist/cjs/cli/status.js +16 -16
  22. package/dist/cjs/cli/status.js.map +1 -1
  23. package/dist/cjs/cli/types.d.cts +1 -1
  24. package/dist/cjs/cli/types.d.ts +1 -1
  25. package/dist/cjs/cli.js +3 -3
  26. package/dist/cjs/cli.js.map +1 -1
  27. package/dist/cjs/commands/index.d.cts +10 -0
  28. package/dist/cjs/commands/index.d.ts +10 -0
  29. package/dist/cjs/commands/index.js +42 -0
  30. package/dist/cjs/commands/index.js.map +1 -0
  31. package/dist/cjs/commands/map.d.cts +21 -0
  32. package/dist/cjs/commands/map.d.ts +21 -0
  33. package/dist/cjs/commands/map.js +90 -0
  34. package/dist/cjs/commands/map.js.map +1 -0
  35. package/dist/cjs/commands/peek.d.cts +20 -0
  36. package/dist/cjs/commands/peek.d.ts +20 -0
  37. package/dist/cjs/commands/peek.js +175 -0
  38. package/dist/cjs/commands/peek.js.map +1 -0
  39. package/dist/cjs/commands/related.d.cts +6 -0
  40. package/dist/cjs/commands/related.d.ts +6 -0
  41. package/dist/cjs/commands/related.js +232 -0
  42. package/dist/cjs/commands/related.js.map +1 -0
  43. package/dist/cjs/commands/scope.d.cts +10 -0
  44. package/dist/cjs/commands/scope.d.ts +10 -0
  45. package/dist/cjs/commands/scope.js +124 -0
  46. package/dist/cjs/commands/scope.js.map +1 -0
  47. package/dist/cjs/commands/search.d.cts +12 -0
  48. package/dist/cjs/commands/search.d.ts +12 -0
  49. package/dist/cjs/{commands.js → commands/search.js} +20 -358
  50. package/dist/cjs/commands/search.js.map +1 -0
  51. package/dist/cjs/commands/status.d.cts +9 -0
  52. package/dist/cjs/commands/status.d.ts +9 -0
  53. package/dist/cjs/commands/status.js +25 -0
  54. package/dist/cjs/commands/status.js.map +1 -0
  55. package/dist/cjs/config/access.d.cts +19 -0
  56. package/dist/cjs/config/access.d.ts +19 -0
  57. package/dist/cjs/config/access.js +149 -0
  58. package/dist/cjs/config/access.js.map +1 -0
  59. package/dist/cjs/config/index.d.cts +5 -0
  60. package/dist/cjs/config/index.d.ts +5 -0
  61. package/dist/cjs/config/index.js +77 -0
  62. package/dist/cjs/config/index.js.map +1 -0
  63. package/dist/cjs/config/load.d.cts +8 -0
  64. package/dist/cjs/config/load.d.ts +8 -0
  65. package/dist/cjs/config/load.js +448 -0
  66. package/dist/cjs/config/load.js.map +1 -0
  67. package/dist/cjs/config/resolve.d.cts +6 -0
  68. package/dist/cjs/config/resolve.d.ts +6 -0
  69. package/dist/cjs/config/resolve.js +50 -0
  70. package/dist/cjs/config/resolve.js.map +1 -0
  71. package/dist/{esm/config.d.ts → cjs/config/types.d.cts} +2 -32
  72. package/dist/cjs/{config.d.cts → config/types.d.ts} +2 -32
  73. package/dist/cjs/config/types.js +39 -0
  74. package/dist/cjs/config/types.js.map +1 -0
  75. package/dist/cjs/config/validate.d.cts +4 -0
  76. package/dist/cjs/config/validate.d.ts +4 -0
  77. package/dist/cjs/config/validate.js +363 -0
  78. package/dist/cjs/config/validate.js.map +1 -0
  79. package/dist/cjs/db/index.d.cts +4 -0
  80. package/dist/cjs/db/index.d.ts +4 -0
  81. package/dist/cjs/db/index.js +42 -0
  82. package/dist/cjs/db/index.js.map +1 -0
  83. package/dist/cjs/{db.d.cts → db/open.d.cts} +2 -8
  84. package/dist/cjs/{db.d.ts → db/open.d.ts} +2 -8
  85. package/dist/cjs/db/open.js +223 -0
  86. package/dist/cjs/db/open.js.map +1 -0
  87. package/dist/cjs/db/reconcile.d.cts +9 -0
  88. package/dist/cjs/db/reconcile.d.ts +9 -0
  89. package/dist/cjs/{db.js → db/reconcile.js} +24 -261
  90. package/dist/cjs/db/reconcile.js.map +1 -0
  91. package/dist/cjs/db/shared.d.cts +5 -0
  92. package/dist/cjs/db/shared.d.ts +5 -0
  93. package/dist/cjs/db/shared.js +45 -0
  94. package/dist/cjs/db/shared.js.map +1 -0
  95. package/dist/cjs/features/embed.d.cts +1 -1
  96. package/dist/cjs/features/embed.d.ts +1 -1
  97. package/dist/cjs/features/embed.js +4 -3
  98. package/dist/cjs/features/embed.js.map +1 -1
  99. package/dist/cjs/features/index.d.cts +1 -1
  100. package/dist/cjs/features/index.d.ts +1 -1
  101. package/dist/cjs/features/index.js +4 -2
  102. package/dist/cjs/features/index.js.map +1 -1
  103. package/dist/cjs/features/links.js +38 -20
  104. package/dist/cjs/features/links.js.map +1 -1
  105. package/dist/cjs/features/tags.d.cts +2 -0
  106. package/dist/cjs/features/tags.d.ts +2 -0
  107. package/dist/cjs/features/tags.js +188 -0
  108. package/dist/cjs/features/tags.js.map +1 -0
  109. package/dist/cjs/features/types.d.cts +2 -2
  110. package/dist/cjs/features/types.d.ts +2 -2
  111. package/dist/cjs/index.d.cts +6 -6
  112. package/dist/cjs/index.d.ts +6 -6
  113. package/dist/cjs/index.js +15 -15
  114. package/dist/cjs/index.js.map +1 -1
  115. package/dist/cjs/scan.d.cts +3 -1
  116. package/dist/cjs/scan.d.ts +3 -1
  117. package/dist/cjs/scan.js +9 -6
  118. package/dist/cjs/scan.js.map +1 -1
  119. package/dist/cjs/sql-functions.d.cts +2 -0
  120. package/dist/cjs/sql-functions.d.ts +2 -0
  121. package/dist/cjs/sql-functions.js +64 -0
  122. package/dist/cjs/sql-functions.js.map +1 -0
  123. package/dist/cjs/watch.d.cts +1 -1
  124. package/dist/cjs/watch.d.ts +1 -1
  125. package/dist/cjs/watch.js +12 -12
  126. package/dist/cjs/watch.js.map +1 -1
  127. package/dist/esm/cli/download.js +1 -1
  128. package/dist/esm/cli/download.js.map +1 -1
  129. package/dist/esm/cli/init.js +1 -1
  130. package/dist/esm/cli/init.js.map +1 -1
  131. package/dist/esm/cli/map.js +1 -1
  132. package/dist/esm/cli/map.js.map +1 -1
  133. package/dist/esm/cli/named.js +1 -1
  134. package/dist/esm/cli/named.js.map +1 -1
  135. package/dist/esm/cli/path.js +1 -1
  136. package/dist/esm/cli/path.js.map +1 -1
  137. package/dist/esm/cli/peek.js +1 -1
  138. package/dist/esm/cli/peek.js.map +1 -1
  139. package/dist/esm/cli/related.js +1 -1
  140. package/dist/esm/cli/related.js.map +1 -1
  141. package/dist/esm/cli/search.js +1 -1
  142. package/dist/esm/cli/search.js.map +1 -1
  143. package/dist/esm/cli/shared.d.ts +2 -2
  144. package/dist/esm/cli/shared.js +2 -2
  145. package/dist/esm/cli/shared.js.map +1 -1
  146. package/dist/esm/cli/status.js +3 -3
  147. package/dist/esm/cli/status.js.map +1 -1
  148. package/dist/esm/cli/types.d.ts +1 -1
  149. package/dist/esm/cli/types.js.map +1 -1
  150. package/dist/esm/cli.js +1 -1
  151. package/dist/esm/cli.js.map +1 -1
  152. package/dist/esm/commands/index.d.ts +10 -0
  153. package/dist/esm/commands/index.js +8 -0
  154. package/dist/esm/commands/index.js.map +1 -0
  155. package/dist/esm/commands/map.d.ts +21 -0
  156. package/dist/esm/commands/map.js +56 -0
  157. package/dist/esm/commands/map.js.map +1 -0
  158. package/dist/esm/commands/peek.d.ts +20 -0
  159. package/dist/esm/commands/peek.js +63 -0
  160. package/dist/esm/commands/peek.js.map +1 -0
  161. package/dist/esm/commands/related.d.ts +6 -0
  162. package/dist/esm/commands/related.js +47 -0
  163. package/dist/esm/commands/related.js.map +1 -0
  164. package/dist/esm/commands/scope.d.ts +10 -0
  165. package/dist/esm/commands/scope.js +60 -0
  166. package/dist/esm/commands/scope.js.map +1 -0
  167. package/dist/esm/commands/search.d.ts +12 -0
  168. package/dist/esm/commands/search.js +258 -0
  169. package/dist/esm/commands/search.js.map +1 -0
  170. package/dist/esm/commands/status.d.ts +9 -0
  171. package/dist/esm/commands/status.js +16 -0
  172. package/dist/esm/commands/status.js.map +1 -0
  173. package/dist/esm/config/access.d.ts +19 -0
  174. package/dist/esm/config/access.js +89 -0
  175. package/dist/esm/config/access.js.map +1 -0
  176. package/dist/esm/config/index.d.ts +5 -0
  177. package/dist/esm/config/index.js +4 -0
  178. package/dist/esm/config/index.js.map +1 -0
  179. package/dist/esm/config/load.d.ts +8 -0
  180. package/dist/esm/config/load.js +242 -0
  181. package/dist/esm/config/load.js.map +1 -0
  182. package/dist/esm/config/resolve.d.ts +6 -0
  183. package/dist/esm/config/resolve.js +35 -0
  184. package/dist/esm/config/resolve.js.map +1 -0
  185. package/dist/{cjs/config.d.ts → esm/config/types.d.ts} +2 -32
  186. package/dist/esm/config/types.js +15 -0
  187. package/dist/esm/config/types.js.map +1 -0
  188. package/dist/esm/config/validate.d.ts +4 -0
  189. package/dist/esm/config/validate.js +226 -0
  190. package/dist/esm/config/validate.js.map +1 -0
  191. package/dist/esm/db/index.d.ts +4 -0
  192. package/dist/esm/db/index.js +5 -0
  193. package/dist/esm/db/index.js.map +1 -0
  194. package/dist/esm/{db.d.ts → db/open.d.ts} +2 -8
  195. package/dist/esm/db/open.js +160 -0
  196. package/dist/esm/db/open.js.map +1 -0
  197. package/dist/esm/db/reconcile.d.ts +9 -0
  198. package/dist/esm/{db.js → db/reconcile.js} +12 -216
  199. package/dist/esm/db/reconcile.js.map +1 -0
  200. package/dist/esm/db/shared.d.ts +5 -0
  201. package/dist/esm/db/shared.js +20 -0
  202. package/dist/esm/db/shared.js.map +1 -0
  203. package/dist/esm/features/embed.d.ts +1 -1
  204. package/dist/esm/features/embed.js +2 -1
  205. package/dist/esm/features/embed.js.map +1 -1
  206. package/dist/esm/features/index.d.ts +1 -1
  207. package/dist/esm/features/index.js +3 -1
  208. package/dist/esm/features/index.js.map +1 -1
  209. package/dist/esm/features/links.js +36 -21
  210. package/dist/esm/features/links.js.map +1 -1
  211. package/dist/esm/features/tags.d.ts +2 -0
  212. package/dist/esm/features/tags.js +80 -0
  213. package/dist/esm/features/tags.js.map +1 -0
  214. package/dist/esm/features/types.d.ts +2 -2
  215. package/dist/esm/features/types.js.map +1 -1
  216. package/dist/esm/index.d.ts +6 -6
  217. package/dist/esm/index.js +3 -3
  218. package/dist/esm/index.js.map +1 -1
  219. package/dist/esm/scan.d.ts +3 -1
  220. package/dist/esm/scan.js +5 -2
  221. package/dist/esm/scan.js.map +1 -1
  222. package/dist/esm/sql-functions.d.ts +2 -0
  223. package/dist/esm/sql-functions.js +47 -0
  224. package/dist/esm/sql-functions.js.map +1 -0
  225. package/dist/esm/watch.d.ts +1 -1
  226. package/dist/esm/watch.js +2 -2
  227. package/dist/esm/watch.js.map +1 -1
  228. package/package.json +2 -1
  229. package/schema.json +4 -0
  230. package/skills/sense/SKILL.md +3 -3
  231. package/skills/sense-bases/EXAMPLES.md +103 -0
  232. package/skills/sense-bases/SKILL.md +137 -0
  233. package/skills/sense-setup/SKILL.md +1 -1
  234. package/dist/cjs/commands.d.cts +0 -58
  235. package/dist/cjs/commands.d.ts +0 -58
  236. package/dist/cjs/commands.js.map +0 -1
  237. package/dist/cjs/config.js +0 -919
  238. package/dist/cjs/config.js.map +0 -1
  239. package/dist/cjs/db.js.map +0 -1
  240. package/dist/esm/commands.d.ts +0 -58
  241. package/dist/esm/commands.js +0 -486
  242. package/dist/esm/commands.js.map +0 -1
  243. package/dist/esm/config.js +0 -599
  244. package/dist/esm/config.js.map +0 -1
  245. package/dist/esm/db.js.map +0 -1
package/dist/esm/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { parseArgs } from 'node:util';
3
3
  import { COMMANDS, USAGE } from './cli/index.js';
4
- import { loadConfig, SUPPORTED_CONFIG_VERSION } from './config.js';
4
+ import { loadConfig, SUPPORTED_CONFIG_VERSION } from './config/index.js';
5
5
  // Parsing and dispatch only; commands lazy-load from src/cli/, so nothing heavy is imported
6
6
  // here. Flags parse per command -- this file handles only --version/--help/--list/--config.
7
7
  // Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { COMMANDS, USAGE } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport { loadConfig, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\n// Parsing and dispatch only; commands lazy-load from src/cli/, so nothing heavy is imported\n// here. Flags parse per command -- this file handles only --version/--help/--list/--config.\n\n// Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.\nfunction packageVersion(): string {\n const load = createRequire(import.meta.url);\n for (const rel of ['../package.json', '../../package.json', '../../../package.json']) {\n try {\n const pkg = load(rel) as { name?: string; version?: string };\n if (pkg.name === 'sensemaking' && pkg.version) return pkg.version;\n } catch {}\n }\n return 'unknown';\n}\n\nfunction usage(name: string): string {\n const lines = Object.values(USAGE).map((u) => ` ${name} ${u}`);\n return [`usage: ${name} <name> [params...] [--format table|json|csv] [--config path]`, ...lines, ` ${name} --list`, ` ${name} --version`].join('\\n');\n}\n\nfunction resolveConfigFor(name: string, configPath: string | undefined) {\n const cfg = loadConfig(configPath);\n if (cfg.migratedFrom !== undefined) {\n console.warn(`${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`);\n }\n if (cfg.unknownKeys !== undefined) {\n console.warn(`${name}: ${cfg.configPath} sets ${cfg.unknownKeys.join(', ')}, which this build does not read (no effect); see schema.json for the keys it does`);\n }\n return cfg;\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n // stdout is a pipe whenever the caller pipes or redirects, and a reader that goes away\n // (`| head`) makes every later write fail. The failure arrives asynchronously, after the\n // write loop has already returned: console.log absorbs it, process.stdout.write (what the\n // streaming formats use) does not, so with no listener the process dies on an unhandled\n // 'error' event having already done its work. Neither throwing nor exiting is safe from\n // here. The event is async, so a throw is an uncaught exception rather than something\n // bin/cli.js can catch, and process.exit() would skip the cleanup `sense watch` runs on the\n // way out, leaving a heartbeat behind that makes a dead watcher look alive.\n process.stdout.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EPIPE') return;\n console.error(`sense: ${err.message}`);\n process.exitCode = 1;\n });\n\n const first = argv[0];\n\n // No command word, or the first token looks like a flag: the only flags this level knows.\n if (!first || first.startsWith('-')) {\n let values: Record<string, string | boolean | undefined>;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n version: { type: 'boolean', default: false, short: 'v' },\n help: { type: 'boolean', default: false, short: 'h' },\n list: { type: 'boolean', default: false },\n config: { type: 'string' },\n },\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n\n try {\n if (values.version) {\n console.log(packageVersion());\n return;\n }\n if (values.help) {\n console.log(usage(name));\n return;\n }\n if (values.list) {\n const queries = resolveConfigFor(name, values.config as string | undefined).queries;\n for (const queryName of Object.keys(queries).sort()) {\n // Every entry names its verb, so --list can label both kinds rather than only\n // marking the exception.\n console.log(`${queryName} (${'search' in queries[queryName] ? 'search' : 'sql'})`);\n }\n return;\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n console.error(usage(name));\n process.exit(2);\n }\n\n // Breaking renames get one release of a pointer instead of an \"unknown\" error: find ->\n // search, and query -> sql (which took its name back from the search sense of \"query\").\n const RENAMED: Record<string, string> = { find: 'search', query: 'sql' };\n if (RENAMED[first]) {\n console.error(`${name}: ${first} is now ${RENAMED[first]}`);\n console.error(usage(name));\n process.exit(2);\n }\n\n const ctx: Ctx = {\n name,\n argv: argv.slice(1),\n resolveConfig: (configPath) => resolveConfigFor(name, configPath),\n usageError(message) {\n console.error(message);\n process.exit(2);\n },\n };\n\n try {\n const load = COMMANDS[first];\n if (load) await (await load()).default(ctx);\n else await (await import('./cli/named.ts')).default(ctx, first);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["createRequire","parseArgs","COMMANDS","USAGE","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","lines","Object","values","map","u","join","resolveConfigFor","configPath","cfg","migratedFrom","undefined","console","warn","unknownKeys","cli","argv","process","stdout","on","err","code","error","message","exitCode","first","startsWith","args","options","type","default","short","help","list","config","allowPositionals","exit","log","queries","queryName","keys","sort","RENAMED","find","query","ctx","slice","resolveConfig","usageError"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { COMMANDS, USAGE } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport { loadConfig, SUPPORTED_CONFIG_VERSION } from './config/index.ts';\n\n// Parsing and dispatch only; commands lazy-load from src/cli/, so nothing heavy is imported\n// here. Flags parse per command -- this file handles only --version/--help/--list/--config.\n\n// Works from dist/cjs and dist/esm alike: walk up past the dist type-marker package.json.\nfunction packageVersion(): string {\n const load = createRequire(import.meta.url);\n for (const rel of ['../package.json', '../../package.json', '../../../package.json']) {\n try {\n const pkg = load(rel) as { name?: string; version?: string };\n if (pkg.name === 'sensemaking' && pkg.version) return pkg.version;\n } catch {}\n }\n return 'unknown';\n}\n\nfunction usage(name: string): string {\n const lines = Object.values(USAGE).map((u) => ` ${name} ${u}`);\n return [`usage: ${name} <name> [params...] [--format table|json|csv] [--config path]`, ...lines, ` ${name} --list`, ` ${name} --version`].join('\\n');\n}\n\nfunction resolveConfigFor(name: string, configPath: string | undefined) {\n const cfg = loadConfig(configPath);\n if (cfg.migratedFrom !== undefined) {\n console.warn(`${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`);\n }\n if (cfg.unknownKeys !== undefined) {\n console.warn(`${name}: ${cfg.configPath} sets ${cfg.unknownKeys.join(', ')}, which this build does not read (no effect); see schema.json for the keys it does`);\n }\n return cfg;\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n // stdout is a pipe whenever the caller pipes or redirects, and a reader that goes away\n // (`| head`) makes every later write fail. The failure arrives asynchronously, after the\n // write loop has already returned: console.log absorbs it, process.stdout.write (what the\n // streaming formats use) does not, so with no listener the process dies on an unhandled\n // 'error' event having already done its work. Neither throwing nor exiting is safe from\n // here. The event is async, so a throw is an uncaught exception rather than something\n // bin/cli.js can catch, and process.exit() would skip the cleanup `sense watch` runs on the\n // way out, leaving a heartbeat behind that makes a dead watcher look alive.\n process.stdout.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EPIPE') return;\n console.error(`sense: ${err.message}`);\n process.exitCode = 1;\n });\n\n const first = argv[0];\n\n // No command word, or the first token looks like a flag: the only flags this level knows.\n if (!first || first.startsWith('-')) {\n let values: Record<string, string | boolean | undefined>;\n try {\n ({ values } = parseArgs({\n args: argv,\n options: {\n version: { type: 'boolean', default: false, short: 'v' },\n help: { type: 'boolean', default: false, short: 'h' },\n list: { type: 'boolean', default: false },\n config: { type: 'string' },\n },\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n\n try {\n if (values.version) {\n console.log(packageVersion());\n return;\n }\n if (values.help) {\n console.log(usage(name));\n return;\n }\n if (values.list) {\n const queries = resolveConfigFor(name, values.config as string | undefined).queries;\n for (const queryName of Object.keys(queries).sort()) {\n // Every entry names its verb, so --list can label both kinds rather than only\n // marking the exception.\n console.log(`${queryName} (${'search' in queries[queryName] ? 'search' : 'sql'})`);\n }\n return;\n }\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n console.error(usage(name));\n process.exit(2);\n }\n\n // Breaking renames get one release of a pointer instead of an \"unknown\" error: find ->\n // search, and query -> sql (which took its name back from the search sense of \"query\").\n const RENAMED: Record<string, string> = { find: 'search', query: 'sql' };\n if (RENAMED[first]) {\n console.error(`${name}: ${first} is now ${RENAMED[first]}`);\n console.error(usage(name));\n process.exit(2);\n }\n\n const ctx: Ctx = {\n name,\n argv: argv.slice(1),\n resolveConfig: (configPath) => resolveConfigFor(name, configPath),\n usageError(message) {\n console.error(message);\n process.exit(2);\n },\n };\n\n try {\n const load = COMMANDS[first];\n if (load) await (await load()).default(ctx);\n else await (await import('./cli/named.ts')).default(ctx, first);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["createRequire","parseArgs","COMMANDS","USAGE","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","lines","Object","values","map","u","join","resolveConfigFor","configPath","cfg","migratedFrom","undefined","console","warn","unknownKeys","cli","argv","process","stdout","on","err","code","error","message","exitCode","first","startsWith","args","options","type","default","short","help","list","config","allowPositionals","exit","log","queries","queryName","keys","sort","RENAMED","find","query","ctx","slice","resolveConfig","usageError"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export type { TreeMap } from './map.js';
2
+ export { mapTree } from './map.js';
3
+ export type { Peek } from './peek.js';
4
+ export { peek, resolveNote } from './peek.js';
5
+ export { relatedNotes } from './related.js';
6
+ export { scopedPaths } from './scope.js';
7
+ export type { SearchOptions } from './search.js';
8
+ export { search } from './search.js';
9
+ export type { PresetCoverage } from './status.js';
10
+ export { presetCoverage } from './status.js';
@@ -0,0 +1,8 @@
1
+ // The three commands: mapTree (orient), search (locate), peek (structure).
2
+ // Each returns data; cli.ts renders. All of them degrade when a feature is off.
3
+ export { mapTree } from './map.js';
4
+ export { peek, resolveNote } from './peek.js';
5
+ export { relatedNotes } from './related.js';
6
+ export { scopedPaths } from './scope.js';
7
+ export { search } from './search.js';
8
+ export { presetCoverage } from './status.js';
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/index.ts"],"sourcesContent":["// The three commands: mapTree (orient), search (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nexport type { TreeMap } from './map.ts';\nexport { mapTree } from './map.ts';\nexport type { Peek } from './peek.ts';\nexport { peek, resolveNote } from './peek.ts';\nexport { relatedNotes } from './related.ts';\nexport { scopedPaths } from './scope.ts';\nexport type { SearchOptions } from './search.ts';\nexport { search } from './search.ts';\nexport type { PresetCoverage } from './status.ts';\nexport { presetCoverage } from './status.ts';\n"],"names":["mapTree","peek","resolveNote","relatedNotes","scopedPaths","search","presetCoverage"],"mappings":"AAAA,2EAA2E;AAC3E,gFAAgF;AAGhF,SAASA,OAAO,QAAQ,WAAW;AAEnC,SAASC,IAAI,EAAEC,WAAW,QAAQ,YAAY;AAC9C,SAASC,YAAY,QAAQ,eAAe;AAC5C,SAASC,WAAW,QAAQ,aAAa;AAEzC,SAASC,MAAM,QAAQ,cAAc;AAErC,SAASC,cAAc,QAAQ,cAAc"}
@@ -0,0 +1,21 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.js';
3
+ import type { Row } from '../output.js';
4
+ import type { PresetCoverage } from './status.js';
5
+ export interface TreeMap {
6
+ docs: {
7
+ count: number;
8
+ bytes: number;
9
+ };
10
+ fields: Row[];
11
+ fieldsTotal: number;
12
+ features: {
13
+ on: FeatureName[];
14
+ off: FeatureName[];
15
+ };
16
+ presets: PresetCoverage[];
17
+ hubs: Row[];
18
+ recent: Row[];
19
+ recentCaveat: string | null;
20
+ }
21
+ export declare function mapTree(db: DatabaseSync, cfg: ResolvedConfig, overrides?: SearchOverrides): TreeMap;
@@ -0,0 +1,56 @@
1
+ import { featureEnabled, featureStates } from '../config/index.js';
2
+ import { INTERNAL_COLUMNS, scopedPaths, setupMapScope } from './scope.js';
3
+ import { presetCoverage } from './status.js';
4
+ // A result row is capped at SQLITE_MAX_COLUMN (2000, default); two aggregate expressions
5
+ // per field keeps a chunk's row safely under that regardless of how many fields the tree has.
6
+ const MAP_COLUMN_CHUNK = 300;
7
+ function chunk(items, size) {
8
+ const out = [];
9
+ for(let i = 0; i < items.length; i += size)out.push(items.slice(i, i + size));
10
+ return out;
11
+ }
12
+ // What is this scope: fixed-size output regardless of tree size. Coverage and features stay
13
+ // global -- they describe the tree, not the current question.
14
+ export function mapTree(db, cfg, overrides = {}) {
15
+ setupMapScope(db, scopedPaths(db, cfg, overrides));
16
+ const scopeWhere = 'WHERE "path" IN (SELECT "path" FROM _map_scope)';
17
+ const scopeAnd = 'AND f."path" IN (SELECT "path" FROM _map_scope)';
18
+ const docs = db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM("_size"), 0) AS bytes FROM frontmatter ${scopeWhere}`).get();
19
+ const columns = db.prepare('PRAGMA table_info(frontmatter)').all().map((r)=>r.name).filter((name)=>!INTERNAL_COLUMNS.has(name));
20
+ // Observed types, not declared: SQLite types per value, so a field can be text in most notes
21
+ // and numeric in a few. One aggregate scan per chunk of columns; FILTER matches COUNT's nulls.
22
+ const allFields = [];
23
+ for (const group of chunk(columns, MAP_COLUMN_CHUNK)){
24
+ const exprs = group.map((name, i)=>{
25
+ const quoted = `"${name.split('"').join('""')}"`;
26
+ return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;
27
+ });
28
+ const result = db.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`).get();
29
+ group.forEach((name, i)=>{
30
+ var _result_;
31
+ return allFields.push({
32
+ field: name,
33
+ coverage: result[`n${i}`],
34
+ type: (_result_ = result[`t${i}`]) !== null && _result_ !== void 0 ? _result_ : ''
35
+ });
36
+ });
37
+ }
38
+ allFields.sort((a, b)=>b.coverage - a.coverage);
39
+ const fields = allFields.slice(0, 20);
40
+ 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 ${scopeAnd} ORDER BY f."_rank" DESC LIMIT 8`).all() : [];
41
+ const recent = db.prepare(`SELECT "path", datetime("_mtime" / 1000, 'unixepoch') AS modified FROM frontmatter ${scopeWhere} ORDER BY "_mtime" DESC LIMIT 5`).all();
42
+ // A fresh clone/copy stamps files with checkout time, not edit history; second granularity
43
+ // matches the `recent` table above and is coarse enough to catch that without an exact-ms match.
44
+ const topSecond = db.prepare(`SELECT COUNT(*) AS n FROM frontmatter ${scopeWhere} GROUP BY CAST("_mtime" / 1000 AS INTEGER) ORDER BY n DESC LIMIT 1`).get();
45
+ const recentCaveat = topSecond && docs.count > 1 && topSecond.n > docs.count / 2 ? `${topSecond.n} of ${docs.count} files share one modified second, so recency likely reflects a checkout or copy, not edit history` : null;
46
+ return {
47
+ docs,
48
+ fields,
49
+ fieldsTotal: allFields.length,
50
+ features: featureStates(cfg),
51
+ presets: presetCoverage(db, cfg),
52
+ hubs,
53
+ recent,
54
+ recentCaveat
55
+ };
56
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/map.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled, featureStates } from '../config/index.ts';\nimport type { Row } from '../output.ts';\nimport { INTERNAL_COLUMNS, scopedPaths, setupMapScope } from './scope.ts';\nimport type { PresetCoverage } from './status.ts';\nimport { presetCoverage } from './status.ts';\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 features: { on: FeatureName[]; off: FeatureName[] };\n presets: PresetCoverage[];\n hubs: Row[];\n recent: Row[];\n recentCaveat: string | null;\n}\n\n// A result row is capped at SQLITE_MAX_COLUMN (2000, default); two aggregate expressions\n// per field keeps a chunk's row safely under that regardless of how many fields the tree has.\nconst MAP_COLUMN_CHUNK = 300;\n\nfunction chunk<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));\n return out;\n}\n\n// What is this scope: fixed-size output regardless of tree size. Coverage and features stay\n// global -- they describe the tree, not the current question.\nexport function mapTree(db: DatabaseSync, cfg: ResolvedConfig, overrides: SearchOverrides = {}): TreeMap {\n setupMapScope(db, scopedPaths(db, cfg, overrides));\n const scopeWhere = 'WHERE \"path\" IN (SELECT \"path\" FROM _map_scope)';\n const scopeAnd = 'AND f.\"path\" IN (SELECT \"path\" FROM _map_scope)';\n\n const docs = db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter ${scopeWhere}`).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 // Observed types, not declared: SQLite types per value, so a field can be text in most notes\n // and numeric in a few. One aggregate scan per chunk of columns; FILTER matches COUNT's nulls.\n const allFields: Row[] = [];\n for (const group of chunk(columns, MAP_COLUMN_CHUNK)) {\n const exprs = group.map((name, i) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\n return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const result = db.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`).get() as Record<string, number | string | null>;\n group.forEach((name, i) => allFields.push({ field: name, coverage: result[`n${i}`] as number, type: (result[`t${i}`] as string) ?? '' }));\n }\n allFields.sort((a, b) => (b.coverage as number) - (a.coverage as number));\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 ${scopeAnd} 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 ${scopeWhere} ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n // A fresh clone/copy stamps files with checkout time, not edit history; second granularity\n // matches the `recent` table above and is coarse enough to catch that without an exact-ms match.\n const topSecond = db.prepare(`SELECT COUNT(*) AS n FROM frontmatter ${scopeWhere} GROUP BY CAST(\"_mtime\" / 1000 AS INTEGER) ORDER BY n DESC LIMIT 1`).get() as { n: number } | undefined;\n const recentCaveat = topSecond && docs.count > 1 && topSecond.n > docs.count / 2 ? `${topSecond.n} of ${docs.count} files share one modified second, so recency likely reflects a checkout or copy, not edit history` : null;\n\n return { docs, fields, fieldsTotal: allFields.length, features: featureStates(cfg), presets: presetCoverage(db, cfg), hubs, recent, recentCaveat };\n}\n"],"names":["featureEnabled","featureStates","INTERNAL_COLUMNS","scopedPaths","setupMapScope","presetCoverage","MAP_COLUMN_CHUNK","chunk","items","size","out","i","length","push","slice","mapTree","db","cfg","overrides","scopeWhere","scopeAnd","docs","prepare","get","columns","all","map","r","name","filter","has","allFields","group","exprs","quoted","split","join","result","forEach","field","coverage","type","sort","a","b","fields","hubs","recent","topSecond","recentCaveat","count","n","fieldsTotal","features","presets"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.js';
3
+ import type { Row } from '../output.js';
4
+ export declare function resolveNote(paths: string[], arg: string): string;
5
+ export interface Peek {
6
+ path: string;
7
+ tokens: number;
8
+ frontmatter: Row;
9
+ parseError: string | null;
10
+ sections: Row[];
11
+ outbound: string[];
12
+ backlinks: string[];
13
+ unresolved: string[];
14
+ sectionsTotal: number;
15
+ outboundTotal: number;
16
+ backlinksTotal: number;
17
+ unresolvedTotal: number;
18
+ off: FeatureName[];
19
+ }
20
+ export declare function peek(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string, overrides?: SearchOverrides): Peek;
@@ -0,0 +1,63 @@
1
+ import posix from 'node:path/posix';
2
+ import { featureEnabled } from '../config/index.js';
3
+ import { SenseError } from '../errors.js';
4
+ import { INTERNAL_COLUMNS, scopedPaths } from './scope.js';
5
+ // Note resolution shared by peek and path: an exact path, or a unique basename (case
6
+ // insensitive, .md stripped).
7
+ export function resolveNote(paths, arg) {
8
+ const exact = paths.find((p)=>p === arg);
9
+ if (exact) return exact;
10
+ const base = posix.basename(arg).replace(/\.md$/i, '').toLowerCase();
11
+ const matches = paths.filter((p)=>posix.basename(p).replace(/\.md$/i, '').toLowerCase() === base);
12
+ if (matches.length === 1) return matches[0];
13
+ if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `"${arg}" is ambiguous: ${matches.join(', ')}`);
14
+ throw new SenseError('NOTE_NOT_FOUND', `no note matches "${arg}"`);
15
+ }
16
+ const PEEK_LIST_LIMIT = 20;
17
+ // peek: everything about one note except its prose -- frontmatter, outline with line
18
+ // ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.
19
+ export function peek(db, cfg, pathArg, overrides = {}) {
20
+ var _row__parse_error, _row__size;
21
+ const paths = db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
22
+ const path = resolveNote(paths, pathArg);
23
+ const row = db.prepare('SELECT * FROM frontmatter WHERE "path" = ?').get(path);
24
+ const parseError = (_row__parse_error = row._parse_error) !== null && _row__parse_error !== void 0 ? _row__parse_error : null;
25
+ const frontmatter = {};
26
+ for (const [key, value] of Object.entries(row)){
27
+ if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;
28
+ }
29
+ const sectionsTotal = featureEnabled(cfg, 'sections') ? db.prepare('SELECT COUNT(*) AS n FROM sections WHERE "path" = ?').get(path).n : 0;
30
+ const sections = featureEnabled(cfg, 'sections') ? db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE "path" = ? ORDER BY idx LIMIT ?').all(path, PEEK_LIST_LIMIT) : [];
31
+ let outbound = [];
32
+ let backlinks = [];
33
+ let unresolved = [];
34
+ let backlinksTotal = 0;
35
+ const _allowed = scopedPaths(db, cfg, overrides);
36
+ if (featureEnabled(cfg, 'links')) {
37
+ const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path);
38
+ outbound = [
39
+ ...new Set(out.filter((l)=>l.dst !== null).map((l)=>l.dst))
40
+ ];
41
+ unresolved = out.filter((l)=>l.dst === null).map((l)=>l.target);
42
+ backlinksTotal = db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path).n;
43
+ backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LIST_LIMIT).map((r)=>r.src);
44
+ }
45
+ return {
46
+ path,
47
+ tokens: Math.ceil(((_row__size = row._size) !== null && _row__size !== void 0 ? _row__size : 0) / 4),
48
+ frontmatter,
49
+ parseError,
50
+ sections,
51
+ outbound: outbound.slice(0, PEEK_LIST_LIMIT),
52
+ backlinks,
53
+ unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),
54
+ sectionsTotal,
55
+ outboundTotal: outbound.length,
56
+ backlinksTotal,
57
+ unresolvedTotal: unresolved.length,
58
+ off: [
59
+ 'sections',
60
+ 'links'
61
+ ].filter((name)=>!featureEnabled(cfg, name))
62
+ };
63
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport type { Row } from '../output.ts';\nimport { INTERNAL_COLUMNS, scopedPaths } from './scope.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n // Set when the note's frontmatter was refused, so an empty frontmatter block reads as \"did\n // not parse\" rather than \"has none\". Same reason `_parse_error` sits in the row.\n parseError: string | null;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks (or a note thousands of\n // headings), and peek's whole point is bounded output. Query the sections/links tables\n // directly for the full list.\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n // Bounded k-hop expansion beyond the immediate ring already shown by outbound/backlinks\n // (depth starts at 2).\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n// peek: 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: ResolvedConfig, pathArg: string, overrides: SearchOverrides = {}): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\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 sectionsTotal = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?').get(path) as { n: number }).n : 0;\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?').all(path, PEEK_LIST_LIMIT) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n const _allowed = scopedPaths(db, cfg, overrides);\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_LIST_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 parseError,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n}\n"],"names":["posix","featureEnabled","SenseError","INTERNAL_COLUMNS","scopedPaths","resolveNote","paths","arg","exact","find","p","base","basename","replace","toLowerCase","matches","filter","length","join","PEEK_LIST_LIMIT","peek","db","cfg","pathArg","overrides","row","prepare","all","map","r","path","get","parseError","_parse_error","frontmatter","key","value","Object","entries","has","sectionsTotal","n","sections","outbound","backlinks","unresolved","backlinksTotal","_allowed","out","Set","l","dst","target","src","tokens","Math","ceil","_size","slice","outboundTotal","unresolvedTotal","off","name"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { ResolvedConfig, SearchOverrides } from '../config/index.js';
3
+ export declare function relatedNotes(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{
4
+ path: string;
5
+ similarity: number;
6
+ }>>;
@@ -0,0 +1,47 @@
1
+ import { embedEnabled, resolveSearch } from '../config/index.js';
2
+ import { SenseError } from '../errors.js';
3
+ import { embedPending, hasEmbedding, modelPresent, similarNotes } from '../features/embed.js';
4
+ import { resolveNote } from './peek.js';
5
+ import { scopedPaths, scopeHasEmbeddings } from './scope.js';
6
+ // Notes most similar by cosine, excluding self and everything already linked either way.
7
+ // Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.
8
+ export async function relatedNotes(db, cfg, pathArg, overrides, k) {
9
+ const paths = db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
10
+ const path = resolveNote(paths, pathArg);
11
+ const outbound = db.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL').all(path).map((r)=>r.dst);
12
+ const backlinks = db.prepare('SELECT DISTINCT src FROM links WHERE dst = ?').all(path).map((r)=>r.src);
13
+ const exclude = new Set([
14
+ path,
15
+ ...outbound,
16
+ ...backlinks
17
+ ]);
18
+ // Vectors are the only signal `related` has, so every way of not having them is an error
19
+ // naming the cause. An empty table then means one thing: nothing near in meaning that this
20
+ // note does not already link to, which is a real answer.
21
+ const effective = resolveSearch(cfg, overrides);
22
+ if (!embedEnabled(cfg)) {
23
+ throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an "embed" block naming one to sense.config.json, then run `sense download` (search works without it, on words and links)');
24
+ }
25
+ // search gates on the same flag (see wantsVectors above); reading it here too keeps
26
+ // `semantic: false` meaning one thing. Without this, an overlapping semantic-on preset's
27
+ // vectors would answer for a scope that declined them.
28
+ if (!effective.semantic) {
29
+ throw new SenseError('PRESET_NOT_SEMANTIC', `preset "${effective.presetName}" sets "semantic": false, so this scope has no vectors and related has no other signal; search it instead (words and links), or set semantic back on for that preset`);
30
+ }
31
+ if (!modelPresent(cfg)) {
32
+ throw new SenseError('EMBED_MODEL_MISSING', 'related ranks notes by meaning, so it needs the embedding model, which is not downloaded; run `sense download` (search still works without it, on words and links)');
33
+ }
34
+ const allowed = scopedPaths(db, cfg, overrides);
35
+ // Top up pending rows before the seed check, or a fresh index reports every note as
36
+ // having no indexed text until some search has run.
37
+ await embedPending(db, cfg, cfg.baseDir);
38
+ if (!hasEmbedding(db, path)) {
39
+ throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);
40
+ }
41
+ if (!scopeHasEmbeddings(db, cfg, allowed)) return [];
42
+ return similarNotes(db, cfg, path, {
43
+ exclude,
44
+ allowed,
45
+ k
46
+ });
47
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedEnabled, resolveSearch } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { embedPending, hasEmbedding, modelPresent, similarNotes } from '../features/embed.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\n// Notes most similar by cosine, excluding self and everything already linked either way.\n// Its own command, not a peek section: a full embeddings scan is ~480ms at 26k notes.\nexport async function relatedNotes(db: DatabaseSync, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides, k: number): Promise<Array<{ path: string; similarity: number }>> {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const outbound = (db.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL').all(path) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ?').all(path) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause. An empty table then means one thing: nothing near in meaning that this\n // note does not already link to, which is a real answer.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json, then run `sense download` (search works without it, on words and links)');\n }\n // search gates on the same flag (see wantsVectors above); reading it here too keeps\n // `semantic: false` meaning one thing. Without this, an overlapping semantic-on preset's\n // vectors would answer for a scope that declined them.\n if (!effective.semantic) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" sets \"semantic\": false, so this scope has no vectors and related has no other signal; search it instead (words and links), or set semantic back on for that preset`);\n }\n if (!modelPresent(cfg)) {\n throw new SenseError('EMBED_MODEL_MISSING', 'related ranks notes by meaning, so it needs the embedding model, which is not downloaded; run `sense download` (search still works without it, on words and links)');\n }\n const allowed = scopedPaths(db, cfg, overrides);\n // Top up pending rows before the seed check, or a fresh index reports every note as\n // having no indexed text until some search has run.\n await embedPending(db, cfg, cfg.baseDir);\n if (!hasEmbedding(db, path)) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!scopeHasEmbeddings(db, cfg, allowed)) return [];\n return similarNotes(db, cfg, path, { exclude, allowed, k });\n}\n"],"names":["embedEnabled","resolveSearch","SenseError","embedPending","hasEmbedding","modelPresent","similarNotes","resolveNote","scopedPaths","scopeHasEmbeddings","relatedNotes","db","cfg","pathArg","overrides","k","paths","prepare","all","map","r","path","outbound","dst","backlinks","src","exclude","Set","effective","semantic","presetName","allowed","baseDir"],"mappings":"AAEA,SAASA,YAAY,EAAEC,aAAa,QAAQ,qBAAqB;AACjE,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,YAAY,EAAEC,YAAY,EAAEC,YAAY,EAAEC,YAAY,QAAQ,uBAAuB;AAC9F,SAASC,WAAW,QAAQ,YAAY;AACxC,SAASC,WAAW,EAAEC,kBAAkB,QAAQ,aAAa;AAE7D,yFAAyF;AACzF,sFAAsF;AACtF,OAAO,eAAeC,aAAaC,EAAgB,EAAEC,GAAmB,EAAEC,OAAe,EAAEC,SAA0B,EAAEC,CAAS;IAC9H,MAAMC,QAAQ,AAACL,GAAGM,OAAO,CAAC,kCAAkCC,GAAG,GAA+BC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAC/G,MAAMA,OAAOd,YAAYS,OAAOH;IAEhC,MAAMS,WAAW,AAACX,GAAGM,OAAO,CAAC,oEAAoEC,GAAG,CAACG,MAAiCF,GAAG,CAAC,CAACC,IAAMA,EAAEG,GAAG;IACtJ,MAAMC,YAAY,AAACb,GAAGM,OAAO,CAAC,gDAAgDC,GAAG,CAACG,MAAiCF,GAAG,CAAC,CAACC,IAAMA,EAAEK,GAAG;IACnI,MAAMC,UAAU,IAAIC,IAAI;QAACN;WAASC;WAAaE;KAAU;IAEzD,yFAAyF;IACzF,2FAA2F;IAC3F,yDAAyD;IACzD,MAAMI,YAAY3B,cAAcW,KAAKE;IACrC,IAAI,CAACd,aAAaY,MAAM;QACtB,MAAM,IAAIV,WAAW,kBAAkB;IACzC;IACA,oFAAoF;IACpF,yFAAyF;IACzF,uDAAuD;IACvD,IAAI,CAAC0B,UAAUC,QAAQ,EAAE;QACvB,MAAM,IAAI3B,WAAW,uBAAuB,CAAC,QAAQ,EAAE0B,UAAUE,UAAU,CAAC,oKAAoK,CAAC;IACnP;IACA,IAAI,CAACzB,aAAaO,MAAM;QACtB,MAAM,IAAIV,WAAW,uBAAuB;IAC9C;IACA,MAAM6B,UAAUvB,YAAYG,IAAIC,KAAKE;IACrC,oFAAoF;IACpF,oDAAoD;IACpD,MAAMX,aAAaQ,IAAIC,KAAKA,IAAIoB,OAAO;IACvC,IAAI,CAAC5B,aAAaO,IAAIU,OAAO;QAC3B,MAAM,IAAInB,WAAW,qBAAqB,GAAGmB,KAAK,4GAA4G,CAAC;IACjK;IACA,IAAI,CAACZ,mBAAmBE,IAAIC,KAAKmB,UAAU,OAAO,EAAE;IACpD,OAAOzB,aAAaK,IAAIC,KAAKS,MAAM;QAAEK;QAASK;QAAShB;IAAE;AAC3D"}
@@ -0,0 +1,10 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { ResolvedConfig, SearchOverrides } from '../config/index.js';
3
+ export declare const INTERNAL_COLUMNS: Set<string>;
4
+ export declare function inScope(path: string, include: string[], exclude?: string[]): boolean;
5
+ export declare function scopeHasEmbeddings(db: DatabaseSync, cfg: ResolvedConfig, scopedPaths: Set<string>): boolean;
6
+ export declare function rawScope(db: DatabaseSync, cfg: ResolvedConfig, overrides: SearchOverrides, allPaths?: string[]): Set<string>;
7
+ export declare function narrowByWhere(db: DatabaseSync, paths: Set<string>, where: string | undefined): Set<string>;
8
+ export declare function scopedPaths(db: DatabaseSync, cfg: ResolvedConfig, overrides: SearchOverrides): Set<string>;
9
+ export declare function materializeScope(db: DatabaseSync, table: string, paths: Set<string>): void;
10
+ export declare function setupMapScope(db: DatabaseSync, paths: Set<string>): void;
@@ -0,0 +1,60 @@
1
+ import { matchesGlob } from 'node:path';
2
+ import { anyPresetEmbeds, resolveSearch } from '../config/index.js';
3
+ export const INTERNAL_COLUMNS = new Set([
4
+ 'path',
5
+ '_mtime',
6
+ '_ctime',
7
+ '_size',
8
+ '_rank',
9
+ '_parse_error'
10
+ ]);
11
+ // node:path's matchesGlob is experimental (stable behind an unstable-API flag) as of the
12
+ // engines floor (Node >=22.20); scope filtering only ever needs single-pattern matching, so
13
+ // it's used here in JS rather than running a directory walk in the query path.
14
+ export function inScope(path, include, exclude) {
15
+ if (!include.some((g)=>matchesGlob(path, g))) return false;
16
+ if (exclude === null || exclude === void 0 ? void 0 : exclude.some((g)=>matchesGlob(path, g))) return false;
17
+ return true;
18
+ }
19
+ export function scopeHasEmbeddings(db, cfg, scopedPaths) {
20
+ if (!anyPresetEmbeds(cfg)) return false; // the embeddings table doesn't exist at all in this case
21
+ const rows = db.prepare('SELECT DISTINCT "path" FROM embeddings').all();
22
+ return rows.some((r)=>scopedPaths.has(r.path));
23
+ }
24
+ // Scope only (no --where): preset_files for a named preset, JS glob matching for an ad hoc
25
+ // include/exclude override. Shared by scopedPaths() and search(), which also needs the
26
+ // pre-where set to size the candidate-pool filter.
27
+ export function rawScope(db, cfg, overrides, allPaths) {
28
+ const effective = resolveSearch(cfg, overrides);
29
+ const { include, exclude } = effective;
30
+ const adHocScope = overrides.include !== undefined || overrides.exclude !== undefined || overrides.noExclude === true;
31
+ if (!adHocScope) return new Set(db.prepare('SELECT "path" FROM preset_files WHERE preset = ?').all(effective.presetName).map((r)=>r.path));
32
+ const paths = allPaths !== null && allPaths !== void 0 ? allPaths : db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
33
+ return new Set(paths.filter((p)=>inScope(p, include, exclude)));
34
+ }
35
+ export function narrowByWhere(db, paths, where) {
36
+ if (!where) return paths;
37
+ const whereRows = db.prepare(`SELECT "path" FROM frontmatter f WHERE (${where})`).all();
38
+ const wherePaths = new Set(whereRows.map((r)=>r.path));
39
+ return new Set([
40
+ ...paths
41
+ ].filter((p)=>wherePaths.has(p)));
42
+ }
43
+ // The scope resolver for non-search commands (path, peek, map): same coverage rule search()
44
+ // applies, then narrowed by the resolved `where`.
45
+ export function scopedPaths(db, cfg, overrides) {
46
+ const effective = resolveSearch(cfg, overrides);
47
+ return narrowByWhere(db, rawScope(db, cfg, overrides), effective.where);
48
+ }
49
+ // Materializes a path set into a named temp table (same shape as traverse.ts's allowed_nodes)
50
+ // so a query can join/filter against it cheaply instead of binding a parameter per path.
51
+ export function materializeScope(db, table, paths) {
52
+ db.exec(`DROP TABLE IF EXISTS ${table}`);
53
+ db.exec(`CREATE TEMP TABLE ${table} ("path" TEXT PRIMARY KEY)`);
54
+ db.prepare(`INSERT INTO ${table} SELECT DISTINCT value FROM json_each(?1)`).run(JSON.stringify([
55
+ ...paths
56
+ ]));
57
+ }
58
+ export function setupMapScope(db, paths) {
59
+ materializeScope(db, '_map_scope', paths);
60
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/scope.ts"],"sourcesContent":["import { matchesGlob } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { anyPresetEmbeds, resolveSearch } from '../config/index.ts';\n\nexport const INTERNAL_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_rank', '_parse_error']);\n\n// node:path's matchesGlob is experimental (stable behind an unstable-API flag) as of the\n// engines floor (Node >=22.20); scope filtering only ever needs single-pattern matching, so\n// it's used here in JS rather than running a directory walk in the query path.\nexport function inScope(path: string, include: string[], exclude?: string[]): boolean {\n if (!include.some((g) => matchesGlob(path, g))) return false;\n if (exclude?.some((g) => matchesGlob(path, g))) return false;\n return true;\n}\n\nexport function scopeHasEmbeddings(db: DatabaseSync, cfg: ResolvedConfig, scopedPaths: Set<string>): boolean {\n if (!anyPresetEmbeds(cfg)) return false; // the embeddings table doesn't exist at all in this case\n const rows = db.prepare('SELECT DISTINCT \"path\" FROM embeddings').all() as Array<{ path: string }>;\n return rows.some((r) => scopedPaths.has(r.path));\n}\n\n// Scope only (no --where): preset_files for a named preset, JS glob matching for an ad hoc\n// include/exclude override. Shared by scopedPaths() and search(), which also needs the\n// pre-where set to size the candidate-pool filter.\nexport function rawScope(db: DatabaseSync, cfg: ResolvedConfig, overrides: SearchOverrides, allPaths?: string[]): Set<string> {\n const effective = resolveSearch(cfg, overrides);\n const { include, exclude } = effective;\n const adHocScope = overrides.include !== undefined || overrides.exclude !== undefined || overrides.noExclude === true;\n if (!adHocScope) return new Set((db.prepare('SELECT \"path\" FROM preset_files WHERE preset = ?').all(effective.presetName) as Array<{ path: string }>).map((r) => r.path));\n const paths = allPaths ?? (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n return new Set(paths.filter((p) => inScope(p, include, exclude)));\n}\n\nexport function narrowByWhere(db: DatabaseSync, paths: Set<string>, where: string | undefined): Set<string> {\n if (!where) return paths;\n const whereRows = db.prepare(`SELECT \"path\" FROM frontmatter f WHERE (${where})`).all() as Array<{ path: string }>;\n const wherePaths = new Set(whereRows.map((r) => r.path));\n return new Set([...paths].filter((p) => wherePaths.has(p)));\n}\n\n// The scope resolver for non-search commands (path, peek, map): same coverage rule search()\n// applies, then narrowed by the resolved `where`.\nexport function scopedPaths(db: DatabaseSync, cfg: ResolvedConfig, overrides: SearchOverrides): Set<string> {\n const effective = resolveSearch(cfg, overrides);\n return narrowByWhere(db, rawScope(db, cfg, overrides), effective.where);\n}\n\n// Materializes a path set into a named temp table (same shape as traverse.ts's allowed_nodes)\n// so a query can join/filter against it cheaply instead of binding a parameter per path.\nexport function materializeScope(db: DatabaseSync, table: string, paths: Set<string>): void {\n db.exec(`DROP TABLE IF EXISTS ${table}`);\n db.exec(`CREATE TEMP TABLE ${table} (\"path\" TEXT PRIMARY KEY)`);\n db.prepare(`INSERT INTO ${table} SELECT DISTINCT value FROM json_each(?1)`).run(JSON.stringify([...paths]));\n}\n\nexport function setupMapScope(db: DatabaseSync, paths: Set<string>): void {\n materializeScope(db, '_map_scope', paths);\n}\n"],"names":["matchesGlob","anyPresetEmbeds","resolveSearch","INTERNAL_COLUMNS","Set","inScope","path","include","exclude","some","g","scopeHasEmbeddings","db","cfg","scopedPaths","rows","prepare","all","r","has","rawScope","overrides","allPaths","effective","adHocScope","undefined","noExclude","presetName","map","paths","filter","p","narrowByWhere","where","whereRows","wherePaths","materializeScope","table","exec","run","JSON","stringify","setupMapScope"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { ResolvedConfig } from '../config/index.js';
3
+ import type { Row } from '../output.js';
4
+ export interface SearchOptions {
5
+ k?: number;
6
+ where?: string;
7
+ preset?: string;
8
+ include?: string[];
9
+ exclude?: string[];
10
+ noExclude?: boolean;
11
+ }
12
+ export declare function search(db: DatabaseSync, cfg: ResolvedConfig, terms: string, opts?: SearchOptions): Promise<Row[]>;