first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
// Package memory implements the persistent memory storage layer for pigo.
//
// It is backed by a pure-Go SQLite database (modernc.org/sqlite, no CGO) with
// an FTS5 full-text index over Markdown memory files. This file provides the
// Store type together with database opening and idempotent schema migration.
// Later nodes extend the package with path resolution, reconcile (lazy
// indexing/pruning) and BM25 search.
package memory
import (
"database/sql"
"fmt"
"os"
"path/filepath"
_ "modernc.org/sqlite" // pure-Go SQLite driver, registers the "sqlite" driver
)
// Store is the handle to the memory database. Fields are unexported; later
// nodes access the underlying *sql.DB via DB() and the configured roots via the
// package-internal fields.
type Store struct {
db *sql.DB
root string // memory root directory (magic layout: global/projects/sessions)
ccBase string // optional Claude Code base dir for the "cc" scope; "" disables
}
// Open opens (creating if necessary) the SQLite database at dbPath and runs the
// idempotent schema migration. The parent directory of dbPath is created with
// os.MkdirAll before opening. root is the memory root directory and ccBase is
// the optional Claude Code base directory; both are retained for use by later
// nodes and are not required to exist here.
//
// Calling Open twice on the same file is safe: the migration uses
// CREATE ... IF NOT EXISTS throughout.
func Open(dbPath, root, ccBase string) (*Store, error) {
if dbPath == "" {
return nil, fmt.Errorf("memory: empty dbPath")
}
// modernc.org/sqlite understands the ":memory:" DSN; only create a parent
// directory for real file paths.
if dbPath != ":memory:" {
if dir := filepath.Dir(dbPath); dir != "" && dir != "." {
if err := os.MkdirAll(dir, 0o755); err != nil {
return nil, fmt.Errorf("memory: create db dir %q: %w", dir, err)
}
}
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, fmt.Errorf("memory: open db %q: %w", dbPath, err)
}
// A single global DB with a single connection: writes are serialized, which
// matches the low write frequency and avoids SQLITE_BUSY contention.
db.SetMaxOpenConns(1)
if err := db.Ping(); err != nil {
db.Close()
return nil, fmt.Errorf("memory: ping db %q: %w", dbPath, err)
}
if err := migrate(db); err != nil {
db.Close()
return nil, fmt.Errorf("memory: migrate: %w", err)
}
return &Store{db: db, root: root, ccBase: ccBase}, nil
}
// migrate applies the idempotent schema DDL.
func migrate(db *sql.DB) error {
if _, err := db.Exec(schemaDDL); err != nil {
return err
}
return nil
}
// DB returns the underlying *sql.DB for use by later nodes and tests. It may be
// nil if the store was not successfully opened.
func (s *Store) DB() *sql.DB {
if s == nil {
return nil
}
return s.db
}
// Root returns the memory root directory the store was opened with (the magic
// layout root holding global/projects/sessions). It is the canonical source for
// the memory root used by checkpoint persistence and context rebuild
// (<root>/sessions/<id>/checkpoint.md); callers must resolve the memory root
// through this accessor rather than re-deriving it from another store's dir. It
// is "" for a nil store.
func (s *Store) Root() string {
if s == nil {
return ""
}
return s.root
}
// CountByScope returns the number of indexed memory entries grouped by scope.
// Scopes with no entries are omitted from the map. It reflects the current
// contents of memory_index; callers that want fresh counts should Reconcile
// first. A nil store or nil db yields an empty map.
func (s *Store) CountByScope() (map[Scope]int, error) {
out := make(map[Scope]int)
if s == nil || s.db == nil {
return out, nil
}
rows, err := s.db.Query(`SELECT scope, COUNT(*) FROM memory_index GROUP BY scope`)
if err != nil {
return nil, fmt.Errorf("memory: count by scope: %w", err)
}
defer rows.Close()
for rows.Next() {
var scope string
var n int
if err := rows.Scan(&scope, &n); err != nil {
return nil, fmt.Errorf("memory: scan scope count: %w", err)
}
out[Scope(scope)] = n
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("memory: iterate scope counts: %w", err)
}
return out, nil
}
// Close closes the underlying database connection.
func (s *Store) Close() error {
if s == nil || s.db == nil {
return nil
}
return s.db.Close()
}