@neriros/ralphy 2.9.4 → 2.10.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 +113 -41
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -56407,7 +56407,7 @@ function log(msg) {
|
|
|
56407
56407
|
// package.json
|
|
56408
56408
|
var package_default = {
|
|
56409
56409
|
name: "@neriros/ralphy",
|
|
56410
|
-
version: "2.
|
|
56410
|
+
version: "2.10.1",
|
|
56411
56411
|
description: "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
|
|
56412
56412
|
keywords: [
|
|
56413
56413
|
"agent",
|
|
@@ -56538,7 +56538,7 @@ var HELP_TEXT = [
|
|
|
56538
56538
|
" --linear-label <name> Filter by label name (repeatable, any-of)",
|
|
56539
56539
|
" --poll-interval <s> Seconds between Linear polls (default: 60)",
|
|
56540
56540
|
" --concurrency <n> Max concurrent task loops (default: 1)",
|
|
56541
|
-
" --worktree Run each task in its own git worktree (
|
|
56541
|
+
" --worktree Run each task in its own git worktree (~/.ralph/<project>/worktrees/<name>)",
|
|
56542
56542
|
" --in-progress-status <name> Linear status to set when work starts on an issue",
|
|
56543
56543
|
" --done-status <name> Linear status to set when work completes successfully",
|
|
56544
56544
|
" --done-label <name> Linear label to add when work completes successfully",
|
|
@@ -70178,25 +70178,82 @@ async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
|
70178
70178
|
|
|
70179
70179
|
// apps/cli/src/agent/state.ts
|
|
70180
70180
|
import { join as join10 } from "path";
|
|
70181
|
+
var TaskStateSchema = exports_external.enum(["started", "processed", "failed"]);
|
|
70182
|
+
var TaskEntrySchema = exports_external.object({
|
|
70183
|
+
issueId: exports_external.string(),
|
|
70184
|
+
identifier: exports_external.string(),
|
|
70185
|
+
state: TaskStateSchema,
|
|
70186
|
+
changeName: exports_external.string().optional(),
|
|
70187
|
+
startedAt: exports_external.string().optional(),
|
|
70188
|
+
finishedAt: exports_external.string().optional(),
|
|
70189
|
+
exitCode: exports_external.number().optional(),
|
|
70190
|
+
commentPosted: exports_external.boolean().optional()
|
|
70191
|
+
});
|
|
70181
70192
|
var AgentStateSchema = exports_external.object({
|
|
70193
|
+
tasks: exports_external.record(exports_external.string(), TaskEntrySchema).default({}),
|
|
70194
|
+
lastPollAt: exports_external.string().nullable().default(null)
|
|
70195
|
+
});
|
|
70196
|
+
var LegacyAgentStateSchema = exports_external.object({
|
|
70182
70197
|
processedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
70183
70198
|
startedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
70184
70199
|
failedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
70185
70200
|
lastPollAt: exports_external.string().nullable().default(null),
|
|
70186
|
-
changeMeta: exports_external.record(exports_external.string(), exports_external.object({
|
|
70187
|
-
|
|
70188
|
-
|
|
70189
|
-
|
|
70190
|
-
|
|
70201
|
+
changeMeta: exports_external.record(exports_external.string(), exports_external.object({ issueId: exports_external.string(), identifier: exports_external.string() })).default({})
|
|
70202
|
+
}).partial();
|
|
70203
|
+
function migrateLegacy(raw) {
|
|
70204
|
+
const parsed = LegacyAgentStateSchema.safeParse(raw);
|
|
70205
|
+
if (!parsed.success)
|
|
70206
|
+
return AgentStateSchema.parse({});
|
|
70207
|
+
const legacy = parsed.data;
|
|
70208
|
+
const tasks = {};
|
|
70209
|
+
const byIssueId = new Map;
|
|
70210
|
+
for (const [changeName, meta] of Object.entries(legacy.changeMeta ?? {})) {
|
|
70211
|
+
byIssueId.set(meta.issueId, { identifier: meta.identifier, changeName });
|
|
70212
|
+
}
|
|
70213
|
+
const fold = (ids, state) => {
|
|
70214
|
+
for (const issueId of ids ?? []) {
|
|
70215
|
+
const found = byIssueId.get(issueId);
|
|
70216
|
+
if (!found)
|
|
70217
|
+
continue;
|
|
70218
|
+
tasks[found.identifier] = {
|
|
70219
|
+
issueId,
|
|
70220
|
+
identifier: found.identifier,
|
|
70221
|
+
state,
|
|
70222
|
+
changeName: found.changeName
|
|
70223
|
+
};
|
|
70224
|
+
}
|
|
70225
|
+
};
|
|
70226
|
+
fold(legacy.startedIssueIds, "started");
|
|
70227
|
+
fold(legacy.processedIssueIds, "processed");
|
|
70228
|
+
fold(legacy.failedIssueIds, "failed");
|
|
70229
|
+
for (const issueId of legacy.startedIssueIds ?? []) {
|
|
70230
|
+
const found = byIssueId.get(issueId);
|
|
70231
|
+
if (!found)
|
|
70232
|
+
continue;
|
|
70233
|
+
const entry = tasks[found.identifier];
|
|
70234
|
+
if (entry)
|
|
70235
|
+
entry.commentPosted = true;
|
|
70236
|
+
}
|
|
70237
|
+
return { tasks, lastPollAt: legacy.lastPollAt ?? null };
|
|
70238
|
+
}
|
|
70191
70239
|
function statePath(projectRoot) {
|
|
70192
70240
|
return join10(projectRoot, ".ralph", "agent-state.json");
|
|
70193
70241
|
}
|
|
70242
|
+
function looksLegacy(raw) {
|
|
70243
|
+
if (typeof raw !== "object" || raw === null)
|
|
70244
|
+
return false;
|
|
70245
|
+
const r = raw;
|
|
70246
|
+
return "processedIssueIds" in r || "startedIssueIds" in r || "failedIssueIds" in r || "changeMeta" in r;
|
|
70247
|
+
}
|
|
70194
70248
|
async function readAgentState(projectRoot) {
|
|
70195
70249
|
const file = Bun.file(statePath(projectRoot));
|
|
70196
70250
|
if (!await file.exists()) {
|
|
70197
70251
|
return AgentStateSchema.parse({});
|
|
70198
70252
|
}
|
|
70199
|
-
|
|
70253
|
+
const raw = await file.json();
|
|
70254
|
+
if (looksLegacy(raw))
|
|
70255
|
+
return migrateLegacy(raw);
|
|
70256
|
+
return AgentStateSchema.parse(raw);
|
|
70200
70257
|
}
|
|
70201
70258
|
async function writeAgentState(projectRoot, state) {
|
|
70202
70259
|
await Bun.write(statePath(projectRoot), JSON.stringify(state, null, 2) + `
|
|
@@ -70371,15 +70428,19 @@ class AgentCoordinator {
|
|
|
70371
70428
|
return { found: 0, added: 0 };
|
|
70372
70429
|
}
|
|
70373
70430
|
const state = this.state;
|
|
70374
|
-
const
|
|
70375
|
-
const
|
|
70431
|
+
const tasksByIssueId = new Map;
|
|
70432
|
+
for (const entry of Object.values(state.tasks)) {
|
|
70433
|
+
tasksByIssueId.set(entry.issueId, entry);
|
|
70434
|
+
}
|
|
70435
|
+
const isProcessed = (id) => tasksByIssueId.get(id)?.state === "processed";
|
|
70436
|
+
const isFailed = (id) => tasksByIssueId.get(id)?.state === "failed";
|
|
70376
70437
|
const queued = new Set(this.queue.map((i) => i.id));
|
|
70377
70438
|
const active = new Set(this.workers.map((w) => w.issueId));
|
|
70378
70439
|
let added = 0;
|
|
70379
70440
|
for (const issue of issues) {
|
|
70380
|
-
if (
|
|
70441
|
+
if (isProcessed(issue.id))
|
|
70381
70442
|
continue;
|
|
70382
|
-
if (
|
|
70443
|
+
if (isFailed(issue.id))
|
|
70383
70444
|
continue;
|
|
70384
70445
|
if (queued.has(issue.id))
|
|
70385
70446
|
continue;
|
|
@@ -70387,7 +70448,7 @@ class AgentCoordinator {
|
|
|
70387
70448
|
continue;
|
|
70388
70449
|
if (this.pendingIds.has(issue.id))
|
|
70389
70450
|
continue;
|
|
70390
|
-
const blocker = issue.blockedByIds.find((bid) => !
|
|
70451
|
+
const blocker = issue.blockedByIds.find((bid) => !isProcessed(bid));
|
|
70391
70452
|
if (blocker !== undefined) {
|
|
70392
70453
|
this.deps.onLog(` \u23F8 ${issue.identifier} skipped \u2014 blocked by unresolved dependency`, "yellow");
|
|
70393
70454
|
continue;
|
|
@@ -70445,6 +70506,18 @@ class AgentCoordinator {
|
|
|
70445
70506
|
this.launchWorker(issue);
|
|
70446
70507
|
}
|
|
70447
70508
|
}
|
|
70509
|
+
upsertTask(issue, patch) {
|
|
70510
|
+
if (!this.state)
|
|
70511
|
+
return;
|
|
70512
|
+
const existing = this.state.tasks[issue.identifier];
|
|
70513
|
+
this.state.tasks[issue.identifier] = {
|
|
70514
|
+
issueId: issue.id,
|
|
70515
|
+
identifier: issue.identifier,
|
|
70516
|
+
state: existing?.state ?? "started",
|
|
70517
|
+
...existing,
|
|
70518
|
+
...patch
|
|
70519
|
+
};
|
|
70520
|
+
}
|
|
70448
70521
|
async launchWorker(issue) {
|
|
70449
70522
|
let changeName;
|
|
70450
70523
|
try {
|
|
@@ -70459,6 +70532,14 @@ class AgentCoordinator {
|
|
|
70459
70532
|
this.pendingIds.delete(issue.id);
|
|
70460
70533
|
return;
|
|
70461
70534
|
}
|
|
70535
|
+
if (this.state) {
|
|
70536
|
+
this.upsertTask(issue, {
|
|
70537
|
+
state: "started",
|
|
70538
|
+
changeName,
|
|
70539
|
+
startedAt: this.state.tasks[issue.identifier]?.startedAt ?? new Date().toISOString()
|
|
70540
|
+
});
|
|
70541
|
+
this.deps.saveState(this.state);
|
|
70542
|
+
}
|
|
70462
70543
|
this.deps.onLog(`\u25B6 ${issue.identifier} \u2192 ${changeName} (worker started)`, "cyan");
|
|
70463
70544
|
const handle = this.deps.spawnWorker(changeName, issue);
|
|
70464
70545
|
const worker = {
|
|
@@ -70479,12 +70560,12 @@ class AgentCoordinator {
|
|
|
70479
70560
|
this.workers.splice(idx, 1);
|
|
70480
70561
|
const ok = code === 0;
|
|
70481
70562
|
this.deps.onLog(`${ok ? "\u2713" : "\u2717"} ${issue.identifier} \u2192 ${changeName} exited (code ${code})`, ok ? "green" : "red");
|
|
70482
|
-
if (
|
|
70483
|
-
this.
|
|
70484
|
-
|
|
70485
|
-
|
|
70486
|
-
|
|
70487
|
-
|
|
70563
|
+
if (this.state) {
|
|
70564
|
+
this.upsertTask(issue, {
|
|
70565
|
+
state: ok ? "processed" : "failed",
|
|
70566
|
+
finishedAt: new Date().toISOString(),
|
|
70567
|
+
exitCode: code
|
|
70568
|
+
});
|
|
70488
70569
|
this.deps.saveState(this.state);
|
|
70489
70570
|
}
|
|
70490
70571
|
this.notifyExited(issue, changeName, code);
|
|
@@ -70496,12 +70577,12 @@ class AgentCoordinator {
|
|
|
70496
70577
|
const updater = this.deps.updater;
|
|
70497
70578
|
if (!updater)
|
|
70498
70579
|
return;
|
|
70499
|
-
const
|
|
70500
|
-
if (this.opts.postComments !== false && !
|
|
70580
|
+
const alreadyCommented = this.state?.tasks[issue.identifier]?.commentPosted === true;
|
|
70581
|
+
if (this.opts.postComments !== false && !alreadyCommented) {
|
|
70501
70582
|
try {
|
|
70502
70583
|
await updater.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${changeName}\``);
|
|
70503
|
-
if (this.state
|
|
70504
|
-
this.
|
|
70584
|
+
if (this.state) {
|
|
70585
|
+
this.upsertTask(issue, { commentPosted: true });
|
|
70505
70586
|
await this.deps.saveState(this.state);
|
|
70506
70587
|
}
|
|
70507
70588
|
} catch (err) {
|
|
@@ -70520,7 +70601,7 @@ class AgentCoordinator {
|
|
|
70520
70601
|
if (this.opts.postComments !== false) {
|
|
70521
70602
|
const body = ok ? `\u2705 Ralph completed work on this issue. Change: \`${changeName}\`` : `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}\`
|
|
70522
70603
|
|
|
70523
|
-
` + `This issue has been quarantined and will not be auto-resumed on the next poll. ` + `Inspect the worktree at
|
|
70604
|
+
` + `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 (e.g. lint/typecheck), then run \`ralph clean --name ${changeName}\` to ` + `clear the quarantine and let the next poll re-pick the issue.`;
|
|
70524
70605
|
try {
|
|
70525
70606
|
await updater.postComment(issue, body);
|
|
70526
70607
|
} catch (err) {
|
|
@@ -70577,9 +70658,10 @@ class AgentCoordinator {
|
|
|
70577
70658
|
}
|
|
70578
70659
|
|
|
70579
70660
|
// apps/cli/src/agent/worktree.ts
|
|
70580
|
-
import { join as join13 } from "path";
|
|
70661
|
+
import { basename, join as join13 } from "path";
|
|
70662
|
+
import { homedir as homedir2 } from "os";
|
|
70581
70663
|
function worktreesDir(projectRoot) {
|
|
70582
|
-
return join13(
|
|
70664
|
+
return join13(homedir2(), ".ralph", basename(projectRoot), "worktrees");
|
|
70583
70665
|
}
|
|
70584
70666
|
function branchForChange(changeName) {
|
|
70585
70667
|
return `ralph/${changeName}`;
|
|
@@ -70936,13 +71018,6 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70936
71018
|
issueByChange.set(changeName, issue);
|
|
70937
71019
|
if (workerBranch)
|
|
70938
71020
|
branchByChange.set(changeName, workerBranch);
|
|
70939
|
-
try {
|
|
70940
|
-
const s = await readAgentState(projectRoot);
|
|
70941
|
-
s.changeMeta[changeName] = { issueId: issue.id, identifier: issue.identifier };
|
|
70942
|
-
await writeAgentState(projectRoot, s);
|
|
70943
|
-
} catch (err) {
|
|
70944
|
-
appendLog(`! failed to record agent meta for ${changeName}: ${err.message}`, "yellow");
|
|
70945
|
-
}
|
|
70946
71021
|
if (cfg.setupScript) {
|
|
70947
71022
|
await runScript("setup", cfg.setupScript, workerCwd);
|
|
70948
71023
|
}
|
|
@@ -71648,7 +71723,7 @@ try {
|
|
|
71648
71723
|
`);
|
|
71649
71724
|
process.exit(1);
|
|
71650
71725
|
}
|
|
71651
|
-
const worktreeDir = join17(projectRoot,
|
|
71726
|
+
const worktreeDir = join17(worktreesDir(projectRoot), args.name);
|
|
71652
71727
|
const changeDir = join17(tasksDir, args.name);
|
|
71653
71728
|
const stateDir = join17(statesDir, args.name);
|
|
71654
71729
|
const branch = `ralph/${args.name}`;
|
|
@@ -71690,14 +71765,11 @@ try {
|
|
|
71690
71765
|
}
|
|
71691
71766
|
try {
|
|
71692
71767
|
const agentState = await readAgentState(projectRoot);
|
|
71693
|
-
const
|
|
71694
|
-
if (
|
|
71695
|
-
|
|
71696
|
-
agentState.startedIssueIds = agentState.startedIssueIds.filter((id) => id !== meta.issueId);
|
|
71697
|
-
agentState.failedIssueIds = agentState.failedIssueIds.filter((id) => id !== meta.issueId);
|
|
71698
|
-
delete agentState.changeMeta[args.name];
|
|
71768
|
+
const entry = Object.values(agentState.tasks).find((t) => t.changeName === args.name);
|
|
71769
|
+
if (entry) {
|
|
71770
|
+
delete agentState.tasks[entry.identifier];
|
|
71699
71771
|
await writeAgentState(projectRoot, agentState);
|
|
71700
|
-
removed.push(`agent-state entry for ${
|
|
71772
|
+
removed.push(`agent-state entry for ${entry.identifier} (${entry.issueId})`);
|
|
71701
71773
|
}
|
|
71702
71774
|
} catch {}
|
|
71703
71775
|
if (removed.length === 0) {
|