paths

package
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package paths provides centralized path resolution for all SageOx directories.

Architecture Overview

This package implements an XDG-inspired directory structure within ~/.sageox/ for discoverability, while supporting full XDG compliance via OX_XDG_ENABLE=1 for users who prefer standard XDG locations.

Directory Structure

Default layout (~/.sageox/):

~/.sageox/
├── config/    # User configuration (XDG_CONFIG_HOME equivalent)
├── data/      # Persistent data like team contexts (XDG_DATA_HOME equivalent)
├── cache/     # Disposable cached data (XDG_CACHE_HOME equivalent)
└── state/     # Runtime state like daemon sockets (XDG_STATE_HOME equivalent)

Why ~/.sageox/ Instead of XDG by Default

  1. Single discoverable location - users can easily find all SageOx data
  2. Easier to inspect, backup, and troubleshoot
  3. Follows precedent of ~/.docker, ~/.cargo, ~/.npm
  4. XDG purists can opt-in via OX_XDG_ENABLE=1

XDG Compatibility Mode

Set OX_XDG_ENABLE=1 to use standard XDG locations:

config/ → $XDG_CONFIG_HOME/sageox/  (default: ~/.config/sageox/)
data/   → $XDG_DATA_HOME/sageox/    (default: ~/.local/share/sageox/)
cache/  → $XDG_CACHE_HOME/sageox/   (default: ~/.cache/sageox/)
state/  → $XDG_STATE_HOME/sageox/   (default: ~/.local/state/sageox/)

Note: When OX_XDG_ENABLE=1, daemon state uses $XDG_RUNTIME_DIR/sageox/ if available, falling back to /tmp/sageox/ for ephemeral runtime files (sockets, locks).

Usage

All path access should go through this package. Never hardcode paths elsewhere:

// Good
configPath := paths.UserConfigFile()
teamsDir := paths.TeamsDataDir()

// Bad - don't do this
configPath := filepath.Join(os.UserHomeDir(), ".sageox", "config", "config.yaml")

Thread Safety

All functions in this package are safe for concurrent use. Path resolution is deterministic based on environment variables read at call time.

Package paths provides canonical path resolution for all SageOx data locations.

XDG BASE DIRECTORY SPECIFICATION COMPLIANCE ===========================================

The ox CLI MUST follow the XDG Base Directory Specification (v0.8+): https://specifications.freedesktop.org/basedir-spec/latest/

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Requirements:

  • Config data MUST be stored in $XDG_CONFIG_HOME/sageox (default: ~/.config/sageox/)
  • Persistent data MUST be stored in $XDG_DATA_HOME/sageox (default: ~/.local/share/sageox/)
  • Cache data MUST be stored in $XDG_CACHE_HOME/sageox (default: ~/.cache/sageox/)
  • State data SHOULD be stored in $XDG_STATE_HOME/sageox (default: ~/.local/state/sageox/)
  • Runtime data MAY be stored in $XDG_RUNTIME_DIR/sageox if available

Legacy mode (OX_XDG_DISABLE=1) is provided for backwards compatibility only and SHOULD NOT be used in new installations.

Path Construction:

  • All path functions in this package are CANONICAL
  • Code MUST NOT construct SageOx paths manually with filepath.Join()
  • Changes to path locations REQUIRE Ryan's review

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AuthFile

func AuthFile() string

AuthFile returns the path to the authentication tokens file. Contains per-endpoint auth tokens. Should have 0600 permissions.

func CacheDir

func CacheDir() string

CacheDir returns the cache directory per XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/latest/

Contains: guidance/, sessions/, daemon/ (logs)

XDG mode (default): $XDG_CACHE_HOME/sageox (default: ~/.cache/sageox) Legacy mode (OX_XDG_DISABLE=1): ~/.sageox/cache

func CodeDBDataDir added in v0.4.0

func CodeDBDataDir(projectRoot string) string

CodeDBDataDir returns the legacy per-worktree CodeDB directory. Deprecated: Use CodeDBSharedDir for new code. This is kept for migration detection.

func CodeDBSharedDir added in v0.5.0

