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.
- package/LICENSE +102 -0
- package/README.md +113 -0
- package/dist/capture/bootstrap.d.ts +25 -0
- package/dist/capture/bootstrap.js +188 -0
- package/dist/capture/commit-miner.d.ts +59 -0
- package/dist/capture/commit-miner.js +381 -0
- package/dist/capture/harvest.d.ts +50 -0
- package/dist/capture/harvest.js +207 -0
- package/dist/capture/pr-miner.d.ts +57 -0
- package/dist/capture/pr-miner.js +185 -0
- package/dist/capture/session-briefing.d.ts +36 -0
- package/dist/capture/session-briefing.js +150 -0
- package/dist/capture/session-extraction.d.ts +23 -0
- package/dist/capture/session-extraction.js +54 -0
- package/dist/capture/triage.d.ts +30 -0
- package/dist/capture/triage.js +108 -0
- package/dist/cli/commands/capture.d.ts +5 -0
- package/dist/cli/commands/capture.js +357 -0
- package/dist/cli/commands/hosts.d.ts +5 -0
- package/dist/cli/commands/hosts.js +98 -0
- package/dist/cli/commands/knowledge.d.ts +5 -0
- package/dist/cli/commands/knowledge.js +121 -0
- package/dist/cli/commands/memory.d.ts +6 -0
- package/dist/cli/commands/memory.js +392 -0
- package/dist/cli/commands/sync.d.ts +5 -0
- package/dist/cli/commands/sync.js +307 -0
- package/dist/cli/commands/tickets.d.ts +6 -0
- package/dist/cli/commands/tickets.js +328 -0
- package/dist/cli/commands/verify.d.ts +5 -0
- package/dist/cli/commands/verify.js +136 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.js +46 -0
- package/dist/cli/shared.d.ts +37 -0
- package/dist/cli/shared.js +175 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.js +51 -0
- package/dist/context/generate.d.ts +24 -0
- package/dist/context/generate.js +115 -0
- package/dist/critique/critique.d.ts +40 -0
- package/dist/critique/critique.js +110 -0
- package/dist/db/schema.d.ts +28 -0
- package/dist/db/schema.js +269 -0
- package/dist/db/store.d.ts +182 -0
- package/dist/db/store.js +906 -0
- package/dist/debug.d.ts +14 -0
- package/dist/debug.js +25 -0
- package/dist/embeddings/provider.d.ts +16 -0
- package/dist/embeddings/provider.js +100 -0
- package/dist/embeddings/search.d.ts +22 -0
- package/dist/embeddings/search.js +95 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/knowledge/chunk.d.ts +9 -0
- package/dist/knowledge/chunk.js +78 -0
- package/dist/knowledge/extract.d.ts +34 -0
- package/dist/knowledge/extract.js +113 -0
- package/dist/knowledge/ingest.d.ts +79 -0
- package/dist/knowledge/ingest.js +305 -0
- package/dist/knowledge/llm.d.ts +21 -0
- package/dist/knowledge/llm.js +110 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +532 -0
- package/dist/security/evidence.d.ts +11 -0
- package/dist/security/evidence.js +15 -0
- package/dist/security/seal.d.ts +3 -0
- package/dist/security/seal.js +28 -0
- package/dist/security/sync-push.d.ts +2 -0
- package/dist/security/sync-push.js +37 -0
- package/dist/security/url.d.ts +6 -0
- package/dist/security/url.js +67 -0
- package/dist/sync/client.d.ts +84 -0
- package/dist/sync/client.js +391 -0
- package/dist/sync/server.d.ts +75 -0
- package/dist/sync/server.js +659 -0
- package/dist/tickets/provider.d.ts +80 -0
- package/dist/tickets/provider.js +375 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.js +5 -0
- package/dist/ui/page.d.ts +5 -0
- package/dist/ui/page.js +841 -0
- package/dist/ui/server.d.ts +10 -0
- package/dist/ui/server.js +437 -0
- package/dist/verify/check.d.ts +38 -0
- package/dist/verify/check.js +121 -0
- package/dist/verify/engine.d.ts +39 -0
- package/dist/verify/engine.js +178 -0
- package/dist/verify/hooks.d.ts +24 -0
- package/dist/verify/hooks.js +125 -0
- package/dist/verify/runners.d.ts +26 -0
- package/dist/verify/runners.js +193 -0
- 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
|
+
}
|