bmalph 2.2.1 → 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 (41) hide show
  1. package/README.md +111 -30
  2. package/dist/cli.js +1 -0
  3. package/dist/commands/doctor.d.ts +14 -2
  4. package/dist/commands/doctor.js +83 -35
  5. package/dist/commands/init.d.ts +1 -0
  6. package/dist/commands/init.js +74 -7
  7. package/dist/commands/upgrade.js +8 -5
  8. package/dist/installer.d.ts +15 -4
  9. package/dist/installer.js +190 -101
  10. package/dist/platform/aider.d.ts +2 -0
  11. package/dist/platform/aider.js +71 -0
  12. package/dist/platform/claude-code.d.ts +2 -0
  13. package/dist/platform/claude-code.js +88 -0
  14. package/dist/platform/codex.d.ts +2 -0
  15. package/dist/platform/codex.js +67 -0
  16. package/dist/platform/copilot.d.ts +2 -0
  17. package/dist/platform/copilot.js +71 -0
  18. package/dist/platform/cursor.d.ts +2 -0
  19. package/dist/platform/cursor.js +71 -0
  20. package/dist/platform/detect.d.ts +7 -0
  21. package/dist/platform/detect.js +23 -0
  22. package/dist/platform/index.d.ts +4 -0
  23. package/dist/platform/index.js +3 -0
  24. package/dist/platform/registry.d.ts +4 -0
  25. package/dist/platform/registry.js +27 -0
  26. package/dist/platform/resolve.d.ts +8 -0
  27. package/dist/platform/resolve.js +24 -0
  28. package/dist/platform/types.d.ts +41 -0
  29. package/dist/platform/types.js +7 -0
  30. package/dist/platform/windsurf.d.ts +2 -0
  31. package/dist/platform/windsurf.js +71 -0
  32. package/dist/transition/artifacts.js +1 -1
  33. package/dist/transition/specs-changelog.js +4 -1
  34. package/dist/utils/config.d.ts +2 -0
  35. package/dist/utils/validate.js +16 -0
  36. package/package.json +1 -1
  37. package/ralph/drivers/claude-code.sh +118 -0
  38. package/ralph/drivers/codex.sh +81 -0
  39. package/ralph/ralph_import.sh +11 -0
  40. package/ralph/ralph_loop.sh +37 -64
  41. package/ralph/templates/ralphrc.template +7 -0
