Files
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

93 lines
3.1 KiB
Go

package paths
import (
"os"
"path/filepath"
)
// Paths provides access to all no-mistakes filesystem locations.
// The root defaults to ~/.no-mistakes but can be overridden via NM_HOME
// or by using WithRoot (for testing).
type Paths struct {
root string
}
// New returns Paths rooted at NM_HOME or ~/.no-mistakes.
func New() (*Paths, error) {
if env := os.Getenv("NM_HOME"); env != "" {
return &Paths{root: env}, nil
}
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
return &Paths{root: filepath.Join(home, ".no-mistakes")}, nil
}
// WithRoot returns Paths rooted at a custom directory (for testing).
func WithRoot(root string) *Paths {
return &Paths{root: root}
}
func (p *Paths) Root() string { return p.root }
func (p *Paths) DB() string { return filepath.Join(p.root, "state.sqlite") }
func (p *Paths) Socket() string { return filepath.Join(p.root, "socket") }
func (p *Paths) PIDFile() string { return filepath.Join(p.root, "daemon.pid") }
func (p *Paths) ConfigFile() string { return filepath.Join(p.root, "config.yaml") }
// LockFile is the OS-level advisory lock used to enforce a single live daemon
// per NM_HOME (see the singleton lock in internal/daemon). Distinct from
// PIDFile, which is an informational record a live daemon writes for
// CLI/status consumers: LockFile is what actually prevents two daemons from
// ever running startup recovery or binding the socket concurrently for the
// same root.
func (p *Paths) LockFile() string { return filepath.Join(p.root, "daemon.lock") }
func (p *Paths) UpdateCheckFile() string {
return filepath.Join(p.root, "update-check.json")
}
// TelemetryGateFile persists the read-surface telemetry dedupe state so
// high-frequency status polling stays rate-limited across CLI processes.
func (p *Paths) TelemetryGateFile() string {
return filepath.Join(p.root, "telemetry-gate.json")
}
func (p *Paths) ReposDir() string { return filepath.Join(p.root, "repos") }
func (p *Paths) RepoDir(repoID string) string {
return filepath.Join(p.root, "repos", repoID+".git")
}
func (p *Paths) WorktreesDir() string { return filepath.Join(p.root, "worktrees") }
func (p *Paths) WorktreeDir(repoID, runID string) string {
return filepath.Join(p.root, "worktrees", repoID, runID)
}
func (p *Paths) LogsDir() string { return filepath.Join(p.root, "logs") }
func (p *Paths) RunLogDir(runID string) string {
return filepath.Join(p.root, "logs", runID)
}
func (p *Paths) DaemonLog() string { return filepath.Join(p.root, "logs", "daemon.log") }
func (p *Paths) CLILog() string { return filepath.Join(p.root, "logs", "cli.log") }
// ServerPIDsDir holds PID-tracking files for managed agent servers
// (opencode, rovodev) so a freshly started daemon can reap orphans left
// behind by a crashed predecessor.
func (p *Paths) ServerPIDsDir() string { return filepath.Join(p.root, "servers") }
// EnsureDirs creates all required directories under root.
func (p *Paths) EnsureDirs() error {
dirs := []string{
p.root,
p.ReposDir(),
p.WorktreesDir(),
p.LogsDir(),
p.ServerPIDsDir(),
}
for _, d := range dirs {
if err := os.MkdirAll(d, 0o755); err != nil {
return err
}
}
return nil
}