@voidwire/lore 0.1.13 → 0.1.15

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
@@ -185,6 +185,7 @@ async function handleSearch(args: string[]): Promise<void> {
185
185
 
186
186
  const limit = parsed.has("limit") ? parseInt(parsed.get("limit")!, 10) : 20;
187
187
  const since = parsed.get("since");
188
+ const project = parsed.get("project");
188
189
 
189
190
  // Handle prismis passthrough
190
191
  if (source === "prismis") {
@@ -255,7 +256,7 @@ async function handleSearch(args: string[]): Promise<void> {
255
256
  }
256
257
 
257
258
  try {
258
- const results = await semanticSearch(query, { source, limit });
259
+ const results = await semanticSearch(query, { source, limit, project });
259
260
  output({
260
261
  success: true,
261
262
  results,
@@ -318,9 +319,10 @@ function handleList(args: string[]): void {
318
319
  ? parseInt(parsed.get("limit")!, 10)
319
320
  : undefined;
320
321
  const format = parsed.get("format") || "json";
322
+ const project = parsed.get("project");
321
323
 
322
324
  try {
323
- const result = list(domain, { limit });
325
+ const result = list(domain, { limit, project });
324
326
 
325
327
  if (format === "human") {
326
328
  console.log(formatHumanOutput(result));
@@ -580,6 +582,7 @@ Usage:
580
582
  Search Options:
581
583
  --exact Use FTS5 text search (bypasses semantic search)
582
584
  --limit <n> Maximum results (default: 20)
585
+ --project <name> Filter results by project
583
586
  --since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
584
587
  --sources List indexed sources with counts
585
588
 
@@ -637,6 +640,7 @@ Usage:
637
640
  Options:
638
641
  --exact Use FTS5 text search (bypasses semantic search)
639
642
  --limit <n> Maximum results (default: 20)
643
+ --project <name> Filter results by project (post-filters KNN results)
640
644
  --since <date> Filter by date (today, yesterday, this-week, YYYY-MM-DD)
641
645
  --sources List indexed sources with counts
642
646
  --help Show this help
@@ -664,6 +668,7 @@ Examples:
664
668
  lore search "authentication"
665
669
  lore search blogs "typescript patterns"
666
670
  lore search commits --since this-week "refactor"
671
+ lore search "authentication" --project=momentum --limit 5
667
672
  lore search --exact "def process_data"
668
673
  lore search prismis "kubernetes security"
669
674
  lore search atuin "docker build"
@@ -682,6 +687,7 @@ Usage:
682
687
  Options:
683
688
  --limit <n> Maximum entries (default: all)
684
689
  --format <fmt> Output format: json (default), jsonl, human
690
+ --project <name> Filter by project name
685
691
  --domains List available domains
686
692
  --help Show this help
687
693
 
@@ -707,6 +713,7 @@ Available Domains:
707
713
  Examples:
708
714
  lore list development
709
715
  lore list commits --limit 10 --format human
716
+ lore list commits --project=momentum --limit 5
710
717
  lore list books --format jsonl
711
718
  `);
712
719
  process.exit(0);
package/lib/list.ts CHANGED
@@ -59,8 +59,18 @@ const PERSONAL_SUBTYPES: Partial<Record<Domain, string>> = {
59
59
  habits: "habit",
60
60
  };
61
61
 
62
+ // Maps source to metadata field containing project name
63
+ const PROJECT_FIELD: Record<string, string> = {
64
+ commits: "project",
65
+ sessions: "project",
66
+ tasks: "project",
67
+ captures: "context",
68
+ teachings: "source",
69
+ };
70
+
62
71
  export interface ListOptions {
63
72
  limit?: number;
73
+ project?: string;
64
74
  }
65
75
 
66
76
  export interface ListEntry {
@@ -93,13 +103,27 @@ function queryBySource(
93
103
  db: Database,
94
104
  source: string,
95
105
  limit?: number,
106
+ project?: string,
96
107
  ): ListEntry[] {
97
- const sql = limit
98
- ? `SELECT title, content, metadata FROM search WHERE source = ? LIMIT ?`
99
- : `SELECT title, content, metadata FROM search WHERE source = ?`;
108
+ let sql = "SELECT title, content, metadata FROM search WHERE source = ?";
109
+ const params: (string | number)[] = [source];
110
+
111
+ // Add project filter if provided and source has a project field
112
+ if (project) {
113
+ const field = PROJECT_FIELD[source];
114
+ if (field) {
115
+ sql += ` AND json_extract(metadata, '$.${field}') = ?`;
116
+ params.push(project);
117
+ }
118
+ }
119
+
120
+ if (limit) {
121
+ sql += " LIMIT ?";
122
+ params.push(limit);
123
+ }
100
124
 
101
- const stmt = limit ? db.prepare(sql) : db.prepare(sql);
102
- const rows = (limit ? stmt.all(source, limit) : stmt.all(source)) as RawRow[];
125
+ const stmt = db.prepare(sql);
126
+ const rows = stmt.all(...params) as RawRow[];
103
127
 
104
128
  return rows.map((row) => ({
105
129
  title: row.title,
@@ -166,7 +190,7 @@ export function list(domain: Domain, options: ListOptions = {}): ListResult {
166
190
  if (personalType) {
167
191
  entries = queryPersonalType(db, personalType, options.limit);
168
192
  } else {
169
- entries = queryBySource(db, domain, options.limit);
193
+ entries = queryBySource(db, domain, options.limit, options.project);
170
194
  }
171
195
 
172
196
  return {
package/lib/semantic.ts CHANGED
@@ -32,8 +32,21 @@ export interface SemanticResult {
32
32
  export interface SemanticSearchOptions {
33
33
  source?: string;
34
34
  limit?: number;
35
+ project?: string;
35
36
  }
36
37
 
38
+ /**
39
+ * Maps source types to their project field name in metadata JSON.
40
+ * Different sources store project names in different fields.
41
+ */
42
+ const PROJECT_FIELD: Record<string, string> = {
43
+ commits: "project",
44
+ sessions: "project",
45
+ tasks: "project",
46
+ captures: "context",
47
+ teachings: "source",
48
+ };
49
+
37
50
  const MODEL_NAME = "nomic-ai/nomic-embed-text-v1.5";
38
51
 
39
52
  interface EmbeddingPipeline {
@@ -223,6 +236,24 @@ export async function semanticSearch(
223
236
  const stmt = db.prepare(sql);
224
237
  const results = stmt.all(...params) as SemanticResult[];
225
238
 
239
+ // Post-filter by project if specified
240
+ // KNN WHERE clause doesn't support json_extract on joined metadata,
241
+ // so we filter after the query returns
242
+ if (options.project) {
243
+ return results.filter((result) => {
244
+ const field = PROJECT_FIELD[result.source];
245
+ if (!field) return false;
246
+
247
+ try {
248
+ const metadata = JSON.parse(result.metadata);
249
+ return metadata[field] === options.project;
250
+ } catch {
251
+ // Skip results with malformed metadata
252
+ return false;
253
+ }
254
+ });
255
+ }
256
+
226
257
  return results;
227
258
  } finally {
228
259
  db.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",