ajhahn.de
← eeco
Go 1997 lines
package config

import (
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

// TestMain pins the workspace owner so Load resolves a deterministic
// username across machines instead of picking up the dev box's
// `git config user.name`. Every Load in this package then scopes the
// workspace under <root>/tester/.eeco.
func TestMain(m *testing.M) {
	os.Setenv("EECO_USERNAME", "tester")
	// Pin the user-global config dir to an empty temp dir so the global
	// layer is a hermetic no-op and tests never read the dev box's
	// ~/.config/eeco. Tests that exercise the global layer override via
	// t.Setenv(GlobalConfigEnv, ...).
	gdir, err := os.MkdirTemp("", "eeco-global-")
	if err != nil {
		panic(err)
	}
	os.Setenv(GlobalConfigEnv, gdir)
	code := m.Run()
	os.RemoveAll(gdir)
	os.Exit(code)
}

func TestFindRepoRoot_WalksUpToDotGit(t *testing.T) {
	root := t.TempDir()
	if err := os.Mkdir(filepath.Join(root, ".git"), 0o755); err != nil {
		t.Fatal(err)
	}
	deep := filepath.Join(root, "a", "b", "c")
	if err := os.MkdirAll(deep, 0o755); err != nil {
		t.Fatal(err)
	}

	got, err := FindRepoRoot(deep)
	if err != nil {
		t.Fatalf("FindRepoRoot(%q) error: %v", deep, err)
	}
	wantRoot, _ := filepath.EvalSymlinks(root)
	gotRoot, _ := filepath.EvalSymlinks(got)
	if gotRoot != wantRoot {
		t.Fatalf("FindRepoRoot = %q, want %q", gotRoot, wantRoot)
	}
}

func TestFindRepoRoot_AcceptsGitFile(t *testing.T) {
	// Worktrees use a `.git` *file* with a gitdir pointer; FindRepoRoot
	// must accept that too.
	root := t.TempDir()
	if err := os.WriteFile(filepath.Join(root, ".git"), []byte("gitdir: x\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	got, err := FindRepoRoot(root)
	if err != nil {
		t.Fatal(err)
	}
	wantRoot, _ := filepath.EvalSymlinks(root)
	gotRoot, _ := filepath.EvalSymlinks(got)
	if gotRoot != wantRoot {
		t.Fatalf("FindRepoRoot = %q, want %q", gotRoot, wantRoot)
	}
}

func TestFindRepoRoot_ErrorsOutsideRepo(t *testing.T) {
	// A fresh temp directory should not be inside any git repo on any
	// sane test machine; if it is, the test environment is broken.
	dir := t.TempDir()
	if _, err := FindRepoRoot(dir); err == nil {
		t.Fatal("expected error outside repo, got nil")
	}
}

// newHostWithPrivateRepo builds a host repo (<root>/.git) containing eeco's
// private workspace-history repo at <root>/tester/.git with the engine
// workspace <root>/tester/.eeco beside it — the on-disk shape `eeco init`
// leaves and the cwd the harness launches from to load the emitted cockpit.
// It returns the host root and the private workspace dir (<root>/tester).
func newHostWithPrivateRepo(t *testing.T) (host, priv string) {
	t.Helper()
	host = newRepo(t)
	priv = filepath.Join(host, "tester")
	if err := os.MkdirAll(filepath.Join(priv, ".git"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(priv, DefaultWorkspace), 0o755); err != nil {
		t.Fatal(err)
	}
	return host, priv
}

func TestResolveProjectRoot_SkipsPrivateWorkspaceRepo(t *testing.T) {
	// FIX-1: from inside <username>/ (and deeper), root detection must walk
	// past the private <username>/.git and resolve the host project root.
	host, priv := newHostWithPrivateRepo(t)
	wantRoot, _ := filepath.EvalSymlinks(host)

	for _, start := range []string{priv, filepath.Join(priv, DefaultWorkspace, "memory")} {
		if err := os.MkdirAll(start, 0o755); err != nil {
			t.Fatal(err)
		}
		got, err := resolveProjectRoot(start)
		if err != nil {
			t.Fatalf("resolveProjectRoot(%q) error: %v", start, err)
		}
		gotRoot, _ := filepath.EvalSymlinks(got)
		if gotRoot != wantRoot {
			t.Fatalf("resolveProjectRoot(%q) = %q, want host root %q (must skip the private <username>/.git)", start, gotRoot, wantRoot)
		}
	}
}

func TestResolveProjectRoot_NormalRepoUnchanged(t *testing.T) {
	// A repo with no .eeco beside its .git resolves exactly like FindRepoRoot.
	root := newRepo(t)
	deep := filepath.Join(root, "a", "b")
	if err := os.MkdirAll(deep, 0o755); err != nil {
		t.Fatal(err)
	}
	got, err := resolveProjectRoot(deep)
	if err != nil {
		t.Fatal(err)
	}
	wantRoot, _ := filepath.EvalSymlinks(root)
	gotRoot, _ := filepath.EvalSymlinks(got)
	if gotRoot != wantRoot {
		t.Fatalf("resolveProjectRoot = %q, want %q", gotRoot, wantRoot)
	}
}

func TestResolveProjectRoot_PrivateOnlyFallsBack(t *testing.T) {
	// A private workspace repo with no host repo above it (a shape eeco init
	// never produces) falls back to the private repo rather than erroring, so
	// the fix is never worse than a plain FindRepoRoot.
	base := t.TempDir()
	priv := filepath.Join(base, "tester")
	if err := os.MkdirAll(filepath.Join(priv, ".git"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(priv, DefaultWorkspace), 0o755); err != nil {
		t.Fatal(err)
	}
	got, err := resolveProjectRoot(priv)
	if err != nil {
		t.Fatalf("resolveProjectRoot fallback error: %v", err)
	}
	wantRoot, _ := filepath.EvalSymlinks(priv)
	gotRoot, _ := filepath.EvalSymlinks(got)
	if gotRoot != wantRoot {
		t.Fatalf("resolveProjectRoot fallback = %q, want private repo %q", gotRoot, wantRoot)
	}
}

func TestLoad_FromInsidePrivateWorkspaceRepo(t *testing.T) {
	// The FIX-1 repro end-to-end: launched from <repo>/<username>/, Load must
	// resolve the host repo root and the real workspace, not the nested
	// private repo (which made <repo>/<username>/<username>/.eeco missing).
	host, priv := newHostWithPrivateRepo(t)
	write(t, host, "go.mod", "module x\n")

	cfg, err := Load(priv, "")
	if err != nil {
		t.Fatal(err)
	}
	wantRoot, _ := filepath.EvalSymlinks(host)
	if gotRoot, _ := filepath.EvalSymlinks(cfg.RepoRoot); gotRoot != wantRoot {
		t.Fatalf("RepoRoot = %q, want host root %q", gotRoot, wantRoot)
	}
	wantUserDir, _ := filepath.EvalSymlinks(priv)
	if gotUserDir, _ := filepath.EvalSymlinks(cfg.UserDir); gotUserDir != wantUserDir {
		t.Fatalf("UserDir = %q, want %q", gotUserDir, wantUserDir)
	}
	wantWS, _ := filepath.EvalSymlinks(filepath.Join(host, "tester", DefaultWorkspace))
	if gotWS, _ := filepath.EvalSymlinks(cfg.Workspace); gotWS != wantWS {
		t.Fatalf("Workspace = %q, want %q", gotWS, wantWS)
	}
}

func TestDetectProfile(t *testing.T) {
	cases := []struct {
		name string
		seed map[string]string // path -> contents
		want Profile
	}{
		{"go", map[string]string{"go.mod": "module x\n"}, ProfileGo},
		{"zig", map[string]string{"build.zig": ""}, ProfileZig},
		{"rust", map[string]string{"Cargo.toml": "[package]\n"}, ProfileRust},
		{"node", map[string]string{"package.json": "{}"}, ProfileNode},
		{"python-pyproject", map[string]string{"pyproject.toml": ""}, ProfilePython},
		{"python-requirements", map[string]string{"requirements.txt": ""}, ProfilePython},
		{"python-requirements-dev", map[string]string{"requirements-dev.txt": ""}, ProfilePython},
		{"python-venv", map[string]string{".venv/bin/python": ""}, ProfilePython},
		{"generic-empty", map[string]string{}, ProfileGeneric},
		{"generic-random", map[string]string{"some-file.txt": "x"}, ProfileGeneric},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			dir := t.TempDir()
			for path, content := range tc.seed {
				full := filepath.Join(dir, path)
				if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
					t.Fatal(err)
				}
				if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
					t.Fatal(err)
				}
			}
			got := DetectProfile(dir)
			if got != tc.want {
				t.Fatalf("DetectProfile = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestDetectProfile_GoWinsOverPython(t *testing.T) {
	// A polyglot repo with both go.mod and pyproject.toml resolves to
	// the documented precedence order (Go first).
	dir := t.TempDir()
	write(t, dir, "go.mod", "module x\n")
	write(t, dir, "pyproject.toml", "")
	if got := DetectProfile(dir); got != ProfileGo {
		t.Fatalf("DetectProfile polyglot = %q, want %q", got, ProfileGo)
	}
}

func TestGateFor(t *testing.T) {
	cases := map[Profile][][]string{
		ProfileGo:      {{"go", "vet", "./..."}},
		ProfileZig:     {{"zig", "build", "--summary", "none"}},
		ProfileRust:    {{"cargo", "check", "--quiet"}},
		ProfileNode:    {{"npm", "run", "--if-present", "typecheck"}},
		ProfilePython:  {{"python3", "-m", "compileall", "-q", "."}},
		ProfileGeneric: nil,
	}
	for p, want := range cases {
		got := GateFor(p)
		if !reflect.DeepEqual(got, want) {
			t.Errorf("GateFor(%q) = %v, want %v", p, got, want)
		}
	}
}

func TestGateFor_ReturnsFreshSlice(t *testing.T) {
	a := GateFor(ProfileGo)
	b := GateFor(ProfileGo)
	a[0][0] = "MUTATED"
	if b[0][0] == "MUTATED" {
		t.Fatal("GateFor returned a shared backing array; expected a fresh copy")
	}
}

func TestLoad_DefaultsAndRepoRoot(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if got, want := cfg.WorkspaceName, DefaultWorkspace; got != want {
		t.Errorf("workspace name = %q, want %q", got, want)
	}
	wantWS := filepath.Join(root, "tester", DefaultWorkspace)
	gotWS, _ := filepath.EvalSymlinks(filepath.Dir(cfg.Workspace))
	wantWSDir, _ := filepath.EvalSymlinks(filepath.Dir(wantWS))
	if gotWS != wantWSDir {
		t.Errorf("workspace parent = %q, want %q", gotWS, wantWSDir)
	}
	if cfg.Profile != ProfileGo {
		t.Errorf("profile = %q, want %q", cfg.Profile, ProfileGo)
	}
	if !reflect.DeepEqual(cfg.Gate, [][]string{{"go", "vet", "./..."}}) {
		t.Errorf("gate = %v", cfg.Gate)
	}
}

func TestLoad_RejectsBadWorkspaceName(t *testing.T) {
	root := newRepo(t)
	cases := []string{"..", ".", "foo/bar", "/abs", `back\slash`}
	for _, name := range cases {
		if _, err := Load(root, name); err == nil {
			t.Errorf("Load(%q) succeeded; expected error", name)
		}
	}
}

func TestLoad_ConfigLocalOverride(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", strings.Join([]string{
		"# comment line",
		"",
		`profile = "generic"`,
		"gate=make check",
		"unknown_key=ignored",
	}, "\n"))

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Profile != ProfileGeneric {
		t.Errorf("profile override = %q, want generic", cfg.Profile)
	}
	if !reflect.DeepEqual(cfg.Gate, [][]string{{"make", "check"}}) {
		t.Errorf("gate override = %v, want [[make check]]", cfg.Gate)
	}
}

func TestLoad_ConfigLocalProfileResetsGate(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "profile=rust\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Profile != ProfileRust {
		t.Fatalf("profile = %q, want rust", cfg.Profile)
	}
	if !reflect.DeepEqual(cfg.Gate, [][]string{{"cargo", "check", "--quiet"}}) {
		t.Fatalf("gate after profile override = %v", cfg.Gate)
	}
}

func TestLoad_ConfigLocalEmptyGateDisablesIt(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "gate=\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Gate != nil {
		t.Fatalf("gate = %v, want nil", cfg.Gate)
	}
}

func TestLoad_ConfigLocalMultiStepGate(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	// Three `gate=` lines: the first resets the profile default, all
	// three append, so the chain runs in declared order.
	write(t, wsDir, "config.local", strings.Join([]string{
		"gate=go vet ./...",
		"gate=staticcheck ./...",
		"gate=go test ./...",
	}, "\n"))

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := [][]string{
		{"go", "vet", "./..."},
		{"staticcheck", "./..."},
		{"go", "test", "./..."},
	}
	if !reflect.DeepEqual(cfg.Gate, want) {
		t.Fatalf("gate chain = %v, want %v", cfg.Gate, want)
	}
}

func TestLoad_DefaultStaleDays(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.StaleDays != DefaultStaleDays {
		t.Errorf("StaleDays = %d, want %d", cfg.StaleDays, DefaultStaleDays)
	}
}

func TestLoad_ConfigLocalStaleDaysOverride(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "stale_days=7\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.StaleDays != 7 {
		t.Errorf("StaleDays = %d, want 7", cfg.StaleDays)
	}
}

