Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions cmd/entire/cli/review/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ type Deps struct {
// Injected to avoid an import cycle: review → checkpoint → codex → review.
HeadHasReviewCheckpoint func(ctx context.Context) (bool, string)

// ReviewCheckpointContext returns best-effort checkpoint context for the
// branch review scope. Injected from the cli package because checkpoint
// readers cannot be imported here without cycling through agent reviewers.
ReviewCheckpointContext func(ctx context.Context, worktreeRoot string, scopeBaseRef string) string

// ReviewerFor maps an agent registry name to its AgentReviewer
// implementation. Returns nil for non-launchable agents (cursor, opencode,
// factoryai-droid, copilot-cli). Injected to break the import cycle:
Expand Down Expand Up @@ -323,12 +328,17 @@ func runSingleAgentPath(
return fmt.Errorf("resolve HEAD: %w", shaErr)
}
scopeBaseRef := detectScope(ctx, worktreeRoot, out)
checkpointContext := ""
if deps.ReviewCheckpointContext != nil {
checkpointContext = deps.ReviewCheckpointContext(ctx, worktreeRoot, scopeBaseRef)
}

runCfg := reviewtypes.RunConfig{
PromptOverride: cfg.Prompt,
Skills: cfg.Skills,
ScopeBaseRef: scopeBaseRef,
StartingSHA: headSHA,
PromptOverride: cfg.Prompt,
Skills: cfg.Skills,
ScopeBaseRef: scopeBaseRef,
CheckpointContext: checkpointContext,
StartingSHA: headSHA,
}

// 7. Branch on launchability.
Expand Down Expand Up @@ -407,6 +417,10 @@ func runMultiAgentPath(
}

scopeBaseRef := detectScope(ctx, worktreeRoot, out)
checkpointContext := ""
if deps.ReviewCheckpointContext != nil {
checkpointContext = deps.ReviewCheckpointContext(ctx, worktreeRoot, scopeBaseRef)
}

// Build per-agent reviewers with individual RunConfigs (each agent has
// its own skills + always-prompt from s.Review[name]).
Expand All @@ -428,11 +442,12 @@ func runMultiAgentPath(
reviewers = append(reviewers, &perAgentConfiguredReviewer{
inner: reviewer,
cfg: reviewtypes.RunConfig{
PromptOverride: agentCfg.Prompt,
Skills: agentCfg.Skills,
PerRunPrompt: picked.PerRun,
ScopeBaseRef: scopeBaseRef,
StartingSHA: headSHA,
PromptOverride: agentCfg.Prompt,
Skills: agentCfg.Skills,
PerRunPrompt: picked.PerRun,
ScopeBaseRef: scopeBaseRef,
CheckpointContext: checkpointContext,
StartingSHA: headSHA,
},
})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/entire/cli/review/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ type stubDispatchReviewer struct {
}

func (r *stubDispatchReviewer) Name() string { return r.name }
func (r *stubDispatchReviewer) Start(_ context.Context, _ reviewtypes.RunConfig) (reviewtypes.Process, error) {
func (r *stubDispatchReviewer) Start(context.Context, reviewtypes.RunConfig) (reviewtypes.Process, error) {
return &stubDispatchProcess{}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/entire/cli/review/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ func ComposeReviewPrompt(cfg reviewtypes.RunConfig) string {
if cfg.ScopeBaseRef != "" {
sections = append(sections, "Scope: review only the commits unique to this branch vs "+cfg.ScopeBaseRef+".")
}
if trimmed := strings.TrimRight(cfg.CheckpointContext, "\n\r "); trimmed != "" {
sections = append(sections, trimmed)
}

return strings.Join(sections, "\n\n")
}
32 changes: 27 additions & 5 deletions cmd/entire/cli/review/prompt_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package review

import (
"strings"
"testing"

reviewtypes "github.com/entireio/cli/cmd/entire/cli/review/types"
Expand Down Expand Up @@ -60,14 +61,35 @@ func TestComposeReviewPrompt_AllSectionsWithScope(t *testing.T) {
}
}

func TestComposeReviewPrompt_IncludesCheckpointContext(t *testing.T) {
t.Parallel()
cfg := reviewtypes.RunConfig{
Skills: []string{"/x"},
ScopeBaseRef: "main",
CheckpointContext: "Commits in scope (newest first):\n abc123 checkpoint data\n",
}
got := ComposeReviewPrompt(cfg)
for _, want := range []string{
"/x",
"Scope: review only the commits unique to this branch vs main.",
"Commits in scope (newest first):",
"abc123 checkpoint data",
} {
if !strings.Contains(got, want) {
t.Errorf("prompt missing %q:\n%s", want, got)
}
}
}

func TestComposeReviewPrompt_PromptOverrideIsVerbatim(t *testing.T) {
t.Parallel()
cfg := reviewtypes.RunConfig{
Skills: []string{"/review"},
AlwaysPrompt: "always-on instructions",
PerRunPrompt: "per-run focus",
ScopeBaseRef: "main",
PromptOverride: "custom prompt\nleave untouched",
Skills: []string{"/review"},
AlwaysPrompt: "always-on instructions",
PerRunPrompt: "per-run focus",
ScopeBaseRef: "main",
CheckpointContext: "Commits in scope (newest first):\n abc123 checkpoint data\n",
PromptOverride: "custom prompt\nleave untouched",
}
got := ComposeReviewPrompt(cfg)
want := "custom prompt\nleave untouched"
Expand Down
6 changes: 3 additions & 3 deletions cmd/entire/cli/review/tui_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (m reviewTUIModel) Init() tea.Cmd {
}

// Update handles all incoming messages.
func (m reviewTUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:ireturn // bubbletea interface
func (m reviewTUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:ireturn // tea.Model is an interface; required by Bubble Tea.
switch msg := msg.(type) {
case agentEventMsg:
return m.handleAgentEvent(msg)
Expand Down Expand Up @@ -166,7 +166,7 @@ func (m reviewTUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { //nolint:iret
}

// handleAgentEvent processes an agentEventMsg, updating the relevant row.
func (m reviewTUIModel) handleAgentEvent(msg agentEventMsg) (tea.Model, tea.Cmd) { //nolint:ireturn // bubbletea interface
func (m reviewTUIModel) handleAgentEvent(msg agentEventMsg) (tea.Model, tea.Cmd) { //nolint:ireturn // tea.Model is an interface; required by Bubble Tea.
idx, ok := m.rowIdx[msg.agent]
if !ok {
return m, nil
Expand Down Expand Up @@ -229,7 +229,7 @@ func (m reviewTUIModel) handleAgentEvent(msg agentEventMsg) (tea.Model, tea.Cmd)
}

// handleKey processes keyboard input.
func (m reviewTUIModel) handleKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { //nolint:ireturn // bubbletea interface
func (m reviewTUIModel) handleKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { //nolint:ireturn // tea.Model is an interface; required by Bubble Tea.
// Any key after finished dismisses.
if m.finished {
return m, tea.Quit
Expand Down
7 changes: 7 additions & 0 deletions cmd/entire/cli/review/types/reviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ type RunConfig struct {
// base for `git diff` operations the agent may perform.
ScopeBaseRef string

// CheckpointContext is best-effort context derived from checkpoints in the
// branch review scope. It is appended to generated prompts so every agent
// can use checkpoint IDs, file summaries, and transcript lookup commands
// while reviewing. PromptOverride remains verbatim and does not receive
// this context.
CheckpointContext string

// StartingSHA is HEAD at invocation time, propagated to the lifecycle
// hook via ENTIRE_REVIEW_STARTING_SHA so checkpoint metadata records
// the commit that was reviewed.
Expand Down
1 change: 1 addition & 0 deletions cmd/entire/cli/review_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func buildReviewDeps(attachCmd *cobra.Command) cliReview.Deps {
return NewSilentError(err)
},
HeadHasReviewCheckpoint: headHasReviewCheckpoint,
ReviewCheckpointContext: reviewCheckpointContext,
ReviewerFor: launchableReviewerFor,
PromptForAgentFn: nil, // use real PromptForAgent
AttachCmd: attachCmd,
Expand Down
Loading
Loading