@neriros/ralphy 2.16.7 → 2.17.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 +60 -1
- package/dist/cli/index.js +381 -99
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,65 @@ graph LR
|
|
|
21
21
|
|
|
22
22
|
Each iteration reads the `## Steering` section of `proposal.md`, picks the first unchecked item from `tasks.md`, does the work, validates, and checks the item off. When all items are checked the loop archives the change automatically.
|
|
23
23
|
|
|
24
|
+
## Agent Mode Flow
|
|
25
|
+
|
|
26
|
+
The full orchestration cycle from Linear poll through teardown:
|
|
27
|
+
|
|
28
|
+
```mermaid
|
|
29
|
+
flowchart TD
|
|
30
|
+
LINEAR_POLL["Linear poll\n(todo / in-progress / conflicted)"]
|
|
31
|
+
|
|
32
|
+
LINEAR_POLL --> ISSUE_STATE{issue state}
|
|
33
|
+
ISSUE_STATE -- todo --> MODE_FRESH[mode: fresh\nscaffold change]
|
|
34
|
+
ISSUE_STATE -- in-progress --> MODE_RESUME[mode: resume]
|
|
35
|
+
ISSUE_STATE -- conflicted --> MODE_CONFLICT_FIX[mode: conflict-fix\nprepend fix task]
|
|
36
|
+
|
|
37
|
+
MODE_FRESH & MODE_RESUME & MODE_CONFLICT_FIX --> SET_IN_PROGRESS[Linear: setInProgress]
|
|
38
|
+
SET_IN_PROGRESS --> USE_WORKTREE{useWorktree?}
|
|
39
|
+
USE_WORKTREE -- yes --> SCAFFOLD[scaffolding\ncreate worktree + branch]
|
|
40
|
+
USE_WORKTREE -- no --> WORKER
|
|
41
|
+
SCAFFOLD --> WORKER([working\nClaude agent loop])
|
|
42
|
+
|
|
43
|
+
WORKER --> EXIT_CODE{exitCode?}
|
|
44
|
+
EXIT_CODE -- "non-zero" --> GAVE_UP
|
|
45
|
+
EXIT_CODE -- "0" --> WANT_PR{wantPr?}
|
|
46
|
+
|
|
47
|
+
WANT_PR -- no --> DONE
|
|
48
|
+
WANT_PR -- yes --> PUSH_AND_CREATE_PR["push + pr-create\n↺ rebase/hook-fix on rejection"]
|
|
49
|
+
|
|
50
|
+
PUSH_AND_CREATE_PR -- gave-up --> GAVE_UP
|
|
51
|
+
PUSH_AND_CREATE_PR -- no commits ahead --> DONE
|
|
52
|
+
|
|
53
|
+
PUSH_AND_CREATE_PR -- pr opened --> WATCH_LOOP
|
|
54
|
+
|
|
55
|
+
subgraph WATCH_LOOP["watch loop"]
|
|
56
|
+
direction LR
|
|
57
|
+
CONFLICT_CHECK[conflict-check] --> CI_POLL[ci-poll / ci-fix]
|
|
58
|
+
CI_POLL --> CONFLICT_CHECK
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
WATCH_LOOP -- green & clean --> DONE
|
|
62
|
+
WATCH_LOOP -- gave-up --> GAVE_UP
|
|
63
|
+
|
|
64
|
+
DONE([done]) --> WORKTREE_CLEANUP
|
|
65
|
+
GAVE_UP([gave-up]) --> WORKTREE_CLEANUP
|
|
66
|
+
|
|
67
|
+
subgraph WORKTREE_CLEANUP["cleanup"]
|
|
68
|
+
direction LR
|
|
69
|
+
SHOULD_REMOVE_WORKTREE{useWorktree\n& success\n& cleanupOnSuccess?}
|
|
70
|
+
SHOULD_REMOVE_WORKTREE -- yes --> REMOVE_WORKTREE[remove worktree]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
WORKTREE_CLEANUP --> TEARDOWN[teardown]
|
|
74
|
+
TEARDOWN --> OUTCOME{ok?}
|
|
75
|
+
|
|
76
|
+
OUTCOME -- "yes,\nnot conflict-fix" --> LINEAR_SET_DONE["Linear: setDone\nclearInProgress\npost comment"]
|
|
77
|
+
OUTCOME -- "yes,\nconflict-fix" --> LINEAR_CLEAR_CONFLICTED["Linear: clearConflicted\npost comment"]
|
|
78
|
+
OUTCOME -- no --> LINEAR_SET_ERROR["Linear: setError\nclearInProgress\npost comment"]
|
|
79
|
+
|
|
80
|
+
LINEAR_SET_DONE & LINEAR_CLEAR_CONFLICTED & LINEAR_SET_ERROR --> LINEAR_POLL
|
|
81
|
+
```
|
|
82
|
+
|
|
24
83
|
## Installation
|
|
25
84
|
|
|
26
85
|
### npm (global)
|
|
@@ -156,7 +215,7 @@ Marker types are `"label"` or `"status"`. Combine markers under `apply` when one
|
|
|
156
215
|
|
|
157
216
|
With `--worktree` (or `useWorktree: true` in config) each task runs in an isolated worktree at `.ralph/worktrees/<change-name>` checked out onto a fresh `ralph/<change-name>` branch. The change is scaffolded _inside_ the worktree, and the loop's cwd is the worktree, so concurrent workers can't stomp on each other.
|
|
158
217
|
|
|
159
|
-
Use `setupScript` (run inside the worktree right after scaffolding) to install dependencies, copy `.env`, etc. Use `teardownScript` (run after the loop exits
|
|
218
|
+
Use `setupScript` (run inside the worktree right after scaffolding) to install dependencies, copy `.env`, etc. Use `teardownScript` (run after the loop exits and worktree cleanup) to gather artifacts or roll back local mutations. Both run via `sh -c`; failures are logged but never block the loop. With `cleanupWorktreeOnSuccess: true` the worktree is removed when the worker exits 0 — failed workers always keep their worktree (and branch) for human inspection.
|
|
160
219
|
|
|
161
220
|
**`appendPrompt`** (or `--prompt` in agent mode) is appended to every scaffolded `proposal.md` under an `## Additional instructions` section — use it for cross-cutting guidance every task should see.
|
|
162
221
|
|
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.
|
|
35033
|
-
return "2.
|
|
35032
|
+
if ("2.17.1")
|
|
35033
|
+
return "2.17.1";
|
|
35034
35034
|
} catch {}
|
|
35035
35035
|
const dirsToTry = [];
|
|
35036
35036
|
try {
|
|
@@ -35340,7 +35340,7 @@ var VERSION, VALID_MODES, VALID_MODELS, INDICATOR_KEYS, GET_KEYS, HELP_TEXT;
|
|
|
35340
35340
|
var init_cli = __esm(() => {
|
|
35341
35341
|
init_output();
|
|
35342
35342
|
VERSION = getVersion();
|
|
35343
|
-
VALID_MODES = new Set(["task", "list", "status", "init", "agent", "clean"]);
|
|
35343
|
+
VALID_MODES = new Set(["task", "list", "status", "init", "agent", "clean", "debug"]);
|
|
35344
35344
|
VALID_MODELS = new Set(["haiku", "sonnet", "opus"]);
|
|
35345
35345
|
INDICATOR_KEYS = new Set([
|
|
35346
35346
|
"getTodo",
|
|
@@ -35365,6 +35365,7 @@ var init_cli = __esm(() => {
|
|
|
35365
35365
|
" init Initialize OpenSpec in current directory",
|
|
35366
35366
|
" agent Poll Linear for new tasks and run loops concurrently",
|
|
35367
35367
|
" clean Remove worktree, branch, openspec change, and task state for --name",
|
|
35368
|
+
" debug Show agent log timeline, Linear state, and GitHub PR for --name",
|
|
35368
35369
|
"",
|
|
35369
35370
|
"Options:",
|
|
35370
35371
|
" --name <name> Change name (required for most commands)",
|
|
@@ -59949,6 +59950,9 @@ class AgentCoordinator {
|
|
|
59949
59950
|
capture("agent_linear_poll_failed", { error: err.message });
|
|
59950
59951
|
return { found: 0, added: 0 };
|
|
59951
59952
|
}
|
|
59953
|
+
if (todo.length + inProgress.length + conflicted.length > 0) {
|
|
59954
|
+
this.deps.onLog(` poll: ${todo.length} todo, ${inProgress.length} in-progress, ${conflicted.length} conflicted`, "gray");
|
|
59955
|
+
}
|
|
59952
59956
|
const queuedIds = new Set(this.queue.map((q) => q.issue.id));
|
|
59953
59957
|
const activeIds = new Set(this.workers.map((w) => w.issueId));
|
|
59954
59958
|
const eligible = (id) => !queuedIds.has(id) && !activeIds.has(id) && !this.pendingIds.has(id);
|
|
@@ -59970,6 +59974,7 @@ class AgentCoordinator {
|
|
|
59970
59974
|
this.queue.push({ issue, mode: "resume" });
|
|
59971
59975
|
queuedIds.add(issue.id);
|
|
59972
59976
|
added += 1;
|
|
59977
|
+
this.deps.onLog(` \u21B3 ${issue.identifier} queued (resume)`, "gray");
|
|
59973
59978
|
}
|
|
59974
59979
|
for (const issue of conflicted) {
|
|
59975
59980
|
if (atTicketLimit())
|
|
@@ -59979,6 +59984,7 @@ class AgentCoordinator {
|
|
|
59979
59984
|
this.queue.push({ issue, mode: "conflict-fix" });
|
|
59980
59985
|
queuedIds.add(issue.id);
|
|
59981
59986
|
added += 1;
|
|
59987
|
+
this.deps.onLog(` \u21B3 ${issue.identifier} queued (conflict-fix)`, "gray");
|
|
59982
59988
|
}
|
|
59983
59989
|
for (const issue of todo) {
|
|
59984
59990
|
if (atTicketLimit())
|
|
@@ -59990,6 +59996,7 @@ class AgentCoordinator {
|
|
|
59990
59996
|
this.queue.push({ issue, mode: "fresh" });
|
|
59991
59997
|
queuedIds.add(issue.id);
|
|
59992
59998
|
added += 1;
|
|
59999
|
+
this.deps.onLog(` \u21B3 ${issue.identifier} queued (fresh)`, "gray");
|
|
59993
60000
|
}
|
|
59994
60001
|
if (added > 0) {
|
|
59995
60002
|
const modeRank = {
|
|
@@ -60048,6 +60055,7 @@ class AgentCoordinator {
|
|
|
60048
60055
|
try {
|
|
60049
60056
|
await this.deps.postComment(w.issue, `\uD83D\uDD04 Ralph progress update: iteration ${count} on \`${w.changeName}\``);
|
|
60050
60057
|
w.lastReportedIteration = count;
|
|
60058
|
+
this.deps.onLog(` ${w.issueIdentifier}: posted progress comment (iteration ${count})`, "gray");
|
|
60051
60059
|
} catch (err) {
|
|
60052
60060
|
this.deps.onLog(`! Linear progress comment failed for ${w.issueIdentifier}: ${err.message}`, "red");
|
|
60053
60061
|
}
|
|
@@ -60087,6 +60095,7 @@ class AgentCoordinator {
|
|
|
60087
60095
|
capture("agent_conflict_detected", { issue_identifier: issue.identifier });
|
|
60088
60096
|
try {
|
|
60089
60097
|
await this.deps.applyIndicator(issue, this.opts.setConflicted);
|
|
60098
|
+
this.deps.onLog(` ${issue.identifier}: setConflicted applied`, "gray");
|
|
60090
60099
|
} catch (err) {
|
|
60091
60100
|
this.deps.onLog(`! Linear setConflicted failed for ${issue.identifier}: ${err.message}`, "red");
|
|
60092
60101
|
capture("agent_indicator_failed", {
|
|
@@ -60100,6 +60109,7 @@ class AgentCoordinator {
|
|
|
60100
60109
|
if (this.opts.postComments !== false) {
|
|
60101
60110
|
try {
|
|
60102
60111
|
await this.deps.postComment(issue, `\u26A0 Ralph detected merge conflicts on this PR (${pr.url}) \u2014 re-running to resolve`);
|
|
60112
|
+
this.deps.onLog(` ${issue.identifier}: posted conflict comment`, "gray");
|
|
60103
60113
|
} catch (err) {
|
|
60104
60114
|
this.deps.onLog(`! Linear conflict comment failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
60105
60115
|
}
|
|
@@ -60139,6 +60149,7 @@ class AgentCoordinator {
|
|
|
60139
60149
|
if (mode !== "resume" && this.opts.setInProgress) {
|
|
60140
60150
|
try {
|
|
60141
60151
|
await this.deps.applyIndicator(issue, this.opts.setInProgress);
|
|
60152
|
+
this.deps.onLog(` ${issue.identifier}: setInProgress applied`, "gray");
|
|
60142
60153
|
} catch (err) {
|
|
60143
60154
|
this.deps.onLog(`! Linear setInProgress failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
60144
60155
|
capture("agent_indicator_failed", {
|
|
@@ -60159,6 +60170,7 @@ class AgentCoordinator {
|
|
|
60159
60170
|
if (!alreadyPosted) {
|
|
60160
60171
|
try {
|
|
60161
60172
|
await this.deps.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${prep.changeName}\``);
|
|
60173
|
+
this.deps.onLog(` ${issue.identifier}: posted "started" comment`, "gray");
|
|
60162
60174
|
} catch (err) {
|
|
60163
60175
|
this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
|
|
60164
60176
|
}
|
|
@@ -60212,6 +60224,7 @@ class AgentCoordinator {
|
|
|
60212
60224
|
` + `This issue has been quarantined and will not be auto-resumed on the next poll. ` + `Inspect the worktree at \`~/.ralph/<project>/worktrees/${changeName}\`, fix the ` + `underlying failure, then remove the error marker on this Linear issue (or run ` + `\`ralph clean --name ${changeName}\`) to clear the quarantine.`;
|
|
60213
60225
|
try {
|
|
60214
60226
|
await this.deps.postComment(issue, body);
|
|
60227
|
+
this.deps.onLog(` ${issue.identifier}: posted completion comment`, "gray");
|
|
60215
60228
|
} catch (err) {
|
|
60216
60229
|
this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
|
|
60217
60230
|
}
|
|
@@ -60221,6 +60234,7 @@ class AgentCoordinator {
|
|
|
60221
60234
|
if (this.opts.clearConflicted) {
|
|
60222
60235
|
try {
|
|
60223
60236
|
await this.deps.removeIndicator(issue, this.opts.clearConflicted);
|
|
60237
|
+
this.deps.onLog(` ${issue.identifier}: clearConflicted applied`, "gray");
|
|
60224
60238
|
} catch (err) {
|
|
60225
60239
|
this.deps.onLog(`! Linear clearConflicted failed for ${issue.identifier}: ${err.message}`, "red");
|
|
60226
60240
|
capture("agent_indicator_failed", {
|
|
@@ -60234,6 +60248,7 @@ class AgentCoordinator {
|
|
|
60234
60248
|
} else if (this.opts.setDone) {
|
|
60235
60249
|
try {
|
|
60236
60250
|
await this.deps.applyIndicator(issue, this.opts.setDone);
|
|
60251
|
+
this.deps.onLog(` ${issue.identifier}: setDone applied`, "gray");
|
|
60237
60252
|
} catch (err) {
|
|
60238
60253
|
this.deps.onLog(`! Linear setDone failed for ${issue.identifier}: ${err.message}`, "red");
|
|
60239
60254
|
capture("agent_indicator_failed", {
|
|
@@ -60245,12 +60260,14 @@ class AgentCoordinator {
|
|
|
60245
60260
|
if (this.opts.setInProgress) {
|
|
60246
60261
|
try {
|
|
60247
60262
|
await this.deps.removeIndicator(issue, this.opts.setInProgress);
|
|
60263
|
+
this.deps.onLog(` ${issue.identifier}: clearInProgress applied`, "gray");
|
|
60248
60264
|
} catch {}
|
|
60249
60265
|
}
|
|
60250
60266
|
}
|
|
60251
60267
|
} else if (this.opts.setError) {
|
|
60252
60268
|
try {
|
|
60253
60269
|
await this.deps.applyIndicator(issue, this.opts.setError);
|
|
60270
|
+
this.deps.onLog(` ${issue.identifier}: setError applied`, "gray");
|
|
60254
60271
|
} catch (err) {
|
|
60255
60272
|
this.deps.onLog(`! Linear setError failed for ${issue.identifier}: ${err.message}`, "red");
|
|
60256
60273
|
capture("agent_indicator_failed", {
|
|
@@ -60262,6 +60279,7 @@ class AgentCoordinator {
|
|
|
60262
60279
|
if (this.opts.setInProgress) {
|
|
60263
60280
|
try {
|
|
60264
60281
|
await this.deps.removeIndicator(issue, this.opts.setInProgress);
|
|
60282
|
+
this.deps.onLog(` ${issue.identifier}: clearInProgress applied`, "gray");
|
|
60265
60283
|
} catch {}
|
|
60266
60284
|
}
|
|
60267
60285
|
}
|
|
@@ -60778,6 +60796,8 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
|
60778
60796
|
} catch (err) {
|
|
60779
60797
|
ctx.log(`! conflict check failed: ${err.message}`, "yellow");
|
|
60780
60798
|
}
|
|
60799
|
+
if (!conflicting && ciConfirmedGreen)
|
|
60800
|
+
return 0;
|
|
60781
60801
|
if (conflicting) {
|
|
60782
60802
|
outerAttempt++;
|
|
60783
60803
|
ciConfirmedGreen = false;
|
|
@@ -60838,6 +60858,88 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
|
60838
60858
|
}
|
|
60839
60859
|
return 0;
|
|
60840
60860
|
}
|
|
60861
|
+
async function runPrPhase(input, deps) {
|
|
60862
|
+
const { changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue, wantFixCi, cfg } = input;
|
|
60863
|
+
const { cmd, log: log2, emit, respawnWorker, registerPr, checkPrConflict } = deps;
|
|
60864
|
+
if (!branch || !issue) {
|
|
60865
|
+
log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
60866
|
+
return PR_FAILED_EXIT;
|
|
60867
|
+
}
|
|
60868
|
+
const ctx = {
|
|
60869
|
+
changeName,
|
|
60870
|
+
cwd: cwd2,
|
|
60871
|
+
branch,
|
|
60872
|
+
changeDir,
|
|
60873
|
+
stateFilePath,
|
|
60874
|
+
cfg,
|
|
60875
|
+
cmd,
|
|
60876
|
+
log: log2,
|
|
60877
|
+
emit,
|
|
60878
|
+
respawnWorker
|
|
60879
|
+
};
|
|
60880
|
+
try {
|
|
60881
|
+
const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
|
|
60882
|
+
if (status.stdout.trim()) {
|
|
60883
|
+
log2(`! ${changeName} has uncommitted changes after worker exit \u2014 the agent should commit everything before finishing. These changes will not be included in the PR.`, "yellow");
|
|
60884
|
+
}
|
|
60885
|
+
} catch (err) {
|
|
60886
|
+
log2(`! git status check failed for ${changeName}: ${err.message}`, "yellow");
|
|
60887
|
+
}
|
|
60888
|
+
const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue);
|
|
60889
|
+
if (prGaveUp)
|
|
60890
|
+
return PR_FAILED_EXIT;
|
|
60891
|
+
if (!pr) {
|
|
60892
|
+
log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
|
|
60893
|
+
return 0;
|
|
60894
|
+
}
|
|
60895
|
+
log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
|
|
60896
|
+
registerPr?.(changeName, pr.url);
|
|
60897
|
+
return fixConflictsAndCiLoop(ctx, pr.url, wantFixCi, checkPrConflict);
|
|
60898
|
+
}
|
|
60899
|
+
async function runWorktreeCleanupPhase(input, deps) {
|
|
60900
|
+
const { changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg } = input;
|
|
60901
|
+
const { git, log: log2, emit } = deps;
|
|
60902
|
+
if (!useWorktree || cwd2 === projectRoot)
|
|
60903
|
+
return;
|
|
60904
|
+
emit("cleanup", "checking worktree safety");
|
|
60905
|
+
if (effectiveCode !== 0 || !cfg.cleanupWorktreeOnSuccess)
|
|
60906
|
+
return;
|
|
60907
|
+
const check = await isWorktreeSafeToRemove(cwd2, cfg.prBaseBranch, git).catch((err) => ({
|
|
60908
|
+
safe: false,
|
|
60909
|
+
reason: `safety check failed: ${err.message}`,
|
|
60910
|
+
dirty: "",
|
|
60911
|
+
unpushedCommits: ""
|
|
60912
|
+
}));
|
|
60913
|
+
if (!check.safe) {
|
|
60914
|
+
log2(`! preserving worktree for ${changeName}: ${check.reason}`, "yellow");
|
|
60915
|
+
if (check.dirty)
|
|
60916
|
+
log2(` uncommitted:
|
|
60917
|
+
${check.dirty}`, "yellow");
|
|
60918
|
+
if (check.unpushedCommits)
|
|
60919
|
+
log2(` commits:
|
|
60920
|
+
${check.unpushedCommits}`, "yellow");
|
|
60921
|
+
log2(` path: ${cwd2}`, "yellow");
|
|
60922
|
+
return;
|
|
60923
|
+
}
|
|
60924
|
+
try {
|
|
60925
|
+
await removeWorktree(projectRoot, cwd2, git);
|
|
60926
|
+
log2(` removed worktree ${cwd2}`, "gray");
|
|
60927
|
+
} catch (err) {
|
|
60928
|
+
log2(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
|
|
60929
|
+
}
|
|
60930
|
+
}
|
|
60931
|
+
async function runTeardownPhase(input, deps) {
|
|
60932
|
+
const { cwd: cwd2, teardownScript } = input;
|
|
60933
|
+
const { runScript, log: log2, emit } = deps;
|
|
60934
|
+
if (!teardownScript)
|
|
60935
|
+
return;
|
|
60936
|
+
emit("teardown", teardownScript);
|
|
60937
|
+
try {
|
|
60938
|
+
await runScript("teardown", teardownScript, cwd2);
|
|
60939
|
+
} catch (err) {
|
|
60940
|
+
log2(`! teardown script threw: ${err.message}`, "yellow");
|
|
60941
|
+
}
|
|
60942
|
+
}
|
|
60841
60943
|
async function runPostTask(input, deps) {
|
|
60842
60944
|
const { log: log2, cmd, git, runScript } = deps;
|
|
60843
60945
|
const emit = (phase, detail) => deps.onPhase?.(phase, detail);
|
|
@@ -60857,88 +60959,22 @@ async function runPostTask(input, deps) {
|
|
|
60857
60959
|
respawnWorker
|
|
60858
60960
|
} = input;
|
|
60859
60961
|
let effectiveCode = exitCode;
|
|
60860
|
-
|
|
60861
|
-
|
|
60862
|
-
if (!branch || !issue) {
|
|
60863
|
-
log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
60864
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
60865
|
-
} else {
|
|
60866
|
-
const ctx = {
|
|
60867
|
-
changeName,
|
|
60868
|
-
cwd: cwd2,
|
|
60869
|
-
branch,
|
|
60870
|
-
changeDir,
|
|
60871
|
-
stateFilePath,
|
|
60872
|
-
cfg,
|
|
60873
|
-
cmd,
|
|
60874
|
-
log: log2,
|
|
60875
|
-
emit,
|
|
60876
|
-
respawnWorker
|
|
60877
|
-
};
|
|
60878
|
-
try {
|
|
60879
|
-
const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
|
|
60880
|
-
if (status.stdout.trim()) {
|
|
60881
|
-
log2(`! ${changeName} has uncommitted changes after worker exit \u2014 the agent should commit everything before finishing. These changes will not be included in the PR.`, "yellow");
|
|
60882
|
-
}
|
|
60883
|
-
} catch (err) {
|
|
60884
|
-
log2(`! git status check failed for ${changeName}: ${err.message}`, "yellow");
|
|
60885
|
-
}
|
|
60886
|
-
{
|
|
60887
|
-
const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue);
|
|
60888
|
-
if (prGaveUp) {
|
|
60889
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
60890
|
-
} else if (!pr) {
|
|
60891
|
-
log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
|
|
60892
|
-
} else {
|
|
60893
|
-
log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
|
|
60894
|
-
deps.registerPr?.(changeName, pr.url);
|
|
60895
|
-
const loopCode = await fixConflictsAndCiLoop(ctx, pr.url, wantFixCi, deps.checkPrConflict);
|
|
60896
|
-
if (loopCode !== 0)
|
|
60897
|
-
effectiveCode = loopCode;
|
|
60898
|
-
}
|
|
60899
|
-
}
|
|
60900
|
-
}
|
|
60901
|
-
}
|
|
60902
|
-
if (effectiveCode === 0)
|
|
60903
|
-
emit("done");
|
|
60904
|
-
else
|
|
60905
|
-
emit("gave-up", `exit ${effectiveCode}`);
|
|
60906
|
-
if (effectiveCode === 0 && cfg.teardownScript) {
|
|
60907
|
-
emit("teardown", cfg.teardownScript);
|
|
60908
|
-
try {
|
|
60909
|
-
await runScript("teardown", cfg.teardownScript, cwd2);
|
|
60910
|
-
} catch (err) {
|
|
60911
|
-
log2(`! teardown script threw: ${err.message}`, "yellow");
|
|
60912
|
-
}
|
|
60962
|
+
if (effectiveCode !== 0 && wantPr) {
|
|
60963
|
+
log2(` skipping PR phase for ${changeName} (worker exited with code ${effectiveCode})`, "gray");
|
|
60913
60964
|
}
|
|
60914
|
-
if (
|
|
60915
|
-
|
|
60916
|
-
|
|
60917
|
-
|
|
60918
|
-
|
|
60919
|
-
|
|
60920
|
-
|
|
60921
|
-
|
|
60922
|
-
|
|
60923
|
-
if (!check.safe) {
|
|
60924
|
-
log2(`! preserving worktree for ${changeName}: ${check.reason}`, "yellow");
|
|
60925
|
-
if (check.dirty)
|
|
60926
|
-
log2(` uncommitted:
|
|
60927
|
-
${check.dirty}`, "yellow");
|
|
60928
|
-
if (check.unpushedCommits)
|
|
60929
|
-
log2(` commits:
|
|
60930
|
-
${check.unpushedCommits}`, "yellow");
|
|
60931
|
-
log2(` path: ${cwd2}`, "yellow");
|
|
60932
|
-
} else {
|
|
60933
|
-
try {
|
|
60934
|
-
await removeWorktree(projectRoot, cwd2, git);
|
|
60935
|
-
log2(` removed worktree ${cwd2}`, "gray");
|
|
60936
|
-
} catch (err) {
|
|
60937
|
-
log2(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
|
|
60938
|
-
}
|
|
60939
|
-
}
|
|
60940
|
-
}
|
|
60965
|
+
if (effectiveCode === 0 && wantPr) {
|
|
60966
|
+
effectiveCode = await runPrPhase({ changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue, wantFixCi, cfg }, {
|
|
60967
|
+
cmd,
|
|
60968
|
+
log: log2,
|
|
60969
|
+
emit,
|
|
60970
|
+
respawnWorker,
|
|
60971
|
+
...deps.registerPr !== undefined ? { registerPr: deps.registerPr } : {},
|
|
60972
|
+
...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {}
|
|
60973
|
+
});
|
|
60941
60974
|
}
|
|
60975
|
+
emit(effectiveCode === 0 ? "done" : "gave-up", effectiveCode !== 0 ? `exit ${effectiveCode}` : undefined);
|
|
60976
|
+
await runWorktreeCleanupPhase({ changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg }, { git, log: log2, emit });
|
|
60977
|
+
await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log2, emit });
|
|
60942
60978
|
return effectiveCode;
|
|
60943
60979
|
}
|
|
60944
60980
|
var CI_FAILED_EXIT = 70, PR_FAILED_EXIT = 71;
|
|
@@ -61408,12 +61444,18 @@ PR: ${prUrl}` : ""
|
|
|
61408
61444
|
onPhase: (phase, detail) => onWorkerPhase(changeName, phase, detail)
|
|
61409
61445
|
},
|
|
61410
61446
|
checkPrConflict: async (prUrl) => {
|
|
61411
|
-
|
|
61412
|
-
|
|
61413
|
-
|
|
61414
|
-
|
|
61415
|
-
|
|
61447
|
+
for (let attempt2 = 0;attempt2 < 5; attempt2++) {
|
|
61448
|
+
try {
|
|
61449
|
+
const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "mergeable", "--jq", ".mergeable"], cwd2);
|
|
61450
|
+
const mergeable = res.stdout.trim();
|
|
61451
|
+
if (mergeable !== "UNKNOWN")
|
|
61452
|
+
return mergeable === "CONFLICTING";
|
|
61453
|
+
} catch {
|
|
61454
|
+
return false;
|
|
61455
|
+
}
|
|
61456
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
61416
61457
|
}
|
|
61458
|
+
return false;
|
|
61417
61459
|
}
|
|
61418
61460
|
});
|
|
61419
61461
|
cwdByChange.delete(changeName);
|
|
@@ -61584,7 +61626,7 @@ var exports_json_runner = {};
|
|
|
61584
61626
|
__export(exports_json_runner, {
|
|
61585
61627
|
runAgentJson: () => runAgentJson
|
|
61586
61628
|
});
|
|
61587
|
-
import { join as
|
|
61629
|
+
import { join as join21 } from "path";
|
|
61588
61630
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
61589
61631
|
import { homedir as homedir4 } from "os";
|
|
61590
61632
|
function cleanOutputLine2(raw) {
|
|
@@ -61609,7 +61651,7 @@ async function runAgentJson({
|
|
|
61609
61651
|
statesDir,
|
|
61610
61652
|
tasksDir
|
|
61611
61653
|
}) {
|
|
61612
|
-
await mkdir6(
|
|
61654
|
+
await mkdir6(join21(homedir4(), ".ralph"), { recursive: true }).catch(() => {
|
|
61613
61655
|
return;
|
|
61614
61656
|
});
|
|
61615
61657
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -61715,7 +61757,7 @@ var init_json_runner = __esm(() => {
|
|
|
61715
61757
|
});
|
|
61716
61758
|
|
|
61717
61759
|
// apps/cli/src/index.ts
|
|
61718
|
-
import { resolve as resolve2, join as
|
|
61760
|
+
import { resolve as resolve2, join as join22, dirname as dirname6 } from "path";
|
|
61719
61761
|
import { exists as exists2, mkdir as mkdir7, rm } from "fs/promises";
|
|
61720
61762
|
|
|
61721
61763
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -73684,6 +73726,7 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
73684
73726
|
}, undefined, false, undefined, this)
|
|
73685
73727
|
}, undefined, false, undefined, this);
|
|
73686
73728
|
case "clean":
|
|
73729
|
+
case "debug":
|
|
73687
73730
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
|
|
73688
73731
|
children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {}, undefined, false, undefined, this)
|
|
73689
73732
|
}, undefined, false, undefined, this);
|
|
@@ -73706,6 +73749,235 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
73706
73749
|
// apps/cli/src/index.ts
|
|
73707
73750
|
init_layout();
|
|
73708
73751
|
init_worktree();
|
|
73752
|
+
|
|
73753
|
+
// apps/cli/src/debug.ts
|
|
73754
|
+
init_log();
|
|
73755
|
+
import { join as join20 } from "path";
|
|
73756
|
+
var LOG_LINE_RE = /^\[(.+?)\] \[(.+?)\] (.+)$/;
|
|
73757
|
+
function parseLog(content) {
|
|
73758
|
+
return content.split(`
|
|
73759
|
+
`).filter(Boolean).flatMap((line) => {
|
|
73760
|
+
const m = LOG_LINE_RE.exec(line);
|
|
73761
|
+
if (!m)
|
|
73762
|
+
return [];
|
|
73763
|
+
const ts = new Date(m[1]);
|
|
73764
|
+
if (isNaN(ts.getTime()))
|
|
73765
|
+
return [];
|
|
73766
|
+
return [{ ts, type: m[2], text: m[3] }];
|
|
73767
|
+
});
|
|
73768
|
+
}
|
|
73769
|
+
function fmtTs(d) {
|
|
73770
|
+
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
73771
|
+
}
|
|
73772
|
+
var SPAWN_RE = /\u25B6 (\S+) \u2192 (\S+)/;
|
|
73773
|
+
async function resolveDebugTarget(opts) {
|
|
73774
|
+
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
73775
|
+
const agentLines = await agentLogFile.exists() ? parseLog(await agentLogFile.text()) : [];
|
|
73776
|
+
if (opts.name && !opts.issue) {
|
|
73777
|
+
for (const line of agentLines) {
|
|
73778
|
+
const m = SPAWN_RE.exec(line.text);
|
|
73779
|
+
if (m && m[2] === opts.name)
|
|
73780
|
+
return { changeName: opts.name, identifier: m[1] };
|
|
73781
|
+
}
|
|
73782
|
+
return { changeName: opts.name, identifier: undefined };
|
|
73783
|
+
}
|
|
73784
|
+
if (opts.issue && !opts.name) {
|
|
73785
|
+
for (const line of agentLines) {
|
|
73786
|
+
const m = SPAWN_RE.exec(line.text);
|
|
73787
|
+
if (m && m[1] === opts.issue)
|
|
73788
|
+
return { changeName: m[2], identifier: opts.issue };
|
|
73789
|
+
}
|
|
73790
|
+
return { changeName: opts.issue, identifier: opts.issue };
|
|
73791
|
+
}
|
|
73792
|
+
return { changeName: opts.name, identifier: opts.issue };
|
|
73793
|
+
}
|
|
73794
|
+
async function fetchLinearIssue(identifier) {
|
|
73795
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
73796
|
+
if (!apiKey)
|
|
73797
|
+
return null;
|
|
73798
|
+
const query = `
|
|
73799
|
+
query($identifier: String!) {
|
|
73800
|
+
issues(filter: { identifier: { eq: $identifier } }, first: 1) {
|
|
73801
|
+
nodes {
|
|
73802
|
+
identifier title url
|
|
73803
|
+
state { name type }
|
|
73804
|
+
labels { nodes { name } }
|
|
73805
|
+
}
|
|
73806
|
+
}
|
|
73807
|
+
}
|
|
73808
|
+
`;
|
|
73809
|
+
try {
|
|
73810
|
+
const res = await fetch("https://api.linear.app/graphql", {
|
|
73811
|
+
method: "POST",
|
|
73812
|
+
headers: { "Content-Type": "application/json", Authorization: apiKey },
|
|
73813
|
+
body: JSON.stringify({ query, variables: { identifier } })
|
|
73814
|
+
});
|
|
73815
|
+
const json = await res.json();
|
|
73816
|
+
return json.data?.issues?.nodes?.[0] ?? null;
|
|
73817
|
+
} catch {
|
|
73818
|
+
return null;
|
|
73819
|
+
}
|
|
73820
|
+
}
|
|
73821
|
+
function spawnGh(args) {
|
|
73822
|
+
const result2 = Bun.spawnSync(["gh", ...args], { stderr: "ignore" });
|
|
73823
|
+
if (result2.exitCode !== 0)
|
|
73824
|
+
return null;
|
|
73825
|
+
try {
|
|
73826
|
+
return JSON.parse(result2.stdout.toString());
|
|
73827
|
+
} catch {
|
|
73828
|
+
return null;
|
|
73829
|
+
}
|
|
73830
|
+
}
|
|
73831
|
+
async function fetchGithubPr(changeName) {
|
|
73832
|
+
const branch = `ralph/${changeName}`;
|
|
73833
|
+
const prs = spawnGh([
|
|
73834
|
+
"pr",
|
|
73835
|
+
"list",
|
|
73836
|
+
"--head",
|
|
73837
|
+
branch,
|
|
73838
|
+
"--state",
|
|
73839
|
+
"all",
|
|
73840
|
+
"--json",
|
|
73841
|
+
"number,title,url,state,mergeable"
|
|
73842
|
+
]);
|
|
73843
|
+
if (!prs?.length)
|
|
73844
|
+
return null;
|
|
73845
|
+
const pr = prs[0];
|
|
73846
|
+
const checks = spawnGh([
|
|
73847
|
+
"pr",
|
|
73848
|
+
"checks",
|
|
73849
|
+
String(pr.number),
|
|
73850
|
+
"--json",
|
|
73851
|
+
"name,state,conclusion"
|
|
73852
|
+
]) ?? [];
|
|
73853
|
+
return { ...pr, checks };
|
|
73854
|
+
}
|
|
73855
|
+
async function runDebug(opts) {
|
|
73856
|
+
const { projectRoot } = opts;
|
|
73857
|
+
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
73858
|
+
const agentLogContent = await agentLogFile.exists() ? await agentLogFile.text() : "";
|
|
73859
|
+
const agentLines = parseLog(agentLogContent);
|
|
73860
|
+
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget({
|
|
73861
|
+
...opts.name !== undefined ? { name: opts.name } : {},
|
|
73862
|
+
...opts.issue !== undefined ? { issue: opts.issue } : {}
|
|
73863
|
+
});
|
|
73864
|
+
if (!changeName) {
|
|
73865
|
+
process.stderr.write(`! Could not resolve a change name for ${opts.issue ?? opts.name}. Has this issue been started?
|
|
73866
|
+
`);
|
|
73867
|
+
process.exit(1);
|
|
73868
|
+
}
|
|
73869
|
+
const relevant = agentLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
73870
|
+
if (!issueIdentifier) {
|
|
73871
|
+
for (const line of relevant) {
|
|
73872
|
+
const m = SPAWN_RE.exec(line.text);
|
|
73873
|
+
if (m && m[2] === changeName) {
|
|
73874
|
+
issueIdentifier = m[1];
|
|
73875
|
+
break;
|
|
73876
|
+
}
|
|
73877
|
+
}
|
|
73878
|
+
}
|
|
73879
|
+
const workerLogPath = join20(projectRoot, ".ralph", "logs", `${changeName}.log`);
|
|
73880
|
+
const workerLogFile = Bun.file(workerLogPath);
|
|
73881
|
+
const workerLines = await workerLogFile.exists() ? parseLog(await workerLogFile.text()) : [];
|
|
73882
|
+
const merged = [...relevant, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
73883
|
+
const seen = new Set;
|
|
73884
|
+
const timeline = merged.filter((l) => {
|
|
73885
|
+
const key = `${l.ts.getTime()}:${l.type}:${l.text}`;
|
|
73886
|
+
if (seen.has(key))
|
|
73887
|
+
return false;
|
|
73888
|
+
seen.add(key);
|
|
73889
|
+
return true;
|
|
73890
|
+
});
|
|
73891
|
+
const out = (s) => process.stdout.write(s + `
|
|
73892
|
+
`);
|
|
73893
|
+
out(`
|
|
73894
|
+
=== Ralph Debug: ${changeName}${issueIdentifier ? ` (${issueIdentifier})` : ""} ===
|
|
73895
|
+
`);
|
|
73896
|
+
out("\u2500\u2500 Timeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
73897
|
+
if (!timeline.length) {
|
|
73898
|
+
out(" (no log entries found)");
|
|
73899
|
+
} else {
|
|
73900
|
+
for (const line of timeline) {
|
|
73901
|
+
const prefix = line.type === "output" ? " \u2502" : " \xB7";
|
|
73902
|
+
out(`${prefix} ${fmtTs(line.ts)} [${line.type.padEnd(7)}] ${line.text}`);
|
|
73903
|
+
}
|
|
73904
|
+
}
|
|
73905
|
+
out("");
|
|
73906
|
+
out("\u2500\u2500 Current Linear state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
73907
|
+
if (!issueIdentifier) {
|
|
73908
|
+
out(" (unknown identifier \u2014 pass --issue to query Linear directly)");
|
|
73909
|
+
} else if (!process.env.LINEAR_API_KEY) {
|
|
73910
|
+
out(" (set LINEAR_API_KEY to fetch current Linear state)");
|
|
73911
|
+
} else {
|
|
73912
|
+
const issue = await fetchLinearIssue(issueIdentifier);
|
|
73913
|
+
if (!issue) {
|
|
73914
|
+
out(` ! Could not fetch ${issueIdentifier} from Linear`);
|
|
73915
|
+
} else {
|
|
73916
|
+
const labels = issue.labels.nodes.map((l) => l.name).join(", ") || "(none)";
|
|
73917
|
+
out(` Title : ${issue.title}`);
|
|
73918
|
+
out(` Status : ${issue.state.name} (${issue.state.type})`);
|
|
73919
|
+
out(` Labels : ${labels}`);
|
|
73920
|
+
out(` URL : ${issue.url}`);
|
|
73921
|
+
}
|
|
73922
|
+
}
|
|
73923
|
+
out("");
|
|
73924
|
+
out("\u2500\u2500 Current GitHub PR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
73925
|
+
const pr = await fetchGithubPr(changeName);
|
|
73926
|
+
if (!pr) {
|
|
73927
|
+
out(` (no PR found for branch ralph/${changeName})`);
|
|
73928
|
+
} else {
|
|
73929
|
+
const failing = pr.checks.filter((c) => c.conclusion === "FAILURE" || c.conclusion === "failure");
|
|
73930
|
+
const pending = pr.checks.filter((c) => c.state === "PENDING" || c.state === "IN_PROGRESS");
|
|
73931
|
+
out(` PR #${pr.number} : ${pr.url}`);
|
|
73932
|
+
out(` State : ${pr.state}`);
|
|
73933
|
+
out(` Mergeable : ${pr.mergeable}`);
|
|
73934
|
+
if (pr.checks.length) {
|
|
73935
|
+
out(` Checks : ${pr.checks.length} total, ${failing.length} failing, ${pending.length} pending`);
|
|
73936
|
+
for (const c of failing)
|
|
73937
|
+
out(` \u2717 ${c.name}`);
|
|
73938
|
+
for (const c of pending)
|
|
73939
|
+
out(` \u29D7 ${c.name}`);
|
|
73940
|
+
} else {
|
|
73941
|
+
out(" Checks : (none or not yet available)");
|
|
73942
|
+
}
|
|
73943
|
+
}
|
|
73944
|
+
out("");
|
|
73945
|
+
out("\u2500\u2500 Diagnosis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
73946
|
+
const lastEvent = timeline.at(-1);
|
|
73947
|
+
if (lastEvent)
|
|
73948
|
+
out(` Last event : ${fmtTs(lastEvent.ts)} ${lastEvent.text}`);
|
|
73949
|
+
const exitLine = relevant.find((l) => /exited \(code \d+\)/.test(l.text));
|
|
73950
|
+
if (exitLine) {
|
|
73951
|
+
const code = Number(/code (\d+)/.exec(exitLine.text)?.[1]);
|
|
73952
|
+
const meaning = code === 0 ? "success" : code === 70 ? "CI fix loop exhausted its attempt budget" : code === 71 ? "push or PR creation failed (pre-push hook or remote rejection)" : "worker subprocess failed";
|
|
73953
|
+
out(` Exit code : ${code} \u2014 ${meaning}`);
|
|
73954
|
+
}
|
|
73955
|
+
const logHas = (s) => relevant.some((l) => l.text.includes(s));
|
|
73956
|
+
if (logHas("setError applied"))
|
|
73957
|
+
out(" \u26A0 setError applied \u2014 issue is quarantined in Linear");
|
|
73958
|
+
if (logHas("setDone applied"))
|
|
73959
|
+
out(" \u2713 setDone applied \u2014 issue marked done in Linear");
|
|
73960
|
+
if (logHas("clearConflicted applied"))
|
|
73961
|
+
out(" \u2713 clearConflicted applied \u2014 conflicts resolved");
|
|
73962
|
+
if (logHas("setConflicted applied"))
|
|
73963
|
+
out(" \u26A0 setConflicted applied \u2014 merge conflicts detected");
|
|
73964
|
+
if (logHas("skipping PR phase"))
|
|
73965
|
+
out(" \u21A9 PR phase skipped \u2014 worker exited non-zero");
|
|
73966
|
+
if (pr?.mergeable === "CONFLICTING")
|
|
73967
|
+
out(" \u26A0 PR currently has merge conflicts");
|
|
73968
|
+
if (pr?.checks.some((c) => c.conclusion === "FAILURE" || c.conclusion === "failure")) {
|
|
73969
|
+
out(" \u26A0 PR has failing CI checks");
|
|
73970
|
+
}
|
|
73971
|
+
const worktreePath = join20(projectRoot, ".ralph", "worktrees", changeName);
|
|
73972
|
+
const worktreeExists = await Bun.file(join20(worktreePath, ".git")).exists();
|
|
73973
|
+
if (worktreeExists)
|
|
73974
|
+
out(` Worktree : ${worktreePath}`);
|
|
73975
|
+
if (!timeline.length)
|
|
73976
|
+
out(" (no log entries \u2014 has this change been started yet?)");
|
|
73977
|
+
out("");
|
|
73978
|
+
}
|
|
73979
|
+
|
|
73980
|
+
// apps/cli/src/index.ts
|
|
73709
73981
|
init_src();
|
|
73710
73982
|
if (typeof globalThis.Bun === "undefined") {
|
|
73711
73983
|
process.stderr.write(`ralph requires the Bun runtime (https://bun.sh/). It is not compatible with plain Node.js.
|
|
@@ -73715,7 +73987,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
73715
73987
|
async function findProjectRoot() {
|
|
73716
73988
|
let dir = process.cwd();
|
|
73717
73989
|
while (dir !== "/") {
|
|
73718
|
-
if (await exists2(
|
|
73990
|
+
if (await exists2(join22(dir, "openspec")))
|
|
73719
73991
|
return dir;
|
|
73720
73992
|
dir = resolve2(dir, "..");
|
|
73721
73993
|
}
|
|
@@ -73756,22 +74028,32 @@ try {
|
|
|
73756
74028
|
const tasksDir = layout.tasksDir;
|
|
73757
74029
|
if (args.mode === "init") {
|
|
73758
74030
|
await mkdir7(statesDir, { recursive: true });
|
|
73759
|
-
const openspecBin =
|
|
74031
|
+
const openspecBin = join22(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
73760
74032
|
Bun.spawnSync({
|
|
73761
74033
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
73762
74034
|
stdio: ["inherit", "inherit", "inherit"],
|
|
73763
74035
|
cwd: process.cwd()
|
|
73764
74036
|
});
|
|
73765
74037
|
}
|
|
74038
|
+
if (args.mode === "debug") {
|
|
74039
|
+
if (!args.name) {
|
|
74040
|
+
process.stderr.write(`Error: --name is required for debug mode
|
|
74041
|
+
`);
|
|
74042
|
+
process.exit(1);
|
|
74043
|
+
}
|
|
74044
|
+
await runDebug({ name: args.name, projectRoot });
|
|
74045
|
+
await shutdown();
|
|
74046
|
+
process.exit(0);
|
|
74047
|
+
}
|
|
73766
74048
|
if (args.mode === "clean") {
|
|
73767
74049
|
if (!args.name) {
|
|
73768
74050
|
process.stderr.write(`Error: --name is required for clean mode
|
|
73769
74051
|
`);
|
|
73770
74052
|
process.exit(1);
|
|
73771
74053
|
}
|
|
73772
|
-
const worktreeDir =
|
|
73773
|
-
const changeDir =
|
|
73774
|
-
const stateDir =
|
|
74054
|
+
const worktreeDir = join22(worktreesDir(projectRoot), args.name);
|
|
74055
|
+
const changeDir = join22(tasksDir, args.name);
|
|
74056
|
+
const stateDir = join22(statesDir, args.name);
|
|
73775
74057
|
const branch = `ralph/${args.name}`;
|
|
73776
74058
|
const removed = [];
|
|
73777
74059
|
if (await exists2(worktreeDir)) {
|
|
@@ -73823,13 +74105,13 @@ try {
|
|
|
73823
74105
|
process.exit(0);
|
|
73824
74106
|
}
|
|
73825
74107
|
if (args.mode === "task" && args.name) {
|
|
73826
|
-
await mkdir7(
|
|
73827
|
-
await mkdir7(
|
|
74108
|
+
await mkdir7(join22(statesDir, args.name), { recursive: true });
|
|
74109
|
+
await mkdir7(join22(tasksDir, args.name), { recursive: true });
|
|
73828
74110
|
}
|
|
73829
74111
|
if (args.mode === "agent") {
|
|
73830
74112
|
await mkdir7(statesDir, { recursive: true });
|
|
73831
74113
|
await mkdir7(tasksDir, { recursive: true });
|
|
73832
|
-
await mkdir7(
|
|
74114
|
+
await mkdir7(join22(projectRoot, ".ralph"), { recursive: true });
|
|
73833
74115
|
}
|
|
73834
74116
|
if (args.mode === "agent" && args.jsonOutput) {
|
|
73835
74117
|
const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
|