@voidwire/lore 1.0.8 → 1.1.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.
@@ -13,6 +13,7 @@
13
13
  import { readFileSync, statSync, existsSync } from "fs";
14
14
  import { join } from "path";
15
15
  import { checkPath, type IndexerContext } from "../indexer";
16
+ import { complete } from "@voidwire/llm-core";
16
17
 
17
18
  function fileMtime(path: string): string {
18
19
  return statSync(path).mtime.toISOString();
@@ -24,7 +25,74 @@ function toISO(dateStr: string, fallback: string): string {
24
25
  return s.includes("T") ? s : `${s.slice(0, 10)}T00:00:00Z`;
25
26
  }
26
27
 
28
+ const ENRICH_PROMPTS: Record<string, string> = {
29
+ person: `You are enriching a personal contact entry for search indexing.
30
+ The "relationship" field is the EXACT relationship — do NOT add other relationship types.
31
+ Generate synonyms and alternative phrasings ONLY for the stated relationship.
32
+ Example: relationship "uncle" → uncle, family member, relative, parent's brother, parent's sibling. NOT: cousin, nephew, aunt.
33
+ Example: relationship "daughter" → daughter, child, kid, offspring, family member. NOT: son, niece, nephew.
34
+ Include both singular and plural forms where applicable.
35
+ Keep under 80 words. Output only the description, no headers or formatting.`,
36
+ book: `You are enriching a book entry for search indexing.
37
+ Generate: genre, themes, subject matter, and related topics.
38
+ Include both singular and plural forms of key terms.
39
+ Keep under 80 words. Output only the description, no headers or formatting.`,
40
+ movie: `You are enriching a movie entry for search indexing.
41
+ Generate: genre, themes, notable actors or director if well-known, and related topics.
42
+ Include both singular and plural forms of key terms.
43
+ Keep under 80 words. Output only the description, no headers or formatting.`,
44
+ interest: `You are enriching a personal interest entry for search indexing.
45
+ Generate: related activities, domains, synonyms, and common alternative phrasings.
46
+ Include both singular and plural forms.
47
+ Keep under 80 words. Output only the description, no headers or formatting.`,
48
+ habit: `You are enriching a personal habit/routine entry for search indexing.
49
+ Generate: related routines, synonyms, categories, and common alternative phrasings.
50
+ Include both singular and plural forms.
51
+ Keep under 80 words. Output only the description, no headers or formatting.`,
52
+ };
53
+
54
+ const ENRICH_TIMEOUT_MS = 30_000;
55
+ const ENRICH_MAX_FAILURES = 3;
56
+
57
+ let enrichmentFailures = 0;
58
+
59
+ async function enrich(type: string, input: string): Promise<string | null> {
60
+ if (enrichmentFailures >= ENRICH_MAX_FAILURES) return null;
61
+ const systemPrompt = ENRICH_PROMPTS[type];
62
+ if (!systemPrompt) return null;
63
+ try {
64
+ const result = await Promise.race([
65
+ complete({
66
+ prompt: input,
67
+ systemPrompt,
68
+ temperature: 0.3,
69
+ maxTokens: 150,
70
+ }),
71
+ new Promise<never>((_, reject) =>
72
+ setTimeout(
73
+ () => reject(new Error("Enrichment timed out")),
74
+ ENRICH_TIMEOUT_MS,
75
+ ),
76
+ ),
77
+ ]);
78
+ enrichmentFailures = 0;
79
+ return result.text.replace(/<\|[^|]+\|>/g, "").trim();
80
+ } catch (e) {
81
+ enrichmentFailures++;
82
+ console.warn(
83
+ `Enrichment failed (${enrichmentFailures}/${ENRICH_MAX_FAILURES}): ${e}`,
84
+ );
85
+ if (enrichmentFailures >= ENRICH_MAX_FAILURES) {
86
+ console.warn(
87
+ "Max enrichment failures reached, disabling for remaining entries",
88
+ );
89
+ }
90
+ return null;
91
+ }
92
+ }
93
+
27
94
  export async function indexPersonal(ctx: IndexerContext): Promise<void> {
95
+ enrichmentFailures = 0;
28
96
  const personalDir = ctx.config.paths.personal;
29
97
 
30
98
  if (!checkPath("personal", "paths.personal", personalDir)) return;
@@ -37,7 +105,12 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
37
105
  const books = JSON.parse(readFileSync(booksPath, "utf-8"));
38
106
  for (const book of books) {
39
107
  if (!book.title) continue;
40
- const content = `${book.title} by ${book.author || "unknown"}\n${book.notes || ""}`;
108
+ let content = `${book.title} by ${book.author || "unknown"}\n${book.notes || ""}`;
109
+ const enriched = await enrich(
110
+ "book",
111
+ JSON.stringify({ title: book.title, author: book.author }),
112
+ );
113
+ if (enriched) content = enriched;
41
114
  const timestamp = book.date_read
42
115
  ? toISO(book.date_read, booksTs)
43
116
  : booksTs;
@@ -65,7 +138,15 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
65
138
  const people = JSON.parse(readFileSync(peoplePath, "utf-8"));
66
139
  for (const person of people) {
67
140
  if (!person.name) continue;
68
- const content = `${person.name}\n${person.relationship || ""}\n${person.notes || ""}`;
141
+ let content = `${person.name}\n${person.relationship || ""}\n${person.notes || ""}`;
142
+ const enriched = await enrich(
143
+ "person",
144
+ JSON.stringify({
145
+ name: person.name,
146
+ relationship: person.relationship,
147
+ }),
148
+ );
149
+ if (enriched) content = enriched;
69
150
 
70
151
  ctx.insert({
71
152
  source: "personal",
@@ -91,9 +172,14 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
91
172
  for (const movie of movies) {
92
173
  if (!movie.title) continue;
93
174
  const year = movie.year || "";
94
- const content = year
175
+ let content = year
95
176
  ? `${movie.title} (${year})\n${movie.notes || ""}`
96
177
  : `${movie.title}\n${movie.notes || ""}`;
178
+ const enriched = await enrich(
179
+ "movie",
180
+ JSON.stringify({ title: movie.title, year: movie.year }),
181
+ );
182
+ if (enriched) content = enriched;
97
183
  const timestamp = movie.date_watched
98
184
  ? toISO(movie.date_watched, moviesTs)
99
185
  : moviesTs;
@@ -147,11 +233,17 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
147
233
  const interests = JSON.parse(readFileSync(interestsPath, "utf-8"));
148
234
  for (const interest of interests) {
149
235
  if (typeof interest !== "string" || !interest) continue;
236
+ let content = interest;
237
+ const enriched = await enrich(
238
+ "interest",
239
+ JSON.stringify({ name: interest }),
240
+ );
241
+ if (enriched) content = enriched;
150
242
 
151
243
  ctx.insert({
152
244
  source: "personal",
153
245
  title: `[interest] ${interest}`,
154
- content: interest,
246
+ content,
155
247
  topic: "",
156
248
  type: "interest",
157
249
  timestamp: interestsTs,
@@ -173,7 +265,12 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
173
265
  const habitName = habit.habit || "";
174
266
  if (!habitName) continue;
175
267
  const frequency = habit.frequency || "";
176
- const content = frequency ? `${habitName} (${frequency})` : habitName;
268
+ let content = frequency ? `${habitName} (${frequency})` : habitName;
269
+ const enriched = await enrich(
270
+ "habit",
271
+ JSON.stringify({ habit: habitName, frequency }),
272
+ );
273
+ if (enriched) content = enriched;
177
274
 
178
275
  ctx.insert({
179
276
  source: "personal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "1.0.8",
3
+ "version": "1.1.1",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -44,7 +44,8 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@huggingface/transformers": "^3.2.6",
47
- "@iarna/toml": "^2.2.5"
47
+ "@iarna/toml": "^2.2.5",
48
+ "@voidwire/llm-core": "^0.3.1"
48
49
  },
49
50
  "devDependencies": {
50
51
  "bun-types": "1.3.5"