ralph-prd 3.0.4 → 3.1.1

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/bin/cli.mjs CHANGED
@@ -17,10 +17,12 @@
17
17
 
18
18
  import { existsSync, readFileSync } from 'fs';
19
19
  import { resolve, dirname } from 'path';
20
- import { fileURLToPath } from 'url';
20
+ import { fileURLToPath, pathToFileURL } from 'url';
21
21
 
22
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
23
23
  const PKG_ROOT = resolve(__dirname, '..');
24
+ const RUNNER_PATH = resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs');
25
+ const RUNNER_URL = pathToFileURL(RUNNER_PATH).href;
24
26
 
25
27
  // ─── Version ─────────────────────────────────────────────────────────────────
26
28
 
@@ -50,9 +52,9 @@ if (subcommand === 'run') {
50
52
 
51
53
  // Import and run ralph-claude.mjs directly from the package
52
54
  // Override process.argv so ralph-claude.mjs sees the correct args
53
- process.argv = [process.argv[0], resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'), ...runArgs];
55
+ process.argv = [process.argv[0], RUNNER_PATH, ...runArgs];
54
56
 
55
- await import(resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'));
57
+ await import(RUNNER_URL);
56
58
 
57
59
  } else if (subcommand === 'init' || !subcommand) {
58
60
  // npx ralph-prd init OR npx ralph-prd (legacy: bare invocation = init)
@@ -61,12 +63,12 @@ if (subcommand === 'run') {
61
63
 
62
64
  } else if (subcommand === '--update-skills') {
63
65
  // npx ralph-prd --update-skills
64
- process.argv = [process.argv[0], 'ralph-claude.mjs', '--update-skills'];
65
- await import(resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'));
66
+ process.argv = [process.argv[0], RUNNER_PATH, '--update-skills'];
67
+ await import(RUNNER_URL);
66
68
 
67
69
  } else {
68
70
  // Assume it's a plan file path (legacy: npx ralph-prd docs/feature/plan.md)
69
71
  // Treat bare arguments as plan files for backward compat
70
- process.argv = [process.argv[0], resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'), ...args];
71
- await import(resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'));
72
+ process.argv = [process.argv[0], RUNNER_PATH, ...args];
73
+ await import(RUNNER_URL);
72
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-prd",
3
- "version": "3.0.4",
3
+ "version": "3.1.1",
4
4
  "type": "module",
5
5
  "description": "AI-powered phased implementation runner for Claude Code — from PRD to shipped code",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "exports": {
10
10
  ".": "./ralph/index.mjs",
11
- "./transport": "./ralph/lib/transport.mjs"
11
+ "./transport": "./ralph/lib/transport.mjs",
12
+ "./plan-validator": "./ralph/lib/plan-validator.mjs"
12
13
  },
13
14
  "keywords": [
14
15
  "claude",
package/ralph/index.mjs CHANGED
@@ -8,3 +8,4 @@
8
8
 
9
9
  export { send, preflight, getCumulativeCost, TransportError } from './lib/transport.mjs';
10
10
  export { resolveRepos } from './lib/config.mjs';
11
+ export { validatePlan, PlanValidationError } from './lib/plan-validator.mjs';
@@ -26,7 +26,7 @@ import { relative } from 'path';
26
26
  /** Timeout for the preflight check only — should always be fast. */
27
27
  const PREFLIGHT_TIMEOUT_MS = 30_000;
28
28
 
29
- /** Default timeout for send() — 20 minutes per CLI session. */
29
+ /** Default timeout for send() — 20 minutes of inactivity per CLI session. */
30
30
  const SEND_TIMEOUT_MS = 20 * 60 * 1000;
31
31
 
32
32
  const CLI_FLAGS = [
@@ -371,16 +371,24 @@ export async function send(prompt, { onChunk, signal, timeoutMs } = {}) {
371
371
  const child = spawn(cliBin, CLI_FLAGS, { stdio: ['pipe', 'pipe', 'pipe'] });
372
372
 
373
373
  // ── Timeout: kills the CLI if no output arrives within the timeout window ──
374
- const timer = setTimeout(() => {
374
+ // This is a no-activity timeout, not a hard cap — it resets on every stdout
375
+ // chunk so long-running tasks that keep producing output are never killed.
376
+ let timer = setTimeout(onTimeout, timeout);
377
+ function onTimeout() {
375
378
  child.kill();
376
379
  done(reject, new TransportError(
377
- `\`claude\` CLI timed out after ${(timeout / 1000).toFixed(0)}s with no response. ` +
380
+ `\`claude\` CLI timed out after ${(timeout / 1000).toFixed(0)}s with no output. ` +
378
381
  'The session may have hung or lost connectivity.',
379
382
  'timeout'
380
383
  ));
381
- }, timeout);
384
+ }
385
+ function resetTimer() {
386
+ clearTimeout(timer);
387
+ timer = setTimeout(onTimeout, timeout);
388
+ }
382
389
 
383
390
  child.stdout.on('data', (chunk) => {
391
+ resetTimer();
384
392
  lineBuffer += chunk.toString();
385
393
  const lines = lineBuffer.split('\n');
386
394
  lineBuffer = lines.pop() ?? '';