Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 20 additions & 0 deletions cmd/entire/cli/agent/claudecode/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package claudecode

import (
"context"

"github.com/entireio/cli/cmd/entire/cli/agent"
)

var _ agent.ModelLister = (*ClaudeCodeAgent)(nil)

// ListModels returns common Claude model aliases for `entire review --model`.
// Claude Code's CLI accepts these aliases (per `claude --help`) as well as full
// model identifiers; the list is advisory and intentionally non-exhaustive.
func (c *ClaudeCodeAgent) ListModels(_ context.Context) ([]agent.ModelInfo, error) {
return []agent.ModelInfo{
{ID: "opus", Note: "alias — latest Claude Opus"},
{ID: "sonnet", Note: "alias — latest Claude Sonnet"},
{ID: "haiku", Note: "alias — latest Claude Haiku (fast)"},
}, nil
}
6 changes: 5 additions & 1 deletion cmd/entire/cli/agent/claudecode/reviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ func NewReviewer() *reviewtypes.ReviewerTemplate {
// Exposed at package level for test inspection of argv and env.
func buildReviewCmd(ctx context.Context, cfg reviewtypes.RunConfig) *exec.Cmd {
prompt := review.ComposeReviewPrompt(cfg)
cmd := exec.CommandContext(ctx, "claude", "-p", prompt, "--output-format", "stream-json", "--verbose")
args := []string{"-p", prompt, "--output-format", "stream-json", "--verbose"}
if cfg.Model != "" {
args = append(args, "--model", cfg.Model)
}
cmd := exec.CommandContext(ctx, "claude", args...)
cmd.Env = review.AppendReviewEnv(os.Environ(), "claude-code", cfg, prompt)
return cmd
}
Expand Down
20 changes: 20 additions & 0 deletions cmd/entire/cli/agent/codex/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package codex

import (
"context"

"github.com/entireio/cli/cmd/entire/cli/agent"
)

var _ agent.ModelLister = (*CodexAgent)(nil)

// ListModels returns example Codex model identifiers for `entire review
// --model`. Codex has no model-enumeration command, so these are advisory
// examples — `--model` forwards any value the codex CLI accepts.
func (c *CodexAgent) ListModels(_ context.Context) ([]agent.ModelInfo, error) {
return []agent.ModelInfo{
{ID: "gpt-5-codex", Note: "example — Codex-tuned"},
{ID: "gpt-5", Note: "example"},
{ID: "o3", Note: "example"},
}, nil
}
6 changes: 5 additions & 1 deletion cmd/entire/cli/agent/codex/reviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ func NewReviewer() *reviewtypes.ReviewerTemplate {
func buildCodexReviewCmd(ctx context.Context, cfg reviewtypes.RunConfig) *exec.Cmd {
promptCfg := cfg
promptCfg.Skills = expandCodexBuiltinReview(cfg.Skills)
args := []string{codexExecCommand, "--skip-git-repo-check", "--json", "-"}
args := []string{codexExecCommand, "--skip-git-repo-check", "--json"}
if cfg.Model != "" {
args = append(args, "--model", cfg.Model)
}
args = append(args, "-")
prompt := review.ComposeReviewPrompt(promptCfg)
cmd := exec.CommandContext(ctx, "codex", args...)
cmd.Stdin = strings.NewReader(prompt)
Expand Down
19 changes: 19 additions & 0 deletions cmd/entire/cli/agent/geminicli/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package geminicli

import (
"context"

"github.com/entireio/cli/cmd/entire/cli/agent"
)

var _ agent.ModelLister = (*GeminiCLIAgent)(nil)

// ListModels returns example Gemini model identifiers for `entire review
// --model`. The Gemini CLI has no model-enumeration command, so these are
// advisory examples — `--model` forwards any value the gemini CLI accepts.
func (g *GeminiCLIAgent) ListModels(_ context.Context) ([]agent.ModelInfo, error) {
return []agent.ModelInfo{
{ID: "gemini-2.5-pro", Note: "example"},
{ID: "gemini-2.5-flash", Note: "example — faster"},
}, nil
}
6 changes: 5 additions & 1 deletion cmd/entire/cli/agent/geminicli/reviewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ func buildGeminiReviewCmd(ctx context.Context, cfg reviewtypes.RunConfig) *exec.
// Per the existing GenerateText implementation: pass "-p " " " as the
// argv placeholder to trigger headless (non-interactive) mode, and pipe
// the actual prompt via stdin to avoid argv size limits.
cmd := exec.CommandContext(ctx, "gemini", "-p", " ")
args := []string{"-p", " "}
if cfg.Model != "" {
args = append(args, "--model", cfg.Model)
}
cmd := exec.CommandContext(ctx, "gemini", args...)
cmd.Stdin = strings.NewReader(prompt)
// Agent name must equal string(ag.Name()) — adoptReviewEnv compares
// ENTIRE_REVIEW_AGENT against it; any drift silently skips adoption.
Expand Down
41 changes: 41 additions & 0 deletions cmd/entire/cli/agent/model_lister.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package agent

import "context"

// ModelInfo describes one model an agent can run via `--model`.
type ModelInfo struct {
// ID is the value passed to the agent CLI's --model flag (an exact model
// identifier or a provider alias such as "sonnet").
ID string
// Note is an optional short human hint (e.g. "alias", "faster",
// "example") shown alongside the ID. It carries no behavior.
Note string
}

// ModelLister is an optional capability for agents that can advertise the
// models usable with `entire review --model`.
//
// Built-in agents whose CLI has no model-enumeration command (claude-code,
// codex, gemini) return a curated, intentionally non-exhaustive list of common
// models/aliases — `--model` ultimately accepts anything the agent CLI does.
// Agents whose CLI can enumerate models live (e.g. Pi's `pi --list-models`)
// may shell out instead.
type ModelLister interface {
Agent

// ListModels returns the advertised models for this agent. The list is
// advisory; callers must still allow arbitrary `--model` values.
ListModels(ctx context.Context) ([]ModelInfo, error)
}

// AsModelLister returns the agent as a ModelLister if it implements the
// capability. Unlike AsTextGenerator this does not consult CapabilityDeclarer:
// the model list is advisory only, so a plain type assertion is sufficient and
// keeps the external-agent capability protocol unchanged.
func AsModelLister(ag Agent) (ModelLister, bool) {
if ag == nil {
return nil, false
}
ml, ok := ag.(ModelLister)
return ml, ok
}
28 changes: 21 additions & 7 deletions cmd/entire/cli/integration_test/review_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,17 @@ func TestReviewCommand_PassesReviewEnvToSpawnedAgentHook(t *testing.T) {
env := NewFeatureBranchEnv(t)
enableReviewAgent(t, env, "claude-code")
env.WriteSettings(map[string]any{
"enabled": true,
"review": map[string]any{
"claude-code": map[string]any{
"skills": []string{"/review"},
"enabled": true,
"review_default_profile": "general",
"review_profiles": map[string]any{
"general": map[string]any{
"task": "Test review task.",
"agents": map[string]any{
"claude-code": map[string]any{
"skills": []string{"/review"},
},
},
"master": "claude-code",
},
},
})
Expand Down Expand Up @@ -229,9 +236,16 @@ func TestReview_MissingSkillAtSpawn_ErrorsCleanly(t *testing.T) {
enableReviewAgent(t, env, "claude-code")

env.WriteSettings(map[string]any{
"review": map[string]any{
"claude-code": map[string]any{
"skills": []string{"/nonexistent:skill-xyz"},
"review_default_profile": "general",
"review_profiles": map[string]any{
"general": map[string]any{
"task": "Test review task.",
"agents": map[string]any{
"claude-code": map[string]any{
"skills": []string{"/nonexistent:skill-xyz"},
},
},
"master": "claude-code",
},
},
})
Expand Down
2 changes: 1 addition & 1 deletion cmd/entire/cli/labs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var experimentalCommands = []experimentalCommandInfo{
{
Name: "review",
Invocation: "entire review",
Summary: "Run configured review skills against the current branch",
Summary: "Run a review profile against the current branch",
},
{
Name: "investigate",
Expand Down
Loading
Loading