@neriros/ralphy 2.16.7 → 2.17.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 +60 -1
- package/dist/cli/index.js +368 -94
- 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.0")
|
|
35033
|
+
return "2.17.0";
|
|
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
|
}
|
|
@@ -60838,6 +60856,88 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
|
60838
60856
|
}
|
|
60839
60857
|
return 0;
|
|
60840
60858
|
}
|
|
60859
|
+
async function runPrPhase(input, deps) {
|
|
60860
|
+
const { changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue, wantFixCi, cfg } = input;
|
|
60861
|
+
const { cmd, log: log2, emit, respawnWorker, registerPr, checkPrConflict } = deps;
|
|
60862
|
+
if (!branch || !issue) {
|
|
60863
|
+
log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
60864
|
+
return PR_FAILED_EXIT;
|
|
60865
|
+
}
|
|
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
|
+
const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue);
|
|
60887
|
+
if (prGaveUp)
|
|
60888
|
+
return PR_FAILED_EXIT;
|
|
60889
|
+
if (!pr) {
|
|
60890
|
+
log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
|
|
60891
|
+
return 0;
|
|
60892
|
+
}
|
|
60893
|
+
log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
|
|
60894
|
+
registerPr?.(changeName, pr.url);
|
|
60895
|
+
return fixConflictsAndCiLoop(ctx, pr.url, wantFixCi, checkPrConflict);
|
|
60896
|
+
}
|
|
60897
|
+
async function runWorktreeCleanupPhase(input, deps) {
|
|
60898
|
+
const { changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg } = input;
|
|
60899
|
+
const { git, log: log2, emit } = deps;
|
|
60900
|
+
if (!useWorktree || cwd2 === projectRoot)
|
|
60901
|
+
return;
|
|
60902
|
+
emit("cleanup", "checking worktree safety");
|
|
60903
|
+
if (effectiveCode !== 0 || !cfg.cleanupWorktreeOnSuccess)
|
|
60904
|
+
return;
|
|
60905
|
+
const check = await isWorktreeSafeToRemove(cwd2, cfg.prBaseBranch, git).catch((err) => ({
|
|
60906
|
+
safe: false,
|
|
60907
|
+
reason: `safety check failed: ${err.message}`,
|
|
60908
|
+
dirty: "",
|
|
60909
|
+
unpushedCommits: ""
|
|
60910
|
+
}));
|
|
60911
|
+
if (!check.safe) {
|
|
60912
|
+
log2(`! preserving worktree for ${changeName}: ${check.reason}`, "yellow");
|
|
60913
|
+
if (check.dirty)
|
|
60914
|
+
log2(` uncommitted:
|
|
60915
|
+
${check.dirty}`, "yellow");
|
|
60916
|
+
if (check.unpushedCommits)
|
|
60917
|
+
log2(` commits:
|
|
60918
|
+
${check.unpushedCommits}`, "yellow");
|
|
60919
|
+
log2(` path: ${cwd2}`, "yellow");
|
|
60920
|
+
return;
|
|
60921
|
+
}
|
|
60922
|
+
try {
|
|
60923
|
+
await removeWorktree(projectRoot, cwd2, git);
|
|
60924
|
+
log2(` removed worktree ${cwd2}`, "gray");
|
|
60925
|
+
} catch (err) {
|
|
60926
|
+
log2(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
|
|
60927
|
+
}
|
|
60928
|
+
}
|
|
60929
|
+
async function runTeardownPhase(input, deps) {
|
|
60930
|
+
const { cwd: cwd2, teardownScript } = input;
|
|
60931
|
+
const { runScript, log: log2, emit } = deps;
|
|
60932
|
+
if (!teardownScript)
|
|
60933
|
+
return;
|
|
60934
|
+
emit("teardown", teardownScript);
|
|
60935
|
+
try {
|
|
60936
|
+
await runScript("teardown", teardownScript, cwd2);
|
|
60937
|
+
} catch (err) {
|
|
60938
|
+
log2(`! teardown script threw: ${err.message}`, "yellow");
|
|
60939
|
+
}
|
|
60940
|
+
}
|
|
60841
60941
|
async function runPostTask(input, deps) {
|
|
60842
60942
|
const { log: log2, cmd, git, runScript } = deps;
|
|
60843
60943
|
const emit = (phase, detail) => deps.onPhase?.(phase, detail);
|
|
@@ -60857,88 +60957,22 @@ async function runPostTask(input, deps) {
|
|
|
60857
60957
|
respawnWorker
|
|
60858
60958
|
} = input;
|
|
60859
60959
|
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
|
-
}
|
|
60960
|
+
if (effectiveCode !== 0 && wantPr) {
|
|
60961
|
+
log2(` skipping PR phase for ${changeName} (worker exited with code ${effectiveCode})`, "gray");
|
|
60913
60962
|
}
|
|
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
|
-
}
|
|
60963
|
+
if (effectiveCode === 0 && wantPr) {
|
|
60964
|
+
effectiveCode = await runPrPhase({ changeName, cwd: cwd2, branch, changeDir, stateFilePath, issue, wantFixCi, cfg }, {
|
|
60965
|
+
cmd,
|
|
60966
|
+
log: log2,
|
|
60967
|
+
emit,
|
|
60968
|
+
respawnWorker,
|
|
60969
|
+
...deps.registerPr !== undefined ? { registerPr: deps.registerPr } : {},
|
|
60970
|
+
...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {}
|
|
60971
|
+
});
|
|
60941
60972
|
}
|
|
60973
|
+
emit(effectiveCode === 0 ? "done" : "gave-up", effectiveCode !== 0 ? `exit ${effectiveCode}` : undefined);
|
|
60974
|
+
await runWorktreeCleanupPhase({ changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg }, { git, log: log2, emit });
|
|
60975
|
+
await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log2, emit });
|
|
60942
60976
|
return effectiveCode;
|
|
60943
60977
|
}
|
|
60944
60978
|
var CI_FAILED_EXIT = 70, PR_FAILED_EXIT = 71;
|
|
@@ -61584,7 +61618,7 @@ var exports_json_runner = {};
|
|
|
61584
61618
|
__export(exports_json_runner, {
|
|
61585
61619
|
runAgentJson: () => runAgentJson
|
|
61586
61620
|
});
|
|
61587
|
-
import { join as
|
|
61621
|
+
import { join as join21 } from "path";
|
|
61588
61622
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
61589
61623
|
import { homedir as homedir4 } from "os";
|
|
61590
61624
|
function cleanOutputLine2(raw) {
|
|
@@ -61609,7 +61643,7 @@ async function runAgentJson({
|
|
|
61609
61643
|
statesDir,
|
|
61610
61644
|
tasksDir
|
|
61611
61645
|
}) {
|
|
61612
|
-
await mkdir6(
|
|
61646
|
+
await mkdir6(join21(homedir4(), ".ralph"), { recursive: true }).catch(() => {
|
|
61613
61647
|
return;
|
|
61614
61648
|
});
|
|
61615
61649
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -61715,7 +61749,7 @@ var init_json_runner = __esm(() => {
|
|
|
61715
61749
|
});
|
|
61716
61750
|
|
|
61717
61751
|
// apps/cli/src/index.ts
|
|
61718
|
-
import { resolve as resolve2, join as
|
|
61752
|
+
import { resolve as resolve2, join as join22, dirname as dirname6 } from "path";
|
|
61719
61753
|
import { exists as exists2, mkdir as mkdir7, rm } from "fs/promises";
|
|
61720
61754
|
|
|
61721
61755
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -73684,6 +73718,7 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
73684
73718
|
}, undefined, false, undefined, this)
|
|
73685
73719
|
}, undefined, false, undefined, this);
|
|
73686
73720
|
case "clean":
|
|
73721
|
+
case "debug":
|
|
73687
73722
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
|
|
73688
73723
|
children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {}, undefined, false, undefined, this)
|
|
73689
73724
|
}, undefined, false, undefined, this);
|
|
@@ -73706,6 +73741,235 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
73706
73741
|
// apps/cli/src/index.ts
|
|
73707
73742
|
init_layout();
|
|
73708
73743
|
init_worktree();
|
|
73744
|
+
|
|
73745
|
+
// apps/cli/src/debug.ts
|
|
73746
|
+
init_log();
|
|
73747
|
+
import { join as join20 } from "path";
|
|
73748
|
+
var LOG_LINE_RE = /^\[(.+?)\] \[(.+?)\] (.+)$/;
|
|
73749
|
+
function parseLog(content) {
|
|
73750
|
+
return content.split(`
|
|
73751
|
+
`).filter(Boolean).flatMap((line) => {
|
|
73752
|
+
const m = LOG_LINE_RE.exec(line);
|
|
73753
|
+
if (!m)
|
|
73754
|
+
return [];
|
|
73755
|
+
const ts = new Date(m[1]);
|
|
73756
|
+
if (isNaN(ts.getTime()))
|
|
73757
|
+
return [];
|
|
73758
|
+
return [{ ts, type: m[2], text: m[3] }];
|
|
73759
|
+
});
|
|
73760
|
+
}
|
|
73761
|
+
function fmtTs(d) {
|
|
73762
|
+
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
73763
|
+
}
|
|
73764
|
+
var SPAWN_RE = /\u25B6 (\S+) \u2192 (\S+)/;
|
|
73765
|
+
async function resolveDebugTarget(opts) {
|
|
73766
|
+
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
73767
|
+
const agentLines = await agentLogFile.exists() ? parseLog(await agentLogFile.text()) : [];
|
|
73768
|
+
if (opts.name && !opts.issue) {
|
|
73769
|
+
for (const line of agentLines) {
|
|
73770
|
+
const m = SPAWN_RE.exec(line.text);
|
|
73771
|
+
if (m && m[2] === opts.name)
|
|
73772
|
+
return { changeName: opts.name, identifier: m[1] };
|
|
73773
|
+
}
|
|
73774
|
+
return { changeName: opts.name, identifier: undefined };
|
|
73775
|
+
}
|
|
73776
|
+
if (opts.issue && !opts.name) {
|
|
73777
|
+
for (const line of agentLines) {
|
|
73778
|
+
const m = SPAWN_RE.exec(line.text);
|
|
73779
|
+
if (m && m[1] === opts.issue)
|
|
73780
|
+
return { changeName: m[2], identifier: opts.issue };
|
|
73781
|
+
}
|
|
73782
|
+
return { changeName: opts.issue, identifier: opts.issue };
|
|
73783
|
+
}
|
|
73784
|
+
return { changeName: opts.name, identifier: opts.issue };
|
|
73785
|
+
}
|
|
73786
|
+
async function fetchLinearIssue(identifier) {
|
|
73787
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
73788
|
+
if (!apiKey)
|
|
73789
|
+
return null;
|
|
73790
|
+
const query = `
|
|
73791
|
+
query($identifier: String!) {
|
|
73792
|
+
issues(filter: { identifier: { eq: $identifier } }, first: 1) {
|
|
73793
|
+
nodes {
|
|
73794
|
+
identifier title url
|
|
73795
|
+
state { name type }
|
|
73796
|
+
labels { nodes { name } }
|
|
73797
|
+
}
|
|
73798
|
+
}
|
|
73799
|
+
}
|
|
73800
|
+
`;
|
|
73801
|
+
try {
|
|
73802
|
+
const res = await fetch("https://api.linear.app/graphql", {
|
|
73803
|
+
method: "POST",
|
|
73804
|
+
headers: { "Content-Type": "application/json", Authorization: apiKey },
|
|
73805
|
+
body: JSON.stringify({ query, variables: { identifier } })
|
|
73806
|
+
});
|
|
73807
|
+
const json = await res.json();
|
|
73808
|
+
return json.data?.issues?.nodes?.[0] ?? null;
|
|
73809
|
+
} catch {
|
|
73810
|
+
return null;
|
|
73811
|
+
}
|
|
73812
|
+
}
|
|
73813
|
+
function spawnGh(args) {
|
|
73814
|
+
const result2 = Bun.spawnSync(["gh", ...args], { stderr: "ignore" });
|
|
73815
|
+
if (result2.exitCode !== 0)
|
|
73816
|
+
return null;
|
|
73817
|
+
try {
|
|
73818
|
+
return JSON.parse(result2.stdout.toString());
|
|
73819
|
+
} catch {
|
|
73820
|
+
return null;
|
|
73821
|
+
}
|
|
73822
|
+
}
|
|
73823
|
+
async function fetchGithubPr(changeName) {
|
|
73824
|
+
const branch = `ralph/${changeName}`;
|
|
73825
|
+
const prs = spawnGh([
|
|
73826
|
+
"pr",
|
|
73827
|
+
"list",
|
|
73828
|
+
"--head",
|
|
73829
|
+
branch,
|
|
73830
|
+
"--state",
|
|
73831
|
+
"all",
|
|
73832
|
+
"--json",
|
|
73833
|
+
"number,title,url,state,mergeable"
|
|
73834
|
+
]);
|
|
73835
|
+
if (!prs?.length)
|
|
73836
|
+
return null;
|
|
73837
|
+
const pr = prs[0];
|
|
73838
|
+
const checks = spawnGh([
|
|
73839
|
+
"pr",
|
|
73840
|
+
"checks",
|
|
73841
|
+
String(pr.number),
|
|
73842
|
+
"--json",
|
|
73843
|
+
"name,state,conclusion"
|
|
73844
|
+
]) ?? [];
|
|
73845
|
+
return { ...pr, checks };
|
|
73846
|
+
}
|
|
73847
|
+
async function runDebug(opts) {
|
|
73848
|
+
const { projectRoot } = opts;
|
|
73849
|
+
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
73850
|
+
const agentLogContent = await agentLogFile.exists() ? await agentLogFile.text() : "";
|
|
73851
|
+
const agentLines = parseLog(agentLogContent);
|
|
73852
|
+
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget({
|
|
73853
|
+
...opts.name !== undefined ? { name: opts.name } : {},
|
|
73854
|
+
...opts.issue !== undefined ? { issue: opts.issue } : {}
|
|
73855
|
+
});
|
|
73856
|
+
if (!changeName) {
|
|
73857
|
+
process.stderr.write(`! Could not resolve a change name for ${opts.issue ?? opts.name}. Has this issue been started?
|
|
73858
|
+
`);
|
|
73859
|
+
process.exit(1);
|
|
73860
|
+
}
|
|
73861
|
+
const relevant = agentLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
73862
|
+
if (!issueIdentifier) {
|
|
73863
|
+
for (const line of relevant) {
|
|
73864
|
+
const m = SPAWN_RE.exec(line.text);
|
|
73865
|
+
if (m && m[2] === changeName) {
|
|
73866
|
+
issueIdentifier = m[1];
|
|
73867
|
+
break;
|
|
73868
|
+
}
|
|
73869
|
+
}
|
|
73870
|
+
}
|
|
73871
|
+
const workerLogPath = join20(projectRoot, ".ralph", "logs", `${changeName}.log`);
|
|
73872
|
+
const workerLogFile = Bun.file(workerLogPath);
|
|
73873
|
+
const workerLines = await workerLogFile.exists() ? parseLog(await workerLogFile.text()) : [];
|
|
73874
|
+
const merged = [...relevant, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
73875
|
+
const seen = new Set;
|
|
73876
|
+
const timeline = merged.filter((l) => {
|
|
73877
|
+
const key = `${l.ts.getTime()}:${l.type}:${l.text}`;
|
|
73878
|
+
if (seen.has(key))
|
|
73879
|
+
return false;
|
|
73880
|
+
seen.add(key);
|
|
73881
|
+
return true;
|
|
73882
|
+
});
|
|
73883
|
+
const out = (s) => process.stdout.write(s + `
|
|
73884
|
+
`);
|
|
73885
|
+
out(`
|
|
73886
|
+
=== Ralph Debug: ${changeName}${issueIdentifier ? ` (${issueIdentifier})` : ""} ===
|
|
73887
|
+
`);
|
|
73888
|
+
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");
|
|
73889
|
+
if (!timeline.length) {
|
|
73890
|
+
out(" (no log entries found)");
|
|
73891
|
+
} else {
|
|
73892
|
+
for (const line of timeline) {
|
|
73893
|
+
const prefix = line.type === "output" ? " \u2502" : " \xB7";
|
|
73894
|
+
out(`${prefix} ${fmtTs(line.ts)} [${line.type.padEnd(7)}] ${line.text}`);
|
|
73895
|
+
}
|
|
73896
|
+
}
|
|
73897
|
+
out("");
|
|
73898
|
+
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");
|
|
73899
|
+
if (!issueIdentifier) {
|
|
73900
|
+
out(" (unknown identifier \u2014 pass --issue to query Linear directly)");
|
|
73901
|
+
} else if (!process.env.LINEAR_API_KEY) {
|
|
73902
|
+
out(" (set LINEAR_API_KEY to fetch current Linear state)");
|
|
73903
|
+
} else {
|
|
73904
|
+
const issue = await fetchLinearIssue(issueIdentifier);
|
|
73905
|
+
if (!issue) {
|
|
73906
|
+
out(` ! Could not fetch ${issueIdentifier} from Linear`);
|
|
73907
|
+
} else {
|
|
73908
|
+
const labels = issue.labels.nodes.map((l) => l.name).join(", ") || "(none)";
|
|
73909
|
+
out(` Title : ${issue.title}`);
|
|
73910
|
+
out(` Status : ${issue.state.name} (${issue.state.type})`);
|
|
73911
|
+
out(` Labels : ${labels}`);
|
|
73912
|
+
out(` URL : ${issue.url}`);
|
|
73913
|
+
}
|
|
73914
|
+
}
|
|
73915
|
+
out("");
|
|
73916
|
+
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");
|
|
73917
|
+
const pr = await fetchGithubPr(changeName);
|
|
73918
|
+
if (!pr) {
|
|
73919
|
+
out(` (no PR found for branch ralph/${changeName})`);
|
|
73920
|
+
} else {
|
|
73921
|
+
const failing = pr.checks.filter((c) => c.conclusion === "FAILURE" || c.conclusion === "failure");
|
|
73922
|
+
const pending = pr.checks.filter((c) => c.state === "PENDING" || c.state === "IN_PROGRESS");
|
|
73923
|
+
out(` PR #${pr.number} : ${pr.url}`);
|
|
73924
|
+
out(` State : ${pr.state}`);
|
|
73925
|
+
out(` Mergeable : ${pr.mergeable}`);
|
|
73926
|
+
if (pr.checks.length) {
|
|
73927
|
+
out(` Checks : ${pr.checks.length} total, ${failing.length} failing, ${pending.length} pending`);
|
|
73928
|
+
for (const c of failing)
|
|
73929
|
+
out(` \u2717 ${c.name}`);
|
|
73930
|
+
for (const c of pending)
|
|
73931
|
+
out(` \u29D7 ${c.name}`);
|
|
73932
|
+
} else {
|
|
73933
|
+
out(" Checks : (none or not yet available)");
|
|
73934
|
+
}
|
|
73935
|
+
}
|
|
73936
|
+
out("");
|
|
73937
|
+
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");
|
|
73938
|
+
const lastEvent = timeline.at(-1);
|
|
73939
|
+
if (lastEvent)
|
|
73940
|
+
out(` Last event : ${fmtTs(lastEvent.ts)} ${lastEvent.text}`);
|
|
73941
|
+
const exitLine = relevant.find((l) => /exited \(code \d+\)/.test(l.text));
|
|
73942
|
+
if (exitLine) {
|
|
73943
|
+
const code = Number(/code (\d+)/.exec(exitLine.text)?.[1]);
|
|
73944
|
+
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";
|
|
73945
|
+
out(` Exit code : ${code} \u2014 ${meaning}`);
|
|
73946
|
+
}
|
|
73947
|
+
const logHas = (s) => relevant.some((l) => l.text.includes(s));
|
|
73948
|
+
if (logHas("setError applied"))
|
|
73949
|
+
out(" \u26A0 setError applied \u2014 issue is quarantined in Linear");
|
|
73950
|
+
if (logHas("setDone applied"))
|
|
73951
|
+
out(" \u2713 setDone applied \u2014 issue marked done in Linear");
|
|
73952
|
+
if (logHas("clearConflicted applied"))
|
|
73953
|
+
out(" \u2713 clearConflicted applied \u2014 conflicts resolved");
|
|
73954
|
+
if (logHas("setConflicted applied"))
|
|
73955
|
+
out(" \u26A0 setConflicted applied \u2014 merge conflicts detected");
|
|
73956
|
+
if (logHas("skipping PR phase"))
|
|
73957
|
+
out(" \u21A9 PR phase skipped \u2014 worker exited non-zero");
|
|
73958
|
+
if (pr?.mergeable === "CONFLICTING")
|
|
73959
|
+
out(" \u26A0 PR currently has merge conflicts");
|
|
73960
|
+
if (pr?.checks.some((c) => c.conclusion === "FAILURE" || c.conclusion === "failure")) {
|
|
73961
|
+
out(" \u26A0 PR has failing CI checks");
|
|
73962
|
+
}
|
|
73963
|
+
const worktreePath = join20(projectRoot, ".ralph", "worktrees", changeName);
|
|
73964
|
+
const worktreeExists = await Bun.file(join20(worktreePath, ".git")).exists();
|
|
73965
|
+
if (worktreeExists)
|
|
73966
|
+
out(` Worktree : ${worktreePath}`);
|
|
73967
|
+
if (!timeline.length)
|
|
73968
|
+
out(" (no log entries \u2014 has this change been started yet?)");
|
|
73969
|
+
out("");
|
|
73970
|
+
}
|
|
73971
|
+
|
|
73972
|
+
// apps/cli/src/index.ts
|
|
73709
73973
|
init_src();
|
|
73710
73974
|
if (typeof globalThis.Bun === "undefined") {
|
|
73711
73975
|
process.stderr.write(`ralph requires the Bun runtime (https://bun.sh/). It is not compatible with plain Node.js.
|
|
@@ -73715,7 +73979,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
73715
73979
|
async function findProjectRoot() {
|
|
73716
73980
|
let dir = process.cwd();
|
|
73717
73981
|
while (dir !== "/") {
|
|
73718
|
-
if (await exists2(
|
|
73982
|
+
if (await exists2(join22(dir, "openspec")))
|
|
73719
73983
|
return dir;
|
|
73720
73984
|
dir = resolve2(dir, "..");
|
|
73721
73985
|
}
|
|
@@ -73756,22 +74020,32 @@ try {
|
|
|
73756
74020
|
const tasksDir = layout.tasksDir;
|
|
73757
74021
|
if (args.mode === "init") {
|
|
73758
74022
|
await mkdir7(statesDir, { recursive: true });
|
|
73759
|
-
const openspecBin =
|
|
74023
|
+
const openspecBin = join22(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
73760
74024
|
Bun.spawnSync({
|
|
73761
74025
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
73762
74026
|
stdio: ["inherit", "inherit", "inherit"],
|
|
73763
74027
|
cwd: process.cwd()
|
|
73764
74028
|
});
|
|
73765
74029
|
}
|
|
74030
|
+
if (args.mode === "debug") {
|
|
74031
|
+
if (!args.name) {
|
|
74032
|
+
process.stderr.write(`Error: --name is required for debug mode
|
|
74033
|
+
`);
|
|
74034
|
+
process.exit(1);
|
|
74035
|
+
}
|
|
74036
|
+
await runDebug({ name: args.name, projectRoot });
|
|
74037
|
+
await shutdown();
|
|
74038
|
+
process.exit(0);
|
|
74039
|
+
}
|
|
73766
74040
|
if (args.mode === "clean") {
|
|
73767
74041
|
if (!args.name) {
|
|
73768
74042
|
process.stderr.write(`Error: --name is required for clean mode
|
|
73769
74043
|
`);
|
|
73770
74044
|
process.exit(1);
|
|
73771
74045
|
}
|
|
73772
|
-
const worktreeDir =
|
|
73773
|
-
const changeDir =
|
|
73774
|
-
const stateDir =
|
|
74046
|
+
const worktreeDir = join22(worktreesDir(projectRoot), args.name);
|
|
74047
|
+
const changeDir = join22(tasksDir, args.name);
|
|
74048
|
+
const stateDir = join22(statesDir, args.name);
|
|
73775
74049
|
const branch = `ralph/${args.name}`;
|
|
73776
74050
|
const removed = [];
|
|
73777
74051
|
if (await exists2(worktreeDir)) {
|
|
@@ -73823,13 +74097,13 @@ try {
|
|
|
73823
74097
|
process.exit(0);
|
|
73824
74098
|
}
|
|
73825
74099
|
if (args.mode === "task" && args.name) {
|
|
73826
|
-
await mkdir7(
|
|
73827
|
-
await mkdir7(
|
|
74100
|
+
await mkdir7(join22(statesDir, args.name), { recursive: true });
|
|
74101
|
+
await mkdir7(join22(tasksDir, args.name), { recursive: true });
|
|
73828
74102
|
}
|
|
73829
74103
|
if (args.mode === "agent") {
|
|
73830
74104
|
await mkdir7(statesDir, { recursive: true });
|
|
73831
74105
|
await mkdir7(tasksDir, { recursive: true });
|
|
73832
|
-
await mkdir7(
|
|
74106
|
+
await mkdir7(join22(projectRoot, ".ralph"), { recursive: true });
|
|
73833
74107
|
}
|
|
73834
74108
|
if (args.mode === "agent" && args.jsonOutput) {
|
|
73835
74109
|
const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
|