package orchestrator_test import ( "context" "orchestra/internal/continuity" "orchestra/internal/domain" "orchestra/internal/orchestrator" "os" "os/exec" "path/filepath" "testing" ) func initRepo(t *testing.T, dir string) { t.Helper() run := func(args ...string) { cmd := exec.Command("git", args...) cmd.Dir = dir cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@t") if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("git %v: %v: %s", args, err, out) } } if err := os.MkdirAll(dir, 0755); err != nil { t.Fatal(err) } run("init") if err := os.WriteFile(filepath.Join(dir, "README"), []byte("x"), 0644); err != nil { t.Fatal(err) } run("add", "README") run("commit", "-m", "init") } func TestPerProjectGitWorktreesResolvesByProject(t *testing.T) { base := t.TempDir() repoA := filepath.Join(base, "repo-a") repoB := filepath.Join(base, "repo-b") initRepo(t, repoA) initRepo(t, repoB) w := orchestrator.PerProjectGitWorktrees{ Projects: map[string]orchestrator.ProjectRepo{ "proj-a": {Repo: repoA, WorktreeRoot: filepath.Join(base, "wt-a")}, }, Default: orchestrator.GitWorktrees{Root: filepath.Join(base, "wt-default"), Repo: repoB}, } pathA, err := w.Create(context.Background(), domain.Task{ID: "t1", Project: "proj-a"}) if err != nil { t.Fatalf("create for proj-a: %v", err) } if filepath.Dir(pathA) != filepath.Join(base, "wt-a") { t.Fatalf("expected proj-a worktree under wt-a, got %s", pathA) } pathDefault, err := w.Create(context.Background(), domain.Task{ID: "t2", Project: "unconfigured-project"}) if err != nil { t.Fatalf("create for unconfigured project: %v", err) } if filepath.Dir(pathDefault) != filepath.Join(base, "wt-default") { t.Fatalf("expected unconfigured project to use default worktree root, got %s", pathDefault) } } // TestGitWorktreesCommitsTaskFile guards AUDIT.md's B6: nothing wrote a // TASK.md into a worktree in the first place, so ยง6.2 pickup validation had // nothing to check. GitWorktrees.Create must now write and commit an // immutable TASK.md whose on-disk hash matches continuity.RenderTaskFile. func TestGitWorktreesCommitsTaskFile(t *testing.T) { base := t.TempDir() repo := filepath.Join(base, "repo") initRepo(t, repo) w := orchestrator.GitWorktrees{Root: filepath.Join(base, "wt"), Repo: repo} task := domain.Task{ID: "t1", Project: "p", Source: "jsonl", ExternalID: "1", Title: "do the thing"} path, err := w.Create(context.Background(), task) if err != nil { t.Fatalf("create: %v", err) } want := continuity.RenderTaskFile(task) got, err := os.ReadFile(filepath.Join(path, "TASK.md")) if err != nil { t.Fatalf("read TASK.md: %v", err) } if string(got) != string(want) { t.Fatalf("TASK.md content mismatch:\ngot: %s\nwant: %s", got, want) } status, err := exec.Command("git", "-C", path, "status", "--porcelain", "--", "TASK.md").Output() if err != nil { t.Fatal(err) } if len(status) != 0 { t.Fatalf("TASK.md not committed, status: %s", status) } sha, err := continuity.TaskFileHash(path) if err != nil { t.Fatal(err) } if err := continuity.VerifyTaskFile(path, sha); err != nil { t.Fatalf("VerifyTaskFile: %v", err) } // Re-creating (path already exists) must not touch the committed file. path2, err := w.Create(context.Background(), task) if err != nil { t.Fatalf("recreate: %v", err) } if path2 != path { t.Fatalf("recreate returned different path: %s vs %s", path2, path) } got2, err := os.ReadFile(filepath.Join(path, "TASK.md")) if err != nil { t.Fatal(err) } if string(got2) != string(want) { t.Fatalf("TASK.md changed on recreate") } }