@neriros/ralphy 2.20.4 → 2.21.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/README.md +11 -7
- package/dist/cli/index.js +87 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,7 @@ Linear is the source of truth for which issues Ralph has touched. The `linear.in
|
|
|
148
148
|
| `getInProgress` | `{filter: Marker[]}` | Issues to resume after restart |
|
|
149
149
|
| `getConflicted` | `{filter: Marker[]}` | Issues whose PR is conflicted (re-fix run) |
|
|
150
150
|
| `getReview` | `{filter: Marker[]}` | Done issues flagged for review follow-up |
|
|
151
|
+
| `getAutoMerge` | `{filter: Marker[]}` | Issues whose PR should be auto-merged once required checks pass |
|
|
151
152
|
| `setInProgress` | `Marker` or `{apply: Marker[]}` | Applied when a worker spawns (any non-resume mode) |
|
|
152
153
|
| `setDone` | `Marker` or `{apply: Marker[]}` | Applied on clean exit |
|
|
153
154
|
| `setError` | `Marker` or `{apply: Marker[]}` | Applied on non-zero exit (quarantine signal — issue is _not_ auto-resumed) |
|
|
@@ -167,6 +168,7 @@ Example `ralphy.config.json`:
|
|
|
167
168
|
"model": "opus",
|
|
168
169
|
"useWorktree": true,
|
|
169
170
|
"createPrOnSuccess": true,
|
|
171
|
+
"autoMergeStrategy": "squash",
|
|
170
172
|
"fixCiOnFailure": true,
|
|
171
173
|
"linear": {
|
|
172
174
|
"team": "ENG",
|
|
@@ -182,6 +184,7 @@ Example `ralphy.config.json`:
|
|
|
182
184
|
"getInProgress": { "filter": [{ "type": "status", "value": "In Progress" }] },
|
|
183
185
|
"getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflicted" }] },
|
|
184
186
|
"getReview": { "filter": [{ "type": "label", "value": "ralph:review" }] },
|
|
187
|
+
"getAutoMerge": { "filter": [{ "type": "label", "value": "ralph:auto-merge" }] },
|
|
185
188
|
"setInProgress": { "type": "status", "value": "In Progress" },
|
|
186
189
|
"setDone": {
|
|
187
190
|
"apply": [
|
|
@@ -225,13 +228,14 @@ Done issues whose PR `gh pr view --json mergeable` reports as `CONFLICTING` get
|
|
|
225
228
|
|
|
226
229
|
### PR + CI integration
|
|
227
230
|
|
|
228
|
-
| Flag / config | Behavior
|
|
229
|
-
| ------------------------------------- |
|
|
230
|
-
| `createPrOnSuccess` / `--create-pr` | After a clean exit, push the worker's branch and `gh pr create`. Title: `<ID>: <title>`. Idempotent — surfaces the existing URL if the PR is already open. Requires `--worktree` and `gh` authenticated. `prBaseBranch` defaults to `main
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
231
|
+
| Flag / config | Behavior |
|
|
232
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
233
|
+
| `createPrOnSuccess` / `--create-pr` | After a clean exit, push the worker's branch and `gh pr create`. Title: `<ID>: <title>`. Idempotent — surfaces the existing URL if the PR is already open. Requires `--worktree` and `gh` authenticated. `prBaseBranch` defaults to `main`; override per-issue by labelling the Linear issue with `ralph:branch:<branch-name>`. |
|
|
234
|
+
| `getAutoMerge` indicator | Opt an issue in for GitHub auto-merge (any-of label/status filter, same shape as `getReview`). When matched, Ralph runs `gh pr merge <url> --auto --<strategy>` right after opening the PR so GitHub merges as soon as required checks pass. Strategy comes from `autoMergeStrategy` (`squash` \| `merge` \| `rebase`, default `squash`). Failures are logged but non-fatal — the CI/conflict watch loop continues. |
|
|
235
|
+
| `fixCiOnFailure` / `--fix-ci` | After the PR opens, poll `gh pr checks`. On failure, pull failed logs via `gh run view --log-failed`, append them to `## Steering`, re-spawn the worker, and push the new commits — repeat until green or `maxCiFixAttempts` (default `5`) is hit. While this loop runs, `setDone` is **not** applied; if CI is never green the worker is treated as failed. |
|
|
236
|
+
| `ciPollIntervalSeconds` | Seconds between CI status polls (default `30`). |
|
|
237
|
+
| `ignoreCiChecks` | Array of check names to ignore when computing pass/fail. |
|
|
238
|
+
| `codeReviewTrigger` / `--code-review` | See [Code-review iteration](#code-review-iteration). |
|
|
235
239
|
|
|
236
240
|
### Worktrees, setup, teardown
|
|
237
241
|
|
package/dist/cli/index.js
CHANGED
|
@@ -35029,8 +35029,8 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
35029
35029
|
import { resolve } from "path";
|
|
35030
35030
|
function getVersion() {
|
|
35031
35031
|
try {
|
|
35032
|
-
if ("2.
|
|
35033
|
-
return "2.
|
|
35032
|
+
if ("2.21.1")
|
|
35033
|
+
return "2.21.1";
|
|
35034
35034
|
} catch {}
|
|
35035
35035
|
const dirsToTry = [];
|
|
35036
35036
|
try {
|
|
@@ -35361,6 +35361,7 @@ var init_cli = __esm(() => {
|
|
|
35361
35361
|
"getInProgress",
|
|
35362
35362
|
"getConflicted",
|
|
35363
35363
|
"getReview",
|
|
35364
|
+
"getAutoMerge",
|
|
35364
35365
|
"setInProgress",
|
|
35365
35366
|
"setDone",
|
|
35366
35367
|
"setError",
|
|
@@ -35372,7 +35373,8 @@ var init_cli = __esm(() => {
|
|
|
35372
35373
|
"getTodo",
|
|
35373
35374
|
"getInProgress",
|
|
35374
35375
|
"getConflicted",
|
|
35375
|
-
"getReview"
|
|
35376
|
+
"getReview",
|
|
35377
|
+
"getAutoMerge"
|
|
35376
35378
|
]);
|
|
35377
35379
|
HELP_TEXT = [
|
|
35378
35380
|
`ralph v${VERSION}`,
|
|
@@ -35415,7 +35417,7 @@ var init_cli = __esm(() => {
|
|
|
35415
35417
|
" --indicator getTodo:status:Todo",
|
|
35416
35418
|
" --indicator setDone:label:shipped",
|
|
35417
35419
|
" --indicator setDone:status:Done (combined with above \u2192 multi-marker)",
|
|
35418
|
-
" Keys: getTodo, getInProgress, getConflicted, getReview,",
|
|
35420
|
+
" Keys: getTodo, getInProgress, getConflicted, getReview, getAutoMerge,",
|
|
35419
35421
|
" setInProgress, setDone, setError, setConflicted,",
|
|
35420
35422
|
" clearConflicted, clearReview",
|
|
35421
35423
|
" Types: label, status",
|
|
@@ -59591,9 +59593,14 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Ralp
|
|
|
59591
59593
|
// Open a pull request after a task succeeds.
|
|
59592
59594
|
"createPrOnSuccess": false,
|
|
59593
59595
|
|
|
59594
|
-
// Base branch for pull requests.
|
|
59596
|
+
// Base branch for pull requests. Override per-issue by labelling the
|
|
59597
|
+
// Linear issue with "ralph:branch:<branch-name>".
|
|
59595
59598
|
"prBaseBranch": "main",
|
|
59596
59599
|
|
|
59600
|
+
// Merge strategy used when GitHub auto-merge is enabled (see getAutoMerge
|
|
59601
|
+
// indicator below). One of "squash", "merge", "rebase".
|
|
59602
|
+
"autoMergeStrategy": "squash",
|
|
59603
|
+
|
|
59597
59604
|
// Let the agent attempt to fix CI failures after a PR is created.
|
|
59598
59605
|
"fixCiOnFailure": false,
|
|
59599
59606
|
|
|
@@ -59655,6 +59662,11 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Ralp
|
|
|
59655
59662
|
// and prepend a task that ingests the non-Ralph comments).
|
|
59656
59663
|
// "getReview": { "filter": [{ "type": "label", "value": "ralph:review" }] },
|
|
59657
59664
|
|
|
59665
|
+
// Issues opted in for auto-merge: when an issue matches, Ralph enables
|
|
59666
|
+
// GitHub auto-merge ("gh pr merge --auto --<autoMergeStrategy>") right
|
|
59667
|
+
// after opening the PR so the PR merges as soon as required checks pass.
|
|
59668
|
+
// "getAutoMerge": { "filter": [{ "type": "label", "value": "ralph:auto-merge" }] },
|
|
59669
|
+
|
|
59658
59670
|
// Applied when Ralph picks up an issue.
|
|
59659
59671
|
// "setInProgress": { "type": "label", "value": "ralph:in-progress" },
|
|
59660
59672
|
|
|
@@ -59694,6 +59706,7 @@ var init_config = __esm(() => {
|
|
|
59694
59706
|
getInProgress: GetIndicatorSchema.optional(),
|
|
59695
59707
|
getConflicted: GetIndicatorSchema.optional(),
|
|
59696
59708
|
getReview: GetIndicatorSchema.optional(),
|
|
59709
|
+
getAutoMerge: GetIndicatorSchema.optional(),
|
|
59697
59710
|
setInProgress: SetIndicatorSchema.optional(),
|
|
59698
59711
|
setDone: SetIndicatorSchema.optional(),
|
|
59699
59712
|
setError: SetIndicatorSchema.optional(),
|
|
@@ -59736,6 +59749,7 @@ var init_config = __esm(() => {
|
|
|
59736
59749
|
appendPrompt: exports_external.string().optional(),
|
|
59737
59750
|
createPrOnSuccess: exports_external.boolean().default(false),
|
|
59738
59751
|
prBaseBranch: exports_external.string().default("main"),
|
|
59752
|
+
autoMergeStrategy: exports_external.enum(["squash", "merge", "rebase"]).default("squash"),
|
|
59739
59753
|
fixCiOnFailure: exports_external.boolean().default(false),
|
|
59740
59754
|
maxCiFixAttempts: exports_external.number().int().positive().default(5),
|
|
59741
59755
|
ciPollIntervalSeconds: exports_external.number().int().positive().default(30),
|
|
@@ -60087,6 +60101,23 @@ async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
|
60087
60101
|
labelId
|
|
60088
60102
|
});
|
|
60089
60103
|
}
|
|
60104
|
+
function baseBranchFromLabels(labels) {
|
|
60105
|
+
for (const label of labels) {
|
|
60106
|
+
if (label.toLowerCase().startsWith(BRANCH_LABEL_PREFIX)) {
|
|
60107
|
+
const value = label.slice(BRANCH_LABEL_PREFIX.length).trim();
|
|
60108
|
+
if (value)
|
|
60109
|
+
return value;
|
|
60110
|
+
}
|
|
60111
|
+
}
|
|
60112
|
+
return;
|
|
60113
|
+
}
|
|
60114
|
+
function issueMatchesGetIndicator(issue, indicator) {
|
|
60115
|
+
if (!indicator || indicator.filter.length === 0)
|
|
60116
|
+
return false;
|
|
60117
|
+
const labels = new Set(issue.labels.map((l) => l.toLowerCase()));
|
|
60118
|
+
const stateName = issue.state.name.toLowerCase();
|
|
60119
|
+
return indicator.filter.some((m) => m.type === "label" ? labels.has(m.value.toLowerCase()) : stateName === m.value.toLowerCase());
|
|
60120
|
+
}
|
|
60090
60121
|
async function removeLabelFromIssue(apiKey, issueId, labelId) {
|
|
60091
60122
|
const mutation = `mutation RemoveLabel($id: String!, $labelId: String!) {
|
|
60092
60123
|
issueRemoveLabel(id: $id, labelId: $labelId) { success }
|
|
@@ -60096,7 +60127,7 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
|
|
|
60096
60127
|
labelId
|
|
60097
60128
|
});
|
|
60098
60129
|
}
|
|
60099
|
-
var LINEAR_API = "https://api.linear.app/graphql";
|
|
60130
|
+
var LINEAR_API = "https://api.linear.app/graphql", BRANCH_LABEL_PREFIX = "ralph:branch:";
|
|
60100
60131
|
|
|
60101
60132
|
// apps/cli/src/agent/coordinator.ts
|
|
60102
60133
|
class AgentCoordinator {
|
|
@@ -60842,6 +60873,7 @@ ${pe.stderr ?? ""}`;
|
|
|
60842
60873
|
}
|
|
60843
60874
|
}
|
|
60844
60875
|
async function createPrWithRetry(ctx, issue) {
|
|
60876
|
+
const base2 = ctx.base;
|
|
60845
60877
|
const maxAttempts = ctx.cfg.maxCiFixAttempts;
|
|
60846
60878
|
let hookFixAttempt = 0;
|
|
60847
60879
|
let nonFfRebaseAttempted = false;
|
|
@@ -60849,7 +60881,7 @@ async function createPrWithRetry(ctx, issue) {
|
|
|
60849
60881
|
while (true) {
|
|
60850
60882
|
try {
|
|
60851
60883
|
ctx.emit("pr-create", "git push + gh pr create");
|
|
60852
|
-
pr = await createPullRequest({ cwd: ctx.cwd, branch: ctx.branch, issue, base:
|
|
60884
|
+
pr = await createPullRequest({ cwd: ctx.cwd, branch: ctx.branch, issue, base: base2 }, ctx.cmd);
|
|
60853
60885
|
return { pr, gaveUp: false };
|
|
60854
60886
|
} catch (err) {
|
|
60855
60887
|
const e = err;
|
|
@@ -60961,10 +60993,10 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
|
60961
60993
|
ctx.emit("conflict-fix-inner", `attempt ${outerAttempt}/${maxOuterAttempts}`);
|
|
60962
60994
|
ctx.log(` merge conflicts on PR (attempt ${outerAttempt}/${maxOuterAttempts}) \u2014 spawning resolution task`, "yellow");
|
|
60963
60995
|
const conflictCode = await runWorkerWithFixTask(ctx, "Resolve PR merge conflicts", [
|
|
60964
|
-
`The PR ${prUrl} has merge conflicts with \`${ctx.
|
|
60996
|
+
`The PR ${prUrl} has merge conflicts with \`${ctx.base}\`.`,
|
|
60965
60997
|
"",
|
|
60966
60998
|
"Steps:",
|
|
60967
|
-
`1. \`git fetch origin ${ctx.
|
|
60999
|
+
`1. \`git fetch origin ${ctx.base}\` then rebase or merge \`${ctx.base}\` into the current branch.`,
|
|
60968
61000
|
"2. Resolve conflicts in the files git lists.",
|
|
60969
61001
|
"3. Stage and commit the resolution."
|
|
60970
61002
|
].join(`
|
|
@@ -61016,16 +61048,32 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
|
61016
61048
|
return 0;
|
|
61017
61049
|
}
|
|
61018
61050
|
async function runPrPhase(input, deps) {
|
|
61019
|
-
const {
|
|
61051
|
+
const {
|
|
61052
|
+
changeName,
|
|
61053
|
+
cwd: cwd2,
|
|
61054
|
+
branch,
|
|
61055
|
+
changeDir,
|
|
61056
|
+
stateFilePath,
|
|
61057
|
+
issue,
|
|
61058
|
+
wantFixCi,
|
|
61059
|
+
wantAutoMerge,
|
|
61060
|
+
cfg
|
|
61061
|
+
} = input;
|
|
61020
61062
|
const { cmd, log: log2, emit, respawnWorker, registerPr, checkPrConflict } = deps;
|
|
61021
61063
|
if (!branch || !issue) {
|
|
61022
61064
|
log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
61023
61065
|
return PR_FAILED_EXIT;
|
|
61024
61066
|
}
|
|
61067
|
+
const labelBase = baseBranchFromLabels(issue.labels);
|
|
61068
|
+
const base2 = labelBase ?? cfg.prBaseBranch;
|
|
61069
|
+
if (labelBase && labelBase !== cfg.prBaseBranch) {
|
|
61070
|
+
log2(` base branch override from label: ${labelBase}`, "gray");
|
|
61071
|
+
}
|
|
61025
61072
|
const ctx = {
|
|
61026
61073
|
changeName,
|
|
61027
61074
|
cwd: cwd2,
|
|
61028
61075
|
branch,
|
|
61076
|
+
base: base2,
|
|
61029
61077
|
changeDir,
|
|
61030
61078
|
stateFilePath,
|
|
61031
61079
|
cfg,
|
|
@@ -61046,11 +61094,21 @@ async function runPrPhase(input, deps) {
|
|
|
61046
61094
|
if (prGaveUp)
|
|
61047
61095
|
return PR_FAILED_EXIT;
|
|
61048
61096
|
if (!pr) {
|
|
61049
|
-
log2(` no commits ahead of ${
|
|
61097
|
+
log2(` no commits ahead of ${base2} \u2014 skipping PR`, "gray");
|
|
61050
61098
|
return 0;
|
|
61051
61099
|
}
|
|
61052
61100
|
log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
|
|
61053
61101
|
registerPr?.(changeName, pr.url);
|
|
61102
|
+
if (wantAutoMerge) {
|
|
61103
|
+
try {
|
|
61104
|
+
await cmd.run(["gh", "pr", "merge", pr.url, "--auto", `--${cfg.autoMergeStrategy}`], cwd2);
|
|
61105
|
+
log2(` enabled auto-merge (${cfg.autoMergeStrategy}) on ${pr.url}`, "green");
|
|
61106
|
+
emit("auto-merge-enabled", cfg.autoMergeStrategy);
|
|
61107
|
+
} catch (err) {
|
|
61108
|
+
const e = err;
|
|
61109
|
+
log2(`! failed to enable auto-merge on ${pr.url}: ${e.stderr?.trim() || e.message}`, "yellow");
|
|
61110
|
+
}
|
|
61111
|
+
}
|
|
61054
61112
|
return fixConflictsAndCiLoop(ctx, pr.url, wantFixCi, checkPrConflict);
|
|
61055
61113
|
}
|
|
61056
61114
|
async function runWorktreeCleanupPhase(input, deps) {
|
|
@@ -61112,6 +61170,7 @@ async function runPostTask(input, deps) {
|
|
|
61112
61170
|
useWorktree,
|
|
61113
61171
|
wantPr,
|
|
61114
61172
|
wantFixCi,
|
|
61173
|
+
wantAutoMerge,
|
|
61115
61174
|
cfg,
|
|
61116
61175
|
respawnWorker
|
|
61117
61176
|
} = input;
|
|
@@ -61120,7 +61179,17 @@ async function runPostTask(input, deps) {
|
|
|
61120
61179
|
log2(` skipping PR phase for ${changeName} (worker exited with code ${effectiveCode})`, "gray");
|
|
61121
61180
|
}
|
|
61122
61181
|
if (effectiveCode === 0 && wantPr) {
|
|
61123
|
-
effectiveCode = await runPrPhase({
|
|
61182
|
+
effectiveCode = await runPrPhase({
|
|
61183
|
+
changeName,
|
|
61184
|
+
cwd: cwd2,
|
|
61185
|
+
branch,
|
|
61186
|
+
changeDir,
|
|
61187
|
+
stateFilePath,
|
|
61188
|
+
issue,
|
|
61189
|
+
wantFixCi,
|
|
61190
|
+
wantAutoMerge,
|
|
61191
|
+
cfg
|
|
61192
|
+
}, {
|
|
61124
61193
|
cmd,
|
|
61125
61194
|
log: log2,
|
|
61126
61195
|
emit,
|
|
@@ -61646,6 +61715,8 @@ PR: ${prUrl}` : ""
|
|
|
61646
61715
|
const tracedCmd = onWorkerCmd ? traceCmdRunner(cmdRunner, (cmd) => onWorkerCmd(changeName, cmd, "start"), (cmd, ms, ok) => onWorkerCmd(changeName, cmd, "end", ms, ok)) : cmdRunner;
|
|
61647
61716
|
const wantPr = args.createPr || cfg.createPrOnSuccess;
|
|
61648
61717
|
const wantFixCi = args.fixCi || cfg.fixCiOnFailure;
|
|
61718
|
+
const issueForChange = issueByChange.get(changeName);
|
|
61719
|
+
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
61649
61720
|
const wrapped = handle.exited.then(async (code) => {
|
|
61650
61721
|
const workerLayout = projectLayout(cwd2);
|
|
61651
61722
|
const effectiveCode = await runPostTask({
|
|
@@ -61660,9 +61731,11 @@ PR: ${prUrl}` : ""
|
|
|
61660
61731
|
useWorktree,
|
|
61661
61732
|
wantPr,
|
|
61662
61733
|
wantFixCi,
|
|
61734
|
+
wantAutoMerge,
|
|
61663
61735
|
cfg: {
|
|
61664
61736
|
teardownScript: cfg.teardownScript ?? null,
|
|
61665
61737
|
prBaseBranch: cfg.prBaseBranch,
|
|
61738
|
+
autoMergeStrategy: cfg.autoMergeStrategy,
|
|
61666
61739
|
maxCiFixAttempts: cfg.maxCiFixAttempts,
|
|
61667
61740
|
ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
|
|
61668
61741
|
cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
|
|
@@ -73223,6 +73296,8 @@ function phaseColor(phase) {
|
|
|
73223
73296
|
case "ci-poll":
|
|
73224
73297
|
case "ci-fix":
|
|
73225
73298
|
return "blue";
|
|
73299
|
+
case "auto-merge-enabled":
|
|
73300
|
+
return "green";
|
|
73226
73301
|
case "teardown":
|
|
73227
73302
|
case "cleanup":
|
|
73228
73303
|
return "gray";
|