From 1ff5949705c98eaa6b911a2c5676422c05b12771 Mon Sep 17 00:00:00 2001 From: Gilles Major Date: Wed, 21 Oct 2026 22:25:31 +0200 Subject: [PATCH] feat(cli): restore the `pair` command the pair-programming skill depends on (#3341) * feat(cli): restore the `pair` command the pair-programming skill depends on The bundled pair-programming skill tells Claude Code to run `claude-flow pair --start --mode tdd`, but the v3 CLI never shipped `pair` (it was a v2 simple-command), so `ruflo pair` fails with "Unknown command" and none of the skill's modes can be activated or switched. Add a minimal, non-interactive `ruflo pair` that keeps session state in `.claude-flow/sessions/pair/current.json`: start/status/switch/mode/end/ history, driver/navigator roles, switch-mode interval with a `switchDue` signal. The v2 spellings (`pair --start`, `--end`, `--history`) still work. The skill docs now describe how the agent reads `pair status --json` each turn and act on it, and point at the new syntax. Co-Authored-By: Claude Fable 5.1 * feat(cli): accept the pair mode as a bare word `ruflo pair start tdd` silently started driver mode because only --mode was read. A positional now wins over the option default. Co-Authored-By: Claude Fable 5.1 --------- Co-authored-by: Gilles Major Co-authored-by: Claude Fable 5.1 Co-authored-by: rUv --- .agents/skills/pair-programming/SKILL.md | 133 ++++---- .claude/skills/pair-programming/SKILL.md | 133 ++++---- .../.claude/skills/pair-programming/SKILL.md | 133 ++++---- .../cli/__tests__/pair-command.test.ts | 187 ++++++++++ v3/@claude-flow/cli/src/commands/index.ts | 10 +- v3/@claude-flow/cli/src/commands/pair.ts | 320 ++++++++++++++++++ 6 files changed, 730 insertions(+), 186 deletions(-) create mode 100644 v3/@claude-flow/cli/__tests__/pair-command.test.ts create mode 100644 v3/@claude-flow/cli/src/commands/pair.ts diff --git a/.agents/skills/pair-programming/SKILL.md b/.agents/skills/pair-programming/SKILL.md index 623cf2e60b..8fa80d4ec7 100644 --- a/.agents/skills/pair-programming/SKILL.md +++ b/.agents/skills/pair-programming/SKILL.md @@ -19,10 +19,21 @@ This skill provides professional pair programming capabilities with AI assistanc - **Code Review**: Security scanning, performance analysis, best practice enforcement - **Session Persistence**: Auto-save, recovery, export, and sharing +## How Claude Code Uses This + +The CLI holds the session state; Claude Code reads it. There is no REPL to type into. + +1. At the start of every turn run `ruflo pair status --json`. If `active` is `false`, this skill is dormant. +2. Follow `mode` and `aiRole`. As **driver** you write the code and the human reviews. As **navigator** you review and suggest and never edit unless asked. `tdd`, `review`, `mentor` and `debug` refine that behaviour as described below. +3. When `switchDue` is `true`, run `ruflo pair switch`, announce the swap, and continue in the new role. +4. Change mode mid-session with `ruflo pair mode `. The human can run the same commands from their shell. + +State lives in `.claude-flow/sessions/pair/current.json`. `ruflo pair end` archives it to `history.jsonl` next to it. + ## Prerequisites **Required:** -- Claude Flow CLI installed (`npm install -g claude-flow@alpha`) +- Ruflo CLI installed (`npm install -g ruflo`), which provides the `ruflo pair` command - Git repository (optional but recommended) **Recommended:** @@ -35,13 +46,13 @@ This skill provides professional pair programming capabilities with AI assistanc ### Basic Session ```bash # Start simple pair programming -claude-flow pair --start +ruflo pair start ``` ### TDD Session ```bash # Test-driven development -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --test-first \ --coverage 90 @@ -56,23 +67,23 @@ claude-flow pair --start \ #### Starting Sessions ```bash # Basic start -claude-flow pair --start +ruflo pair start # Expert refactoring session -claude-flow pair --start \ +ruflo pair start \ --agent senior-dev \ --focus refactor \ --verify \ --threshold 0.98 # Debugging session -claude-flow pair --start \ +ruflo pair start \ --agent debugger-expert \ --focus debug \ --review # Learning session -claude-flow pair --start \ +ruflo pair start \ --mode mentor \ --pace slow \ --examples @@ -81,10 +92,10 @@ claude-flow pair --start \ #### Session Management ```bash # Check status -claude-flow pair --status +ruflo pair status # View history -claude-flow pair --history +ruflo pair history # Pause session $pause [--reason ] @@ -93,7 +104,7 @@ $pause [--reason ] $resume # End session -claude-flow pair --end [--save] [--report] +ruflo pair end [--save] [--report] ``` ### Available Modes @@ -102,7 +113,7 @@ claude-flow pair --end [--save] [--report] You write code while AI provides guidance. ```bash -claude-flow pair --start --mode driver +ruflo pair start --mode driver ``` **Your Responsibilities:** @@ -137,7 +148,7 @@ $patterns - Get pattern recommendations AI writes code while you provide direction. ```bash -claude-flow pair --start --mode navigator +ruflo pair start --mode navigator ``` **Your Responsibilities:** @@ -173,13 +184,13 @@ Automatically alternates roles at intervals. ```bash # Default 10-minute intervals -claude-flow pair --start --mode switch +ruflo pair start --mode switch # 5-minute intervals (rapid) -claude-flow pair --start --mode switch --interval 5m +ruflo pair start --mode switch --interval 5m # 15-minute intervals (deep focus) -claude-flow pair --start --mode switch --interval 15m +ruflo pair start --mode switch --interval 15m ``` **Handoff Process:** @@ -199,7 +210,7 @@ claude-flow pair --start --mode switch --interval 15m **TDD Mode** - Test-Driven Development: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --test-first \ --coverage 100 @@ -208,7 +219,7 @@ Workflow: Write failing test → Implement → Refactor → Repeat **Review Mode** - Continuous code review: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode review \ --strict \ --security @@ -217,7 +228,7 @@ Features: Real-time feedback, security scanning, performance analysis **Mentor Mode** - Learning-focused: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode mentor \ --explain-all \ --pace slow @@ -226,7 +237,7 @@ Features: Detailed explanations, step-by-step guidance, pattern teaching **Debug Mode** - Problem-solving: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode debug \ --verbose \ --trace @@ -559,19 +570,19 @@ Create `.claude-flow$pair-config.json`: #### CLI Configuration ```bash # Set configuration -claude-flow pair config set defaultMode switch -claude-flow pair config set verification.threshold 0.98 +ruflo pair config set defaultMode switch +ruflo pair config set verification.threshold 0.98 # Get configuration -claude-flow pair config get -claude-flow pair config get defaultMode +ruflo pair config get +ruflo pair config get defaultMode # Export/Import -claude-flow pair config export > config.json -claude-flow pair config import config.json +ruflo pair config export > config.json +ruflo pair config import config.json # Reset -claude-flow pair config reset +ruflo pair config reset ``` #### Profile Management @@ -580,17 +591,17 @@ Create reusable profiles: ```bash # Create profile -claude-flow pair profile create refactoring \ +ruflo pair profile create refactoring \ --mode driver \ --verify true \ --threshold 0.98 \ --focus refactor # Use profile -claude-flow pair --start --profile refactoring +ruflo pair start --profile refactoring # List profiles -claude-flow pair profile list +ruflo pair profile list ``` Profile configuration: @@ -629,7 +640,7 @@ Implementing user authentication with JWT tokens: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode switch \ --agent senior-dev \ --focus implement \ @@ -676,7 +687,7 @@ Debugging a memory leak in Node.js: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode navigator \ --agent debugger-expert \ --focus debug \ @@ -717,7 +728,7 @@ Building shopping cart with test-driven development: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --agent tdd-specialist \ --test-first @@ -759,7 +770,7 @@ Modernizing legacy code: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode driver \ --focus refactor \ --verify \ @@ -807,7 +818,7 @@ Optimizing slow React application: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode switch \ --agent performance-expert \ --focus optimize \ @@ -852,7 +863,7 @@ Building RESTful API with Express: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode navigator \ --agent backend-expert \ --focus implement \ @@ -897,28 +908,28 @@ $test --integration ```bash # Refactoring template -claude-flow pair --template refactor +ruflo pair --template refactor # Focus: Code improvement # Verification: High (0.98) # Testing: After each change # Review: Continuous # Feature template -claude-flow pair --template feature +ruflo pair --template feature # Focus: Implementation # Verification: Standard (0.95) # Testing: On completion # Review: Pre-commit # Debug template -claude-flow pair --template debug +ruflo pair --template debug # Focus: Problem solving # Verification: Moderate (0.90) # Testing: Regression tests # Review: Root cause # Learning template -claude-flow pair --template learn +ruflo pair --template learn # Mode: Mentor # Pace: Slow # Explanations: Detailed @@ -930,7 +941,7 @@ claude-flow pair --template learn #### Session Status ```bash -claude-flow pair --status +ruflo pair status ``` **Output:** @@ -962,7 +973,7 @@ Next Switch: in 3 minutes #### Session History ```bash -claude-flow pair --history +ruflo pair history ``` **Output:** @@ -993,32 +1004,32 @@ claude-flow pair --history ```bash # Save session -claude-flow pair --save [--name ] +ruflo pair --save [--name ] # Load session -claude-flow pair --load +ruflo pair --load # Export session -claude-flow pair --export [--format json|md] +ruflo pair --export [--format json|md] # Generate report -claude-flow pair --report +ruflo pair --report ``` #### Background Sessions ```bash # Start in background -claude-flow pair --start --background +ruflo pair start --background # Monitor background session -claude-flow pair --monitor +ruflo pair --monitor # Attach to background session -claude-flow pair --attach +ruflo pair --attach # End background session -claude-flow pair --end +ruflo pair end ``` ### Advanced Features @@ -1054,30 +1065,30 @@ $lint --fix && $test && $review --strict ```bash # Start with recording -claude-flow pair --start --record +ruflo pair start --record # Replay session -claude-flow pair --replay +ruflo pair --replay # Session analytics -claude-flow pair --analytics +ruflo pair --analytics ``` #### Integration Options **With Git:** ```bash -claude-flow pair --start --git --auto-commit +ruflo pair start --git --auto-commit ``` **With CI/CD:** ```bash -claude-flow pair --start --ci --non-interactive +ruflo pair start --ci --non-interactive ``` **With IDE:** ```bash -claude-flow pair --start --ide vscode +ruflo pair start --ide vscode ``` ### Best Practices @@ -1131,7 +1142,7 @@ claude-flow pair --start --ide vscode - Validate JSON syntax - Check file permissions - Review priority order (CLI > env > project > user > global) -- Run `claude-flow pair config validate` +- Run `ruflo pair config validate` ### Quality Metrics @@ -1195,8 +1206,8 @@ Default shortcuts: ### Related Commands -- `claude-flow pair --help` - Show help -- `claude-flow pair config` - Manage configuration -- `claude-flow pair profile` - Manage profiles -- `claude-flow pair templates` - List templates -- `claude-flow pair agents` - List available agents +- `ruflo pair --help` - Show help +- `ruflo pair config` - Manage configuration +- `ruflo pair profile` - Manage profiles +- `ruflo pair templates` - List templates +- `ruflo pair agents` - List available agents diff --git a/.claude/skills/pair-programming/SKILL.md b/.claude/skills/pair-programming/SKILL.md index 7b667b7a2f..e69ff349e1 100644 --- a/.claude/skills/pair-programming/SKILL.md +++ b/.claude/skills/pair-programming/SKILL.md @@ -19,10 +19,21 @@ This skill provides professional pair programming capabilities with AI assistanc - **Code Review**: Security scanning, performance analysis, best practice enforcement - **Session Persistence**: Auto-save, recovery, export, and sharing +## How Claude Code Uses This + +The CLI holds the session state; Claude Code reads it. There is no REPL to type into. + +1. At the start of every turn run `ruflo pair status --json`. If `active` is `false`, this skill is dormant. +2. Follow `mode` and `aiRole`. As **driver** you write the code and the human reviews. As **navigator** you review and suggest and never edit unless asked. `tdd`, `review`, `mentor` and `debug` refine that behaviour as described below. +3. When `switchDue` is `true`, run `ruflo pair switch`, announce the swap, and continue in the new role. +4. Change mode mid-session with `ruflo pair mode `. The human can run the same commands from their shell. + +State lives in `.claude-flow/sessions/pair/current.json`. `ruflo pair end` archives it to `history.jsonl` next to it. + ## Prerequisites **Required:** -- Claude Flow CLI installed (`npm install -g claude-flow@alpha`) +- Ruflo CLI installed (`npm install -g ruflo`), which provides the `ruflo pair` command - Git repository (optional but recommended) **Recommended:** @@ -35,13 +46,13 @@ This skill provides professional pair programming capabilities with AI assistanc ### Basic Session ```bash # Start simple pair programming -claude-flow pair --start +ruflo pair start ``` ### TDD Session ```bash # Test-driven development -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --test-first \ --coverage 90 @@ -56,23 +67,23 @@ claude-flow pair --start \ #### Starting Sessions ```bash # Basic start -claude-flow pair --start +ruflo pair start # Expert refactoring session -claude-flow pair --start \ +ruflo pair start \ --agent senior-dev \ --focus refactor \ --verify \ --threshold 0.98 # Debugging session -claude-flow pair --start \ +ruflo pair start \ --agent debugger-expert \ --focus debug \ --review # Learning session -claude-flow pair --start \ +ruflo pair start \ --mode mentor \ --pace slow \ --examples @@ -81,10 +92,10 @@ claude-flow pair --start \ #### Session Management ```bash # Check status -claude-flow pair --status +ruflo pair status # View history -claude-flow pair --history +ruflo pair history # Pause session /pause [--reason ] @@ -93,7 +104,7 @@ claude-flow pair --history /resume # End session -claude-flow pair --end [--save] [--report] +ruflo pair end [--save] [--report] ``` ### Available Modes @@ -102,7 +113,7 @@ claude-flow pair --end [--save] [--report] You write code while AI provides guidance. ```bash -claude-flow pair --start --mode driver +ruflo pair start --mode driver ``` **Your Responsibilities:** @@ -137,7 +148,7 @@ claude-flow pair --start --mode driver AI writes code while you provide direction. ```bash -claude-flow pair --start --mode navigator +ruflo pair start --mode navigator ``` **Your Responsibilities:** @@ -173,13 +184,13 @@ Automatically alternates roles at intervals. ```bash # Default 10-minute intervals -claude-flow pair --start --mode switch +ruflo pair start --mode switch # 5-minute intervals (rapid) -claude-flow pair --start --mode switch --interval 5m +ruflo pair start --mode switch --interval 5m # 15-minute intervals (deep focus) -claude-flow pair --start --mode switch --interval 15m +ruflo pair start --mode switch --interval 15m ``` **Handoff Process:** @@ -199,7 +210,7 @@ claude-flow pair --start --mode switch --interval 15m **TDD Mode** - Test-Driven Development: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --test-first \ --coverage 100 @@ -208,7 +219,7 @@ Workflow: Write failing test → Implement → Refactor → Repeat **Review Mode** - Continuous code review: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode review \ --strict \ --security @@ -217,7 +228,7 @@ Features: Real-time feedback, security scanning, performance analysis **Mentor Mode** - Learning-focused: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode mentor \ --explain-all \ --pace slow @@ -226,7 +237,7 @@ Features: Detailed explanations, step-by-step guidance, pattern teaching **Debug Mode** - Problem-solving: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode debug \ --verbose \ --trace @@ -559,19 +570,19 @@ Create `.claude-flow/pair-config.json`: #### CLI Configuration ```bash # Set configuration -claude-flow pair config set defaultMode switch -claude-flow pair config set verification.threshold 0.98 +ruflo pair config set defaultMode switch +ruflo pair config set verification.threshold 0.98 # Get configuration -claude-flow pair config get -claude-flow pair config get defaultMode +ruflo pair config get +ruflo pair config get defaultMode # Export/Import -claude-flow pair config export > config.json -claude-flow pair config import config.json +ruflo pair config export > config.json +ruflo pair config import config.json # Reset -claude-flow pair config reset +ruflo pair config reset ``` #### Profile Management @@ -580,17 +591,17 @@ Create reusable profiles: ```bash # Create profile -claude-flow pair profile create refactoring \ +ruflo pair profile create refactoring \ --mode driver \ --verify true \ --threshold 0.98 \ --focus refactor # Use profile -claude-flow pair --start --profile refactoring +ruflo pair start --profile refactoring # List profiles -claude-flow pair profile list +ruflo pair profile list ``` Profile configuration: @@ -629,7 +640,7 @@ Implementing user authentication with JWT tokens: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode switch \ --agent senior-dev \ --focus implement \ @@ -676,7 +687,7 @@ Debugging a memory leak in Node.js: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode navigator \ --agent debugger-expert \ --focus debug \ @@ -717,7 +728,7 @@ Building shopping cart with test-driven development: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --agent tdd-specialist \ --test-first @@ -759,7 +770,7 @@ Modernizing legacy code: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode driver \ --focus refactor \ --verify \ @@ -807,7 +818,7 @@ Optimizing slow React application: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode switch \ --agent performance-expert \ --focus optimize \ @@ -852,7 +863,7 @@ Building RESTful API with Express: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode navigator \ --agent backend-expert \ --focus implement \ @@ -897,28 +908,28 @@ claude-flow pair --start \ ```bash # Refactoring template -claude-flow pair --template refactor +ruflo pair --template refactor # Focus: Code improvement # Verification: High (0.98) # Testing: After each change # Review: Continuous # Feature template -claude-flow pair --template feature +ruflo pair --template feature # Focus: Implementation # Verification: Standard (0.95) # Testing: On completion # Review: Pre-commit # Debug template -claude-flow pair --template debug +ruflo pair --template debug # Focus: Problem solving # Verification: Moderate (0.90) # Testing: Regression tests # Review: Root cause # Learning template -claude-flow pair --template learn +ruflo pair --template learn # Mode: Mentor # Pace: Slow # Explanations: Detailed @@ -930,7 +941,7 @@ claude-flow pair --template learn #### Session Status ```bash -claude-flow pair --status +ruflo pair status ``` **Output:** @@ -962,7 +973,7 @@ Next Switch: in 3 minutes #### Session History ```bash -claude-flow pair --history +ruflo pair history ``` **Output:** @@ -993,32 +1004,32 @@ claude-flow pair --history ```bash # Save session -claude-flow pair --save [--name ] +ruflo pair --save [--name ] # Load session -claude-flow pair --load +ruflo pair --load # Export session -claude-flow pair --export [--format json|md] +ruflo pair --export [--format json|md] # Generate report -claude-flow pair --report +ruflo pair --report ``` #### Background Sessions ```bash # Start in background -claude-flow pair --start --background +ruflo pair start --background # Monitor background session -claude-flow pair --monitor +ruflo pair --monitor # Attach to background session -claude-flow pair --attach +ruflo pair --attach # End background session -claude-flow pair --end +ruflo pair end ``` ### Advanced Features @@ -1054,30 +1065,30 @@ Use custom commands: ```bash # Start with recording -claude-flow pair --start --record +ruflo pair start --record # Replay session -claude-flow pair --replay +ruflo pair --replay # Session analytics -claude-flow pair --analytics +ruflo pair --analytics ``` #### Integration Options **With Git:** ```bash -claude-flow pair --start --git --auto-commit +ruflo pair start --git --auto-commit ``` **With CI/CD:** ```bash -claude-flow pair --start --ci --non-interactive +ruflo pair start --ci --non-interactive ``` **With IDE:** ```bash -claude-flow pair --start --ide vscode +ruflo pair start --ide vscode ``` ### Best Practices @@ -1131,7 +1142,7 @@ claude-flow pair --start --ide vscode - Validate JSON syntax - Check file permissions - Review priority order (CLI > env > project > user > global) -- Run `claude-flow pair config validate` +- Run `ruflo pair config validate` ### Quality Metrics @@ -1195,8 +1206,8 @@ Default shortcuts: ### Related Commands -- `claude-flow pair --help` - Show help -- `claude-flow pair config` - Manage configuration -- `claude-flow pair profile` - Manage profiles -- `claude-flow pair templates` - List templates -- `claude-flow pair agents` - List available agents +- `ruflo pair --help` - Show help +- `ruflo pair config` - Manage configuration +- `ruflo pair profile` - Manage profiles +- `ruflo pair templates` - List templates +- `ruflo pair agents` - List available agents diff --git a/v3/@claude-flow/cli/.claude/skills/pair-programming/SKILL.md b/v3/@claude-flow/cli/.claude/skills/pair-programming/SKILL.md index 7b667b7a2f..e69ff349e1 100644 --- a/v3/@claude-flow/cli/.claude/skills/pair-programming/SKILL.md +++ b/v3/@claude-flow/cli/.claude/skills/pair-programming/SKILL.md @@ -19,10 +19,21 @@ This skill provides professional pair programming capabilities with AI assistanc - **Code Review**: Security scanning, performance analysis, best practice enforcement - **Session Persistence**: Auto-save, recovery, export, and sharing +## How Claude Code Uses This + +The CLI holds the session state; Claude Code reads it. There is no REPL to type into. + +1. At the start of every turn run `ruflo pair status --json`. If `active` is `false`, this skill is dormant. +2. Follow `mode` and `aiRole`. As **driver** you write the code and the human reviews. As **navigator** you review and suggest and never edit unless asked. `tdd`, `review`, `mentor` and `debug` refine that behaviour as described below. +3. When `switchDue` is `true`, run `ruflo pair switch`, announce the swap, and continue in the new role. +4. Change mode mid-session with `ruflo pair mode `. The human can run the same commands from their shell. + +State lives in `.claude-flow/sessions/pair/current.json`. `ruflo pair end` archives it to `history.jsonl` next to it. + ## Prerequisites **Required:** -- Claude Flow CLI installed (`npm install -g claude-flow@alpha`) +- Ruflo CLI installed (`npm install -g ruflo`), which provides the `ruflo pair` command - Git repository (optional but recommended) **Recommended:** @@ -35,13 +46,13 @@ This skill provides professional pair programming capabilities with AI assistanc ### Basic Session ```bash # Start simple pair programming -claude-flow pair --start +ruflo pair start ``` ### TDD Session ```bash # Test-driven development -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --test-first \ --coverage 90 @@ -56,23 +67,23 @@ claude-flow pair --start \ #### Starting Sessions ```bash # Basic start -claude-flow pair --start +ruflo pair start # Expert refactoring session -claude-flow pair --start \ +ruflo pair start \ --agent senior-dev \ --focus refactor \ --verify \ --threshold 0.98 # Debugging session -claude-flow pair --start \ +ruflo pair start \ --agent debugger-expert \ --focus debug \ --review # Learning session -claude-flow pair --start \ +ruflo pair start \ --mode mentor \ --pace slow \ --examples @@ -81,10 +92,10 @@ claude-flow pair --start \ #### Session Management ```bash # Check status -claude-flow pair --status +ruflo pair status # View history -claude-flow pair --history +ruflo pair history # Pause session /pause [--reason ] @@ -93,7 +104,7 @@ claude-flow pair --history /resume # End session -claude-flow pair --end [--save] [--report] +ruflo pair end [--save] [--report] ``` ### Available Modes @@ -102,7 +113,7 @@ claude-flow pair --end [--save] [--report] You write code while AI provides guidance. ```bash -claude-flow pair --start --mode driver +ruflo pair start --mode driver ``` **Your Responsibilities:** @@ -137,7 +148,7 @@ claude-flow pair --start --mode driver AI writes code while you provide direction. ```bash -claude-flow pair --start --mode navigator +ruflo pair start --mode navigator ``` **Your Responsibilities:** @@ -173,13 +184,13 @@ Automatically alternates roles at intervals. ```bash # Default 10-minute intervals -claude-flow pair --start --mode switch +ruflo pair start --mode switch # 5-minute intervals (rapid) -claude-flow pair --start --mode switch --interval 5m +ruflo pair start --mode switch --interval 5m # 15-minute intervals (deep focus) -claude-flow pair --start --mode switch --interval 15m +ruflo pair start --mode switch --interval 15m ``` **Handoff Process:** @@ -199,7 +210,7 @@ claude-flow pair --start --mode switch --interval 15m **TDD Mode** - Test-Driven Development: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --test-first \ --coverage 100 @@ -208,7 +219,7 @@ Workflow: Write failing test → Implement → Refactor → Repeat **Review Mode** - Continuous code review: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode review \ --strict \ --security @@ -217,7 +228,7 @@ Features: Real-time feedback, security scanning, performance analysis **Mentor Mode** - Learning-focused: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode mentor \ --explain-all \ --pace slow @@ -226,7 +237,7 @@ Features: Detailed explanations, step-by-step guidance, pattern teaching **Debug Mode** - Problem-solving: ```bash -claude-flow pair --start \ +ruflo pair start \ --mode debug \ --verbose \ --trace @@ -559,19 +570,19 @@ Create `.claude-flow/pair-config.json`: #### CLI Configuration ```bash # Set configuration -claude-flow pair config set defaultMode switch -claude-flow pair config set verification.threshold 0.98 +ruflo pair config set defaultMode switch +ruflo pair config set verification.threshold 0.98 # Get configuration -claude-flow pair config get -claude-flow pair config get defaultMode +ruflo pair config get +ruflo pair config get defaultMode # Export/Import -claude-flow pair config export > config.json -claude-flow pair config import config.json +ruflo pair config export > config.json +ruflo pair config import config.json # Reset -claude-flow pair config reset +ruflo pair config reset ``` #### Profile Management @@ -580,17 +591,17 @@ Create reusable profiles: ```bash # Create profile -claude-flow pair profile create refactoring \ +ruflo pair profile create refactoring \ --mode driver \ --verify true \ --threshold 0.98 \ --focus refactor # Use profile -claude-flow pair --start --profile refactoring +ruflo pair start --profile refactoring # List profiles -claude-flow pair profile list +ruflo pair profile list ``` Profile configuration: @@ -629,7 +640,7 @@ Implementing user authentication with JWT tokens: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode switch \ --agent senior-dev \ --focus implement \ @@ -676,7 +687,7 @@ Debugging a memory leak in Node.js: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode navigator \ --agent debugger-expert \ --focus debug \ @@ -717,7 +728,7 @@ Building shopping cart with test-driven development: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode tdd \ --agent tdd-specialist \ --test-first @@ -759,7 +770,7 @@ Modernizing legacy code: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode driver \ --focus refactor \ --verify \ @@ -807,7 +818,7 @@ Optimizing slow React application: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode switch \ --agent performance-expert \ --focus optimize \ @@ -852,7 +863,7 @@ Building RESTful API with Express: ```bash # Session setup -claude-flow pair --start \ +ruflo pair start \ --mode navigator \ --agent backend-expert \ --focus implement \ @@ -897,28 +908,28 @@ claude-flow pair --start \ ```bash # Refactoring template -claude-flow pair --template refactor +ruflo pair --template refactor # Focus: Code improvement # Verification: High (0.98) # Testing: After each change # Review: Continuous # Feature template -claude-flow pair --template feature +ruflo pair --template feature # Focus: Implementation # Verification: Standard (0.95) # Testing: On completion # Review: Pre-commit # Debug template -claude-flow pair --template debug +ruflo pair --template debug # Focus: Problem solving # Verification: Moderate (0.90) # Testing: Regression tests # Review: Root cause # Learning template -claude-flow pair --template learn +ruflo pair --template learn # Mode: Mentor # Pace: Slow # Explanations: Detailed @@ -930,7 +941,7 @@ claude-flow pair --template learn #### Session Status ```bash -claude-flow pair --status +ruflo pair status ``` **Output:** @@ -962,7 +973,7 @@ Next Switch: in 3 minutes #### Session History ```bash -claude-flow pair --history +ruflo pair history ``` **Output:** @@ -993,32 +1004,32 @@ claude-flow pair --history ```bash # Save session -claude-flow pair --save [--name ] +ruflo pair --save [--name ] # Load session -claude-flow pair --load +ruflo pair --load # Export session -claude-flow pair --export [--format json|md] +ruflo pair --export [--format json|md] # Generate report -claude-flow pair --report +ruflo pair --report ``` #### Background Sessions ```bash # Start in background -claude-flow pair --start --background +ruflo pair start --background # Monitor background session -claude-flow pair --monitor +ruflo pair --monitor # Attach to background session -claude-flow pair --attach +ruflo pair --attach # End background session -claude-flow pair --end +ruflo pair end ``` ### Advanced Features @@ -1054,30 +1065,30 @@ Use custom commands: ```bash # Start with recording -claude-flow pair --start --record +ruflo pair start --record # Replay session -claude-flow pair --replay +ruflo pair --replay # Session analytics -claude-flow pair --analytics +ruflo pair --analytics ``` #### Integration Options **With Git:** ```bash -claude-flow pair --start --git --auto-commit +ruflo pair start --git --auto-commit ``` **With CI/CD:** ```bash -claude-flow pair --start --ci --non-interactive +ruflo pair start --ci --non-interactive ``` **With IDE:** ```bash -claude-flow pair --start --ide vscode +ruflo pair start --ide vscode ``` ### Best Practices @@ -1131,7 +1142,7 @@ claude-flow pair --start --ide vscode - Validate JSON syntax - Check file permissions - Review priority order (CLI > env > project > user > global) -- Run `claude-flow pair config validate` +- Run `ruflo pair config validate` ### Quality Metrics @@ -1195,8 +1206,8 @@ Default shortcuts: ### Related Commands -- `claude-flow pair --help` - Show help -- `claude-flow pair config` - Manage configuration -- `claude-flow pair profile` - Manage profiles -- `claude-flow pair templates` - List templates -- `claude-flow pair agents` - List available agents +- `ruflo pair --help` - Show help +- `ruflo pair config` - Manage configuration +- `ruflo pair profile` - Manage profiles +- `ruflo pair templates` - List templates +- `ruflo pair agents` - List available agents diff --git a/v3/@claude-flow/cli/__tests__/pair-command.test.ts b/v3/@claude-flow/cli/__tests__/pair-command.test.ts new file mode 100644 index 0000000000..f5ecd68a98 --- /dev/null +++ b/v3/@claude-flow/cli/__tests__/pair-command.test.ts @@ -0,0 +1,187 @@ +/** + * `ruflo pair` — session state the pair-programming skill drives. + * + * Every test points ctx.cwd at a fresh temp dir so nothing touches the + * repo's own .claude-flow/. Covers: start/status/switch/mode/end/history + * round trip, the v2 `--start`/`--end` spellings, mode validation, the + * switch-mode "switch due" signal, and the lazy registry entry. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; + +import { pairCommand, parseIntervalMinutes, readSession, PAIR_MODES } from '../src/commands/pair.js'; +import type { CommandContext, ParsedFlags } from '../src/types.js'; + +let cwd: string; + +function ctx(flags: ParsedFlags = { _: [] }, args: string[] = []): CommandContext { + return { args, flags, cwd, interactive: false }; +} + +function sub(name: string) { + const found = pairCommand.subcommands!.find(s => s.name === name); + if (!found?.action) throw new Error(`missing subcommand ${name}`); + return found.action; +} + +beforeEach(() => { + cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'pair-command-test-')); +}); + +afterEach(() => { + fs.rmSync(cwd, { recursive: true, force: true }); +}); + +describe('ruflo pair — registry', () => { + it('is wired into the lazy command loader map', () => { + // Importing commands/index.js drags in every command module (and their + // built workspace deps), so read the registry source instead. + const registry = fs.readFileSync(new URL('../src/commands/index.ts', import.meta.url), 'utf-8'); + expect(registry).toContain("pair: () => import('./pair.js')"); + expect(pairCommand.subcommands?.map(s => s.name)).toEqual(['start', 'status', 'switch', 'mode', 'end', 'history']); + }); + + it('offers every mode the skill documents as a --mode choice', () => { + const startSub = pairCommand.subcommands!.find(s => s.name === 'start')!; + const modeOption = startSub.options!.find(o => o.name === 'mode'); + expect(modeOption?.choices).toEqual(Object.keys(PAIR_MODES)); + expect(Object.keys(PAIR_MODES)).toEqual(['driver', 'navigator', 'switch', 'tdd', 'review', 'mentor', 'debug']); + }); +}); + +describe('ruflo pair — session lifecycle', () => { + it('reports no session before start and does not create files', async () => { + const result = await sub('status')(ctx({ _: [], json: true })); + expect(result).toMatchObject({ success: true, data: { active: false } }); + expect(fs.existsSync(path.join(cwd, '.claude-flow'))).toBe(false); + }); + + it('start writes current.json with the mode, a default role and no interval', async () => { + const result = await sub('start')(ctx({ _: [], mode: 'tdd', json: true })); + expect(result.success).toBe(true); + const session = readSession(cwd); + expect(session).toMatchObject({ mode: 'tdd', aiRole: 'driver', switchIntervalMinutes: null, switches: 0 }); + expect(fs.existsSync(path.join(cwd, '.claude-flow', 'sessions', 'pair', 'current.json'))).toBe(true); + }); + + it('navigator and review modes put the AI in the navigator seat by default', async () => { + await sub('start')(ctx({ _: [], mode: 'review' })); + expect(readSession(cwd)?.aiRole).toBe('navigator'); + }); + + it('accepts the mode as a bare word: pair start tdd', async () => { + await sub('start')(ctx({ _: [], mode: 'driver' }, ['tdd'])); + expect(readSession(cwd)?.mode).toBe('tdd'); + }); + + it('--role overrides the mode default', async () => { + await sub('start')(ctx({ _: [], mode: 'tdd', role: 'navigator' })); + expect(readSession(cwd)?.aiRole).toBe('navigator'); + }); + + it('refuses to start a second session', async () => { + await sub('start')(ctx({ _: [], mode: 'driver' })); + const second = await sub('start')(ctx({ _: [], mode: 'tdd' })); + expect(second.success).toBe(false); + expect(readSession(cwd)?.mode).toBe('driver'); + }); + + it('rejects an unknown mode without writing anything', async () => { + const result = await sub('start')(ctx({ _: [], mode: 'yolo' })); + expect(result.success).toBe(false); + expect(readSession(cwd)).toBeNull(); + }); + + it('switch swaps roles and counts', async () => { + await sub('start')(ctx({ _: [], mode: 'driver' })); + await sub('switch')(ctx()); + expect(readSession(cwd)).toMatchObject({ aiRole: 'navigator', switches: 1 }); + await sub('switch')(ctx()); + expect(readSession(cwd)).toMatchObject({ aiRole: 'driver', switches: 2 }); + }); + + it('mode changes the active mode and validates the positional', async () => { + await sub('start')(ctx({ _: [], mode: 'driver' })); + const bad = await sub('mode')(ctx({ _: [] }, ['nope'])); + expect(bad.success).toBe(false); + const good = await sub('mode')(ctx({ _: [] }, ['debug'])); + expect(good.success).toBe(true); + expect(readSession(cwd)?.mode).toBe('debug'); + }); + + it('end moves the session into history.jsonl and clears current.json', async () => { + await sub('start')(ctx({ _: [], mode: 'mentor' })); + const id = readSession(cwd)!.id; + const ended = await sub('end')(ctx({ _: [], json: true })); + expect(ended.success).toBe(true); + expect(readSession(cwd)).toBeNull(); + const history = (await sub('history')(ctx({ _: [], json: true }))).data as Array<{ id: string; endedAt?: string }>; + expect(history).toHaveLength(1); + expect(history[0].id).toBe(id); + expect(history[0].endedAt).toBeTruthy(); + }); +}); + +describe('ruflo pair — switch mode timing', () => { + it('defaults the interval to 10 minutes and flags a due switch once elapsed', async () => { + await sub('start')(ctx({ _: [], mode: 'switch' })); + const session = readSession(cwd)!; + expect(session.switchIntervalMinutes).toBe(10); + + let status = await sub('status')(ctx({ _: [], json: true })); + expect((status.data as { switchDue: boolean }).switchDue).toBe(false); + + // Backdate the last switch past the interval and re-read. + session.lastSwitchAt = new Date(Date.now() - 11 * 60_000).toISOString(); + fs.writeFileSync(path.join(cwd, '.claude-flow', 'sessions', 'pair', 'current.json'), JSON.stringify(session)); + status = await sub('status')(ctx({ _: [], json: true })); + expect((status.data as { switchDue: boolean }).switchDue).toBe(true); + + await sub('switch')(ctx()); + status = await sub('status')(ctx({ _: [], json: true })); + expect((status.data as { switchDue: boolean }).switchDue).toBe(false); + }); + + it('honours --interval with unit suffixes and rejects garbage', async () => { + const bad = await sub('start')(ctx({ _: [], mode: 'switch', interval: 'soon' })); + expect(bad.success).toBe(false); + expect(readSession(cwd)).toBeNull(); + await sub('start')(ctx({ _: [], mode: 'switch', interval: '1h' })); + expect(readSession(cwd)?.switchIntervalMinutes).toBe(60); + }); +}); + +describe('parseIntervalMinutes', () => { + it('parses minutes by default and s/m/h suffixes', () => { + expect(parseIntervalMinutes('15')).toBe(15); + expect(parseIntervalMinutes('15m')).toBe(15); + expect(parseIntervalMinutes('2h')).toBe(120); + expect(parseIntervalMinutes('90s')).toBe(1.5); + expect(parseIntervalMinutes(5)).toBe(5); + }); + it('returns null for empty, zero, negative or unparseable input', () => { + expect(parseIntervalMinutes(undefined)).toBeNull(); + expect(parseIntervalMinutes('')).toBeNull(); + expect(parseIntervalMinutes('0')).toBeNull(); + expect(parseIntervalMinutes(-3)).toBeNull(); + expect(parseIntervalMinutes('ten minutes')).toBeNull(); + }); +}); + +describe('ruflo pair — v2 flag spellings', () => { + it('`pair --start --mode tdd` starts and `pair --end` ends', async () => { + const started = await pairCommand.action!(ctx({ _: [], start: true, mode: 'tdd' })); + expect(started.success).toBe(true); + expect(readSession(cwd)?.mode).toBe('tdd'); + const ended = await pairCommand.action!(ctx({ _: [], end: true })); + expect(ended.success).toBe(true); + expect(readSession(cwd)).toBeNull(); + }); + + it('bare `pair` is status', async () => { + const result = await pairCommand.action!(ctx({ _: [], json: true })); + expect(result.data).toEqual({ active: false }); + }); +}); diff --git a/v3/@claude-flow/cli/src/commands/index.ts b/v3/@claude-flow/cli/src/commands/index.ts index 73b6917912..c6847d4e74 100644 --- a/v3/@claude-flow/cli/src/commands/index.ts +++ b/v3/@claude-flow/cli/src/commands/index.ts @@ -79,6 +79,9 @@ const commandLoaders: Record = { 'transfer-store': () => import('./transfer-store.js'), cleanup: () => import('./cleanup.js'), autopilot: () => import('./autopilot.js'), + // Pair programming session state (restores the v2 `pair` command the + // bundled pair-programming skill depends on) + pair: () => import('./pair.js'), // GAIA Benchmark Harness (ADR-133) 'gaia-bench': () => import('./gaia-bench.js'), // MetaHarness integration (ADR-150) — dispatcher over plugins/ruflo-metaharness/ @@ -223,6 +226,7 @@ export async function getGuidanceCommand() { return loadCommand('guidance'); } export async function getApplianceCommand() { return loadCommand('appliance'); } export async function getCleanupCommand() { return loadCommand('cleanup'); } export async function getAutopilotCommand() { return loadCommand('autopilot'); } +export async function getPairCommand() { return loadCommand('pair'); } export async function getTransportCommand() { return loadCommand('transport'); } /** @@ -279,7 +283,7 @@ export async function getCommandsByCategory(): Promise analyzeCmd, routeCmd, progressCmd, providersCmd, pluginsCmd, deploymentCmd, claimsCmd, issuesCmd, updateCmd, processCmd, guidanceCmd, applianceCmd, - cleanupCmd, autopilotCmd, policyCmd, + cleanupCmd, autopilotCmd, policyCmd, pairCmd, ] = await Promise.all([ loadCommand('daemon'), loadCommand('doctor'), loadCommand('embeddings'), loadCommand('neural'), loadCommand('performance'), loadCommand('security'), loadCommand('ruvector'), loadCommand('hive-mind'), @@ -287,7 +291,7 @@ export async function getCommandsByCategory(): Promise loadCommand('analyze'), loadCommand('route'), loadCommand('progress'), loadCommand('providers'), loadCommand('plugins'), loadCommand('deployment'), loadCommand('claims'), loadCommand('issues'), loadCommand('update'), loadCommand('process'), loadCommand('guidance'), loadCommand('appliance'), - loadCommand('cleanup'), loadCommand('autopilot'), loadCommand('policy'), + loadCommand('cleanup'), loadCommand('autopilot'), loadCommand('policy'), loadCommand('pair'), ]); return { @@ -302,7 +306,7 @@ export async function getCommandsByCategory(): Promise ].filter(Boolean) as Command[], utility: [ configCmd, doctorCmd, daemonCmd, completionsCmd, - migrateCmd, workflowCmd, + migrateCmd, workflowCmd, pairCmd, ].filter(Boolean) as Command[], analysis: [ analyzeCmd, routeCmd, progressCmd, diff --git a/v3/@claude-flow/cli/src/commands/pair.ts b/v3/@claude-flow/cli/src/commands/pair.ts new file mode 100644 index 0000000000..92f2d8ae14 --- /dev/null +++ b/v3/@claude-flow/cli/src/commands/pair.ts @@ -0,0 +1,320 @@ +/** + * `ruflo pair` — pair programming session state. + * + * The bundled `pair-programming` skill has told Claude Code to run + * `claude-flow pair --start --mode tdd` since v2, but the v3 CLI never + * shipped the command, so the skill's modes could not be activated or + * switched. This restores a minimal, non-interactive version: the session + * (mode, who currently drives, when roles last swapped) lives in + * `.claude-flow/sessions/pair/current.json` in the project so both the human + * and the agent can read it between turns. There is no REPL — Claude Code + * cannot type into one — the agent reads `pair status --json` and behaves + * according to the recorded mode and role. + * + * Legacy v2 spellings (`pair --start`, `--status`, `--end`, `--history`) + * still work so existing skill docs and muscle memory keep functioning. + */ + +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import type { Command, CommandContext, CommandResult } from '../types.js'; +import { output } from '../output.js'; + +export const PAIR_MODES = { + driver: 'AI writes the code, human reviews and steers', + navigator: 'Human writes the code, AI reviews and suggests', + switch: 'Roles swap every --interval (default 10m)', + tdd: 'Red-green-refactor; AI writes failing tests first', + review: 'Read-only review of existing code, no edits without approval', + mentor: 'AI explains every step and asks before acting', + debug: 'Hypothesis-driven debugging; AI reproduces before fixing', +} as const; + +export type PairMode = keyof typeof PAIR_MODES; +export type PairRole = 'driver' | 'navigator'; + +export interface PairSession { + id: string; + mode: PairMode; + /** Who currently types. Always refers to the AI's role. */ + aiRole: PairRole; + switchIntervalMinutes: number | null; + startedAt: string; + lastSwitchAt: string; + switches: number; + endedAt?: string; +} + +const SESSION_DIR = ['.claude-flow', 'sessions', 'pair']; +const DEFAULT_SWITCH_INTERVAL_MINUTES = 10; + +function sessionDir(cwd: string): string { + return path.join(cwd, ...SESSION_DIR); +} + +function currentPath(cwd: string): string { + return path.join(sessionDir(cwd), 'current.json'); +} + +function historyPath(cwd: string): string { + return path.join(sessionDir(cwd), 'history.jsonl'); +} + +export function readSession(cwd: string): PairSession | null { + try { + return JSON.parse(fs.readFileSync(currentPath(cwd), 'utf-8')) as PairSession; + } catch { + return null; + } +} + +function writeSession(cwd: string, session: PairSession): void { + fs.mkdirSync(sessionDir(cwd), { recursive: true }); + const tmp = currentPath(cwd) + '.tmp-' + process.pid; + fs.writeFileSync(tmp, JSON.stringify(session, null, 2) + '\n', 'utf-8'); + fs.renameSync(tmp, currentPath(cwd)); +} + +export function readHistory(cwd: string): PairSession[] { + try { + return fs.readFileSync(historyPath(cwd), 'utf-8') + .split('\n') + .filter(Boolean) + .map(line => JSON.parse(line) as PairSession); + } catch { + return []; + } +} + +/** Accepts `10`, `10m`, `1h`, `90s`; returns minutes or null when unparseable. */ +export function parseIntervalMinutes(raw: unknown): number | null { + if (raw === undefined || raw === null || raw === '') return null; + if (typeof raw === 'number') return raw > 0 ? raw : null; + const match = /^(\d+(?:\.\d+)?)\s*(s|m|h)?$/i.exec(String(raw).trim()); + if (!match) return null; + const value = parseFloat(match[1]); + const unit = (match[2] ?? 'm').toLowerCase(); + const minutes = unit === 'h' ? value * 60 : unit === 's' ? value / 60 : value; + return minutes > 0 ? minutes : null; +} + +function isMode(value: unknown): value is PairMode { + return typeof value === 'string' && value in PAIR_MODES; +} + +function otherRole(role: PairRole): PairRole { + return role === 'driver' ? 'navigator' : 'driver'; +} + +/** Default AI role implied by a mode, used when --role is not given. */ +function defaultRoleFor(mode: PairMode): PairRole { + return mode === 'navigator' || mode === 'review' ? 'navigator' : 'driver'; +} + +function switchDue(session: PairSession, now: Date): boolean { + if (session.mode !== 'switch' || !session.switchIntervalMinutes) return false; + const elapsedMinutes = (now.getTime() - new Date(session.lastSwitchAt).getTime()) / 60_000; + return elapsedMinutes >= session.switchIntervalMinutes; +} + +function describe(session: PairSession, now: Date): Record { + return { + ...session, + modeDescription: PAIR_MODES[session.mode], + humanRole: otherRole(session.aiRole), + switchDue: switchDue(session, now), + }; +} + +function printSession(session: PairSession, now: Date): void { + output.writeln(`Session: ${session.id}`); + output.writeln(`Mode: ${session.mode} — ${PAIR_MODES[session.mode]}`); + output.writeln(`AI role: ${session.aiRole} (human: ${otherRole(session.aiRole)})`); + if (session.switchIntervalMinutes) { + output.writeln(`Interval: every ${session.switchIntervalMinutes}m, ${session.switches} switch(es) so far`); + if (switchDue(session, now)) output.printWarning('Role switch is due — run `ruflo pair switch`.'); + } + output.writeln(`Started: ${session.startedAt}`); +} + +function noSession(ctx: CommandContext): CommandResult { + if (ctx.flags.json) output.printJson({ active: false }); + else output.writeln('No active pair session. Start one with `ruflo pair start --mode `.'); + return { success: true, data: { active: false } }; +} + +async function startAction(ctx: CommandContext): Promise { + // `pair start tdd` and `pair start --mode tdd` both work; a bare word wins + // over the option default so it is never silently ignored. + const mode = ctx.args[0] ?? ctx.flags.mode ?? 'driver'; + if (!isMode(mode)) { + output.printError(`Unknown mode "${String(mode)}". Choose one of: ${Object.keys(PAIR_MODES).join(', ')}`); + return { success: false, exitCode: 1 }; + } + const existing = readSession(ctx.cwd); + if (existing) { + output.printError(`A pair session is already active (${existing.id}). End it first with \`ruflo pair end\`.`); + return { success: false, exitCode: 1, data: describe(existing, new Date()) }; + } + const interval = ctx.flags.interval !== undefined ? parseIntervalMinutes(ctx.flags.interval) : null; + if (ctx.flags.interval !== undefined && interval === null) { + output.printError(`Invalid --interval "${String(ctx.flags.interval)}". Use e.g. 10m, 90s or 1h.`); + return { success: false, exitCode: 1 }; + } + const role = ctx.flags.role; + const now = new Date(); + const session: PairSession = { + id: `pair_${now.getTime()}`, + mode, + aiRole: role === 'driver' || role === 'navigator' ? role : defaultRoleFor(mode), + switchIntervalMinutes: mode === 'switch' ? (interval ?? DEFAULT_SWITCH_INTERVAL_MINUTES) : interval, + startedAt: now.toISOString(), + lastSwitchAt: now.toISOString(), + switches: 0, + }; + writeSession(ctx.cwd, session); + if (ctx.flags.json) output.printJson(describe(session, now)); + else { + output.printSuccess('Pair session started.'); + printSession(session, now); + } + return { success: true, data: describe(session, now) }; +} + +const startSub: Command = { + name: 'start', + description: 'Start a pair programming session', + options: [ + { name: 'mode', short: 'm', description: 'Collaboration mode', type: 'string', default: 'driver', choices: Object.keys(PAIR_MODES) }, + { name: 'role', description: "AI's starting role (defaults from mode)", type: 'string', choices: ['driver', 'navigator'] }, + { name: 'interval', short: 'i', description: 'Role switch interval for switch mode (e.g. 10m, 1h)', type: 'string' }, + { name: 'json', description: 'Output as JSON', type: 'boolean', default: false }, + ], + action: startAction, +}; + +async function statusAction(ctx: CommandContext): Promise { + const session = readSession(ctx.cwd); + if (!session) return noSession(ctx); + const now = new Date(); + if (ctx.flags.json) output.printJson({ active: true, ...describe(session, now) }); + else printSession(session, now); + return { success: true, data: { active: true, ...describe(session, now) } }; +} + +const statusSub: Command = { + name: 'status', + description: 'Show the active pair session', + options: [{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false }], + action: statusAction, +}; + +async function switchAction(ctx: CommandContext): Promise { + const session = readSession(ctx.cwd); + if (!session) return noSession(ctx); + const now = new Date(); + session.aiRole = otherRole(session.aiRole); + session.lastSwitchAt = now.toISOString(); + session.switches += 1; + writeSession(ctx.cwd, session); + if (ctx.flags.json) output.printJson(describe(session, now)); + else output.printSuccess(`Switched — AI is now ${session.aiRole}, human is ${otherRole(session.aiRole)}.`); + return { success: true, data: describe(session, now) }; +} + +const switchSub: Command = { + name: 'switch', + description: 'Swap driver and navigator roles', + options: [{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false }], + action: switchAction, +}; + +async function modeAction(ctx: CommandContext): Promise { + const session = readSession(ctx.cwd); + if (!session) return noSession(ctx); + const mode = ctx.args[0]; + if (!isMode(mode)) { + output.printError(`Usage: ruflo pair mode <${Object.keys(PAIR_MODES).join('|')}>`); + return { success: false, exitCode: 1 }; + } + const now = new Date(); + session.mode = mode; + if (mode === 'switch' && !session.switchIntervalMinutes) { + session.switchIntervalMinutes = DEFAULT_SWITCH_INTERVAL_MINUTES; + session.lastSwitchAt = now.toISOString(); + } + writeSession(ctx.cwd, session); + if (ctx.flags.json) output.printJson(describe(session, now)); + else output.printSuccess(`Mode changed to ${mode} — ${PAIR_MODES[mode]}.`); + return { success: true, data: describe(session, now) }; +} + +const modeSub: Command = { + name: 'mode', + description: 'Change the mode of the active session: ruflo pair mode ', + options: [{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false }], + action: modeAction, +}; + +async function endAction(ctx: CommandContext): Promise { + const session = readSession(ctx.cwd); + if (!session) return noSession(ctx); + session.endedAt = new Date().toISOString(); + fs.appendFileSync(historyPath(ctx.cwd), JSON.stringify(session) + '\n', 'utf-8'); + fs.rmSync(currentPath(ctx.cwd), { force: true }); + if (ctx.flags.json) output.printJson(session); + else output.printSuccess(`Pair session ${session.id} ended after ${session.switches} role switch(es).`); + return { success: true, data: session }; +} + +const endSub: Command = { + name: 'end', + description: 'End the active session and append it to the history', + options: [{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false }], + action: endAction, +}; + +async function historyAction(ctx: CommandContext): Promise { + const history = readHistory(ctx.cwd); + if (ctx.flags.json) output.printJson(history); + else if (history.length === 0) output.writeln('No past pair sessions.'); + else for (const s of history) output.writeln(`${s.id} ${s.mode.padEnd(9)} ${s.startedAt} → ${s.endedAt ?? '?'} (${s.switches} switches)`); + return { success: true, data: history }; +} + +const historySub: Command = { + name: 'history', + description: 'List past pair sessions', + options: [{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false }], + action: historyAction, +}; + +export const pairCommand: Command = { + name: 'pair', + description: 'Pair programming session: modes, driver/navigator roles, switching', + subcommands: [startSub, statusSub, switchSub, modeSub, endSub, historySub], + // Top-level flags kept for the v2 spelling used throughout the skill docs. + options: [ + { name: 'start', description: 'Alias for `pair start`', type: 'boolean', default: false }, + { name: 'status', description: 'Alias for `pair status`', type: 'boolean', default: false }, + { name: 'end', description: 'Alias for `pair end`', type: 'boolean', default: false }, + { name: 'history', description: 'Alias for `pair history`', type: 'boolean', default: false }, + ...(startSub.options ?? []), + ], + examples: [ + { command: 'ruflo pair start --mode tdd', description: 'Start a test-first session with the AI driving' }, + { command: 'ruflo pair start --mode switch --interval 15m', description: 'Swap roles every 15 minutes' }, + { command: 'ruflo pair status --json', description: 'Machine-readable state for the agent to read each turn' }, + { command: 'ruflo pair switch', description: 'Swap driver and navigator now' }, + { command: 'ruflo pair mode review', description: 'Change mode mid-session' }, + { command: 'ruflo pair end', description: 'End the session and record it in the history' }, + ], + action: async (ctx: CommandContext): Promise => { + if (ctx.flags.start) return startAction(ctx); + if (ctx.flags.end) return endAction(ctx); + if (ctx.flags.history) return historyAction(ctx); + return statusAction(ctx); + }, +}; + +export default pairCommand;