diff --git a/internal/app/app.go b/internal/app/app.go index 94b1dc3..0b5c72c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -58,6 +58,15 @@ func New(configPath, version, updateToken string) (*App, error) { // Относительные пути (db, worktree) — рядом с .exe, а не от CWD запуска. cfg.ResolveExePaths() + // Каталог worktree может отсутствовать на свежей машине. Decider (analyst) и + // воркер запускают opencode с cwd=worktree, а git clone не создаёт родительский + // каталог — поэтому создаём его заранее, до любых запусков субагентов. + if cfg.Paths.Worktree != "" { + if err := os.MkdirAll(cfg.Paths.Worktree, 0o755); err != nil { + return nil, fmt.Errorf("app: создать каталог worktree %s: %w", cfg.Paths.Worktree, err) + } + } + // SQLite для задач и трасс ctx := context.Background() diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 2d7a8ba..cf7ba90 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -108,6 +108,43 @@ func TestMain(t *testing.T) { t.Log("main.go exists") } +// TestNew_WorktreeCreated проверяет, что New создаёт каталог worktree (decider и +// воркер запускают opencode с cwd=worktree — каталог должен существовать заранее). +func TestNew_WorktreeCreated(t *testing.T) { + tmp := t.TempDir() + wt := filepath.Join(tmp, "nested", "worktrees") + configPath := filepath.Join(tmp, "config.yaml") + content := strings.Join([]string{ + "telegram:", + " token: \"test:token\"", + " chat_id: \"12345\"", + "paths:", + " db: \"" + filepath.Join(tmp, "test.db") + "\"", + " worktree: \"" + wt + "\"", + "", + }, "\n") + if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + + a, err := New(configPath, "dev", "") + if err != nil { + t.Fatalf("New() err = %v", err) + } + defer a.Store.Close() + st, err := os.Stat(wt) + if err != nil { + t.Fatalf("worktree должен существовать после New: %v", err) + } + if !st.IsDir() { + t.Fatalf("worktree не директория: %v", st.Mode()) + } + if a.Worker.Worktree != wt { + t.Errorf("Worker.Worktree = %q, want %q", a.Worker.Worktree, wt) + } + t.Logf("worktree создан: %s", wt) +} + // TestNew_UpdateWiring проверяет: 1) update.base_url берётся из update-блока // (не git.base_url); 2) приоритет токена: вшитый updateToken > update.token. func TestNew_UpdateWiring(t *testing.T) {