@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 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
- | `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. |
232
- | `ciPollIntervalSeconds` | Seconds between CI status polls (default `30`). |
233
- | `ignoreCiChecks` | Array of check names to ignore when computing pass/fail. |
234
- | `codeReviewTrigger` / `--code-review` | See [Code-review iteration](#code-review-iteration). |
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.20.4")
35033
- return "2.20.4";
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: ctx.cfg.prBaseBranch }, ctx.cmd);
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.cfg.prBaseBranch}\`.`,
60996
+ `The PR ${prUrl} has merge conflicts with \`${ctx.base}\`.`,
60965
60997
  "",
60966
60998
  "Steps:",
60967
- `1. \`git fetch origin ${ctx.cfg.prBaseBranch}\` then rebase or merge \`${ctx.cfg.prBaseBranch}\` into the current branch.`,
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 { changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue, wantFixCi, cfg } = input;
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 ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
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({ changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue, wantFixCi, cfg }, {
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.20.4",
3
+ "version": "2.21.1",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",