@justfortytwo/memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/dist/contract.d.ts +19 -0
  4. package/dist/contract.js +41 -0
  5. package/dist/contract.js.map +1 -0
  6. package/dist/db.d.ts +11 -0
  7. package/dist/db.js +33 -0
  8. package/dist/db.js.map +1 -0
  9. package/dist/dispatch.d.ts +3 -0
  10. package/dist/dispatch.js +43 -0
  11. package/dist/dispatch.js.map +1 -0
  12. package/dist/embedder.d.ts +18 -0
  13. package/dist/embedder.js +44 -0
  14. package/dist/embedder.js.map +1 -0
  15. package/dist/enrichment.d.ts +58 -0
  16. package/dist/enrichment.js +109 -0
  17. package/dist/enrichment.js.map +1 -0
  18. package/dist/gate-approval-store.d.ts +17 -0
  19. package/dist/gate-approval-store.js +114 -0
  20. package/dist/gate-approval-store.js.map +1 -0
  21. package/dist/index.d.ts +16 -0
  22. package/dist/index.js +72 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/memory.d.ts +74 -0
  25. package/dist/memory.js +179 -0
  26. package/dist/memory.js.map +1 -0
  27. package/dist/migrate.d.ts +2 -0
  28. package/dist/migrate.js +40 -0
  29. package/dist/migrate.js.map +1 -0
  30. package/dist/migrations/001_init.d.ts +3 -0
  31. package/dist/migrations/001_init.js +40 -0
  32. package/dist/migrations/001_init.js.map +1 -0
  33. package/dist/migrations/002_fts.d.ts +3 -0
  34. package/dist/migrations/002_fts.js +26 -0
  35. package/dist/migrations/002_fts.js.map +1 -0
  36. package/dist/migrations/003_approvals.d.ts +3 -0
  37. package/dist/migrations/003_approvals.js +37 -0
  38. package/dist/migrations/003_approvals.js.map +1 -0
  39. package/dist/tools.d.ts +2 -0
  40. package/dist/tools.js +66 -0
  41. package/dist/tools.js.map +1 -0
  42. package/package.json +68 -0
