agent: move "sketch-base" into git
The agent's notion of "initial commit" is kind of special, in that it
is used as the "base" for a bunch of git operations. It's hard for
the user to change (we only provide a workflow via restart), yet
sometimes you want to do just that.
So, instead we put it as data inside of it, named as a tag sketch-base.
It's abusing tags, but branches are no better.
diff --git a/loop/agent.go b/loop/agent.go
index a0f981d..41a22f5 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -84,8 +84,10 @@
// If commit is non-nil, it shows the diff for just that specific commit.
Diff(commit *string) (string, error)
- // InitialCommit returns the Git commit hash that was saved when the agent was instantiated.
- InitialCommit() string
+ // SketchGitBase returns the commit that's the "base" for Sketch's work. It
+ // starts out as the commit where sketch started, but a user can move it if need
+ // be, for example in the case of a rebase. It is stored as a git tag.
+ SketchGitBase() string
// Title returns the current title of the conversation.
Title() string
@@ -298,7 +300,6 @@
url string
firstMessageIndex int // index of the first message in the current conversation
lastHEAD string // hash of the last HEAD that was pushed to the host (only when under docker)
- initialCommit string // hash of the Git HEAD when the agent was instantiated or Init()
gitRemoteAddr string // HTTP URL of the host git repo (only when under docker)
outsideHTTP string // base address of the outside webserver (only when under docker)
ready chan struct{} // closed when the agent is initialized (only when under docker)
@@ -833,10 +834,8 @@
return fmt.Errorf("git checkout %s: %s: %w", ini.Commit, checkoutOut, err)
}
}
- a.lastHEAD = ini.Commit
a.gitRemoteAddr = ini.GitRemoteAddr
a.outsideHTTP = ini.OutsideHTTP
- a.initialCommit = ini.Commit
if ini.HostAddr != "" {
a.url = "http://" + ini.HostAddr
}
@@ -850,11 +849,16 @@
}
a.repoRoot = repoRoot
- commitHash, err := resolveRef(ctx, a.repoRoot, "HEAD")
if err != nil {
return fmt.Errorf("resolveRef: %w", err)
}
- a.initialCommit = commitHash
+
+ cmd := exec.CommandContext(ctx, "git", "tag", "-f", a.SketchGitBaseRef(), "HEAD")
+ cmd.Dir = repoRoot
+ if out, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("git tag -f %s %s: %s: %w", a.SketchGitBaseRef(), "HEAD", out, err)
+ }
+ a.lastHEAD = ini.Commit
if experiment.Enabled("memory") {
slog.Info("running codebase analysis")
@@ -869,7 +873,7 @@
if experiment.Enabled("llm_review") {
llmCodeReview = codereview.DoLLMReview
}
- codereview, err := codereview.NewCodeReviewer(ctx, a.repoRoot, a.initialCommit, llmCodeReview)
+ codereview, err := codereview.NewCodeReviewer(ctx, a.repoRoot, a.SketchGitBaseRef(), llmCodeReview)
if err != nil {
return fmt.Errorf("Agent.Init: codereview.NewCodeReviewer: %w", err)
}
@@ -877,7 +881,7 @@
a.gitOrigin = getGitOrigin(ctx, ini.WorkingDir)
}
- a.lastHEAD = a.initialCommit
+ a.lastHEAD = a.SketchGitBase()
a.convo = a.initConvo()
close(a.ready)
return nil
@@ -1505,7 +1509,7 @@
// Diff returns a unified diff of changes made since the agent was instantiated.
func (a *Agent) Diff(commit *string) (string, error) {
- if a.initialCommit == "" {
+ if a.SketchGitBase() == "" {
return "", fmt.Errorf("no initial commit reference available")
}
@@ -1530,7 +1534,7 @@
}
// Otherwise, get the diff between the initial commit and the current state using exec.Command
- cmd := exec.CommandContext(ctx, "git", "diff", "--unified=10", a.initialCommit)
+ cmd := exec.CommandContext(ctx, "git", "diff", "--unified=10", a.SketchGitBaseRef())
cmd.Dir = a.repoRoot
output, err := cmd.CombinedOutput()
if err != nil {
@@ -1540,9 +1544,26 @@
return string(output), nil
}
-// InitialCommit returns the Git commit hash that was saved when the agent was instantiated.
-func (a *Agent) InitialCommit() string {
- return a.initialCommit
+// SketchGitBaseRef distinguishes between the typical container version, where sketch-base is
+// unambiguous, and the "unsafe" version, where we need to use a session id to disambiguate.
+func (a *Agent) SketchGitBaseRef() string {
+ if a.IsInContainer() {
+ return "sketch-base"
+ } else {
+ return "sketch-base-" + a.SessionID()
+ }
+}
+
+// SketchGitBase returns the Git commit hash that was saved when the agent was instantiated.
+func (a *Agent) SketchGitBase() string {
+ cmd := exec.CommandContext(context.Background(), "git", "rev-parse", a.SketchGitBaseRef())
+ cmd.Dir = a.repoRoot
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ slog.Warn("could not identify sketch-base", slog.String("error", err.Error()))
+ return "HEAD"
+ }
+ return string(strings.TrimSpace(string(output)))
}
// removeGitHooks removes the Git hooks directory from the repository
@@ -1597,7 +1618,7 @@
// Format: <hash>\0<subject>\0<body>\0
// This uses NULL bytes as separators to avoid issues with newlines in commit messages
// Limit to 100 commits to avoid overwhelming the user
- cmd := exec.CommandContext(ctx, "git", "log", "-n", "100", "--pretty=format:%H%x00%s%x00%b%x00", "^"+a.initialCommit, head)
+ cmd := exec.CommandContext(ctx, "git", "log", "-n", "100", "--pretty=format:%H%x00%s%x00%b%x00", "^"+a.SketchGitBaseRef(), head)
cmd.Dir = a.repoRoot
output, err := cmd.Output()
if err != nil {
@@ -1837,7 +1858,6 @@
return fmt.Errorf("git checkout %s: %s: %w", revision, out, err)
}
a.lastHEAD = revision
- a.initialCommit = revision
return nil
}
@@ -1923,7 +1943,7 @@
ClientGOARCH: a.config.ClientGOARCH,
WorkingDir: a.workingDir,
RepoRoot: a.repoRoot,
- InitialCommit: a.initialCommit,
+ InitialCommit: a.SketchGitBase(),
Codebase: a.codebase,
}