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 +7 -2
- package/bin/install.mjs +10 -1
- package/package.json +1 -1
- package/ralph/lib/config.mjs +8 -6
- package/ralph/ralph-claude.mjs +75 -48
- package/ralph/ralph.config.sample.yaml +10 -0
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
|
|
98
|
-
--
|
|
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
package/ralph/lib/config.mjs
CHANGED
|
@@ -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
|
|
33
|
-
* @property {
|
|
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
|
|
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)) {
|
package/ralph/ralph-claude.mjs
CHANGED
|
@@ -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
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
759
|
-
|
|
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
|