@neriros/ralphy 2.7.8 → 2.8.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 +2 -0
- package/dist/cli/index.js +273 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,6 +140,8 @@ Use `setupScript` (run inside the worktree right after scaffolding) to install d
|
|
|
140
140
|
|
|
141
141
|
**`fixCiOnFailure`** (or `--fix-ci`) watches the PR's checks via `gh pr checks` and, on failure, fetches the failed-run logs (`gh run view --log-failed`), appends them to `proposal.md` under `## Steering`, re-spawns the task loop in the worktree, and pushes the new commits — repeating until checks go green or `maxCiFixAttempts` is hit (default 5, polling interval `ciPollIntervalSeconds` defaults to 30s). Requires `--create-pr`.
|
|
142
142
|
|
|
143
|
+
When `fixCiOnFailure` is enabled, the issue is **not** moved to `doneStatus` (and `doneLabel` is not applied, and the issue is not marked processed in `.ralph/agent-state.json`) until CI actually goes green. If the fix loop exhausts its attempts the worker is treated as failed for completion-marking purposes and the issue will be re-picked-up on the next poll (the resume-in-progress filter ensures that).
|
|
144
|
+
|
|
143
145
|
Every CLI flag is also configurable in `ralphy.config.json`; CLI values override config when both are set. The agent forwards `maxRuntimeMinutesPerTask` / `maxConsecutiveFailuresPerTask` / `iterationDelaySeconds` / `logRawStream` / `taskVerbose` to each spawned `ralph task` worker.
|
|
144
146
|
|
|
145
147
|
Failed workers (non-zero exit) are not marked processed, so they'll be retried on the next poll. SIGINT/SIGTERM cleanly stops polling and kills active workers. All Linear side effects are best-effort — failures log a warning but never block the task loop.
|
package/dist/cli/index.js
CHANGED
|
@@ -50549,7 +50549,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50549
50549
|
|
|
50550
50550
|
// apps/cli/src/index.ts
|
|
50551
50551
|
import { resolve, join as join17, dirname as dirname4 } from "path";
|
|
50552
|
-
import { exists, mkdir as mkdir3 } from "fs/promises";
|
|
50552
|
+
import { exists, mkdir as mkdir3, rm } from "fs/promises";
|
|
50553
50553
|
|
|
50554
50554
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
50555
50555
|
import { Stream } from "stream";
|
|
@@ -56117,7 +56117,7 @@ function log(msg) {
|
|
|
56117
56117
|
}
|
|
56118
56118
|
|
|
56119
56119
|
// apps/cli/src/cli.ts
|
|
56120
|
-
var VALID_MODES = new Set(["task", "list", "status", "init", "agent"]);
|
|
56120
|
+
var VALID_MODES = new Set(["task", "list", "status", "init", "agent", "clean"]);
|
|
56121
56121
|
var VALID_MODELS = new Set(["haiku", "sonnet", "opus"]);
|
|
56122
56122
|
var HELP_TEXT = [
|
|
56123
56123
|
"Usage: ralph <command> [options]",
|
|
@@ -56128,6 +56128,7 @@ var HELP_TEXT = [
|
|
|
56128
56128
|
" status Show detailed change status",
|
|
56129
56129
|
" init Initialize OpenSpec in current directory",
|
|
56130
56130
|
" agent Poll Linear for new tasks and run loops concurrently",
|
|
56131
|
+
" clean Remove worktree, branch, openspec change, and task state for --name",
|
|
56131
56132
|
"",
|
|
56132
56133
|
"Options:",
|
|
56133
56134
|
" --name <name> Change name (required for most commands)",
|
|
@@ -60611,54 +60612,70 @@ function countTaskItems(content) {
|
|
|
60611
60612
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
60612
60613
|
return { checked, unchecked };
|
|
60613
60614
|
}
|
|
60614
|
-
function buildRows(statesDir) {
|
|
60615
|
+
function buildRows(statesDir, projectRoot) {
|
|
60615
60616
|
const storage = getStorage();
|
|
60616
|
-
const entries = storage.list(statesDir);
|
|
60617
60617
|
const rows = [];
|
|
60618
|
-
|
|
60619
|
-
|
|
60620
|
-
|
|
60621
|
-
|
|
60622
|
-
|
|
60623
|
-
|
|
60624
|
-
|
|
60625
|
-
|
|
60626
|
-
|
|
60618
|
+
const seenNames = new Set;
|
|
60619
|
+
const sources = [{ dir: statesDir, label: "main" }];
|
|
60620
|
+
if (projectRoot) {
|
|
60621
|
+
const worktreesRoot = join3(projectRoot, ".ralph", "worktrees");
|
|
60622
|
+
for (const wt of storage.list(worktreesRoot)) {
|
|
60623
|
+
sources.push({
|
|
60624
|
+
dir: join3(worktreesRoot, wt, ".ralph", "tasks"),
|
|
60625
|
+
label: `wt:${wt}`
|
|
60626
|
+
});
|
|
60627
60627
|
}
|
|
60628
|
-
|
|
60629
|
-
|
|
60630
|
-
const
|
|
60631
|
-
|
|
60628
|
+
}
|
|
60629
|
+
for (const { dir, label } of sources) {
|
|
60630
|
+
for (const entry of storage.list(dir)) {
|
|
60631
|
+
if (seenNames.has(entry))
|
|
60632
|
+
continue;
|
|
60633
|
+
const raw = storage.read(join3(dir, entry, ".ralph-state.json"));
|
|
60634
|
+
if (raw === null)
|
|
60635
|
+
continue;
|
|
60636
|
+
let state;
|
|
60637
|
+
try {
|
|
60638
|
+
state = JSON.parse(raw);
|
|
60639
|
+
} catch {
|
|
60640
|
+
continue;
|
|
60641
|
+
}
|
|
60642
|
+
if (String(state.status ?? "") === "completed")
|
|
60643
|
+
continue;
|
|
60644
|
+
const promptRaw = String(state.prompt ?? "");
|
|
60645
|
+
const firstLine = promptRaw.split(`
|
|
60632
60646
|
`).find((l) => l.trim() !== "") ?? "";
|
|
60633
|
-
|
|
60634
|
-
|
|
60635
|
-
|
|
60636
|
-
|
|
60637
|
-
|
|
60638
|
-
|
|
60639
|
-
|
|
60640
|
-
|
|
60641
|
-
|
|
60642
|
-
|
|
60643
|
-
|
|
60644
|
-
|
|
60645
|
-
|
|
60646
|
-
|
|
60647
|
-
|
|
60648
|
-
|
|
60649
|
-
|
|
60650
|
-
|
|
60651
|
-
|
|
60652
|
-
|
|
60647
|
+
let progress = "\u2014";
|
|
60648
|
+
let progressStyled = true;
|
|
60649
|
+
const tasksContent = storage.read(join3(dir, entry, "tasks.md"));
|
|
60650
|
+
if (tasksContent !== null) {
|
|
60651
|
+
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
60652
|
+
const total = checked + unchecked;
|
|
60653
|
+
if (total > 0) {
|
|
60654
|
+
progress = `${checked}/${total}`;
|
|
60655
|
+
progressStyled = false;
|
|
60656
|
+
}
|
|
60657
|
+
}
|
|
60658
|
+
seenNames.add(entry);
|
|
60659
|
+
rows.push({
|
|
60660
|
+
name: String(state.name ?? entry),
|
|
60661
|
+
phase: String(state.status ?? "active"),
|
|
60662
|
+
status: String(state.status ?? "unknown"),
|
|
60663
|
+
iters: String(state.iteration ?? 0),
|
|
60664
|
+
progress,
|
|
60665
|
+
progressStyled,
|
|
60666
|
+
prompt: firstLine.replace(/^#+\s*/, "").trim().slice(0, 60),
|
|
60667
|
+
source: label
|
|
60668
|
+
});
|
|
60669
|
+
}
|
|
60653
60670
|
}
|
|
60654
60671
|
return rows;
|
|
60655
60672
|
}
|
|
60656
|
-
function TaskList({ statesDir }) {
|
|
60673
|
+
function TaskList({ statesDir, projectRoot }) {
|
|
60657
60674
|
const { exit } = use_app_default();
|
|
60658
60675
|
import_react22.useEffect(() => {
|
|
60659
60676
|
exit();
|
|
60660
60677
|
}, [exit]);
|
|
60661
|
-
const rows = buildRows(statesDir);
|
|
60678
|
+
const rows = buildRows(statesDir, projectRoot);
|
|
60662
60679
|
if (rows.length === 0) {
|
|
60663
60680
|
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
60664
60681
|
flexDirection: "column",
|
|
@@ -60681,9 +60698,10 @@ function TaskList({ statesDir }) {
|
|
|
60681
60698
|
phase: Math.max(5, ...rows.map((r) => r.phase.length)),
|
|
60682
60699
|
status: Math.max(6, ...rows.map((r) => r.status.length)),
|
|
60683
60700
|
iters: 5,
|
|
60684
|
-
progress: 8
|
|
60701
|
+
progress: 8,
|
|
60702
|
+
source: Math.max(6, ...rows.map((r) => r.source.length))
|
|
60685
60703
|
};
|
|
60686
|
-
const ruleWidth = cols.name + cols.phase + cols.status + cols.iters + cols.progress + 60 +
|
|
60704
|
+
const ruleWidth = cols.name + cols.phase + cols.status + cols.iters + cols.progress + cols.source + 60 + 12;
|
|
60687
60705
|
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
60688
60706
|
flexDirection: "column",
|
|
60689
60707
|
children: [
|
|
@@ -60717,6 +60735,11 @@ function TaskList({ statesDir }) {
|
|
|
60717
60735
|
children: "Progress".padEnd(cols.progress)
|
|
60718
60736
|
}, undefined, false, undefined, this),
|
|
60719
60737
|
" ",
|
|
60738
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
60739
|
+
bold: true,
|
|
60740
|
+
children: "Source".padEnd(cols.source)
|
|
60741
|
+
}, undefined, false, undefined, this),
|
|
60742
|
+
" ",
|
|
60720
60743
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
60721
60744
|
bold: true,
|
|
60722
60745
|
children: "Description"
|
|
@@ -60745,6 +60768,11 @@ function TaskList({ statesDir }) {
|
|
|
60745
60768
|
children: row.progress.padStart(cols.progress)
|
|
60746
60769
|
}, undefined, false, undefined, this) : row.progress.padStart(cols.progress),
|
|
60747
60770
|
" ",
|
|
60771
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
60772
|
+
dimColor: true,
|
|
60773
|
+
children: row.source.padEnd(cols.source)
|
|
60774
|
+
}, undefined, false, undefined, this),
|
|
60775
|
+
" ",
|
|
60748
60776
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
60749
60777
|
dimColor: true,
|
|
60750
60778
|
children: row.prompt
|
|
@@ -69758,7 +69786,12 @@ async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
|
69758
69786
|
import { join as join10 } from "path";
|
|
69759
69787
|
var AgentStateSchema = exports_external.object({
|
|
69760
69788
|
processedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
69761
|
-
|
|
69789
|
+
startedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
69790
|
+
lastPollAt: exports_external.string().nullable().default(null),
|
|
69791
|
+
changeMeta: exports_external.record(exports_external.string(), exports_external.object({
|
|
69792
|
+
issueId: exports_external.string(),
|
|
69793
|
+
identifier: exports_external.string()
|
|
69794
|
+
})).default({})
|
|
69762
69795
|
});
|
|
69763
69796
|
function statePath(projectRoot) {
|
|
69764
69797
|
return join10(projectRoot, ".ralph", "agent-state.json");
|
|
@@ -70049,9 +70082,14 @@ class AgentCoordinator {
|
|
|
70049
70082
|
const updater = this.deps.updater;
|
|
70050
70083
|
if (!updater)
|
|
70051
70084
|
return;
|
|
70052
|
-
|
|
70085
|
+
const alreadyStarted = this.state?.startedIssueIds.includes(issue.id) ?? false;
|
|
70086
|
+
if (this.opts.postComments !== false && !alreadyStarted) {
|
|
70053
70087
|
try {
|
|
70054
70088
|
await updater.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${changeName}\``);
|
|
70089
|
+
if (this.state && !this.state.startedIssueIds.includes(issue.id)) {
|
|
70090
|
+
this.state.startedIssueIds.push(issue.id);
|
|
70091
|
+
await this.deps.saveState(this.state);
|
|
70092
|
+
}
|
|
70055
70093
|
} catch (err) {
|
|
70056
70094
|
this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
|
|
70057
70095
|
}
|
|
@@ -70299,7 +70337,10 @@ var bunCmdRunner = {
|
|
|
70299
70337
|
const stderr = await new Response(proc.stderr).text();
|
|
70300
70338
|
const code = await proc.exited;
|
|
70301
70339
|
if (code !== 0) {
|
|
70302
|
-
const
|
|
70340
|
+
const firstStderrLine = stderr.trim().split(`
|
|
70341
|
+
`)[0] ?? "";
|
|
70342
|
+
const summary = firstStderrLine ? `: ${firstStderrLine}` : "";
|
|
70343
|
+
const err = new Error(`\`${cmd.join(" ")}\` exited ${code}${summary}`);
|
|
70303
70344
|
err.stderr = stderr;
|
|
70304
70345
|
err.code = code;
|
|
70305
70346
|
throw err;
|
|
@@ -70312,11 +70353,28 @@ function nextId() {
|
|
|
70312
70353
|
lineCounter += 1;
|
|
70313
70354
|
return `${Date.now()}-${lineCounter}`;
|
|
70314
70355
|
}
|
|
70356
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
70357
|
+
function fmtElapsed(ms) {
|
|
70358
|
+
const s = Math.floor(ms / 1000);
|
|
70359
|
+
if (s < 60)
|
|
70360
|
+
return `${s}s`;
|
|
70361
|
+
const m = Math.floor(s / 60);
|
|
70362
|
+
const rem = s % 60;
|
|
70363
|
+
if (m < 60)
|
|
70364
|
+
return `${m}m${rem.toString().padStart(2, "0")}s`;
|
|
70365
|
+
const h = Math.floor(m / 60);
|
|
70366
|
+
return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
|
|
70367
|
+
}
|
|
70315
70368
|
function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
70316
70369
|
const { exit } = use_app_default();
|
|
70317
70370
|
const [logs, setLogs] = import_react57.useState([]);
|
|
70318
70371
|
const [, setTick] = import_react57.useState(0);
|
|
70372
|
+
const [clock, setClock] = import_react57.useState(0);
|
|
70319
70373
|
const coordRef = import_react57.useRef(null);
|
|
70374
|
+
const workerMetaRef = import_react57.useRef(new Map);
|
|
70375
|
+
const nextPollAtRef = import_react57.useRef(0);
|
|
70376
|
+
const pollIntervalRef = import_react57.useRef(0);
|
|
70377
|
+
const [pollStatus, setPollStatus] = import_react57.useState({ state: "idle", lastFound: null, lastAdded: null, lastAt: null, filterDesc: "" });
|
|
70320
70378
|
function appendLog(text, color) {
|
|
70321
70379
|
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
70322
70380
|
}
|
|
@@ -70329,6 +70387,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70329
70387
|
appendLog(`agent mode \u2014 config: ${cfgPath}`, "gray");
|
|
70330
70388
|
const concurrency = args.concurrency || cfg.concurrency;
|
|
70331
70389
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
70390
|
+
pollIntervalRef.current = pollInterval;
|
|
70332
70391
|
appendLog(`concurrency=${concurrency} pollInterval=${pollInterval}s`, "gray");
|
|
70333
70392
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
70334
70393
|
if (!apiKey) {
|
|
@@ -70402,6 +70461,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70402
70461
|
issueByChange.set(changeName, issue);
|
|
70403
70462
|
if (workerBranch)
|
|
70404
70463
|
branchByChange.set(changeName, workerBranch);
|
|
70464
|
+
try {
|
|
70465
|
+
const s = await readAgentState(projectRoot);
|
|
70466
|
+
s.changeMeta[changeName] = { issueId: issue.id, identifier: issue.identifier };
|
|
70467
|
+
await writeAgentState(projectRoot, s);
|
|
70468
|
+
} catch (err) {
|
|
70469
|
+
appendLog(`! failed to record agent meta for ${changeName}: ${err.message}`, "yellow");
|
|
70470
|
+
}
|
|
70405
70471
|
if (cfg.setupScript) {
|
|
70406
70472
|
await runScript("setup", cfg.setupScript, workerCwd);
|
|
70407
70473
|
}
|
|
@@ -70447,19 +70513,28 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70447
70513
|
stderr: "ignore",
|
|
70448
70514
|
stdin: "ignore"
|
|
70449
70515
|
});
|
|
70516
|
+
workerMetaRef.current.set(changeName, {
|
|
70517
|
+
startedAt: Date.now(),
|
|
70518
|
+
statesDir: statesDirByChange.get(changeName) ?? statesDir,
|
|
70519
|
+
iter: 0
|
|
70520
|
+
});
|
|
70450
70521
|
const wantPr = args.createPr || cfg.createPrOnSuccess;
|
|
70522
|
+
const CI_FAILED_EXIT = 70;
|
|
70523
|
+
const PR_FAILED_EXIT = 71;
|
|
70451
70524
|
const wrapped = proc.exited.then(async (code) => {
|
|
70452
70525
|
if (cfg.teardownScript) {
|
|
70453
70526
|
try {
|
|
70454
70527
|
await runScript("teardown", cfg.teardownScript, cwd2);
|
|
70455
70528
|
} catch {}
|
|
70456
70529
|
}
|
|
70530
|
+
let effectiveCode = code;
|
|
70457
70531
|
const ok = code === 0;
|
|
70458
70532
|
if (ok && wantPr) {
|
|
70459
70533
|
const branch = branchByChange.get(changeName);
|
|
70460
70534
|
const prIssue = issueByChange.get(changeName);
|
|
70461
70535
|
if (!branch || !prIssue) {
|
|
70462
70536
|
appendLog(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
70537
|
+
effectiveCode = PR_FAILED_EXIT;
|
|
70463
70538
|
} else {
|
|
70464
70539
|
try {
|
|
70465
70540
|
const pr = await createPullRequest({ cwd: cwd2, branch, issue: prIssue, base: cfg.prBaseBranch }, bunCmdRunner);
|
|
@@ -70513,17 +70588,21 @@ ${stamped}
|
|
|
70513
70588
|
pollIntervalSeconds: cfg.ciPollIntervalSeconds
|
|
70514
70589
|
});
|
|
70515
70590
|
if (!result2.success) {
|
|
70516
|
-
appendLog(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"})`, "red");
|
|
70591
|
+
appendLog(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
|
|
70592
|
+
effectiveCode = CI_FAILED_EXIT;
|
|
70517
70593
|
}
|
|
70518
70594
|
}
|
|
70519
70595
|
}
|
|
70520
70596
|
} catch (err) {
|
|
70521
|
-
|
|
70597
|
+
const e = err;
|
|
70598
|
+
const detail = e.stderr?.trim() || e.message;
|
|
70599
|
+
appendLog(`! PR create failed for ${changeName}: ${detail}`, "red");
|
|
70600
|
+
effectiveCode = PR_FAILED_EXIT;
|
|
70522
70601
|
}
|
|
70523
70602
|
}
|
|
70524
70603
|
}
|
|
70525
70604
|
if (useWorktree && cwd2 !== projectRoot) {
|
|
70526
|
-
if (
|
|
70605
|
+
if (effectiveCode === 0 && cfg.cleanupWorktreeOnSuccess) {
|
|
70527
70606
|
try {
|
|
70528
70607
|
await removeWorktree(projectRoot, cwd2, bunGitRunner);
|
|
70529
70608
|
appendLog(` removed worktree ${cwd2}`, "gray");
|
|
@@ -70536,7 +70615,8 @@ ${stamped}
|
|
|
70536
70615
|
statesDirByChange.delete(changeName);
|
|
70537
70616
|
branchByChange.delete(changeName);
|
|
70538
70617
|
issueByChange.delete(changeName);
|
|
70539
|
-
|
|
70618
|
+
workerMetaRef.current.delete(changeName);
|
|
70619
|
+
return effectiveCode;
|
|
70540
70620
|
});
|
|
70541
70621
|
return { exited: wrapped, kill: () => proc.kill() };
|
|
70542
70622
|
},
|
|
@@ -70588,15 +70668,25 @@ ${stamped}
|
|
|
70588
70668
|
});
|
|
70589
70669
|
coordRef.current = coord2;
|
|
70590
70670
|
await coord2.init();
|
|
70671
|
+
const filterDesc = `team=${filter2.team ?? "*"}, assignee=${filter2.assignee ?? "*"}, statuses=${filter2.statuses?.length ? filter2.statuses.join(",") : "open"}${filter2.labels?.length ? `, labels=${filter2.labels.join(",")}` : ""}`;
|
|
70591
70672
|
const tick = async () => {
|
|
70592
70673
|
if (cancelled)
|
|
70593
70674
|
return;
|
|
70594
|
-
|
|
70595
|
-
appendLog(`\u2026 polling Linear (${filterDesc})`);
|
|
70675
|
+
setPollStatus((p) => ({ ...p, state: "polling", filterDesc }));
|
|
70596
70676
|
const { found, added } = await coord2.pollOnce();
|
|
70597
|
-
appendLog(` found ${found} open, ${added} new (queue=${coord2.queuedCount})`);
|
|
70598
70677
|
if (cancelled)
|
|
70599
70678
|
return;
|
|
70679
|
+
if (added > 0) {
|
|
70680
|
+
appendLog(` ${added} new issue${added === 1 ? "" : "s"} queued (found ${found} open)`);
|
|
70681
|
+
}
|
|
70682
|
+
setPollStatus({
|
|
70683
|
+
state: "idle",
|
|
70684
|
+
lastFound: found,
|
|
70685
|
+
lastAdded: added,
|
|
70686
|
+
lastAt: Date.now(),
|
|
70687
|
+
filterDesc
|
|
70688
|
+
});
|
|
70689
|
+
nextPollAtRef.current = Date.now() + pollInterval * 1000;
|
|
70600
70690
|
pollTimer = setTimeout(tick, pollInterval * 1000);
|
|
70601
70691
|
};
|
|
70602
70692
|
tick();
|
|
@@ -70621,7 +70711,34 @@ ${stamped}
|
|
|
70621
70711
|
process.off("SIGTERM", onSig);
|
|
70622
70712
|
};
|
|
70623
70713
|
}, []);
|
|
70714
|
+
import_react57.useEffect(() => {
|
|
70715
|
+
let cancelled = false;
|
|
70716
|
+
const interval = setInterval(() => {
|
|
70717
|
+
if (cancelled)
|
|
70718
|
+
return;
|
|
70719
|
+
(async () => {
|
|
70720
|
+
for (const [changeName, meta] of workerMetaRef.current) {
|
|
70721
|
+
try {
|
|
70722
|
+
const file = Bun.file(join14(meta.statesDir, changeName, ".ralph-state.json"));
|
|
70723
|
+
if (await file.exists()) {
|
|
70724
|
+
const json = await file.json();
|
|
70725
|
+
meta.iter = json.iteration ?? meta.iter;
|
|
70726
|
+
}
|
|
70727
|
+
} catch {}
|
|
70728
|
+
}
|
|
70729
|
+
if (!cancelled)
|
|
70730
|
+
setClock((c) => c + 1);
|
|
70731
|
+
})();
|
|
70732
|
+
}, 1000);
|
|
70733
|
+
return () => {
|
|
70734
|
+
cancelled = true;
|
|
70735
|
+
clearInterval(interval);
|
|
70736
|
+
};
|
|
70737
|
+
}, []);
|
|
70624
70738
|
const coord = coordRef.current;
|
|
70739
|
+
const spinnerFrame = SPINNER_FRAMES[clock % SPINNER_FRAMES.length];
|
|
70740
|
+
const now2 = Date.now();
|
|
70741
|
+
const secsToNextPoll = nextPollAtRef.current ? Math.max(0, Math.ceil((nextPollAtRef.current - now2) / 1000)) : null;
|
|
70625
70742
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
70626
70743
|
flexDirection: "column",
|
|
70627
70744
|
children: [
|
|
@@ -70641,23 +70758,41 @@ ${stamped}
|
|
|
70641
70758
|
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70642
70759
|
dimColor: true,
|
|
70643
70760
|
children: [
|
|
70761
|
+
spinnerFrame,
|
|
70762
|
+
" ",
|
|
70763
|
+
pollStatus.state === "polling" ? `polling Linear (${pollStatus.filterDesc})` : pollStatus.lastAt !== null ? `last poll: ${pollStatus.lastFound} open, ${pollStatus.lastAdded} new${secsToNextPoll !== null ? ` \xB7 next in ${secsToNextPoll}s` : ""}` : "starting\u2026"
|
|
70764
|
+
]
|
|
70765
|
+
}, undefined, true, undefined, this),
|
|
70766
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70767
|
+
dimColor: true,
|
|
70768
|
+
children: [
|
|
70769
|
+
" ",
|
|
70644
70770
|
"workers active: ",
|
|
70645
70771
|
coord?.activeCount ?? 0,
|
|
70646
70772
|
" \xB7 queued: ",
|
|
70647
70773
|
coord?.queuedCount ?? 0
|
|
70648
70774
|
]
|
|
70649
70775
|
}, undefined, true, undefined, this),
|
|
70650
|
-
coord?.activeWorkers.map((w) =>
|
|
70651
|
-
|
|
70652
|
-
|
|
70653
|
-
|
|
70654
|
-
|
|
70655
|
-
|
|
70656
|
-
|
|
70657
|
-
|
|
70658
|
-
|
|
70659
|
-
|
|
70660
|
-
|
|
70776
|
+
coord?.activeWorkers.map((w) => {
|
|
70777
|
+
const meta = workerMetaRef.current.get(w.changeName);
|
|
70778
|
+
const elapsed = meta ? fmtElapsed(now2 - meta.startedAt) : "\u2013";
|
|
70779
|
+
const iter = meta?.iter ?? 0;
|
|
70780
|
+
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70781
|
+
color: "cyan",
|
|
70782
|
+
children: [
|
|
70783
|
+
" ",
|
|
70784
|
+
spinnerFrame,
|
|
70785
|
+
" ",
|
|
70786
|
+
w.issueIdentifier,
|
|
70787
|
+
" (",
|
|
70788
|
+
w.changeName,
|
|
70789
|
+
") \xB7 iter ",
|
|
70790
|
+
iter,
|
|
70791
|
+
" \xB7 ",
|
|
70792
|
+
elapsed
|
|
70793
|
+
]
|
|
70794
|
+
}, w.changeName, true, undefined, this);
|
|
70795
|
+
})
|
|
70661
70796
|
]
|
|
70662
70797
|
}, undefined, true, undefined, this)
|
|
70663
70798
|
]
|
|
@@ -70808,7 +70943,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
70808
70943
|
switch (args.mode) {
|
|
70809
70944
|
case "list":
|
|
70810
70945
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskList, {
|
|
70811
|
-
statesDir
|
|
70946
|
+
statesDir,
|
|
70947
|
+
projectRoot
|
|
70812
70948
|
}, undefined, false, undefined, this);
|
|
70813
70949
|
case "agent":
|
|
70814
70950
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AgentMode, {
|
|
@@ -70844,6 +70980,10 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
70844
70980
|
children: "Initialized openspec directory"
|
|
70845
70981
|
}, undefined, false, undefined, this)
|
|
70846
70982
|
}, undefined, false, undefined, this);
|
|
70983
|
+
case "clean":
|
|
70984
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
|
|
70985
|
+
children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {}, undefined, false, undefined, this)
|
|
70986
|
+
}, undefined, false, undefined, this);
|
|
70847
70987
|
case "task": {
|
|
70848
70988
|
if (!args.name) {
|
|
70849
70989
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
@@ -70927,6 +71067,76 @@ try {
|
|
|
70927
71067
|
cwd: process.cwd()
|
|
70928
71068
|
});
|
|
70929
71069
|
}
|
|
71070
|
+
if (args.mode === "clean") {
|
|
71071
|
+
if (!args.name) {
|
|
71072
|
+
process.stderr.write(`Error: --name is required for clean mode
|
|
71073
|
+
`);
|
|
71074
|
+
process.exit(1);
|
|
71075
|
+
}
|
|
71076
|
+
const worktreeDir = join17(projectRoot, ".ralph", "worktrees", args.name);
|
|
71077
|
+
const changeDir = join17(tasksDir, args.name);
|
|
71078
|
+
const stateDir = join17(statesDir, args.name);
|
|
71079
|
+
const branch = `ralph/${args.name}`;
|
|
71080
|
+
const removed = [];
|
|
71081
|
+
if (await exists(worktreeDir)) {
|
|
71082
|
+
const proc = Bun.spawn({
|
|
71083
|
+
cmd: ["git", "worktree", "remove", "--force", worktreeDir],
|
|
71084
|
+
cwd: projectRoot,
|
|
71085
|
+
stdout: "pipe",
|
|
71086
|
+
stderr: "pipe"
|
|
71087
|
+
});
|
|
71088
|
+
const code = await proc.exited;
|
|
71089
|
+
if (code !== 0) {
|
|
71090
|
+
await rm(worktreeDir, { recursive: true, force: true });
|
|
71091
|
+
await Bun.spawn({
|
|
71092
|
+
cmd: ["git", "worktree", "prune"],
|
|
71093
|
+
cwd: projectRoot,
|
|
71094
|
+
stdout: "ignore",
|
|
71095
|
+
stderr: "ignore"
|
|
71096
|
+
}).exited;
|
|
71097
|
+
}
|
|
71098
|
+
removed.push(`worktree ${worktreeDir}`);
|
|
71099
|
+
}
|
|
71100
|
+
const branchProc = Bun.spawn({
|
|
71101
|
+
cmd: ["git", "branch", "-D", branch],
|
|
71102
|
+
cwd: projectRoot,
|
|
71103
|
+
stdout: "ignore",
|
|
71104
|
+
stderr: "ignore"
|
|
71105
|
+
});
|
|
71106
|
+
if (await branchProc.exited === 0)
|
|
71107
|
+
removed.push(`branch ${branch}`);
|
|
71108
|
+
if (await exists(changeDir)) {
|
|
71109
|
+
await rm(changeDir, { recursive: true, force: true });
|
|
71110
|
+
removed.push(`openspec change ${changeDir}`);
|
|
71111
|
+
}
|
|
71112
|
+
if (await exists(stateDir)) {
|
|
71113
|
+
await rm(stateDir, { recursive: true, force: true });
|
|
71114
|
+
removed.push(`task state ${stateDir}`);
|
|
71115
|
+
}
|
|
71116
|
+
try {
|
|
71117
|
+
const agentState = await readAgentState(projectRoot);
|
|
71118
|
+
const meta = agentState.changeMeta[args.name];
|
|
71119
|
+
if (meta) {
|
|
71120
|
+
agentState.processedIssueIds = agentState.processedIssueIds.filter((id) => id !== meta.issueId);
|
|
71121
|
+
agentState.startedIssueIds = agentState.startedIssueIds.filter((id) => id !== meta.issueId);
|
|
71122
|
+
delete agentState.changeMeta[args.name];
|
|
71123
|
+
await writeAgentState(projectRoot, agentState);
|
|
71124
|
+
removed.push(`agent-state entry for ${meta.identifier} (${meta.issueId})`);
|
|
71125
|
+
}
|
|
71126
|
+
} catch {}
|
|
71127
|
+
if (removed.length === 0) {
|
|
71128
|
+
process.stdout.write(`Nothing to clean for '${args.name}'
|
|
71129
|
+
`);
|
|
71130
|
+
} else {
|
|
71131
|
+
process.stdout.write(`Cleaned '${args.name}':
|
|
71132
|
+
`);
|
|
71133
|
+
for (const r of removed)
|
|
71134
|
+
process.stdout.write(` \u2713 removed ${r}
|
|
71135
|
+
`);
|
|
71136
|
+
}
|
|
71137
|
+
await shutdown();
|
|
71138
|
+
process.exit(0);
|
|
71139
|
+
}
|
|
70930
71140
|
if (args.mode === "task" && args.name) {
|
|
70931
71141
|
await mkdir3(join17(statesDir, args.name), { recursive: true });
|
|
70932
71142
|
await mkdir3(join17(tasksDir, args.name), { recursive: true });
|