func TestLoad_ConfigLocalStaleDaysMalformed(t *testing.T) {
	cases := []string{"stale_days=abc\n", "stale_days=-3\n"}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_ConfigLocalInitDetectionThreshold(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "init_detection_threshold=0.85\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.InitDetectionThreshold != 0.85 {
		t.Errorf("InitDetectionThreshold = %v, want 0.85", cfg.InitDetectionThreshold)
	}
}

func TestLoad_ConfigLocalInitDetectionThresholdMalformed(t *testing.T) {
	cases := []string{
		"init_detection_threshold=abc\n",
		"init_detection_threshold=2\n",
		"init_detection_threshold=-0.1\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_ConfigLocalMalformed(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "no-equals-sign-here\n")

	if _, err := Load(root, ""); err == nil {
		t.Fatal("expected malformed config.local to error")
	}
}

func TestLoad_SessionSettingsPath(t *testing.T) {
	t.Run("unset default is empty", func(t *testing.T) {
		t.Setenv("EECO_SESSION_SETTINGS", "")
		root := newRepo(t)
		cfg, err := Load(root, "")
		if err != nil {
			t.Fatal(err)
		}
		if cfg.SessionSettingsPath != "" {
			t.Errorf("SessionSettingsPath = %q, want empty", cfg.SessionSettingsPath)
		}
	})

	t.Run("env supplies the default", func(t *testing.T) {
		t.Setenv("EECO_SESSION_SETTINGS", "/abs/env/settings.json")
		root := newRepo(t)
		cfg, err := Load(root, "")
		if err != nil {
			t.Fatal(err)
		}
		if cfg.SessionSettingsPath != "/abs/env/settings.json" {
			t.Errorf("SessionSettingsPath = %q, want the env value", cfg.SessionSettingsPath)
		}
	})

	t.Run("config.local overrides env", func(t *testing.T) {
		envPath := filepath.Join(t.TempDir(), "env-settings.json")
		localPath := filepath.Join(t.TempDir(), "local-settings.json")
		t.Setenv("EECO_SESSION_SETTINGS", envPath)
		root := newRepo(t)
		wsDir := filepath.Join(root, "tester", DefaultWorkspace)
		if err := os.MkdirAll(wsDir, 0o755); err != nil {
			t.Fatal(err)
		}
		write(t, wsDir, "config.local", "session_settings_path="+localPath+"\n")
		cfg, err := Load(root, "")
		if err != nil {
			t.Fatal(err)
		}
		if cfg.SessionSettingsPath != localPath {
			t.Errorf("SessionSettingsPath = %q, want the config.local value", cfg.SessionSettingsPath)
		}
	})

	t.Run("empty value clears the env default", func(t *testing.T) {
		t.Setenv("EECO_SESSION_SETTINGS", "/abs/env/settings.json")
		root := newRepo(t)
		wsDir := filepath.Join(root, "tester", DefaultWorkspace)
		if err := os.MkdirAll(wsDir, 0o755); err != nil {
			t.Fatal(err)
		}
		write(t, wsDir, "config.local", "session_settings_path=\n")
		cfg, err := Load(root, "")
		if err != nil {
			t.Fatal(err)
		}
		if cfg.SessionSettingsPath != "" {
			t.Errorf("SessionSettingsPath = %q, want empty after explicit clear", cfg.SessionSettingsPath)
		}
	})

	t.Run("relative path is rejected", func(t *testing.T) {
		t.Setenv("EECO_SESSION_SETTINGS", "")
		root := newRepo(t)
		wsDir := filepath.Join(root, "tester", DefaultWorkspace)
		if err := os.MkdirAll(wsDir, 0o755); err != nil {
			t.Fatal(err)
		}
		write(t, wsDir, "config.local", "session_settings_path=rel/settings.json\n")
		if _, err := Load(root, ""); err == nil {
			t.Fatal("expected a relative session_settings_path to error")
		}
	})
}

func TestLoad_DefaultBugReportDir(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.BugReportDir != DefaultBugReportDir {
		t.Errorf("BugReportDir = %q, want %q", cfg.BugReportDir, DefaultBugReportDir)
	}
}

func TestLoad_ConfigLocalBugReportDirOverride(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "bug_report_dir=my-bugs\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.BugReportDir != "my-bugs" {
		t.Errorf("BugReportDir = %q, want my-bugs", cfg.BugReportDir)
	}
}