@@ -0,0 +1,114 @@
1
+ // memory's implementation of gate's host-integration seam.
2
+ //
3
+ // @justfortytwo/gate (the safety gate) defines the ApprovalStore + AuditLogger
4
+ // interfaces but ships only standalone in-memory / JSONL stores. This is the
5
+ // RICHER, durable backing the gate's `decide()` accepts via opts.store / opts.audit:
6
+ // a transactional store + audit trail on memory's own SQLite db (tables created by
7
+ // migration 003_approvals).
8
+ //
9
+ // Dependency direction is ONE-WAY: memory -> gate (we import gate's TYPES; gate
10
+ // never imports memory). No cycle. gate is a peerDependency of memory.
11
+ import { randomUUID } from 'node:crypto';
12
+ function nowIso() {
13
+ return new Date().toISOString();
14
+ }
15
+ /**
16
+ * Fail-closed transition guard (same invariant as gate's stock stores): only a
17
+ * pending call may be approved/denied, and an approval may be revoked (-> denied)
18
+ * before it is consumed. Terminal states (executed/denied/expired) are immutable,
19
+ * so a decision can never resurrect a spent or denied one-shot.
20
+ */
21
+ function canDecide(current, next) {
22
+ if (current === 'pending')
23
+ return true;
24
+ if (current === 'approved' && next === 'denied')
25
+ return true;
26
+ return false;
27
+ }
28
+ function toApproval(r) {
29
+ return {
30
+ id: r.id,
31
+ tool: r.tool,
32
+ target: r.target,
33
+ payload: JSON.parse(r.payload),
34
+ tier: r.tier,
35
+ tool_use_id: r.tool_use_id,
36
+ session_id: r.session_id,
37
+ status: r.status,
38
+ created_at: r.created_at,
39
+ updated_at: r.updated_at,
40
+ };
41
+ }
42
+ /**
43
+ * SQLite-backed ApprovalStore + AuditLogger for the gate. Pass an instance
44
+ * to gate's `decide(manifest, ctx, { store, audit })` so staged one-shots and the
45
+ * audit trail live in memory's durable db instead of the gate's JSONL default.
46
+ */
47
+ export class GateApprovalStore {
48
+ h;
49
+ constructor(h) {
50
+ this.h = h;
51
+ }
52
+ async addPending(input) {
53
+ const id = `pa_${randomUUID()}`;
54
+ const ts = nowIso();
55
+ // Upsert keyed on tool_use_id: a re-request replaces the prior (terminal) row
56
+ // with a fresh pending one, so there is exactly one staged row per tool_use_id
57
+ // and "most recent wins" is deterministic (no fragile timestamp/uuid tie-break).
58
+ this.h.raw
59
+ .prepare(`INSERT INTO approvals
60
+ (id, tool, target, payload, tier, tool_use_id, session_id, status, decided_by, created_at, updated_at)
61
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', NULL, ?, ?)
62
+ ON CONFLICT(tool_use_id) DO UPDATE SET
63
+ id = excluded.id, tool = excluded.tool, target = excluded.target,
64
+ payload = excluded.payload, tier = excluded.tier, session_id = excluded.session_id,
65
+ status = 'pending', decided_by = NULL,
66
+ created_at = excluded.created_at, updated_at = excluded.updated_at`)
67
+ .run(id, input.tool, input.target, JSON.stringify(input.payload ?? {}), input.tier, input.tool_use_id, input.session_id ?? null, ts, ts);
68
+ return id;
69
+ }
70
+ async getByToolUseId(toolUseId) {
71
+ // tool_use_id is UNIQUE (one staged row each), so this is unambiguous.
72
+ const row = this.h.raw
73
+ .prepare(`SELECT * FROM approvals WHERE tool_use_id = ? LIMIT 1`)
74
+ .get(toolUseId);
75
+ return row ? toApproval(row) : undefined;
76
+ }
77
+ async markExecutedByToolUseId(toolUseId) {
78
+ // Atomic compare-and-set approved -> executed: succeeds exactly once.
79
+ const txn = this.h.raw.transaction(() => {
80
+ const info = this.h.raw
81
+ .prepare(`UPDATE approvals SET status = 'executed', updated_at = ?
82
+ WHERE tool_use_id = ? AND status = 'approved'`)
83
+ .run(nowIso(), toolUseId);
84
+ return info.changes > 0;
85
+ });
86
+ return txn();
87
+ }
88
+ async setDecisionByToolUseId(toolUseId, status, by) {
89
+ const txn = this.h.raw.transaction(() => {
90
+ const row = this.h.raw
91
+ .prepare(`SELECT id, status FROM approvals WHERE tool_use_id = ? LIMIT 1`)
92
+ .get(toolUseId);
93
+ if (!row || !canDecide(row.status, status))
94
+ return false;
95
+ this.h.raw
96
+ .prepare(`UPDATE approvals SET status = ?, decided_by = ?, updated_at = ? WHERE id = ?`)
97
+ .run(status, by ?? null, nowIso(), row.id);
98
+ return true;
99
+ });
100
+ return txn();
101
+ }
102
+ async list() {
103
+ const rows = this.h.raw
104
+ .prepare(`SELECT * FROM approvals ORDER BY created_at ASC, id ASC`)
105
+ .all();
106
+ return rows.map(toApproval);
107
+ }
108
+ async log(entry) {
109
+ this.h.raw
110
+ .prepare(`INSERT INTO audit_log (actor, kind, content, approval_status, meta) VALUES (?, ?, ?, ?, ?)`)
111
+ .run(entry.actor, entry.kind, entry.content, entry.approval_status ?? null, JSON.stringify(entry.meta ?? {}));
112
+ }
113
+ }
114
+ //# sourceMappingURL=gate-approval-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-approval-store.js","sourceRoot":"","sources":["../src/gate-approval-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ export * from './contract.js';
3
+ export * from './embedder.js';
4
+ export { openDb, type DbHandles, EMBED_DIM } from './db.js';
5
+ export { runMigrations } from './migrate.js';
6
+ export { store, query, recall, recallDocs, lexical, reindex, exportRange, reembed, type MemoryInput, type MemoryRow, type RecallRow, type QueryOpts, type DocRecallRow, type ReindexResult, } from './memory.js';
7
+ export { enrich, enrichFromTurn, type EnrichmentCandidate, type EnrichmentResult } from './enrichment.js';
8
+ export { toolDefinitions } from './tools.js';
9
+ export { GateApprovalStore } from './gate-approval-store.js';
10
+ /**
11
+ * Boot the MCP server over stdio. Kept behind an entrypoint guard so that
12
+ * IMPORTING this package as a library (e.g. the installer calling
13
+ * `runMigrations`/`openDb`) does NOT open a DB, run migrations, or connect a
14
+ * transport. Only running the `fortytwo-memory` bin directly starts the server.
15
+ */
16
+ export declare function startServer(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { resolve, dirname } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { mkdirSync, realpathSync } from 'node:fs';
8
+ import { openDb } from './db.js';
9
+ import { runMigrations } from './migrate.js';
10
+ import { FakeEmbedder, OllamaEmbedder } from './embedder.js';
11
+ import { toolDefinitions } from './tools.js';
12
+ import { callTool } from './dispatch.js';
13
+ import { MEMORY_SERVER_ID } from './contract.js';
14
+ // Public surface re-exports so consumers can `import { ... } from '@justfortytwo/memory'`.
15
+ export * from './contract.js';
16
+ export * from './embedder.js';
17
+ export { openDb, EMBED_DIM } from './db.js';
18
+ export { runMigrations } from './migrate.js';
19
+ export { store, query, recall, recallDocs, lexical, reindex, exportRange, reembed, } from './memory.js';
20
+ export { enrich, enrichFromTurn } from './enrichment.js';
21
+ export { toolDefinitions } from './tools.js';
22
+ // memory's implementation of gate's ApprovalStore + AuditLogger seam (memory -> gate).
23
+ export { GateApprovalStore } from './gate-approval-store.js';
24
+ /**
25
+ * Boot the MCP server over stdio. Kept behind an entrypoint guard so that
26
+ * IMPORTING this package as a library (e.g. the installer calling
27
+ * `runMigrations`/`openDb`) does NOT open a DB, run migrations, or connect a
28
+ * transport. Only running the `fortytwo-memory` bin directly starts the server.
29
+ */
30
+ export async function startServer() {
31
+ // Standalone, persona-agnostic: DB_PATH (env) or ./memory.db. No repo-root coupling.
32
+ const DB_PATH = process.env.DB_PATH ? resolve(process.env.DB_PATH) : resolve('memory.db');
33
+ // EMBED_MODEL present → real Ollama embedder; absent → deterministic FakeEmbedder
34
+ // (lets the server boot with zero infra for tests / first-run smoke checks).
35
+ const embedder = process.env.EMBED_MODEL
36
+ ? new OllamaEmbedder(process.env.EMBED_MODEL, process.env.OLLAMA_BASE_URL)
37
+ : new FakeEmbedder();
38
+ mkdirSync(dirname(DB_PATH), { recursive: true });
39
+ const h = openDb(DB_PATH);
40
+ await runMigrations(h.k);
41
+ const server = new Server({ name: MEMORY_SERVER_ID, version: '0.1.0' }, { capabilities: { tools: {} } });
42
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
43
+ tools: toolDefinitions(),
44
+ }));
45
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
46
+ const { name, arguments: args = {} } = req.params;
47
+ const result = await callTool(h, embedder, name, args);
48
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
49
+ });
50
+ const transport = new StdioServerTransport();
51
+ await server.connect(transport);
52
+ }
53
+ // Run the server only when invoked directly as the `fortytwo-memory` bin.
54
+ // Realpath comparison so the npm bin symlink resolves to this module.
55
+ function invokedAsBin() {
56
+ const argv1 = process.argv[1];
57
+ if (!argv1)
58
+ return false;
59
+ try {
60
+ return realpathSync(argv1) === realpathSync(fileURLToPath(import.meta.url));
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ if (invokedAsBin()) {
67
+ startServer().catch((err) => {
68
+ process.stderr.write(`fortytwo-memory: ${err?.stack ?? err}\n`);
69
+ process.exit(1);
70
+ });
71
+ }
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import type { DbHandles } from './db.js';
2
+ import type { Embedder } from './embedder.js';
3
+ /** A memory to write. `content` is required; everything else is provenance. */
4
+ export interface MemoryInput {
5
+ content: string;
6
+ /** Where this came from, e.g. "owner", "web", "tool:foo". Free-form. */
7
+ source?: string;
8
+ /** How it was observed, e.g. "stated", "inferred", "imported". Free-form. */
9
+ observed?: string;
10
+ /** ISO date the memory pertains to. Defaults to today (UTC) at write time. */
11
+ date?: string;
12
+ /** Free-form tags for filtering. */
13
+ tags?: string[];
14
+ /** Arbitrary structured provenance. Stored as JSON. */
15
+ meta?: Record<string, unknown>;
16
+ /**
17
+ * If set, this memory SUPERSEDES the referenced memory id. The prior row is
18
+ * kept (history is never silently destroyed) but flagged superseded.
19
+ * See enrichment.ts for the dedupe/supersede design.
20
+ */
21
+ supersedes?: number | null;
22
+ }
23
+ export interface MemoryRow {
24
+ id: number;
25
+ ts: string;
26
+ content: string;
27
+ source: string | null;
28
+ observed: string | null;
29
+ date: string | null;
30
+ tags: string;
31
+ meta: string;
32
+ superseded_by: number | null;
33
+ }
34
+ /** Atomic insert of the relational row + its embedding (raw transaction). */
35
+ export declare function store(h: DbHandles, embedder: Embedder, m: MemoryInput): Promise<number>;
36
+ export interface QueryOpts {
37
+ source?: string;
38
+ observed?: string;
39
+ tag?: string;
40
+ since?: string;
41
+ until?: string;
42
+ /** When false, superseded rows are excluded (default: true — only live rows). */
43
+ liveOnly?: boolean;
44
+ limit?: number;
45
+ }
46
+ export declare function query(h: DbHandles, opts?: QueryOpts): Promise<MemoryRow[]>;
47
+ export interface RecallRow extends MemoryRow {
48
+ distance: number;
49
+ }
50
+ /** Semantic top-k recall over the memory store by meaning. */
51
+ export declare function recall(h: DbHandles, embedder: Embedder, text: string, k?: number): Promise<RecallRow[]>;
52
+ /** Full-text keyword search over the memory store (FTS5). */
53
+ export declare function lexical(h: DbHandles, text: string, k?: number): MemoryRow[];
54
+ export interface ReindexResult {
55
+ indexed: number;
56
+ removed: number;
57
+ }
58
+ /**
59
+ * Self-heal the doc recall index from a directory of markdown files. Hashes
60
+ * content to skip unchanged files; removes index rows for files that vanished.
61
+ * Generic version of the original assistant's curated-doc reindex (no curated-corpus assumptions).
62
+ */
63
+ export declare function reindex(h: DbHandles, embedder: Embedder, root: string): Promise<ReindexResult>;
64
+ export interface DocRecallRow {
65
+ file_path: string;
66
+ distance: number;
67
+ preview: string;
68
+ }
69
+ /** Semantic top-k recall over reindexed markdown (the doc_vec index). */
70
+ export declare function recallDocs(h: DbHandles, embedder: Embedder, text: string, k?: number): Promise<DocRecallRow[]>;
71
+ /** Render a date range of memories to markdown (for debugging/export). */
72
+ export declare function exportRange(h: DbHandles, since: string, until: string): Promise<string>;
73
+ /** Re-embed a stored memory's content (e.g. after an embedder/model change). */
74
+ export declare function reembed(h: DbHandles, embedder: Embedder, id: number): Promise<boolean>;
package/dist/memory.js ADDED
@@ -0,0 +1,179 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readdirSync, readFileSync, existsSync } from 'node:fs';
3
+ import { vecToBuffer } from './embedder.js';
4
+ function todayUtc() {
5
+ return new Date().toISOString().slice(0, 10);
6
+ }
7
+ /** Atomic insert of the relational row + its embedding (raw transaction). */
8
+ export async function store(h, embedder, m) {
9
+ const vec = vecToBuffer(await embedder.embed(m.content));
10
+ const ins = h.raw.prepare(`INSERT INTO memories (content, source, observed, date, tags, meta)
11
+ VALUES (?, ?, ?, ?, ?, ?)`);
12
+ const insVec = h.raw.prepare('INSERT INTO memory_vec (rowid, embedding) VALUES (?, ?)');
13
+ const markSuperseded = h.raw.prepare('UPDATE memories SET superseded_by = ? WHERE id = ?');
14
+ const txn = h.raw.transaction(() => {
15
+ const info = ins.run(m.content, m.source ?? null, m.observed ?? null, m.date ?? todayUtc(), JSON.stringify(m.tags ?? []), JSON.stringify(m.meta ?? {}));
16
+ // vec0's xUpdate only accepts a 64-bit INTEGER binding; better-sqlite3
17
+ // promotes BigInt to SQLITE_INTEGER, while a plain Number fails vec0's
18
+ // "only integers allowed" check. Bind as BigInt; return as Number.
19
+ const id = Number(info.lastInsertRowid);
20
+ insVec.run(BigInt(id), vec);
21
+ // SUPERSEDE: keep history, link the old row forward (never a silent overwrite).
22
+ if (m.supersedes != null)
23
+ markSuperseded.run(id, m.supersedes);
24
+ return id;
25
+ });
26
+ return txn();
27
+ }
28
+ export async function query(h, opts = {}) {
29
+ let q = h.k('memories').select('*');
30
+ if (opts.source)
31
+ q = q.where('source', opts.source);
32
+ if (opts.observed)
33
+ q = q.where('observed', opts.observed);
34
+ if (opts.tag)
35
+ q = q.where('tags', 'like', `%"${opts.tag}"%`);
36
+ if (opts.since)
37
+ q = q.where('ts', '>=', opts.since);
38
+ if (opts.until)
39
+ q = q.where('ts', '<=', opts.until);
40
+ if (opts.liveOnly !== false)
41
+ q = q.whereNull('superseded_by');
42
+ return q.orderBy('ts', 'desc').limit(opts.limit ?? 50);
43
+ }
44
+ /** Semantic top-k recall over the memory store by meaning. */
45
+ export async function recall(h, embedder, text, k = 5) {
46
+ const qv = vecToBuffer(await embedder.embed(text));
47
+ // sqlite-vec's vec0 module needs the KNN size constraint at prepare time.
48
+ // `LIMIT ?` is only visible to vec0's xBestIndex on SQLite >= 3.41, so we
49
+ // use the portable `k = ?` form in the WHERE clause instead.
50
+ const stmt = h.raw.prepare(`SELECT m.*, v.distance
51
+ FROM memory_vec v
52
+ JOIN memories m ON m.id = v.rowid
53
+ WHERE v.embedding MATCH ? AND k = ?
54
+ AND m.superseded_by IS NULL
55
+ ORDER BY v.distance`);
56
+ return stmt.all(qv, k);
57
+ }
58
+ /** Full-text keyword search over the memory store (FTS5). */
59
+ export function lexical(h, text, k = 50) {
60
+ const stmt = h.raw.prepare(`SELECT m.*
61
+ FROM memory_fts f
62
+ JOIN memories m ON m.id = f.rowid
63
+ WHERE memory_fts MATCH ?
64
+ AND m.superseded_by IS NULL
65
+ ORDER BY rank
66
+ LIMIT ?`);
67
+ // FTS5 MATCH treats spaces as AND; quote each token defensively.
68
+ const safe = text.split(/\s+/).filter(Boolean).map((t) => `"${t.replace(/"/g, '""')}"`).join(' ');
69
+ return stmt.all(safe, k);
70
+ }
71
+ function sha256(s) {
72
+ return createHash('sha256').update(s).digest('hex');
73
+ }
74
+ function listMarkdown(root) {
75
+ if (!existsSync(root))
76
+ return [];
77
+ return readdirSync(root)
78
+ .filter((f) => f.endsWith('.md'))
79
+ .map((f) => `${root}/${f}`);
80
+ }
81
+ /**
82
+ * Self-heal the doc recall index from a directory of markdown files. Hashes
83
+ * content to skip unchanged files; removes index rows for files that vanished.
84
+ * Generic version of the original assistant's curated-doc reindex (no curated-corpus assumptions).
85
+ */
86
+ export async function reindex(h, embedder, root) {
87
+ const files = listMarkdown(root);
88
+ const currentPaths = new Set(files);
89
+ const selIndexed = h.raw.prepare('SELECT id, file_path FROM index_state');
90
+ const selState = h.raw.prepare('SELECT id, sha256 FROM index_state WHERE file_path = ?');
91
+ const insState = h.raw.prepare('INSERT INTO index_state (file_path, sha256) VALUES (?, ?)');
92
+ const updState = h.raw.prepare("UPDATE index_state SET sha256 = ?, embedded_at = datetime('now') WHERE id = ?");
93
+ const delState = h.raw.prepare('DELETE FROM index_state WHERE id = ?');
94
+ const delVec = h.raw.prepare('DELETE FROM doc_vec WHERE rowid = ?');
95
+ const insVec = h.raw.prepare('INSERT INTO doc_vec (rowid, embedding) VALUES (?, ?)');
96
+ let indexed = 0;
97
+ let removed = 0;
98
+ for (const row of selIndexed.all()) {
99
+ if (currentPaths.has(row.file_path))
100
+ continue;
101
+ const t = h.raw.transaction(() => {
102
+ delVec.run(BigInt(row.id));
103
+ delState.run(row.id);
104
+ });
105
+ t();
106
+ removed++;
107
+ }
108
+ for (const path of files) {
109
+ const content = readFileSync(path, 'utf8');
110
+ const hash = sha256(content);
111
+ const existing = selState.get(path);
112
+ if (existing && existing.sha256 === hash)
113
+ continue;
114
+ const vec = vecToBuffer(await embedder.embed(content));
115
+ const t = h.raw.transaction(() => {
116
+ let id;
117
+ if (existing) {
118
+ id = existing.id;
119
+ updState.run(hash, id);
120
+ delVec.run(BigInt(id));
121
+ }
122
+ else {
123
+ const info = insState.run(path, hash);
124
+ id = Number(info.lastInsertRowid);
125
+ }
126
+ // vec0's xUpdate only accepts a 64-bit INTEGER binding. Bind as BigInt.
127
+ insVec.run(BigInt(id), vec);
128
+ });
129
+ t();
130
+ indexed++;
131
+ }
132
+ return { indexed, removed };
133
+ }
134
+ /** Semantic top-k recall over reindexed markdown (the doc_vec index). */
135
+ export async function recallDocs(h, embedder, text, k = 5) {
136
+ const qv = vecToBuffer(await embedder.embed(text));
137
+ const stmt = h.raw.prepare(`SELECT s.file_path, v.distance
138
+ FROM doc_vec v
139
+ JOIN index_state s ON s.id = v.rowid
140
+ WHERE v.embedding MATCH ? AND k = ?
141
+ ORDER BY v.distance`);
142
+ const rows = stmt.all(qv, k);
143
+ return rows.map((r) => {
144
+ let preview = '';
145
+ try {
146
+ preview = readFileSync(r.file_path, 'utf8').slice(0, 200);
147
+ }
148
+ catch { /* removed since indexing */ }
149
+ return { file_path: r.file_path, distance: r.distance, preview };
150
+ });
151
+ }
152
+ /** Render a date range of memories to markdown (for debugging/export). */
153
+ export async function exportRange(h, since, until) {
154
+ const rows = await h.k('memories')
155
+ .whereBetween('ts', [`${since} 00:00:00`, `${until} 23:59:59`])
156
+ .orderBy('ts', 'asc');
157
+ if (rows.length === 0)
158
+ return `# Memories — ${since}..${until}\n\n_(no entries)_\n`;
159
+ const body = rows
160
+ .map((r) => `### ${r.ts}${r.source ? ` · ${r.source}` : ''}${r.observed ? `/${r.observed}` : ''}\n\n${r.content}\n`)
161
+ .join('\n');
162
+ return `# Memories — ${since}..${until}\n\n${body}`;
163
+ }
164
+ /** Re-embed a stored memory's content (e.g. after an embedder/model change). */
165
+ export async function reembed(h, embedder, id) {
166
+ const row = await h.k('memories').select('content').where({ id }).first();
167
+ if (!row)
168
+ return false;
169
+ const vec = vecToBuffer(await embedder.embed(row.content));
170
+ const delVec = h.raw.prepare('DELETE FROM memory_vec WHERE rowid = ?');
171
+ const insVec = h.raw.prepare('INSERT INTO memory_vec (rowid, embedding) VALUES (?, ?)');
172
+ const txn = h.raw.transaction(() => {
173
+ delVec.run(BigInt(id));
174
+ insVec.run(BigInt(id), vec);
175
+ });
176
+ txn();
177
+ return true;
178
+ }
179
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function runMigrations(k: Knex): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { openDb } from './db.js';
2
+ import { resolve } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { dirname } from 'node:path';
5
+ import * as m001 from './migrations/001_init.js';
6
+ import * as m002 from './migrations/002_fts.js';
7
+ import * as m003 from './migrations/003_approvals.js';
8
+ // Static import list — deterministic under both vitest (resolves .js → .ts) and
9
+ // the built server (dist/migrations/*.js). No knex CLI, no dynamic-import path
10
+ // fragility.
11
+ const MIGRATIONS = [
12
+ { name: '001_init', up: m001.up, down: m001.down },
13
+ { name: '002_fts', up: m002.up, down: m002.down },
14
+ { name: '003_approvals', up: m003.up, down: m003.down },
15
+ ];
16
+ export async function runMigrations(k) {
17
+ await k.raw(`CREATE TABLE IF NOT EXISTS _migration_state (
18
+ name text primary key,
19
+ applied_at text not null default (datetime('now'))
20
+ )`);
21
+ const rows = (await k.raw('SELECT name FROM _migration_state'));
22
+ const done = new Set((Array.isArray(rows) ? rows : []).map((r) => r.name));
23
+ for (const m of MIGRATIONS) {
24
+ if (done.has(m.name))
25
+ continue;
26
+ await m.up(k);
27
+ await k.raw('INSERT INTO _migration_state (name) VALUES (?)', [m.name]);
28
+ }
29
+ }
30
+ // `npm run migrate` entry point: open the DB at DB_PATH and apply migrations.
31
+ if (import.meta.url === `file://${process.argv[1]}`) {
32
+ const dbPath = process.env.DB_PATH ? resolve(process.env.DB_PATH) : resolve('memory.db');
33
+ mkdirSync(dirname(dbPath), { recursive: true });
34
+ const h = openDb(dbPath);
35
+ await runMigrations(h.k);
36
+ await h.k.destroy();
37
+ // eslint-disable-next-line no-console
38
+ console.error(`[memory] migrations applied to ${dbPath}`);
39
+ }
40
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,0BAA0B,CAAC;AACjD,OAAO,KAAK,IAAI,MAAM,yBAAyB,CAAC;AAChD,OAAO,KAAK,IAAI,MAAM,+BAA+B,CAAC;AAItD,gFAAgF;AAChF,+EAA+E;AAC/E,aAAa;AACb,MAAM,UAAU,GAAwC;IACtD,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;IAClD,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;IACjD,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;CACxD,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,CAAO;IACzC,MAAM,CAAC,CAAC,GAAG,CACT;;;OAGG,CACJ,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAA4B,CAAC;IAC3F,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3E,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/B,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACd,MAAM,CAAC,CAAC,GAAG,CAAC,gDAAgD,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACzF,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,MAAM,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACpB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(k: Knex): Promise<void>;
3
+ export declare function down(k: Knex): Promise<void>;
@@ -0,0 +1,40 @@
1
+ // Generic memory store. Stripped from the original assistant's 001_init: the journal_entries
2
+ // channel/direction/actor/approval_status columns, the `entities` table, and
3
+ // the registry_pending / registry_reminders tables (all original-assistant orchestration).
4
+ //
5
+ // The vec0 tables (memory_vec, doc_vec) are created on the raw sqlite-vec
6
+ // handle in db.ts — NOT here — because Knex's connection does not load the
7
+ // sqlite-vec extension. FTS5 (compiled into SQLite) is a migration (002_fts).
8
+ export async function up(k) {
9
+ await k.schema
10
+ .createTable('memories', (t) => {
11
+ t.increments('id').primary();
12
+ t.datetime('ts', { useTz: false }).notNullable().defaultTo(k.fn.now());
13
+ t.text('content').notNullable();
14
+ t.string('source').nullable(); // free-form provenance: owner | web | tool:foo
15
+ t.string('observed').nullable(); // free-form: stated | inferred | imported
16
+ t.string('date').nullable(); // ISO date the memory pertains to
17
+ t.text('tags').notNullable().defaultTo('[]');
18
+ t.text('meta').notNullable().defaultTo('{}');
19
+ // SUPERSEDE: links a stale row forward to the row that replaced it.
20
+ // History is never destroyed — superseded rows remain queryable.
21
+ t.integer('superseded_by').nullable();
22
+ t.index(['ts']);
23
+ t.index(['source']);
24
+ t.index(['observed']);
25
+ t.index(['date']);
26
+ t.index(['superseded_by']);
27
+ })
28
+ .createTable('index_state', (t) => {
29
+ t.increments('id').primary();
30
+ t.string('file_path').notNullable().unique();
31
+ t.string('sha256').notNullable();
32
+ t.datetime('embedded_at', { useTz: false }).notNullable().defaultTo(k.fn.now());
33
+ });
34
+ }
35
+ export async function down(k) {
36
+ await k.schema
37
+ .dropTableIfExists('index_state')
38
+ .dropTableIfExists('memories');
39
+ }
40
+ //# sourceMappingURL=001_init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"001_init.js","sourceRoot":"","sources":["../../src/migrations/001_init.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAC7F,6EAA6E;AAC7E,2FAA2F;AAC3F,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,CAAO;IAC9B,MAAM,CAAC,CAAC,MAAM;SACX,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;QAC7B,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;QACvE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QAChC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAG,+CAA+C;QAChF,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,0CAA0C;QAC3E,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAK,kCAAkC;QACnE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7C,oEAAoE;QACpE,iEAAiE;QACjE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAChB,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpB,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACtB,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC;SACD,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;QAChC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC;QAC7C,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;QACjC,CAAC,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,CAAO;IAChC,MAAM,CAAC,CAAC,MAAM;SACX,iBAAiB,CAAC,aAAa,CAAC;SAChC,iBAAiB,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(k: Knex): Promise<void>;
3
+ export declare function down(k: Knex): Promise<void>;
@@ -0,0 +1,26 @@
1
+ // SQLite-only (FTS5 is built in). The triggers keep memory_fts in sync with the
2
+ // `memories` content column. Generic rename of the original assistant's journal_fts (003_fts).
3
+ export async function up(k) {
4
+ if (k.client.config.client !== 'better-sqlite3')
5
+ return;
6
+ await k.raw(`CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(content, content='memories', content_rowid='id')`);
7
+ await k.raw(`CREATE TRIGGER IF NOT EXISTS memory_ai AFTER INSERT ON memories BEGIN
8
+ INSERT INTO memory_fts(rowid, content) VALUES (new.id, new.content);
9
+ END`);
10
+ await k.raw(`CREATE TRIGGER IF NOT EXISTS memory_ad AFTER DELETE ON memories BEGIN
11
+ INSERT INTO memory_fts(memory_fts, rowid, content) VALUES('delete', old.id, old.content);
12
+ END`);
13
+ await k.raw(`CREATE TRIGGER IF NOT EXISTS memory_au AFTER UPDATE ON memories BEGIN
14
+ INSERT INTO memory_fts(memory_fts, rowid, content) VALUES('delete', old.id, old.content);
15
+ INSERT INTO memory_fts(rowid, content) VALUES (new.id, new.content);
16
+ END`);
17
+ }
18
+ export async function down(k) {
19
+ if (k.client.config.client !== 'better-sqlite3')
20
+ return;
21
+ await k.raw('DROP TRIGGER IF EXISTS memory_au');
22
+ await k.raw('DROP TRIGGER IF EXISTS memory_ad');
23
+ await k.raw('DROP TRIGGER IF EXISTS memory_ai');
24
+ await k.raw('DROP TABLE IF EXISTS memory_fts');
25
+ }
26
+ //# sourceMappingURL=002_fts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"002_fts.js","sourceRoot":"","sources":["../../src/migrations/002_fts.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,CAAO;IAC9B,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO;IACxD,MAAM,CAAC,CAAC,GAAG,CAAC,2GAA2G,CAAC,CAAC;IACzH,MAAM,CAAC,CAAC,GAAG,CAAC;;MAER,CAAC,CAAC;IACN,MAAM,CAAC,CAAC,GAAG,CAAC;;MAER,CAAC,CAAC;IACN,MAAM,CAAC,CAAC,GAAG,CAAC;;;MAGR,CAAC,CAAC;AACR,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,CAAO;IAChC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO;IACxD,MAAM,CAAC,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,MAAM,CAAC,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,MAAM,CAAC,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,MAAM,CAAC,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(k: Knex): Promise<void>;
3
+ export declare function down(k: Knex): Promise<void>;