aidimag 1.0.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 (91) hide show
  1. package/LICENSE +102 -0
  2. package/README.md +113 -0
  3. package/dist/capture/bootstrap.d.ts +25 -0
  4. package/dist/capture/bootstrap.js +188 -0
  5. package/dist/capture/commit-miner.d.ts +59 -0
  6. package/dist/capture/commit-miner.js +381 -0
  7. package/dist/capture/harvest.d.ts +50 -0
  8. package/dist/capture/harvest.js +207 -0
  9. package/dist/capture/pr-miner.d.ts +57 -0
  10. package/dist/capture/pr-miner.js +185 -0
  11. package/dist/capture/session-briefing.d.ts +36 -0
  12. package/dist/capture/session-briefing.js +150 -0
  13. package/dist/capture/session-extraction.d.ts +23 -0
  14. package/dist/capture/session-extraction.js +54 -0
  15. package/dist/capture/triage.d.ts +30 -0
  16. package/dist/capture/triage.js +108 -0
  17. package/dist/cli/commands/capture.d.ts +5 -0
  18. package/dist/cli/commands/capture.js +357 -0
  19. package/dist/cli/commands/hosts.d.ts +5 -0
  20. package/dist/cli/commands/hosts.js +98 -0
  21. package/dist/cli/commands/knowledge.d.ts +5 -0
  22. package/dist/cli/commands/knowledge.js +121 -0
  23. package/dist/cli/commands/memory.d.ts +6 -0
  24. package/dist/cli/commands/memory.js +392 -0
  25. package/dist/cli/commands/sync.d.ts +5 -0
  26. package/dist/cli/commands/sync.js +307 -0
  27. package/dist/cli/commands/tickets.d.ts +6 -0
  28. package/dist/cli/commands/tickets.js +328 -0
  29. package/dist/cli/commands/verify.d.ts +5 -0
  30. package/dist/cli/commands/verify.js +136 -0
  31. package/dist/cli/index.d.ts +19 -0
  32. package/dist/cli/index.js +46 -0
  33. package/dist/cli/shared.d.ts +37 -0
  34. package/dist/cli/shared.js +175 -0
  35. package/dist/config.d.ts +55 -0
  36. package/dist/config.js +51 -0
  37. package/dist/context/generate.d.ts +24 -0
  38. package/dist/context/generate.js +115 -0
  39. package/dist/critique/critique.d.ts +40 -0
  40. package/dist/critique/critique.js +110 -0
  41. package/dist/db/schema.d.ts +28 -0
  42. package/dist/db/schema.js +269 -0
  43. package/dist/db/store.d.ts +182 -0
  44. package/dist/db/store.js +906 -0
  45. package/dist/debug.d.ts +14 -0
  46. package/dist/debug.js +25 -0
  47. package/dist/embeddings/provider.d.ts +16 -0
  48. package/dist/embeddings/provider.js +100 -0
  49. package/dist/embeddings/search.d.ts +22 -0
  50. package/dist/embeddings/search.js +95 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.js +12 -0
  53. package/dist/knowledge/chunk.d.ts +9 -0
  54. package/dist/knowledge/chunk.js +78 -0
  55. package/dist/knowledge/extract.d.ts +34 -0
  56. package/dist/knowledge/extract.js +113 -0
  57. package/dist/knowledge/ingest.d.ts +79 -0
  58. package/dist/knowledge/ingest.js +305 -0
  59. package/dist/knowledge/llm.d.ts +21 -0
  60. package/dist/knowledge/llm.js +110 -0
  61. package/dist/mcp/server.d.ts +11 -0
  62. package/dist/mcp/server.js +532 -0
  63. package/dist/security/evidence.d.ts +11 -0
  64. package/dist/security/evidence.js +15 -0
  65. package/dist/security/seal.d.ts +3 -0
  66. package/dist/security/seal.js +28 -0
  67. package/dist/security/sync-push.d.ts +2 -0
  68. package/dist/security/sync-push.js +37 -0
  69. package/dist/security/url.d.ts +6 -0
  70. package/dist/security/url.js +67 -0
  71. package/dist/sync/client.d.ts +84 -0
  72. package/dist/sync/client.js +391 -0
  73. package/dist/sync/server.d.ts +75 -0
  74. package/dist/sync/server.js +659 -0
  75. package/dist/tickets/provider.d.ts +80 -0
  76. package/dist/tickets/provider.js +375 -0
  77. package/dist/types.d.ts +133 -0
  78. package/dist/types.js +5 -0
  79. package/dist/ui/page.d.ts +5 -0
  80. package/dist/ui/page.js +841 -0
  81. package/dist/ui/server.d.ts +10 -0
  82. package/dist/ui/server.js +437 -0
  83. package/dist/verify/check.d.ts +38 -0
  84. package/dist/verify/check.js +121 -0
  85. package/dist/verify/engine.d.ts +39 -0
  86. package/dist/verify/engine.js +178 -0
  87. package/dist/verify/hooks.d.ts +24 -0
  88. package/dist/verify/hooks.js +125 -0
  89. package/dist/verify/runners.d.ts +26 -0
  90. package/dist/verify/runners.js +193 -0
  91. package/package.json +78 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * memory_critique (KARPATHY_LAYERS Feature 3) — Verifier layer.
