diff --git a/CHANGELOG.md b/CHANGELOG.md index a61ddaf1..c52c0ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Stable Tmux Socket Directory** - Moved tmux sockets from `/tmp/tmux-{uid}/` to `~/.claudio/sockets/` via `TMUX_TMPDIR` to prevent macOS periodic `/tmp` cleanup from killing active tmux servers. `ListClaudioSockets` checks both locations for backward compatibility. ### Changed +- **Ship Experimental Features** - Graduated intelligent naming, terminal support, inline multiplan, inline ultraplan, and grouped instance view from experimental to default. These features are now always enabled without configuration. Only subprocess mode remains experimental. - **Extract `createTmuxSession()` Helper** - Extracted duplicated tmux session setup from `Start()` and `StartWithResume()` into a reusable `createTmuxSession()` method, eliminating ~40 lines of duplication. - **Extract `buildInstanceCallbacks()` Helper** - Consolidated duplicated callback wiring between `newInstanceManager` and `newInstanceManagerWithBackend` into a shared method to prevent sync bugs when adding new callbacks. - **Log tmux session option errors** - Replaced silent `_ =` error discards in `createTmuxSession` and recovery paths with Debug/Warn-level logging for better diagnostics. diff --git a/README.md b/README.md index 22b10b78..1af7caf1 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ Press `:` to enter command mode for advanced operations: | `:a "task"` | Add a new instance | | `:plan "objective"` | Start inline plan generation | | `:ultraplan "objective"` | Start inline ultraplan workflow | -| `:multiplan "objective"` | Multi-pass planning (requires experimental flag) | +| `:multiplan "objective"` | Multi-pass planning | | `:tripleshot "task"` | Start tripleshot execution (aliases: `:triple`, `:3shot`) | | `:adversarial-retry` | Restart stuck adversarial role | | `:group create [name]` | Create a new instance group | @@ -302,11 +302,7 @@ adversarial: # Experimental features experimental: - intelligent_naming: false - triple_shot: false - inline_plan: false - inline_ultraplan: false - grouped_instance_view: false + subprocess_mode: false ``` ### Environment Variables diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index a48fa05c..7ec66e63 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -361,31 +361,15 @@ Enable experimental features (disabled by default). ```yaml experimental: - # Use Claude (Anthropic API) to generate descriptive instance names - intelligent_naming: false - - # Spawn 3 parallel attempts, judge selects best - triple_shot: false - - # Enable :multiplan command in TUI - inline_plan: false - - # Enable :ultraplan command in TUI - inline_ultraplan: false - - # Visual group organization in sidebar - grouped_instance_view: false + # Use stream-json subprocess backend instead of tmux + subprocess_mode: false ``` **Feature Descriptions:** | Feature | Description | |---------|-------------| -| `intelligent_naming` | Claude (Anthropic API) generates short names for instances based on task and output | -| `triple_shot` | Three parallel attempts per task with judge selection | -| `inline_plan` | Enables `:multiplan` command (`:plan` always available) | -| `inline_ultraplan` | Enables `:ultraplan` command in standard TUI | -| `grouped_instance_view` | Organizes instances by group in sidebar | +| `subprocess_mode` | Uses the stream-json subprocess backend instead of the default tmux backend | --- diff --git a/docs/guide/inline-planning.md b/docs/guide/inline-planning.md index 2cdd4fbf..eefe2ac4 100644 --- a/docs/guide/inline-planning.md +++ b/docs/guide/inline-planning.md @@ -1,9 +1,7 @@ -# Inline Planning (Experimental) +# Inline Planning Inline planning brings the power of Plan and UltraPlan workflows directly into the standard Claudio TUI. Instead of running a separate command, you can create plans, organize tasks into groups, and execute them all from within your normal session. -> **Note:** This feature is experimental. Enable it via the `experimental.inline_plan` and `experimental.inline_ultraplan` configuration options. - ## Overview ``` @@ -19,30 +17,6 @@ Inline planning brings the power of Plan and UltraPlan workflows directly into t └─────────────────────────────────────────────────────────────────────┘ ``` -## Enabling Inline Planning - -Add these options to your config file (`~/.config/claudio/config.yaml`): - -```yaml -experimental: - # Enable :plan command in TUI - inline_plan: true - - # Enable :ultraplan command in TUI - inline_ultraplan: true - - # Enable visual group organization in sidebar - grouped_instance_view: true -``` - -Or set via environment variables: - -```bash -export CLAUDIO_EXPERIMENTAL_INLINE_PLAN=true -export CLAUDIO_EXPERIMENTAL_INLINE_ULTRAPLAN=true -export CLAUDIO_EXPERIMENTAL_GROUPED_INSTANCE_VIEW=true -``` - ## Quick Start ```bash @@ -307,7 +281,6 @@ Plans use JSON format compatible with the `claudio plan` command: ### Groups not displaying -- Ensure `experimental.grouped_instance_view` is enabled - Toggle with `:group show` command - Check that instances have group assignments diff --git a/docs/guide/tui-navigation.md b/docs/guide/tui-navigation.md index eb4ec914..5b5fc4ed 100644 --- a/docs/guide/tui-navigation.md +++ b/docs/guide/tui-navigation.md @@ -164,7 +164,7 @@ Press `:` to enter command mode for advanced operations. Type a command and pres | `:D` | Remove selected instance (with confirmation) | | `:plan "objective"` | Start inline plan generation | | `:ultraplan "objective"` | Start inline UltraPlan workflow | -| `:multiplan "objective"` | Multi-pass planning (requires `experimental.inline_plan`) | +| `:multiplan "objective"` | Multi-pass planning | | `:tripleshot "task"` | Start TripleShot execution (aliases: `:triple`, `:3shot`) | | `:adversarial-retry` | Restart a stuck adversarial implementer or reviewer | | `:group create [name]` | Create a new instance group | @@ -188,21 +188,6 @@ This starts the inline planning workflow: 3. You review and edit tasks in the plan editor 4. Confirm to spawn instances organized by groups -### Enabling Experimental Commands - -Some commands require experimental flags in your config: - -```yaml -# ~/.config/claudio/config.yaml -experimental: - inline_plan: true # Enables :multiplan - inline_ultraplan: true # Enables :ultraplan - triple_shot: true # Enables :tripleshot - grouped_instance_view: true # Enables :group commands -``` - ---- - ## Adding Instances Press `a` to add a new instance: diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b8f22c79..d2c89366 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -509,29 +509,17 @@ Controls experimental features that may change or be removed. These features are | Key | Type | Default | Description | |-----|------|---------|-------------| -| `experimental.intelligent_naming` | bool | `false` | Use Claude (Anthropic API) to generate short, descriptive instance names | -| `experimental.triple_shot` | bool | `false` | Spawn three parallel instances and select the best solution | -| `experimental.inline_plan` | bool | `false` | Enable `:multiplan` command in the TUI (`:plan` is always available) | -| `experimental.inline_ultraplan` | bool | `false` | Enable `:ultraplan` command in the TUI | -| `experimental.grouped_instance_view` | bool | `false` | Enable visual group organization in the sidebar | +| `experimental.subprocess_mode` | bool | `false` | Use stream-json subprocess backend instead of tmux | **Feature descriptions:** | Feature | Description | |---------|-------------| -| `intelligent_naming` | Uses Claude (Anthropic API) to generate short, descriptive instance names for the sidebar based on the task and Claude's initial output. Requires `ANTHROPIC_API_KEY`. | -| `triple_shot` | Spawns three parallel instances working on the same problem, then uses a judge instance to evaluate and select the best solution. | -| `inline_plan` | Enables the `:multiplan` command in the standard TUI for multi-pass planning with 3 planners + assessor. The `:plan` command is always available without this setting. | -| `inline_ultraplan` | Enables the `:ultraplan` command in the standard TUI, allowing you to start an UltraPlan workflow with parallel task execution. | -| `grouped_instance_view` | Organizes instances visually by execution group in the TUI sidebar. Related tasks are grouped together, with sub-groups for dependency chains. | +| `subprocess_mode` | Uses the stream-json subprocess backend instead of the default tmux backend for instance execution. | ```yaml experimental: - intelligent_naming: false - triple_shot: false - inline_plan: false # enables :multiplan; :plan is always available - inline_ultraplan: false - grouped_instance_view: false + subprocess_mode: false ``` See the [Inline Planning Guide](../guide/inline-planning.md) for detailed usage of inline planning features. @@ -556,9 +544,7 @@ Replace dots with underscores and use uppercase: | `resources.cost_limit` | `CLAUDIO_RESOURCES_COST_LIMIT` | | `ultraplan.max_parallel` | `CLAUDIO_ULTRAPLAN_MAX_PARALLEL` | | `paths.worktree_dir` | `CLAUDIO_PATHS_WORKTREE_DIR` | -| `experimental.inline_plan` | `CLAUDIO_EXPERIMENTAL_INLINE_PLAN` | -| `experimental.inline_ultraplan` | `CLAUDIO_EXPERIMENTAL_INLINE_ULTRAPLAN` | -| `experimental.grouped_instance_view` | `CLAUDIO_EXPERIMENTAL_GROUPED_INSTANCE_VIEW` | +| `experimental.subprocess_mode` | `CLAUDIO_EXPERIMENTAL_SUBPROCESS_MODE` | **Priority:** Environment variables override config file values. @@ -681,11 +667,7 @@ ultraplan: # Experimental features (disabled by default) experimental: - intelligent_naming: false - triple_shot: false - inline_plan: false - inline_ultraplan: false - grouped_instance_view: false + subprocess_mode: false ``` --- diff --git a/docs/reference/keyboard-shortcuts.md b/docs/reference/keyboard-shortcuts.md index 82f69c04..c1437147 100644 --- a/docs/reference/keyboard-shortcuts.md +++ b/docs/reference/keyboard-shortcuts.md @@ -56,8 +56,6 @@ These shortcuts use a vim-style `g` prefix. Press `g` first, then the action key | `gr` | Retry failed tasks in current group | | `gf` | Force-start next group (ignore dependencies) | -> **Note:** Group commands require `experimental.grouped_instance_view: true` in your config. - ## Views | Key | Action | diff --git a/internal/config/config.go b/internal/config/config.go index 251f020f..00930279 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -370,28 +370,10 @@ func (s *SparseCheckoutConfig) GetSparseDirectories() []string { // ExperimentalConfig controls experimental features that may change or be removed type ExperimentalConfig struct { - // IntelligentNaming uses Claude to generate short, descriptive instance names - // for the sidebar based on the task and Claude's initial output. - // Requires ANTHROPIC_API_KEY to be set. (default: false) - IntelligentNaming bool `mapstructure:"intelligent_naming"` - - // TerminalSupport enables the embedded terminal pane feature. - // When enabled, :term, :t, and :termdir commands become available - // to interact with a terminal inside Claudio. (default: false) - TerminalSupport bool `mapstructure:"terminal_support"` - - // InlinePlan enables the :plan command in the standard TUI, allowing users to - // start a Plan workflow directly from the main interface. (default: false) - InlinePlan bool `mapstructure:"inline_plan"` - - // InlineUltraPlan enables the :ultraplan command in the standard TUI, allowing - // users to start an UltraPlan workflow directly from the main interface. (default: false) - InlineUltraPlan bool `mapstructure:"inline_ultraplan"` - - // GroupedInstanceView enables visual grouping of instances by execution group - // in the TUI sidebar. Related tasks are organized together, with sub-groups - // for dependency chains. (default: false) - GroupedInstanceView bool `mapstructure:"grouped_instance_view"` + // SubprocessMode uses direct subprocess execution (claude --print --output-format stream-json) + // instead of tmux sessions for pipeline instances. This replaces screen scraping with typed + // NDJSON event parsing for more reliable completion detection. (default: false) + SubprocessMode bool `mapstructure:"subprocess_mode"` } // ResolveWorktreeDir returns the resolved worktree directory path. @@ -535,11 +517,7 @@ func Default() *Config { }, }, Experimental: ExperimentalConfig{ - IntelligentNaming: false, // Disabled by default until stable - TerminalSupport: false, // Disabled by default until stable - InlinePlan: false, // Controls :multiplan only; :plan is always available - InlineUltraPlan: false, // Disabled by default until stable - GroupedInstanceView: false, // Disabled by default until stable + SubprocessMode: false, // Disabled by default until stable }, } } @@ -667,11 +645,7 @@ func SetDefaults() { viper.SetDefault("paths.sparse_checkout.cone_mode", defaults.Paths.SparseCheckout.ConeMode) // Experimental defaults - viper.SetDefault("experimental.intelligent_naming", defaults.Experimental.IntelligentNaming) - viper.SetDefault("experimental.terminal_support", defaults.Experimental.TerminalSupport) - viper.SetDefault("experimental.inline_plan", defaults.Experimental.InlinePlan) - viper.SetDefault("experimental.inline_ultraplan", defaults.Experimental.InlineUltraPlan) - viper.SetDefault("experimental.grouped_instance_view", defaults.Experimental.GroupedInstanceView) + viper.SetDefault("experimental.subprocess_mode", defaults.Experimental.SubprocessMode) } // Load reads the configuration from viper into a Config struct and validates it diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index fd8193eb..f336f7e4 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -1532,19 +1532,9 @@ func (o *Orchestrator) initBudgetManager() { } // initNamer initializes the intelligent naming service. -// This is optional - requires both: -// 1. experimental.intelligent_naming config set to true -// 2. ANTHROPIC_API_KEY environment variable set -// If disabled or API key not set, instances use their original task as the display name. +// This is optional - requires ANTHROPIC_API_KEY environment variable set. +// If API key not set, instances use their original task as the display name. func (o *Orchestrator) initNamer() { - // Check if intelligent naming is enabled in config - if o.config == nil || !o.config.Experimental.IntelligentNaming { - if o.logger != nil { - o.logger.Debug("intelligent naming disabled via config") - } - return - } - client, err := namer.NewAnthropicClient() if err != nil { // API key not set or other issue - namer won't be available diff --git a/internal/orchestrator/session.go b/internal/orchestrator/session.go index 4b755378..96fcb9bd 100644 --- a/internal/orchestrator/session.go +++ b/internal/orchestrator/session.go @@ -131,9 +131,9 @@ type Session struct { Instances []*Instance `json:"instances"` // Groups holds optional visual groupings of instances for the TUI. - // When GroupedInstanceView is enabled, instances are organized into groups - // rather than displayed as a flat list. Groups can have sub-groups for - // representing nested dependencies (e.g., in Plan/UltraPlan workflows). + // Instances are organized into groups rather than displayed as a flat list. + // Groups can have sub-groups for representing nested dependencies + // (e.g., in Plan/UltraPlan workflows). // IMPORTANT: Always use thread-safe accessor methods (GetGroups, AddGroup, etc.) // instead of direct access to avoid race conditions with TUI rendering. Groups []*InstanceGroup `json:"groups,omitempty"` diff --git a/internal/orchestrator/session/types.go b/internal/orchestrator/session/types.go index 852c7fb3..9dbc0559 100644 --- a/internal/orchestrator/session/types.go +++ b/internal/orchestrator/session/types.go @@ -24,9 +24,9 @@ type SessionData struct { Instances []*InstanceData `json:"instances"` // Groups holds optional visual groupings of instances for the TUI. - // When GroupedInstanceView is enabled, instances are organized into groups - // rather than displayed as a flat list. Groups can have sub-groups for - // representing nested dependencies (e.g., in Plan/UltraPlan workflows). + // Instances are organized into groups rather than displayed as a flat list. + // Groups can have sub-groups for representing nested dependencies + // (e.g., in Plan/UltraPlan workflows). Groups []*InstanceGroup `json:"groups,omitempty"` // UltraPlan holds the ultra-plan session state (nil for regular sessions) diff --git a/internal/tui/command/handler.go b/internal/tui/command/handler.go index 0b1b46f7..597c589a 100644 --- a/internal/tui/command/handler.go +++ b/internal/tui/command/handler.go @@ -15,7 +15,6 @@ import ( "github.com/Iron-Ham/claudio/internal/orchestrator/workflows/tripleshot" "github.com/Iron-Ham/claudio/internal/tui/msg" tea "github.com/charmbracelet/bubbletea" - "github.com/spf13/viper" ) // Dependencies defines the interface for dependencies that the CommandHandler needs. @@ -777,25 +776,11 @@ func cmdFilter(_ Dependencies) Result { return Result{FilterMode: &filterMode} } -// terminalDisabledError is the error message when terminal support is disabled. -const terminalDisabledError = "Terminal support is disabled. Enable it in :config under Experimental" - -// isTerminalEnabled checks if the experimental terminal support feature is enabled. -func isTerminalEnabled() bool { - return viper.GetBool("experimental.terminal_support") -} - func cmdTerminal(_ Dependencies) Result { - if !isTerminalEnabled() { - return Result{ErrorMessage: terminalDisabledError} - } return Result{ToggleTerminal: true} } func cmdTerminalFocus(deps Dependencies) Result { - if !isTerminalEnabled() { - return Result{ErrorMessage: terminalDisabledError} - } if deps.IsTerminalVisible() { return Result{ EnterTerminalMode: true, @@ -806,17 +791,11 @@ func cmdTerminalFocus(deps Dependencies) Result { } func cmdTerminalDirWorktree(_ Dependencies) Result { - if !isTerminalEnabled() { - return Result{ErrorMessage: terminalDisabledError} - } mode := 1 // TerminalDirWorktree return Result{TerminalDirMode: &mode} } func cmdTerminalDirProject(_ Dependencies) Result { - if !isTerminalEnabled() { - return Result{ErrorMessage: terminalDisabledError} - } mode := 0 // TerminalDirProject (the directory where Claudio was started) return Result{TerminalDirMode: &mode} } @@ -1099,11 +1078,6 @@ func cmdPlan(deps Dependencies) Result { } func cmdMultiPlan(deps Dependencies) Result { - // Check if inline plan is enabled in config (multiplan remains experimental) - if !viper.GetBool("experimental.inline_plan") { - return Result{ErrorMessage: "MultiPlan mode is disabled. Enable it in :config under Experimental"} - } - // Don't allow starting multiplan mode if already in ultraplan mode if deps.IsUltraPlanMode() { return Result{ErrorMessage: "Cannot start multiplan mode while in ultraplan mode"} @@ -1126,11 +1100,6 @@ func cmdMultiPlan(deps Dependencies) Result { // - :ultraplan --multi-pass [obj] - Use multi-pass planning // - :ultraplan --plan - Load existing plan file func cmdUltraPlan(deps Dependencies, args string) Result { - // Check if inline ultraplan is enabled in config - if !viper.GetBool("experimental.inline_ultraplan") { - return Result{ErrorMessage: "UltraPlan mode is disabled. Enable it in :config under Experimental"} - } - // Don't allow starting another ultraplan if already in ultraplan mode if deps.IsUltraPlanMode() { return Result{ErrorMessage: "Already in ultraplan mode"} diff --git a/internal/tui/command/handler_test.go b/internal/tui/command/handler_test.go index 36663b9d..3dfda3ed 100644 --- a/internal/tui/command/handler_test.go +++ b/internal/tui/command/handler_test.go @@ -728,10 +728,6 @@ func TestDiffCommand(t *testing.T) { } func TestTerminalCommands(t *testing.T) { - // Terminal commands require experimental.terminal_support to be enabled - viper.Set("experimental.terminal_support", true) - defer viper.Set("experimental.terminal_support", false) - t.Run("term toggles terminal visibility", func(t *testing.T) { h := New() deps := newMockDeps() @@ -831,29 +827,6 @@ func TestTerminalCommands(t *testing.T) { }) } -func TestTerminalCommandsDisabled(t *testing.T) { - // When terminal support is disabled, commands should return an error - viper.Set("experimental.terminal_support", false) - - commands := []string{"term", "terminal", "t", "termdir worktree", "termdir wt", "termdir project", "termdir proj", "termdir invoke", "termdir invocation"} - - for _, cmd := range commands { - t.Run(cmd, func(t *testing.T) { - h := New() - deps := newMockDeps() - deps.terminalVisible = true // Even with terminal visible, should fail - - result := h.Execute(cmd, deps) - if result.ErrorMessage == "" { - t.Error("expected error message when terminal support is disabled") - } - if result.ToggleTerminal || result.EnterTerminalMode || result.TerminalDirMode != nil { - t.Error("expected no terminal state changes when disabled") - } - }) - } -} - func TestInstanceControlCommandsNoInstance(t *testing.T) { // All instance control commands should return "No instance selected" when no instance commands := []string{ @@ -1723,33 +1696,9 @@ func TestPlanCommand(t *testing.T) { }) } -// TestMultiPlanCommand tests the multiplan command with config check (remains experimental) +// TestMultiPlanCommand tests the multiplan command func TestMultiPlanCommand(t *testing.T) { - t.Run("disabled by default", func(t *testing.T) { - // Reset viper to ensure clean state - viper.Reset() - - h := New() - deps := newMockDeps() - - result := h.Execute("multiplan", deps) - - if result.ErrorMessage == "" { - t.Error("expected error when multiplan mode is disabled") - } - if result.ErrorMessage != "MultiPlan mode is disabled. Enable it in :config under Experimental" { - t.Errorf("unexpected error message: %q", result.ErrorMessage) - } - if result.StartMultiPlanMode != nil { - t.Error("StartMultiPlanMode should be nil when disabled") - } - }) - - t.Run("enabled via config", func(t *testing.T) { - // Reset and enable plan mode - viper.Reset() - viper.Set("experimental.inline_plan", true) - + t.Run("starts multiplan mode", func(t *testing.T) { h := New() deps := newMockDeps() @@ -1764,15 +1713,9 @@ func TestMultiPlanCommand(t *testing.T) { if result.InfoMessage != "Enter an objective for multiplan mode (3 planners + 1 assessor)" { t.Errorf("unexpected info message: %q", result.InfoMessage) } - - // Clean up - viper.Reset() }) t.Run("mp alias works", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_plan", true) - h := New() deps := newMockDeps() @@ -1784,14 +1727,9 @@ func TestMultiPlanCommand(t *testing.T) { if result.StartMultiPlanMode == nil || !*result.StartMultiPlanMode { t.Error("expected StartMultiPlanMode to be true with mp alias") } - - viper.Reset() }) t.Run("blocked in ultraplan mode", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_plan", true) - h := New() deps := newMockDeps() deps.ultraPlanMode = true @@ -1804,14 +1742,9 @@ func TestMultiPlanCommand(t *testing.T) { if result.StartMultiPlanMode != nil { t.Error("StartMultiPlanMode should be nil when blocked") } - - viper.Reset() }) t.Run("allowed when in triple-shot mode", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_plan", true) - h := New() deps := newMockDeps() deps.tripleShotMode = true @@ -1825,37 +1758,12 @@ func TestMultiPlanCommand(t *testing.T) { if result.StartMultiPlanMode == nil || !*result.StartMultiPlanMode { t.Error("StartMultiPlanMode should be true when in triple-shot mode") } - - viper.Reset() }) } -// TestUltraPlanCommand tests the ultraplan command with config check and argument parsing +// TestUltraPlanCommand tests the ultraplan command argument parsing func TestUltraPlanCommand(t *testing.T) { - t.Run("disabled by default", func(t *testing.T) { - // Reset viper to ensure clean state - viper.Reset() - - h := New() - deps := newMockDeps() - - result := h.Execute("ultraplan", deps) - - if result.ErrorMessage == "" { - t.Error("expected error when ultraplan mode is disabled") - } - if result.ErrorMessage != "UltraPlan mode is disabled. Enable it in :config under Experimental" { - t.Errorf("unexpected error message: %q", result.ErrorMessage) - } - if result.StartUltraPlanMode != nil { - t.Error("StartUltraPlanMode should be nil when disabled") - } - }) - - t.Run("enabled via config without objective", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - + t.Run("without objective", func(t *testing.T) { h := New() deps := newMockDeps() @@ -1876,14 +1784,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.UltraPlanFromFile != nil { t.Error("UltraPlanFromFile should be nil without --plan flag") } - - viper.Reset() }) - t.Run("enabled with objective", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - + t.Run("with objective", func(t *testing.T) { h := New() deps := newMockDeps() @@ -1901,14 +1804,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.InfoMessage != "Starting ultraplan: Add user authentication" { t.Errorf("unexpected info message: %q", result.InfoMessage) } - - viper.Reset() }) t.Run("multi-pass flag", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - h := New() deps := newMockDeps() @@ -1926,14 +1824,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.UltraPlanObjective == nil || *result.UltraPlanObjective != "Implement new feature" { t.Errorf("expected objective 'Implement new feature', got: %v", result.UltraPlanObjective) } - - viper.Reset() }) t.Run("plan flag with file", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - h := New() deps := newMockDeps() @@ -1951,14 +1844,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.InfoMessage != "Loading ultraplan from: /path/to/plan.json" { t.Errorf("unexpected info message: %q", result.InfoMessage) } - - viper.Reset() }) t.Run("plan flag without file", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - h := New() deps := newMockDeps() @@ -1967,14 +1855,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.ErrorMessage != "Usage: :ultraplan --plan " { t.Errorf("expected usage error, got: %q", result.ErrorMessage) } - - viper.Reset() }) t.Run("blocked in ultraplan mode", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - h := New() deps := newMockDeps() deps.ultraPlanMode = true @@ -1987,14 +1870,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.StartUltraPlanMode != nil { t.Error("StartUltraPlanMode should be nil when blocked") } - - viper.Reset() }) t.Run("blocked when in triple-shot mode", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - h := New() deps := newMockDeps() deps.tripleShotMode = true @@ -2008,14 +1886,9 @@ func TestUltraPlanCommand(t *testing.T) { if result.StartUltraPlanMode != nil { t.Error("StartUltraPlanMode should be nil when blocked") } - - viper.Reset() }) t.Run("up alias works", func(t *testing.T) { - viper.Reset() - viper.Set("experimental.inline_ultraplan", true) - h := New() deps := newMockDeps() @@ -2030,8 +1903,6 @@ func TestUltraPlanCommand(t *testing.T) { if result.UltraPlanObjective == nil || *result.UltraPlanObjective != "my objective" { t.Errorf("expected objective 'my objective', got: %v", result.UltraPlanObjective) } - - viper.Reset() }) } diff --git a/internal/tui/command_test.go b/internal/tui/command_test.go index 9a930ea7..6afd3541 100644 --- a/internal/tui/command_test.go +++ b/internal/tui/command_test.go @@ -7,7 +7,6 @@ import ( "github.com/Iron-Ham/claudio/internal/tui/command" "github.com/Iron-Ham/claudio/internal/tui/terminal" tea "github.com/charmbracelet/bubbletea" - "github.com/spf13/viper" ) // testModel creates a Model with the commandHandler and terminalManager initialized for testing. @@ -550,10 +549,6 @@ func TestCommandAliasesRequiringInstance(t *testing.T) { } func TestTerminalFocusCommand(t *testing.T) { - // Terminal commands require experimental.terminal_support to be enabled - viper.Set("experimental.terminal_support", true) - defer viper.Set("experimental.terminal_support", false) - t.Run("t command attempts focus when terminal visible", func(t *testing.T) { // Note: enterTerminalMode() requires a running terminal process to actually // set focused=true. This test verifies the command path is correct. diff --git a/internal/tui/config/config.go b/internal/tui/config/config.go index 4fc5e795..b5003419 100644 --- a/internal/tui/config/config.go +++ b/internal/tui/config/config.go @@ -644,37 +644,9 @@ func New() Model { Name: "Experimental", Items: []ConfigItem{ { - Key: "experimental.intelligent_naming", - Label: "Intelligent Naming", - Description: "Use Claude to generate short, descriptive instance names (requires ANTHROPIC_API_KEY)", - Type: "bool", - Category: "experimental", - }, - { - Key: "experimental.terminal_support", - Label: "Terminal Support", - Description: "Enable embedded terminal pane (:term, :t, :termdir commands)", - Type: "bool", - Category: "experimental", - }, - { - Key: "experimental.inline_plan", - Label: "Inline MultiPlan", - Description: "Enable :multiplan command for multi-pass planning with 3 planners + assessor", - Type: "bool", - Category: "experimental", - }, - { - Key: "experimental.inline_ultraplan", - Label: "Inline UltraPlan", - Description: "Enable :ultraplan command in standard TUI to start UltraPlan workflows", - Type: "bool", - Category: "experimental", - }, - { - Key: "experimental.grouped_instance_view", - Label: "Grouped Instance View", - Description: "Enable visual grouping of instances by execution group in the sidebar", + Key: "experimental.subprocess_mode", + Label: "Subprocess Mode", + Description: "Use direct subprocess execution instead of tmux for pipeline instances", Type: "bool", Category: "experimental", }, @@ -1351,11 +1323,7 @@ func (m *Model) resetCurrentToDefault() { "logging.max_size_mb": defaults.Logging.MaxSizeMB, "logging.max_backups": defaults.Logging.MaxBackups, // Experimental - "experimental.intelligent_naming": defaults.Experimental.IntelligentNaming, - "experimental.terminal_support": defaults.Experimental.TerminalSupport, - "experimental.inline_plan": defaults.Experimental.InlinePlan, - "experimental.inline_ultraplan": defaults.Experimental.InlineUltraPlan, - "experimental.grouped_instance_view": defaults.Experimental.GroupedInstanceView, + "experimental.subprocess_mode": defaults.Experimental.SubprocessMode, } if defaultVal, ok := defaultValues[item.Key]; ok { diff --git a/internal/tui/keyhandler.go b/internal/tui/keyhandler.go index 87c0b80b..d50afa7a 100644 --- a/internal/tui/keyhandler.go +++ b/internal/tui/keyhandler.go @@ -9,7 +9,6 @@ import ( tuimsg "github.com/Iron-Ham/claudio/internal/tui/msg" "github.com/Iron-Ham/claudio/internal/tui/view" tea "github.com/charmbracelet/bubbletea" - "github.com/spf13/viper" ) // ----------------------------------------------------------------------------- @@ -686,10 +685,6 @@ func (m Model) handleEnterInputMode() (tea.Model, tea.Cmd) { // handleToggleTerminal toggles terminal pane visibility. func (m Model) handleToggleTerminal() (tea.Model, tea.Cmd) { - // Check if terminal support is enabled - if !viper.GetBool("experimental.terminal_support") { - return m, nil - } sessionID := "" if m.orchestrator != nil { sessionID = m.orchestrator.SessionID() @@ -700,10 +695,6 @@ func (m Model) handleToggleTerminal() (tea.Model, tea.Cmd) { // handleSwitchTerminalDir switches terminal directory mode. func (m Model) handleSwitchTerminalDir() (tea.Model, tea.Cmd) { - // Check if terminal support is enabled - if !viper.GetBool("experimental.terminal_support") { - return m, nil - } if m.terminalManager.IsVisible() { m.switchTerminalDir() } diff --git a/internal/tui/model.go b/internal/tui/model.go index e7ce1e89..f2f9626e 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -1325,10 +1325,11 @@ func (m Model) IsAddingTask() bool { return m.addingTask } -// IntelligentNamingEnabled returns whether intelligent naming is enabled in config +// IntelligentNamingEnabled returns whether intelligent naming is enabled. +// Always returns true — intelligent naming is now a default feature. +// Actual name generation still requires ANTHROPIC_API_KEY to be set. func (m Model) IntelligentNamingEnabled() bool { - cfg := config.Get() - return cfg.Experimental.IntelligentNaming + return true } // ----------------------------------------------------------------------------- diff --git a/internal/tui/terminal_test.go b/internal/tui/terminal_test.go index 52ef48a4..e958be03 100644 --- a/internal/tui/terminal_test.go +++ b/internal/tui/terminal_test.go @@ -4,8 +4,6 @@ import ( "testing" "github.com/Iron-Ham/claudio/internal/tui/terminal" - tea "github.com/charmbracelet/bubbletea" - "github.com/spf13/viper" ) func TestTerminalHeightConstants(t *testing.T) { @@ -338,100 +336,3 @@ func TestGetTerminalDir(t *testing.T) { }) } } - -func TestTerminalKeybindingsDisabled(t *testing.T) { - // Save original value and restore after test - originalValue := viper.GetBool("experimental.terminal_support") - defer viper.Set("experimental.terminal_support", originalValue) - - // Disable terminal support - viper.Set("experimental.terminal_support", false) - - t.Run("backtick key does nothing when terminal disabled", func(t *testing.T) { - m := Model{ - terminalManager: terminal.NewManager(), - } - - // Verify terminal is not visible initially - if m.IsTerminalVisible() { - t.Fatal("terminal should not be visible initially") - } - - // Call handleToggleTerminal (triggered by backtick key) - newModel, _ := m.handleToggleTerminal() - resultModel := newModel.(Model) - - // Verify terminal is still not visible - if resultModel.IsTerminalVisible() { - t.Error("terminal should remain hidden when terminal support is disabled") - } - }) - - t.Run("T key does nothing when terminal disabled", func(t *testing.T) { - m := Model{ - terminalManager: terminal.NewManager(), - } - - // Verify terminal is not visible initially - if m.IsTerminalVisible() { - t.Fatal("terminal should not be visible initially") - } - - // Call handleToggleTerminal (triggered by T key) - newModel, _ := m.handleToggleTerminal() - resultModel := newModel.(Model) - - // Verify terminal is still not visible - if resultModel.IsTerminalVisible() { - t.Error("terminal should remain hidden when terminal support is disabled") - } - }) - - t.Run("ctrl+shift+t does nothing when terminal disabled", func(t *testing.T) { - m := Model{ - terminalManager: terminal.NewManager(), - } - // Force terminal to be visible to test the switch dir function - m.terminalManager.SetLayout(terminal.LayoutVisible) - - // Call handleSwitchTerminalDir (triggered by ctrl+shift+t) - newModel, _ := m.handleSwitchTerminalDir() - resultModel := newModel.(Model) - - // Verify no error occurred and model is returned unchanged - if resultModel.errorMessage != "" { - t.Errorf("unexpected error message: %s", resultModel.errorMessage) - } - }) -} - -func TestHandleNormalModeKeyTerminalDisabled(t *testing.T) { - // Save original value and restore after test - originalValue := viper.GetBool("experimental.terminal_support") - defer viper.Set("experimental.terminal_support", originalValue) - - // Disable terminal support - viper.Set("experimental.terminal_support", false) - - t.Run("backtick key in normal mode does nothing when terminal disabled", func(t *testing.T) { - m := Model{ - terminalManager: terminal.NewManager(), - inputMode: false, // Normal mode - } - - // Create backtick key message - keyMsg := tea.KeyMsg{ - Type: tea.KeyRunes, - Runes: []rune{'`'}, - } - - // Handle the key in normal mode - newModel, _ := m.handleNormalModeKey(keyMsg) - resultModel := newModel.(Model) - - // Verify terminal is still not visible - if resultModel.IsTerminalVisible() { - t.Error("terminal should remain hidden when terminal support is disabled") - } - }) -}