opencodekit 0.20.3 → 0.20.5

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.
Files changed (32) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/AGENTS.md +14 -9
  3. package/dist/template/.opencode/agent/build.md +0 -32
  4. package/dist/template/.opencode/agent/plan.md +0 -14
  5. package/dist/template/.opencode/agent/review.md +0 -40
  6. package/dist/template/.opencode/command/create.md +11 -61
  7. package/dist/template/.opencode/command/plan.md +11 -12
  8. package/dist/template/.opencode/command/pr.md +4 -16
  9. package/dist/template/.opencode/command/research.md +7 -16
  10. package/dist/template/.opencode/command/resume.md +2 -11
  11. package/dist/template/.opencode/command/review-codebase.md +9 -15
  12. package/dist/template/.opencode/command/ship.md +12 -53
  13. package/dist/template/.opencode/memory/project/user.md +7 -0
  14. package/dist/template/.opencode/memory.db +0 -0
  15. package/dist/template/.opencode/memory.db-shm +0 -0
  16. package/dist/template/.opencode/memory.db-wal +0 -0
  17. package/dist/template/.opencode/opencode.json +54 -67
  18. package/dist/template/.opencode/package.json +1 -1
  19. package/dist/template/.opencode/plugin/README.md +1 -1
  20. package/dist/template/.opencode/plugin/lib/compact.ts +194 -0
  21. package/dist/template/.opencode/plugin/lib/db/graph.ts +253 -0
  22. package/dist/template/.opencode/plugin/lib/db/observations.ts +8 -3
  23. package/dist/template/.opencode/plugin/lib/db/schema.ts +96 -5
  24. package/dist/template/.opencode/plugin/lib/db/types.ts +73 -0
  25. package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +36 -3
  26. package/dist/template/.opencode/plugin/lib/memory-db.ts +12 -1
  27. package/dist/template/.opencode/plugin/lib/memory-tools.ts +137 -1
  28. package/dist/template/.opencode/plugin/memory.ts +2 -1
  29. package/dist/template/.opencode/skill/memory-grounding/SKILL.md +68 -0
  30. package/dist/template/.opencode/skill/verification-gates/SKILL.md +63 -0
  31. package/dist/template/.opencode/skill/workspace-setup/SKILL.md +76 -0
  32. package/package.json +1 -1
@@ -175,23 +175,25 @@
175
175
  "output": 32000
176
176
  },
177
177
  "options": {
178
- "thinking_budget": 24000,
179
- "type": "enabled"
178
+ "reasoningEffort": "high"
180
179
  },
181
180
  "reasoning": true,
182
181
  "temperature": true,
183
182
  "tool_call": true,
