synwire-storage 0.1.0

Configurable persistent storage layout, project identity, and migration for Synwire
Documentation

synwire-storage

Product-scoped storage path management for the Synwire workspace. Provides StorageLayout for computing stable, platform-correct paths for all Synwire subsystems, and RepoId/WorktreeId for two-level project identity.

Quick start

use synwire_storage::{StorageLayout, WorktreeId};
use std::path::Path;

let layout = StorageLayout::new("myapp")?;
let worktree = WorktreeId::for_path(Path::new("/path/to/repo"))?;

// Durable data
println!("{}", layout.session_db("sess-001").display());
println!("{}", layout.experience_db(&worktree).display());
println!("{}", layout.skills_dir().display());

// Cache (may be deleted and regenerated)
println!("{}", layout.index_cache(&worktree).display());
println!("{}", layout.graph_dir(&worktree).display());

StorageLayout

StorageLayout computes all Synwire storage paths for a given product name using a consistent hierarchy rooted at the platform data and cache directories.

Path layout

$XDG_DATA_HOME/<product>/          (Linux)
~/Library/Application Support/<product>/   (macOS)
%APPDATA%/<product>/               (Windows)

├── sessions/<session_id>.db        — checkpoint databases
├── experience/<worktree_key>.db    — per-worktree experience pool
├── skills/                         — global agent skills
├── logs/                           — rotating log files
├── daemon.pid                      — daemon PID file
├── daemon.sock                     — daemon UDS socket
└── global/
    ├── registry.json               — project registry
    ├── experience.db               — cross-project experience
    ├── dependencies.db             — cross-project dependency index
    └── config.json                 — global product config

$XDG_CACHE_HOME/<product>/

├── indices/<worktree_key>/         — vector + BM25 indices
├── graphs/<worktree_key>/          — code dependency graphs
├── communities/<worktree_key>/     — community detection state
├── lsp/<worktree_key>/             — LSP server caches
├── models/                         — embedding model download cache
└── repos/<owner>/<repo>/           — cloned repositories

Durable vs cache

Location Durability Examples
$DATA/<product>/ Durable — never delete Sessions, experience, skills, logs
$CACHE/<product>/ Regenerable — safe to delete Indices, graphs, communities, cloned repos

Path methods

Method Returns Purpose
session_db(id) PathBuf SQLite checkpoint DB for a session
experience_db(worktree) PathBuf Per-worktree experience pool
skills_dir() PathBuf Global agent skills directory
logs_dir() PathBuf Rotating log files
daemon_pid_file() PathBuf Daemon PID file
daemon_socket() PathBuf Daemon Unix domain socket
global_experience_db() PathBuf Cross-project experience
global_dependency_db() PathBuf Cross-project dependency index
index_cache(worktree) PathBuf Vector + BM25 index cache
graph_dir(worktree) PathBuf Code dependency graph
communities_dir(worktree) PathBuf Community detection state
repos_cache() PathBuf Root of cloned repos
repo_cache(owner, repo) PathBuf A specific cloned repo

Construction

use synwire_storage::{StorageLayout, StorageConfig};

// Platform-default paths
let layout = StorageLayout::new("myapp")?;

// Override root (useful in tests)
let layout = StorageLayout::with_root("/tmp/test", "myapp");

// Apply programmatic config on top
let config = StorageConfig {
    data_home: Some("/data/myapp".into()),
    ..Default::default()
};
let layout = StorageLayout::new("myapp")?.with_config(&config);

Project-local config

Each project can supply .<product>/config.json to override paths for that project:

{
  "data_home": "/custom/data",
  "cache_home": "/custom/cache"
}

Load it:

let cfg = layout.load_project_config(std::path::Path::new("/path/to/project"))?;
if let Some(cfg) = cfg {
    let layout = layout.with_config(&cfg);
}

Two-level identity

Synwire identifies projects at two levels to support multi-worktree repositories cleanly.

RepoId

RepoId is stable across all clones and worktrees of the same repository. It is derived from:

  1. Git available: SHA-1 of the first (root) commit (git rev-list --max-parents=0 HEAD)
  2. Git unavailable: SHA-256 of the canonical directory path
use synwire_storage::identity::RepoId;
use std::path::Path;

let id = RepoId::for_path(Path::new("/path/to/repo"))?;
println!("{id}");  // e.g. "a3f2c1..."

WorktreeId

WorktreeId identifies a specific working copy within a repository family. It combines RepoId with a SHA-256 of the canonicalised worktree root path.

use synwire_storage::WorktreeId;
use std::path::Path;

let wid = WorktreeId::for_path(Path::new("/path/to/repo"))?;
println!("{}", wid.key());        // "a3f2c1...-def456789012"
println!("{}", wid.display_name); // "myrepo@main"

The key() method produces a compact string safe for use in directory names: <repo_id>-<worktree_hash[:12]>.

Why two levels?

Scenario RepoId WorktreeId
Two clones of same repo Same Different
Same repo, two branches (worktrees) Same Different
Two unrelated repos Different Different

This lets the experience pool and dependency index be shared across branches of the same repo (via RepoId), while vector indices are per-worktree (via WorktreeId).

Configuration

Source Priority Applies to
SYNWIRE_DATA_DIR env var Highest Data home
SYNWIRE_CACHE_DIR env var Highest Cache home
StorageLayout::with_root(root, name) Explicit Both
.<product>/config.json Per-project Either
Platform default (directories::BaseDirs) Lowest Both

CLI override example:

SYNWIRE_DATA_DIR=/mnt/data/synwire synwire-mcp-server --project .

Migration

Future versions will provide StorageLayout::migrate(from_version) to move or rewrite paths between schema versions. In v0.1, no migration is needed — all paths are new.

Path mapping from pre-StorageLayout layouts:

Old path New path
$CACHE/synwire/indices/ StorageLayout::index_cache(worktree)
$CACHE/synwire/graphs/ StorageLayout::graph_dir(worktree)
$DATA/synwire/skills/ StorageLayout::skills_dir()

See the migration guide for shell commands.

Feature flags

No optional features. Always depends on directories and sha2.