@@ -0,0 +1,67 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { isEnoent, formatError } from "../utils/errors.js";
4
+ export const codexPlatform = {
5
+ id: "codex",
6
+ displayName: "OpenAI Codex",
7
+ tier: "full",
8
+ instructionsFile: "AGENTS.md",
9
+ commandDelivery: { kind: "inline" },
10
+ instructionsSectionMarker: "## BMAD-METHOD Integration",
11
+ generateInstructionsSnippet: () => `
12
+ ## BMAD-METHOD Integration
13
+
14
+ Run the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
15
+
16
+ ### Phases
17
+
18
+ | Phase | Focus | Key Agents |
19
+ |-------|-------|-----------|
20
+ | 1. Analysis | Understand the problem | Analyst agent |
21
+ | 2. Planning | Define the solution | Product Manager agent |
22
+ | 3. Solutioning | Design the architecture | Architect agent |
23
+ | 4. Implementation | Build it | Developer agent, then Ralph autonomous loop |
24
+
25
+ ### Workflow
26
+
27
+ 1. Work through Phases 1-3 using BMAD agents and workflows
28
+ 2. Use the bmalph-implement transition to prepare Ralph format, then start Ralph
29
+
30
+ ### Available Agents
31
+
32
+ | Agent | Role |
33
+ |-------|------|
34
+ | Analyst | Research, briefs, discovery |
35
+ | Architect | Technical design, architecture |
36
+ | Product Manager | PRDs, epics, stories |
37
+ | Scrum Master | Sprint planning, status, coordination |
38
+ | Developer | Implementation, coding |
39
+ | UX Designer | User experience, wireframes |
40
+ | QA Engineer | Test automation, quality assurance |
41
+ `,
42
+ getDoctorChecks: () => [
43
+ {
44
+ id: "instructions-file",
45
+ label: "AGENTS.md contains BMAD snippet",
46
+ check: async (projectDir) => {
47
+ try {
48
+ const content = await readFile(join(projectDir, "AGENTS.md"), "utf-8");
49
+ if (content.includes("BMAD-METHOD Integration")) {
50
+ return { passed: true };
51
+ }
52
+ return {
53
+ passed: false,
54
+ detail: "missing BMAD-METHOD Integration section",
55
+ hint: "Run: bmalph init",
56
+ };
57
+ }
58
+ catch (err) {
59
+ if (isEnoent(err)) {
60
+ return { passed: false, detail: "AGENTS.md not found", hint: "Run: bmalph init" };
61
+ }
62
+ return { passed: false, detail: formatError(err), hint: "Check file permissions" };
63
+ }
64
+ },
65
+ },
66
+ ],
67
+ };
@@ -0,0 +1,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const copilotPlatform: Platform;
@@ -0,0 +1,71 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { isEnoent, formatError } from "../utils/errors.js";
4
+ export const copilotPlatform = {
5
+ id: "copilot",
6
+ displayName: "GitHub Copilot",
7
+ tier: "instructions-only",
8
+ instructionsFile: ".github/copilot-instructions.md",
9
+ commandDelivery: { kind: "none" },
10
+ instructionsSectionMarker: "## BMAD-METHOD Integration",
11
+ generateInstructionsSnippet: () => `
12
+ ## BMAD-METHOD Integration
13
+
14
+ Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
15
+
16
+ ### Phases
17
+
18
+ | Phase | Focus | Key Agents |
19
+ |-------|-------|-----------|
20
+ | 1. Analysis | Understand the problem | Analyst agent |
21
+ | 2. Planning | Define the solution | Product Manager agent |
22
+ | 3. Solutioning | Design the architecture | Architect agent |
23
+
24
+ ### Workflow
25
+
26
+ Work through Phases 1-3 using BMAD agents and workflows interactively.
27
+
28
+ > **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
29
+
30
+ ### Available Agents
31
+
32
+ | Agent | Role |
33
+ |-------|------|
34
+ | Analyst | Research, briefs, discovery |
35
+ | Architect | Technical design, architecture |
36
+ | Product Manager | PRDs, epics, stories |
37
+ | Scrum Master | Sprint planning, status, coordination |
38
+ | Developer | Implementation, coding |
39
+ | UX Designer | User experience, wireframes |
40
+ | QA Engineer | Test automation, quality assurance |
41
+ `,
42
+ getDoctorChecks: () => [
43
+ {
44
+ id: "instructions-file",
45
+ label: ".github/copilot-instructions.md contains BMAD snippet",
46
+ check: async (projectDir) => {
47
+ try {
48
+ const content = await readFile(join(projectDir, ".github/copilot-instructions.md"), "utf-8");
49
+ if (content.includes("BMAD-METHOD Integration")) {
50
+ return { passed: true };
51
+ }
52
+ return {
53
+ passed: false,
54
+ detail: "missing BMAD-METHOD Integration section",
55
+ hint: "Run: bmalph init",
56
+ };
57
+ }
58
+ catch (err) {
59
+ if (isEnoent(err)) {
60
+ return {
61
+ passed: false,
62
+ detail: ".github/copilot-instructions.md not found",
63
+ hint: "Run: bmalph init",
64
+ };
65
+ }
66
+ return { passed: false, detail: formatError(err), hint: "Check file permissions" };
67
+ }
68
+ },
69
+ },
70
+ ],
71
+ };
@@ -0,0 +1,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const cursorPlatform: Platform;
@@ -0,0 +1,71 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { isEnoent, formatError } from "../utils/errors.js";
4
+ export const cursorPlatform = {
5
+ id: "cursor",
6
+ displayName: "Cursor",
7
+ tier: "instructions-only",
8
+ instructionsFile: ".cursor/rules/bmad.mdc",
9
+ commandDelivery: { kind: "none" },
10
+ instructionsSectionMarker: "## BMAD-METHOD Integration",
11
+ generateInstructionsSnippet: () => `
12
+ ## BMAD-METHOD Integration
13
+
14
+ Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
15
+
16
+ ### Phases
17
+
18
+ | Phase | Focus | Key Agents |
19
+ |-------|-------|-----------|
20
+ | 1. Analysis | Understand the problem | Analyst agent |
21
+ | 2. Planning | Define the solution | Product Manager agent |
22
+ | 3. Solutioning | Design the architecture | Architect agent |
23
+
24
+ ### Workflow
25
+
26
+ Work through Phases 1-3 using BMAD agents and workflows interactively.
27
+
28
+ > **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
29
+
30
+ ### Available Agents
31
+
32
+ | Agent | Role |
33
+ |-------|------|
34
+ | Analyst | Research, briefs, discovery |
35
+ | Architect | Technical design, architecture |
36
+ | Product Manager | PRDs, epics, stories |
37
+ | Scrum Master | Sprint planning, status, coordination |
38
+ | Developer | Implementation, coding |
39
+ | UX Designer | User experience, wireframes |
40
+ | QA Engineer | Test automation, quality assurance |
41
+ `,
42
+ getDoctorChecks: () => [
43
+ {
44
+ id: "instructions-file",
45
+ label: ".cursor/rules/bmad.mdc contains BMAD snippet",
46
+ check: async (projectDir) => {
47
+ try {
48
+ const content = await readFile(join(projectDir, ".cursor/rules/bmad.mdc"), "utf-8");
49
+ if (content.includes("BMAD-METHOD Integration")) {
50
+ return { passed: true };
51
+ }
52
+ return {
53
+ passed: false,
54
+ detail: "missing BMAD-METHOD Integration section",
55
+ hint: "Run: bmalph init",
56
+ };
57
+ }
58
+ catch (err) {
59
+ if (isEnoent(err)) {
60
+ return {
61
+ passed: false,
62
+ detail: ".cursor/rules/bmad.mdc not found",
63
+ hint: "Run: bmalph init",
64
+ };
65
+ }
66
+ return { passed: false, detail: formatError(err), hint: "Check file permissions" };
67
+ }
68
+ },
69
+ },
70
+ ],
71
+ };
@@ -0,0 +1,7 @@
1
+ import type { PlatformId } from "./types.js";
2
+ interface DetectionResult {
3
+ detected: PlatformId | null;
4
+ candidates: PlatformId[];
5
+ }
6
+ export declare function detectPlatform(projectDir: string): Promise<DetectionResult>;
7
+ export {};
@@ -0,0 +1,23 @@
1
+ import { exists } from "../utils/file-system.js";
2
+ import { join } from "path";
3
+ const DETECTION_MARKERS = [
4
+ { platform: "claude-code", markers: [".claude"] },
5
+ { platform: "codex", markers: ["AGENTS.md"] },
6
+ { platform: "cursor", markers: [".cursor"] },
7
+ { platform: "windsurf", markers: [".windsurf"] },
8
+ { platform: "copilot", markers: [".github/copilot-instructions.md"] },
9
+ { platform: "aider", markers: [".aider.conf.yml"] },
10
+ ];
11
+ export async function detectPlatform(projectDir) {
12
+ const candidates = [];
13
+ for (const { platform, markers } of DETECTION_MARKERS) {
14
+ for (const marker of markers) {
15
+ if (await exists(join(projectDir, marker))) {
16
+ candidates.push(platform);
17
+ break;
18
+ }
19
+ }
20
+ }
21
+ const detected = candidates.length === 1 ? (candidates[0] ?? null) : null;
22
+ return { detected, candidates };
23
+ }
@@ -0,0 +1,4 @@
1
+ export type { Platform, PlatformId, PlatformTier, CommandDelivery, PlatformDoctorCheck, } from "./types.js";
2
+ export { getPlatform, getAllPlatforms, isPlatformId } from "./registry.js";
3
+ export { resolveProjectPlatform } from "./resolve.js";
4
+ export { detectPlatform } from "./detect.js";
@@ -0,0 +1,3 @@
1
+ export { getPlatform, getAllPlatforms, isPlatformId } from "./registry.js";
2
+ export { resolveProjectPlatform } from "./resolve.js";
3
+ export { detectPlatform } from "./detect.js";
@@ -0,0 +1,4 @@
1
+ import type { Platform, PlatformId } from "./types.js";
2
+ export declare function getPlatform(id: PlatformId): Platform;
3
+ export declare function getAllPlatforms(): Platform[];
4
+ export declare function isPlatformId(value: string): value is PlatformId;
@@ -0,0 +1,27 @@
1
+ import { claudeCodePlatform } from "./claude-code.js";
2
+ import { codexPlatform } from "./codex.js";
3
+ import { cursorPlatform } from "./cursor.js";
4
+ import { windsurfPlatform } from "./windsurf.js";
5
+ import { copilotPlatform } from "./copilot.js";
6
+ import { aiderPlatform } from "./aider.js";
7
+ const PLATFORMS = new Map([
8
+ ["claude-code", claudeCodePlatform],
9
+ ["codex", codexPlatform],
10
+ ["cursor", cursorPlatform],
11
+ ["windsurf", windsurfPlatform],
12
+ ["copilot", copilotPlatform],
13
+ ["aider", aiderPlatform],
14
+ ]);
15
+ export function getPlatform(id) {
16
+ const platform = PLATFORMS.get(id);
17
+ if (!platform) {
18
+ throw new Error(`Unknown platform: ${id}`);
19
+ }
20
+ return platform;
21
+ }
22
+ export function getAllPlatforms() {
23
+ return [...PLATFORMS.values()];
24
+ }
25
+ export function isPlatformId(value) {
26
+ return PLATFORMS.has(value);
27
+ }
@@ -0,0 +1,8 @@
1
+ import type { Platform } from "./types.js";
2
+ /**
3
+ * Resolve the platform for a project from its config, defaulting to claude-code.
4
+ *
5
+ * Used by doctor and upgrade commands to determine which platform checks and
6
+ * assets to use. Falls back to claude-code when config is missing or unreadable.
7
+ */
8
+ export declare function resolveProjectPlatform(projectDir: string): Promise<Platform>;
@@ -0,0 +1,24 @@
1
+ import { readConfig } from "../utils/config.js";
2
+ import { isEnoent, formatError } from "../utils/errors.js";
3
+ import { warn } from "../utils/logger.js";
4
+ import { getPlatform } from "./registry.js";
5
+ /**
6
+ * Resolve the platform for a project from its config, defaulting to claude-code.
7
+ *
8
+ * Used by doctor and upgrade commands to determine which platform checks and
9
+ * assets to use. Falls back to claude-code when config is missing or unreadable.
10
+ */
11
+ export async function resolveProjectPlatform(projectDir) {
12
+ try {
13
+ const config = await readConfig(projectDir);
14
+ if (config?.platform) {
15
+ return getPlatform(config.platform);
16
+ }
17
+ }
18
+ catch (err) {
19
+ if (!isEnoent(err)) {
20
+ warn(`Failed to read project config: ${formatError(err)}`);
21
+ }
22
+ }
23
+ return getPlatform("claude-code");
24
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Platform abstraction layer for bmalph.
3
+ *
4
+ * Controls how `bmalph init/upgrade/doctor` install instruction files,
5
+ * deliver slash commands, and run health checks per platform.
6
+ */
7
+ /** Supported platform identifiers. */
8
+ export type PlatformId = "claude-code" | "codex" | "cursor" | "windsurf" | "copilot" | "aider";
9
+ /** Full platforms support Phases 1-4 (planning + Ralph implementation). */
10
+ /** Instructions-only platforms support Phases 1-3 (planning only). */
11
+ export type PlatformTier = "full" | "instructions-only";
12
+ /** How slash commands are delivered to the platform. */
13
+ export type CommandDelivery = {
14
+ kind: "directory";
15
+ dir: string;
16
+ } | {
17
+ kind: "inline";
18
+ } | {
19
+ kind: "none";
20
+ };
21
+ /** Result of a single platform-specific doctor check. */
22
+ export interface PlatformDoctorCheck {
23
+ id: string;
24
+ label: string;
25
+ check: (projectDir: string) => Promise<{
26
+ passed: boolean;
27
+ detail?: string;
28
+ hint?: string;
29
+ }>;
30
+ }
31
+ /** Platform definition controlling install, upgrade, and doctor behavior. */
32
+ export interface Platform {
33
+ readonly id: PlatformId;
34
+ readonly displayName: string;
35
+ readonly tier: PlatformTier;
36
+ readonly instructionsFile: string;
37
+ readonly commandDelivery: CommandDelivery;
38
+ readonly instructionsSectionMarker: string;
39
+ readonly generateInstructionsSnippet: () => string;
40
+ readonly getDoctorChecks: () => PlatformDoctorCheck[];
41
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Platform abstraction layer for bmalph.
3
+ *
4
+ * Controls how `bmalph init/upgrade/doctor` install instruction files,
5
+ * deliver slash commands, and run health checks per platform.
6
+ */
7
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const windsurfPlatform: Platform;
@@ -0,0 +1,71 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { isEnoent, formatError } from "../utils/errors.js";
4
+ export const windsurfPlatform = {
5
+ id: "windsurf",
6
+ displayName: "Windsurf",
7
+ tier: "instructions-only",
8
+ instructionsFile: ".windsurf/rules/bmad.md",
9
+ commandDelivery: { kind: "none" },
10
+ instructionsSectionMarker: "## BMAD-METHOD Integration",
11
+ generateInstructionsSnippet: () => `
12
+ ## BMAD-METHOD Integration
13
+
14
+ Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
15
+
16
+ ### Phases
17
+
18
+ | Phase | Focus | Key Agents |
19
+ |-------|-------|-----------|
20
+ | 1. Analysis | Understand the problem | Analyst agent |
21
+ | 2. Planning | Define the solution | Product Manager agent |
22
+ | 3. Solutioning | Design the architecture | Architect agent |
23
+
24
+ ### Workflow
25
+
26
+ Work through Phases 1-3 using BMAD agents and workflows interactively.
27
+
28
+ > **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
29
+
30
+ ### Available Agents
31
+
32
+ | Agent | Role |
33
+ |-------|------|
34
+ | Analyst | Research, briefs, discovery |
35
+ | Architect | Technical design, architecture |
36
+ | Product Manager | PRDs, epics, stories |
37
+ | Scrum Master | Sprint planning, status, coordination |
38
+ | Developer | Implementation, coding |
39
+ | UX Designer | User experience, wireframes |
40
+ | QA Engineer | Test automation, quality assurance |
41
+ `,
42
+ getDoctorChecks: () => [
43
+ {
44
+ id: "instructions-file",
45
+ label: ".windsurf/rules/bmad.md contains BMAD snippet",
46
+ check: async (projectDir) => {
47
+ try {
48
+ const content = await readFile(join(projectDir, ".windsurf/rules/bmad.md"), "utf-8");
49
+ if (content.includes("BMAD-METHOD Integration")) {
50
+ return { passed: true };
51
+ }
52
+ return {
53
+ passed: false,
54
+ detail: "missing BMAD-METHOD Integration section",
55
+ hint: "Run: bmalph init",
56
+ };
57
+ }
58
+ catch (err) {
59
+ if (isEnoent(err)) {
60
+ return {
61
+ passed: false,
62
+ detail: ".windsurf/rules/bmad.md not found",
63
+ hint: "Run: bmalph init",
64
+ };
65
+ }
66
+ return { passed: false, detail: formatError(err), hint: "Check file permissions" };
67
+ }
68
+ },
69
+ },
70
+ ],
71
+ };
@@ -39,7 +39,7 @@ export async function validateArtifacts(files, artifactsDir) {
39
39
  }
40
40
  }
41
41
  catch {
42
- // Cannot read readiness file, skip
42
+ warnings.push("Could not read readiness report — NO-GO status unverified");
43
43
  }
44
44
  }