func TestLoad_ConfigLocalBugReportDirEmptyResetsToDefault(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "bug_report_dir=\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.BugReportDir != DefaultBugReportDir {
		t.Errorf("BugReportDir = %q, want default %q", cfg.BugReportDir, DefaultBugReportDir)
	}
}

func TestLoad_ConfigLocalBugReportDirRejected(t *testing.T) {
	cases := []string{
		"bug_report_dir=/abs/path\n",
		"bug_report_dir=..\n",
		"bug_report_dir=../escape\n",
		"bug_report_dir=sub/../../escape\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultContextPath(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.ContextPath != DefaultContextPath {
		t.Errorf("ContextPath = %q, want %q", cfg.ContextPath, DefaultContextPath)
	}
}

func TestLoad_ConfigLocalContextPathOverride(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "context_path=brief/project.md\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.ContextPath != "brief/project.md" {
		t.Errorf("ContextPath = %q, want brief/project.md", cfg.ContextPath)
	}
}

func TestLoad_ConfigLocalContextPathEmptyResetsToDefault(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "context_path=\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.ContextPath != DefaultContextPath {
		t.Errorf("ContextPath = %q, want default %q", cfg.ContextPath, DefaultContextPath)
	}
}

func TestLoad_ConfigLocalContextPathRejected(t *testing.T) {
	cases := []string{
		"context_path=/abs/path.md\n",
		"context_path=..\n",
		"context_path=../escape.md\n",
		"context_path=sub/../../escape.md\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultContextBudget(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.ContextBudget != DefaultContextBudget {
		t.Errorf("ContextBudget = %d, want %d", cfg.ContextBudget, DefaultContextBudget)
	}
}

func TestLoad_ConfigLocalContextBudgetOverride(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "context_budget=800\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.ContextBudget != 800 {
		t.Errorf("ContextBudget = %d, want 800", cfg.ContextBudget)
	}
}

