From 09fe54e74b465eae017a881c0ea6e70b54254ae3 Mon Sep 17 00:00:00 2001 From: Hesham Salman Date: Fri, 20 Mar 2026 18:01:14 -0400 Subject: [PATCH] feat: graduate experimental features to default Ship intelligent naming, terminal support, inline multiplan, inline ultraplan, and grouped instance view as default features. Only subprocess mode remains experimental. Remove config struct fields, viper registrations, feature gates, disabled-state tests, and update all documentation. Also clean up stale triple_shot references from docs. --- CHANGELOG.md | 1 + README.md | 8 +- docs/guide/configuration.md | 22 +--- docs/guide/inline-planning.md | 29 +----- docs/guide/tui-navigation.md | 17 +-- docs/reference/configuration.md | 28 +---- docs/reference/keyboard-shortcuts.md | 2 - internal/config/config.go | 38 ++----- internal/orchestrator/orchestrator.go | 14 +-- internal/orchestrator/session.go | 6 +- internal/orchestrator/session/types.go | 6 +- internal/tui/command/handler.go | 31 ------ internal/tui/command/handler_test.go | 139 +------------------------ internal/tui/command_test.go | 5 - internal/tui/config/config.go | 40 +------ internal/tui/keyhandler.go | 9 -- internal/tui/model.go | 7 +- internal/tui/terminal_test.go | 99 ------------------ 18 files changed, 40 insertions(+), 461 deletions(-) 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") - } - }) -}