resume picker: address Copilot/Cursor review comments · Entire
resume picker: address Copilot/Cursor review comments
5d9e2a5→main·
Soph·1mo ago·4 files·+80 added/-7 removed
- TTY gate: bare
entire resumenow bails with guidance when there's no interactive terminal instead of opening a form that can't render (cursor, High). - huh form uses RunWithContext(ctx) so it honors the command context, and context.Canceled (like ErrUserAborted) exits cleanly without a noisy "selection failed" error (cursor, Medium/Low).
- branchCheckedOutElsewhere returns false when the current worktree root can't be resolved, instead of treating every worktree (incl. the current checkout) as "elsewhere" and printing a misleading clash hint (cursor, Medium).
- captureSessionBranch clears state.Branch on a detached HEAD so a stale branch from a previous turn can't mis-map the session on resume; the derivation fallback takes over (Copilot).
- buildCheckpointBranchIndex defers iter.Close() on the branch iterator (Copilot).
- resumeSingleSession doc comment updated to the keep-by-default behavior, and its kept-log message aligned to the "Keeping existing ..." wording used by the multi-session path (Copilot).
Adds TestCaptureSessionBranch (on-branch capture + detached-HEAD clear).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
cb36c5558f2cView transcript
Changes
4
cmd/entire/cli
Mresume.go+4/-3
- Mresume_picker.go+21/-4
strategy
Acapture_branch_test.go+50
- Mmanual_commit_hooks.go+5
// resumeSingleSession restores a single session (fallback when multi-session restore fails).
// Always overwrites existing session logs to ensure consistency with checkpoint state.
// If force is false, prompts for confirmation when local log has newer timestamps.
// By default it never overwrites an existing local session log — if one is present it is
// kept and only the resume command is printed; --force overwrites it from the checkpoint.
// A missing local log is always restored from the checkpoint.
func resumeSingleSession(ctx context.Context, w, _ io.Writer, ag agent.Agent, sessionID string, checkpointID id.CheckpointID, repoRoot string, force bool) error {
sessionLogPath, err := resolveTranscriptPath(ctx, sessionID, ag)
if err != nil {
// Handle error scenario
}
if !force {
if _, statErr := os.Stat(sessionLogPath); statErr == nil {
fmt.Fprintf(w, "Session '%s' already has a local log — keeping it (use --force to overwrite from checkpoint).\n", sessionID)
fmt.Fprintf(w, "Keeping existing local session log for '%s' (use --force to overwrite from checkpoint).\n", sessionID)
fmt.Fprintf(w, "\nTo continue this session:\n")
fmt.Fprintf(w, " %s\n", ag.FormatResumeCommand(sessionID))
return nil
}
}
}
package strategy
import (
"testing"
"github.com/entireio/cli/cmd/entire/cli/testutil"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
)
// TestCaptureSessionBranch verifies the branch is recorded while on a branch and
// cleared on a detached HEAD (so a stale value can't survive into resume).
func TestCaptureSessionBranch(t *testing.T) {
t.Parallel()
dir := t.TempDir()
testutil.InitRepo(t, dir)
testutil.WriteFile(t, dir, "f.txt", "x")
testutil.GitAdd(t, dir, "f.txt")
testutil.GitCommit(t, dir, "init")
repo, err := git.PlainOpen(dir)
if err != nil {
t.Fatalf("open repo: %v", err)
}
defer repo.Close()
head, err := repo.Head()
if err != nil {
t.Fatalf("head: %v", err)
}
// On a branch: captures the current branch name (overwriting any prior value).
state := &SessionState{Branch: "stale"}
captureSessionBranch(repo, state)
if want := head.Name().Short(); state.Branch != want {
t.Errorf("on-branch: Branch = %q, want %q", state.Branch, want)
}
// Detached HEAD: clears the stale branch so resume derives it instead.
if err := repo.Storer.SetReference(plumbing.NewHashReference(plumbing.HEAD, head.Hash())); err != nil {
t.Fatalf("detach HEAD: %v", err)
}
state.Branch = "stale-branch"
captureSessionBranch(repo, state)
if state.Branch != "" {
t.Errorf("detached HEAD should clear Branch, got %q", state.Branch)
}
}