func CodeDBSharedDir(repoID, endpointURL string) string

CodeDBSharedDir returns the directory for the shared CodeDB index (committed content). Stored inside the ledger's local cache, shared across all worktrees for the same repo. Format: ~/.local/share/sageox/<endpoint>/ledgers/<repoID>/.sageox/cache/codedb/

func ConfigDir

func ConfigDir() string

ConfigDir returns the configuration directory per XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/latest/

Contains: config.yaml, auth.json, git-credentials.json, verification-cache.json, machine-id

XDG mode (default): $XDG_CONFIG_HOME/sageox (default: ~/.config/sageox) Legacy mode (OX_XDG_DISABLE=1): ~/.sageox/config

func CreateTeamSymlinks(ledgerPath string, teamIDs []string) error

CreateTeamSymlinks creates symlinks from team context directories to the ledger. This is a placeholder for future implementation when team context integration with the new ledger structure is defined.

The intent is to create bidirectional references:

  • From ledger to team contexts it uses
  • From team contexts to ledgers that reference them

func DaemonCacheDir

func DaemonCacheDir() string

DaemonCacheDir returns the base directory for daemon cache files. Used for heartbeats and other daemon-managed cache data.

~/.cache/sageox/ (or XDG equivalent)

func DaemonLogFile

func DaemonLogFile(repoID, workspaceID string) string

DaemonLogFile returns the path to a specific daemon's log file. Ensures the log directory exists with proper permissions.

Returns /tmp/<username>/sageox/logs/daemon_<repo_id>_<workspace_id>.log

Uses composite identifier (repo_id + workspace_id) for consistency with heartbeats:

  • repo_id makes logs debuggable (can see which repo)
  • workspace_id ensures uniqueness (supports multiple worktrees)

Creates entire directory tree with 0700 permissions (owner-only access):

/tmp/<username>/sageox/       (0700 - owner-only)
/tmp/<username>/sageox/logs/  (0700 - owner-only)

CRITICAL: All directories are 0700 to prevent other users from accessing daemon logs, which may contain sensitive information.

If directory creation fails, still returns the path (caller will get error when opening file).

func DaemonPidFile

func DaemonPidFile(workspaceID string) string

DaemonPidFile returns the path to a daemon's PID file.

func DaemonRegistryFile

func DaemonRegistryFile() string

DaemonRegistryFile returns the path to the daemon registry. Contains JSON registry of all active daemons.

func DaemonSocketFile

func DaemonSocketFile(workspaceID string) string

DaemonSocketFile returns the path to a daemon's Unix socket.

func DaemonStateDir

func DaemonStateDir() string

DaemonStateDir returns the directory for daemon runtime state. Contains sockets, PIDs, and the daemon registry.

func DataDir

func DataDir() string

DataDir returns the persistent data directory per XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/latest/

Contains: teams/ (team context repositories)

XDG mode (default): $XDG_DATA_HOME/sageox (default: ~/.local/share/sageox) Legacy mode (OX_XDG_DISABLE=1): ~/.sageox/data

func DetectLegacyLedgerPath

func DetectLegacyLedgerPath(projectRoot string) string

DetectLegacyLedgerPath checks for the old repo_sageox_ledger/ sibling directory. Returns the path if it exists, empty string if not.

The legacy format is: <project_parent>/<repo_name>_sageox_ledger/ For example: /Users/dev/Code/myrepo_sageox_ledger/

func EndpointSlug

func EndpointSlug(path string) string

EndpointSlug extracts the endpoint slug from a SageOx data path. This is used to verify endpoint consistency between project config and local paths.

The function looks for paths in the data directory structure:

~/.local/share/sageox/<endpoint>/teams/...
~/.local/share/sageox/<endpoint>/ledgers/...

Returns the endpoint slug (e.g., "sageox.ai", "localhost") if found, or empty string if the path is not a SageOx data path or the endpoint cannot be determined.

Examples:

~/.local/share/sageox/sageox.ai/teams/team_abc -> "sageox.ai"
~/.local/share/sageox/localhost/ledgers/xyz123 -> "localhost"
/some/other/path -> ""

