diff --git a/CLAUDE.md b/CLAUDE.md index 061b0ab..36c04ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,7 +169,7 @@ tmux attach -t ### Running Tests ```bash -# Run all tests (484 tests) +# Run all tests (490 tests) npm test # Run specific test suites @@ -214,7 +214,7 @@ CLAUDE_MIN_VERSION="2.0.76" # Minimum Claude CLI version ``` **CLI Options:** -- `--output-format json|text` - Set Claude output format (default: json) +- `--output-format json|text` - Set Claude output format (default: json). Note: `--live` mode requires JSON and will auto-switch from text to json. - `--allowed-tools "Write,Read,Bash(git *)"` - Restrict allowed tools - `--no-continue` - Disable session continuity, start fresh each loop @@ -445,13 +445,13 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false ## Test Suite -### Test Files (484 tests total) +### Test Files (490 tests total) | File | Tests | Description | |------|-------|-------------| | `test_circuit_breaker_recovery.bats` | 19 | Cooldown timer, auto-reset, parse_iso_to_epoch, CLI flag (Issue #160) | | `test_cli_parsing.bats` | 35 | CLI argument parsing for all flags + monitor parameter forwarding | -| `test_cli_modern.bats` | 33 | Modern CLI commands (Phase 1.1) + build_claude_command fix | +| `test_cli_modern.bats` | 39 | Modern CLI commands (Phase 1.1) + build_claude_command fix + live mode text format fix (#164) | | `test_json_parsing.bats` | 52 | JSON output format parsing + Claude CLI format + session management + array format | | `test_session_continuity.bats` | 44 | Session lifecycle management + expiration + circuit breaker integration + issue #91 fix | | `test_exit_detection.bats` | 53 | Exit signal detection + EXIT_SIGNAL-based completion indicators + progress detection | diff --git a/ralph_loop.sh b/ralph_loop.sh index 18c1df6..c243052 100755 --- a/ralph_loop.sh +++ b/ralph_loop.sh @@ -1048,21 +1048,24 @@ execute_claude_code() { session_id=$(init_claude_session) fi + # Live mode requires JSON output (stream-json) — override text format + if [[ "$LIVE_OUTPUT" == "true" && "$CLAUDE_OUTPUT_FORMAT" == "text" ]]; then + log_status "WARN" "Live mode requires JSON output format. Overriding text → json for this session." + CLAUDE_OUTPUT_FORMAT="json" + fi + # Build the Claude CLI command with modern flags - # Note: We use the modern CLI with -p flag when CLAUDE_OUTPUT_FORMAT is "json" - # For backward compatibility, fall back to stdin piping for text mode local use_modern_cli=false - if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then - # Modern approach: use CLI flags (builds CLAUDE_CMD_ARGS array) - if build_claude_command "$PROMPT_FILE" "$loop_context" "$session_id"; then - use_modern_cli=true - log_status "INFO" "Using modern CLI mode (JSON output)" - else - log_status "WARN" "Failed to build modern CLI command, falling back to legacy mode" - fi + if build_claude_command "$PROMPT_FILE" "$loop_context" "$session_id"; then + use_modern_cli=true + log_status "INFO" "Using modern CLI mode (${CLAUDE_OUTPUT_FORMAT} output)" else - log_status "INFO" "Using legacy CLI mode (text output)" + log_status "WARN" "Failed to build modern CLI command, falling back to legacy mode" + if [[ "$LIVE_OUTPUT" == "true" ]]; then + log_status "ERROR" "Live mode requires a built Claude command. Falling back to background mode." + LIVE_OUTPUT=false + fi fi # Execute Claude Code @@ -1091,6 +1094,14 @@ execute_claude_code() { fi fi + if [[ "$LIVE_OUTPUT" == "true" ]]; then + # Safety check: live mode requires a successfully built modern command + if [[ "$use_modern_cli" != "true" || ${#CLAUDE_CMD_ARGS[@]} -eq 0 ]]; then + log_status "ERROR" "Live mode requires a built Claude command. Falling back to background mode." + LIVE_OUTPUT=false + fi + fi + if [[ "$LIVE_OUTPUT" == "true" ]]; then log_status "INFO" "📺 Live output mode enabled - showing Claude Code streaming..." echo -e "${PURPLE}━━━━━━━━━━━━━━━━ Claude Code Output ━━━━━━━━━━━━━━━━${NC}" @@ -1613,7 +1624,7 @@ Options: -s, --status Show current status and exit -m, --monitor Start with tmux session and live monitor (requires tmux) -v, --verbose Show detailed progress updates during execution - -l, --live Show Claude Code output in real-time (streaming mode) + -l, --live Show Claude Code output in real-time (auto-switches to JSON output) -t, --timeout MIN Set Claude Code execution timeout in minutes (default: $CLAUDE_TIMEOUT_MINUTES) --reset-circuit Reset circuit breaker to CLOSED state --circuit-status Show circuit breaker status and exit @@ -1622,6 +1633,7 @@ Options: Modern CLI Options (Phase 1.1): --output-format FORMAT Set Claude output format: json or text (default: $CLAUDE_OUTPUT_FORMAT) + Note: --live mode requires JSON and will auto-switch --allowed-tools TOOLS Comma-separated list of allowed tools (default: $CLAUDE_ALLOWED_TOOLS) --no-continue Disable session continuity across loops --session-expiry HOURS Set session expiration time in hours (default: $CLAUDE_SESSION_EXPIRY_HOURS) diff --git a/tests/unit/test_cli_modern.bats b/tests/unit/test_cli_modern.bats index 7c0017d..6bdbfe7 100644 --- a/tests/unit/test_cli_modern.bats +++ b/tests/unit/test_cli_modern.bats @@ -667,3 +667,99 @@ EOF [[ "$output" == *'_env_MAX_CALLS_PER_HOUR'* ]] [[ "$output" == *'_env_CLAUDE_TIMEOUT_MINUTES'* ]] } + +# ============================================================================= +# LIVE MODE + TEXT FORMAT FIX TESTS (Issue #164) +# Tests for: live mode format override, always-call build_claude_command, +# and safety check for empty CLAUDE_CMD_ARGS +# ============================================================================= + +@test "build_claude_command works for text format (populates CLAUDE_CMD_ARGS)" { + export CLAUDE_CODE_CMD="claude" + export CLAUDE_OUTPUT_FORMAT="text" + export CLAUDE_ALLOWED_TOOLS="Write,Read" + export CLAUDE_USE_CONTINUE="false" + + echo "Test prompt content" > "$PROMPT_FILE" + + build_claude_command "$PROMPT_FILE" "" "" + + # CLAUDE_CMD_ARGS should be populated even in text mode + [[ ${#CLAUDE_CMD_ARGS[@]} -gt 0 ]] + + local cmd_string="${CLAUDE_CMD_ARGS[*]}" + + # Should contain claude command and -p flag + [[ "$cmd_string" == *"claude"* ]] + [[ "$cmd_string" == *"-p"* ]] + [[ "$cmd_string" == *"Test prompt content"* ]] + + # Should NOT contain --output-format (text mode omits it) + [[ "$cmd_string" != *"--output-format"* ]] + + # Should still include allowed tools + [[ "$cmd_string" == *"--allowedTools"* ]] + [[ "$cmd_string" == *"Write"* ]] +} + +@test "build_claude_command works for json format (includes --output-format json)" { + export CLAUDE_CODE_CMD="claude" + export CLAUDE_OUTPUT_FORMAT="json" + export CLAUDE_ALLOWED_TOOLS="" + export CLAUDE_USE_CONTINUE="false" + + echo "Test prompt" > "$PROMPT_FILE" + + build_claude_command "$PROMPT_FILE" "" "" + + local cmd_string="${CLAUDE_CMD_ARGS[*]}" + + # Should contain --output-format json + [[ "$cmd_string" == *"--output-format"* ]] + [[ "$cmd_string" == *"json"* ]] + [[ "$cmd_string" == *"-p"* ]] +} + +@test "live mode overrides text to json format in ralph_loop.sh" { + # Verify ralph_loop.sh contains the live mode format override logic + run grep -A3 'LIVE_OUTPUT.*true.*CLAUDE_OUTPUT_FORMAT.*text' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # Should find the override block + [[ "$output" == *"CLAUDE_OUTPUT_FORMAT"* ]] + [[ "$output" == *"json"* ]] +} + +@test "live mode format override preserves json format unchanged" { + # The override should only trigger when format is "text", not "json" + # Verify the condition checks for text specifically + run grep 'CLAUDE_OUTPUT_FORMAT.*text' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # Should check specifically for "text" (not a blanket override) + [[ "$output" == *'"text"'* ]] +} + +@test "safety check prevents live mode with empty CLAUDE_CMD_ARGS" { + # Verify ralph_loop.sh has the safety check for empty CLAUDE_CMD_ARGS + # The check also verifies use_modern_cli is true (not just non-empty array) + run grep -A3 'use_modern_cli.*CLAUDE_CMD_ARGS.*-eq 0' "${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # Should find safety check that falls back to background mode + [[ "$output" == *"LIVE_OUTPUT"* ]] || [[ "$output" == *"background"* ]] +} + +@test "build_claude_command is called regardless of output format in ralph_loop.sh" { + # Verify that build_claude_command is NOT gated behind JSON-only check + # The old pattern was: if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then build_claude_command... + # The new pattern should call build_claude_command unconditionally + + # Check that build_claude_command call is NOT inside a JSON-only conditional + # Look for the actual call site (not the function definition or comments) + local script="${BATS_TEST_DIRNAME}/../../ralph_loop.sh" + + # The old pattern: "json" check immediately followed by build_claude_command + # should no longer exist as a gate + run bash -c "sed -n '/# Build the Claude CLI command/,/# Execute Claude Code/p' '$script' | grep -c 'CLAUDE_OUTPUT_FORMAT.*json.*build_claude_command'" + + # Should find 0 matches (the gate has been removed) + [[ "$output" == "0" ]] +}