@voidwire/lore 1.6.0 → 1.6.2

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
@@ -59,6 +59,7 @@ import {
59
59
  type ObservationSubtype,
60
60
  type ObservationConfidence,
61
61
  type PurgeableSource,
62
+ type ContradictionDecision,
62
63
  } from "./index";
63
64
  import { isValidLoreType, LORE_TYPES } from "./lib/types";
64
65
  import { runIndexer } from "./lib/indexer";
@@ -164,6 +165,26 @@ function fail(error: string, code: number = 1): never {
164
165
  process.exit(code);
165
166
  }
166
167
 
168
+ /**
169
+ * Log contradiction decisions to stderr (CLI context).
170
+ * Only logs non-ADD actions — ADD is the default/expected path.
171
+ */
172
+ function logContradictionDecisions(decisions: ContradictionDecision[]): void {
173
+ for (const d of decisions) {
174
+ if (d.action === "NOOP") {
175
+ console.error(
176
+ `[contradiction] NOOP: skipped as redundant (topic: ${d.topic})`,
177
+ );
178
+ } else if (d.action === "DELETE+ADD") {
179
+ console.error(
180
+ `[contradiction] DELETE+ADD: replaced rowid ${d.deleteRowid} (topic: ${d.topic})`,
181
+ );
182
+ } else if (d.error) {
183
+ console.error(`[contradiction] ADD (fallback): ${d.error}`);
184
+ }
185
+ }
186
+ }
187
+
167
188
  // ============================================================================
168
189
  // Search Command
169
190
  // ============================================================================