func EnsureDir

func EnsureDir(path string) (string, error)

EnsureDir creates a directory and all parent directories if they don't exist. Returns the path for convenience in chained calls. SECURITY: Uses 0700 (owner-only) to prevent other users from listing contents.

func EnsureDirForFile

func EnsureDirForFile(filePath string) (string, error)

EnsureDirForFile creates the parent directory for a file path. Returns the original path for convenience. SECURITY: Uses 0700 (owner-only) to prevent other users from listing contents.

func EnsureMigrated

func EnsureMigrated() error

EnsureMigrated runs migration once if needed. This is intended to be called early in application startup. Returns any error from the migration attempt.

func GitCredentialsFile

func GitCredentialsFile() string

GitCredentialsFile returns the path to git server credentials. Fallback storage when keychain is unavailable. Should have 0600 permissions.

func GuidanceCacheDir

func GuidanceCacheDir() string

GuidanceCacheDir returns the directory for cached guidance content. Contains obfuscated guidance cache files.

func HeartbeatCacheDir

func HeartbeatCacheDir(ep string) string

HeartbeatCacheDir returns the directory for daemon heartbeat files. Heartbeats are organized by endpoint to support multi-environment usage.

~/.cache/sageox/<endpoint>/heartbeats/

The endpoint is normalized via NormalizeSlug() which strips common prefixes (api., www., app.) and removes port numbers.

IMPORTANT: ep is REQUIRED. Use endpoint.GetForProject(projectRoot) to get the correct endpoint for a project context.

func LedgerNeedsMigration

func LedgerNeedsMigration(projectRoot, ep string) bool

LedgerNeedsMigration checks if ledger migration from the old structure to the new endpoint-namespaced structure is needed.

Returns true if: - Old structure exists (repo_sageox_ledger/) - New structure doesn't exist (repo_sageox/<endpoint>/ledger/)

The endpoint parameter determines the target namespace. If empty, uses current endpoint.

func LedgersDataDir

func LedgersDataDir(repoID, ep string) string

LedgersDataDir returns the CANONICAL directory for ledger git checkouts.

Format:

~/.local/share/sageox/<endpoint>/ledgers/<repo_id>/

This centralized location allows all worktrees to share a single ledger and enables daemons to discover ledgers at a known location.

The endpoint is normalized via NormalizeSlug() which strips common prefixes (api., www., app.) and removes port numbers.

IMPORTANT: ep is REQUIRED. Use endpoint.GetForProject(projectRoot) to get the correct endpoint for a project context. Only use endpoint.Get() during login or when no project context exists.

If repoID is empty, returns the base ledgers directory for that endpoint.

func LegacySessionCacheDirs added in v0.5.0

func LegacySessionCacheDirs(repoID string) []string

LegacySessionCacheDirs returns additional session cache directories from older ox versions that used different cache paths. On macOS, older versions used ~/Library/Caches/sageox/sessions/ (native cache) before the switch to XDG (~/.cache/sageox/sessions/). Returns only directories that exist on disk. The repoID parameter scopes to a specific repo; empty returns all legacy bases.

func MachineIDFile

func MachineIDFile() string

MachineIDFile returns the path to the machine identifier file. Contains a unique machine ID used for HMAC operations.

func MigrateLedgerToNewStructure

func MigrateLedgerToNewStructure(projectRoot, ep string) error

MigrateLedgerToNewStructure migrates from the old repo_sageox_ledger/ sibling directory to the new repo_sageox/<endpoint>/ledger/ structure.

Steps:

  1. Verify old path exists
  2. Create new directory structure (repo_sageox/<endpoint>/)
  3. Move ledger contents (not rename, to preserve git)
  4. Create team symlinks (placeholder for future implementation)
  5. Remove old directory

Returns error if migration fails, nil on success or if no migration needed.

The migration is designed to be safe and reversible:

  • Files are copied before the original is removed
  • If copy fails partway through, original remains intact
  • The old directory is only removed after successful copy

func MigrateTeamContext

