package shell import ( "fmt" "strings" "time" "github.com/lucasdataproyects/devfactory/core" dfshell "github.com/lucasdataproyects/devfactory/shell" pcore "github.com/lucasdataproyects/parallel-executor/core" ) const defaultTimeout = 30 * time.Minute // ExecuteIssue ejecuta claude para resolver una issue en un worktree. // Invoca `claude -p` con el prompt de fix-issue dentro del worktree. func ExecuteIssue(spec pcore.WorktreeSpec, timeout time.Duration) pcore.ExecutionResult { start := time.Now() if timeout == 0 { timeout = defaultTimeout } prompt := fmt.Sprintf( "Read the issue file dev/issues/%04d-*.md and implement all tasks. "+ "Run tests after each change. Follow pure core / impure shell pattern. "+ "When done, commit all changes with a descriptive message.", spec.Issue.Number, ) result := dfshell.RunWithTimeout("claude", timeout, "-p", prompt, "--cwd", spec.WorkDir, ) duration := time.Since(start).Round(time.Second).String() if result.IsErr() { return pcore.ExecutionResult{ Issue: spec.Issue, Success: false, Duration: duration, Error: result.Error().Error(), } } cmdResult := result.Unwrap() if !cmdResult.Success() { return pcore.ExecutionResult{ Issue: spec.Issue, Success: false, Duration: duration, Error: cmdResult.Stderr, } } return pcore.ExecutionResult{ Issue: spec.Issue, Success: true, Duration: duration, } } // PushWorktreeBranch hace push de la branch del worktree al remote. func PushWorktreeBranch(spec pcore.WorktreeSpec) core.Result[struct{}] { result := dfshell.Run("git", "-C", spec.WorkDir, "push", "-u", "origin", spec.BranchName, ) if result.IsErr() { return core.Err[struct{}](fmt.Errorf("push failed for %s: %w", spec.BranchName, result.Error())) } return core.Ok(struct{}{}) } // MergeBranchToMaster mergea una branch a master con --no-ff. func MergeBranchToMaster(branchName string, repoRoot string) core.Result[struct{}] { // Checkout master result := dfshell.Run("git", "-C", repoRoot, "checkout", "master") if result.IsErr() { return core.Err[struct{}](result.Error()) } // Merge --no-ff message := fmt.Sprintf("merge: %s — parallel execution", branchName) result = dfshell.Run("git", "-C", repoRoot, "merge", "--no-ff", "-m", message, branchName, ) if result.IsErr() { return core.Err[struct{}](fmt.Errorf("merge failed for %s: %w", branchName, result.Error())) } return core.Ok(struct{}{}) } // DeleteBranch elimina una branch local. func DeleteBranch(branchName string, repoRoot string) core.Result[struct{}] { result := dfshell.Run("git", "-C", repoRoot, "branch", "-d", branchName) if result.IsErr() { return core.Err[struct{}](result.Error()) } return core.Ok(struct{}{}) } // ReadIssueFiles lee todos los archivos de issues de un directorio. func ReadIssueFiles(issuesDir string) core.Result[map[string]string] { entries := dfshell.ListDir(issuesDir) if entries.IsErr() { return core.Err[map[string]string](entries.Error()) } files := make(map[string]string) for _, entry := range entries.Unwrap() { if !strings.HasSuffix(entry, ".md") || entry == "README.md" { continue } content := dfshell.ReadString(issuesDir + "/" + entry) if content.IsOk() { files[entry] = content.Unwrap() } } return core.Ok(files) }