CLAUDE: Update REPL for new GameState and standardize UV commands

Updated terminal client REPL to work with refactored GameState structure
where current_batter/pitcher/catcher are now LineupPlayerState objects
instead of integer IDs. Also standardized all documentation to properly
show 'uv run' prefixes for Python commands.

REPL Updates:
- terminal_client/display.py: Access lineup_id from LineupPlayerState objects
- terminal_client/repl.py: Fix typos (self.current_game → self.current_game_id)
- tests/unit/terminal_client/test_commands.py: Create proper LineupPlayerState
  objects in test fixtures (2 tests fixed, all 105 terminal client tests passing)

Documentation Updates (100+ command examples):
- CLAUDE.md: Updated pytest examples to use 'uv run' prefix
- terminal_client/CLAUDE.md: Updated ~40 command examples
- tests/CLAUDE.md: Updated all test commands (unit, integration, debugging)
- app/*/CLAUDE.md: Updated test and server startup commands (5 files)

All Python commands now consistently use 'uv run' prefix to align with
project's UV migration, improving developer experience and preventing
confusion about virtual environment activation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-11-04 09:59:13 -06:00
parent 7de70b34d5
commit 440adf2c26
12 changed files with 144 additions and 121 deletions

View File

@ -1158,13 +1158,13 @@ game_data = await db_ops.load_game_state(game_id)
**Run Tests**:
```bash
# All unit tests
pytest tests/unit/ -v
uv run pytest tests/unit/ -v
# Integration tests (requires database)
pytest tests/integration/ -v -m integration
uv run pytest tests/integration/ -v -m integration
# Specific file
pytest tests/unit/models/test_game_models.py -v
uv run pytest tests/unit/models/test_game_models.py -v
```
### Patterns Established
@ -1559,7 +1559,7 @@ logger.info(f"Rebuilt state for game {state.game_id}: {state.play_count} plays,
**Integration Tests**:
- Known issue: Integration tests in `tests/integration/test_game_engine.py` must be run individually
- Reason: Database connection pooling conflicts when running in parallel
- Workaround: `pytest tests/integration/test_game_engine.py::TestClassName::test_method -v`
- Workaround: `uv run pytest tests/integration/test_game_engine.py::TestClassName::test_method -v`
- All tests pass when run individually
**Terminal Client**:

View File

@ -426,7 +426,7 @@ const games = await api.get('/games/');
5. **Test the endpoint**
```bash
# Start server
python -m app.main
uv run python -m app.main
# Test with curl
curl -X POST http://localhost:8000/api/teams/ \
@ -549,7 +549,7 @@ FastAPI automatically generates interactive API documentation:
1. Start the backend server:
```bash
python -m app.main
uv run python -m app.main
```
2. Open Swagger UI:

View File

@ -917,13 +917,13 @@ if requires_cardset_validation(state.league_id):
**Run Tests**:
```bash
# All config tests
pytest tests/unit/config/ -v
uv run pytest tests/unit/config/ -v
# Specific file
pytest tests/unit/config/test_league_configs.py -v
uv run pytest tests/unit/config/test_league_configs.py -v
# Specific test
pytest tests/unit/config/test_play_outcome.py::test_is_hit -v
uv run pytest tests/unit/config/test_play_outcome.py::test_is_hit -v
```
### Test Examples

View File

@ -1138,10 +1138,10 @@ state = await state_manager.recover_game(game_id)
**Solution**:
```bash
# Run integration tests individually
pytest tests/integration/test_game_engine.py::TestGameEngine::test_resolve_play -v
uv run pytest tests/integration/test_game_engine.py::TestGameEngine::test_resolve_play -v
# Or use -x flag to stop on first failure
pytest tests/integration/test_game_engine.py -x -v
uv run pytest tests/integration/test_game_engine.py -x -v
```
### Issue: Type errors with SQLAlchemy models
@ -1163,27 +1163,27 @@ See `backend/CLAUDE.md` section on Type Checking for comprehensive guidance.
```bash
# All core unit tests
pytest tests/unit/core/ -v
uv run pytest tests/unit/core/ -v
# Specific module
pytest tests/unit/core/test_game_engine.py -v
pytest tests/unit/core/test_play_resolver.py -v
pytest tests/unit/core/test_runner_advancement.py -v # Groundball tests (30 tests)
pytest tests/unit/core/test_flyball_advancement.py -v # Flyball tests (21 tests)
pytest tests/unit/core/test_state_manager.py -v
uv run pytest tests/unit/core/test_game_engine.py -v
uv run pytest tests/unit/core/test_play_resolver.py -v
uv run pytest tests/unit/core/test_runner_advancement.py -v # Groundball tests (30 tests)
uv run pytest tests/unit/core/test_flyball_advancement.py -v # Flyball tests (21 tests)
uv run pytest tests/unit/core/test_state_manager.py -v
# With coverage
pytest tests/unit/core/ --cov=app.core --cov-report=html
uv run pytest tests/unit/core/ --cov=app.core --cov-report=html
```
### Integration Tests
```bash
# All core integration tests
pytest tests/integration/test_state_persistence.py -v
uv run pytest tests/integration/test_state_persistence.py -v
# Run individually (recommended due to connection pooling)
pytest tests/integration/test_game_engine.py::TestGameEngine::test_complete_game -v
uv run pytest tests/integration/test_game_engine.py::TestGameEngine::test_complete_game -v
```
### Terminal Client
@ -1192,7 +1192,7 @@ Best tool for testing game engine in isolation:
```bash
# Start REPL
python -m terminal_client
uv run python -m terminal_client
# Create and play game
⚾ > new_game

View File

@ -915,13 +915,13 @@ app/database/
**Running Tests**:
```bash
# All database integration tests
pytest tests/integration/database/ -v
uv run pytest tests/integration/database/ -v
# Specific operation test
pytest tests/integration/database/test_operations.py::TestGameOperations::test_create_game -v
uv run pytest tests/integration/database/test_operations.py::TestGameOperations::test_create_game -v
# State persistence tests
pytest tests/integration/test_state_persistence.py -v
uv run pytest tests/integration/test_state_persistence.py -v
```
**Test Requirements**:

View File

@ -1050,16 +1050,16 @@ def test_advance_runner_scoring():
```bash
# All model tests
pytest tests/unit/models/ -v
uv run pytest tests/unit/models/ -v
# Specific file
pytest tests/unit/models/test_game_models.py -v
uv run pytest tests/unit/models/test_game_models.py -v
# Specific test
pytest tests/unit/models/test_game_models.py::test_advance_runner_scoring -v
uv run pytest tests/unit/models/test_game_models.py::test_advance_runner_scoring -v
# With coverage
pytest tests/unit/models/ --cov=app.models --cov-report=html
uv run pytest tests/unit/models/ --cov=app.models --cov-report=html
```
---

View File

@ -1,11 +1,11 @@
[project]
name = "paper-dynasty-backend"
name = "strat-gameplay-backend"
version = "0.1.0"
description = "Paper Dynasty Real-Time Game Engine Backend - FastAPI with WebSocket support"
description = "Strat Real-Time Game Engine Backend - FastAPI with WebSocket support"
readme = "README.md"
requires-python = ">=3.13"
authors = [
{ name = "Paper Dynasty Team" }
{ name = "Cal Corum" }
]
dependencies = [
"aiofiles==24.1.0",

View File

@ -259,7 +259,7 @@ For running individual commands outside REPL mode.
### new-game
```bash
python -m terminal_client new-game [OPTIONS]
uv run python -m terminal_client new-game [OPTIONS]
Options:
--league TEXT League (sba or pd) [default: sba]
@ -268,13 +268,13 @@ Options:
--away-team INTEGER Away team ID [default: 2]
Example:
python -m terminal_client start-game --league sba
python -m terminal_client start-game --game-id <uuid> --home-team 5 --away-team 3
uv run python -m terminal_client start-game --league sba
uv run python -m terminal_client start-game --game-id <uuid> --home-team 5 --away-team 3
```
### Defensive Decision
```bash
python -m terminal_client defensive [OPTIONS]
uv run python -m terminal_client defensive [OPTIONS]
Options:
--game-id TEXT Game UUID (uses current if not provided)
@ -287,13 +287,13 @@ Options:
--hold TEXT Comma-separated bases to hold (e.g., 1,3)
Example:
python -m terminal_client defensive --alignment shifted_left
python -m terminal_client defensive --infield double_play --hold 1,3
uv run python -m terminal_client defensive --alignment shifted_left
uv run python -m terminal_client defensive --infield double_play --hold 1,3
```
### Offensive Decision
```bash
python -m terminal_client offensive [OPTIONS]
uv run python -m terminal_client offensive [OPTIONS]
Options:
--game-id TEXT Game UUID (uses current if not provided)
@ -304,19 +304,19 @@ Options:
--bunt Bunt attempt (flag)
Example:
python -m terminal_client offensive --approach power
python -m terminal_client offensive --steal 2 --hit-run
uv run python -m terminal_client offensive --approach power
uv run python -m terminal_client offensive --steal 2 --hit-run
```
### Resolve Play
```bash
python -m terminal_client resolve [OPTIONS]
uv run python -m terminal_client resolve [OPTIONS]
Options:
--game-id TEXT Game UUID (uses current if not provided)
Example:
python -m terminal_client resolve
uv run python -m terminal_client resolve
```
Resolves the current play using submitted defensive and offensive decisions.
@ -324,37 +324,37 @@ Displays full play result with dice rolls, runner advancement, and updated game
### Game Status
```bash
python -m terminal_client status [OPTIONS]
uv run python -m terminal_client status [OPTIONS]
Options:
--game-id TEXT Game UUID (uses current if not provided)
Example:
python -m terminal_client status
uv run python -m terminal_client status
```
### Box Score
```bash
python -m terminal_client box-score [OPTIONS]
uv run python -m terminal_client box-score [OPTIONS]
Options:
--game-id TEXT Game UUID (uses current if not provided)
Example:
python -m terminal_client box-score
uv run python -m terminal_client box-score
```
### Quick Play
```bash
python -m terminal_client quick-play [OPTIONS]
uv run python -m terminal_client quick-play [OPTIONS]
Options:
--count INTEGER Number of plays to execute [default: 1]
--game-id TEXT Game UUID (uses current if not provided)
Example:
python -m terminal_client quick-play --count 10
python -m terminal_client quick-play --count 27 # Full inning
uv run python -m terminal_client quick-play --count 10
uv run python -m terminal_client quick-play --count 27 # Full inning
```
**Use Case**: Rapidly advance game state for testing specific scenarios (e.g., test 9th inning logic).
@ -363,20 +363,20 @@ Submits default decisions (normal alignment, normal approach) and auto-resolves
### List Games
```bash
python -m terminal_client list-games
uv run python -m terminal_client list-games
Example:
python -m terminal_client list-games
uv run python -m terminal_client list-games
```
Shows all active games in the state manager.
### Use Game
```bash
python -m terminal_client use-game <game_id>
uv run python -m terminal_client use-game <game_id>
Example:
python -m terminal_client use-game a1b2c3d4-e5f6-7890-abcd-ef1234567890
uv run python -m terminal_client use-game a1b2c3d4-e5f6-7890-abcd-ef1234567890
```
Switch to a different game (sets as "current" game for subsequent commands).
@ -387,7 +387,8 @@ Switch to a different game (sets as "current" game for subsequent commands).
```bash
# Start REPL
python -m terminal_client
uv run python -m terminal_client
# Or: source .venv/bin/activate && python -m terminal_client
# Create and play a game
⚾ > new_game
@ -415,45 +416,45 @@ python -m terminal_client
```bash
# Test defensive shifts
python -m terminal_client start-game
python -m terminal_client defensive --alignment shifted_left --infield double_play
python -m terminal_client offensive
python -m terminal_client resolve
uv run python -m terminal_client start-game
uv run python -m terminal_client defensive --alignment shifted_left --infield double_play
uv run python -m terminal_client offensive
uv run python -m terminal_client resolve
# Test stealing
python -m terminal_client start-game
python -m terminal_client defensive
python -m terminal_client offensive --steal 2,3 # Double steal
python -m terminal_client resolve
uv run python -m terminal_client start-game
uv run python -m terminal_client defensive
uv run python -m terminal_client offensive --steal 2,3 # Double steal
uv run python -m terminal_client resolve
# Test hit-and-run
python -m terminal_client defensive
python -m terminal_client offensive --hit-run
python -m terminal_client resolve
uv run python -m terminal_client defensive
uv run python -m terminal_client offensive --hit-run
uv run python -m terminal_client resolve
# Advance to late game quickly
python -m terminal_client start-game
python -m terminal_client quick-play --count 50 # ~6 innings
python -m terminal_client status
uv run python -m terminal_client start-game
uv run python -m terminal_client quick-play --count 50 # ~6 innings
uv run python -m terminal_client status
```
### Multiple Games
```bash
# Start game 1
python -m terminal_client start-game
uv run python -m terminal_client start-game
# ... play some ...
# Start game 2
python -m terminal_client start-game
uv run python -m terminal_client start-game
# ... play some ...
# List all games
python -m terminal_client list-games
uv run python -m terminal_client list-games
# Switch back to game 1
python -m terminal_client use-game <game-1-uuid>
python -m terminal_client status
uv run python -m terminal_client use-game <game-1-uuid>
uv run python -m terminal_client status
```
## Display Features
@ -553,15 +554,15 @@ This allows direct async calls to GameEngine without WebSocket overhead.
```bash
# Test game startup validation
python -m terminal_client start-game
uv run python -m terminal_client start-game
# Should fail: No lineups set
# Test invalid decisions
python -m terminal_client defensive --alignment invalid_value
uv run python -m terminal_client defensive --alignment invalid_value
# Should fail: ValidationError
# Test resolve without decisions
python -m terminal_client resolve
uv run python -m terminal_client resolve
# Should fail: No decisions submitted
```
@ -569,21 +570,21 @@ python -m terminal_client resolve
```bash
# Full game flow
python -m terminal_client start-game
uv run python -m terminal_client start-game
# ... set lineups via database directly ...
python -m terminal_client start-game # Retry
python -m terminal_client quick-play --count 100
python -m terminal_client status
uv run python -m terminal_client start-game # Retry
uv run python -m terminal_client quick-play --count 100
uv run python -m terminal_client status
# Verify: Game status = "completed"
# State persistence
python -m terminal_client start-game
uv run python -m terminal_client start-game
# Note the game UUID
python -m terminal_client quick-play --count 10
uv run python -m terminal_client quick-play --count 10
# Kill terminal
# Restart and use same UUID
python -m terminal_client use-game <uuid>
python -m terminal_client status
uv run python -m terminal_client use-game <uuid>
uv run python -m terminal_client status
# Verify: State recovered from database
```
@ -657,24 +658,29 @@ Logs appear in both terminal and `backend/logs/app_YYYYMMDD.log`.
python terminal_client/main.py # ❌
# Correct
python -m terminal_client start-game # ✅
uv run python -m terminal_client start-game # ✅
```
### Import Errors
```bash
# Ensure you're in backend directory
cd backend
source venv/bin/activate
# Recommended: Use UV (auto-activates venv)
uv run python -m terminal_client start-game
# Alternative: Manually activate venv
source .venv/bin/activate
python -m terminal_client start-game
```
### Game Not Found
```bash
# Check active games
python -m terminal_client list-games
uv run python -m terminal_client list-games
# Use specific game ID
python -m terminal_client status --game-id <uuid>
uv run python -m terminal_client status --game-id <uuid>
```
### Validation Errors

View File

@ -67,10 +67,12 @@ def display_game_state(state: GameState) -> None:
state_text.append("Empty\n", style="dim")
# Current players
if state.current_batter_lineup_id:
state_text.append(f"\nBatter: Lineup #{state.current_batter_lineup_id}\n")
if state.current_pitcher_lineup_id:
state_text.append(f"Pitcher: Lineup #{state.current_pitcher_lineup_id}\n")
if state.current_batter:
state_text.append(f"\nBatter: Lineup #{state.current_batter.lineup_id}\n")
if state.current_pitcher:
state_text.append(f"Pitcher: Lineup #{state.current_pitcher.lineup_id}\n")
if state.current_catcher:
state_text.append(f"Catcher: Lineup #{state.current_catcher.lineup_id}\n")
# Pending decision - provide clear guidance
if state.pending_decision:

View File

@ -480,7 +480,7 @@ Press Ctrl+D or type 'quit' to exit.
# Read physical card based on dice
manual_outcome groundball_c SS
"""
game_id = self.current_game
game_id = self.current_game_id
if not game_id:
display.print_error("No game selected. Create one with 'new_game'")
return
@ -509,7 +509,7 @@ Press Ctrl+D or type 'quit' to exit.
Note: Must call 'roll_dice' first before submitting outcome.
"""
game_id = self.current_game
game_id = self.current_game_id
if not game_id:
display.print_error("No game selected. Create one with 'new_game'")
return

View File

@ -26,16 +26,16 @@ tests/
**Fast, reliable, no database required**:
```bash
# All unit tests
pytest tests/unit/ -v
uv run pytest tests/unit/ -v
# Specific module
pytest tests/unit/core/test_game_engine.py -v
uv run pytest tests/unit/core/test_game_engine.py -v
# Specific test
pytest tests/unit/core/test_game_engine.py::TestGameEngine::test_start_game -v
uv run pytest tests/unit/core/test_game_engine.py::TestGameEngine::test_start_game -v
# With coverage
pytest tests/unit/ --cov=app --cov-report=html
uv run pytest tests/unit/ --cov=app --cov-report=html
```
**Unit tests should always pass**. If they don't, it's a real code issue.
@ -59,16 +59,16 @@ pytest tests/unit/ --cov=app --cov-report=html
```bash
# Run one test at a time (always works)
pytest tests/integration/database/test_operations.py::TestDatabaseOperationsGame::test_create_game -v
uv run pytest tests/integration/database/test_operations.py::TestDatabaseOperationsGame::test_create_game -v
# Run test class serially
pytest tests/integration/database/test_operations.py::TestDatabaseOperationsGame -v
uv run pytest tests/integration/database/test_operations.py::TestDatabaseOperationsGame -v
# Run entire file (may have conflicts after first test)
pytest tests/integration/database/test_operations.py -v
uv run pytest tests/integration/database/test_operations.py -v
# Force serial execution (slower but more reliable)
pytest tests/integration/ -v -x # -x stops on first failure
uv run pytest tests/integration/ -v -x # -x stops on first failure
```
**DO NOT**:
@ -85,11 +85,11 @@ pytest tests/integration/ -v -x # -x stops on first failure
```bash
# Run everything (expect integration failures due to connection issues)
pytest tests/ -v
uv run pytest tests/ -v
# Run with markers
pytest tests/ -v -m "not integration" # Skip integration tests
pytest tests/ -v -m integration # Only integration tests
uv run pytest tests/ -v -m "not integration" # Skip integration tests
uv run pytest tests/ -v -m integration # Only integration tests
```
## Test Configuration
@ -359,26 +359,26 @@ with time_machine.travel("2025-10-31 12:00:00", tick=False):
```bash
# Show full output including print statements
pytest tests/unit/core/test_game_engine.py -v -s
uv run pytest tests/unit/core/test_game_engine.py -v -s
# Show local variables on failure
pytest tests/unit/core/test_game_engine.py -v -l
uv run pytest tests/unit/core/test_game_engine.py -v -l
# Stop on first failure
pytest tests/unit/core/test_game_engine.py -v -x
uv run pytest tests/unit/core/test_game_engine.py -v -x
# Show full traceback
pytest tests/unit/core/test_game_engine.py -v --tb=long
uv run pytest tests/unit/core/test_game_engine.py -v --tb=long
```
### Interactive Debugging
```bash
# Drop into debugger on failure
pytest tests/unit/core/test_game_engine.py --pdb
uv run pytest tests/unit/core/test_game_engine.py --pdb
# Drop into debugger on first failure
pytest tests/unit/core/test_game_engine.py --pdb -x
uv run pytest tests/unit/core/test_game_engine.py --pdb -x
```
### Logging in Tests
@ -386,7 +386,7 @@ pytest tests/unit/core/test_game_engine.py --pdb -x
Tests capture logs by default. View with `-o log_cli=true`:
```bash
pytest tests/unit/core/test_game_engine.py -v -o log_cli=true -o log_cli_level=DEBUG
uv run pytest tests/unit/core/test_game_engine.py -v -o log_cli=true -o log_cli_level=DEBUG
```
## CI/CD Considerations
@ -396,13 +396,13 @@ pytest tests/unit/core/test_game_engine.py -v -o log_cli=true -o log_cli_level=D
```yaml
# Run fast unit tests on every commit
- name: Unit Tests
run: pytest tests/unit/ -v --cov=app
run: uv run pytest tests/unit/ -v --cov=app
# Run integration tests serially (slower but reliable)
- name: Integration Tests
run: |
pytest tests/integration/database/test_operations.py::TestDatabaseOperationsGame -v
pytest tests/integration/database/test_operations.py::TestDatabaseOperationsLineup -v
uv run pytest tests/integration/database/test_operations.py::TestDatabaseOperationsGame -v
uv run pytest tests/integration/database/test_operations.py::TestDatabaseOperationsLineup -v
# ... run each test class separately
```
@ -422,11 +422,12 @@ pytest tests/unit/core/test_game_engine.py -v -o log_cli=true -o log_cli_level=D
**Solution**:
```bash
# Set PYTHONPATH
export PYTHONPATH=/mnt/NV2/Development/strat-gameplay-webapp/backend
# Or run from backend directory
# Recommended: Use UV (handles PYTHONPATH automatically)
cd /mnt/NV2/Development/strat-gameplay-webapp/backend
uv run pytest tests/unit/ -v
# Alternative: Set PYTHONPATH manually
export PYTHONPATH=/mnt/NV2/Development/strat-gameplay-webapp/backend
pytest tests/unit/ -v
```

View File

@ -27,12 +27,19 @@ async def test_create_new_game_success(game_commands):
with patch('terminal_client.commands.uuid4', return_value=game_id):
with patch('terminal_client.commands.Config') as mock_config:
# Setup mocks
from app.models.game_models import LineupPlayerState
mock_batter = LineupPlayerState(
lineup_id=1,
card_id=100,
position='CF',
batting_order=1
)
mock_state = GameState(
game_id=game_id,
league_id='sba',
home_team_id=1,
away_team_id=2,
current_batter_lineup_id=1,
current_batter=mock_batter,
inning=1,
half='top'
)
@ -60,12 +67,19 @@ async def test_create_new_game_with_pd_league(game_commands):
with patch('terminal_client.commands.uuid4', return_value=game_id):
with patch('terminal_client.commands.Config') as mock_config:
# Setup mocks
from app.models.game_models import LineupPlayerState
mock_batter = LineupPlayerState(
lineup_id=1,
card_id=200,
position='SS',
batting_order=1
)
mock_state = GameState(
game_id=game_id,
league_id='pd',
home_team_id=3,
away_team_id=5,
current_batter_lineup_id=1,
current_batter=mock_batter,
inning=1,
half='top'
)