Skip to content

Conversation

@alexander-akhmetov
Copy link
Owner

@alexander-akhmetov alexander-akhmetov commented Feb 2, 2026

This PR mostly refactors the code to make alternative executors possible, there are no user-facing changes except for the breaking config change.

  • Refactor the hardcoded Claude Code dependency into a configurable executor, selected via executor: in config or --executor CLI flag
  • Restructure config: replace top-level claude_flags/claude_config_dir/anthropic_api_key with executor: claude selector and nested claude: block

Config changes

Old config:

claude_flags: "--model opus"
claude_config_dir: "/path/to/config"
anthropic_api_key: "sk-ant-..."

New config

executor: "claude"  in the future could be "codex", "pi", etc.

claude:
  flags: "--model opus"
  config_dir: "/path/to/config"
  anthropic_api_key: "sk-ant-..."

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the Claude Code integration to support a pluggable executor architecture, allowing different coding agents to be selected via configuration or CLI flag.

Changes:

  • Introduced an executor abstraction layer with factory pattern in internal/llm
  • Restructured configuration to use executor selector and nested claude block instead of top-level Claude-specific fields
  • Updated all references throughout the codebase to use the new nested configuration structure

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/tui/view.go Updated UI rendering to access Claude config via nested structure
internal/tui/tui.go Added executor config field and setter method
internal/tui/start.go Wired executor config through to TUI initialization
internal/tui/run.go Added executor flag and updated Claude invocation to use factory pattern
internal/tui/plan.go Updated plan creator to use executor config and factory pattern
internal/tui/helpers_test.go Updated test assertions for nested Claude config structure
internal/tui/helpers.go Updated skip permissions helper to use nested config fields
internal/tui/configcmd.go Updated config display command to show executor and nested Claude settings
internal/safety/safety_test.go Updated tests for new config structure with executor and nested Claude fields
internal/safety/safety.go Added ClaudeConfig struct and updated Config to use executor field
internal/loop/loop.go Extensive updates to use executor config, factory pattern, and conditional Claude-specific logic
internal/llm/factory_test.go Added comprehensive tests for new executor factory
internal/llm/factory.go Created executor factory with config-driven invoker creation
internal/config/defaults/config.yaml Updated default config with executor field and nested Claude block
internal/config/config_test.go Added tests for executor config loading and merging
internal/config/config.go Added ClaudeConfig struct and updated Config to support nested structure
internal/config/compat_test.go Updated compatibility tests for new config structure
internal/config/compat.go Updated config conversion to use nested Claude structure

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

internal/tui/run.go:124

  • The executor config's ExtraFlags (which contains the Claude flags from config) are not being merged with the buildCommonFlags() result. This means that any Claude flags configured in the config file or environment will be ignored in print mode.

Consider concatenating both flag sources, for example:

