omo-memory 0.1.11 → 0.1.12
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/README.md +83 -3
- package/dist/cli.js +113 -3
- package/dist/conceptExtraction.js +188 -0
- package/dist/globalMemory.js +162 -0
- package/dist/globalMemoryCanonical.js +32 -0
- package/dist/globalMemoryImport.js +194 -0
- package/dist/graphTui.js +239 -0
- package/dist/mcp.js +26 -3
- package/dist/mcpOntologyTools.js +117 -0
- package/dist/memory.js +66 -29
- package/dist/memoryDb.js +145 -1
- package/dist/memoryRecall.js +56 -0
- package/dist/memoryReport.js +33 -0
- package/dist/ontologyCore.js +142 -0
- package/dist/ontologyGraph.js +173 -0
- package/dist/ontologyQueries.js +30 -0
- package/dist/ontologySupersede.js +49 -0
- package/dist/retentionPolicy.js +76 -0
- package/dist/retentionRecompute.js +175 -0
- package/docs/adapter-integration.md +113 -3
- package/docs/epic-omo-memory.md +63 -7
- package/package.json +3 -1
package/dist/memory.js
CHANGED
|
@@ -9,32 +9,6 @@ export class PurgeConfirmationError extends Error {
|
|
|
9
9
|
this.name = "PurgeConfirmationError";
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
export function memoryPaths() {
|
|
13
|
-
return { dbPath: defaultDbPath() };
|
|
14
|
-
}
|
|
15
|
-
export function doctorReport(dbPath = defaultDbPath()) {
|
|
16
|
-
const db = openMemoryDb(dbPath);
|
|
17
|
-
try {
|
|
18
|
-
migrate(db);
|
|
19
|
-
const project = resolveStoredProject(db, resolveProjectContext());
|
|
20
|
-
const schemaVersion = Number(db.prepare("SELECT value FROM schema_meta WHERE key = 'schema_version'").pluck().get());
|
|
21
|
-
const count = (table) => Number(db.prepare(`SELECT COUNT(*) FROM ${table}`).pluck().get());
|
|
22
|
-
return {
|
|
23
|
-
paths: { dbPath },
|
|
24
|
-
schemaVersion,
|
|
25
|
-
project,
|
|
26
|
-
counts: {
|
|
27
|
-
projects: count("projects"),
|
|
28
|
-
sessions: count("sessions"),
|
|
29
|
-
events: count("events"),
|
|
30
|
-
handoffs: count("handoffs"),
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
db.close();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
12
|
export function upsertProject(db, project) {
|
|
39
13
|
const now = new Date().toISOString();
|
|
40
14
|
db.prepare(`
|
|
@@ -64,8 +38,7 @@ export function startSession(input, dbPath = defaultDbPath()) {
|
|
|
64
38
|
}
|
|
65
39
|
}
|
|
66
40
|
export function bootstrapSession(input, dbPath = defaultDbPath()) {
|
|
67
|
-
|
|
68
|
-
return { ...session, recentEvents: recentEvents(input.limit, dbPath) };
|
|
41
|
+
return startSession({ host: input.host, adapter: input.adapter }, dbPath);
|
|
69
42
|
}
|
|
70
43
|
export function recordEvent(input, dbPath = defaultDbPath()) {
|
|
71
44
|
const db = openMemoryDb(dbPath);
|
|
@@ -141,6 +114,50 @@ export function exportMemory(dbPath = defaultDbPath()) {
|
|
|
141
114
|
.prepare(`
|
|
142
115
|
SELECT id, session_id AS sessionId, summary_md AS summaryMd, created_at AS createdAt FROM handoffs
|
|
143
116
|
WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
117
|
+
`)
|
|
118
|
+
.all(project.id);
|
|
119
|
+
const concepts = db
|
|
120
|
+
.prepare(`
|
|
121
|
+
SELECT id, kind, label, description, aliases_json AS aliasesJson, payload_json AS payloadJson,
|
|
122
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt,
|
|
123
|
+
COALESCE(score, 0) AS score,
|
|
124
|
+
COALESCE(retention_class, 'working') AS retentionClass,
|
|
125
|
+
COALESCE(manual_pin, 0) AS manualPin,
|
|
126
|
+
COALESCE(ref_count, 0) AS refCount,
|
|
127
|
+
COALESCE(project_spread, 1) AS projectSpread,
|
|
128
|
+
first_seen AS firstSeen, last_seen AS lastSeen
|
|
129
|
+
FROM concepts WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
130
|
+
`)
|
|
131
|
+
.all(project.id);
|
|
132
|
+
const relations = db
|
|
133
|
+
.prepare(`
|
|
134
|
+
SELECT id, source_type AS sourceType, source_id AS sourceId, target_type AS targetType, target_id AS targetId,
|
|
135
|
+
relation, weight, payload_json AS payloadJson, valid_from AS validFrom, valid_to AS validTo,
|
|
136
|
+
created_at AS createdAt, updated_at AS updatedAt
|
|
137
|
+
FROM relations WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
138
|
+
`)
|
|
139
|
+
.all(project.id);
|
|
140
|
+
const durableMemories = db
|
|
141
|
+
.prepare(`
|
|
142
|
+
SELECT id, type, summary, body, source_event_id AS sourceEventId, source_handoff_id AS sourceHandoffId,
|
|
143
|
+
confidence, status, COALESCE(retention_class, 'durable') AS retentionClass,
|
|
144
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
145
|
+
FROM durable_memories WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
146
|
+
`)
|
|
147
|
+
.all(project.id);
|
|
148
|
+
const decisionRecords = db
|
|
149
|
+
.prepare(`
|
|
150
|
+
SELECT id, title, rationale, alternatives_json AS alternativesJson, evidence_json AS evidenceJson,
|
|
151
|
+
status, reversible, source_event_id AS sourceEventId, supersedes_decision_id AS supersedesDecisionId,
|
|
152
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
153
|
+
FROM decision_records WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
154
|
+
`)
|
|
155
|
+
.all(project.id);
|
|
156
|
+
const memoryReferences = db
|
|
157
|
+
.prepare(`
|
|
158
|
+
SELECT id, source_type AS sourceType, source_id AS sourceId, target_type AS targetType, target_id AS targetId,
|
|
159
|
+
ref_kind AS refKind, weight, created_at AS createdAt
|
|
160
|
+
FROM memory_references WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
144
161
|
`)
|
|
145
162
|
.all(project.id);
|
|
146
163
|
return {
|
|
@@ -151,6 +168,11 @@ export function exportMemory(dbPath = defaultDbPath()) {
|
|
|
151
168
|
sessions,
|
|
152
169
|
events,
|
|
153
170
|
handoffs,
|
|
171
|
+
concepts,
|
|
172
|
+
relations,
|
|
173
|
+
durableMemories,
|
|
174
|
+
decisionRecords,
|
|
175
|
+
memoryReferences,
|
|
154
176
|
};
|
|
155
177
|
}
|
|
156
178
|
finally {
|
|
@@ -165,6 +187,18 @@ export function purgeMemory(input, dbPath = defaultDbPath()) {
|
|
|
165
187
|
migrate(db);
|
|
166
188
|
const project = resolveStoredProject(db, resolveProjectContext());
|
|
167
189
|
const deleteProject = db.transaction(() => {
|
|
190
|
+
const relations = db
|
|
191
|
+
.prepare("DELETE FROM relations WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
192
|
+
.run(project.id, project.repoRoot).changes;
|
|
193
|
+
const decisionRecords = db
|
|
194
|
+
.prepare("DELETE FROM decision_records WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
195
|
+
.run(project.id, project.repoRoot).changes;
|
|
196
|
+
const durableMemories = db
|
|
197
|
+
.prepare("DELETE FROM durable_memories WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
198
|
+
.run(project.id, project.repoRoot).changes;
|
|
199
|
+
const concepts = db
|
|
200
|
+
.prepare("DELETE FROM concepts WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
201
|
+
.run(project.id, project.repoRoot).changes;
|
|
168
202
|
const events = db
|
|
169
203
|
.prepare("DELETE FROM events WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
170
204
|
.run(project.id, project.repoRoot).changes;
|
|
@@ -174,8 +208,11 @@ export function purgeMemory(input, dbPath = defaultDbPath()) {
|
|
|
174
208
|
const sessions = db
|
|
175
209
|
.prepare("DELETE FROM sessions WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
176
210
|
.run(project.id, project.repoRoot).changes;
|
|
211
|
+
const memoryReferences = db
|
|
212
|
+
.prepare("DELETE FROM memory_references WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
213
|
+
.run(project.id, project.repoRoot).changes;
|
|
177
214
|
const projects = db.prepare("DELETE FROM projects WHERE id = ? OR repo_root = ?").run(project.id, project.repoRoot).changes;
|
|
178
|
-
return { events, handoffs, sessions, projects };
|
|
215
|
+
return { events, handoffs, sessions, projects, concepts, relations, durableMemories, decisionRecords, memoryReferences };
|
|
179
216
|
});
|
|
180
217
|
return { project, deleted: deleteProject() };
|
|
181
218
|
}
|
package/dist/memoryDb.js
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdirSync } from "node:fs";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import { defaultDbPath } from "./projectContext.js";
|
|
5
|
-
export const SCHEMA_VERSION =
|
|
5
|
+
export const SCHEMA_VERSION = 3;
|
|
6
6
|
export function openMemoryDb(dbPath = defaultDbPath()) {
|
|
7
7
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
8
8
|
const db = new Database(dbPath);
|
|
@@ -57,9 +57,153 @@ export function migrate(db) {
|
|
|
57
57
|
FOREIGN KEY(project_id) REFERENCES projects(id),
|
|
58
58
|
FOREIGN KEY(session_id) REFERENCES sessions(id)
|
|
59
59
|
);
|
|
60
|
+
|
|
61
|
+
CREATE TABLE IF NOT EXISTS concepts (
|
|
62
|
+
id TEXT PRIMARY KEY,
|
|
63
|
+
project_id TEXT NOT NULL,
|
|
64
|
+
kind TEXT NOT NULL,
|
|
65
|
+
label TEXT NOT NULL,
|
|
66
|
+
description TEXT,
|
|
67
|
+
aliases_json TEXT NOT NULL DEFAULT '[]',
|
|
68
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
69
|
+
valid_from TEXT,
|
|
70
|
+
valid_to TEXT,
|
|
71
|
+
created_at TEXT NOT NULL,
|
|
72
|
+
updated_at TEXT NOT NULL,
|
|
73
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_concepts_project_kind ON concepts(project_id, kind);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_concepts_project_label ON concepts(project_id, label);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_concepts_valid_to ON concepts(project_id, valid_to);
|
|
78
|
+
|
|
79
|
+
CREATE TABLE IF NOT EXISTS durable_memories (
|
|
80
|
+
id TEXT PRIMARY KEY,
|
|
81
|
+
project_id TEXT NOT NULL,
|
|
82
|
+
type TEXT NOT NULL,
|
|
83
|
+
summary TEXT NOT NULL,
|
|
84
|
+
body TEXT,
|
|
85
|
+
source_event_id TEXT,
|
|
86
|
+
source_handoff_id TEXT,
|
|
87
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
88
|
+
status TEXT NOT NULL,
|
|
89
|
+
valid_from TEXT,
|
|
90
|
+
valid_to TEXT,
|
|
91
|
+
created_at TEXT NOT NULL,
|
|
92
|
+
updated_at TEXT NOT NULL,
|
|
93
|
+
FOREIGN KEY(project_id) REFERENCES projects(id),
|
|
94
|
+
FOREIGN KEY(source_event_id) REFERENCES events(id),
|
|
95
|
+
FOREIGN KEY(source_handoff_id) REFERENCES handoffs(id)
|
|
96
|
+
);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_durable_memories_project_type ON durable_memories(project_id, type);
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_durable_memories_project_status ON durable_memories(project_id, status);
|
|
99
|
+
CREATE INDEX IF NOT EXISTS idx_durable_memories_source_event ON durable_memories(source_event_id);
|
|
100
|
+
|
|
101
|
+
CREATE TABLE IF NOT EXISTS decision_records (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
project_id TEXT NOT NULL,
|
|
104
|
+
title TEXT NOT NULL,
|
|
105
|
+
rationale TEXT NOT NULL,
|
|
106
|
+
alternatives_json TEXT NOT NULL DEFAULT '[]',
|
|
107
|
+
evidence_json TEXT NOT NULL DEFAULT '[]',
|
|
108
|
+
status TEXT NOT NULL,
|
|
109
|
+
reversible INTEGER NOT NULL DEFAULT 1,
|
|
110
|
+
source_event_id TEXT,
|
|
111
|
+
supersedes_decision_id TEXT,
|
|
112
|
+
valid_from TEXT,
|
|
113
|
+
valid_to TEXT,
|
|
114
|
+
created_at TEXT NOT NULL,
|
|
115
|
+
updated_at TEXT NOT NULL,
|
|
116
|
+
FOREIGN KEY(project_id) REFERENCES projects(id),
|
|
117
|
+
FOREIGN KEY(source_event_id) REFERENCES events(id),
|
|
118
|
+
FOREIGN KEY(supersedes_decision_id) REFERENCES decision_records(id)
|
|
119
|
+
);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_decision_records_project_status ON decision_records(project_id, status);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_decision_records_source_event ON decision_records(source_event_id);
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
124
|
+
id TEXT PRIMARY KEY,
|
|
125
|
+
project_id TEXT NOT NULL,
|
|
126
|
+
source_type TEXT NOT NULL,
|
|
127
|
+
source_id TEXT NOT NULL,
|
|
128
|
+
target_type TEXT NOT NULL,
|
|
129
|
+
target_id TEXT NOT NULL,
|
|
130
|
+
relation TEXT NOT NULL,
|
|
131
|
+
weight REAL NOT NULL DEFAULT 1,
|
|
132
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
133
|
+
valid_from TEXT,
|
|
134
|
+
valid_to TEXT,
|
|
135
|
+
created_at TEXT NOT NULL,
|
|
136
|
+
updated_at TEXT NOT NULL,
|
|
137
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
138
|
+
);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_relations_project_source ON relations(project_id, source_type, source_id);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_relations_project_target ON relations(project_id, target_type, target_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_relations_project_relation ON relations(project_id, relation);
|
|
142
|
+
|
|
143
|
+
CREATE TABLE IF NOT EXISTS memory_references (
|
|
144
|
+
id TEXT PRIMARY KEY,
|
|
145
|
+
project_id TEXT NOT NULL,
|
|
146
|
+
source_type TEXT NOT NULL,
|
|
147
|
+
source_id TEXT NOT NULL,
|
|
148
|
+
target_type TEXT NOT NULL,
|
|
149
|
+
target_id TEXT NOT NULL,
|
|
150
|
+
ref_kind TEXT NOT NULL DEFAULT 'mentions',
|
|
151
|
+
weight REAL NOT NULL DEFAULT 1,
|
|
152
|
+
created_at TEXT NOT NULL,
|
|
153
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
154
|
+
);
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_memory_references_project_source ON memory_references(project_id, source_type, source_id);
|
|
60
156
|
`);
|
|
157
|
+
// schema v3 upgrade: add retention/reference columns to existing v1/v2 DBs (idempotent)
|
|
158
|
+
const addCol = (sql) => {
|
|
159
|
+
try {
|
|
160
|
+
db.exec(sql);
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
const m = String(e.message || e);
|
|
164
|
+
if (!/duplicate column name|already exists/i.test(m))
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
addCol("ALTER TABLE concepts ADD COLUMN score REAL NOT NULL DEFAULT 0");
|
|
169
|
+
addCol("ALTER TABLE concepts ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'working'");
|
|
170
|
+
addCol("ALTER TABLE concepts ADD COLUMN manual_pin INTEGER NOT NULL DEFAULT 0");
|
|
171
|
+
addCol("ALTER TABLE concepts ADD COLUMN ref_count INTEGER NOT NULL DEFAULT 0");
|
|
172
|
+
addCol("ALTER TABLE concepts ADD COLUMN project_spread INTEGER NOT NULL DEFAULT 1");
|
|
173
|
+
addCol("ALTER TABLE concepts ADD COLUMN first_seen TEXT");
|
|
174
|
+
addCol("ALTER TABLE concepts ADD COLUMN last_seen TEXT");
|
|
175
|
+
addCol("ALTER TABLE durable_memories ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'durable'");
|
|
176
|
+
compactMemoryReferences(db);
|
|
177
|
+
db.exec(`
|
|
178
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_references_unique_edge ON memory_references(
|
|
179
|
+
project_id, source_type, source_id, target_type, target_id, ref_kind
|
|
180
|
+
)
|
|
181
|
+
`);
|
|
182
|
+
recomputeConceptReferenceCounts(db);
|
|
61
183
|
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', ?)").run(String(SCHEMA_VERSION));
|
|
62
184
|
}
|
|
185
|
+
function compactMemoryReferences(db) {
|
|
186
|
+
db.exec(`
|
|
187
|
+
DELETE FROM memory_references
|
|
188
|
+
WHERE id NOT IN (
|
|
189
|
+
SELECT MIN(id)
|
|
190
|
+
FROM memory_references
|
|
191
|
+
GROUP BY project_id, source_type, source_id, target_type, target_id, ref_kind
|
|
192
|
+
)
|
|
193
|
+
`);
|
|
194
|
+
}
|
|
195
|
+
function recomputeConceptReferenceCounts(db) {
|
|
196
|
+
db.exec(`
|
|
197
|
+
UPDATE concepts
|
|
198
|
+
SET ref_count = (
|
|
199
|
+
SELECT COUNT(*)
|
|
200
|
+
FROM memory_references
|
|
201
|
+
WHERE memory_references.project_id = concepts.project_id
|
|
202
|
+
AND memory_references.target_type = 'concept'
|
|
203
|
+
AND memory_references.target_id = concepts.id
|
|
204
|
+
)
|
|
205
|
+
`);
|
|
206
|
+
}
|
|
63
207
|
export function initMemory(dbPath = defaultDbPath()) {
|
|
64
208
|
const db = openMemoryDb(dbPath);
|
|
65
209
|
try {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { migrate, openMemoryDb } from "./memoryDb.js";
|
|
2
|
+
import { defaultDbPath, resolveProjectContext } from "./projectContext.js";
|
|
3
|
+
import { resolveStoredProject } from "./projectMigration.js";
|
|
4
|
+
export function recallEvents(input, dbPath = defaultDbPath()) {
|
|
5
|
+
const terms = recallTerms(input.query);
|
|
6
|
+
if (terms.length === 0)
|
|
7
|
+
return [];
|
|
8
|
+
const db = openMemoryDb(dbPath);
|
|
9
|
+
try {
|
|
10
|
+
migrate(db);
|
|
11
|
+
const project = resolveStoredProject(db, resolveProjectContext());
|
|
12
|
+
const minimumMatches = Math.min(2, terms.length);
|
|
13
|
+
const score = terms.map(() => "CASE WHEN LOWER(summary) LIKE ? ESCAPE '\\' THEN 1 ELSE 0 END").join(" + ");
|
|
14
|
+
const patternArgs = terms.map((term) => `%${escapeLikeTerm(term)}%`);
|
|
15
|
+
return db
|
|
16
|
+
.prepare(`
|
|
17
|
+
SELECT id, type, summary, created_at AS createdAt, session_id AS sessionId
|
|
18
|
+
FROM events
|
|
19
|
+
WHERE project_id = ? AND (${score}) >= ?
|
|
20
|
+
ORDER BY created_at DESC
|
|
21
|
+
LIMIT ?
|
|
22
|
+
`)
|
|
23
|
+
.all(project.id, ...patternArgs, minimumMatches, input.limit);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
db.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function recallTerms(query) {
|
|
30
|
+
return Array.from(new Set(query.toLocaleLowerCase().match(/[\p{L}\p{N}_-]{3,}/gu) ?? []))
|
|
31
|
+
.filter((term) => !GENERIC_RECALL_TERMS.has(term))
|
|
32
|
+
.slice(0, 8);
|
|
33
|
+
}
|
|
34
|
+
function escapeLikeTerm(term) {
|
|
35
|
+
return term.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
36
|
+
}
|
|
37
|
+
const GENERIC_RECALL_TERMS = new Set([
|
|
38
|
+
"action",
|
|
39
|
+
"asked",
|
|
40
|
+
"current",
|
|
41
|
+
"help",
|
|
42
|
+
"memory",
|
|
43
|
+
"need",
|
|
44
|
+
"needs",
|
|
45
|
+
"omo",
|
|
46
|
+
"please",
|
|
47
|
+
"prompt",
|
|
48
|
+
"request",
|
|
49
|
+
"requested",
|
|
50
|
+
"session",
|
|
51
|
+
"user",
|
|
52
|
+
"want",
|
|
53
|
+
"wants",
|
|
54
|
+
"work",
|
|
55
|
+
"working",
|
|
56
|
+
]);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { migrate, openMemoryDb } from "./memoryDb.js";
|
|
2
|
+
import { defaultDbPath, resolveProjectContext } from "./projectContext.js";
|
|
3
|
+
import { resolveStoredProject } from "./projectMigration.js";
|
|
4
|
+
export function memoryPaths() {
|
|
5
|
+
return { dbPath: defaultDbPath() };
|
|
6
|
+
}
|
|
7
|
+
export function doctorReport(dbPath = defaultDbPath()) {
|
|
8
|
+
const db = openMemoryDb(dbPath);
|
|
9
|
+
try {
|
|
10
|
+
migrate(db);
|
|
11
|
+
const project = resolveStoredProject(db, resolveProjectContext());
|
|
12
|
+
const schemaVersion = Number(db.prepare("SELECT value FROM schema_meta WHERE key = 'schema_version'").pluck().get());
|
|
13
|
+
const count = (table) => Number(db.prepare(`SELECT COUNT(*) FROM ${table}`).pluck().get());
|
|
14
|
+
return {
|
|
15
|
+
paths: { dbPath },
|
|
16
|
+
schemaVersion,
|
|
17
|
+
project,
|
|
18
|
+
counts: {
|
|
19
|
+
projects: count("projects"),
|
|
20
|
+
sessions: count("sessions"),
|
|
21
|
+
events: count("events"),
|
|
22
|
+
handoffs: count("handoffs"),
|
|
23
|
+
concepts: count("concepts"),
|
|
24
|
+
relations: count("relations"),
|
|
25
|
+
durableMemories: count("durable_memories"),
|
|
26
|
+
decisionRecords: count("decision_records"),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
db.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { upsertProject } from "./memory.js";
|
|
3
|
+
import { migrate, openMemoryDb } from "./memoryDb.js";
|
|
4
|
+
import { redactSecrets } from "./privacy.js";
|
|
5
|
+
export { listOntologyRows } from "./ontologyQueries.js";
|
|
6
|
+
export { supersedeDurableMemory } from "./ontologySupersede.js";
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
function normalizeLabel(label) {
|
|
11
|
+
return label.trim().toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
export function upsertConcept(dbPath, project, input) {
|
|
14
|
+
const db = openMemoryDb(dbPath);
|
|
15
|
+
try {
|
|
16
|
+
migrate(db);
|
|
17
|
+
upsertProject(db, project);
|
|
18
|
+
const label = normalizeLabel(input.label);
|
|
19
|
+
const now = nowIso();
|
|
20
|
+
const existing = db.prepare(`SELECT id FROM concepts WHERE project_id = ? AND label = ? LIMIT 1`).get(project.id, label);
|
|
21
|
+
if (existing?.id) {
|
|
22
|
+
db.prepare("UPDATE concepts SET last_seen = ?, updated_at = ? WHERE id = ? AND project_id = ?").run(now, now, existing.id, project.id);
|
|
23
|
+
const row = db
|
|
24
|
+
.prepare(`SELECT id, kind, label, description, aliases_json AS aliasesJson, payload_json AS payloadJson,
|
|
25
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt,
|
|
26
|
+
COALESCE(score, 0) AS score, COALESCE(retention_class, 'working') AS retentionClass,
|
|
27
|
+
COALESCE(manual_pin, 0) AS manualPin, COALESCE(ref_count, 0) AS refCount,
|
|
28
|
+
COALESCE(project_spread, 1) AS projectSpread, first_seen AS firstSeen, last_seen AS lastSeen
|
|
29
|
+
FROM concepts WHERE id = ?`)
|
|
30
|
+
.get(existing.id);
|
|
31
|
+
return row;
|
|
32
|
+
}
|
|
33
|
+
const id = randomUUID();
|
|
34
|
+
const score = input.score ?? 0;
|
|
35
|
+
const retentionClass = input.retentionClass ?? "working";
|
|
36
|
+
const manualPin = input.manualPin ?? 0;
|
|
37
|
+
const created = now;
|
|
38
|
+
db.prepare(`
|
|
39
|
+
INSERT INTO concepts (
|
|
40
|
+
id, project_id, kind, label, description, aliases_json, payload_json,
|
|
41
|
+
valid_from, valid_to, created_at, updated_at,
|
|
42
|
+
score, retention_class, manual_pin, ref_count, project_spread, first_seen, last_seen
|
|
43
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
44
|
+
`).run(id, project.id, input.kind, label, input.description ?? null, "[]", "{}", null, null, created, created, score, retentionClass, manualPin, 0, 1, created, created);
|
|
45
|
+
const row = db
|
|
46
|
+
.prepare(`SELECT id, kind, label, description, aliases_json AS aliasesJson, payload_json AS payloadJson,
|
|
47
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt,
|
|
48
|
+
COALESCE(score, 0) AS score, COALESCE(retention_class, 'working') AS retentionClass,
|
|
49
|
+
COALESCE(manual_pin, 0) AS manualPin, COALESCE(ref_count, 0) AS refCount,
|
|
50
|
+
COALESCE(project_spread, 1) AS projectSpread, first_seen AS firstSeen, last_seen AS lastSeen
|
|
51
|
+
FROM concepts WHERE id = ?`)
|
|
52
|
+
.get(id);
|
|
53
|
+
return row;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
db.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function createDurableMemory(dbPath, project, input) {
|
|
60
|
+
const db = openMemoryDb(dbPath);
|
|
61
|
+
try {
|
|
62
|
+
migrate(db);
|
|
63
|
+
upsertProject(db, project);
|
|
64
|
+
const id = randomUUID();
|
|
65
|
+
const created = nowIso();
|
|
66
|
+
const redactedSummary = redactSecrets(input.summary);
|
|
67
|
+
const redactedBody = input.body == null ? null : redactSecrets(input.body);
|
|
68
|
+
const status = input.status ?? "active";
|
|
69
|
+
const retentionClass = input.retentionClass ?? "durable";
|
|
70
|
+
const confidence = input.confidence ?? 0;
|
|
71
|
+
db.prepare(`
|
|
72
|
+
INSERT INTO durable_memories (
|
|
73
|
+
id, project_id, type, summary, body, source_event_id, source_handoff_id,
|
|
74
|
+
confidence, status, retention_class, valid_from, valid_to, created_at, updated_at
|
|
75
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
+
`).run(id, project.id, input.type, redactedSummary, redactedBody, input.sourceEventId ?? null, input.sourceHandoffId ?? null, confidence, status, retentionClass, null, null, created, created);
|
|
77
|
+
const row = db
|
|
78
|
+
.prepare(`SELECT id, type, summary, body, source_event_id AS sourceEventId, source_handoff_id AS sourceHandoffId,
|
|
79
|
+
confidence, status, COALESCE(retention_class, 'durable') AS retentionClass,
|
|
80
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
81
|
+
FROM durable_memories WHERE id = ?`)
|
|
82
|
+
.get(id);
|
|
83
|
+
return row;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
db.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function recordMemoryReference(dbPath, project, input) {
|
|
90
|
+
const db = openMemoryDb(dbPath);
|
|
91
|
+
try {
|
|
92
|
+
migrate(db);
|
|
93
|
+
upsertProject(db, project);
|
|
94
|
+
const id = randomUUID();
|
|
95
|
+
const created = nowIso();
|
|
96
|
+
const refKind = input.refKind ?? "mentions";
|
|
97
|
+
const weight = input.weight ?? 1;
|
|
98
|
+
const inserted = db
|
|
99
|
+
.prepare(`
|
|
100
|
+
INSERT INTO memory_references (
|
|
101
|
+
id, project_id, source_type, source_id, target_type, target_id, ref_kind, weight, created_at
|
|
102
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
103
|
+
ON CONFLICT(project_id, source_type, source_id, target_type, target_id, ref_kind) DO NOTHING
|
|
104
|
+
`)
|
|
105
|
+
.run(id, project.id, input.sourceType, input.sourceId, input.targetType, input.targetId, refKind, weight, created);
|
|
106
|
+
if (inserted.changes > 0 && input.targetType === "concept") {
|
|
107
|
+
db.prepare("UPDATE concepts SET ref_count = COALESCE(ref_count, 0) + 1, last_seen = ?, updated_at = ? WHERE id = ? AND project_id = ?").run(created, created, input.targetId, project.id);
|
|
108
|
+
}
|
|
109
|
+
const row = db
|
|
110
|
+
.prepare(`SELECT id, source_type AS sourceType, source_id AS sourceId, target_type AS targetType, target_id AS targetId,
|
|
111
|
+
ref_kind AS refKind, weight, created_at AS createdAt
|
|
112
|
+
FROM memory_references
|
|
113
|
+
WHERE project_id = ? AND source_type = ? AND source_id = ? AND target_type = ? AND target_id = ? AND ref_kind = ?`)
|
|
114
|
+
.get(project.id, input.sourceType, input.sourceId, input.targetType, input.targetId, refKind);
|
|
115
|
+
return row;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
db.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function updateDurableRetention(dbPath, project, durableId, update) {
|
|
122
|
+
const db = openMemoryDb(dbPath);
|
|
123
|
+
try {
|
|
124
|
+
migrate(db);
|
|
125
|
+
const existing = db.prepare("SELECT retention_class FROM durable_memories WHERE id = ? AND project_id = ?").get(durableId, project.id);
|
|
126
|
+
if (!existing) {
|
|
127
|
+
throw new Error("durable memory not found for project");
|
|
128
|
+
}
|
|
129
|
+
const now = nowIso();
|
|
130
|
+
db.prepare("UPDATE durable_memories SET retention_class = COALESCE(?, retention_class), updated_at = ? WHERE id = ? AND project_id = ?").run(update.retentionClass ?? null, now, durableId, project.id);
|
|
131
|
+
const row = db
|
|
132
|
+
.prepare(`SELECT id, type, summary, body, source_event_id AS sourceEventId, source_handoff_id AS sourceHandoffId,
|
|
133
|
+
confidence, status, COALESCE(retention_class, 'durable') AS retentionClass,
|
|
134
|
+
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
135
|
+
FROM durable_memories WHERE id = ?`)
|
|
136
|
+
.get(durableId);
|
|
137
|
+
return row;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
db.close();
|
|
141
|
+
}
|
|
142
|
+
}
|