Skip to content
3 changes: 3 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ ralphy --parallel --sandbox
- Retryable rate-limit or quota errors are detected and deferred for later retry
- Local changes are stashed before the merge phase and restored after
- Agents should not modify PRD files, `.ralphy/progress.txt`, `.ralphy-worktrees`, or `.ralphy-sandboxes`
- Parallel batches can be conflict-aware (file-overlap graph coloring) to reduce merge collisions
- Worktree cleanup is tracked and recovered across crashes; stale worktrees are cleaned on next run
- Merge phase uses a global lock to avoid concurrent merge corruption from multiple ralphy processes

## Options

Expand Down
12 changes: 6 additions & 6 deletions cli/src/cli/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ export async function runLoop(options: RuntimeOptions): Promise<void> {
if (!existsSync(options.prdFile)) {
logError(`${options.prdFile} not found in current directory`);
logInfo(`Create a ${options.prdFile} file with tasks`);
process.exit(1);
throw new Error(`PRD source not found: ${options.prdFile}`);
}
} else if (options.prdSource === "markdown-folder") {
if (!existsSync(options.prdFile)) {
logError(`PRD folder ${options.prdFile} not found`);
logInfo(`Create a ${options.prdFile}/ folder with markdown files containing tasks`);
process.exit(1);
throw new Error(`PRD folder not found: ${options.prdFile}`);
}
}

if (options.prdSource === "github" && !options.githubRepo) {
logError("GitHub repository not specified. Use --github owner/repo");
process.exit(1);
throw new Error("GitHub repository not specified");
}

// Check engine availability
Expand All @@ -61,7 +61,7 @@ export async function runLoop(options: RuntimeOptions): Promise<void> {

if (!available) {
logError(`${engine.name} CLI not found. Make sure '${engine.cliCommand}' is in your PATH.`);
process.exit(1);
throw new Error(`${engine.name} CLI not available`);
}

// Create task source with caching for better performance
Expand Down Expand Up @@ -91,7 +91,7 @@ export async function runLoop(options: RuntimeOptions): Promise<void> {
logError("Cannot run in parallel/branch mode: repository has no commits yet.");
logInfo("Please make an initial commit first:");
logInfo(' git add . && git commit -m "Initial commit"');
process.exit(1);
throw new Error("Repository has no commits yet");
}
}

Expand Down Expand Up @@ -195,6 +195,6 @@ export async function runLoop(options: RuntimeOptions): Promise<void> {
}

if (result.tasksFailed > 0) {
process.exit(1);
throw new Error(`${result.tasksFailed} task(s) failed`);
}
}
6 changes: 3 additions & 3 deletions cli/src/cli/commands/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function runTask(task: string, options: RuntimeOptions): Promise<vo

if (!available) {
logError(`${engine.name} CLI not found. Make sure '${engine.cliCommand}' is in your PATH.`);
process.exit(1);
throw new Error(`${engine.name} CLI not available`);
}

logInfo(`Running task with ${engine.name}...`);
Expand Down Expand Up @@ -129,7 +129,7 @@ export async function runTask(task: string, options: RuntimeOptions): Promise<vo
tasksFailed: 1,
});
notifyTaskFailed(task, result.error || "Unknown error");
process.exit(1);
throw new Error(result.error || "Unknown error");
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
Expand All @@ -140,6 +140,6 @@ export async function runTask(task: string, options: RuntimeOptions): Promise<vo
tasksFailed: 1,
});
notifyTaskFailed(task, errorMsg);
process.exit(1);
throw error instanceof Error ? error : new Error(errorMsg);
}
}
80 changes: 80 additions & 0 deletions cli/src/config/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export const PROGRESS_UPDATE_INTERVAL = 500;
export const HEARTBEAT_INTERVAL = 5000;
// Default retry count used by CLI/runtime options.
export const DEFAULT_MAX_RETRIES = 3;
// Legacy alias retained for older modules.
export const MAX_RETRIES = DEFAULT_MAX_RETRIES;
export const UI_LABELS = {
PLANNING: "[PLANNING]",
EXECUTION: "[EXECUTION]",
DONE: "[DONE]",
FAIL: "[FAIL]",
OK: "[OK]",
};
export const SPINNER_CHARS = ["|", "/", "-", "\\"];
export const MAX_EXECUTION_TIME = 300000; // 5 minutes
export const PROGRESS_POLL_INTERVAL = 2000;
export const WATCHER_DEBOUNCE = 250;
export const PLANNING_COOLDOWN = 2000;

