@neriros/ralphy 2.7.9 → 2.8.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/dist/cli/index.js +279 -61
- package/package.json +1 -1
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,13 @@ 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
|
+
failedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
69791
|
+
lastPollAt: exports_external.string().nullable().default(null),
|
|
69792
|
+
changeMeta: exports_external.record(exports_external.string(), exports_external.object({
|
|
69793
|
+
issueId: exports_external.string(),
|
|
69794
|
+
identifier: exports_external.string()
|
|
69795
|
+
})).default({})
|
|
69762
69796
|
});
|
|
69763
69797
|
function statePath(projectRoot) {
|
|
69764
69798
|
return join10(projectRoot, ".ralph", "agent-state.json");
|
|
@@ -69944,12 +69978,15 @@ class AgentCoordinator {
|
|
|
69944
69978
|
}
|
|
69945
69979
|
const state = this.state;
|
|
69946
69980
|
const seen = new Set(state.processedIssueIds);
|
|
69981
|
+
const failed = new Set(state.failedIssueIds);
|
|
69947
69982
|
const queued = new Set(this.queue.map((i) => i.id));
|
|
69948
69983
|
const active = new Set(this.workers.map((w) => w.issueId));
|
|
69949
69984
|
let added = 0;
|
|
69950
69985
|
for (const issue of issues) {
|
|
69951
69986
|
if (seen.has(issue.id))
|
|
69952
69987
|
continue;
|
|
69988
|
+
if (failed.has(issue.id))
|
|
69989
|
+
continue;
|
|
69953
69990
|
if (queued.has(issue.id))
|
|
69954
69991
|
continue;
|
|
69955
69992
|
if (active.has(issue.id))
|
|
@@ -70040,6 +70077,10 @@ class AgentCoordinator {
|
|
|
70040
70077
|
this.state.processedIssueIds.push(issue.id);
|
|
70041
70078
|
this.deps.saveState(this.state);
|
|
70042
70079
|
}
|
|
70080
|
+
if (!ok && this.state && !this.state.failedIssueIds.includes(issue.id)) {
|
|
70081
|
+
this.state.failedIssueIds.push(issue.id);
|
|
70082
|
+
this.deps.saveState(this.state);
|
|
70083
|
+
}
|
|
70043
70084
|
this.notifyExited(issue, changeName, code);
|
|
70044
70085
|
this.deps.onWorkersChanged();
|
|
70045
70086
|
this.spawnNext();
|
|
@@ -70049,9 +70090,14 @@ class AgentCoordinator {
|
|
|
70049
70090
|
const updater = this.deps.updater;
|
|
70050
70091
|
if (!updater)
|
|
70051
70092
|
return;
|
|
70052
|
-
|
|
70093
|
+
const alreadyStarted = this.state?.startedIssueIds.includes(issue.id) ?? false;
|
|
70094
|
+
if (this.opts.postComments !== false && !alreadyStarted) {
|
|
70053
70095
|
try {
|
|
70054
70096
|
await updater.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${changeName}\``);
|
|
70097
|
+
if (this.state && !this.state.startedIssueIds.includes(issue.id)) {
|
|
70098
|
+
this.state.startedIssueIds.push(issue.id);
|
|
70099
|
+
await this.deps.saveState(this.state);
|
|
70100
|
+
}
|
|
70055
70101
|
} catch (err) {
|
|
70056
70102
|
this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
|
|
70057
70103
|
}
|
|
@@ -70066,7 +70112,9 @@ class AgentCoordinator {
|
|
|
70066
70112
|
return;
|
|
70067
70113
|
const ok = code === 0;
|
|
70068
70114
|
if (this.opts.postComments !== false) {
|
|
70069
|
-
const body = ok ? `\u2705 Ralph completed work on this issue. Change: \`${changeName}\`` : `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}
|
|
70115
|
+
const body = ok ? `\u2705 Ralph completed work on this issue. Change: \`${changeName}\`` : `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}\`
|
|
70116
|
+
|
|
70117
|
+
` + `This issue has been quarantined and will not be auto-resumed on the next poll. ` + `Inspect the worktree at \`.ralph/worktrees/${changeName}\`, fix the underlying ` + `failure (e.g. lint/typecheck), then run \`ralph clean --name ${changeName}\` to ` + `clear the quarantine and let the next poll re-pick the issue.`;
|
|
70070
70118
|
try {
|
|
70071
70119
|
await updater.postComment(issue, body);
|
|
70072
70120
|
} catch (err) {
|
|
@@ -70299,7 +70347,10 @@ var bunCmdRunner = {
|
|
|
70299
70347
|
const stderr = await new Response(proc.stderr).text();
|
|
70300
70348
|
const code = await proc.exited;
|
|
70301
70349
|
if (code !== 0) {
|
|
70302
|
-
const
|
|
70350
|
+
const firstStderrLine = stderr.trim().split(`
|
|
70351
|
+
`)[0] ?? "";
|
|
70352
|
+
const summary = firstStderrLine ? `: ${firstStderrLine}` : "";
|
|
70353
|
+
const err = new Error(`\`${cmd.join(" ")}\` exited ${code}${summary}`);
|
|
70303
70354
|
err.stderr = stderr;
|
|
70304
70355
|
err.code = code;
|
|
70305
70356
|
throw err;
|
|
@@ -70312,11 +70363,28 @@ function nextId() {
|
|
|
70312
70363
|
lineCounter += 1;
|
|
70313
70364
|
return `${Date.now()}-${lineCounter}`;
|
|
70314
70365
|
}
|
|
70366
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
70367
|
+
function fmtElapsed(ms) {
|
|
70368
|
+
const s = Math.floor(ms / 1000);
|
|
70369
|
+
if (s < 60)
|
|
70370
|
+
return `${s}s`;
|
|
70371
|
+
const m = Math.floor(s / 60);
|
|
70372
|
+
const rem = s % 60;
|
|
70373
|
+
if (m < 60)
|
|
70374
|
+
return `${m}m${rem.toString().padStart(2, "0")}s`;
|
|
70375
|
+
const h = Math.floor(m / 60);
|
|
70376
|
+
return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
|
|
70377
|
+
}
|
|
70315
70378
|
function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
70316
70379
|
const { exit } = use_app_default();
|
|
70317
70380
|
const [logs, setLogs] = import_react57.useState([]);
|
|
70318
70381
|
const [, setTick] = import_react57.useState(0);
|
|
70382
|
+
const [clock, setClock] = import_react57.useState(0);
|
|
70319
70383
|
const coordRef = import_react57.useRef(null);
|
|
70384
|
+
const workerMetaRef = import_react57.useRef(new Map);
|
|
70385
|
+
const nextPollAtRef = import_react57.useRef(0);
|
|
70386
|
+
const pollIntervalRef = import_react57.useRef(0);
|
|
70387
|
+
const [pollStatus, setPollStatus] = import_react57.useState({ state: "idle", lastFound: null, lastAdded: null, lastAt: null, filterDesc: "" });
|
|
70320
70388
|
function appendLog(text, color) {
|
|
70321
70389
|
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
70322
70390
|
}
|
|
@@ -70329,6 +70397,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70329
70397
|
appendLog(`agent mode \u2014 config: ${cfgPath}`, "gray");
|
|
70330
70398
|
const concurrency = args.concurrency || cfg.concurrency;
|
|
70331
70399
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
70400
|
+
pollIntervalRef.current = pollInterval;
|
|
70332
70401
|
appendLog(`concurrency=${concurrency} pollInterval=${pollInterval}s`, "gray");
|
|
70333
70402
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
70334
70403
|
if (!apiKey) {
|
|
@@ -70402,6 +70471,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70402
70471
|
issueByChange.set(changeName, issue);
|
|
70403
70472
|
if (workerBranch)
|
|
70404
70473
|
branchByChange.set(changeName, workerBranch);
|
|
70474
|
+
try {
|
|
70475
|
+
const s = await readAgentState(projectRoot);
|
|
70476
|
+
s.changeMeta[changeName] = { issueId: issue.id, identifier: issue.identifier };
|
|
70477
|
+
await writeAgentState(projectRoot, s);
|
|
70478
|
+
} catch (err) {
|
|
70479
|
+
appendLog(`! failed to record agent meta for ${changeName}: ${err.message}`, "yellow");
|
|
70480
|
+
}
|
|
70405
70481
|
if (cfg.setupScript) {
|
|
70406
70482
|
await runScript("setup", cfg.setupScript, workerCwd);
|
|
70407
70483
|
}
|
|
@@ -70447,8 +70523,14 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70447
70523
|
stderr: "ignore",
|
|
70448
70524
|
stdin: "ignore"
|
|
70449
70525
|
});
|
|
70526
|
+
workerMetaRef.current.set(changeName, {
|
|
70527
|
+
startedAt: Date.now(),
|
|
70528
|
+
statesDir: statesDirByChange.get(changeName) ?? statesDir,
|
|
70529
|
+
iter: 0
|
|
70530
|
+
});
|
|
70450
70531
|
const wantPr = args.createPr || cfg.createPrOnSuccess;
|
|
70451
70532
|
const CI_FAILED_EXIT = 70;
|
|
70533
|
+
const PR_FAILED_EXIT = 71;
|
|
70452
70534
|
const wrapped = proc.exited.then(async (code) => {
|
|
70453
70535
|
if (cfg.teardownScript) {
|
|
70454
70536
|
try {
|
|
@@ -70462,6 +70544,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70462
70544
|
const prIssue = issueByChange.get(changeName);
|
|
70463
70545
|
if (!branch || !prIssue) {
|
|
70464
70546
|
appendLog(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
70547
|
+
effectiveCode = PR_FAILED_EXIT;
|
|
70465
70548
|
} else {
|
|
70466
70549
|
try {
|
|
70467
70550
|
const pr = await createPullRequest({ cwd: cwd2, branch, issue: prIssue, base: cfg.prBaseBranch }, bunCmdRunner);
|
|
@@ -70521,7 +70604,10 @@ ${stamped}
|
|
|
70521
70604
|
}
|
|
70522
70605
|
}
|
|
70523
70606
|
} catch (err) {
|
|
70524
|
-
|
|
70607
|
+
const e = err;
|
|
70608
|
+
const detail = e.stderr?.trim() || e.message;
|
|
70609
|
+
appendLog(`! PR create failed for ${changeName}: ${detail}`, "red");
|
|
70610
|
+
effectiveCode = PR_FAILED_EXIT;
|
|
70525
70611
|
}
|
|
70526
70612
|
}
|
|
70527
70613
|
}
|
|
@@ -70539,6 +70625,7 @@ ${stamped}
|
|
|
70539
70625
|
statesDirByChange.delete(changeName);
|
|
70540
70626
|
branchByChange.delete(changeName);
|
|
70541
70627
|
issueByChange.delete(changeName);
|
|
70628
|
+
workerMetaRef.current.delete(changeName);
|
|
70542
70629
|
return effectiveCode;
|
|
70543
70630
|
});
|
|
70544
70631
|
return { exited: wrapped, kill: () => proc.kill() };
|
|
@@ -70591,15 +70678,25 @@ ${stamped}
|
|
|
70591
70678
|
});
|
|
70592
70679
|
coordRef.current = coord2;
|
|
70593
70680
|
await coord2.init();
|
|
70681
|
+
const filterDesc = `team=${filter2.team ?? "*"}, assignee=${filter2.assignee ?? "*"}, statuses=${filter2.statuses?.length ? filter2.statuses.join(",") : "open"}${filter2.labels?.length ? `, labels=${filter2.labels.join(",")}` : ""}`;
|
|
70594
70682
|
const tick = async () => {
|
|
70595
70683
|
if (cancelled)
|
|
70596
70684
|
return;
|
|
70597
|
-
|
|
70598
|
-
appendLog(`\u2026 polling Linear (${filterDesc})`);
|
|
70685
|
+
setPollStatus((p) => ({ ...p, state: "polling", filterDesc }));
|
|
70599
70686
|
const { found, added } = await coord2.pollOnce();
|
|
70600
|
-
appendLog(` found ${found} open, ${added} new (queue=${coord2.queuedCount})`);
|
|
70601
70687
|
if (cancelled)
|
|
70602
70688
|
return;
|
|
70689
|
+
if (added > 0) {
|
|
70690
|
+
appendLog(` ${added} new issue${added === 1 ? "" : "s"} queued (found ${found} open)`);
|
|
70691
|
+
}
|
|
70692
|
+
setPollStatus({
|
|
70693
|
+
state: "idle",
|
|
70694
|
+
lastFound: found,
|
|
70695
|
+
lastAdded: added,
|
|
70696
|
+
lastAt: Date.now(),
|
|
70697
|
+
filterDesc
|
|
70698
|
+
});
|
|
70699
|
+
nextPollAtRef.current = Date.now() + pollInterval * 1000;
|
|
70603
70700
|
pollTimer = setTimeout(tick, pollInterval * 1000);
|
|
70604
70701
|
};
|
|
70605
70702
|
tick();
|
|
@@ -70624,7 +70721,34 @@ ${stamped}
|
|
|
70624
70721
|
process.off("SIGTERM", onSig);
|
|
70625
70722
|
};
|
|
70626
70723
|
}, []);
|
|
70724
|
+
import_react57.useEffect(() => {
|
|
70725
|
+
let cancelled = false;
|
|
70726
|
+
const interval = setInterval(() => {
|
|
70727
|
+
if (cancelled)
|
|
70728
|
+
return;
|
|
70729
|
+
(async () => {
|
|
70730
|
+
for (const [changeName, meta] of workerMetaRef.current) {
|
|
70731
|
+
try {
|
|
70732
|
+
const file = Bun.file(join14(meta.statesDir, changeName, ".ralph-state.json"));
|
|
70733
|
+
if (await file.exists()) {
|
|
70734
|
+
const json = await file.json();
|
|
70735
|
+
meta.iter = json.iteration ?? meta.iter;
|
|
70736
|
+
}
|
|
70737
|
+
} catch {}
|
|
70738
|
+
}
|
|
70739
|
+
if (!cancelled)
|
|
70740
|
+
setClock((c) => c + 1);
|
|
70741
|
+
})();
|
|
70742
|
+
}, 1000);
|
|
70743
|
+
return () => {
|
|
70744
|
+
cancelled = true;
|
|
70745
|
+
clearInterval(interval);
|
|
70746
|
+
};
|
|
70747
|
+
}, []);
|
|
70627
70748
|
const coord = coordRef.current;
|
|
70749
|
+
const spinnerFrame = SPINNER_FRAMES[clock % SPINNER_FRAMES.length];
|
|
70750
|
+
const now2 = Date.now();
|
|
70751
|
+
const secsToNextPoll = nextPollAtRef.current ? Math.max(0, Math.ceil((nextPollAtRef.current - now2) / 1000)) : null;
|
|
70628
70752
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
70629
70753
|
flexDirection: "column",
|
|
70630
70754
|
children: [
|
|
@@ -70644,23 +70768,41 @@ ${stamped}
|
|
|
70644
70768
|
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70645
70769
|
dimColor: true,
|
|
70646
70770
|
children: [
|
|
70771
|
+
spinnerFrame,
|
|
70772
|
+
" ",
|
|
70773
|
+
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"
|
|
70774
|
+
]
|
|
70775
|
+
}, undefined, true, undefined, this),
|
|
70776
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70777
|
+
dimColor: true,
|
|
70778
|
+
children: [
|
|
70779
|
+
" ",
|
|
70647
70780
|
"workers active: ",
|
|
70648
70781
|
coord?.activeCount ?? 0,
|
|
70649
70782
|
" \xB7 queued: ",
|
|
70650
70783
|
coord?.queuedCount ?? 0
|
|
70651
70784
|
]
|
|
70652
70785
|
}, undefined, true, undefined, this),
|
|
70653
|
-
coord?.activeWorkers.map((w) =>
|
|
70654
|
-
|
|
70655
|
-
|
|
70656
|
-
|
|
70657
|
-
|
|
70658
|
-
|
|
70659
|
-
|
|
70660
|
-
|
|
70661
|
-
|
|
70662
|
-
|
|
70663
|
-
|
|
70786
|
+
coord?.activeWorkers.map((w) => {
|
|
70787
|
+
const meta = workerMetaRef.current.get(w.changeName);
|
|
70788
|
+
const elapsed = meta ? fmtElapsed(now2 - meta.startedAt) : "\u2013";
|
|
70789
|
+
const iter = meta?.iter ?? 0;
|
|
70790
|
+
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70791
|
+
color: "cyan",
|
|
70792
|
+
children: [
|
|
70793
|
+
" ",
|
|
70794
|
+
spinnerFrame,
|
|
70795
|
+
" ",
|
|
70796
|
+
w.issueIdentifier,
|
|
70797
|
+
" (",
|
|
70798
|
+
w.changeName,
|
|
70799
|
+
") \xB7 iter ",
|
|
70800
|
+
iter,
|
|
70801
|
+
" \xB7 ",
|
|
70802
|
+
elapsed
|
|
70803
|
+
]
|
|
70804
|
+
}, w.changeName, true, undefined, this);
|
|
70805
|
+
})
|
|
70664
70806
|
]
|
|
70665
70807
|
}, undefined, true, undefined, this)
|
|
70666
70808
|
]
|
|
@@ -70811,7 +70953,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
70811
70953
|
switch (args.mode) {
|
|
70812
70954
|
case "list":
|
|
70813
70955
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskList, {
|
|
70814
|
-
statesDir
|
|
70956
|
+
statesDir,
|
|
70957
|
+
projectRoot
|
|
70815
70958
|
}, undefined, false, undefined, this);
|
|
70816
70959
|
case "agent":
|
|
70817
70960
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AgentMode, {
|
|
@@ -70847,6 +70990,10 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
70847
70990
|
children: "Initialized openspec directory"
|
|
70848
70991
|
}, undefined, false, undefined, this)
|
|
70849
70992
|
}, undefined, false, undefined, this);
|
|
70993
|
+
case "clean":
|
|
70994
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
|
|
70995
|
+
children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {}, undefined, false, undefined, this)
|
|
70996
|
+
}, undefined, false, undefined, this);
|
|
70850
70997
|
case "task": {
|
|
70851
70998
|
if (!args.name) {
|
|
70852
70999
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
@@ -70930,6 +71077,77 @@ try {
|
|
|
70930
71077
|
cwd: process.cwd()
|
|
70931
71078
|
});
|
|
70932
71079
|
}
|
|
71080
|
+
if (args.mode === "clean") {
|
|
71081
|
+
if (!args.name) {
|
|
71082
|
+
process.stderr.write(`Error: --name is required for clean mode
|
|
71083
|
+
`);
|
|
71084
|
+
process.exit(1);
|
|
71085
|
+
}
|
|
71086
|
+
const worktreeDir = join17(projectRoot, ".ralph", "worktrees", args.name);
|
|
71087
|
+
const changeDir = join17(tasksDir, args.name);
|
|
71088
|
+
const stateDir = join17(statesDir, args.name);
|
|
71089
|
+
const branch = `ralph/${args.name}`;
|
|
71090
|
+
const removed = [];
|
|
71091
|
+
if (await exists(worktreeDir)) {
|
|
71092
|
+
const proc = Bun.spawn({
|
|
71093
|
+
cmd: ["git", "worktree", "remove", "--force", worktreeDir],
|
|
71094
|
+
cwd: projectRoot,
|
|
71095
|
+
stdout: "pipe",
|
|
71096
|
+
stderr: "pipe"
|
|
71097
|
+
});
|
|
71098
|
+
const code = await proc.exited;
|
|
71099
|
+
if (code !== 0) {
|
|
71100
|
+
await rm(worktreeDir, { recursive: true, force: true });
|
|
71101
|
+
await Bun.spawn({
|
|
71102
|
+
cmd: ["git", "worktree", "prune"],
|
|
71103
|
+
cwd: projectRoot,
|
|
71104
|
+
stdout: "ignore",
|
|
71105
|
+
stderr: "ignore"
|
|
71106
|
+
}).exited;
|
|
71107
|
+
}
|
|
71108
|
+
removed.push(`worktree ${worktreeDir}`);
|
|
71109
|
+
}
|
|
71110
|
+
const branchProc = Bun.spawn({
|
|
71111
|
+
cmd: ["git", "branch", "-D", branch],
|
|
71112
|
+
cwd: projectRoot,
|
|
71113
|
+
stdout: "ignore",
|
|
71114
|
+
stderr: "ignore"
|
|
71115
|
+
});
|
|
71116
|
+
if (await branchProc.exited === 0)
|
|
71117
|
+
removed.push(`branch ${branch}`);
|
|
71118
|
+
if (await exists(changeDir)) {
|
|
71119
|
+
await rm(changeDir, { recursive: true, force: true });
|
|
71120
|
+
removed.push(`openspec change ${changeDir}`);
|
|
71121
|
+
}
|
|
71122
|
+
if (await exists(stateDir)) {
|
|
71123
|
+
await rm(stateDir, { recursive: true, force: true });
|
|
71124
|
+
removed.push(`task state ${stateDir}`);
|
|
71125
|
+
}
|
|
71126
|
+
try {
|
|
71127
|
+
const agentState = await readAgentState(projectRoot);
|
|
71128
|
+
const meta = agentState.changeMeta[args.name];
|
|
71129
|
+
if (meta) {
|
|
71130
|
+
agentState.processedIssueIds = agentState.processedIssueIds.filter((id) => id !== meta.issueId);
|
|
71131
|
+
agentState.startedIssueIds = agentState.startedIssueIds.filter((id) => id !== meta.issueId);
|
|
71132
|
+
agentState.failedIssueIds = agentState.failedIssueIds.filter((id) => id !== meta.issueId);
|
|
71133
|
+
delete agentState.changeMeta[args.name];
|
|
71134
|
+
await writeAgentState(projectRoot, agentState);
|
|
71135
|
+
removed.push(`agent-state entry for ${meta.identifier} (${meta.issueId})`);
|
|
71136
|
+
}
|
|
71137
|
+
} catch {}
|
|
71138
|
+
if (removed.length === 0) {
|
|
71139
|
+
process.stdout.write(`Nothing to clean for '${args.name}'
|
|
71140
|
+
`);
|
|
71141
|
+
} else {
|
|
71142
|
+
process.stdout.write(`Cleaned '${args.name}':
|
|
71143
|
+
`);
|
|
71144
|
+
for (const r of removed)
|
|
71145
|
+
process.stdout.write(` \u2713 removed ${r}
|
|
71146
|
+
`);
|
|
71147
|
+
}
|
|
71148
|
+
await shutdown();
|
|
71149
|
+
process.exit(0);
|
|
71150
|
+
}
|
|
70933
71151
|
if (args.mode === "task" && args.name) {
|
|
70934
71152
|
await mkdir3(join17(statesDir, args.name), { recursive: true });
|
|
70935
71153
|
await mkdir3(join17(tasksDir, args.name), { recursive: true });
|