bmalph 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +111 -30
  2. package/bundled-versions.json +1 -2
  3. package/dist/cli.js +3 -2
  4. package/dist/commands/check-updates.js +5 -27
  5. package/dist/commands/doctor.d.ts +14 -2
  6. package/dist/commands/doctor.js +99 -56
  7. package/dist/commands/init.d.ts +1 -0
  8. package/dist/commands/init.js +75 -9
  9. package/dist/commands/upgrade.js +8 -5
  10. package/dist/installer.d.ts +16 -5
  11. package/dist/installer.js +245 -128
  12. package/dist/platform/aider.d.ts +2 -0
  13. package/dist/platform/aider.js +71 -0
  14. package/dist/platform/claude-code.d.ts +2 -0
  15. package/dist/platform/claude-code.js +88 -0
  16. package/dist/platform/codex.d.ts +2 -0
  17. package/dist/platform/codex.js +67 -0
  18. package/dist/platform/copilot.d.ts +2 -0
  19. package/dist/platform/copilot.js +71 -0
  20. package/dist/platform/cursor.d.ts +2 -0
  21. package/dist/platform/cursor.js +71 -0
  22. package/dist/platform/detect.d.ts +7 -0
  23. package/dist/platform/detect.js +23 -0
  24. package/dist/platform/index.d.ts +4 -0
  25. package/dist/platform/index.js +3 -0
  26. package/dist/platform/registry.d.ts +4 -0
  27. package/dist/platform/registry.js +27 -0
  28. package/dist/platform/resolve.d.ts +8 -0
  29. package/dist/platform/resolve.js +24 -0
  30. package/dist/platform/types.d.ts +41 -0
  31. package/dist/platform/types.js +7 -0
  32. package/dist/platform/windsurf.d.ts +2 -0
  33. package/dist/platform/windsurf.js +71 -0
  34. package/dist/transition/artifacts.js +1 -1
  35. package/dist/transition/fix-plan.d.ts +1 -1
  36. package/dist/transition/fix-plan.js +3 -2
  37. package/dist/transition/orchestration.js +1 -1
  38. package/dist/transition/specs-changelog.js +4 -1
  39. package/dist/transition/specs-index.js +2 -3
  40. package/dist/utils/config.d.ts +2 -1
  41. package/dist/utils/errors.js +3 -0
  42. package/dist/utils/github.d.ts +0 -1
  43. package/dist/utils/github.js +1 -18
  44. package/dist/utils/json.js +2 -2
  45. package/dist/utils/state.js +7 -1
  46. package/dist/utils/validate.d.ts +1 -0
  47. package/dist/utils/validate.js +35 -4
  48. package/package.json +4 -4
  49. package/ralph/drivers/claude-code.sh +118 -0
  50. package/ralph/drivers/codex.sh +81 -0
  51. package/ralph/ralph_import.sh +11 -0
  52. package/ralph/ralph_loop.sh +37 -64
  53. package/ralph/templates/ralphrc.template +7 -0
@@ -4,11 +4,6 @@ const BMAD_REPO = {
4
4
  repo: "BMAD-METHOD",
5
5
  branch: "main",
6
6
  };
7
- const RALPH_REPO = {
8
- owner: "snarktank",
9
- repo: "ralph",
10
- branch: "main",
11
- };
12
7
  const DEFAULT_TIMEOUT_MS = 10000;
13
8
  const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
14
9
  const DEFAULT_MAX_CACHE_SIZE = 100;