45
45
  return warnings;
@@ -36,7 +36,10 @@ export async function generateSpecsChangelog(oldSpecsDir, newSourceDir) {
36
36
  debug(`Could not read old spec file ${file}: ${formatError(err)}`);
37
37
  return "";
38
38
  });
39
- const newContent = await readFile(join(newSourceDir, file), "utf-8");
39
+ const newContent = await readFile(join(newSourceDir, file), "utf-8").catch((err) => {
40
+ debug(`Could not read new spec file ${file}: ${formatError(err)}`);
41
+ return "";
42
+ });
40
43
  if (oldContent !== newContent) {
41
44
  changes.push({
42
45
  file,
@@ -1,3 +1,4 @@
1
+ import type { PlatformId } from "../platform/types.js";
1
2
  export interface UpstreamVersions {
2
3
  bmadCommit: string;
3
4
  }
@@ -5,6 +6,7 @@ export interface BmalphConfig {
5
6
  name: string;
6
7
  description: string;
7
8
  createdAt: string;
9
+ platform?: PlatformId;
8
10
  upstreamVersions?: UpstreamVersions;
9
11
  }
10
12
  export declare function readConfig(projectDir: string): Promise<BmalphConfig | null>;
@@ -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 = /[<>:"/\\|?*]/;
@@ -50,6 +58,13 @@ export function validateConfig(data) {
50
58
  throw new Error("config.createdAt must be a string");
51
59
  }
52
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
+ }
53
68
  const upstreamVersions = data.upstreamVersions !== undefined
54
69
  ? validateUpstreamVersions(data.upstreamVersions)
55
70
  : undefined;
@@ -57,6 +72,7 @@ export function validateConfig(data) {
57
72
  name: data.name,
58
73
  description,
59
74
  createdAt: data.createdAt,
75
+ ...(platform !== undefined && { platform }),
60
76
  upstreamVersions,
61
77
  };
62
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmalph",
3
- "version": "2.2.1",
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": {
@@ -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
+ }