mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-10-23 03:47:00 +09:00
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:
6 files changed
+730
-186
No files matched your search
@@ -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
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user