@neriros/ralphy 2.13.14 → 2.14.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/dist/cli/index.js +457 -268
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -50837,8 +50837,8 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50837
50837
|
});
|
|
50838
50838
|
|
|
50839
50839
|
// apps/cli/src/index.ts
|
|
50840
|
-
import { resolve as resolve2, join as join19, dirname as
|
|
50841
|
-
import { exists as exists2, mkdir as
|
|
50840
|
+
import { resolve as resolve2, join as join19, dirname as dirname6 } from "path";
|
|
50841
|
+
import { exists as exists2, mkdir as mkdir6, rm } from "fs/promises";
|
|
50842
50842
|
|
|
50843
50843
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
50844
50844
|
import { Stream } from "stream";
|
|
@@ -56116,6 +56116,12 @@ function Static(props) {
|
|
|
56116
56116
|
}
|
|
56117
56117
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/components/Transform.js
|
|
56118
56118
|
var import_react12 = __toESM(require_react(), 1);
|
|
56119
|
+
function Transform({ children, transform: transform2 }) {
|
|
56120
|
+
if (children === undefined || children === null) {
|
|
56121
|
+
return null;
|
|
56122
|
+
}
|
|
56123
|
+
return import_react12.default.createElement("ink-text", { style: { flexGrow: 0, flexShrink: 1, flexDirection: "row" }, internal_transform: transform2 }, children);
|
|
56124
|
+
}
|
|
56119
56125
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/components/Newline.js
|
|
56120
56126
|
var import_react13 = __toESM(require_react(), 1);
|
|
56121
56127
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/components/Spacer.js
|
|
@@ -56411,10 +56417,13 @@ function log(msg) {
|
|
|
56411
56417
|
import { readFileSync as readFileSync2 } from "fs";
|
|
56412
56418
|
import { resolve } from "path";
|
|
56413
56419
|
function getVersion() {
|
|
56420
|
+
try {
|
|
56421
|
+
if ("2.14.0")
|
|
56422
|
+
return "2.14.0";
|
|
56423
|
+
} catch {}
|
|
56414
56424
|
const dirsToTry = [];
|
|
56415
56425
|
try {
|
|
56416
|
-
|
|
56417
|
-
dirsToTry.push(cliDir);
|
|
56426
|
+
dirsToTry.push(import.meta.dir);
|
|
56418
56427
|
} catch {}
|
|
56419
56428
|
dirsToTry.push(process.cwd());
|
|
56420
56429
|
for (const startDir of dirsToTry) {
|
|
@@ -70108,7 +70117,9 @@ function TaskLoop({ opts }) {
|
|
|
70108
70117
|
|
|
70109
70118
|
// apps/cli/src/components/AgentMode.tsx
|
|
70110
70119
|
var import_react57 = __toESM(require_react(), 1);
|
|
70111
|
-
import { join as join16, relative } from "path";
|
|
70120
|
+
import { join as join16, relative, dirname as dirname4 } from "path";
|
|
70121
|
+
import { homedir as homedir3 } from "os";
|
|
70122
|
+
import { appendFile, mkdir as mkdir4 } from "fs/promises";
|
|
70112
70123
|
|
|
70113
70124
|
// apps/cli/src/agent/config.ts
|
|
70114
70125
|
import { join as join10 } from "path";
|
|
@@ -70279,45 +70290,34 @@ var DEFAULT_CONFIG_TEMPLATE = `{
|
|
|
70279
70290
|
// Post a progress update every N iterations. 0 disables. Requires postComments.
|
|
70280
70291
|
"updateEveryIterations": 10,
|
|
70281
70292
|
|
|
70282
|
-
// ---------------------------------------------------------------------------
|
|
70283
|
-
// Linear indicators \u2014 COMMENTED OUT BY DEFAULT
|
|
70284
|
-
//
|
|
70285
70293
|
// Indicators map Ralph lifecycle events to Linear labels/statuses.
|
|
70286
|
-
// WARNING:
|
|
70287
|
-
//
|
|
70288
|
-
|
|
70289
|
-
|
|
70290
|
-
|
|
70291
|
-
|
|
70292
|
-
|
|
70293
|
-
|
|
70294
|
-
|
|
70295
|
-
|
|
70296
|
-
|
|
70297
|
-
|
|
70298
|
-
|
|
70299
|
-
|
|
70300
|
-
|
|
70301
|
-
|
|
70302
|
-
|
|
70303
|
-
|
|
70304
|
-
|
|
70305
|
-
|
|
70306
|
-
|
|
70307
|
-
|
|
70308
|
-
|
|
70309
|
-
|
|
70310
|
-
|
|
70311
|
-
|
|
70312
|
-
|
|
70313
|
-
// "setConflicted": { "type": "label", "value": "ralph:conflict" },
|
|
70314
|
-
//
|
|
70315
|
-
// // Label-only marker(s) removed once the conflict is fixed.
|
|
70316
|
-
// // Note: only label-typed markers are valid here \u2014 status removal is not supported.
|
|
70317
|
-
// "clearConflicted": { "type": "label", "value": "ralph:conflict" }
|
|
70318
|
-
// }
|
|
70319
|
-
// ---------------------------------------------------------------------------
|
|
70320
|
-
"indicators": {}
|
|
70294
|
+
// WARNING: activating indicators will query AND mutate your Linear workspace.
|
|
70295
|
+
// Uncomment each entry after confirming the label/status names match your workspace.
|
|
70296
|
+
"indicators": {
|
|
70297
|
+
// Issues to pick up (any-of filter \u2014 Ralph will start working on these).
|
|
70298
|
+
// "getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
|
|
70299
|
+
|
|
70300
|
+
// Issues already in flight (resume after restart).
|
|
70301
|
+
// "getInProgress": { "filter": [{ "type": "label", "value": "ralph:in-progress" }] },
|
|
70302
|
+
|
|
70303
|
+
// Issues whose PR has a merge conflict (Ralph will attempt a re-fix run).
|
|
70304
|
+
// "getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflict" }] },
|
|
70305
|
+
|
|
70306
|
+
// Applied when Ralph picks up an issue.
|
|
70307
|
+
// "setInProgress": { "type": "label", "value": "ralph:in-progress" },
|
|
70308
|
+
|
|
70309
|
+
// Applied on clean success.
|
|
70310
|
+
// "setDone": { "type": "status", "value": "In Review" },
|
|
70311
|
+
|
|
70312
|
+
// Applied when the task exits with an error (quarantine signal).
|
|
70313
|
+
// "setError": { "type": "label", "value": "ralph:error" },
|
|
70314
|
+
|
|
70315
|
+
// Applied when a PR merge conflict is detected.
|
|
70316
|
+
// "setConflicted": { "type": "label", "value": "ralph:conflict" },
|
|
70317
|
+
|
|
70318
|
+
// Label removed once the conflict is fixed (status removal is not supported here).
|
|
70319
|
+
// "clearConflicted": { "type": "label", "value": "ralph:conflict" }
|
|
70320
|
+
}
|
|
70321
70321
|
}
|
|
70322
70322
|
}
|
|
70323
70323
|
`;
|
|
@@ -70529,14 +70529,19 @@ async function fetchTeamIdByKey(apiKey, teamKey) {
|
|
|
70529
70529
|
});
|
|
70530
70530
|
return data.teams.nodes[0]?.id ?? null;
|
|
70531
70531
|
}
|
|
70532
|
-
async function createIssueLabel(apiKey, teamId, name) {
|
|
70533
|
-
const mutation = `mutation CreateLabel($teamId: String!, $name: String!) {
|
|
70532
|
+
async function createIssueLabel(apiKey, teamId, name, parentId) {
|
|
70533
|
+
const mutation = parentId ? `mutation CreateLabel($teamId: String!, $name: String!, $parentId: String!) {
|
|
70534
|
+
issueLabelCreate(input: { teamId: $teamId, name: $name, parentId: $parentId }) {
|
|
70535
|
+
success
|
|
70536
|
+
issueLabel { id }
|
|
70537
|
+
}
|
|
70538
|
+
}` : `mutation CreateLabel($teamId: String!, $name: String!) {
|
|
70534
70539
|
issueLabelCreate(input: { teamId: $teamId, name: $name }) {
|
|
70535
70540
|
success
|
|
70536
70541
|
issueLabel { id }
|
|
70537
70542
|
}
|
|
70538
70543
|
}`;
|
|
70539
|
-
const data = await linearRequest(apiKey, mutation, { teamId, name });
|
|
70544
|
+
const data = await linearRequest(apiKey, mutation, parentId ? { teamId, name, parentId } : { teamId, name });
|
|
70540
70545
|
return data.issueLabelCreate.issueLabel?.id ?? null;
|
|
70541
70546
|
}
|
|
70542
70547
|
async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
@@ -70789,7 +70794,7 @@ class AgentCoordinator {
|
|
|
70789
70794
|
this.pendingIds.delete(issue.id);
|
|
70790
70795
|
return;
|
|
70791
70796
|
}
|
|
70792
|
-
if (mode
|
|
70797
|
+
if (mode !== "resume" && this.opts.setInProgress) {
|
|
70793
70798
|
try {
|
|
70794
70799
|
await this.deps.applyIndicator(issue, this.opts.setInProgress);
|
|
70795
70800
|
} catch (err) {
|
|
@@ -70895,6 +70900,11 @@ class AgentCoordinator {
|
|
|
70895
70900
|
error: err.message
|
|
70896
70901
|
});
|
|
70897
70902
|
}
|
|
70903
|
+
if (this.opts.setInProgress) {
|
|
70904
|
+
try {
|
|
70905
|
+
await this.deps.removeIndicator(issue, this.opts.setInProgress);
|
|
70906
|
+
} catch {}
|
|
70907
|
+
}
|
|
70898
70908
|
}
|
|
70899
70909
|
} else if (this.opts.setError) {
|
|
70900
70910
|
try {
|
|
@@ -70907,6 +70917,11 @@ class AgentCoordinator {
|
|
|
70907
70917
|
error: err.message
|
|
70908
70918
|
});
|
|
70909
70919
|
}
|
|
70920
|
+
if (this.opts.setInProgress) {
|
|
70921
|
+
try {
|
|
70922
|
+
await this.deps.removeIndicator(issue, this.opts.setInProgress);
|
|
70923
|
+
} catch {}
|
|
70924
|
+
}
|
|
70910
70925
|
}
|
|
70911
70926
|
}
|
|
70912
70927
|
stop() {
|
|
@@ -71134,6 +71149,7 @@ async function createPullRequest(input, runner) {
|
|
|
71134
71149
|
// apps/cli/src/agent/ci.ts
|
|
71135
71150
|
var PR_CHECKS_FIELDS = "name,bucket,link,workflow,event";
|
|
71136
71151
|
var TRANSIENT_GH_RE = /HTTP 5\d\d|Gateway Timeout|Bad Gateway|Service Unavailable|connection reset|ECONNRESET|ETIMEDOUT|getaddrinfo|EAI_AGAIN|could not resolve host/i;
|
|
71152
|
+
var NO_CHECKS_RE = /no checks reported/i;
|
|
71137
71153
|
var GH_RETRY_DELAYS = [5000, 15000, 45000];
|
|
71138
71154
|
async function runGhWithRetry(cmd, runner, cwd2, onRetry, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
71139
71155
|
let lastErr;
|
|
@@ -71158,7 +71174,18 @@ ${e.stdout ?? ""}`;
|
|
|
71158
71174
|
throw lastErr;
|
|
71159
71175
|
}
|
|
71160
71176
|
async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry) {
|
|
71161
|
-
|
|
71177
|
+
let out;
|
|
71178
|
+
try {
|
|
71179
|
+
out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
|
|
71180
|
+
} catch (err) {
|
|
71181
|
+
const e = err;
|
|
71182
|
+
const blob = `${e.message}
|
|
71183
|
+
${e.stderr ?? ""}
|
|
71184
|
+
${e.stdout ?? ""}`;
|
|
71185
|
+
if (NO_CHECKS_RE.test(blob))
|
|
71186
|
+
return { bucket: "pass", failedRunIds: [] };
|
|
71187
|
+
throw err;
|
|
71188
|
+
}
|
|
71162
71189
|
const checks = JSON.parse(out.stdout || "[]").filter((c) => c.bucket !== "skipping");
|
|
71163
71190
|
if (checks.some((c) => c.bucket === "pending")) {
|
|
71164
71191
|
return { bucket: "pending", failedRunIds: [] };
|
|
@@ -71261,6 +71288,261 @@ async function reactivateState(stateFilePath, log2, changeName) {
|
|
|
71261
71288
|
log2(`! could not reactivate state for ${changeName}: ${err.message}`, "yellow");
|
|
71262
71289
|
}
|
|
71263
71290
|
}
|
|
71291
|
+
async function runWorkerWithFixTask(ctx, heading, body) {
|
|
71292
|
+
try {
|
|
71293
|
+
await prependFixTask(join14(ctx.changeDir, "tasks.md"), heading, body);
|
|
71294
|
+
} catch (err) {
|
|
71295
|
+
ctx.log(`! could not prepend fix task: ${err.message}`, "red");
|
|
71296
|
+
return 1;
|
|
71297
|
+
}
|
|
71298
|
+
await reactivateState(ctx.stateFilePath, ctx.log, ctx.changeName);
|
|
71299
|
+
return ctx.respawnWorker();
|
|
71300
|
+
}
|
|
71301
|
+
async function pushWithLeases(ctx) {
|
|
71302
|
+
try {
|
|
71303
|
+
ctx.emit("pushing", "after conflict resolution");
|
|
71304
|
+
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
71305
|
+
return true;
|
|
71306
|
+
} catch (pushErr) {
|
|
71307
|
+
const pe = pushErr;
|
|
71308
|
+
const blob = `${pe.message}
|
|
71309
|
+
${pe.stderr ?? ""}`;
|
|
71310
|
+
if (!/non-fast-forward|Updates were rejected/i.test(blob)) {
|
|
71311
|
+
ctx.log(`! push after conflict fix failed: ${pe.message}`, "red");
|
|
71312
|
+
return false;
|
|
71313
|
+
}
|
|
71314
|
+
try {
|
|
71315
|
+
await ctx.cmd.run(["git", "push", "--force-with-lease", "origin", ctx.branch], ctx.cwd);
|
|
71316
|
+
return true;
|
|
71317
|
+
} catch (forceErr) {
|
|
71318
|
+
ctx.log(`! force-push after conflict fix failed: ${forceErr.message}`, "red");
|
|
71319
|
+
return false;
|
|
71320
|
+
}
|
|
71321
|
+
}
|
|
71322
|
+
}
|
|
71323
|
+
async function commitResidualChanges(ctx, maxAttempts) {
|
|
71324
|
+
let hookFixAttempt = 0;
|
|
71325
|
+
while (true) {
|
|
71326
|
+
ctx.emit("committing", "git status");
|
|
71327
|
+
let dirty = "";
|
|
71328
|
+
try {
|
|
71329
|
+
const status = await ctx.cmd.run(["git", "status", "--porcelain"], ctx.cwd);
|
|
71330
|
+
dirty = status.stdout.trim();
|
|
71331
|
+
} catch (err) {
|
|
71332
|
+
ctx.log(`! git status failed for ${ctx.changeName}: ${err.message}`, "yellow");
|
|
71333
|
+
break;
|
|
71334
|
+
}
|
|
71335
|
+
if (!dirty)
|
|
71336
|
+
break;
|
|
71337
|
+
try {
|
|
71338
|
+
ctx.emit("committing", "git add -A");
|
|
71339
|
+
await ctx.cmd.run(["git", "add", "-A"], ctx.cwd);
|
|
71340
|
+
ctx.emit("committing", "git commit");
|
|
71341
|
+
await ctx.cmd.run(["git", "commit", "-m", `chore(ralph): residual changes for ${ctx.changeName}`], ctx.cwd);
|
|
71342
|
+
ctx.log(` committed residual changes for ${ctx.changeName}`, "gray");
|
|
71343
|
+
break;
|
|
71344
|
+
} catch (err) {
|
|
71345
|
+
const e = err;
|
|
71346
|
+
const detail = e.stderr?.trim() || e.message;
|
|
71347
|
+
const combined = `${e.stdout ?? ""}
|
|
71348
|
+
${e.stderr ?? ""}`;
|
|
71349
|
+
if (/nothing to commit/i.test(combined) || /empty git commit/i.test(combined))
|
|
71350
|
+
break;
|
|
71351
|
+
if (hookFixAttempt >= maxAttempts) {
|
|
71352
|
+
ctx.log(`! commit rejected for ${ctx.changeName} after ${hookFixAttempt} hook-fix attempts (host pre-commit hook still failing) \u2014 worktree preserved at ${ctx.cwd}`, "red");
|
|
71353
|
+
ctx.log(` detail: ${detail}`, "red");
|
|
71354
|
+
return { gaveUp: true, hookFixAttempt };
|
|
71355
|
+
}
|
|
71356
|
+
hookFixAttempt += 1;
|
|
71357
|
+
ctx.emit("commit-retry", `${hookFixAttempt}/${maxAttempts}`);
|
|
71358
|
+
ctx.log(`! commit rejected for ${ctx.changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
|
|
71359
|
+
ctx.log(` detail: ${detail}`, "yellow");
|
|
71360
|
+
const retryCode = await runWorkerWithFixTask(ctx, "Fix host pre-commit hook rejection", `Committing residual changes was rejected by the host repo's pre-commit hook. ` + `Fix the underlying problem, then the commit will be retried.
|
|
71361
|
+
|
|
71362
|
+
` + combined.trim());
|
|
71363
|
+
if (retryCode !== 0) {
|
|
71364
|
+
ctx.log(`! worker re-run after commit rejection exited code ${retryCode} \u2014 giving up`, "red");
|
|
71365
|
+
return { gaveUp: true, hookFixAttempt };
|
|
71366
|
+
}
|
|
71367
|
+
}
|
|
71368
|
+
}
|
|
71369
|
+
return { gaveUp: false, hookFixAttempt };
|
|
71370
|
+
}
|
|
71371
|
+
async function createPrWithRetry(ctx, issue, initialHookFixAttempt) {
|
|
71372
|
+
const maxAttempts = ctx.cfg.maxCiFixAttempts;
|
|
71373
|
+
let hookFixAttempt = initialHookFixAttempt;
|
|
71374
|
+
let nonFfRebaseAttempted = false;
|
|
71375
|
+
let pr = null;
|
|
71376
|
+
while (true) {
|
|
71377
|
+
try {
|
|
71378
|
+
ctx.emit("pr-create", "git push + gh pr create");
|
|
71379
|
+
pr = await createPullRequest({ cwd: ctx.cwd, branch: ctx.branch, issue, base: ctx.cfg.prBaseBranch }, ctx.cmd);
|
|
71380
|
+
return { pr, gaveUp: false };
|
|
71381
|
+
} catch (err) {
|
|
71382
|
+
const e = err;
|
|
71383
|
+
const detail = e.stderr?.trim() || e.message;
|
|
71384
|
+
const combined = `${e.stdout ?? ""}
|
|
71385
|
+
${e.stderr ?? ""}`;
|
|
71386
|
+
const isNonFastForward = /non-fast-forward|Updates were rejected because the (tip of your current branch is behind|remote contains work)/i.test(combined) && !/pre-push hook|hook declined/i.test(combined);
|
|
71387
|
+
const isHookReject = /pre-push hook|hook declined/i.test(combined);
|
|
71388
|
+
const pushRejected = isHookReject || /failed to push some refs/i.test(combined);
|
|
71389
|
+
if (isNonFastForward && !nonFfRebaseAttempted) {
|
|
71390
|
+
nonFfRebaseAttempted = true;
|
|
71391
|
+
ctx.emit("rebasing", `git pull --rebase origin ${ctx.branch}`);
|
|
71392
|
+
ctx.log(` non-fast-forward push for ${ctx.changeName} \u2014 rebasing onto origin/${ctx.branch}`, "yellow");
|
|
71393
|
+
try {
|
|
71394
|
+
await ctx.cmd.run(["git", "fetch", "origin", ctx.branch], ctx.cwd);
|
|
71395
|
+
await ctx.cmd.run(["git", "pull", "--rebase", "origin", ctx.branch], ctx.cwd);
|
|
71396
|
+
continue;
|
|
71397
|
+
} catch (rebaseErr) {
|
|
71398
|
+
const re = rebaseErr;
|
|
71399
|
+
const reBlob = `${re.stdout ?? ""}
|
|
71400
|
+
${re.stderr ?? ""}`;
|
|
71401
|
+
const isConflict = /CONFLICT|Merge conflict|could not apply|both modified/i.test(reBlob);
|
|
71402
|
+
if (!isConflict) {
|
|
71403
|
+
ctx.log(`! rebase failed for ${ctx.changeName}: ${rebaseErr.message} \u2014 giving up`, "red");
|
|
71404
|
+
return { pr: null, gaveUp: true };
|
|
71405
|
+
}
|
|
71406
|
+
ctx.emit("rebasing", "conflicts detected \u2014 aborting + queueing fix task");
|
|
71407
|
+
try {
|
|
71408
|
+
await ctx.cmd.run(["git", "rebase", "--abort"], ctx.cwd);
|
|
71409
|
+
} catch {}
|
|
71410
|
+
let conflictedFiles = "";
|
|
71411
|
+
try {
|
|
71412
|
+
const r = await ctx.cmd.run(["git", "diff", "--name-only", `HEAD..origin/${ctx.branch}`], ctx.cwd);
|
|
71413
|
+
conflictedFiles = r.stdout.trim();
|
|
71414
|
+
} catch {}
|
|
71415
|
+
if (hookFixAttempt >= maxAttempts) {
|
|
71416
|
+
ctx.log(`! merge conflict on rebase of ${ctx.branch} after ${hookFixAttempt} attempts \u2014 worktree preserved at ${ctx.cwd}`, "red");
|
|
71417
|
+
ctx.log(` detail: ${reBlob.trim().split(`
|
|
71418
|
+
`).slice(0, 8).join(`
|
|
71419
|
+
`)}`, "red");
|
|
71420
|
+
return { pr: null, gaveUp: true };
|
|
71421
|
+
}
|
|
71422
|
+
hookFixAttempt += 1;
|
|
71423
|
+
ctx.emit("rebasing", `conflict-fix ${hookFixAttempt}/${maxAttempts}`);
|
|
71424
|
+
ctx.log(`! merge conflict rebasing ${ctx.branch} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
|
|
71425
|
+
const retryCode2 = await runWorkerWithFixTask(ctx, "Resolve merge conflict with origin/" + ctx.branch, `Push to origin/${ctx.branch} was rejected as non-fast-forward, and rebasing ` + `onto origin/${ctx.branch} produced merge conflicts.
|
|
71426
|
+
|
|
71427
|
+
` + `Run \`git fetch origin ${ctx.branch}\` and \`git rebase origin/${ctx.branch}\`, ` + `resolve every conflict, \`git add\` the resolved files, and finish with ` + `\`git rebase --continue\`. The push will be retried after this loop ` + `iteration finishes.
|
|
71428
|
+
|
|
71429
|
+
` + (conflictedFiles ? `Files that differ between your branch and origin/${ctx.branch}:
|
|
71430
|
+
${conflictedFiles}
|
|
71431
|
+
|
|
71432
|
+
` : "") + `Rebase output:
|
|
71433
|
+
${reBlob.trim()}`);
|
|
71434
|
+
if (retryCode2 !== 0) {
|
|
71435
|
+
ctx.log(`! worker re-run after merge conflict exited code ${retryCode2} \u2014 giving up`, "red");
|
|
71436
|
+
return { pr: null, gaveUp: true };
|
|
71437
|
+
}
|
|
71438
|
+
nonFfRebaseAttempted = false;
|
|
71439
|
+
continue;
|
|
71440
|
+
}
|
|
71441
|
+
}
|
|
71442
|
+
if (!pushRejected || hookFixAttempt >= maxAttempts) {
|
|
71443
|
+
if (pushRejected) {
|
|
71444
|
+
ctx.log(`! push rejected for ${ctx.changeName} after ${hookFixAttempt} fix attempts (push still failing) \u2014 worktree preserved at ${ctx.cwd}`, "red");
|
|
71445
|
+
ctx.log(` detail: ${detail}`, "red");
|
|
71446
|
+
} else {
|
|
71447
|
+
ctx.log(`! PR create failed for ${ctx.changeName}: ${detail}`, "red");
|
|
71448
|
+
}
|
|
71449
|
+
return { pr: null, gaveUp: true };
|
|
71450
|
+
}
|
|
71451
|
+
hookFixAttempt += 1;
|
|
71452
|
+
ctx.emit("push-retry", `${hookFixAttempt}/${maxAttempts}`);
|
|
71453
|
+
ctx.log(`! push rejected for ${ctx.changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
|
|
71454
|
+
ctx.log(` detail: ${detail}`, "yellow");
|
|
71455
|
+
const retryCode = await runWorkerWithFixTask(ctx, "Fix push rejection", `Push to origin/${ctx.branch} was rejected. Fix the underlying problem ` + `(e.g. failing pre-push hook checks), then the push will be retried.
|
|
71456
|
+
|
|
71457
|
+
` + combined.trim());
|
|
71458
|
+
if (retryCode !== 0) {
|
|
71459
|
+
ctx.log(`! worker re-run after push rejection exited code ${retryCode} \u2014 giving up`, "red");
|
|
71460
|
+
return { pr: null, gaveUp: true };
|
|
71461
|
+
}
|
|
71462
|
+
}
|
|
71463
|
+
}
|
|
71464
|
+
}
|
|
71465
|
+
async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
|
|
71466
|
+
const wantConflictLoop = !!checkPrConflict;
|
|
71467
|
+
const maxOuterAttempts = ctx.cfg.maxCiFixAttempts;
|
|
71468
|
+
let outerAttempt = 0;
|
|
71469
|
+
let ciConfirmedGreen = false;
|
|
71470
|
+
while (outerAttempt < maxOuterAttempts) {
|
|
71471
|
+
if (wantConflictLoop) {
|
|
71472
|
+
ctx.emit("conflict-check");
|
|
71473
|
+
let conflicting = false;
|
|
71474
|
+
try {
|
|
71475
|
+
conflicting = await checkPrConflict(prUrl);
|
|
71476
|
+
} catch (err) {
|
|
71477
|
+
ctx.log(`! conflict check failed: ${err.message}`, "yellow");
|
|
71478
|
+
}
|
|
71479
|
+
if (conflicting) {
|
|
71480
|
+
outerAttempt++;
|
|
71481
|
+
ciConfirmedGreen = false;
|
|
71482
|
+
ctx.emit("conflict-fix-inner", `attempt ${outerAttempt}/${maxOuterAttempts}`);
|
|
71483
|
+
ctx.log(` merge conflicts on PR (attempt ${outerAttempt}/${maxOuterAttempts}) \u2014 spawning resolution task`, "yellow");
|
|
71484
|
+
const conflictCode = await runWorkerWithFixTask(ctx, "Resolve PR merge conflicts", [
|
|
71485
|
+
`The PR ${prUrl} has merge conflicts with \`${ctx.cfg.prBaseBranch}\`.`,
|
|
71486
|
+
"",
|
|
71487
|
+
"Steps:",
|
|
71488
|
+
`1. \`git fetch origin ${ctx.cfg.prBaseBranch}\` then rebase or merge \`${ctx.cfg.prBaseBranch}\` into the current branch.`,
|
|
71489
|
+
"2. Resolve conflicts in the files git lists.",
|
|
71490
|
+
"3. Stage and commit the resolution."
|
|
71491
|
+
].join(`
|
|
71492
|
+
`));
|
|
71493
|
+
if (conflictCode !== 0) {
|
|
71494
|
+
ctx.log(`! conflict resolution worker exited code ${conflictCode} \u2014 giving up`, "red");
|
|
71495
|
+
return PR_FAILED_EXIT;
|
|
71496
|
+
}
|
|
71497
|
+
const pushed = await pushWithLeases(ctx);
|
|
71498
|
+
if (!pushed)
|
|
71499
|
+
return PR_FAILED_EXIT;
|
|
71500
|
+
continue;
|
|
71501
|
+
}
|
|
71502
|
+
}
|
|
71503
|
+
if (!wantFixCi)
|
|
71504
|
+
break;
|
|
71505
|
+
if (!ciConfirmedGreen) {
|
|
71506
|
+
ctx.log(` watching CI for ${prUrl} (max ${ctx.cfg.maxCiFixAttempts} fix attempts)`, "gray");
|
|
71507
|
+
ctx.emit("ci-poll", "starting");
|
|
71508
|
+
const result2 = await fixCiUntilGreen({
|
|
71509
|
+
onPhase: (p, d) => ctx.emit(p, d),
|
|
71510
|
+
getStatus: () => getPrChecksStatus(prUrl, ctx.cmd, ctx.cwd, (n, ms, why) => ctx.log(` gh transient (try ${n}) \u2014 retry in ${Math.round(ms / 1000)}s \xB7 ${why}`, "yellow")),
|
|
71511
|
+
getFailedLogs: (ids) => fetchFailedRunLogs(ids, ctx.cmd, ctx.cwd),
|
|
71512
|
+
runTaskWithSteering: async (steering) => {
|
|
71513
|
+
try {
|
|
71514
|
+
await prependFixTask(join14(ctx.changeDir, "tasks.md"), "Fix failing CI checks", steering);
|
|
71515
|
+
} catch (err) {
|
|
71516
|
+
ctx.log(`! could not prepend fix task: ${err.message}`, "red");
|
|
71517
|
+
}
|
|
71518
|
+
return ctx.respawnWorker();
|
|
71519
|
+
},
|
|
71520
|
+
pushBranch: async () => {
|
|
71521
|
+
await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
|
|
71522
|
+
},
|
|
71523
|
+
log: ctx.log,
|
|
71524
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
71525
|
+
}, {
|
|
71526
|
+
maxAttempts: ctx.cfg.maxCiFixAttempts,
|
|
71527
|
+
pollIntervalSeconds: ctx.cfg.ciPollIntervalSeconds
|
|
71528
|
+
});
|
|
71529
|
+
if (!result2.success) {
|
|
71530
|
+
ctx.log(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
|
|
71531
|
+
return CI_FAILED_EXIT;
|
|
71532
|
+
}
|
|
71533
|
+
ciConfirmedGreen = true;
|
|
71534
|
+
}
|
|
71535
|
+
if (wantConflictLoop) {
|
|
71536
|
+
continue;
|
|
71537
|
+
}
|
|
71538
|
+
return 0;
|
|
71539
|
+
}
|
|
71540
|
+
if (outerAttempt >= maxOuterAttempts) {
|
|
71541
|
+
ctx.log(`! outer fix loop exhausted ${maxOuterAttempts} attempts \u2014 giving up`, "red");
|
|
71542
|
+
return CI_FAILED_EXIT;
|
|
71543
|
+
}
|
|
71544
|
+
return 0;
|
|
71545
|
+
}
|
|
71264
71546
|
async function runPostTask(input, deps) {
|
|
71265
71547
|
const { log: log2, cmd, git, runScript } = deps;
|
|
71266
71548
|
const emit = (phase, detail) => deps.onPhase?.(phase, detail);
|
|
@@ -71292,201 +71574,33 @@ async function runPostTask(input, deps) {
|
|
|
71292
71574
|
log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
71293
71575
|
effectiveCode = PR_FAILED_EXIT;
|
|
71294
71576
|
} else {
|
|
71295
|
-
const
|
|
71296
|
-
|
|
71297
|
-
|
|
71298
|
-
|
|
71299
|
-
|
|
71300
|
-
|
|
71301
|
-
|
|
71302
|
-
|
|
71303
|
-
|
|
71304
|
-
|
|
71577
|
+
const ctx = {
|
|
71578
|
+
changeName,
|
|
71579
|
+
cwd: cwd2,
|
|
71580
|
+
branch,
|
|
71581
|
+
changeDir,
|
|
71582
|
+
stateFilePath,
|
|
71583
|
+
cfg,
|
|
71584
|
+
cmd,
|
|
71585
|
+
log: log2,
|
|
71586
|
+
emit,
|
|
71587
|
+
respawnWorker
|
|
71305
71588
|
};
|
|
71306
|
-
|
|
71307
|
-
|
|
71308
|
-
|
|
71309
|
-
emit("committing", "git status");
|
|
71310
|
-
let dirty = "";
|
|
71311
|
-
try {
|
|
71312
|
-
const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
|
|
71313
|
-
dirty = status.stdout.trim();
|
|
71314
|
-
} catch (err) {
|
|
71315
|
-
log2(`! git status failed for ${changeName}: ${err.message}`, "yellow");
|
|
71316
|
-
break;
|
|
71317
|
-
}
|
|
71318
|
-
if (!dirty)
|
|
71319
|
-
break;
|
|
71320
|
-
try {
|
|
71321
|
-
emit("committing", "git add -A");
|
|
71322
|
-
await cmd.run(["git", "add", "-A"], cwd2);
|
|
71323
|
-
emit("committing", "git commit");
|
|
71324
|
-
await cmd.run(["git", "commit", "-m", `chore(ralph): residual changes for ${changeName}`], cwd2);
|
|
71325
|
-
log2(` committed residual changes for ${changeName}`, "gray");
|
|
71326
|
-
break;
|
|
71327
|
-
} catch (err) {
|
|
71328
|
-
const e = err;
|
|
71329
|
-
const detail = e.stderr?.trim() || e.message;
|
|
71330
|
-
const combined = `${e.stdout ?? ""}
|
|
71331
|
-
${e.stderr ?? ""}`;
|
|
71332
|
-
if (/nothing to commit/i.test(combined))
|
|
71333
|
-
break;
|
|
71334
|
-
if (hookFixAttempt >= maxHookFixAttempts) {
|
|
71335
|
-
log2(`! commit rejected for ${changeName} after ${hookFixAttempt} hook-fix attempts (host pre-commit hook still failing) \u2014 worktree preserved at ${cwd2}`, "red");
|
|
71336
|
-
log2(` detail: ${detail}`, "red");
|
|
71337
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71338
|
-
commitGaveUp = true;
|
|
71339
|
-
break;
|
|
71340
|
-
}
|
|
71341
|
-
hookFixAttempt += 1;
|
|
71342
|
-
emit("commit-retry", `${hookFixAttempt}/${maxHookFixAttempts}`);
|
|
71343
|
-
log2(`! commit rejected for ${changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71344
|
-
log2(` detail: ${detail}`, "yellow");
|
|
71345
|
-
const retryCode = await runWorkerWithFixTask("Fix host pre-commit hook rejection", `Committing residual changes was rejected by the host repo's pre-commit hook. ` + `Fix the underlying problem, then the commit will be retried.
|
|
71346
|
-
|
|
71347
|
-
` + combined.trim());
|
|
71348
|
-
if (retryCode !== 0) {
|
|
71349
|
-
log2(`! worker re-run after commit rejection exited code ${retryCode} \u2014 giving up`, "red");
|
|
71350
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71351
|
-
commitGaveUp = true;
|
|
71352
|
-
break;
|
|
71353
|
-
}
|
|
71354
|
-
}
|
|
71355
|
-
}
|
|
71356
|
-
let pr = null;
|
|
71357
|
-
let prGaveUp = commitGaveUp;
|
|
71358
|
-
let nonFfRebaseAttempted = false;
|
|
71359
|
-
while (!prGaveUp) {
|
|
71360
|
-
try {
|
|
71361
|
-
emit("pr-create", "git push + gh pr create");
|
|
71362
|
-
pr = await createPullRequest({ cwd: cwd2, branch, issue, base: cfg.prBaseBranch }, cmd);
|
|
71363
|
-
break;
|
|
71364
|
-
} catch (err) {
|
|
71365
|
-
const e = err;
|
|
71366
|
-
const detail = e.stderr?.trim() || e.message;
|
|
71367
|
-
const combined = `${e.stdout ?? ""}
|
|
71368
|
-
${e.stderr ?? ""}`;
|
|
71369
|
-
const isNonFastForward = /non-fast-forward|Updates were rejected because the (tip of your current branch is behind|remote contains work)/i.test(combined) && !/pre-push hook|hook declined/i.test(combined);
|
|
71370
|
-
const isHookReject = /pre-push hook|hook declined/i.test(combined);
|
|
71371
|
-
const pushRejected = isHookReject || /failed to push some refs/i.test(combined);
|
|
71372
|
-
if (isNonFastForward && !nonFfRebaseAttempted) {
|
|
71373
|
-
nonFfRebaseAttempted = true;
|
|
71374
|
-
emit("rebasing", `git pull --rebase origin ${branch}`);
|
|
71375
|
-
log2(` non-fast-forward push for ${changeName} \u2014 rebasing onto origin/${branch}`, "yellow");
|
|
71376
|
-
try {
|
|
71377
|
-
await cmd.run(["git", "fetch", "origin", branch], cwd2);
|
|
71378
|
-
await cmd.run(["git", "pull", "--rebase", "origin", branch], cwd2);
|
|
71379
|
-
continue;
|
|
71380
|
-
} catch (rebaseErr) {
|
|
71381
|
-
const re = rebaseErr;
|
|
71382
|
-
const reBlob = `${re.stdout ?? ""}
|
|
71383
|
-
${re.stderr ?? ""}`;
|
|
71384
|
-
const isConflict = /CONFLICT|Merge conflict|could not apply|both modified/i.test(reBlob);
|
|
71385
|
-
if (!isConflict) {
|
|
71386
|
-
log2(`! rebase failed for ${changeName}: ${rebaseErr.message} \u2014 giving up`, "red");
|
|
71387
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71388
|
-
prGaveUp = true;
|
|
71389
|
-
break;
|
|
71390
|
-
}
|
|
71391
|
-
emit("rebasing", "conflicts detected \u2014 aborting + queueing fix task");
|
|
71392
|
-
try {
|
|
71393
|
-
await cmd.run(["git", "rebase", "--abort"], cwd2);
|
|
71394
|
-
} catch {}
|
|
71395
|
-
let conflictedFiles = "";
|
|
71396
|
-
try {
|
|
71397
|
-
const r = await cmd.run(["git", "diff", "--name-only", `HEAD..origin/${branch}`], cwd2);
|
|
71398
|
-
conflictedFiles = r.stdout.trim();
|
|
71399
|
-
} catch {}
|
|
71400
|
-
if (hookFixAttempt >= maxHookFixAttempts) {
|
|
71401
|
-
log2(`! merge conflict on rebase of ${branch} after ${hookFixAttempt} attempts \u2014 worktree preserved at ${cwd2}`, "red");
|
|
71402
|
-
log2(` detail: ${reBlob.trim().split(`
|
|
71403
|
-
`).slice(0, 8).join(`
|
|
71404
|
-
`)}`, "red");
|
|
71405
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71406
|
-
prGaveUp = true;
|
|
71407
|
-
break;
|
|
71408
|
-
}
|
|
71409
|
-
hookFixAttempt += 1;
|
|
71410
|
-
emit("rebasing", `conflict-fix ${hookFixAttempt}/${maxHookFixAttempts}`);
|
|
71411
|
-
log2(`! merge conflict rebasing ${branch} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71412
|
-
const retryCode2 = await runWorkerWithFixTask("Resolve merge conflict with origin/" + branch, `Push to origin/${branch} was rejected as non-fast-forward, and rebasing ` + `onto origin/${branch} produced merge conflicts.
|
|
71413
|
-
|
|
71414
|
-
` + `Run \`git fetch origin ${branch}\` and \`git rebase origin/${branch}\`, ` + `resolve every conflict, \`git add\` the resolved files, and finish with ` + `\`git rebase --continue\`. The push will be retried after this loop ` + `iteration finishes.
|
|
71415
|
-
|
|
71416
|
-
` + (conflictedFiles ? `Files that differ between your branch and origin/${branch}:
|
|
71417
|
-
${conflictedFiles}
|
|
71418
|
-
|
|
71419
|
-
` : "") + `Rebase output:
|
|
71420
|
-
${reBlob.trim()}`);
|
|
71421
|
-
if (retryCode2 !== 0) {
|
|
71422
|
-
log2(`! worker re-run after merge conflict exited code ${retryCode2} \u2014 giving up`, "red");
|
|
71423
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71424
|
-
prGaveUp = true;
|
|
71425
|
-
break;
|
|
71426
|
-
}
|
|
71427
|
-
nonFfRebaseAttempted = false;
|
|
71428
|
-
continue;
|
|
71429
|
-
}
|
|
71430
|
-
}
|
|
71431
|
-
if (!pushRejected || hookFixAttempt >= maxHookFixAttempts) {
|
|
71432
|
-
if (pushRejected) {
|
|
71433
|
-
log2(`! push rejected for ${changeName} after ${hookFixAttempt} fix attempts (push still failing) \u2014 worktree preserved at ${cwd2}`, "red");
|
|
71434
|
-
log2(` detail: ${detail}`, "red");
|
|
71435
|
-
} else {
|
|
71436
|
-
log2(`! PR create failed for ${changeName}: ${detail}`, "red");
|
|
71437
|
-
}
|
|
71438
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71439
|
-
prGaveUp = true;
|
|
71440
|
-
break;
|
|
71441
|
-
}
|
|
71442
|
-
hookFixAttempt += 1;
|
|
71443
|
-
emit("push-retry", `${hookFixAttempt}/${maxHookFixAttempts}`);
|
|
71444
|
-
log2(`! push rejected for ${changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71445
|
-
log2(` detail: ${detail}`, "yellow");
|
|
71446
|
-
const retryCode = await runWorkerWithFixTask("Fix push rejection", `Push to origin/${branch} was rejected. Fix the underlying problem ` + `(e.g. failing pre-push hook checks), then the push will be retried.
|
|
71447
|
-
|
|
71448
|
-
` + combined.trim());
|
|
71449
|
-
if (retryCode !== 0) {
|
|
71450
|
-
log2(`! worker re-run after push rejection exited code ${retryCode} \u2014 giving up`, "red");
|
|
71451
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71452
|
-
prGaveUp = true;
|
|
71453
|
-
break;
|
|
71454
|
-
}
|
|
71455
|
-
}
|
|
71456
|
-
}
|
|
71457
|
-
if (prGaveUp) {} else if (!pr) {
|
|
71458
|
-
log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
|
|
71589
|
+
const { gaveUp: commitGaveUp, hookFixAttempt } = await commitResidualChanges(ctx, cfg.maxCiFixAttempts);
|
|
71590
|
+
if (commitGaveUp) {
|
|
71591
|
+
effectiveCode = PR_FAILED_EXIT;
|
|
71459
71592
|
} else {
|
|
71460
|
-
|
|
71461
|
-
|
|
71462
|
-
|
|
71463
|
-
|
|
71464
|
-
|
|
71465
|
-
|
|
71466
|
-
|
|
71467
|
-
|
|
71468
|
-
|
|
71469
|
-
|
|
71470
|
-
|
|
71471
|
-
await prependFixTask(join14(changeDir, "tasks.md"), "Fix failing CI checks", steering);
|
|
71472
|
-
} catch (err) {
|
|
71473
|
-
log2(`! could not prepend fix task: ${err.message}`, "red");
|
|
71474
|
-
}
|
|
71475
|
-
return respawnWorker();
|
|
71476
|
-
},
|
|
71477
|
-
pushBranch: async () => {
|
|
71478
|
-
await cmd.run(["git", "push", "origin", branch], cwd2);
|
|
71479
|
-
},
|
|
71480
|
-
log: log2,
|
|
71481
|
-
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
71482
|
-
}, {
|
|
71483
|
-
maxAttempts: cfg.maxCiFixAttempts,
|
|
71484
|
-
pollIntervalSeconds: cfg.ciPollIntervalSeconds
|
|
71485
|
-
});
|
|
71486
|
-
if (!result2.success) {
|
|
71487
|
-
log2(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
|
|
71488
|
-
effectiveCode = CI_FAILED_EXIT;
|
|
71489
|
-
}
|
|
71593
|
+
const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue, hookFixAttempt);
|
|
71594
|
+
if (prGaveUp) {
|
|
71595
|
+
effectiveCode = PR_FAILED_EXIT;
|
|
71596
|
+
} else if (!pr) {
|
|
71597
|
+
log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
|
|
71598
|
+
} else {
|
|
71599
|
+
log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
|
|
71600
|
+
deps.registerPr?.(changeName, pr.url);
|
|
71601
|
+
const loopCode = await fixConflictsAndCiLoop(ctx, pr.url, wantFixCi, deps.checkPrConflict);
|
|
71602
|
+
if (loopCode !== 0)
|
|
71603
|
+
effectiveCode = loopCode;
|
|
71490
71604
|
}
|
|
71491
71605
|
}
|
|
71492
71606
|
}
|
|
@@ -71663,13 +71777,31 @@ function buildAgentCoordinator(input) {
|
|
|
71663
71777
|
teamId = fetched;
|
|
71664
71778
|
teamIdCache.set(t, teamId);
|
|
71665
71779
|
}
|
|
71666
|
-
const
|
|
71780
|
+
const colonIdx = name.indexOf(":");
|
|
71781
|
+
let parentId;
|
|
71782
|
+
let childName = name;
|
|
71783
|
+
if (colonIdx > 0) {
|
|
71784
|
+
const groupName = name.slice(0, colonIdx);
|
|
71785
|
+
childName = name.slice(colonIdx + 1);
|
|
71786
|
+
const existingGroup = map2.get(groupName.toLowerCase());
|
|
71787
|
+
if (existingGroup) {
|
|
71788
|
+
parentId = existingGroup;
|
|
71789
|
+
} else {
|
|
71790
|
+
const groupId = await createIssueLabel(apiKey, teamId, groupName);
|
|
71791
|
+
if (groupId) {
|
|
71792
|
+
map2.set(groupName.toLowerCase(), groupId);
|
|
71793
|
+
parentId = groupId;
|
|
71794
|
+
}
|
|
71795
|
+
}
|
|
71796
|
+
}
|
|
71797
|
+
const newId = await createIssueLabel(apiKey, teamId, childName, parentId);
|
|
71667
71798
|
if (!newId)
|
|
71668
71799
|
return null;
|
|
71669
71800
|
map2.set(name.toLowerCase(), newId);
|
|
71670
71801
|
onLog(` created Linear label '${name}' for team ${t}`, "gray");
|
|
71671
71802
|
return newId;
|
|
71672
|
-
} catch {
|
|
71803
|
+
} catch (err) {
|
|
71804
|
+
onLog(`! Linear label '${name}' creation threw: ${err.message}`, "yellow");
|
|
71673
71805
|
return null;
|
|
71674
71806
|
}
|
|
71675
71807
|
}
|
|
@@ -71896,6 +72028,13 @@ PR: ${prUrl}` : ""
|
|
|
71896
72028
|
return null;
|
|
71897
72029
|
}
|
|
71898
72030
|
};
|
|
72031
|
+
const ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
72032
|
+
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
72033
|
+
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
72034
|
+
const ITER_HEADER_LINE_RE = /^\u2500\u2500/;
|
|
72035
|
+
function isLogWorthy(clean) {
|
|
72036
|
+
return !BOX_ONLY_RE.test(clean) && !STATUS_BAR_LINE_RE.test(clean) && !ITER_HEADER_LINE_RE.test(clean);
|
|
72037
|
+
}
|
|
71899
72038
|
async function pump(stream, label) {
|
|
71900
72039
|
if (!stream)
|
|
71901
72040
|
return;
|
|
@@ -71915,16 +72054,18 @@ PR: ${prUrl}` : ""
|
|
|
71915
72054
|
`)) >= 0) {
|
|
71916
72055
|
const line = buf.slice(0, nl);
|
|
71917
72056
|
buf = buf.slice(nl + 1);
|
|
71918
|
-
|
|
71919
|
-
|
|
72057
|
+
const clean = line.replace(ANSI_RE, "").trim();
|
|
72058
|
+
if (writer && clean && isLogWorthy(clean))
|
|
72059
|
+
writer.write(clean + `
|
|
71920
72060
|
`);
|
|
71921
72061
|
if (line)
|
|
71922
72062
|
onWorkerOutput?.(changeName, label === "err" ? `! ${line}` : line);
|
|
71923
72063
|
}
|
|
71924
72064
|
}
|
|
71925
72065
|
if (buf) {
|
|
71926
|
-
|
|
71927
|
-
|
|
72066
|
+
const clean = buf.replace(ANSI_RE, "").trim();
|
|
72067
|
+
if (writer && clean && isLogWorthy(clean))
|
|
72068
|
+
writer.write(clean + `
|
|
71928
72069
|
`);
|
|
71929
72070
|
onWorkerOutput?.(changeName, label === "err" ? `! ${buf}` : buf);
|
|
71930
72071
|
}
|
|
@@ -72010,6 +72151,14 @@ PR: ${prUrl}` : ""
|
|
|
72010
72151
|
},
|
|
72011
72152
|
...onWorkerPhase && {
|
|
72012
72153
|
onPhase: (phase, detail) => onWorkerPhase(changeName, phase, detail)
|
|
72154
|
+
},
|
|
72155
|
+
checkPrConflict: async (prUrl) => {
|
|
72156
|
+
try {
|
|
72157
|
+
const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "mergeable", "--jq", ".mergeable"], cwd2);
|
|
72158
|
+
return res.stdout.trim() === "CONFLICTING";
|
|
72159
|
+
} catch {
|
|
72160
|
+
return false;
|
|
72161
|
+
}
|
|
72013
72162
|
}
|
|
72014
72163
|
});
|
|
72015
72164
|
cwdByChange.delete(changeName);
|
|
@@ -72164,6 +72313,22 @@ function prLabel(prUrl) {
|
|
|
72164
72313
|
const m = prUrl.match(/\/pull\/(\d+)/);
|
|
72165
72314
|
return m ? `#${m[1]}` : "PR";
|
|
72166
72315
|
}
|
|
72316
|
+
var HYPERLINKS_SUPPORTED = !process.env["TMUX"];
|
|
72317
|
+
function Link({ url, label, color }) {
|
|
72318
|
+
if (!HYPERLINKS_SUPPORTED)
|
|
72319
|
+
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
72320
|
+
color,
|
|
72321
|
+
children: label
|
|
72322
|
+
}, undefined, false, undefined, this);
|
|
72323
|
+
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Transform, {
|
|
72324
|
+
transform: (output) => `\x1B]8;;${url}\x07${output}\x1B]8;;\x07`,
|
|
72325
|
+
children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
72326
|
+
color,
|
|
72327
|
+
underline: true,
|
|
72328
|
+
children: label
|
|
72329
|
+
}, undefined, false, undefined, this)
|
|
72330
|
+
}, undefined, false, undefined, this);
|
|
72331
|
+
}
|
|
72167
72332
|
var ANSI_STRIP_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
72168
72333
|
var BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
72169
72334
|
var STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -72265,6 +72430,21 @@ function displayTailLines(activeCount) {
|
|
|
72265
72430
|
return 8;
|
|
72266
72431
|
return 5;
|
|
72267
72432
|
}
|
|
72433
|
+
var AGENT_LOG_PATH = join16(homedir3(), ".ralph", "agent-mode.log");
|
|
72434
|
+
var SESSION_START = new Date().toISOString();
|
|
72435
|
+
mkdir4(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
72436
|
+
return;
|
|
72437
|
+
});
|
|
72438
|
+
function writeAgentLog(text) {
|
|
72439
|
+
const clean = text.replace(ANSI_STRIP_RE, "").trim();
|
|
72440
|
+
if (!clean)
|
|
72441
|
+
return;
|
|
72442
|
+
const line = `[${new Date().toISOString()}] ${clean}
|
|
72443
|
+
`;
|
|
72444
|
+
appendFile(AGENT_LOG_PATH, line).catch(() => {
|
|
72445
|
+
return;
|
|
72446
|
+
});
|
|
72447
|
+
}
|
|
72268
72448
|
function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
72269
72449
|
const { exit } = use_app_default();
|
|
72270
72450
|
const { stdout } = use_stdout_default();
|
|
@@ -72280,11 +72460,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72280
72460
|
const [pollStatus, setPollStatus] = import_react57.useState({ state: "idle", lastFound: null, lastAdded: null, lastAt: null, filterDesc: "" });
|
|
72281
72461
|
function appendLog(text, color) {
|
|
72282
72462
|
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
72463
|
+
writeAgentLog(text);
|
|
72283
72464
|
}
|
|
72284
72465
|
import_react57.useEffect(() => {
|
|
72285
72466
|
let pollTimer = null;
|
|
72286
72467
|
let cancelled = false;
|
|
72287
72468
|
async function init2() {
|
|
72469
|
+
writeAgentLog(`=== session start ${SESSION_START} ===`);
|
|
72288
72470
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
72289
72471
|
const cfg2 = await loadRalphyConfig(projectRoot);
|
|
72290
72472
|
cfgRef.current = cfg2;
|
|
@@ -72305,6 +72487,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72305
72487
|
onLog: appendLog,
|
|
72306
72488
|
onWorkersChanged: () => setTick((t) => t + 1),
|
|
72307
72489
|
onWorkerStarted: (changeName, dir, logFile, changeDir) => {
|
|
72490
|
+
writeAgentLog(`worker-started ${changeName} log=${logFile}`);
|
|
72308
72491
|
workerMetaRef.current.set(changeName, {
|
|
72309
72492
|
startedAt: Date.now(),
|
|
72310
72493
|
statesDir: dir,
|
|
@@ -72321,14 +72504,17 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72321
72504
|
});
|
|
72322
72505
|
},
|
|
72323
72506
|
onWorkerExited: (changeName) => {
|
|
72507
|
+
writeAgentLog(`worker-exited ${changeName}`);
|
|
72324
72508
|
workerMetaRef.current.delete(changeName);
|
|
72325
72509
|
},
|
|
72326
72510
|
onWorkerPhase: (changeName, phase, detail) => {
|
|
72327
72511
|
const m = workerMetaRef.current.get(changeName);
|
|
72328
72512
|
if (!m)
|
|
72329
72513
|
return;
|
|
72330
|
-
if (m.phase !== phase)
|
|
72514
|
+
if (m.phase !== phase) {
|
|
72515
|
+
writeAgentLog(`phase ${changeName}: ${phase}${detail ? ` (${detail})` : ""}`);
|
|
72331
72516
|
m.phaseStartedAt = Date.now();
|
|
72517
|
+
}
|
|
72332
72518
|
m.phase = phase;
|
|
72333
72519
|
m.phaseDetail = detail ?? "";
|
|
72334
72520
|
},
|
|
@@ -72459,9 +72645,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72459
72645
|
setFocusedIdx(n - 1);
|
|
72460
72646
|
}
|
|
72461
72647
|
}, { isActive: isRawModeSupported && activeCount > 1 });
|
|
72462
|
-
const FIXED_OVERHEAD = 22;
|
|
72463
72648
|
const nonFocusedCount = Math.max(0, activeCount - 1);
|
|
72464
|
-
const
|
|
72649
|
+
const tasksBoxLines = activeCount > 0 ? 5 : 0;
|
|
72650
|
+
const FIXED_OVERHEAD = 5 + 5 + tasksBoxLines + 8 + nonFocusedCount * 4;
|
|
72651
|
+
const focusedTailLines = Math.max(3, termHeight - FIXED_OVERHEAD);
|
|
72465
72652
|
const compactTailLines = displayTailLines(activeCount);
|
|
72466
72653
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
72467
72654
|
flexDirection: "column",
|
|
@@ -72961,9 +73148,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72961
73148
|
dimColor: true,
|
|
72962
73149
|
children: "\u2197 LINEAR"
|
|
72963
73150
|
}, undefined, false, undefined, this),
|
|
72964
|
-
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(
|
|
72965
|
-
|
|
72966
|
-
|
|
73151
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
|
|
73152
|
+
url: w.issue.url,
|
|
73153
|
+
label: w.issueIdentifier,
|
|
73154
|
+
color: "blue"
|
|
72967
73155
|
}, undefined, false, undefined, this)
|
|
72968
73156
|
]
|
|
72969
73157
|
}, undefined, true, undefined, this),
|
|
@@ -72974,9 +73162,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72974
73162
|
dimColor: true,
|
|
72975
73163
|
children: "\u2197 PR"
|
|
72976
73164
|
}, undefined, false, undefined, this),
|
|
72977
|
-
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(
|
|
72978
|
-
|
|
72979
|
-
|
|
73165
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
|
|
73166
|
+
url: prUrl,
|
|
73167
|
+
label: prLabel(prUrl),
|
|
73168
|
+
color: "green"
|
|
72980
73169
|
}, undefined, false, undefined, this)
|
|
72981
73170
|
]
|
|
72982
73171
|
}, undefined, true, undefined, this)
|
|
@@ -73085,11 +73274,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
73085
73274
|
}
|
|
73086
73275
|
|
|
73087
73276
|
// packages/openspec/src/openspec-change-store.ts
|
|
73088
|
-
import { join as join17, dirname as
|
|
73089
|
-
import { readdir, mkdir as
|
|
73277
|
+
import { join as join17, dirname as dirname5 } from "path";
|
|
73278
|
+
import { readdir, mkdir as mkdir5 } from "fs/promises";
|
|
73090
73279
|
function resolveOpenspecBin() {
|
|
73091
73280
|
const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
|
|
73092
|
-
return join17(
|
|
73281
|
+
return join17(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
73093
73282
|
}
|
|
73094
73283
|
function runOpenspec(args, options = {}) {
|
|
73095
73284
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -73147,7 +73336,7 @@ class OpenSpecChangeStore {
|
|
|
73147
73336
|
}
|
|
73148
73337
|
async writeTaskList(name, content) {
|
|
73149
73338
|
const path = join17("openspec", "changes", name, "tasks.md");
|
|
73150
|
-
await
|
|
73339
|
+
await mkdir5(dirname5(path), { recursive: true });
|
|
73151
73340
|
await Bun.write(path, content);
|
|
73152
73341
|
}
|
|
73153
73342
|
async appendSteering(name, message) {
|
|
@@ -73158,7 +73347,7 @@ class OpenSpecChangeStore {
|
|
|
73158
73347
|
|
|
73159
73348
|
${existing.trimStart()}` : `${message}
|
|
73160
73349
|
`;
|
|
73161
|
-
await
|
|
73350
|
+
await mkdir5(dirname5(path), { recursive: true });
|
|
73162
73351
|
await Bun.write(path, updated);
|
|
73163
73352
|
}
|
|
73164
73353
|
async readSection(name, artifact, heading) {
|
|
@@ -73369,8 +73558,8 @@ try {
|
|
|
73369
73558
|
const statesDir = layout.statesDir;
|
|
73370
73559
|
const tasksDir = layout.tasksDir;
|
|
73371
73560
|
if (args.mode === "init") {
|
|
73372
|
-
await
|
|
73373
|
-
const openspecBin = join19(
|
|
73561
|
+
await mkdir6(statesDir, { recursive: true });
|
|
73562
|
+
const openspecBin = join19(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
73374
73563
|
Bun.spawnSync({
|
|
73375
73564
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
73376
73565
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -73437,13 +73626,13 @@ try {
|
|
|
73437
73626
|
process.exit(0);
|
|
73438
73627
|
}
|
|
73439
73628
|
if (args.mode === "task" && args.name) {
|
|
73440
|
-
await
|
|
73441
|
-
await
|
|
73629
|
+
await mkdir6(join19(statesDir, args.name), { recursive: true });
|
|
73630
|
+
await mkdir6(join19(tasksDir, args.name), { recursive: true });
|
|
73442
73631
|
}
|
|
73443
73632
|
if (args.mode === "agent") {
|
|
73444
|
-
await
|
|
73445
|
-
await
|
|
73446
|
-
await
|
|
73633
|
+
await mkdir6(statesDir, { recursive: true });
|
|
73634
|
+
await mkdir6(tasksDir, { recursive: true });
|
|
73635
|
+
await mkdir6(join19(projectRoot, ".ralph"), { recursive: true });
|
|
73447
73636
|
}
|
|
73448
73637
|
await runWithContext(createDefaultContext(), async () => {
|
|
73449
73638
|
const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
|