diff --git a/internal/storage/migrations.go b/internal/storage/migrations.go new file mode 100644 index 0000000..7afa808 --- /dev/null +++ b/internal/storage/migrations.go @@ -0,0 +1,113 @@ +package storage + +var migrations = []string{ + divacodeSchema, + companionSchema, +} + +const divacodeSchema = ` +CREATE TABLE IF NOT EXISTS conversations ( + id TEXT PRIMARY KEY, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL REFERENCES conversations(id), + role TEXT NOT NULL CHECK(role IN ('user','assistant','system','tool')), + content TEXT NOT NULL, + tool_call TEXT, + token_count INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id); + +CREATE TABLE IF NOT EXISTS context_state ( + conversation_id TEXT PRIMARY KEY REFERENCES conversations(id), + token_count INTEGER NOT NULL DEFAULT 0, + summary TEXT +); + +CREATE TABLE IF NOT EXISTS memories ( + id TEXT PRIMARY KEY, + content TEXT NOT NULL, + embedding BLOB, + source TEXT, + importance REAL DEFAULT 0.5, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + last_accessed TEXT +); + +CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance); +CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at); +` + +const companionSchema = ` +CREATE TABLE IF NOT EXISTS personality_traits ( + id TEXT PRIMARY KEY, + trait TEXT NOT NULL UNIQUE, + value REAL NOT NULL DEFAULT 0.5, + min_value REAL NOT NULL DEFAULT 0.0, + max_value REAL NOT NULL DEFAULT 1.0, + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS relationship_metrics ( + id TEXT PRIMARY KEY, + metric TEXT NOT NULL UNIQUE, + value REAL NOT NULL DEFAULT 0.0, + updated_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS shared_topics ( + id TEXT PRIMARY KEY, + topic TEXT NOT NULL UNIQUE, + count INTEGER NOT NULL DEFAULT 1, + last_discussed TEXT +); + +CREATE TABLE IF NOT EXISTS inside_jokes ( + id TEXT PRIMARY KEY, + joke TEXT NOT NULL, + context TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS diary_entries ( + id TEXT PRIMARY KEY, + date TEXT NOT NULL, + summary TEXT NOT NULL, + mood TEXT, + topics TEXT, + observations TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS observations ( + id TEXT PRIMARY KEY, + content TEXT NOT NULL, + confidence REAL DEFAULT 0.5, + category TEXT, + applied INTEGER DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS agent_promises ( + id TEXT PRIMARY KEY, + promise TEXT NOT NULL, + context TEXT, + fulfilled INTEGER DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + deadline TEXT +); + +CREATE TABLE IF NOT EXISTS agent_strategies ( + id TEXT PRIMARY KEY, + situation TEXT NOT NULL, + strategy TEXT NOT NULL, + outcome TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +` diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go new file mode 100644 index 0000000..35dbc04 --- /dev/null +++ b/internal/storage/sqlite.go @@ -0,0 +1,48 @@ +package storage + +import ( + "database/sql" + + _ "modernc.org/sqlite" +) + +type DB struct { + *sql.DB +} + +func Open(path string) (*DB, error) { + db, err := sql.Open("sqlite", path+"?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)") + if err != nil { + return nil, err + } + if err := db.Ping(); err != nil { + return nil, err + } + return &DB{db}, nil +} + +func (db *DB) Migrate() error { + for _, m := range migrations { + if err := db.Exec(m); err != nil { + return err + } + } + return nil +} + +func (db *DB) Exec(query string, args ...interface{}) error { + _, err := db.DB.Exec(query, args...) + return err +} + +func (db *DB) Query(query string, args ...interface{}) (*sql.Rows, error) { + return db.DB.Query(query, args...) +} + +func (db *DB) QueryRow(query string, args ...interface{}) *sql.Row { + return db.DB.QueryRow(query, args...) +} + +func (db *DB) Close() error { + return db.DB.Close() +}