Files
repo_Claude/utils/parallel-executor/shell/executor.go
T
egutierrez c36aa18c67 feat: añadir skills create-tui, init-frontend, init-go-module y utilidades
Nuevas skills para crear TUIs, inicializar frontends React y módulos Go.
Incluye binario parallel-executor y utilidades de soporte.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:15:34 +01:00

128 lines
3.3 KiB
Go

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)
}