@neriros/ralphy 2.20.3 → 2.21.0

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
@@ -245,7 +245,7 @@ Both scripts log failures but never block the loop. **`appendPrompt`** (or `--pr
245
245
 
246
246
  ### Dashboard and logs
247
247
 
248
- The terminal dashboard shows three always-visible panels: **RALPH AGENT** (engine/model, concurrency, poll interval, active limits, feature flags, Linear filter), **POLL STATUS + WORKERS** (last-poll bucket breakdown — `found N │ todo · resume · conflict · review · mention` (each colored when non-zero) plus `↺ Ns` next-poll countdown, active/queued worker totals), and **TASKS tab bar** (numbered worker tabs — `Tab` / `← →` / `1-9` to switch).
248
+ The terminal dashboard shows three always-visible panels: **RALPH AGENT** (engine/model, concurrency, poll interval, active limits, feature flags, Linear filter), **POLL STATUS + WORKERS** (last-poll bucket breakdown — `todo · res · conf · rev · @` (each colored when non-zero) plus `↺ Ns` next-poll countdown, active/queued worker totals), and **TASKS tab bar** (numbered worker tabs — `Tab` / `← →` / `1-9` to switch).
249
249
 
250
250
  Each worker card shows: priority badge + identifier + title + mode badge, `↗ LINEAR`, `↗ PR`, `▶ TASK` (first unchecked task from `tasks.md`, refreshed every second), `PHASE` with color + elapsed time, `⏵ CMD` when a shell command is in flight, `LOG` path for `tail -f`, and `─ OUTPUT ─` with live stdout/stderr.
251
251
 
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.3")
35033
- return "2.20.3";
35032
+ if ("2.21.0")
35033
+ return "2.21.0";
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";
@@ -73659,18 +73734,6 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73659
73734
  }, undefined, false, undefined, this),
73660
73735
  pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
73661
73736
  children: [
73662
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73663
- dimColor: true,
73664
- children: "\u2502"
73665
- }, undefined, false, undefined, this),
73666
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73667
- dimColor: true,
73668
- children: "found"
73669
- }, undefined, false, undefined, this),
73670
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73671
- color: "white",
73672
- children: pollStatus.lastFound
73673
- }, undefined, false, undefined, this),
73674
73737
  pollStatus.lastBuckets && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
73675
73738
  children: [
73676
73739
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
@@ -73691,7 +73754,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73691
73754
  }, undefined, false, undefined, this),
73692
73755
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73693
73756
  dimColor: true,
73694
- children: "resume"
73757
+ children: "res"
73695
73758
  }, undefined, false, undefined, this),
73696
73759
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73697
73760
  color: pollStatus.lastBuckets.inProgress > 0 ? "cyan" : "white",
@@ -73703,7 +73766,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73703
73766
  }, undefined, false, undefined, this),
73704
73767
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73705
73768
  dimColor: true,
73706
- children: "conflict"
73769
+ children: "conf"
73707
73770
  }, undefined, false, undefined, this),
73708
73771
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73709
73772
  color: pollStatus.lastBuckets.conflicted > 0 ? "red" : "white",
@@ -73715,7 +73778,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73715
73778
  }, undefined, false, undefined, this),
73716
73779
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73717
73780
  dimColor: true,
73718
- children: "review"
73781
+ children: "rev"
73719
73782
  }, undefined, false, undefined, this),
73720
73783
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73721
73784
  color: pollStatus.lastBuckets.review > 0 ? "yellow" : "white",
@@ -73727,7 +73790,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73727
73790
  }, undefined, false, undefined, this),
73728
73791
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73729
73792
  dimColor: true,
73730
- children: "mention"
73793
+ children: "@"
73731
73794
  }, undefined, false, undefined, this),
73732
73795
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73733
73796
  color: pollStatus.lastBuckets.mentions > 0 ? "magenta" : "white",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.20.3",
3
+ "version": "2.21.0",
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",