ralph-prd 1.0.8 → 1.0.11

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 CHANGED
@@ -94,8 +94,10 @@ Options:
94
94
  --i-did-this Skip Claude self-commit; run separate commit step
95
95
  --send-it Push branch + open PR when all phases complete
96
96
  --wait-for-it Pause before each commit for review
97
- --skip-ship-check Skip the post-commit ship-check step
98
- --skip-on-verify-fail Skip verification and continue instead of hard-stopping when all repair attempts fail
97
+ --skip-ship-check Skip the post-commit ship-check step entirely
98
+ --ship-check-retries=N Retry ship-check up to N times per phase before giving up (default 1)
99
+ --skip-on-ship-check-fail Log and continue when all ship-check retries fail instead of hard-stopping
100
+ --skip-on-verify-fail Skip verification and continue instead of hard-stopping when all repair attempts fail
99
101
  --update-skills Re-fetch skills from tahaJemmali/skills and exit
100
102
  --version, -v Print installed version and exit
101
103
  ```
@@ -147,6 +149,9 @@ writableDirs:
147
149
  flags:
148
150
  maxRepairs: 3
149
151
  sendIt: false
152
+ skipShipCheck: false
153
+ shipCheckRetries: 1
154
+ skipOnShipCheckFail: true
150
155
  skipOnVerifyFail: false
151
156
 
152
157
  hooks:
package/bin/install.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  * from the skills repo via `npx skills add tahaJemmali/skills`.
8
8
  */
9
9
 
10
- import { existsSync, mkdirSync, cpSync, rmSync, readFileSync, writeFileSync } from 'fs';
10
+ import { existsSync, mkdirSync, cpSync, rmSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
11
11
  import { resolve, dirname } from 'path';
12
12
  import { fileURLToPath } from 'url';
13
13
  import { spawnSync } from 'child_process';
@@ -62,6 +62,14 @@ const pkg = JSON.parse(readFileSync(resolve(PKG_ROOT, 'package.json'), 'utf8'));
62
62
  writeFileSync(resolve(ralphDst, '.ralph-version'), pkg.version + '\n', 'utf8');
63
63
  ok(`Installed ralph runner v${pkg.version} -> .claude/ralph/`);
64
64
 
65
+ // Create default ralph.config.yaml if it doesn't exist yet
66
+ const configDst = resolve(ralphDst, 'ralph.config.yaml');
67
+ const configSrc = resolve(ralphDst, 'ralph.config.sample.yaml');
68
+ if (!existsSync(configDst) && existsSync(configSrc)) {
69
+ copyFileSync(configSrc, configDst);
70
+ ok('Created default ralph.config.yaml -> .claude/ralph/ralph.config.yaml');
71
+ }
72
+
65
73
  // Install skills via skills.sh
66
74
  info('Installing skills from tahaJemmali/skills…');
67
75
  const REQUIRED_SKILLS = [
@@ -111,6 +119,7 @@ console.log('');
111
119
  info(`Installed to: ${claudeDir}`);
112
120
  info('');
113
121
  info('Quick start:');
122
+ info(' 0. Configure: edit .claude/ralph/ralph.config.yaml');
114
123
  info(' 1. Write a PRD: claude then /write-a-prd');
115
124
  info(' 2. Create a plan: claude then /prd-to-plan');
116
125
  info(' 3. Execute: node .claude/ralph/ralph-claude.mjs docs/<feature>/plan.md');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-prd",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
4
4
  "description": "AI-powered phased implementation runner for Claude Code — from PRD to shipped code",
5
5
  "bin": {
6
6
  "ralph-prd": "./bin/install.mjs"
@@ -29,8 +29,10 @@ function isGitRepo(dirPath) {
29
29
  * @property {number} maxRepairs - Max repair attempts per phase before hard-stopping (default 3)
30
30
  * @property {number|null} onlyPhase - When set, only this 1-based phase index is run (force re-run)
31
31
  * @property {string} logLevel - "none" | "necessary" | "dump" (default "necessary")
32
- * @property {boolean} skipShipCheck - Skip the post-commit ship-check step for every phase
33
- * @property {boolean} skipOnVerifyFail - Skip verification and continue instead of hard-stopping when all repair attempts fail
32
+ * @property {boolean} skipShipCheck - Skip the post-commit ship-check step for every phase
33
+ * @property {number} shipCheckRetries - Max ship-check attempts per phase before giving up (default 1)
34
+ * @property {boolean} skipOnShipCheckFail - When true, log and continue after all retries fail instead of hard-stopping
35
+ * @property {boolean} skipOnVerifyFail - Skip verification and continue instead of hard-stopping when all repair attempts fail
34
36
  */
35
37
 
36
38
  /**
@@ -61,7 +63,7 @@ function isGitRepo(dirPath) {
61
63
  function parseConfigYaml(content) {
62
64
  const repos = [];
63
65
  const writableDirs = [];
64
- const flags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, logLevel: 'necessary', skipShipCheck: false, skipOnVerifyFail: false };
66
+ const flags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, logLevel: 'necessary', skipShipCheck: false, shipCheckRetries: 1, skipOnShipCheckFail: true, skipOnVerifyFail: false };
65
67
  const hooks = { afterCommit: null };
66
68
  let section = null;
67
69
  let current = null;
@@ -116,9 +118,9 @@ function parseConfigYaml(content) {
116
118
  const [, key, val] = match;
117
119
  if (!(key in flags)) continue;
118
120
  const trimmedVal = val.trim();
119
- if (key === 'maxRepairs') {
121
+ if (key === 'maxRepairs' || key === 'shipCheckRetries') {
120
122
  const n = parseInt(trimmedVal, 10);
121
- if (!isNaN(n) && n > 0) flags.maxRepairs = n;
123
+ if (!isNaN(n) && n > 0) flags[key] = n;
122
124
  } else if (key === 'onlyPhase') {
123
125
  const n = parseInt(trimmedVal, 10);
124
126
  if (!isNaN(n) && n > 0) flags.onlyPhase = n;
@@ -161,7 +163,7 @@ function parseConfigYaml(content) {
161
163
  */
162
164
  export function resolveRepos(runnerDir) {
163
165
  const configPath = join(runnerDir, CONFIG_FILENAME);
164
- const defaultFlags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, skipShipCheck: false, skipOnVerifyFail: false };
166
+ const defaultFlags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, skipShipCheck: false, shipCheckRetries: 1, skipOnShipCheckFail: true, skipOnVerifyFail: false };
165
167
  const defaultHooks = { afterCommit: null };
166
168
 
167
169
  if (!existsSync(configPath)) {
@@ -351,6 +351,7 @@ async function main() {
351
351
  const sendIt = sendItArg || configFlags.sendIt;
352
352
  const waitForIt = waitForItArg || configFlags.waitForIt;
353
353
  const skipShipCheck = skipShipCheckArg || configFlags.skipShipCheck;
354
+ const skipOnShipCheckFail = configFlags.skipOnShipCheckFail;
354
355
  const skipOnVerifyFail = skipOnVerifyFailArg || configFlags.skipOnVerifyFail;
355
356
  const onlyPhase = onlyPhaseArg ?? configFlags.onlyPhase ?? null;
356
357
  const logLevel = logLevelArg ?? configFlags.logLevel ?? 'necessary';
@@ -706,61 +707,87 @@ async function main() {
706
707
  if (skipShipCheck) {
707
708
  console.log(` [${ts()}] ship-check… skipped`);
708
709
  } else {
709
- const repoState = gatherRepoState(repos);
710
- const shipCheckStart = Date.now();
711
- process.stdout.write(` [${ts()}] ship-check… `);
712
- try {
713
- ({ nextTaskNum: taskNum } = await runShipCheck({
714
- phase,
715
- repoState,
716
- logWriter,
717
- phaseNum,
718
- startTaskNum: taskNum,
719
- send,
720
- }));
721
- const dur = ((Date.now() - shipCheckStart) / 1000).toFixed(1);
722
- console.log(`VERDICT: APPROVED (${dur}s)`);
723
-
724
- // Ship-check repair may have modified files — commit any leftovers.
725
- const postShipChanges = await scanChangedRepos(repos);
726
- if (postShipChanges.length > 0) {
727
- process.stdout.write(` [${ts()}] post-ship-check commit… `);
728
- try {
729
- const { nextTaskNum, anyCommitted } = await runCommitStep({
730
- phase,
731
- repos,
732
- safetyHeader,
733
- logWriter,
734
- phaseNum,
735
- taskNum,
736
- send,
737
- });
738
- taskNum = nextTaskNum;
739
- console.log(anyCommitted ? 'ok' : 'skipped (no changes)');
740
- } catch (err) {
710
+ const maxAttempts = configFlags.shipCheckRetries ?? 1;
711
+ let shipCheckPassed = false;
712
+
713
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
714
+ const repoState = gatherRepoState(repos);
715
+ const shipCheckStart = Date.now();
716
+ const attemptSuffix = maxAttempts > 1 ? ` (attempt ${attempt}/${maxAttempts})` : '';
717
+ process.stdout.write(` [${ts()}] ship-check${attemptSuffix}… `);
718
+
719
+ try {
720
+ ({ nextTaskNum: taskNum } = await runShipCheck({
721
+ phase,
722
+ repoState,
723
+ logWriter,
724
+ phaseNum,
725
+ startTaskNum: taskNum,
726
+ send,
727
+ }));
728
+ const dur = ((Date.now() - shipCheckStart) / 1000).toFixed(1);
729
+ console.log(`VERDICT: APPROVED (${dur}s)`);
730
+ shipCheckPassed = true;
731
+
732
+ // Ship-check repair may have modified files — commit any leftovers.
733
+ const postShipChanges = await scanChangedRepos(repos);
734
+ if (postShipChanges.length > 0) {
735
+ process.stdout.write(` [${ts()}] post-ship-check commit… `);
736
+ try {
737
+ const { nextTaskNum, anyCommitted } = await runCommitStep({
738
+ phase,
739
+ repos,
740
+ safetyHeader,
741
+ logWriter,
742
+ phaseNum,
743
+ taskNum,
744
+ send,
745
+ });
746
+ taskNum = nextTaskNum;
747
+ console.log(anyCommitted ? 'ok' : 'skipped (no changes)');
748
+ } catch (err) {
749
+ console.log('failed');
750
+ const msg = err instanceof CommitError
751
+ ? `Phase "${err.phaseName}" post-ship-check commit failed: ${err.message}`
752
+ : `Unexpected error during post-ship-check commit: ${err.message}`;
753
+ console.error(`\n${msg}`);
754
+ console.error(`Logs: ${logsDir}`);
755
+ notify('Ralph — failed', msg);
756
+ process.exit(1);
757
+ }
758
+ }
759
+ break;
760
+ } catch (err) {
761
+ const dur = ((Date.now() - shipCheckStart) / 1000).toFixed(1);
762
+ if (err instanceof ShipCheckError) {
763
+ console.log(`VERDICT: REMARKS (${dur}s)`);
764
+ console.error(`\nShip-check failed for phase "${err.phaseName}":`);
765
+ if (err.findings) console.error(err.findings);
766
+ } else {
741
767
  console.log('failed');
742
- const msg = err instanceof CommitError
743
- ? `Phase "${err.phaseName}" post-ship-check commit failed: ${err.message}`
744
- : `Unexpected error during post-ship-check commit: ${err.message}`;
745
- console.error(`\n${msg}`);
768
+ console.error(`\nUnexpected error during ship-check: ${err.message}`);
746
769
  console.error(`Logs: ${logsDir}`);
747
- notify('Ralph — failed', msg);
770
+ notify('Ralph — failed', `Ship-check failed for "${phase.title}"`);
748
771
  process.exit(1);
749
772
  }
773
+
774
+ if (attempt < maxAttempts) {
775
+ console.error(` Retrying ship-check (${attempt}/${maxAttempts} attempts used)…`);
776
+ }
750
777
  }
751
- } catch (err) {
752
- const dur = ((Date.now() - shipCheckStart) / 1000).toFixed(1);
753
- if (err instanceof ShipCheckError) {
754
- console.log(`VERDICT: REMARKS (${dur}s)`);
755
- console.error(`\nShip-check failed for phase "${err.phaseName}":`);
756
- if (err.findings) console.error(err.findings);
778
+ }
779
+
780
+ if (!shipCheckPassed) {
781
+ const attempts = `${maxAttempts} attempt${maxAttempts === 1 ? '' : 's'}`;
782
+ console.error(`\nShip-check did not pass after ${attempts} for phase "${phase.title}".`);
783
+ console.error(`Logs: ${logsDir}`);
784
+ if (skipOnShipCheckFail) {
785
+ console.error(`Continuing anyway (skipOnShipCheckFail: true).`);
786
+ notify('Ralph — ship-check skipped', `"${phase.title}" ship-check failed after ${attempts} — continuing`);
757
787
  } else {
758
- console.log('failed');
759
- console.error(`\nUnexpected error during ship-check: ${err.message}`);
788
+ notify('Ralph — failed', `Ship-check failed for "${phase.title}" after ${attempts}`);
789
+ process.exit(1);
760
790
  }
761
- console.error(`Logs: ${logsDir}`);
762
- notify('Ralph — failed', `Ship-check failed for "${phase.title}"`);
763
- process.exit(1);
764
791
  }
765
792
  }
766
793
 
@@ -54,6 +54,16 @@ flags:
54
54
  # CLI equivalent: --skip-ship-check
55
55
  skipShipCheck: false
56
56
 
57
+ # Max ship-check attempts per phase before giving up. On final failure,
58
+ # behaviour is controlled by skipOnShipCheckFail below.
59
+ # CLI equivalent: --ship-check-retries=N
60
+ shipCheckRetries: 1
61
+
62
+ # When true, log and continue if ship-check fails after all retries instead
63
+ # of hard-stopping. Defaults to true.
64
+ # CLI equivalent: --skip-on-ship-check-fail
65
+ skipOnShipCheckFail: true
66
+
57
67
  # When all repair attempts are exhausted, skip verification and continue to
58
68
  # the commit step instead of hard-stopping. Useful when the acceptance
59
69
  # criteria require external credentials or manual steps that the agent cannot