Skip to content

Commit 74ce8f5

Browse files
Patel230claude
andcommitted
fix: robust end-to-end evolution pipeline — 10 bug fixes
- evolve.sh: replace dangerous git commit --amend with safe separate commit for DAY_COUNT - evolve.sh: fallback merge now detects conflicts and aborts instead of swallowing with || true - phases_communicate.go: journal timeout uses parent ctx instead of context.Background() - git.go: remove --admin flag that bypassed branch protection on conflict - git.go: quote branch name in push command to prevent shell injection - engine.go: log error from handleCommitAndPR instead of silently swallowing it - engine.go: fix wrong buildUserMessage() passed as learning context (use empty string) - phases.go: log verifyProtected errors instead of discarding with _ - phases.go: add conventional commit format guidance to agent prompt (fixes "test message") - analysis.go: fix string(rune('0'+count)) → strconv.Itoa(count) (broken for counts > 9) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b10d000 commit 74ce8f5

6 files changed

Lines changed: 20 additions & 82 deletions

File tree

internal/evolution/analysis.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"os/exec"
55
"path/filepath"
66
"sort"
7+
"strconv"
78
"strings"
89
)
910

@@ -144,7 +145,7 @@ func findHotspots(repoPath string) []string {
144145
limit = len(entries)
145146
}
146147
for _, e := range entries[:limit] {
147-
results = append(results, e.name+" (changed "+string(rune('0'+e.count))+"x)")
148+
results = append(results, e.name+" (changed "+strconv.Itoa(e.count)+"x)")
148149
}
149150
return results
150151
}