func MigrateTeamContext(teamID, legacyPath string) error

MigrateTeamContext moves a team context from sibling directory to ~/.sageox/data/teams/ Note: Legacy paths are always migrated to production endpoint location.

func NewLedgerPath

func NewLedgerPath(projectRoot, ep string) string

NewLedgerPath returns the new endpoint-namespaced ledger path.

Format: <project_parent>/<repo_name>_sageox/<endpoint>/ledger/ For production endpoints: <project_parent>/<repo_name>_sageox/ledger/

Examples:

  • Production: /Users/dev/Code/myrepo_sageox/ledger/
  • Staging: /Users/dev/Code/myrepo_sageox/staging.sageox.ai/ledger/
  • Localhost: /Users/dev/Code/myrepo_sageox/localhost/ledger/

IMPORTANT: ep is REQUIRED. Use endpoint.GetForProject(projectRoot) to get the correct endpoint for a project context.

func SageoxDir

func SageoxDir() string

SageoxDir returns the legacy base SageOx directory.

XDG mode (default): Returns empty string (use specific *Dir functions instead) Legacy mode (OX_XDG_DISABLE=1): ~/.sageox

func SessionCacheDir

func SessionCacheDir(repoID string) string

SessionCacheDir returns the directory for session cache. If repoID is empty, returns the base sessions directory. Otherwise returns the repo-specific session directory.

func StateDir

func StateDir() string

StateDir returns the runtime state directory per XDG Base Directory Specification. https://specifications.freedesktop.org/basedir-spec/latest/

Contains: daemon/ (sockets, PIDs, registry)

