@voidwire/lore 0.1.4 → 0.1.6

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
@@ -11,7 +11,7 @@
11
11
  * lore search <query> Search all sources
12
12
  * lore search <source> <query> Search specific source
13
13
  * lore list <domain> List domain entries
14
- * lore capture task|knowledge|note Capture knowledge
14
+ * lore capture task|knowledge|note|teaching Capture knowledge
15
15
  *
16
16
  * Exit codes:
17
17
  * 0 - Success
@@ -29,6 +29,7 @@ import {
29
29
  captureTask,
30
30
  captureKnowledge,
31
31
  captureNote,
32
+ captureTeaching,
32
33
  semanticSearch,
33
34
  isOllamaAvailable,
34
35
  hasEmbeddings,
@@ -40,6 +41,7 @@ import {
40
41
  type TaskInput,
41
42
  type KnowledgeInput,
42
43
  type NoteInput,
44
+ type TeachingInput,
43
45
  type KnowledgeCaptureType,
44
46
  } from "./index";
45
47
 
@@ -224,48 +226,51 @@ async function handleSearch(args: string[]): Promise<void> {
224
226
  return;
225
227
  }
226
228
 
227
- // Route semantic vs FTS5 based on --exact flag and availability
228
- if (!exact) {
229
+ // FTS5 path (explicit --exact only)
230
+ if (exact) {
229
231
  try {
230
- const canUseSemantic = hasEmbeddings() && (await isOllamaAvailable());
231
- if (canUseSemantic) {
232
- const results = await semanticSearch(query, { source, limit });
233
- output({
234
- success: true,
235
- results,
236
- count: results.length,
237
- mode: "semantic",
238
- });
239
- console.error(
240
- `✅ ${results.length} result${results.length !== 1 ? "s" : ""} found (semantic)`,
241
- );
242
- process.exit(0);
243
- }
244
- // Fall through to FTS5 if semantic not available
245
- } catch (error) {
246
- // Semantic search failed, fall back to FTS5
232
+ const results = search(query, { source, limit, since });
233
+ output({
234
+ success: true,
235
+ results,
236
+ count: results.length,
237
+ mode: "exact",
238
+ });
247
239
  console.error(
248
- `⚠️ Semantic search unavailable: ${error instanceof Error ? error.message : "Unknown error"}`,
240
+ `✅ ${results.length} result${results.length !== 1 ? "s" : ""} found (exact)`,
249
241
  );
242
+ process.exit(0);
243
+ } catch (error) {
244
+ const message = error instanceof Error ? error.message : "Unknown error";
245
+ fail(message, 2);
250
246
  }
247
+ return;
248
+ }
249
+
250
+ // Semantic path (default) - fail if unavailable
251
+ if (!hasEmbeddings()) {
252
+ fail("No embeddings found. Run lore-embed-all first.", 2);
253
+ }
254
+
255
+ if (!(await isOllamaAvailable())) {
256
+ fail("Ollama not available. Start Ollama or check SQLITE_VEC_PATH.", 2);
251
257
  }
252
258
 
253
- // FTS5 path (default fallback or explicit --exact)
254
259
  try {
255
- const results = search(query, { source, limit, since });
260
+ const results = await semanticSearch(query, { source, limit });
256
261
  output({
257
262
  success: true,
258
263
  results,
259
264
  count: results.length,
260
- mode: exact ? "exact" : "fts5",
265
+ mode: "semantic",
261
266
  });
262
267
  console.error(
263
- `✅ ${results.length} result${results.length !== 1 ? "s" : ""} found (${exact ? "exact" : "fts5"})`,
268
+ `✅ ${results.length} result${results.length !== 1 ? "s" : ""} found (semantic)`,
264
269
  );
265
270
  process.exit(0);
266
271
  } catch (error) {
267
272
  const message = error instanceof Error ? error.message : "Unknown error";
268
- fail(message, 2);
273
+ fail(`Semantic search failed: ${message}`, 2);
269
274
  }
270
275
  }
271
276
 
@@ -433,6 +438,34 @@ function handleCaptureNote(args: string[]): void {
433
438
  }
434
439
  }
435
440
 
441
+ function handleCaptureTeaching(args: string[]): void {
442
+ const parsed = parseArgs(args);
443
+
444
+ const required = ["domain", "confidence", "text"];
445
+ const missing = required.filter((f) => !parsed.has(f));
446
+ if (missing.length > 0) {
447
+ fail(`Missing required fields: ${missing.join(", ")}`);
448
+ }
449
+
450
+ const input: TeachingInput = {
451
+ domain: parsed.get("domain")!,
452
+ confidence: parsed.get("confidence")!,
453
+ text: parsed.get("text")!,
454
+ source: parsed.get("source"),
455
+ };
456
+
457
+ const result = captureTeaching(input);
458
+ output(result);
459
+
460
+ if (result.success) {
461
+ console.error("✅ Teaching logged");
462
+ process.exit(0);
463
+ } else {
464
+ console.error(`❌ ${result.error}`);
465
+ process.exit(2);
466
+ }
467
+ }
468
+
436
469
  function handleCapture(args: string[]): void {
437
470
  if (hasFlag(args, "help")) {
438
471
  showCaptureHelp();
@@ -455,9 +488,12 @@ function handleCapture(args: string[]): void {
455
488
  case "note":
456
489
  handleCaptureNote(captureArgs);
457
490
  break;
491
+ case "teaching":
492
+ handleCaptureTeaching(captureArgs);
493
+ break;
458
494
  default:
459
495
  fail(
460
- `Unknown capture type: ${captureType}. Use: task, knowledge, or note`,
496
+ `Unknown capture type: ${captureType}. Use: task, knowledge, note, or teaching`,
461
497
  );
462
498
  }
463
499
  }
@@ -481,7 +517,7 @@ Usage:
481
517
  lore search --sources List indexed sources
482
518
  lore list <domain> List domain entries
483
519
  lore list --domains List available domains
484
- lore capture task|knowledge|note Capture knowledge
520
+ lore capture task|knowledge|note|teaching Capture knowledge
485
521
 
486
522
  Search Options:
487
523
  --exact Use FTS5 text search (bypasses semantic search)
@@ -515,6 +551,12 @@ Capture Types:
515
551
  --tags Comma-separated tags
516
552
  --context Optional context
517
553
 
554
+ teaching Log teaching/learning
555
+ --domain Subject area (required)
556
+ --confidence Certainty level (required)
557
+ --text Teaching content (required)
558
+ --source Optional source identifier
559
+
518
560
  Examples:
519
561
  lore search "authentication"
520
562
  lore search blogs "typescript patterns"
@@ -620,6 +662,7 @@ Usage:
620
662
  lore capture task Log task completion
621
663
  lore capture knowledge Log insight/learning
622
664
  lore capture note Quick note
665
+ lore capture teaching Log teaching moment
623
666
 
624
667
  Capture Types:
625
668
 
@@ -651,10 +694,19 @@ Capture Types:
651
694
  --tags Comma-separated tags
652
695
  --context Optional context
653
696
 
697
+ teaching - Log teaching or learning moment
698
+ Required:
699
+ --domain Subject area (e.g., typescript, architecture)
700
+ --confidence Certainty level (e.g., high, medium, low)
701
+ --text Teaching content
702
+ Optional:
703
+ --source Source identifier (defaults to "manual")
704
+
654
705
  Examples:
655
706
  lore capture task --project=lore --name="Add help" --problem="No subcommand help" --solution="Added per-command help functions"
656
707
  lore capture knowledge --context=lore --text="Unified CLI works" --type=learning
657
708
  lore capture note --text="Remember to update docs" --tags=docs,todo
709
+ lore capture teaching --domain=patterns --confidence=high --text="Prefer composition over inheritance"
658
710
  `);
659
711
  process.exit(0);
660
712
  }
package/index.ts CHANGED
@@ -46,11 +46,13 @@ export {
46
46
  captureKnowledge,
47
47
  captureTask,
48
48
  captureNote,
49
+ captureTeaching,
49
50
  type CaptureResult,
50
51
  type KnowledgeInput,
51
52
  type KnowledgeCaptureType,
52
53
  type TaskInput,
53
54
  type NoteInput,
55
+ type TeachingInput,
54
56
  type CaptureEvent,
55
57
  } from "./lib/capture";
56
58
 
package/lib/capture.ts CHANGED
@@ -50,6 +50,13 @@ export interface NoteInput {
50
50
  context?: string;
51
51
  }
52
52
 
53
+ export interface TeachingInput {
54
+ domain: string;
55
+ confidence: string;
56
+ text: string;
57
+ source?: string;
58
+ }
59
+
53
60
  interface TaskEvent {
54
61
  event: "captured";
55
62
  type: "task";
@@ -91,7 +98,19 @@ interface NoteEvent {
91
98
  };
92
99
  }
93
100
 
94
- type CaptureEvent = TaskEvent | KnowledgeEvent | NoteEvent;
101
+ interface TeachingEvent {
102
+ event: "captured";
103
+ type: "teaching";
104
+ timestamp: string;
105
+ data: {
106
+ domain: string;
107
+ confidence: string;
108
+ text: string;
109
+ source: string;
110
+ };
111
+ }
112
+
113
+ type CaptureEvent = TaskEvent | KnowledgeEvent | NoteEvent | TeachingEvent;
95
114
 
96
115
  function getLogPath(): string {
97
116
  const dataHome =
@@ -209,4 +228,23 @@ export function captureNote(input: NoteInput): CaptureResult {
209
228
  return writeEvent(event);
210
229
  }
211
230
 
231
+ /**
232
+ * Capture a teaching moment
233
+ */
234
+ export function captureTeaching(input: TeachingInput): CaptureResult {
235
+ const event: TeachingEvent = {
236
+ event: "captured",
237
+ type: "teaching",
238
+ timestamp: "",
239
+ data: {
240
+ domain: input.domain,
241
+ confidence: input.confidence,
242
+ text: input.text,
243
+ source: input.source || "manual",
244
+ },
245
+ };
246
+
247
+ return writeEvent(event);
248
+ }
249
+
212
250
  export type { CaptureEvent };
package/lib/semantic.ts CHANGED
@@ -223,8 +223,8 @@ export async function semanticSearch(
223
223
 
224
224
  const limit = options.limit ?? 20;
225
225
 
226
- // KNN query with join to search table
227
- // Group by doc_id to return best chunk per document
226
+ // KNN query - 1:1 mapping between search rows and embeddings
227
+ // Content is pre-chunked at ingest time
228
228
  let sql: string;
229
229
  const params: (Uint8Array | string | number)[] = [queryBlob];
230
230
 
@@ -235,17 +235,16 @@ export async function semanticSearch(
235
235
  s.title,
236
236
  s.content,
237
237
  s.metadata,
238
- MIN(e.distance) as distance
238
+ e.distance
239
239
  FROM embeddings e
240
240
  JOIN search s ON e.doc_id = s.rowid
241
241
  WHERE e.embedding MATCH ?
242
242
  AND k = ?
243
243
  AND s.source = ?
244
- GROUP BY s.rowid
245
- ORDER BY distance
244
+ ORDER BY e.distance
246
245
  LIMIT ?
247
246
  `;
248
- params.push(limit * 3); // Fetch more for grouping
247
+ params.push(limit);
249
248
  params.push(options.source);
250
249
  params.push(limit);
251
250
  } else {
@@ -255,16 +254,15 @@ export async function semanticSearch(
255
254
  s.title,
256
255
  s.content,
257
256
  s.metadata,
258
- MIN(e.distance) as distance
257
+ e.distance
259
258
  FROM embeddings e
260
259
  JOIN search s ON e.doc_id = s.rowid
261
260
  WHERE e.embedding MATCH ?
262
261
  AND k = ?
263
- GROUP BY s.rowid
264
- ORDER BY distance
262
+ ORDER BY e.distance
265
263
  LIMIT ?
266
264
  `;
267
- params.push(limit * 3); // Fetch more for grouping
265
+ params.push(limit);
268
266
  params.push(limit);
269
267
  }
270
268
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",