func TestLoad_ConfigLocalContextBudgetEmptyResetsToDefault(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "context_budget=\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.ContextBudget != DefaultContextBudget {
		t.Errorf("ContextBudget = %d, want default %d", cfg.ContextBudget, DefaultContextBudget)
	}
}

func TestLoad_ConfigLocalContextBudgetRejected(t *testing.T) {
	cases := []string{
		"context_budget=-1\n",
		"context_budget=notanumber\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultBriefIncludeNotes(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.BriefIncludeNotes != DefaultBriefIncludeNotes {
		t.Errorf("BriefIncludeNotes = %v, want %v", cfg.BriefIncludeNotes, DefaultBriefIncludeNotes)
	}
}

func TestLoad_ConfigLocalBriefIncludeNotesAccepted(t *testing.T) {
	cases := []struct {
		body string
		want bool
	}{
		{"brief_include_notes=true\n", true},
		{"brief_include_notes=false\n", false},
		{"brief_include_notes=1\n", true},
		{"brief_include_notes=0\n", false},
		{"brief_include_notes=\n", DefaultBriefIncludeNotes},
	}
	for _, tc := range cases {
		t.Run(tc.body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatal(err)
			}
			if cfg.BriefIncludeNotes != tc.want {
				t.Errorf("BriefIncludeNotes = %v, want %v", cfg.BriefIncludeNotes, tc.want)
			}
		})
	}
}

func TestLoad_ConfigLocalBriefIncludeNotesRejected(t *testing.T) {
	cases := []string{
		"brief_include_notes=yes\n",
		"brief_include_notes=no\n",
		"brief_include_notes=notabool\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultSessionStartPinnedBodies(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.SessionStartPinnedBodies != DefaultSessionStartPinnedBodies {
		t.Errorf("SessionStartPinnedBodies = %v, want %v",
			cfg.SessionStartPinnedBodies, DefaultSessionStartPinnedBodies)
	}
}

func TestLoad_ConfigLocalSessionStartPinnedBodiesAccepted(t *testing.T) {
	cases := []struct {
		body string
		want bool
	}{
		{"session_start_pinned_bodies=true\n", true},
		{"session_start_pinned_bodies=false\n", false},
		{"session_start_pinned_bodies=1\n", true},
		{"session_start_pinned_bodies=0\n", false},
		{"session_start_pinned_bodies=\n", DefaultSessionStartPinnedBodies},
	}
	for _, tc := range cases {
		t.Run(tc.body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatal(err)
			}
			if cfg.SessionStartPinnedBodies != tc.want {
				t.Errorf("SessionStartPinnedBodies = %v, want %v",
					cfg.SessionStartPinnedBodies, tc.want)
			}
		})
	}
}

func TestLoad_ConfigLocalSessionStartPinnedBodiesRejected(t *testing.T) {
	cases := []string{
		"session_start_pinned_bodies=yes\n",
		"session_start_pinned_bodies=no\n",
		"session_start_pinned_bodies=notabool\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultVersionLocationsEmpty(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if len(cfg.VersionLocations) != 0 {
		t.Errorf("VersionLocations = %v, want empty", cfg.VersionLocations)
	}
}

func TestLoad_ConfigLocalVersionLocationsRepeatable(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", strings.Join([]string{
		`version_locations=CHANGELOG.md:^## \[v(\d+\.\d+\.\d+)\]`,
		`version_locations=VERSION:^v(\d+\.\d+\.\d+)`,
		"",
		"version_locations=", // blank — ignored, no phantom entry
	}, "\n")+"\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{
		`CHANGELOG.md:^## \[v(\d+\.\d+\.\d+)\]`,
		`VERSION:^v(\d+\.\d+\.\d+)`,
	}
	if !reflect.DeepEqual(cfg.VersionLocations, want) {
		t.Fatalf("VersionLocations = %v, want %v", cfg.VersionLocations, want)
	}
}

func TestLoad_ConfigLocalVersionLocationsRejected(t *testing.T) {
	cases := []string{
		"version_locations=no-colon-here\n",
		"version_locations=:no-path\n",
		"version_locations=/abs/path:v(\\d+)\n",
		"version_locations=..:v(\\d+)\n",
		"version_locations=../escape.md:v(\\d+)\n",
		"version_locations=sub/../../escape.md:v(\\d+)\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_ConfigLocalVersionLocationsAuto(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "version_locations=auto\n")
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"auto"}
	if !reflect.DeepEqual(cfg.VersionLocations, want) {
		t.Fatalf("VersionLocations = %v, want %v", cfg.VersionLocations, want)
	}
}

func TestLoad_ConfigLocalVersionLocationsAutoRejectsMix(t *testing.T) {
	cases := map[string]string{
		"auto then explicit": "version_locations=auto\n" +
			`version_locations=VERSION:v(\d+\.\d+\.\d+)` + "\n",
		"explicit then auto": `version_locations=VERSION:v(\d+\.\d+\.\d+)` + "\n" +
			"version_locations=auto\n",
		"auto twice": "version_locations=auto\nversion_locations=auto\n",
	}
	for name, body := range cases {
		t.Run(name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultVersionAnchorEmpty(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.VersionAnchor != "" {
		t.Errorf("VersionAnchor default = %q, want empty", cfg.VersionAnchor)
	}
}

func TestLoad_ConfigLocalVersionAnchorTag(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "version_anchor=tag\n")
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.VersionAnchor != "tag" {
		t.Errorf("VersionAnchor = %q, want %q", cfg.VersionAnchor, "tag")
	}
}

func TestLoad_ConfigLocalVersionAnchorFile(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	body := `version_anchor=VERSION:^v(\d+\.\d+\.\d+)` + "\n"
	write(t, wsDir, "config.local", body)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := `VERSION:^v(\d+\.\d+\.\d+)`
	if cfg.VersionAnchor != want {
		t.Errorf("VersionAnchor = %q, want %q", cfg.VersionAnchor, want)
	}
}

func TestLoad_ConfigLocalVersionAnchorEmptyResetsToDefault(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "version_anchor=\n")
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.VersionAnchor != "" {
		t.Errorf("VersionAnchor = %q, want empty", cfg.VersionAnchor)
	}
}

func TestLoad_ConfigLocalVersionAnchorRejected(t *testing.T) {
	cases := []string{
		"version_anchor=no-colon-here\n",
		"version_anchor=:no-path\n",
		"version_anchor=VERSION:\n",
		"version_anchor=/abs/path:v(\\d+)\n",
		"version_anchor=..:v(\\d+)\n",
		"version_anchor=../escape.md:v(\\d+)\n",
		"version_anchor=sub/../../escape.md:v(\\d+)\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestLoad_DefaultPreCommitWorkflows(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"leak-guard", "version-sync"}
	if !reflect.DeepEqual(cfg.PreCommitWorkflows, want) {
		t.Errorf("PreCommitWorkflows default = %v, want %v", cfg.PreCommitWorkflows, want)
	}
}

func TestLoad_ConfigLocalPreCommitWorkflowsReplacesDefault(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", strings.Join([]string{
		"pre_commit_workflows=leak-guard",
		"pre_commit_workflows=comment-hygiene",
		"",
		"pre_commit_workflows=", // blank — ignored, no phantom entry
	}, "\n")+"\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"leak-guard", "comment-hygiene"}
	if !reflect.DeepEqual(cfg.PreCommitWorkflows, want) {
		t.Fatalf("PreCommitWorkflows = %v, want %v", cfg.PreCommitWorkflows, want)
	}
}