// CLI Defaults
export const DEFAULT_RETRY_DELAY = 5;
export const DEFAULT_MAX_PARALLEL = 3;
export const DEFAULT_MAX_REPLANS = 3;
export const DEFAULT_MAX_ITERATIONS = 0;

// AI Engine Defaults
export const DEFAULT_AI_ENGINE_TIMEOUT_MS = 80 * 60 * 1000; // 80 minutes
export const STREAM_HEARTBEAT_INTERVAL_MS = 30000; // 30 seconds without output = potential hang

// Parallel Execution
export const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
export const INITIAL_POOL_SIZE_MULTIPLIER = 1;
export const MAX_POOL_SIZE_MULTIPLIER = 5;
export const PLANNING_CONCURRENCY = 5;
export const POOL_INCREMENT = 2;

// Progress Monitoring
export const MAX_OPERATIONS_HISTORY = 10;
export const RECENT_ACTIONS_COUNT = 3;
export const FIND_WORKTREE_RETRIES = 20;
export const MAX_DISPLAYED_ACTIONS = 3;

// Sandbox Management
export const DEFAULT_MAX_SANDBOX_AGE_MS = 60 * 60 * 1000; // 1 hour
export const SANDBOX_STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
export const SANDBOX_BACKGROUND_CLEANUP_DELAY_MS = 5 * 60 * 1000; // 5 minutes
export const MS_PER_MINUTE = 60000;
export const CLEANUP_DELAY_MS = 5000;
export const COPY_BACK_CONCURRENCY = 10;
export const SANDBOX_DIR_PREFIX = "agent-";
export const SANDBOX_SUFFIX = "";
export const DEFAULT_IGNORE_PATTERNS = [
".git",
"node_modules",
".ralphy-sandboxes",
".ralphy-worktrees",
".ralphy",
"agent-*",
"sandbox-*",
];

// File Utilities
export const MAX_FILE_SIZE_FOR_HASH = 2 * 1024 * 1024; // 2MB
export const DEFAULT_RECURSION_DEPTH = 5;

// Lock Management
export const LOCK_TIMEOUT_MS = 30000; // 30 seconds
export const LOCK_MAX_LOCKS = 5000; // Maximum number of locks
export const LOCK_DIR = ".ralphy/locks"; // Lock directory
export const LOCK_CLEANUP_INTERVAL_MS = 60000; // 1 minute between cleanups

// Path Constants
export const SANDBOX_DIR = ".ralphy-worktrees";
export const PLANNING_CACHE_FILE = "planning-cache.json";

// Hash Store Constants
export const HASH_STORE_DIR = ".ralphy-hashes";
export const HASH_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
export const HASH_REFERENCE_SUFFIX = ".hash-ref";
export const ENABLE_HASH_STORE = true; // Feature flag
4 changes: 4 additions & 0 deletions cli/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const NotificationsSchema = z.object({
discord_webhook: z.string().default(""),
slack_webhook: z.string().default(""),
custom_webhook: z.string().default(""),
telemetry_webhook: z.string().default(""),
});

/**
Expand Down Expand Up @@ -109,6 +110,8 @@ export interface RuntimeOptions {
browserEnabled: "auto" | "true" | "false";
/** Override default model for the engine */
modelOverride?: string;
/** Enable comprehensive OpenCode debugging */
debugOpenCode?: boolean;
/** Skip automatic branch merging after parallel execution */
skipMerge?: boolean;
/** Use lightweight sandboxes instead of git worktrees for parallel execution */
Expand Down Expand Up @@ -142,4 +145,5 @@ export const DEFAULT_OPTIONS: RuntimeOptions = {
githubLabel: "",
autoCommit: true,
browserEnabled: "auto",
debugOpenCode: false,
};
Loading