c36aa18c67
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>
128 lines
3.3 KiB
Go
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)
|
|
}
|