func TestLoad_ConfigLocalPreCommitWorkflowsEmptyDisables(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "pre_commit_workflows=\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if len(cfg.PreCommitWorkflows) != 0 {
		t.Errorf("PreCommitWorkflows = %v, want empty (default disabled)", cfg.PreCommitWorkflows)
	}
}

func TestLoad_ConfigLocalPreCommitWorkflowsRejectsWhitespace(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "pre_commit_workflows=leak-guard version-sync\n")

	if _, err := Load(root, ""); err == nil {
		t.Fatal("expected error on whitespace-containing workflow name")
	}
}

func TestLoad_DefaultPostMergeWorkflows(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"memory-drift", "doc-drift", "manifest-refresh", "cockpit-sync"}
	if !reflect.DeepEqual(cfg.PostMergeWorkflows, want) {
		t.Errorf("PostMergeWorkflows default = %v, want %v", cfg.PostMergeWorkflows, want)
	}
}

func TestLoad_ConfigLocalPostMergeWorkflowsReplacesDefault(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", strings.Join([]string{
		"post_merge_workflows=memory-drift",
		"post_merge_workflows=", // blank — ignored, no phantom entry
	}, "\n")+"\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"memory-drift"}
	if !reflect.DeepEqual(cfg.PostMergeWorkflows, want) {
		t.Fatalf("PostMergeWorkflows = %v, want %v", cfg.PostMergeWorkflows, want)
	}
}

func TestLoad_ConfigLocalPostMergeWorkflowsEmptyDisables(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "post_merge_workflows=\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if len(cfg.PostMergeWorkflows) != 0 {
		t.Errorf("PostMergeWorkflows = %v, want empty (default disabled)", cfg.PostMergeWorkflows)
	}
}

func TestLoad_ConfigLocalPostMergeWorkflowsRejectsWhitespace(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "post_merge_workflows=memory-drift doc-drift\n")

	if _, err := Load(root, ""); err == nil {
		t.Fatal("expected error on whitespace-containing workflow name")
	}
}

func TestLoad_DefaultSessionFiles(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if len(cfg.SessionFiles) != 0 {
		t.Errorf("SessionFiles default = %v, want empty", cfg.SessionFiles)
	}
}

func TestLoad_ConfigLocalSessionFilesRepoRelative(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", strings.Join([]string{
		"session_files=CLAUDE.md",
		"session_files=AGENTS.md",
		"",
	}, "\n"))

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"CLAUDE.md", "AGENTS.md"}
	if !reflect.DeepEqual(cfg.SessionFiles, want) {
		t.Errorf("SessionFiles = %v, want %v", cfg.SessionFiles, want)
	}
}

func TestLoad_ConfigLocalSessionFilesAbsoluteAccepted(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	abs := filepath.Join(t.TempDir(), "cursor-rules.md")
	write(t, wsDir, "config.local", "session_files="+abs+"\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if len(cfg.SessionFiles) != 1 || cfg.SessionFiles[0] != abs {
		t.Errorf("SessionFiles = %v, want [%q]", cfg.SessionFiles, abs)
	}
}

func TestLoad_ConfigLocalSessionFilesMixed(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	abs := filepath.Join(t.TempDir(), "cursor-rules.md")
	write(t, wsDir, "config.local", strings.Join([]string{
		"session_files=CLAUDE.md",
		"session_files=" + abs,
		"",
	}, "\n"))

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	want := []string{"CLAUDE.md", abs}
	if !reflect.DeepEqual(cfg.SessionFiles, want) {
		t.Errorf("SessionFiles = %v, want %v", cfg.SessionFiles, want)
	}
}

func TestLoad_ConfigLocalSessionFilesRejected(t *testing.T) {
	cases := []string{
		"session_files=..\n",
		"session_files=../escape.md\n",
		"session_files=sub/../../escape.md\n",
		"session_files=has space.md\n",
	}
	for _, body := range cases {
		t.Run(body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

func TestWriteLocalKeys_UpsertPreservesAndRoundTrips(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", strings.Join([]string{
		"# keep me",
		"stale_days=7",
		"unknown_key=keep",
	}, "\n")+"\n")

	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if err := WriteLocalKeys(cfg, map[string]string{
		"stale_days": "9",        // replace in place
		"automation": "scaffold", // append
		"ai_budget":  "3",        // append
	}); err != nil {
		t.Fatal(err)
	}

	b, _ := os.ReadFile(filepath.Join(wsDir, "config.local"))
	got := string(b)
	if !strings.Contains(got, "# keep me") || !strings.Contains(got, "unknown_key=keep") {
		t.Errorf("comments / unknown keys not preserved:\n%s", got)
	}
	if strings.Contains(got, "stale_days=7") || !strings.Contains(got, "stale_days=9") {
		t.Errorf("stale_days not replaced in place:\n%s", got)
	}

	// The override must round-trip through Load.
	cfg2, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg2.StaleDays != 9 {
		t.Errorf("StaleDays = %d, want 9", cfg2.StaleDays)
	}
	if cfg2.Automation != AutomationScaffold {
		t.Errorf("Automation = %q, want scaffold", cfg2.Automation)
	}
	if cfg2.AIBudget != 3 {
		t.Errorf("AIBudget = %d, want 3", cfg2.AIBudget)
	}
}

func TestWriteLocalKeys_CreatesFileWhenMissing(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if err := WriteLocalKeys(cfg, map[string]string{"automation": "auto"}); err != nil {
		t.Fatal(err)
	}
	cfg2, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg2.Automation != AutomationAuto {
		t.Errorf("Automation = %q, want auto", cfg2.Automation)
	}
}

func TestWriteLocalKeys_RequiresInitialisedWorkspace(t *testing.T) {
	root := newRepo(t)
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if err := WriteLocalKeys(cfg, map[string]string{"automation": "auto"}); err == nil {
		t.Fatal("expected an error when the workspace is not initialised")
	}
}

// --- helpers ---