3
+ *
4
+ * The "second AI critic" Karpathy recommends, but grounded in *verified project
5
+ * memory* rather than another model's opinions. Given a summary of what an agent
6
+ * did (and the files it touched), it surfaces:
7
+ * - guardrail violations (kind=GUARDRAIL, level never/ask-first)
8
+ * - confirmations (memory the work is consistent with)
9
+ * - contradictions (in-scope rules the summary doesn't appear to honor)
10
+ * - missing coverage (changed files with no memory → ask the user)
11
+ *
12
+ * Matching is heuristic (keyword overlap + scope) layered on real retrieval —
13
+ * deliberately conservative: it raises questions, the agent decides.
14
+ */
15
+ import type { MemoryStore } from "../db/store.js";
16
+ import type { GuardrailLevel, MemoryEntry } from "../types.js";
17
+ export interface CritiqueInput {
18
+ summary: string;
19
+ filesChanged: string[];
20
+ }
21
+ export interface CritiqueResult {
22
+ guardrailsViolated: Array<{
23
+ memory: MemoryEntry;
24
+ level: GuardrailLevel;
25
+ concern: string;
26
+ }>;
27
+ contradictions: Array<{
28
+ memory: MemoryEntry;
29
+ concern: string;
30
+ }>;
31
+ confirmations: Array<{
32
+ memory: MemoryEntry;
33
+ }>;
34
+ missingCoverage: string[];
35
+ }
36
+ /**
37
+ * Critique a unit of work against project memory. Async because it folds in
38
+ * semantic recall when embeddings are configured (degrades to keyword-only).
39
+ */
40
+ export declare function critique(store: MemoryStore, input: CritiqueInput): Promise<CritiqueResult>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * memory_critique (KARPATHY_LAYERS Feature 3) — Verifier layer.
3
+ *
4
+ * The "second AI critic" Karpathy recommends, but grounded in *verified project
5
+ * memory* rather than another model's opinions. Given a summary of what an agent
6
+ * did (and the files it touched), it surfaces:
7
+ * - guardrail violations (kind=GUARDRAIL, level never/ask-first)
8
+ * - confirmations (memory the work is consistent with)
9
+ * - contradictions (in-scope rules the summary doesn't appear to honor)
10
+ * - missing coverage (changed files with no memory → ask the user)
11
+ *
12
+ * Matching is heuristic (keyword overlap + scope) layered on real retrieval —
13
+ * deliberately conservative: it raises questions, the agent decides.
14
+ */
15
+ import { hybridSearch } from "../embeddings/search.js";
16
+ const STOP = new Set([
17
+ "the", "a", "an", "and", "or", "but", "to", "of", "in", "on", "for", "with", "is", "are",
18
+ "be", "this", "that", "it", "as", "at", "by", "from", "we", "i", "you", "should", "will",
19
+ "add", "added", "adds", "use", "used", "uses", "change", "changed", "update", "updated",
20
+ ]);
21
+ function tokenize(text) {
22
+ return new Set(text
23
+ .toLowerCase()
24
+ .split(/[^a-z0-9_]+/)
25
+ .filter((t) => t.length > 2 && !STOP.has(t)));
26
+ }
27
+ /** Jaccard overlap between two token sets (0..1). */
28
+ function overlap(a, b) {
29
+ if (a.size === 0 || b.size === 0)
30
+ return 0;
31
+ let inter = 0;
32
+ for (const t of a)
33
+ if (b.has(t))
34
+ inter++;
35
+ return inter / (a.size + b.size - inter);
36
+ }
37
+ function scopesFile(m, files) {
38
+ if (m.scope.paths.length === 0)
39
+ return false; // repo-wide → not "specific to" a file
40
+ return m.scope.paths.some((sp) => files.some((f) => f.startsWith(sp) || sp.startsWith(f)));
41
+ }
42
+ const CONFIRM_THRESHOLD = 0.1;
43
+ const GUARDRAIL_THRESHOLD = 0.06; // guardrails surface on weaker signal — better safe
44
+ /**
45
+ * Critique a unit of work against project memory. Async because it folds in
46
+ * semantic recall when embeddings are configured (degrades to keyword-only).
47
+ */
48
+ export async function critique(store, input) {
49
+ const summaryTokens = tokenize(input.summary);
50
+ const files = input.filesChanged ?? [];
51
+ // 1) memories scoped to the touched files
52
+ const scoped = files.length ? store.getForFiles(files, 50) : [];
53
+ // 2) semantically/keyword-related memories beyond scope
54
+ const { results: related } = await hybridSearch(store, { query: input.summary, limit: 20 }).catch(() => ({
55
+ results: [],
56
+ semantic: false,
57
+ }));
58
+ // 3) every guardrail, project-wide
59
+ const guardrails = store.list(10_000).filter((m) => m.kind === "GUARDRAIL" && m.status !== "REFUTED");
60
+ const pool = new Map();
61
+ for (const m of [...scoped, ...related]) {
62
+ if (m.status === "REFUTED" || m.kind === "GUARDRAIL")
63
+ continue;
64
+ pool.set(m.id, m);
65
+ }
66
+ const result = {
67
+ guardrailsViolated: [],
68
+ contradictions: [],
69
+ confirmations: [],
70
+ missingCoverage: [],
71
+ };
72
+ for (const g of guardrails) {
73
+ const score = overlap(summaryTokens, tokenize(g.claim));
74
+ const inScope = scopesFile(g, files);
75
+ if ((score >= GUARDRAIL_THRESHOLD || inScope) && g.guardrailLevel !== "always") {
76
+ const level = g.guardrailLevel ?? "ask-first";
77
+ result.guardrailsViolated.push({
78
+ memory: g,
79
+ level,
80
+ concern: level === "never"
81
+ ? "Your change appears to touch a NEVER guardrail. Refuse or justify explicitly before proceeding."
82
+ : "An ASK-FIRST guardrail covers this area. Confirm with the user before continuing.",
83
+ });
84
+ }
85
+ }
86
+ for (const m of pool.values()) {
87
+ const score = overlap(summaryTokens, tokenize(m.claim));
88
+ const inScope = scopesFile(m, files);
89
+ if (score >= CONFIRM_THRESHOLD) {
90
+ result.confirmations.push({ memory: m });
91
+ }
92
+ else if (inScope && (m.kind === "INVARIANT" || m.kind === "CONVENTION" || m.kind === "DECISION")) {
93
+ // in scope but the summary doesn't mention honoring it → worth a look
94
+ result.contradictions.push({
95
+ memory: m,
96
+ concern: `This ${m.kind} governs ${m.scope.paths.join(", ")} which you changed, but your summary doesn't confirm it still holds. Re-check it.`,
97
+ });
98
+ }
99
+ }
100
+ // changed files with no scoped memory at all → suggest asking the user
101
+ const covered = new Set();
102
+ for (const m of scoped)
103
+ for (const sp of m.scope.paths)
104
+ for (const f of files)
105
+ if (f.startsWith(sp) || sp.startsWith(f))
106
+ covered.add(f);
107
+ result.missingCoverage = files.filter((f) => !covered.has(f));
108
+ return result;
109
+ }
110
+ //# sourceMappingURL=critique.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * SQLite schema for aidimag. Lives in <repo>/.aidimag/memory.db
3
+ * FTS5 powers Phase 1 search; sqlite-vec embeddings arrive in Phase 2.
4
+ */
5
+ export declare const SCHEMA_VERSION = 9;
6
+ /** Idempotent migrations for pre-existing DBs (failures = already applied). */
7
+ export declare const MIGRATIONS: string[];
8
+ /**
9
+ * v6 guarded migration: SQLite can't ALTER a CHECK constraint, so adding the
10
+ * TICKET_REF evidence type requires rebuilding the evidence table. Runs once,
11
+ * gated on the stored schema_version (store.ts).
12
+ */
13
+ export declare const EVIDENCE_REBUILD_V6 = "\nALTER TABLE evidence RENAME TO evidence_v5;\nCREATE TABLE evidence (\n id TEXT PRIMARY KEY,\n memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n type TEXT NOT NULL CHECK (type IN (\n 'COMMIT_REF','TEST_RESULT','EXEC_TRACE','STATIC_CHECK','HUMAN_ATTESTED','TICKET_REF')),\n payload TEXT NOT NULL,\n last_run TEXT,\n result TEXT NOT NULL DEFAULT 'UNKNOWN' CHECK (result IN ('PASS','FAIL','UNKNOWN'))\n);\nINSERT INTO evidence SELECT * FROM evidence_v5;\nDROP TABLE evidence_v5;\nCREATE INDEX IF NOT EXISTS idx_evidence_memory ON evidence(memory_id);\n";
14
+ /**
15
+ * v8 guarded rebuild: SQLite can't ALTER a CHECK constraint, so adding the
16
+ * GUARDRAIL + SKILL memory kinds requires rebuilding the memories and proposals
17
+ * tables. Rowids are preserved (FTS is a contentless mirror keyed on rowid) and
18
+ * the FTS index is rebuilt afterwards. Runs once, gated on the stored
19
+ * schema_version (store.ts), with foreign_keys disabled around it.
20
+ */
21
+ export declare const MEMORIES_REBUILD_V8 = "\nCREATE TABLE memories_v8 (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL CHECK (kind IN (\n 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',\n 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),\n claim TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.5,\n status TEXT NOT NULL DEFAULT 'UNVERIFIED' CHECK (status IN (\n 'VERIFIED','UNVERIFIED','STALE','REFUTED')),\n created_by TEXT NOT NULL DEFAULT 'human',\n created_at TEXT NOT NULL,\n verified_at TEXT,\n superseded_by TEXT REFERENCES memories(id),\n updated_at TEXT,\n pinned INTEGER NOT NULL DEFAULT 0,\n guardrail_level TEXT CHECK (guardrail_level IN ('always','ask-first','never'))\n);\nINSERT INTO memories_v8 (rowid, id, kind, claim, confidence, status, created_by,\n created_at, verified_at, superseded_by, updated_at, pinned, guardrail_level)\n SELECT rowid, id, kind, claim, confidence, status, created_by,\n created_at, verified_at, superseded_by, updated_at, pinned, guardrail_level FROM memories;\nDROP TABLE memories;\nALTER TABLE memories_v8 RENAME TO memories;\nINSERT INTO memories_fts(memories_fts) VALUES('rebuild');\n";
22
+ export declare const PROPOSALS_REBUILD_V8 = "\nCREATE TABLE proposals_v8 (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL CHECK (kind IN (\n 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',\n 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),\n claim TEXT NOT NULL,\n paths TEXT NOT NULL DEFAULT '[]',\n symbols TEXT NOT NULL DEFAULT '[]',\n evidence TEXT NOT NULL DEFAULT '[]',\n source TEXT NOT NULL,\n source_ref TEXT,\n rationale TEXT,\n created_at TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','APPROVED','REJECTED')),\n memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,\n updated_at TEXT,\n ticket_ref TEXT,\n guardrail_level TEXT CHECK (guardrail_level IN ('always','ask-first','never'))\n);\nINSERT INTO proposals_v8 (id, kind, claim, paths, symbols, evidence, source, source_ref,\n rationale, created_at, status, memory_id, updated_at, ticket_ref, guardrail_level)\n SELECT id, kind, claim, paths, symbols, evidence, source, source_ref,\n rationale, created_at, status, memory_id, updated_at, ticket_ref, guardrail_level FROM proposals;\nDROP TABLE proposals;\nALTER TABLE proposals_v8 RENAME TO proposals;\n";
23
+ /**
24
+ * v9 guarded rebuild: Add new event types (updated, evidence_added, evidence_removed)
25
+ * to the events table CHECK constraint. SQLite can't ALTER a CHECK constraint.
26
+ */
27
+ export declare const EVENTS_REBUILD_V9 = "\nALTER TABLE events RENAME TO events_v8;\nCREATE TABLE events (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n type TEXT NOT NULL CHECK (type IN (\n 'memory_created','status_changed','evidence_result',\n 'refuted','superseded','forgotten',\n 'proposal_created','proposal_approved','proposal_rejected',\n 'verification_report','updated','evidence_added','evidence_removed')),\n memory_id TEXT,\n payload TEXT NOT NULL DEFAULT '{}',\n machine TEXT NOT NULL,\n schema_version INTEGER NOT NULL,\n created_at TEXT NOT NULL,\n synced INTEGER NOT NULL DEFAULT 0\n);\nINSERT INTO events SELECT * FROM events_v8;\nDROP TABLE events_v8;\nCREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced, seq);\n";
28
+ export declare const SCHEMA_SQL = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS memories (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL CHECK (kind IN (\n 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',\n 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),\n claim TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.5,\n status TEXT NOT NULL DEFAULT 'UNVERIFIED' CHECK (status IN (\n 'VERIFIED','UNVERIFIED','STALE','REFUTED')),\n created_by TEXT NOT NULL DEFAULT 'human',\n created_at TEXT NOT NULL,\n verified_at TEXT,\n superseded_by TEXT REFERENCES memories(id),\n updated_at TEXT,\n pinned INTEGER NOT NULL DEFAULT 0,\n guardrail_level TEXT CHECK (guardrail_level IN ('always','ask-first','never'))\n);\n\n-- scope: one row per path / symbol a memory applies to\nCREATE TABLE IF NOT EXISTS memory_scopes (\n memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n scope_type TEXT NOT NULL CHECK (scope_type IN ('path','symbol')),\n value TEXT NOT NULL,\n PRIMARY KEY (memory_id, scope_type, value)\n);\n\nCREATE TABLE IF NOT EXISTS evidence (\n id TEXT PRIMARY KEY,\n memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n type TEXT NOT NULL CHECK (type IN (\n 'COMMIT_REF','TEST_RESULT','EXEC_TRACE','STATIC_CHECK','HUMAN_ATTESTED','TICKET_REF')),\n payload TEXT NOT NULL,\n last_run TEXT,\n result TEXT NOT NULL DEFAULT 'UNKNOWN' CHECK (result IN ('PASS','FAIL','UNKNOWN'))\n);\n\nCREATE TABLE IF NOT EXISTS memory_links (\n from_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n to_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,\n relation TEXT NOT NULL CHECK (relation IN ('supports','contradicts','refines')),\n PRIMARY KEY (from_id, to_id, relation)\n);\n\n-- Full-text search over claims (Phase 1 retrieval)\nCREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(\n claim,\n content='memories',\n content_rowid='rowid'\n);\n\nCREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN\n INSERT INTO memories_fts(rowid, claim) VALUES (new.rowid, new.claim);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, claim) VALUES ('delete', old.rowid, old.claim);\nEND;\n\nCREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE OF claim ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, claim) VALUES ('delete', old.rowid, old.claim);\n INSERT INTO memories_fts(rowid, claim) VALUES (new.rowid, new.claim);\nEND;\n\n-- Phase 2: capture pipeline \u2014 proposed memories awaiting human review\nCREATE TABLE IF NOT EXISTS proposals (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL CHECK (kind IN (\n 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',\n 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),\n claim TEXT NOT NULL,\n paths TEXT NOT NULL DEFAULT '[]', -- JSON string[]\n symbols TEXT NOT NULL DEFAULT '[]', -- JSON string[]\n evidence TEXT NOT NULL DEFAULT '[]', -- JSON {type,payload}[]\n source TEXT NOT NULL, -- 'commit-miner' | 'session:<agent-id>' | ...\n source_ref TEXT, -- e.g. commit sha\n rationale TEXT, -- why the source thinks this is worth remembering\n created_at TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','APPROVED','REJECTED')),\n memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL, -- set when approved\n updated_at TEXT,\n ticket_ref TEXT, -- ticket id (e.g. XXX-2100) when known\n guardrail_level TEXT -- always | ask-first | never (GUARDRAIL proposals)\n);\n\nCREATE INDEX IF NOT EXISTS idx_proposals_status ON proposals(status);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_proposals_dedupe ON proposals(source, source_ref, claim);\n\n-- Phase 6: sync \u2014 deletions must propagate, so deletes leave tombstones\nCREATE TABLE IF NOT EXISTS tombstones (\n id TEXT NOT NULL, -- deleted row id\n tbl TEXT NOT NULL CHECK (tbl IN ('memories','proposals')),\n deleted_at TEXT NOT NULL,\n PRIMARY KEY (id, tbl)\n);\n\nCREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);\nCREATE INDEX IF NOT EXISTS idx_memories_kind ON memories(kind);\nCREATE INDEX IF NOT EXISTS idx_scopes_value ON memory_scopes(value);\nCREATE INDEX IF NOT EXISTS idx_evidence_memory ON evidence(memory_id);\n\n-- Passive capture: every MCP memory_search is logged locally. Zero-hit\n-- queries are coverage gaps \u2014 things agents needed but memory couldn't answer.\n-- Surfaced via `dim gaps` and the session briefing; never synced.\nCREATE TABLE IF NOT EXISTS search_log (\n id TEXT PRIMARY KEY,\n query TEXT NOT NULL,\n paths TEXT NOT NULL DEFAULT '[]', -- JSON string[] (scope filter, if any)\n result_count INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'mcp', -- 'mcp' | 'cli'\n created_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_search_log_created ON search_log(created_at);\nCREATE INDEX IF NOT EXISTS idx_search_log_hits ON search_log(result_count, created_at);\n\n-- SaaS groundwork: local append-only event log (CLOUD_DESIGN sync model).\n-- Every memory-lifecycle change is recorded here and shipped to the sync\n-- server on `dim sync`; the server aggregates evidence_result events from\n-- multiple machines into consensus confidence.\nCREATE TABLE IF NOT EXISTS events (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE, -- uuid (idempotent server ingest)\n type TEXT NOT NULL CHECK (type IN (\n 'memory_created','status_changed','evidence_result',\n 'refuted','superseded','forgotten',\n 'proposal_created','proposal_approved','proposal_rejected',\n 'verification_report')),\n memory_id TEXT, -- subject memory/proposal id\n payload TEXT NOT NULL DEFAULT '{}', -- JSON event body\n machine TEXT NOT NULL, -- stable per-machine id\n schema_version INTEGER NOT NULL,\n created_at TEXT NOT NULL,\n synced INTEGER NOT NULL DEFAULT 0 -- 1 once pushed to the server\n);\nCREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced, seq);\n";
@@ -0,0 +1,269 @@
1
+ /**
2
+ * SQLite schema for aidimag. Lives in <repo>/.aidimag/memory.db
3
+ * FTS5 powers Phase 1 search; sqlite-vec embeddings arrive in Phase 2.
4
+ */
5
+ export const SCHEMA_VERSION = 9;
6
+ /** Idempotent migrations for pre-existing DBs (failures = already applied). */
7
+ export const MIGRATIONS = [
8
+ "ALTER TABLE memories ADD COLUMN updated_at TEXT",
9
+ "ALTER TABLE proposals ADD COLUMN updated_at TEXT",
10
+ // T1 tickets: ticket id extracted from branch/commit message (offline)
11
+ "ALTER TABLE proposals ADD COLUMN ticket_ref TEXT",
12
+ // v7: pinned memories — exempt from time decay, still falsifiable by evidence
13
+ "ALTER TABLE memories ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0",
14
+ // v8: GUARDRAIL enforcement level (always | ask-first | never). The new kind
15
+ // values themselves need a CHECK rebuild (MEMORIES_REBUILD_V8), but the
16
+ // column adds cleanly on old DBs.
17
+ "ALTER TABLE memories ADD COLUMN guardrail_level TEXT",
18
+ "ALTER TABLE proposals ADD COLUMN guardrail_level TEXT",
19
+ ];
20
+ /**
21
+ * v6 guarded migration: SQLite can't ALTER a CHECK constraint, so adding the
22
+ * TICKET_REF evidence type requires rebuilding the evidence table. Runs once,
23
+ * gated on the stored schema_version (store.ts).
24
+ */
25
+ export const EVIDENCE_REBUILD_V6 = `
26
+ ALTER TABLE evidence RENAME TO evidence_v5;
27
+ CREATE TABLE evidence (
28
+ id TEXT PRIMARY KEY,
29
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
30
+ type TEXT NOT NULL CHECK (type IN (
31
+ 'COMMIT_REF','TEST_RESULT','EXEC_TRACE','STATIC_CHECK','HUMAN_ATTESTED','TICKET_REF')),
32
+ payload TEXT NOT NULL,
33
+ last_run TEXT,
34
+ result TEXT NOT NULL DEFAULT 'UNKNOWN' CHECK (result IN ('PASS','FAIL','UNKNOWN'))
35
+ );
36
+ INSERT INTO evidence SELECT * FROM evidence_v5;
37
+ DROP TABLE evidence_v5;
38
+ CREATE INDEX IF NOT EXISTS idx_evidence_memory ON evidence(memory_id);
39
+ `;
40
+ /**
41
+ * v8 guarded rebuild: SQLite can't ALTER a CHECK constraint, so adding the
42
+ * GUARDRAIL + SKILL memory kinds requires rebuilding the memories and proposals
43
+ * tables. Rowids are preserved (FTS is a contentless mirror keyed on rowid) and
44
+ * the FTS index is rebuilt afterwards. Runs once, gated on the stored
45
+ * schema_version (store.ts), with foreign_keys disabled around it.
46
+ */
47
+ export const MEMORIES_REBUILD_V8 = `
48
+ CREATE TABLE memories_v8 (
49
+ id TEXT PRIMARY KEY,
50
+ kind TEXT NOT NULL CHECK (kind IN (
51
+ 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',
52
+ 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),
53
+ claim TEXT NOT NULL,
54
+ confidence REAL NOT NULL DEFAULT 0.5,
55
+ status TEXT NOT NULL DEFAULT 'UNVERIFIED' CHECK (status IN (
56
+ 'VERIFIED','UNVERIFIED','STALE','REFUTED')),
57
+ created_by TEXT NOT NULL DEFAULT 'human',
58
+ created_at TEXT NOT NULL,
59
+ verified_at TEXT,
60
+ superseded_by TEXT REFERENCES memories(id),
61
+ updated_at TEXT,
62
+ pinned INTEGER NOT NULL DEFAULT 0,
63
+ guardrail_level TEXT CHECK (guardrail_level IN ('always','ask-first','never'))
64
+ );
65
+ INSERT INTO memories_v8 (rowid, id, kind, claim, confidence, status, created_by,
66
+ created_at, verified_at, superseded_by, updated_at, pinned, guardrail_level)
67
+ SELECT rowid, id, kind, claim, confidence, status, created_by,
68
+ created_at, verified_at, superseded_by, updated_at, pinned, guardrail_level FROM memories;
69
+ DROP TABLE memories;
70
+ ALTER TABLE memories_v8 RENAME TO memories;
71
+ INSERT INTO memories_fts(memories_fts) VALUES('rebuild');
72
+ `;
73
+ export const PROPOSALS_REBUILD_V8 = `
74
+ CREATE TABLE proposals_v8 (
75
+ id TEXT PRIMARY KEY,
76
+ kind TEXT NOT NULL CHECK (kind IN (
77
+ 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',
78
+ 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),
79
+ claim TEXT NOT NULL,
80
+ paths TEXT NOT NULL DEFAULT '[]',
81
+ symbols TEXT NOT NULL DEFAULT '[]',
82
+ evidence TEXT NOT NULL DEFAULT '[]',
83
+ source TEXT NOT NULL,
84
+ source_ref TEXT,
85
+ rationale TEXT,
86
+ created_at TEXT NOT NULL,
87
+ status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','APPROVED','REJECTED')),
88
+ memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
89
+ updated_at TEXT,
90
+ ticket_ref TEXT,
91
+ guardrail_level TEXT CHECK (guardrail_level IN ('always','ask-first','never'))
92
+ );
93
+ INSERT INTO proposals_v8 (id, kind, claim, paths, symbols, evidence, source, source_ref,
94
+ rationale, created_at, status, memory_id, updated_at, ticket_ref, guardrail_level)
95
+ SELECT id, kind, claim, paths, symbols, evidence, source, source_ref,
96
+ rationale, created_at, status, memory_id, updated_at, ticket_ref, guardrail_level FROM proposals;
97
+ DROP TABLE proposals;
98
+ ALTER TABLE proposals_v8 RENAME TO proposals;
99
+ `;
100
+ /**
101
+ * v9 guarded rebuild: Add new event types (updated, evidence_added, evidence_removed)
102
+ * to the events table CHECK constraint. SQLite can't ALTER a CHECK constraint.
103
+ */
104
+ export const EVENTS_REBUILD_V9 = `
105
+ ALTER TABLE events RENAME TO events_v8;
106
+ CREATE TABLE events (
107
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
108
+ id TEXT NOT NULL UNIQUE,
109
+ type TEXT NOT NULL CHECK (type IN (
110
+ 'memory_created','status_changed','evidence_result',
111
+ 'refuted','superseded','forgotten',
112
+ 'proposal_created','proposal_approved','proposal_rejected',
113
+ 'verification_report','updated','evidence_added','evidence_removed')),
114
+ memory_id TEXT,
115
+ payload TEXT NOT NULL DEFAULT '{}',
116
+ machine TEXT NOT NULL,
117
+ schema_version INTEGER NOT NULL,
118
+ created_at TEXT NOT NULL,
119
+ synced INTEGER NOT NULL DEFAULT 0
120
+ );
121
+ INSERT INTO events SELECT * FROM events_v8;
122
+ DROP TABLE events_v8;
123
+ CREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced, seq);
124
+ `;
125
+ export const SCHEMA_SQL = `
126
+ PRAGMA journal_mode = WAL;
127
+ PRAGMA foreign_keys = ON;
128
+
129
+ CREATE TABLE IF NOT EXISTS meta (
130
+ key TEXT PRIMARY KEY,
131
+ value TEXT NOT NULL
132
+ );
133
+
134
+ CREATE TABLE IF NOT EXISTS memories (
135
+ id TEXT PRIMARY KEY,
136
+ kind TEXT NOT NULL CHECK (kind IN (
137
+ 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',
138
+ 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),
139
+ claim TEXT NOT NULL,
140
+ confidence REAL NOT NULL DEFAULT 0.5,
141
+ status TEXT NOT NULL DEFAULT 'UNVERIFIED' CHECK (status IN (
142
+ 'VERIFIED','UNVERIFIED','STALE','REFUTED')),
143
+ created_by TEXT NOT NULL DEFAULT 'human',
144
+ created_at TEXT NOT NULL,
145
+ verified_at TEXT,
146
+ superseded_by TEXT REFERENCES memories(id),
147
+ updated_at TEXT,
148
+ pinned INTEGER NOT NULL DEFAULT 0,
149
+ guardrail_level TEXT CHECK (guardrail_level IN ('always','ask-first','never'))
150
+ );
151
+
152
+ -- scope: one row per path / symbol a memory applies to
153
+ CREATE TABLE IF NOT EXISTS memory_scopes (
154
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
155
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('path','symbol')),
156
+ value TEXT NOT NULL,
157
+ PRIMARY KEY (memory_id, scope_type, value)
158
+ );
159
+
160
+ CREATE TABLE IF NOT EXISTS evidence (
161
+ id TEXT PRIMARY KEY,
162
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
163
+ type TEXT NOT NULL CHECK (type IN (
164
+ 'COMMIT_REF','TEST_RESULT','EXEC_TRACE','STATIC_CHECK','HUMAN_ATTESTED','TICKET_REF')),
165
+ payload TEXT NOT NULL,
166
+ last_run TEXT,
167
+ result TEXT NOT NULL DEFAULT 'UNKNOWN' CHECK (result IN ('PASS','FAIL','UNKNOWN'))
168
+ );
169
+
170
+ CREATE TABLE IF NOT EXISTS memory_links (
171
+ from_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
172
+ to_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
173
+ relation TEXT NOT NULL CHECK (relation IN ('supports','contradicts','refines')),
174
+ PRIMARY KEY (from_id, to_id, relation)
175
+ );
176
+
177
+ -- Full-text search over claims (Phase 1 retrieval)
178
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
179
+ claim,
180
+ content='memories',
181
+ content_rowid='rowid'
182
+ );
183
+
184
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
185
+ INSERT INTO memories_fts(rowid, claim) VALUES (new.rowid, new.claim);
186
+ END;
187
+
188
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
189
+ INSERT INTO memories_fts(memories_fts, rowid, claim) VALUES ('delete', old.rowid, old.claim);
190
+ END;
191
+
192
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE OF claim ON memories BEGIN
193
+ INSERT INTO memories_fts(memories_fts, rowid, claim) VALUES ('delete', old.rowid, old.claim);
194
+ INSERT INTO memories_fts(rowid, claim) VALUES (new.rowid, new.claim);
195
+ END;
196
+
197
+ -- Phase 2: capture pipeline — proposed memories awaiting human review
198
+ CREATE TABLE IF NOT EXISTS proposals (
199
+ id TEXT PRIMARY KEY,
200
+ kind TEXT NOT NULL CHECK (kind IN (
201
+ 'DECISION','CONVENTION','GOTCHA','FAILED_APPROACH',
202
+ 'ARCHITECTURE','INVARIANT','TODO_CONTEXT','GUARDRAIL','SKILL')),
203
+ claim TEXT NOT NULL,
204
+ paths TEXT NOT NULL DEFAULT '[]', -- JSON string[]
205
+ symbols TEXT NOT NULL DEFAULT '[]', -- JSON string[]
206
+ evidence TEXT NOT NULL DEFAULT '[]', -- JSON {type,payload}[]
207
+ source TEXT NOT NULL, -- 'commit-miner' | 'session:<agent-id>' | ...
208
+ source_ref TEXT, -- e.g. commit sha
209
+ rationale TEXT, -- why the source thinks this is worth remembering
210
+ created_at TEXT NOT NULL,
211
+ status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','APPROVED','REJECTED')),
212
+ memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL, -- set when approved
213
+ updated_at TEXT,
214
+ ticket_ref TEXT, -- ticket id (e.g. XXX-2100) when known
215
+ guardrail_level TEXT -- always | ask-first | never (GUARDRAIL proposals)
216
+ );
217
+
218
+ CREATE INDEX IF NOT EXISTS idx_proposals_status ON proposals(status);
219
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_proposals_dedupe ON proposals(source, source_ref, claim);
220
+
221
+ -- Phase 6: sync — deletions must propagate, so deletes leave tombstones
222
+ CREATE TABLE IF NOT EXISTS tombstones (
223
+ id TEXT NOT NULL, -- deleted row id
224
+ tbl TEXT NOT NULL CHECK (tbl IN ('memories','proposals')),
225
+ deleted_at TEXT NOT NULL,
226
+ PRIMARY KEY (id, tbl)
227
+ );
228
+
229
+ CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
230
+ CREATE INDEX IF NOT EXISTS idx_memories_kind ON memories(kind);
231
+ CREATE INDEX IF NOT EXISTS idx_scopes_value ON memory_scopes(value);
232
+ CREATE INDEX IF NOT EXISTS idx_evidence_memory ON evidence(memory_id);
233
+
234
+ -- Passive capture: every MCP memory_search is logged locally. Zero-hit
235
+ -- queries are coverage gaps — things agents needed but memory couldn't answer.
236
+ -- Surfaced via \`dim gaps\` and the session briefing; never synced.
237
+ CREATE TABLE IF NOT EXISTS search_log (
238
+ id TEXT PRIMARY KEY,
239
+ query TEXT NOT NULL,
240
+ paths TEXT NOT NULL DEFAULT '[]', -- JSON string[] (scope filter, if any)
241
+ result_count INTEGER NOT NULL,
242
+ source TEXT NOT NULL DEFAULT 'mcp', -- 'mcp' | 'cli'
243
+ created_at TEXT NOT NULL
244
+ );
245
+ CREATE INDEX IF NOT EXISTS idx_search_log_created ON search_log(created_at);
246
+ CREATE INDEX IF NOT EXISTS idx_search_log_hits ON search_log(result_count, created_at);
247
+
248
+ -- SaaS groundwork: local append-only event log (CLOUD_DESIGN sync model).
249
+ -- Every memory-lifecycle change is recorded here and shipped to the sync
250
+ -- server on \`dim sync\`; the server aggregates evidence_result events from
251
+ -- multiple machines into consensus confidence.
252
+ CREATE TABLE IF NOT EXISTS events (
253
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
254
+ id TEXT NOT NULL UNIQUE, -- uuid (idempotent server ingest)
255
+ type TEXT NOT NULL CHECK (type IN (
256
+ 'memory_created','status_changed','evidence_result',
257
+ 'refuted','superseded','forgotten',
258
+ 'proposal_created','proposal_approved','proposal_rejected',
259
+ 'verification_report')),
260
+ memory_id TEXT, -- subject memory/proposal id
261
+ payload TEXT NOT NULL DEFAULT '{}', -- JSON event body
262
+ machine TEXT NOT NULL, -- stable per-machine id
263
+ schema_version INTEGER NOT NULL,
264
+ created_at TEXT NOT NULL,
265
+ synced INTEGER NOT NULL DEFAULT 0 -- 1 once pushed to the server
266
+ );
267
+ CREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced, seq);
268
+ `;
269
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,182 @@
1
+ /**
2
+ * MemoryStore — all persistence for aidimag.
3
+ * Local-first: a single SQLite file at <repo>/.aidimag/memory.db
4
+ */
5
+ import type { Evidence, EvidenceType, GuardrailLevel, MemoryEntry, MemoryKind, MemoryLink, MemorySearchOptions, MemoryStatus, MemoryStatusSummary, MemoryWriteInput, Proposal, ProposalInput, ProposalStatus } from "../types.js";
6
+ export declare const AIDIMAG_DIR = ".aidimag";
7
+ export declare const DB_FILE = "memory.db";
8
+ /** Walk up from `start` looking for an existing .aidimag dir (or .git as repo root). */
9
+ export declare function findRepoRoot(start?: string): string | null;
10
+ export declare function dbPathFor(repoRoot: string): string;
11
+ /**
12
+ * Stable per-machine id (CLOUD_DESIGN: "3 of 4 machines confirm…").
13
+ * Persisted once in ~/.aidimag/machine-id; hostname-prefixed for readability.
14
+ */
15
+ export declare function machineId(): string;
16
+ /** Event types shipped to the sync server (see schema.ts events table). */
17
+ export type MemoryEventType = "memory_created" | "status_changed" | "evidence_result" | "refuted" | "superseded" | "forgotten" | "proposal_created" | "proposal_approved" | "proposal_rejected" | "verification_report" | "updated" | "evidence_added" | "evidence_removed";
18
+ export interface MemoryEvent {
19
+ seq: number;
20
+ id: string;
21
+ type: MemoryEventType;
22
+ memoryId: string | null;
23
+ payload: Record<string, unknown>;
24
+ machine: string;
25
+ schemaVersion: number;
26
+ createdAt: string;
27
+ }
28
+ export declare class MemoryStore {
29
+ private db;
30
+ readonly dbPath: string;
31
+ /** true if the sqlite-vec extension loaded (vector search available) */
32
+ readonly vecAvailable: boolean;
33
+ private readonly machine;
34
+ constructor(dbPath: string);
35
+ /** Open the store for the repo containing `cwd`. Throws if not initialized and create=false. */
36
+ static open(cwd?: string, opts?: {
37
+ create?: boolean;
38
+ }): MemoryStore;
39
+ /** Append an event to the local log (shipped to the sync server on `dim sync`). */
40
+ recordEvent(type: MemoryEventType, memoryId: string | null, payload?: Record<string, unknown>): void;
41
+ /** Events not yet pushed to the sync server, oldest first. */
42
+ unsyncedEvents(limit?: number): MemoryEvent[];
43
+ markEventsSynced(seqs: number[]): void;
44
+ write(input: MemoryWriteInput): MemoryEntry;
45
+ private static evTrustKey;
46
+ /** Mark an evidence payload as approved to execute on this machine. */
47
+ trustEvidencePayload(payload: string): void;
48
+ isEvidencePayloadTrusted(payload: string): boolean;
49
+ /** Executable-evidence payloads currently NOT trusted (i.e. arrived via sync). */
50
+ untrustedEvidence(): Array<{
51
+ memoryId: string;
52
+ claim: string;
53
+ type: string;
54
+ payload: string;
55
+ }>;
56
+ /** Trust every currently-untrusted executable payload (after human inspection). Returns count. */
57
+ trustAllEvidence(): number;
58
+ /**
59
+ * One-time migration: evidence that existed before the trust gate shipped
60
+ * was locally authored (sync always re-writes through applyRemoteMemory,
61
+ * which doesn't trust) — grandfather it so `dim verify` keeps working.
62
+ */
63
+ private migrateEvidenceTrust;
64
+ get(id: string): MemoryEntry | null;
65
+ search(opts: MemorySearchOptions): MemoryEntry[];
66
+ /** All memories whose scope overlaps the given file paths (plus repo-wide memories). */
67
+ getForFiles(paths: string[], limit?: number): MemoryEntry[];
68
+ list(limit?: number): MemoryEntry[];
69
+ statusSummary(): MemoryStatusSummary;
70
+ /**
71
+ * Ensure the vec0 table exists for the given model+dimension.
72
+ * If the model changed since last index, drops and requires reindex.
73
+ * Returns false if vector search is unavailable.
74
+ */
75
+ ensureVecTable(model: string, dim: number): boolean;
76
+ upsertEmbedding(memoryId: string, vector: number[]): void;
77
+ /** Memory ids missing an embedding (for reindex/backfill). */
78
+ unembeddedIds(): string[];
79
+ /** KNN over memory embeddings → [memoryId, distance] pairs, nearest first. */
80
+ knn(queryVector: number[], k?: number): Array<{
81
+ id: string;
82
+ distance: number;
83
+ }>;
84
+ /** Queue a proposed memory for human review. Returns null if a duplicate already exists. */
85
+ propose(input: ProposalInput): Proposal | null;
86
+ getProposal(id: string): Proposal | null;
87
+ listProposals(status?: ProposalStatus, limit?: number): Proposal[];
88
+ /**
89
+ * Approve a proposal: creates the verified memory and removes the proposal row
90
+ * (tombstoned for team sync). `overrides.claim` lets the reviewer reword before promote.
91
+ */
92
+ approveProposal(id: string, overrides?: {
93
+ claim?: string;
94
+ pinned?: boolean;
95
+ }): MemoryEntry;
96
+ rejectProposal(id: string): Proposal;
97
+ /** Claims from rejected proposals — used by triage (survives proposal row deletion). */
98
+ listRejectedClaims(limit?: number): string[];
99
+ /**
100
+ * Remove resolved proposals (approved/rejected) and tombstone for sync.
101
+ * Use after upgrading brains that still carry historical APPROVED rows.
102
+ */
103
+ gcResolvedProposals(opts?: {
104
+ dryRun?: boolean;
105
+ }): {
106
+ removed: number;
107
+ };
108
+ findProposalByPrefix(idOrPrefix: string): Proposal | null;
109
+ /**
110
+ * Record a memory search (MCP or CLI). Zero-hit queries are coverage gaps:
111
+ * an agent needed knowledge the brain couldn't supply. Local-only, never synced.
112
+ */
113
+ logSearch(query: string, paths: string[], resultCount: number, source?: string): void;
114
+ /**
115
+ * Zero-hit searches grouped by normalized query — the repo's knowledge gaps,
116
+ * most-asked first. `sinceDays` bounds the window (default 30).
117
+ */
118
+ searchGaps(opts?: {
119
+ sinceDays?: number;
120
+ limit?: number;
121
+ }): Array<{
122
+ query: string;
123
+ misses: number;
124
+ lastAsked: string;
125
+ paths: string[];
126
+ }>;
127
+ /** Clear logged searches (e.g. after the gaps have been addressed). */
128
+ clearSearchGaps(): number;
129
+ /** Last mined commit sha (commit-miner cursor), or null. */
130
+ getMeta(key: string): string | null;
131
+ setMeta(key: string, value: string): void;
132
+ private hydrateProposal;
133
+ setStatus(id: string, status: MemoryStatus): void;
134
+ setConfidence(id: string, confidence: number): void;
135
+ /**
136
+ * Pin/unpin a memory. Pinned memories never decay with time (but evidence
137
+ * failure still marks them STALE — pinning removes the clock, not the test).
138
+ */
139
+ setPinned(id: string, pinned: boolean): void;
140
+ touchVerified(id: string): void;
141
+ updateEvidenceResult(evidenceId: string, result: Evidence["result"], lastRun: string): void;
142
+ refute(id: string, supersededBy?: string): void;
143
+ forget(id: string): void;
144
+ /** Delete a proposal row and record a tombstone so team sync drops it on the remote brain. */
145
+ private discardProposal;
146
+ link(fromId: string, toId: string, relation: MemoryLink["relation"]): void;
147
+ update(id: string, updates: {
148
+ claim?: string;
149
+ kind?: MemoryKind;
150
+ guardrailLevel?: GuardrailLevel;
151
+ }): void;
152
+ addEvidence(memoryId: string, ev: {
153
+ type: EvidenceType;
154
+ payload: string;
155
+ }): string;
156
+ removeEvidence(evidenceId: string): void;
157
+ /** Local changes since `sinceIso` (null = everything), for pushing to a sync server. */
158
+ changedSince(sinceIso: string | null): {
159
+ memories: MemoryEntry[];
160
+ proposals: Proposal[];
161
+ tombstones: Array<{
162
+ id: string;
163
+ tbl: string;
164
+ deletedAt: string;
165
+ }>;
166
+ };
167
+ /** Per-memory updated_at (for LWW comparisons). */
168
+ memoryUpdatedAt(id: string): string | null;
169
+ isTombstoned(id: string, tbl: "memories" | "proposals"): boolean;
170
+ /** Upsert a remote memory snapshot (caller already did the LWW check). */
171
+ applyRemoteMemory(m: MemoryEntry & {
172
+ updatedAt?: string | null;
173
+ }): void;
174
+ proposalUpdatedAt(id: string): string | null;
175
+ applyRemoteProposal(p: Proposal & {
176
+ updatedAt?: string | null;
177
+ }): void;
178
+ /** Apply a remote tombstone: delete locally without creating a new tombstone loop. */
179
+ applyRemoteTombstone(id: string, tbl: "memories" | "proposals", deletedAt: string): void;
180
+ close(): void;
181
+ private hydrate;
182
+ }