Let the agent set the merge commit title, defaulting to the task title

This commit is contained in:
Ian Gulliver
2026-10-14 21:21:54 -07:00
parent 73f6dd73d2
commit 01c60d6618
24 changed files with 373 additions and 202 deletions
+63 -5
View File
@@ -2,17 +2,26 @@ package agents
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"strings"
"unicode/utf8"
"g.fc.run/misc/byre/internal/api"
"g.fc.run/misc/byre/internal/git"
"g.fc.run/misc/byre/internal/mcp"
"g.fc.run/misc/byre/internal/store"
)
func mergeInPrompt(target string) string {
const maxMergeTitle = 72
func mergeInPrompt(target, mergeTitle string) string {
return fmt.Sprintf("Merge %[1]s into this branch: run `git merge --no-ff --no-commit %[1]s`. "+
"Resolve any conflicts keeping both sides' intent; regenerate generated files instead of hand-merging them. "+
"Check that the build and tests pass. Leave the merge uncommitted: byre concludes it when the turn ends.", target)
"Check that the build and tests pass. Leave the merge uncommitted: byre concludes it when the turn ends. "+
"Finally, if the merge commit title (currently \"%[2]s\") no longer describes all the work on this branch, set a better one with set_merge_title.", target, mergeTitle)
}
func (m *Manager) MergeIn(ctx context.Context, task api.Task, user api.User, target string) (api.AgentSession, error) {
@@ -21,13 +30,14 @@ func (m *Manager) MergeIn(ctx context.Context, task api.Task, user api.User, tar
return api.AgentSession{}, err
}
prompt := mergeInPrompt(target, task.MergeTitle)
for _, sess := range sessions {
if sess.State != api.AgentSessionStateCompleted {
return m.Send(ctx, sess.Id, "byre", mergeInPrompt(target), "")
return m.Send(ctx, sess.Id, "byre", prompt, "")
}
}
return m.create(ctx, task, user, mergeInPrompt(target), "", "byre", nil)
return m.create(ctx, task, user, prompt, "", "byre", nil)
}
func (m *Manager) Merge(ctx context.Context, task api.Task, project api.Project, user api.User) (api.AgentSession, error) {
@@ -85,7 +95,7 @@ func (m *Manager) squashAndClose(ctx context.Context, taskID, login string) erro
return err
}
commit, err := git.SquashMerge(project.RepoPath, task.WorktreePath, project.DefaultBranch, task.Title, []string{
commit, err := git.SquashMerge(project.RepoPath, task.WorktreePath, project.DefaultBranch, task.MergeTitle, []string{
"GIT_AUTHOR_NAME=" + user.GitName,
"GIT_AUTHOR_EMAIL=" + user.GitEmail,
"GIT_COMMITTER_NAME=" + user.GitName,
@@ -111,3 +121,51 @@ func (m *Manager) RemoveTask(ctx context.Context, task api.Task, project api.Pro
}
return git.RemoveWorktree(project.RepoPath, task.WorktreePath, task.Branch)
}
type setMergeTitleArgs struct {
Title string `json:"title"`
}
func (m *Manager) setMergeTitleTool(l *live) mcp.Tool {
return mcp.Tool{
Name: "set_merge_title",
Description: "Set the title of the single commit this task becomes when it is merged onto the default branch. " +
"It starts as the task title. Write one line in the imperative mood that describes all the work on the branch, " +
"for example \"Add retry with backoff to webhook delivery\". It replaces any earlier title.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"title": {"type": "string", "description": "One line, at most 72 characters."}
},
"required": ["title"]
}`),
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
return m.setMergeTitle(ctx, l, raw)
},
}
}
func (m *Manager) setMergeTitle(ctx context.Context, l *live, raw json.RawMessage) (string, error) {
args := setMergeTitleArgs{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", fmt.Errorf("malformed set_merge_title call: %w", err)
}
title := strings.TrimSpace(args.Title)
if title == "" || strings.ContainsAny(title, "\r\n") {
return "", errors.New("title must be one non-empty line")
}
if utf8.RuneCountInString(title) > maxMergeTitle {
return "", fmt.Errorf("title is %d characters; keep it to %d", utf8.RuneCountInString(title), maxMergeTitle)
}
err = m.store.SetMergeTitle(ctx, l.taskID, title, agentActor)
if errors.Is(err, store.ErrNotFound) {
return "", errors.New("this task is no longer active")
}
if err != nil {
return "", err
}
return fmt.Sprintf("Merge commit title set to %q.", title), nil
}