func newRepo(t *testing.T) string {
	t.Helper()
	root := t.TempDir()
	if err := os.Mkdir(filepath.Join(root, ".git"), 0o755); err != nil {
		t.Fatal(err)
	}
	return root
}

func write(t *testing.T, dir, name, content string) {
	t.Helper()
	full := filepath.Join(dir, name)
	if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
}

func TestLoad_DefaultWorkspaceHistory(t *testing.T) {
	root := newRepo(t)
	write(t, root, "go.mod", "module x\n")
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.WorkspaceHistory != DefaultWorkspaceHistory {
		t.Errorf("WorkspaceHistory = %q, want %q (default)", cfg.WorkspaceHistory, DefaultWorkspaceHistory)
	}
	if DefaultWorkspaceHistory != WorkspaceHistoryManual {
		t.Errorf("DefaultWorkspaceHistory = %q, want manual (safe-default floor)", DefaultWorkspaceHistory)
	}
}

func TestLoad_ConfigLocalWorkspaceHistory(t *testing.T) {
	cases := []struct {
		val  string
		want WorkspaceHistory
	}{
		{"off", WorkspaceHistoryOff},
		{"manual", WorkspaceHistoryManual},
		{"auto", WorkspaceHistoryAuto},
		{"", WorkspaceHistoryManual},         // empty resets to default
		{"nonsense", WorkspaceHistoryManual}, // unknown → default (floor)
	}
	for _, tc := range cases {
		t.Run(tc.val, func(t *testing.T) {
			root := newRepo(t)
			write(t, root, "go.mod", "module x\n")
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", "workspace_history="+tc.val+"\n")
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatalf("Load: %v", err)
			}
			if cfg.WorkspaceHistory != tc.want {
				t.Errorf("workspace_history=%q%q, want %q", tc.val, cfg.WorkspaceHistory, tc.want)
			}
		})
	}
}

func TestWorkspaceHistory_EnabledAuto(t *testing.T) {
	cases := []struct {
		h       WorkspaceHistory
		enabled bool
		auto    bool
	}{
		{WorkspaceHistoryOff, false, false},
		{WorkspaceHistoryManual, true, false},
		{WorkspaceHistoryAuto, true, true},
	}
	for _, tc := range cases {
		if got := tc.h.Enabled(); got != tc.enabled {
			t.Errorf("%q.Enabled() = %v, want %v", tc.h, got, tc.enabled)
		}
		if got := tc.h.Auto(); got != tc.auto {
			t.Errorf("%q.Auto() = %v, want %v", tc.h, got, tc.auto)
		}
	}
}

// --- H1.2: branch/edge coverage deepening (test-only) ---

// Group A — pure/exported functions (no fixtures, direct in-package calls).

// TestGateSteps covers GateSteps (config.go:586-591), which was 0%: the
// nil-chain non-nil-empty contract plus single/multi-step joining.
func TestGateSteps(t *testing.T) {
	t.Run("nil chain yields non-nil empty", func(t *testing.T) {
		got := GateSteps(nil)
		if got == nil {
			t.Fatal("GateSteps(nil) = nil, want non-nil empty slice")
		}
		if len(got) != 0 {
			t.Errorf("GateSteps(nil) = %v, want empty", got)
		}
	})
	cases := []struct {
		name string
		in   [][]string
		want []string
	}{
		{"single multi-arg step", [][]string{{"go", "vet", "./..."}}, []string{"go vet ./..."}},
		{"two steps", [][]string{{"go", "vet"}, {"staticcheck", "./..."}}, []string{"go vet", "staticcheck ./..."}},
		{"single word step", [][]string{{"make"}}, []string{"make"}},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := GateSteps(tc.in); !reflect.DeepEqual(got, tc.want) {
				t.Errorf("GateSteps(%v) = %v, want %v", tc.in, got, tc.want)
			}
		})
	}
}

// TestValidateWorkspaceName covers the empty (config.go:647-649) and
// not-clean (650-652) reject arms by calling the validator directly: Load
// maps "" to DefaultWorkspace before validating, so the empty arm is only
// reachable here.
func TestValidateWorkspaceName(t *testing.T) {
	cases := []struct {
		name    string
		wantErr bool
	}{
		{"", true},
		{"a//b", true},
		{"./x", true},
		{"/abs", true},
		{"a/b", true},
		{`a\b`, true},
		{".", true},
		{"..", true},
		{".eeco", false},
		{"workspace", false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if err := validateWorkspaceName(tc.name); (err != nil) != tc.wantErr {
				t.Errorf("validateWorkspaceName(%q) err = %v, wantErr = %v", tc.name, err, tc.wantErr)
			}
		})
	}
}

// TestSlugUsername covers the space->dash arm (config.go:551-552) and the
// drop/trim edges, including a result that trims to empty.
func TestSlugUsername(t *testing.T) {
	cases := []struct {
		in   string
		want string
	}{
		{"Jane Doe", "Jane-Doe"},
		{"  ada  ", "ada"},
		{"a@b!c", "abc"},
		{"...x...", "x"},
		{"日本語", ""},
		{".", ""},
		{"", ""},
	}
	for _, tc := range cases {
		t.Run(tc.in, func(t *testing.T) {
			if got := slugUsername(tc.in); got != tc.want {
				t.Errorf("slugUsername(%q) = %q, want %q", tc.in, got, tc.want)
			}
		})
	}
}

// Group B — per-key config.local edge tables (driven via Load). This is
// the "garbage in config.local -> documented default/error, never crash"
// exit, one function per typed key not already covered.

// TestLoad_ConfigLocalAICommand covers the ai_command split/empty arms
// (config.go:760-762).
func TestLoad_ConfigLocalAICommand(t *testing.T) {
	cases := []struct {
		name string
		body string
		want []string
	}{
		{"multi-arg", "ai_command=my tool --flag\n", []string{"my", "tool", "--flag"}},
		{"empty leaves nil", "ai_command=\n", nil},
		{"single", "ai_command=solo\n", []string{"solo"}},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatal(err)
			}
			if !reflect.DeepEqual(cfg.AICommand, tc.want) {
				t.Errorf("AICommand = %v, want %v", cfg.AICommand, tc.want)
			}
		})
	}
}

// TestLoad_ConfigLocalAIProvider covers the ai_provider passthrough
// (config.go:778), including the floor invariant that an unknown value is
// stored verbatim without error.
func TestLoad_ConfigLocalAIProvider(t *testing.T) {
	cases := []struct {
		name string
		body string
		want string
	}{
		{"cli", "ai_provider=cli\n", "cli"},
		{"anthropic", "ai_provider=anthropic\n", "anthropic"},
		{"empty", "ai_provider=\n", ""},
		{"unknown tolerated", "ai_provider=galaxy-brain\n", "galaxy-brain"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatalf("Load: %v", err)
			}
			if cfg.AIProvider != tc.want {
				t.Errorf("AIProvider = %q, want %q", cfg.AIProvider, tc.want)
			}
		})
	}
}

