omo-memory 0.1.7 → 0.1.9

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 CHANGED
@@ -11,6 +11,7 @@ It gives lazycodex, omo-on-opencode, lfg, and future OMO adapters a shared local
11
11
 
12
12
  - Project-local DB: `<project-root>/.omo/memory/state.sqlite`
13
13
  - Project namespacing: by git remote + project root hash
14
+ - Move handling: if a project directory moves with its `.omo` ledger, existing rows are migrated to the new root automatically.
14
15
  - Privacy default: local-only, no network sync, no secrets by design
15
16
  - Intended adapters: Codex/lazycodex, OpenCode/OMO, GrokBuild/lfg
16
17
 
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { installHooks } from "./hooks.js";
4
- import { bootstrapSession, doctorReport, exportMemory, initMemory, purgeMemory, recentEvents, recordEvent, startSession, writeHandoff } from "./memory.js";
5
4
  import { runMcpServer } from "./mcp.js";
5
+ import { bootstrapSession, doctorReport, exportMemory, purgeMemory, recentEvents, recordEvent, startSession, writeHandoff } from "./memory.js";
6
+ import { initMemory } from "./memoryDb.js";
6
7
  async function main(argv) {
7
8
  const [command, subcommand, ...rest] = argv;
8
9
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
@@ -48,7 +49,10 @@ function runCommand(command, subcommand, rest) {
48
49
  const summary = readFlag(rest, "--summary") ?? fail("event record requires --summary");
49
50
  const payloadJson = readFlag(rest, "--payload-json");
50
51
  const sessionId = readFlag(rest, "--session-id");
51
- return { ok: true, ...recordEvent({ type, summary, ...(payloadJson === undefined ? {} : { payloadJson }), ...(sessionId === undefined ? {} : { sessionId }) }) };
52
+ return {
53
+ ok: true,
54
+ ...recordEvent({ type, summary, ...(payloadJson === undefined ? {} : { payloadJson }), ...(sessionId === undefined ? {} : { sessionId }) }),
55
+ };
52
56
  }
53
57
  if (command === "recent") {
54
58
  const limitRaw = readFlag([subcommand, ...rest].filter((value) => value !== undefined), "--limit");
@@ -2,64 +2,46 @@ export const CODEX_MARKETPLACE = "islee23520";
2
2
  export const CODEX_PLUGIN = "omo-memory";
3
3
  export const CODEX_SKILL = `---
4
4
  name: omo-memory
5
- description: Use OMO Memory as the shared local memory ledger across Codex, Grok, and OpenCode. Trigger when starting non-trivial workspace work, needing prior session context, recording decisions, QA evidence, task state, handoffs, or using the omo-memory MCP tools.
5
+ description: Use OMO Memory for concise local project memory: bootstrap recent events, record decisions, QA evidence, blockers, and handoffs without secrets.
6
6
  ---
7
7
 
8
8
  # OMO Memory
9
9
 
10
- Use the \`omo-memory\` MCP server as the shared local memory ledger across Codex, Grok, and OpenCode.
11
-
12
- ## Session Start
13
-
14
- At the beginning of non-trivial workspace work, call \`memory_bootstrap_session\` with:
10
+ For non-trivial workspace work, call \`memory_bootstrap_session\` before edits:
15
11
 
16
12
  - \`host\`: \`codex\`
17
13
  - \`adapter\`: \`lazycodex\`
18
- - \`limit\`: \`5\`
19
-
20
- Read \`recentEvents\` before changing files. Keep the returned \`sessionId\` for the turn/session.
21
-
22
- ## During Work
23
-
24
- Record durable, concise facts with \`memory_record_event\` and the active \`sessionId\`: decisions, QA evidence, task state, blockers, and handoff-worthy facts.
14
+ - \`limit\`: \`2\`
25
15
 
26
- Use \`memory_write_handoff\` for explicit handoff summaries.
16
+ Read \`recentEvents\`, keep the returned \`sessionId\`, and record only durable summaries with \`memory_record_event\`: decisions, QA evidence, task state, blockers, and handoffs.
27
17
 
28
- ## Privacy
29
-
30
- Never store full transcripts, API keys, tokens, \`.env\` contents, auth files, cookies, bearer headers, or raw secret-bearing logs. Store summaries and evidence references only.
18
+ Use \`memory_write_handoff\` only for explicit handoff summaries. Never store transcripts, secrets, \`.env\`, auth files, cookies, bearer headers, or raw secret-bearing logs.
31
19
  `;
32
20
  export const GROK_SKILL = CODEX_SKILL.replace("Codex, Grok", "Grok, Codex")
33
21
  .replace("- `host`: `codex`", "- `host`: `grok`")
34
22
  .replace("- `adapter`: `lazycodex`", "- `adapter`: `lfg`");
35
23
  export const CODEX_AGENTS_BLOCK = `### OMO Memory lifecycle
36
24
 
37
- When the \`omo-memory\` MCP server is available, use it as the shared local memory ledger for Codex/Grok/OpenCode work.
38
-
39
- At the beginning of a non-trivial workspace session, call \`memory_bootstrap_session\` with:
25
+ When the \`omo-memory\` MCP server is available, use it for concise local project memory. For non-trivial workspace work, call \`memory_bootstrap_session\` before edits:
40
26
 
41
27
  - \`host\`: \`codex\`
42
28
  - \`adapter\`: \`lazycodex\`
43
- - \`limit\`: \`5\`
44
-
45
- Read the returned \`recentEvents\` before making changes. Keep the returned \`sessionId\` for this session.
29
+ - \`limit\`: \`2\`
46
30
 
47
- During work, record concise durable state with \`memory_record_event\` using that \`sessionId\`: decisions, task state, QA evidence, important blockers, and handoff-worthy facts. Use \`memory_write_handoff\` only for explicit handoff summaries. Do not store full transcripts, API keys, tokens, \`.env\` contents, auth files, cookies, bearer headers, or raw secret-bearing logs.`;
31
+ Read \`recentEvents\`, keep the returned \`sessionId\`, and record only durable summaries with \`memory_record_event\`: decisions, task state, QA evidence, blockers, and handoffs. Never store transcripts, secrets, \`.env\`, auth files, cookies, bearer headers, or raw secret-bearing logs.`;
48
32
  export const GROK_AGENTS_BLOCK = CODEX_AGENTS_BLOCK.replace("- `host`: `codex`", "- `host`: `grok`").replace("- `adapter`: `lazycodex`", "- `adapter`: `lfg`");
49
33
  export const SESSION_BOOTSTRAP_SCRIPT = `#!/usr/bin/env node
50
34
  import { spawnSync } from "node:child_process";
51
35
 
52
36
  const host = process.env["OMO_MEMORY_HOST"] ?? "codex";
53
37
  const adapter = process.env["OMO_MEMORY_ADAPTER"] ?? "lazycodex";
54
- const limit = process.env["OMO_MEMORY_LIMIT"] ?? "5";
38
+ const limit = process.env["OMO_MEMORY_LIMIT"] ?? "2";
39
+ const args = ["session", "bootstrap", "--host", host, "--adapter", adapter, "--limit", limit];
55
40
 
56
- const result = spawnSync("npx", ["-y", "omo-memory", "session", "bootstrap", "--host", host, "--adapter", adapter, "--limit", limit], {
57
- encoding: "utf8",
58
- stdio: ["ignore", "pipe", "pipe"],
59
- timeout: 5000
60
- });
41
+ const direct = process.env["OMO_MEMORY_CLI"] ?? "omo-memory";
42
+ const result = runBootstrap(direct, args) ?? runBootstrap("npx", ["-y", "omo-memory", ...args]);
61
43
 
62
- if (result.status !== 0) {
44
+ if (result === undefined || result.status !== 0) {
63
45
  process.stdout.write("OMO Memory: bootstrap unavailable; continue without blocking the session.\\n");
64
46
  process.exit(0);
65
47
  }
@@ -69,7 +51,6 @@ try {
69
51
  const recentEvents = Array.isArray(payload.recentEvents) ? payload.recentEvents : [];
70
52
  process.stdout.write(\`OMO Memory sessionId: \${payload.sessionId}\\n\`);
71
53
  if (recentEvents.length === 0) {
72
- process.stdout.write("OMO Memory recentEvents: none for this project.\\n");
73
54
  process.exit(0);
74
55
  }
75
56
  process.stdout.write("OMO Memory recentEvents:\\n");
@@ -80,11 +61,21 @@ try {
80
61
  const detail = error instanceof Error ? error.message : String(error);
81
62
  process.stdout.write(\`OMO Memory: bootstrap returned unreadable output; \${detail}\\n\`);
82
63
  }
64
+
65
+ function runBootstrap(command, commandArgs) {
66
+ const result = spawnSync(command, commandArgs, {
67
+ encoding: "utf8",
68
+ stdio: ["ignore", "pipe", "pipe"],
69
+ timeout: 2000
70
+ });
71
+ if (result.error && result.error.code === "ENOENT") return undefined;
72
+ return result;
73
+ }
83
74
  `;
84
75
  export const GROK_HOOK_SCRIPT = SESSION_BOOTSTRAP_SCRIPT.replace('?? "codex"', '?? "grok"').replace('?? "lazycodex"', '?? "lfg"');
85
76
  export const GROK_PLUGIN_JSON = `{
86
77
  "name": "omo-memory",
87
- "version": "0.1.7",
78
+ "version": "0.1.9",
88
79
  "description": "Project-local OMO Memory bootstrap hook and MCP server for Grok.",
89
80
  "author": {
90
81
  "name": "islee23520"
@@ -109,7 +100,7 @@ export const GROK_MCP_JSON = `{
109
100
  `;
110
101
  export const CODEX_PLUGIN_JSON = `{
111
102
  "name": "omo-memory",
112
- "version": "0.1.7",
103
+ "version": "0.1.9",
113
104
  "description": "Session-start OMO Memory bootstrap hook for Codex.",
114
105
  "author": "islee23520",
115
106
  "homepage": "https://github.com/islee23520/omo-memory",
@@ -146,7 +137,7 @@ export const CODEX_HOOKS_JSON = `{
146
137
  {
147
138
  "type": "command",
148
139
  "command": "node \\"\${PLUGIN_ROOT}/scripts/omo-memory-session.mjs\\"",
149
- "timeout": 5,
140
+ "timeout": 2,
150
141
  "description": "omo-memory session bootstrap",
151
142
  "statusMessage": "OMO Memory: loading recent session memory"
152
143
  }
@@ -164,7 +155,7 @@ export const GROK_HOOKS_JSON = `{
164
155
  {
165
156
  "type": "command",
166
157
  "command": "node \\"{{HOME}}/.grok/hooks/omo-memory-session.mjs\\"",
167
- "timeout": 5,
158
+ "timeout": 2,
168
159
  "description": "omo-memory session bootstrap",
169
160
  "statusMessage": "OMO Memory: loading recent session memory"
170
161
  }
package/dist/mcp.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { z } from "zod";
4
- import { bootstrapSession, exportMemory, initMemory, memoryPaths, purgeMemory, PurgeConfirmationError, recentEvents, recordEvent, resolveProjectContext, startSession, writeHandoff } from "./memory.js";
4
+ import { bootstrapSession, exportMemory, memoryPaths, PurgeConfirmationError, purgeMemory, recentEvents, recordEvent, startSession, writeHandoff, } from "./memory.js";
5
+ import { initMemory } from "./memoryDb.js";
6
+ import { resolveProjectContext } from "./projectContext.js";
5
7
  export async function runMcpServer() {
6
8
  const server = new McpServer({ name: "omo-memory", version: "0.1.2" });
7
9
  server.registerTool("memory_init", {
package/dist/memory.js CHANGED
@@ -1,94 +1,22 @@
1
- import Database from "better-sqlite3";
2
- import { createHash, randomUUID } from "node:crypto";
3
- import { mkdirSync } from "node:fs";
4
- import { dirname, join, resolve } from "node:path";
5
- import { execFileSync } from "node:child_process";
6
- import { redactSecrets, sanitizeGitRemote } from "./privacy.js";
1
+ import { randomUUID } from "node:crypto";
2
+ import { migrate, openMemoryDb, SCHEMA_VERSION } from "./memoryDb.js";
3
+ import { redactSecrets } from "./privacy.js";
4
+ import { defaultDbPath, resolveProjectContext } from "./projectContext.js";
5
+ import { resolveStoredProject } from "./projectMigration.js";
7
6
  export class PurgeConfirmationError extends Error {
8
7
  constructor() {
9
8
  super("purge requires --yes");
10
9
  this.name = "PurgeConfirmationError";
11
10
  }
12
11
  }
13
- const SCHEMA_VERSION = 1;
14
- export function defaultDbPath() {
15
- return process.env["OMO_MEMORY_DB"] ?? join(resolveProjectContext().repoRoot, ".omo", "memory", "state.sqlite");
16
- }
17
12
  export function memoryPaths() {
18
13
  return { dbPath: defaultDbPath() };
19
14
  }
20
- export function openMemoryDb(dbPath = defaultDbPath()) {
21
- mkdirSync(dirname(dbPath), { recursive: true });
22
- const db = new Database(dbPath);
23
- db.pragma("journal_mode = WAL");
24
- return db;
25
- }
26
- export function migrate(db) {
27
- db.exec(`
28
- CREATE TABLE IF NOT EXISTS schema_meta (
29
- key TEXT PRIMARY KEY,
30
- value TEXT NOT NULL
31
- );
32
-
33
- CREATE TABLE IF NOT EXISTS projects (
34
- id TEXT PRIMARY KEY,
35
- repo_root TEXT NOT NULL,
36
- git_remote TEXT,
37
- created_at TEXT NOT NULL,
38
- last_seen_at TEXT NOT NULL
39
- );
40
-
41
- CREATE TABLE IF NOT EXISTS sessions (
42
- id TEXT PRIMARY KEY,
43
- project_id TEXT NOT NULL,
44
- host TEXT NOT NULL,
45
- adapter TEXT NOT NULL,
46
- started_at TEXT NOT NULL,
47
- ended_at TEXT,
48
- git_branch TEXT,
49
- git_head TEXT,
50
- FOREIGN KEY(project_id) REFERENCES projects(id)
51
- );
52
-
53
- CREATE TABLE IF NOT EXISTS events (
54
- id TEXT PRIMARY KEY,
55
- session_id TEXT,
56
- project_id TEXT NOT NULL,
57
- type TEXT NOT NULL,
58
- summary TEXT NOT NULL,
59
- payload_json TEXT,
60
- created_at TEXT NOT NULL,
61
- FOREIGN KEY(project_id) REFERENCES projects(id),
62
- FOREIGN KEY(session_id) REFERENCES sessions(id)
63
- );
64
-
65
- CREATE TABLE IF NOT EXISTS handoffs (
66
- id TEXT PRIMARY KEY,
67
- project_id TEXT NOT NULL,
68
- session_id TEXT,
69
- summary_md TEXT NOT NULL,
70
- created_at TEXT NOT NULL,
71
- FOREIGN KEY(project_id) REFERENCES projects(id),
72
- FOREIGN KEY(session_id) REFERENCES sessions(id)
73
- );
74
- `);
75
- db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', ?)").run(String(SCHEMA_VERSION));
76
- }
77
- export function initMemory(dbPath = defaultDbPath()) {
78
- const db = openMemoryDb(dbPath);
79
- try {
80
- migrate(db);
81
- return { dbPath, schemaVersion: SCHEMA_VERSION };
82
- }
83
- finally {
84
- db.close();
85
- }
86
- }
87
15
  export function doctorReport(dbPath = defaultDbPath()) {
88
16
  const db = openMemoryDb(dbPath);
89
17
  try {
90
18
  migrate(db);
91
- const project = resolveProjectContext();
19
+ const project = resolveStoredProject(db, resolveProjectContext());
92
20
  const schemaVersion = Number(db.prepare("SELECT value FROM schema_meta WHERE key = 'schema_version'").pluck().get());
93
21
  const count = (table) => Number(db.prepare(`SELECT COUNT(*) FROM ${table}`).pluck().get());
94
22
  return {
@@ -107,15 +35,6 @@ export function doctorReport(dbPath = defaultDbPath()) {
107
35
  db.close();
108
36
  }
109
37
  }
110
- export function resolveProjectContext(cwd = process.cwd()) {
111
- const repoRoot = gitValue(["rev-parse", "--show-toplevel"], cwd) ?? resolve(cwd);
112
- const rawGitRemote = gitValue(["config", "--get", "remote.origin.url"], repoRoot);
113
- const gitRemote = sanitizeGitRemote(rawGitRemote);
114
- const gitBranch = gitValue(["rev-parse", "--abbrev-ref", "HEAD"], repoRoot);
115
- const gitHead = gitValue(["rev-parse", "HEAD"], repoRoot);
116
- const id = createHash("sha256").update(`${rawGitRemote ?? ""}\n${repoRoot}`).digest("hex").slice(0, 24);
117
- return { id, repoRoot, gitRemote, gitBranch, gitHead };
118
- }
119
38
  export function upsertProject(db, project) {
120
39
  const now = new Date().toISOString();
121
40
  db.prepare(`
@@ -131,7 +50,7 @@ export function startSession(input, dbPath = defaultDbPath()) {
131
50
  const db = openMemoryDb(dbPath);
132
51
  try {
133
52
  migrate(db);
134
- const project = resolveProjectContext();
53
+ const project = resolveStoredProject(db, resolveProjectContext());
135
54
  upsertProject(db, project);
136
55
  const sessionId = randomUUID();
137
56
  db.prepare(`
@@ -152,7 +71,7 @@ export function recordEvent(input, dbPath = defaultDbPath()) {
152
71
  const db = openMemoryDb(dbPath);
153
72
  try {
154
73
  migrate(db);
155
- const project = resolveProjectContext();
74
+ const project = resolveStoredProject(db, resolveProjectContext());
156
75
  upsertProject(db, project);
157
76
  const eventId = randomUUID();
158
77
  db.prepare(`
@@ -169,14 +88,16 @@ export function recentEvents(limit, dbPath = defaultDbPath()) {
169
88
  const db = openMemoryDb(dbPath);
170
89
  try {
171
90
  migrate(db);
172
- const project = resolveProjectContext();
173
- return db.prepare(`
91
+ const project = resolveStoredProject(db, resolveProjectContext());
92
+ return db
93
+ .prepare(`
174
94
  SELECT id, type, summary, created_at AS createdAt, session_id AS sessionId
175
95
  FROM events
176
96
  WHERE project_id = ?
177
97
  ORDER BY created_at DESC
178
98
  LIMIT ?
179
- `).all(project.id, limit);
99
+ `)
100
+ .all(project.id, limit);
180
101
  }
181
102
  finally {
182
103
  db.close();
@@ -186,7 +107,7 @@ export function writeHandoff(summaryMd, sessionId, dbPath = defaultDbPath()) {
186
107
  const db = openMemoryDb(dbPath);
187
108
  try {
188
109
  migrate(db);
189
- const project = resolveProjectContext();
110
+ const project = resolveStoredProject(db, resolveProjectContext());
190
111
  upsertProject(db, project);
191
112
  const handoffId = randomUUID();
192
113
  db.prepare(`
@@ -203,19 +124,25 @@ export function exportMemory(dbPath = defaultDbPath()) {
203
124
  const db = openMemoryDb(dbPath);
204
125
  try {
205
126
  migrate(db);
206
- const project = resolveProjectContext();
207
- const sessions = db.prepare(`
127
+ const project = resolveStoredProject(db, resolveProjectContext());
128
+ const sessions = db
129
+ .prepare(`
208
130
  SELECT id, host, adapter, started_at AS startedAt, ended_at AS endedAt, git_branch AS gitBranch, git_head AS gitHead FROM sessions
209
131
  WHERE project_id = ? ORDER BY started_at ASC, id ASC
210
- `).all(project.id);
211
- const events = db.prepare(`
132
+ `)
133
+ .all(project.id);
134
+ const events = db
135
+ .prepare(`
212
136
  SELECT id, session_id AS sessionId, type, summary, payload_json AS payloadJson, created_at AS createdAt FROM events
213
137
  WHERE project_id = ? ORDER BY created_at ASC, id ASC
214
- `).all(project.id);
215
- const handoffs = db.prepare(`
138
+ `)
139
+ .all(project.id);
140
+ const handoffs = db
141
+ .prepare(`
216
142
  SELECT id, session_id AS sessionId, summary_md AS summaryMd, created_at AS createdAt FROM handoffs
217
143
  WHERE project_id = ? ORDER BY created_at ASC, id ASC
218
- `).all(project.id);
144
+ `)
145
+ .all(project.id);
219
146
  return {
220
147
  schemaVersion: SCHEMA_VERSION,
221
148
  exportedAt: new Date().toISOString(),
@@ -236,11 +163,17 @@ export function purgeMemory(input, dbPath = defaultDbPath()) {
236
163
  const db = openMemoryDb(dbPath);
237
164
  try {
238
165
  migrate(db);
239
- const project = resolveProjectContext();
166
+ const project = resolveStoredProject(db, resolveProjectContext());
240
167
  const deleteProject = db.transaction(() => {
241
- const events = db.prepare("DELETE FROM events WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)").run(project.id, project.repoRoot).changes;
242
- const handoffs = db.prepare("DELETE FROM handoffs WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)").run(project.id, project.repoRoot).changes;
243
- const sessions = db.prepare("DELETE FROM sessions WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)").run(project.id, project.repoRoot).changes;
168
+ const events = db
169
+ .prepare("DELETE FROM events WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
170
+ .run(project.id, project.repoRoot).changes;
171
+ const handoffs = db
172
+ .prepare("DELETE FROM handoffs WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
173
+ .run(project.id, project.repoRoot).changes;
174
+ const sessions = db
175
+ .prepare("DELETE FROM sessions WHERE project_id IN (SELECT id FROM projects WHERE id = ? OR repo_root = ?)")
176
+ .run(project.id, project.repoRoot).changes;
244
177
  const projects = db.prepare("DELETE FROM projects WHERE id = ? OR repo_root = ?").run(project.id, project.repoRoot).changes;
245
178
  return { events, handoffs, sessions, projects };
246
179
  });
@@ -250,11 +183,3 @@ export function purgeMemory(input, dbPath = defaultDbPath()) {
250
183
  db.close();
251
184
  }
252
185
  }
253
- function gitValue(args, cwd) {
254
- try {
255
- return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
256
- }
257
- catch {
258
- return null;
259
- }
260
- }
@@ -0,0 +1,72 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import Database from "better-sqlite3";
4
+ import { defaultDbPath } from "./projectContext.js";
5
+ export const SCHEMA_VERSION = 1;
6
+ export function openMemoryDb(dbPath = defaultDbPath()) {
7
+ mkdirSync(dirname(dbPath), { recursive: true });
8
+ const db = new Database(dbPath);
9
+ db.pragma("journal_mode = WAL");
10
+ return db;
11
+ }
12
+ export function migrate(db) {
13
+ db.exec(`
14
+ CREATE TABLE IF NOT EXISTS schema_meta (
15
+ key TEXT PRIMARY KEY,
16
+ value TEXT NOT NULL
17
+ );
18
+
19
+ CREATE TABLE IF NOT EXISTS projects (
20
+ id TEXT PRIMARY KEY,
21
+ repo_root TEXT NOT NULL,
22
+ git_remote TEXT,
23
+ created_at TEXT NOT NULL,
24
+ last_seen_at TEXT NOT NULL
25
+ );
26
+
27
+ CREATE TABLE IF NOT EXISTS sessions (
28
+ id TEXT PRIMARY KEY,
29
+ project_id TEXT NOT NULL,
30
+ host TEXT NOT NULL,
31
+ adapter TEXT NOT NULL,
32
+ started_at TEXT NOT NULL,
33
+ ended_at TEXT,
34
+ git_branch TEXT,
35
+ git_head TEXT,
36
+ FOREIGN KEY(project_id) REFERENCES projects(id)
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS events (
40
+ id TEXT PRIMARY KEY,
41
+ session_id TEXT,
42
+ project_id TEXT NOT NULL,
43
+ type TEXT NOT NULL,
44
+ summary TEXT NOT NULL,
45
+ payload_json TEXT,
46
+ created_at TEXT NOT NULL,
47
+ FOREIGN KEY(project_id) REFERENCES projects(id),
48
+ FOREIGN KEY(session_id) REFERENCES sessions(id)
49
+ );
50
+
51
+ CREATE TABLE IF NOT EXISTS handoffs (
52
+ id TEXT PRIMARY KEY,
53
+ project_id TEXT NOT NULL,
54
+ session_id TEXT,
55
+ summary_md TEXT NOT NULL,
56
+ created_at TEXT NOT NULL,
57
+ FOREIGN KEY(project_id) REFERENCES projects(id),
58
+ FOREIGN KEY(session_id) REFERENCES sessions(id)
59
+ );
60
+ `);
61
+ db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', ?)").run(String(SCHEMA_VERSION));
62
+ }
63
+ export function initMemory(dbPath = defaultDbPath()) {
64
+ const db = openMemoryDb(dbPath);
65
+ try {
66
+ migrate(db);
67
+ return { dbPath, schemaVersion: SCHEMA_VERSION };
68
+ }
69
+ finally {
70
+ db.close();
71
+ }
72
+ }
@@ -0,0 +1,27 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { join, resolve } from "node:path";
4
+ import { sanitizeGitRemote } from "./privacy.js";
5
+ export function defaultDbPath() {
6
+ return process.env["OMO_MEMORY_DB"] ?? join(resolveProjectContext().repoRoot, ".omo", "memory", "state.sqlite");
7
+ }
8
+ export function resolveProjectContext(cwd = process.cwd()) {
9
+ const repoRoot = gitValue(["rev-parse", "--show-toplevel"], cwd) ?? resolve(cwd);
10
+ const rawGitRemote = gitValue(["config", "--get", "remote.origin.url"], repoRoot);
11
+ const gitRemote = sanitizeGitRemote(rawGitRemote);
12
+ const gitBranch = gitValue(["rev-parse", "--abbrev-ref", "HEAD"], repoRoot);
13
+ const gitHead = gitValue(["rev-parse", "HEAD"], repoRoot);
14
+ const id = createHash("sha256")
15
+ .update(`${rawGitRemote ?? ""}\n${repoRoot}`)
16
+ .digest("hex")
17
+ .slice(0, 24);
18
+ return { id, repoRoot, gitRemote, gitBranch, gitHead };
19
+ }
20
+ function gitValue(args, cwd) {
21
+ try {
22
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
@@ -0,0 +1,40 @@
1
+ export function resolveStoredProject(db, project) {
2
+ const current = readProjectById(db, project.id);
3
+ if (current !== undefined) {
4
+ updateProjectMetadata(db, project);
5
+ return project;
6
+ }
7
+ const candidate = findMovedProjectCandidate(db, project);
8
+ if (candidate === undefined || candidate.id === project.id)
9
+ return project;
10
+ updateProjectMetadata(db, { ...project, id: candidate.id });
11
+ return { ...project, id: candidate.id };
12
+ }
13
+ function findMovedProjectCandidate(db, project) {
14
+ const byRoot = readProjectByRepoRoot(db, project.repoRoot);
15
+ if (byRoot !== undefined)
16
+ return byRoot;
17
+ if (project.gitRemote !== null) {
18
+ const byRemote = readProjectByGitRemote(db, project.gitRemote);
19
+ if (byRemote !== undefined)
20
+ return byRemote;
21
+ }
22
+ const allProjects = db
23
+ .prepare("SELECT id, repo_root, git_remote, created_at FROM projects ORDER BY last_seen_at DESC, created_at DESC")
24
+ .all();
25
+ return allProjects.length === 1 ? allProjects[0] : undefined;
26
+ }
27
+ function readProjectById(db, id) {
28
+ return db.prepare("SELECT id, repo_root, git_remote, created_at FROM projects WHERE id = ?").get(id);
29
+ }
30
+ function readProjectByRepoRoot(db, repoRoot) {
31
+ return db.prepare("SELECT id, repo_root, git_remote, created_at FROM projects WHERE repo_root = ?").get(repoRoot);
32
+ }
33
+ function readProjectByGitRemote(db, gitRemote) {
34
+ return db
35
+ .prepare("SELECT id, repo_root, git_remote, created_at FROM projects WHERE git_remote = ? ORDER BY last_seen_at DESC, created_at DESC")
36
+ .get(gitRemote);
37
+ }
38
+ function updateProjectMetadata(db, project) {
39
+ db.prepare("UPDATE projects SET repo_root = ?, git_remote = ?, last_seen_at = ? WHERE id = ?").run(project.repoRoot, project.gitRemote, new Date().toISOString(), project.id);
40
+ }
@@ -11,6 +11,7 @@ No full transcript capture by default. Do not store API keys, tokens, `.env` con
11
11
  - Record concise events with a stable `type`, a human-readable `summary`, and optional redacted JSON metadata in `payloadJson`.
12
12
  - Store handoffs as summary markdown that another host can read without needing the originating transcript.
13
13
  - Treat CLI and MCP as two entrypoints to the same core functions and schema.
14
+ - When a project directory moves with its `.omo` ledger, OMO Memory migrates matching project rows to the new root automatically.
14
15
  - Use `OMO_MEMORY_DB` only when the caller explicitly chooses a different database path, such as an isolated smoke test.
15
16
 
16
17
  ## Adapter Metadata
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo-memory",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Host-neutral local SQLite memory and session ledger for OMO adapters, exposed through CLI and MCP.",
5
5
  "type": "module",
6
6
  "files": [
@@ -25,6 +25,9 @@
25
25
  "scripts": {
26
26
  "build": "tsc -p tsconfig.json",
27
27
  "typecheck": "tsc --noEmit",
28
+ "lint": "biome check .",
29
+ "format": "biome format --write .",
30
+ "check": "npm run lint && npm run typecheck && npm run smoke",
28
31
  "start": "node dist/cli.js",
29
32
  "prepack": "npm run build",
30
33
  "presmoke": "npm run build",
@@ -40,6 +43,7 @@
40
43
  "zod": "^4.4.3"
41
44
  },
42
45
  "devDependencies": {
46
+ "@biomejs/biome": "^2.5.1",
43
47
  "@types/better-sqlite3": "^7.6.13",
44
48
  "@types/node": "^24.10.1",
45
49
  "typescript": "^6.0.3"