/** * MemoryStore — all persistence for aidimag. * Local-first: a single SQLite file at /.aidimag/memory.db */ import type { Evidence, EvidenceType, GuardrailLevel, MemoryEntry, MemoryKind, MemoryLink, MemorySearchOptions, MemoryStatus, MemoryStatusSummary, MemoryWriteInput, Proposal, ProposalInput, ProposalStatus } from "../types.js"; export declare const AIDIMAG_DIR = ".aidimag"; export declare const DB_FILE = "memory.db"; /** Walk up from `start` looking for an existing .aidimag dir (or .git as repo root). */ export declare function findRepoRoot(start?: string): string | null; export declare function dbPathFor(repoRoot: string): string; /** * Stable per-machine id (CLOUD_DESIGN: "3 of 4 machines confirm…"). * Persisted once in ~/.aidimag/machine-id; hostname-prefixed for readability. */ export declare function machineId(): string; /** Event types shipped to the sync server (see schema.ts events table). */ 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" | "evidence_signed"; export interface MemoryEvent { seq: number; id: string; type: MemoryEventType; memoryId: string | null; payload: Record; machine: string; schemaVersion: number; createdAt: string; } /** Session-scoped short-term working memory note (TTL-expiring, never synced). */ export interface ScratchpadEntry { id: string; sessionId: string; content: string; createdBy: string; createdAt: string; expiresAt: string; } /** A memory flagged by the provenance audit, with why and how risky. */ export interface MemoryAuditFinding { memory: MemoryEntry; /** heuristic risk score — higher = review sooner */ risk: number; reasons: string[]; } export declare class MemoryStore { private db; readonly dbPath: string; /** true if the sqlite-vec extension loaded (vector search available) */ readonly vecAvailable: boolean; private readonly machine; constructor(dbPath: string); /** Open the store for the repo containing `cwd`. Throws if not initialized and create=false. */ static open(cwd?: string, opts?: { create?: boolean; }): MemoryStore; /** Append an event to the local log (shipped to the sync server on `dim sync`). */ recordEvent(type: MemoryEventType, memoryId: string | null, payload?: Record): void; /** Events not yet pushed to the sync server, oldest first. */ unsyncedEvents(limit?: number): MemoryEvent[]; markEventsSynced(seqs: number[]): void; write(input: MemoryWriteInput): MemoryEntry; private static evTrustKey; /** Mark an evidence payload as approved to execute on this machine. */ trustEvidencePayload(payload: string): void; isEvidencePayloadTrusted(payload: string): boolean; /** Executable-evidence payloads currently NOT trusted (i.e. arrived via sync). */ untrustedEvidence(): Array<{ memoryId: string; claim: string; type: string; payload: string; }>; /** Trust every currently-untrusted executable payload (after human inspection). Returns count. */ trustAllEvidence(): number; /** * One-time migration: evidence that existed before the trust gate shipped * was locally authored (sync always re-writes through applyRemoteMemory, * which doesn't trust) — grandfather it so `dim verify` keeps working. */ private migrateEvidenceTrust; get(id: string): MemoryEntry | null; search(opts: MemorySearchOptions): MemoryEntry[]; /** All memories whose scope overlaps the given file paths (plus repo-wide memories). */ getForFiles(paths: string[], limit?: number): MemoryEntry[]; list(limit?: number): MemoryEntry[]; statusSummary(): MemoryStatusSummary; /** * Ensure the vec0 table exists for the given model+dimension. * If the model changed since last index, drops and requires reindex. * Returns false if vector search is unavailable. */ ensureVecTable(model: string, dim: number): boolean; upsertEmbedding(memoryId: string, vector: number[]): void; /** Memory ids missing an embedding (for reindex/backfill). */ unembeddedIds(): string[]; /** KNN over memory embeddings → [memoryId, distance] pairs, nearest first. */ knn(queryVector: number[], k?: number): Array<{ id: string; distance: number; }>; /** Queue a proposed memory for human review. Returns null if a duplicate already exists. */ propose(input: ProposalInput): Proposal | null; getProposal(id: string): Proposal | null; listProposals(status?: ProposalStatus, limit?: number): Proposal[]; /** * Approve a proposal: creates the verified memory and removes the proposal row * (tombstoned for team sync). `overrides.claim` lets the reviewer reword before promote. */ approveProposal(id: string, overrides?: { claim?: string; pinned?: boolean; }): MemoryEntry; rejectProposal(id: string): Proposal; /** Claims from rejected proposals — used by triage (survives proposal row deletion). */ listRejectedClaims(limit?: number): string[]; /** * Remove resolved proposals (approved/rejected) and tombstone for sync. * Use after upgrading brains that still carry historical APPROVED rows. */ gcResolvedProposals(opts?: { dryRun?: boolean; }): { removed: number; }; findProposalByPrefix(idOrPrefix: string): Proposal | null; /** * Record a memory search (MCP or CLI). Zero-hit queries are coverage gaps: * an agent needed knowledge the brain couldn't supply. Local-only, never synced. */ logSearch(query: string, paths: string[], resultCount: number, source?: string): void; /** * Zero-hit searches grouped by normalized query — the repo's knowledge gaps, * most-asked first. `sinceDays` bounds the window (default 30). */ searchGaps(opts?: { sinceDays?: number; limit?: number; }): Array<{ query: string; misses: number; lastAsked: string; paths: string[]; }>; /** Clear logged searches (e.g. after the gaps have been addressed). */ clearSearchGaps(): number; scratchpadWrite(content: string, opts?: { sessionId?: string; ttlHours?: number; createdBy?: string; }): ScratchpadEntry; /** Read scratchpad notes (newest first). Expired entries are purged first. */ scratchpadRead(sessionId?: string, limit?: number): ScratchpadEntry[]; /** Clear scratchpad notes (one session, or all). Returns count removed. */ scratchpadClear(sessionId?: string): number; /** Drop expired scratchpad entries. Returns count purged. */ purgeExpiredScratchpad(): number; auditMemories(opts?: { limit?: number; }): MemoryAuditFinding[]; /** * Apply retention policy: forget memories older than maxAgeDays with no evidence * (and optionally STALE memories older than staleAgeDays regardless of evidence). * Returns the list of forgotten (or would-be-forgotten, in dry-run mode) memories. */ applyRetention(opts: { maxAgeDays: number; staleAgeDays?: number; preservePinned?: boolean; preserveSources?: string[]; dryRun?: boolean; }): MemoryEntry[]; /** Last mined commit sha (commit-miner cursor), or null. */ getMeta(key: string): string | null; setMeta(key: string, value: string): void; private hydrateProposal; setStatus(id: string, status: MemoryStatus): void; setConfidence(id: string, confidence: number): void; /** * Pin/unpin a memory. Pinned memories never decay with time (but evidence * failure still marks them STALE — pinning removes the clock, not the test). */ setPinned(id: string, pinned: boolean): void; touchVerified(id: string): void; updateEvidenceResult(evidenceId: string, result: Evidence["result"], lastRun: string): void; refute(id: string, supersededBy?: string): void; forget(id: string): void; /** Delete a proposal row and record a tombstone so team sync drops it on the remote brain. */ private discardProposal; link(fromId: string, toId: string, relation: MemoryLink["relation"]): void; update(id: string, updates: { claim?: string; kind?: MemoryKind; guardrailLevel?: GuardrailLevel; }): void; addEvidence(memoryId: string, ev: { type: EvidenceType; payload: string; }): string; /** Sign an evidence row with a cryptographic signature. */ signEvidence(evidenceId: string, signature: string, signedBy: string): void; /** Get a single evidence row by ID. */ getEvidence(evidenceId: string): { id: string; memoryId: string; type: string; payload: string; result: string; } | undefined; /** Resolve an 8-char evidence ID prefix to a full ID. */ resolveEvidenceId(shortId: string): string | undefined; removeEvidence(evidenceId: string): void; /** Local changes since `sinceIso` (null = everything), for pushing to a sync server. */ changedSince(sinceIso: string | null): { memories: MemoryEntry[]; proposals: Proposal[]; tombstones: Array<{ id: string; tbl: string; deletedAt: string; }>; }; /** Per-memory updated_at (for LWW comparisons). */ memoryUpdatedAt(id: string): string | null; isTombstoned(id: string, tbl: "memories" | "proposals"): boolean; /** Upsert a remote memory snapshot (caller already did the LWW check). */ applyRemoteMemory(m: MemoryEntry & { updatedAt?: string | null; }): void; proposalUpdatedAt(id: string): string | null; applyRemoteProposal(p: Proposal & { updatedAt?: string | null; }): void; /** Apply a remote tombstone: delete locally without creating a new tombstone loop. */ applyRemoteTombstone(id: string, tbl: "memories" | "proposals", deletedAt: string): void; close(): void; /** Mark a memory as synced to cloud with the server sequence number. */ markMemorySynced(id: string, seq: number): void; /** Mark a memory as unsynced (e.g., removed from cloud but kept locally). */ markMemoryUnsynced(id: string): void; /** Mark a proposal as synced to cloud. */ markProposalSynced(id: string, seq: number): void; /** Mark a proposal as unsynced. */ markProposalUnsynced(id: string): void; /** Get all memories that are not synced to cloud. */ getUnsyncedMemories(limit?: number): MemoryEntry[]; /** Get all memories that are synced to cloud. */ getSyncedMemories(): MemoryEntry[]; /** Get sync status summary for cloud quota management. */ getSyncStatusSummary(): { synced: number; unsynced: number; total: number; }; /** Check if a memory is synced to cloud. */ isMemorySynced(id: string): boolean; private hydrate; }