// TestLoad_ConfigLocalAIModel covers the ai_model passthrough
// (config.go:782): an opaque identifier is stored verbatim, empty resets.
func TestLoad_ConfigLocalAIModel(t *testing.T) {
	cases := []struct {
		name string
		body string
		want string
	}{
		{"identifier", "ai_model=claude-x\n", "claude-x"},
		{"empty", "ai_model=\n", ""},
		{"opaque chars", "ai_model=anything/with:chars\n", "anything/with:chars"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatalf("Load: %v", err)
			}
			if cfg.AIModel != tc.want {
				t.Errorf("AIModel = %q, want %q", cfg.AIModel, tc.want)
			}
		})
	}
}

// TestLoad_ConfigLocalAIAPIKeyEnv covers ai_api_key_env (config.go:786-790):
// a name is taken verbatim, empty falls back to the default env-var name.
func TestLoad_ConfigLocalAIAPIKeyEnv(t *testing.T) {
	cases := []struct {
		name string
		body string
		want string
	}{
		{"custom name", "ai_api_key_env=MY_VAR\n", "MY_VAR"},
		{"empty falls back to default", "ai_api_key_env=\n", DefaultAIAPIKeyEnv},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatalf("Load: %v", err)
			}
			if cfg.AIAPIKeyEnv != tc.want {
				t.Errorf("AIAPIKeyEnv = %q, want %q", cfg.AIAPIKeyEnv, tc.want)
			}
		})
	}
}

// TestLoad_ConfigLocalSessionStartDocs covers session_start_docs accept
// (config.go:809-810 empty-skip, 815 clean, 819 append) and reject
// (812 absolute, 816 escape) arms.
func TestLoad_ConfigLocalSessionStartDocs(t *testing.T) {
	t.Run("accepted with empty-skip and clean", func(t *testing.T) {
		root := newRepo(t)
		wsDir := filepath.Join(root, "tester", DefaultWorkspace)
		if err := os.MkdirAll(wsDir, 0o755); err != nil {
			t.Fatal(err)
		}
		write(t, wsDir, "config.local", strings.Join([]string{
			"session_start_docs=docs/a.md",
			"session_start_docs=b.md",
			"session_start_docs=", // empty value: skipped, no phantom entry
			"session_start_docs=sub/./c.md",
			"",
		}, "\n"))
		cfg, err := Load(root, "")
		if err != nil {
			t.Fatal(err)
		}
		want := []string{"docs/a.md", "b.md", "sub/c.md"}
		if !reflect.DeepEqual(cfg.SessionStartDocs, want) {
			t.Errorf("SessionStartDocs = %v, want %v", cfg.SessionStartDocs, want)
		}
	})
	for _, body := range []string{
		"session_start_docs=/abs/x.md\n",
		"session_start_docs=..\n",
		"session_start_docs=../escape.md\n",
		"session_start_docs=sub/../../escape.md\n",
	} {
		t.Run("rejected "+body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

// TestLoad_ConfigLocalSessionFilesEmptyAndSlash covers session_files
// empty-skip (config.go:828-829) and the backslash-prefix reject (837-839),
// which is reachable on unix because filepath.IsAbs(`\x`) is false there
// but the HasPrefix(`\`) guard still fires.
func TestLoad_ConfigLocalSessionFilesEmptyAndSlash(t *testing.T) {
	t.Run("empty value skipped", func(t *testing.T) {
		root := newRepo(t)
		wsDir := filepath.Join(root, "tester", DefaultWorkspace)
		if err := os.MkdirAll(wsDir, 0o755); err != nil {
			t.Fatal(err)
		}
		write(t, wsDir, "config.local", strings.Join([]string{
			"session_files=CLAUDE.md",
			"session_files=",
			"",
		}, "\n"))
		cfg, err := Load(root, "")
		if err != nil {
			t.Fatal(err)
		}
		if len(cfg.SessionFiles) != 1 || cfg.SessionFiles[0] != "CLAUDE.md" {
			t.Errorf("SessionFiles = %v, want [CLAUDE.md]", cfg.SessionFiles)
		}
	})
	t.Run("backslash-prefix rejected", func(t *testing.T) {
		root := newRepo(t)
		wsDir := filepath.Join(root, "tester", DefaultWorkspace)
		if err := os.MkdirAll(wsDir, 0o755); err != nil {
			t.Fatal(err)
		}
		write(t, wsDir, "config.local", `session_files=\x`+"\n")
		if _, err := Load(root, ""); err == nil {
			t.Fatal(`expected error for session_files=\x`)
		}
	})
}

// TestLoad_ConfigLocalSessionStartMailbox covers session_start_mailbox
// accept/empty (config.go:849-850 disable, 858 clean) and reject
// (851 absolute, 855 escape) arms.
func TestLoad_ConfigLocalSessionStartMailbox(t *testing.T) {
	accept := []struct {
		name string
		body string
		want string
	}{
		{"simple name", "session_start_mailbox=Inbox.md\n", "Inbox.md"},
		{"empty disables", "session_start_mailbox=\n", ""},
		{"clean relative", "session_start_mailbox=sub/./M.md\n", "sub/M.md"},
	}
	for _, tc := range accept {
		t.Run(tc.name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatal(err)
			}
			if cfg.SessionStartMailbox != tc.want {
				t.Errorf("SessionStartMailbox = %q, want %q", cfg.SessionStartMailbox, tc.want)
			}
		})
	}
	for _, body := range []string{
		"session_start_mailbox=/abs/M.md\n",
		"session_start_mailbox=..\n",
		"session_start_mailbox=../escape.md\n",
	} {
		t.Run("rejected "+body, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", body)
			if _, err := Load(root, ""); err == nil {
				t.Fatalf("expected error for %q", body)
			}
		})
	}
}

// TestLoad_ConfigLocalSessionStartRoadmapGlob covers the
// session_start_roadmap_glob passthrough (config.go:865): the glob is
// stored verbatim, empty disables discovery.
func TestLoad_ConfigLocalSessionStartRoadmapGlob(t *testing.T) {
	cases := []struct {
		name string
		body string
		want string
	}{
		{"glob verbatim", "session_start_roadmap_glob=plan*.md\n", "plan*.md"},
		{"empty disables", "session_start_roadmap_glob=\n", ""},
		{"other glob verbatim", "session_start_roadmap_glob=ROADMAP-*.md\n", "ROADMAP-*.md"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := newRepo(t)
			wsDir := filepath.Join(root, "tester", DefaultWorkspace)
			if err := os.MkdirAll(wsDir, 0o755); err != nil {
				t.Fatal(err)
			}
			write(t, wsDir, "config.local", tc.body)
			cfg, err := Load(root, "")
			if err != nil {
				t.Fatalf("Load: %v", err)
			}
			if cfg.SessionStartRoadmapGlob != tc.want {
				t.Errorf("SessionStartRoadmapGlob = %q, want %q", cfg.SessionStartRoadmapGlob, tc.want)
			}
		})
	}
}

