sensemaking 0.27.1 → 0.28.1

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 (138) hide show
  1. package/README.md +2 -2
  2. package/bin/cli.js +16 -7
  3. package/dist/cjs/cli/constants.d.cts +2 -0
  4. package/dist/cjs/cli/constants.d.ts +2 -0
  5. package/dist/cjs/cli/constants.js +21 -0
  6. package/dist/cjs/cli/constants.js.map +1 -0
  7. package/dist/cjs/cli/named.js +2 -1
  8. package/dist/cjs/cli/named.js.map +1 -1
  9. package/dist/cjs/cli/shared.js +5 -4
  10. package/dist/cjs/cli/shared.js.map +1 -1
  11. package/dist/cjs/cli.js +10 -9
  12. package/dist/cjs/cli.js.map +1 -1
  13. package/dist/cjs/embed/query.js.map +1 -1
  14. package/dist/cjs/store/duckdb/engine.d.cts +11 -0
  15. package/dist/cjs/store/duckdb/engine.d.ts +11 -0
  16. package/dist/cjs/store/duckdb/engine.js +193 -0
  17. package/dist/cjs/store/duckdb/engine.js.map +1 -0
  18. package/dist/cjs/store/duckdb/lexical.js +34 -20
  19. package/dist/cjs/store/duckdb/lexical.js.map +1 -1
  20. package/dist/cjs/store/duckdb/native.d.cts +5 -2
  21. package/dist/cjs/store/duckdb/native.d.ts +5 -2
  22. package/dist/cjs/store/duckdb/native.js +157 -13
  23. package/dist/cjs/store/duckdb/native.js.map +1 -1
  24. package/dist/cjs/store/duckdb/open.js +151 -3
  25. package/dist/cjs/store/duckdb/open.js.map +1 -1
  26. package/dist/cjs/store/duckdb/ordered-bm25.d.cts +3 -3
  27. package/dist/cjs/store/duckdb/ordered-bm25.d.ts +3 -3
  28. package/dist/cjs/store/duckdb/ordered-bm25.js +4 -8
  29. package/dist/cjs/store/duckdb/ordered-bm25.js.map +1 -1
  30. package/dist/cjs/store/engine.d.cts +28 -0
  31. package/dist/cjs/store/engine.d.ts +28 -0
  32. package/dist/cjs/store/engine.js +362 -0
  33. package/dist/cjs/store/engine.js.map +1 -0
  34. package/dist/cjs/store/native.d.cts +8 -5
  35. package/dist/cjs/store/native.d.ts +8 -5
  36. package/dist/cjs/store/native.js +78 -73
  37. package/dist/cjs/store/native.js.map +1 -1
  38. package/dist/cjs/store/open.js +100 -40
  39. package/dist/cjs/store/open.js.map +1 -1
  40. package/dist/cjs/store/sqlite/engine.d.cts +12 -0
  41. package/dist/cjs/store/sqlite/engine.d.ts +12 -0
  42. package/dist/cjs/store/sqlite/engine.js +53 -0
  43. package/dist/cjs/store/sqlite/engine.js.map +1 -0
  44. package/dist/cjs/store/sqlite/lexical.js +3 -4
  45. package/dist/cjs/store/sqlite/lexical.js.map +1 -1
  46. package/dist/cjs/store/sqlite/open.js +119 -6
  47. package/dist/cjs/store/sqlite/open.js.map +1 -1
  48. package/dist/cjs/store/turso/engine.d.cts +9 -0
  49. package/dist/cjs/store/turso/engine.d.ts +9 -0
  50. package/dist/cjs/store/turso/engine.js +189 -0
  51. package/dist/cjs/store/turso/engine.js.map +1 -0
  52. package/dist/cjs/store/turso/lexical.js +16 -11
  53. package/dist/cjs/store/turso/lexical.js.map +1 -1
  54. package/dist/cjs/store/turso/native.d.cts +5 -1
  55. package/dist/cjs/store/turso/native.d.ts +5 -1
  56. package/dist/cjs/store/turso/native.js +157 -8
  57. package/dist/cjs/store/turso/native.js.map +1 -1
  58. package/dist/cjs/store/turso/open.js +172 -27
  59. package/dist/cjs/store/turso/open.js.map +1 -1
  60. package/dist/cjs/store/turso/reconcile.d.cts +0 -1
  61. package/dist/cjs/store/turso/reconcile.d.ts +0 -1
  62. package/dist/cjs/store/turso/reconcile.js +22 -23
  63. package/dist/cjs/store/turso/reconcile.js.map +1 -1
  64. package/dist/cjs/store/types.d.cts +2 -0
  65. package/dist/cjs/store/types.d.ts +2 -0
  66. package/dist/cjs/store/types.js.map +1 -1
  67. package/dist/esm/cli/constants.d.ts +2 -0
  68. package/dist/esm/cli/constants.js +2 -0
  69. package/dist/esm/cli/constants.js.map +1 -0
  70. package/dist/esm/cli/named.js +2 -1
  71. package/dist/esm/cli/named.js.map +1 -1
  72. package/dist/esm/cli/shared.js +6 -5
  73. package/dist/esm/cli/shared.js.map +1 -1
  74. package/dist/esm/cli.js +10 -9
  75. package/dist/esm/cli.js.map +1 -1
  76. package/dist/esm/embed/query.js +1 -1
  77. package/dist/esm/embed/query.js.map +1 -1
  78. package/dist/esm/store/duckdb/engine.d.ts +11 -0
  79. package/dist/esm/store/duckdb/engine.js +18 -0
  80. package/dist/esm/store/duckdb/engine.js.map +1 -0
  81. package/dist/esm/store/duckdb/lexical.js +11 -11
  82. package/dist/esm/store/duckdb/lexical.js.map +1 -1
  83. package/dist/esm/store/duckdb/native.d.ts +5 -2
  84. package/dist/esm/store/duckdb/native.js +16 -12
  85. package/dist/esm/store/duckdb/native.js.map +1 -1
  86. package/dist/esm/store/duckdb/open.js +40 -2
  87. package/dist/esm/store/duckdb/open.js.map +1 -1
  88. package/dist/esm/store/duckdb/ordered-bm25.d.ts +3 -3
  89. package/dist/esm/store/duckdb/ordered-bm25.js +4 -9
  90. package/dist/esm/store/duckdb/ordered-bm25.js.map +1 -1
  91. package/dist/esm/store/engine.d.ts +28 -0
  92. package/dist/esm/store/engine.js +85 -0
  93. package/dist/esm/store/engine.js.map +1 -0
  94. package/dist/esm/store/native.d.ts +8 -5
  95. package/dist/esm/store/native.js +59 -57
  96. package/dist/esm/store/native.js.map +1 -1
  97. package/dist/esm/store/open.js +24 -7
  98. package/dist/esm/store/open.js.map +1 -1
  99. package/dist/esm/store/sqlite/engine.d.ts +12 -0
  100. package/dist/esm/store/sqlite/engine.js +16 -0
  101. package/dist/esm/store/sqlite/engine.js.map +1 -0
  102. package/dist/esm/store/sqlite/lexical.js +2 -3
  103. package/dist/esm/store/sqlite/lexical.js.map +1 -1
  104. package/dist/esm/store/sqlite/open.js +38 -7
  105. package/dist/esm/store/sqlite/open.js.map +1 -1
  106. package/dist/esm/store/turso/engine.d.ts +9 -0
  107. package/dist/esm/store/turso/engine.js +14 -0
  108. package/dist/esm/store/turso/engine.js.map +1 -0
  109. package/dist/esm/store/turso/lexical.js +9 -9
  110. package/dist/esm/store/turso/lexical.js.map +1 -1
  111. package/dist/esm/store/turso/native.d.ts +5 -1
  112. package/dist/esm/store/turso/native.js +16 -10
  113. package/dist/esm/store/turso/native.js.map +1 -1
  114. package/dist/esm/store/turso/open.js +44 -3
  115. package/dist/esm/store/turso/open.js.map +1 -1
  116. package/dist/esm/store/turso/reconcile.d.ts +0 -1
  117. package/dist/esm/store/turso/reconcile.js +3 -8
  118. package/dist/esm/store/turso/reconcile.js.map +1 -1
  119. package/dist/esm/store/types.d.ts +2 -0
  120. package/dist/esm/store/types.js.map +1 -1
  121. package/package.json +21 -7
  122. package/skills/sense/EXAMPLES.md +0 -99
  123. package/skills/sense/SKILL.md +0 -145
  124. package/skills/sense/references/search.md +0 -63
  125. package/skills/sense/references/sql.md +0 -120
  126. package/skills/sense/references/stores/duckdb.md +0 -35
  127. package/skills/sense/references/stores/sqlite.md +0 -66
  128. package/skills/sense/references/stores/turso.md +0 -36
  129. package/skills/sense/references/terminology.md +0 -32
  130. package/skills/sense-bases/EXAMPLES.md +0 -103
  131. package/skills/sense-bases/SKILL.md +0 -147
  132. package/skills/sense-setup/EXAMPLES.md +0 -129
  133. package/skills/sense-setup/SKILL.md +0 -112
  134. package/skills/sense-setup/references/embeddings.md +0 -51
  135. package/skills/sense-setup/references/store-benchmarks.md +0 -16
  136. package/skills/sense-setup/references/stores/duckdb.md +0 -28
  137. package/skills/sense-setup/references/stores/sqlite.md +0 -21
  138. package/skills/sense-setup/references/stores/turso.md +0 -25
@@ -9,9 +9,6 @@ function _export(target, all) {
9
9
  });
10
10
  }
