|
1 | 1 | package commands |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "context" |
5 | 6 | "fmt" |
| 7 | + "io" |
| 8 | + "os" |
6 | 9 | "os/exec" |
7 | 10 | "strconv" |
8 | 11 | ) |
@@ -56,32 +59,80 @@ func cmdAutofix(ctx Context) Result { |
56 | 59 | testCmd, testArgs := detectTestCmd(ctx.RepoPath) |
57 | 60 |
|
58 | 61 | for attempt := 1; attempt <= maxAttempts; attempt++ { |
59 | | - fmt.Printf("%s[attempt %d/%d]%s running %s %v\n", |
60 | | - ColorDim, attempt, maxAttempts, ColorReset, testCmd, testArgs) |
| 62 | + fmt.Printf("%s[attempt %d/%d]%s %s %s\n", |
| 63 | + ColorDim, attempt, maxAttempts, ColorReset, testCmd, joinArgs(testArgs)) |
61 | 64 |
|
| 65 | + // Run with live output — capture into buf AND stream to terminal simultaneously. |
| 66 | + var buf bytes.Buffer |
62 | 67 | cmd := exec.Command(testCmd, testArgs...) |
63 | 68 | cmd.Dir = ctx.RepoPath |
64 | | - output, err := cmd.CombinedOutput() |
| 69 | + cmd.Stdout = io.MultiWriter(os.Stdout, &buf) |
| 70 | + cmd.Stderr = io.MultiWriter(os.Stderr, &buf) |
| 71 | + |
| 72 | + err := cmd.Run() |
65 | 73 |
|
66 | 74 | if err == nil { |
67 | | - PrintSuccess("tests passed on attempt %d/%d", attempt, maxAttempts) |
| 75 | + PrintSuccess("all tests pass (%d/%d)", attempt, maxAttempts) |
68 | 76 | return Result{Handled: true} |
69 | 77 | } |
70 | 78 |
|
71 | | - // Tests failed — print failure output |
72 | | - fmt.Printf("%s── Test failures ───────────────────%s\n", ColorDim, ColorReset) |
73 | | - fmt.Println(string(output)) |
74 | | - fmt.Printf("%s──────────────────────────────────%s\n\n", ColorDim, ColorReset) |
75 | | - |
| 79 | + fmt.Println() // blank line after test output |
76 | 80 | if attempt == maxAttempts { |
77 | 81 | break |
78 | 82 | } |
79 | 83 |
|
80 | | - // Ask agent to fix |
81 | | - prompt := fmt.Sprintf("The following tests are failing. Fix them:\n\n%s", string(output)) |
| 84 | + // Truncate failure output sent to agent (keep last 200 lines — most relevant). |
| 85 | + failureOutput := tailLines(buf.String(), 200) |
| 86 | + prompt := fmt.Sprintf( |
| 87 | + "Tests failed (attempt %d/%d). Fix the failures shown below, then ensure all tests pass.\n\n```\n%s\n```", |
| 88 | + attempt, maxAttempts, failureOutput) |
82 | 89 | ctx.REPL.StreamAndPrint(context.Background(), ctx.Agent, prompt, ctx.RepoPath) |
83 | 90 | } |
84 | 91 |
|
85 | 92 | fmt.Printf("%sautofix gave up after %d attempts%s\n\n", ColorRed, maxAttempts, ColorReset) |
86 | 93 | return Result{Handled: true} |
87 | 94 | } |
| 95 | + |
| 96 | +func joinArgs(args []string) string { |
| 97 | + result := "" |
| 98 | + for i, a := range args { |
| 99 | + if i > 0 { |
| 100 | + result += " " |
| 101 | + } |
| 102 | + result += a |
| 103 | + } |
| 104 | + return result |
| 105 | +} |
| 106 | + |
| 107 | +// tailLines returns the last n lines of s. |
| 108 | +func tailLines(s string, n int) string { |
| 109 | + lines := splitLines(s) |
| 110 | + if len(lines) <= n { |
| 111 | + return s |
| 112 | + } |
| 113 | + dropped := len(lines) - n |
| 114 | + return fmt.Sprintf("[... %d lines omitted ...]\n", dropped) + joinLines(lines[dropped:]) |
| 115 | +} |
| 116 | + |
| 117 | +func splitLines(s string) []string { |
| 118 | + var lines []string |
| 119 | + start := 0 |
| 120 | + for i := 0; i < len(s); i++ { |
| 121 | + if s[i] == '\n' { |
| 122 | + lines = append(lines, s[start:i+1]) |
| 123 | + start = i + 1 |
| 124 | + } |
| 125 | + } |
| 126 | + if start < len(s) { |
| 127 | + lines = append(lines, s[start:]) |
| 128 | + } |
| 129 | + return lines |
| 130 | +} |
| 131 | + |
| 132 | +func joinLines(lines []string) string { |
| 133 | + var b bytes.Buffer |
| 134 | + for _, l := range lines { |
| 135 | + b.WriteString(l) |
| 136 | + } |
| 137 | + return b.String() |
| 138 | +} |
0 commit comments