diff --git a/internal/herdr/adapter.go b/internal/herdr/adapter.go index 47e4c96..aa9639a 100644 --- a/internal/herdr/adapter.go +++ b/internal/herdr/adapter.go @@ -120,7 +120,15 @@ const LaunchContextFile = ".orchestra/launch.md" // evidence is worth having, and is not worth refusing to start work over. func WriteLaunchContext(worktree, prompt string) error { path := filepath.Join(worktree, LaunchContextFile) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0o755); err != nil { + return err + } + // Orchestra's own scratch directory must not show up as the session's + // work. A .gitignore of "*" ignores the directory including itself, so the + // tree stays clean for the agent, for the gate, and for review evidence. + // The worker's done marker lives here too and had the same problem. + if err := os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*\n"), 0o644); err != nil { return err } return os.WriteFile(path, []byte(prompt), 0o644) diff --git a/internal/herdr/tmux_test.go b/internal/herdr/tmux_test.go index f2dba3f..55eb64e 100644 --- a/internal/herdr/tmux_test.go +++ b/internal/herdr/tmux_test.go @@ -3,6 +3,7 @@ package herdr import ( "context" "os" + "os/exec" "path/filepath" "strings" "testing" @@ -98,3 +99,29 @@ func TestTmuxSessionNameKeepsCollisionResistantSuffix(t *testing.T) { t.Fatalf("unsafe tmux session names %q %q", a, b) } } + +// The launch dump is Orchestra's scratch space, not the session's work. A +// worktree that starts dirty pollutes the gate, the review diff, and the +// agent's own reading of `git status`. +func TestWriteLaunchContextLeavesTheWorktreeClean(t *testing.T) { + dir := t.TempDir() + for _, args := range [][]string{{"init"}, {"config", "user.email", "t@t"}, {"config", "user.name", "t"}, {"commit", "--allow-empty", "-m", "init"}} { + if out, err := exec.Command("git", append([]string{"-C", dir}, args...)...).CombinedOutput(); err != nil { + t.Fatalf("git %v: %v: %s", args, err, out) + } + } + if err := WriteLaunchContext(dir, "the instruction"); err != nil { + t.Fatal(err) + } + b, err := os.ReadFile(filepath.Join(dir, LaunchContextFile)) + if err != nil || string(b) != "the instruction" { + t.Fatalf("launch context = %q, err %v", b, err) + } + out, err := exec.Command("git", "-C", dir, "status", "--short").CombinedOutput() + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(string(out)) != "" { + t.Fatalf("worktree is dirty after a launch dump:\n%s", out) + } +}