@voidwire/lore 1.6.3 → 1.7.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/index.ts +0 -2
- package/lib/capture.ts +0 -46
- package/lib/contradiction.ts +1 -24
- package/lib/indexers/index.ts +0 -2
- package/lib/list.ts +0 -2
- package/lib/projects.ts +0 -1
- package/lib/purge.ts +2 -19
- package/lib/realtime.ts +11 -52
- package/lib/source-map.ts +30 -0
- package/lib/types.ts +8 -0
- package/package.json +1 -1
- package/lib/indexers/learnings.ts +0 -57
package/index.ts
CHANGED
|
@@ -67,7 +67,6 @@ export {
|
|
|
67
67
|
captureNote,
|
|
68
68
|
captureTeaching,
|
|
69
69
|
captureInsight,
|
|
70
|
-
captureLearning,
|
|
71
70
|
captureObservation,
|
|
72
71
|
type CaptureResult,
|
|
73
72
|
type KnowledgeInput,
|
|
@@ -77,7 +76,6 @@ export {
|
|
|
77
76
|
type TeachingInput,
|
|
78
77
|
type InsightInput,
|
|
79
78
|
type InsightType,
|
|
80
|
-
type LearningInput,
|
|
81
79
|
type ObservationInput,
|
|
82
80
|
type ObservationSubtype,
|
|
83
81
|
type ObservationConfidence,
|
package/lib/capture.ts
CHANGED
|
@@ -74,13 +74,6 @@ export interface InsightInput {
|
|
|
74
74
|
source: "auto";
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export interface LearningInput {
|
|
78
|
-
topic: string; // "spanish", "guitar", "kubernetes" - the learning topic
|
|
79
|
-
persona: string; // "marcus", "elena", etc.
|
|
80
|
-
content: string; // "Covered verb conjugations, struggles with subjunctive"
|
|
81
|
-
session_summary?: string; // Longer form session notes
|
|
82
|
-
}
|
|
83
|
-
|
|
84
77
|
export type ObservationSubtype =
|
|
85
78
|
| "term"
|
|
86
79
|
| "style"
|
|
@@ -164,18 +157,6 @@ interface InsightEvent {
|
|
|
164
157
|
};
|
|
165
158
|
}
|
|
166
159
|
|
|
167
|
-
interface LearningEvent {
|
|
168
|
-
event: "captured";
|
|
169
|
-
type: "learning";
|
|
170
|
-
timestamp: string;
|
|
171
|
-
data: {
|
|
172
|
-
topic: string; // Learning topic (spanish, guitar, etc.)
|
|
173
|
-
persona: string;
|
|
174
|
-
content: string;
|
|
175
|
-
session_summary?: string;
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
160
|
interface ObservationEvent {
|
|
180
161
|
event: "captured";
|
|
181
162
|
type: "observation";
|
|
@@ -195,7 +176,6 @@ type CaptureEvent =
|
|
|
195
176
|
| NoteEvent
|
|
196
177
|
| TeachingEvent
|
|
197
178
|
| InsightEvent
|
|
198
|
-
| LearningEvent
|
|
199
179
|
| ObservationEvent;
|
|
200
180
|
|
|
201
181
|
function getLogPath(): string {
|
|
@@ -372,32 +352,6 @@ export function captureInsight(input: InsightInput): CaptureResult {
|
|
|
372
352
|
return writeEvent(event);
|
|
373
353
|
}
|
|
374
354
|
|
|
375
|
-
/**
|
|
376
|
-
* Capture a learning session progress
|
|
377
|
-
*/
|
|
378
|
-
export function captureLearning(input: LearningInput): CaptureResult {
|
|
379
|
-
if (!input.topic || !input.persona || !input.content) {
|
|
380
|
-
return {
|
|
381
|
-
success: false,
|
|
382
|
-
error: "Missing required fields: topic, persona, content",
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const event: LearningEvent = {
|
|
387
|
-
event: "captured",
|
|
388
|
-
type: "learning",
|
|
389
|
-
timestamp: "",
|
|
390
|
-
data: {
|
|
391
|
-
topic: input.topic,
|
|
392
|
-
persona: input.persona,
|
|
393
|
-
content: input.content,
|
|
394
|
-
session_summary: input.session_summary,
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
return writeEvent(event);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
355
|
const VALID_OBSERVATION_SUBTYPES: ObservationSubtype[] = [
|
|
402
356
|
"term",
|
|
403
357
|
"style",
|
package/lib/contradiction.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { hybridSearch, type HybridResult } from "./semantic.js";
|
|
|
18
18
|
import { PURGEABLE_SOURCES } from "./purge.js";
|
|
19
19
|
import { complete } from "@voidwire/llm-core";
|
|
20
20
|
import type { CaptureEvent } from "./capture.js";
|
|
21
|
+
import { getSourceForEvent } from "./source-map.js";
|
|
21
22
|
|
|
22
23
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -169,27 +170,3 @@ function parseClassification(raw: string): ContradictionResult {
|
|
|
169
170
|
// Unparseable — default to ADD
|
|
170
171
|
return { action: "ADD" };
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Map event type to source name (mirrors realtime.ts getSourceForEvent).
|
|
175
|
-
*/
|
|
176
|
-
function getSourceForEvent(event: CaptureEvent): string {
|
|
177
|
-
switch (event.type) {
|
|
178
|
-
case "knowledge":
|
|
179
|
-
return "captures";
|
|
180
|
-
case "teaching":
|
|
181
|
-
return "teachings";
|
|
182
|
-
case "observation":
|
|
183
|
-
return "observations";
|
|
184
|
-
case "insight":
|
|
185
|
-
return "insights";
|
|
186
|
-
case "learning":
|
|
187
|
-
return "learnings";
|
|
188
|
-
case "task":
|
|
189
|
-
return "flux";
|
|
190
|
-
case "note":
|
|
191
|
-
return "captures";
|
|
192
|
-
default:
|
|
193
|
-
return "captures";
|
|
194
|
-
}
|
|
195
|
-
}
|
package/lib/indexers/index.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import type { IndexerFunction } from "../indexer";
|
|
9
9
|
import { indexEvents } from "./events";
|
|
10
|
-
import { indexLearnings } from "./learnings";
|
|
11
10
|
import { indexReadmes } from "./readmes";
|
|
12
11
|
import { indexDevelopment } from "./development";
|
|
13
12
|
import { indexCaptures } from "./captures";
|
|
@@ -24,7 +23,6 @@ import { indexPersonal } from "./personal";
|
|
|
24
23
|
|
|
25
24
|
export const indexers: Record<string, IndexerFunction> = {
|
|
26
25
|
events: indexEvents,
|
|
27
|
-
learnings: indexLearnings,
|
|
28
26
|
readmes: indexReadmes,
|
|
29
27
|
development: indexDevelopment,
|
|
30
28
|
captures: indexCaptures,
|
package/lib/list.ts
CHANGED
|
@@ -29,7 +29,6 @@ export type Source =
|
|
|
29
29
|
| "teachings"
|
|
30
30
|
| "sessions"
|
|
31
31
|
| "insights"
|
|
32
|
-
| "learnings"
|
|
33
32
|
| "observations";
|
|
34
33
|
|
|
35
34
|
export const SOURCES: Source[] = [
|
|
@@ -51,7 +50,6 @@ export const SOURCES: Source[] = [
|
|
|
51
50
|
"teachings",
|
|
52
51
|
"sessions",
|
|
53
52
|
"insights",
|
|
54
|
-
"learnings",
|
|
55
53
|
"observations",
|
|
56
54
|
];
|
|
57
55
|
|
package/lib/projects.ts
CHANGED
package/lib/purge.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* deleteEntries(matches.map(m => m.rowid));
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { Database } from "bun:sqlite";
|
|
13
12
|
import {
|
|
14
13
|
existsSync,
|
|
15
14
|
readFileSync,
|
|
@@ -18,7 +17,7 @@ import {
|
|
|
18
17
|
unlinkSync,
|
|
19
18
|
} from "fs";
|
|
20
19
|
import { join } from "path";
|
|
21
|
-
import {
|
|
20
|
+
import { openDatabase } from "./db.js";
|
|
22
21
|
import { getConfig } from "./config.js";
|
|
23
22
|
|
|
24
23
|
// Only these sources can be purged — indexed sources (blogs, commits, etc.) are never purgeable
|
|
@@ -101,25 +100,9 @@ export function deleteEntries(
|
|
|
101
100
|
return { deleted: 0, rowids: [], logEntriesRemoved: 0 };
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
// openDatabase(false) triggers SQLITE_MISUSE with custom_sqlite)
|
|
106
|
-
const dbPath = getDatabasePath();
|
|
107
|
-
if (!existsSync(dbPath)) {
|
|
108
|
-
throw new Error(`Database not found: ${dbPath}. Run lore-db-init first.`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const db = new Database(dbPath);
|
|
103
|
+
const db = openDatabase(false);
|
|
112
104
|
|
|
113
105
|
try {
|
|
114
|
-
// Load sqlite-vec extension for embeddings table access
|
|
115
|
-
const vecPath = process.env.SQLITE_VEC_PATH;
|
|
116
|
-
if (!vecPath) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
'SQLITE_VEC_PATH not set. Get path with: python3 -c "import sqlite_vec; print(sqlite_vec.loadable_path())"',
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
db.loadExtension(vecPath);
|
|
122
|
-
|
|
123
106
|
const deleteSearch = db.prepare("DELETE FROM search WHERE rowid = ?");
|
|
124
107
|
const deleteEmbedding = db.prepare(
|
|
125
108
|
"DELETE FROM embeddings WHERE doc_id = ?",
|
package/lib/realtime.ts
CHANGED
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { Database } from "bun:sqlite";
|
|
18
|
-
import { existsSync } from "fs";
|
|
19
18
|
import {
|
|
20
19
|
embedDocuments,
|
|
21
|
-
getDatabasePath,
|
|
22
20
|
MODEL_NAME,
|
|
23
21
|
EMBEDDING_DIM,
|
|
24
22
|
serializeEmbedding,
|
|
25
23
|
} from "./semantic.js";
|
|
24
|
+
import { openDatabase } from "./db.js";
|
|
26
25
|
import { hashContent, getCachedEmbedding, cacheEmbedding } from "./cache.js";
|
|
27
26
|
import type { CaptureEvent } from "./capture.js";
|
|
27
|
+
import { getSourceForEvent } from "./source-map.js";
|
|
28
28
|
import {
|
|
29
29
|
isContradictionCheckable,
|
|
30
30
|
findCandidates,
|
|
@@ -44,23 +44,9 @@ export async function indexAndEmbed(
|
|
|
44
44
|
): Promise<ContradictionDecision[]> {
|
|
45
45
|
if (events.length === 0) return [];
|
|
46
46
|
|
|
47
|
-
const
|
|
48
|
-
if (!existsSync(dbPath)) {
|
|
49
|
-
throw new Error(`Database not found: ${dbPath}. Run lore-db-init first.`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const db = new Database(dbPath);
|
|
47
|
+
const db = openDatabase(false);
|
|
53
48
|
|
|
54
49
|
try {
|
|
55
|
-
// Load sqlite-vec extension for embeddings table
|
|
56
|
-
const vecPath = process.env.SQLITE_VEC_PATH;
|
|
57
|
-
if (!vecPath) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
'SQLITE_VEC_PATH not set. Get path with: python3 -c "import sqlite_vec; print(sqlite_vec.loadable_path())"',
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
db.loadExtension(vecPath);
|
|
63
|
-
|
|
64
50
|
// 0. Contradiction detection — filter events before insert
|
|
65
51
|
// For purgeable sources, check if the new event contradicts or
|
|
66
52
|
// duplicates existing entries. NOOP skips the event, DELETE+ADD
|
|
@@ -137,9 +123,9 @@ export async function indexAndEmbed(
|
|
|
137
123
|
function insertSearchEntry(db: Database, event: CaptureEvent): number {
|
|
138
124
|
const source = getSourceForEvent(event);
|
|
139
125
|
const title = buildTitle(event);
|
|
140
|
-
const content = getContentForEmbedding(event);
|
|
141
|
-
const metadata = buildMetadata(event);
|
|
142
126
|
const data = event.data as Record<string, unknown>;
|
|
127
|
+
const content = String(data.content || data.text || "");
|
|
128
|
+
const metadata = buildMetadata(event);
|
|
143
129
|
const topic = String(data.topic || "");
|
|
144
130
|
const type = extractType(event);
|
|
145
131
|
const timestamp = event.timestamp || new Date().toISOString();
|
|
@@ -171,30 +157,6 @@ function deleteSearchAndEmbedding(db: Database, rowid: number): void {
|
|
|
171
157
|
db.prepare("DELETE FROM embeddings WHERE doc_id = ?").run(rowid);
|
|
172
158
|
}
|
|
173
159
|
|
|
174
|
-
/**
|
|
175
|
-
* Map event type to source name used in search table
|
|
176
|
-
*/
|
|
177
|
-
function getSourceForEvent(event: CaptureEvent): string {
|
|
178
|
-
switch (event.type) {
|
|
179
|
-
case "knowledge":
|
|
180
|
-
return "captures";
|
|
181
|
-
case "teaching":
|
|
182
|
-
return "teachings";
|
|
183
|
-
case "observation":
|
|
184
|
-
return "observations";
|
|
185
|
-
case "insight":
|
|
186
|
-
return "insights";
|
|
187
|
-
case "learning":
|
|
188
|
-
return "learnings";
|
|
189
|
-
case "task":
|
|
190
|
-
return "flux";
|
|
191
|
-
case "note":
|
|
192
|
-
return "captures";
|
|
193
|
-
default:
|
|
194
|
-
return "captures";
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
160
|
/**
|
|
199
161
|
* Build title for FTS5 entry (type is a first-class column, not a title prefix)
|
|
200
162
|
*/
|
|
@@ -210,24 +172,26 @@ function buildTitle(event: CaptureEvent): string {
|
|
|
210
172
|
return `${data.topic || "general"}`;
|
|
211
173
|
case "insight":
|
|
212
174
|
return `${data.topic || "general"}`;
|
|
213
|
-
case "learning":
|
|
214
|
-
return `${data.topic || "general"}`;
|
|
215
175
|
case "task":
|
|
216
176
|
return `${data.topic || "general"}: ${data.name || "untitled"}`;
|
|
217
177
|
case "note":
|
|
218
178
|
return `${data.topic || "general"}`;
|
|
179
|
+
default:
|
|
180
|
+
return `${data.topic || "general"}`;
|
|
219
181
|
}
|
|
220
182
|
}
|
|
221
183
|
|
|
222
184
|
/**
|
|
223
185
|
* Extract content for embedding from event
|
|
224
|
-
* Concatenates topic+content for richer embeddings (matches lore-embed-all)
|
|
186
|
+
* Concatenates type+topic+content for richer embeddings (matches lore-embed-all)
|
|
225
187
|
*/
|
|
226
188
|
function getContentForEmbedding(event: CaptureEvent): string {
|
|
227
189
|
const data = event.data as Record<string, unknown>;
|
|
228
190
|
const content = String(data.content || data.text || "");
|
|
229
191
|
const topic = String(data.topic || "").trim();
|
|
230
|
-
|
|
192
|
+
const type = extractType(event);
|
|
193
|
+
const parts = [type, topic, content].filter(Boolean);
|
|
194
|
+
return parts.join(" ").trim() || content;
|
|
231
195
|
}
|
|
232
196
|
|
|
233
197
|
/**
|
|
@@ -253,9 +217,6 @@ function buildMetadata(event: CaptureEvent): string {
|
|
|
253
217
|
metadata.subtype = data.subtype;
|
|
254
218
|
metadata.session_id = data.session_id;
|
|
255
219
|
break;
|
|
256
|
-
case "learning":
|
|
257
|
-
metadata.persona = data.persona;
|
|
258
|
-
break;
|
|
259
220
|
case "task":
|
|
260
221
|
metadata.name = data.name;
|
|
261
222
|
metadata.problem = data.problem;
|
|
@@ -349,8 +310,6 @@ function extractType(event: CaptureEvent): string {
|
|
|
349
310
|
return String(data.subtype || "pattern");
|
|
350
311
|
case "insight":
|
|
351
312
|
return String(data.subtype || "insight");
|
|
352
|
-
case "learning":
|
|
353
|
-
return "learning";
|
|
354
313
|
case "task":
|
|
355
314
|
return "task";
|
|
356
315
|
case "note":
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/source-map.ts - Shared event-to-source mapping
|
|
3
|
+
*
|
|
4
|
+
* Maps capture event types to their source names in the search table.
|
|
5
|
+
* Single source of truth — used by realtime.ts and contradiction.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CaptureEvent } from "./capture.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Map event type to source name used in search table
|
|
12
|
+
*/
|
|
13
|
+
export function getSourceForEvent(event: CaptureEvent): string {
|
|
14
|
+
switch (event.type) {
|
|
15
|
+
case "knowledge":
|
|
16
|
+
return "captures";
|
|
17
|
+
case "teaching":
|
|
18
|
+
return "teachings";
|
|
19
|
+
case "observation":
|
|
20
|
+
return "observations";
|
|
21
|
+
case "insight":
|
|
22
|
+
return "insights";
|
|
23
|
+
case "task":
|
|
24
|
+
return "flux";
|
|
25
|
+
case "note":
|
|
26
|
+
return "captures";
|
|
27
|
+
default:
|
|
28
|
+
return "captures";
|
|
29
|
+
}
|
|
30
|
+
}
|
package/lib/types.ts
CHANGED
|
@@ -18,6 +18,14 @@ export enum LoreType {
|
|
|
18
18
|
Task = "task",
|
|
19
19
|
Todo = "todo",
|
|
20
20
|
Idea = "idea",
|
|
21
|
+
Knowledge = "knowledge",
|
|
22
|
+
Insight = "insight",
|
|
23
|
+
Summary = "summary",
|
|
24
|
+
Observation = "observation",
|
|
25
|
+
Context = "context",
|
|
26
|
+
Completion = "completion",
|
|
27
|
+
Problem = "problem",
|
|
28
|
+
Tool = "tool",
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/**
|
package/package.json
CHANGED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/indexers/learnings.ts - Learnings indexer
|
|
3
|
-
*
|
|
4
|
-
* Reads log.jsonl and indexes learning captures.
|
|
5
|
-
* Filters for event=captured AND type=learning.
|
|
6
|
-
*
|
|
7
|
-
* Source: learnings
|
|
8
|
-
* Topic: data.topic
|
|
9
|
-
* Type: (empty)
|
|
10
|
-
* Timestamp: event timestamp
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { readFileSync } from "fs";
|
|
14
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
15
|
-
|
|
16
|
-
export async function indexLearnings(ctx: IndexerContext): Promise<void> {
|
|
17
|
-
const logPath = `${ctx.config.paths.data}/log.jsonl`;
|
|
18
|
-
if (
|
|
19
|
-
!checkPath(
|
|
20
|
-
"learnings",
|
|
21
|
-
"log.jsonl",
|
|
22
|
-
logPath,
|
|
23
|
-
"populated by Sable session hooks",
|
|
24
|
-
)
|
|
25
|
-
)
|
|
26
|
-
return;
|
|
27
|
-
|
|
28
|
-
const lines = readFileSync(logPath, "utf-8").split("\n").filter(Boolean);
|
|
29
|
-
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
try {
|
|
32
|
-
const event = JSON.parse(line);
|
|
33
|
-
if (event.event !== "captured" || event.type !== "learning") continue;
|
|
34
|
-
|
|
35
|
-
const topic = event.data?.topic || "general";
|
|
36
|
-
const content = event.data?.content || "";
|
|
37
|
-
const persona = event.data?.persona;
|
|
38
|
-
|
|
39
|
-
if (!content) continue;
|
|
40
|
-
|
|
41
|
-
const metadata: Record<string, unknown> = {};
|
|
42
|
-
if (persona) metadata.persona = persona;
|
|
43
|
-
|
|
44
|
-
ctx.insert({
|
|
45
|
-
source: "learnings",
|
|
46
|
-
title: topic,
|
|
47
|
-
content,
|
|
48
|
-
topic,
|
|
49
|
-
timestamp: event.timestamp || "",
|
|
50
|
-
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
51
|
-
});
|
|
52
|
-
} catch {
|
|
53
|
-
// Skip malformed JSON
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|