configFlags := execCfg.ExtraFlags
cliFlags := strings.Join(buildCommonFlags(), " ")
combinedFlags := strings.TrimSpace(configFlags + " " + cliFlags)
opts := llm.InvokeOptions{
    WorkingDir: workingDir,
    ExtraFlags: combinedFlags,
    ...
}
	opts := llm.InvokeOptions{
		WorkingDir: workingDir,
		ExtraFlags: strings.Join(buildCommonFlags(), " "),

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@alexander-akhmetov alexander-akhmetov marked this pull request as ready for review February 3, 2026 00:19

This comment was marked as outdated.

…s`/`claude_config_dir`/`anthropic_api_key` with `executor` selector and per-executor `claude:` block
…`ExecutorConfig` struct and `NewInvoker()` factory function
…ker() with factory, guard Claude-specific hooks/flags, update log messages
…t.go, run.go (add --executor flag), and plan.go
- Cache invoker after successful factory creation in loop.go
- Wire Claude EnvConfig (config_dir, api_key) through run.go
- Add PROGRAMMATOR_ANTHROPIC_API_KEY env var handling in safety.go
- Fix "invoke Claude" error message to "invoke executor" in plan.go
- Add test coverage for PROGRAMMATOR_EXECUTOR and PROGRAMMATOR_ANTHROPIC_API_KEY

This comment was marked as outdated.

When review agents error, the loop would retry without incrementing
the iteration counter or tracking stagnation. This caused an infinite
loop that consumed all memory.

Fix by:
1. Recording agent errors as no-progress iterations to trigger stagnation
2. Checking safety limits before retrying review
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 337 to 359
}
}

if v := os.Getenv("PROGRAMMATOR_EXECUTOR"); v != "" {
c.Executor = v
c.sources = append(c.sources, "env:PROGRAMMATOR_EXECUTOR")
}

if v := os.Getenv("PROGRAMMATOR_CLAUDE_FLAGS"); v != "" {
c.ClaudeFlags = v
c.Claude.Flags = v
c.sources = append(c.sources, "env:PROGRAMMATOR_CLAUDE_FLAGS")
}

if v := os.Getenv("CLAUDE_CONFIG_DIR"); v != "" {
c.ClaudeConfigDir = v
c.Claude.ConfigDir = v
c.sources = append(c.sources, "env:CLAUDE_CONFIG_DIR")
}

if v := os.Getenv("PROGRAMMATOR_ANTHROPIC_API_KEY"); v != "" {
c.AnthropicAPIKey = v
c.Claude.AnthropicAPIKey = v
c.sources = append(c.sources, "env:PROGRAMMATOR_ANTHROPIC_API_KEY")
}

Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaking change removes the old config keys (claude_flags, claude_config_dir, anthropic_api_key) and moves them under the nested claude block, but there's no backward compatibility handling or migration support. Consider adding detection of old keys in parseConfigWithTracking (similar to how review.passes is handled at line 278) to either automatically migrate them with a deprecation warning or provide a clear error message explaining the config migration path. This would significantly improve user experience during upgrades.

Copilot uses AI. Check for mistakes.
Comment on lines 13 to 19
return llm.ExecutorConfig{
Name: c.Executor,
Claude: llm.EnvConfig{
ClaudeConfigDir: c.Claude.ConfigDir,
AnthropicAPIKey: c.Claude.AnthropicAPIKey,
},
ExtraFlags: c.Claude.Flags,
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ToExecutorConfig method unconditionally copies c.Claude.Flags to ExtraFlags regardless of the executor type (c.Executor). This means if a user configures a non-Claude executor in the future, they will still get Claude-specific flags passed to it. Consider only setting ExtraFlags when c.Executor is "claude" or empty, or add separate flag fields for each executor type.

Suggested change
return llm.ExecutorConfig{
Name: c.Executor,
Claude: llm.EnvConfig{
ClaudeConfigDir: c.Claude.ConfigDir,
AnthropicAPIKey: c.Claude.AnthropicAPIKey,
},
ExtraFlags: c.Claude.Flags,
var extraFlags []string
// Only apply Claude-specific flags when using the Claude executor or when
// no executor is explicitly specified (Claude as default).
if c.Executor == "" || c.Executor == "claude" {
extraFlags = c.Claude.Flags
}
return llm.ExecutorConfig{
Name: c.Executor,
Claude: llm.EnvConfig{
ClaudeConfigDir: c.Claude.ConfigDir,
AnthropicAPIKey: c.Claude.AnthropicAPIKey,
},
ExtraFlags: extraFlags,

Copilot uses AI. Check for mistakes.
Comment on lines 1300 to 1301
}
l.reviewConfig.EnvConfig = l.executorConfig.Claude
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EnvConfig is set unconditionally at line 1301 (l.reviewConfig.EnvConfig = l.executorConfig.Claude) even when the executor is not Claude. This could cause issues if a non-Claude executor is configured. Consider moving this line inside the isClaudeExecutor() conditional block (after line 1300) to ensure Claude-specific configuration is only applied when using the Claude executor.

Suggested change
}
l.reviewConfig.EnvConfig = l.executorConfig.Claude
l.reviewConfig.EnvConfig = l.executorConfig.Claude
}

Copilot uses AI. Check for mistakes.
@@ -175,7 +177,7 @@ func (p *planCreator) run() (string, error) {
// Invoke Claude
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment "Invoke Claude" should be executor-agnostic to align with the PR's goal of supporting alternative executors. Consider changing it to "Invoke executor" to match the updated error message at line 180.

Copilot uses AI. Check for mistakes.
The claudeFlags field duplicated executorConfig.ExtraFlags since both
are set from cfg.Claude.Flags. Use executorConfig.ExtraFlags directly
to prevent config drift.
- Move reviewConfig.EnvConfig inside isClaudeExecutor() guard
- Guard ExtraFlags in ToExecutorConfig by executor type
- Rename invokeClaudePrint → invokeExecutor
- Rename invokeClaude → invokeExecutor in plan creator
- Rename processClaudeStatus → processStatus
- Rename loopBreakToClaudeInvocation → loopBreakToInvocation
- Update comments/logs to use generic "executor" terminology
- Add inline comments to Executor and Name fields
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant