ralph-prd 1.0.6 → 1.0.9

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
@@ -93,8 +93,10 @@ Options:
93
93
  --only-phase N Force re-run phase N (1-based)
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
- --wait-for-it Pause before each commit for review
97
- --update-skills Re-fetch skills from tahaJemmali/skills and exit
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
99
+ --update-skills Re-fetch skills from tahaJemmali/skills and exit
98
100
  --version, -v Print installed version and exit
99
101
  ```
100
102
 
@@ -145,6 +147,7 @@ writableDirs:
145
147
  flags:
146
148
  maxRepairs: 3
147
149
  sendIt: false
150
+ skipOnVerifyFail: false
148
151
 
149
152
  hooks:
150
153
  afterCommit: npm test
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-prd",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
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,7 +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
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
33
36
  */
34
37
 
35
38
  /**
@@ -60,7 +63,7 @@ function isGitRepo(dirPath) {
60
63
  function parseConfigYaml(content) {
61
64
  const repos = [];
62
65
  const writableDirs = [];
63
- const flags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, logLevel: 'necessary', skipShipCheck: false };
66
+ const flags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, logLevel: 'necessary', skipShipCheck: false, shipCheckRetries: 1, skipOnShipCheckFail: true, skipOnVerifyFail: false };
64
67
  const hooks = { afterCommit: null };
65
68
  let section = null;
66
69
  let current = null;
@@ -115,9 +118,9 @@ function parseConfigYaml(content) {
115
118
  const [, key, val] = match;
116
119
  if (!(key in flags)) continue;
117
120
  const trimmedVal = val.trim();
118
- if (key === 'maxRepairs') {
121
+ if (key === 'maxRepairs' || key === 'shipCheckRetries') {
119
122
  const n = parseInt(trimmedVal, 10);
120
- if (!isNaN(n) && n > 0) flags.maxRepairs = n;
123
+ if (!isNaN(n) && n > 0) flags[key] = n;
121
124
  } else if (key === 'onlyPhase') {
122
125
  const n = parseInt(trimmedVal, 10);
123
126
  if (!isNaN(n) && n > 0) flags.onlyPhase = n;
@@ -160,7 +163,7 @@ function parseConfigYaml(content) {
160
163
  */
161
164
  export function resolveRepos(runnerDir) {
162
165
  const configPath = join(runnerDir, CONFIG_FILENAME);
163
- const defaultFlags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, skipShipCheck: false };
166
+ const defaultFlags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, skipShipCheck: false, shipCheckRetries: 1, skipOnShipCheckFail: true, skipOnVerifyFail: false };
164
167
  const defaultHooks = { afterCommit: null };
165
168
 
166
169
  if (!existsSync(configPath)) {
@@ -134,6 +134,8 @@ const sendItArg = args.includes('--send-it');
134
134
  const waitForItArg = args.includes('--wait-for-it');
135
135
  // Skip the post-commit ship-check step. Use when you don't have a ship-check skill.
136
136
  const skipShipCheckArg = args.includes('--skip-ship-check');
137
+ // Skip verification and continue (rather than hard-stop) when all repair attempts fail.
138
+ const skipOnVerifyFailArg = args.includes('--skip-on-verify-fail');
137
139
  // Run only one specific phase (1-based), force re-run even if already complete.
138
140
  const onlyPhaseArg = (() => {
139
141
  const idx = args.indexOf('--only-phase');
@@ -154,7 +156,7 @@ const logLevelArg = (() => {
154
156
  if (!planArg) {
155
157
  console.error(
156
158
  'Usage: node ralph-claude.mjs <plan-file.md> ' +
157
- '[--reset|--dry-run|--i-did-this|--send-it|--wait-for-it|--skip-ship-check|--only-phase N|--log-level none|necessary|dump|--update-skills|--version]'
159
+ '[--reset|--dry-run|--i-did-this|--send-it|--wait-for-it|--skip-ship-check|--skip-on-verify-fail|--only-phase N|--log-level none|necessary|dump|--update-skills|--version]'
158
160
  );
159
161
  process.exit(1);
160
162
  }
@@ -349,6 +351,8 @@ async function main() {
349
351
  const sendIt = sendItArg || configFlags.sendIt;
350
352
  const waitForIt = waitForItArg || configFlags.waitForIt;
351
353
  const skipShipCheck = skipShipCheckArg || configFlags.skipShipCheck;
354
+ const skipOnShipCheckFail = configFlags.skipOnShipCheckFail;
355
+ const skipOnVerifyFail = skipOnVerifyFailArg || configFlags.skipOnVerifyFail;
352
356
  const onlyPhase = onlyPhaseArg ?? configFlags.onlyPhase ?? null;
353
357
  const logLevel = logLevelArg ?? configFlags.logLevel ?? 'necessary';
354
358
 
@@ -574,19 +578,26 @@ async function main() {
574
578
  }));
575
579
  console.log('ok');
576
580
  } catch (err) {
577
- console.log('failed');
578
- let errMsg;
579
- if (err instanceof VerificationError) {
580
- errMsg = `Verification failed for phase "${err.phaseName}"`;
581
- console.error(`\n${errMsg}:`);
581
+ if (err instanceof VerificationError && skipOnVerifyFail) {
582
+ console.log(`skipped (failed ${configFlags.maxRepairs} time${configFlags.maxRepairs === 1 ? '' : 's'})`);
583
+ console.error(`\nVerification for "${err.phaseName}" failed ${configFlags.maxRepairs} time${configFlags.maxRepairs === 1 ? '' : 's'} — skipping to avoid token waste.`);
582
584
  if (err.failureNotes) console.error(err.failureNotes);
585
+ console.error(`Logs: ${logsDir}`);
583
586
  } else {
584
- errMsg = `Unexpected error during verification: ${err.message}`;
585
- console.error(`\n${errMsg}`);
587
+ console.log('failed');
588
+ let errMsg;
589
+ if (err instanceof VerificationError) {
590
+ errMsg = `Verification failed for phase "${err.phaseName}"`;
591
+ console.error(`\n${errMsg}:`);
592
+ if (err.failureNotes) console.error(err.failureNotes);
593
+ } else {
594
+ errMsg = `Unexpected error during verification: ${err.message}`;
595
+ console.error(`\n${errMsg}`);
596
+ }
597
+ console.error(`Logs: ${logsDir}`);
598
+ notify('Ralph — failed', errMsg);
599
+ process.exit(1);
586
600
  }
587
- console.error(`Logs: ${logsDir}`);
588
- notify('Ralph — failed', errMsg);
589
- process.exit(1);
590
601
  }
591
602
 
592
603
  // Checkpoint: verification done
@@ -696,33 +707,87 @@ async function main() {
696
707
  if (skipShipCheck) {
697
708
  console.log(` [${ts()}] ship-check… skipped`);
698
709
  } else {
699
- const repoState = gatherRepoState(repos);
700
- const shipCheckStart = Date.now();
701
- process.stdout.write(` [${ts()}] ship-check… `);
702
- try {
703
- ({ nextTaskNum: taskNum } = await runShipCheck({
704
- phase,
705
- repoState,
706
- logWriter,
707
- phaseNum,
708
- startTaskNum: taskNum,
709
- send,
710
- }));
711
- const dur = ((Date.now() - shipCheckStart) / 1000).toFixed(1);
712
- console.log(`VERDICT: APPROVED (${dur}s)`);
713
- } catch (err) {
714
- const dur = ((Date.now() - shipCheckStart) / 1000).toFixed(1);
715
- if (err instanceof ShipCheckError) {
716
- console.log(`VERDICT: REMARKS (${dur}s)`);
717
- console.error(`\nShip-check failed for phase "${err.phaseName}":`);
718
- if (err.findings) console.error(err.findings);
719
- } else {
720
- console.log('failed');
721
- console.error(`\nUnexpected error during ship-check: ${err.message}`);
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 {
767
+ console.log('failed');
768
+ console.error(`\nUnexpected error during ship-check: ${err.message}`);
769
+ console.error(`Logs: ${logsDir}`);
770
+ notify('Ralph — failed', `Ship-check failed for "${phase.title}"`);
771
+ process.exit(1);
772
+ }
773
+
774
+ if (attempt < maxAttempts) {
775
+ console.error(` Retrying ship-check (${attempt}/${maxAttempts} attempts used)…`);
776
+ }
722
777
  }
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}".`);
723
783
  console.error(`Logs: ${logsDir}`);
724
- notify('Ralph — failed', `Ship-check failed for "${phase.title}"`);
725
- process.exit(1);
784
+ if (skipOnShipCheckFail) {
785
+ console.error(`Continuing anyway (skipOnShipCheckFail: true).`);
786
+ notify('Ralph — ship-check skipped', `"${phase.title}" ship-check failed after ${attempts} — continuing`);
787
+ } else {
788
+ notify('Ralph — failed', `Ship-check failed for "${phase.title}" after ${attempts}`);
789
+ process.exit(1);
790
+ }
726
791
  }
727
792
  }
728
793
 
@@ -53,3 +53,10 @@ flags:
53
53
  # have not installed a ship-check skill or want to disable the review gate.
54
54
  # CLI equivalent: --skip-ship-check
55
55
  skipShipCheck: false
56
+
57
+ # When all repair attempts are exhausted, skip verification and continue to
58
+ # the commit step instead of hard-stopping. Useful when the acceptance
59
+ # criteria require external credentials or manual steps that the agent cannot
60
+ # perform. Defaults to false (hard stop).
61
+ # CLI equivalent: --skip-on-verify-fail
62
+ skipOnVerifyFail: false