Let the agent set the merge commit title, defaulting to the task title
This commit is contained in:
@@ -554,7 +554,7 @@ func (m *Manager) ensureLive(ctx context.Context, sess api.AgentSession, task ap
|
||||
GitName: creator.GitName,
|
||||
GitEmail: creator.GitEmail,
|
||||
Guidance: guidance,
|
||||
MCPServers: []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: []mcp.Tool{m.askTool(l), m.createTaskTool(l)}}},
|
||||
MCPServers: []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: []mcp.Tool{m.askTool(l), m.createTaskTool(l), m.setMergeTitleTool(l)}}},
|
||||
Hooks: map[string][]claude.HookMatcher{
|
||||
"UserPromptSubmit": {{Func: remindGuidance}},
|
||||
"Stop": {{Func: m.bounceProseQuestion(l)}},
|
||||
|
||||
@@ -612,9 +612,10 @@ func TestMergeResolvesConflict(t *testing.T) {
|
||||
if parent := gitOut(t, e.repo, "rev-parse", "main^"); parent != moved {
|
||||
t.Fatalf("merge commit parent %s, want %s", parent, moved)
|
||||
}
|
||||
if log := gitOut(t, e.repo, "log", "-1", "--format=%s|%an|%cn"); log != task.Title+"|"+gitName+"|"+gitName {
|
||||
t.Fatalf("unexpected merge commit %s", log)
|
||||
if log := gitOut(t, e.repo, "log", "-1", "--format=%s|%b|%an|%cn"); log != task.MergeTitle+"||"+gitName+"|"+gitName {
|
||||
t.Fatalf("unexpected merge commit %s, want title %q", log, task.MergeTitle)
|
||||
}
|
||||
t.Logf("merge commit title: %s", task.MergeTitle)
|
||||
notes, err := os.ReadFile(filepath.Join(e.repo, "notes.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("read notes.txt: %v", err)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"g.fc.run/misc/byre/internal/api"
|
||||
"g.fc.run/misc/byre/internal/store"
|
||||
)
|
||||
|
||||
func TestSetMergeTitle(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), func(api.Event) {})
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
|
||||
now := time.Now().UTC()
|
||||
_, err = st.EnsureUser(ctx, "alice", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("ensure user: %v", err)
|
||||
}
|
||||
err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now})
|
||||
if err != nil {
|
||||
t.Fatalf("create project: %v", err)
|
||||
}
|
||||
err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "Fix the thing", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now})
|
||||
if err != nil {
|
||||
t.Fatalf("create task: %v", err)
|
||||
}
|
||||
|
||||
task, err := st.GetTask(ctx, "t1")
|
||||
if err != nil || task.MergeTitle != "Fix the thing" {
|
||||
t.Fatalf("default merge title %q, want the task title: %v", task.MergeTitle, err)
|
||||
}
|
||||
|
||||
m := New(st, DefaultLimits, t.TempDir())
|
||||
l := &live{id: "s1", vendor: api.Claude, taskID: "t1", projectID: "p1"}
|
||||
call := func(title string) error {
|
||||
raw, err := json.Marshal(setMergeTitleArgs{Title: title})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
_, err = m.setMergeTitle(ctx, l, raw)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, bad := range []string{"", " ", "two\nlines", strings.Repeat("x", maxMergeTitle+1)} {
|
||||
if call(bad) == nil {
|
||||
t.Fatalf("set_merge_title accepted %q", bad)
|
||||
}
|
||||
}
|
||||
|
||||
err = call(" Add retry with backoff to webhook delivery ")
|
||||
if err != nil {
|
||||
t.Fatalf("set_merge_title: %v", err)
|
||||
}
|
||||
task, err = st.GetTask(ctx, "t1")
|
||||
if err != nil || task.MergeTitle != "Add retry with backoff to webhook delivery" || task.Title != "Fix the thing" {
|
||||
t.Fatalf("after set_merge_title: %+v %v", task, err)
|
||||
}
|
||||
|
||||
if !strings.Contains(mergeInPrompt("main", task.MergeTitle), `currently "Add retry with backoff to webhook delivery"`) {
|
||||
t.Fatalf("merge-in prompt doesn't show the current title: %s", mergeInPrompt("main", task.MergeTitle))
|
||||
}
|
||||
|
||||
_, err = st.DiscardTask(ctx, "t1", "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("discard: %v", err)
|
||||
}
|
||||
if call("Too late") == nil {
|
||||
t.Fatalf("set_merge_title accepted a title for a closed task")
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ func (m *Manager) CreateTask(ctx context.Context, project api.Project, title, cr
|
||||
Id: id,
|
||||
ProjectId: project.Id,
|
||||
Title: title,
|
||||
MergeTitle: title,
|
||||
Branch: "byre/" + id,
|
||||
WorktreePath: filepath.Join(m.worktreeRoot, project.Id, id),
|
||||
Status: api.TaskStatusActive,
|
||||
|
||||
+80
-76
@@ -499,7 +499,10 @@ type Task struct {
|
||||
Id string `json:"id"`
|
||||
|
||||
// MergeCommit merged: the squash commit on the default branch; empty when there was nothing to merge.
|
||||
MergeCommit *string `json:"merge_commit,omitempty"`
|
||||
MergeCommit *string `json:"merge_commit,omitempty"`
|
||||
|
||||
// MergeTitle The title of the squash commit Merge makes: the task title until the agent sets another with set_merge_title.
|
||||
MergeTitle string `json:"merge_title"`
|
||||
ProjectId string `json:"project_id"`
|
||||
Status TaskStatus `json:"status"`
|
||||
Title string `json:"title"`
|
||||
@@ -1683,81 +1686,82 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
// const string: with thousands of chunks the chained `+` fold is several
|
||||
// times slower for the Go compiler than parsing a slice literal.
|
||||
var swaggerSpec = []string{
|
||||
"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",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17
-17
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15
-16
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+22
-2
@@ -159,6 +159,8 @@ CREATE TABLE turn_queue_next (
|
||||
INSERT INTO turn_queue_next (session_id, actor, text, created_at) SELECT session_id, actor, text, created_at FROM turn_queue;
|
||||
DROP TABLE turn_queue;
|
||||
ALTER TABLE turn_queue_next RENAME TO turn_queue;
|
||||
`}, {"add-task-merge-title", `
|
||||
ALTER TABLE tasks ADD COLUMN merge_title TEXT NOT NULL DEFAULT '';
|
||||
`}}
|
||||
|
||||
type Store struct {
|
||||
@@ -412,6 +414,21 @@ func (s *Store) GetTask(ctx context.Context, id string) (api.Task, error) {
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (s *Store) SetMergeTitle(ctx context.Context, id, title, actor string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
projectID := ""
|
||||
err := tx.QueryRowContext(ctx, `UPDATE tasks SET merge_title = ? WHERE id = ? AND status = ? RETURNING project_id`,
|
||||
title, id, string(api.TaskStatusActive)).Scan(&projectID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "task.merge_title", ProjectId: &projectID, TaskId: &id, Actor: actor, Payload: map[string]any{"id": id, "merge_title": title}})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) MergeTask(ctx context.Context, id, commit, actor string) (api.Task, error) {
|
||||
return s.closeTask(ctx, id, api.TaskStatusMerged, commit, actor, "task.merged", map[string]any{"id": id, "commit": commit})
|
||||
}
|
||||
@@ -710,7 +727,7 @@ func (s *Store) write(ctx context.Context, fn func(tx *sql.Tx, emit emitFunc) er
|
||||
|
||||
const projectColumns = `id, name, repo_path, default_branch, remote, created_by, created_at`
|
||||
|
||||
const taskColumns = `id, project_id, title, branch, worktree_path, base_ref, status, created_by, created_at, closed_at, merge_commit`
|
||||
const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, status, created_by, created_at, closed_at, merge_commit`
|
||||
|
||||
const sessionColumns = `id, task_id, vendor, model, vendor_session_id, state, role, created_by, created_at`
|
||||
|
||||
@@ -735,10 +752,13 @@ func scanTask(row scanner) (api.Task, error) {
|
||||
createdAt := ""
|
||||
closedAt := sql.NullString{}
|
||||
mergeCommit := ""
|
||||
err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.Branch, &t.WorktreePath, &t.BaseRef, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit)
|
||||
err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if t.MergeTitle == "" {
|
||||
t.MergeTitle = t.Title
|
||||
}
|
||||
t.Status = api.TaskStatus(status)
|
||||
t.CreatedAt, err = parseTime(createdAt)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user