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 +5 -2
- package/package.json +1 -1
- package/ralph/lib/config.mjs +8 -5
- package/ralph/ralph-claude.mjs +101 -36
- package/ralph/ralph.config.sample.yaml +7 -0
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
|
|
97
|
-
--
|
|
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
package/ralph/lib/config.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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)) {
|
package/ralph/ralph-claude.mjs
CHANGED
|
@@ -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
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
725
|
-
|
|
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
|