@voidwire/lore 1.0.8 → 1.1.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.
@@ -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,65 @@ 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 = 10_000;
55
+
56
+ let enrichmentDisabled = false;
57
+
58
+ async function enrich(type: string, input: string): Promise<string | null> {
59
+ if (enrichmentDisabled) return null;
60
+ const systemPrompt = ENRICH_PROMPTS[type];
61
+ if (!systemPrompt) return null;
62
+ try {
63
+ const result = await Promise.race([
64
+ complete({
65
+ prompt: input,
66
+ systemPrompt,
67
+ temperature: 0.3,
68
+ maxTokens: 150,
69
+ }),
70
+ new Promise<never>((_, reject) =>
71
+ setTimeout(
72
+ () => reject(new Error("Enrichment timed out")),
73
+ ENRICH_TIMEOUT_MS,
74
+ ),
75
+ ),
76
+ ]);
77
+ return result.text.replace(/<\|[^|]+\|>/g, "").trim();
78
+ } catch (e) {
79
+ console.warn(`Enrichment failed, disabling for remaining entries: ${e}`);
80
+ enrichmentDisabled = true;
81
+ return null;
82
+ }
83
+ }
84
+
27
85
  export async function indexPersonal(ctx: IndexerContext): Promise<void> {
86
+ enrichmentDisabled = false;
28
87
  const personalDir = ctx.config.paths.personal;
29
88
 
30
89
  if (!checkPath("personal", "paths.personal", personalDir)) return;
@@ -37,7 +96,12 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
37
96
  const books = JSON.parse(readFileSync(booksPath, "utf-8"));
38
97
  for (const book of books) {
39
98
  if (!book.title) continue;
40
- const content = `${book.title} by ${book.author || "unknown"}\n${book.notes || ""}`;
99
+ let content = `${book.title} by ${book.author || "unknown"}\n${book.notes || ""}`;
100
+ const enriched = await enrich(
101
+ "book",
102
+ JSON.stringify({ title: book.title, author: book.author }),
103
+ );
104
+ if (enriched) content = enriched;
41
105
  const timestamp = book.date_read
42
106
  ? toISO(book.date_read, booksTs)
43
107
  : booksTs;
@@ -65,7 +129,15 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
65
129
  const people = JSON.parse(readFileSync(peoplePath, "utf-8"));
66
130
  for (const person of people) {
67
131
  if (!person.name) continue;
68
- const content = `${person.name}\n${person.relationship || ""}\n${person.notes || ""}`;
132
+ let content = `${person.name}\n${person.relationship || ""}\n${person.notes || ""}`;
133
+ const enriched = await enrich(
134
+ "person",
135
+ JSON.stringify({
136
+ name: person.name,
137
+ relationship: person.relationship,
138
+ }),
139
+ );
140
+ if (enriched) content = enriched;
69
141
 
70
142
  ctx.insert({
71
143
  source: "personal",
@@ -91,9 +163,14 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
91
163
  for (const movie of movies) {
92
164
  if (!movie.title) continue;
93
165
  const year = movie.year || "";
94
- const content = year
166
+ let content = year
95
167
  ? `${movie.title} (${year})\n${movie.notes || ""}`
96
168
  : `${movie.title}\n${movie.notes || ""}`;
169
+ const enriched = await enrich(
170
+ "movie",
171
+ JSON.stringify({ title: movie.title, year: movie.year }),
172
+ );
173
+ if (enriched) content = enriched;
97
174
  const timestamp = movie.date_watched
98
175
  ? toISO(movie.date_watched, moviesTs)
99
176
  : moviesTs;
@@ -147,11 +224,17 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
147
224
  const interests = JSON.parse(readFileSync(interestsPath, "utf-8"));
148
225
  for (const interest of interests) {
149
226
  if (typeof interest !== "string" || !interest) continue;
227
+ let content = interest;
228
+ const enriched = await enrich(
229
+ "interest",
230
+ JSON.stringify({ name: interest }),
231
+ );
232
+ if (enriched) content = enriched;
150
233
 
151
234
  ctx.insert({
152
235
  source: "personal",
153
236
  title: `[interest] ${interest}`,
154
- content: interest,
237
+ content,
155
238
  topic: "",
156
239
  type: "interest",
157
240
  timestamp: interestsTs,
@@ -173,7 +256,12 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
173
256
  const habitName = habit.habit || "";
174
257
  if (!habitName) continue;
175
258
  const frequency = habit.frequency || "";
176
- const content = frequency ? `${habitName} (${frequency})` : habitName;
259
+ let content = frequency ? `${habitName} (${frequency})` : habitName;
260
+ const enriched = await enrich(
261
+ "habit",
262
+ JSON.stringify({ habit: habitName, frequency }),
263
+ );
264
+ if (enriched) content = enriched;
177
265
 
178
266
  ctx.insert({
179
267
  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.0",
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"