11
11
  _export(exports, {
12
- get CONTENT_FTS_DDL () {
13
- return CONTENT_FTS_DDL;
14
- },
15
12
  get CONTENT_FTS_NAMES () {
16
13
  return CONTENT_FTS_NAMES;
17
14
  },
@@ -32,6 +29,7 @@ var _errorsts = require("../../errors.js");
32
29
  var _segmentts = require("../../text/segment.js");
33
30
  var _sharedts = require("../shared.js");
34
31
  var _transactionts = require("../transaction.js");
32
+ var _enginets = require("./engine.js");
35
33
  var _lexicaltextts = require("./lexical-text.js");
36
34
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
37
35
  try {
@@ -158,10 +156,6 @@ function _ts_generator(thisArg, body) {
158
156
  };
159
157
  }
160
158
  }
161
- var CONTENT_FTS_DDL = [
162
- "CREATE INDEX IF NOT EXISTS content_fts ON content USING fts (title_stem, summary_stem, text_stem) WITH (weights = 'title_stem=10.0,summary_stem=5.0,text_stem=1.0')",
163
- "CREATE INDEX IF NOT EXISTS content_fts_ngram ON content USING fts (title_ngram, summary_ngram, text_ngram) WITH (tokenizer='ngram', weights='title_ngram=10.0,summary_ngram=5.0,text_ngram=1.0')"
164
- ];
165
159
  var CONTENT_FTS_NAMES = [
166
160
  'content_fts',
167
161
  'content_fts_ngram'
@@ -298,51 +292,56 @@ function reconcileTursoContentWithStrategy(conn, touched, docs, strategy) {
298
292
  _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
299
293
  if (!bulk) return [
300
294
  3,
301
- 20
295
+ 21
302
296
  ];
303
297
  _state.label = 13;
304
298
  case 13:
305
299
  _state.trys.push([
306
300
  13,
307
- 18,
308
301
  19,
309
- 20
302
+ 20,
303
+ 21
310
304
  ]);
311
- _iterator1 = CONTENT_FTS_DDL[Symbol.iterator]();
312
- _state.label = 14;
305
+ return [
306
+ 4,
307
+ (0, _enginets.tursoFts)()
308
+ ];
313
309
  case 14:
310
+ _iterator1 = _state.sent().indexDdl()[Symbol.iterator]();
311
+ _state.label = 15;
312
+ case 15:
314
313
  if (!!(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done)) return [
315
314
  3,
316
- 17
315
+ 18
317
316
  ];
318
317
  ddl = _step1.value;
319
318
  return [
320
319
  4,
321
320
  conn.exec(ddl)
322
321
  ];
323
- case 15:
324
- _state.sent();
325
- _state.label = 16;
326
322
  case 16:
323
+ _state.sent();
324
+ _state.label = 17;
325
+ case 17:
327
326
  _iteratorNormalCompletion1 = true;
328
327
  return [
329
328
  3,
330
- 14
329
+ 15
331
330
  ];
332
- case 17:
331
+ case 18:
333
332
  return [
334
333
  3,
335
- 20
334
+ 21
336
335
  ];
337
- case 18:
336
+ case 19:
338
337
  err = _state.sent();
339
338
  _didIteratorError1 = true;
340
339
  _iteratorError1 = err;
341
340
  return [
342
341
  3,
343
- 20
342
+ 21
344
343
  ];
345
- case 19:
344
+ case 20:
346
345
  try {
347
346
  if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
348
347
  _iterator1.return();
@@ -355,7 +354,7 @@ function reconcileTursoContentWithStrategy(conn, touched, docs, strategy) {
355
354
  return [
356
355
  7
357
356
  ];
358
- case 20:
357
+ case 21:
359
358
  return [
360
359
  2
361
360
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/turso/reconcile.ts"],"sourcesContent":["import type { Config } from '../../config/index.ts';\nimport { SenseError } from '../../errors.ts';\nimport type { ReconcileDelta } from '../../features/types.ts';\nimport type { ParsedDoc } from '../../scan/index.ts';\nimport { hasUnspacedRun } from '../../text/segment.ts';\nimport { quoteIdent, recordReconcileDuration } from '../shared.ts';\nimport { BEGIN_WRITE } from '../transaction.ts';\nimport type { Connection, ReconcileDialect } from '../types.ts';\nimport { stemFolded } from './lexical-text.ts';\n\n// This store's dialect (types.ts's ReconcileDialect) for the shared orchestration in\n// store/reconcile.ts. content is a plain table with \"_ngram\" sidecars for lexical.ts.\n\n// The ngram index is scoped to disjoint \"_ngram\" sidecar columns: a second index over the same\n// columns makes a bare substring match a whole word, defeating the prefix-query rejection.\n// The two FTS indexes, named here (not open.ts) because reconcile drops and rebuilds them around\n// a bulk load: Tantivy maintains them per inserted row, which is quadratic in what is already indexed.\nexport const CONTENT_FTS_DDL = [\n `CREATE INDEX IF NOT EXISTS content_fts ON content USING fts (title_stem, summary_stem, text_stem) WITH (weights = 'title_stem=10.0,summary_stem=5.0,text_stem=1.0')`,\n `CREATE INDEX IF NOT EXISTS content_fts_ngram ON content USING fts (title_ngram, summary_ngram, text_ngram) WITH (tokenizer='ngram', weights='title_ngram=10.0,summary_ngram=5.0,text_ngram=1.0')`,\n] as const;\nexport const CONTENT_FTS_NAMES = ['content_fts', 'content_fts_ngram'] as const;\n\n// '' when the field has no unspaced-script run (the common case), so the ngram index carries\n// nothing for it -- same \"pay for nothing when absent\" shape as sqlite's segmentField sidecars.\nfunction ngramSidecar(text: string): string {\n return hasUnspacedRun(text) ? text : '';\n}\n\n// Not a compile-time cap on ALTER TABLE ADD COLUMN (spike-measured: turso accepts 10,000 with\n// no error); the real fence is a SELECT projecting more than this many result columns, which fails to prepare.\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\n// Retained policy boundary from 2026-08-30 corpus-scale measurements; the optimum after later\n// update-path changes remains pending matched corpus-scale crossover evidence.\nexport const FTS_REBUILD_THRESHOLD = 250;\n\nexport type TursoFtsStrategy = 'incremental' | 'rebuild';\n\n// No rowid coupling (unlike sqlite's FTS5 content): `path` is content's own primary key.\nconst INSERT_CONTENT_SQL = `INSERT INTO content (\"path\", title, summary, text, title_stem, summary_stem, text_stem, title_ngram, summary_ngram, text_ngram) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n\nfunction contentRow(doc: ParsedDoc): unknown[] {\n const { title, summary, text } = doc.search;\n return [doc.relPath, title, summary, text, stemFolded(title), stemFolded(summary), stemFolded(text), ngramSidecar(title), ngramSidecar(summary), ngramSidecar(text)];\n}\n\nexport function tursoFtsStrategy(delta: ReconcileDelta): TursoFtsStrategy {\n const churn = delta.reparsed.length + delta.vanished.length;\n return delta.files.length === 0 || churn > FTS_REBUILD_THRESHOLD ? 'rebuild' : 'incremental';\n}\n\n// Kept transaction-free because the shared reconcile owns the one write transaction. The explicit\n// strategy is internal composition for real-engine diagnostics; production selects it from delta.\nexport async function reconcileTursoContentWithStrategy(conn: Connection, touched: string[], docs: ParsedDoc[], strategy: TursoFtsStrategy): Promise<void> {\n // Tantivy indexes per inserted row at a cost that grows with the batch, so a large insert is\n // superlinear and a rebuild wins past the threshold.\n const bulk = strategy === 'rebuild';\n if (bulk) for (const name of CONTENT_FTS_NAMES) await conn.exec(`DROP INDEX IF EXISTS ${name}`);\n\n // content is a plain table keyed by its own path (no rowid subquery, unlike sqlite's FTS5\n // content), so vanished and reparsed docs delete in one pass.\n if (touched.length > 0) {\n const placeholders = touched.map(() => '?').join(', ');\n await conn.runBatch(`DELETE FROM content WHERE \"path\" IN (${placeholders})`, [touched]);\n }\n if (docs.length > 0) await conn.runBatch(INSERT_CONTENT_SQL, docs.map(contentRow));\n if (bulk) for (const ddl of CONTENT_FTS_DDL) await conn.exec(ddl);\n}\n\nasync function reconcileTursoContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, _cfg: Config): Promise<void> {\n await reconcileTursoContentWithStrategy(conn, touched, docs, tursoFtsStrategy(delta));\n}\n\n// turso's ADD COLUMN is metadata-only, so a loop costs nothing extra over one statement.\nasync function addColumns(conn: Connection, names: string[]): Promise<void> {\n for (const name of names) await conn.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(name)}`);\n}\n\nexport const tursoDialect: ReconcileDialect = {\n beginMode: () => BEGIN_WRITE,\n checkColumnLimit(count) {\n if (count > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${count} columns, crossing turso's SELECT result-set column limit (${MAX_FRONTMATTER_COLUMNS}; ALTER TABLE ADD COLUMN itself accepts far more, but a query projecting past this many columns fails to prepare). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n },\n addColumns,\n reconcileContent: reconcileTursoContent,\n recordDuration: recordReconcileDuration,\n};\n"],"names":["CONTENT_FTS_DDL","CONTENT_FTS_NAMES","FTS_REBUILD_THRESHOLD","reconcileTursoContentWithStrategy","tursoDialect","tursoFtsStrategy","ngramSidecar","text","hasUnspacedRun","MAX_FRONTMATTER_COLUMNS","INSERT_CONTENT_SQL","contentRow","doc","search","title","summary","relPath","stemFolded","delta","churn","reparsed","length","vanished","files","conn","touched","docs","strategy","bulk","name","placeholders","ddl","exec","map","join","runBatch","reconcileTursoContent","_cfg","addColumns","names","quoteIdent","beginMode","BEGIN_WRITE","checkColumnLimit","count","SenseError","reconcileContent","recordDuration","recordReconcileDuration"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/turso/reconcile.ts"],"sourcesContent":["import type { Config } from '../../config/index.ts';\nimport { SenseError } from '../../errors.ts';\nimport type { ReconcileDelta } from '../../features/types.ts';\nimport type { ParsedDoc } from '../../scan/index.ts';\nimport { hasUnspacedRun } from '../../text/segment.ts';\nimport { quoteIdent, recordReconcileDuration } from '../shared.ts';\nimport { BEGIN_WRITE } from '../transaction.ts';\nimport type { Connection, ReconcileDialect } from '../types.ts';\nimport { tursoFts } from './engine.ts';\nimport { stemFolded } from './lexical-text.ts';\n\n// This store's dialect (types.ts's ReconcileDialect) for the shared orchestration in\n// store/reconcile.ts. content is a plain table with \"_ngram\" sidecars for lexical.ts.\n\n// The two FTS indexes (engine.ts's indexDdl), named here because reconcile drops and rebuilds them around\n// a bulk load: Tantivy maintains them per inserted row, which is quadratic in what is already indexed.\nexport const CONTENT_FTS_NAMES = ['content_fts', 'content_fts_ngram'] as const;\n\n// '' when the field has no unspaced-script run (the common case), so the ngram index carries\n// nothing for it -- same \"pay for nothing when absent\" shape as sqlite's segmentField sidecars.\nfunction ngramSidecar(text: string): string {\n return hasUnspacedRun(text) ? text : '';\n}\n\n// Not a compile-time cap on ALTER TABLE ADD COLUMN (spike-measured: turso accepts 10,000 with\n// no error); the real fence is a SELECT projecting more than this many result columns, which fails to prepare.\nconst MAX_FRONTMATTER_COLUMNS = 2000;\n\n// Retained policy boundary from 2026-08-30 corpus-scale measurements; the optimum after later\n// update-path changes remains pending matched corpus-scale crossover evidence.\nexport const FTS_REBUILD_THRESHOLD = 250;\n\nexport type TursoFtsStrategy = 'incremental' | 'rebuild';\n\n// No rowid coupling (unlike sqlite's FTS5 content): `path` is content's own primary key.\nconst INSERT_CONTENT_SQL = `INSERT INTO content (\"path\", title, summary, text, title_stem, summary_stem, text_stem, title_ngram, summary_ngram, text_ngram) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n\nfunction contentRow(doc: ParsedDoc): unknown[] {\n const { title, summary, text } = doc.search;\n return [doc.relPath, title, summary, text, stemFolded(title), stemFolded(summary), stemFolded(text), ngramSidecar(title), ngramSidecar(summary), ngramSidecar(text)];\n}\n\nexport function tursoFtsStrategy(delta: ReconcileDelta): TursoFtsStrategy {\n const churn = delta.reparsed.length + delta.vanished.length;\n return delta.files.length === 0 || churn > FTS_REBUILD_THRESHOLD ? 'rebuild' : 'incremental';\n}\n\n// Kept transaction-free because the shared reconcile owns the one write transaction. The explicit\n// strategy is internal composition for real-engine diagnostics; production selects it from delta.\nexport async function reconcileTursoContentWithStrategy(conn: Connection, touched: string[], docs: ParsedDoc[], strategy: TursoFtsStrategy): Promise<void> {\n // Tantivy indexes per inserted row at a cost that grows with the batch, so a large insert is\n // superlinear and a rebuild wins past the threshold.\n const bulk = strategy === 'rebuild';\n if (bulk) for (const name of CONTENT_FTS_NAMES) await conn.exec(`DROP INDEX IF EXISTS ${name}`);\n\n // content is a plain table keyed by its own path (no rowid subquery, unlike sqlite's FTS5\n // content), so vanished and reparsed docs delete in one pass.\n if (touched.length > 0) {\n const placeholders = touched.map(() => '?').join(', ');\n await conn.runBatch(`DELETE FROM content WHERE \"path\" IN (${placeholders})`, [touched]);\n }\n if (docs.length > 0) await conn.runBatch(INSERT_CONTENT_SQL, docs.map(contentRow));\n if (bulk) for (const ddl of (await tursoFts()).indexDdl()) await conn.exec(ddl);\n}\n\nasync function reconcileTursoContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, _cfg: Config): Promise<void> {\n await reconcileTursoContentWithStrategy(conn, touched, docs, tursoFtsStrategy(delta));\n}\n\n// turso's ADD COLUMN is metadata-only, so a loop costs nothing extra over one statement.\nasync function addColumns(conn: Connection, names: string[]): Promise<void> {\n for (const name of names) await conn.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(name)}`);\n}\n\nexport const tursoDialect: ReconcileDialect = {\n beginMode: () => BEGIN_WRITE,\n checkColumnLimit(count) {\n if (count > MAX_FRONTMATTER_COLUMNS) {\n throw new SenseError(\n 'COLUMN_LIMIT',\n `frontmatter would need ${count} columns, crossing turso's SELECT result-set column limit (${MAX_FRONTMATTER_COLUMNS}; ALTER TABLE ADD COLUMN itself accepts far more, but a query projecting past this many columns fails to prepare). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`\n );\n }\n },\n addColumns,\n reconcileContent: reconcileTursoContent,\n recordDuration: recordReconcileDuration,\n};\n"],"names":["CONTENT_FTS_NAMES","FTS_REBUILD_THRESHOLD","reconcileTursoContentWithStrategy","tursoDialect","tursoFtsStrategy","ngramSidecar","text","hasUnspacedRun","MAX_FRONTMATTER_COLUMNS","INSERT_CONTENT_SQL","contentRow","doc","search","title","summary","relPath","stemFolded","delta","churn","reparsed","length","vanished","files","conn","touched","docs","strategy","bulk","name","placeholders","ddl","exec","map","join","runBatch","tursoFts","indexDdl","reconcileTursoContent","_cfg","addColumns","names","quoteIdent","beginMode","BEGIN_WRITE","checkColumnLimit","count","SenseError","reconcileContent","recordDuration","recordReconcileDuration"],"mappings":"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"}
@@ -50,6 +50,8 @@ export interface OpenDialect<Handle> {
50
50
  ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
51
51
  prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
52
52
  setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;
53
+ engineIdentity?(): Promise<string>;
54
+ checkEngine?(): Promise<void>;
53
55
  prepareLexical?(conn: Connection): Promise<void>;
54
56
  assertLexicalReady?(conn: Connection): Promise<void>;
55
57
  createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: {
@@ -50,6 +50,8 @@ export interface OpenDialect<Handle> {
50
50
  ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
51
51
  prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;
52
52
  setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;
53
+ engineIdentity?(): Promise<string>;
54
+ checkEngine?(): Promise<void>;
53
55
  prepareLexical?(conn: Connection): Promise<void>;
54
56
  assertLexicalReady?(conn: Connection): Promise<void>;
55
57
  createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/types.ts"],"sourcesContent":["// The backing-store interface: a minimal portable statement surface (exec/prepare), plus\n// dedicated interfaces exactly where engines diverge (lexical index, vector scan, raw sql).\n\nimport type { Config, ResolvedConfig, StoreName } from '../config/index.ts';\nimport type { ReconcileDelta } from '../features/types.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\n\n// 'vectors': the store's VectorStore is functionally implemented, not present-but-inert; checked\n// at open (index.ts) before a config with an \"embed\" block is allowed onto a store lacking it.\n// 'segment': the engine can run segment() (Intl.Segmenter grapheme clustering) as a SQL function;\n// turso's client cannot register it and has no rewrite for it, unlike has()/basename(), which now\n// work everywhere. An array, not a bare union, so a runtime check reads the same list the type does.\nexport const CAPABILITY_NAMES = ['vectors', 'segment'] as const;\nexport type Capability = (typeof CAPABILITY_NAMES)[number];\n\nexport interface RunResult {\n changes: number | bigint;\n lastInsertRowid: number | bigint;\n}\n\n// A prepared statement's async surface: every supported engine crosses a real async boundary\n// for a query (DuckDB has no synchronous client), so run/get/all return Promises.\nexport interface Statement {\n run(...params: unknown[]): Promise<RunResult>;\n get(...params: unknown[]): Promise<unknown>;\n all(...params: unknown[]): Promise<unknown[]>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n columns(): Array<{ name: string }>;\n setReadBigInts(enabled: boolean): void;\n}\n\n// Connection surface feature-owned SQL runs against inside a store's hot loops (schema,\n// reconcile), portable so a feature never imports an engine-specific client type.\nexport interface Connection {\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Bulk-inserts rows that cannot conflict, through a path that binds no per-value parameters.\n // Optional: a store that has nothing faster than its own INSERT omits it and callers fall back\n // (appendRows in shared.ts). `columns` names the values each row carries; a table column the\n // caller does not write takes its default.\n appendRows?(table: string, columns: string[], rows: unknown[][]): Promise<void>;\n}\n\n// One reconcile algorithm (src/store/reconcile.ts), parameterised per engine. reconcileContent is\n// the load-bearing member: it lands every content-table change and owns its own multi-step\n// strategy (sqlite FTS5 incremental, duckdb combined delete/insert, turso incremental-or-rebuild).\nexport interface ReconcileDialect {\n // BEGIN mode for the whole reconcile: sqlite/turso 'BEGIN IMMEDIATE', duckdb 'BEGIN'.\n beginMode(): string;\n // Throws SenseError('COLUMN_LIMIT', ...) past this store's own column ceiling and reasoning.\n checkColumnLimit(count: number): void;\n // Adds `names` to frontmatter, already filtered to columns this connection doesn't have yet.\n // sqlite/turso loop (their ADD COLUMN is metadata-only); duckdb issues one statement per call.\n addColumns(conn: Connection, names: string[]): Promise<void>;\n // Replaces this engine's existing-row frontmatter upsert, inside the caller's\n // publication transaction. Preserve non-writable columns and repair missing rows.\n updateFrontmatter?(conn: Connection, writableColumns: string[], rows: unknown[][]): Promise<void>;\n // Reconciles searchable content for `touched` paths and parsed `docs`; `delta` carries the tree\n // state a strategy may need. Must not open its own transaction, and must not return before its own\n // multi-step strategy (e.g. turso's DROP/rebuild) completes.\n reconcileContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, cfg: Config): Promise<void>;\n // Records this reconcile's write-transaction duration. sqlite/turso use it for open()'s derived\n // busy_timeout; duckdb has no such PRAGMA and omits it.\n recordDuration?(conn: Connection, ms: number): Promise<void>;\n}\n\n// One open algorithm (src/store/open.ts), parameterised per engine. `Handle` is whatever this\n// store needs to close the connection and construct its Store (sqlite: {db}; duckdb:\n// {instance, duckdb}; turso: db) -- opaque to the shared orchestration, threaded through unchanged.\nexport interface OpenDialect<Handle> {\n // Cache filename under STATE_DIR, e.g. 'cache.db'.\n filename: string;\n // Cache shape version, independent of the config's own `version`; bumping it rebuilds an existing tree.\n schemaVersion: string;\n reconcileDialect: ReconcileDialect;\n // Opens the physical connection and applies pragmas due before any SQL runs (sqlite: busy_timeout\n // + WAL; turso: connect-time timeout; duckdb: none).\n connect(dbPath: string, cfg: ResolvedConfig, options: { existingOnly: boolean; observational: boolean }): Promise<{ handle: Handle; conn: Connection }>;\n // Releases the handle, for both the rebuild-and-reopen branch and error cleanup on this attempt.\n close(handle: Handle, options?: { observational?: boolean }): Promise<void>;\n // True when connect() failed because another process holds the cache file, which the orchestration\n // retries. Each engine words it differently, so the match is the dialect's own (`native-not-emulated`).\n // Absent for sqlite, whose file lock is shared and whose concurrent-open failure is a different defect.\n // Matching on message text is forced, not chosen: measured 2026-09-02, duckdb throws a plain Error\n // whose only own properties are stack and message, and turso sets code to the constant\n // 'GenericFailure' on every failure alike with rawCode undefined. Neither exposes anything a\n // predicate could switch on, so each dialect pins its engine's wordings and unit-tests them.\n isLocked?(err: Error): boolean;\n // Minimal, configuration-independent schema needed before the builder can capture a planning\n // snapshot. This bootstrap may commit independently because no feature generation is published.\n ensureCoreSchema(handle: Handle, conn: Connection): Promise<void>;\n // Configuration-dependent persistent DDL. The builder invokes this only inside its fenced write\n // transaction, so a failed feature enable cannot leave catalog changes under the old signature.\n ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Connection-local preparation with no durable cache effect (DuckDB's TEMP vector stage).\n prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Installs the derived busy_timeout PRAGMA right before reconcile (sqlite/turso); absent for\n // duckdb, which has no such PRAGMA.\n setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;\n // Persistent lexical preparation belongs to build, while no-build calls only the readiness\n // check. SQLite and Turso maintain lexical state during reconcile and omit both hooks.\n prepareLexical?(conn: Connection): Promise<void>;\n assertLexicalReady?(conn: Connection): Promise<void>;\n createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: { observational: boolean }): Store;\n}\n\nexport interface FieldStat {\n field: string;\n coverage: number;\n type: string;\n}\n\nexport interface DocumentStore {\n // Frontmatter column names (including internal ones; callers filter).\n columns(): Promise<string[]>;\n // Per-column coverage (non-null count) and observed type set, aggregated in one SQL query,\n // never one row per note. `scopeWhere` is a caller-built WHERE fragment, per LexicalQueryOptions.\n fieldStats(columns: string[], scopeWhere: string): Promise<FieldStat[]>;\n}\n\n// A ranked match, nothing more. Snippets are cut from the document above the store\n// (commands/search.ts), identically for every engine, so no store carries them.\nexport interface LexicalHit {\n path: string;\n}\n\nexport interface LexicalQueryOptions {\n whereJoin: string;\n whereCond: string;\n scopeCond: string;\n limit: number;\n}\n\nexport interface LexicalIndex {\n // Ranked word-match query with snippet, scoped by the caller-built SQL fragments (the same\n // fragments narrowByWhere/materializeScope produce elsewhere).\n query(terms: string, opts: LexicalQueryOptions): Promise<LexicalHit[]>;\n}\n\nexport interface VectorCandidate {\n path: string;\n lines: string;\n similarity: number;\n}\n\nexport interface VectorSimilar {\n path: string;\n similarity: number;\n}\n\nexport interface VectorWriteRow {\n path: string;\n chunk: number;\n scale: number;\n vector: Buffer;\n}\n\nexport interface VectorStore {\n // Rows whose vector is NULL: never embedded, or added since.\n pending(): Promise<Array<{ path: string; chunk: number }>>;\n // One batch write per call (never per row) so a provider's embedding batch stays inside a\n // single store method.\n writeVectors(rows: VectorWriteRow[]): Promise<void>;\n candidates(queryVector: Float32Array, storeDims: number, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;\n similar(path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]>;\n hasVector(path: string): Promise<boolean>;\n}\n\n// The `sense sql` passthrough: string in, streamed rows out. Each store registers the same\n// sense-supplied functions and applies its own read-bigints/error-translation behavior.\nexport interface RawStatement {\n columns(): Array<{ name: string }>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n}\n\nexport interface SqlSession {\n prepare(sql: string): Promise<RawStatement>;\n}\n\nexport interface Store {\n readonly name: StoreName;\n readonly capabilities: ReadonlySet<Capability>;\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n // One crossing, N rows: every external write loop (search's candidate insert, graph ring's\n // temp-table writes) goes through this instead of looping over `run()`. Same contract as Connection.runBatch.\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Pins one snapshot across multi-statement reads; `map` and `peek` use it. A network-bound\n // write (search's embedding top-up) must stay outside it. Nesting joins the enclosing transaction.\n transaction<T>(fn: () => Promise<T>): Promise<T>;\n docs: DocumentStore;\n lexical: LexicalIndex;\n vectors: VectorStore;\n raw: SqlSession;\n // Engine-level facts for `sense status` (e.g. a derived busy_timeout PRAGMA reading); each\n // store owns what it reports and how it is worded. Empty when there is nothing to report.\n engineStatus(): Promise<Record<string, string>>;\n close(): Promise<void>;\n}\n"],"names":["CAPABILITY_NAMES"],"mappings":"AAAA,yFAAyF;AACzF,4FAA4F;;;;;+BAW/EA;;;eAAAA;;;AAAN,IAAMA,mBAAmB;IAAC;IAAW;CAAU"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/store/types.ts"],"sourcesContent":["// The backing-store interface: a minimal portable statement surface (exec/prepare), plus\n// dedicated interfaces exactly where engines diverge (lexical index, vector scan, raw sql).\n\nimport type { Config, ResolvedConfig, StoreName } from '../config/index.ts';\nimport type { ReconcileDelta } from '../features/types.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\n\n// 'vectors': the store's VectorStore is functionally implemented, not present-but-inert; checked\n// at open (index.ts) before a config with an \"embed\" block is allowed onto a store lacking it.\n// 'segment': the engine can run segment() (Intl.Segmenter grapheme clustering) as a SQL function;\n// turso's client cannot register it and has no rewrite for it, unlike has()/basename(), which now\n// work everywhere. An array, not a bare union, so a runtime check reads the same list the type does.\nexport const CAPABILITY_NAMES = ['vectors', 'segment'] as const;\nexport type Capability = (typeof CAPABILITY_NAMES)[number];\n\nexport interface RunResult {\n changes: number | bigint;\n lastInsertRowid: number | bigint;\n}\n\n// A prepared statement's async surface: every supported engine crosses a real async boundary\n// for a query (DuckDB has no synchronous client), so run/get/all return Promises.\nexport interface Statement {\n run(...params: unknown[]): Promise<RunResult>;\n get(...params: unknown[]): Promise<unknown>;\n all(...params: unknown[]): Promise<unknown[]>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n columns(): Array<{ name: string }>;\n setReadBigInts(enabled: boolean): void;\n}\n\n// Connection surface feature-owned SQL runs against inside a store's hot loops (schema,\n// reconcile), portable so a feature never imports an engine-specific client type.\nexport interface Connection {\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Bulk-inserts rows that cannot conflict, through a path that binds no per-value parameters.\n // Optional: a store that has nothing faster than its own INSERT omits it and callers fall back\n // (appendRows in shared.ts). `columns` names the values each row carries; a table column the\n // caller does not write takes its default.\n appendRows?(table: string, columns: string[], rows: unknown[][]): Promise<void>;\n}\n\n// One reconcile algorithm (src/store/reconcile.ts), parameterised per engine. reconcileContent is\n// the load-bearing member: it lands every content-table change and owns its own multi-step\n// strategy (sqlite FTS5 incremental, duckdb combined delete/insert, turso incremental-or-rebuild).\nexport interface ReconcileDialect {\n // BEGIN mode for the whole reconcile: sqlite/turso 'BEGIN IMMEDIATE', duckdb 'BEGIN'.\n beginMode(): string;\n // Throws SenseError('COLUMN_LIMIT', ...) past this store's own column ceiling and reasoning.\n checkColumnLimit(count: number): void;\n // Adds `names` to frontmatter, already filtered to columns this connection doesn't have yet.\n // sqlite/turso loop (their ADD COLUMN is metadata-only); duckdb issues one statement per call.\n addColumns(conn: Connection, names: string[]): Promise<void>;\n // Replaces this engine's existing-row frontmatter upsert, inside the caller's\n // publication transaction. Preserve non-writable columns and repair missing rows.\n updateFrontmatter?(conn: Connection, writableColumns: string[], rows: unknown[][]): Promise<void>;\n // Reconciles searchable content for `touched` paths and parsed `docs`; `delta` carries the tree\n // state a strategy may need. Must not open its own transaction, and must not return before its own\n // multi-step strategy (e.g. turso's DROP/rebuild) completes.\n reconcileContent(conn: Connection, touched: string[], docs: ParsedDoc[], delta: ReconcileDelta, cfg: Config): Promise<void>;\n // Records this reconcile's write-transaction duration. sqlite/turso use it for open()'s derived\n // busy_timeout; duckdb has no such PRAGMA and omits it.\n recordDuration?(conn: Connection, ms: number): Promise<void>;\n}\n\n// One open algorithm (src/store/open.ts), parameterised per engine. `Handle` is whatever this\n// store needs to close the connection and construct its Store (sqlite: {db}; duckdb:\n// {instance, duckdb}; turso: db) -- opaque to the shared orchestration, threaded through unchanged.\nexport interface OpenDialect<Handle> {\n // Cache filename under STATE_DIR, e.g. 'cache.db'.\n filename: string;\n // Cache shape version, independent of the config's own `version`; bumping it rebuilds an existing tree.\n schemaVersion: string;\n reconcileDialect: ReconcileDialect;\n // Opens the physical connection and applies pragmas due before any SQL runs (sqlite: busy_timeout\n // + WAL; turso: connect-time timeout; duckdb: none).\n connect(dbPath: string, cfg: ResolvedConfig, options: { existingOnly: boolean; observational: boolean }): Promise<{ handle: Handle; conn: Connection }>;\n // Releases the handle, for both the rebuild-and-reopen branch and error cleanup on this attempt.\n close(handle: Handle, options?: { observational?: boolean }): Promise<void>;\n // True when connect() failed because another process holds the cache file, which the orchestration\n // retries. Each engine words it differently, so the match is the dialect's own (`native-not-emulated`).\n // Absent for sqlite, whose file lock is shared and whose concurrent-open failure is a different defect.\n // Matching on message text is forced, not chosen: measured 2026-09-02, duckdb throws a plain Error\n // whose only own properties are stack and message, and turso sets code to the constant\n // 'GenericFailure' on every failure alike with rawCode undefined. Neither exposes anything a\n // predicate could switch on, so each dialect pins its engine's wordings and unit-tests them.\n isLocked?(err: Error): boolean;\n // Minimal, configuration-independent schema needed before the builder can capture a planning\n // snapshot. This bootstrap may commit independently because no feature generation is published.\n ensureCoreSchema(handle: Handle, conn: Connection): Promise<void>;\n // Configuration-dependent persistent DDL. The builder invokes this only inside its fenced write\n // transaction, so a failed feature enable cannot leave catalog changes under the old signature.\n ensureFeatureSchema(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Connection-local preparation with no durable cache effect (DuckDB's TEMP vector stage).\n prepareConnection?(handle: Handle, conn: Connection, cfg: Config): Promise<void>;\n // Installs the derived busy_timeout PRAGMA right before reconcile (sqlite/turso); absent for\n // duckdb, which has no such PRAGMA.\n setDerivedBusyTimeout?(handle: Handle, conn: Connection, ms: number): Promise<void>;\n // The engine that builds this store's derived index (store/engine.ts): the npm package for duckdb/turso, Node's\n // bundled SQLite for sqlite. A recorded identity that differs rebuilds the cache; checkEngine runs before any build under a new one.\n engineIdentity?(): Promise<string>;\n checkEngine?(): Promise<void>;\n // Persistent lexical preparation belongs to build, while no-build calls only the readiness\n // check. SQLite and Turso maintain lexical state during reconcile and omit both hooks.\n prepareLexical?(conn: Connection): Promise<void>;\n assertLexicalReady?(conn: Connection): Promise<void>;\n createStore(handle: Handle, conn: Connection, cfg: ResolvedConfig, options: { observational: boolean }): Store;\n}\n\nexport interface FieldStat {\n field: string;\n coverage: number;\n type: string;\n}\n\nexport interface DocumentStore {\n // Frontmatter column names (including internal ones; callers filter).\n columns(): Promise<string[]>;\n // Per-column coverage (non-null count) and observed type set, aggregated in one SQL query,\n // never one row per note. `scopeWhere` is a caller-built WHERE fragment, per LexicalQueryOptions.\n fieldStats(columns: string[], scopeWhere: string): Promise<FieldStat[]>;\n}\n\n// A ranked match, nothing more. Snippets are cut from the document above the store\n// (commands/search.ts), identically for every engine, so no store carries them.\nexport interface LexicalHit {\n path: string;\n}\n\nexport interface LexicalQueryOptions {\n whereJoin: string;\n whereCond: string;\n scopeCond: string;\n limit: number;\n}\n\nexport interface LexicalIndex {\n // Ranked word-match query with snippet, scoped by the caller-built SQL fragments (the same\n // fragments narrowByWhere/materializeScope produce elsewhere).\n query(terms: string, opts: LexicalQueryOptions): Promise<LexicalHit[]>;\n}\n\nexport interface VectorCandidate {\n path: string;\n lines: string;\n similarity: number;\n}\n\nexport interface VectorSimilar {\n path: string;\n similarity: number;\n}\n\nexport interface VectorWriteRow {\n path: string;\n chunk: number;\n scale: number;\n vector: Buffer;\n}\n\nexport interface VectorStore {\n // Rows whose vector is NULL: never embedded, or added since.\n pending(): Promise<Array<{ path: string; chunk: number }>>;\n // One batch write per call (never per row) so a provider's embedding batch stays inside a\n // single store method.\n writeVectors(rows: VectorWriteRow[]): Promise<void>;\n candidates(queryVector: Float32Array, storeDims: number, fetch: number, allowed?: Set<string>): Promise<VectorCandidate[]>;\n similar(path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Promise<VectorSimilar[]>;\n hasVector(path: string): Promise<boolean>;\n}\n\n// The `sense sql` passthrough: string in, streamed rows out. Each store registers the same\n// sense-supplied functions and applies its own read-bigints/error-translation behavior.\nexport interface RawStatement {\n columns(): Array<{ name: string }>;\n iterate(...params: unknown[]): AsyncIterable<unknown>;\n}\n\nexport interface SqlSession {\n prepare(sql: string): Promise<RawStatement>;\n}\n\nexport interface Store {\n readonly name: StoreName;\n readonly capabilities: ReadonlySet<Capability>;\n exec(sql: string): Promise<void>;\n prepare(sql: string): Promise<Statement>;\n // One crossing, N rows: every external write loop (search's candidate insert, graph ring's\n // temp-table writes) goes through this instead of looping over `run()`. Same contract as Connection.runBatch.\n runBatch(sql: string, paramRows: unknown[][]): Promise<void>;\n // Pins one snapshot across multi-statement reads; `map` and `peek` use it. A network-bound\n // write (search's embedding top-up) must stay outside it. Nesting joins the enclosing transaction.\n transaction<T>(fn: () => Promise<T>): Promise<T>;\n docs: DocumentStore;\n lexical: LexicalIndex;\n vectors: VectorStore;\n raw: SqlSession;\n // Engine-level facts for `sense status` (e.g. a derived busy_timeout PRAGMA reading); each\n // store owns what it reports and how it is worded. Empty when there is nothing to report.\n engineStatus(): Promise<Record<string, string>>;\n close(): Promise<void>;\n}\n"],"names":["CAPABILITY_NAMES"],"mappings":"AAAA,yFAAyF;AACzF,4FAA4F;;;;;+BAW/EA;;;eAAAA;;;AAAN,IAAMA,mBAAmB;IAAC;IAAW;CAAU"}
@@ -0,0 +1,2 @@
1
+ export declare const ERROR_CODE = 4;
2
+ export declare const USAGE_CODE = 6;
@@ -0,0 +1,2 @@
1
+ export const ERROR_CODE = 4;
2
+ export const USAGE_CODE = 6;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/cli/constants.ts"],"sourcesContent":["export const ERROR_CODE = 4;\nexport const USAGE_CODE = 6;\n"],"names":["ERROR_CODE","USAGE_CODE"],"mappings":"AAAA,OAAO,MAAMA,aAAa,EAAE;AAC5B,OAAO,MAAMC,aAAa,EAAE"}
@@ -3,6 +3,7 @@ import { search } from '../commands/search.js';
3
3
  import { resolveSearch } from '../config/index.js';
4
4
  import { printRows } from '../output/output.js';
5
5
  import { prepareDocumentEmbeddings } from '../store/open.js';
6
+ import { USAGE_CODE } from './constants.js';
6
7
  import { CONFIG, FORMAT, NO_BUILD, parse, parseK, parseSnippetCharLimit, parseSnippetCountLimit, rowFormatOf, runSql, SEARCH_FLAGS, withResolvedDb } from './shared.js';
7
8
  // Fallback when the first positional is not a command: a saved query, { sql } or { search }.
8
9
  // SEARCH_FLAGS override a saved search's fields the same way they override a preset's.
@@ -26,7 +27,7 @@ export default async function named(ctx, queryName) {
26
27
  if (entry === undefined) {
27
28
  console.error(`unknown command or saved entry: "${queryName}"`);
28
29
  console.error(`saved queries: ${Object.keys(cfg.queries).sort().join(', ')}`);
29
- process.exit(2);
30
+ process.exit(USAGE_CODE);
30
31
  }
31
32
  if ('sql' in entry) {
32
33
  // A saved statement written against `scope` is preset-agnostic, so the same entry can be
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/cli/named.ts"],"sourcesContent":["import { scopedPaths } from '../commands/scope.ts';\nimport { search } from '../commands/search.ts';\nimport { resolveSearch } from '../config/index.ts';\nimport { printRows } from '../output/output.ts';\nimport { type BuildRequirement, prepareDocumentEmbeddings } from '../store/open.ts';\nimport { CONFIG, FORMAT, NO_BUILD, parse, parseK, parseSnippetCharLimit, parseSnippetCountLimit, rowFormatOf, runSql, SEARCH_FLAGS, withResolvedDb } from './shared.ts';\nimport type { Ctx } from './types.ts';\n\n// Fallback when the first positional is not a command: a saved query, { sql } or { search }.\n// SEARCH_FLAGS override a saved search's fields the same way they override a preset's.\nexport default async function named(ctx: Ctx, queryName: string): Promise<void> {\n const usage = `usage: ${ctx.name} ${queryName} [params...] [--format table|json|csv] [--config path] [--where \"<sql>\"] [--k n] [--snippet-char-limit n] [--snippet-count-limit n] [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--no-build]`;\n const { values, positionals: params } = parse(ctx.argv, usage, { ...SEARCH_FLAGS, ...NO_BUILD, ...FORMAT, ...CONFIG });\n const format = rowFormatOf(values);\n const configPath = values.config as string | undefined;\n const noBuild = values['no-build'] === true;\n\n const cfg = ctx.resolveConfig(configPath, { writeMigration: !noBuild, query: true });\n const entry = cfg.queries[queryName];\n if (entry === undefined) {\n console.error(`unknown command or saved entry: \"${queryName}\"`);\n console.error(`saved queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n\n if ('sql' in entry) {\n // A saved statement written against `scope` is preset-agnostic, so the same entry can be\n // re-pointed at another layer from the command line instead of being copied per preset.\n await runSql(cfg, entry.sql, params, format, `query \"${queryName}\"`, noBuild, values.preset as string | undefined);\n return;\n }\n\n // A saved search's text is fixed in config; there is nowhere for a positional to bind.\n if (params.length > 0) {\n ctx.usageError(`\"${queryName}\" is a saved search and takes no positional parameters; edit its \"search\" in sense.config.json, or use \"${ctx.name} search\" directly`);\n }\n const k = parseK(values.k as string | undefined, ctx.usageError) ?? entry.k;\n const snippetCharLimit = parseSnippetCharLimit(values['snippet-char-limit'] as string | undefined, ctx.usageError);\n const snippetCountLimit = parseSnippetCountLimit(values['snippet-count-limit'] as string | undefined, ctx.usageError);\n const where = (values.where as string | undefined) ?? entry.where;\n const preset = (values.preset as string | undefined) ?? entry.preset;\n const include = (values.include as string[] | undefined) ?? entry.include;\n const exclude = (values.exclude as string[] | undefined) ?? entry.exclude;\n const noExclude = values['no-exclude'] === true;\n const overrides = { k, snippetCharLimit, snippetCountLimit, where, preset, include, exclude, noExclude };\n await withResolvedDb(\n cfg,\n {\n noBuild,\n requirements: (resolvedCfg) => {\n const signals = resolveSearch(resolvedCfg, overrides).signals;\n const requirements = new Set<BuildRequirement>(['core']);\n if (signals.words !== undefined) requirements.add('lexical');\n return requirements;\n },\n },\n async (store, resolvedCfg, build) => {\n if (build && resolveSearch(resolvedCfg, overrides).signals.vectors !== undefined) {\n await prepareDocumentEmbeddings(store, resolvedCfg, await scopedPaths(store, resolvedCfg, overrides));\n }\n printRows(await search(store, resolvedCfg, entry.search, overrides), format);\n }\n );\n}\n"],"names":["scopedPaths","search","resolveSearch","printRows","prepareDocumentEmbeddings","CONFIG","FORMAT","NO_BUILD","parse","parseK","parseSnippetCharLimit","parseSnippetCountLimit","rowFormatOf","runSql","SEARCH_FLAGS","withResolvedDb","named","ctx","queryName","values","usage","name","positionals","params","argv","format","configPath","config","noBuild","cfg","resolveConfig","writeMigration","query","entry","queries","undefined","console","error","Object","keys","sort","join","process","exit","sql","preset","length","usageError","k","snippetCharLimit","snippetCountLimit","where","include","exclude","noExclude","overrides","requirements","resolvedCfg","signals","Set","words","add","store","build","vectors"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/cli/named.ts"],"sourcesContent":["import { scopedPaths } from '../commands/scope.ts';\nimport { search } from '../commands/search.ts';\nimport { resolveSearch } from '../config/index.ts';\nimport { printRows } from '../output/output.ts';\nimport { type BuildRequirement, prepareDocumentEmbeddings } from '../store/open.ts';\nimport { USAGE_CODE } from './constants.ts';\nimport { CONFIG, FORMAT, NO_BUILD, parse, parseK, parseSnippetCharLimit, parseSnippetCountLimit, rowFormatOf, runSql, SEARCH_FLAGS, withResolvedDb } from './shared.ts';\nimport type { Ctx } from './types.ts';\n\n// Fallback when the first positional is not a command: a saved query, { sql } or { search }.\n// SEARCH_FLAGS override a saved search's fields the same way they override a preset's.\nexport default async function named(ctx: Ctx, queryName: string): Promise<void> {\n const usage = `usage: ${ctx.name} ${queryName} [params...] [--format table|json|csv] [--config path] [--where \"<sql>\"] [--k n] [--snippet-char-limit n] [--snippet-count-limit n] [--preset name] [--include glob ...] [--exclude glob ...] [--no-exclude] [--no-build]`;\n const { values, positionals: params } = parse(ctx.argv, usage, { ...SEARCH_FLAGS, ...NO_BUILD, ...FORMAT, ...CONFIG });\n const format = rowFormatOf(values);\n const configPath = values.config as string | undefined;\n const noBuild = values['no-build'] === true;\n\n const cfg = ctx.resolveConfig(configPath, { writeMigration: !noBuild, query: true });\n const entry = cfg.queries[queryName];\n if (entry === undefined) {\n console.error(`unknown command or saved entry: \"${queryName}\"`);\n console.error(`saved queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(USAGE_CODE);\n }\n\n if ('sql' in entry) {\n // A saved statement written against `scope` is preset-agnostic, so the same entry can be\n // re-pointed at another layer from the command line instead of being copied per preset.\n await runSql(cfg, entry.sql, params, format, `query \"${queryName}\"`, noBuild, values.preset as string | undefined);\n return;\n }\n\n // A saved search's text is fixed in config; there is nowhere for a positional to bind.\n if (params.length > 0) {\n ctx.usageError(`\"${queryName}\" is a saved search and takes no positional parameters; edit its \"search\" in sense.config.json, or use \"${ctx.name} search\" directly`);\n }\n const k = parseK(values.k as string | undefined, ctx.usageError) ?? entry.k;\n const snippetCharLimit = parseSnippetCharLimit(values['snippet-char-limit'] as string | undefined, ctx.usageError);\n const snippetCountLimit = parseSnippetCountLimit(values['snippet-count-limit'] as string | undefined, ctx.usageError);\n const where = (values.where as string | undefined) ?? entry.where;\n const preset = (values.preset as string | undefined) ?? entry.preset;\n const include = (values.include as string[] | undefined) ?? entry.include;\n const exclude = (values.exclude as string[] | undefined) ?? entry.exclude;\n const noExclude = values['no-exclude'] === true;\n const overrides = { k, snippetCharLimit, snippetCountLimit, where, preset, include, exclude, noExclude };\n await withResolvedDb(\n cfg,\n {\n noBuild,\n requirements: (resolvedCfg) => {\n const signals = resolveSearch(resolvedCfg, overrides).signals;\n const requirements = new Set<BuildRequirement>(['core']);\n if (signals.words !== undefined) requirements.add('lexical');\n return requirements;\n },\n },\n async (store, resolvedCfg, build) => {\n if (build && resolveSearch(resolvedCfg, overrides).signals.vectors !== undefined) {\n await prepareDocumentEmbeddings(store, resolvedCfg, await scopedPaths(store, resolvedCfg, overrides));\n }\n printRows(await search(store, resolvedCfg, entry.search, overrides), format);\n }\n );\n}\n"],"names":["scopedPaths","search","resolveSearch","printRows","prepareDocumentEmbeddings","USAGE_CODE","CONFIG","FORMAT","NO_BUILD","parse","parseK","parseSnippetCharLimit","parseSnippetCountLimit","rowFormatOf","runSql","SEARCH_FLAGS","withResolvedDb","named","ctx","queryName","values","usage","name","positionals","params","argv","format","configPath","config","noBuild","cfg","resolveConfig","writeMigration","query","entry","queries","undefined","console","error","Object","keys","sort","join","process","exit","sql","preset","length","usageError","k","snippetCharLimit","snippetCountLimit","where","include","exclude","noExclude","overrides","requirements","resolvedCfg","signals","Set","words","add","store","build","vectors"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { columnHint } from '../output/column-hint.js';
4
4
  import { printRowStream } from '../output/output.js';
5
5
  import { searchError } from '../output/search-error.js';
6
6
  import { openStoreFor } from '../store/index.js';
7
+ import { USAGE_CODE } from './constants.js';
7
8
  // Spreadable option fragments -- one flag name keeps one meaning across every command's table.
8
9
  export const FORMAT = {
9
10
  format: {
@@ -75,7 +76,7 @@ function pickFormat(values, allowed) {
75
76
  const format = String(values.format);
76
77
  if (allowed.includes(format)) return format;
77
78
  console.error(`unknown --format "${format}"; expected ${allowed.join(', ')}`);
78
- process.exit(2);
79
+ process.exit(USAGE_CODE);
79
80
  }
80
81
  export function formatOf(values) {
81
82
  return pickFormat(values, [
@@ -92,7 +93,7 @@ export function rowFormatOf(values) {
92
93
  'csv'
93
94
  ]);
94
95
  }
95
- // Per-command parseArgs: strict (a foreign flag exits 2), and every table gets --help for free.
96
+ // Per-command parseArgs rejects foreign flags as usage errors; every table gets --help for free.
96
97
  export function parse(argv, usage, options) {
97
98
  let values;
98
99
  let positionals;
@@ -113,7 +114,7 @@ export function parse(argv, usage, options) {
113
114
  } catch (err) {
114
115
  console.error(err.message);
115
116
  console.error(usage);
116
- process.exit(2);
117
+ process.exit(USAGE_CODE);
117
118
  }
118
119
  if (values.help) {
119
120
  console.log(usage);
@@ -185,14 +186,14 @@ export async function runSql(cfg, sql, params, format, label, noBuild, preset) {
185
186
  const placeholderCount = ((_sql_match = sql.match(/\?/g)) !== null && _sql_match !== void 0 ? _sql_match : []).length;
186
187
  if (params.length !== placeholderCount) {
187
188
  console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);
188
- process.exit(2);
189
+ process.exit(USAGE_CODE);
189
190
  }
190
191
  // Naming a preset and never joining it would return the whole index while reading as scoped,
191
192
  // which is the one hazard of binding rather than applying. Refuse instead.
192
193
  if (preset !== undefined && !/\bscope\b/i.test(sql)) {
193
194
  console.error(`--preset binds a temporary "scope" table of the preset's paths, and ${label} never joins it, so the preset would have no effect`);
194
195
  console.error(`add: JOIN scope ON scope."path" = <table>."path"`);
195
- process.exit(2);
196
+ process.exit(USAGE_CODE);
196
197
  }
197
198
  await withResolvedDb(cfg, {
198
199
  noBuild,
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/cli/shared.ts"],"sourcesContent":["import type { ParseArgsOptionsConfig } from 'node:util';\nimport { parseArgs } from 'node:util';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { resolvePreset } from '../config/index.ts';\nimport { columnHint } from '../output/column-hint.ts';\nimport type { Row, RowFormat } from '../output/output.ts';\nimport { printRowStream } from '../output/output.ts';\nimport { searchError } from '../output/search-error.ts';\nimport { openStoreFor } from '../store/index.ts';\nimport type { BuildRequirement, OpenResult } from '../store/open.ts';\nimport type { Store } from '../store/types.ts';\nimport type { Ctx } from './types.ts';\n\n// Spreadable option fragments -- one flag name keeps one meaning across every command's table.\nexport const FORMAT: ParseArgsOptionsConfig = { format: { type: 'string', default: 'table' } };\nexport const CONFIG: ParseArgsOptionsConfig = { config: { type: 'string' } };\nexport const NO_BUILD: ParseArgsOptionsConfig = { 'no-build': { type: 'boolean', default: false } };\n// The scope vocabulary every scoped command shares: named preset, ad hoc include/exclude\n// globs, a where SQL condition. search adds k on top.\nexport const SCOPE: ParseArgsOptionsConfig = {\n where: { type: 'string' },\n preset: { type: 'string' },\n include: { type: 'string', multiple: true },\n exclude: { type: 'string', multiple: true },\n // Widening, which no other flag can express: --include and --exclude each override their\n // own side of the preset, so neither can drop an exclusion the preset declares.\n 'no-exclude': { type: 'boolean', default: false },\n};\nexport const SEARCH_FLAGS: ParseArgsOptionsConfig = { ...SCOPE, k: { type: 'string' }, 'snippet-char-limit': { type: 'string' }, 'snippet-count-limit': { type: 'string' } };\n\ntype Values = Record<string, string | boolean | string[] | undefined>;\n\n// The SCOPE fragment's parsed values as the overrides every scoped command passes down.\n// One place to read them: a new scope field is added to the fragment and here, not per-command.\nexport function scopeOf(values: Values): SearchOverrides {\n return {\n preset: values.preset as string | undefined,\n include: values.include as string[] | undefined,\n exclude: values.exclude as string[] | undefined,\n where: values.where as string | undefined,\n noExclude: values['no-exclude'] === true,\n };\n}\n\n// parseArgs is strict about flag names; this applies the same strictness to the value.\n// An unrecognised --format is a usage error, not a silent fallback to `table`.\nfunction pickFormat<T extends string>(values: Values, allowed: readonly T[]): T {\n const format = String(values.format);\n if ((allowed as readonly string[]).includes(format)) return format as T;\n console.error(`unknown --format \"${format}\"; expected ${allowed.join(', ')}`);\n process.exit(2);\n}\n\nexport function formatOf(values: Values): 'table' | 'json' {\n return pickFormat(values, ['table', 'json'] as const);\n}\n\n// csv is a row set rendered as rows; map, peek, status, and path render structures instead,\n// and take formatOf.\nexport function rowFormatOf(values: Values): RowFormat {\n return pickFormat(values, ['table', 'json', 'csv'] as const);\n}\n\n// Per-command parseArgs: strict (a foreign flag exits 2), and every table gets --help for free.\nexport function parse(argv: string[], usage: string, options: ParseArgsOptionsConfig): { values: Values; positionals: string[] } {\n let values: Values;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: { ...options, help: { type: 'boolean', default: false, short: 'h' } },\n strict: true,\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage);\n process.exit(2);\n }\n if (values.help) {\n console.log(usage);\n process.exit(0);\n }\n return { values, positionals };\n}\n\n// --k must be a positive integer: SQLite reads a bound LIMIT of -1 as \"unlimited\" and 0 as \"nothing\", and parseInt would silently truncate \"5.9\".\n// All three are caller mistakes worth a usage error, matching the config-level SavedSearch validation.\nexport function parseK(k: string | undefined, usageError: (message: string) => never): number | undefined {\n if (k === undefined) return undefined;\n const parsed = Number(k);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--k expects a positive integer, got \"${k}\"`);\n return parsed;\n}\n\n// Same positive-integer rule as --k, beside it since both flags come from SEARCH_FLAGS.\nexport function parseSnippetCharLimit(value: string | undefined, usageError: (message: string) => never): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--snippet-char-limit expects a positive integer, got \"${value}\"`);\n return parsed;\n}\n\nexport function parseSnippetCountLimit(value: string | undefined, usageError: (message: string) => never): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--snippet-count-limit expects a positive integer, got \"${value}\"`);\n return parsed;\n}\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport interface DbOpenOptions {\n noBuild: boolean;\n requirements: ReadonlySet<BuildRequirement> | ((cfg: ResolvedConfig) => ReadonlySet<BuildRequirement>);\n}\n\nexport async function withResolvedDb(cfg: ResolvedConfig, options: DbOpenOptions, fn: (store: OpenResult['store'], cfg: ResolvedConfig, build: boolean) => void | Promise<void>): Promise<void> {\n const requirements = typeof options.requirements === 'function' ? options.requirements(cfg) : options.requirements;\n const build = !options.noBuild && cfg.build !== false;\n const { store, warnings } = await openStoreFor(cfg, {\n build,\n requirements,\n });\n printWarnings(warnings);\n try {\n await fn(store, cfg, build);\n } finally {\n await store.close();\n }\n}\n\nexport async function withDb(ctx: Ctx, configPath: string | undefined, options: DbOpenOptions, fn: (store: OpenResult['store'], cfg: ResolvedConfig, build: boolean) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig(configPath, { writeMigration: !options.noBuild, query: true });\n return withResolvedDb(cfg, options, fn);\n}\n\n// `--preset` on SQL binds a temp `scope` table rather than transparently filtering: temp views shadowing the base tables can't cover `content`, since FTS5 MATCH treats the table name as a hidden column.\n// A flag that silently scoped three tables of four would look scoped and not be.\nasync function bindScope(store: Store, cfg: ResolvedConfig, preset: string): Promise<void> {\n const { name } = resolvePreset(cfg, preset); // unknown names throw, listing what is declared\n await store.exec('DROP TABLE IF EXISTS temp.scope');\n await store.exec('CREATE TEMP TABLE scope (\"path\" TEXT PRIMARY KEY)');\n const insert = await store.prepare('INSERT INTO temp.scope (\"path\") SELECT \"path\" FROM preset_files WHERE preset = ?');\n await insert.run(name);\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport async function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: RowFormat, label: string, noBuild: boolean, preset?: string): Promise<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 // Naming a preset and never joining it would return the whole index while reading as scoped,\n // which is the one hazard of binding rather than applying. Refuse instead.\n if (preset !== undefined && !/\\bscope\\b/i.test(sql)) {\n console.error(`--preset binds a temporary \"scope\" table of the preset's paths, and ${label} never joins it, so the preset would have no effect`);\n console.error(`add: JOIN scope ON scope.\"path\" = <table>.\"path\"`);\n process.exit(2);\n }\n await withResolvedDb(cfg, { noBuild, requirements: new Set<BuildRequirement>(['core']) }, async (store) => {\n if (preset !== undefined) await bindScope(store, cfg, preset);\n // Streamed, not collected: `sql`'s result size is the caller's business, not ours. columns() reads the statement's own metadata, so a 0-row csv gets a header too.\n // A mid-stream SQLite error (e.g. SQLITE_BUSY outlasting busy_timeout) may leave partial output; the nonzero exit code is the failure signal, not a completeness guarantee.\n try {\n const statement = await store.raw.prepare(sql);\n const columns = statement.columns().map((c) => c.name);\n await printRowStream(statement.iterate(...params) as AsyncIterable<Row>, format, columns);\n } catch (err) {\n const hinted = columnHint(await store.docs.columns(), err as Error); // columns read while the store is still open\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5 punctuation trap applies to its bound parameters.\n // SQL without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(hinted, params.join(' '));\n throw hinted;\n }\n });\n}\n"],"names":["parseArgs","resolvePreset","columnHint","printRowStream","searchError","openStoreFor","FORMAT","format","type","default","CONFIG","config","NO_BUILD","SCOPE","where","preset","include","multiple","exclude","SEARCH_FLAGS","k","scopeOf","values","noExclude","pickFormat","allowed","String","includes","console","error","join","process","exit","formatOf","rowFormatOf","parse","argv","usage","options","positionals","args","help","short","strict","allowPositionals","err","message","log","parseK","usageError","undefined","parsed","Number","isInteger","parseSnippetCharLimit","value","parseSnippetCountLimit","printWarnings","warnings","w","warn","withResolvedDb","cfg","fn","requirements","build","noBuild","store","close","withDb","ctx","configPath","resolveConfig","writeMigration","query","bindScope","name","exec","insert","prepare","run","runSql","sql","params","label","placeholderCount","match","length","test","Set","statement","raw","columns","map","c","iterate","hinted","docs"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/cli/shared.ts"],"sourcesContent":["import type { ParseArgsOptionsConfig } from 'node:util';\nimport { parseArgs } from 'node:util';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { resolvePreset } from '../config/index.ts';\nimport { columnHint } from '../output/column-hint.ts';\nimport type { Row, RowFormat } from '../output/output.ts';\nimport { printRowStream } from '../output/output.ts';\nimport { searchError } from '../output/search-error.ts';\nimport { openStoreFor } from '../store/index.ts';\nimport type { BuildRequirement, OpenResult } from '../store/open.ts';\nimport type { Store } from '../store/types.ts';\nimport { USAGE_CODE } from './constants.ts';\nimport type { Ctx } from './types.ts';\n\n// Spreadable option fragments -- one flag name keeps one meaning across every command's table.\nexport const FORMAT: ParseArgsOptionsConfig = { format: { type: 'string', default: 'table' } };\nexport const CONFIG: ParseArgsOptionsConfig = { config: { type: 'string' } };\nexport const NO_BUILD: ParseArgsOptionsConfig = { 'no-build': { type: 'boolean', default: false } };\n// The scope vocabulary every scoped command shares: named preset, ad hoc include/exclude\n// globs, a where SQL condition. search adds k on top.\nexport const SCOPE: ParseArgsOptionsConfig = {\n where: { type: 'string' },\n preset: { type: 'string' },\n include: { type: 'string', multiple: true },\n exclude: { type: 'string', multiple: true },\n // Widening, which no other flag can express: --include and --exclude each override their\n // own side of the preset, so neither can drop an exclusion the preset declares.\n 'no-exclude': { type: 'boolean', default: false },\n};\nexport const SEARCH_FLAGS: ParseArgsOptionsConfig = { ...SCOPE, k: { type: 'string' }, 'snippet-char-limit': { type: 'string' }, 'snippet-count-limit': { type: 'string' } };\n\ntype Values = Record<string, string | boolean | string[] | undefined>;\n\n// The SCOPE fragment's parsed values as the overrides every scoped command passes down.\n// One place to read them: a new scope field is added to the fragment and here, not per-command.\nexport function scopeOf(values: Values): SearchOverrides {\n return {\n preset: values.preset as string | undefined,\n include: values.include as string[] | undefined,\n exclude: values.exclude as string[] | undefined,\n where: values.where as string | undefined,\n noExclude: values['no-exclude'] === true,\n };\n}\n\n// parseArgs is strict about flag names; this applies the same strictness to the value.\n// An unrecognised --format is a usage error, not a silent fallback to `table`.\nfunction pickFormat<T extends string>(values: Values, allowed: readonly T[]): T {\n const format = String(values.format);\n if ((allowed as readonly string[]).includes(format)) return format as T;\n console.error(`unknown --format \"${format}\"; expected ${allowed.join(', ')}`);\n process.exit(USAGE_CODE);\n}\n\nexport function formatOf(values: Values): 'table' | 'json' {\n return pickFormat(values, ['table', 'json'] as const);\n}\n\n// csv is a row set rendered as rows; map, peek, status, and path render structures instead,\n// and take formatOf.\nexport function rowFormatOf(values: Values): RowFormat {\n return pickFormat(values, ['table', 'json', 'csv'] as const);\n}\n\n// Per-command parseArgs rejects foreign flags as usage errors; every table gets --help for free.\nexport function parse(argv: string[], usage: string, options: ParseArgsOptionsConfig): { values: Values; positionals: string[] } {\n let values: Values;\n let positionals: string[];\n try {\n ({ values, positionals } = parseArgs({\n args: argv,\n options: { ...options, help: { type: 'boolean', default: false, short: 'h' } },\n strict: true,\n allowPositionals: true,\n }));\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage);\n process.exit(USAGE_CODE);\n }\n if (values.help) {\n console.log(usage);\n process.exit(0);\n }\n return { values, positionals };\n}\n\n// --k must be a positive integer: SQLite reads a bound LIMIT of -1 as \"unlimited\" and 0 as \"nothing\", and parseInt would silently truncate \"5.9\".\n// All three are caller mistakes worth a usage error, matching the config-level SavedSearch validation.\nexport function parseK(k: string | undefined, usageError: (message: string) => never): number | undefined {\n if (k === undefined) return undefined;\n const parsed = Number(k);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--k expects a positive integer, got \"${k}\"`);\n return parsed;\n}\n\n// Same positive-integer rule as --k, beside it since both flags come from SEARCH_FLAGS.\nexport function parseSnippetCharLimit(value: string | undefined, usageError: (message: string) => never): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--snippet-char-limit expects a positive integer, got \"${value}\"`);\n return parsed;\n}\n\nexport function parseSnippetCountLimit(value: string | undefined, usageError: (message: string) => never): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) usageError(`--snippet-count-limit expects a positive integer, got \"${value}\"`);\n return parsed;\n}\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport interface DbOpenOptions {\n noBuild: boolean;\n requirements: ReadonlySet<BuildRequirement> | ((cfg: ResolvedConfig) => ReadonlySet<BuildRequirement>);\n}\n\nexport async function withResolvedDb(cfg: ResolvedConfig, options: DbOpenOptions, fn: (store: OpenResult['store'], cfg: ResolvedConfig, build: boolean) => void | Promise<void>): Promise<void> {\n const requirements = typeof options.requirements === 'function' ? options.requirements(cfg) : options.requirements;\n const build = !options.noBuild && cfg.build !== false;\n const { store, warnings } = await openStoreFor(cfg, {\n build,\n requirements,\n });\n printWarnings(warnings);\n try {\n await fn(store, cfg, build);\n } finally {\n await store.close();\n }\n}\n\nexport async function withDb(ctx: Ctx, configPath: string | undefined, options: DbOpenOptions, fn: (store: OpenResult['store'], cfg: ResolvedConfig, build: boolean) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig(configPath, { writeMigration: !options.noBuild, query: true });\n return withResolvedDb(cfg, options, fn);\n}\n\n// `--preset` on SQL binds a temp `scope` table rather than transparently filtering: temp views shadowing the base tables can't cover `content`, since FTS5 MATCH treats the table name as a hidden column.\n// A flag that silently scoped three tables of four would look scoped and not be.\nasync function bindScope(store: Store, cfg: ResolvedConfig, preset: string): Promise<void> {\n const { name } = resolvePreset(cfg, preset); // unknown names throw, listing what is declared\n await store.exec('DROP TABLE IF EXISTS temp.scope');\n await store.exec('CREATE TEMP TABLE scope (\"path\" TEXT PRIMARY KEY)');\n const insert = await store.prepare('INSERT INTO temp.scope (\"path\") SELECT \"path\" FROM preset_files WHERE preset = ?');\n await insert.run(name);\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport async function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: RowFormat, label: string, noBuild: boolean, preset?: string): Promise<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(USAGE_CODE);\n }\n // Naming a preset and never joining it would return the whole index while reading as scoped,\n // which is the one hazard of binding rather than applying. Refuse instead.\n if (preset !== undefined && !/\\bscope\\b/i.test(sql)) {\n console.error(`--preset binds a temporary \"scope\" table of the preset's paths, and ${label} never joins it, so the preset would have no effect`);\n console.error(`add: JOIN scope ON scope.\"path\" = <table>.\"path\"`);\n process.exit(USAGE_CODE);\n }\n await withResolvedDb(cfg, { noBuild, requirements: new Set<BuildRequirement>(['core']) }, async (store) => {\n if (preset !== undefined) await bindScope(store, cfg, preset);\n // Streamed, not collected: `sql`'s result size is the caller's business, not ours. columns() reads the statement's own metadata, so a 0-row csv gets a header too.\n // A mid-stream SQLite error (e.g. SQLITE_BUSY outlasting busy_timeout) may leave partial output; the nonzero exit code is the failure signal, not a completeness guarantee.\n try {\n const statement = await store.raw.prepare(sql);\n const columns = statement.columns().map((c) => c.name);\n await printRowStream(statement.iterate(...params) as AsyncIterable<Row>, format, columns);\n } catch (err) {\n const hinted = columnHint(await store.docs.columns(), err as Error); // columns read while the store is still open\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5 punctuation trap applies to its bound parameters.\n // SQL without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(hinted, params.join(' '));\n throw hinted;\n }\n });\n}\n"],"names":["parseArgs","resolvePreset","columnHint","printRowStream","searchError","openStoreFor","USAGE_CODE","FORMAT","format","type","default","CONFIG","config","NO_BUILD","SCOPE","where","preset","include","multiple","exclude","SEARCH_FLAGS","k","scopeOf","values","noExclude","pickFormat","allowed","String","includes","console","error","join","process","exit","formatOf","rowFormatOf","parse","argv","usage","options","positionals","args","help","short","strict","allowPositionals","err","message","log","parseK","usageError","undefined","parsed","Number","isInteger","parseSnippetCharLimit","value","parseSnippetCountLimit","printWarnings","warnings","w","warn","withResolvedDb","cfg","fn","requirements","build","noBuild","store","close","withDb","ctx","configPath","resolveConfig","writeMigration","query","bindScope","name","exec","insert","prepare","run","runSql","sql","params","label","placeholderCount","match","length","test","Set","statement","raw","columns","map","c","iterate","hinted","docs"],"mappings":"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"}
package/dist/esm/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { parseArgs } from 'node:util';
3
+ import { ERROR_CODE, USAGE_CODE } from './cli/constants.js';
3
4
  import { COMMANDS, USAGE } from './cli/index.js';
4
5
  import { loadConfig, SUPPORTED_CONFIG_VERSION } from './config/index.js';
5
6
  // Parsing and dispatch only; commands lazy-load from src/cli/, so nothing heavy is imported
@@ -43,14 +44,14 @@ function resolveConfigFor(name, configPath, options = {}) {
43
44
  }
44
45
  return cfg;
45
46
  }
46
- // Thrown errors -> exit 1 with the message verbatim; usage errors exit 2 directly.
47
+ // Thrown errors exit with ERROR_CODE and the message verbatim; usage errors exit with USAGE_CODE.
47
48
  export default async function cli(argv, name) {
48
49
  // A closed pipe reader's EPIPE arrives async, after the write loop returns: console.log absorbs it, process.stdout.write (streaming formats) does not, so an unhandled listener kills the process.
49
50
  // A throw here is uncaught (async); process.exit() would skip `sense watch`'s cleanup, leaving a heartbeat behind that makes a dead watcher look alive.
50
51
  process.stdout.on('error', (err)=>{
51
52
  if (err.code === 'EPIPE') return;
52
53
  console.error(`sense: ${err.message}`);
53
- process.exitCode = 1;
54
+ process.exitCode = ERROR_CODE;
54
55
  });
55
56
  const first = argv[0];
56
57
  // No command word, or the first token looks like a flag: the only flags this level knows.
@@ -83,7 +84,7 @@ export default async function cli(argv, name) {
83
84
  } catch (err) {
84
85
  console.error(err.message);
85
86
  console.error(usage(name));
86
- process.exit(2);
87
+ process.exit(USAGE_CODE);
87
88
  }
88
89
  try {
89
90
  if (values.version) {
@@ -105,12 +106,12 @@ export default async function cli(argv, name) {
105
106
  }
106
107
  } catch (err) {
107
108
  console.error(err.message);
108
- process.exit(1);
109
+ process.exit(ERROR_CODE);
109
110
  }
110
111
  console.error(usage(name));
111
- process.exit(2);
112
+ process.exit(USAGE_CODE);
112
113
  }
113
- // Breaking renames get one release of a pointer instead of an "unknown" error: find ->
114
+ // Renamed verbs get a pointer to their current name instead of the unknown-entry error: find ->
114
115
  // search, and query -> sql (which took its name back from the search sense of "query").
115
116
  const RENAMED = {
116
117
  find: 'search',
@@ -119,7 +120,7 @@ export default async function cli(argv, name) {
119
120
  if (RENAMED[first]) {
120
121
  console.error(`${name}: ${first} is now ${RENAMED[first]}`);
121
122
  console.error(usage(name));
122
- process.exit(2);
123
+ process.exit(USAGE_CODE);
123
124
  }
124
125
  const ctx = {
125
126
  name,
@@ -127,7 +128,7 @@ export default async function cli(argv, name) {
127
128
  resolveConfig: (configPath, options)=>resolveConfigFor(name, configPath, options),
128
129
  usageError (message) {
129
130
  console.error(message);
130
- process.exit(2);
131
+ process.exit(USAGE_CODE);
131
132
  }
132
133
  };
133
134
  try {
@@ -136,6 +137,6 @@ export default async function cli(argv, name) {
136
137
  else await (await import('./cli/named.js')).default(ctx, first);
137
138
  } catch (err) {
138
139
  console.error(err.message);
139
- process.exit(1);
140
+ process.exit(ERROR_CODE);
140
141
  }
141
142
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/open-source/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 type { LoadConfigOptions } from './config/index.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, options: LoadConfigOptions & { query?: boolean } = {}) {\n // Read the query policy before allowing a migration to write configuration state.\n let cfg = loadConfig(configPath, options.query ? { writeMigration: false } : options);\n const writeMigration = options.writeMigration !== false && (!options.query || cfg.build !== false);\n if (options.query && writeMigration && cfg.migratedFrom !== undefined) cfg = loadConfig(configPath);\n if (cfg.migratedFrom !== undefined) {\n console.warn(\n !writeMigration\n ? `${name}: ${cfg.configPath} needs migration from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}; using the migrated form in memory only (run ${name} build to update the file)`\n : `${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`\n );\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 // A closed pipe reader's EPIPE arrives async, after the write loop returns: console.log absorbs it, process.stdout.write (streaming formats) does not, so an unhandled listener kills the process.\n // A throw here is uncaught (async); process.exit() would skip `sense watch`'s cleanup, 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, options) => resolveConfigFor(name, configPath, options),\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","options","cfg","query","writeMigration","build","migratedFrom","undefined","console","warn","unknownKeys","cli","argv","process","stdout","on","err","code","error","message","exitCode","first","startsWith","args","type","default","short","help","list","config","allowPositionals","exit","log","queries","queryName","keys","sort","RENAMED","find","ctx","slice","resolveConfig","usageError"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { parseArgs } from 'node:util';\nimport { ERROR_CODE, USAGE_CODE } from './cli/constants.ts';\nimport { COMMANDS, USAGE } from './cli/index.ts';\nimport type { Ctx } from './cli/types.ts';\nimport type { LoadConfigOptions } from './config/index.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, options: LoadConfigOptions & { query?: boolean } = {}) {\n // Read the query policy before allowing a migration to write configuration state.\n let cfg = loadConfig(configPath, options.query ? { writeMigration: false } : options);\n const writeMigration = options.writeMigration !== false && (!options.query || cfg.build !== false);\n if (options.query && writeMigration && cfg.migratedFrom !== undefined) cfg = loadConfig(configPath);\n if (cfg.migratedFrom !== undefined) {\n console.warn(\n !writeMigration\n ? `${name}: ${cfg.configPath} needs migration from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}; using the migrated form in memory only (run ${name} build to update the file)`\n : `${name}: migrated ${cfg.configPath} from config version ${cfg.migratedFrom} to ${SUPPORTED_CONFIG_VERSION}`\n );\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 with ERROR_CODE and the message verbatim; usage errors exit with USAGE_CODE.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n // A closed pipe reader's EPIPE arrives async, after the write loop returns: console.log absorbs it, process.stdout.write (streaming formats) does not, so an unhandled listener kills the process.\n // A throw here is uncaught (async); process.exit() would skip `sense watch`'s cleanup, 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 = ERROR_CODE;\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(USAGE_CODE);\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(ERROR_CODE);\n }\n\n console.error(usage(name));\n process.exit(USAGE_CODE);\n }\n\n // Renamed verbs get a pointer to their current name instead of the unknown-entry 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(USAGE_CODE);\n }\n\n const ctx: Ctx = {\n name,\n argv: argv.slice(1),\n resolveConfig: (configPath, options) => resolveConfigFor(name, configPath, options),\n usageError(message) {\n console.error(message);\n process.exit(USAGE_CODE);\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(ERROR_CODE);\n }\n}\n"],"names":["createRequire","parseArgs","ERROR_CODE","USAGE_CODE","COMMANDS","USAGE","loadConfig","SUPPORTED_CONFIG_VERSION","packageVersion","load","url","rel","pkg","name","version","usage","lines","Object","values","map","u","join","resolveConfigFor","configPath","options","cfg","query","writeMigration","build","migratedFrom","undefined","console","warn","unknownKeys","cli","argv","process","stdout","on","err","code","error","message","exitCode","first","startsWith","args","type","default","short","help","list","config","allowPositionals","exit","log","queries","queryName","keys","sort","RENAMED","find","ctx","slice","resolveConfig","usageError"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { checkLanguageFit } from './langfit.js';
12
12
  import { getProvider } from './registry.js';
13
13
  import { STORE_DIMS } from './types.js';
14
14
  // Slice + re-normalize (Matryoshka); optionally round through int8 storage. Exported for
15
- // benchmark/lib/embed.mjs, which scores the same lever math offline.
15
+ // benchmark/lib/embed.ts, which scores the same lever math offline.
16
16
  export function toStore(full, dims, int8) {
17
17
  const v = new Float32Array(dims);
18
18
  let norm = 0;