@neriros/ralphy 2.23.0 → 2.23.2

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
@@ -240,14 +240,15 @@ Done issues whose PR `gh pr view --json mergeable` reports as `CONFLICTING` get
240
240
 
241
241
  ### PR + CI integration
242
242
 
243
- | Flag / config | Behavior |
244
- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
245
- | `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>`. |
246
- | `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. |
247
- | `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. |
248
- | `ciPollIntervalSeconds` | Seconds between CI status polls (default `30`). |
249
- | `ignoreCiChecks` | Array of check names to ignore when computing pass/fail. |
250
- | `codeReviewTrigger` / `--code-review` | See [Code-review iteration](#code-review-iteration). |
243
+ | Flag / config | Behavior |
244
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
245
+ | `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>`. |
246
+ | `stackPrsOnDependencies` / `--stack-prs` | When the Linear issue is blocked by another issue (`blocked_by` relation) that has exactly one open GitHub PR, open this PR against that blocker's head branch instead of `prBaseBranch`. Resolves the blocker's PR via Linear's auto-attachment + `gh pr view --json state,headRefName`. Falls back to `prBaseBranch` when zero / multiple blockers (or PRs) match. A `ralph:branch:<name>` label still wins. |
247
+ | `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. |
248
+ | `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. |
249
+ | `ciPollIntervalSeconds` | Seconds between CI status polls (default `30`). |
250
+ | `ignoreCiChecks` | Array of check names to ignore when computing pass/fail. |
251
+ | `codeReviewTrigger` / `--code-review` | See [Code-review iteration](#code-review-iteration). |
251
252
 
252
253
  ### Worktrees, setup, teardown
253
254
 
@@ -302,19 +303,20 @@ Failed workers are not marked processed, so they retry on the next poll. SIGINT
302
303
 
303
304
  **Agent-mode flags**
304
305
 
305
- | Option | Behavior |
306
- | ------------------------- | ------------------------------------------------------------------------------------ |
307
- | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
308
- | `--linear-assignee <id>` | Assignee filter (user id, email, or `me`) |
309
- | `--poll-interval <s>` | Seconds between Linear polls (default `60`) |
310
- | `--concurrency <n>` | Max concurrent task loops (default `1`) |
311
- | `--max-tickets <n>` | Stop picking up new issues after N have been started this run (`0` = unlimited) |
312
- | `--worktree` | Run each task in its own git worktree |
313
- | `--indicator <k>:<t>:<v>` | Override one `linear.indicators` entry (repeatable, e.g. `setDone:status:Done`) |
314
- | `--create-pr` | Push worker branch + open a GitHub PR on success (needs `--worktree`) |
315
- | `--fix-ci` | After PR opens, re-run task on CI failures until green (needs `--create-pr`) |
316
- | `--code-review` | Watch open tracked PRs for unresolved review comments and prepend a code-review task |
317
- | `--json-output` | Emit JSONL to stdout instead of rendering the Ink dashboard (CI / scripting) |
306
+ | Option | Behavior |
307
+ | ------------------------- | -------------------------------------------------------------------------------------------- |
308
+ | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
309
+ | `--linear-assignee <id>` | Assignee filter (user id, email, or `me`) |
310
+ | `--poll-interval <s>` | Seconds between Linear polls (default `60`) |
311
+ | `--concurrency <n>` | Max concurrent task loops (default `1`) |
312
+ | `--max-tickets <n>` | Stop picking up new issues after N have been started this run (`0` = unlimited) |
313
+ | `--worktree` | Run each task in its own git worktree |
314
+ | `--indicator <k>:<t>:<v>` | Override one `linear.indicators` entry (repeatable, e.g. `setDone:status:Done`) |
315
+ | `--create-pr` | Push worker branch + open a GitHub PR on success (needs `--worktree`) |
316
+ | `--fix-ci` | After PR opens, re-run task on CI failures until green (needs `--create-pr`) |
317
+ | `--stack-prs` | Open the PR against a blocker issue's open-PR head branch when present (needs `--create-pr`) |
318
+ | `--code-review` | Watch open tracked PRs for unresolved review comments and prepend a code-review task |
319
+ | `--json-output` | Emit JSONL to stdout instead of rendering the Ink dashboard (CI / scripting) |
318
320
 
319
321
  **List-mode flags**
320
322
 
package/dist/cli/index.js CHANGED
@@ -9553,7 +9553,7 @@ Learn more about this warning here: https://reactjs.org/link/legacy-context`, so
9553
9553
 
9554
9554
  ` + ("" + errorBoundaryMessage);
9555
9555
  console["error"](combinedMessage);
9556
- } else {}
9556
+ }
9557
9557
  } catch (e) {
9558
9558
  setTimeout(function() {
9559
9559
  throw e;
@@ -18037,7 +18037,7 @@ var require_backend = __commonJS((exports, module) => {
18037
18037
  987: (module2, __unused_webpack_exports, __webpack_require__2) => {
18038
18038
  if (true) {
18039
18039
  module2.exports = __webpack_require__2(786);
18040
- } else {}
18040
+ }
18041
18041
  },
18042
18042
  126: (__unused_webpack_module, exports2, __webpack_require__2) => {
18043
18043
  var process3 = __webpack_require__2(169);
@@ -18487,7 +18487,7 @@ var require_backend = __commonJS((exports, module) => {
18487
18487
  189: (module2, __unused_webpack_exports, __webpack_require__2) => {
18488
18488
  if (true) {
18489
18489
  module2.exports = __webpack_require__2(126);
18490
- } else {}
18490
+ }
18491
18491
  },
18492
18492
  206: function(module2, exports2, __webpack_require__2) {
18493
18493
  var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;
@@ -18502,7 +18502,7 @@ var require_backend = __commonJS((exports, module) => {
18502
18502
  (function(root, factory) {
18503
18503
  if (true) {
18504
18504
  __WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__2(430)], __WEBPACK_AMD_DEFINE_FACTORY__ = factory, __WEBPACK_AMD_DEFINE_RESULT__ = typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" ? __WEBPACK_AMD_DEFINE_FACTORY__.apply(exports2, __WEBPACK_AMD_DEFINE_ARRAY__) : __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module2.exports = __WEBPACK_AMD_DEFINE_RESULT__);
18505
- } else {}
18505
+ }
18506
18506
  })(this, function ErrorStackParser(StackFrame) {
18507
18507
  var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
18508
18508
  var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
@@ -19198,7 +19198,7 @@ var require_backend = __commonJS((exports, module) => {
19198
19198
  (function(root, factory) {
19199
19199
  if (true) {
19200
19200
  __WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = factory, __WEBPACK_AMD_DEFINE_RESULT__ = typeof __WEBPACK_AMD_DEFINE_FACTORY__ === "function" ? __WEBPACK_AMD_DEFINE_FACTORY__.apply(exports2, __WEBPACK_AMD_DEFINE_ARRAY__) : __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module2.exports = __WEBPACK_AMD_DEFINE_RESULT__);
19201
- } else {}
19201
+ }
19202
19202
  })(this, function() {
19203
19203
  function _isNumber(n) {
19204
19204
  return !isNaN(parseFloat(n)) && isFinite(n);
@@ -24572,7 +24572,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
24572
24572
  var suffix = "";
24573
24573
  if (true) {
24574
24574
  suffix = " (<anonymous>)";
24575
- } else {}
24575
+ }
24576
24576
  return `
24577
24577
  ` + prefix + name + suffix;
24578
24578
  }
@@ -27301,7 +27301,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
27301
27301
  hasChanges = true;
27302
27302
  updateMostRecentlyInspectedElementIfNecessary(devtoolsInstance.id);
27303
27303
  }
27304
- } else {}
27304
+ }
27305
27305
  }
27306
27306
  } catch (err) {
27307
27307
  _iterator2.e(err);
@@ -29080,7 +29080,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29080
29080
  if (contentFiber === null) {
29081
29081
  throw new Error("There should always be an Offscreen Fiber child in a hydrated Suspense boundary.");
29082
29082
  }
29083
- } else {}
29083
+ }
29084
29084
  var _isTimedOut = fiber.memoizedState !== null;
29085
29085
  if (!_isTimedOut) {
29086
29086
  newSuspenseNode.rects = measureInstance(newInstance);
@@ -29114,7 +29114,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29114
29114
  if (_contentFiber === null) {
29115
29115
  throw new Error("There should always be an Offscreen Fiber child in a hydrated Suspense boundary.");
29116
29116
  }
29117
- } else {}
29117
+ }
29118
29118
  var suspenseState = fiber.memoizedState;
29119
29119
  var _isTimedOut3 = suspenseState !== null;
29120
29120
  if (!_isTimedOut3) {
@@ -29214,7 +29214,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29214
29214
  trackThrownPromisesFromRetryCache(newSuspenseNode, fiber.stateNode);
29215
29215
  mountSuspenseChildrenRecursively(_contentFiber2, traceNearestHostComponentUpdate, stashedSuspenseParent, stashedSuspensePrevious, stashedSuspenseRemaining);
29216
29216
  shouldPopSuspenseNode = false;
29217
- } else {}
29217
+ }
29218
29218
  } else {
29219
29219
  if (fiber.child !== null) {
29220
29220
  mountChildrenRecursively(fiber.child, traceNearestHostComponentUpdate);
@@ -29800,7 +29800,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29800
29800
  shouldPopSuspenseNode = false;
29801
29801
  } else if (previousHydrated && !nextHydrated) {
29802
29802
  throw new Error("Encountered a dehydrated Suspense boundary that was previously hydrated.");
29803
- } else {}
29803
+ }
29804
29804
  } else {
29805
29805
  if (nextFiber.child !== prevFiber.child) {
29806
29806
  updateFlags |= updateChildrenRecursively(nextFiber.child, prevFiber.child, traceNearestHostComponentUpdate);
@@ -29846,8 +29846,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29846
29846
  recordResetChildren(fiberInstance);
29847
29847
  }
29848
29848
  updateFlags &= ~ShouldResetChildren;
29849
- } else {}
29850
- } else {}
29849
+ }
29850
+ }
29851
29851
  if ((updateFlags & ShouldResetSuspenseChildren) !== NoUpdate) {
29852
29852
  if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
29853
29853
  var _suspenseNode3 = fiberInstance.suspenseNode;
@@ -29855,7 +29855,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29855
29855
  recordResetSuspenseChildren(_suspenseNode3);
29856
29856
  updateFlags &= ~ShouldResetSuspenseChildren;
29857
29857
  }
29858
- } else {}
29858
+ }
29859
29859
  }
29860
29860
  if ((updateFlags & ShouldResetParentSuspenseChildren) !== NoUpdate) {
29861
29861
  if (fiberInstance !== null && fiberInstance.kind === FIBER_INSTANCE) {
@@ -29864,7 +29864,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
29864
29864
  updateFlags &= ~ShouldResetParentSuspenseChildren;
29865
29865
  updateFlags |= ShouldResetSuspenseChildren;
29866
29866
  }
29867
- } else {}
29867
+ }
29868
29868
  }
29869
29869
  return updateFlags;
29870
29870
  } finally {
@@ -32928,7 +32928,7 @@ The error thrown in the component is:
32928
32928
  rendererInterface = renderer_attach(hook, id, renderer, global2, shouldStartProfilingNow, profilingSettings);
32929
32929
  } else if (renderer.ComponentTree) {
32930
32930
  rendererInterface = legacy_renderer_attach(hook, id, renderer, global2);
32931
- } else {}
32931
+ }
32932
32932
  }
32933
32933
  return rendererInterface;
32934
32934
  }
@@ -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.23.0")
35033
- return "2.23.0";
35032
+ if ("2.23.2")
35033
+ return "2.23.2";
35034
35034
  } catch {}
35035
35035
  const dirsToTry = [];
35036
35036
  try {
@@ -35118,12 +35118,13 @@ async function parseArgs(argv) {
35118
35118
  fromAgent: false,
35119
35119
  linearTeam: "",
35120
35120
  linearAssignee: "",
35121
- pollInterval: 60,
35122
- concurrency: 1,
35121
+ pollInterval: 0,
35122
+ concurrency: 0,
35123
35123
  worktree: false,
35124
35124
  indicators: {},
35125
35125
  createPr: false,
35126
35126
  fixCi: false,
35127
+ stackPrs: false,
35127
35128
  codeReview: false,
35128
35129
  maxTickets: 0,
35129
35130
  projectRoot: undefined,
@@ -35319,6 +35320,9 @@ async function parseArgs(argv) {
35319
35320
  case "--fix-ci":
35320
35321
  result2.fixCi = true;
35321
35322
  break;
35323
+ case "--stack-prs":
35324
+ result2.stackPrs = true;
35325
+ break;
35322
35326
  case "--code-review":
35323
35327
  result2.codeReview = true;
35324
35328
  break;
@@ -35423,6 +35427,7 @@ var init_cli = __esm(() => {
35423
35427
  " (attachment upserts a single 'Ralphy' entry; value = subtitle)",
35424
35428
  " --create-pr Push the worker branch and open a GitHub PR on success (needs --worktree)",
35425
35429
  " --fix-ci After opening the PR, re-run on CI failures until green (needs --create-pr)",
35430
+ " --stack-prs Base the PR on a blocker issue's open-PR head branch when present (needs --create-pr)",
35426
35431
  " --code-review Watch open tracked PRs for unresolved review comments and prepend a code-review task",
35427
35432
  " --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
35428
35433
  " --json-output Emit JSONL to stdout instead of the Ink dashboard (for scripting/CI)",
@@ -59526,6 +59531,12 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Ralp
59526
59531
  // Linear issue with "ralph:branch:<branch-name>".
59527
59532
  "prBaseBranch": "main",
59528
59533
 
59534
+ // When true, if the Linear issue is blocked by another issue whose
59535
+ // single open GitHub PR can be resolved (via Linear's auto-attachment),
59536
+ // open this PR against that blocker's head branch instead of prBaseBranch.
59537
+ // A "ralph:branch:<name>" label on the issue still wins over this.
59538
+ "stackPrsOnDependencies": false,
59539
+
59529
59540
  // Merge strategy used when GitHub auto-merge is enabled (see getAutoMerge
59530
59541
  // indicator below). One of "squash", "merge", "rebase".
59531
59542
  "autoMergeStrategy": "squash",
@@ -59679,6 +59690,7 @@ var init_config = __esm(() => {
59679
59690
  appendPrompt: exports_external.string().optional(),
59680
59691
  createPrOnSuccess: exports_external.boolean().default(false),
59681
59692
  prBaseBranch: exports_external.string().default("main"),
59693
+ stackPrsOnDependencies: exports_external.boolean().default(false),
59682
59694
  autoMergeStrategy: exports_external.enum(["squash", "merge", "rebase"]).default("squash"),
59683
59695
  fixCiOnFailure: exports_external.boolean().default(false),
59684
59696
  maxCiFixAttempts: exports_external.number().int().positive().default(5),
@@ -61155,15 +61167,33 @@ async function runPrPhase(input, deps) {
61155
61167
  wantAutoMerge,
61156
61168
  cfg
61157
61169
  } = input;
61158
- const { cmd, log: log2, emit, respawnWorker, registerPr, checkPrConflict } = deps;
61170
+ const {
61171
+ cmd,
61172
+ log: log2,
61173
+ emit,
61174
+ respawnWorker,
61175
+ registerPr,
61176
+ checkPrConflict,
61177
+ resolveDependencyBaseBranch
61178
+ } = deps;
61159
61179
  if (!branch || !issue) {
61160
61180
  log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
61161
61181
  return PR_FAILED_EXIT;
61162
61182
  }
61163
61183
  const labelBase = baseBranchFromLabels(issue.labels);
61164
- const base2 = labelBase ?? cfg.prBaseBranch;
61184
+ let base2 = labelBase ?? cfg.prBaseBranch;
61165
61185
  if (labelBase && labelBase !== cfg.prBaseBranch) {
61166
61186
  log2(` base branch override from label: ${labelBase}`, "gray");
61187
+ } else if (cfg.stackPrsOnDependencies && resolveDependencyBaseBranch) {
61188
+ try {
61189
+ const dependencyBase = await resolveDependencyBaseBranch(issue);
61190
+ if (dependencyBase && dependencyBase !== base2) {
61191
+ log2(` base branch override from blocker PR: ${dependencyBase}`, "gray");
61192
+ base2 = dependencyBase;
61193
+ }
61194
+ } catch (err) {
61195
+ log2(`! could not resolve dependency base branch for ${issue.identifier}: ${err.message}`, "yellow");
61196
+ }
61167
61197
  }
61168
61198
  const ctx = {
61169
61199
  changeName,
@@ -61291,7 +61321,8 @@ async function runPostTask(input, deps) {
61291
61321
  emit,
61292
61322
  respawnWorker,
61293
61323
  ...deps.registerPr !== undefined ? { registerPr: deps.registerPr } : {},
61294
- ...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {}
61324
+ ...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {},
61325
+ ...deps.resolveDependencyBaseBranch !== undefined ? { resolveDependencyBaseBranch: deps.resolveDependencyBaseBranch } : {}
61295
61326
  });
61296
61327
  }
61297
61328
  emit(effectiveCode === 0 ? "done" : "gave-up", effectiveCode !== 0 ? `exit ${effectiveCode}` : undefined);
@@ -61839,7 +61870,8 @@ PR: ${prUrl}` : ""
61839
61870
  maxCiFixAttempts: cfg.maxCiFixAttempts,
61840
61871
  ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
61841
61872
  cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
61842
- ignoreCiChecks: cfg.ignoreCiChecks
61873
+ ignoreCiChecks: cfg.ignoreCiChecks,
61874
+ stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies
61843
61875
  },
61844
61876
  respawnWorker: respawn
61845
61877
  }, {
@@ -61868,7 +61900,8 @@ PR: ${prUrl}` : ""
61868
61900
  await new Promise((r) => setTimeout(r, 2000));
61869
61901
  }
61870
61902
  return false;
61871
- }
61903
+ },
61904
+ resolveDependencyBaseBranch: (issue) => resolveDependencyBaseBranch(issue, tracedCmd, cwd2)
61872
61905
  });
61873
61906
  cwdByChange.delete(changeName);
61874
61907
  statesDirByChange.delete(changeName);
@@ -61956,6 +61989,45 @@ PR: ${prUrl}` : ""
61956
61989
  markPrUnavailable(changeName);
61957
61990
  return null;
61958
61991
  }
61992
+ async function resolveDependencyBaseBranch(issue, runner, runnerCwd) {
61993
+ const blockerIds = issue.blockedByIds;
61994
+ if (blockerIds.length === 0)
61995
+ return null;
61996
+ const candidates = [];
61997
+ for (const blockerId of blockerIds) {
61998
+ let attachments;
61999
+ try {
62000
+ attachments = await fetchIssueAttachments(apiKey, blockerId);
62001
+ } catch (err) {
62002
+ onLog(`! could not fetch attachments for blocker ${blockerId} of ${issue.identifier}: ${err.message}`, "yellow");
62003
+ continue;
62004
+ }
62005
+ const prUrls = attachments.map((a) => a.url).filter((url) => /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/.test(url));
62006
+ const openHeads = [];
62007
+ for (const url of prUrls) {
62008
+ try {
62009
+ const res = await runner.run(["gh", "pr", "view", url, "--json", "state,headRefName", "--jq", "."], runnerCwd);
62010
+ const parsed = JSON.parse(res.stdout.trim());
62011
+ if (parsed.state === "OPEN" && parsed.headRefName) {
62012
+ openHeads.push(parsed.headRefName);
62013
+ }
62014
+ } catch (err) {
62015
+ onLog(`! gh pr view failed for ${url} (blocker of ${issue.identifier}): ${err.message}`, "yellow");
62016
+ }
62017
+ }
62018
+ if (openHeads.length === 1) {
62019
+ candidates.push(openHeads[0]);
62020
+ } else if (openHeads.length > 1) {
62021
+ onLog(` ${issue.identifier}: blocker ${blockerId} has ${openHeads.length} open PRs \u2014 skipping dependency base resolution`, "gray");
62022
+ }
62023
+ }
62024
+ if (candidates.length === 1)
62025
+ return candidates[0];
62026
+ if (candidates.length > 1) {
62027
+ onLog(` ${issue.identifier}: ${candidates.length} blockers have open PRs \u2014 falling back to default base`, "gray");
62028
+ }
62029
+ return null;
62030
+ }
61959
62031
  async function discoverPrUrlFromLinear(issue) {
61960
62032
  try {
61961
62033
  const attachments = await fetchIssueAttachments(apiKey, issue.id);
package/dist/mcp/index.js CHANGED
@@ -14104,7 +14104,7 @@ function finalize(ctx, schema) {
14104
14104
  result.$schema = "http://json-schema.org/draft-07/schema#";
14105
14105
  } else if (ctx.target === "draft-04") {
14106
14106
  result.$schema = "http://json-schema.org/draft-04/schema#";
14107
- } else if (ctx.target === "openapi-3.0") {} else {}
14107
+ } else if (ctx.target === "openapi-3.0") {}
14108
14108
  if (ctx.external?.uri) {
14109
14109
  const id = ctx.external.registry.get(schema)?.id;
14110
14110
  if (!id)
@@ -14352,7 +14352,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
14352
14352
  if (val === undefined) {
14353
14353
  if (ctx.unrepresentable === "throw") {
14354
14354
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
14355
- } else {}
14355
+ }
14356
14356
  } else if (typeof val === "bigint") {
14357
14357
  if (ctx.unrepresentable === "throw") {
14358
14358
  throw new Error("BigInt literals cannot be represented in JSON Schema");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.23.0",
3
+ "version": "2.23.2",
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",