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.
- package/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +14 -9
- package/dist/template/.opencode/agent/build.md +0 -32
- package/dist/template/.opencode/agent/plan.md +0 -14
- package/dist/template/.opencode/agent/review.md +0 -40
- package/dist/template/.opencode/command/create.md +11 -61
- package/dist/template/.opencode/command/plan.md +11 -12
- package/dist/template/.opencode/command/pr.md +4 -16
- package/dist/template/.opencode/command/research.md +7 -16
- package/dist/template/.opencode/command/resume.md +2 -11
- package/dist/template/.opencode/command/review-codebase.md +9 -15
- package/dist/template/.opencode/command/ship.md +12 -53
- package/dist/template/.opencode/memory/project/user.md +7 -0
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +54 -67
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/README.md +1 -1
- package/dist/template/.opencode/plugin/lib/compact.ts +194 -0
- package/dist/template/.opencode/plugin/lib/db/graph.ts +253 -0
- package/dist/template/.opencode/plugin/lib/db/observations.ts +8 -3
- package/dist/template/.opencode/plugin/lib/db/schema.ts +96 -5
- package/dist/template/.opencode/plugin/lib/db/types.ts +73 -0
- package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +36 -3
- package/dist/template/.opencode/plugin/lib/memory-db.ts +12 -1
- package/dist/template/.opencode/plugin/lib/memory-tools.ts +137 -1
- package/dist/template/.opencode/plugin/memory.ts +2 -1
- package/dist/template/.opencode/skill/memory-grounding/SKILL.md +68 -0
- package/dist/template/.opencode/skill/verification-gates/SKILL.md +63 -0
- package/dist/template/.opencode/skill/workspace-setup/SKILL.md +76 -0
- package/package.json +1 -1
|
@@ -28,10 +28,11 @@ export function storeObservation(input: ObservationInput): number {
|
|
|
28
28
|
.query(
|
|
29
29
|
`
|
|
30
30
|
INSERT INTO observations (
|
|
31
|
-
type, title, subtitle, facts, narrative, concepts,
|
|
31
|
+
type, title, subtitle, facts, narrative, raw_source, concepts,
|
|
32
32
|
files_read, files_modified, confidence, bead_id,
|
|
33
|
-
supersedes, markdown_file, source,
|
|
34
|
-
|
|
33
|
+
supersedes, markdown_file, source, wing, hall, room,
|
|
34
|
+
created_at, created_at_epoch
|
|
35
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
35
36
|
`,
|
|
36
37
|
)
|
|
37
38
|
.run(
|
|
@@ -40,6 +41,7 @@ export function storeObservation(input: ObservationInput): number {
|
|
|
40
41
|
input.subtitle ?? null,
|
|
41
42
|
input.facts ? JSON.stringify(input.facts) : null,
|
|
42
43
|
input.narrative ?? null,
|
|
44
|
+
input.raw_source ?? null,
|
|
43
45
|
input.concepts ? JSON.stringify(input.concepts) : null,
|
|
44
46
|
input.files_read ? JSON.stringify(input.files_read) : null,
|
|
45
47
|
input.files_modified ? JSON.stringify(input.files_modified) : null,
|
|
@@ -48,6 +50,9 @@ export function storeObservation(input: ObservationInput): number {
|
|
|
48
50
|
input.supersedes ?? null,
|
|
49
51
|
input.markdown_file ?? null,
|
|
50
52
|
input.source ?? "manual",
|
|
53
|
+
input.wing ?? null,
|
|
54
|
+
input.hall ?? null,
|
|
55
|
+
input.room ?? null,
|
|
51
56
|
now.toISOString(),
|
|
52
57
|
now.getTime(),
|
|
53
58
|
);
|
|
@@ -28,10 +28,10 @@ function logRecovery(message: string): void {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// ============================================================================
|
|
31
|
-
// Schema v2
|
|
31
|
+
// Schema v3 (v2 + navigation, entity graph, raw source, chunk type)
|
|
32
32
|
// ============================================================================
|
|
33
33
|
|
|
34
|
-
const SCHEMA_VERSION =
|
|
34
|
+
const SCHEMA_VERSION = 3;
|
|
35
35
|
|
|
36
36
|
const SCHEMA_SQL = `
|
|
37
37
|
-- Schema versioning for migrations
|
|
@@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS schema_versions (
|
|
|
41
41
|
applied_at TEXT NOT NULL
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
-- Observations table (
|
|
44
|
+
-- Observations table (v3: added raw_source, wing, hall, room)
|
|
45
45
|
CREATE TABLE IF NOT EXISTS observations (
|
|
46
46
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
47
|
type TEXT NOT NULL CHECK(type IN ('decision','bugfix','feature','pattern','discovery','learning','warning')),
|
|
@@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS observations (
|
|
|
49
49
|
subtitle TEXT,
|
|
50
50
|
facts TEXT,
|
|
51
51
|
narrative TEXT,
|
|
52
|
+
raw_source TEXT,
|
|
52
53
|
concepts TEXT,
|
|
53
54
|
files_read TEXT,
|
|
54
55
|
files_modified TEXT,
|
|
@@ -59,6 +60,9 @@ CREATE TABLE IF NOT EXISTS observations (
|
|
|
59
60
|
valid_until TEXT,
|
|
60
61
|
markdown_file TEXT,
|
|
61
62
|
source TEXT CHECK(source IN ('manual','curator','imported')) DEFAULT 'manual',
|
|
63
|
+
wing TEXT,
|
|
64
|
+
hall TEXT CHECK(hall IS NULL OR hall IN ('facts','events','discoveries','preferences','advice')),
|
|
65
|
+
room TEXT,
|
|
62
66
|
created_at TEXT NOT NULL,
|
|
63
67
|
created_at_epoch INTEGER NOT NULL,
|
|
64
68
|
updated_at TEXT,
|
|
@@ -84,6 +88,10 @@ CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_e
|
|
|
84
88
|
CREATE INDEX IF NOT EXISTS idx_observations_bead_id ON observations(bead_id);
|
|
85
89
|
CREATE INDEX IF NOT EXISTS idx_observations_superseded ON observations(superseded_by) WHERE superseded_by IS NOT NULL;
|
|
86
90
|
CREATE INDEX IF NOT EXISTS idx_observations_source ON observations(source);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_observations_wing ON observations(wing) WHERE wing IS NOT NULL;
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_observations_hall ON observations(hall) WHERE hall IS NOT NULL;
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_observations_room ON observations(room) WHERE room IS NOT NULL;
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_observations_navigation ON observations(wing, hall, room) WHERE wing IS NOT NULL;
|
|
87
95
|
|
|
88
96
|
-- Memory files table
|
|
89
97
|
CREATE TABLE IF NOT EXISTS memory_files (
|
|
@@ -102,7 +110,7 @@ CREATE INDEX IF NOT EXISTS idx_memory_files_path ON memory_files(file_path);
|
|
|
102
110
|
|
|
103
111
|
|
|
104
112
|
|
|
105
|
-
-- Temporal messages table (
|
|
113
|
+
-- Temporal messages table (v3: added chunk_type)
|
|
106
114
|
CREATE TABLE IF NOT EXISTS temporal_messages (
|
|
107
115
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
108
116
|
session_id TEXT NOT NULL,
|
|
@@ -112,6 +120,7 @@ CREATE TABLE IF NOT EXISTS temporal_messages (
|
|
|
112
120
|
token_estimate INTEGER NOT NULL DEFAULT 0,
|
|
113
121
|
time_created INTEGER NOT NULL,
|
|
114
122
|
distillation_id INTEGER,
|
|
123
|
+
chunk_type TEXT,
|
|
115
124
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
116
125
|
FOREIGN KEY(distillation_id) REFERENCES distillations(id) ON DELETE SET NULL
|
|
117
126
|
);
|
|
@@ -139,6 +148,27 @@ CREATE TABLE IF NOT EXISTS distillations (
|
|
|
139
148
|
CREATE INDEX IF NOT EXISTS idx_distillations_session ON distillations(session_id, time_created DESC);
|
|
140
149
|
CREATE INDEX IF NOT EXISTS idx_distillations_time ON distillations(time_created DESC);
|
|
141
150
|
|
|
151
|
+
-- Entity triples table (v3: temporal knowledge graph)
|
|
152
|
+
CREATE TABLE IF NOT EXISTS entity_triples (
|
|
153
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
154
|
+
subject TEXT NOT NULL,
|
|
155
|
+
predicate TEXT NOT NULL,
|
|
156
|
+
object TEXT NOT NULL,
|
|
157
|
+
valid_from TEXT NOT NULL,
|
|
158
|
+
valid_to TEXT,
|
|
159
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
160
|
+
source_observation_id INTEGER,
|
|
161
|
+
created_at TEXT NOT NULL,
|
|
162
|
+
created_at_epoch INTEGER NOT NULL,
|
|
163
|
+
FOREIGN KEY(source_observation_id) REFERENCES observations(id) ON DELETE SET NULL
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_entity_subject ON entity_triples(subject);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_entity_object ON entity_triples(object);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_entity_predicate ON entity_triples(predicate);
|
|
169
|
+
CREATE INDEX IF NOT EXISTS idx_entity_valid ON entity_triples(valid_from, valid_to);
|
|
170
|
+
CREATE INDEX IF NOT EXISTS idx_entity_active ON entity_triples(subject, valid_to) WHERE valid_to IS NULL;
|
|
171
|
+
|
|
142
172
|
-- FTS5 for distillations (v2)
|
|
143
173
|
CREATE VIRTUAL TABLE IF NOT EXISTS distillations_fts USING fts5(
|
|
144
174
|
content,
|
|
@@ -243,6 +273,47 @@ CREATE INDEX IF NOT EXISTS idx_temporal_undistilled ON temporal_messages(session
|
|
|
243
273
|
CREATE INDEX IF NOT EXISTS idx_temporal_time ON temporal_messages(time_created DESC);
|
|
244
274
|
`;
|
|
245
275
|
|
|
276
|
+
// Migration from v2 to v3
|
|
277
|
+
const MIGRATION_V2_TO_V3 = `
|
|
278
|
+
-- Add raw_source column to observations
|
|
279
|
+
ALTER TABLE observations ADD COLUMN raw_source TEXT;
|
|
280
|
+
|
|
281
|
+
-- Add navigation columns to observations
|
|
282
|
+
ALTER TABLE observations ADD COLUMN wing TEXT;
|
|
283
|
+
ALTER TABLE observations ADD COLUMN hall TEXT CHECK(hall IS NULL OR hall IN ('facts','events','discoveries','preferences','advice'));
|
|
284
|
+
ALTER TABLE observations ADD COLUMN room TEXT;
|
|
285
|
+
|
|
286
|
+
-- Navigation indexes
|
|
287
|
+
CREATE INDEX IF NOT EXISTS idx_observations_wing ON observations(wing) WHERE wing IS NOT NULL;
|
|
288
|
+
CREATE INDEX IF NOT EXISTS idx_observations_hall ON observations(hall) WHERE hall IS NOT NULL;
|
|
289
|
+
CREATE INDEX IF NOT EXISTS idx_observations_room ON observations(room) WHERE room IS NOT NULL;
|
|
290
|
+
CREATE INDEX IF NOT EXISTS idx_observations_navigation ON observations(wing, hall, room) WHERE wing IS NOT NULL;
|
|
291
|
+
|
|
292
|
+
-- Add chunk_type to temporal_messages
|
|
293
|
+
ALTER TABLE temporal_messages ADD COLUMN chunk_type TEXT;
|
|
294
|
+
|
|
295
|
+
-- Entity triples table (temporal knowledge graph)
|
|
296
|
+
CREATE TABLE IF NOT EXISTS entity_triples (
|
|
297
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
298
|
+
subject TEXT NOT NULL,
|
|
299
|
+
predicate TEXT NOT NULL,
|
|
300
|
+
object TEXT NOT NULL,
|
|
301
|
+
valid_from TEXT NOT NULL,
|
|
302
|
+
valid_to TEXT,
|
|
303
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
304
|
+
source_observation_id INTEGER,
|
|
305
|
+
created_at TEXT NOT NULL,
|
|
306
|
+
created_at_epoch INTEGER NOT NULL,
|
|
307
|
+
FOREIGN KEY(source_observation_id) REFERENCES observations(id) ON DELETE SET NULL
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
CREATE INDEX IF NOT EXISTS idx_entity_subject ON entity_triples(subject);
|
|
311
|
+
CREATE INDEX IF NOT EXISTS idx_entity_object ON entity_triples(object);
|
|
312
|
+
CREATE INDEX IF NOT EXISTS idx_entity_predicate ON entity_triples(predicate);
|
|
313
|
+
CREATE INDEX IF NOT EXISTS idx_entity_valid ON entity_triples(valid_from, valid_to);
|
|
314
|
+
CREATE INDEX IF NOT EXISTS idx_entity_active ON entity_triples(subject, valid_to) WHERE valid_to IS NULL;
|
|
315
|
+
`;
|
|
316
|
+
|
|
246
317
|
// ============================================================================
|
|
247
318
|
// Database Manager
|
|
248
319
|
// ============================================================================
|
|
@@ -429,7 +500,7 @@ function initializeSchema(db: Database): void {
|
|
|
429
500
|
}
|
|
430
501
|
|
|
431
502
|
if (currentVersion === 0) {
|
|
432
|
-
// Fresh install — run full
|
|
503
|
+
// Fresh install — run full v3 schema
|
|
433
504
|
db.exec(SCHEMA_SQL);
|
|
434
505
|
|
|
435
506
|
// Run FTS triggers
|
|
@@ -443,6 +514,9 @@ function initializeSchema(db: Database): void {
|
|
|
443
514
|
if (currentVersion < 2) {
|
|
444
515
|
migrateV1ToV2(db);
|
|
445
516
|
}
|
|
517
|
+
if (currentVersion < 3) {
|
|
518
|
+
migrateV2ToV3(db);
|
|
519
|
+
}
|
|
446
520
|
}
|
|
447
521
|
|
|
448
522
|
// Record schema version
|
|
@@ -501,3 +575,20 @@ function migrateV1ToV2(db: Database): void {
|
|
|
501
575
|
// Triggers may already exist
|
|
502
576
|
}
|
|
503
577
|
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Migrate from schema v2 to v3.
|
|
581
|
+
* Adds: raw_source, wing/hall/room navigation, chunk_type, entity_triples table.
|
|
582
|
+
*/
|
|
583
|
+
function migrateV2ToV3(db: Database): void {
|
|
584
|
+
for (const stmt of MIGRATION_V2_TO_V3.split(";")) {
|
|
585
|
+
const trimmed = stmt.trim();
|
|
586
|
+
if (trimmed) {
|
|
587
|
+
try {
|
|
588
|
+
db.run(trimmed);
|
|
589
|
+
} catch {
|
|
590
|
+
// Statement may fail if already applied (e.g. column exists)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
@@ -46,6 +46,69 @@ export const MEMORY_CONFIG = {
|
|
|
46
46
|
},
|
|
47
47
|
} as const;
|
|
48
48
|
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Navigation Types (v3: structured navigation)
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
export type HallType = "facts" | "events" | "discoveries" | "preferences" | "advice";
|
|
54
|
+
|
|
55
|
+
export const VALID_HALLS: HallType[] = ["facts", "events", "discoveries", "preferences", "advice"];
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Entity Graph Types (v3: temporal knowledge graph)
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
export interface EntityTripleInput {
|
|
62
|
+
subject: string;
|
|
63
|
+
predicate: string;
|
|
64
|
+
object: string;
|
|
65
|
+
valid_from?: string; // ISO date or epoch
|
|
66
|
+
valid_to?: string | null;
|
|
67
|
+
confidence?: number; // 0.0–1.0
|
|
68
|
+
source_observation_id?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface EntityTripleRow {
|
|
72
|
+
id: number;
|
|
73
|
+
subject: string;
|
|
74
|
+
predicate: string;
|
|
75
|
+
object: string;
|
|
76
|
+
valid_from: string;
|
|
77
|
+
valid_to: string | null;
|
|
78
|
+
confidence: number;
|
|
79
|
+
source_observation_id: number | null;
|
|
80
|
+
created_at: string;
|
|
81
|
+
created_at_epoch: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface EntityQueryResult {
|
|
85
|
+
id: number;
|
|
86
|
+
subject: string;
|
|
87
|
+
predicate: string;
|
|
88
|
+
object: string;
|
|
89
|
+
valid_from: string;
|
|
90
|
+
valid_to: string | null;
|
|
91
|
+
confidence: number;
|
|
92
|
+
is_active: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Compact Format Types (v3: AAAK-inspired compression)
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
export interface CompactEntry {
|
|
100
|
+
code: string; // 3-letter entity code
|
|
101
|
+
full: string; // Full name
|
|
102
|
+
role?: string; // Role or type
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface CompactResult {
|
|
106
|
+
compressed: string;
|
|
107
|
+
token_estimate: number;
|
|
108
|
+
original_tokens: number;
|
|
109
|
+
compression_ratio: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
49
112
|
// ============================================================================
|
|
50
113
|
// Observation Types
|
|
51
114
|
// ============================================================================
|
|
@@ -68,6 +131,7 @@ export interface ObservationRow {
|
|
|
68
131
|
subtitle: string | null;
|
|
69
132
|
facts: string | null; // JSON array
|
|
70
133
|
narrative: string | null;
|
|
134
|
+
raw_source: string | null; // v3: verbatim source text
|
|
71
135
|
concepts: string | null; // JSON array
|
|
72
136
|
files_read: string | null; // JSON array
|
|
73
137
|
files_modified: string | null; // JSON array
|
|
@@ -78,6 +142,9 @@ export interface ObservationRow {
|
|
|
78
142
|
valid_until: string | null;
|
|
79
143
|
markdown_file: string | null;
|
|
80
144
|
source: ObservationSource;
|
|
145
|
+
wing: string | null; // v3: navigation wing (project/person)
|
|
146
|
+
hall: HallType | null; // v3: navigation hall (facts/events/...)
|
|
147
|
+
room: string | null; // v3: navigation room (topic)
|
|
81
148
|
created_at: string;
|
|
82
149
|
created_at_epoch: number;
|
|
83
150
|
updated_at: string | null;
|
|
@@ -89,6 +156,7 @@ export interface ObservationInput {
|
|
|
89
156
|
subtitle?: string;
|
|
90
157
|
facts?: string[];
|
|
91
158
|
narrative?: string;
|
|
159
|
+
raw_source?: string; // v3: verbatim source text
|
|
92
160
|
concepts?: string[];
|
|
93
161
|
files_read?: string[];
|
|
94
162
|
files_modified?: string[];
|
|
@@ -97,6 +165,9 @@ export interface ObservationInput {
|
|
|
97
165
|
supersedes?: number;
|
|
98
166
|
markdown_file?: string;
|
|
99
167
|
source?: ObservationSource;
|
|
168
|
+
wing?: string; // v3: navigation wing
|
|
169
|
+
hall?: HallType; // v3: navigation hall
|
|
170
|
+
room?: string; // v3: navigation room
|
|
100
171
|
}
|
|
101
172
|
|
|
102
173
|
export interface SearchIndexResult {
|
|
@@ -136,6 +207,7 @@ export interface TemporalMessageRow {
|
|
|
136
207
|
token_estimate: number;
|
|
137
208
|
time_created: number;
|
|
138
209
|
distillation_id: number | null;
|
|
210
|
+
chunk_type: string | null; // v3: exchange-pair | paragraph | sliding-window
|
|
139
211
|
created_at: string;
|
|
140
212
|
}
|
|
141
213
|
|
|
@@ -146,6 +218,7 @@ export interface TemporalMessageInput {
|
|
|
146
218
|
content: string;
|
|
147
219
|
token_estimate: number;
|
|
148
220
|
time_created: number;
|
|
221
|
+
chunk_type?: string; // v3: exchange-pair | paragraph | sliding-window
|
|
149
222
|
}
|
|
150
223
|
|
|
151
224
|
// ============================================================================
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory Plugin — Admin Tools
|
|
3
3
|
*
|
|
4
|
-
* memory-admin (
|
|
4
|
+
* memory-admin (12 operations).
|
|
5
5
|
*
|
|
6
6
|
* Uses factory pattern: createAdminTools(deps) returns tool definitions.
|
|
7
7
|
*/
|
|
@@ -20,10 +20,13 @@ import {
|
|
|
20
20
|
type ConfidenceLevel,
|
|
21
21
|
checkFTS5Available,
|
|
22
22
|
checkpointWAL,
|
|
23
|
+
findGraphContradictions,
|
|
23
24
|
getCaptureStats,
|
|
24
25
|
getDatabaseSizes,
|
|
25
26
|
getDistillationStats,
|
|
27
|
+
getEntityGraphStats,
|
|
26
28
|
getMarkdownFilesInSqlite,
|
|
29
|
+
getMemoryDB,
|
|
27
30
|
getObservationStats,
|
|
28
31
|
type ObservationType,
|
|
29
32
|
rebuildFTS5,
|
|
@@ -73,11 +76,12 @@ export function createAdminTools(deps: AdminToolDeps) {
|
|
|
73
76
|
});
|
|
74
77
|
const captureStats = getCaptureStats();
|
|
75
78
|
const distillStats = getDistillationStats();
|
|
79
|
+
const graphStats = getEntityGraphStats();
|
|
76
80
|
return [
|
|
77
81
|
"## Memory System Status\n",
|
|
78
82
|
`**Database**: ${(sizes.total / 1024).toFixed(1)} KB`,
|
|
79
83
|
`**FTS5**: ${checkFTS5Available() ? "Available (porter stemming)" : "Unavailable"}`,
|
|
80
|
-
`**Schema**:
|
|
84
|
+
`**Schema**: v3 (4-tier + entity graph)\n`,
|
|
81
85
|
"### Observations",
|
|
82
86
|
...Object.entries(stats).map(([k, v]) => ` ${k}: ${v}`),
|
|
83
87
|
` Archivable (>${olderThanDays}d): ${archivable}\n`,
|
|
@@ -86,7 +90,11 @@ export function createAdminTools(deps: AdminToolDeps) {
|
|
|
86
90
|
` Sessions: ${captureStats.sessions}\n`,
|
|
87
91
|
"### Distillations",
|
|
88
92
|
` Total: ${distillStats.total} (${distillStats.sessions} sessions)`,
|
|
89
|
-
` Avg compression: ${(distillStats.avgCompression * 100).toFixed(1)}
|
|
93
|
+
` Avg compression: ${(distillStats.avgCompression * 100).toFixed(1)}%\n`,
|
|
94
|
+
"### Entity Graph",
|
|
95
|
+
` Triples: ${graphStats.total_triples} (active: ${graphStats.active_triples})`,
|
|
96
|
+
` Entities: ${graphStats.unique_entities}`,
|
|
97
|
+
` Predicates: ${graphStats.unique_predicates}`,
|
|
90
98
|
].join("\n");
|
|
91
99
|
}
|
|
92
100
|
case "full": {
|
|
@@ -132,6 +140,31 @@ export function createAdminTools(deps: AdminToolDeps) {
|
|
|
132
140
|
}
|
|
133
141
|
case "lint": {
|
|
134
142
|
const result = lintMemory({ staleDays: olderThanDays });
|
|
143
|
+
|
|
144
|
+
// Entity graph contradiction scan
|
|
145
|
+
const graphStats = getEntityGraphStats();
|
|
146
|
+
if (graphStats.total_triples > 0) {
|
|
147
|
+
try {
|
|
148
|
+
// Check each active triple for contradictions
|
|
149
|
+
const db = getMemoryDB();
|
|
150
|
+
const activeTriples = db.query(
|
|
151
|
+
"SELECT DISTINCT subject, predicate, object FROM entity_triples WHERE valid_to IS NULL LIMIT 200"
|
|
152
|
+
).all() as { subject: string; predicate: string; object: string }[];
|
|
153
|
+
for (const t of activeTriples) {
|
|
154
|
+
const contradictions = findGraphContradictions(t.subject, t.predicate, t.object);
|
|
155
|
+
if (contradictions.length > 0) {
|
|
156
|
+
result.issues.push({
|
|
157
|
+
severity: "medium" as const,
|
|
158
|
+
title: `Graph contradiction: ${t.subject} ↔ ${t.object}`,
|
|
159
|
+
detail: `Active triple "${t.subject} —[${t.predicate}]→ ${t.object}" has ${contradictions.length} conflicting predicate(s): ${contradictions.map(c => c.predicate).join(", ")}`,
|
|
160
|
+
suggestion: `Use memory-graph-invalidate to close outdated triples`,
|
|
161
|
+
type: "contradiction" as const,
|
|
162
|
+
observation_ids: contradictions.map(c => c.source_observation_id).filter((id): id is number => id != null),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch { /* Graph table may not exist yet */ }
|
|
167
|
+
}
|
|
135
168
|
if (result.issues.length === 0) {
|
|
136
169
|
return `Memory lint: clean (${result.stats.total_observations} observations, 0 issues).`;
|
|
137
170
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory Database Module
|
|
2
|
+
* Memory Database Module v3 — Barrel Export
|
|
3
3
|
*
|
|
4
4
|
* Re-exports all functions and types from sub-modules in ./db/.
|
|
5
5
|
* This preserves backward compatibility for existing imports from "./lib/memory-db.js".
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* db/observations.ts — Observation CRUD, search, timeline, stats
|
|
11
11
|
* db/pipeline.ts — Temporal messages, distillations, relevance scoring
|
|
12
12
|
* db/maintenance.ts — Memory files, FTS5, DB maintenance
|
|
13
|
+
* db/graph.ts — Entity graph: temporal triples, queries, stats
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
// Memory Files, FTS5, and Maintenance
|
|
@@ -52,6 +53,16 @@ export {
|
|
|
52
53
|
storeDistillation,
|
|
53
54
|
storeTemporalMessage,
|
|
54
55
|
} from "./db/pipeline.js";
|
|
56
|
+
// Entity Graph Operations (v3)
|
|
57
|
+
export {
|
|
58
|
+
addEntityTriple,
|
|
59
|
+
findContradictions as findGraphContradictions,
|
|
60
|
+
getEntityGraphStats,
|
|
61
|
+
getEntityTimeline,
|
|
62
|
+
getTripleById,
|
|
63
|
+
invalidateTriple,
|
|
64
|
+
queryEntity,
|
|
65
|
+
} from "./db/graph.js";
|
|
55
66
|
// Database Manager
|
|
56
67
|
export { closeMemoryDB, getMemoryDB } from "./db/schema.js";
|
|
57
68
|
// Types & Configuration
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory Plugin — Core Tools
|
|
3
3
|
*
|
|
4
|
-
* observation, memory-search, memory-get, memory-read, memory-update, memory-timeline
|
|
4
|
+
* observation, memory-search, memory-get, memory-read, memory-update, memory-timeline,
|
|
5
|
+
* memory-graph-add, memory-graph-query, memory-graph-invalidate, memory-compact
|
|
5
6
|
*
|
|
6
7
|
* Uses factory pattern: createCoreTools(deps) returns tool definitions
|
|
7
8
|
* that can be spread into plugin's tool:{} export.
|
|
@@ -11,17 +12,23 @@ import { readdir } from "node:fs/promises";
|
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
13
14
|
import {
|
|
15
|
+
addEntityTriple,
|
|
14
16
|
type ConfidenceLevel,
|
|
15
17
|
checkFTS5Available,
|
|
18
|
+
getMemoryDB,
|
|
16
19
|
getMemoryFile,
|
|
17
20
|
getObservationsByIds,
|
|
18
21
|
getTimelineAroundObservation,
|
|
22
|
+
invalidateTriple,
|
|
19
23
|
type ObservationSource,
|
|
20
24
|
type ObservationType,
|
|
25
|
+
queryEntity,
|
|
21
26
|
searchDistillationsFTS,
|
|
22
27
|
searchObservationsFTS,
|
|
23
28
|
storeObservation,
|
|
24
29
|
upsertMemoryFile,
|
|
30
|
+
type HallType,
|
|
31
|
+
VALID_HALLS,
|
|
25
32
|
} from "./memory-db.js";
|
|
26
33
|
import {
|
|
27
34
|
autoDetectFiles,
|
|
@@ -32,6 +39,7 @@ import {
|
|
|
32
39
|
VALID_TYPES,
|
|
33
40
|
} from "./memory-helpers.js";
|
|
34
41
|
import { validateObservation } from "./validate.js";
|
|
42
|
+
import { compactObservations } from "./compact.js";
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
45
|
* Wrap a memory tool execute function with DB error handling.
|
|
@@ -118,6 +126,22 @@ export function createCoreTools(deps: CoreToolDeps) {
|
|
|
118
126
|
.string()
|
|
119
127
|
.optional()
|
|
120
128
|
.describe("manual, curator, imported"),
|
|
129
|
+
wing: tool.schema
|
|
130
|
+
.string()
|
|
131
|
+
.optional()
|
|
132
|
+
.describe("Navigation wing (project or person name)"),
|
|
133
|
+
hall: tool.schema
|
|
134
|
+
.string()
|
|
135
|
+
.optional()
|
|
136
|
+
.describe("Navigation hall: facts, events, discoveries, preferences, advice"),
|
|
137
|
+
room: tool.schema
|
|
138
|
+
.string()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe("Navigation room (topic name, e.g. auth-migration)"),
|
|
141
|
+
raw_source: tool.schema
|
|
142
|
+
.string()
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Verbatim source text to preserve losslessly alongside narrative"),
|
|
121
145
|
},
|
|
122
146
|
execute: withDBErrorHandling(async (args) => {
|
|
123
147
|
const obsType = args.type as ObservationType;
|
|
@@ -157,6 +181,10 @@ export function createCoreTools(deps: CoreToolDeps) {
|
|
|
157
181
|
}
|
|
158
182
|
|
|
159
183
|
const source = (args.source ?? "manual") as ObservationSource;
|
|
184
|
+
const hall = args.hall as HallType | undefined;
|
|
185
|
+
if (hall && !VALID_HALLS.includes(hall)) {
|
|
186
|
+
return `Error: Invalid hall "${args.hall}". Valid: ${VALID_HALLS.join(", ")}`;
|
|
187
|
+
}
|
|
160
188
|
|
|
161
189
|
// Validation gate: check for duplicates, contradictions, low quality
|
|
162
190
|
const validation = validateObservation({
|
|
@@ -172,6 +200,9 @@ export function createCoreTools(deps: CoreToolDeps) {
|
|
|
172
200
|
bead_id: args.bead_id,
|
|
173
201
|
supersedes,
|
|
174
202
|
source,
|
|
203
|
+
wing: args.wing,
|
|
204
|
+
hall,
|
|
205
|
+
room: args.room,
|
|
175
206
|
});
|
|
176
207
|
|
|
177
208
|
if (validation.verdict === "reject") {
|
|
@@ -188,6 +219,7 @@ export function createCoreTools(deps: CoreToolDeps) {
|
|
|
188
219
|
subtitle: args.subtitle,
|
|
189
220
|
facts,
|
|
190
221
|
narrative,
|
|
222
|
+
raw_source: args.raw_source,
|
|
191
223
|
concepts,
|
|
192
224
|
files_read: filesRead,
|
|
193
225
|
files_modified: filesModified,
|
|
@@ -195,6 +227,9 @@ export function createCoreTools(deps: CoreToolDeps) {
|
|
|
195
227
|
bead_id: args.bead_id,
|
|
196
228
|
supersedes,
|
|
197
229
|
source,
|
|
230
|
+
wing: args.wing,
|
|
231
|
+
hall,
|
|
232
|
+
room: args.room,
|
|
198
233
|
});
|
|
199
234
|
|
|
200
235
|
const warnings = validation.issues.length > 0
|
|
@@ -395,5 +430,106 @@ export function createCoreTools(deps: CoreToolDeps) {
|
|
|
395
430
|
return lines.join("\n");
|
|
396
431
|
}),
|
|
397
432
|
}),
|
|
433
|
+
|
|
434
|
+
"memory-graph-add": tool({
|
|
435
|
+
description: `Add a triple to the entity knowledge graph.\n\nStores a subject-predicate-object relationship with optional temporal bounds and confidence.\nUse for structured facts like "project uses typescript" or "user prefers dark-mode".\n\nExample:\nmemory-graph-add({ subject: "project", predicate: "uses", object: "typescript" })\nmemory-graph-add({ subject: "auth", predicate: "depends-on", object: "jwt", confidence: 0.9, source_observation_id: 42 })`,
|
|
436
|
+
args: {
|
|
437
|
+
subject: tool.schema.string().describe("Entity subject"),
|
|
438
|
+
predicate: tool.schema.string().describe("Relationship type (e.g. uses, depends-on, prefers)"),
|
|
439
|
+
object: tool.schema.string().describe("Entity object"),
|
|
440
|
+
valid_from: tool.schema.string().optional().describe("Start date (ISO, default: today)"),
|
|
441
|
+
valid_to: tool.schema.string().optional().describe("End date (ISO, null = still active)"),
|
|
442
|
+
confidence: tool.schema.number().optional().describe("Confidence 0.0-1.0 (default: 1.0)"),
|
|
443
|
+
source_observation_id: tool.schema.number().optional().describe("Link to source observation"),
|
|
444
|
+
},
|
|
445
|
+
execute: withDBErrorHandling(async (args) => {
|
|
446
|
+
if (!args.subject?.trim() || !args.predicate?.trim() || !args.object?.trim()) {
|
|
447
|
+
return "Error: subject, predicate, and object are required.";
|
|
448
|
+
}
|
|
449
|
+
const id = addEntityTriple({
|
|
450
|
+
subject: args.subject,
|
|
451
|
+
predicate: args.predicate,
|
|
452
|
+
object: args.object,
|
|
453
|
+
valid_from: args.valid_from,
|
|
454
|
+
valid_to: args.valid_to,
|
|
455
|
+
confidence: args.confidence,
|
|
456
|
+
source_observation_id: args.source_observation_id,
|
|
457
|
+
});
|
|
458
|
+
return `Triple #${id} added: ${args.subject} —[${args.predicate}]→ ${args.object}`;
|
|
459
|
+
}),
|
|
460
|
+
}),
|
|
461
|
+
|
|
462
|
+
"memory-graph-query": tool({
|
|
463
|
+
description: `Query the entity knowledge graph.\n\nFind relationships for an entity with optional time and direction filters.\nReturns triples where the entity appears as subject, object, or both.\n\nExample:\nmemory-graph-query({ entity: "typescript" })\nmemory-graph-query({ entity: "auth", direction: "out", active_only: true })\nmemory-graph-query({ entity: "project", as_of: "2025-06-01" })`,
|
|
464
|
+
args: {
|
|
465
|
+
entity: tool.schema.string().describe("Entity to query"),
|
|
466
|
+
direction: tool.schema.string().optional().describe("out (subject), in (object), both (default)"),
|
|
467
|
+
predicate: tool.schema.string().optional().describe("Filter by predicate"),
|
|
468
|
+
as_of: tool.schema.string().optional().describe("ISO date for point-in-time query"),
|
|
469
|
+
active_only: tool.schema.boolean().optional().describe("Only active triples (default: false)"),
|
|
470
|
+
limit: tool.schema.number().optional().describe("Max results (default: 50)"),
|
|
471
|
+
},
|
|
472
|
+
execute: withDBErrorHandling(async (args) => {
|
|
473
|
+
if (!args.entity?.trim()) return "Error: entity is required.";
|
|
474
|
+
const results = queryEntity(args.entity, {
|
|
475
|
+
direction: args.direction as "out" | "in" | "both" | undefined,
|
|
476
|
+
predicate: args.predicate,
|
|
477
|
+
as_of: args.as_of,
|
|
478
|
+
activeOnly: args.active_only,
|
|
479
|
+
limit: args.limit,
|
|
480
|
+
});
|
|
481
|
+
if (results.length === 0) return `No triples found for entity "${args.entity}".`;
|
|
482
|
+
const lines: string[] = [
|
|
483
|
+
`## Entity Graph: ${args.entity} (${results.length} triples)\n`,
|
|
484
|
+
"| ID | Subject | Predicate | Object | Active | Confidence |",
|
|
485
|
+
"|---|---|---|---|---|---|",
|
|
486
|
+
];
|
|
487
|
+
for (const r of results) {
|
|
488
|
+
lines.push(`| ${r.id} | ${r.subject} | ${r.predicate} | ${r.object} | ${r.is_active ? "\u2705" : "\u274C"} | ${(r.confidence * 100).toFixed(0)}% |`);
|
|
489
|
+
}
|
|
490
|
+
return lines.join("\n");
|
|
491
|
+
}),
|
|
492
|
+
}),
|
|
493
|
+
|
|
494
|
+
"memory-graph-invalidate": tool({
|
|
495
|
+
description: `Invalidate (close) entity triples in the knowledge graph.\n\nMarks active triples matching subject+predicate+object as no longer valid by setting valid_to.\nUse when a fact is no longer true (e.g. project stopped using a library).\n\nExample:\nmemory-graph-invalidate({ subject: "project", predicate: "uses", object: "moment.js" })`,
|
|
496
|
+
args: {
|
|
497
|
+
subject: tool.schema.string().describe("Entity subject"),
|
|
498
|
+
predicate: tool.schema.string().describe("Relationship type"),
|
|
499
|
+
object: tool.schema.string().describe("Entity object"),
|
|
500
|
+
end_date: tool.schema.string().optional().describe("End date (ISO, default: today)"),
|
|
501
|
+
},
|
|
502
|
+
execute: withDBErrorHandling(async (args) => {
|
|
503
|
+
if (!args.subject?.trim() || !args.predicate?.trim() || !args.object?.trim()) {
|
|
504
|
+
return "Error: subject, predicate, and object are required.";
|
|
505
|
+
}
|
|
506
|
+
const count = invalidateTriple(args.subject, args.predicate, args.object, args.end_date);
|
|
507
|
+
return count > 0
|
|
508
|
+
? `Invalidated ${count} triple(s): ${args.subject} —[${args.predicate}]→ ${args.object}`
|
|
509
|
+
: `No active triples found matching ${args.subject} —[${args.predicate}]→ ${args.object}.`;
|
|
510
|
+
}),
|
|
511
|
+
}),
|
|
512
|
+
|
|
513
|
+
"memory-compact": tool({
|
|
514
|
+
description: `Compact observations into a dense, token-efficient format.\n\nUses AAAK-inspired pipe-separated compression achieving ~3-5x token reduction.\nUseful for injecting memory into prompts with minimal token cost.\n\nExample:\nmemory-compact({})\nmemory-compact({ limit: 20 })`,
|
|
515
|
+
args: {
|
|
516
|
+
limit: tool.schema.number().optional().describe("Max observations to compact (default: all)"),
|
|
517
|
+
},
|
|
518
|
+
execute: withDBErrorHandling(async (args) => {
|
|
519
|
+
const db = getMemoryDB();
|
|
520
|
+
const limitClause = args.limit ? `LIMIT ${Number(args.limit)}` : "";
|
|
521
|
+
const observations = db.query(
|
|
522
|
+
`SELECT id, type, title, narrative, concepts, wing, hall, room, confidence, created_at
|
|
523
|
+
FROM observations
|
|
524
|
+
WHERE superseded_by IS NULL
|
|
525
|
+
ORDER BY created_at_epoch DESC ${limitClause}`
|
|
526
|
+
).all() as { id: number; type: string; title: string; narrative?: string | null; concepts?: string | null; wing?: string | null; hall?: string | null; room?: string | null; confidence?: string | null; created_at?: string | null }[];
|
|
527
|
+
if (observations.length === 0) return "No observations to compact.";
|
|
528
|
+
const result = compactObservations(observations);
|
|
529
|
+
if (!result.compressed) return "No observations to compact.";
|
|
530
|
+
const ratio = result.compression_ratio > 0 ? ` (${(result.compression_ratio * 100).toFixed(0)}% of original)` : "";
|
|
531
|
+
return `## Compact Memory (${observations.length} observations, ~${result.token_estimate} tokens${ratio})\n\n${result.compressed}`;
|
|
532
|
+
}),
|
|
533
|
+
}),
|
|
398
534
|
};
|
|
399
535
|
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* 5. Context Management — messages.transform → token budget enforcement
|
|
12
12
|
*
|
|
13
13
|
* Tools: observation, memory-search, memory-get, memory-read,
|
|
14
|
-
* memory-update, memory-timeline, memory-
|
|
14
|
+
* memory-update, memory-timeline, memory-graph-add, memory-graph-query,
|
|
15
|
+
* memory-graph-invalidate, memory-compact, memory-admin
|
|
15
16
|
*
|
|
16
17
|
* Module structure:
|
|
17
18
|
* memory.ts — Plugin entry (this file)
|