package e2e import ( "encoding/json" "os" "os/exec" "path/filepath" "strings" "testing" ) func agentLibCmd(args ...string) *exec.Cmd { return exec.Command(binPath, args...) } type syncEnv struct { home string stateDir string configDir string config string workRepo string } func newSyncEnv(t *testing.T, h *harness) *syncEnv { t.Helper() base := t.TempDir() env := &syncEnv{ home: filepath.Join(base, "home"), stateDir: filepath.Join(base, "state"), configDir: filepath.Join(base, "config"), workRepo: filepath.Join(base, "work-repo"), } for _, d := range []string{env.home, env.stateDir, env.configDir, env.workRepo} { if err := os.MkdirAll(d, 0o755); err != nil { t.Fatal(err) } } return env } func (e *syncEnv) writeConfig(t *testing.T, repoURL, token string) { t.Helper() cfg := map[string]string{"repo_url": repoURL, "ref": "main"} if token != "" { cfg["token"] = token } data, err := json.Marshal(cfg) if err != nil { t.Fatal(err) } e.config = filepath.Join(e.configDir, "config.json") os.WriteFile(e.config, data, 0o600) } func (e *syncEnv) run(t *testing.T, args ...string) (string, error) { t.Helper() cmd := agentLibCmd(append([]string{"sync", "--config", e.config}, args...)...) cmd.Env = append(os.Environ(), "HOME="+e.home, "USERPROFILE="+e.home, "AGENT_LIB_STATE_DIR="+e.stateDir, ) out, err := cmd.CombinedOutput() return string(out), err } func buildWorkRepo(t *testing.T, h *harness, dir string) { t.Helper() h.git(t, dir, "init", "-b", "main") write := func(rel, content string) { p := filepath.Join(dir, rel) os.MkdirAll(filepath.Dir(p), 0o755) os.WriteFile(p, []byte(content), 0o644) } write("skills/own-skill/SKILL.md", "---\nname: Own Skill\n---\n# Own\n") write("skills/own-skill/run.sh", "#!/bin/sh\necho hi\n") os.Chmod(filepath.Join(dir, "skills/own-skill/run.sh"), 0o755) write("commands/deploy.md", "---\nname: Deploy\n---\nbody\n") write("agents/helper.md", "---\nname: Helper\n---\nbody\n") write("mcp/internal.yaml", "servers: {}\n") h.git(t, dir, "add", "-A") h.git(t, dir, "commit", "-m", "work repo") } func TestSyncHappyPathAndManifest(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") out, err := env.run(t) if err != nil { t.Fatalf("sync failed: %v\n%s", err, out) } skillMd := filepath.Join(env.home, ".agents/skills/own-skill/SKILL.md") if _, err := os.Stat(skillMd); err != nil { t.Error("skill must deploy to ~/.agents/skills//") } runSh := filepath.Join(env.home, ".agents/skills/own-skill/run.sh") info, err := os.Stat(runSh) if err != nil { t.Fatal("skill folder artifacts must be preserved") } if info.Mode()&0o111 == 0 { t.Error("executable bit must survive deployment") } if _, err := os.Stat(filepath.Join(env.home, ".config/opencode/commands/deploy.md")); err != nil { t.Error("command must deploy to ~/.config/opencode/commands/.md") } if _, err := os.Stat(filepath.Join(env.home, ".config/opencode/agents/helper.md")); err != nil { t.Error("agent must deploy to ~/.config/opencode/agents/.md") } if _, err := os.Stat(filepath.Join(env.home, ".config/opencode/mcp")); err == nil { t.Error("MCP must never be deployed") } mcpEverywhere := false filepath.WalkDir(env.home, func(p string, d os.DirEntry, err error) error { if d != nil && !d.IsDir() && strings.HasSuffix(p, ".yaml") { mcpEverywhere = true } return nil }) if mcpEverywhere { t.Error("no yaml fragment may land anywhere in HOME") } manifestData, err := os.ReadFile(filepath.Join(env.stateDir, "manifest.json")) if err != nil { t.Fatal("manifest must exist in the state dir") } var manifest struct { Version int `json:"version"` Items map[string]struct { Name string `json:"name"` Type string `json:"type"` Origin string `json:"origin"` Revision string `json:"revision"` Hash string `json:"hash"` } `json:"items"` } if err := json.Unmarshal(manifestData, &manifest); err != nil { t.Fatalf("manifest not parseable: %v", err) } skill := manifest.Items["skills/own-skill"] if skill.Origin != "own" || skill.Hash == "" || skill.Revision == "" { t.Errorf("manifest record incomplete: %+v", skill) } } func TestSyncIdempotentSecondRun(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") if _, err := env.run(t); err != nil { t.Fatal(err) } skillMd := filepath.Join(env.home, ".agents/skills/own-skill/SKILL.md") firstInfo, _ := os.Stat(skillMd) manifestBefore := readFile(t, filepath.Join(env.stateDir, "manifest.json")) out, err := env.run(t) if err != nil { t.Fatalf("second sync failed: %v\n%s", err, out) } if !contains(out, "kept: 3") { t.Errorf("second run must keep everything:\n%s", out) } secondInfo, _ := os.Stat(skillMd) if !firstInfo.ModTime().Equal(secondInfo.ModTime()) { t.Error("unchanged items must not be rewritten (byte-level no-op)") } if got := readFile(t, filepath.Join(env.stateDir, "manifest.json")); got != manifestBefore { t.Error("manifest bytes must not change on a no-op sync") } } func TestSyncUnreachableRepoKeepsState(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") if _, err := env.run(t); err != nil { t.Fatal(err) } skillBefore := readFile(t, filepath.Join(env.home, ".agents/skills/own-skill/SKILL.md")) env.writeConfig(t, filepath.Join(t.TempDir(), "does-not-exist"), "") out, err := env.run(t) if err == nil { t.Fatalf("unreachable repo must fail:\n%s", out) } if !contains(out, "error") { t.Errorf("failure must be a clean error:\n%s", out) } if got := readFile(t, filepath.Join(env.home, ".agents/skills/own-skill/SKILL.md")); got != skillBefore { t.Error("previous state must survive a failed sync") } } func TestSyncPicksUpChanges(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") env.run(t) p := filepath.Join(env.workRepo, "commands/deploy.md") os.WriteFile(p, []byte("---\nname: Deploy\ndescription: updated\n---\nnew body\n"), 0o644) h.git(t, env.workRepo, "add", "-A") h.git(t, env.workRepo, "commit", "-m", "update command") out, err := env.run(t) if err != nil { t.Fatalf("sync after upstream change failed: %v\n%s", err, out) } if !contains(out, "updated: 1") { t.Errorf("changed item must be updated:\n%s", out) } deployed := readFile(t, filepath.Join(env.home, ".config/opencode/commands/deploy.md")) if !contains(deployed, "new body") { t.Error("updated content must land on disk") } } func TestSyncVendoredContentDeploys(t *testing.T) { h := newHarness(t) standardFixture(h, t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) upstreamWork := env.workRepo cmd := agentLibCmd("vendor", "add", "superpowers", h.upstreamDir) cmd.Dir = upstreamWork if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("vendor add into work repo: %v\n%s", err, out) } h.git(t, upstreamWork, "add", "-A") h.git(t, upstreamWork, "commit", "-m", "vendor superpowers") env.writeConfig(t, upstreamWork, "") out, err := env.run(t) if err != nil { t.Fatalf("sync with vendored content failed: %v\n%s", err, out) } if _, err := os.Stat(filepath.Join(env.home, ".agents/skills/good-skill/SKILL.md")); err != nil { t.Error("vendored skill must deploy under its deployed name") } manifestData := []byte(readFile(t, filepath.Join(env.stateDir, "manifest.json"))) if !contains(string(manifestData), `"origin": "superpowers"`) { t.Errorf("manifest must record the source origin:\n%s", manifestData) } } func TestSyncTokenNeverLeaked(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "super-secret-token") out, err := env.run(t) if err != nil { t.Fatalf("sync with token failed: %v\n%s", err, out) } if contains(out, "super-secret-token") { t.Errorf("token must never appear in output:\n%s", out) } for _, path := range []string{ filepath.Join(env.stateDir, "manifest.json"), filepath.Join(env.workRepo, "agent-lib.lock.json"), } { if data, err := os.ReadFile(path); err == nil && strings.Contains(string(data), "super-secret-token") { t.Errorf("token leaked into %s", path) } } }