internal/evolution/engine.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ func (e *Engine) handlePostRunTests(ctx context.Context, day int, output string,
192192

193193
e.logger.Info("tests passed, creating feature branch")
194194
if err := e.handleCommitAndPR(ctx, day, output, p, result); err != nil {
195+
e.logger.Warn("commit/PR flow failed, continuing", "err", err)
195196
return nil
196197
}
197198

@@ -365,7 +366,7 @@ func (e *Engine) handlePRReviewAndMerge(ctx context.Context, p iteragent.Provide
365366
result.Status = "merged"
366367
result.PRNumber = e.prNumber
367368
result.PRURL = e.prURL
368-
_ = e.appendLearningJSONL(firstLine(extractCommitMessage(output)), "evolution", buildUserMessage(e.repoPath, "", ""), "")
369+
_ = e.appendLearningJSONL(firstLine(extractCommitMessage(output)), "evolution", "", "")
369370
e.appendJournal(result, output, p.Name(), true)
370371

371372
_ = e.switchToMain(ctx)

internal/evolution/git.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func (e *Engine) createFeatureBranch(ctx context.Context, day int) (string, erro
9494

9595
func (e *Engine) pushBranch(ctx context.Context) error {
9696
_, err := e.runTool(ctx, "bash", map[string]string{
97-
"cmd": fmt.Sprintf("git push -u origin %s", e.branchName),
97+
"cmd": fmt.Sprintf("git push -u origin %q", e.branchName),
9898
})
9999
return err
100100
}
@@ -205,16 +205,6 @@ func (e *Engine) mergePR(ctx context.Context) error {
205205
"cmd": fmt.Sprintf("gh pr merge %d --repo %s --squash --delete-branch", e.prNumber, e.repo),
206206
})
207207
if err != nil {
208-
if strings.Contains(strings.ToLower(out), "no mergeable") || strings.Contains(strings.ToLower(out), "conflict") {
209-
e.logger.Warn("PR has merge conflicts, attempting merge with --admin flag (bypasses branch protection)", "pr", e.prNumber)
210-
mergeOut, mergeErr := e.runTool(ctx, "bash", map[string]string{
211-
"cmd": fmt.Sprintf("gh pr merge %d --repo %s --squash --admin --delete-branch 2>&1 || echo 'MERGE_FAILED'", e.prNumber, e.repo),
212-
})
213-
if mergeErr != nil || strings.Contains(mergeOut, "MERGE_FAILED") {
214-
e.logger.Warn("PR merge failed, will retry next session", "output", mergeOut)
215-
return fmt.Errorf("merge conflict: %s", mergeOut)
216-
}
217-
}
218208
return fmt.Errorf("PR merge failed: %w, output: %s", err, out)
219209
}
220210

internal/evolution/phases.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (e *Engine) runTaskAttempt(ctx context.Context, p iteragent.Provider, task
198198
if extraContext != "" {
199199
userMsg += "\n\n" + extraContext
200200
}
201-
userMsg += "\n\nAfter implementing, run: go build ./... && go test ./...\nThen commit your changes."
201+
userMsg += "\n\nAfter implementing, run: go build ./... && go test ./...\nThen commit your changes using a conventional commit message (e.g. feat: ..., fix: ..., chore: ..., refactor: ..., test: ..., docs: ...)."
202202

203203
a := e.newAgent(p, tools, systemPrompt, skills)
204204
var taskOutput string
@@ -219,7 +219,9 @@ func (e *Engine) runTaskAttempt(ctx context.Context, p iteragent.Provider, task
219219
return false, fmt.Sprintf("Agent error: %s", taskErr)
220220
}
221221

222-
if violations, _ := e.verifyProtected(ctx); len(violations) > 0 {
222+
if violations, err := e.verifyProtected(ctx); err != nil {
223+
e.logger.Warn("verifyProtected check failed", "err", err)
224+
} else if len(violations) > 0 {
223225
e.logger.Warn("protected files modified, reverting", "number", task.Number, "files", violations)
224226
_ = e.revert(ctx)
225227
return false, fmt.Sprintf("Protected files were modified (not allowed): %v", violations)

internal/evolution/phases_communicate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (e *Engine) readDayCount() string {
6161

6262
// writeJournalEntry always produces a journal entry — via agent or fallback.
6363
func (e *Engine) writeJournalEntry(ctx context.Context, p iteragent.Provider, day string) {
64-
journalCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
64+
journalCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
6565
defer cancel()
6666

6767
recentCommits, _ := e.runTool(journalCtx, "bash", map[string]string{"cmd": "git log --oneline -8"})

scripts/evolution/evolve.sh

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -176,79 +176,18 @@ log "Generating stats..."
176176
python3 scripts/build/generate_stats.py . 2>/dev/null || true
177177
git add docs/stats.json memory/weekly_summary.md 2>/dev/null || true
178178

179-
<<<<<<< Updated upstream
180179
# ── Final commit and PR ──
181180
log "Creating pull request..."
182181

183182
BRANCH="evolution/day-${DAY}"
184-
=======
185-
# ── Final commit and push ──
186-
log "Pushing changes..."
187-
<<<<<<< Updated upstream
188-
<<<<<<< Updated upstream
189-
>>>>>>> Stashed changes
190-
=======
191-
>>>>>>> Stashed changes
192-
=======
193-
>>>>>>> Stashed changes
194-
195-
# Re-calculate day after pull (pull may overwrite DAY_COUNT)
196-
DAY=$(( ($(date -u +%s) - $(date -d "$BIRTH_DATE" +%s 2>/dev/null || date -j -f "%Y-%m-%d" "$BIRTH_DATE" +%s)) / 86400 ))
197-
echo "$DAY" > "${REPOPATH}/DAY_COUNT"
198-
199-
<<<<<<< Updated upstream
200-
<<<<<<< Updated upstream
201-
<<<<<<< Updated upstream
202-
# Stage and commit all changes
203-
=======
204-
>>>>>>> Stashed changes
205-
=======
206-
>>>>>>> Stashed changes
207-
=======
208-
>>>>>>> Stashed changes
209-
if [[ -n $(git status -s) ]]; then
210-
git add -A
211-
git commit -m "iterate: Day $DAY evolution session" 2>/dev/null || true
212-
fi
213-
<<<<<<< Updated upstream
214-
=======
215-
git pull --rebase origin main 2>/dev/null || true
216-
<<<<<<< Updated upstream
217-
<<<<<<< Updated upstream
218-
=======
219-
=======
220-
221-
# Always ensure DAY_COUNT is correct after pull
222-
echo "$DAY" > "${REPOPATH}/DAY_COUNT"
223-
git add DAY_COUNT 2>/dev/null || true
224-
git commit --amend --no-edit 2>/dev/null || git commit -m "iterate: Day $DAY evolution session" 2>/dev/null || true
225-
226-
git push origin main 2>/dev/null || log "Push failed"
227-
>>>>>>> Stashed changes
228-
229-
# Always ensure DAY_COUNT is correct after pull
230-
echo "$DAY" > "${REPOPATH}/DAY_COUNT"
231-
git add DAY_COUNT 2>/dev/null || true
232-
git commit --amend --no-edit 2>/dev/null || git commit -m "iterate: Day $DAY evolution session" 2>/dev/null || true
233-
234-
git push origin main 2>/dev/null || log "Push failed"
235-
>>>>>>> Stashed changes
236-
237-
# Always ensure DAY_COUNT is correct after pull
238-
echo "$DAY" > "${REPOPATH}/DAY_COUNT"
239-
git add DAY_COUNT 2>/dev/null || true
240-
git commit --amend --no-edit 2>/dev/null || git commit -m "iterate: Day $DAY evolution session" 2>/dev/null || true
241-
242-
git push origin main 2>/dev/null || log "Push failed"
243-
>>>>>>> Stashed changes
244183

245184
# Pull latest main
246185
git pull --rebase origin main 2>/dev/null || true
247186

248187
# Ensure DAY_COUNT is correct after pull
249188
echo "$DAY" > "${REPOPATH}/DAY_COUNT"
250189
git add DAY_COUNT 2>/dev/null || true
251-
git diff --cached --quiet || git commit --amend --no-edit 2>/dev/null || true
190+
git diff --cached --quiet || git commit -m "chore: update DAY_COUNT to day $DAY" 2>/dev/null || true
252191

253192
# Check if there are changes to push
254193
if [[ -z $(git diff origin/main HEAD --stat 2>/dev/null) ]]; then
@@ -317,10 +256,15 @@ else
317256
log "Falling back to direct push to main..."
318257
git checkout main 2>/dev/null || true
319258
git pull --rebase origin main 2>/dev/null || true
320-
git merge "$BRANCH" --no-edit 2>/dev/null || true
321-
git push origin main 2>/dev/null || log "Direct push also failed"
322-
git push origin --delete "$BRANCH" 2>/dev/null || true
323-
PR_NUMBER=""
259+
if ! git merge "$BRANCH" --no-edit 2>/dev/null; then
260+
log "ERROR: Merge conflict during fallback — aborting merge, changes remain on $BRANCH"
261+
git merge --abort 2>/dev/null || true
262+
PR_NUMBER=""
263+
else
264+
git push origin main 2>/dev/null || log "Direct push also failed"
265+
git push origin --delete "$BRANCH" 2>/dev/null || true
266+
PR_NUMBER=""
267+
fi
324268
fi
325269

326270
# Get PR number

0 commit comments

Comments
 (0)