@voidwire/lore 0.8.0 → 0.9.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.
package/cli.ts CHANGED
@@ -217,6 +217,22 @@ async function handleSearch(args: string[]): Promise<void> {
217
217
  }
218
218
  }
219
219
 
220
+ // Parse --source flag (comma-separated list)
221
+ const sourceFilter = parseList(parsed.get("source"));
222
+
223
+ // Validate source filter values against SOURCES constant
224
+ if (sourceFilter) {
225
+ const invalid = sourceFilter.filter((s) => !SOURCES.includes(s as Source));
226
+ if (invalid.length > 0) {
227
+ fail(
228
+ `Invalid source: ${invalid.join(", ")}. Valid sources: ${SOURCES.join(", ")}`,
229
+ );
230
+ }
231
+ }
232
+
233
+ // Resolve effective source: --source flag takes precedence over positional arg
234
+ const effectiveSource: string | string[] | undefined = sourceFilter ?? source;
235
+
220
236
  // Handle prismis passthrough
221
237
  if (source === "prismis") {
222
238
  searchPrismis(query, { limit })
@@ -262,7 +278,12 @@ async function handleSearch(args: string[]): Promise<void> {
262
278
  // FTS5 path (explicit --exact only)
263
279
  if (exact) {
264
280
  try {
265
- const results = search(query, { source, limit, since, type });
281
+ const results = search(query, {
282
+ source: effectiveSource,
283
+ limit,
284
+ since,
285
+ type,
286
+ });
266
287
  output({
267
288
  success: true,
268
289
  results,
@@ -291,7 +312,7 @@ async function handleSearch(args: string[]): Promise<void> {
291
312
  if (semanticOnly) {
292
313
  try {
293
314
  const results = await semanticSearch(query, {
294
- source,
315
+ source: effectiveSource,
295
316
  limit,
296
317
  project,
297
318
  type,
@@ -321,7 +342,7 @@ async function handleSearch(args: string[]): Promise<void> {
321
342
  // Hybrid path (default) - combines vector + keyword
322
343
  try {
323
344
  const results = await hybridSearch(query, {
324
- source,
345
+ source: effectiveSource,
325
346
  limit,
326
347
  project,
327
348
  since,
@@ -835,6 +856,7 @@ Usage:
835
856
 
836
857
  Search Options:
837
858
  --exact Use FTS5 text search (bypasses semantic search)
859
+ --source <src> Filter by source (comma-separated: captures,teachings)
838
860
  --type <types> Filter by knowledge type (gotcha, decision, learning, etc.)
839
861
  --limit <n> Maximum results (default: 20)
840
862
  --project <name> Filter results by project
@@ -883,6 +905,8 @@ Capture Types:
883
905
 
884
906
  Examples:
885
907
  lore search "authentication"
908
+ lore search --source=captures "sable"
909
+ lore search --source=captures,teachings "schema"
886
910
  lore search --type=gotcha "sable"
887
911
  lore search --type=gotcha,decision "lore"
888
912
  lore search blogs "typescript patterns"
@@ -909,6 +933,8 @@ Search Modes:
909
933
  --semantic Vector search only
910
934
 
911
935
  Options:
936
+ --source <src> Filter by source (pre-filters before search)
937
+ Comma-separated: --source=captures,teachings
912
938
  --type <types> Filter by knowledge type (pre-filters before search)
913
939
  Comma-separated: --type=gotcha,decision
914
940
  Valid: gotcha, decision, pattern, learning, preference,
@@ -944,6 +970,9 @@ See also:
944
970
 
945
971
  Examples:
946
972
  lore search "authentication" # hybrid (default)
973
+ lore search --source=captures "sable" # filter by source
974
+ lore search --source=captures,teachings "schema" # multiple sources
975
+ lore search --source=flux --type=todo "sable" # combined filters
947
976
  lore search --type=gotcha "sable" # filter by type
948
977
  lore search --type=gotcha,decision "lore" # multiple types
949
978
  lore search --exact "def process_data" # keyword only
package/lib/realtime.ts CHANGED
@@ -87,13 +87,15 @@ function insertSearchEntry(db: Database, event: CaptureEvent): number {
87
87
  const title = buildTitle(event);
88
88
  const content = getContentForEmbedding(event);
89
89
  const metadata = buildMetadata(event);
90
+ const data = event.data as Record<string, unknown>;
91
+ const topic = String(data.topic || "");
90
92
 
91
93
  const stmt = db.prepare(`
92
- INSERT INTO search (source, title, content, metadata)
93
- VALUES (?, ?, ?, ?)
94
+ INSERT INTO search (source, title, content, metadata, topic)
95
+ VALUES (?, ?, ?, ?, ?)
94
96
  `);
95
97
 
96
- const result = stmt.run(source, title, content, metadata);
98
+ const result = stmt.run(source, title, content, metadata, topic);
97
99
  return Number(result.lastInsertRowid);
98
100
  }
99
101
 
@@ -147,10 +149,13 @@ function buildTitle(event: CaptureEvent): string {
147
149
 
148
150
  /**
149
151
  * Extract content for embedding from event
152
+ * Concatenates topic+content for richer embeddings (matches lore-embed-all)
150
153
  */
151
154
  function getContentForEmbedding(event: CaptureEvent): string {
152
155
  const data = event.data as Record<string, unknown>;
153
- return String(data.content || data.text || "");
156
+ const content = String(data.content || data.text || "");
157
+ const topic = String(data.topic || "").trim();
158
+ return topic ? `${topic} ${content}`.trim() : content;
154
159
  }
155
160
 
156
161
  /**
@@ -255,13 +260,40 @@ function insertEmbedding(
255
260
  const source = getSourceForEvent(event);
256
261
  const data = event.data as Record<string, unknown>;
257
262
  const topic = String(data.topic || "");
263
+ const type = extractType(event);
258
264
 
259
265
  const embeddingBlob = serializeEmbedding(embedding);
260
266
 
261
267
  const stmt = db.prepare(`
262
- INSERT INTO embeddings (doc_id, chunk_idx, source, topic, embedding)
263
- VALUES (?, 0, ?, ?, ?)
268
+ INSERT INTO embeddings (doc_id, chunk_idx, source, topic, type, embedding)
269
+ VALUES (?, 0, ?, ?, ?, ?)
264
270
  `);
265
271
 
266
- stmt.run(docId, source, topic, embeddingBlob);
272
+ stmt.run(docId, source, topic, type, embeddingBlob);
273
+ }
274
+
275
+ /**
276
+ * Extract type value for embeddings partition column
277
+ */
278
+ function extractType(event: CaptureEvent): string {
279
+ const data = event.data as Record<string, unknown>;
280
+
281
+ switch (event.type) {
282
+ case "knowledge":
283
+ return String(data.subtype || "general");
284
+ case "teaching":
285
+ return "teaching";
286
+ case "observation":
287
+ return String(data.subtype || "pattern");
288
+ case "insight":
289
+ return String(data.subtype || "insight");
290
+ case "learning":
291
+ return "learning";
292
+ case "task":
293
+ return "task";
294
+ case "note":
295
+ return "note";
296
+ default:
297
+ return "general";
298
+ }
267
299
  }
package/lib/search.ts CHANGED
@@ -19,7 +19,7 @@ export interface SearchResult {
19
19
  }
20
20
 
21
21
  export interface SearchOptions {
22
- source?: string;
22
+ source?: string | string[];
23
23
  limit?: number;
24
24
  since?: string;
25
25
  type?: string | string[];
@@ -70,8 +70,17 @@ export function search(
70
70
  const params: (string | number)[] = [escapeFts5Query(query)];
71
71
 
72
72
  if (options.source) {
73
- conditions.push("source = ?");
74
- params.push(options.source);
73
+ const sources = Array.isArray(options.source)
74
+ ? options.source
75
+ : [options.source];
76
+ if (sources.length === 1) {
77
+ conditions.push("source = ?");
78
+ params.push(sources[0]);
79
+ } else {
80
+ const placeholders = sources.map(() => "?").join(", ");
81
+ conditions.push(`source IN (${placeholders})`);
82
+ params.push(...sources);
83
+ }
75
84
  }
76
85
 
77
86
  if (options.type) {
package/lib/semantic.ts CHANGED
@@ -22,7 +22,7 @@ export interface SemanticResult {
22
22
  }
23
23
 
24
24
  export interface SemanticSearchOptions {
25
- source?: string;
25
+ source?: string | string[];
26
26
  limit?: number;
27
27
  project?: string;
28
28
  type?: string | string[];
@@ -222,8 +222,17 @@ export async function semanticSearch(
222
222
  params.push(limit);
223
223
 
224
224
  if (options.source) {
225
- conditions.push("e.source = ?");
226
- params.push(options.source);
225
+ const sources = Array.isArray(options.source)
226
+ ? options.source
227
+ : [options.source];
228
+ if (sources.length === 1) {
229
+ conditions.push("e.source = ?");
230
+ params.push(sources[0]);
231
+ } else {
232
+ const placeholders = sources.map(() => "?").join(", ");
233
+ conditions.push(`e.source IN (${placeholders})`);
234
+ params.push(...sources);
235
+ }
227
236
  }
228
237
 
229
238
  if (options.project) {
@@ -278,7 +287,7 @@ export interface HybridResult {
278
287
  }
279
288
 
280
289
  export interface HybridSearchOptions {
281
- source?: string;
290
+ source?: string | string[];
282
291
  limit?: number;
283
292
  project?: string;
284
293
  since?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",