Documentation
¶
Overview ¶
Package files is the markdown layer: memory and note files are the source of truth for durable knowledge. It parses/serializes YAML frontmatter, writes atomically, keeps the SQLite index mirrors in sync, and watches the trees for out-of-band edits. Ported from Seam v1 (internal/note + internal/watcher).
Index ¶
- Variables
- func AtomicWrite(path string, data []byte, perm os.FileMode) error
- func ContentHash(content string) string
- func MemoryRelPath(project, name string) string
- func NoteRelPath(project, slug string) string
- func ParseMemory(content, relPath string) (core.Memory, error)
- func ParseNote(content, relPath string) (core.Note, error)
- func RenderMemory(m core.Memory) (string, error)
- func RenderNote(n core.Note) (string, error)
- type Indexer
- func (ix *Indexer) AllFilePaths(ctx context.Context) ([]string, error)
- func (ix *Indexer) ClearContentHash(ctx context.Context, relPath string) error
- func (ix *Indexer) ContentHashByFilePath(ctx context.Context, relPath string) (hash string, found bool, err error)
- func (ix *Indexer) DeleteByFilePath(ctx context.Context, relPath string) error
- func (ix *Indexer) IDByFilePath(ctx context.Context, relPath string) (id string, found bool, err error)
- func (ix *Indexer) IndexMemory(ctx context.Context, m core.Memory) error
- func (ix *Indexer) IndexNote(ctx context.Context, n core.Note) error
- type Manager
- func (m *Manager) Close() error
- func (m *Manager) Indexer() *Indexer
- func (m *Manager) MoveMemory(ctx context.Context, mem core.Memory, toProject string) (core.Memory, error)
- func (m *Manager) Reconcile(ctx context.Context) error
- func (m *Manager) Remove(ctx context.Context, relPath string) error
- func (m *Manager) SetEmbedder(e llm.Embedder)
- func (m *Manager) Start(ctx context.Context) error
- func (m *Manager) Store() *Store
- func (m *Manager) WriteMemory(ctx context.Context, mem core.Memory) (core.Memory, error)
- func (m *Manager) WriteNote(ctx context.Context, note core.Note) (core.Note, error)
- type Store
- func (s *Store) DataDir() string
- func (s *Store) Exists(relPath string) bool
- func (s *Store) ReadMemory(relPath string) (core.Memory, error)
- func (s *Store) ReadNote(relPath string) (core.Note, error)
- func (s *Store) Remove(relPath string) error
- func (s *Store) WriteMemory(m core.Memory) (core.Memory, error)
- func (s *Store) WriteNote(n core.Note) (core.Note, error)
Constants ¶
This section is empty.
Variables ¶
var ErrPathOccupied = errors.New("target path belongs to a different item")
ErrPathOccupied is returned when a write would land on a file owned by a different item (by id). In particular a superseded or archived memory keeps its tombstone file at memory/{project}/{name}.md, so that name stays occupied until the tombstone is deleted; a new memory silently overwriting it would destroy readable supersession history. Callers free the name (memory_delete) or pick another one.
var ErrTreeEscape = errors.New("computed path escapes the item's tree")
ErrTreeEscape is returned when an item's computed file path would land outside its own tree: a memory outside memory/, or a note outside notes/. A hostile or corrupt project value (e.g. "../notes") cleans to a path that stays inside the data dir -- which the traversal guard accepts -- but crosses into the other tree; this containment check is the files-layer backstop behind the MCP layer's project validation.
Functions ¶
func AtomicWrite ¶
AtomicWrite writes data to path atomically: it writes a temp file in the same directory, fsyncs it, then renames over the target. This prevents a crash mid-write from corrupting a source-of-truth markdown file. Ported from Seam v1 (note.AtomicWriteFile).
func ContentHash ¶
ContentHash returns the SHA-256 hex digest of a file's full content. It is the change-detection key the reconciler compares against the index.
func MemoryRelPath ¶
MemoryRelPath returns the data-dir-relative path of a memory file: memory/{project|_global}/{name}.md.
func NoteRelPath ¶
NoteRelPath returns the data-dir-relative path of a note file: notes/{project|_global}/{slug}.md.
func ParseMemory ¶
ParseMemory parses memory file content into a core.Memory. relPath is the data-dir-relative file path recorded on the result.
func RenderMemory ¶
RenderMemory serializes a memory to full markdown file content.
Types ¶
type Indexer ¶
type Indexer struct {
// contains filtered or unexported fields
}
Indexer mirrors memory/note files into the SQLite index tables and the unified self-contained FTS5 table. The files on disk are the source of truth; these mirrors are rebuildable and kept in sync by the watcher + reconciler.
func (*Indexer) AllFilePaths ¶
AllFilePaths returns every data-dir-relative file_path currently in the memory and note indexes. The reconciler uses it to find rows whose file has vanished.
func (*Indexer) ClearContentHash ¶
ClearContentHash blanks the recorded content hash for the row at a path. The empty hash never matches a real digest, so the next reconcile (or watcher event) treats the file as changed and re-indexes it -- the retry mechanism for a failed embed. A missing row is a no-op.
func (*Indexer) ContentHashByFilePath ¶
func (ix *Indexer) ContentHashByFilePath(ctx context.Context, relPath string) (hash string, found bool, err error)
ContentHashByFilePath returns the indexed content hash for a path, and whether a row exists. The reconciler uses it to skip unchanged files.
func (*Indexer) DeleteByFilePath ¶
DeleteByFilePath removes the index (and FTS) row for a data-dir-relative path. It is a no-op if no row references that path. Used by the watcher/reconciler when a file disappears from disk.
func (*Indexer) IDByFilePath ¶
func (ix *Indexer) IDByFilePath(ctx context.Context, relPath string) (id string, found bool, err error)
IDByFilePath returns the id of the index row holding a data-dir-relative path, and whether such a row exists. The write guard uses it to detect a path already owned by a different item (the file_path column is UNIQUE).
func (*Indexer) IndexMemory ¶
IndexMemory upserts a memory into memories_index and refreshes its FTS row.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the running files subsystem: it owns the filesystem Store and the SQLite Indexer, reconciles the trees against the index at startup, and watches them for out-of-band edits. Application writes go through it so their own writes are suppressed in the watcher (no re-index loop). An optional embedder keeps the vector index in sync with the file content (best-effort).
func NewManager ¶
NewManager builds a Manager over dataDir backed by db. It does not touch the filesystem or start watching until Start is called.
func (*Manager) Close ¶
Close stops the watcher and drains its work: after Close returns, no debounce handler is running or will run, and the event-loop goroutine has exited -- so the caller may close the DB the handlers write to. Safe to call more than once, and without a prior Start.
func (*Manager) MoveMemory ¶
func (m *Manager) MoveMemory(ctx context.Context, mem core.Memory, toProject string) (core.Memory, error)
MoveMemory relocates a memory to another project, keeping its ULID. It mirrors the fixed note-move recipe: refuse when a different memory already owns the target path (WriteMemory's occupancy guard), and write the new file BEFORE removing the old one -- the index row is keyed by id, so the write repoints its file_path and a failed write leaves the memory intact at its old path instead of deleting it outright. The memory keeps its name; inbound [[name]] wiki-links resolve globally by bare name, so a move needs no link rewrite. The caller is responsible for bumping Updated. It is a no-op when toProject already equals the memory's project (idempotent for a retried apply).
func (*Manager) Reconcile ¶
Reconcile brings the index into agreement with the trees on disk: it re-indexes changed/new files and drops index rows whose file has been deleted.
func (*Manager) Remove ¶
Remove deletes a memory/note file (suppressing the watcher) and its index row.
func (*Manager) SetEmbedder ¶
SetEmbedder enables vector indexing. When set, every (re)indexed item is embedded and its vector upserted; embedding failures are logged, not fatal, so a slow or down embedder never blocks a write or an edit. Nil disables it.
It MUST be called before Start, from the goroutine that owns the Manager. After Start, the watcher's handler goroutines read m.embedder without synchronization, so setting it on a running Manager is a data race. The field is unguarded deliberately: the embedder is fixed at startup (main.go resolves it from config, then starts), so a lock would cost every indexed write to protect against a call that has no legitimate reason to happen.
func (*Manager) Start ¶
Start creates the tree directories, begins watching them, reconciles the index against disk, and launches the event loop in a background goroutine. The loop stops when ctx is cancelled or Close is called.
func (*Manager) WriteMemory ¶
WriteMemory writes a memory through the Store (suppressing the watcher's view of its own write) and indexes it synchronously. It returns the stored memory with FilePath and ContentHash populated. A path already owned by a different memory -- notably the tombstone file of a superseded memory whose name the write would revive -- is refused with ErrPathOccupied rather than overwritten.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes memory and note files under a data directory. It owns no database; the index mirror and watcher are layered on top.
func (*Store) ReadMemory ¶
ReadMemory reads and parses the memory at a data-dir-relative path.
func (*Store) Remove ¶
Remove deletes the file at a data-dir-relative path. A missing file is not an error (the desired end-state already holds).
func (*Store) WriteMemory ¶
WriteMemory renders m, writes it atomically to its computed path, and returns m updated with FilePath and ContentHash. Name must be a safe filename.