Files
byre/internal/agents/mergefacts.go
T

140 lines
3.0 KiB
Go
Raw Normal View History

package agents
import (
"context"
"errors"
"log/slog"
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/git"
"g.fc.run/ai/byre/internal/store"
)
const allTasks = "*"
func (m *Manager) observeFacts(ev api.Event) {
switch ev.Kind {
case "task.created", "turn.ended", "snapshot.created", "review.started", "review.finished", "review.finding", "user.message", "board.posted":
if ev.TaskId != nil {
m.MarkFacts(*ev.TaskId)
}
case "task.merged", "task.merged_to_main":
if ev.ProjectId != nil {
m.MarkFacts("project:" + *ev.ProjectId)
}
case "user.updated":
m.MarkFacts(allTasks)
}
}
func (m *Manager) MarkFacts(key string) {
m.factsMu.Lock()
m.factsDirty[key] = true
m.factsMu.Unlock()
select {
case m.factsPoke <- struct{}{}:
default:
}
}
func (m *Manager) factsLoop() {
for {
select {
case <-m.watchStop:
return
case <-m.factsPoke:
}
m.factsMu.Lock()
dirty := m.factsDirty
m.factsDirty = map[string]bool{}
m.factsMu.Unlock()
err := m.settleFacts(context.Background(), dirty)
if err != nil {
slog.Error("settle merge facts", "err", err)
}
}
}
func (m *Manager) settleFacts(ctx context.Context, dirty map[string]bool) error {
tasks, err := m.store.ActiveTasks(ctx)
if err != nil {
return err
}
projects := map[string]api.Project{}
for _, t := range tasks {
if !dirty[allTasks] && !dirty[t.Id] && !dirty["project:"+t.ProjectId] {
continue
}
p, ok := projects[t.ProjectId]
if !ok {
p, err = m.store.GetProject(ctx, t.ProjectId)
if err != nil {
return err
}
projects[t.ProjectId] = p
}
err = m.settleTaskFacts(ctx, t, p)
if err != nil {
return err
}
}
return nil
}
func (m *Manager) refreshFacts(ctx context.Context, taskID string) {
t, err := m.store.GetTask(ctx, taskID)
if err == nil {
var p api.Project
p, err = m.store.GetProject(ctx, t.ProjectId)
if err == nil {
err = m.settleTaskFacts(ctx, t, p)
}
}
if err != nil {
slog.Error("refresh merge facts", "task", taskID, "err", err)
}
}
func (m *Manager) settleTaskFacts(ctx context.Context, t api.Task, p api.Project) error {
m.factsRun.Lock()
defer m.factsRun.Unlock()
g, err := m.gitFacts(ctx, t, p)
if err != nil {
slog.Error("compute merge facts", "task", t.Id, "err", err)
return nil
}
err = m.store.SettleMergeFacts(ctx, t.Id, g)
if errors.Is(err, store.ErrNotFound) {
return nil
}
return err
}
func (m *Manager) gitFacts(ctx context.Context, t api.Task, p api.Project) (store.GitFacts, error) {
g := store.GitFacts{}
if t.WorktreePath == "" {
return g, nil
}
tree, err := git.Snapshot(t.WorktreePath)
if err != nil {
return g, err
}
g.UnmergedCommits, err = git.Ahead(t.WorktreePath, p.DefaultBranch)
if err != nil {
return g, err
}
g.UnmergedFiles, err = git.ChangedFileCount(t.WorktreePath, p.DefaultBranch, tree)
if err != nil {
return g, err
}
latest, err := m.store.LastReviewable(ctx, t.Id)
if errors.Is(err, store.ErrNotFound) {
return g, nil
}
if err != nil {
return g, err
}
g.UnreviewedChanges = latest.TreeSha != tree
return g, nil
}