diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 60be0ff..c238d39 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -15,7 +15,7 @@ or synchronous interaction. - Tool outputs MUST be self-describing JSON with enough context for any consumer to interpret without consulting the producing tool. -- Inter-agent communication MUST use the swarm mail +- Inter-agent communication MUST use the comms messaging system, not ad-hoc coordination. **Rationale**: A swarm of autonomous agents cannot rely on @@ -29,8 +29,8 @@ Replicator MUST be independently installable and usable without any other hero being present. Optional integrations MUST degrade gracefully. -- The binary MUST deliver core value (hive, swarm mail, - orchestration) when deployed alone with no external +- The binary MUST deliver core value (org, comms, + forge) when deployed alone with no external services running. - Dewey integration MUST degrade gracefully: when Dewey is unavailable, memory tools return structured diff --git a/.uf/replicator/replicator.log b/.uf/replicator/replicator.log new file mode 100644 index 0000000..e69de29 diff --git a/AGENTS.md b/AGENTS.md index bde2346..a32134f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,19 +30,19 @@ Use in-memory databases for tests (`db.OpenMemory()`). Tools are registered via the `registry` package and served over stdio JSON-RPC. Each tool has: -- A name (e.g., `hive_cells`) +- A name (e.g., `org_cells`) - A description - A JSON schema for arguments - An execute function -### Naming Convention: The Hive Metaphor +### Naming Convention | Concept | Name | |---------|------| -| Work items | **Hive** | +| Work items | **Org** | | Individual item | **Cell** | -| Agent coordination | **Swarm** | -| Messaging | **Swarm Mail** | +| Agent coordination | **Forge** | +| Messaging | **Comms** | | Parallel workers | **Workers** | | Task orchestrator | **Coordinator** | | File locks | **Reservations** | @@ -55,7 +55,7 @@ core principles: 1. **I. Autonomous Collaboration**: Tools are callable independently via MCP. Outputs are self-describing JSON. - Inter-agent communication uses swarm mail. + Inter-agent communication uses comms. 2. **II. Composability First**: The binary works standalone. Dewey integration degrades gracefully. Database schema is compatible with cyborg-swarm. @@ -227,10 +227,10 @@ make install # Install to GOPATH/bin | Command | Purpose | |---------|---------| -| `replicator init` | Per-repo setup: creates `.uf/replicator/` with empty `cells.json` | +| `replicator init` | Per-repo setup: creates `.uf/replicator/` with empty `cells.json` + scaffolds agent kit | | `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 cells` | List org cells (work items) | | `replicator doctor` | Check environment health | | `replicator stats` | Display activity summary | | `replicator query` | Run preset SQL analytics queries | @@ -242,11 +242,12 @@ make install # Install to GOPATH/bin ``` cmd/replicator/ CLI entrypoint (cobra) internal/ + agentkit/ Embedded agent kit (commands, skills, agents) config/ Configuration db/ SQLite + migrations (7 tables) - hive/ Cell domain logic (CRUD, epics, sessions, sync) - swarmmail/ Agent messaging + file reservations - swarm/ Orchestration (decompose, spawn, worktree, review, insights) + org/ Cell domain logic (CRUD, epics, sessions, sync) + comms/ Agent messaging + file reservations + forge/ Orchestration (decompose, spawn, worktree, review, insights) memory/ Dewey proxy + deprecated tool stubs gitutil/ Git worktree operations (os/exec) doctor/ Health check engine @@ -256,9 +257,9 @@ internal/ ui/ Centralized lipgloss styles + table helpers tools/ registry/ Tool registration framework - hive/ Hive tool handlers (11 tools) - swarmmail/ Swarm mail tool handlers (10 tools) - swarm/ Swarm tool handlers (24 tools) + org/ Org tool handlers (11 tools) + comms/ Comms tool handlers (10 tools) + forge/ Forge tool handlers (24 tools) memory/ Memory tool handlers (8 tools) test/parity/ Shape comparison engine + fixtures ``` @@ -273,6 +274,7 @@ originally by [Joel Hooks](https://github.com/joelhooks). - 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) +- Go 1.25+ + cobra (CLI), modernc.org/sqlite (pure Go SQLite), embed (stdlib) (003-rename-terminology) ## Recent Changes - 001-go-rewrite-phases: Added Go 1.25+ + `cobra` (CLI), `modernc.org/sqlite` (pure Go SQLite), stdlib `encoding/json` (MCP JSON-RPC), stdlib `os/exec` (git operations) diff --git a/README.md b/README.md index b2873a9..596d709 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ Multi-agent coordination for AI coding agents. Single Go binary, zero runtime de All 5 implementation phases are complete: - [x] Phase 0: MCP server, SQLite, tool registry -- [x] Phase 1: Hive (11 tools) + Swarm Mail (10 tools) -- [x] Phase 2: Swarm Orchestration (24 tools) +- [x] Phase 1: Org (11 tools) + Comms (10 tools) +- [x] Phase 2: Forge Orchestration (24 tools) - [x] Phase 3: Memory / Dewey proxy (8 tools) - [x] Phase 4: CLI (9 commands) - [x] Phase 5: Parity testing (100% shape match) @@ -76,9 +76,9 @@ Replicator exposes 53 tools via the [MCP protocol](https://modelcontextprotocol. | Category | Tools | Purpose | |----------|-------|---------| -| **Hive** | 11 | Work item tracking: create, query, update, close, epics, sessions, sync | -| **Swarm Mail** | 10 | Agent messaging: send, inbox, ack, file reservations | -| **Swarm** | 24 | Orchestration: decompose, spawn, worktrees, progress, review, insights | +| **Org** | 11 | Work item tracking: create, query, update, close, epics, sessions, sync | +| **Comms** | 10 | Agent messaging: send, inbox, ack, file reservations | +| **Forge** | 24 | Orchestration: decompose, spawn, worktrees, progress, review, insights | | **Memory** | 8 | Dewey proxy: store/find learnings, deprecated tool stubs | See the full [Tool Reference](docs/tools.md) for schemas and examples. @@ -127,7 +127,7 @@ flowchart LR Agent["AI Agent\n(OpenCode, Claude)"] MCP["MCP Server\n(stdio JSON-RPC)"] Reg["Tool Registry\n(53 tools)"] - Domain["Domain Logic\n(hive, swarm, mail)"] + Domain["Domain Logic\n(org, forge, comms)"] DB["SQLite\n(WAL mode)"] Dewey["Dewey\n(semantic memory)"] Git["Git\n(worktrees)"] @@ -145,11 +145,12 @@ flowchart LR ``` cmd/replicator/ CLI entrypoint (cobra) internal/ + agentkit/ Embedded agent kit (commands, skills, agents) config/ Configuration (env vars, defaults) db/ SQLite + migrations (7 tables) - hive/ Cell CRUD, epics, sessions, sync - swarmmail/ Agent messaging, file reservations - swarm/ Decomposition, spawning, worktrees, review, insights + org/ Cell CRUD, epics, sessions, sync + comms/ Agent messaging, file reservations + forge/ Decomposition, spawning, worktrees, review, insights memory/ Dewey proxy, deprecated tool stubs gitutil/ Git worktree operations (os/exec) doctor/ Health check engine @@ -159,9 +160,9 @@ internal/ ui/ Centralized lipgloss styles + table helpers tools/ registry/ Tool registration framework - hive/ Hive tool handlers (11) - swarmmail/ Swarm mail tool handlers (10) - swarm/ Swarm tool handlers (24) + org/ Org tool handlers (11) + comms/ Comms tool handlers (10) + forge/ Forge tool handlers (24) memory/ Memory tool handlers (8) test/parity/ Shape comparison engine + fixtures docs/ Generated tool reference diff --git a/cmd/replicator/cells.go b/cmd/replicator/cells.go index 256b603..2fd0b47 100644 --- a/cmd/replicator/cells.go +++ b/cmd/replicator/cells.go @@ -7,7 +7,7 @@ import ( "github.com/unbound-force/replicator/internal/config" "github.com/unbound-force/replicator/internal/db" - "github.com/unbound-force/replicator/internal/hive" + "github.com/unbound-force/replicator/internal/org" ) // jsonOutput controls whether cells are printed as JSON or a styled table. @@ -22,7 +22,7 @@ func listCells(cfg *config.Config) error { } defer store.Close() - cells, err := hive.QueryCells(store, hive.CellQuery{}) + cells, err := org.QueryCells(store, org.CellQuery{}) if err != nil { return fmt.Errorf("query cells: %w", err) } @@ -36,5 +36,5 @@ func listCells(cfg *config.Config) error { return nil } - return hive.FormatCells(cells, os.Stdout) + return org.FormatCells(cells, os.Stdout) } diff --git a/cmd/replicator/docs.go b/cmd/replicator/docs.go index 67947b4..df84e95 100644 --- a/cmd/replicator/docs.go +++ b/cmd/replicator/docs.go @@ -11,11 +11,11 @@ import ( "github.com/spf13/cobra" "github.com/unbound-force/replicator/internal/db" "github.com/unbound-force/replicator/internal/memory" - "github.com/unbound-force/replicator/internal/tools/hive" + commstools "github.com/unbound-force/replicator/internal/tools/comms" + forgetools "github.com/unbound-force/replicator/internal/tools/forge" memorytools "github.com/unbound-force/replicator/internal/tools/memory" + "github.com/unbound-force/replicator/internal/tools/org" "github.com/unbound-force/replicator/internal/tools/registry" - swarmtools "github.com/unbound-force/replicator/internal/tools/swarm" - swarmmailtools "github.com/unbound-force/replicator/internal/tools/swarmmail" ) func docsCmd() *cobra.Command { @@ -26,7 +26,7 @@ func docsCmd() *cobra.Command { Long: `Generates a markdown document listing all registered MCP tools with their names, descriptions, and argument schemas. -Output is grouped by category: Hive, Swarm Mail, Swarm, Memory.`, +Output is grouped by category: Org, Comms, Forge, Memory.`, RunE: func(cmd *cobra.Command, args []string) error { return runDocs(outputFlag) }, @@ -45,9 +45,9 @@ func runDocs(outputPath string) error { defer store.Close() reg := registry.New() - hive.Register(reg, store) - swarmmailtools.Register(reg, store) - swarmtools.Register(reg, store) + org.Register(reg, store) + commstools.Register(reg, store) + forgetools.Register(reg, store) memClient := memory.NewClient("http://localhost:3333/mcp/") memorytools.Register(reg, memClient) @@ -69,9 +69,9 @@ var categories = []struct { prefix string name string }{ - {"hive_", "Hive"}, - {"swarmmail_", "Swarm Mail"}, - {"swarm_", "Swarm"}, + {"org_", "Org"}, + {"comms_", "Comms"}, + {"forge_", "Forge"}, {"hivemind_", "Memory"}, } diff --git a/cmd/replicator/docs_test.go b/cmd/replicator/docs_test.go index cd2f2bd..4bc76fb 100644 --- a/cmd/replicator/docs_test.go +++ b/cmd/replicator/docs_test.go @@ -7,11 +7,11 @@ import ( "github.com/unbound-force/replicator/internal/db" "github.com/unbound-force/replicator/internal/memory" - "github.com/unbound-force/replicator/internal/tools/hive" + commstools "github.com/unbound-force/replicator/internal/tools/comms" + forgetools "github.com/unbound-force/replicator/internal/tools/forge" memorytools "github.com/unbound-force/replicator/internal/tools/memory" + "github.com/unbound-force/replicator/internal/tools/org" "github.com/unbound-force/replicator/internal/tools/registry" - swarmtools "github.com/unbound-force/replicator/internal/tools/swarm" - swarmmailtools "github.com/unbound-force/replicator/internal/tools/swarmmail" ) func buildFullRegistry(t *testing.T) *registry.Registry { @@ -23,9 +23,9 @@ func buildFullRegistry(t *testing.T) *registry.Registry { t.Cleanup(func() { store.Close() }) reg := registry.New() - hive.Register(reg, store) - swarmmailtools.Register(reg, store) - swarmtools.Register(reg, store) + org.Register(reg, store) + commstools.Register(reg, store) + forgetools.Register(reg, store) memClient := memory.NewClient("http://localhost:3333/mcp/") memorytools.Register(reg, memClient) return reg @@ -56,7 +56,7 @@ func TestWriteDocs_HasCategoryHeaders(t *testing.T) { writeDocs(&buf, reg) output := buf.String() - for _, header := range []string{"## Hive", "## Swarm Mail", "## Swarm", "## Memory"} { + for _, header := range []string{"## Org", "## Comms", "## Forge", "## Memory"} { if !strings.Contains(output, header) { t.Errorf("missing category header: %q", header) } diff --git a/cmd/replicator/init.go b/cmd/replicator/init.go index 8fddc5e..2730eb8 100644 --- a/cmd/replicator/init.go +++ b/cmd/replicator/init.go @@ -6,50 +6,75 @@ import ( "path/filepath" "github.com/spf13/cobra" + "github.com/unbound-force/replicator/internal/agentkit" "github.com/unbound-force/replicator/internal/ui" ) func initCmd() *cobra.Command { var pathFlag string + var forceFlag bool cmd := &cobra.Command{ Use: "init", - Short: "Initialize a project directory for swarm operations", - Long: `Creates a .uf/replicator/ directory with an empty cells.json in the target -directory. Idempotent — safe to run multiple times. + Short: "Initialize a project directory for project operations", + Long: `Creates a .uf/replicator/ directory with an empty cells.json and scaffolds +the agent kit into .opencode/ (commands, skills, and agent definitions). + +Idempotent — safe to run multiple times. Existing agent kit files are +skipped unless --force is used. This is the per-repo initialization command. It does not require the global database (replicator setup) or any external services.`, RunE: func(cmd *cobra.Command, args []string) error { - return runInit(pathFlag) + return runInit(pathFlag, forceFlag) }, } - cmd.Flags().StringVar(&pathFlag, "path", ".", "Target directory for .uf/replicator/ initialization") + cmd.Flags().StringVar(&pathFlag, "path", ".", "Target directory for initialization") + cmd.Flags().BoolVar(&forceFlag, "force", false, "Overwrite existing agent kit files") return cmd } -// 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 { +// runInit creates the .uf/replicator/ directory, seeds cells.json, and +// scaffolds the agent kit into .opencode/. Uses styled output: green for +// created, dim for skipped, yellow for overwritten. +func runInit(targetDir string, force bool) error { styles := ui.NewStyles(os.Stdout) - hiveDir := filepath.Join(targetDir, ".uf", "replicator") + replicatorDir := filepath.Join(targetDir, ".uf", "replicator") + + // Create .uf/replicator/ directory (idempotent). + cellsPath := filepath.Join(replicatorDir, "cells.json") + if _, err := os.Stat(cellsPath); err != nil { + // cells.json doesn't exist — create directory and file. + if err := os.MkdirAll(replicatorDir, 0o755); err != nil { + return fmt.Errorf("create .uf/replicator directory: %w", err) + } + if err := os.WriteFile(cellsPath, []byte("[]\n"), 0o644); err != nil { + return fmt.Errorf("write cells.json: %w", err) + } + fmt.Println(styles.Pass.Render("created .uf/replicator/cells.json")) + } else { + fmt.Println(styles.Dim.Render("skipped .uf/replicator/cells.json (exists)")) + } - // Check if already initialized. - if info, err := os.Stat(hiveDir); err == nil && info.IsDir() { - fmt.Println(styles.Dim.Render("already initialized")) - return nil + // Scaffold agent kit into .opencode/. + if force { + fmt.Println(styles.Warn.Render("--force: existing agent kit files will be overwritten")) } - // Create .uf/replicator/ directory. - if err := os.MkdirAll(hiveDir, 0o755); err != nil { - return fmt.Errorf("create .uf/replicator directory: %w", err) + results, err := agentkit.Scaffold(targetDir, force) + if err != nil { + return fmt.Errorf("scaffold agent kit: %w", err) } - // Write empty cells.json. - cellsPath := filepath.Join(hiveDir, "cells.json") - if err := os.WriteFile(cellsPath, []byte("[]\n"), 0o644); err != nil { - return fmt.Errorf("write cells.json: %w", err) + for _, r := range results { + switch r.Action { + case "created": + fmt.Println(styles.Pass.Render(fmt.Sprintf("created .opencode/%s", r.Path))) + case "skipped": + fmt.Println(styles.Dim.Render(fmt.Sprintf("skipped .opencode/%s (exists)", r.Path))) + case "overwritten": + fmt.Println(styles.Warn.Render(fmt.Sprintf("overwritten .opencode/%s", r.Path))) + } } - 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 c492281..d476b4a 100644 --- a/cmd/replicator/init_test.go +++ b/cmd/replicator/init_test.go @@ -8,12 +8,13 @@ import ( func TestRunInit_FreshDirectory(t *testing.T) { dir := t.TempDir() - if err := runInit(dir); err != nil { + if err := runInit(dir, false); err != nil { t.Fatalf("runInit: %v", err) } - hiveDir := filepath.Join(dir, ".uf", "replicator") - info, err := os.Stat(hiveDir) + // Verify .uf/replicator/ directory created. + replicatorDir := filepath.Join(dir, ".uf", "replicator") + info, err := os.Stat(replicatorDir) if err != nil { t.Fatalf(".uf/replicator/ not created: %v", err) } @@ -21,7 +22,8 @@ func TestRunInit_FreshDirectory(t *testing.T) { t.Fatal(".uf/replicator/ is not a directory") } - cellsPath := filepath.Join(hiveDir, "cells.json") + // Verify cells.json created with empty array. + cellsPath := filepath.Join(replicatorDir, "cells.json") data, err := os.ReadFile(cellsPath) if err != nil { t.Fatalf("cells.json not created: %v", err) @@ -29,13 +31,84 @@ func TestRunInit_FreshDirectory(t *testing.T) { if string(data) != "[]\n" { t.Errorf("cells.json content = %q, want %q", string(data), "[]\n") } + + // Verify agent kit files created (16 total: 1 cells.json + 15 agent kit). + agentKitFiles := []string{ + ".opencode/command/forge.md", + ".opencode/command/org.md", + ".opencode/command/inbox.md", + ".opencode/command/forge-status.md", + ".opencode/command/handoff.md", + ".opencode/skills/always-on-guidance/SKILL.md", + ".opencode/skills/forge-coordination/SKILL.md", + ".opencode/skills/replicator-cli/SKILL.md", + ".opencode/skills/testing-patterns/SKILL.md", + ".opencode/skills/system-design/SKILL.md", + ".opencode/skills/learning-systems/SKILL.md", + ".opencode/skills/forge-global/SKILL.md", + ".opencode/agents/coordinator.md", + ".opencode/agents/worker.md", + ".opencode/agents/background-worker.md", + } + for _, rel := range agentKitFiles { + full := filepath.Join(dir, rel) + if _, err := os.Stat(full); err != nil { + t.Errorf("expected agent kit file %s to exist: %v", rel, err) + } + } +} + +func TestRunInit_AgentKitSkipsExisting(t *testing.T) { + dir := t.TempDir() + + // Pre-create a file that init would scaffold. + forgePath := filepath.Join(dir, ".opencode", "command", "forge.md") + os.MkdirAll(filepath.Dir(forgePath), 0o755) + original := []byte("# my custom forge\n") + os.WriteFile(forgePath, original, 0o644) + + if err := runInit(dir, false); err != nil { + t.Fatalf("runInit: %v", err) + } + + // Verify the pre-existing file was NOT overwritten. + data, _ := os.ReadFile(forgePath) + if string(data) != string(original) { + t.Errorf("forge.md was overwritten: got %q, want %q", string(data), string(original)) + } + + // Verify other agent kit files were still created. + orgPath := filepath.Join(dir, ".opencode", "command", "org.md") + if _, err := os.Stat(orgPath); err != nil { + t.Errorf("org.md should have been created: %v", err) + } +} + +func TestRunInit_ForceOverwrites(t *testing.T) { + dir := t.TempDir() + + // Pre-create a file that init would scaffold. + forgePath := filepath.Join(dir, ".opencode", "command", "forge.md") + os.MkdirAll(filepath.Dir(forgePath), 0o755) + original := []byte("# my custom forge\n") + os.WriteFile(forgePath, original, 0o644) + + if err := runInit(dir, true); err != nil { + t.Fatalf("runInit with force: %v", err) + } + + // Verify the pre-existing file WAS overwritten. + data, _ := os.ReadFile(forgePath) + if string(data) == string(original) { + t.Error("forge.md was NOT overwritten despite force=true") + } } func TestRunInit_AlreadyInitialized(t *testing.T) { dir := t.TempDir() // First init. - if err := runInit(dir); err != nil { + if err := runInit(dir, false); err != nil { t.Fatalf("first runInit: %v", err) } @@ -44,7 +117,7 @@ func TestRunInit_AlreadyInitialized(t *testing.T) { os.WriteFile(cellsPath, []byte(`[{"id":"test"}]`), 0o644) // Second init — should be idempotent. - if err := runInit(dir); err != nil { + if err := runInit(dir, false); err != nil { t.Fatalf("second runInit: %v", err) } @@ -53,6 +126,12 @@ func TestRunInit_AlreadyInitialized(t *testing.T) { if string(data) != `[{"id":"test"}]` { t.Errorf("cells.json was overwritten: got %q", string(data)) } + + // Verify agent kit files still exist (scaffolded on first run, skipped on second). + forgePath := filepath.Join(dir, ".opencode", "command", "forge.md") + if _, err := os.Stat(forgePath); err != nil { + t.Errorf("agent kit files should exist after second init: %v", err) + } } func TestRunInit_CustomPath(t *testing.T) { @@ -60,7 +139,7 @@ func TestRunInit_CustomPath(t *testing.T) { target := filepath.Join(parent, "myproject") os.MkdirAll(target, 0o755) - if err := runInit(target); err != nil { + if err := runInit(target, false); err != nil { t.Fatalf("runInit with custom path: %v", err) } @@ -68,10 +147,15 @@ func TestRunInit_CustomPath(t *testing.T) { if _, err := os.Stat(cellsPath); err != nil { t.Fatalf("cells.json not created at custom path: %v", err) } + + forgePath := filepath.Join(target, ".opencode", "command", "forge.md") + if _, err := os.Stat(forgePath); err != nil { + t.Fatalf("agent kit not created at custom path: %v", err) + } } func TestRunInit_InvalidPath(t *testing.T) { - err := runInit("/nonexistent/path/that/cannot/exist") + err := runInit("/nonexistent/path/that/cannot/exist", false) if err == nil { t.Fatal("expected error for invalid path") } diff --git a/cmd/replicator/serve.go b/cmd/replicator/serve.go index 7622676..a401a78 100644 --- a/cmd/replicator/serve.go +++ b/cmd/replicator/serve.go @@ -12,11 +12,11 @@ import ( "github.com/unbound-force/replicator/internal/db" "github.com/unbound-force/replicator/internal/mcp" "github.com/unbound-force/replicator/internal/memory" - "github.com/unbound-force/replicator/internal/tools/hive" + commstools "github.com/unbound-force/replicator/internal/tools/comms" + forgetools "github.com/unbound-force/replicator/internal/tools/forge" memorytools "github.com/unbound-force/replicator/internal/tools/memory" + "github.com/unbound-force/replicator/internal/tools/org" "github.com/unbound-force/replicator/internal/tools/registry" - swarmtools "github.com/unbound-force/replicator/internal/tools/swarm" - swarmmailtools "github.com/unbound-force/replicator/internal/tools/swarmmail" ) // serveMCP starts the MCP JSON-RPC server on stdio. @@ -37,9 +37,9 @@ func serveMCP() error { defer store.Close() reg := registry.New() - hive.Register(reg, store) - swarmmailtools.Register(reg, store) - swarmtools.Register(reg, store) + org.Register(reg, store) + commstools.Register(reg, store) + forgetools.Register(reg, store) // Memory tools proxy to Dewey for semantic search. memClient := memory.NewClient(cfg.DeweyURL) diff --git a/docs/tools.md b/docs/tools.md index b42ab17..daffdff 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -2,64 +2,11 @@ Auto-generated by `replicator docs`. 53 tools registered. -> Regenerate this file: `replicator docs --output docs/tools.md` +## Org (11 tools) -## Quick Examples +### `org_cells` -### Create a work item - -```json -{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{ - "name": "hive_create", - "arguments": {"title": "Fix login bug", "type": "bug", "priority": 2} -}} -``` - -### Send a message between agents - -```json -{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{ - "name": "swarmmail_send", - "arguments": { - "to": ["worker-1"], - "subject": "Task assigned", - "body": "Please implement the login fix", - "importance": "high" - } -}} -``` - -### Decompose a task - -```json -{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{ - "name": "swarm_decompose", - "arguments": { - "task": "Add user authentication with OAuth2", - "max_subtasks": 5 - } -}} -``` - -### Store a learning - -```json -{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{ - "name": "hivemind_store", - "arguments": { - "information": "SQLite WAL mode requires single-writer concurrency", - "tags": "sqlite,gotcha,concurrency" - } -}} -``` - ---- - -## Hive (11 tools) - -### `hive_cells` - -Query cells from the hive database with flexible filtering. Use to list work items, find by status/type, or look up a cell by partial ID. +Query cells from the org database with flexible filtering. Use to list work items, find by status/type, or look up a cell by partial ID. ```json { @@ -100,7 +47,7 @@ Query cells from the hive database with flexible filtering. Use to list work ite } ``` -### `hive_close` +### `org_close` Close a cell with a reason. @@ -122,9 +69,9 @@ Close a cell with a reason. } ``` -### `hive_create` +### `org_create` -Create a new cell (work item) in the hive. +Create a new cell (work item) in the org. ```json { @@ -161,7 +108,7 @@ Create a new cell (work item) in the hive. } ``` -### `hive_create_epic` +### `org_create_epic` Create an epic with subtasks in one atomic operation. @@ -208,9 +155,9 @@ Create an epic with subtasks in one atomic operation. } ``` -### `hive_query` +### `org_query` -Query cells with filters (alias for hive_cells). +Query cells with filters (alias for org_cells). ```json { @@ -248,7 +195,7 @@ Query cells with filters (alias for hive_cells). } ``` -### `hive_ready` +### `org_ready` Get the next ready cell (unblocked, highest priority). @@ -259,7 +206,7 @@ Get the next ready cell (unblocked, highest priority). } ``` -### `hive_session_end` +### `org_session_end` End current session with handoff notes for next session. @@ -274,7 +221,7 @@ End current session with handoff notes for next session. } ``` -### `hive_session_start` +### `org_session_start` Start a new work session. Returns previous session's handoff notes if available. @@ -289,7 +236,7 @@ Start a new work session. Returns previous session's handoff notes if available. } ``` -### `hive_start` +### `org_start` Mark a cell as in-progress. @@ -307,7 +254,7 @@ Mark a cell as in-progress. } ``` -### `hive_sync` +### `org_sync` Sync cells to git and push. @@ -326,7 +273,7 @@ Sync cells to git and push. } ``` -### `hive_update` +### `org_update` Update a cell's status, description, or priority. @@ -361,9 +308,9 @@ Update a cell's status, description, or priority. } ``` -## Swarm Mail (10 tools) +## Comms (10 tools) -### `swarmmail_ack` +### `comms_ack` Acknowledge a message. @@ -381,9 +328,9 @@ Acknowledge a message. } ``` -### `swarmmail_health` +### `comms_health` -Check swarm mail database health. +Check comms database health. ```json { @@ -392,7 +339,7 @@ Check swarm mail database health. } ``` -### `swarmmail_inbox` +### `comms_inbox` Fetch inbox (max 5 messages, bodies excluded). @@ -411,9 +358,9 @@ Fetch inbox (max 5 messages, bodies excluded). } ``` -### `swarmmail_init` +### `comms_init` -Initialize swarm mail session. Registers the agent. +Initialize comms session. Registers the agent. ```json { @@ -435,7 +382,7 @@ Initialize swarm mail session. Registers the agent. } ``` -### `swarmmail_read_message` +### `comms_read_message` Fetch one message body by ID. @@ -453,7 +400,7 @@ Fetch one message body by ID. } ``` -### `swarmmail_release` +### `comms_release` Release file reservations. @@ -477,7 +424,7 @@ Release file reservations. } ``` -### `swarmmail_release_agent` +### `comms_release_agent` Release all file reservations for a specific agent. @@ -495,7 +442,7 @@ Release all file reservations for a specific agent. } ``` -### `swarmmail_release_all` +### `comms_release_all` Release all file reservations in the project. @@ -506,7 +453,7 @@ Release all file reservations in the project. } ``` -### `swarmmail_reserve` +### `comms_reserve` Reserve file paths for exclusive editing. @@ -536,9 +483,9 @@ Reserve file paths for exclusive editing. } ``` -### `swarmmail_send` +### `comms_send` -Send a message to other agents via swarm mail. +Send a message to other agents via comms. ```json { @@ -580,9 +527,9 @@ Send a message to other agents via swarm mail. } ``` -## Swarm (24 tools) +## Forge (24 tools) -### `swarm_adversarial_review` +### `forge_adversarial_review` VDD-style adversarial code review using hostile, fresh-context agent. @@ -603,7 +550,7 @@ VDD-style adversarial code review using hostile, fresh-context agent. } ``` -### `swarm_broadcast` +### `forge_broadcast` Broadcast context update to all agents working on the same epic. @@ -647,7 +594,7 @@ Broadcast context update to all agents working on the same epic. } ``` -### `swarm_complete` +### `forge_complete` Mark subtask complete with Verification Gate. @@ -692,7 +639,7 @@ Mark subtask complete with Verification Gate. } ``` -### `swarm_complete_subtask` +### `forge_complete_subtask` Handle subtask completion after Task agent returns. @@ -720,7 +667,7 @@ Handle subtask completion after Task agent returns. } ``` -### `swarm_decompose` +### `forge_decompose` Generate decomposition prompt for breaking task into subtasks. @@ -747,7 +694,7 @@ Generate decomposition prompt for breaking task into subtasks. } ``` -### `swarm_evaluation_prompt` +### `forge_evaluation_prompt` Generate self-evaluation prompt for a completed subtask. @@ -776,7 +723,7 @@ Generate self-evaluation prompt for a completed subtask. } ``` -### `swarm_get_file_insights` +### `forge_get_file_insights` Get file-specific gotchas for worker context. @@ -797,9 +744,9 @@ Get file-specific gotchas for worker context. } ``` -### `swarm_get_pattern_insights` +### `forge_get_pattern_insights` -Get common failure patterns across swarms. +Get common failure patterns across forges. ```json { @@ -808,7 +755,7 @@ Get common failure patterns across swarms. } ``` -### `swarm_get_strategy_insights` +### `forge_get_strategy_insights` Get strategy success rates for decomposition planning. @@ -826,7 +773,7 @@ Get strategy success rates for decomposition planning. } ``` -### `swarm_init` +### `forge_init` Initialize swarm session and check tool availability. @@ -848,7 +795,7 @@ Initialize swarm session and check tool availability. } ``` -### `swarm_plan_prompt` +### `forge_plan_prompt` Generate strategy-specific decomposition prompt. @@ -884,7 +831,7 @@ Generate strategy-specific decomposition prompt. } ``` -### `swarm_progress` +### `forge_progress` Report progress on a subtask to coordinator. @@ -934,7 +881,7 @@ Report progress on a subtask to coordinator. } ``` -### `swarm_record_outcome` +### `forge_record_outcome` Record subtask outcome for implicit feedback scoring. @@ -989,7 +936,7 @@ Record subtask outcome for implicit feedback scoring. } ``` -### `swarm_review` +### `forge_review` Generate a review prompt for a completed subtask. @@ -1021,7 +968,7 @@ Generate a review prompt for a completed subtask. } ``` -### `swarm_review_feedback` +### `forge_review_feedback` Send review feedback to a worker. Tracks attempts (max 3). @@ -1061,7 +1008,7 @@ Send review feedback to a worker. Tracks attempts (max 3). } ``` -### `swarm_select_strategy` +### `forge_select_strategy` Analyze task and recommend decomposition strategy. @@ -1083,7 +1030,7 @@ Analyze task and recommend decomposition strategy. } ``` -### `swarm_spawn_subtask` +### `forge_spawn_subtask` Prepare a subtask for spawning with Task tool. @@ -1122,7 +1069,7 @@ Prepare a subtask for spawning with Task tool. } ``` -### `swarm_status` +### `forge_status` Get status of a swarm by epic ID. @@ -1144,7 +1091,7 @@ Get status of a swarm by epic ID. } ``` -### `swarm_subtask_prompt` +### `forge_subtask_prompt` Generate the prompt for a spawned subtask agent. @@ -1187,7 +1134,7 @@ Generate the prompt for a spawned subtask agent. } ``` -### `swarm_validate_decomposition` +### `forge_validate_decomposition` Validate a decomposition response against CellTreeSchema. @@ -1205,7 +1152,7 @@ Validate a decomposition response against CellTreeSchema. } ``` -### `swarm_worktree_cleanup` +### `forge_worktree_cleanup` Remove a worktree after completion or abort. Idempotent. @@ -1229,7 +1176,7 @@ Remove a worktree after completion or abort. Idempotent. } ``` -### `swarm_worktree_create` +### `forge_worktree_create` Create a git worktree for isolated task execution. @@ -1255,7 +1202,7 @@ Create a git worktree for isolated task execution. } ``` -### `swarm_worktree_list` +### `forge_worktree_list` List all active worktrees for a project. @@ -1273,7 +1220,7 @@ List all active worktrees for a project. } ``` -### `swarm_worktree_merge` +### `forge_worktree_merge` Cherry-pick commits from worktree back to main branch. diff --git a/internal/agentkit/agentkit.go b/internal/agentkit/agentkit.go new file mode 100644 index 0000000..fdc76f9 --- /dev/null +++ b/internal/agentkit/agentkit.go @@ -0,0 +1,68 @@ +// Package agentkit provides embedded agent kit content for scaffolding +// new project directories. The kit includes command definitions, skill +// files, and agent role descriptions that are written to .opencode/ +// during `replicator init`. +package agentkit + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +//go:embed content/* +var content embed.FS + +// ScaffoldResult describes the outcome of writing a single agent kit file. +type ScaffoldResult struct { + Path string `json:"path"` + Action string `json:"action"` // "created", "skipped", "overwritten" +} + +// Scaffold writes the embedded agent kit files to targetDir/.opencode/. +// If force is false, existing files are skipped. If force is true, +// existing files are overwritten. +func Scaffold(targetDir string, force bool) ([]ScaffoldResult, error) { + var results []ScaffoldResult + + err := fs.WalkDir(content, "content", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + + // Strip "content/" prefix to get relative path under .opencode/. + relPath, _ := filepath.Rel("content", path) + destPath := filepath.Join(targetDir, ".opencode", relPath) + + // Check if file exists. + if _, statErr := os.Stat(destPath); statErr == nil { + if !force { + results = append(results, ScaffoldResult{Path: relPath, Action: "skipped"}) + return nil + } + results = append(results, ScaffoldResult{Path: relPath, Action: "overwritten"}) + } else { + results = append(results, ScaffoldResult{Path: relPath, Action: "created"}) + } + + // Create parent directories. + if mkErr := os.MkdirAll(filepath.Dir(destPath), 0o755); mkErr != nil { + return fmt.Errorf("create directory for %s: %w", relPath, mkErr) + } + + // Read from embedded FS and write to disk. + data, readErr := content.ReadFile(path) + if readErr != nil { + return fmt.Errorf("read embedded %s: %w", path, readErr) + } + + return os.WriteFile(destPath, data, 0o644) + }) + + return results, err +} diff --git a/internal/agentkit/agentkit_test.go b/internal/agentkit/agentkit_test.go new file mode 100644 index 0000000..6c90658 --- /dev/null +++ b/internal/agentkit/agentkit_test.go @@ -0,0 +1,151 @@ +package agentkit + +import ( + "os" + "path/filepath" + "testing" +) + +func TestScaffold_FreshDirectory(t *testing.T) { + dir := t.TempDir() + results, err := Scaffold(dir, false) + if err != nil { + t.Fatalf("Scaffold: %v", err) + } + + // Expect 15 files: 5 commands + 7 skills + 3 agents. + if len(results) != 15 { + t.Errorf("Scaffold returned %d results, want 15", len(results)) + for _, r := range results { + t.Logf(" %s: %s", r.Action, r.Path) + } + } + + // All should be "created". + for _, r := range results { + if r.Action != "created" { + t.Errorf("result %s: action = %q, want %q", r.Path, r.Action, "created") + } + } + + // Spot-check a few files exist on disk. + checks := []string{ + ".opencode/command/forge.md", + ".opencode/command/org.md", + ".opencode/command/inbox.md", + ".opencode/command/forge-status.md", + ".opencode/command/handoff.md", + ".opencode/skills/always-on-guidance/SKILL.md", + ".opencode/skills/forge-coordination/SKILL.md", + ".opencode/skills/replicator-cli/SKILL.md", + ".opencode/skills/testing-patterns/SKILL.md", + ".opencode/skills/system-design/SKILL.md", + ".opencode/skills/learning-systems/SKILL.md", + ".opencode/skills/forge-global/SKILL.md", + ".opencode/agents/coordinator.md", + ".opencode/agents/worker.md", + ".opencode/agents/background-worker.md", + } + for _, rel := range checks { + full := filepath.Join(dir, rel) + if _, err := os.Stat(full); err != nil { + t.Errorf("expected file %s to exist: %v", rel, err) + } + } +} + +func TestScaffold_SkipsExisting(t *testing.T) { + dir := t.TempDir() + + // Pre-create a file that Scaffold would write. + forgePath := filepath.Join(dir, ".opencode", "command", "forge.md") + os.MkdirAll(filepath.Dir(forgePath), 0o755) + original := []byte("# custom content\n") + os.WriteFile(forgePath, original, 0o644) + + results, err := Scaffold(dir, false) + if err != nil { + t.Fatalf("Scaffold: %v", err) + } + + // Find the forge.md result — should be "skipped". + var found bool + for _, r := range results { + if r.Path == filepath.Join("command", "forge.md") { + found = true + if r.Action != "skipped" { + t.Errorf("forge.md action = %q, want %q", r.Action, "skipped") + } + } + } + if !found { + t.Error("forge.md not found in results") + } + + // Verify file content was NOT overwritten. + data, _ := os.ReadFile(forgePath) + if string(data) != string(original) { + t.Errorf("forge.md was overwritten: got %q", string(data)) + } +} + +func TestScaffold_ForceOverwrites(t *testing.T) { + dir := t.TempDir() + + // Pre-create a file that Scaffold would write. + forgePath := filepath.Join(dir, ".opencode", "command", "forge.md") + os.MkdirAll(filepath.Dir(forgePath), 0o755) + original := []byte("# custom content\n") + os.WriteFile(forgePath, original, 0o644) + + results, err := Scaffold(dir, true) + if err != nil { + t.Fatalf("Scaffold: %v", err) + } + + // Find the forge.md result — should be "overwritten". + for _, r := range results { + if r.Path == filepath.Join("command", "forge.md") { + if r.Action != "overwritten" { + t.Errorf("forge.md action = %q, want %q", r.Action, "overwritten") + } + } + } + + // Verify file content WAS overwritten with embedded content. + data, _ := os.ReadFile(forgePath) + if string(data) == string(original) { + t.Error("forge.md was NOT overwritten despite force=true") + } +} + +func TestScaffold_FileCount(t *testing.T) { + dir := t.TempDir() + results, err := Scaffold(dir, false) + if err != nil { + t.Fatalf("Scaffold: %v", err) + } + + // Count by category. + var commands, skills, agents int + for _, r := range results { + switch { + case len(r.Path) > 8 && r.Path[:8] == "command/": + commands++ + case len(r.Path) > 7 && r.Path[:7] == "skills/": + skills++ + case len(r.Path) > 7 && r.Path[:7] == "agents/": + agents++ + } + } + + if commands != 5 { + t.Errorf("commands = %d, want 5", commands) + } + if skills != 7 { + t.Errorf("skills = %d, want 7", skills) + } + if agents != 3 { + t.Errorf("agents = %d, want 3", agents) + } +} diff --git a/internal/agentkit/content/agents/background-worker.md b/internal/agentkit/content/agents/background-worker.md new file mode 100644 index 0000000..6cf408d --- /dev/null +++ b/internal/agentkit/content/agents/background-worker.md @@ -0,0 +1,22 @@ +--- +name: background-worker +description: Runs background tasks without MCP tool access. +--- + +# Background Worker + +For tasks that don't need MCP tools: doc edits, formatting, summaries. + +## Constraints + +- No MCP tools available +- No file reservations +- No comms messaging +- Use for static content only + +## Suitable Tasks + +- Documentation updates +- Code formatting +- Report generation +- Static analysis summaries diff --git a/internal/agentkit/content/agents/coordinator.md b/internal/agentkit/content/agents/coordinator.md new file mode 100644 index 0000000..b97e1ba --- /dev/null +++ b/internal/agentkit/content/agents/coordinator.md @@ -0,0 +1,21 @@ +--- +name: coordinator +description: Orchestrates forge coordination and supervises worker agents. +--- + +# Forge Coordinator + +Orchestrates work: decomposes tasks, spawns workers, monitors progress, reviews results. + +## Rules + +- Always initialize comms first (`comms_init`) +- Never reserve files (workers reserve their own) +- Review every worker completion (`forge_review`) +- Store learnings after forge completion (`hivemind_store`) +- Check inbox regularly for blocked workers (`comms_inbox`) +- Use `forge_broadcast` to share context updates with all workers + +## Available Tools + +All `org_*`, `comms_*`, `forge_*`, and `hivemind_*` tools. diff --git a/internal/agentkit/content/agents/worker.md b/internal/agentkit/content/agents/worker.md new file mode 100644 index 0000000..3a70500 --- /dev/null +++ b/internal/agentkit/content/agents/worker.md @@ -0,0 +1,25 @@ +--- +name: worker +description: Executes a single subtask with file reservations and progress reporting. +--- + +# Forge Worker + +Executes scoped subtasks and reports to coordinator. + +## Checklist + +1. `comms_init` — initialize comms first +2. `hivemind_find` — check for prior learnings before coding +3. `comms_reserve` — reserve assigned files exclusively +4. Implement changes to reserved files +5. `forge_progress` — report at 25%, 50%, 75% milestones +6. `hivemind_store` — store any learnings discovered +7. `forge_complete` — mark subtask as done + +## Constraints + +- Only edit files you have reserved +- Report progress at regular intervals +- Store learnings for future agents +- Never modify files outside your assignment diff --git a/internal/agentkit/content/command/forge-status.md b/internal/agentkit/content/command/forge-status.md new file mode 100644 index 0000000..54146d4 --- /dev/null +++ b/internal/agentkit/content/command/forge-status.md @@ -0,0 +1,24 @@ +--- +description: Check forge coordination status - workers, messages, cells +--- + +# /forge:status + +Show active forge state. + +## Workflow + +1. `forge_status(epic_id, project_key)` — worker progress summary +2. `comms_inbox()` — pending messages from workers +3. `org_cells(status="in_progress")` — active cells + +## Interpreting Results + +- **Workers**: Each subtask shows completion percentage and status +- **Messages**: Unread messages may indicate blocked workers +- **Cells**: In-progress cells are actively being worked on + +## Quick Health Check + +Run all three tools to get a complete picture of forge state. +If any workers are blocked, read their messages and respond. diff --git a/internal/agentkit/content/command/forge.md b/internal/agentkit/content/command/forge.md new file mode 100644 index 0000000..a786f04 --- /dev/null +++ b/internal/agentkit/content/command/forge.md @@ -0,0 +1,67 @@ +--- +description: Decompose task into subtasks and coordinate parallel agents +--- + +# /forge + +Decompose a task and spawn parallel workers. + +## Task + +$ARGUMENTS + +## Workflow + +1. Initialize comms: `comms_init(project_path=".", task_description="Forge: ")` +2. Check prior learnings: `hivemind_find(query="")` +3. Decompose: `forge_decompose(task="", context="")` +4. Create epic: `org_create_epic(epic_title="", subtasks=[...])` +5. For each subtask: `forge_spawn_subtask(bead_id, epic_id, subtask_title, files)` +6. Monitor: check `comms_inbox()` every few minutes +7. Review: `forge_review(task_id, files_touched)` for each completed worker +8. Complete: `forge_complete(bead_id, summary, files_touched)` +9. Store learnings: `hivemind_store(information="...", tags="forge,")` + +## Rules + +- Always create a forge, even for small tasks +- Coordinator orchestrates, workers execute +- Workers reserve their own files via `comms_reserve` +- Check inbox regularly for blocked workers +- Review every worker's output before marking complete +- Store learnings after completion + +## Strategy Selection + +Before decomposing, check historical success rates: + +``` +forge_get_strategy_insights(task="") +``` + +Choose from: `file-based`, `feature-based`, `risk-based`, or `auto`. + +## Monitoring + +While workers are active: + +1. `comms_inbox()` — check for messages from workers +2. `forge_status(epic_id, project_key)` — check worker progress +3. `org_cells(status="in_progress")` — see active cells + +## Completion + +After all workers finish: + +1. `forge_complete(bead_id, summary, files_touched)` — mark epic done +2. `forge_record_outcome(bead_id, duration_ms, success)` — record for learning +3. `hivemind_store(information="...", tags="forge,")` — store learnings +4. `org_sync()` — persist state to git + +## Error Recovery + +If a worker is blocked: + +1. Read the worker's message: `comms_read_message(message_id)` +2. Acknowledge: `comms_ack(message_id)` +3. Either unblock or reassign the subtask diff --git a/internal/agentkit/content/command/handoff.md b/internal/agentkit/content/command/handoff.md new file mode 100644 index 0000000..5cc5707 --- /dev/null +++ b/internal/agentkit/content/command/handoff.md @@ -0,0 +1,25 @@ +--- +description: End a session cleanly - release reservations, sync state, generate handoff note +--- + +# /handoff + +Wrap up a session cleanly. + +## Workflow + +1. Summarize completed work and open blockers +2. `comms_release_all()` — free all file reservations +3. `org_update()` / `org_close()` — update cell statuses +4. `org_sync()` — persist state to git +5. `org_session_end(handoff_notes="...")` — save handoff for next session + +## Handoff Note Template + +Include in your handoff notes: + +- **Completed**: What tasks were finished +- **In Progress**: What was started but not finished +- **Blocked**: What is waiting on external input +- **Next Steps**: What the next agent should do first +- **Gotchas**: Any surprises or edge cases discovered diff --git a/internal/agentkit/content/command/inbox.md b/internal/agentkit/content/command/inbox.md new file mode 100644 index 0000000..3a9ca90 --- /dev/null +++ b/internal/agentkit/content/command/inbox.md @@ -0,0 +1,26 @@ +--- +description: Check comms inbox for messages from other agents +--- + +# /inbox + +Check your message inbox. + +## Workflow + +1. `comms_inbox()` — get message headers (max 5) +2. `comms_read_message(message_id)` — read full message body +3. `comms_ack(message_id)` — acknowledge when handled + +## Filtering + +- `comms_inbox(urgent_only=true)` — show only urgent messages +- `comms_inbox(limit=3)` — limit results + +## Sending + +To send a message to another agent: + +``` +comms_send(to=["worker-1"], subject="...", body="...", importance="normal") +``` diff --git a/internal/agentkit/content/command/org.md b/internal/agentkit/content/command/org.md new file mode 100644 index 0000000..072ea07 --- /dev/null +++ b/internal/agentkit/content/command/org.md @@ -0,0 +1,36 @@ +--- +description: Query and manage work items (cells) +--- + +# /org + +Manage cells with `org_*` tools. + +## Common Actions + +- List ready: `org_ready()` +- Query by status: `org_cells(status="open")` +- Create: `org_create(title="...", type="task", priority=1)` +- Update: `org_update(id="...", status="in_progress")` +- Close: `org_close(id="...", reason="Done")` +- Start: `org_start(id="...")` + +## Usage + +- `/org` — show ready cells +- `/org create "Fix auth bug"` — create a cell +- `/org close "Done"` — close a cell +- `/org status` — show in-progress cells + +## Epics + +Create an epic with subtasks in one call: + +``` +org_create_epic(epic_title="...", subtasks=[{title: "...", files: [...]}]) +``` + +## Sessions + +- `org_session_start()` — begin a work session, get previous handoff notes +- `org_session_end(handoff_notes="...")` — end session with context for next agent diff --git a/internal/agentkit/content/skills/always-on-guidance/SKILL.md b/internal/agentkit/content/skills/always-on-guidance/SKILL.md new file mode 100644 index 0000000..92950bc --- /dev/null +++ b/internal/agentkit/content/skills/always-on-guidance/SKILL.md @@ -0,0 +1,46 @@ +--- +description: Global coding rules and tool usage discipline +tags: [always-on, coding, quality] +--- + +# Always-On Guidance + +Rules that apply to every coding session. + +## Tool Usage Discipline + +- Read files before editing — never guess at content +- Use `org_*` tools for work item management +- Use `comms_*` tools for agent messaging and file reservations +- Use `forge_*` tools for multi-agent coordination +- Use `hivemind_*` tools for learning storage and retrieval +- Check `hivemind_find` before solving problems from scratch + +## Code Quality + +- Functions do one thing well +- Names reveal intent — no abbreviations +- Comments explain *why*, not *what* +- No dead code or unused imports +- Error messages include context + +## Testing + +- Write tests for all new code +- Use `db.OpenMemory()` for database tests +- Use `t.TempDir()` for filesystem tests +- Standard library `testing` package only — no testify +- Test names: `TestXxx_Description` + +## Error Handling + +- Return errors, don't panic +- Wrap errors with context: `fmt.Errorf("operation: %w", err)` +- Handle all error paths — no ignored returns +- Use `errors.Is` for sentinel error checks + +## Git Discipline + +- Conventional commits: `type: description` +- Never force push to main +- Commit early, commit often diff --git a/internal/agentkit/content/skills/forge-coordination/SKILL.md b/internal/agentkit/content/skills/forge-coordination/SKILL.md new file mode 100644 index 0000000..4aad0ce --- /dev/null +++ b/internal/agentkit/content/skills/forge-coordination/SKILL.md @@ -0,0 +1,61 @@ +--- +description: Multi-agent coordination patterns for forge sessions +tags: [forge, coordination, multi-agent] +--- + +# Forge Coordination + +Patterns for coordinating parallel agent work. + +## Coordinator Protocol + +1. **Initialize**: `comms_init(project_path=".", task_description="...")` +2. **Check learnings**: `hivemind_find(query="")` +3. **Select strategy**: `forge_get_strategy_insights(task="")` +4. **Decompose**: `forge_decompose(task="", context="")` +5. **Create epic**: `org_create_epic(epic_title="", subtasks=[...])` +6. **Spawn workers**: `forge_spawn_subtask(bead_id, epic_id, subtask_title, files)` +7. **Monitor**: `comms_inbox()` + `forge_status(epic_id, project_key)` +8. **Review**: `forge_review(task_id, files_touched)` for each worker +9. **Complete**: `forge_complete(bead_id, summary, files_touched)` +10. **Learn**: `hivemind_store(information="...", tags="forge,")` + +## Worker Protocol + +1. **Initialize**: `comms_init(project_path=".", task_description="...")` +2. **Check learnings**: `hivemind_find(query="")` +3. **Reserve files**: `comms_reserve(paths=[...], reason="...")` +4. **Implement**: Make changes to reserved files +5. **Report progress**: `forge_progress(bead_id, progress_percent, status)` +6. **Store learnings**: `hivemind_store(information="...", tags="...")` +7. **Complete**: `forge_complete(bead_id, summary, files_touched)` + +## File Reservation Rules + +- Workers MUST reserve files before editing +- Coordinators NEVER reserve files +- Use `comms_reserve(paths=[...], exclusive=true)` for exclusive access +- Release files when done: `comms_release(paths=[...])` +- Emergency release: `comms_release_all()` (coordinator only) + +## Progress Reporting + +Report at milestones: 25%, 50%, 75%, 100% + +``` +forge_progress( + project_key="replicator", + agent_name="worker-1", + bead_id="", + status="in_progress", + progress_percent=50, + message="Implemented core logic, starting tests" +) +``` + +## Conflict Resolution + +If a file reservation fails: +1. Check who holds the reservation +2. Send a message via `comms_send` to negotiate +3. Wait for release or escalate to coordinator diff --git a/internal/agentkit/content/skills/forge-global/SKILL.md b/internal/agentkit/content/skills/forge-global/SKILL.md new file mode 100644 index 0000000..f4067e4 --- /dev/null +++ b/internal/agentkit/content/skills/forge-global/SKILL.md @@ -0,0 +1,51 @@ +--- +description: Cross-project forge coordination patterns +tags: [forge, global, coordination] +--- + +# Forge Global + +Patterns for forge coordination that apply across projects. + +## When to Forge + +Use a forge when: +- Task touches 3+ files +- Task has independent subtasks that can parallelize +- Task benefits from specialized workers (e.g., tests vs implementation) + +Don't forge when: +- Task is a single-file change +- Task requires sequential steps with tight coupling +- Task is exploratory or investigative + +## File Reservation Protocol + +1. Workers MUST call `comms_reserve(paths=[...])` before editing +2. Reservations are exclusive by default +3. Set `ttl_seconds` to auto-release after timeout +4. Always release when done: `comms_release(paths=[...])` +5. Coordinator can emergency release: `comms_release_all()` + +## Worker Spawning + +Each worker gets: +- A bead ID (cell in the org) +- An epic ID (parent cell) +- A list of assigned files +- Shared context from the coordinator + +Workers operate independently and report back via comms. + +## Broadcast + +Coordinator can broadcast context updates to all workers: + +``` +forge_broadcast( + project_path=".", + agent_name="coordinator", + epic_id="", + message="API contract changed, update imports" +) +``` diff --git a/internal/agentkit/content/skills/learning-systems/SKILL.md b/internal/agentkit/content/skills/learning-systems/SKILL.md new file mode 100644 index 0000000..b459c44 --- /dev/null +++ b/internal/agentkit/content/skills/learning-systems/SKILL.md @@ -0,0 +1,63 @@ +--- +description: How the forge learns from outcomes +tags: [learning, forge, insights] +--- + +# Learning Systems + +The forge improves over time by recording outcomes and querying insights. + +## Recording Outcomes + +After every forge completion, record the outcome: + +``` +forge_record_outcome( + bead_id="", + duration_ms=120000, + success=true, + strategy="file-based", + files_touched=["internal/foo/bar.go"], + error_count=0, + retry_count=0 +) +``` + +## Querying Insights + +### Strategy Insights + +Which decomposition strategies work best: + +``` +forge_get_strategy_insights(task="") +``` + +Returns success rates for file-based, feature-based, and risk-based strategies. + +### File Insights + +Historical gotchas for specific files: + +``` +forge_get_file_insights(files=["internal/foo/bar.go"]) +``` + +Returns past failure patterns, edge cases, and performance traps. + +### Pattern Insights + +Common failure patterns across all forges: + +``` +forge_get_pattern_insights() +``` + +Returns top 5 most frequent failure patterns with recommendations. + +## When to Store vs Query + +- **Store** after completing work: learnings, decisions, gotchas +- **Query** before starting work: check if someone solved it before +- Use `hivemind_store` for general learnings +- Use `forge_record_outcome` for structured forge metrics diff --git a/internal/agentkit/content/skills/replicator-cli/SKILL.md b/internal/agentkit/content/skills/replicator-cli/SKILL.md new file mode 100644 index 0000000..1b9c1a9 --- /dev/null +++ b/internal/agentkit/content/skills/replicator-cli/SKILL.md @@ -0,0 +1,45 @@ +--- +description: Replicator CLI quick reference +tags: [cli, reference, replicator] +--- + +# Replicator CLI + +Quick reference for all replicator commands. + +## Commands + +| Command | Purpose | +|---------|---------| +| `replicator init` | Per-repo setup: creates `.uf/replicator/` + agent kit | +| `replicator setup` | Per-machine setup: creates global SQLite DB | +| `replicator serve` | Start MCP JSON-RPC server on stdio | +| `replicator cells` | List work items (cells) | +| `replicator doctor` | Check environment health | +| `replicator stats` | Display activity summary | +| `replicator query` | Run preset SQL analytics queries | +| `replicator docs` | Generate MCP tool reference (markdown) | +| `replicator version` | Print version, commit, build date | + +## Build Targets + +```bash +make build # Build binary to ./bin/replicator +make test # Run all tests +make vet # Go vet +make check # Vet + test +make serve # Build and run MCP server +make install # Install to GOPATH/bin +``` + +## Init Flags + +- `--path ` — target directory (default: `.`) +- `--force` — overwrite existing agent kit files + +## MCP Tool Categories + +- `org_*` (11 tools) — work item management +- `comms_*` (10 tools) — agent messaging and file reservations +- `forge_*` (24 tools) — multi-agent coordination +- `hivemind_*` (8 tools) — learning storage and retrieval diff --git a/internal/agentkit/content/skills/system-design/SKILL.md b/internal/agentkit/content/skills/system-design/SKILL.md new file mode 100644 index 0000000..693b2f5 --- /dev/null +++ b/internal/agentkit/content/skills/system-design/SKILL.md @@ -0,0 +1,39 @@ +--- +description: System design principles for clean architecture +tags: [design, architecture, principles] +--- + +# System Design + +Core design principles for the replicator codebase. + +## Deep Modules + +Modules should have simple interfaces and complex implementations. +A deep module hides complexity behind a clean API. + +## Fight Complexity + +- Reduce the number of concepts a developer must hold in mind +- Make the common case simple, the edge case possible +- Prefer explicit over implicit behavior + +## SOLID Principles + +- **Single Responsibility**: Each package owns one domain concept +- **Open/Closed**: Extend via interfaces, not modification +- **Liskov Substitution**: Subtypes must be substitutable +- **Interface Segregation**: Small, focused interfaces +- **Dependency Inversion**: Depend on abstractions, not concretions + +## DRY + +Extract only when there are 3+ duplications. Premature abstraction +is worse than duplication. + +## Dependency Injection + +- Constructor injection: `NewFoo(deps)` or `Options` structs +- No global state or package-level variables +- External dependencies (filesystem, network, time) behind interfaces +- Makes testing straightforward with in-memory implementations diff --git a/internal/agentkit/content/skills/testing-patterns/SKILL.md b/internal/agentkit/content/skills/testing-patterns/SKILL.md new file mode 100644 index 0000000..809e192 --- /dev/null +++ b/internal/agentkit/content/skills/testing-patterns/SKILL.md @@ -0,0 +1,75 @@ +--- +description: Go testing patterns for the replicator project +tags: [testing, go, patterns] +--- + +# Testing Patterns + +Go testing conventions for replicator. + +## Framework + +Standard library `testing` package only. No testify, gomega, or external +assertion libraries. Use `t.Errorf` / `t.Fatalf` directly. + +## Test Naming + +`TestXxx_Description` — e.g., `TestCreateCell_Defaults`, `TestReadyCell_PriorityOrder`. + +## Isolation Patterns + +### Database Tests + +```go +store := db.OpenMemory() +defer store.Close() +``` + +Every test gets its own in-memory SQLite database. No shared state. + +### Filesystem Tests + +```go +dir := t.TempDir() +// dir is automatically cleaned up +``` + +### HTTP Tests + +```go +srv := httptest.NewServer(handler) +defer srv.Close() +``` + +### Git Tests + +```go +if testing.Short() { + t.Skip("skipping git test in short mode") +} +dir := t.TempDir() +// exec.Command("git", "init", dir) +``` + +## Parity Tests + +Build tag: `//go:build parity` + +Compare Go response shapes against TypeScript fixtures in +`test/parity/fixtures/`. Run with: + +```bash +go test -tags parity ./test/parity/ -count=1 -v +``` + +## Assertions + +Use direct comparisons: + +```go +if got != want { + t.Errorf("FunctionName() = %v, want %v", got, want) +} +``` + +For slices and structs, use `reflect.DeepEqual` or compare field by field. diff --git a/internal/swarmmail/agent.go b/internal/comms/agent.go similarity index 99% rename from internal/swarmmail/agent.go rename to internal/comms/agent.go index 91459c2..c4911bc 100644 --- a/internal/swarmmail/agent.go +++ b/internal/comms/agent.go @@ -2,7 +2,7 @@ // // Agents register themselves, send messages to each other, and reserve files // for exclusive editing to prevent conflicts during parallel work. -package swarmmail +package comms import ( "fmt" diff --git a/internal/swarmmail/agent_test.go b/internal/comms/agent_test.go similarity index 98% rename from internal/swarmmail/agent_test.go rename to internal/comms/agent_test.go index 4463cbf..edba978 100644 --- a/internal/swarmmail/agent_test.go +++ b/internal/comms/agent_test.go @@ -1,4 +1,4 @@ -package swarmmail +package comms import ( "testing" diff --git a/internal/swarmmail/message.go b/internal/comms/message.go similarity index 99% rename from internal/swarmmail/message.go rename to internal/comms/message.go index 77c6b4f..5bf56b9 100644 --- a/internal/swarmmail/message.go +++ b/internal/comms/message.go @@ -1,4 +1,4 @@ -package swarmmail +package comms import ( "encoding/json" diff --git a/internal/swarmmail/message_test.go b/internal/comms/message_test.go similarity index 99% rename from internal/swarmmail/message_test.go rename to internal/comms/message_test.go index 064c88a..57e7eef 100644 --- a/internal/swarmmail/message_test.go +++ b/internal/comms/message_test.go @@ -1,4 +1,4 @@ -package swarmmail +package comms import "testing" diff --git a/internal/swarmmail/reservation.go b/internal/comms/reservation.go similarity index 99% rename from internal/swarmmail/reservation.go rename to internal/comms/reservation.go index 26a67a1..78c16a2 100644 --- a/internal/swarmmail/reservation.go +++ b/internal/comms/reservation.go @@ -1,4 +1,4 @@ -package swarmmail +package comms import ( "fmt" diff --git a/internal/swarmmail/reservation_test.go b/internal/comms/reservation_test.go similarity index 99% rename from internal/swarmmail/reservation_test.go rename to internal/comms/reservation_test.go index 2c24358..a18c978 100644 --- a/internal/swarmmail/reservation_test.go +++ b/internal/comms/reservation_test.go @@ -1,4 +1,4 @@ -package swarmmail +package comms import "testing" diff --git a/internal/swarm/decompose.go b/internal/forge/decompose.go similarity index 99% rename from internal/swarm/decompose.go rename to internal/forge/decompose.go index 10e6452..264bf63 100644 --- a/internal/swarm/decompose.go +++ b/internal/forge/decompose.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "encoding/json" diff --git a/internal/swarm/decompose_test.go b/internal/forge/decompose_test.go similarity index 99% rename from internal/swarm/decompose_test.go rename to internal/forge/decompose_test.go index cf84244..30f1c08 100644 --- a/internal/swarm/decompose_test.go +++ b/internal/forge/decompose_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "strings" diff --git a/internal/swarm/init.go b/internal/forge/init.go similarity index 79% rename from internal/swarm/init.go rename to internal/forge/init.go index ad68b61..be0cefa 100644 --- a/internal/swarm/init.go +++ b/internal/forge/init.go @@ -1,10 +1,10 @@ -// Package swarm implements multi-agent coordination for parallel task execution. +// Package forge implements multi-agent coordination for parallel task execution. // -// The swarm package provides session initialization, task decomposition (prompt +// The forge package provides session initialization, task decomposition (prompt // generation), subtask spawning, progress tracking, worktree management, code // review prompts, and historical insights. Most functions are either prompt // generators (returning strings) or event recorders (writing to the events table). -package swarm +package forge import ( "encoding/json" @@ -14,7 +14,7 @@ import ( "github.com/unbound-force/replicator/internal/db" ) -// Init initializes a swarm session by recording an event and returning session info. +// Init initializes a forge session by recording an event and returning session info. // The isolation parameter specifies the isolation strategy ("worktree" or "reservation"). func Init(store *db.Store, projectPath, isolation string) (map[string]any, error) { if projectPath == "" { @@ -35,10 +35,10 @@ func Init(store *db.Store, projectPath, isolation string) (map[string]any, error _, err = store.DB.Exec( "INSERT INTO events (type, payload, project_key) VALUES (?, ?, ?)", - "swarm_init", string(payloadJSON), projectPath, + "forge_init", string(payloadJSON), projectPath, ) if err != nil { - return nil, fmt.Errorf("record swarm_init event: %w", err) + return nil, fmt.Errorf("record forge_init event: %w", err) } return map[string]any{ diff --git a/internal/swarm/init_test.go b/internal/forge/init_test.go similarity index 89% rename from internal/swarm/init_test.go rename to internal/forge/init_test.go index e799e2f..64d3e8c 100644 --- a/internal/swarm/init_test.go +++ b/internal/forge/init_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "testing" @@ -61,8 +61,8 @@ func TestInit_RecordsEvent(t *testing.T) { Init(store, "/tmp/project", "worktree") var count int - store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'swarm_init'").Scan(&count) + store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'forge_init'").Scan(&count) if count != 1 { - t.Errorf("expected 1 swarm_init event, got %d", count) + t.Errorf("expected 1 forge_init event, got %d", count) } } diff --git a/internal/swarm/insights.go b/internal/forge/insights.go similarity index 96% rename from internal/swarm/insights.go rename to internal/forge/insights.go index 1c9b9eb..c9ec99f 100644 --- a/internal/swarm/insights.go +++ b/internal/forge/insights.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "encoding/json" @@ -10,7 +10,7 @@ import ( // GetStrategyInsights queries the events table for historical success rates by strategy. func GetStrategyInsights(store *db.Store, task string) (map[string]any, error) { rows, err := store.DB.Query( - "SELECT payload FROM events WHERE type = 'swarm_outcome'", + "SELECT payload FROM events WHERE type = 'forge_outcome'", ) if err != nil { return nil, fmt.Errorf("query outcomes: %w", err) @@ -91,7 +91,7 @@ func GetFileInsights(store *db.Store, files []string) (map[string]any, error) { } rows, err := store.DB.Query( - "SELECT payload FROM events WHERE type = 'swarm_outcome'", + "SELECT payload FROM events WHERE type = 'forge_outcome'", ) if err != nil { return nil, fmt.Errorf("query outcomes: %w", err) @@ -156,7 +156,7 @@ func GetFileInsights(store *db.Store, files []string) (map[string]any, error) { // GetPatternInsights queries the events table for the top 5 most frequent failure patterns. func GetPatternInsights(store *db.Store) (map[string]any, error) { rows, err := store.DB.Query( - "SELECT payload FROM events WHERE type = 'swarm_outcome'", + "SELECT payload FROM events WHERE type = 'forge_outcome'", ) if err != nil { return nil, fmt.Errorf("query outcomes: %w", err) diff --git a/internal/swarm/insights_test.go b/internal/forge/insights_test.go similarity index 97% rename from internal/swarm/insights_test.go rename to internal/forge/insights_test.go index e676a1f..d90d05a 100644 --- a/internal/swarm/insights_test.go +++ b/internal/forge/insights_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "encoding/json" @@ -41,7 +41,7 @@ func TestGetStrategyInsights_WithData(t *testing.T) { "strategy": o.strategy, "success": o.success, }) - store.DB.Exec("INSERT INTO events (type, payload) VALUES (?, ?)", "swarm_outcome", string(payload)) + store.DB.Exec("INSERT INTO events (type, payload) VALUES (?, ?)", "forge_outcome", string(payload)) } result, err := GetStrategyInsights(store, "task") @@ -88,7 +88,7 @@ func TestGetFileInsights_WithData(t *testing.T) { } for _, p := range payloads { data, _ := json.Marshal(p) - store.DB.Exec("INSERT INTO events (type, payload) VALUES (?, ?)", "swarm_outcome", string(data)) + store.DB.Exec("INSERT INTO events (type, payload) VALUES (?, ?)", "forge_outcome", string(data)) } result, err := GetFileInsights(store, []string{"auth.go", "db.go"}) @@ -139,7 +139,7 @@ func TestGetPatternInsights_WithData(t *testing.T) { } for _, p := range payloads { data, _ := json.Marshal(p) - store.DB.Exec("INSERT INTO events (type, payload) VALUES (?, ?)", "swarm_outcome", string(data)) + store.DB.Exec("INSERT INTO events (type, payload) VALUES (?, ?)", "forge_outcome", string(data)) } result, err := GetPatternInsights(store) diff --git a/internal/swarm/progress.go b/internal/forge/progress.go similarity index 96% rename from internal/swarm/progress.go rename to internal/forge/progress.go index 541a0d5..117560d 100644 --- a/internal/swarm/progress.go +++ b/internal/forge/progress.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "encoding/json" @@ -29,7 +29,7 @@ func Progress(store *db.Store, projectKey, agentName, beadID, status string, pro _, err = store.DB.Exec( "INSERT INTO events (type, payload, project_key) VALUES (?, ?, ?)", - "swarm_progress", string(payloadJSON), projectKey, + "forge_progress", string(payloadJSON), projectKey, ) if err != nil { return fmt.Errorf("record progress event: %w", err) @@ -60,7 +60,7 @@ func Complete(store *db.Store, projectKey, agentName, beadID, summary string, fi _, err = store.DB.Exec( "INSERT INTO events (type, payload, project_key) VALUES (?, ?, ?)", - "swarm_complete", string(payloadJSON), projectKey, + "forge_complete", string(payloadJSON), projectKey, ) if err != nil { return nil, fmt.Errorf("record complete event: %w", err) @@ -160,7 +160,7 @@ func RecordOutcome(store *db.Store, beadID string, durationMs int, success bool, _, err = store.DB.Exec( "INSERT INTO events (type, payload) VALUES (?, ?)", - "swarm_outcome", string(payloadJSON), + "forge_outcome", string(payloadJSON), ) if err != nil { return fmt.Errorf("record outcome event: %w", err) diff --git a/internal/swarm/progress_test.go b/internal/forge/progress_test.go similarity index 92% rename from internal/swarm/progress_test.go rename to internal/forge/progress_test.go index 79d6525..d5539ed 100644 --- a/internal/swarm/progress_test.go +++ b/internal/forge/progress_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "testing" @@ -13,7 +13,7 @@ func TestProgress(t *testing.T) { } var count int - store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'swarm_progress'").Scan(&count) + store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'forge_progress'").Scan(&count) if count != 1 { t.Errorf("expected 1 progress event, got %d", count) } @@ -46,7 +46,7 @@ func TestComplete(t *testing.T) { // Verify event. var count int - store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'swarm_complete'").Scan(&count) + store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'forge_complete'").Scan(&count) if count != 1 { t.Errorf("expected 1 complete event, got %d", count) } @@ -133,7 +133,7 @@ func TestRecordOutcome(t *testing.T) { } var count int - store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'swarm_outcome'").Scan(&count) + store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'forge_outcome'").Scan(&count) if count != 1 { t.Errorf("expected 1 outcome event, got %d", count) } diff --git a/internal/swarm/review.go b/internal/forge/review.go similarity index 98% rename from internal/swarm/review.go rename to internal/forge/review.go index a6e3b3f..2b93d81 100644 --- a/internal/swarm/review.go +++ b/internal/forge/review.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "encoding/json" @@ -201,7 +201,7 @@ func Broadcast(store *db.Store, projectPath, agentName, epicID, message, importa _, err = store.DB.Exec( "INSERT INTO events (type, payload, project_key) VALUES (?, ?, ?)", - "swarm_broadcast", string(payloadJSON), projectPath, + "forge_broadcast", string(payloadJSON), projectPath, ) if err != nil { return fmt.Errorf("record broadcast event: %w", err) diff --git a/internal/swarm/review_test.go b/internal/forge/review_test.go similarity index 95% rename from internal/swarm/review_test.go rename to internal/forge/review_test.go index 8a3333c..23ed635 100644 --- a/internal/swarm/review_test.go +++ b/internal/forge/review_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "strings" @@ -136,7 +136,7 @@ func TestBroadcast(t *testing.T) { } var count int - store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'swarm_broadcast'").Scan(&count) + store.DB.QueryRow("SELECT COUNT(*) FROM events WHERE type = 'forge_broadcast'").Scan(&count) if count != 1 { t.Errorf("expected 1 broadcast event, got %d", count) } @@ -152,7 +152,7 @@ func TestBroadcast_DefaultImportance(t *testing.T) { // Verify default importance was set. var payload string - store.DB.QueryRow("SELECT payload FROM events WHERE type = 'swarm_broadcast'").Scan(&payload) + store.DB.QueryRow("SELECT payload FROM events WHERE type = 'forge_broadcast'").Scan(&payload) if !strings.Contains(payload, `"info"`) { t.Errorf("expected default importance 'info' in payload: %s", payload) } diff --git a/internal/swarm/spawn.go b/internal/forge/spawn.go similarity index 93% rename from internal/swarm/spawn.go rename to internal/forge/spawn.go index b34c46e..0b64240 100644 --- a/internal/swarm/spawn.go +++ b/internal/forge/spawn.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "encoding/json" @@ -34,11 +34,11 @@ func SubtaskPrompt(agentName, beadID, epicID, title string, files []string, shar } sb.WriteString("## Instructions\n\n") - sb.WriteString("1. Reserve files before editing using `swarmmail_reserve`\n") + sb.WriteString("1. Reserve files before editing using `comms_reserve`\n") sb.WriteString("2. Implement the changes described above\n") sb.WriteString("3. Write tests for new code\n") - sb.WriteString("4. Report progress with `swarm_progress`\n") - sb.WriteString("5. Complete with `swarm_complete` when done\n") + sb.WriteString("4. Report progress with `forge_progress`\n") + sb.WriteString("5. Complete with `forge_complete` when done\n") return sb.String() } diff --git a/internal/swarm/spawn_test.go b/internal/forge/spawn_test.go similarity index 97% rename from internal/swarm/spawn_test.go rename to internal/forge/spawn_test.go index a5ffb29..65ec960 100644 --- a/internal/swarm/spawn_test.go +++ b/internal/forge/spawn_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "strings" @@ -21,8 +21,8 @@ func TestSubtaskPrompt(t *testing.T) { "auth.go", "auth_test.go", "JWT tokens", - "swarmmail_reserve", - "swarm_complete", + "comms_reserve", + "forge_complete", } for _, check := range checks { if !strings.Contains(prompt, check) { diff --git a/internal/swarm/worktree.go b/internal/forge/worktree.go similarity index 99% rename from internal/swarm/worktree.go rename to internal/forge/worktree.go index e1a6197..f506f1d 100644 --- a/internal/swarm/worktree.go +++ b/internal/forge/worktree.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "fmt" diff --git a/internal/swarm/worktree_test.go b/internal/forge/worktree_test.go similarity index 99% rename from internal/swarm/worktree_test.go rename to internal/forge/worktree_test.go index 55b0ad0..e13750f 100644 --- a/internal/swarm/worktree_test.go +++ b/internal/forge/worktree_test.go @@ -1,4 +1,4 @@ -package swarm +package forge import ( "os" diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 828f3b8..6203ced 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/unbound-force/replicator/internal/db" - "github.com/unbound-force/replicator/internal/tools/hive" + "github.com/unbound-force/replicator/internal/tools/org" "github.com/unbound-force/replicator/internal/tools/registry" ) @@ -55,7 +55,7 @@ func testServer(t *testing.T) (*Server, *db.Store, *testLogger) { t.Cleanup(func() { store.Close() }) reg := registry.New() - hive.Register(reg, store) + org.Register(reg, store) logger := &testLogger{} return NewServer(reg, "test", logger), store, logger } @@ -101,9 +101,9 @@ func TestToolsList(t *testing.T) { names[tool.Name] = true } for _, expected := range []string{ - "hive_cells", "hive_create", "hive_close", "hive_update", - "hive_create_epic", "hive_query", "hive_start", "hive_ready", - "hive_sync", "hive_session_start", "hive_session_end", + "org_cells", "org_create", "org_close", "org_update", + "org_create_epic", "org_query", "org_start", "org_ready", + "org_sync", "org_session_start", "org_session_end", } { if !names[expected] { t.Errorf("missing tool: %s", expected) @@ -111,10 +111,10 @@ func TestToolsList(t *testing.T) { } } -func TestToolsCall_HiveCells_Empty(t *testing.T) { +func TestToolsCall_OrgCells_Empty(t *testing.T) { s, _, _ := testServer(t) result := call(t, s, "tools/call", toolsCallParams{ - Name: "hive_cells", + Name: "org_cells", Arguments: json.RawMessage(`{}`), }) @@ -129,10 +129,10 @@ func TestToolsCall_HiveCells_Empty(t *testing.T) { } } -func TestToolsCall_HiveCreate(t *testing.T) { +func TestToolsCall_OrgCreate(t *testing.T) { s, _, _ := testServer(t) result := call(t, s, "tools/call", toolsCallParams{ - Name: "hive_create", + Name: "org_create", Arguments: json.RawMessage(`{"title": "Test cell", "type": "bug"}`), }) @@ -159,13 +159,13 @@ func TestToolsCall_CreateThenQuery(t *testing.T) { // Create a cell. call(t, s, "tools/call", toolsCallParams{ - Name: "hive_create", + Name: "org_create", Arguments: json.RawMessage(`{"title": "My task"}`), }) // Query cells. result := call(t, s, "tools/call", toolsCallParams{ - Name: "hive_cells", + Name: "org_cells", Arguments: json.RawMessage(`{}`), }) @@ -214,7 +214,7 @@ func TestToolsCall_LogsToolName(t *testing.T) { s, _, logger := testServer(t) call(t, s, "tools/call", toolsCallParams{ - Name: "hive_cells", + Name: "org_cells", Arguments: json.RawMessage(`{}`), }) @@ -233,8 +233,8 @@ func TestToolsCall_LogsToolName(t *testing.T) { // Verify keyvals contain "tool" and "duration". kvMap := keyvalMap(entry.Keyvals) - if kvMap["tool"] != "hive_cells" { - t.Errorf("log tool = %v, want %q", kvMap["tool"], "hive_cells") + if kvMap["tool"] != "org_cells" { + t.Errorf("log tool = %v, want %q", kvMap["tool"], "org_cells") } if _, ok := kvMap["duration"]; !ok { t.Error("log entry missing 'duration' key") @@ -249,11 +249,11 @@ func TestToolsCall_LogsMultipleCalls(t *testing.T) { // Two tool calls should produce two log entries. call(t, s, "tools/call", toolsCallParams{ - Name: "hive_cells", + Name: "org_cells", Arguments: json.RawMessage(`{}`), }) call(t, s, "tools/call", toolsCallParams{ - Name: "hive_create", + Name: "org_create", Arguments: json.RawMessage(`{"title":"logged"}`), }) @@ -264,11 +264,11 @@ func TestToolsCall_LogsMultipleCalls(t *testing.T) { kv0 := keyvalMap(entries[0].Keyvals) kv1 := keyvalMap(entries[1].Keyvals) - if kv0["tool"] != "hive_cells" { - t.Errorf("first call tool = %v, want hive_cells", kv0["tool"]) + if kv0["tool"] != "org_cells" { + t.Errorf("first call tool = %v, want org_cells", kv0["tool"]) } - if kv1["tool"] != "hive_create" { - t.Errorf("second call tool = %v, want hive_create", kv1["tool"]) + if kv1["tool"] != "org_create" { + t.Errorf("second call tool = %v, want org_create", kv1["tool"]) } } @@ -281,12 +281,12 @@ func TestNewServer_NilLogger(t *testing.T) { defer store.Close() reg := registry.New() - hive.Register(reg, store) + org.Register(reg, store) s := NewServer(reg, "test", nil) // Should not panic. call(t, s, "tools/call", toolsCallParams{ - Name: "hive_cells", + Name: "org_cells", Arguments: json.RawMessage(`{}`), }) } diff --git a/internal/hive/cells.go b/internal/org/cells.go similarity index 99% rename from internal/hive/cells.go rename to internal/org/cells.go index 629e9da..96e8a96 100644 --- a/internal/hive/cells.go +++ b/internal/org/cells.go @@ -4,7 +4,7 @@ // but using the hive/swarm metaphor. Each cell has a type (task, bug, // feature, epic, chore), a status (open, in_progress, blocked, closed), // and optional parent-child relationships for epics. -package hive +package org import ( "crypto/rand" diff --git a/internal/hive/cells_test.go b/internal/org/cells_test.go similarity index 99% rename from internal/hive/cells_test.go rename to internal/org/cells_test.go index a485d6e..c0d076f 100644 --- a/internal/hive/cells_test.go +++ b/internal/org/cells_test.go @@ -1,4 +1,4 @@ -package hive +package org import ( "testing" diff --git a/internal/hive/epic.go b/internal/org/epic.go similarity index 99% rename from internal/hive/epic.go rename to internal/org/epic.go index e0cb6d2..9aab211 100644 --- a/internal/hive/epic.go +++ b/internal/org/epic.go @@ -1,4 +1,4 @@ -package hive +package org import ( "encoding/json" diff --git a/internal/hive/epic_test.go b/internal/org/epic_test.go similarity index 99% rename from internal/hive/epic_test.go rename to internal/org/epic_test.go index cc08dea..5e2dafe 100644 --- a/internal/hive/epic_test.go +++ b/internal/org/epic_test.go @@ -1,4 +1,4 @@ -package hive +package org import "testing" diff --git a/internal/hive/format.go b/internal/org/format.go similarity index 99% rename from internal/hive/format.go rename to internal/org/format.go index 22f8cf7..dc3e651 100644 --- a/internal/hive/format.go +++ b/internal/org/format.go @@ -1,4 +1,4 @@ -package hive +package org import ( "fmt" diff --git a/internal/hive/format_test.go b/internal/org/format_test.go similarity index 99% rename from internal/hive/format_test.go rename to internal/org/format_test.go index 2a17fa9..febcb2e 100644 --- a/internal/hive/format_test.go +++ b/internal/org/format_test.go @@ -1,4 +1,4 @@ -package hive +package org import ( "bytes" diff --git a/internal/hive/session.go b/internal/org/session.go similarity index 99% rename from internal/hive/session.go rename to internal/org/session.go index a7af550..0311d8f 100644 --- a/internal/hive/session.go +++ b/internal/org/session.go @@ -1,4 +1,4 @@ -package hive +package org import ( "crypto/rand" diff --git a/internal/hive/session_test.go b/internal/org/session_test.go similarity index 99% rename from internal/hive/session_test.go rename to internal/org/session_test.go index f0385f5..4f61a60 100644 --- a/internal/hive/session_test.go +++ b/internal/org/session_test.go @@ -1,4 +1,4 @@ -package hive +package org import "testing" diff --git a/internal/hive/start_ready_test.go b/internal/org/start_ready_test.go similarity index 99% rename from internal/hive/start_ready_test.go rename to internal/org/start_ready_test.go index a7516af..56ad87d 100644 --- a/internal/hive/start_ready_test.go +++ b/internal/org/start_ready_test.go @@ -1,4 +1,4 @@ -package hive +package org import "testing" diff --git a/internal/hive/sync.go b/internal/org/sync.go similarity index 99% rename from internal/hive/sync.go rename to internal/org/sync.go index 7edcd66..ac94d42 100644 --- a/internal/hive/sync.go +++ b/internal/org/sync.go @@ -1,4 +1,4 @@ -package hive +package org import ( "encoding/json" diff --git a/internal/hive/sync_test.go b/internal/org/sync_test.go similarity index 99% rename from internal/hive/sync_test.go rename to internal/org/sync_test.go index fc73a02..fc87fe0 100644 --- a/internal/hive/sync_test.go +++ b/internal/org/sync_test.go @@ -1,4 +1,4 @@ -package hive +package org import ( "encoding/json" diff --git a/internal/query/presets.go b/internal/query/presets.go index 6d4cc7d..f7ebb01 100644 --- a/internal/query/presets.go +++ b/internal/query/presets.go @@ -2,7 +2,7 @@ // // Each preset is a named SQL query that produces a styled table. // Presets cover common observability needs: agent activity, cell status, -// swarm completion rates, and recent events. +// forge completion rates, and recent events. package query import ( @@ -18,7 +18,7 @@ import ( const ( AgentActivity24h = "agent_activity_24h" CellsByStatus = "cells_by_status" - SwarmCompletionRate = "swarm_completion_rate" + ForgeCompletionRate = "forge_completion_rate" RecentEvents = "recent_events" ) @@ -27,7 +27,7 @@ func ListPresets() []string { return []string{ AgentActivity24h, CellsByStatus, - SwarmCompletionRate, + ForgeCompletionRate, RecentEvents, } } @@ -39,8 +39,8 @@ func Run(store *db.Store, presetName string, w io.Writer) error { return runAgentActivity(store, w) case CellsByStatus: return runCellsByStatus(store, w) - case SwarmCompletionRate: - return runSwarmCompletionRate(store, w) + case ForgeCompletionRate: + return runForgeCompletionRate(store, w) case RecentEvents: return runRecentEvents(store, w) default: @@ -122,22 +122,22 @@ func runCellsByStatus(store *db.Store, w io.Writer) error { return nil } -func runSwarmCompletionRate(store *db.Store, w io.Writer) error { +func runForgeCompletionRate(store *db.Store, w io.Writer) error { styles := ui.NewStyles(w) - // Count completed vs total swarm events. + // Count completed vs total forge events. var total, completed int - store.DB.QueryRow(`SELECT COUNT(*) FROM events WHERE type LIKE 'swarm_%'`).Scan(&total) - store.DB.QueryRow(`SELECT COUNT(*) FROM events WHERE type = 'swarm_complete'`).Scan(&completed) + store.DB.QueryRow(`SELECT COUNT(*) FROM events WHERE type LIKE 'forge_%'`).Scan(&total) + store.DB.QueryRow(`SELECT COUNT(*) FROM events WHERE type = 'forge_complete'`).Scan(&completed) - fmt.Fprintln(w, styles.Bold.Render("Swarm Completion Rate:")) - fmt.Fprintf(w, " Total swarm events: %d\n", total) + fmt.Fprintln(w, styles.Bold.Render("Forge Completion Rate:")) + fmt.Fprintf(w, " Total forge events: %d\n", total) fmt.Fprintf(w, " Completed: %d\n", completed) if total > 0 { rate := float64(completed) / float64(total) * 100 fmt.Fprintf(w, " Completion rate: %.1f%%\n", rate) } else { - fmt.Fprintln(w, styles.Dim.Render(" Completion rate: N/A (no swarm events)")) + fmt.Fprintln(w, styles.Dim.Render(" Completion rate: N/A (no forge events)")) } return nil } diff --git a/internal/query/presets_test.go b/internal/query/presets_test.go index 739ed7c..fb0d4da 100644 --- a/internal/query/presets_test.go +++ b/internal/query/presets_test.go @@ -27,7 +27,7 @@ func TestListPresets(t *testing.T) { expected := map[string]bool{ AgentActivity24h: true, CellsByStatus: true, - SwarmCompletionRate: true, + ForgeCompletionRate: true, RecentEvents: true, } for _, p := range presets { @@ -69,9 +69,9 @@ func TestRun_AgentActivity_WithData(t *testing.T) { store := testStore(t) // Insert events with agent_name in payload. - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('swarm_init', '{"agent_name": "worker-1"}', 'test')`) - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('swarm_init', '{"agent_name": "worker-1"}', 'test')`) - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('swarm_init', '{"agent_name": "worker-2"}', 'test')`) + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('forge_init', '{"agent_name": "worker-1"}', 'test')`) + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('forge_init', '{"agent_name": "worker-1"}', 'test')`) + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('forge_init', '{"agent_name": "worker-2"}', 'test')`) var buf bytes.Buffer err := Run(store, AgentActivity24h, &buf) @@ -122,17 +122,17 @@ func TestRun_CellsByStatus_WithData(t *testing.T) { } } -func TestRun_SwarmCompletionRate_Empty(t *testing.T) { +func TestRun_ForgeCompletionRate_Empty(t *testing.T) { store := testStore(t) var buf bytes.Buffer - err := Run(store, SwarmCompletionRate, &buf) + err := Run(store, ForgeCompletionRate, &buf) if err != nil { t.Fatalf("Run: %v", err) } output := buf.String() - if !strings.Contains(output, "Swarm Completion Rate") { + if !strings.Contains(output, "Forge Completion Rate") { t.Error("expected header") } if !strings.Contains(output, "N/A") { @@ -140,21 +140,21 @@ func TestRun_SwarmCompletionRate_Empty(t *testing.T) { } } -func TestRun_SwarmCompletionRate_WithData(t *testing.T) { +func TestRun_ForgeCompletionRate_WithData(t *testing.T) { store := testStore(t) - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('swarm_init', '{}', 'test')`) - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('swarm_progress', '{}', 'test')`) - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('swarm_complete', '{}', 'test')`) + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('forge_init', '{}', 'test')`) + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('forge_progress', '{}', 'test')`) + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES ('forge_complete', '{}', 'test')`) var buf bytes.Buffer - err := Run(store, SwarmCompletionRate, &buf) + err := Run(store, ForgeCompletionRate, &buf) if err != nil { t.Fatalf("Run: %v", err) } output := buf.String() - if !strings.Contains(output, "Total swarm events") { + if !strings.Contains(output, "Total forge events") { t.Error("expected total count") } if !strings.Contains(output, "Completed") { diff --git a/internal/stats/stats_test.go b/internal/stats/stats_test.go index 4c7be6f..5e689a0 100644 --- a/internal/stats/stats_test.go +++ b/internal/stats/stats_test.go @@ -47,9 +47,9 @@ func TestRun_WithEvents(t *testing.T) { // Insert some events. for i := 0; i < 3; i++ { - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES (?, '{}', 'test')`, "swarm_init") + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES (?, '{}', 'test')`, "forge_init") } - store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES (?, '{}', 'test')`, "swarm_complete") + store.DB.Exec(`INSERT INTO events (type, payload, project_key) VALUES (?, '{}', 'test')`, "forge_complete") var buf bytes.Buffer err := Run(store, &buf) @@ -58,11 +58,11 @@ func TestRun_WithEvents(t *testing.T) { } output := buf.String() - if !strings.Contains(output, "swarm_init") { - t.Error("expected swarm_init in output") + if !strings.Contains(output, "forge_init") { + t.Error("expected forge_init in output") } - if !strings.Contains(output, "swarm_complete") { - t.Error("expected swarm_complete in output") + if !strings.Contains(output, "forge_complete") { + t.Error("expected forge_complete in output") } } diff --git a/internal/tools/swarmmail/tools.go b/internal/tools/comms/tools.go similarity index 73% rename from internal/tools/swarmmail/tools.go rename to internal/tools/comms/tools.go index 421872b..772fa19 100644 --- a/internal/tools/swarmmail/tools.go +++ b/internal/tools/comms/tools.go @@ -1,33 +1,33 @@ -// Package swarmmail registers MCP tools for swarm mail operations. -package swarmmail +// Package comms registers MCP tools for comms operations. +package comms import ( "encoding/json" "fmt" + "github.com/unbound-force/replicator/internal/comms" "github.com/unbound-force/replicator/internal/db" - "github.com/unbound-force/replicator/internal/swarmmail" "github.com/unbound-force/replicator/internal/tools/registry" ) -// Register adds all swarm mail tools to the registry. +// Register adds all comms tools to the registry. func Register(reg *registry.Registry, store *db.Store) { - reg.Register(swarmmailInit(store)) - reg.Register(swarmmailSend(store)) - reg.Register(swarmmailInbox(store)) - reg.Register(swarmmailReadMessage(store)) - reg.Register(swarmmailReserve(store)) - reg.Register(swarmmailRelease(store)) - reg.Register(swarmmailReleaseAll(store)) - reg.Register(swarmmailReleaseAgent(store)) - reg.Register(swarmmailAck(store)) - reg.Register(swarmmailHealth(store)) + reg.Register(commsInit(store)) + reg.Register(commsSend(store)) + reg.Register(commsInbox(store)) + reg.Register(commsReadMessage(store)) + reg.Register(commsReserve(store)) + reg.Register(commsRelease(store)) + reg.Register(commsReleaseAll(store)) + reg.Register(commsReleaseAgent(store)) + reg.Register(commsAck(store)) + reg.Register(commsHealth(store)) } -func swarmmailInit(store *db.Store) *registry.Tool { +func commsInit(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_init", - Description: "Initialize swarm mail session. Registers the agent.", + Name: "comms_init", + Description: "Initialize comms session. Registers the agent.", InputSchema: json.RawMessage(`{ "type": "object", "required": ["project_path"], @@ -50,7 +50,7 @@ func swarmmailInit(store *db.Store) *registry.Tool { if agentName == "" { agentName = "default" } - agent, err := swarmmail.Init(store, agentName, input.ProjectPath, input.TaskDescription) + agent, err := comms.Init(store, agentName, input.ProjectPath, input.TaskDescription) if err != nil { return "", err } @@ -60,10 +60,10 @@ func swarmmailInit(store *db.Store) *registry.Tool { } } -func swarmmailSend(store *db.Store) *registry.Tool { +func commsSend(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_send", - Description: "Send a message to other agents via swarm mail.", + Name: "comms_send", + Description: "Send a message to other agents via comms.", InputSchema: json.RawMessage(`{ "type": "object", "required": ["to", "subject", "body"], @@ -79,7 +79,7 @@ func swarmmailSend(store *db.Store) *registry.Tool { Execute: func(args json.RawMessage) (string, error) { var input struct { From string `json:"from"` - swarmmail.SendInput + comms.SendInput } if err := json.Unmarshal(args, &input); err != nil { return "", err @@ -88,7 +88,7 @@ func swarmmailSend(store *db.Store) *registry.Tool { if fromAgent == "" { fromAgent = "unknown" } - if err := swarmmail.Send(store, fromAgent, input.SendInput); err != nil { + if err := comms.Send(store, fromAgent, input.SendInput); err != nil { return "", err } return `{"status": "sent"}`, nil @@ -96,9 +96,9 @@ func swarmmailSend(store *db.Store) *registry.Tool { } } -func swarmmailInbox(store *db.Store) *registry.Tool { +func commsInbox(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_inbox", + Name: "comms_inbox", Description: "Fetch inbox (max 5 messages, bodies excluded).", InputSchema: json.RawMessage(`{ "type": "object", @@ -120,7 +120,7 @@ func swarmmailInbox(store *db.Store) *registry.Tool { if agentName == "" { agentName = "default" } - summaries, err := swarmmail.Inbox(store, agentName, input.Limit, input.UrgentOnly) + summaries, err := comms.Inbox(store, agentName, input.Limit, input.UrgentOnly) if err != nil { return "", err } @@ -130,9 +130,9 @@ func swarmmailInbox(store *db.Store) *registry.Tool { } } -func swarmmailReadMessage(store *db.Store) *registry.Tool { +func commsReadMessage(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_read_message", + Name: "comms_read_message", Description: "Fetch one message body by ID.", InputSchema: json.RawMessage(`{ "type": "object", @@ -148,7 +148,7 @@ func swarmmailReadMessage(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - msg, err := swarmmail.ReadMessage(store, input.MessageID) + msg, err := comms.ReadMessage(store, input.MessageID) if err != nil { return "", err } @@ -158,9 +158,9 @@ func swarmmailReadMessage(store *db.Store) *registry.Tool { } } -func swarmmailReserve(store *db.Store) *registry.Tool { +func commsReserve(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_reserve", + Name: "comms_reserve", Description: "Reserve file paths for exclusive editing.", InputSchema: json.RawMessage(`{ "type": "object", @@ -187,7 +187,7 @@ func swarmmailReserve(store *db.Store) *registry.Tool { if agentName == "" { agentName = "default" } - reservations, err := swarmmail.Reserve(store, agentName, input.Paths, input.Exclusive, input.Reason, input.TTLSeconds) + reservations, err := comms.Reserve(store, agentName, input.Paths, input.Exclusive, input.Reason, input.TTLSeconds) if err != nil { return "", err } @@ -197,9 +197,9 @@ func swarmmailReserve(store *db.Store) *registry.Tool { } } -func swarmmailRelease(store *db.Store) *registry.Tool { +func commsRelease(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_release", + Name: "comms_release", Description: "Release file reservations.", InputSchema: json.RawMessage(`{ "type": "object", @@ -216,7 +216,7 @@ func swarmmailRelease(store *db.Store) *registry.Tool { if len(args) > 0 { json.Unmarshal(args, &input) } - if err := swarmmail.Release(store, input.Paths, input.ReservationIDs); err != nil { + if err := comms.Release(store, input.Paths, input.ReservationIDs); err != nil { return "", err } return `{"status": "released"}`, nil @@ -224,16 +224,16 @@ func swarmmailRelease(store *db.Store) *registry.Tool { } } -func swarmmailReleaseAll(store *db.Store) *registry.Tool { +func commsReleaseAll(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_release_all", + Name: "comms_release_all", Description: "Release all file reservations in the project.", InputSchema: json.RawMessage(`{ "type": "object", "properties": {} }`), Execute: func(args json.RawMessage) (string, error) { - if err := swarmmail.ReleaseAll(store, ""); err != nil { + if err := comms.ReleaseAll(store, ""); err != nil { return "", err } return `{"status": "all_released"}`, nil @@ -241,9 +241,9 @@ func swarmmailReleaseAll(store *db.Store) *registry.Tool { } } -func swarmmailReleaseAgent(store *db.Store) *registry.Tool { +func commsReleaseAgent(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_release_agent", + Name: "comms_release_agent", Description: "Release all file reservations for a specific agent.", InputSchema: json.RawMessage(`{ "type": "object", @@ -259,7 +259,7 @@ func swarmmailReleaseAgent(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - if err := swarmmail.ReleaseAgent(store, input.AgentName); err != nil { + if err := comms.ReleaseAgent(store, input.AgentName); err != nil { return "", err } return fmt.Sprintf(`{"status": "released", "agent": %q}`, input.AgentName), nil @@ -267,9 +267,9 @@ func swarmmailReleaseAgent(store *db.Store) *registry.Tool { } } -func swarmmailAck(store *db.Store) *registry.Tool { +func commsAck(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_ack", + Name: "comms_ack", Description: "Acknowledge a message.", InputSchema: json.RawMessage(`{ "type": "object", @@ -285,7 +285,7 @@ func swarmmailAck(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - if err := swarmmail.Ack(store, input.MessageID); err != nil { + if err := comms.Ack(store, input.MessageID); err != nil { return "", err } return `{"status": "acknowledged"}`, nil @@ -293,10 +293,10 @@ func swarmmailAck(store *db.Store) *registry.Tool { } } -func swarmmailHealth(store *db.Store) *registry.Tool { +func commsHealth(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarmmail_health", - Description: "Check swarm mail database health.", + Name: "comms_health", + Description: "Check comms database health.", InputSchema: json.RawMessage(`{ "type": "object", "properties": {} diff --git a/internal/tools/swarm/tools.go b/internal/tools/forge/tools.go similarity index 81% rename from internal/tools/swarm/tools.go rename to internal/tools/forge/tools.go index 08369cd..5aeba15 100644 --- a/internal/tools/swarm/tools.go +++ b/internal/tools/forge/tools.go @@ -1,58 +1,58 @@ -// Package swarm registers MCP tools for swarm orchestration operations. -package swarm +// Package forge registers MCP tools for forge orchestration operations. +package forge import ( "encoding/json" "github.com/unbound-force/replicator/internal/db" - "github.com/unbound-force/replicator/internal/swarm" + "github.com/unbound-force/replicator/internal/forge" "github.com/unbound-force/replicator/internal/tools/registry" ) -// Register adds all swarm orchestration tools to the registry. +// Register adds all forge orchestration tools to the registry. func Register(reg *registry.Registry, store *db.Store) { // Init & decomposition. - reg.Register(swarmInit(store)) - reg.Register(swarmSelectStrategy()) - reg.Register(swarmPlanPrompt()) - reg.Register(swarmDecompose()) - reg.Register(swarmValidateDecomposition()) + reg.Register(forgeInit(store)) + reg.Register(forgeSelectStrategy()) + reg.Register(forgePlanPrompt()) + reg.Register(forgeDecompose()) + reg.Register(forgeValidateDecomposition()) // Subtask spawning. - reg.Register(swarmSubtaskPrompt()) - reg.Register(swarmSpawnSubtask()) - reg.Register(swarmCompleteSubtask(store)) + reg.Register(forgeSubtaskPrompt()) + reg.Register(forgeSpawnSubtask()) + reg.Register(forgeCompleteSubtask(store)) // Progress & status. - reg.Register(swarmProgress(store)) - reg.Register(swarmComplete(store)) - reg.Register(swarmStatus(store)) - reg.Register(swarmRecordOutcome(store)) + reg.Register(forgeProgress(store)) + reg.Register(forgeComplete(store)) + reg.Register(forgeStatus(store)) + reg.Register(forgeRecordOutcome(store)) // Worktree management. - reg.Register(swarmWorktreeCreate()) - reg.Register(swarmWorktreeMerge()) - reg.Register(swarmWorktreeCleanup()) - reg.Register(swarmWorktreeList()) + reg.Register(forgeWorktreeCreate()) + reg.Register(forgeWorktreeMerge()) + reg.Register(forgeWorktreeCleanup()) + reg.Register(forgeWorktreeList()) // Review & broadcast. - reg.Register(swarmReview()) - reg.Register(swarmReviewFeedback(store)) - reg.Register(swarmAdversarialReview()) - reg.Register(swarmEvaluationPrompt()) - reg.Register(swarmBroadcast(store)) + reg.Register(forgeReview()) + reg.Register(forgeReviewFeedback(store)) + reg.Register(forgeAdversarialReview()) + reg.Register(forgeEvaluationPrompt()) + reg.Register(forgeBroadcast(store)) // Insights. - reg.Register(swarmGetStrategyInsights(store)) - reg.Register(swarmGetFileInsights(store)) - reg.Register(swarmGetPatternInsights(store)) + reg.Register(forgeGetStrategyInsights(store)) + reg.Register(forgeGetFileInsights(store)) + reg.Register(forgeGetPatternInsights(store)) } // --- Init & Decomposition --- -func swarmInit(store *db.Store) *registry.Tool { +func forgeInit(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_init", + Name: "forge_init", Description: "Initialize swarm session and check tool availability.", InputSchema: json.RawMessage(`{ "type": "object", @@ -69,7 +69,7 @@ func swarmInit(store *db.Store) *registry.Tool { if len(args) > 0 { json.Unmarshal(args, &input) } - result, err := swarm.Init(store, input.ProjectPath, input.Isolation) + result, err := forge.Init(store, input.ProjectPath, input.Isolation) if err != nil { return "", err } @@ -79,9 +79,9 @@ func swarmInit(store *db.Store) *registry.Tool { } } -func swarmSelectStrategy() *registry.Tool { +func forgeSelectStrategy() *registry.Tool { return ®istry.Tool{ - Name: "swarm_select_strategy", + Name: "forge_select_strategy", Description: "Analyze task and recommend decomposition strategy.", InputSchema: json.RawMessage(`{ "type": "object", @@ -99,7 +99,7 @@ func swarmSelectStrategy() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - strategy, err := swarm.SelectStrategy(input.Task, input.CodebaseContext) + strategy, err := forge.SelectStrategy(input.Task, input.CodebaseContext) if err != nil { return "", err } @@ -110,9 +110,9 @@ func swarmSelectStrategy() *registry.Tool { } } -func swarmPlanPrompt() *registry.Tool { +func forgePlanPrompt() *registry.Tool { return ®istry.Tool{ - Name: "swarm_plan_prompt", + Name: "forge_plan_prompt", Description: "Generate strategy-specific decomposition prompt.", InputSchema: json.RawMessage(`{ "type": "object", @@ -134,15 +134,15 @@ func swarmPlanPrompt() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - prompt := swarm.PlanPrompt(input.Task, input.Strategy, input.Context, input.MaxSubtasks) + prompt := forge.PlanPrompt(input.Task, input.Strategy, input.Context, input.MaxSubtasks) return prompt, nil }, } } -func swarmDecompose() *registry.Tool { +func forgeDecompose() *registry.Tool { return ®istry.Tool{ - Name: "swarm_decompose", + Name: "forge_decompose", Description: "Generate decomposition prompt for breaking task into subtasks.", InputSchema: json.RawMessage(`{ "type": "object", @@ -162,15 +162,15 @@ func swarmDecompose() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - prompt := swarm.Decompose(input.Task, input.Context, input.MaxSubtasks) + prompt := forge.Decompose(input.Task, input.Context, input.MaxSubtasks) return prompt, nil }, } } -func swarmValidateDecomposition() *registry.Tool { +func forgeValidateDecomposition() *registry.Tool { return ®istry.Tool{ - Name: "swarm_validate_decomposition", + Name: "forge_validate_decomposition", Description: "Validate a decomposition response against CellTreeSchema.", InputSchema: json.RawMessage(`{ "type": "object", @@ -186,7 +186,7 @@ func swarmValidateDecomposition() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.ValidateDecomposition(input.Response) + result, err := forge.ValidateDecomposition(input.Response) if err != nil { return "", err } @@ -198,9 +198,9 @@ func swarmValidateDecomposition() *registry.Tool { // --- Subtask Spawning --- -func swarmSubtaskPrompt() *registry.Tool { +func forgeSubtaskPrompt() *registry.Tool { return ®istry.Tool{ - Name: "swarm_subtask_prompt", + Name: "forge_subtask_prompt", Description: "Generate the prompt for a spawned subtask agent.", InputSchema: json.RawMessage(`{ "type": "object", @@ -227,15 +227,15 @@ func swarmSubtaskPrompt() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - prompt := swarm.SubtaskPrompt(input.AgentName, input.BeadID, input.EpicID, input.SubtaskTitle, input.Files, input.SharedCtx) + prompt := forge.SubtaskPrompt(input.AgentName, input.BeadID, input.EpicID, input.SubtaskTitle, input.Files, input.SharedCtx) return prompt, nil }, } } -func swarmSpawnSubtask() *registry.Tool { +func forgeSpawnSubtask() *registry.Tool { return ®istry.Tool{ - Name: "swarm_spawn_subtask", + Name: "forge_spawn_subtask", Description: "Prepare a subtask for spawning with Task tool.", InputSchema: json.RawMessage(`{ "type": "object", @@ -261,7 +261,7 @@ func swarmSpawnSubtask() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.SpawnSubtask(input.BeadID, input.EpicID, input.Title, input.Files, input.Description, input.SharedCtx) + result, err := forge.SpawnSubtask(input.BeadID, input.EpicID, input.Title, input.Files, input.Description, input.SharedCtx) if err != nil { return "", err } @@ -271,9 +271,9 @@ func swarmSpawnSubtask() *registry.Tool { } } -func swarmCompleteSubtask(store *db.Store) *registry.Tool { +func forgeCompleteSubtask(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_complete_subtask", + Name: "forge_complete_subtask", Description: "Handle subtask completion after Task agent returns.", InputSchema: json.RawMessage(`{ "type": "object", @@ -293,7 +293,7 @@ func swarmCompleteSubtask(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.CompleteSubtask(store, input.BeadID, input.TaskResult, input.FilesTouched) + result, err := forge.CompleteSubtask(store, input.BeadID, input.TaskResult, input.FilesTouched) if err != nil { return "", err } @@ -305,9 +305,9 @@ func swarmCompleteSubtask(store *db.Store) *registry.Tool { // --- Progress & Status --- -func swarmProgress(store *db.Store) *registry.Tool { +func forgeProgress(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_progress", + Name: "forge_progress", Description: "Report progress on a subtask to coordinator.", InputSchema: json.RawMessage(`{ "type": "object", @@ -335,7 +335,7 @@ func swarmProgress(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - err := swarm.Progress(store, input.ProjectKey, input.AgentName, input.BeadID, input.Status, input.ProgressPercent, input.Message, input.FilesTouched) + err := forge.Progress(store, input.ProjectKey, input.AgentName, input.BeadID, input.Status, input.ProgressPercent, input.Message, input.FilesTouched) if err != nil { return "", err } @@ -344,9 +344,9 @@ func swarmProgress(store *db.Store) *registry.Tool { } } -func swarmComplete(store *db.Store) *registry.Tool { +func forgeComplete(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_complete", + Name: "forge_complete", Description: "Mark subtask complete with Verification Gate.", InputSchema: json.RawMessage(`{ "type": "object", @@ -376,7 +376,7 @@ func swarmComplete(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.Complete(store, input.ProjectKey, input.AgentName, input.BeadID, input.Summary, input.FilesTouched, input.Evaluation, input.SkipVerification, input.SkipReview) + result, err := forge.Complete(store, input.ProjectKey, input.AgentName, input.BeadID, input.Summary, input.FilesTouched, input.Evaluation, input.SkipVerification, input.SkipReview) if err != nil { return "", err } @@ -386,9 +386,9 @@ func swarmComplete(store *db.Store) *registry.Tool { } } -func swarmStatus(store *db.Store) *registry.Tool { +func forgeStatus(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_status", + Name: "forge_status", Description: "Get status of a swarm by epic ID.", InputSchema: json.RawMessage(`{ "type": "object", @@ -406,7 +406,7 @@ func swarmStatus(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.Status(store, input.EpicID, input.ProjectKey) + result, err := forge.Status(store, input.EpicID, input.ProjectKey) if err != nil { return "", err } @@ -416,9 +416,9 @@ func swarmStatus(store *db.Store) *registry.Tool { } } -func swarmRecordOutcome(store *db.Store) *registry.Tool { +func forgeRecordOutcome(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_record_outcome", + Name: "forge_record_outcome", Description: "Record subtask outcome for implicit feedback scoring.", InputSchema: json.RawMessage(`{ "type": "object", @@ -448,7 +448,7 @@ func swarmRecordOutcome(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - err := swarm.RecordOutcome(store, input.BeadID, input.DurationMs, input.Success, input.Strategy, input.FilesTouched, input.ErrorCount, input.RetryCount, input.Criteria) + err := forge.RecordOutcome(store, input.BeadID, input.DurationMs, input.Success, input.Strategy, input.FilesTouched, input.ErrorCount, input.RetryCount, input.Criteria) if err != nil { return "", err } @@ -459,9 +459,9 @@ func swarmRecordOutcome(store *db.Store) *registry.Tool { // --- Worktree Management --- -func swarmWorktreeCreate() *registry.Tool { +func forgeWorktreeCreate() *registry.Tool { return ®istry.Tool{ - Name: "swarm_worktree_create", + Name: "forge_worktree_create", Description: "Create a git worktree for isolated task execution.", InputSchema: json.RawMessage(`{ "type": "object", @@ -481,7 +481,7 @@ func swarmWorktreeCreate() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.WorktreeCreate(input.ProjectPath, input.TaskID, input.StartCommit) + result, err := forge.WorktreeCreate(input.ProjectPath, input.TaskID, input.StartCommit) if err != nil { return "", err } @@ -491,9 +491,9 @@ func swarmWorktreeCreate() *registry.Tool { } } -func swarmWorktreeMerge() *registry.Tool { +func forgeWorktreeMerge() *registry.Tool { return ®istry.Tool{ - Name: "swarm_worktree_merge", + Name: "forge_worktree_merge", Description: "Cherry-pick commits from worktree back to main branch.", InputSchema: json.RawMessage(`{ "type": "object", @@ -513,7 +513,7 @@ func swarmWorktreeMerge() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.WorktreeMerge(input.ProjectPath, input.TaskID, input.StartCommit) + result, err := forge.WorktreeMerge(input.ProjectPath, input.TaskID, input.StartCommit) if err != nil { return "", err } @@ -523,9 +523,9 @@ func swarmWorktreeMerge() *registry.Tool { } } -func swarmWorktreeCleanup() *registry.Tool { +func forgeWorktreeCleanup() *registry.Tool { return ®istry.Tool{ - Name: "swarm_worktree_cleanup", + Name: "forge_worktree_cleanup", Description: "Remove a worktree after completion or abort. Idempotent.", InputSchema: json.RawMessage(`{ "type": "object", @@ -545,7 +545,7 @@ func swarmWorktreeCleanup() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.WorktreeCleanup(input.ProjectPath, input.TaskID, input.CleanupAll) + result, err := forge.WorktreeCleanup(input.ProjectPath, input.TaskID, input.CleanupAll) if err != nil { return "", err } @@ -555,9 +555,9 @@ func swarmWorktreeCleanup() *registry.Tool { } } -func swarmWorktreeList() *registry.Tool { +func forgeWorktreeList() *registry.Tool { return ®istry.Tool{ - Name: "swarm_worktree_list", + Name: "forge_worktree_list", Description: "List all active worktrees for a project.", InputSchema: json.RawMessage(`{ "type": "object", @@ -573,7 +573,7 @@ func swarmWorktreeList() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.WorktreeList(input.ProjectPath) + result, err := forge.WorktreeList(input.ProjectPath) if err != nil { return "", err } @@ -585,9 +585,9 @@ func swarmWorktreeList() *registry.Tool { // --- Review & Broadcast --- -func swarmReview() *registry.Tool { +func forgeReview() *registry.Tool { return ®istry.Tool{ - Name: "swarm_review", + Name: "forge_review", Description: "Generate a review prompt for a completed subtask.", InputSchema: json.RawMessage(`{ "type": "object", @@ -609,15 +609,15 @@ func swarmReview() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - prompt := swarm.Review(input.ProjectKey, input.EpicID, input.TaskID, input.FilesTouched) + prompt := forge.Review(input.ProjectKey, input.EpicID, input.TaskID, input.FilesTouched) return prompt, nil }, } } -func swarmReviewFeedback(store *db.Store) *registry.Tool { +func forgeReviewFeedback(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_review_feedback", + Name: "forge_review_feedback", Description: "Send review feedback to a worker. Tracks attempts (max 3).", InputSchema: json.RawMessage(`{ "type": "object", @@ -643,7 +643,7 @@ func swarmReviewFeedback(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.ReviewFeedback(store, input.ProjectKey, input.TaskID, input.WorkerID, input.Status, input.Issues, input.Summary) + result, err := forge.ReviewFeedback(store, input.ProjectKey, input.TaskID, input.WorkerID, input.Status, input.Issues, input.Summary) if err != nil { return "", err } @@ -653,9 +653,9 @@ func swarmReviewFeedback(store *db.Store) *registry.Tool { } } -func swarmAdversarialReview() *registry.Tool { +func forgeAdversarialReview() *registry.Tool { return ®istry.Tool{ - Name: "swarm_adversarial_review", + Name: "forge_adversarial_review", Description: "VDD-style adversarial code review using hostile, fresh-context agent.", InputSchema: json.RawMessage(`{ "type": "object", @@ -673,15 +673,15 @@ func swarmAdversarialReview() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - prompt := swarm.AdversarialReview(input.Diff, input.TestOutput) + prompt := forge.AdversarialReview(input.Diff, input.TestOutput) return prompt, nil }, } } -func swarmEvaluationPrompt() *registry.Tool { +func forgeEvaluationPrompt() *registry.Tool { return ®istry.Tool{ - Name: "swarm_evaluation_prompt", + Name: "forge_evaluation_prompt", Description: "Generate self-evaluation prompt for a completed subtask.", InputSchema: json.RawMessage(`{ "type": "object", @@ -701,15 +701,15 @@ func swarmEvaluationPrompt() *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - prompt := swarm.EvaluationPrompt(input.BeadID, input.SubtaskTitle, input.FilesTouched) + prompt := forge.EvaluationPrompt(input.BeadID, input.SubtaskTitle, input.FilesTouched) return prompt, nil }, } } -func swarmBroadcast(store *db.Store) *registry.Tool { +func forgeBroadcast(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_broadcast", + Name: "forge_broadcast", Description: "Broadcast context update to all agents working on the same epic.", InputSchema: json.RawMessage(`{ "type": "object", @@ -735,7 +735,7 @@ func swarmBroadcast(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - err := swarm.Broadcast(store, input.ProjectPath, input.AgentName, input.EpicID, input.Message, input.Importance, input.FilesAffected) + err := forge.Broadcast(store, input.ProjectPath, input.AgentName, input.EpicID, input.Message, input.Importance, input.FilesAffected) if err != nil { return "", err } @@ -746,9 +746,9 @@ func swarmBroadcast(store *db.Store) *registry.Tool { // --- Insights --- -func swarmGetStrategyInsights(store *db.Store) *registry.Tool { +func forgeGetStrategyInsights(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_get_strategy_insights", + Name: "forge_get_strategy_insights", Description: "Get strategy success rates for decomposition planning.", InputSchema: json.RawMessage(`{ "type": "object", @@ -764,7 +764,7 @@ func swarmGetStrategyInsights(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.GetStrategyInsights(store, input.Task) + result, err := forge.GetStrategyInsights(store, input.Task) if err != nil { return "", err } @@ -774,9 +774,9 @@ func swarmGetStrategyInsights(store *db.Store) *registry.Tool { } } -func swarmGetFileInsights(store *db.Store) *registry.Tool { +func forgeGetFileInsights(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_get_file_insights", + Name: "forge_get_file_insights", Description: "Get file-specific gotchas for worker context.", InputSchema: json.RawMessage(`{ "type": "object", @@ -792,7 +792,7 @@ func swarmGetFileInsights(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - result, err := swarm.GetFileInsights(store, input.Files) + result, err := forge.GetFileInsights(store, input.Files) if err != nil { return "", err } @@ -802,16 +802,16 @@ func swarmGetFileInsights(store *db.Store) *registry.Tool { } } -func swarmGetPatternInsights(store *db.Store) *registry.Tool { +func forgeGetPatternInsights(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "swarm_get_pattern_insights", - Description: "Get common failure patterns across swarms.", + Name: "forge_get_pattern_insights", + Description: "Get common failure patterns across forges.", InputSchema: json.RawMessage(`{ "type": "object", "properties": {} }`), Execute: func(args json.RawMessage) (string, error) { - result, err := swarm.GetPatternInsights(store) + result, err := forge.GetPatternInsights(store) if err != nil { return "", err } diff --git a/internal/tools/hive/tools.go b/internal/tools/org/tools.go similarity index 74% rename from internal/tools/hive/tools.go rename to internal/tools/org/tools.go index 92063f2..57b4d90 100644 --- a/internal/tools/hive/tools.go +++ b/internal/tools/org/tools.go @@ -1,33 +1,33 @@ -// Package hive registers MCP tools for hive cell operations. -package hive +// Package org registers MCP tools for org cell operations. +package org import ( "encoding/json" "github.com/unbound-force/replicator/internal/db" - "github.com/unbound-force/replicator/internal/hive" + "github.com/unbound-force/replicator/internal/org" "github.com/unbound-force/replicator/internal/tools/registry" ) -// Register adds all hive tools to the registry. +// Register adds all org tools to the registry. func Register(reg *registry.Registry, store *db.Store) { - reg.Register(hiveCells(store)) - reg.Register(hiveCreate(store)) - reg.Register(hiveClose(store)) - reg.Register(hiveUpdate(store)) - reg.Register(hiveCreateEpic(store)) - reg.Register(hiveQuery(store)) - reg.Register(hiveStart(store)) - reg.Register(hiveReady(store)) - reg.Register(hiveSync(store)) - reg.Register(hiveSessionStart(store)) - reg.Register(hiveSessionEnd(store)) + reg.Register(orgCells(store)) + reg.Register(orgCreate(store)) + reg.Register(orgClose(store)) + reg.Register(orgUpdate(store)) + reg.Register(orgCreateEpic(store)) + reg.Register(orgQuery(store)) + reg.Register(orgStart(store)) + reg.Register(orgReady(store)) + reg.Register(orgSync(store)) + reg.Register(orgSessionStart(store)) + reg.Register(orgSessionEnd(store)) } -func hiveCells(store *db.Store) *registry.Tool { +func orgCells(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_cells", - Description: "Query cells from the hive database with flexible filtering. Use to list work items, find by status/type, or look up a cell by partial ID.", + Name: "org_cells", + Description: "Query cells from the org database with flexible filtering. Use to list work items, find by status/type, or look up a cell by partial ID.", InputSchema: json.RawMessage(`{ "type": "object", "properties": { @@ -39,11 +39,11 @@ func hiveCells(store *db.Store) *registry.Tool { } }`), Execute: func(args json.RawMessage) (string, error) { - var q hive.CellQuery + var q org.CellQuery if len(args) > 0 { json.Unmarshal(args, &q) } - cells, err := hive.QueryCells(store, q) + cells, err := org.QueryCells(store, q) if err != nil { return "", err } @@ -53,10 +53,10 @@ func hiveCells(store *db.Store) *registry.Tool { } } -func hiveCreate(store *db.Store) *registry.Tool { +func orgCreate(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_create", - Description: "Create a new cell (work item) in the hive.", + Name: "org_create", + Description: "Create a new cell (work item) in the org.", InputSchema: json.RawMessage(`{ "type": "object", "required": ["title"], @@ -69,11 +69,11 @@ func hiveCreate(store *db.Store) *registry.Tool { } }`), Execute: func(args json.RawMessage) (string, error) { - var input hive.CreateCellInput + var input org.CreateCellInput if err := json.Unmarshal(args, &input); err != nil { return "", err } - cell, err := hive.CreateCell(store, input) + cell, err := org.CreateCell(store, input) if err != nil { return "", err } @@ -83,9 +83,9 @@ func hiveCreate(store *db.Store) *registry.Tool { } } -func hiveClose(store *db.Store) *registry.Tool { +func orgClose(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_close", + Name: "org_close", Description: "Close a cell with a reason.", InputSchema: json.RawMessage(`{ "type": "object", @@ -103,7 +103,7 @@ func hiveClose(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - if err := hive.CloseCell(store, input.ID, input.Reason); err != nil { + if err := org.CloseCell(store, input.ID, input.Reason); err != nil { return "", err } return `{"status": "closed"}`, nil @@ -111,9 +111,9 @@ func hiveClose(store *db.Store) *registry.Tool { } } -func hiveUpdate(store *db.Store) *registry.Tool { +func orgUpdate(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_update", + Name: "org_update", Description: "Update a cell's status, description, or priority.", InputSchema: json.RawMessage(`{ "type": "object", @@ -135,7 +135,7 @@ func hiveUpdate(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - if err := hive.UpdateCell(store, input.ID, input.Status, input.Description, input.Priority); err != nil { + if err := org.UpdateCell(store, input.ID, input.Status, input.Description, input.Priority); err != nil { return "", err } return `{"status": "updated"}`, nil @@ -143,9 +143,9 @@ func hiveUpdate(store *db.Store) *registry.Tool { } } -func hiveCreateEpic(store *db.Store) *registry.Tool { +func orgCreateEpic(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_create_epic", + Name: "org_create_epic", Description: "Create an epic with subtasks in one atomic operation.", InputSchema: json.RawMessage(`{ "type": "object", @@ -168,11 +168,11 @@ func hiveCreateEpic(store *db.Store) *registry.Tool { } }`), Execute: func(args json.RawMessage) (string, error) { - var input hive.CreateEpicInput + var input org.CreateEpicInput if err := json.Unmarshal(args, &input); err != nil { return "", err } - epic, subtasks, err := hive.CreateEpic(store, input) + epic, subtasks, err := org.CreateEpic(store, input) if err != nil { return "", err } @@ -186,10 +186,10 @@ func hiveCreateEpic(store *db.Store) *registry.Tool { } } -func hiveQuery(store *db.Store) *registry.Tool { +func orgQuery(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_query", - Description: "Query cells with filters (alias for hive_cells).", + Name: "org_query", + Description: "Query cells with filters (alias for org_cells).", InputSchema: json.RawMessage(`{ "type": "object", "properties": { @@ -201,11 +201,11 @@ func hiveQuery(store *db.Store) *registry.Tool { } }`), Execute: func(args json.RawMessage) (string, error) { - var q hive.CellQuery + var q org.CellQuery if len(args) > 0 { json.Unmarshal(args, &q) } - cells, err := hive.QueryCells(store, q) + cells, err := org.QueryCells(store, q) if err != nil { return "", err } @@ -215,9 +215,9 @@ func hiveQuery(store *db.Store) *registry.Tool { } } -func hiveStart(store *db.Store) *registry.Tool { +func orgStart(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_start", + Name: "org_start", Description: "Mark a cell as in-progress.", InputSchema: json.RawMessage(`{ "type": "object", @@ -233,7 +233,7 @@ func hiveStart(store *db.Store) *registry.Tool { if err := json.Unmarshal(args, &input); err != nil { return "", err } - if err := hive.StartCell(store, input.ID); err != nil { + if err := org.StartCell(store, input.ID); err != nil { return "", err } return `{"status": "in_progress"}`, nil @@ -241,16 +241,16 @@ func hiveStart(store *db.Store) *registry.Tool { } } -func hiveReady(store *db.Store) *registry.Tool { +func orgReady(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_ready", + Name: "org_ready", Description: "Get the next ready cell (unblocked, highest priority).", InputSchema: json.RawMessage(`{ "type": "object", "properties": {} }`), Execute: func(args json.RawMessage) (string, error) { - cell, err := hive.ReadyCell(store) + cell, err := org.ReadyCell(store) if err != nil { return "", err } @@ -263,9 +263,9 @@ func hiveReady(store *db.Store) *registry.Tool { } } -func hiveSync(store *db.Store) *registry.Tool { +func orgSync(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_sync", + Name: "org_sync", Description: "Sync cells to git and push.", InputSchema: json.RawMessage(`{ "type": "object", @@ -286,7 +286,7 @@ func hiveSync(store *db.Store) *registry.Tool { if projectPath == "" { projectPath = "." } - if err := hive.Sync(store, projectPath); err != nil { + if err := org.Sync(store, projectPath); err != nil { return "", err } return `{"status": "synced"}`, nil @@ -294,9 +294,9 @@ func hiveSync(store *db.Store) *registry.Tool { } } -func hiveSessionStart(store *db.Store) *registry.Tool { +func orgSessionStart(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_session_start", + Name: "org_session_start", Description: "Start a new work session. Returns previous session's handoff notes if available.", InputSchema: json.RawMessage(`{ "type": "object", @@ -311,7 +311,7 @@ func hiveSessionStart(store *db.Store) *registry.Tool { if len(args) > 0 { json.Unmarshal(args, &input) } - notes, err := hive.SessionStart(store, input.ActiveCellID) + notes, err := org.SessionStart(store, input.ActiveCellID) if err != nil { return "", err } @@ -325,9 +325,9 @@ func hiveSessionStart(store *db.Store) *registry.Tool { } } -func hiveSessionEnd(store *db.Store) *registry.Tool { +func orgSessionEnd(store *db.Store) *registry.Tool { return ®istry.Tool{ - Name: "hive_session_end", + Name: "org_session_end", Description: "End current session with handoff notes for next session.", InputSchema: json.RawMessage(`{ "type": "object", @@ -342,7 +342,7 @@ func hiveSessionEnd(store *db.Store) *registry.Tool { if len(args) > 0 { json.Unmarshal(args, &input) } - if err := hive.SessionEnd(store, input.HandoffNotes); err != nil { + if err := org.SessionEnd(store, input.HandoffNotes); err != nil { return "", err } return `{"status": "ended"}`, nil diff --git a/replicator b/replicator new file mode 100755 index 0000000..851e572 Binary files /dev/null and b/replicator differ diff --git a/specs/003-rename-terminology/checklists/requirements.md b/specs/003-rename-terminology/checklists/requirements.md new file mode 100644 index 0000000..f6e196a --- /dev/null +++ b/specs/003-rename-terminology/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: Terminology Rename + Agent Kit + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-04-06 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass validation. +- FR-008 lists 7 skill files but one (`forge-coordination`) appears twice (plugin skill + global skill). The implementation should create it once in `.opencode/skills/`. This is an artifact of the upstream having separate plugin-skills and global-skills directories; in replicator's delivery model they're all installed to the same location. +- The spec references "45 non-deprecated tools" (US1) and "53 tools" (SC-001). Both are correct: 53 total = 45 renamed + 8 unchanged `hivemind_*`. +- All decisions from the triage discussion are locked in: `forge` for orchestration, `replicator.db` stays, no migration, Speckit tier, project-local skills. diff --git a/specs/003-rename-terminology/plan.md b/specs/003-rename-terminology/plan.md new file mode 100644 index 0000000..edf2239 --- /dev/null +++ b/specs/003-rename-terminology/plan.md @@ -0,0 +1,377 @@ +# Implementation Plan: Terminology Rename + Agent Kit + +**Branch**: `003-rename-terminology` | **Date**: 2026-04-06 | **Spec**: [spec.md](spec.md) +**Input**: Feature specification from `specs/003-rename-terminology/spec.md` + +## Summary + +Rename the three core MCP tool families (hive→org, swarmmail→comms, swarm→forge) across all Go packages, tool registrations, and documentation. Simultaneously ship an agent kit (5 commands, 7 skills, 3 agents) embedded in the binary and scaffolded by `replicator init`. + +The rename is a mechanical refactor touching 6 package directories, 45 tool `Name:` strings, 4 parity fixture files, and all import paths. The agent kit introduces `embed.FS` for 15 template files and extends the `init` command with `--force` flag support. + +## Technical Context + +**Language/Version**: Go 1.25+ +**Primary Dependencies**: cobra (CLI), modernc.org/sqlite (pure Go SQLite), embed (stdlib) +**Storage**: SQLite at `~/.config/uf/replicator/replicator.db` (WAL mode) +**Testing**: `go test` (stdlib only), parity tests with `//go:build parity` tag +**Target Platform**: macOS, Linux (cross-compiled via GoReleaser) +**Project Type**: CLI + MCP server +**Performance Goals**: N/A (rename is behavior-preserving) +**Constraints**: Zero behavioral change to tool responses; all 53 tools must pass existing tests +**Scale/Scope**: 53 MCP tools, ~20 Go files with import path changes, 15 new embedded files + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Status | Notes | +|-----------|--------|-------| +| **I. Autonomous Collaboration** | ✅ PASS | Tools remain independently callable via MCP. Only names change, not behavior or response shapes. | +| **II. Composability First** | ✅ PASS | Binary remains standalone. Agent kit is scaffolded locally, no external service dependency. `embed.FS` is stdlib — no new dependencies. | +| **III. Observable Quality** | ✅ PASS | All tool responses remain JSON. Parity test fixtures updated to new names. `replicator docs` updated to new categories. | +| **IV. Testability** | ✅ PASS | All tests continue using `db.OpenMemory()`, `t.TempDir()`, `httptest`. New init tests verify agent kit scaffolding with `t.TempDir()`. | + +No violations. No complexity tracking needed. + +## Project Structure + +### Documentation (this feature) + +```text +specs/003-rename-terminology/ +├── plan.md # This file +├── research.md # Rename impact analysis +├── quickstart.md # Implementation quick-reference +└── tasks.md # Phase 2 output (/speckit.tasks) +``` + +### Source Code — Before → After + +```text +# Package renames (git mv) +internal/hive/ → internal/org/ +internal/swarmmail/ → internal/comms/ +internal/swarm/ → internal/forge/ +internal/tools/hive/ → internal/tools/org/ +internal/tools/swarmmail/ → internal/tools/comms/ +internal/tools/swarm/ → internal/tools/forge/ + +# New files (agent kit) +internal/agentkit/ +├── agentkit.go # embed.FS + scaffold logic +├── agentkit_test.go # Tests for scaffold, skip, force +└── content/ # Embedded template files + ├── command/ + │ ├── forge.md + │ ├── org.md + │ ├── inbox.md + │ ├── forge-status.md + │ └── handoff.md + ├── skills/ + │ ├── always-on-guidance/SKILL.md + │ ├── forge-coordination/SKILL.md + │ ├── replicator-cli/SKILL.md + │ ├── testing-patterns/SKILL.md + │ ├── system-design/SKILL.md + │ ├── learning-systems/SKILL.md + │ └── forge-global/SKILL.md + └── agents/ + ├── coordinator.md + ├── worker.md + └── background-worker.md + +# Modified files (import paths + tool names) +cmd/replicator/serve.go # Import path updates +cmd/replicator/docs.go # Import paths + category map +cmd/replicator/init.go # Agent kit integration + --force flag +cmd/replicator/init_test.go # Agent kit test cases +test/parity/parity_test.go # Import paths + fixture tool names +test/parity/fixtures/*.json # Tool name keys updated +``` + +**Structure Decision**: The existing single-project Go structure is preserved. The `internal/agentkit/` package is added for embed.FS content and scaffold logic, following the existing pattern of domain packages under `internal/`. + +## Implementation Phases + +### Phase 1: Directory Renames [US1] + +Rename 6 Go package directories using `git mv` to preserve history. This is the foundation — all subsequent phases depend on these directories existing at their new paths. + +**Files touched**: +- `internal/hive/` → `internal/org/` (11 files) +- `internal/swarmmail/` → `internal/comms/` (6 files) +- `internal/swarm/` → `internal/forge/` (14 files) +- `internal/tools/hive/` → `internal/tools/org/` (1 file) +- `internal/tools/swarmmail/` → `internal/tools/comms/` (1 file) +- `internal/tools/swarm/` → `internal/tools/forge/` (1 file) + +**Risk**: LOW. `git mv` preserves history. The build will break until Phase 2 completes (expected). + +**Checkpoint**: `ls internal/org/ internal/comms/ internal/forge/` succeeds. Old directories do not exist. + +--- + +### Phase 2: Import Path Updates [US1] + +Update all Go import paths from old package names to new ones. Also update package declarations in renamed files. + +**Files touched** (all `.go` files importing renamed packages): +- `cmd/replicator/serve.go` — 4 import paths +- `cmd/replicator/docs.go` — 4 import paths + import aliases +- `cmd/replicator/cells.go` — if it imports hive +- `test/parity/parity_test.go` — 4 import paths + aliases +- `internal/tools/org/tools.go` — package declaration + imports +- `internal/tools/comms/tools.go` — package declaration + imports +- `internal/tools/forge/tools.go` — package declaration + imports +- `internal/org/*.go` — package declarations (hive → org) +- `internal/comms/*.go` — package declarations (swarmmail → comms) +- `internal/forge/*.go` — package declarations (swarm → forge) +- `internal/query/presets.go` — if it references hive/swarm packages +- `internal/stats/stats_test.go` — if it references hive/swarm packages + +**Import path mapping**: +``` +github.com/unbound-force/replicator/internal/hive → .../internal/org +github.com/unbound-force/replicator/internal/swarmmail → .../internal/comms +github.com/unbound-force/replicator/internal/swarm → .../internal/forge +github.com/unbound-force/replicator/internal/tools/hive → .../internal/tools/org +github.com/unbound-force/replicator/internal/tools/swarmmail → .../internal/tools/comms +github.com/unbound-force/replicator/internal/tools/swarm → .../internal/tools/forge +``` + +**Risk**: MEDIUM. Many files touched. Compiler will catch all missed imports. + +**Checkpoint**: `go vet ./...` passes. `go build ./cmd/replicator` succeeds. + +--- + +### Phase 3: Tool Name String Updates [US1] + +Update the 45 MCP tool `Name:` string literals from old prefixes to new prefixes. Also update Go function names for consistency (e.g., `hiveCells` → `orgCells`). + +**Tool name mapping** (45 tools): + +| Old Name | New Name | +|----------|----------| +| `hive_cells` | `org_cells` | +| `hive_create` | `org_create` | +| `hive_close` | `org_close` | +| `hive_update` | `org_update` | +| `hive_create_epic` | `org_create_epic` | +| `hive_query` | `org_query` | +| `hive_start` | `org_start` | +| `hive_ready` | `org_ready` | +| `hive_sync` | `org_sync` | +| `hive_session_start` | `org_session_start` | +| `hive_session_end` | `org_session_end` | +| `swarmmail_init` | `comms_init` | +| `swarmmail_send` | `comms_send` | +| `swarmmail_inbox` | `comms_inbox` | +| `swarmmail_read_message` | `comms_read_message` | +| `swarmmail_reserve` | `comms_reserve` | +| `swarmmail_release` | `comms_release` | +| `swarmmail_release_all` | `comms_release_all` | +| `swarmmail_release_agent` | `comms_release_agent` | +| `swarmmail_ack` | `comms_ack` | +| `swarmmail_health` | `comms_health` | +| `swarm_init` | `forge_init` | +| `swarm_select_strategy` | `forge_select_strategy` | +| `swarm_plan_prompt` | `forge_plan_prompt` | +| `swarm_decompose` | `forge_decompose` | +| `swarm_validate_decomposition` | `forge_validate_decomposition` | +| `swarm_subtask_prompt` | `forge_subtask_prompt` | +| `swarm_spawn_subtask` | `forge_spawn_subtask` | +| `swarm_complete_subtask` | `forge_complete_subtask` | +| `swarm_progress` | `forge_progress` | +| `swarm_complete` | `forge_complete` | +| `swarm_status` | `forge_status` | +| `swarm_record_outcome` | `forge_record_outcome` | +| `swarm_worktree_create` | `forge_worktree_create` | +| `swarm_worktree_merge` | `forge_worktree_merge` | +| `swarm_worktree_cleanup` | `forge_worktree_cleanup` | +| `swarm_worktree_list` | `forge_worktree_list` | +| `swarm_review` | `forge_review` | +| `swarm_review_feedback` | `forge_review_feedback` | +| `swarm_adversarial_review` | `forge_adversarial_review` | +| `swarm_evaluation_prompt` | `forge_evaluation_prompt` | +| `swarm_broadcast` | `forge_broadcast` | +| `swarm_get_strategy_insights` | `forge_get_strategy_insights` | +| `swarm_get_file_insights` | `forge_get_file_insights` | +| `swarm_get_pattern_insights` | `forge_get_pattern_insights` | + +**Unchanged** (8 tools): `hivemind_store`, `hivemind_find`, `hivemind_get`, `hivemind_remove`, `hivemind_validate`, `hivemind_stats`, `hivemind_index`, `hivemind_sync`. + +**Files touched**: +- `internal/tools/org/tools.go` — 11 Name strings + 11 function names +- `internal/tools/comms/tools.go` — 10 Name strings + 10 function names +- `internal/tools/forge/tools.go` — 24 Name strings + 24 function names +- Tool description strings that reference old names (e.g., "alias for hive_cells") + +**Risk**: LOW. Mechanical find-and-replace. Parity tests will catch any missed renames. + +**Checkpoint**: `go test ./internal/tools/... -count=1` passes. `grep -r '"hive_\|"swarmmail_\|"swarm_' internal/tools/` returns zero matches (excluding `hivemind_`). + +--- + +### Phase 4: Parity Fixtures + Docs Category Map [US1, US4] + +Update parity test fixtures to use new tool names. Update the `categories` slice in `docs.go` to map new prefixes to new display names. + +**Files touched**: +- `test/parity/fixtures/hive.json` — rename tool name keys to `org_*` +- `test/parity/fixtures/swarmmail.json` — rename tool name keys to `comms_*` +- `test/parity/fixtures/swarm.json` — rename tool name keys to `forge_*` +- `test/parity/parity_test.go` — update hardcoded tool name strings (e.g., `"hive_create"` → `"org_create"`, `"hive_cells"` → `"org_cells"`) +- `cmd/replicator/docs.go` — update `categories` slice: + ```go + {"org_", "Org"}, + {"comms_", "Comms"}, + {"forge_", "Forge"}, + {"hivemind_", "Memory"}, + ``` +- `cmd/replicator/docs.go` — update Long description text +- `internal/query/presets.go` — update any hardcoded tool name references +- `internal/stats/stats_test.go` — update any hardcoded tool name references + +**Risk**: LOW. Fixture files are JSON with tool name keys. Mechanical rename. + +**Checkpoint**: `go test -tags parity ./test/parity/ -count=1` passes. `replicator docs` output shows Org/Comms/Forge/Memory categories. + +--- + +### Phase 5: Write Agent Kit Content [US2, US3] + +Create the `internal/agentkit/` package with `embed.FS` and 15 template files. The content is adapted from the upstream `joelhooks/swarm-tools` Claude Code plugin, rewritten for OpenCode conventions and the new org/comms/forge terminology. + +**Files created**: +- `internal/agentkit/content/command/forge.md` — Forge orchestrator command (adapted from upstream `/swarm:swarm`) +- `internal/agentkit/content/command/org.md` — Org management command +- `internal/agentkit/content/command/inbox.md` — Comms inbox command +- `internal/agentkit/content/command/forge-status.md` — Forge status command +- `internal/agentkit/content/command/handoff.md` — Session handoff command +- `internal/agentkit/content/skills/always-on-guidance/SKILL.md` +- `internal/agentkit/content/skills/forge-coordination/SKILL.md` +- `internal/agentkit/content/skills/replicator-cli/SKILL.md` +- `internal/agentkit/content/skills/testing-patterns/SKILL.md` +- `internal/agentkit/content/skills/system-design/SKILL.md` +- `internal/agentkit/content/skills/learning-systems/SKILL.md` +- `internal/agentkit/content/skills/forge-global/SKILL.md` +- `internal/agentkit/content/agents/coordinator.md` +- `internal/agentkit/content/agents/worker.md` +- `internal/agentkit/content/agents/background-worker.md` +- `internal/agentkit/agentkit.go` — `Scaffold(targetDir string, force bool) ([]string, error)` function + `embed.FS` +- `internal/agentkit/agentkit_test.go` — Tests for scaffold, skip-existing, force-overwrite + +**Design decisions**: +- Use `embed.FS` (stdlib) — no external dependency, files compiled into binary +- `Scaffold()` returns `[]string` of created/skipped files for CLI output +- Files are written to `.opencode/` under the target directory +- Skip logic: check `os.Stat()` before writing; skip if exists and `force=false` + +**Risk**: MEDIUM. Content authoring is the largest creative effort. Template quality matters for agent usability. + +**Checkpoint**: `go test ./internal/agentkit/ -count=1` passes. `go build ./cmd/replicator` succeeds. + +--- + +### Phase 6: Enhance `replicator init` [US2] + +Integrate the agent kit scaffold into the `init` command. Add `--force` flag. + +**Files touched**: +- `cmd/replicator/init.go` — import `agentkit`, call `Scaffold()`, add `--force` flag +- `cmd/replicator/init_test.go` — add tests for agent kit creation, skip-existing, force-overwrite + +**Behavior changes**: +1. `replicator init` now creates `.uf/replicator/cells.json` AND scaffolds 15 agent kit files to `.opencode/` +2. `--force` flag overwrites existing agent kit files +3. Without `--force`, existing files are skipped with a "skipped" message +4. Output lists each file created/skipped using styled output + +**Risk**: LOW. The `agentkit.Scaffold()` function encapsulates all complexity. Init just calls it. + +**Checkpoint**: `go test ./cmd/replicator/ -count=1 -run TestRunInit` passes. Manual smoke test: `replicator init` in temp dir creates 16 files. + +--- + +### Phase 7: Documentation Updates [US4] + +Update all documentation to use the new terminology consistently. + +**Files touched**: +- `AGENTS.md` — naming convention table, project structure, MCP protocol example, CLI commands table, constitution references +- `README.md` — if it references old tool names +- `.specify/memory/constitution.md` — update "swarm mail" references to "comms", "hive" to "org" +- `specs/003-rename-terminology/spec.md` — mark as implemented + +**Risk**: LOW. Text changes only. + +**Checkpoint**: `grep -r 'hive_\|swarmmail_\|swarm_' AGENTS.md README.md .specify/memory/constitution.md` returns zero matches (excluding historical attribution and `hivemind_`). + +--- + +### Phase 8: Verification [ALL] + +Full CI-equivalent verification pass. + +**Commands**: +```bash +# CI parity gate (from .github/workflows/ci.yml) +go vet ./... +go test ./... -count=1 -race +go build -o bin/replicator ./cmd/replicator + +# Parity tests +go test -tags parity ./test/parity/ -count=1 -v + +# Grep verification (SC-005) +grep -rn '"hive_\|"swarmmail_\|"swarm_' --include='*.go' | grep -v hivemind_ +# Expected: zero matches + +# Init smoke test (SC-002) +tmpdir=$(mktemp -d) +./bin/replicator init --path "$tmpdir" +find "$tmpdir" -type f | wc -l +# Expected: 16 files + +# Tool count verification (SC-001) +# Run replicator serve, call tools/list, count by prefix +``` + +**Risk**: LOW. Verification only, no code changes. + +**Checkpoint**: All commands pass with zero failures. + +## Dependency Graph + +``` +Phase 1 (dir renames) + └─→ Phase 2 (import paths) + └─→ Phase 3 (tool name strings) + └─→ Phase 4 (parity fixtures + docs map) + └─→ Phase 8 (verification) + +Phase 5 (agent kit content) ← independent, can start anytime + └─→ Phase 6 (init enhancement) + └─→ Phase 8 (verification) + +Phase 7 (documentation) ← depends on Phase 3 (needs final names) + └─→ Phase 8 (verification) +``` + +Phases 1–4 are strictly sequential (each breaks the build until the next completes). +Phase 5 is independent and can be developed in parallel with Phases 1–4. +Phase 6 depends on Phase 5. +Phase 7 depends on Phase 3 (needs final tool names). +Phase 8 depends on all other phases. + +## Risk Assessment + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| Missed tool name in string literal | LOW | HIGH (broken tool) | Grep verification in Phase 8 + parity tests | +| Import path typo | LOW | HIGH (build failure) | `go vet` catches immediately | +| Agent kit content quality | MEDIUM | MEDIUM (poor agent UX) | Adapt from proven upstream templates | +| Parity fixture key mismatch | LOW | MEDIUM (false test failure) | Mechanical rename, verified by test run | +| `embed.FS` path issues | LOW | LOW (build failure) | Well-documented stdlib feature, tested in Phase 5 | diff --git a/specs/003-rename-terminology/quickstart.md b/specs/003-rename-terminology/quickstart.md new file mode 100644 index 0000000..96a4166 --- /dev/null +++ b/specs/003-rename-terminology/quickstart.md @@ -0,0 +1,171 @@ +# Quickstart: Terminology Rename + Agent Kit + +**Branch**: `003-rename-terminology` | **Date**: 2026-04-06 + +## At a Glance + +| What | Details | +|------|---------| +| **Goal** | Rename hive→org, swarmmail→comms, swarm→forge. Ship agent kit via `replicator init`. | +| **Phases** | 8 (dir renames → imports → tool names → fixtures → agent kit → init → docs → verify) | +| **Files changed** | ~35 Go files + 4 JSON fixtures + 15 new embedded files | +| **Tools renamed** | 45 of 53 (8 `hivemind_*` unchanged) | +| **New package** | `internal/agentkit/` (embed.FS + scaffold logic) | +| **New flag** | `replicator init --force` | +| **CI commands** | `go vet ./...` → `go test ./... -count=1 -race` → `go build` | + +## Rename Cheat Sheet + +``` +hive_* → org_* (11 tools) +swarmmail_* → comms_* (10 tools) +swarm_* → forge_* (24 tools) +hivemind_* → hivemind_* (8 tools, UNCHANGED) +``` + +## Package Rename Commands + +```bash +# Phase 1: Directory renames +git mv internal/hive internal/org +git mv internal/swarmmail internal/comms +git mv internal/swarm internal/forge +git mv internal/tools/hive internal/tools/org +git mv internal/tools/swarmmail internal/tools/comms +git mv internal/tools/swarm internal/tools/forge +``` + +## Import Path Find-Replace + +``` +OLD: github.com/unbound-force/replicator/internal/hive +NEW: github.com/unbound-force/replicator/internal/org + +OLD: github.com/unbound-force/replicator/internal/swarmmail +NEW: github.com/unbound-force/replicator/internal/comms + +OLD: github.com/unbound-force/replicator/internal/swarm +NEW: github.com/unbound-force/replicator/internal/forge + +OLD: github.com/unbound-force/replicator/internal/tools/hive +NEW: github.com/unbound-force/replicator/internal/tools/org + +OLD: github.com/unbound-force/replicator/internal/tools/swarmmail +NEW: github.com/unbound-force/replicator/internal/tools/comms + +OLD: github.com/unbound-force/replicator/internal/tools/swarm +NEW: github.com/unbound-force/replicator/internal/tools/forge +``` + +## Package Declaration Updates + +``` +internal/org/*.go: package hive → package org +internal/comms/*.go: package swarmmail → package comms +internal/forge/*.go: package swarm → package forge +internal/tools/org/: package hive → package org +internal/tools/comms/: package swarmmail → package comms (note: was "swarmmail" not "swarmmailtools") +internal/tools/forge/: package swarm → package forge +``` + +## Import Alias Updates + +In files that use import aliases (serve.go, docs.go, parity_test.go): + +```go +// OLD +import ( + "github.com/unbound-force/replicator/internal/tools/hive" + swarmmailtools "github.com/unbound-force/replicator/internal/tools/swarmmail" + swarmtools "github.com/unbound-force/replicator/internal/tools/swarm" +) + +// NEW +import ( + "github.com/unbound-force/replicator/internal/tools/org" + commstools "github.com/unbound-force/replicator/internal/tools/comms" + forgetools "github.com/unbound-force/replicator/internal/tools/forge" +) +``` + +**Note**: The `hive` tool package was imported without an alias (it didn't conflict). After rename to `org`, it still won't conflict, so no alias needed. But `comms` and `forge` tool packages need aliases to avoid conflicting with the domain packages `internal/comms` and `internal/forge`. + +## Docs Category Map + +```go +// OLD +{"hive_", "Hive"}, +{"swarmmail_", "Swarm Mail"}, +{"swarm_", "Swarm"}, +{"hivemind_", "Memory"}, + +// NEW +{"org_", "Org"}, +{"comms_", "Comms"}, +{"forge_", "Forge"}, +{"hivemind_", "Memory"}, +``` + +## Agent Kit File Tree + +``` +.opencode/ +├── command/ +│ ├── forge.md # /forge orchestrator +│ ├── org.md # /org cell management +│ ├── inbox.md # /inbox check messages +│ ├── forge-status.md # /forge:status +│ └── handoff.md # /handoff session end +├── skills/ +│ ├── always-on-guidance/SKILL.md +│ ├── forge-coordination/SKILL.md +│ ├── replicator-cli/SKILL.md +│ ├── testing-patterns/SKILL.md +│ ├── system-design/SKILL.md +│ ├── learning-systems/SKILL.md +│ └── forge-global/SKILL.md +└── agents/ + ├── coordinator.md + ├── worker.md + └── background-worker.md +``` + +## Verification Commands + +```bash +# Build + test (CI parity) +go vet ./... +go test ./... -count=1 -race +go build -o bin/replicator ./cmd/replicator + +# Parity tests +go test -tags parity ./test/parity/ -count=1 -v + +# Grep for stale names (expect zero matches) +grep -rn '"hive_\|"swarmmail_\|"swarm_' --include='*.go' | grep -v hivemind_ + +# Init smoke test +tmpdir=$(mktemp -d) +./bin/replicator init --path "$tmpdir" +find "$tmpdir" -type f | wc -l # expect 16 + +# Tool count +# Start MCP server, call tools/list, verify: +# 11 org_* + 10 comms_* + 24 forge_* + 8 hivemind_* = 53 +``` + +## Key Gotchas + +1. **Build breaks between Phase 1 and Phase 2**: After `git mv`, all imports are broken. Phase 2 must complete before `go build` works again. Commit Phases 1+2 together. + +2. **Package name ≠ directory name after git mv**: `git mv internal/hive internal/org` moves the directory but does NOT update `package hive` declarations inside the files. Must manually update all `package` declarations. + +3. **Import alias conflicts**: After rename, `internal/org` (domain) and `internal/tools/org` (handlers) both have package name `org`. The tool handler package in `serve.go` and `docs.go` needs an alias (e.g., `orgtools`). Same for `comms`/`forge`. + +4. **Parity fixture keys**: The JSON fixture files use tool names as top-level keys. The `request.name` field inside each fixture also contains the tool name. Both must be updated. + +5. **`hivemind_*` is intentionally unchanged**: Don't rename these 8 tools. They're deprecated Dewey proxy stubs that will be removed in a future version. + +6. **Description strings**: Some tool descriptions reference old names (e.g., "alias for hive_cells"). These must be updated too. + +7. **Prompt-generating tools**: `swarm/spawn.go`, `swarm/review.go`, etc. generate text prompts that may contain tool name references. These must be updated to use `forge_*`, `org_*`, `comms_*` names. diff --git a/specs/003-rename-terminology/research.md b/specs/003-rename-terminology/research.md new file mode 100644 index 0000000..4b9fd33 --- /dev/null +++ b/specs/003-rename-terminology/research.md @@ -0,0 +1,249 @@ +# Research: Terminology Rename + Agent Kit + +**Branch**: `003-rename-terminology` | **Date**: 2026-04-06 + +## Rename Impact Analysis + +### Package Inventory + +Analyzed all Go source files to map the rename blast radius. + +#### Domain Packages (3 renames) + +| Current | New | Files | Test Files | Total | +|---------|-----|-------|------------|-------| +| `internal/hive/` | `internal/org/` | 6 | 5 | 11 | +| `internal/swarmmail/` | `internal/comms/` | 3 | 3 | 6 | +| `internal/swarm/` | `internal/forge/` | 8 | 6 | 14 | + +#### Tool Handler Packages (3 renames) + +| Current | New | Files | +|---------|-----|-------| +| `internal/tools/hive/` | `internal/tools/org/` | 1 (`tools.go`) | +| `internal/tools/swarmmail/` | `internal/tools/comms/` | 1 (`tools.go`) | +| `internal/tools/swarm/` | `internal/tools/forge/` | 1 (`tools.go`) | + +#### Unchanged Packages + +| Package | Reason | +|---------|--------| +| `internal/memory/` | Contains `hivemind_*` tools — intentionally unchanged per FR-004 | +| `internal/tools/memory/` | Same — deprecated Dewey proxy stubs | +| `internal/tools/registry/` | Framework code, no tool-specific names | +| `internal/db/` | Database layer, no tool-specific names | +| `internal/config/` | Configuration, no tool-specific names | +| `internal/mcp/` | JSON-RPC server, dispatches by name from registry | +| `internal/ui/` | Lipgloss styles, no tool-specific names | +| `internal/gitutil/` | Git operations, no tool-specific names | +| `internal/doctor/` | Health checks, no tool-specific names | +| `internal/stats/` | Statistics, may reference tool names in queries | +| `internal/query/` | Preset SQL queries, may reference tool names | + +### Import Path Consumers + +Files that import the renamed packages (found via grep): + +``` +cmd/replicator/serve.go — imports hive, swarmmail, swarm tool packages +cmd/replicator/docs.go — imports hive, swarmmail, swarm tool packages +cmd/replicator/cells.go — may import hive domain package +test/parity/parity_test.go — imports all 4 tool packages +internal/tools/org/tools.go — imports hive domain package (self) +internal/tools/comms/tools.go — imports swarmmail domain package (self) +internal/tools/forge/tools.go — imports swarm domain package (self) +``` + +### Tool Name String Occurrences + +Files containing tool name strings (beyond the tool handler packages): + +| File | Old References | Context | +|------|---------------|---------| +| `cmd/replicator/docs.go` | `"hive_"`, `"swarmmail_"`, `"swarm_"` | Category prefix map | +| `test/parity/parity_test.go` | `"hive_create"`, `"hive_cells"`, `"hive_close"`, etc. | Fixture tool name lookups | +| `test/parity/fixtures/hive.json` | Tool name keys | Parity fixture data | +| `test/parity/fixtures/swarmmail.json` | Tool name keys | Parity fixture data | +| `test/parity/fixtures/swarm.json` | Tool name keys | Parity fixture data | +| `internal/swarm/spawn.go` | May reference tool names in prompts | Subtask prompt generation | +| `internal/swarm/review.go` | May reference tool names in prompts | Review prompt generation | +| `internal/swarm/init.go` | May reference tool names | Init response | +| `internal/swarm/progress.go` | May reference tool names | Progress tracking | +| `internal/swarm/insights.go` | May reference tool names | Insight queries | +| `internal/query/presets.go` | May reference tool names in SQL | Preset analytics queries | +| `internal/stats/stats_test.go` | May reference tool names | Test fixtures | +| `internal/mcp/server_test.go` | May reference tool names | Server test fixtures | + +### Parity Test Fixture Structure + +Each fixture file is a JSON object mapping tool names to `{request, typescript_response}`: + +```json +{ + "hive_cells": { + "request": {"name": "hive_cells", "arguments": {}}, + "typescript_response": {"content": [{"type": "text", "text": "[]"}]} + } +} +``` + +The tool name appears in: +1. The top-level key (e.g., `"hive_cells"`) +2. The `request.name` field +3. Hardcoded references in `parity_test.go` (e.g., `reg.Get("hive_create")`) + +All three must be updated consistently. + +### Docs Command Category Map + +Current `categories` in `cmd/replicator/docs.go`: + +```go +var categories = []struct { + prefix string + name string +}{ + {"hive_", "Hive"}, + {"swarmmail_", "Swarm Mail"}, + {"swarm_", "Swarm"}, + {"hivemind_", "Memory"}, +} +``` + +**Important ordering note**: The `swarm_` prefix must come after `swarmmail_` in the current code because `swarmmail_` starts with `swarm`. After the rename, `comms_` and `forge_` have no prefix overlap, so ordering is no longer critical. However, `hivemind_` must remain to categorize the 8 deprecated memory tools. + +New categories: +```go +{"org_", "Org"}, +{"comms_", "Comms"}, +{"forge_", "Forge"}, +{"hivemind_", "Memory"}, +``` + +### Init Command Current Behavior + +`cmd/replicator/init.go` currently: +1. Creates `.uf/replicator/` directory +2. Writes `cells.json` with `[]\n` +3. Returns early if `.uf/replicator/` already exists (full idempotency) + +The early return on "already initialized" means the current init is all-or-nothing. For the agent kit, we need finer-grained skip logic: `.uf/replicator/` may exist but `.opencode/` may not. + +**Design decision**: Change the init flow to: +1. Create `.uf/replicator/cells.json` (skip if exists) +2. Call `agentkit.Scaffold(targetDir, force)` (per-file skip logic) +3. Report results for both steps + +This means removing the early return and checking each artifact independently. + +## Agent Kit Content Research + +### Upstream Source Analysis + +The upstream `joelhooks/swarm-tools` Claude Code plugin provides: + +**Commands** (in `claude-plugin/`): +- `/swarm:swarm` — Full Socratic orchestrator (decompose → spawn → monitor → review → complete) +- `/swarm:status` — Check swarm status +- `/swarm:handoff` — End session with handoff notes +- Plus others specific to Claude Code + +**Skills** (in `claude-plugin/skills/`): +- `always-on-guidance` — General coding principles +- `swarm-coordination` — How to coordinate parallel workers +- Plus others + +**Agents** (in `claude-plugin/agents/`): +- `coordinator` — Orchestrates the swarm +- `worker` — Executes subtasks +- `background-worker` — Runs in background + +### Adaptation Strategy + +For the replicator agent kit, we adapt the upstream content with these changes: + +1. **Tool name references**: All `hive_*` → `org_*`, `swarmmail_*` → `comms_*`, `swarm_*` → `forge_*` +2. **Platform**: Claude Code → OpenCode conventions (`.opencode/` directory structure) +3. **Orchestrator**: Lightweight version of `/swarm:swarm` — delegates to MCP tools without the full Socratic planning flow +4. **Excluded**: `ralph` command (Codex-specific), `cli-builder`/`queue`/`skill-creator`/`skill-generator` (deferred per spec assumptions) + +### Agent Kit File Manifest + +| # | Path (under `.opencode/`) | Source | Description | +|---|--------------------------|--------|-------------| +| 1 | `command/forge.md` | Adapted from `/swarm:swarm` | Lightweight forge orchestrator | +| 2 | `command/org.md` | New | Quick org cell management | +| 3 | `command/inbox.md` | New | Check comms inbox | +| 4 | `command/forge-status.md` | Adapted from `/swarm:status` | Check forge status | +| 5 | `command/handoff.md` | Adapted from `/swarm:handoff` | Session handoff | +| 6 | `skills/always-on-guidance/SKILL.md` | Adapted | General coding guidance | +| 7 | `skills/forge-coordination/SKILL.md` | Adapted from `swarm-coordination` | Forge coordination patterns | +| 8 | `skills/replicator-cli/SKILL.md` | New | Replicator CLI reference | +| 9 | `skills/testing-patterns/SKILL.md` | New | Go testing patterns for replicator | +| 10 | `skills/system-design/SKILL.md` | New | System design principles | +| 11 | `skills/learning-systems/SKILL.md` | New | Learning from outcomes | +| 12 | `skills/forge-global/SKILL.md` | Adapted | Global forge skill | +| 13 | `agents/coordinator.md` | Adapted | Coordinator agent definition | +| 14 | `agents/worker.md` | Adapted | Worker agent definition | +| 15 | `agents/background-worker.md` | Adapted | Background worker definition | + +### embed.FS Design + +```go +package agentkit + +import "embed" + +//go:embed content/* +var content embed.FS + +// Scaffold writes agent kit files to targetDir/.opencode/. +// Returns a list of results (created/skipped/overwritten). +func Scaffold(targetDir string, force bool) ([]ScaffoldResult, error) { + // Walk content FS + // For each file, check if target exists + // Write if !exists || force + // Return results +} + +type ScaffoldResult struct { + Path string // relative path under .opencode/ + Action string // "created", "skipped", "overwritten" +} +``` + +The `embed` directive `//go:embed content/*` captures all files under `internal/agentkit/content/`. The `fs.WalkDir` function traverses the embedded filesystem. Each file is written to the corresponding path under `.opencode/` in the target directory. + +## Technical Decisions + +### Decision 1: Clean Break vs. Aliases + +**Chosen**: Clean break (no backward-compatible aliases). + +**Rationale**: Aliases add complexity (dual registration, documentation confusion) for minimal benefit. The rename is a major version change. Agents update their prompts once. + +**Rejected**: Registering both old and new names pointing to the same handler. This would inflate the tool count from 53 to 98 and confuse `tools/list` consumers. + +### Decision 2: Package Names + +**Chosen**: `org`, `comms`, `forge` (short, Go-idiomatic). + +**Rationale**: Go convention favors short package names. `org` is clear for "organization of work items". `comms` is the natural abbreviation of "communications". `forge` evokes "forging" work through orchestration. + +**Rejected**: `organization` (too long), `comm` (ambiguous), `orchestrator` (too long, conflicts with existing coordinator concept). + +### Decision 3: Agent Kit Location + +**Chosen**: `internal/agentkit/` with `embed.FS`. + +**Rationale**: Follows the existing `internal/` package pattern. `embed.FS` is stdlib — no new dependencies. Files are compiled into the binary, ensuring version consistency. + +**Rejected**: External file distribution (requires separate install step), `go generate` (adds build complexity), runtime HTTP fetch (violates Composability First principle). + +### Decision 4: Init Behavior Change + +**Chosen**: Per-file skip logic with `--force` flag. + +**Rationale**: The current all-or-nothing early return doesn't work when init creates artifacts in two locations (`.uf/` and `.opencode/`). Per-file skip logic is more user-friendly and matches the spec's acceptance scenarios. + +**Rejected**: Separate `replicator init-kit` command (fragments the UX), always-overwrite (destructive, violates user expectations). diff --git a/specs/003-rename-terminology/spec.md b/specs/003-rename-terminology/spec.md new file mode 100644 index 0000000..0124540 --- /dev/null +++ b/specs/003-rename-terminology/spec.md @@ -0,0 +1,137 @@ +# Feature Specification: Terminology Rename + Agent Kit + +**Feature Branch**: `003-rename-terminology` +**Created**: 2026-04-06 +**Status**: Ready +**Input**: User description: "Rename hive→org, swarmmail→comms, swarm→forge. Ship agent kit (commands, skills, agents) via replicator init." +**References**: [Issue #9](https://github.com/unbound-force/replicator/issues/9), [upstream swarm-tools commands](https://github.com/joelhooks/swarm-tools/tree/main/packages/opencode-swarm-plugin/claude-plugin) + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - New Tool Names (Priority: P1) + +An AI coding agent connects to the replicator MCP server and discovers tools named with the new terminology: `org_cells`, `org_create`, `comms_send`, `comms_inbox`, `forge_decompose`, `forge_worktree_create`, etc. All tools function identically to their predecessors -- only the names have changed. The agent's existing workflows continue working after updating tool name references. + +**Why this priority**: Tool names are the public API surface. Every agent prompt, every tutorial, every config that references `hive_cells` or `swarm_decompose` must change. This is the foundation that all other stories depend on. + +**Independent Test**: Call `tools/list` on the MCP server and verify all 45 non-deprecated tools use the new prefixes (`org_`, `comms_`, `forge_`). Call each tool and verify identical behavior to the old name. + +**Acceptance Scenarios**: + +1. **Given** the MCP server is running, **When** `tools/list` is called, **Then** no tool names start with `hive_`, `swarmmail_`, or `swarm_`. All use `org_`, `comms_`, or `forge_` prefixes. The 8 deprecated `hivemind_*` tools retain their names. +2. **Given** the old tool `hive_create` existed, **When** `org_create` is called with the same arguments, **Then** the response shape and behavior are identical. +3. **Given** the `replicator docs` command exists, **When** it is run, **Then** the generated tool reference uses the new category names (Org, Comms, Forge, Memory) and all tool names match. + +--- + +### User Story 2 - Agent Kit Scaffolding (Priority: P1) + +A developer runs `replicator init` in a project directory and receives a complete agent kit: 5 slash commands, 7 skills, and 3 agent definitions installed to `.opencode/`. The commands reference the new tool names and provide a lightweight orchestration workflow. + +**Why this priority**: Without the agent kit, replicator provides raw MCP tools but no higher-level workflows. The commands and skills are what make the tools usable by AI agents in practice. + +**Independent Test**: Run `replicator init` in an empty directory. Verify 15 files are created across `.opencode/command/`, `.opencode/skills/`, and `.opencode/agents/`. Open `/forge` command and verify it references `forge_*` tools. + +**Acceptance Scenarios**: + +1. **Given** a directory without `.opencode/`, **When** `replicator init` is run, **Then** `.uf/replicator/cells.json` is created AND 15 agent kit files are created under `.opencode/`. +2. **Given** `.opencode/command/forge.md` already exists, **When** `replicator init` is run without `--force`, **Then** the existing file is NOT overwritten and a message indicates it was skipped. +3. **Given** `replicator init --force` is run, **When** agent kit files already exist, **Then** all files are overwritten with the latest versions from the binary. +4. **Given** the agent kit is installed, **When** an AI agent loads the project in OpenCode, **Then** `/forge`, `/org`, `/inbox`, `/forge:status`, and `/handoff` are available as slash commands. + +--- + +### User Story 3 - Lightweight Forge Orchestrator (Priority: P2) + +An AI coding agent invokes `/forge` with a task description. The command decomposes the task into subtasks using `forge_decompose`, creates an epic with `org_create_epic`, spawns workers, monitors progress via `comms_inbox`, reviews results, and completes the forge. The orchestrator is lightweight -- it delegates to MCP tools without the full Socratic planning flow. + +**Why this priority**: The forge orchestrator is the flagship workflow that ties all tools together. It's P2 because the individual tools (P1) must work first. + +**Independent Test**: Run `/forge "add a health check endpoint"` and verify it decomposes the task, creates an epic, and provides worker prompts. + +**Acceptance Scenarios**: + +1. **Given** an agent invokes `/forge` with a task, **When** the command executes, **Then** it calls `forge_decompose`, creates an epic via `org_create_epic`, and produces worker spawn prompts. +2. **Given** the forge is active, **When** the agent checks status, **Then** `/forge:status` shows active workers, pending cells, and recent messages. +3. **Given** a forge completes, **When** `/handoff` is invoked, **Then** reservations are released, state is synced, and a handoff note is generated. + +--- + +### User Story 4 - Consistent Naming Across Documentation (Priority: P3) + +All documentation (README, AGENTS.md, constitution, tool reference, spec artifacts) uses the new terminology consistently. The naming convention table reflects the updated metaphor. No stale references to the old names remain in active documentation. + +**Why this priority**: Documentation consistency is important but doesn't block functionality. It depends on all other stories being complete. + +**Independent Test**: Search all active documentation for `hive_`, `swarmmail_`, `swarm_` (as tool prefixes). Zero matches outside of historical attribution. + +**Acceptance Scenarios**: + +1. **Given** the rename is complete, **When** AGENTS.md is read, **Then** the naming convention table shows Org/Comms/Forge terminology and the project structure reflects renamed packages. +2. **Given** the rename is complete, **When** `replicator docs` is run, **Then** the output groups tools under Org, Comms, Forge, and Memory categories. +3. **Given** the rename is complete, **When** all `.go` source files are searched for old tool name strings, **Then** zero matches for `"hive_"`, `"swarmmail_"`, or `"swarm_"` as tool name prefixes (excluding `hivemind_` which is intentionally unchanged). + +--- + +### Edge Cases + +- What happens if a project already has `.opencode/command/forge.md` from a previous `replicator init`? The file is skipped unless `--force` is passed. +- What happens if an agent sends a request using an old tool name (`hive_cells`)? The MCP server returns "Unknown tool" error. There are no backward-compatible aliases. +- What happens to parity test fixtures that reference old tool names? The fixtures are updated to use the new names. Parity testing compares response shapes, not tool names. +- What happens to the `hivemind_*` deprecated tools? They keep their names. They are Dewey proxy stubs with deprecation messages and are not part of the org/comms/forge namespace. + +## Requirements *(mandatory)* + +### Functional Requirements + +#### Terminology Rename +- **FR-001**: All 11 `hive_*` MCP tools MUST be renamed to `org_*` (e.g., `hive_cells` → `org_cells`, `hive_create` → `org_create`). +- **FR-002**: All 10 `swarmmail_*` MCP tools MUST be renamed to `comms_*` (e.g., `swarmmail_send` → `comms_send`). +- **FR-003**: All 24 `swarm_*` MCP tools MUST be renamed to `forge_*` (e.g., `swarm_decompose` → `forge_decompose`). +- **FR-004**: The 8 `hivemind_*` tools MUST retain their current names (deprecated Dewey proxy). +- **FR-005**: Tool behavior, argument schemas, and response shapes MUST remain identical -- only the `name` field changes. +- **FR-006**: The `replicator docs` command MUST group tools under Org, Comms, Forge, and Memory categories using the new prefixes. + +#### Agent Kit +- **FR-007**: `replicator init` MUST create 5 command files in `.opencode/command/`: `forge.md`, `org.md`, `inbox.md`, `forge-status.md`, `handoff.md`. +- **FR-008**: `replicator init` MUST create 7 skill files in `.opencode/skills/`: `always-on-guidance/SKILL.md`, `forge-coordination/SKILL.md`, `replicator-cli/SKILL.md`, `testing-patterns/SKILL.md`, `system-design/SKILL.md`, `learning-systems/SKILL.md`, `forge-global/SKILL.md`. +- **FR-009**: `replicator init` MUST create 3 agent files in `.opencode/agents/`: `coordinator.md`, `worker.md`, `background-worker.md`. +- **FR-010**: Agent kit files MUST be embedded in the binary and written from embedded content on `init`. +- **FR-011**: `replicator init` MUST NOT overwrite existing agent kit files unless `--force` is passed. +- **FR-012**: All command files MUST reference the new tool names (`org_*`, `comms_*`, `forge_*`). + +#### Naming Convention +- **FR-013**: The naming convention table in AGENTS.md MUST be updated: Work items=Org, Individual item=Cell, Agent coordination=Forge, Messaging=Comms. +- **FR-014**: All active documentation (README, AGENTS.md, constitution) MUST use the new terminology consistently. + +### Key Entities + +- **Org**: The work item management domain (formerly Hive). Contains cells (individual work items), epics, sessions, and sync operations. +- **Comms**: The inter-agent communication domain (formerly Swarm Mail). Contains messages, reservations, and agent registration. +- **Forge**: The orchestration domain (formerly Swarm). Contains task decomposition, worker spawning, worktrees, progress tracking, review, and insights. +- **Agent Kit**: The set of commands, skills, and agents that `replicator init` scaffolds into a project. These files teach AI agents how to use replicator's MCP tools effectively. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: `tools/list` returns exactly 53 tools: 11 `org_*`, 10 `comms_*`, 24 `forge_*`, 8 `hivemind_*`. Zero tools with `hive_`, `swarmmail_`, or `swarm_` prefixes. +- **SC-002**: `replicator init` in an empty directory creates exactly 16 files: 1 cells.json + 5 commands + 7 skills + 3 agents. +- **SC-003**: All 53 tools pass their existing test suites with zero failures after the rename (behavior unchanged). +- **SC-004**: The parity test suite shows 100% shape match for all tools (response shapes unchanged despite name changes). +- **SC-005**: Zero active source files or documentation contain `"hive_"`, `"swarmmail_"`, or `"swarm_"` as tool name prefixes (verified by grep, excluding `hivemind_*`). +- **SC-006**: The `/forge` command file references at least `forge_decompose`, `org_create_epic`, `comms_inbox`, and `forge_status` tools. + +## Assumptions + +- The 8 `hivemind_*` tools keep their names. They are deprecated Dewey proxy stubs and will be removed in a future version, not renamed. +- No backward-compatible aliases are provided for old tool names. This is a clean break. +- The agent kit files are adapted from the upstream `joelhooks/swarm-tools` Claude Code plugin, rewritten for OpenCode conventions and the new terminology. +- The `ralph` command and `ralph-supervisor` skill are excluded (Codex-specific, not applicable to OpenCode). +- The `cli-builder`, `queue`, `skill-creator`, and `skill-generator` global skills are deferred to a future spec. +- Agent kit files are delivered to `.opencode/` in the project directory (not `~/.config/`). + +## Dependencies + +- **Spec 002 (charm-ux)**: Must be merged first. The `replicator docs` command and lipgloss styling are prerequisites. +- **Issue #9**: This spec fully addresses all requirements and comments in the issue. Issue #9 can be closed when this spec is merged. diff --git a/specs/003-rename-terminology/tasks.md b/specs/003-rename-terminology/tasks.md new file mode 100644 index 0000000..ca3d604 --- /dev/null +++ b/specs/003-rename-terminology/tasks.md @@ -0,0 +1,249 @@ +# Tasks: Terminology Rename + Agent Kit + +**Input**: Design documents from `/specs/003-rename-terminology/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, quickstart.md + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +--- + +## Phase 1: Directory Renames [US1] + +**Purpose**: Rename 6 Go package directories using `git mv` to preserve history. Foundation for all subsequent phases. + +**⚠️ CRITICAL**: Build will break after this phase until Phase 2 completes. Phases 1+2 must be committed together. + +- [x] T001 [P] [US1] Rename domain package `internal/hive/` → `internal/org/` via `git mv internal/hive internal/org` +- [x] T002 [P] [US1] Rename domain package `internal/swarmmail/` → `internal/comms/` via `git mv internal/swarmmail internal/comms` +- [x] T003 [P] [US1] Rename domain package `internal/swarm/` → `internal/forge/` via `git mv internal/swarm internal/forge` +- [x] T004 [P] [US1] Rename tool handler package `internal/tools/hive/` → `internal/tools/org/` via `git mv internal/tools/hive internal/tools/org` +- [x] T005 [P] [US1] Rename tool handler package `internal/tools/swarmmail/` → `internal/tools/comms/` via `git mv internal/tools/swarmmail internal/tools/comms` +- [x] T006 [P] [US1] Rename tool handler package `internal/tools/swarm/` → `internal/tools/forge/` via `git mv internal/tools/swarm internal/tools/forge` + +**Checkpoint**: `ls internal/org/ internal/comms/ internal/forge/ internal/tools/org/ internal/tools/comms/ internal/tools/forge/` succeeds. Old directories do not exist. + +--- + +## Phase 2: Package Declarations + Import Paths [US1] + +**Purpose**: Update all `package` declarations in renamed files and fix all import paths across the codebase. Restores compilability. + +**⚠️ CRITICAL**: Must complete before `go build` works. No task in this phase can be skipped. + +### Package declarations (inside renamed directories) + +- [x] T007 [P] [US1] Update `package hive` → `package org` in all 11 files under `internal/org/*.go` (cells.go, cells_test.go, epic.go, epic_test.go, format.go, format_test.go, session.go, session_test.go, start_ready_test.go, sync.go, sync_test.go) +- [x] T008 [P] [US1] Update `package swarmmail` → `package comms` in all 6 files under `internal/comms/*.go` (agent.go, agent_test.go, message.go, message_test.go, reservation.go, reservation_test.go) +- [x] T009 [P] [US1] Update `package swarm` → `package forge` in all 14 files under `internal/forge/*.go` (decompose.go, decompose_test.go, init.go, init_test.go, insights.go, insights_test.go, progress.go, progress_test.go, review.go, review_test.go, spawn.go, spawn_test.go, worktree.go, worktree_test.go). Also update the package-level GoDoc comment in `init.go`. +- [x] T010 [P] [US1] Update `package hive` → `package org` and package-level GoDoc comment in `internal/tools/org/tools.go` +- [x] T011 [P] [US1] Update `package swarmmail` → `package comms` and package-level GoDoc comment in `internal/tools/comms/tools.go` +- [x] T012 [P] [US1] Update `package swarm` → `package forge` and package-level GoDoc comment in `internal/tools/forge/tools.go` + +### Import paths (consumers of renamed packages) + +- [x] T013 [US1] Update import paths in `internal/tools/org/tools.go`: change `internal/hive` → `internal/org` and update all `hive.` qualifier references to `org.` (e.g., `hive.QueryCells` → `org.QueryCells`, `hive.CellQuery` → `org.CellQuery`) +- [x] T014 [US1] Update import paths in `internal/tools/comms/tools.go`: change `internal/swarmmail` → `internal/comms` and update all `swarmmail.` qualifier references to `comms.` (e.g., `swarmmail.Init` → `comms.Init`) +- [x] T015 [US1] Update import paths in `internal/tools/forge/tools.go`: change `internal/swarm` → `internal/forge` and update all `swarm.` qualifier references to `forge.` (e.g., `swarm.Init` → `forge.Init`, `swarm.SubtaskPrompt` → `forge.SubtaskPrompt`) +- [x] T016 [US1] Update import paths and aliases in `cmd/replicator/serve.go`: change `internal/tools/hive` → `internal/tools/org`, `internal/tools/swarmmail` → `internal/tools/comms` (alias `commstools`), `internal/tools/swarm` → `internal/tools/forge` (alias `forgetools`). Update `hive.Register` → `org.Register`, `swarmmailtools.Register` → `commstools.Register`, `swarmtools.Register` → `forgetools.Register`. +- [x] T017 [US1] Update import paths and aliases in `cmd/replicator/docs.go`: change `internal/tools/hive` → `internal/tools/org`, `internal/tools/swarmmail` → `internal/tools/comms` (alias `commstools`), `internal/tools/swarm` → `internal/tools/forge` (alias `forgetools`). Update `hive.Register` → `org.Register`, `swarmmailtools.Register` → `commstools.Register`, `swarmtools.Register` → `forgetools.Register`. +- [x] T018 [US1] Update import paths in `cmd/replicator/cells.go`: change `internal/hive` → `internal/org` and update all `hive.` qualifier references to `org.` (e.g., `hive.QueryCells` → `org.QueryCells`, `hive.FormatCells` → `org.FormatCells`, `hive.CellQuery` → `org.CellQuery`) +- [x] T019 [US1] Update import paths and aliases in `cmd/replicator/docs_test.go`: change `internal/tools/hive` → `internal/tools/org`, `internal/tools/swarmmail` → `internal/tools/comms` (alias `commstools`), `internal/tools/swarm` → `internal/tools/forge` (alias `forgetools`). Update all `hive.Register` → `org.Register`, `swarmmailtools.Register` → `commstools.Register`, `swarmtools.Register` → `forgetools.Register`. +- [x] T020 [US1] Update import paths in `internal/mcp/server_test.go`: change `internal/tools/hive` → `internal/tools/org` and update `hive.Register` → `org.Register` +- [x] T021 [US1] Update import paths and aliases in `test/parity/parity_test.go`: change `internal/tools/hive` → `internal/tools/org` (alias `orgtools`), `internal/tools/swarmmail` → `internal/tools/comms` (alias `commstools`), `internal/tools/swarm` → `internal/tools/forge` (alias `forgetools`). Update `hivetools.Register` → `orgtools.Register`, `swarmmailtools.Register` → `commstools.Register`, `swarmtools.Register` → `forgetools.Register`. + +**Checkpoint**: `go vet ./...` passes. `go build ./cmd/replicator` succeeds. + +--- + +## Phase 3: Tool Name Strings + Function Names [US1] + +**Purpose**: Update the 45 MCP tool `Name:` string literals and Go function names from old prefixes to new prefixes. Also update event type strings in domain packages and prompt-generating code. + +### Tool handler name strings (3 files, parallelizable) + +- [x] T022 [P] [US1] In `internal/tools/org/tools.go`: rename all 11 tool `Name:` strings from `hive_*` → `org_*` (hive_cells→org_cells, hive_create→org_create, hive_close→org_close, hive_update→org_update, hive_create_epic→org_create_epic, hive_query→org_query, hive_start→org_start, hive_ready→org_ready, hive_sync→org_sync, hive_session_start→org_session_start, hive_session_end→org_session_end). Rename 11 Go function names from `hive*` → `org*` (e.g., `hiveCells` → `orgCells`, `hiveCreate` → `orgCreate`). Update the `Register` function's GoDoc comment. Update any description strings referencing old names. +- [x] T023 [P] [US1] In `internal/tools/comms/tools.go`: rename all 10 tool `Name:` strings from `swarmmail_*` → `comms_*` (swarmmail_init→comms_init, swarmmail_send→comms_send, swarmmail_inbox→comms_inbox, swarmmail_read_message→comms_read_message, swarmmail_reserve→comms_reserve, swarmmail_release→comms_release, swarmmail_release_all→comms_release_all, swarmmail_release_agent→comms_release_agent, swarmmail_ack→comms_ack, swarmmail_health→comms_health). Rename 10 Go function names from `swarmmail*` → `comms*` (e.g., `swarmmailInit` → `commsInit`). Update the `Register` function's GoDoc comment. Update any description strings referencing old names (e.g., "swarm mail" → "comms"). +- [x] T024 [P] [US1] In `internal/tools/forge/tools.go`: rename all 24 tool `Name:` strings from `swarm_*` → `forge_*` (swarm_init→forge_init, swarm_select_strategy→forge_select_strategy, swarm_plan_prompt→forge_plan_prompt, swarm_decompose→forge_decompose, swarm_validate_decomposition→forge_validate_decomposition, swarm_subtask_prompt→forge_subtask_prompt, swarm_spawn_subtask→forge_spawn_subtask, swarm_complete_subtask→forge_complete_subtask, swarm_progress→forge_progress, swarm_complete→forge_complete, swarm_status→forge_status, swarm_record_outcome→forge_record_outcome, swarm_worktree_create→forge_worktree_create, swarm_worktree_merge→forge_worktree_merge, swarm_worktree_cleanup→forge_worktree_cleanup, swarm_worktree_list→forge_worktree_list, swarm_review→forge_review, swarm_review_feedback→forge_review_feedback, swarm_adversarial_review→forge_adversarial_review, swarm_evaluation_prompt→forge_evaluation_prompt, swarm_broadcast→forge_broadcast, swarm_get_strategy_insights→forge_get_strategy_insights, swarm_get_file_insights→forge_get_file_insights, swarm_get_pattern_insights→forge_get_pattern_insights). Rename 24 Go function names from `swarm*` → `forge*`. Update the `Register` function's GoDoc comment. Update any description strings referencing old names. + +### Event type strings in domain packages + +- [x] T025 [P] [US1] In `internal/forge/init.go`: update event type string `"swarm_init"` → `"forge_init"` in the DB insert and error message. Also in `internal/forge/init_test.go`: update `"swarm_init"` → `"forge_init"` in the DB query assertion (line 64) and error message (line 66) +- [x] T026 [P] [US1] In `internal/forge/progress.go`: update event type strings `"swarm_progress"` → `"forge_progress"`, `"swarm_complete"` → `"forge_complete"`, `"swarm_outcome"` → `"forge_outcome"` in all DB inserts and error messages. Also in `internal/forge/progress_test.go`: update `"swarm_progress"` → `"forge_progress"` (line 16), `"swarm_complete"` → `"forge_complete"` (line 49), `"swarm_outcome"` → `"forge_outcome"` (line 136) in DB query assertions +- [x] T027 [P] [US1] In `internal/forge/review.go`: update event type string `"swarm_broadcast"` → `"forge_broadcast"` in the DB insert. Also in `internal/forge/review_test.go`: update `"swarm_broadcast"` → `"forge_broadcast"` in DB query assertions (lines 139, 155) +- [x] T028 [P] [US1] In `internal/forge/insights.go`: update all `'swarm_outcome'` → `'forge_outcome'` in SQL queries (3 occurrences across GetStrategyInsights, GetFileInsights, GetPatternInsights) + +### Prompt-generating code (tool name references in generated text) + +- [x] T029 [P] [US1] In `internal/forge/spawn.go`: update tool name references in generated prompt text: `"swarmmail_reserve"` → `"comms_reserve"`, `"swarm_progress"` → `"forge_progress"`, `"swarm_complete"` → `"forge_complete"` +- [x] T030 [P] [US1] In `internal/forge/spawn_test.go`: update expected tool name strings in test assertions: `"swarmmail_reserve"` → `"comms_reserve"`, `"swarm_complete"` → `"forge_complete"` + +**Checkpoint**: `go test ./internal/tools/... -count=1` passes. `go test ./internal/forge/... -count=1` passes. `grep -rn '"hive_\|"swarmmail_\|"swarm_' --include='*.go' internal/tools/ internal/forge/ | grep -v hivemind_` returns zero matches. + +--- + +## Phase 4: Parity Fixtures + Docs + Query/Stats [US1, US4] + +**Purpose**: Update parity test fixtures, docs category map, MCP server tests, and query/stats packages to use new tool names. + +### Parity test fixtures (JSON files) + +- [x] T031 [P] [US1] In `test/parity/fixtures/hive.json`: rename all top-level tool name keys from `hive_*` → `org_*` and update `request.name` fields inside each fixture entry to match +- [x] T032 [P] [US1] In `test/parity/fixtures/swarmmail.json`: rename all top-level tool name keys from `swarmmail_*` → `comms_*` and update `request.name` fields inside each fixture entry to match +- [x] T033 [P] [US1] In `test/parity/fixtures/swarm.json`: rename all top-level tool name keys from `swarm_*` → `forge_*` and update `request.name` fields inside each fixture entry to match + +### Parity test code + +- [x] T034 [US1] In `test/parity/parity_test.go`: update all hardcoded tool name strings — `"hive_cells"` → `"org_cells"`, `"hive_query"` → `"org_query"`, `"hive_ready"` → `"org_ready"`, `"hive_create"` → `"org_create"`, `"hive_close"` → `"org_close"`, `"hive_update"` → `"org_update"`, `"hive_start"` → `"org_start"`, `"hive_cells_with_data"` → `"org_cells_with_data"`, `"swarm_init"` → `"forge_init"`, `"swarm_worktree_list"` → `"forge_worktree_list"`. Update function names `testCellsWithData` references and all `reg.Get("hive_*")` calls to `reg.Get("org_*")`. + +### Docs command + +- [x] T035 [US1] In `cmd/replicator/docs.go`: update `categories` slice — `{"hive_", "Hive"}` → `{"org_", "Org"}`, `{"swarmmail_", "Swarm Mail"}` → `{"comms_", "Comms"}`, `{"swarm_", "Swarm"}` → `{"forge_", "Forge"}`. Update the `Long` description text from "Hive, Swarm Mail, Swarm, Memory" → "Org, Comms, Forge, Memory". +- [x] T036 [US1] In `cmd/replicator/docs_test.go`: update `TestWriteDocs_HasCategoryHeaders` expected headers from `"## Hive"`, `"## Swarm Mail"`, `"## Swarm"` → `"## Org"`, `"## Comms"`, `"## Forge"`. + +### MCP server tests + +- [x] T037 [US1] In `internal/mcp/server_test.go`: update all hardcoded tool name strings — `"hive_cells"` → `"org_cells"`, `"hive_create"` → `"org_create"`, `"hive_close"` → `"org_close"`, etc. in `TestToolsList`, `TestToolsCall_HiveCells_Empty`, `TestToolsCall_HiveCreate`, `TestToolsCall_CreateThenQuery`, `TestToolsCall_LogsToolName`, `TestToolsCall_LogsMultipleCalls`, `TestNewServer_NilLogger`. Update test function names if they reference old names (e.g., `TestToolsCall_HiveCells_Empty` → `TestToolsCall_OrgCells_Empty`). + +### Query and stats packages (event type strings in SQL/tests) + +- [x] T038 [P] [US1] In `internal/query/presets.go`: update SQL queries — `'swarm_%'` → `'forge_%'`, `'swarm_complete'` → `'forge_complete'`. Update display text "Swarm Completion Rate" → "Forge Completion Rate", "Total swarm events" → "Total forge events", "no swarm events" → "no forge events". Update constant `SwarmCompletionRate` → `ForgeCompletionRate` and its string value `"swarm_completion_rate"` → `"forge_completion_rate"`. Rename function `runSwarmCompletionRate` → `runForgeCompletionRate` and update its call site in the `Run` switch statement. Update the package-level GoDoc comment (line 5) from "swarm completion rates" → "forge completion rates". +- [x] T039 [P] [US1] In `internal/query/presets_test.go`: update all references to `SwarmCompletionRate` → `ForgeCompletionRate`, and event type strings `"swarm_init"` → `"forge_init"`, `"swarm_progress"` → `"forge_progress"`, `"swarm_complete"` → `"forge_complete"`. Update assertion strings `"Swarm Completion Rate"` → `"Forge Completion Rate"`, `"Total swarm events"` → `"Total forge events"`. Rename test functions `TestRun_SwarmCompletionRate_Empty` → `TestRun_ForgeCompletionRate_Empty` and `TestRun_SwarmCompletionRate_WithData` → `TestRun_ForgeCompletionRate_WithData`. +- [x] T040 [P] [US1] In `internal/stats/stats_test.go`: update event type strings `"swarm_init"` → `"forge_init"`, `"swarm_complete"` → `"forge_complete"`. Update assertion strings `"swarm_init"` → `"forge_init"`, `"swarm_complete"` → `"forge_complete"`. +- [x] T041 [P] [US1] In `internal/forge/insights_test.go`: update event type strings `"swarm_outcome"` → `"forge_outcome"` in all test fixture DB inserts + +**Checkpoint**: `go vet ./...` passes. `go test ./... -count=1 -race` passes. `go test -tags parity ./test/parity/ -count=1` passes. `replicator docs` output shows Org/Comms/Forge/Memory categories. + +--- + +## Phase 5: Agent Kit Content [US2, US3] + +**Purpose**: Create the `internal/agentkit/` package with `embed.FS` scaffold logic and 15 template files. Independent of Phases 1–4. + +### Scaffold package + +- [x] T042 [US2] Create `internal/agentkit/agentkit.go`: define `embed.FS` with `//go:embed content/*` directive, `ScaffoldResult` struct (`Path string`, `Action string`), and `Scaffold(targetDir string, force bool) ([]ScaffoldResult, error)` function that walks the embedded FS and writes files to `targetDir/.opencode/`, with per-file skip logic (skip if exists and `force=false`) +- [x] T043 [US2] Create `internal/agentkit/agentkit_test.go`: test `Scaffold` in fresh `t.TempDir()` (creates 15 files), test skip-existing behavior (pre-create a file, verify "skipped" result), test `--force` overwrite (pre-create a file, verify "overwritten" result), test file count matches expected 15 + +### Command files (5 files) + +- [x] T044 [P] [US2] Create `internal/agentkit/content/command/forge.md`: lightweight forge orchestrator command adapted from upstream `/swarm:swarm`. Must reference `forge_decompose`, `org_create_epic`, `comms_inbox`, `forge_status` tools. Provides workflow: decompose → create epic → spawn workers → monitor → review → complete. +- [x] T045 [P] [US2] Create `internal/agentkit/content/command/org.md`: org cell management command for quick cell CRUD. References `org_cells`, `org_create`, `org_update`, `org_close`, `org_start` tools. +- [x] T046 [P] [US2] Create `internal/agentkit/content/command/inbox.md`: comms inbox command for checking agent messages. References `comms_inbox`, `comms_read_message`, `comms_ack` tools. +- [x] T047 [P] [US2] Create `internal/agentkit/content/command/forge-status.md`: forge status command adapted from upstream `/swarm:status`. References `forge_status`, `org_cells`, `comms_inbox` tools. +- [x] T048 [P] [US2] Create `internal/agentkit/content/command/handoff.md`: session handoff command adapted from upstream `/swarm:handoff`. References `org_session_end`, `comms_release_all`, `org_sync` tools. + +### Skill files (7 files) + +- [x] T049 [P] [US2] Create `internal/agentkit/content/skills/always-on-guidance/SKILL.md`: general coding guidance skill adapted from upstream. Covers code quality, testing, error handling principles. +- [x] T050 [P] [US2] Create `internal/agentkit/content/skills/forge-coordination/SKILL.md`: forge coordination skill adapted from upstream `swarm-coordination`. Covers worker spawning, progress reporting, file reservation protocol using `comms_reserve`, `forge_progress`, `forge_complete` tools. +- [x] T051 [P] [US2] Create `internal/agentkit/content/skills/replicator-cli/SKILL.md`: replicator CLI reference skill. Documents all CLI commands (init, setup, serve, cells, doctor, stats, query, docs, version). +- [x] T052 [P] [US2] Create `internal/agentkit/content/skills/testing-patterns/SKILL.md`: Go testing patterns skill. Covers `db.OpenMemory()`, `t.TempDir()`, `httptest.NewServer`, parity tests, test naming conventions. +- [x] T053 [P] [US2] Create `internal/agentkit/content/skills/system-design/SKILL.md`: system design principles skill. Covers SOLID, DRY, dependency injection, interface abstractions. +- [x] T054 [P] [US2] Create `internal/agentkit/content/skills/learning-systems/SKILL.md`: learning from outcomes skill. Covers `forge_record_outcome`, `forge_get_strategy_insights`, `forge_get_file_insights`, `forge_get_pattern_insights` tools. +- [x] T055 [P] [US2] Create `internal/agentkit/content/skills/forge-global/SKILL.md`: global forge skill for cross-project coordination patterns. + +### Agent files (3 files) + +- [x] T056 [P] [US2] Create `internal/agentkit/content/agents/coordinator.md`: coordinator agent definition adapted from upstream. Defines role, available tools, workflow for orchestrating forge sessions. +- [x] T057 [P] [US2] Create `internal/agentkit/content/agents/worker.md`: worker agent definition adapted from upstream. Defines role, file reservation protocol, progress reporting, completion workflow. +- [x] T058 [P] [US2] Create `internal/agentkit/content/agents/background-worker.md`: background worker agent definition adapted from upstream. Defines role for non-interactive background tasks. + +**Checkpoint**: `go test ./internal/agentkit/ -count=1` passes. `go build ./cmd/replicator` succeeds. + +--- + +## Phase 6: Enhance `replicator init` [US2] + +**Purpose**: Integrate agent kit scaffold into the `init` command. Add `--force` flag. + +- [x] T059 [US2] In `cmd/replicator/init.go`: import `internal/agentkit`, remove the early-return on "already initialized" (replace with per-artifact skip logic), add `--force` bool flag, call `agentkit.Scaffold(targetDir, force)` after cells.json creation, render each `ScaffoldResult` with styled output (green for created, dim for skipped, yellow for overwritten). When overwriting with `--force`, print a warning that customizations will be lost. Update the `Short` description from "swarm operations" to "project operations". Update the `Long` description to mention agent kit scaffolding. +- [x] T060 [US2] In `cmd/replicator/init_test.go`: update `TestRunInit_FreshDirectory` to verify 16 files created (1 cells.json + 15 agent kit files). Add `TestRunInit_AgentKitSkipsExisting` — pre-create `.opencode/command/forge.md`, run init, verify file is not overwritten. Add `TestRunInit_ForceOverwrites` — pre-create a file, run init with force=true, verify file is overwritten. Update `TestRunInit_AlreadyInitialized` to verify agent kit files are still created even when `.uf/replicator/` already exists. + +**Checkpoint**: `go test ./cmd/replicator/ -count=1 -run TestRunInit` passes. Manual smoke test: `go run ./cmd/replicator init --path /tmp/test-init` creates 16 files. + +--- + +## Phase 7: Documentation Updates [US4] + +**Purpose**: Update all documentation to use new terminology consistently. + +- [x] T061 [P] [US4] In `AGENTS.md`: update the naming convention table (Hive→Org, Swarm Mail→Comms, Swarm→Forge), update the project structure section (hive/→org/, swarmmail/→comms/, swarm/→forge/, tools/hive/→tools/org/, tools/swarmmail/→tools/comms/, tools/swarm/→tools/forge/), add `internal/agentkit/` to the project structure, update MCP protocol section references, update any "hive" or "swarm" references in behavioral constraints and coding conventions. Update Active Technologies section to reference new package names. +- [x] T062 [P] [US4] In `README.md`: update the MCP Tools table categories (Hive→Org, Swarm Mail→Comms, Swarm→Forge), update the Architecture mermaid diagram domain label from "hive, swarm, mail" → "org, forge, comms", update the Package Layout section (hive/→org/, swarmmail/→comms/, swarm/→forge/, tools/hive/→tools/org/, tools/swarmmail/→tools/comms/, tools/swarm/→tools/forge/), add `agentkit/` to the package layout, update the Status section phase descriptions to use new names. +- [x] T063 [P] [US4] In `.specify/memory/constitution.md`: update Principle I "swarm mail messaging system" → "comms messaging system". Update Principle II "hive, swarm mail, orchestration" → "org, comms, orchestration" (or "org, comms, forge"). +- [x] T064 [P] [US4] Verify `cmd/replicator/init.go` `Short` and `Long` descriptions were updated by T059 in Phase 6. If not, update `Short` from "swarm operations" → "project operations" and `Long` to mention agent kit scaffolding. + +**Checkpoint**: `grep -rn 'hive_\|swarmmail_\|swarm_' AGENTS.md README.md .specify/memory/constitution.md | grep -v hivemind_ | grep -v historical` returns zero matches (excluding historical attribution and `hivemind_`). + +--- + +## Phase 8: Verification [ALL] + +**Purpose**: Full CI-equivalent verification pass. No code changes — validation only. + +- [x] T065 [US1] Run `go vet ./...` — must pass with zero errors +- [x] T066 [US1] Run `go test ./... -count=1 -race` — must pass with zero failures +- [x] T067 [US1] Run `go build -o bin/replicator ./cmd/replicator` — must produce binary +- [x] T068 [US1] Run `go test -tags parity ./test/parity/ -count=1 -v` — must pass with 100% shape match +- [x] T069 [US4] Run grep verification: `grep -rn '"hive_\|"swarmmail_\|"swarm_' --include='*.go' | grep -v hivemind_` — must return zero matches (SC-005) +- [x] T070 [US2] Run init smoke test: build binary, run `./bin/replicator init --path $(mktemp -d)`, verify 16 files created (SC-002) +- [x] T071 [US1] Verify tool count: start MCP server, call `tools/list`, confirm 53 tools — 11 `org_*` + 10 `comms_*` + 24 `forge_*` + 8 `hivemind_*` (SC-001) + +**Checkpoint**: All verification commands pass with zero failures. Feature is ready for review. + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +``` +Phase 1 (dir renames) + └─→ Phase 2 (package decls + import paths) + └─→ Phase 3 (tool name strings + event types) + └─→ Phase 4 (parity fixtures + docs + query/stats) + └─→ Phase 8 (verification) + +Phase 5 (agent kit content) ← independent, can start anytime + └─→ Phase 6 (init enhancement) + └─→ Phase 8 (verification) + +Phase 7 (documentation) ← depends on Phase 3 (needs final names) + └─→ Phase 8 (verification) +``` + +- **Phase 1**: No dependencies — can start immediately +- **Phase 2**: Depends on Phase 1 — must complete before build works +- **Phase 3**: Depends on Phase 2 — needs compilable code +- **Phase 4**: Depends on Phase 3 — needs final tool names +- **Phase 5**: Independent — can run in parallel with Phases 1–4 +- **Phase 6**: Depends on Phase 5 — needs agentkit package +- **Phase 7**: Depends on Phase 3 — needs final tool names decided +- **Phase 8**: Depends on ALL other phases — final validation + +### Parallel Opportunities + +- All Phase 1 tasks (T001–T006) can run in parallel +- All Phase 2 package declaration tasks (T007–T012) can run in parallel +- Phase 2 import path tasks (T013–T021) are independent per file +- All Phase 3 tasks can run in parallel (different files) +- Phase 4 fixture tasks (T031–T033) can run in parallel +- Phase 4 query/stats tasks (T038–T041) can run in parallel +- Phase 5 content files (T044–T058) can all run in parallel +- Phase 7 documentation tasks (T061–T064) can run in parallel +- **Phase 5 can run entirely in parallel with Phases 1–4** + +### Commit Strategy + +- **Commit 1**: Phases 1+2 together (dir renames + import fixes — build must work) +- **Commit 2**: Phase 3 (tool name strings) +- **Commit 3**: Phase 4 (parity fixtures + docs + query/stats) +- **Commit 4**: Phase 5 (agent kit content — can be committed independently) +- **Commit 5**: Phase 6 (init enhancement) +- **Commit 6**: Phase 7 (documentation) +- **Commit 7**: Phase 8 verification passes (no code changes, just confirmation) + + diff --git a/test/parity/fixtures/hive.json b/test/parity/fixtures/hive.json index ba887bb..e60ab14 100644 --- a/test/parity/fixtures/hive.json +++ b/test/parity/fixtures/hive.json @@ -1,38 +1,38 @@ { - "hive_cells": { - "request": {"name": "hive_cells", "arguments": {}}, + "org_cells": { + "request": {"name": "org_cells", "arguments": {}}, "typescript_response": {"content": [{"type": "text", "text": "[]"}]} }, - "hive_cells_with_data": { - "request": {"name": "hive_cells", "arguments": {"status": "open"}}, + "org_cells_with_data": { + "request": {"name": "org_cells", "arguments": {"status": "open"}}, "typescript_response": {"content": [{"type": "text", "text": "[{\"id\":\"cell-abc123\",\"title\":\"Test task\",\"description\":\"\",\"type\":\"task\",\"status\":\"open\",\"priority\":1,\"parent_id\":null,\"created_at\":\"2025-01-01T00:00:00Z\",\"updated_at\":\"2025-01-01T00:00:00Z\"}]"}]} }, - "hive_create": { - "request": {"name": "hive_create", "arguments": {"title": "New task", "type": "task", "priority": 1}}, + "org_create": { + "request": {"name": "org_create", "arguments": {"title": "New task", "type": "task", "priority": 1}}, "typescript_response": {"content": [{"type": "text", "text": "{\"id\":\"cell-abc123\",\"title\":\"New task\",\"description\":\"\",\"type\":\"task\",\"status\":\"open\",\"priority\":1,\"parent_id\":null,\"created_at\":\"2025-01-01T00:00:00Z\",\"updated_at\":\"2025-01-01T00:00:00Z\"}"}]} }, - "hive_close": { - "request": {"name": "hive_close", "arguments": {"id": "cell-abc123", "reason": "done"}}, + "org_close": { + "request": {"name": "org_close", "arguments": {"id": "cell-abc123", "reason": "done"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"status\":\"closed\"}"}]} }, - "hive_update": { - "request": {"name": "hive_update", "arguments": {"id": "cell-abc123", "status": "in_progress"}}, + "org_update": { + "request": {"name": "org_update", "arguments": {"id": "cell-abc123", "status": "in_progress"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"status\":\"updated\"}"}]} }, - "hive_create_epic": { - "request": {"name": "hive_create_epic", "arguments": {"epic_title": "Test Epic", "subtasks": [{"title": "Sub 1"}, {"title": "Sub 2"}]}}, + "org_create_epic": { + "request": {"name": "org_create_epic", "arguments": {"epic_title": "Test Epic", "subtasks": [{"title": "Sub 1"}, {"title": "Sub 2"}]}}, "typescript_response": {"content": [{"type": "text", "text": "{\"epic\":{\"id\":\"cell-epic123\",\"title\":\"Test Epic\",\"description\":\"\",\"type\":\"epic\",\"status\":\"open\",\"priority\":1,\"parent_id\":null,\"created_at\":\"2025-01-01T00:00:00Z\",\"updated_at\":\"2025-01-01T00:00:00Z\"},\"subtasks\":[{\"id\":\"cell-sub1\",\"title\":\"Sub 1\",\"type\":\"task\",\"status\":\"open\",\"priority\":1,\"parent_id\":\"cell-epic123\",\"created_at\":\"2025-01-01T00:00:00Z\",\"updated_at\":\"2025-01-01T00:00:00Z\"}]}"}]} }, - "hive_query": { - "request": {"name": "hive_query", "arguments": {}}, + "org_query": { + "request": {"name": "org_query", "arguments": {}}, "typescript_response": {"content": [{"type": "text", "text": "[]"}]} }, - "hive_start": { - "request": {"name": "hive_start", "arguments": {"id": "cell-abc123"}}, + "org_start": { + "request": {"name": "org_start", "arguments": {"id": "cell-abc123"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"status\":\"in_progress\"}"}]} }, - "hive_ready": { - "request": {"name": "hive_ready", "arguments": {}}, + "org_ready": { + "request": {"name": "org_ready", "arguments": {}}, "typescript_response": {"content": [{"type": "text", "text": "{\"message\":\"no ready cells\"}"}]} } } diff --git a/test/parity/fixtures/swarm.json b/test/parity/fixtures/swarm.json index 1d4aa60..5a0c254 100644 --- a/test/parity/fixtures/swarm.json +++ b/test/parity/fixtures/swarm.json @@ -1,18 +1,18 @@ { - "swarm_init": { - "request": {"name": "swarm_init", "arguments": {"project_path": "/tmp/test"}}, + "forge_init": { + "request": {"name": "forge_init", "arguments": {"project_path": "/tmp/test"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"status\":\"initialized\",\"project_path\":\"/tmp/test\",\"isolation\":\"reservation\",\"timestamp\":\"2025-01-01T00:00:00Z\"}"}]} }, - "swarm_decompose": { - "request": {"name": "swarm_decompose", "arguments": {"task": "Build a feature"}}, + "forge_decompose": { + "request": {"name": "forge_decompose", "arguments": {"task": "Build a feature"}}, "typescript_response": {"content": [{"type": "text", "text": "## Task Decomposition"}]} }, - "swarm_status": { - "request": {"name": "swarm_status", "arguments": {"epic_id": "cell-epic123", "project_key": "test"}}, + "forge_status": { + "request": {"name": "forge_status", "arguments": {"epic_id": "cell-epic123", "project_key": "test"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"epic_id\":\"cell-epic123\",\"project_key\":\"test\",\"total\":0,\"counts\":{\"open\":0,\"in_progress\":0,\"blocked\":0,\"closed\":0},\"subtasks\":[]}"}]} }, - "swarm_worktree_list": { - "request": {"name": "swarm_worktree_list", "arguments": {"project_path": "/tmp/test"}}, + "forge_worktree_list": { + "request": {"name": "forge_worktree_list", "arguments": {"project_path": "/tmp/test"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"worktrees\":[],\"count\":0}"}]} } } diff --git a/test/parity/fixtures/swarmmail.json b/test/parity/fixtures/swarmmail.json index b634cd1..f3d5b04 100644 --- a/test/parity/fixtures/swarmmail.json +++ b/test/parity/fixtures/swarmmail.json @@ -1,18 +1,18 @@ { - "swarmmail_init": { - "request": {"name": "swarmmail_init", "arguments": {"project_path": "/tmp/test", "agent_name": "worker-1"}}, + "comms_init": { + "request": {"name": "comms_init", "arguments": {"project_path": "/tmp/test", "agent_name": "worker-1"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"id\":1,\"name\":\"worker-1\",\"project_path\":\"/tmp/test\",\"task_description\":\"\",\"status\":\"active\"}"}]} }, - "swarmmail_send": { - "request": {"name": "swarmmail_send", "arguments": {"to": ["worker-2"], "subject": "test", "body": "hello"}}, + "comms_send": { + "request": {"name": "comms_send", "arguments": {"to": ["worker-2"], "subject": "test", "body": "hello"}}, "typescript_response": {"content": [{"type": "text", "text": "{\"status\":\"sent\"}"}]} }, - "swarmmail_inbox": { - "request": {"name": "swarmmail_inbox", "arguments": {}}, + "comms_inbox": { + "request": {"name": "comms_inbox", "arguments": {}}, "typescript_response": {"content": [{"type": "text", "text": "[]"}]} }, - "swarmmail_health": { - "request": {"name": "swarmmail_health", "arguments": {}}, + "comms_health": { + "request": {"name": "comms_health", "arguments": {}}, "typescript_response": {"content": [{"type": "text", "text": "{\"status\":\"healthy\",\"agents\":0,\"messages\":0,\"reservations\":0}"}]} } } diff --git a/test/parity/parity_test.go b/test/parity/parity_test.go index 062dcbf..e3b721d 100644 --- a/test/parity/parity_test.go +++ b/test/parity/parity_test.go @@ -22,11 +22,11 @@ import ( "github.com/unbound-force/replicator/internal/db" "github.com/unbound-force/replicator/internal/memory" - hivetools "github.com/unbound-force/replicator/internal/tools/hive" + commstools "github.com/unbound-force/replicator/internal/tools/comms" + forgetools "github.com/unbound-force/replicator/internal/tools/forge" memtools "github.com/unbound-force/replicator/internal/tools/memory" + orgtools "github.com/unbound-force/replicator/internal/tools/org" "github.com/unbound-force/replicator/internal/tools/registry" - swarmtools "github.com/unbound-force/replicator/internal/tools/swarm" - swarmmailtools "github.com/unbound-force/replicator/internal/tools/swarmmail" ) // fixtureEntry represents a single tool's fixture data. @@ -84,9 +84,9 @@ func setupRegistry(t *testing.T) (*registry.Registry, *db.Store) { reg := registry.New() // Register all tool families. - hivetools.Register(reg, store) - swarmmailtools.Register(reg, store) - swarmtools.Register(reg, store) + orgtools.Register(reg, store) + commstools.Register(reg, store) + forgetools.Register(reg, store) // Memory tools use a Dewey proxy client. For parity tests, we create // a client pointing to a non-existent server -- the deprecated tools @@ -165,32 +165,32 @@ func TestParity(t *testing.T) { // Tools that need pre-existing data or special handling. // These are tested in dedicated subtests below. skipTools := map[string]string{ - "hive_cells_with_data": "requires pre-existing data, tested separately", - "hive_close": "requires pre-existing cell", - "hive_update": "requires pre-existing cell", - "hive_start": "requires pre-existing cell", + "org_cells_with_data": "requires pre-existing data, tested separately", + "org_close": "requires pre-existing cell", + "org_update": "requires pre-existing cell", + "org_start": "requires pre-existing cell", } // Tools that need argument rewriting (e.g., project_path). pathRewriteTools := map[string]bool{ - "swarm_init": true, - "swarm_worktree_list": true, + "forge_init": true, + "forge_worktree_list": true, } var results []ToolResult // Sort fixture names for deterministic execution order. - // This ensures hive_ready runs before hive_create (which would - // create cells and change the hive_ready response shape). + // This ensures org_ready runs before org_create (which would + // create cells and change the org_ready response shape). names := make([]string, 0, len(fixtures)) for name := range fixtures { names = append(names, name) } sort.Strings(names) - // Run empty-state tools first: hive_cells, hive_query, hive_ready - // must run before any hive_create calls populate the database. - emptyStateTools := []string{"hive_cells", "hive_query", "hive_ready"} + // Run empty-state tools first: org_cells, org_query, org_ready + // must run before any org_create calls populate the database. + emptyStateTools := []string{"org_cells", "org_query", "org_ready"} for _, name := range emptyStateTools { fixture, ok := fixtures[name] if !ok { @@ -204,9 +204,9 @@ func TestParity(t *testing.T) { // Run remaining tools (excluding skip and already-run empty-state tools). alreadyRun := map[string]bool{ - "hive_cells": true, - "hive_query": true, - "hive_ready": true, + "org_cells": true, + "org_query": true, + "org_ready": true, } for _, name := range names { if _, skip := skipTools[name]; skip { @@ -224,22 +224,22 @@ func TestParity(t *testing.T) { } // Test tools that need pre-existing data. - t.Run("hive_close_with_data", func(t *testing.T) { - result := testToolWithSetup(t, reg, store, "hive_close", fixtures) + t.Run("org_close_with_data", func(t *testing.T) { + result := testToolWithSetup(t, reg, store, "org_close", fixtures) results = append(results, result) }) - t.Run("hive_update_with_data", func(t *testing.T) { - result := testToolWithSetup(t, reg, store, "hive_update", fixtures) + t.Run("org_update_with_data", func(t *testing.T) { + result := testToolWithSetup(t, reg, store, "org_update", fixtures) results = append(results, result) }) - t.Run("hive_start_with_data", func(t *testing.T) { - result := testToolWithSetup(t, reg, store, "hive_start", fixtures) + t.Run("org_start_with_data", func(t *testing.T) { + result := testToolWithSetup(t, reg, store, "org_start", fixtures) results = append(results, result) }) - t.Run("hive_cells_with_data", func(t *testing.T) { + t.Run("org_cells_with_data", func(t *testing.T) { result := testCellsWithData(t, reg, store, fixtures) results = append(results, result) }) @@ -347,7 +347,7 @@ func testToolWithSetup(t *testing.T, reg *registry.Registry, store *db.Store, to } // Create a cell to operate on. - createTool := reg.Get("hive_create") + createTool := reg.Get("org_create") createResult, err := createTool.Execute(json.RawMessage(`{"title": "test cell for ` + toolName + `"}`)) if err != nil { t.Fatalf("create cell for %s: %v", toolName, err) @@ -409,10 +409,10 @@ func testToolWithSetup(t *testing.T, reg *registry.Registry, store *db.Store, to func testCellsWithData(t *testing.T, reg *registry.Registry, store *db.Store, fixtures map[string]fixtureEntry) ToolResult { t.Helper() - fixture, ok := fixtures["hive_cells_with_data"] + fixture, ok := fixtures["org_cells_with_data"] if !ok { return ToolResult{ - Name: "hive_cells_with_data", + Name: "org_cells_with_data", Match: false, Differences: []Difference{{ Path: "$", @@ -423,18 +423,18 @@ func testCellsWithData(t *testing.T, reg *registry.Registry, store *db.Store, fi } // Create a cell so the query returns data. - createTool := reg.Get("hive_create") + createTool := reg.Get("org_create") _, err := createTool.Execute(json.RawMessage(`{"title": "test cell for query"}`)) if err != nil { - t.Fatalf("create cell for hive_cells_with_data: %v", err) + t.Fatalf("create cell for org_cells_with_data: %v", err) } // Query cells. - tool := reg.Get("hive_cells") + tool := reg.Get("org_cells") goResult, err := tool.Execute(json.RawMessage(`{"status": "open"}`)) if err != nil { return ToolResult{ - Name: "hive_cells_with_data", + Name: "org_cells_with_data", Match: false, Differences: []Difference{{ Path: "$", @@ -451,13 +451,13 @@ func testCellsWithData(t *testing.T, reg *registry.Registry, store *db.Store, fi match, diffs := ShapeMatch(expectedText, actualText) if !match { for _, d := range diffs { - t.Errorf("hive_cells_with_data shape mismatch at %s: expected %s, got %s", + t.Errorf("org_cells_with_data shape mismatch at %s: expected %s, got %s", d.Path, d.ExpectedType, d.ActualType) } } return ToolResult{ - Name: "hive_cells_with_data", + Name: "org_cells_with_data", Match: match, Differences: diffs, } diff --git a/test/parity/report.go b/test/parity/report.go index 3a51ac9..6609253 100644 --- a/test/parity/report.go +++ b/test/parity/report.go @@ -18,8 +18,8 @@ type ToolResult struct { // // Parity Report // ============= -// hive_cells ✓ -// hive_create ✓ +// org_cells ✓ +// org_create ✓ // hivemind_find ✗ $.content[0].text: expected object, got string // --- // 22/23 tools match (95.7%)