184
183
  "variants": {
185
- "high": {
184
+ "low": {
186
185
  "options": {
187
- "thinking_budget": 16000,
188
- "type": "enabled"
186
+ "reasoningEffort": "low"
189
187
  }
190
188
  },
191
- "max": {
189
+ "medium": {
192
190
  "options": {
193
- "thinking_budget": 32000,
194
- "type": "enabled"
191
+ "reasoningEffort": "medium"
192
+ }
193
+ },
194
+ "high": {
195
+ "options": {
196
+ "reasoningEffort": "high"
195
197
  }
196
198
  }
197
199
  }
@@ -203,20 +205,25 @@
203
205
  "output": 32000
204
206
  },
205
207
  "options": {
206
- "thinking_budget": 10000
208
+ "reasoningEffort": "medium"
207
209
  },
208
210
  "reasoning": true,
209
211
  "temperature": true,
210
212
  "tool_call": true,
211
213
  "variants": {
212
- "high": {
214
+ "low": {
213
215
  "options": {
214
- "thinking_budget": 16000
216
+ "reasoningEffort": "low"
215
217
  }
216
218
  },
217
- "max": {
219
+ "medium": {
218
220
  "options": {
219
- "thinking_budget": 32000
221
+ "reasoningEffort": "medium"
222
+ }
223
+ },
224
+ "high": {
225
+ "options": {
226
+ "reasoningEffort": "high"
220
227
  }
221
228
  }
222
229
  }
@@ -228,40 +235,25 @@
228
235
  "output": 64000
229
236
  },
230
237
  "options": {
231
- "thinking": {
232
- "budget_tokens": 24000,
233
- "type": "enabled"
234
- }
238
+ "reasoningEffort": "high"
235
239
  },
236
240
  "reasoning": true,
237
241
  "temperature": true,
238
242
  "tool_call": true,
239
243
  "variants": {
240
- "adaptive": {
244
+ "low": {
241
245
  "options": {
242
- "max_tokens": 16000,
243
- "output_config": {
244
- "effort": "max"
245
- },
246
- "thinking": {
247
- "type": "adaptive"
248
- }
246
+ "reasoningEffort": "low"
249
247
  }
250
248
  },
251
- "high": {
249
+ "medium": {
252
250
  "options": {
253
- "thinking": {
254
- "budget_tokens": 24000,
255
- "type": "enabled"
256
- }
251
+ "reasoningEffort": "medium"
257
252
  }
258
253
  },
259
- "max": {
254
+ "high": {
260
255
  "options": {
261
- "thinking": {
262
- "budget_tokens": 32000,
263
- "type": "enabled"
264
- }
256
+ "reasoningEffort": "high"
265
257
  }
266
258
  }
267
259
  }
@@ -273,20 +265,25 @@
273
265
  "output": 16000
274
266
  },
275
267
  "options": {
276
- "thinking_budget": 10000
268
+ "reasoningEffort": "medium"
277
269
  },
278
270
  "reasoning": true,
279
271
  "temperature": true,
280
272
  "tool_call": true,
281
273
  "variants": {
282
- "high": {
274
+ "low": {
283
275
  "options": {
284
- "thinking_budget": 16000
276
+ "reasoningEffort": "low"
285
277
  }
286
278
  },
287
- "max": {
279
+ "medium": {
280
+ "options": {
281
+ "reasoningEffort": "medium"
282
+ }
283
+ },
284
+ "high": {
288
285
  "options": {
289
- "thinking_budget": 32000
286
+ "reasoningEffort": "high"
290
287
  }
291
288
  }
292
289
  }
@@ -298,20 +295,25 @@
298
295
  "output": 32000
299
296
  },
300
297
  "options": {
301
- "thinking_budget": 10000
298
+ "reasoningEffort": "medium"
302
299
  },
303
300
  "reasoning": true,
304
301
  "temperature": true,
305
302
  "tool_call": true,
306
303
  "variants": {
307
- "high": {
304
+ "low": {
308
305
  "options": {
309
- "thinking_budget": 16000
306
+ "reasoningEffort": "low"
310
307
  }
311
308
  },
312
- "max": {
309
+ "medium": {
313
310
  "options": {
314
- "thinking_budget": 32000
311
+ "reasoningEffort": "medium"
312
+ }
313
+ },
314
+ "high": {
315
+ "options": {
316
+ "reasoningEffort": "high"
315
317
  }
316
318
  }
317
319
  }
@@ -323,40 +325,25 @@
323
325
  "output": 32000
324
326
  },
325
327
  "options": {
326
- "thinking": {
327
- "budget_tokens": 24000,
328
- "type": "enabled"
329
- }
328
+ "reasoningEffort": "high"
330
329
  },
331
330
  "reasoning": true,
332
331
  "temperature": true,
333
332
  "tool_call": true,
334
333
  "variants": {
335
- "adaptive": {
334
+ "low": {
336
335
  "options": {
337
- "max_tokens": 16000,
338
- "output_config": {
339
- "effort": "max"
340
- },
341
- "thinking": {
342
- "type": "adaptive"
343
- }
336
+ "reasoningEffort": "low"
344
337
  }
345
338
  },
346
- "high": {
339
+ "medium": {
347
340
  "options": {
348
- "thinking": {
349
- "budget_tokens": 16000,
350
- "type": "enabled"
351
- }
341
+ "reasoningEffort": "medium"
352
342
  }
353
343
  },
354
- "max": {
344
+ "high": {
355
345
  "options": {
356
- "thinking": {
357
- "budget_tokens": 32000,
358
- "type": "enabled"
359
- }
346
+ "reasoningEffort": "high"
360
347
  }
361
348
  }
362
349
  }
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@google/stitch-sdk": "^0.0.3",
15
- "@opencode-ai/plugin": "1.3.17"
15
+ "@opencode-ai/plugin": "1.4.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^25.3.0",
@@ -41,7 +41,7 @@ plugin/
41
41
  - Injects relevant knowledge into system prompt (BM25 _ recency _ confidence scoring)
42
42
  - Manages context window via messages.transform (token budget enforcement)
43
43
  - Merges compaction logic (beads, handoffs, project memory, knowledge)
44
- - Provides 7 tools: observation, memory-search, memory-get, memory-read, memory-update, memory-timeline, memory-admin
44
+ - Provides 11 tools: observation, memory-search, memory-get, memory-read, memory-update, memory-timeline, memory-graph-add, memory-graph-query, memory-graph-invalidate, memory-compact, memory-admin
45
45
 
46
46
  - `sessions.ts`
47
47
  - Provides tools: `find_sessions`, `read_session`
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Compact Format — AAAK-Inspired Memory Compression (v3)
3
+ *
4
+ * Inspired by MemPalace's AAAK dialect: a symbolic, pipe-separated format
5
+ * achieving ~3-5x compression while remaining readable by any LLM.
6
+ *
7
+ * Used for L1 wake-up context: compress top observations into a dense
8
+ * format that fits in ~200-300 tokens instead of 800+.
9
+ *
10
+ * Format rules:
11
+ * - Entities: 3-letter uppercase codes (ALC=Alice, KAI=Kai)
12
+ * - Categories: UPPERCASE labels (DECISION, PATTERN, WARNING)
13
+ * - Relationships: arrow notation (→, ←, ↔)
14
+ * - Importance: 1-5 stars (★ to ★★★★★)
15
+ * - Dates: ISO short (2026-03-31)
16
+ * - Pipe-separated fields within lines
17
+ */
18
+
19
+ import type { CompactResult } from "./db/types.js";
20
+
21
+ // ============================================================================
22
+ // Entity Code Generation
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Generate a 3-letter uppercase code from a name.
27
+ * Prioritizes: first 3 consonants, then first 3 chars.
28
+ */
29
+ function generateCode(name: string): string {
30
+ const cleaned = name.replace(/[^a-zA-Z]/g, "").toUpperCase();
31
+ const consonants = cleaned.replace(/[AEIOU]/g, "");
32
+ if (consonants.length >= 3) return consonants.slice(0, 3);
33
+ return cleaned.slice(0, 3).padEnd(3, "X");
34
+ }
35
+
36
+ /**
37
+ * Build entity code map from observation data.
38
+ * Deduplicates codes by appending numbers.
39
+ */
40
+ function buildCodeMap(names: string[]): Map<string, string> {
41
+ const codeMap = new Map<string, string>();
42
+ const usedCodes = new Set<string>();
43
+
44
+ for (const name of names) {
45
+ let code = generateCode(name);
46
+ if (usedCodes.has(code)) {
47
+ // Append incrementing suffix
48
+ let i = 2;
49
+ while (usedCodes.has(`${code.slice(0, 2)}${i}`)) i++;
50
+ code = `${code.slice(0, 2)}${i}`;
51
+ }
52
+ usedCodes.add(code);
53
+ codeMap.set(name.toLowerCase(), code);
54
+ }
55
+
56
+ return codeMap;
57
+ }
58
+
59
+ // ============================================================================
60
+ // Observation Type Mapping
61
+ // ============================================================================
62
+
63
+ const TYPE_LABELS: Record<string, string> = {
64
+ decision: "DECISION",
65
+ bugfix: "FIX",
66
+ feature: "FEAT",
67
+ pattern: "PATTERN",
68
+ discovery: "DISC",
69
+ learning: "LEARN",
70
+ warning: "WARN",
71
+ };
72
+
73
+ const CONFIDENCE_STARS: Record<string, string> = {
74
+ high: "★★★",
75
+ medium: "★★",
76
+ low: "★",
77
+ };
78
+
79
+ // ============================================================================
80
+ // Compact Compression
81
+ // ============================================================================
82
+
83
+ interface ObservationSummary {
84
+ id: number;
85
+ type: string;
86
+ title: string;
87
+ narrative?: string | null;
88
+ concepts?: string | null;
89
+ wing?: string | null;
90
+ hall?: string | null;
91
+ room?: string | null;
92
+ confidence?: string | null;
93
+ created_at?: string | null;
94
+ }
95
+
96
+ /**
97
+ * Compress an array of observations into compact AAAK-inspired format.
98
+ * Returns compressed text + compression metrics.
99
+ */
100
+ export function compactObservations(
101
+ observations: ObservationSummary[],
102
+ ): CompactResult {
103
+ if (observations.length === 0) {
104
+ return {
105
+ compressed: "",
106
+ token_estimate: 0,
107
+ original_tokens: 0,
108
+ compression_ratio: 0,
109
+ };
110
+ }
111
+
112
+ // Estimate original size
113
+ const originalText = observations
114
+ .map(
115
+ (o) =>
116
+ `[${o.type}] ${o.title}: ${o.narrative ?? ""} (${o.concepts ?? ""})`,
117
+ )
118
+ .join("\n");
119
+ const originalTokens = Math.ceil(originalText.length / 4);
120
+
121
+ // Collect entities for code generation
122
+ const entities: string[] = [];
123
+ for (const obs of observations) {
124
+ if (obs.wing) entities.push(obs.wing);
125
+ if (obs.room) entities.push(obs.room);
126
+ }
127
+ const codeMap = buildCodeMap([...new Set(entities)]);
128
+
129
+ // Group by type
130
+ const grouped = new Map<string, ObservationSummary[]>();
131
+ for (const obs of observations) {
132
+ const key = obs.type;
133
+ const existing = grouped.get(key) ?? [];
134
+ existing.push(obs);
135
+ grouped.set(key, existing);
136
+ }
137
+
138
+ // Build compact lines
139
+ const lines: string[] = [];
140
+
141
+ // Header: entity legend (if entities exist)
142
+ if (codeMap.size > 0) {
143
+ const legend = [...codeMap.entries()]
144
+ .map(([name, code]) => `${code}=${name}`)
145
+ .join(" ");
146
+ lines.push(`ENTITIES: ${legend}`);
147
+ }
148
+
149
+ // Observations grouped by type
150
+ for (const [type, obs] of grouped) {
151
+ const label = TYPE_LABELS[type] ?? type.toUpperCase();
152
+ const entries = obs.map((o) => {
153
+ const parts: string[] = [];
154
+
155
+ // Title (abbreviated)
156
+ const shortTitle =
157
+ o.title.length > 60 ? `${o.title.slice(0, 57)}...` : o.title;
158
+ parts.push(shortTitle);
159
+
160
+ // Navigation context
161
+ if (o.wing || o.room) {
162
+ const nav: string[] = [];
163
+ if (o.wing) nav.push(codeMap.get(o.wing.toLowerCase()) ?? o.wing);
164
+ if (o.room) nav.push(codeMap.get(o.room.toLowerCase()) ?? o.room);
165
+ parts.push(`@${nav.join("/")}`);
166
+ }
167
+
168
+ // Confidence
169
+ if (o.confidence) {
170
+ parts.push(CONFIDENCE_STARS[o.confidence] ?? "★★");
171
+ }
172
+
173
+ // Date
174
+ if (o.created_at) {
175
+ parts.push(o.created_at.slice(0, 10));
176
+ }
177
+
178
+ return parts.join(" | ");
179
+ });
180
+
181
+ lines.push(`${label}: ${entries.join(" // ")}`);
182
+ }
183
+
184
+ const compressed = lines.join("\n");
185
+ const compressedTokens = Math.ceil(compressed.length / 4);
186
+
187
+ return {
188
+ compressed,
189
+ token_estimate: compressedTokens,
190
+ original_tokens: originalTokens,
191
+ compression_ratio:
192
+ originalTokens > 0 ? compressedTokens / originalTokens : 0,
193
+ };
194
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Entity Graph Operations (v3)
3
+ *
4
+ * Temporal knowledge graph backed by SQLite entity_triples table.
5
+ * Supports time-aware queries (as_of), invalidation, and entity timeline.
6
+ *
7
+ * Inspired by MemPalace's temporal KG pattern: facts have valid_from/valid_to
8
+ * dates, enabling "what was true on date X?" queries.
9
+ */
10
+
11
+ import { getMemoryDB } from "./schema.js";
12
+ import type {
13
+ EntityQueryResult,
14
+ EntityTripleInput,
15
+ EntityTripleRow,
16
+ } from "./types.js";
17
+
18
+ // ============================================================================
19
+ // CRUD
20
+ // ============================================================================
21
+
22
+ /**
23
+ * Add a new entity triple to the knowledge graph.
24
+ */
25
+ export function addEntityTriple(input: EntityTripleInput): number {
26
+ const db = getMemoryDB();
27
+ const now = new Date();
28
+
29
+ const validFrom = input.valid_from ?? now.toISOString().slice(0, 10);
30
+
31
+ const result = db
32
+ .query(
33
+ `INSERT INTO entity_triples
34
+ (subject, predicate, object, valid_from, valid_to, confidence, source_observation_id, created_at, created_at_epoch)
35
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
36
+ )
37
+ .run(
38
+ input.subject.toLowerCase().trim(),
39
+ input.predicate.toLowerCase().trim(),
40
+ input.object.toLowerCase().trim(),
41
+ validFrom,
42
+ input.valid_to ?? null,
43
+ input.confidence ?? 1.0,
44
+ input.source_observation_id ?? null,
45
+ now.toISOString(),
46
+ now.getTime(),
47
+ );
48
+
49
+ return Number(result.lastInsertRowid);
50
+ }
51
+
52
+ /**
53
+ * Invalidate a triple by setting its valid_to date.
54
+ * Finds active triples matching subject+predicate+object and closes them.
55
+ */
56
+ export function invalidateTriple(
57
+ subject: string,
58
+ predicate: string,
59
+ object: string,
60
+ endDate?: string,
61
+ ): number {
62
+ const db = getMemoryDB();
63
+ const validTo = endDate ?? new Date().toISOString().slice(0, 10);
64
+
65
+ const result = db.run(
66
+ `UPDATE entity_triples
67
+ SET valid_to = ?
68
+ WHERE LOWER(subject) = LOWER(?) AND LOWER(predicate) = LOWER(?) AND LOWER(object) = LOWER(?)
69
+ AND valid_to IS NULL`,
70
+ [validTo, subject.trim(), predicate.trim(), object.trim()],
71
+ );
72
+
73
+ return result.changes;
74
+ }
75
+
76
+ /**
77
+ * Get a triple by ID.
78
+ */
79
+ export function getTripleById(id: number): EntityTripleRow | null {
80
+ const db = getMemoryDB();
81
+ return db
82
+ .query("SELECT * FROM entity_triples WHERE id = ?")
83
+ .get(id) as EntityTripleRow | null;
84
+ }
85
+
86
+ // ============================================================================
87
+ // Queries
88
+ // ============================================================================
89
+
90
+ /**
91
+ * Query entity relationships with optional time filtering.
92
+ * Returns triples where entity appears as subject or object.
93
+ */
94
+ export function queryEntity(
95
+ entity: string,
96
+ options: {
97
+ as_of?: string; // ISO date — filter to triples valid at this time
98
+ direction?: "out" | "in" | "both"; // out = subject, in = object, both = either
99
+ predicate?: string; // filter by predicate
100
+ activeOnly?: boolean; // only return currently active triples
101
+ limit?: number;
102
+ } = {},
103
+ ): EntityQueryResult[] {
104
+ const db = getMemoryDB();
105
+ const direction = options.direction ?? "both";
106
+ const limit = options.limit ?? 50;
107
+ const entityLower = entity.toLowerCase().trim();
108
+
109
+ const conditions: string[] = [];
110
+ const params: (string | number)[] = [];
111
+
112
+ // Direction filter
113
+ if (direction === "out") {
114
+ conditions.push("LOWER(subject) = ?");
115
+ params.push(entityLower);
116
+ } else if (direction === "in") {
117
+ conditions.push("LOWER(object) = ?");
118
+ params.push(entityLower);
119
+ } else {
120
+ conditions.push("(LOWER(subject) = ? OR LOWER(object) = ?)");
121
+ params.push(entityLower, entityLower);
122
+ }
123
+
124
+ // Predicate filter
125
+ if (options.predicate) {
126
+ conditions.push("LOWER(predicate) = ?");
127
+ params.push(options.predicate.toLowerCase().trim());
128
+ }
129
+
130
+ // Time filter
131
+ if (options.as_of) {
132
+ conditions.push("valid_from <= ?");
133
+ params.push(options.as_of);
134
+ conditions.push("(valid_to IS NULL OR valid_to >= ?)");
135
+ params.push(options.as_of);
136
+ } else if (options.activeOnly) {
137
+ conditions.push("valid_to IS NULL");
138
+ }
139
+
140
+ params.push(limit);
141
+
142
+ const sql = `
143
+ SELECT id, subject, predicate, object, valid_from, valid_to, confidence,
144
+ CASE WHEN valid_to IS NULL THEN 1 ELSE 0 END as is_active
145
+ FROM entity_triples
146
+ WHERE ${conditions.join(" AND ")}
147
+ ORDER BY valid_from DESC, created_at_epoch DESC
148
+ LIMIT ?
149
+ `;
150
+
151
+ return db.query(sql).all(...params) as EntityQueryResult[];
152
+ }
153
+
154
+ /**
155
+ * Get entity timeline — all triples involving an entity, sorted chronologically.
156
+ */
157
+ export function getEntityTimeline(
158
+ entity: string,
159
+ options: { limit?: number } = {},
160
+ ): EntityTripleRow[] {
161
+ const db = getMemoryDB();
162
+ const limit = options.limit ?? 100;
163
+ const entityLower = entity.toLowerCase().trim();
164
+
165
+ return db
166
+ .query(
167
+ `SELECT * FROM entity_triples
168
+ WHERE LOWER(subject) = ? OR LOWER(object) = ?
169
+ ORDER BY valid_from ASC, created_at_epoch ASC
170
+ LIMIT ?`,
171
+ )
172
+ .all(entityLower, entityLower, limit) as EntityTripleRow[];
173
+ }
174
+
175
+ /**
176
+ * Find contradictions: active triples with opposing predicates for same subject-object pair.
177
+ */
178
+ export function findContradictions(
179
+ subject: string,
180
+ predicate: string,
181
+ object: string,
182
+ ): EntityTripleRow[] {
183
+ const db = getMemoryDB();
184
+
185
+ return db
186
+ .query(
187
+ `SELECT * FROM entity_triples
188
+ WHERE LOWER(subject) = LOWER(?) AND LOWER(object) = LOWER(?)
189
+ AND LOWER(predicate) != LOWER(?)
190
+ AND valid_to IS NULL
191
+ ORDER BY created_at_epoch DESC`,
192
+ )
193
+ .all(subject.trim(), object.trim(), predicate.trim()) as EntityTripleRow[];
194
+ }
195
+
196
+ // ============================================================================
197
+ // Stats
198
+ // ============================================================================
199
+
200
+ /**
201
+ * Get entity graph statistics.
202
+ */
203
+ export function getEntityGraphStats(): {
204
+ total_triples: number;
205
+ active_triples: number;
206
+ unique_entities: number;
207
+ unique_predicates: number;
208
+ } {
209
+ const db = getMemoryDB();
210
+
211
+ const total = (
212
+ db.query("SELECT COUNT(*) as count FROM entity_triples").get() as {
213
+ count: number;
214
+ }
215
+ ).count;
216
+
217
+ const active = (
218
+ db
219
+ .query(
220
+ "SELECT COUNT(*) as count FROM entity_triples WHERE valid_to IS NULL",
221
+ )
222
+ .get() as {
223
+ count: number;
224
+ }
225
+ ).count;
226
+
227
+ const entities = (
228
+ db
229
+ .query(
230
+ `SELECT COUNT(DISTINCT entity) as count FROM (
231
+ SELECT subject as entity FROM entity_triples
232
+ UNION ALL
233
+ SELECT object as entity FROM entity_triples
234
+ )`,
235
+ )
236
+ .get() as { count: number }
237
+ ).count;
238
+
239
+ const predicates = (
240
+ db
241
+ .query("SELECT COUNT(DISTINCT predicate) as count FROM entity_triples")
242
+ .get() as {
243
+ count: number;
244
+ }
245
+ ).count;
246
+
247
+ return {
248
+ total_triples: total,
249
+ active_triples: active,
250
+ unique_entities: entities,
251
+ unique_predicates: predicates,
252
+ };
253
+ }