@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 +32 -3
- package/lib/realtime.ts +39 -7
- package/lib/search.ts +12 -3
- package/lib/semantic.ts +13 -4
- package/package.json +1 -1
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, {
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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;
|