@voidwire/lore 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.ts CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  type ObservationSubtype,
57
57
  type ObservationConfidence,
58
58
  } from "./index";
59
+ import { isValidLoreType, LORE_TYPES } from "./lib/types";
59
60
 
60
61
  // ============================================================================
61
62
  // Argument Parsing
@@ -204,6 +205,33 @@ async function handleSearch(args: string[]): Promise<void> {
204
205
  const limit = parsed.has("limit") ? parseInt(parsed.get("limit")!, 10) : 20;
205
206
  const since = parsed.get("since");
206
207
  const project = parsed.get("project");
208
+ const type = parseList(parsed.get("type"));
209
+
210
+ // Validate type values against LoreType enum
211
+ if (type) {
212
+ const invalid = type.filter((t) => !isValidLoreType(t));
213
+ if (invalid.length > 0) {
214
+ fail(
215
+ `Invalid type: ${invalid.join(", ")}. Valid types: ${LORE_TYPES.join(", ")}`,
216
+ );
217
+ }
218
+ }
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;
207
235
 
208
236
  // Handle prismis passthrough
209
237
  if (source === "prismis") {
@@ -250,7 +278,12 @@ async function handleSearch(args: string[]): Promise<void> {
250
278
  // FTS5 path (explicit --exact only)
251
279
  if (exact) {
252
280
  try {
253
- const results = search(query, { source, limit, since });
281
+ const results = search(query, {
282
+ source: effectiveSource,
283
+ limit,
284
+ since,
285
+ type,
286
+ });
254
287
  output({
255
288
  success: true,
256
289
  results,
@@ -278,7 +311,12 @@ async function handleSearch(args: string[]): Promise<void> {
278
311
  // Semantic-only path (explicit --semantic)
279
312
  if (semanticOnly) {
280
313
  try {
281
- const results = await semanticSearch(query, { source, limit, project });
314
+ const results = await semanticSearch(query, {
315
+ source: effectiveSource,
316
+ limit,
317
+ project,
318
+ type,
319
+ });
282
320
 
283
321
  if (brief) {
284
322
  console.log(formatBriefSearch(results));
@@ -304,10 +342,11 @@ async function handleSearch(args: string[]): Promise<void> {
304
342
  // Hybrid path (default) - combines vector + keyword
305
343
  try {
306
344
  const results = await hybridSearch(query, {
307
- source,
345
+ source: effectiveSource,
308
346
  limit,
309
347
  project,
310
348
  since,
349
+ type,
311
350
  });
312
351
 
313
352
  if (brief) {
@@ -817,6 +856,8 @@ Usage:
817
856
 
818
857
  Search Options:
819
858
  --exact Use FTS5 text search (bypasses semantic search)
859
+ --source <src> Filter by source (comma-separated: captures,teachings)
860
+ --type <types> Filter by knowledge type (gotcha, decision, learning, etc.)
820
861
  --limit <n> Maximum results (default: 20)
821
862
  --project <name> Filter results by project
822
863
  --brief Compact output (titles only)
@@ -864,6 +905,10 @@ Capture Types:
864
905
 
865
906
  Examples:
866
907
  lore search "authentication"
908
+ lore search --source=captures "sable"
909
+ lore search --source=captures,teachings "schema"
910
+ lore search --type=gotcha "sable"
911
+ lore search --type=gotcha,decision "lore"
867
912
  lore search blogs "typescript patterns"
868
913
  lore sources
869
914
  lore list development
@@ -888,6 +933,12 @@ Search Modes:
888
933
  --semantic Vector search only
889
934
 
890
935
  Options:
936
+ --source <src> Filter by source (pre-filters before search)
937
+ Comma-separated: --source=captures,teachings
938
+ --type <types> Filter by knowledge type (pre-filters before search)
939
+ Comma-separated: --type=gotcha,decision
940
+ Valid: gotcha, decision, pattern, learning, preference,
941
+ term, style, teaching, task, todo, idea
891
942
  --limit <n> Maximum results (default: 20)
892
943
  --project <name> Filter results by project/topic
893
944
  --brief Compact output (titles only)
@@ -919,6 +970,11 @@ See also:
919
970
 
920
971
  Examples:
921
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
976
+ lore search --type=gotcha "sable" # filter by type
977
+ lore search --type=gotcha,decision "lore" # multiple types
922
978
  lore search --exact "def process_data" # keyword only
923
979
  lore search --semantic "login flow concepts" # vector only
924
980
  lore search blogs "typescript patterns"
package/lib/search.ts CHANGED
@@ -19,9 +19,10 @@ 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
+ type?: string | string[];
25
26
  }
26
27
 
27
28
  function getDatabasePath(): string {
@@ -69,8 +70,29 @@ export function search(
69
70
  const params: (string | number)[] = [escapeFts5Query(query)];
70
71
 
71
72
  if (options.source) {
72
- conditions.push("source = ?");
73
- 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
+ }
84
+ }
85
+
86
+ if (options.type) {
87
+ const types = Array.isArray(options.type) ? options.type : [options.type];
88
+ const typeClauses = types.map(
89
+ () =>
90
+ "(json_extract(metadata, '$.type') = ? OR json_extract(metadata, '$.subtype') = ?)",
91
+ );
92
+ conditions.push(`(${typeClauses.join(" OR ")})`);
93
+ types.forEach((t) => {
94
+ params.push(t, t);
95
+ });
74
96
  }
75
97
 
76
98
  if (options.since) {
package/lib/semantic.ts CHANGED
@@ -22,9 +22,10 @@ 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
+ type?: string | string[];
28
29
  }
29
30
 
30
31
  /**
@@ -221,8 +222,17 @@ export async function semanticSearch(
221
222
  params.push(limit);
222
223
 
223
224
  if (options.source) {
224
- conditions.push("e.source = ?");
225
- 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
+ }
226
236
  }
227
237
 
228
238
  if (options.project) {
@@ -230,6 +240,13 @@ export async function semanticSearch(
230
240
  params.push(options.project);
231
241
  }
232
242
 
243
+ if (options.type) {
244
+ const types = Array.isArray(options.type) ? options.type : [options.type];
245
+ const placeholders = types.map(() => "?").join(", ");
246
+ conditions.push(`e.type IN (${placeholders})`);
247
+ params.push(...types);
248
+ }
249
+
233
250
  sql = `
234
251
  SELECT
235
252
  s.rowid,
@@ -270,10 +287,11 @@ export interface HybridResult {
270
287
  }
271
288
 
272
289
  export interface HybridSearchOptions {
273
- source?: string;
290
+ source?: string | string[];
274
291
  limit?: number;
275
292
  project?: string;
276
293
  since?: string;
294
+ type?: string | string[];
277
295
  vectorWeight?: number;
278
296
  textWeight?: number;
279
297
  }
@@ -325,12 +343,14 @@ export async function hybridSearch(
325
343
  source: options.source,
326
344
  limit: fetchLimit,
327
345
  project: options.project,
346
+ type: options.type,
328
347
  }),
329
348
  Promise.resolve(
330
349
  keywordSearch(query, {
331
350
  source: options.source,
332
351
  limit: fetchLimit,
333
352
  since: options.since,
353
+ type: options.type,
334
354
  }),
335
355
  ),
336
356
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -19,6 +19,11 @@
19
19
  "README.md",
20
20
  "LICENSE"
21
21
  ],
22
+ "scripts": {
23
+ "build": "tsc --noEmit false --outDir dist --declaration",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "bun test"
26
+ },
22
27
  "keywords": [
23
28
  "knowledge",
24
29
  "search",
@@ -47,10 +52,5 @@
47
52
  },
48
53
  "devDependencies": {
49
54
  "bun-types": "1.3.5"
50
- },
51
- "scripts": {
52
- "build": "tsc --noEmit false --outDir dist --declaration",
53
- "typecheck": "tsc --noEmit",
54
- "test": "bun test"
55
55
  }
56
- }
56
+ }