@@ -783,8 +804,9 @@ async function handleCaptureTask(args: string[]): Promise<void> {
783
804
 
784
805
  if (result.success && result.event) {
785
806
  try {
786
- await indexAndEmbed([result.event]);
807
+ const decisions = await indexAndEmbed([result.event]);
787
808
  output(result);
809
+ logContradictionDecisions(decisions);
788
810
  console.error("✅ Task logged and indexed");
789
811
  process.exit(0);
790
812
  } catch (error) {
@@ -818,8 +840,9 @@ async function handleCaptureKnowledge(args: string[]): Promise<void> {
818
840
 
819
841
  if (result.success && result.event) {
820
842
  try {
821
- await indexAndEmbed([result.event]);
843
+ const decisions = await indexAndEmbed([result.event]);
822
844
  output(result);
845
+ logContradictionDecisions(decisions);
823
846
  console.error("✅ Knowledge logged and indexed");
824
847
  process.exit(0);
825
848
  } catch (error) {
@@ -851,8 +874,9 @@ async function handleCaptureNote(args: string[]): Promise<void> {
851
874
 
852
875
  if (result.success && result.event) {
853
876
  try {
854
- await indexAndEmbed([result.event]);
877
+ const decisions = await indexAndEmbed([result.event]);
855
878
  output(result);
879
+ logContradictionDecisions(decisions);
856
880
  console.error("✅ Note logged and indexed");
857
881
  process.exit(0);
858
882
  } catch (error) {
@@ -887,8 +911,9 @@ async function handleCaptureTeaching(args: string[]): Promise<void> {
887
911
 
888
912
  if (result.success && result.event) {
889
913
  try {
890
- await indexAndEmbed([result.event]);
914
+ const decisions = await indexAndEmbed([result.event]);
891
915
  output(result);
916
+ logContradictionDecisions(decisions);
892
917
  console.error("✅ Teaching logged and indexed");
893
918
  process.exit(0);
894
919
  } catch (error) {
@@ -924,8 +949,9 @@ async function handleCaptureObservation(args: string[]): Promise<void> {
924
949
 
925
950
  if (result.success && result.event) {
926
951
  try {
927
- await indexAndEmbed([result.event]);
952
+ const decisions = await indexAndEmbed([result.event]);
928
953
  output(result);
954
+ logContradictionDecisions(decisions);
929
955
  console.error("✅ Observation logged and indexed");
930
956
  process.exit(0);
931
957
  } catch (error) {
package/index.ts CHANGED
@@ -102,6 +102,7 @@ export { LoreType, LORE_TYPES, isValidLoreType } from "./lib/types";
102
102
 
103
103
  // Real-time indexing
104
104
  export { indexAndEmbed } from "./lib/realtime";
105
+ export { type ContradictionDecision } from "./lib/contradiction";
105
106
 
106
107
  // Purge
107
108
  export {
@@ -27,6 +27,15 @@ export interface ContradictionResult {
27
27
  deleteRowid?: number;
28
28
  }
29
29
 
30
+ /** Decision record returned to callers for logging */
31
+ export interface ContradictionDecision {
32
+ action: ContradictionAction;
33
+ topic: string;
34
+ source: string;
35
+ deleteRowid?: number;
36
+ error?: string;
37
+ }
38
+
30
39
  // ─── Constants ──────────────────────────────────────────────────────────────
31
40
 
32
41
  const MLX_URL = "http://localhost:8080/v1/chat/completions";
@@ -133,9 +142,6 @@ Otherwise reply: ADD or NOOP`;
133
142
  });
134
143
 
135
144
  if (!resp.ok) {
136
- console.error(
137
- `[contradiction] MLX returned ${resp.status} — defaulting to ADD`,
138
- );
139
145
  return { action: "ADD" };
140
146
  }
141
147
 
@@ -145,12 +151,8 @@ Otherwise reply: ADD or NOOP`;
145
151
 
146
152
  const raw = json.choices?.[0]?.message?.content?.trim() || "";
147
153
  return parseClassification(raw);
148
- } catch (err) {
154
+ } catch {
149
155
  // Timeout, network error, or model unavailable — fail open
150
- const message = err instanceof Error ? err.message : String(err);
151
- console.error(
152
- `[contradiction] classification failed (${message}) — defaulting to ADD`,
153
- );
154
156
  return { action: "ADD" };
155
157
  }
156
158
  }
@@ -183,9 +185,6 @@ function parseClassification(raw: string): ContradictionResult {
183
185
  }
184
186
 
185
187
  // Unparseable — default to ADD
186
- console.error(
187
- `[contradiction] unparseable response "${raw}" — defaulting to ADD`,
188
- );
189
188
  return { action: "ADD" };
190
189
  }
191
190
 
package/lib/realtime.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  isContradictionCheckable,
30
30
  findCandidates,
31
31
  classifyContradiction,
32
+ type ContradictionDecision,
32
33
  } from "./contradiction.js";
33
34
 
34
35
  /**
@@ -38,8 +39,10 @@ import {
38
39
  * 2. Generate embeddings with cache (instant semantic search)
39
40
  * 3. Insert into embeddings table
40
41
  */
41
- export async function indexAndEmbed(events: CaptureEvent[]): Promise<void> {
42
- if (events.length === 0) return;
42
+ export async function indexAndEmbed(
43
+ events: CaptureEvent[],
44
+ ): Promise<ContradictionDecision[]> {
45
+ if (events.length === 0) return [];
43
46
 
44
47
  const dbPath = getDatabasePath();
45
48
  if (!existsSync(dbPath)) {
@@ -63,8 +66,11 @@ export async function indexAndEmbed(events: CaptureEvent[]): Promise<void> {
63
66
  // duplicates existing entries. NOOP skips the event, DELETE+ADD
64
67
  // removes the old entry before inserting the new one.
65
68
  const eventsToIndex: CaptureEvent[] = [];
69
+ const decisions: ContradictionDecision[] = [];
66
70
  for (const event of events) {
67
71
  const source = getSourceForEvent(event);
72
+ const data = event.data as Record<string, unknown>;
73
+ const topic = String(data.topic || "");
68
74
 
69
75
  if (isContradictionCheckable(source)) {
70
76
  try {
@@ -73,37 +79,34 @@ export async function indexAndEmbed(events: CaptureEvent[]): Promise<void> {
73
79
  const result = await classifyContradiction(event, candidates);
74
80
 
75
81
  if (result.action === "NOOP") {
76
- const data = event.data as Record<string, unknown>;
77
- const topic = String(data.topic || "");
78
- console.error(
79
- `[contradiction] NOOP: skipped as redundant (topic: ${topic})`,
80
- );
82
+ decisions.push({ action: "NOOP", topic, source });
81
83
  continue;
82
84
  }
83
85
 
84
86
  if (result.action === "DELETE+ADD" && result.deleteRowid) {
85
87
  deleteSearchAndEmbedding(db, result.deleteRowid);
86
- const data = event.data as Record<string, unknown>;
87
- const topic = String(data.topic || "");
88
- console.error(
89
- `[contradiction] DELETE+ADD: removed rowid ${result.deleteRowid}, topic: ${topic}`,
90
- );
88
+ decisions.push({
89
+ action: "DELETE+ADD",
90
+ topic,
91
+ source,
92
+ deleteRowid: result.deleteRowid,
93
+ });
94
+ } else {
95
+ decisions.push({ action: "ADD", topic, source });
91
96
  }
92
97
  // ADD falls through to normal insert
93
98
  }
94
99
  } catch (err) {
95
100
  // Fail open — if contradiction check fails, proceed with ADD
96
101
  const message = err instanceof Error ? err.message : String(err);
97
- console.error(
98
- `[contradiction] check failed (${message}) — proceeding with ADD`,
99
- );
102
+ decisions.push({ action: "ADD", topic, source, error: message });
100
103
  }
101
104
  }
102
105
 
103
106
  eventsToIndex.push(event);
104
107
  }
105
108
 
106
- if (eventsToIndex.length === 0) return;
109
+ if (eventsToIndex.length === 0) return decisions;
107
110
 
108
111
  // 1. Insert into FTS5 and collect doc IDs
109
112
  const docIds: number[] = [];
@@ -120,6 +123,8 @@ export async function indexAndEmbed(events: CaptureEvent[]): Promise<void> {
120
123
  for (let i = 0; i < eventsToIndex.length; i++) {
121
124
  insertEmbedding(db, docIds[i], embeddings[i], eventsToIndex[i]);
122
125
  }
126
+
127
+ return decisions;
123
128
  } finally {
124
129
  db.close();
125
130
  }
@@ -319,12 +324,14 @@ function insertEmbedding(
319
324
 
320
325
  const embeddingBlob = serializeEmbedding(embedding);
321
326
 
327
+ const timestamp = event.timestamp || new Date().toISOString();
328
+
322
329
  const stmt = db.prepare(`
323
- INSERT INTO embeddings (doc_id, chunk_idx, source, topic, type, embedding)
324
- VALUES (?, 0, ?, ?, ?, ?)
330
+ INSERT INTO embeddings (doc_id, chunk_idx, source, topic, type, timestamp, embedding)
331
+ VALUES (?, 0, ?, ?, ?, ?, ?)
325
332
  `);
326
333
 
327
- stmt.run(docId, source, topic, type, embeddingBlob);
334
+ stmt.run(docId, source, topic, type, timestamp, embeddingBlob);
328
335
  }
329
336
 
330
337
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",