@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 +9 -2
- package/dist/cli/index.js +84 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Ralphy
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@neriros/ralphy)
|
|
4
|
+
[](https://www.npmjs.com/package/@neriros/ralphy)
|
|
5
|
+
[](https://github.com/NeriRos/ralphy/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/NeriRos/ralphy)
|
|
7
|
+
[](https://github.com/NeriRos/ralphy/issues)
|
|
8
|
+
[](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.
|
|
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
|
-
|
|
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
|
-
|
|
71010
|
-
|
|
71011
|
-
|
|
71012
|
-
|
|
71013
|
-
|
|
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
|
}
|