omo-memory 0.1.14 → 0.1.16
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 +13 -64
- package/dist/cli.js +6 -89
- package/dist/mcp.js +2 -2
- package/dist/mcpGlobalTools.js +23 -0
- package/dist/memory.js +37 -31
- package/dist/memoryDb.js +43 -113
- package/dist/memoryReport.js +2 -2
- package/docs/adapter-integration.md +19 -91
- package/docs/epic-omo-memory.md +29 -51
- package/package.json +6 -5
- package/scripts/postinstall-cleanup.mjs +91 -0
- package/dist/conceptExtraction.js +0 -188
- package/dist/graphTui.js +0 -234
- package/dist/graphTuiCanvas.js +0 -104
- package/dist/mcpOntologyTools.js +0 -117
- package/dist/ontologyCore.js +0 -142
- package/dist/ontologyGraph.js +0 -136
- package/dist/ontologyGraphEdges.js +0 -86
- package/dist/ontologyQueries.js +0 -30
- package/dist/ontologySupersede.js +0 -49
- package/dist/retentionPolicy.js +0 -76
- package/dist/retentionRecompute.js +0 -175
- package/scripts/omo-memory-user-prompt.mjs +0 -107
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# OMO Memory
|
|
2
2
|
|
|
3
|
-
OMO Memory is a host-neutral local session/work
|
|
3
|
+
OMO Memory is a host-neutral local session/work/event ledger for OMO adapters.
|
|
4
4
|
|
|
5
5
|
It gives lazycodex, omo-on-opencode, lfg, and future OMO adapters a shared local SQLite ledger that can be accessed through both:
|
|
6
6
|
|
|
7
|
-
- `omo-memory` CLI for
|
|
7
|
+
- `omo-memory` CLI for init, inspection, explicit event recall, handoff, export, purge, and global event import workflows.
|
|
8
8
|
- `omo-memory mcp` stdio server for coding tools and agents.
|
|
9
9
|
|
|
10
10
|
## Product shape
|
|
@@ -23,13 +23,11 @@ npm run build
|
|
|
23
23
|
node dist/cli.js init
|
|
24
24
|
node dist/cli.js global scan --root ..
|
|
25
25
|
node dist/cli.js global migrate --root .. --global-db ~/.omo/memory/global.sqlite
|
|
26
|
+
node dist/cli.js global list --global-db ~/.omo/memory/global.sqlite
|
|
26
27
|
node dist/cli.js session start --host grok --adapter lfg
|
|
27
28
|
node dist/cli.js event record --type decision --summary "Chose SQLite + MCP + CLI for OMO shared memory"
|
|
28
|
-
node dist/cli.js ontology candidates
|
|
29
|
-
node dist/cli.js ontology score
|
|
30
|
-
node dist/cli.js ontology recall --query "sqlite retention"
|
|
31
|
-
node dist/cli.js graph tui
|
|
32
29
|
node dist/cli.js recent
|
|
30
|
+
node dist/cli.js recall --query "sqlite decision"
|
|
33
31
|
node dist/cli.js mcp
|
|
34
32
|
```
|
|
35
33
|
|
|
@@ -42,9 +40,8 @@ npx -y omo-memory init
|
|
|
42
40
|
npx -y omo-memory update
|
|
43
41
|
npx -y omo-memory global scan --root .
|
|
44
42
|
npx -y omo-memory global migrate --root . --global-db ~/.omo/memory/global.sqlite
|
|
43
|
+
npx -y omo-memory global list --global-db ~/.omo/memory/global.sqlite
|
|
45
44
|
npx -y omo-memory session bootstrap --host codex --adapter lazycodex --limit 5
|
|
46
|
-
npx -y omo-memory ontology recall --query "why did we choose sqlite" --limit 5
|
|
47
|
-
npx -y omo-memory graph tui
|
|
48
45
|
npx -y omo-memory recent --limit 5
|
|
49
46
|
npx -y omo-memory recall --query "why did we choose sqlite" --limit 5
|
|
50
47
|
npx -y omo-memory mcp
|
|
@@ -59,6 +56,8 @@ npm link
|
|
|
59
56
|
omo-memory init
|
|
60
57
|
```
|
|
61
58
|
|
|
59
|
+
Install and update run a local Codex cleanup migration through `postinstall`. It removes stale `omo-memory@islee23520` hook state and legacy hook files from `~/.codex` when they exist; it does not touch unrelated OMO/LazyCodex hooks.
|
|
60
|
+
|
|
62
61
|
## MCP registration
|
|
63
62
|
|
|
64
63
|
Register the same MCP server in every host that should read/write the current project's memory DB.
|
|
@@ -105,8 +104,7 @@ The response contains a new `sessionId` and project metadata only. It deliberate
|
|
|
105
104
|
}
|
|
106
105
|
```
|
|
107
106
|
|
|
108
|
-
This is local routing, not transcript scraping. OMO Memory does not automatically read full Codex or Grok transcripts.
|
|
109
|
-
The packaged `scripts/omo-memory-user-prompt.mjs` helper is the supported UserPromptSubmit hook target for adapters that can invoke a command with the hook payload on stdin. It records only the current user prompt as a redacted `user_prompt` event, ignores assistant output, and exits successfully without blocking the host when OMO Memory is unavailable.
|
|
107
|
+
This is local routing, not transcript scraping. OMO Memory does not automatically read full Codex or Grok transcripts. Adapters should record concise user actions, decisions, QA evidence, and handoffs through the CLI or MCP tools; they should retrieve memory only when the user explicitly asks for OMO Memory or when the current user input can be matched to recorded intent.
|
|
110
108
|
|
|
111
109
|
Use explicit retrieval for memory reads:
|
|
112
110
|
|
|
@@ -134,17 +132,11 @@ Initial stdio MCP tools:
|
|
|
134
132
|
- `memory_global_scan`
|
|
135
133
|
- `memory_global_migrate`
|
|
136
134
|
- `memory_global_list`
|
|
137
|
-
|
|
138
|
-
- `memory_ontology_extract`
|
|
139
|
-
- `memory_ontology_score`
|
|
140
|
-
- `memory_ontology_promote`
|
|
141
|
-
- `memory_ontology_demote`
|
|
142
|
-
- `memory_ontology_supersede`
|
|
143
|
-
- `memory_ontology_recall`
|
|
135
|
+
|
|
144
136
|
|
|
145
137
|
## Updates
|
|
146
138
|
|
|
147
|
-
Installed CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup does not run the updater, so stdio handshakes stay clean.
|
|
139
|
+
Installed CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup does not run the updater, so stdio handshakes stay clean. The package `postinstall` cleanup also runs during these updates and removes legacy Codex `omo-memory@islee23520` hook registrations.
|
|
148
140
|
|
|
149
141
|
Manual update:
|
|
150
142
|
|
|
@@ -158,54 +150,11 @@ Disable automatic update for pinned environments:
|
|
|
158
150
|
OMO_MEMORY_AUTO_UPDATE=0 omo-memory doctor
|
|
159
151
|
```
|
|
160
152
|
|
|
161
|
-
##
|
|
162
|
-
|
|
163
|
-
The base ledger remains project-local and chronological: sessions, events, handoffs, and explicit recall. The second-brain layer adds deterministic ontology tables and lifecycle commands:
|
|
164
|
-
|
|
165
|
-
- Global migration copies existing local `.omo/memory/state.sqlite` databases into one global SQLite store with source provenance and an aggregate OMO schema view. It does not delete or rewrite local project ledgers.
|
|
166
|
-
- Concept extraction turns concise event summaries into vocabulary candidates and reference counts.
|
|
167
|
-
- Retention scoring classifies memory as `forget`, `temporary`, `working`, `durable`, or `permanent`; manual pins force `permanent`.
|
|
168
|
-
- Durable memories can be promoted, demoted, superseded, and recalled through CLI or MCP.
|
|
169
|
-
- `omo-memory graph tui` opens an OpenTUI ontology graph viewer for concepts, relations, retention class, and detail panes. This command needs `bun` on `PATH` because OpenTUI's terminal renderer uses Bun native FFI; the rest of the CLI runs on Node.
|
|
170
|
-
|
|
171
|
-
Retention classes:
|
|
172
|
-
|
|
173
|
-
- `forget`: low-value or stale one-off context that can be dropped.
|
|
174
|
-
- `temporary`: short-term context useful during a narrow task.
|
|
175
|
-
- `working`: active project memory worth keeping across the current iteration.
|
|
176
|
-
- `durable`: cross-session knowledge that should survive normal decay.
|
|
177
|
-
- `permanent`: manually pinned or high-score knowledge; only explicit demote, supersede, or purge should change it.
|
|
178
|
-
|
|
179
|
-
Ontology lifecycle commands:
|
|
180
|
-
|
|
181
|
-
```sh
|
|
182
|
-
omo-memory ontology candidates
|
|
183
|
-
omo-memory ontology score
|
|
184
|
-
omo-memory ontology promote --concept linaforge --summary "Linaforge is an active game-engine project"
|
|
185
|
-
omo-memory ontology recall --query "linaforge"
|
|
186
|
-
omo-memory ontology demote --id <durable-id>
|
|
187
|
-
omo-memory ontology supersede --id <durable-id> --summary "Updated durable memory"
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Global second-brain flow:
|
|
191
|
-
|
|
192
|
-
```sh
|
|
193
|
-
omo-memory global scan --root /Users/ilseoblee/workspace
|
|
194
|
-
omo-memory global migrate --root /Users/ilseoblee/workspace --global-db ~/.omo/memory/global.sqlite
|
|
195
|
-
OMO_MEMORY_DB=~/.omo/memory/global.sqlite omo-memory ontology candidates
|
|
196
|
-
OMO_MEMORY_DB=~/.omo/memory/global.sqlite omo-memory ontology score
|
|
197
|
-
bun --version
|
|
198
|
-
omo-memory graph tui --db ~/.omo/memory/global.sqlite --query linaforge
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
OpenTUI graph controls:
|
|
153
|
+
## Cross-project event import
|
|
202
154
|
|
|
203
|
-
- `
|
|
204
|
-
- `Up` / `Down`: move selected concept.
|
|
205
|
-
- `Tab`: move to the next concept.
|
|
206
|
-
- `/` or `f`: focus filter input when supported by the terminal runtime.
|
|
155
|
+
The base ledger is project-local and chronological: sessions, events, handoffs, and explicit recall. Global migration copies existing local `.omo/memory/state.sqlite` databases into one global SQLite store with source provenance so operators can search imported event history across projects. It does not delete or rewrite local project ledgers.
|
|
207
156
|
|
|
208
|
-
|
|
157
|
+
This is not an automatic second brain or knowledge graph. OMO Memory does not ship automatic concept extraction, retention scoring, durable-memory curation, OpenTUI, or terminal graph commands. Use explicit event summaries, `recent`, `recall`, `handoff write`, and global event import for cross-session continuity.
|
|
209
158
|
|
|
210
159
|
## Non-goals for MVP
|
|
211
160
|
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { maybeRunAutoUpdate, runAutoUpdate } from "./autoUpdate.js";
|
|
4
|
-
import {
|
|
5
|
-
import { migrateToGlobalMemory, scanForMemoryDbs } from "./globalMemory.js";
|
|
6
|
-
import { runGraphTui } from "./graphTui.js";
|
|
4
|
+
import { listGlobalMemory, migrateToGlobalMemory, scanForMemoryDbs } from "./globalMemory.js";
|
|
7
5
|
import { runMcpServer } from "./mcp.js";
|
|
8
6
|
import { bootstrapSession, exportMemory, purgeMemory, recentEvents, recordEvent, startSession, writeHandoff } from "./memory.js";
|
|
9
7
|
import { initMemory } from "./memoryDb.js";
|
|
10
8
|
import { recallEvents } from "./memoryRecall.js";
|
|
11
9
|
import { doctorReport } from "./memoryReport.js";
|
|
12
|
-
import { createDurableMemory, listOntologyRows, recordMemoryReference, supersedeDurableMemory, updateDurableRetention } from "./ontologyCore.js";
|
|
13
|
-
import { defaultDbPath } from "./projectContext.js";
|
|
14
|
-
import { recomputeRetentionScores } from "./retentionRecompute.js";
|
|
15
10
|
async function main(argv) {
|
|
16
11
|
const [command, subcommand, ...rest] = argv;
|
|
17
12
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
@@ -28,11 +23,6 @@ async function main(argv) {
|
|
|
28
23
|
return;
|
|
29
24
|
}
|
|
30
25
|
maybeRunAutoUpdate(currentVersion);
|
|
31
|
-
if (command === "graph" && subcommand === "tui") {
|
|
32
|
-
const query = readFlag(rest, "--query");
|
|
33
|
-
await runGraphTui({ dbPath: readFlag(rest, "--db") ?? defaultDbPath(), ...(query === undefined ? {} : { query }) });
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
26
|
const result = runCommand(command, subcommand, rest);
|
|
37
27
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
38
28
|
}
|
|
@@ -53,9 +43,6 @@ function runCommand(command, subcommand, rest) {
|
|
|
53
43
|
if (command === "global") {
|
|
54
44
|
return runGlobalCommand(subcommand, rest);
|
|
55
45
|
}
|
|
56
|
-
if (command === "ontology") {
|
|
57
|
-
return runOntologyCommand(subcommand, rest);
|
|
58
|
-
}
|
|
59
46
|
if (command === "session" && subcommand === "start") {
|
|
60
47
|
const host = parseHost(readFlag(rest, "--host") ?? "unknown");
|
|
61
48
|
const adapter = readFlag(rest, "--adapter") ?? "unknown";
|
|
@@ -104,81 +91,11 @@ function runGlobalCommand(subcommand, rest) {
|
|
|
104
91
|
const globalDbPath = readFlag(rest, "--global-db") ?? fail("global migrate requires --global-db");
|
|
105
92
|
return { ok: true, ...migrateToGlobalMemory({ rootPath, globalDbPath }) };
|
|
106
93
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const dbPath = defaultDbPath();
|
|
111
|
-
if (subcommand === "candidates")
|
|
112
|
-
return ontologyCandidates(dbPath);
|
|
113
|
-
if (subcommand === "score" || subcommand === "recompute") {
|
|
114
|
-
return { ok: true, ...recomputeRetentionScores({ dbPath, nowIso: new Date().toISOString() }) };
|
|
115
|
-
}
|
|
116
|
-
if (subcommand === "promote")
|
|
117
|
-
return ontologyPromote(dbPath, rest);
|
|
118
|
-
if (subcommand === "demote") {
|
|
119
|
-
const id = readFlag(rest, "--id") ?? fail("ontology demote requires --id");
|
|
120
|
-
return { ok: true, durableMemory: updateDurableRetention(dbPath, exportMemory(dbPath).project, id, { retentionClass: "temporary" }) };
|
|
121
|
-
}
|
|
122
|
-
if (subcommand === "supersede") {
|
|
123
|
-
const id = readFlag(rest, "--id") ?? fail("ontology supersede requires --id");
|
|
124
|
-
const newSummary = readFlag(rest, "--summary");
|
|
125
|
-
return { ok: true, ...supersedeDurableMemory(dbPath, exportMemory(dbPath).project, id, newSummary === undefined ? {} : { newSummary }) };
|
|
126
|
-
}
|
|
127
|
-
if (subcommand === "recall") {
|
|
128
|
-
const query = readFlag(rest, "--query") ?? fail("ontology recall requires --query");
|
|
129
|
-
const limit = readPositiveIntFlag(rest, "--limit", 10);
|
|
130
|
-
return { ok: true, durableMemories: recallDurableMemories(dbPath, query, limit) };
|
|
131
|
-
}
|
|
132
|
-
fail(`unknown command: ontology ${subcommand ?? ""}`.trim());
|
|
133
|
-
}
|
|
134
|
-
function ontologyCandidates(dbPath) {
|
|
135
|
-
const memory = exportMemory(dbPath);
|
|
136
|
-
let references = 0;
|
|
137
|
-
for (const event of memory.events) {
|
|
138
|
-
references += applyConceptExtraction(dbPath, memory.project, event.id, event.summary, event.type).references.length;
|
|
94
|
+
if (subcommand === "list") {
|
|
95
|
+
const globalDbPath = readFlag(rest, "--global-db") ?? fail("global list requires --global-db");
|
|
96
|
+
return { ok: true, ...listGlobalMemory(globalDbPath) };
|
|
139
97
|
}
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
function ontologyPromote(dbPath, rest) {
|
|
143
|
-
const memory = exportMemory(dbPath);
|
|
144
|
-
const conceptSelector = readFlag(rest, "--concept") ?? readFlag(rest, "--concept-id") ?? fail("ontology promote requires --concept");
|
|
145
|
-
const concept = findConcept(memory.concepts, conceptSelector) ?? fail(`ontology promote candidate not found: ${conceptSelector}`);
|
|
146
|
-
const summary = readFlag(rest, "--summary") ?? `Durable memory: ${concept.label}`;
|
|
147
|
-
const body = readFlag(rest, "--body") ?? concept.description ?? concept.label;
|
|
148
|
-
const durableMemory = createDurableMemory(dbPath, memory.project, {
|
|
149
|
-
type: "concept",
|
|
150
|
-
summary,
|
|
151
|
-
body,
|
|
152
|
-
confidence: Math.min(1, Math.max(0, concept.score / 100)),
|
|
153
|
-
status: "active",
|
|
154
|
-
retentionClass: "durable",
|
|
155
|
-
});
|
|
156
|
-
const reference = recordMemoryReference(dbPath, memory.project, {
|
|
157
|
-
sourceType: "concept",
|
|
158
|
-
sourceId: concept.id,
|
|
159
|
-
targetType: "durable_memory",
|
|
160
|
-
targetId: durableMemory.id,
|
|
161
|
-
refKind: "promotes",
|
|
162
|
-
weight: 1,
|
|
163
|
-
});
|
|
164
|
-
return { ok: true, concept, durableMemory, reference };
|
|
165
|
-
}
|
|
166
|
-
function findConcept(concepts, selector) {
|
|
167
|
-
const normalized = selector.trim().toLowerCase();
|
|
168
|
-
return concepts.find((concept) => concept.id === selector || concept.label.toLowerCase() === normalized);
|
|
169
|
-
}
|
|
170
|
-
function recallDurableMemories(dbPath, query, limit) {
|
|
171
|
-
const memory = exportMemory(dbPath);
|
|
172
|
-
const terms = query.toLowerCase().match(/[\p{L}\p{N}_-]{3,}/gu) ?? [];
|
|
173
|
-
if (terms.length === 0)
|
|
174
|
-
return [];
|
|
175
|
-
return listOntologyRows(dbPath, memory.project)
|
|
176
|
-
.durableMemories.filter((durable) => durable.status === "active")
|
|
177
|
-
.filter((durable) => {
|
|
178
|
-
const haystack = `${durable.summary} ${durable.body ?? ""}`.toLowerCase();
|
|
179
|
-
return terms.some((term) => haystack.includes(term));
|
|
180
|
-
})
|
|
181
|
-
.slice(0, limit);
|
|
98
|
+
fail(`unknown command: global ${subcommand ?? ""}`.trim());
|
|
182
99
|
}
|
|
183
100
|
function readFlag(args, name) {
|
|
184
101
|
const index = args.indexOf(name);
|
|
@@ -207,7 +124,7 @@ function fail(message) {
|
|
|
207
124
|
throw new Error(message);
|
|
208
125
|
}
|
|
209
126
|
function printHelp() {
|
|
210
|
-
process.stdout.write(`OMO Memory\n\nCommands:\n omo-memory init\n omo-memory doctor\n omo-memory update\n omo-memory export\n omo-memory purge --yes\n omo-memory global scan --root <path> [--json]\n omo-memory global migrate --root <path> --global-db <path> [--json]\n omo-memory
|
|
127
|
+
process.stdout.write(`OMO Memory\n\nCommands:\n omo-memory init\n omo-memory doctor\n omo-memory update\n omo-memory export\n omo-memory purge --yes\n omo-memory global scan --root <path> [--json]\n omo-memory global migrate --root <path> --global-db <path> [--json]\n omo-memory global list --global-db <path> [--json]\n omo-memory session start --host <codex|opencode|grok|unknown> --adapter <name>\n omo-memory session bootstrap --host <codex|opencode|grok|unknown> --adapter <name> [--limit <n>]\n omo-memory event record --type <type> --summary <text> [--session-id <id>]\n omo-memory recent [--limit <n>]\n omo-memory recall --query <text> [--limit <n>]\n omo-memory handoff write (--summary <text> | --summary-file <path>) [--session-id <id>]\n omo-memory mcp\n`);
|
|
211
128
|
}
|
|
212
129
|
function readPackageVersion() {
|
|
213
130
|
const rawPackage = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
package/dist/mcp.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
5
|
+
import { registerGlobalTools } from "./mcpGlobalTools.js";
|
|
6
6
|
import { bootstrapSession, exportMemory, PurgeConfirmationError, purgeMemory, recentEvents, recordEvent, startSession, writeHandoff } from "./memory.js";
|
|
7
7
|
import { initMemory } from "./memoryDb.js";
|
|
8
8
|
import { recallEvents } from "./memoryRecall.js";
|
|
@@ -42,7 +42,7 @@ export async function runMcpServer() {
|
|
|
42
42
|
throw error;
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
|
|
45
|
+
registerGlobalTools(server);
|
|
46
46
|
server.registerTool("memory_start_session", {
|
|
47
47
|
title: "Start OMO Session",
|
|
48
48
|
description: "Record a new OMO adapter session for the current project.",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listGlobalMemory, migrateToGlobalMemory, scanForMemoryDbs } from "./globalMemory.js";
|
|
3
|
+
const nonBlankStringSchema = z.string().trim().min(1);
|
|
4
|
+
export function registerGlobalTools(server) {
|
|
5
|
+
server.registerTool("memory_global_scan", {
|
|
6
|
+
title: "Scan Global OMO Memory Sources",
|
|
7
|
+
description: "Explicitly scan a filesystem root for local OMO memory SQLite databases without importing them.",
|
|
8
|
+
inputSchema: { rootPath: nonBlankStringSchema },
|
|
9
|
+
}, async ({ rootPath }) => jsonResult(scanForMemoryDbs(rootPath)));
|
|
10
|
+
server.registerTool("memory_global_migrate", {
|
|
11
|
+
title: "Migrate OMO Memory To Global SQLite",
|
|
12
|
+
description: "Explicitly create or update a global OMO memory SQLite database from discovered local memory databases.",
|
|
13
|
+
inputSchema: { rootPath: nonBlankStringSchema, globalDbPath: nonBlankStringSchema },
|
|
14
|
+
}, async ({ rootPath, globalDbPath }) => jsonResult(migrateToGlobalMemory({ rootPath, globalDbPath })));
|
|
15
|
+
server.registerTool("memory_global_list", {
|
|
16
|
+
title: "List Global OMO Memory",
|
|
17
|
+
description: "List sources and counts from an explicit global OMO memory SQLite database.",
|
|
18
|
+
inputSchema: { globalDbPath: nonBlankStringSchema },
|
|
19
|
+
}, async ({ globalDbPath }) => jsonResult(listGlobalMemory(globalDbPath)));
|
|
20
|
+
}
|
|
21
|
+
function jsonResult(value) {
|
|
22
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
23
|
+
}
|
package/dist/memory.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { migrate, openMemoryDb, SCHEMA_VERSION } from "./memoryDb.js";
|
|
2
|
+
import { migrate, openMemoryDb, SCHEMA_VERSION, tableExists } from "./memoryDb.js";
|
|
3
3
|
import { redactSecrets } from "./privacy.js";
|
|
4
4
|
import { defaultDbPath, resolveProjectContext } from "./projectContext.js";
|
|
5
5
|
import { resolveStoredProject } from "./projectMigration.js";
|
|
@@ -116,8 +116,9 @@ export function exportMemory(dbPath = defaultDbPath()) {
|
|
|
116
116
|
WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
117
117
|
`)
|
|
118
118
|
.all(project.id);
|
|
119
|
-
const concepts = db
|
|
120
|
-
|
|
119
|
+
const concepts = tableExists(db, "concepts")
|
|
120
|
+
? db
|
|
121
|
+
.prepare(`
|
|
121
122
|
SELECT id, kind, label, description, aliases_json AS aliasesJson, payload_json AS payloadJson,
|
|
122
123
|
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt,
|
|
123
124
|
COALESCE(score, 0) AS score,
|
|
@@ -128,38 +129,47 @@ export function exportMemory(dbPath = defaultDbPath()) {
|
|
|
128
129
|
first_seen AS firstSeen, last_seen AS lastSeen
|
|
129
130
|
FROM concepts WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
130
131
|
`)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
.all(project.id)
|
|
133
|
+
: [];
|
|
134
|
+
const relations = tableExists(db, "relations")
|
|
135
|
+
? db
|
|
136
|
+
.prepare(`
|
|
134
137
|
SELECT id, source_type AS sourceType, source_id AS sourceId, target_type AS targetType, target_id AS targetId,
|
|
135
138
|
relation, weight, payload_json AS payloadJson, valid_from AS validFrom, valid_to AS validTo,
|
|
136
139
|
created_at AS createdAt, updated_at AS updatedAt
|
|
137
140
|
FROM relations WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
138
141
|
`)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
.all(project.id)
|
|
143
|
+
: [];
|
|
144
|
+
const durableMemories = tableExists(db, "durable_memories")
|
|
145
|
+
? db
|
|
146
|
+
.prepare(`
|
|
142
147
|
SELECT id, type, summary, body, source_event_id AS sourceEventId, source_handoff_id AS sourceHandoffId,
|
|
143
148
|
confidence, status, COALESCE(retention_class, 'durable') AS retentionClass,
|
|
144
149
|
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
145
150
|
FROM durable_memories WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
146
151
|
`)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
.all(project.id)
|
|
153
|
+
: [];
|
|
154
|
+
const decisionRecords = tableExists(db, "decision_records")
|
|
155
|
+
? db
|
|
156
|
+
.prepare(`
|
|
150
157
|
SELECT id, title, rationale, alternatives_json AS alternativesJson, evidence_json AS evidenceJson,
|
|
151
158
|
status, reversible, source_event_id AS sourceEventId, supersedes_decision_id AS supersedesDecisionId,
|
|
152
159
|
valid_from AS validFrom, valid_to AS validTo, created_at AS createdAt, updated_at AS updatedAt
|
|
153
160
|
FROM decision_records WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
154
161
|
`)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
.all(project.id)
|
|
163
|
+
: [];
|
|
164
|
+
const memoryReferences = tableExists(db, "memory_references")
|
|
165
|
+
? db
|
|
166
|
+
.prepare(`
|
|
158
167
|
SELECT id, source_type AS sourceType, source_id AS sourceId, target_type AS targetType, target_id AS targetId,
|
|
159
168
|
ref_kind AS refKind, weight, created_at AS createdAt
|
|
160
169
|
FROM memory_references WHERE project_id = ? ORDER BY created_at ASC, id ASC
|
|
161
170
|
`)
|
|
162
|
-
|
|
171
|
+
.all(project.id)
|
|
172
|
+
: [];
|
|
163
173
|
return {
|
|
164
174
|
schemaVersion: SCHEMA_VERSION,
|
|
165
175
|
exportedAt: new Date().toISOString(),
|
|
@@ -187,18 +197,17 @@ export function purgeMemory(input, dbPath = defaultDbPath()) {
|
|
|
187
197
|
migrate(db);
|
|
188
198
|
const project = resolveStoredProject(db, resolveProjectContext());
|
|
189
199
|
const deleteProject = db.transaction(() => {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
.run(project.id, project.repoRoot).changes;
|
|
200
|
+
const deleteLegacyRows = (table) => {
|
|
201
|
+
if (!tableExists(db, table))
|
|
202
|
+
return 0;
|
|
203
|
+
return db.prepare(`DELETE FROM ${table} WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)`).run(project.id, project.repoRoot)
|
|
204
|
+
.changes;
|
|
205
|
+
};
|
|
206
|
+
const memoryReferences = deleteLegacyRows("memory_references");
|
|
207
|
+
const relations = deleteLegacyRows("relations");
|
|
208
|
+
const decisionRecords = deleteLegacyRows("decision_records");
|
|
209
|
+
const durableMemories = deleteLegacyRows("durable_memories");
|
|
210
|
+
const concepts = deleteLegacyRows("concepts");
|
|
202
211
|
const events = db
|
|
203
212
|
.prepare("DELETE FROM events WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
204
213
|
.run(project.id, project.repoRoot).changes;
|
|
@@ -208,9 +217,6 @@ export function purgeMemory(input, dbPath = defaultDbPath()) {
|
|
|
208
217
|
const sessions = db
|
|
209
218
|
.prepare("DELETE FROM sessions WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
|
|
210
219
|
.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;
|
|
214
220
|
const projects = db.prepare("DELETE FROM projects WHERE id = ? OR repo_root = ?").run(project.id, project.repoRoot).changes;
|
|
215
221
|
return { events, handoffs, sessions, projects, concepts, relations, durableMemories, decisionRecords, memoryReferences };
|
|
216
222
|
});
|
package/dist/memoryDb.js
CHANGED
|
@@ -57,105 +57,12 @@ 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);
|
|
156
60
|
`);
|
|
157
|
-
//
|
|
158
|
-
|
|
61
|
+
// Legacy ontology compatibility: existing DBs may contain ontology tables from schema v2/v3.
|
|
62
|
+
// Do not create those tables for fresh DBs, but keep old DBs readable/purgeable.
|
|
63
|
+
const addCol = (table, sql) => {
|
|
64
|
+
if (!tableExists(db, table))
|
|
65
|
+
return;
|
|
159
66
|
try {
|
|
160
67
|
db.exec(sql);
|
|
161
68
|
}
|
|
@@ -165,23 +72,46 @@ export function migrate(db) {
|
|
|
165
72
|
throw e;
|
|
166
73
|
}
|
|
167
74
|
};
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
|
|
75
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN score REAL NOT NULL DEFAULT 0");
|
|
76
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'working'");
|
|
77
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN manual_pin INTEGER NOT NULL DEFAULT 0");
|
|
78
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN ref_count INTEGER NOT NULL DEFAULT 0");
|
|
79
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN project_spread INTEGER NOT NULL DEFAULT 1");
|
|
80
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN first_seen TEXT");
|
|
81
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN last_seen TEXT");
|
|
82
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN valid_from TEXT");
|
|
83
|
+
addCol("concepts", "ALTER TABLE concepts ADD COLUMN valid_to TEXT");
|
|
84
|
+
addCol("relations", "ALTER TABLE relations ADD COLUMN valid_from TEXT");
|
|
85
|
+
addCol("relations", "ALTER TABLE relations ADD COLUMN valid_to TEXT");
|
|
86
|
+
addCol("durable_memories", "ALTER TABLE durable_memories ADD COLUMN source_handoff_id TEXT");
|
|
87
|
+
addCol("durable_memories", "ALTER TABLE durable_memories ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'durable'");
|
|
88
|
+
addCol("durable_memories", "ALTER TABLE durable_memories ADD COLUMN valid_from TEXT");
|
|
89
|
+
addCol("durable_memories", "ALTER TABLE durable_memories ADD COLUMN valid_to TEXT");
|
|
90
|
+
addCol("decision_records", "ALTER TABLE decision_records ADD COLUMN alternatives_json TEXT");
|
|
91
|
+
addCol("decision_records", "ALTER TABLE decision_records ADD COLUMN evidence_json TEXT");
|
|
92
|
+
addCol("decision_records", "ALTER TABLE decision_records ADD COLUMN reversible INTEGER");
|
|
93
|
+
addCol("decision_records", "ALTER TABLE decision_records ADD COLUMN supersedes_decision_id TEXT");
|
|
94
|
+
addCol("decision_records", "ALTER TABLE decision_records ADD COLUMN valid_from TEXT");
|
|
95
|
+
addCol("decision_records", "ALTER TABLE decision_records ADD COLUMN valid_to TEXT");
|
|
96
|
+
addCol("memory_references", "ALTER TABLE memory_references ADD COLUMN ref_kind TEXT NOT NULL DEFAULT 'mentions'");
|
|
97
|
+
addCol("memory_references", "ALTER TABLE memory_references ADD COLUMN weight REAL NOT NULL DEFAULT 1");
|
|
98
|
+
if (tableExists(db, "memory_references")) {
|
|
99
|
+
compactMemoryReferences(db);
|
|
100
|
+
db.exec(`
|
|
101
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_references_unique_edge ON memory_references(
|
|
102
|
+
project_id, source_type, source_id, target_type, target_id, ref_kind
|
|
103
|
+
)
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
106
|
+
if (tableExists(db, "concepts") && tableExists(db, "memory_references")) {
|
|
107
|
+
recomputeConceptReferenceCounts(db);
|
|
108
|
+
}
|
|
183
109
|
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', ?)").run(String(SCHEMA_VERSION));
|
|
184
110
|
}
|
|
111
|
+
export function tableExists(db, tableName) {
|
|
112
|
+
const row = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName);
|
|
113
|
+
return row !== undefined;
|
|
114
|
+
}
|
|
185
115
|
function compactMemoryReferences(db) {
|
|
186
116
|
db.exec(`
|
|
187
117
|
DELETE FROM memory_references
|
package/dist/memoryReport.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { migrate, openMemoryDb } from "./memoryDb.js";
|
|
1
|
+
import { migrate, openMemoryDb, tableExists } from "./memoryDb.js";
|
|
2
2
|
import { defaultDbPath, resolveProjectContext } from "./projectContext.js";
|
|
3
3
|
import { resolveStoredProject } from "./projectMigration.js";
|
|
4
4
|
export function memoryPaths() {
|
|
@@ -10,7 +10,7 @@ export function doctorReport(dbPath = defaultDbPath()) {
|
|
|
10
10
|
migrate(db);
|
|
11
11
|
const project = resolveStoredProject(db, resolveProjectContext());
|
|
12
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());
|
|
13
|
+
const count = (table) => (tableExists(db, table) ? Number(db.prepare(`SELECT COUNT(*) FROM ${table}`).pluck().get()) : 0);
|
|
14
14
|
return {
|
|
15
15
|
paths: { dbPath },
|
|
16
16
|
schemaVersion,
|