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.
- package/README.md +111 -30
- package/bundled-versions.json +1 -2
- package/dist/cli.js +3 -2
- package/dist/commands/check-updates.js +5 -27
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.js +99 -56
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +75 -9
- package/dist/commands/upgrade.js +8 -5
- package/dist/installer.d.ts +16 -5
- package/dist/installer.js +245 -128
- package/dist/platform/aider.d.ts +2 -0
- package/dist/platform/aider.js +71 -0
- package/dist/platform/claude-code.d.ts +2 -0
- package/dist/platform/claude-code.js +88 -0
- package/dist/platform/codex.d.ts +2 -0
- package/dist/platform/codex.js +67 -0
- package/dist/platform/copilot.d.ts +2 -0
- package/dist/platform/copilot.js +71 -0
- package/dist/platform/cursor.d.ts +2 -0
- package/dist/platform/cursor.js +71 -0
- package/dist/platform/detect.d.ts +7 -0
- package/dist/platform/detect.js +23 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.js +3 -0
- package/dist/platform/registry.d.ts +4 -0
- package/dist/platform/registry.js +27 -0
- package/dist/platform/resolve.d.ts +8 -0
- package/dist/platform/resolve.js +24 -0
- package/dist/platform/types.d.ts +41 -0
- package/dist/platform/types.js +7 -0
- package/dist/platform/windsurf.d.ts +2 -0
- package/dist/platform/windsurf.js +71 -0
- package/dist/transition/artifacts.js +1 -1
- package/dist/transition/fix-plan.d.ts +1 -1
- package/dist/transition/fix-plan.js +3 -2
- package/dist/transition/orchestration.js +1 -1
- package/dist/transition/specs-changelog.js +4 -1
- package/dist/transition/specs-index.js +2 -3
- package/dist/utils/config.d.ts +2 -1
- package/dist/utils/errors.js +3 -0
- package/dist/utils/github.d.ts +0 -1
- package/dist/utils/github.js +1 -18
- package/dist/utils/json.js +2 -2
- package/dist/utils/state.js +7 -1
- package/dist/utils/validate.d.ts +1 -0
- package/dist/utils/validate.js +35 -4
- package/package.json +4 -4
- package/ralph/drivers/claude-code.sh +118 -0
- package/ralph/drivers/codex.sh +81 -0
- package/ralph/ralph_import.sh +11 -0
- package/ralph/ralph_loop.sh +37 -64
- package/ralph/templates/ralphrc.template +7 -0
package/dist/utils/github.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/utils/json.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
|
-
import { isEnoent
|
|
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}:
|
|
24
|
+
throw new Error(`Invalid JSON in ${path}`, { cause: err });
|
|
25
25
|
}
|
|
26
26
|
}
|
package/dist/utils/state.js
CHANGED
|
@@ -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;
|
package/dist/utils/validate.d.ts
CHANGED
|
@@ -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:
|
package/dist/utils/validate.js
CHANGED
|
@@ -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.
|
|
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": "^
|
|
66
|
+
"@eslint/js": "^10.0.1",
|
|
67
67
|
"@types/node": "^20.0.0",
|
|
68
68
|
"@vitest/coverage-v8": "^4.0.18",
|
|
69
|
-
"eslint": "^
|
|
69
|
+
"eslint": "^10.0.1",
|
|
70
70
|
"prettier": "^3.8.1",
|
|
71
71
|
"typescript": "^5.9.3",
|
|
72
|
-
"typescript-eslint": "^8.
|
|
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
|
+
}
|
package/ralph/ralph_import.sh
CHANGED
|
@@ -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"
|
package/ralph/ralph_loop.sh
CHANGED
|
@@ -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
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
# =============================================================================
|