@@ -182,27 +177,15 @@ export class GitHubClient {
182
177
  async checkUpstream(bundled) {
183
178
  const errors = [];
184
179
  let bmadStatus = null;
185
- let ralphStatus = null;
186
- // Fetch both in parallel
187
- const [bmadResult, ralphResult] = await Promise.all([
188
- this.fetchLatestCommit(BMAD_REPO),
189
- this.fetchLatestCommit(RALPH_REPO),
190
- ]);
180
+ const bmadResult = await this.fetchLatestCommit(BMAD_REPO);
191
181
  if (bmadResult.success) {
192
182
  bmadStatus = buildUpstreamStatus(BMAD_REPO, bundled.bmadCommit, bmadResult.data.shortSha);
193
183
  }
194
184
  else {
195
185
  errors.push({ ...bmadResult.error, repo: "bmad" });
196
186
  }
197
- if (ralphResult.success) {
198
- ralphStatus = buildUpstreamStatus(RALPH_REPO, bundled.ralphCommit, ralphResult.data.shortSha);
199
- }
200
- else {
201
- errors.push({ ...ralphResult.error, repo: "ralph" });
202
- }
203
187
  return {
204
188
  bmad: bmadStatus,
205
- ralph: ralphStatus,
206
189
  errors,
207
190
  };
208
191
  }
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "fs/promises";
2
- import { isEnoent, formatError } from "./errors.js";
2
+ import { isEnoent } from "./errors.js";
3
3
  /**
4
4
  * Reads and parses a JSON file with proper error discrimination:
5
5
  * - File not found → returns null
@@ -21,6 +21,6 @@ export async function readJsonFile(path) {
21
21
  return JSON.parse(content);
22
22
  }
23
23
  catch (err) {
24
- throw new Error(`Invalid JSON in ${path}: ${formatError(err)}`);
24
+ throw new Error(`Invalid JSON in ${path}`, { cause: err });
25
25
  }
26
26
  }
@@ -1,7 +1,7 @@
1
1
  import { mkdir } from "fs/promises";
2
2
  import { join } from "path";
3
3
  import { readJsonFile } from "./json.js";
4
- import { validateState, validateRalphLoopStatus } from "./validate.js";
4
+ import { validateState, validateRalphLoopStatus, normalizeRalphStatus } from "./validate.js";
5
5
  import { STATE_DIR, RALPH_STATUS_FILE } from "./constants.js";
6
6
  import { atomicWriteFile } from "./file-system.js";
7
7
  import { warn } from "./logger.js";
@@ -186,6 +186,12 @@ export async function readRalphStatus(projectDir) {
186
186
  try {
187
187
  return validateRalphLoopStatus(data);
188
188
  }
189
+ catch {
190
+ // camelCase validation failed — try bash snake_case format
191
+ }
192
+ try {
193
+ return normalizeRalphStatus(data);
194
+ }
189
195
  catch (err) {
190
196
  warn(`Ralph status file is corrupted, using defaults: ${formatError(err)}`);
191
197
  return DEFAULT_RALPH_STATUS;
@@ -27,6 +27,7 @@ export interface RalphLoopStatus {
27
27
  tasksTotal: number;
28
28
  }
29
29
  export declare function validateRalphLoopStatus(data: unknown): RalphLoopStatus;
30
+ export declare function normalizeRalphStatus(data: unknown): RalphLoopStatus;
30
31
  /**
31
32
  * Validates a project name for filesystem safety.
32
33
  * Checks for:
@@ -1,4 +1,12 @@
1
1
  import { MAX_PROJECT_NAME_LENGTH } from "./constants.js";
2
+ const VALID_PLATFORM_IDS = [
3
+ "claude-code",
4
+ "codex",
5
+ "cursor",
6
+ "windsurf",
7
+ "copilot",
8
+ "aider",
9
+ ];
2
10
  const VALID_STATUSES = ["planning", "implementing", "completed"];
3
11
  // Invalid filesystem characters (Windows + POSIX)
4
12
  const INVALID_FS_CHARS = /[<>:"/\\|?*]/;
@@ -37,12 +45,8 @@ function validateUpstreamVersions(data) {
37
45
  if (typeof data.bmadCommit !== "string") {
38
46
  throw new Error("upstreamVersions.bmadCommit must be a string");
39
47
  }
40
- if (typeof data.ralphCommit !== "string") {
41
- throw new Error("upstreamVersions.ralphCommit must be a string");
42
- }
43
48
  return {
44
49
  bmadCommit: data.bmadCommit,
45
- ralphCommit: data.ralphCommit,
46
50
  };
47
51
  }
48
52
  export function validateConfig(data) {
@@ -54,6 +58,13 @@ export function validateConfig(data) {
54
58
  throw new Error("config.createdAt must be a string");
55
59
  }
56
60
  const description = typeof data.description === "string" ? data.description : "";
61
+ let platform;
62
+ if (data.platform !== undefined) {
63
+ if (typeof data.platform !== "string" || !VALID_PLATFORM_IDS.includes(data.platform)) {
64
+ throw new Error(`config.platform must be one of: ${VALID_PLATFORM_IDS.join(", ")}`);
65
+ }
66
+ platform = data.platform;
67
+ }
57
68
  const upstreamVersions = data.upstreamVersions !== undefined
58
69
  ? validateUpstreamVersions(data.upstreamVersions)
59
70
  : undefined;
@@ -61,6 +72,7 @@ export function validateConfig(data) {
61
72
  name: data.name,
62
73
  description,
63
74
  createdAt: data.createdAt,
75
+ ...(platform !== undefined && { platform }),
64
76
  upstreamVersions,
65
77
  };
66
78
  }
@@ -158,6 +170,25 @@ export function validateRalphLoopStatus(data) {
158
170
  tasksTotal: data.tasksTotal,
159
171
  };
160
172
  }
173
+ const BASH_STATUS_MAP = {
174
+ running: "running",
175
+ halted: "blocked",
176
+ stopped: "blocked",
177
+ completed: "completed",
178
+ success: "completed",
179
+ };
180
+ export function normalizeRalphStatus(data) {
181
+ assertObject(data, "normalizeRalphStatus");
182
+ const loopCount = typeof data.loop_count === "number" ? data.loop_count : 0;
183
+ const rawStatus = typeof data.status === "string" ? data.status : undefined;
184
+ const status = (rawStatus !== undefined ? BASH_STATUS_MAP[rawStatus] : undefined) ?? "running";
185
+ return {
186
+ loopCount,
187
+ status,
188
+ tasksCompleted: 0,
189
+ tasksTotal: 0,
190
+ };
191
+ }
161
192
  /**
162
193
  * Validates a project name for filesystem safety.
163
194
  * Checks for:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmalph",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Unified AI Development Framework - BMAD phases with Ralph execution loop for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -63,13 +63,13 @@
63
63
  "inquirer": "^13.2.1"
64
64
  },
65
65
  "devDependencies": {
66
- "@eslint/js": "^9.39.2",
66
+ "@eslint/js": "^10.0.1",
67
67
  "@types/node": "^20.0.0",
68
68
  "@vitest/coverage-v8": "^4.0.18",
69
- "eslint": "^9.39.2",
69
+ "eslint": "^10.0.1",
70
70
  "prettier": "^3.8.1",
71
71
  "typescript": "^5.9.3",
72
- "typescript-eslint": "^8.53.1",
72
+ "typescript-eslint": "^8.56.0",
73
73
  "vitest": "^4.0.18"
74
74
  }
75
75
  }
@@ -0,0 +1,118 @@
1
+ #!/bin/bash
2
+ # Claude Code driver for Ralph
3
+ # Provides platform-specific CLI invocation logic
4
+
5
+ # Driver identification
6
+ driver_name() {
7
+ echo "claude-code"
8
+ }
9
+
10
+ driver_display_name() {
11
+ echo "Claude Code"
12
+ }
13
+
14
+ driver_cli_binary() {
15
+ echo "claude"
16
+ }
17
+
18
+ driver_min_version() {
19
+ echo "2.0.76"
20
+ }
21
+
22
+ # Check if the CLI binary is available
23
+ driver_check_available() {
24
+ command -v "$(driver_cli_binary)" &>/dev/null
25
+ }
26
+
27
+ # Valid tool patterns for --allowedTools validation
28
+ # Sets the global VALID_TOOL_PATTERNS array
29
+ driver_valid_tools() {
30
+ VALID_TOOL_PATTERNS=(
31
+ "Write"
32
+ "Read"
33
+ "Edit"
34
+ "MultiEdit"
35
+ "Glob"
36
+ "Grep"
37
+ "Task"
38
+ "TodoWrite"
39
+ "WebFetch"
40
+ "WebSearch"
41
+ "Bash"
42
+ "Bash(git *)"
43
+ "Bash(npm *)"
44
+ "Bash(bats *)"
45
+ "Bash(python *)"
46
+ "Bash(node *)"
47
+ "NotebookEdit"
48
+ )
49
+ }
50
+
51
+ # Build the CLI command arguments
52
+ # Populates global CLAUDE_CMD_ARGS array
53
+ # Parameters:
54
+ # $1 - prompt_file: path to the prompt file
55
+ # $2 - loop_context: context string for session continuity
56
+ # $3 - session_id: session ID for resume (empty for new session)
57
+ driver_build_command() {
58
+ local prompt_file=$1
59
+ local loop_context=$2
60
+ local session_id=$3
61
+
62
+ # Note: We do NOT use --dangerously-skip-permissions here. Tool permissions
63
+ # are controlled via --allowedTools from CLAUDE_ALLOWED_TOOLS in .ralphrc.
64
+ # This preserves the permission denial circuit breaker (Issue #101).
65
+ CLAUDE_CMD_ARGS=("$(driver_cli_binary)")
66
+
67
+ if [[ ! -f "$prompt_file" ]]; then
68
+ echo "ERROR: Prompt file not found: $prompt_file" >&2
69
+ return 1
70
+ fi
71
+
72
+ # Output format
73
+ if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
74
+ CLAUDE_CMD_ARGS+=("--output-format" "json")
75
+ fi
76
+
77
+ # Allowed tools
78
+ if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
79
+ CLAUDE_CMD_ARGS+=("--allowedTools")
80
+ local IFS=','
81
+ read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
82
+ for tool in "${tools_array[@]}"; do
83
+ tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
84
+ if [[ -n "$tool" ]]; then
85
+ CLAUDE_CMD_ARGS+=("$tool")
86
+ fi
87
+ done
88
+ fi
89
+
90
+ # Session resume
91
+ # IMPORTANT: Use --resume with explicit session ID instead of --continue.
92
+ # --continue resumes the "most recent session in current directory" which
93
+ # can hijack active Claude Code sessions. --resume with a specific session ID
94
+ # ensures we only resume Ralph's own sessions. (Issue #151)
95
+ if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
96
+ CLAUDE_CMD_ARGS+=("--resume" "$session_id")
97
+ fi
98
+
99
+ # Loop context as system prompt
100
+ if [[ -n "$loop_context" ]]; then
101
+ CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
102
+ fi
103
+
104
+ # Prompt content
105
+ local prompt_content
106
+ prompt_content=$(cat "$prompt_file")
107
+ CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
108
+ }
109
+
110
+ # Whether this driver supports session continuity
111
+ driver_supports_sessions() {
112
+ return 0 # true
113
+ }
114
+
115
+ # Stream filter for live output (jq filter for JSON streaming)
116
+ driver_stream_filter() {
117
+ echo '.content // empty | select(type == "string")'
118
+ }
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ # OpenAI Codex driver for Ralph
3
+ # Provides platform-specific CLI invocation logic for Codex
4
+
5
+ driver_name() {
6
+ echo "codex"
7
+ }
8
+
9
+ driver_display_name() {
10
+ echo "OpenAI Codex"
11
+ }
12
+
13
+ driver_cli_binary() {
14
+ echo "codex"
15
+ }
16
+
17
+ driver_min_version() {
18
+ echo "0.1.0"
19
+ }
20
+
21
+ driver_check_available() {
22
+ command -v "$(driver_cli_binary)" &>/dev/null
23
+ }
24
+
25
+ # Codex tool names differ from Claude Code
26
+ driver_valid_tools() {
27
+ VALID_TOOL_PATTERNS=(
28
+ "shell"
29
+ "read_file"
30
+ "write_file"
31
+ "edit_file"
32
+ "list_directory"
33
+ "search_files"
34
+ )
35
+ }
36
+
37
+ # Build Codex CLI command
38
+ # Codex uses: codex exec [--resume <id>] --json "prompt"
39
+ driver_build_command() {
40
+ local prompt_file=$1
41
+ local loop_context=$2
42
+ local session_id=$3
43
+
44
+ CLAUDE_CMD_ARGS=("$(driver_cli_binary)" "exec")
45
+
46
+ if [[ ! -f "$prompt_file" ]]; then
47
+ echo "ERROR: Prompt file not found: $prompt_file" >&2
48
+ return 1
49
+ fi
50
+
51
+ # JSON output
52
+ CLAUDE_CMD_ARGS+=("--json")
53
+
54
+ # Sandbox mode - workspace write access
55
+ CLAUDE_CMD_ARGS+=("--sandbox" "workspace-write")
56
+
57
+ # Session resume — gated on CLAUDE_USE_CONTINUE to respect --no-continue flag
58
+ if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
59
+ CLAUDE_CMD_ARGS+=("--resume" "$session_id")
60
+ fi
61
+
62
+ # Build prompt with context
63
+ local prompt_content
64
+ prompt_content=$(cat "$prompt_file")
65
+ if [[ -n "$loop_context" ]]; then
66
+ prompt_content="$loop_context
67
+
68
+ $prompt_content"
69
+ fi
70
+
71
+ CLAUDE_CMD_ARGS+=("$prompt_content")
72
+ }
73
+
74
+ driver_supports_sessions() {
75
+ return 0 # true - Codex supports session resume
76
+ }
77
+
78
+ # Codex outputs JSONL events
79
+ driver_stream_filter() {
80
+ echo 'select(.type == "message") | .content // empty'
81
+ }
@@ -7,6 +7,17 @@ set -e
7
7
  # Configuration
8
8
  CLAUDE_CODE_CMD="claude"
9
9
 
10
+ # Platform driver support
11
+ SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
12
+ PLATFORM_DRIVER="${PLATFORM_DRIVER:-claude-code}"
13
+
14
+ # Source platform driver if available
15
+ if [[ -f "$SCRIPT_DIR/drivers/${PLATFORM_DRIVER}.sh" ]]; then
16
+ # shellcheck source=/dev/null
17
+ source "$SCRIPT_DIR/drivers/${PLATFORM_DRIVER}.sh"
18
+ CLAUDE_CODE_CMD="$(driver_cli_binary)"
19
+ fi
20
+
10
21
  # Modern CLI Configuration (Phase 1.1)
11
22
  # These flags enable structured JSON output and controlled file operations
12
23
  CLAUDE_OUTPUT_FORMAT="json"
@@ -66,7 +66,8 @@ RALPH_SESSION_HISTORY_FILE="$RALPH_DIR/.ralph_session_history" # Session transi
66
66
  CLAUDE_SESSION_EXPIRY_HOURS=${CLAUDE_SESSION_EXPIRY_HOURS:-24}
67
67
 
68
68
  # Valid tool patterns for --allowed-tools validation
69
- # Tools can be exact matches or pattern matches with wildcards in parentheses
69
+ # Default: Claude Code tools. Platform driver overwrites via driver_valid_tools() in main().
70
+ # Validation runs in main() after load_platform_driver so the correct patterns are in effect.
70
71
  VALID_TOOL_PATTERNS=(
71
72
  "Write"
72
73
  "Read"
@@ -98,6 +99,9 @@ TEST_PERCENTAGE_THRESHOLD=30 # If more than 30% of recent loops are test-only,
98
99
  RALPHRC_FILE=".ralphrc"
99
100
  RALPHRC_LOADED=false
100
101
 
102
+ # Platform driver (set from .ralphrc or environment)
103
+ PLATFORM_DRIVER="${PLATFORM_DRIVER:-claude-code}"
104
+
101
105
  # load_ralphrc - Load project-specific configuration from .ralphrc
102
106
  #
103
107
  # This function sources .ralphrc if it exists, applying project-specific
@@ -155,6 +159,26 @@ load_ralphrc() {
155
159
  return 0
156
160
  }
157
161
 
162
+ # Source platform driver
163
+ load_platform_driver() {
164
+ local driver_file="$SCRIPT_DIR/drivers/${PLATFORM_DRIVER}.sh"
165
+ if [[ ! -f "$driver_file" ]]; then
166
+ log_status "ERROR" "Platform driver not found: $driver_file"
167
+ log_status "ERROR" "Available drivers: $(ls "$SCRIPT_DIR/drivers/"*.sh 2>/dev/null | xargs -n1 basename | sed 's/.sh$//' | tr '\n' ' ')"
168
+ exit 1
169
+ fi
170
+ # shellcheck source=/dev/null
171
+ source "$driver_file"
172
+
173
+ # Initialize driver-specific tool patterns
174
+ driver_valid_tools
175
+
176
+ # Set CLI binary from driver
177
+ CLAUDE_CODE_CMD="$(driver_cli_binary)"
178
+
179
+ log_status "INFO" "Platform driver: $(driver_display_name) ($(driver_cli_binary))"
180
+ }
181
+
158
182
  # Colors for terminal output
159
183
  RED='\033[0;31m'
160
184
  GREEN='\033[0;32m'
@@ -949,68 +973,11 @@ update_session_last_used() {
949
973
  # Global array for Claude command arguments (avoids shell injection)
950
974
  declare -a CLAUDE_CMD_ARGS=()
951
975
 
952
- # Build Claude CLI command with modern flags using array (shell-injection safe)
976
+ # Build CLI command with platform driver (shell-injection safe)
977
+ # Delegates to the active platform driver's driver_build_command()
953
978
  # Populates global CLAUDE_CMD_ARGS array for direct execution
954
- # Uses -p flag with prompt content (Claude CLI does not have --prompt-file)
955
979
  build_claude_command() {
956
- local prompt_file=$1
957
- local loop_context=$2
958
- local session_id=$3
959
-
960
- # Reset global array
961
- # Note: We do NOT use --dangerously-skip-permissions here. Tool permissions
962
- # are controlled via --allowedTools from CLAUDE_ALLOWED_TOOLS in .ralphrc.
963
- # This preserves the permission denial circuit breaker (Issue #101).
964
- CLAUDE_CMD_ARGS=("$CLAUDE_CODE_CMD")
965
-
966
- # Check if prompt file exists
967
- if [[ ! -f "$prompt_file" ]]; then
968
- log_status "ERROR" "Prompt file not found: $prompt_file"
969
- return 1
970
- fi
971
-
972
- # Add output format flag
973
- if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
974
- CLAUDE_CMD_ARGS+=("--output-format" "json")
975
- fi
976
-
977
- # Add allowed tools (each tool as separate array element)
978
- if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
979
- CLAUDE_CMD_ARGS+=("--allowedTools")
980
- # Split by comma and add each tool
981
- local IFS=','
982
- read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
983
- for tool in "${tools_array[@]}"; do
984
- # Trim whitespace
985
- tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
986
- if [[ -n "$tool" ]]; then
987
- CLAUDE_CMD_ARGS+=("$tool")
988
- fi
989
- done
990
- fi
991
-
992
- # Add session continuity flag
993
- # IMPORTANT: Use --resume with explicit session ID instead of --continue
994
- # --continue resumes the "most recent session in current directory" which
995
- # can hijack active Claude Code sessions. --resume with a specific session ID
996
- # ensures we only resume Ralph's own sessions. (Issue #151)
997
- if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
998
- CLAUDE_CMD_ARGS+=("--resume" "$session_id")
999
- fi
1000
- # If no session_id, start fresh - Claude will generate a new session ID
1001
- # which we'll capture via save_claude_session() for future loops
1002
-
1003
- # Add loop context as system prompt (no escaping needed - array handles it)
1004
- if [[ -n "$loop_context" ]]; then
1005
- CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
1006
- fi
1007
-
1008
- # Read prompt file content and use -p flag
1009
- # Note: Claude CLI uses -p for prompts, not --prompt-file (which doesn't exist)
1010
- # Array-based approach maintains shell injection safety
1011
- local prompt_content
1012
- prompt_content=$(cat "$prompt_file")
1013
- CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
980
+ driver_build_command "$@"
1014
981
  }
1015
982
 
1016
983
  # Main execution function
@@ -1418,6 +1385,14 @@ main() {
1418
1385
  fi
1419
1386
  fi
1420
1387
 
1388
+ # Load platform driver (after .ralphrc so PLATFORM_DRIVER can be overridden)
1389
+ load_platform_driver
1390
+
1391
+ # Validate --allowed-tools now that platform-specific VALID_TOOL_PATTERNS are loaded
1392
+ if [[ "${_CLI_ALLOWED_TOOLS:-}" == "true" ]] && ! validate_allowed_tools "$CLAUDE_ALLOWED_TOOLS"; then
1393
+ exit 1
1394
+ fi
1395
+
1421
1396
  log_status "SUCCESS" "🚀 Ralph loop starting with Claude Code"
1422
1397
  log_status "INFO" "Max calls per hour: $MAX_CALLS_PER_HOUR"
1423
1398
  log_status "INFO" "Logs: $LOG_DIR/ | Docs: $DOCS_DIR/ | Status: $STATUS_FILE"
@@ -1750,10 +1725,8 @@ while [[ $# -gt 0 ]]; do
1750
1725
  shift 2
1751
1726
  ;;
1752
1727
  --allowed-tools)
1753
- if ! validate_allowed_tools "$2"; then
1754
- exit 1
1755
- fi
1756
1728
  CLAUDE_ALLOWED_TOOLS="$2"
1729
+ _CLI_ALLOWED_TOOLS=true
1757
1730
  shift 2
1758
1731
  ;;
1759
1732
  --no-continue)
@@ -6,6 +6,13 @@
6
6
  # Values here override global Ralph defaults.
7
7
  # Environment variables override values in this file.
8
8
 
9
+ # =============================================================================
10
+ # PLATFORM DRIVER
11
+ # =============================================================================
12
+
13
+ # Platform driver for Ralph loop (claude-code or codex)
14
+ PLATFORM_DRIVER="${PLATFORM_DRIVER:-claude-code}"
15
+
9
16
  # =============================================================================
10
17
  # PROJECT IDENTIFICATION
11
18
  # =============================================================================