// TestLoad_ConfigLocalInitDetectionThresholdEmpty covers the empty-value
// arm of init_detection_threshold (config.go:1061-1063); the 0.85 and
// malformed cases are covered elsewhere.
func TestLoad_ConfigLocalInitDetectionThresholdEmpty(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, "config.local", "init_detection_threshold=\n")
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if cfg.InitDetectionThreshold != 0 {
		t.Errorf("InitDetectionThreshold = %v, want 0", cfg.InitDetectionThreshold)
	}
}

// Group C — nil guards + IsInitialized depth (local.go arms; the init.go
// nil guards live in init_test.go).

// TestWriteLocalKeys_NilConfig covers local.go:25-27.
func TestWriteLocalKeys_NilConfig(t *testing.T) {
	if err := WriteLocalKeys(nil, map[string]string{"x": "y"}); err == nil {
		t.Fatal("WriteLocalKeys(nil, ...) = nil, want error")
	}
}

// TestWriteLocalKeys_EmptyMap covers local.go:28-30: an empty map is a
// no-op that returns nil and creates no config.local.
func TestWriteLocalKeys_EmptyMap(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	cfg, err := Load(root, "")
	if err != nil {
		t.Fatal(err)
	}
	if err := WriteLocalKeys(cfg, nil); err != nil {
		t.Fatalf("WriteLocalKeys(cfg, nil) = %v, want nil", err)
	}
	if _, err := os.Stat(filepath.Join(wsDir, LocalFilename)); !os.IsNotExist(err) {
		t.Errorf("config.local stat err = %v, want IsNotExist", err)
	}
}

// Group D — filesystem I/O error branches, NO seam (dir-/file-in-the-way).
// Assertions target the package's own wrap text, never the OS errno, so
// they are portable (EISDIR/ENOTDIR are both non-os.ErrNotExist).

// TestApplyLocal_ErrorsWhenConfigLocalIsADirectory covers the applyLocal
// ReadFile non-NotExist arm (config.go:687-688) and the Load read wrap
// (638-639): the workspace stat succeeds (IsDir true), then ReadFile on a
// directory fails.
func TestApplyLocal_ErrorsWhenConfigLocalIsADirectory(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.Mkdir(filepath.Join(wsDir, "config.local"), 0o755); err != nil {
		t.Fatal(err)
	}
	_, err := Load(root, "")
	if err == nil {
		t.Fatal("expected Load to error when config.local is a directory")
	}
	if !strings.Contains(err.Error(), "read config.local") {
		t.Errorf("error = %q, want it to contain %q", err.Error(), "read config.local")
	}
}

// TestWriteLocalKeys_ErrorsWhenConfigLocalIsADirectory covers
// local.go:38-40 (ReadFile non-NotExist). WriteLocalKeys reads only
// cfg.Workspace, so a minimal hand-built Config suffices.
func TestWriteLocalKeys_ErrorsWhenConfigLocalIsADirectory(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.Mkdir(filepath.Join(wsDir, LocalFilename), 0o755); err != nil {
		t.Fatal(err)
	}
	cfg := &Config{Workspace: wsDir}
	err := WriteLocalKeys(cfg, map[string]string{"automation": "auto"})
	if err == nil {
		t.Fatal("expected WriteLocalKeys to error when config.local is a directory")
	}
	if !strings.Contains(err.Error(), "read config.local") {
		t.Errorf("error = %q, want it to contain %q", err.Error(), "read config.local")
	}
}

// TestWriteLocalKeys_PreservesRawNonKVLine covers the no-'=' raw
// passthrough in WriteLocalKeys (local.go:52-54): a malformed line and a
// comment survive an upsert untouched.
func TestWriteLocalKeys_PreservesRawNonKVLine(t *testing.T) {
	root := newRepo(t)
	wsDir := filepath.Join(root, "tester", DefaultWorkspace)
	if err := os.MkdirAll(wsDir, 0o755); err != nil {
		t.Fatal(err)
	}
	write(t, wsDir, LocalFilename, strings.Join([]string{
		"# a comment",
		"raw-line-without-equals",
		"stale_days=7",
	}, "\n")+"\n")
	// WriteLocalKeys reads only cfg.Workspace and parses config.local
	// itself; a hand-built Config avoids Load rejecting the malformed line.
	cfg := &Config{Workspace: wsDir}
	if err := WriteLocalKeys(cfg, map[string]string{"automation": "manual"}); err != nil {
		t.Fatal(err)
	}
	b, err := os.ReadFile(filepath.Join(wsDir, LocalFilename))
	if err != nil {
		t.Fatal(err)
	}
	got := string(b)
	if !strings.Contains(got, "raw-line-without-equals") {
		t.Errorf("raw non-kv line not preserved:\n%s", got)
	}
	if !strings.Contains(got, "# a comment") {
		t.Errorf("comment not preserved:\n%s", got)
	}
}

// Group E — resolveUsername fallback + empty-username Init (the Init half
// lives in init_test.go).

// TestResolveUsername_FallbackWhenNoIdentity covers the final fallback
// return (config.go:536). Every identity source is nulled: EECO_USERNAME
// slugs to empty, USER/USERNAME are empty, and GIT_CONFIG_GLOBAL/SYSTEM
// point at nonexistent files so the host's own git user.name cannot leak
// (mirrors the H1.1 gitx isolation). The bare-.git fixture makes
// gitx.UserName return ("", nil).
func TestResolveUsername_FallbackWhenNoIdentity(t *testing.T) {
	t.Setenv(UsernameEnv, "!!!") // slugs to "" -> candidate skipped (overrides TestMain "tester")
	t.Setenv("USER", "")
	t.Setenv("USERNAME", "")
	t.Setenv("GIT_CONFIG_GLOBAL", filepath.Join(t.TempDir(), "nope-global"))
	t.Setenv("GIT_CONFIG_SYSTEM", filepath.Join(t.TempDir(), "nope-system"))
	root := newRepo(t)
	if got := resolveUsername(root); got != FallbackUsername {
		t.Errorf("resolveUsername = %q, want %q", got, FallbackUsername)
	}
}