From c4a53910e7e8b84fc88261d3113f4a7860b2d3db Mon Sep 17 00:00:00 2001 From: Jay Flowers Date: Mon, 6 Apr 2026 16:04:34 -0400 Subject: [PATCH] refactor: consolidate paths under .uf/replicator/ namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align all filesystem paths with the uf CLI ecosystem: - Per-repo: .hive/ and .unbound-force/ → .uf/replicator/ - Per-machine: ~/.config/swarm-tools/swarm.db → ~/.config/uf/replicator/replicator.db - Clean break from cyborg-swarm -- no auto-migration - All tests, docs, and spec artifacts updated --- AGENTS.md | 9 +++-- README.md | 6 ++-- cmd/replicator/init.go | 14 ++++---- cmd/replicator/init_test.go | 10 +++--- cmd/replicator/serve.go | 4 +-- cmd/replicator/serve_test.go | 10 +++--- cmd/replicator/setup.go | 2 +- internal/config/config.go | 9 +++-- internal/doctor/checks.go | 2 +- internal/hive/sync.go | 12 +++---- internal/hive/sync_test.go | 8 ++--- openspec/changes/add-init-command/design.md | 2 +- openspec/changes/rename-paths/.openspec.yaml | 2 ++ openspec/changes/rename-paths/design.md | 38 ++++++++++++++++++++ openspec/changes/rename-paths/proposal.md | 38 ++++++++++++++++++++ openspec/changes/rename-paths/specs/paths.md | 38 ++++++++++++++++++++ openspec/changes/rename-paths/tasks.md | 36 +++++++++++++++++++ specs/001-go-rewrite-phases/plan.md | 8 ++--- specs/001-go-rewrite-phases/quickstart.md | 2 +- specs/001-go-rewrite-phases/spec.md | 6 ++-- specs/001-go-rewrite-phases/tasks.md | 6 ++-- specs/002-charm-ux/plan.md | 4 +-- specs/002-charm-ux/quickstart.md | 14 ++++---- specs/002-charm-ux/research.md | 2 +- specs/002-charm-ux/spec.md | 16 ++++----- specs/002-charm-ux/tasks.md | 8 ++--- 26 files changed, 228 insertions(+), 78 deletions(-) create mode 100644 openspec/changes/rename-paths/.openspec.yaml create mode 100644 openspec/changes/rename-paths/design.md create mode 100644 openspec/changes/rename-paths/proposal.md create mode 100644 openspec/changes/rename-paths/specs/paths.md create mode 100644 openspec/changes/rename-paths/tasks.md diff --git a/AGENTS.md b/AGENTS.md index 177c83b..bde2346 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,8 +23,7 @@ All code changes follow Red-Green-Refactor: ### Database -Single global database at `~/.config/swarm-tools/swarm.db`. -Schema is compatible with cyborg-swarm's libSQL database. +Single global database at `~/.config/uf/replicator/replicator.db`. Use in-memory databases for tests (`db.OpenMemory()`). ### MCP Protocol @@ -228,8 +227,8 @@ make install # Install to GOPATH/bin | Command | Purpose | |---------|---------| -| `replicator init` | Per-repo setup: creates `.hive/` with empty `cells.json` | -| `replicator setup` | Per-machine setup: creates `~/.config/swarm-tools/` + SQLite DB | +| `replicator init` | Per-repo setup: creates `.uf/replicator/` with empty `cells.json` | +| `replicator setup` | Per-machine setup: creates `~/.config/uf/replicator/` + SQLite DB | | `replicator serve` | Start MCP JSON-RPC server on stdio | | `replicator cells` | List hive cells (work items) | | `replicator doctor` | Check environment health | @@ -271,7 +270,7 @@ originally by [Joel Hooks](https://github.com/joelhooks). ## Active Technologies - Go 1.25+ + `cobra` (CLI), `modernc.org/sqlite` (pure Go SQLite), stdlib `encoding/json` (MCP JSON-RPC), stdlib `os/exec` (git operations) (001-go-rewrite-phases) -- SQLite at `~/.config/swarm-tools/swarm.db` (WAL mode, compatible with cyborg-swarm) (001-go-rewrite-phases) +- SQLite at `~/.config/uf/replicator/replicator.db` (WAL mode) (001-go-rewrite-phases) - Go 1.25+ + `charmbracelet/lipgloss v1.1.0`, `charmbracelet/log v1.0.0`, `muesli/termenv v0.16.0`, `charmbracelet/lipgloss/table` (sub-package of lipgloss) (002-charm-ux) - SQLite via `modernc.org/sqlite` (unchanged) (002-charm-ux) diff --git a/README.md b/README.md index 5707ade..b2873a9 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ Download from [GitHub Releases](https://github.com/unbound-force/replicator/rele ## Usage ```bash -# Per-repo setup (creates .hive/ directory) +# Per-repo setup (creates .uf/replicator/ directory) replicator init -# Per-machine setup (creates ~/.config/swarm-tools/ + SQLite DB) +# Per-machine setup (creates ~/.config/uf/replicator/ + SQLite DB) replicator setup # Start MCP server (AI agents connect via stdio) @@ -116,7 +116,7 @@ For Claude Code, add to `mcp_servers` in your config: | Variable | Default | Purpose | |----------|---------|---------| -| `REPLICATOR_DB` | `~/.config/swarm-tools/swarm.db` | SQLite database path | +| `REPLICATOR_DB` | `~/.config/uf/replicator/replicator.db` | SQLite database path | | `DEWEY_MCP_URL` | `http://localhost:3333/mcp/` | Dewey semantic memory endpoint | | `ZEN_API_KEY` | *(none)* | OpenCode Zen gateway for LLM calls | diff --git a/cmd/replicator/init.go b/cmd/replicator/init.go index e4b2cab..8fddc5e 100644 --- a/cmd/replicator/init.go +++ b/cmd/replicator/init.go @@ -14,7 +14,7 @@ func initCmd() *cobra.Command { cmd := &cobra.Command{ Use: "init", Short: "Initialize a project directory for swarm operations", - Long: `Creates a .hive/ directory with an empty cells.json in the target + Long: `Creates a .uf/replicator/ directory with an empty cells.json in the target directory. Idempotent — safe to run multiple times. This is the per-repo initialization command. It does not require the @@ -23,15 +23,15 @@ global database (replicator setup) or any external services.`, return runInit(pathFlag) }, } - cmd.Flags().StringVar(&pathFlag, "path", ".", "Target directory for .hive/ initialization") + cmd.Flags().StringVar(&pathFlag, "path", ".", "Target directory for .uf/replicator/ initialization") return cmd } -// runInit creates the .hive/ directory and seeds cells.json. +// runInit creates the .uf/replicator/ directory and seeds cells.json. // Uses styled output: green for success, dim for already-initialized. func runInit(targetDir string) error { styles := ui.NewStyles(os.Stdout) - hiveDir := filepath.Join(targetDir, ".hive") + hiveDir := filepath.Join(targetDir, ".uf", "replicator") // Check if already initialized. if info, err := os.Stat(hiveDir); err == nil && info.IsDir() { @@ -39,9 +39,9 @@ func runInit(targetDir string) error { return nil } - // Create .hive/ directory. + // Create .uf/replicator/ directory. if err := os.MkdirAll(hiveDir, 0o755); err != nil { - return fmt.Errorf("create .hive directory: %w", err) + return fmt.Errorf("create .uf/replicator directory: %w", err) } // Write empty cells.json. @@ -50,6 +50,6 @@ func runInit(targetDir string) error { return fmt.Errorf("write cells.json: %w", err) } - fmt.Println(styles.Pass.Render("initialized .hive/")) + fmt.Println(styles.Pass.Render("initialized .uf/replicator/")) return nil } diff --git a/cmd/replicator/init_test.go b/cmd/replicator/init_test.go index ae2f5dd..c492281 100644 --- a/cmd/replicator/init_test.go +++ b/cmd/replicator/init_test.go @@ -12,13 +12,13 @@ func TestRunInit_FreshDirectory(t *testing.T) { t.Fatalf("runInit: %v", err) } - hiveDir := filepath.Join(dir, ".hive") + hiveDir := filepath.Join(dir, ".uf", "replicator") info, err := os.Stat(hiveDir) if err != nil { - t.Fatalf(".hive/ not created: %v", err) + t.Fatalf(".uf/replicator/ not created: %v", err) } if !info.IsDir() { - t.Fatal(".hive/ is not a directory") + t.Fatal(".uf/replicator/ is not a directory") } cellsPath := filepath.Join(hiveDir, "cells.json") @@ -40,7 +40,7 @@ func TestRunInit_AlreadyInitialized(t *testing.T) { } // Write something to cells.json to verify it's not overwritten. - cellsPath := filepath.Join(dir, ".hive", "cells.json") + cellsPath := filepath.Join(dir, ".uf", "replicator", "cells.json") os.WriteFile(cellsPath, []byte(`[{"id":"test"}]`), 0o644) // Second init — should be idempotent. @@ -64,7 +64,7 @@ func TestRunInit_CustomPath(t *testing.T) { t.Fatalf("runInit with custom path: %v", err) } - cellsPath := filepath.Join(target, ".hive", "cells.json") + cellsPath := filepath.Join(target, ".uf", "replicator", "cells.json") if _, err := os.Stat(cellsPath); err != nil { t.Fatalf("cells.json not created at custom path: %v", err) } diff --git a/cmd/replicator/serve.go b/cmd/replicator/serve.go index 7c9ec37..7622676 100644 --- a/cmd/replicator/serve.go +++ b/cmd/replicator/serve.go @@ -50,11 +50,11 @@ func serveMCP() error { } // setupLogger creates a charmbracelet/log logger that writes to both -// stderr and .unbound-force/replicator.log. If the log file cannot be +// stderr and .uf/replicator/replicator.log. If the log file cannot be // created, logging falls back to stderr only and a warning is printed. // The returned io.Closer should be deferred by the caller; it may be nil. func setupLogger() (*charmlog.Logger, io.Closer) { - logDir := filepath.Join(".", ".unbound-force") + logDir := filepath.Join(".", ".uf", "replicator") if err := os.MkdirAll(logDir, 0o755); err != nil { fmt.Fprintf(os.Stderr, "warning: cannot create log directory: %v (logging to stderr only)\n", err) return charmlog.NewWithOptions(os.Stderr, charmlog.Options{ diff --git a/cmd/replicator/serve_test.go b/cmd/replicator/serve_test.go index 4cd3a6b..2833c68 100644 --- a/cmd/replicator/serve_test.go +++ b/cmd/replicator/serve_test.go @@ -8,7 +8,7 @@ import ( func TestSetupLogger_CreatesLogFile(t *testing.T) { // Run setupLogger in a temp directory so it creates - // .unbound-force/replicator.log there. + // .uf/replicator/replicator.log there. dir := t.TempDir() orig, err := os.Getwd() if err != nil { @@ -27,7 +27,7 @@ func TestSetupLogger_CreatesLogFile(t *testing.T) { t.Fatal("expected non-nil logger") } - logPath := filepath.Join(dir, ".unbound-force", "replicator.log") + logPath := filepath.Join(dir, ".uf", "replicator", "replicator.log") if _, err := os.Stat(logPath); err != nil { t.Fatalf("log file not created: %v", err) } @@ -45,7 +45,7 @@ func TestSetupLogger_Truncates(t *testing.T) { t.Cleanup(func() { os.Chdir(orig) }) // First call: write a marker to the log file. - logDir := filepath.Join(dir, ".unbound-force") + logDir := filepath.Join(dir, ".uf", "replicator") os.MkdirAll(logDir, 0o755) logPath := filepath.Join(logDir, "replicator.log") os.WriteFile(logPath, []byte("MARKER_SHOULD_BE_GONE"), 0o644) @@ -74,8 +74,8 @@ func TestSetupLogger_ReadOnlyDir(t *testing.T) { t.Fatalf("Getwd: %v", err) } - // Create the .unbound-force dir as read-only so file creation fails. - logDir := filepath.Join(dir, ".unbound-force") + // Create the .uf/replicator dir as read-only so file creation fails. + logDir := filepath.Join(dir, ".uf", "replicator") os.MkdirAll(logDir, 0o755) os.Chmod(logDir, 0o444) t.Cleanup(func() { diff --git a/cmd/replicator/setup.go b/cmd/replicator/setup.go index cb1a7ec..4703762 100644 --- a/cmd/replicator/setup.go +++ b/cmd/replicator/setup.go @@ -23,7 +23,7 @@ func runSetup() error { return fmt.Errorf("determine home directory: %w", err) } - configDir := filepath.Join(home, ".config", "swarm-tools") + configDir := filepath.Join(home, ".config", "uf", "replicator") if err := os.MkdirAll(configDir, 0o755); err != nil { return fmt.Errorf("create config directory: %w", err) } diff --git a/internal/config/config.go b/internal/config/config.go index 0f0c9e7..f3ccdb5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,6 @@ // Package config manages replicator configuration. // -// The global database lives at ~/.config/swarm-tools/swarm.db -// (shared with the TypeScript cyborg-swarm for migration compatibility). +// The global database lives at ~/.config/uf/replicator/replicator.db. package config import ( @@ -33,11 +32,11 @@ func Load() *Config { func defaultDatabasePath() string { home, err := os.UserHomeDir() if err != nil { - return "swarm.db" + return "replicator.db" } - dir := filepath.Join(home, ".config", "swarm-tools") + dir := filepath.Join(home, ".config", "uf", "replicator") _ = os.MkdirAll(dir, 0o755) - return filepath.Join(dir, "swarm.db") + return filepath.Join(dir, "replicator.db") } func envOr(key, fallback string) string { diff --git a/internal/doctor/checks.go b/internal/doctor/checks.go index 67e591a..7af08ab 100644 --- a/internal/doctor/checks.go +++ b/internal/doctor/checks.go @@ -140,7 +140,7 @@ func checkConfigDir() CheckResult { } } - configDir := home + "/.config/swarm-tools" + configDir := home + "/.config/uf/replicator" info, err := os.Stat(configDir) elapsed := time.Since(start) diff --git a/internal/hive/sync.go b/internal/hive/sync.go index 2122df7..7edcd66 100644 --- a/internal/hive/sync.go +++ b/internal/hive/sync.go @@ -12,17 +12,17 @@ import ( // Sync serializes all cells to JSON and commits them to git. // -// Writes cells to /.hive/cells.json, then runs -// `git add .hive/ && git commit -m "hive sync"` in the project directory. +// Writes cells to /.uf/replicator/cells.json, then runs +// `git add .uf/replicator/ && git commit -m "hive sync"` in the project directory. func Sync(store *db.Store, projectPath string) error { cells, err := QueryCells(store, CellQuery{Limit: 10000}) if err != nil { return fmt.Errorf("query cells for sync: %w", err) } - hiveDir := filepath.Join(projectPath, ".hive") + hiveDir := filepath.Join(projectPath, ".uf", "replicator") if err := os.MkdirAll(hiveDir, 0o755); err != nil { - return fmt.Errorf("create .hive dir: %w", err) + return fmt.Errorf("create .uf/replicator dir: %w", err) } data, err := json.MarshalIndent(cells, "", " ") @@ -35,8 +35,8 @@ func Sync(store *db.Store, projectPath string) error { return fmt.Errorf("write cells.json: %w", err) } - // Stage and commit the .hive directory. - addCmd := exec.Command("git", "add", ".hive/") + // Stage and commit the .uf/replicator directory. + addCmd := exec.Command("git", "add", ".uf/replicator/") addCmd.Dir = projectPath if out, err := addCmd.CombinedOutput(); err != nil { return fmt.Errorf("git add: %w\n%s", err, out) diff --git a/internal/hive/sync_test.go b/internal/hive/sync_test.go index be82ee9..fc73a02 100644 --- a/internal/hive/sync_test.go +++ b/internal/hive/sync_test.go @@ -38,7 +38,7 @@ func TestSync(t *testing.T) { } // Verify cells.json was created. - cellsPath := filepath.Join(dir, ".hive", "cells.json") + cellsPath := filepath.Join(dir, ".uf", "replicator", "cells.json") data, err := os.ReadFile(cellsPath) if err != nil { t.Fatalf("read cells.json: %v", err) @@ -83,14 +83,14 @@ func TestSync_CreatesHiveDir(t *testing.T) { } } - // Sync with no cells -- should still create .hive/cells.json. + // Sync with no cells -- should still create .uf/replicator/cells.json. if err := Sync(store, dir); err != nil { t.Fatalf("Sync: %v", err) } - hiveDir := filepath.Join(dir, ".hive") + hiveDir := filepath.Join(dir, ".uf", "replicator") if _, err := os.Stat(hiveDir); os.IsNotExist(err) { - t.Error(".hive directory was not created") + t.Error(".uf/replicator directory was not created") } } diff --git a/openspec/changes/add-init-command/design.md b/openspec/changes/add-init-command/design.md index 5d13cb5..a71ffdc 100644 --- a/openspec/changes/add-init-command/design.md +++ b/openspec/changes/add-init-command/design.md @@ -1,6 +1,6 @@ ## Context -Replicator has `setup` (per-machine: `~/.config/swarm-tools/` + SQLite) but no `init` (per-repo). The `uf` CLI needs `replicator init` to exist so it can delegate to it during `uf init`, following the same pattern as `dewey init`. +Replicator has `setup` (per-machine: `~/.config/uf/replicator/` + SQLite) but no `init` (per-repo). The `uf` CLI needs `replicator init` to exist so it can delegate to it during `uf init`, following the same pattern as `dewey init`. The existing `setup.go` pattern: cobra command → `runSetup()` function → filesystem ops → print status. Follow this exactly. diff --git a/openspec/changes/rename-paths/.openspec.yaml b/openspec/changes/rename-paths/.openspec.yaml new file mode 100644 index 0000000..796d2d7 --- /dev/null +++ b/openspec/changes/rename-paths/.openspec.yaml @@ -0,0 +1,2 @@ +schema: unbound-force +created: 2026-04-06 diff --git a/openspec/changes/rename-paths/design.md b/openspec/changes/rename-paths/design.md new file mode 100644 index 0000000..397fb1e --- /dev/null +++ b/openspec/changes/rename-paths/design.md @@ -0,0 +1,38 @@ +## Context + +Three different directory names from the upstream fork need consolidation: + +| Current | New | Purpose | +|---------|-----|---------| +| `[repo]/.hive/cells.json` | `[repo]/.uf/replicator/cells.json` | Per-repo cell state | +| `[repo]/.unbound-force/replicator.log` | `[repo]/.uf/replicator/replicator.log` | Per-repo MCP session log | +| `~/.config/swarm-tools/swarm.db` | `~/.config/uf/replicator/replicator.db` | Per-machine SQLite database | + +## Goals / Non-Goals + +### Goals +- Consolidate all replicator paths under `.uf/replicator/` (per-repo) and `~/.config/uf/replicator/` (per-machine) +- Rename database file from `swarm.db` to `replicator.db` +- Update all Go source, tests, documentation, and spec artifacts + +### Non-Goals +- Auto-migration from old paths (clean break) +- Changing the `.unbound-force/config.yaml` ecosystem config (that's UF-level, not replicator-specific) +- Changing `.opencode/` or `.specify/` directory structures +- Modifying the LICENSE attribution text + +## Decisions + +**D1: Pure find-and-replace.** Every change is a string constant swap. No logic modifications, no new functions, no behavioral changes. + +**D2: No migration path.** The old `~/.config/swarm-tools/swarm.db` is abandoned. Users run `replicator setup` to create the new directory. This is a clean break -- the cyborg-swarm TypeScript version can no longer share the same database. + +**D3: Update spec artifacts.** Historical spec files will be updated to reflect the new paths for consistency, since the old paths will cause confusion if someone reads them. + +**D4: Database renamed to `replicator.db`.** Complete naming break from `swarm.db`. Matches the tool name. + +## Risks / Trade-offs + +**Risk: Existing users lose data.** Anyone with cells/events in `~/.config/swarm-tools/swarm.db` will start fresh. Mitigation: this is a pre-release tool with no external users yet. The old database can be manually copied if needed. + +**Trade-off: Breaks cyborg-swarm compatibility.** The Go and TypeScript versions can no longer share a database. This is intentional -- replicator is replacing cyborg-swarm, not coexisting with it long-term. diff --git a/openspec/changes/rename-paths/proposal.md b/openspec/changes/rename-paths/proposal.md new file mode 100644 index 0000000..d7042dd --- /dev/null +++ b/openspec/changes/rename-paths/proposal.md @@ -0,0 +1,38 @@ +## Why + +Replicator uses three different directory names inherited from the upstream fork: `.hive/` (per-repo cells), `.unbound-force/` (per-repo logs), and `~/.config/swarm-tools/` (per-machine database). These names are inconsistent, carry baggage from the TypeScript origin, and don't align with the `uf` CLI ecosystem where `uf` is the common root namespace. + +Consolidating under `.uf/replicator/` (per-repo) and `~/.config/uf/replicator/` (per-machine) gives every replicator artifact a predictable, discoverable location within the UF namespace. + +## What Changes + +### Modified Capabilities +- Per-repo hive state moves from `[repo]/.hive/cells.json` to `[repo]/.uf/replicator/cells.json` +- Per-repo MCP log moves from `[repo]/.unbound-force/replicator.log` to `[repo]/.uf/replicator/replicator.log` +- Per-machine database moves from `~/.config/swarm-tools/swarm.db` to `~/.config/uf/replicator/replicator.db` +- Per-machine config directory moves from `~/.config/swarm-tools/` to `~/.config/uf/replicator/` + +### No Migration +Clean break. Users run `replicator setup` to create the new directories. Old paths remain until manually removed. No auto-detection, no symlinks, no data copying. + +## Impact + +- 6 Go source files (path constants) +- 3 Go test files (assertion strings) +- 5 active documentation files (README, AGENTS.md, etc.) +- 10 completed spec artifact files (historical path references) +- Zero behavioral changes beyond the path locations + +## Constitution Alignment + +### I. Autonomous Collaboration +**PASS**: Path naming is a local concern. No inter-hero protocol changes. + +### II. Composability First +**PASS**: Replicator remains independently installable. The new paths don't depend on other tools being present. The `~/.config/uf/` parent directory may be shared with other `uf` tools but each tool manages its own subdirectory. + +### III. Observable Quality +**PASS**: The `doctor` command checks for the config directory at the new path. The path is documented in README and AGENTS.md. + +### IV. Testability +**PASS**: All tests use `t.TempDir()` for filesystem operations. Path changes are string constant swaps with no logic changes. diff --git a/openspec/changes/rename-paths/specs/paths.md b/openspec/changes/rename-paths/specs/paths.md new file mode 100644 index 0000000..d738bf3 --- /dev/null +++ b/openspec/changes/rename-paths/specs/paths.md @@ -0,0 +1,38 @@ +## MODIFIED Requirements + +### Requirement: Per-repo directory + +`replicator init` MUST create `[repo]/.uf/replicator/cells.json` instead of `[repo]/.hive/cells.json`. + +#### Scenario: Fresh init +- **GIVEN** a directory without `.uf/replicator/` +- **WHEN** `replicator init` is run +- **THEN** `.uf/replicator/cells.json` is created containing `[]` + +#### Scenario: Hive sync +- **GIVEN** cells exist in the database +- **WHEN** `hive_sync` is called +- **THEN** cells are written to `.uf/replicator/cells.json` and `git add .uf/replicator/` is run + +### Requirement: Per-repo log file + +The MCP server MUST write logs to `[repo]/.uf/replicator/replicator.log` instead of `[repo]/.unbound-force/replicator.log`. + +#### Scenario: Server startup +- **GIVEN** a project directory +- **WHEN** `replicator serve` starts +- **THEN** `.uf/replicator/replicator.log` is created (truncating any existing file) + +### Requirement: Per-machine database + +The database MUST be stored at `~/.config/uf/replicator/replicator.db` instead of `~/.config/swarm-tools/swarm.db`. + +#### Scenario: Setup +- **GIVEN** a fresh machine +- **WHEN** `replicator setup` is run +- **THEN** `~/.config/uf/replicator/` is created with `replicator.db` + +#### Scenario: Doctor check +- **GIVEN** the config directory exists +- **WHEN** `replicator doctor` is run +- **THEN** the config_dir check verifies `~/.config/uf/replicator/` exists diff --git a/openspec/changes/rename-paths/tasks.md b/openspec/changes/rename-paths/tasks.md new file mode 100644 index 0000000..05abb26 --- /dev/null +++ b/openspec/changes/rename-paths/tasks.md @@ -0,0 +1,36 @@ +## 1. Go source -- per-machine paths + +- [x] 1.1 In `internal/config/config.go`: change `defaultDatabasePath()` from `~/.config/swarm-tools` to `~/.config/uf/replicator` and `swarm.db` to `replicator.db`. Update package comment. +- [x] 1.2 In `internal/doctor/checks.go`: change `checkConfigDir()` path from `/.config/swarm-tools` to `/.config/uf/replicator` +- [x] 1.3 In `cmd/replicator/setup.go`: change `runSetup()` path from `/.config/swarm-tools` to `/.config/uf/replicator`, update printed message + +## 2. Go source -- per-repo paths + +- [x] 2.1 In `cmd/replicator/init.go`: replace all `.hive` with `.uf/replicator` (dir path, messages, flag description) +- [x] 2.2 In `internal/hive/sync.go`: replace `.hive` with `.uf/replicator` in `Sync()` function and `git add` command +- [x] 2.3 In `cmd/replicator/serve.go`: replace `.unbound-force` with `.uf/replicator` in `setupLogger()` (log dir path and comment) + +## 3. Go tests + +- [x] 3.1 In `cmd/replicator/init_test.go`: replace all `.hive` with `.uf/replicator` in assertions +- [x] 3.2 In `internal/hive/sync_test.go`: replace all `.hive` with `.uf/replicator` in assertions +- [x] 3.3 In `cmd/replicator/serve_test.go`: replace all `.unbound-force` with `.uf/replicator` in assertions and comments + +## 4. Active documentation + +- [x] 4.1 In `README.md`: replace `~/.config/swarm-tools` with `~/.config/uf/replicator`, `swarm.db` with `replicator.db` +- [x] 4.2 In `AGENTS.md`: replace all `~/.config/swarm-tools` with `~/.config/uf/replicator`, `swarm.db` with `replicator.db`, `.hive/` with `.uf/replicator/` where referencing cell state +- [x] 4.3 In `.specify/memory/constitution.md`: replace `swarm.db` with `replicator.db` if referenced + +## 5. Spec artifacts (historical) + +- [x] 5.1 In `specs/001-go-rewrite-phases/`: replace `~/.config/swarm-tools/swarm.db` with `~/.config/uf/replicator/replicator.db` across spec.md, plan.md, tasks.md, quickstart.md +- [x] 5.2 In `specs/002-charm-ux/`: replace `.unbound-force/replicator.log` with `.uf/replicator/replicator.log` across spec.md, tasks.md, plan.md, quickstart.md, research.md +- [x] 5.3 In `openspec/changes/add-init-command/`: replace `~/.config/swarm-tools` with `~/.config/uf/replicator` in design.md + +## 6. Verify + +- [x] 6.1 Run `make check` -- all tests pass +- [x] 6.2 Grep verify: zero remaining `swarm-tools` in Go source/tests/active docs (excluding LICENSE, README credit link) +- [x] 6.3 Grep verify: zero remaining `.hive/` in Go source/tests (old per-repo path) +- [x] 6.4 Grep verify: zero remaining `.unbound-force/replicator` in Go source/tests (old log path) diff --git a/specs/001-go-rewrite-phases/plan.md b/specs/001-go-rewrite-phases/plan.md index 663bd8b..fce5d59 100644 --- a/specs/001-go-rewrite-phases/plan.md +++ b/specs/001-go-rewrite-phases/plan.md @@ -11,7 +11,7 @@ Complete the Go rewrite of cyborg-swarm across 5 phases: finish the hive tool su **Language/Version**: Go 1.25+ **Primary Dependencies**: `cobra` (CLI), `modernc.org/sqlite` (pure Go SQLite), stdlib `encoding/json` (MCP JSON-RPC), stdlib `os/exec` (git operations) -**Storage**: SQLite at `~/.config/swarm-tools/swarm.db` (WAL mode, compatible with cyborg-swarm) +**Storage**: SQLite at `~/.config/uf/replicator/replicator.db` (WAL mode) **Testing**: `go test` (stdlib only, no testify — per TC-001) **Target Platform**: macOS/Linux arm64+amd64, Windows amd64 (via goreleaser) **Project Type**: CLI + MCP server (single binary) @@ -147,7 +147,7 @@ Complete the hive tool suite by adding the 7 remaining tools to match the TypeSc | `hive_query` | `hive.QueryCells()` | existing | Already implemented as `hive_cells` — verify schema parity, may need alias | | `hive_start` | `hive.StartCell()` | `internal/hive/cells.go` | Set status=in_progress, record timestamp | | `hive_ready` | `hive.ReadyCell()` | `internal/hive/cells.go` | Return highest-priority unblocked cell | -| `hive_sync` | `hive.Sync()` | `internal/hive/sync.go` | Serialize cells to `.hive/`, git add+commit | +| `hive_sync` | `hive.Sync()` | `internal/hive/sync.go` | Serialize cells to `.uf/replicator/`, git add+commit | | `hive_session_start` | `hive.SessionStart()` | `internal/hive/session.go` | Create session, return previous handoff notes | | `hive_session_end` | `hive.SessionEnd()` | `internal/hive/session.go` | Save handoff notes for next session | @@ -155,7 +155,7 @@ Complete the hive tool suite by adding the 7 remaining tools to match the TypeSc - `hive_create_epic`: Use `sql.Tx` for atomicity — insert epic row, then insert all subtask rows with `parent_id` pointing to the epic. Roll back on any failure. - `hive_start`: Simple status update with timestamp. Reuse `UpdateCell` pattern. - `hive_ready`: Query cells where `status='open'` and no parent cell is still open (unblocked). Order by `priority DESC`. Return first match. -- `hive_sync`: Shell out to `git` via `internal/gitutil/` to add and commit `.hive/` directory contents. +- `hive_sync`: Shell out to `git` via `internal/gitutil/` to add and commit `.uf/replicator/` directory contents. - `hive_session_start/end`: New `sessions` table in SQLite. Store handoff notes as JSON. Return previous session's notes on start. **Database changes**: @@ -374,7 +374,7 @@ The `doctor` command checks for required dependencies and reports their status. | Git | `git --version` | Exit code 0, version ≥ 2.20 | | Database | Open and ping SQLite | No error | | Dewey | HTTP GET to health endpoint | 200 OK | -| Config dir | `~/.config/swarm-tools/` exists | Directory exists | +| Config dir | `~/.config/uf/replicator/` exists | Directory exists | **Implementation approach** (per AP-002, AP-003): - `doctor.Run(opts Options) (*Result, error)` — core logic, testable diff --git a/specs/001-go-rewrite-phases/quickstart.md b/specs/001-go-rewrite-phases/quickstart.md index 011d008..11ed3d4 100644 --- a/specs/001-go-rewrite-phases/quickstart.md +++ b/specs/001-go-rewrite-phases/quickstart.md @@ -156,7 +156,7 @@ specs/ Feature specifications | Variable | Default | Description | |----------|---------|-------------| -| `REPLICATOR_DB` | `~/.config/swarm-tools/swarm.db` | SQLite database path | +| `REPLICATOR_DB` | `~/.config/uf/replicator/replicator.db` | SQLite database path | | `DEWEY_MCP_URL` | `http://localhost:3333/mcp/` | Dewey MCP endpoint | | `ZEN_API_KEY` | (none) | OpenCode Zen API key | diff --git a/specs/001-go-rewrite-phases/spec.md b/specs/001-go-rewrite-phases/spec.md index 17c5af0..fcdca1a 100644 --- a/specs/001-go-rewrite-phases/spec.md +++ b/specs/001-go-rewrite-phases/spec.md @@ -121,7 +121,7 @@ A maintainer runs a parity test suite that compares the replicator's MCP tool re - What happens when `swarm_worktree_merge` encounters a conflict? The merge fails with a clear error listing the conflicting files, and the worktree is NOT cleaned up (allowing manual resolution). - What happens when Dewey is down during `hivemind_store`? The tool returns a structured error with code `DEWEY_UNAVAILABLE` and a hint to check the Dewey service. - What happens when a CLI command is run but no database exists? The database is auto-created with the full schema on first access. -- How does the system handle the TypeScript cyborg-swarm's database during migration? The schema is wire-compatible -- the Go binary reads and writes the same SQLite database file at `~/.config/swarm-tools/swarm.db`. +- How does the system handle the TypeScript cyborg-swarm's database during migration? The schema is wire-compatible -- the Go binary reads and writes the same SQLite database file at `~/.config/uf/replicator/replicator.db`. ## Requirements *(mandatory)* @@ -131,7 +131,7 @@ A maintainer runs a parity test suite that compares the replicator's MCP tool re - **FR-001**: All 11 hive MCP tools MUST be implemented with argument schemas and response shapes matching the TypeScript version. - **FR-002**: The `hive_create_epic` tool MUST support atomic creation of an epic with N subtasks in a single call. - **FR-003**: The `hive_ready` tool MUST return only unblocked cells (no open dependencies), sorted by priority. -- **FR-004**: The `hive_sync` tool MUST serialize cell state and commit to the project's `.hive/` directory. +- **FR-004**: The `hive_sync` tool MUST serialize cell state and commit to the project's `.uf/replicator/` directory. #### Swarm Mail (Phase 1) - **FR-005**: All 9 swarm mail MCP tools MUST be implemented with matching schemas and response shapes. @@ -189,7 +189,7 @@ A maintainer runs a parity test suite that compares the replicator's MCP tool re ## Assumptions - Phase 0 (scaffold) is complete: MCP server, SQLite, tool registry, and 4 hive tools are working. -- The database schema at `~/.config/swarm-tools/swarm.db` is the shared state between the Go and TypeScript versions during migration. +- The database schema at `~/.config/uf/replicator/replicator.db` is the shared state between the Go and TypeScript versions during migration. - Dewey is the canonical memory backend; Ollama embedding operations are handled by Dewey, not by replicator. - The eval system (`swarm-evals`) remains in TypeScript and is NOT part of this rewrite. - The dashboard web UI (`swarm-dashboard`) remains in TypeScript and is NOT part of this rewrite. diff --git a/specs/001-go-rewrite-phases/tasks.md b/specs/001-go-rewrite-phases/tasks.md index f1039f2..8f0e14b 100644 --- a/specs/001-go-rewrite-phases/tasks.md +++ b/specs/001-go-rewrite-phases/tasks.md @@ -34,7 +34,7 @@ - [x] T007 [P] [US1] Implement `StartCell` in `internal/hive/cells.go` — set `status='in_progress'`, update `updated_at`. Return error if cell not found. - [x] T008 [P] [US1] Implement `ReadyCell` in `internal/hive/cells.go` — query cells where `status='open'` and no parent cell has `status` in ('open', 'in_progress', 'blocked'). Order by `priority DESC`. Return first match (single cell, not a list). - [x] T009 [P] [US1] Add tests in `internal/hive/cells_test.go` — `TestStartCell_Success`, `TestStartCell_NotFound`, `TestReadyCell_UnblockedOnly` (create parent+child, verify only unblocked returned), `TestReadyCell_PriorityOrder`, `TestReadyCell_NoReady` (all blocked, returns nil). -- [x] T010 [US1] Implement `Sync` in `internal/hive/sync.go` — serialize all cells to JSON, write to `.hive/cells.json` in project dir, shell out to `git add .hive/ && git commit -m "hive sync"` via `os/exec`. Accept `projectPath string` parameter. +- [x] T010 [US1] Implement `Sync` in `internal/hive/sync.go` — serialize all cells to JSON, write to `.uf/replicator/cells.json` in project dir, shell out to `git add .uf/replicator/ && git commit -m "hive sync"` via `os/exec`. Accept `projectPath string` parameter. - [x] T011 [US1] Add tests in `internal/hive/sync_test.go` — `TestSync_WritesJSON` (use `t.TempDir()`, verify file contents), `TestSync_CommitsToGit` (init git repo in tempdir, run sync, verify commit exists via `git log`). - [x] T012 [US1] Implement `SessionStart` and `SessionEnd` in `internal/hive/session.go` — `SessionStart(store, agentName, activeCellID)` creates session row, returns previous session's handoff_notes. `SessionEnd(store, handoffNotes)` updates current session with ended_at and handoff_notes. - [x] T013 [US1] Add tests in `internal/hive/session_test.go` — `TestSessionStart_ReturnsHandoff` (create session with notes, start new, verify notes returned), `TestSessionStart_NoPrevious` (first session returns empty), `TestSessionEnd_SavesNotes`. @@ -164,7 +164,7 @@ ### 4A — Doctor Command (US5) -- [x] T062 [P] [US5] Create `internal/doctor/checks.go` — define `CheckResult{Name, Status, Message, Duration}` and `Options{Writer io.Writer, Config *config.Config, GitChecker, DeweyChecker}` types. Implement `Run(opts Options) ([]CheckResult, error)`. Individual check functions: `checkGit()` (run `git --version`, verify exit 0), `checkDatabase()` (open and ping SQLite), `checkDewey()` (HTTP GET to health endpoint), `checkConfigDir()` (verify `~/.config/swarm-tools/` exists). Use interface injection for external checks. +- [x] T062 [P] [US5] Create `internal/doctor/checks.go` — define `CheckResult{Name, Status, Message, Duration}` and `Options{Writer io.Writer, Config *config.Config, GitChecker, DeweyChecker}` types. Implement `Run(opts Options) ([]CheckResult, error)`. Individual check functions: `checkGit()` (run `git --version`, verify exit 0), `checkDatabase()` (open and ping SQLite), `checkDewey()` (HTTP GET to health endpoint), `checkConfigDir()` (verify `~/.config/uf/replicator/` exists). Use interface injection for external checks. - [x] T063 [P] [US5] Add tests in `internal/doctor/checks_test.go` — `TestDoctor_AllPass` (mock all checks passing, verify output contains ✓), `TestDoctor_DeweyDown` (mock Dewey failure, verify ✗ with message), `TestDoctor_GitMissing`, `TestDoctor_CompletesInTwoSeconds`. Use `bytes.Buffer` as writer. - [x] T064 [US5] Create `cmd/replicator/doctor.go` — implement `doctorCmd() *cobra.Command` and `runDoctor()` that creates `doctor.Options` and calls `doctor.Run()`. Format output as table to stdout. @@ -182,7 +182,7 @@ ### 4D — Setup Command (US5) -- [x] T071 [US5] Create `cmd/replicator/setup.go` — implement `setupCmd() *cobra.Command` and `runSetup()`. Create config dir `~/.config/swarm-tools/` if not exists, initialize database, verify git is installed. Print status for each step. +- [x] T071 [US5] Create `cmd/replicator/setup.go` — implement `setupCmd() *cobra.Command` and `runSetup()`. Create config dir `~/.config/uf/replicator/` if not exists, initialize database, verify git is installed. Print status for each step. ### 4E — Wire Up Phase 4 + Checkpoint diff --git a/specs/002-charm-ux/plan.md b/specs/002-charm-ux/plan.md index 1d06fbc..53cb540 100644 --- a/specs/002-charm-ux/plan.md +++ b/specs/002-charm-ux/plan.md @@ -183,10 +183,10 @@ entangling log setup with the style refactoring. | # | Task | Files | FR | Test Strategy | |---|------|-------|----|---------------| | 4.1 | Create log setup in `cmd/replicator/serve.go`: configure `charmbracelet/log` with stderr + file multi-writer | `cmd/replicator/serve.go` | FR-007, FR-008 | Unit: verify log file creation + truncation | -| 4.2 | Create `.unbound-force/` directory on serve startup (0o755) | `cmd/replicator/serve.go` | FR-007 | Unit: verify directory creation in `t.TempDir()` | +| 4.2 | Create `.uf/replicator/` directory on serve startup (0o755) | `cmd/replicator/serve.go` | FR-007 | Unit: verify directory creation in `t.TempDir()` | | 4.3 | Handle log file creation failure: warn to stderr, continue without file | `cmd/replicator/serve.go` | FR-011 | Unit: read-only dir -> no crash, warning emitted | | 4.4 | Add tool call logging to `internal/mcp/server.go`: log tool name, duration, success/error | `internal/mcp/server.go` | FR-009 | Unit: mock logger, verify log entries per tool call | -| 4.5 | Verify CLI commands do NOT create log file | `cmd/replicator/doctor.go` (no change) | FR-010 | Unit: run doctor, verify no `.unbound-force/replicator.log` | +| 4.5 | Verify CLI commands do NOT create log file | `cmd/replicator/doctor.go` (no change) | FR-010 | Unit: run doctor, verify no `.uf/replicator/replicator.log` | | 4.6 | Test log truncation: restart serve, verify file contains only new session entries | — | FR-008 | Integration: write marker, restart, verify marker absent | **Phase Gate**: `go test ./...` passes. `replicator serve` creates log file. CLI commands do not. diff --git a/specs/002-charm-ux/quickstart.md b/specs/002-charm-ux/quickstart.md index f738003..c22d017 100644 --- a/specs/002-charm-ux/quickstart.md +++ b/specs/002-charm-ux/quickstart.md @@ -164,11 +164,11 @@ SERVER_PID=$! echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | ./replicator serve 2>/dev/null # Check log file -cat .unbound-force/replicator.log +cat .uf/replicator/replicator.log ``` Expected: -- `.unbound-force/` directory created +- `.uf/replicator/` directory created - `replicator.log` contains structured entries - Each tool call logged with name + duration @@ -176,14 +176,14 @@ Expected: ```bash # Add a marker to the log -echo "MARKER_OLD_SESSION" >> .unbound-force/replicator.log +echo "MARKER_OLD_SESSION" >> .uf/replicator/replicator.log # Restart serve ./replicator serve & # ... send a request ... # Verify marker is gone -grep -c "MARKER_OLD_SESSION" .unbound-force/replicator.log +grep -c "MARKER_OLD_SESSION" .uf/replicator/replicator.log ``` Expected: 0 matches — log was truncated on startup. @@ -191,9 +191,9 @@ Expected: 0 matches — log was truncated on startup. ### Step 16: CLI Commands Don't Create Log ```bash -rm -f .unbound-force/replicator.log +rm -f .uf/replicator/replicator.log ./replicator doctor -ls .unbound-force/replicator.log 2>&1 +ls .uf/replicator/replicator.log 2>&1 ``` Expected: "No such file or directory" — CLI commands don't create log files. @@ -222,4 +222,4 @@ Expected: Markdown output, no lipgloss styling (docs is excluded per spec). | MCP responses contain ANSI | Logger writing to stdout | Logger must use `os.Stderr` + file, never stdout | | `NO_COLOR` not respected | Old lipgloss version | Verify `go.sum` has lipgloss v1.1.0+ | | Table too wide for terminal | No width constraint | Use `table.Width(termenv.Width())` | -| Log file not created | `.unbound-force/` doesn't exist | `os.MkdirAll` on serve startup | +| Log file not created | `.uf/replicator/` doesn't exist | `os.MkdirAll` on serve startup | diff --git a/specs/002-charm-ux/research.md b/specs/002-charm-ux/research.md index 7009a2f..7b51417 100644 --- a/specs/002-charm-ux/research.md +++ b/specs/002-charm-ux/research.md @@ -293,7 +293,7 @@ func TestServeLogging_CreatesLogFile(t *testing.T) { │ [charmbracelet/log] │ │ │ │ │ │ ▼ ▼ │ -│ stderr .unbound-force/ │ +│ stderr .uf/replicator/ │ │ replicator.log │ └─────────────────────────────────────────────┘ ``` diff --git a/specs/002-charm-ux/spec.md b/specs/002-charm-ux/spec.md index e093159..55fee06 100644 --- a/specs/002-charm-ux/spec.md +++ b/specs/002-charm-ux/spec.md @@ -59,18 +59,18 @@ A developer uses setup, init, version, stats, and query commands and sees visual ### User Story 4 - Structured Logging with Per-Repo Log File (Priority: P2) -When the MCP server runs (`replicator serve`), structured log messages are written to both stderr and a per-repo log file at `[repo]/.unbound-force/replicator.log`. The log file is truncated on each server startup so it only contains the current session's logs. CLI commands (doctor, cells, etc.) log to stderr only -- no log file. +When the MCP server runs (`replicator serve`), structured log messages are written to both stderr and a per-repo log file at `[repo]/.uf/replicator/replicator.log`. The log file is truncated on each server startup so it only contains the current session's logs. CLI commands (doctor, cells, etc.) log to stderr only -- no log file. **Why this priority**: Debugging MCP tool calls is difficult without a persistent log. The per-repo log captures a full session's activity alongside the project it serves, without interleaving with other sessions. -**Independent Test**: Start `replicator serve`, send several MCP requests, stop the server, and verify `[repo]/.unbound-force/replicator.log` contains structured log entries for each tool call. Restart the server and verify the log file is truncated. +**Independent Test**: Start `replicator serve`, send several MCP requests, stop the server, and verify `[repo]/.uf/replicator/replicator.log` contains structured log entries for each tool call. Restart the server and verify the log file is truncated. **Acceptance Scenarios**: -1. **Given** a project directory, **When** `replicator serve` starts, **Then** `.unbound-force/replicator.log` is created (truncating any existing file) and structured log entries begin writing. +1. **Given** a project directory, **When** `replicator serve` starts, **Then** `.uf/replicator/replicator.log` is created (truncating any existing file) and structured log entries begin writing. 2. **Given** the MCP server is running, **When** a `tools/call` request is processed, **Then** a log entry appears in both stderr and the log file with the tool name, duration, and success/error status. 3. **Given** the MCP server is stopped and restarted, **When** the log file is inspected, **Then** it contains only entries from the most recent session (truncated on startup). -4. **Given** a CLI command is run (not `serve`), **When** `replicator doctor` runs, **Then** no `.unbound-force/replicator.log` file is created or modified. +4. **Given** a CLI command is run (not `serve`), **When** `replicator doctor` runs, **Then** no `.uf/replicator/replicator.log` file is created or modified. --- @@ -78,7 +78,7 @@ When the MCP server runs (`replicator serve`), structured log messages are writt - What happens when the terminal does not support colors? The renderer detects the color profile and falls back to plain text automatically. - What happens when the cells table has very long titles? Titles are truncated to fit the terminal width, preserving the table structure. -- What happens when `.unbound-force/` directory does not exist for logging? It is created with `0o755` permissions on `serve` startup. +- What happens when `.uf/replicator/` directory does not exist for logging? It is created with `0o755` permissions on `serve` startup. - What happens when the log file cannot be written (permissions)? A warning is emitted to stderr (via `fmt.Fprintf`, not `charmbracelet/log`, since the logger itself failed to initialize -- bootstrap exception) and the server continues without file logging. - What happens when `NO_COLOR=1` is set? All lipgloss output uses the ASCII color profile, producing no escape codes. This is handled automatically by the renderer. - What happens when two `replicator serve` instances run in the same repo? Only one instance per repository is supported. Concurrent instances will produce interleaved log output in the shared log file. This is a known limitation. @@ -97,7 +97,7 @@ When the MCP server runs (`replicator serve`), structured log messages are writt - **FR-006**: The setup, init, version, stats, and query commands MUST use the shared style system for indicators and headers. #### Logging (US4) -- **FR-007**: The MCP server MUST write structured log messages to both stderr and a per-repo log file at `[repo]/.unbound-force/replicator.log`. +- **FR-007**: The MCP server MUST write structured log messages to both stderr and a per-repo log file at `[repo]/.uf/replicator/replicator.log`. - **FR-008**: The log file MUST be truncated (not appended) on each `replicator serve` startup. - **FR-009**: Each MCP `tools/call` invocation MUST be logged with at minimum: tool name, duration, and success/error status. - **FR-010**: CLI commands (not `serve`) MUST log to stderr only -- no log file creation. @@ -119,7 +119,7 @@ When the MCP server runs (`replicator serve`), structured log messages are writt - **SC-002**: The cells command renders a bordered table with per-row status coloring for 4+ cells in under 50 milliseconds. - **SC-003**: All 9 CLI commands use the shared style system -- zero commands produce raw `fmt.Printf` output in the final implementation. - **SC-004**: When piped (`replicator doctor | cat`), the output contains zero ANSI escape code sequences (verified by `grep -P '\x1b\['`). -- **SC-005**: The MCP server log file at `.unbound-force/replicator.log` contains at least one structured entry per tool call, with tool name and duration. +- **SC-005**: The MCP server log file at `.uf/replicator/replicator.log` contains at least one structured entry per tool call, with tool name and duration. - **SC-006**: Restarting `replicator serve` truncates the log file (file size resets to 0 before new entries). ## Assumptions @@ -127,7 +127,7 @@ When the MCP server runs (`replicator serve`), structured log messages are writt - The existing `internal/doctor/checks.go` separation of health check logic from formatting is preserved -- only the formatting layer changes. - The `cells` command currently dumps raw JSON; it will switch to a human-readable table for TTY and retain JSON output for piped contexts (or with a `--json` flag). - The `docs` command output is markdown (not terminal-styled) and is excluded from lipgloss styling. -- The log file path `.unbound-force/replicator.log` is relative to the working directory where `replicator serve` is launched (typically the project root). +- The log file path `.uf/replicator/replicator.log` is relative to the working directory where `replicator serve` is launched (typically the project root). ## Dependencies diff --git a/specs/002-charm-ux/tasks.md b/specs/002-charm-ux/tasks.md index 95c691e..eb72a70 100644 --- a/specs/002-charm-ux/tasks.md +++ b/specs/002-charm-ux/tasks.md @@ -94,17 +94,17 @@ **Goal**: Add `charmbracelet/log` with per-repo log file for MCP server sessions. -**Independent Test**: Start `replicator serve` → `.unbound-force/replicator.log` created with structured entries. CLI commands do not create log file. +**Independent Test**: Start `replicator serve` → `.uf/replicator/replicator.log` created with structured entries. CLI commands do not create log file. -- [x] T022 [US4] Update `cmd/replicator/serve.go`: on `serveMCP()` entry, create `.unbound-force/` directory with `os.MkdirAll(0o755)`, open `.unbound-force/replicator.log` with `os.Create` (truncate), configure `charmbracelet/log` with `io.MultiWriter(os.Stderr, logFile)` per FR-007, FR-008 +- [x] T022 [US4] Update `cmd/replicator/serve.go`: on `serveMCP()` entry, create `.uf/replicator/` directory with `os.MkdirAll(0o755)`, open `.uf/replicator/replicator.log` with `os.Create` (truncate), configure `charmbracelet/log` with `io.MultiWriter(os.Stderr, logFile)` per FR-007, FR-008 - [x] T023 [US4] Handle log file creation failure in `cmd/replicator/serve.go`: if `os.Create` or `os.MkdirAll` fails, emit warning to stderr via `fmt.Fprintf(os.Stderr, ...)` and continue with stderr-only logger — do not crash per FR-011. Bootstrap exception to CS-008: `charmbracelet/log` cannot be used here because the logger itself is what failed to initialize. Add `defer logFile.Close()` for explicit cleanup. - [x] T024 [US4] Update `internal/mcp/server.go`: add `Logger` field to `Server` struct (interface or `*log.Logger`); update `NewServer` to accept logger; wrap `handleToolsCall` to log tool name, `time.Since(start)` duration, and success/error status per FR-009 - [x] T025 [US4] Update `cmd/replicator/serve.go`: pass configured logger to `mcp.NewServer(reg, Version, logger)` per FR-009 -- [x] T026 [US4] Create `cmd/replicator/serve_test.go`: test that `serveMCP`-style setup in `t.TempDir()` creates `.unbound-force/replicator.log`; test truncation by writing marker, re-creating file, verifying marker absent per FR-007, FR-008 +- [x] T026 [US4] Create `cmd/replicator/serve_test.go`: test that `serveMCP`-style setup in `t.TempDir()` creates `.uf/replicator/replicator.log`; test truncation by writing marker, re-creating file, verifying marker absent per FR-007, FR-008 - [x] T027 [US4] Add test in `cmd/replicator/serve_test.go`: test that log file creation failure (read-only directory) does not panic — logger falls back to stderr-only per FR-011 - [x] T028 [US4] Add test in `internal/mcp/server_test.go`: test that `handleToolsCall` with a mock logger records tool name and duration for a successful call and an error call per FR-009 -**Checkpoint**: `go test ./... -count=1 -race` passes. `replicator serve` creates log file. CLI commands (doctor, cells, etc.) do not create `.unbound-force/replicator.log`. +**Checkpoint**: `go test ./... -count=1 -race` passes. `replicator serve` creates log file. CLI commands (doctor, cells, etc.) do not create `.uf/replicator/replicator.log`. ---