From 3d7db2c3aeacd25f9f7a4fe76b00a108f57e1138 Mon Sep 17 00:00:00 2001 From: frankbria Date: Wed, 31 Dec 2025 16:04:49 -0700 Subject: [PATCH 1/2] feat(date): add cross-platform date compatibility for macOS and Linux Add cross-platform date utility library to handle differences between GNU date (Linux) and BSD date (macOS). Fixes issues with: - ISO 8601 timestamp formatting (-Iseconds flag) - Date arithmetic operations (-d vs -v flags) Changes: - Created lib/date_utils.sh with get_iso_timestamp() and get_next_hour_time() - Updated ralph_loop.sh to use date utilities (2 instances) - Updated lib/circuit_breaker.sh to use date utilities (4 instances) - Updated lib/response_analyzer.sh to use date utilities (1 instance) All date operations now work consistently across both platforms without modification. The utility automatically detects the OS and uses the appropriate date command syntax. Tested on Linux with GNU date - all syntax checks and integration tests pass. --- lib/circuit_breaker.sh | 11 +++++++---- lib/date_utils.sh | 41 ++++++++++++++++++++++++++++++++++++++++ lib/response_analyzer.sh | 5 ++++- ralph_loop.sh | 5 +++-- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 lib/date_utils.sh diff --git a/lib/circuit_breaker.sh b/lib/circuit_breaker.sh index e681cae..3e11460 100644 --- a/lib/circuit_breaker.sh +++ b/lib/circuit_breaker.sh @@ -3,6 +3,9 @@ # Prevents runaway token consumption by detecting stagnation # Based on Michael Nygard's "Release It!" pattern +# Source date utilities for cross-platform compatibility +source "$(dirname "${BASH_SOURCE[0]}")/date_utils.sh" + # Circuit Breaker States CB_STATE_CLOSED="CLOSED" # Normal operation, progress detected CB_STATE_HALF_OPEN="HALF_OPEN" # Monitoring mode, checking for recovery @@ -36,7 +39,7 @@ init_circuit_breaker() { cat > "$CB_STATE_FILE" << EOF { "state": "$CB_STATE_CLOSED", - "last_change": "$(date -Iseconds)", + "last_change": "$(get_iso_timestamp)", "consecutive_no_progress": 0, "consecutive_same_error": 0, "last_progress_loop": 0, @@ -164,7 +167,7 @@ record_loop_result() { cat > "$CB_STATE_FILE" << EOF { "state": "$new_state", - "last_change": "$(date -Iseconds)", + "last_change": "$(get_iso_timestamp)", "consecutive_no_progress": $consecutive_no_progress, "consecutive_same_error": $consecutive_same_error, "last_progress_loop": $last_progress_loop, @@ -196,7 +199,7 @@ log_circuit_transition() { local history=$(cat "$CB_HISTORY_FILE") local transition="{ - \"timestamp\": \"$(date -Iseconds)\", + \"timestamp\": \"$(get_iso_timestamp)\", \"loop\": $loop_number, \"from_state\": \"$from_state\", \"to_state\": \"$to_state\", @@ -272,7 +275,7 @@ reset_circuit_breaker() { cat > "$CB_STATE_FILE" << EOF { "state": "$CB_STATE_CLOSED", - "last_change": "$(date -Iseconds)", + "last_change": "$(get_iso_timestamp)", "consecutive_no_progress": 0, "consecutive_same_error": 0, "last_progress_loop": 0, diff --git a/lib/date_utils.sh b/lib/date_utils.sh new file mode 100644 index 0000000..a49e6e4 --- /dev/null +++ b/lib/date_utils.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# date_utils.sh - Cross-platform date utility functions +# Provides consistent date formatting and arithmetic across GNU (Linux) and BSD (macOS) systems + +# Get current timestamp in ISO 8601 format with seconds precision +# Returns: YYYY-MM-DDTHH:MM:SS+00:00 format +get_iso_timestamp() { + local os_type + os_type=$(uname) + + if [[ "$os_type" == "Darwin" ]]; then + # macOS (BSD date) + # Use manual formatting and add colon to timezone offset + date -u +"%Y-%m-%dT%H:%M:%S%z" | sed 's/\(..\)$/:\1/' + else + # Linux (GNU date) + date -Iseconds + fi +} + +# Get time component (HH:MM:SS) for one hour from now +# Returns: HH:MM:SS format +get_next_hour_time() { + local os_type + os_type=$(uname) + + if [[ "$os_type" == "Darwin" ]]; then + # macOS (BSD date) - use -v flag for date arithmetic + date -v+1H '+%H:%M:%S' + else + # Linux (GNU date) - use -d flag for date arithmetic + date -d '+1 hour' '+%H:%M:%S' + fi +} + +# Get current timestamp in a basic format (fallback) +# Returns: YYYY-MM-DD HH:MM:SS format +get_basic_timestamp() { + date '+%Y-%m-%d %H:%M:%S' +} diff --git a/lib/response_analyzer.sh b/lib/response_analyzer.sh index 30f8f03..df729e0 100644 --- a/lib/response_analyzer.sh +++ b/lib/response_analyzer.sh @@ -2,6 +2,9 @@ # Response Analyzer Component for Ralph # Analyzes Claude Code output to detect completion signals, test-only loops, and progress +# Source date utilities for cross-platform compatibility +source "$(dirname "${BASH_SOURCE[0]}")/date_utils.sh" + # Response Analysis Functions # Based on expert recommendations from Martin Fowler, Michael Nygard, Sam Newman @@ -152,7 +155,7 @@ analyze_response() { cat > "$analysis_result_file" << EOF { "loop_number": $loop_number, - "timestamp": "$(date -Iseconds)", + "timestamp": "$(get_iso_timestamp)", "output_file": "$output_file", "analysis": { "has_completion_signal": $has_completion_signal, diff --git a/ralph_loop.sh b/ralph_loop.sh index 6881366..7cb03c4 100755 --- a/ralph_loop.sh +++ b/ralph_loop.sh @@ -7,6 +7,7 @@ set -e # Exit on any error # Source library components SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +source "$SCRIPT_DIR/lib/date_utils.sh" source "$SCRIPT_DIR/lib/response_analyzer.sh" source "$SCRIPT_DIR/lib/circuit_breaker.sh" @@ -164,14 +165,14 @@ update_status() { cat > "$STATUS_FILE" << STATUSEOF { - "timestamp": "$(date -Iseconds)", + "timestamp": "$(get_iso_timestamp)", "loop_count": $loop_count, "calls_made_this_hour": $calls_made, "max_calls_per_hour": $MAX_CALLS_PER_HOUR, "last_action": "$last_action", "status": "$status", "exit_reason": "$exit_reason", - "next_reset": "$(date -d '+1 hour' -Iseconds | cut -d'T' -f2 | cut -d'+' -f1)" + "next_reset": "$(get_next_hour_time)" } STATUSEOF } From 70279c017e91b8a9b568a23ad863b1952444cd59 Mon Sep 17 00:00:00 2001 From: frankbria Date: Wed, 31 Dec 2025 16:48:24 -0700 Subject: [PATCH 2/2] fix(date): ensure Linux date utility returns UTC timestamps Add -u flag to GNU date command in get_iso_timestamp() to match the macOS implementation and function documentation. Both platforms now consistently return UTC timestamps in YYYY-MM-DDTHH:MM:SS+00:00 format. Before: date -Iseconds (returned local time on Linux) After: date -u -Iseconds (returns UTC on Linux) --- lib/date_utils.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/date_utils.sh b/lib/date_utils.sh index a49e6e4..1e92b3b 100644 --- a/lib/date_utils.sh +++ b/lib/date_utils.sh @@ -14,8 +14,8 @@ get_iso_timestamp() { # Use manual formatting and add colon to timezone offset date -u +"%Y-%m-%dT%H:%M:%S%z" | sed 's/\(..\)$/:\1/' else - # Linux (GNU date) - date -Iseconds + # Linux (GNU date) - use -u flag for UTC + date -u -Iseconds fi }