XDG mode (default): $XDG_RUNTIME_DIR/sageox (ephemeral, doesn't persist across reboots) Legacy mode (OX_XDG_DISABLE=1): ~/.sageox/state

func TeamContextDir

func TeamContextDir(teamID, ep string) string

TeamContextDir returns the CANONICAL directory for a specific team context repository.

~/.sageox/data/<endpoint>/teams/<team_id>/

IMPORTANT: This is the ONLY function that should determine team context paths. NEVER construct team context paths manually (e.g., filepath.Join(projectRoot, ".team-contexts", ...)). Team contexts belong in the user's home directory, NOT in the project working tree.

If ep is empty, uses the current endpoint from environment.

func TeamContextMigrationNeeded

func TeamContextMigrationNeeded(projectRoot, teamID, legacyPath string) bool

TeamContextMigrationNeeded checks if there are team contexts in sibling directories that should be migrated to ~/.sageox/data/teams/ Note: Legacy paths are always migrated to production endpoint location.

func TeamsDataDir

func TeamsDataDir(ep string) string

TeamsDataDir returns the directory containing all team context repositories.

All endpoints use a consistent namespaced structure:

~/.sageox/data/<endpoint>/teams/
e.g. ~/.sageox/data/sageox.ai/teams/
e.g. ~/.sageox/data/staging.sageox.ai/teams/
e.g. ~/.sageox/data/localhost/teams/

The endpoint is normalized via NormalizeSlug() which strips common prefixes (api., www., app.) and removes port numbers.

IMPORTANT: ep is REQUIRED. Use endpoint.GetForProject(projectRoot) to get the correct endpoint for a project context. Only use endpoint.Get() during login or when no project context exists.

func TempDir

func TempDir() string

TempDir returns the base temporary directory for SageOx ephemeral files.

CRITICAL: Uses /tmp/<username>/sageox/ to avoid multi-user permission conflicts.

Why /tmp/<username>/sageox/ (not /tmp/sageox/<username>/)?

  • If user A creates /tmp/sageox/ first, user B cannot write into it (owned by A)
  • /tmp/<username>/ is always owned by that user, so each user can create sageox/ inside
  • Daemon logs are ephemeral - only useful while daemon is running
  • OS automatically cleans /tmp (on reboot or via tmpwatch/systemd-tmpfiles)
  • Standard pattern (many apps use /tmp/<username>/)

Structure:

/tmp/<username>/sageox/logs/daemon-<workspace_id>.log

Example:

ryan: /tmp/ryan/sageox/logs/daemon-abc123.log
ajit: /tmp/ajit/sageox/logs/daemon-def456.log

Note: Heartbeats use cache (bounded size), logs use /tmp (OS cleanup).

func UserConfigFile

func UserConfigFile() string

UserConfigFile returns the path to the user configuration file. Contains user preferences like tips_enabled, telemetry settings, etc.

func VerificationCacheFile

func VerificationCacheFile() string

VerificationCacheFile returns the path to the signature verification cache. Contains HMAC-protected cache entries for guidance signature verification.

Types

type LedgerMigrationStatus

type LedgerMigrationStatus struct {
	// NeedsMigration is true if old structure exists and new doesn't
	NeedsMigration bool
	// LegacyPath is the old ledger path (repo_sageox_ledger/)
	LegacyPath string
	// NewPath is the new ledger path (repo_sageox/<endpoint>/ledger/)
	NewPath string
	// LegacyExists is true if the legacy path exists
	LegacyExists bool
	// NewExists is true if the new path already exists
	NewExists bool
}

LedgerMigrationStatus represents the result of checking ledger migration state.

func CheckLedgerMigrationStatus

func CheckLedgerMigrationStatus(projectRoot, ep string) LedgerMigrationStatus

CheckLedgerMigrationStatus returns detailed status about ledger migration. This is useful for displaying migration status to users.

type LegacyPaths

type LegacyPaths struct {
	// ConfigDir is the old XDG config location (~/.config/sageox)
	ConfigDir string
	// GuidanceCache is the old hardcoded guidance cache (~/.sageox/guidance/cache)
	GuidanceCache string
	// SessionCache is the old XDG cache location (~/.cache/sageox)
	SessionCache string
}

LegacyPaths contains the old path locations for migration detection.

func GetLegacyPaths

func GetLegacyPaths() LegacyPaths

GetLegacyPaths returns the legacy path locations for migration checks.

type MigrationResult

type MigrationResult struct {
	// ConfigMigrated is true if config was migrated
	ConfigMigrated bool
	// GuidanceCacheMigrated is true if guidance cache was migrated
	GuidanceCacheMigrated bool
	// SessionCacheMigrated is true if session cache was migrated
	SessionCacheMigrated bool
	// Errors contains any errors encountered (migration continues on error)
	Errors []error
}

MigrationResult contains the outcome of a migration attempt.

func Migrate

func Migrate() MigrationResult

Migrate performs migration from legacy paths to the new consolidated structure. This is safe to call multiple times - it will only migrate once. Migration is skipped if OX_XDG_ENABLE is set.

Migration moves:

  • ~/.config/sageox/* → ~/.sageox/config/
  • ~/.sageox/guidance/cache/* → ~/.sageox/cache/guidance/
  • ~/.cache/sageox/* → ~/.sageox/cache/

Note: Team contexts in sibling directories are NOT automatically migrated. Use TeamContextMigrationNeeded() and MigrateTeamContext() for those.

type MigrationStatus

type MigrationStatus struct {
	// Needed is true if migration should be performed
	Needed bool
	// LegacyConfigExists is true if ~/.config/sageox exists
	LegacyConfigExists bool
	// LegacyGuidanceCacheExists is true if ~/.sageox/guidance/cache exists
	LegacyGuidanceCacheExists bool
	// LegacySessionCacheExists is true if ~/.cache/sageox exists
	LegacySessionCacheExists bool
	// NewStructureExists is true if ~/.sageox/config exists (new structure)
	NewStructureExists bool
}

MigrationStatus represents the result of checking migration state.

func CheckMigrationStatus

func CheckMigrationStatus() MigrationStatus

CheckMigrationStatus checks whether migration from legacy paths is needed. This does NOT perform migration, only checks the current state.

type TeamContextLegacyPath

type TeamContextLegacyPath struct {
	TeamID      string
	LegacyPath  string // e.g., /Users/foo/Code/sageox_team_abc123_context
	ProjectPath string // the project that referenced it
}

TeamContextLegacyPath represents a team context in the old sibling directory format.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL