@neriros/ralphy 2.9.0 → 2.9.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
@@ -1,5 +1,12 @@
1
1
  # Ralphy
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@neriros/ralphy.svg)](https://www.npmjs.com/package/@neriros/ralphy)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@neriros/ralphy.svg)](https://www.npmjs.com/package/@neriros/ralphy)
5
+ [![license](https://img.shields.io/npm/l/@neriros/ralphy.svg)](https://github.com/NeriRos/ralphy/blob/main/LICENSE)
6
+ [![GitHub stars](https://img.shields.io/github/stars/NeriRos/ralphy.svg?style=social)](https://github.com/NeriRos/ralphy)
7
+ [![GitHub issues](https://img.shields.io/github/issues/NeriRos/ralphy.svg)](https://github.com/NeriRos/ralphy/issues)
8
+ [![Bun](https://img.shields.io/badge/runtime-Bun-fbf0df.svg)](https://bun.sh)
9
+
3
10
  An iterative AI task execution framework. Ralphy orchestrates multi-phase autonomous work using Claude or Codex engines, with built-in state management, progress tracking, and cost safeguards.
4
11
 
5
12
  ## How It Works
@@ -19,9 +26,9 @@ Each iteration reads the `## Steering` section of `proposal.md`, picks the first
19
26
  ### npm (global)
20
27
 
21
28
  ```bash
22
- npm install -g ralphy
29
+ npm install -g @neriros/ralphy
23
30
  # or
24
- bunx ralphy
31
+ bunx @neriros/ralphy
25
32
  ```
26
33
 
27
34
  Requires [Bun](https://bun.sh) as the runtime.
package/dist/cli/index.js CHANGED
@@ -56407,7 +56407,7 @@ function log(msg) {
56407
56407
  // package.json
56408
56408
  var package_default = {
56409
56409
  name: "@neriros/ralphy",
56410
- version: "2.9.0",
56410
+ version: "2.9.2",
56411
56411
  description: "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
56412
56412
  keywords: [
56413
56413
  "agent",
@@ -70066,12 +70066,19 @@ async function fetchOpenIssues(apiKey, filter2) {
70066
70066
  state { name type }
70067
70067
  assignee { id email name }
70068
70068
  labels { nodes { name } }
70069
+ relations(first: 50) {
70070
+ nodes {
70071
+ type
70072
+ relatedIssue { id state { type } }
70073
+ }
70074
+ }
70069
70075
  }
70070
70076
  }
70071
70077
  }`;
70072
70078
  const data = await linearRequest(apiKey, query, {
70073
70079
  filter: where
70074
70080
  });
70081
+ const DONE_STATE_TYPES = new Set(["completed", "cancelled"]);
70075
70082
  return data.issues.nodes.map((n) => ({
70076
70083
  id: n.id,
70077
70084
  identifier: n.identifier,
@@ -70081,7 +70088,8 @@ async function fetchOpenIssues(apiKey, filter2) {
70081
70088
  state: n.state,
70082
70089
  assignee: n.assignee,
70083
70090
  labels: n.labels.nodes.map((l) => l.name),
70084
- priority: n.priority
70091
+ priority: n.priority,
70092
+ blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
70085
70093
  }));
70086
70094
  }
70087
70095
  async function linearRequest(apiKey, query, variables) {
@@ -70379,6 +70387,11 @@ class AgentCoordinator {
70379
70387
  continue;
70380
70388
  if (this.pendingIds.has(issue.id))
70381
70389
  continue;
70390
+ const blocker = issue.blockedByIds.find((bid) => !seen.has(bid));
70391
+ if (blocker !== undefined) {
70392
+ this.deps.onLog(` \u23F8 ${issue.identifier} skipped \u2014 blocked by unresolved dependency`, "yellow");
70393
+ continue;
70394
+ }
70382
70395
  this.queue.push(issue);
70383
70396
  added += 1;
70384
70397
  }
@@ -70593,6 +70606,42 @@ async function createWorktree(projectRoot, changeName, runner) {
70593
70606
  async function removeWorktree(projectRoot, cwd2, runner) {
70594
70607
  await runner.run(["worktree", "remove", "--force", cwd2], projectRoot);
70595
70608
  }
70609
+ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
70610
+ const status = await runner.run(["status", "--porcelain"], cwd2);
70611
+ const dirty = status.stdout.trim();
70612
+ let unpushedCommits = "";
70613
+ try {
70614
+ const log2 = await runner.run(["log", "--oneline", `${base2}..HEAD`, "--no-merges"], cwd2);
70615
+ unpushedCommits = log2.stdout.trim();
70616
+ } catch {
70617
+ unpushedCommits = "<unknown: failed to compare against base>";
70618
+ }
70619
+ if (dirty && unpushedCommits) {
70620
+ return {
70621
+ safe: false,
70622
+ reason: "uncommitted changes AND unpushed commits present",
70623
+ dirty,
70624
+ unpushedCommits
70625
+ };
70626
+ }
70627
+ if (dirty) {
70628
+ return {
70629
+ safe: false,
70630
+ reason: "uncommitted or untracked files present",
70631
+ dirty,
70632
+ unpushedCommits
70633
+ };
70634
+ }
70635
+ if (unpushedCommits) {
70636
+ return {
70637
+ safe: false,
70638
+ reason: `commits ahead of ${base2} were not pushed/PR'd`,
70639
+ dirty,
70640
+ unpushedCommits
70641
+ };
70642
+ }
70643
+ return { safe: true, dirty, unpushedCommits };
70644
+ }
70596
70645
 
70597
70646
  // apps/cli/src/agent/pr.ts
70598
70647
  function defaultTitle(issue) {
@@ -70999,18 +71048,45 @@ ${stamped}
70999
71048
  } catch (err) {
71000
71049
  const e = err;
71001
71050
  const detail = e.stderr?.trim() || e.message;
71002
- appendLog(`! PR create failed for ${changeName}: ${detail}`, "red");
71051
+ const combined = `${e.stdout ?? ""}
71052
+ ${e.stderr ?? ""}`;
71053
+ const pushRejected = /failed to push some refs|pre-push hook|hook declined/i.test(combined);
71054
+ if (pushRejected) {
71055
+ appendLog(`! push rejected for ${changeName} (host repo pre-push hook failed) \u2014 worktree preserved at ${cwd2}`, "red");
71056
+ appendLog(` detail: ${detail}`, "red");
71057
+ } else {
71058
+ appendLog(`! PR create failed for ${changeName}: ${detail}`, "red");
71059
+ }
71003
71060
  effectiveCode = PR_FAILED_EXIT;
71004
71061
  }
71005
71062
  }
71006
71063
  }
71007
71064
  if (useWorktree && cwd2 !== projectRoot) {
71008
71065
  if (effectiveCode === 0 && cfg.cleanupWorktreeOnSuccess) {
71009
- try {
71010
- await removeWorktree(projectRoot, cwd2, bunGitRunner);
71011
- appendLog(` removed worktree ${cwd2}`, "gray");
71012
- } catch (err) {
71013
- appendLog(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
71066
+ const check = await isWorktreeSafeToRemove(cwd2, cfg.prBaseBranch, bunGitRunner).catch((err) => ({
71067
+ safe: false,
71068
+ reason: `safety check failed: ${err.message}`,
71069
+ dirty: "",
71070
+ unpushedCommits: ""
71071
+ }));
71072
+ if (!check.safe) {
71073
+ appendLog(`! preserving worktree for ${changeName}: ${check.reason}`, "yellow");
71074
+ if (check.dirty) {
71075
+ appendLog(` uncommitted:
71076
+ ${check.dirty}`, "yellow");
71077
+ }
71078
+ if (check.unpushedCommits) {
71079
+ appendLog(` commits:
71080
+ ${check.unpushedCommits}`, "yellow");
71081
+ }
71082
+ appendLog(` path: ${cwd2}`, "yellow");
71083
+ } else {
71084
+ try {
71085
+ await removeWorktree(projectRoot, cwd2, bunGitRunner);
71086
+ appendLog(` removed worktree ${cwd2}`, "gray");
71087
+ } catch (err) {
71088
+ appendLog(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
71089
+ }
71014
71090
  }
71015
71091
  }
71016
71092
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.9.0",
3
+ "version": "2.9.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",