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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Gilles Major <gilles@lizy.be>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: rUv <ruv@ruv.net>
This commit is contained in:
authored and GitHub committed 2026-10-21 16:25:31 -04:00
1 parent c6a9d73e6c
commit 1ff5949705
6 files changed
+730 -186

No files matched your search

+72 -61
View File
@@ -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 <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 <reason>]
@@ -93,7 +104,7 @@ $pause [--reason <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 <name>]
ruflo pair --save [--name <name>]
# Load session
claude-flow pair --load <session-id>
ruflo pair --load <session-id>
# Export session
claude-flow pair --export <session-id> [--format json|md]
ruflo pair --export <session-id> [--format json|md]
# Generate report
claude-flow pair --report <session-id>
ruflo pair --report <session-id>
```
#### 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 <session-id>
ruflo pair --attach <session-id>
# End background session
claude-flow pair --end <session-id>
ruflo pair end <session-id>
```
### 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 <session-id>
ruflo pair --replay <session-id>
# Session analytics
claude-flow pair --analytics <session-id>
ruflo pair --analytics <session-id>
```
#### 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
+72 -61
View File
@@ -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 <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 <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 <name>]
ruflo pair --save [--name <name>]
# Load session
claude-flow pair --load <session-id>
ruflo pair --load <session-id>
# Export session
claude-flow pair --export <session-id> [--format json|md]
ruflo pair --export <session-id> [--format json|md]
# Generate report
claude-flow pair --report <session-id>
ruflo pair --report <session-id>
```
#### 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 <session-id>
ruflo pair --attach <session-id>
# End background session
claude-flow pair --end <session-id>
ruflo pair end <session-id>
```
### 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 <session-id>
ruflo pair --replay <session-id>
# Session analytics
claude-flow pair --analytics <session-id>
ruflo pair --analytics <session-id>
```
#### 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
@@ -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 <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 <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 <name>]
ruflo pair --save [--name <name>]
# Load session
claude-flow pair --load <session-id>
ruflo pair --load <session-id>
# Export session
claude-flow pair --export <session-id> [--format json|md]
ruflo pair --export <session-id> [--format json|md]
# Generate report
claude-flow pair --report <session-id>
ruflo pair --report <session-id>
```
#### 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 <session-id>
ruflo pair --attach <session-id>
# End background session
claude-flow pair --end <session-id>
ruflo pair end <session-id>
```
### 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 <session-id>
ruflo pair --replay <session-id>
# Session analytics
claude-flow pair --analytics <session-id>
ruflo pair --analytics <session-id>
```
#### 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
@@ -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 <name> 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 });
});
});
+7 -3
View File
@@ -79,6 +79,9 @@ const commandLoaders: Record<string, CommandLoader> = {
'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<Record<string, Command[]>
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<Record<string, Command[]>
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<Record<string, Command[]>
].filter(Boolean) as Command[],
utility: [
configCmd, doctorCmd, daemonCmd, completionsCmd,
migrateCmd, workflowCmd,
migrateCmd, workflowCmd, pairCmd,
].filter(Boolean) as Command[],
analysis: [
analyzeCmd, routeCmd, progressCmd,
+320
View File
@@ -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<string, unknown> {
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 <mode>`.');
return { success: true, data: { active: false } };
}
async function startAction(ctx: CommandContext): Promise<CommandResult> {
// `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<CommandResult> {
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<CommandResult> {
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<CommandResult> {
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 <mode>',
options: [{ name: 'json', description: 'Output as JSON', type: 'boolean', default: false }],
action: modeAction,
};
async function endAction(ctx: CommandContext): Promise<CommandResult> {
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<CommandResult> {
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<CommandResult> => {
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;