@neriros/ralphy 2.13.15 → 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 +441 -286
- 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,8 +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";
|
|
70112
70121
|
import { homedir as homedir3 } from "os";
|
|
70122
|
+
import { appendFile, mkdir as mkdir4 } from "fs/promises";
|
|
70113
70123
|
|
|
70114
70124
|
// apps/cli/src/agent/config.ts
|
|
70115
70125
|
import { join as join10 } from "path";
|
|
@@ -70280,45 +70290,34 @@ var DEFAULT_CONFIG_TEMPLATE = `{
|
|
|
70280
70290
|
// Post a progress update every N iterations. 0 disables. Requires postComments.
|
|
70281
70291
|
"updateEveryIterations": 10,
|
|
70282
70292
|
|
|
70283
|
-
// ---------------------------------------------------------------------------
|
|
70284
|
-
// Linear indicators \u2014 COMMENTED OUT BY DEFAULT
|
|
70285
|
-
//
|
|
70286
70293
|
// Indicators map Ralph lifecycle events to Linear labels/statuses.
|
|
70287
|
-
// WARNING:
|
|
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
|
-
|
|
70314
|
-
// "setConflicted": { "type": "label", "value": "ralph:conflict" },
|
|
70315
|
-
//
|
|
70316
|
-
// // Label-only marker(s) removed once the conflict is fixed.
|
|
70317
|
-
// // Note: only label-typed markers are valid here \u2014 status removal is not supported.
|
|
70318
|
-
// "clearConflicted": { "type": "label", "value": "ralph:conflict" }
|
|
70319
|
-
// }
|
|
70320
|
-
// ---------------------------------------------------------------------------
|
|
70321
|
-
"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
|
+
}
|
|
70322
70321
|
}
|
|
70323
70322
|
}
|
|
70324
70323
|
`;
|
|
@@ -70530,14 +70529,19 @@ async function fetchTeamIdByKey(apiKey, teamKey) {
|
|
|
70530
70529
|
});
|
|
70531
70530
|
return data.teams.nodes[0]?.id ?? null;
|
|
70532
70531
|
}
|
|
70533
|
-
async function createIssueLabel(apiKey, teamId, name) {
|
|
70534
|
-
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!) {
|
|
70535
70539
|
issueLabelCreate(input: { teamId: $teamId, name: $name }) {
|
|
70536
70540
|
success
|
|
70537
70541
|
issueLabel { id }
|
|
70538
70542
|
}
|
|
70539
70543
|
}`;
|
|
70540
|
-
const data = await linearRequest(apiKey, mutation, { teamId, name });
|
|
70544
|
+
const data = await linearRequest(apiKey, mutation, parentId ? { teamId, name, parentId } : { teamId, name });
|
|
70541
70545
|
return data.issueLabelCreate.issueLabel?.id ?? null;
|
|
70542
70546
|
}
|
|
70543
70547
|
async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
@@ -70790,7 +70794,7 @@ class AgentCoordinator {
|
|
|
70790
70794
|
this.pendingIds.delete(issue.id);
|
|
70791
70795
|
return;
|
|
70792
70796
|
}
|
|
70793
|
-
if (mode
|
|
70797
|
+
if (mode !== "resume" && this.opts.setInProgress) {
|
|
70794
70798
|
try {
|
|
70795
70799
|
await this.deps.applyIndicator(issue, this.opts.setInProgress);
|
|
70796
70800
|
} catch (err) {
|
|
@@ -70896,6 +70900,11 @@ class AgentCoordinator {
|
|
|
70896
70900
|
error: err.message
|
|
70897
70901
|
});
|
|
70898
70902
|
}
|
|
70903
|
+
if (this.opts.setInProgress) {
|
|
70904
|
+
try {
|
|
70905
|
+
await this.deps.removeIndicator(issue, this.opts.setInProgress);
|
|
70906
|
+
} catch {}
|
|
70907
|
+
}
|
|
70899
70908
|
}
|
|
70900
70909
|
} else if (this.opts.setError) {
|
|
70901
70910
|
try {
|
|
@@ -70908,6 +70917,11 @@ class AgentCoordinator {
|
|
|
70908
70917
|
error: err.message
|
|
70909
70918
|
});
|
|
70910
70919
|
}
|
|
70920
|
+
if (this.opts.setInProgress) {
|
|
70921
|
+
try {
|
|
70922
|
+
await this.deps.removeIndicator(issue, this.opts.setInProgress);
|
|
70923
|
+
} catch {}
|
|
70924
|
+
}
|
|
70911
70925
|
}
|
|
70912
70926
|
}
|
|
70913
70927
|
stop() {
|
|
@@ -71135,6 +71149,7 @@ async function createPullRequest(input, runner) {
|
|
|
71135
71149
|
// apps/cli/src/agent/ci.ts
|
|
71136
71150
|
var PR_CHECKS_FIELDS = "name,bucket,link,workflow,event";
|
|
71137
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;
|
|
71138
71153
|
var GH_RETRY_DELAYS = [5000, 15000, 45000];
|
|
71139
71154
|
async function runGhWithRetry(cmd, runner, cwd2, onRetry, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
71140
71155
|
let lastErr;
|
|
@@ -71159,7 +71174,18 @@ ${e.stdout ?? ""}`;
|
|
|
71159
71174
|
throw lastErr;
|
|
71160
71175
|
}
|
|
71161
71176
|
async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry) {
|
|
71162
|
-
|
|
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
|
+
}
|
|
71163
71189
|
const checks = JSON.parse(out.stdout || "[]").filter((c) => c.bucket !== "skipping");
|
|
71164
71190
|
if (checks.some((c) => c.bucket === "pending")) {
|
|
71165
71191
|
return { bucket: "pending", failedRunIds: [] };
|
|
@@ -71262,6 +71288,261 @@ async function reactivateState(stateFilePath, log2, changeName) {
|
|
|
71262
71288
|
log2(`! could not reactivate state for ${changeName}: ${err.message}`, "yellow");
|
|
71263
71289
|
}
|
|
71264
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
|
+
}
|
|
71265
71546
|
async function runPostTask(input, deps) {
|
|
71266
71547
|
const { log: log2, cmd, git, runScript } = deps;
|
|
71267
71548
|
const emit = (phase, detail) => deps.onPhase?.(phase, detail);
|
|
@@ -71293,201 +71574,33 @@ async function runPostTask(input, deps) {
|
|
|
71293
71574
|
log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
|
|
71294
71575
|
effectiveCode = PR_FAILED_EXIT;
|
|
71295
71576
|
} else {
|
|
71296
|
-
const
|
|
71297
|
-
|
|
71298
|
-
|
|
71299
|
-
|
|
71300
|
-
|
|
71301
|
-
|
|
71302
|
-
|
|
71303
|
-
|
|
71304
|
-
|
|
71305
|
-
|
|
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
|
|
71306
71588
|
};
|
|
71307
|
-
|
|
71308
|
-
|
|
71309
|
-
|
|
71310
|
-
emit("committing", "git status");
|
|
71311
|
-
let dirty = "";
|
|
71312
|
-
try {
|
|
71313
|
-
const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
|
|
71314
|
-
dirty = status.stdout.trim();
|
|
71315
|
-
} catch (err) {
|
|
71316
|
-
log2(`! git status failed for ${changeName}: ${err.message}`, "yellow");
|
|
71317
|
-
break;
|
|
71318
|
-
}
|
|
71319
|
-
if (!dirty)
|
|
71320
|
-
break;
|
|
71321
|
-
try {
|
|
71322
|
-
emit("committing", "git add -A");
|
|
71323
|
-
await cmd.run(["git", "add", "-A"], cwd2);
|
|
71324
|
-
emit("committing", "git commit");
|
|
71325
|
-
await cmd.run(["git", "commit", "-m", `chore(ralph): residual changes for ${changeName}`], cwd2);
|
|
71326
|
-
log2(` committed residual changes for ${changeName}`, "gray");
|
|
71327
|
-
break;
|
|
71328
|
-
} catch (err) {
|
|
71329
|
-
const e = err;
|
|
71330
|
-
const detail = e.stderr?.trim() || e.message;
|
|
71331
|
-
const combined = `${e.stdout ?? ""}
|
|
71332
|
-
${e.stderr ?? ""}`;
|
|
71333
|
-
if (/nothing to commit/i.test(combined))
|
|
71334
|
-
break;
|
|
71335
|
-
if (hookFixAttempt >= maxHookFixAttempts) {
|
|
71336
|
-
log2(`! commit rejected for ${changeName} after ${hookFixAttempt} hook-fix attempts (host pre-commit hook still failing) \u2014 worktree preserved at ${cwd2}`, "red");
|
|
71337
|
-
log2(` detail: ${detail}`, "red");
|
|
71338
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71339
|
-
commitGaveUp = true;
|
|
71340
|
-
break;
|
|
71341
|
-
}
|
|
71342
|
-
hookFixAttempt += 1;
|
|
71343
|
-
emit("commit-retry", `${hookFixAttempt}/${maxHookFixAttempts}`);
|
|
71344
|
-
log2(`! commit rejected for ${changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71345
|
-
log2(` detail: ${detail}`, "yellow");
|
|
71346
|
-
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.
|
|
71347
|
-
|
|
71348
|
-
` + combined.trim());
|
|
71349
|
-
if (retryCode !== 0) {
|
|
71350
|
-
log2(`! worker re-run after commit rejection exited code ${retryCode} \u2014 giving up`, "red");
|
|
71351
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71352
|
-
commitGaveUp = true;
|
|
71353
|
-
break;
|
|
71354
|
-
}
|
|
71355
|
-
}
|
|
71356
|
-
}
|
|
71357
|
-
let pr = null;
|
|
71358
|
-
let prGaveUp = commitGaveUp;
|
|
71359
|
-
let nonFfRebaseAttempted = false;
|
|
71360
|
-
while (!prGaveUp) {
|
|
71361
|
-
try {
|
|
71362
|
-
emit("pr-create", "git push + gh pr create");
|
|
71363
|
-
pr = await createPullRequest({ cwd: cwd2, branch, issue, base: cfg.prBaseBranch }, cmd);
|
|
71364
|
-
break;
|
|
71365
|
-
} catch (err) {
|
|
71366
|
-
const e = err;
|
|
71367
|
-
const detail = e.stderr?.trim() || e.message;
|
|
71368
|
-
const combined = `${e.stdout ?? ""}
|
|
71369
|
-
${e.stderr ?? ""}`;
|
|
71370
|
-
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);
|
|
71371
|
-
const isHookReject = /pre-push hook|hook declined/i.test(combined);
|
|
71372
|
-
const pushRejected = isHookReject || /failed to push some refs/i.test(combined);
|
|
71373
|
-
if (isNonFastForward && !nonFfRebaseAttempted) {
|
|
71374
|
-
nonFfRebaseAttempted = true;
|
|
71375
|
-
emit("rebasing", `git pull --rebase origin ${branch}`);
|
|
71376
|
-
log2(` non-fast-forward push for ${changeName} \u2014 rebasing onto origin/${branch}`, "yellow");
|
|
71377
|
-
try {
|
|
71378
|
-
await cmd.run(["git", "fetch", "origin", branch], cwd2);
|
|
71379
|
-
await cmd.run(["git", "pull", "--rebase", "origin", branch], cwd2);
|
|
71380
|
-
continue;
|
|
71381
|
-
} catch (rebaseErr) {
|
|
71382
|
-
const re = rebaseErr;
|
|
71383
|
-
const reBlob = `${re.stdout ?? ""}
|
|
71384
|
-
${re.stderr ?? ""}`;
|
|
71385
|
-
const isConflict = /CONFLICT|Merge conflict|could not apply|both modified/i.test(reBlob);
|
|
71386
|
-
if (!isConflict) {
|
|
71387
|
-
log2(`! rebase failed for ${changeName}: ${rebaseErr.message} \u2014 giving up`, "red");
|
|
71388
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71389
|
-
prGaveUp = true;
|
|
71390
|
-
break;
|
|
71391
|
-
}
|
|
71392
|
-
emit("rebasing", "conflicts detected \u2014 aborting + queueing fix task");
|
|
71393
|
-
try {
|
|
71394
|
-
await cmd.run(["git", "rebase", "--abort"], cwd2);
|
|
71395
|
-
} catch {}
|
|
71396
|
-
let conflictedFiles = "";
|
|
71397
|
-
try {
|
|
71398
|
-
const r = await cmd.run(["git", "diff", "--name-only", `HEAD..origin/${branch}`], cwd2);
|
|
71399
|
-
conflictedFiles = r.stdout.trim();
|
|
71400
|
-
} catch {}
|
|
71401
|
-
if (hookFixAttempt >= maxHookFixAttempts) {
|
|
71402
|
-
log2(`! merge conflict on rebase of ${branch} after ${hookFixAttempt} attempts \u2014 worktree preserved at ${cwd2}`, "red");
|
|
71403
|
-
log2(` detail: ${reBlob.trim().split(`
|
|
71404
|
-
`).slice(0, 8).join(`
|
|
71405
|
-
`)}`, "red");
|
|
71406
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71407
|
-
prGaveUp = true;
|
|
71408
|
-
break;
|
|
71409
|
-
}
|
|
71410
|
-
hookFixAttempt += 1;
|
|
71411
|
-
emit("rebasing", `conflict-fix ${hookFixAttempt}/${maxHookFixAttempts}`);
|
|
71412
|
-
log2(`! merge conflict rebasing ${branch} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71413
|
-
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.
|
|
71414
|
-
|
|
71415
|
-
` + `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.
|
|
71416
|
-
|
|
71417
|
-
` + (conflictedFiles ? `Files that differ between your branch and origin/${branch}:
|
|
71418
|
-
${conflictedFiles}
|
|
71419
|
-
|
|
71420
|
-
` : "") + `Rebase output:
|
|
71421
|
-
${reBlob.trim()}`);
|
|
71422
|
-
if (retryCode2 !== 0) {
|
|
71423
|
-
log2(`! worker re-run after merge conflict exited code ${retryCode2} \u2014 giving up`, "red");
|
|
71424
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71425
|
-
prGaveUp = true;
|
|
71426
|
-
break;
|
|
71427
|
-
}
|
|
71428
|
-
nonFfRebaseAttempted = false;
|
|
71429
|
-
continue;
|
|
71430
|
-
}
|
|
71431
|
-
}
|
|
71432
|
-
if (!pushRejected || hookFixAttempt >= maxHookFixAttempts) {
|
|
71433
|
-
if (pushRejected) {
|
|
71434
|
-
log2(`! push rejected for ${changeName} after ${hookFixAttempt} fix attempts (push still failing) \u2014 worktree preserved at ${cwd2}`, "red");
|
|
71435
|
-
log2(` detail: ${detail}`, "red");
|
|
71436
|
-
} else {
|
|
71437
|
-
log2(`! PR create failed for ${changeName}: ${detail}`, "red");
|
|
71438
|
-
}
|
|
71439
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71440
|
-
prGaveUp = true;
|
|
71441
|
-
break;
|
|
71442
|
-
}
|
|
71443
|
-
hookFixAttempt += 1;
|
|
71444
|
-
emit("push-retry", `${hookFixAttempt}/${maxHookFixAttempts}`);
|
|
71445
|
-
log2(`! push rejected for ${changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
|
|
71446
|
-
log2(` detail: ${detail}`, "yellow");
|
|
71447
|
-
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.
|
|
71448
|
-
|
|
71449
|
-
` + combined.trim());
|
|
71450
|
-
if (retryCode !== 0) {
|
|
71451
|
-
log2(`! worker re-run after push rejection exited code ${retryCode} \u2014 giving up`, "red");
|
|
71452
|
-
effectiveCode = PR_FAILED_EXIT;
|
|
71453
|
-
prGaveUp = true;
|
|
71454
|
-
break;
|
|
71455
|
-
}
|
|
71456
|
-
}
|
|
71457
|
-
}
|
|
71458
|
-
if (prGaveUp) {} else if (!pr) {
|
|
71459
|
-
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;
|
|
71460
71592
|
} else {
|
|
71461
|
-
|
|
71462
|
-
|
|
71463
|
-
|
|
71464
|
-
|
|
71465
|
-
|
|
71466
|
-
|
|
71467
|
-
|
|
71468
|
-
|
|
71469
|
-
|
|
71470
|
-
|
|
71471
|
-
|
|
71472
|
-
await prependFixTask(join14(changeDir, "tasks.md"), "Fix failing CI checks", steering);
|
|
71473
|
-
} catch (err) {
|
|
71474
|
-
log2(`! could not prepend fix task: ${err.message}`, "red");
|
|
71475
|
-
}
|
|
71476
|
-
return respawnWorker();
|
|
71477
|
-
},
|
|
71478
|
-
pushBranch: async () => {
|
|
71479
|
-
await cmd.run(["git", "push", "origin", branch], cwd2);
|
|
71480
|
-
},
|
|
71481
|
-
log: log2,
|
|
71482
|
-
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
71483
|
-
}, {
|
|
71484
|
-
maxAttempts: cfg.maxCiFixAttempts,
|
|
71485
|
-
pollIntervalSeconds: cfg.ciPollIntervalSeconds
|
|
71486
|
-
});
|
|
71487
|
-
if (!result2.success) {
|
|
71488
|
-
log2(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
|
|
71489
|
-
effectiveCode = CI_FAILED_EXIT;
|
|
71490
|
-
}
|
|
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;
|
|
71491
71604
|
}
|
|
71492
71605
|
}
|
|
71493
71606
|
}
|
|
@@ -71664,13 +71777,31 @@ function buildAgentCoordinator(input) {
|
|
|
71664
71777
|
teamId = fetched;
|
|
71665
71778
|
teamIdCache.set(t, teamId);
|
|
71666
71779
|
}
|
|
71667
|
-
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);
|
|
71668
71798
|
if (!newId)
|
|
71669
71799
|
return null;
|
|
71670
71800
|
map2.set(name.toLowerCase(), newId);
|
|
71671
71801
|
onLog(` created Linear label '${name}' for team ${t}`, "gray");
|
|
71672
71802
|
return newId;
|
|
71673
|
-
} catch {
|
|
71803
|
+
} catch (err) {
|
|
71804
|
+
onLog(`! Linear label '${name}' creation threw: ${err.message}`, "yellow");
|
|
71674
71805
|
return null;
|
|
71675
71806
|
}
|
|
71676
71807
|
}
|
|
@@ -71897,6 +72028,13 @@ PR: ${prUrl}` : ""
|
|
|
71897
72028
|
return null;
|
|
71898
72029
|
}
|
|
71899
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
|
+
}
|
|
71900
72038
|
async function pump(stream, label) {
|
|
71901
72039
|
if (!stream)
|
|
71902
72040
|
return;
|
|
@@ -71916,16 +72054,18 @@ PR: ${prUrl}` : ""
|
|
|
71916
72054
|
`)) >= 0) {
|
|
71917
72055
|
const line = buf.slice(0, nl);
|
|
71918
72056
|
buf = buf.slice(nl + 1);
|
|
71919
|
-
|
|
71920
|
-
|
|
72057
|
+
const clean = line.replace(ANSI_RE, "").trim();
|
|
72058
|
+
if (writer && clean && isLogWorthy(clean))
|
|
72059
|
+
writer.write(clean + `
|
|
71921
72060
|
`);
|
|
71922
72061
|
if (line)
|
|
71923
72062
|
onWorkerOutput?.(changeName, label === "err" ? `! ${line}` : line);
|
|
71924
72063
|
}
|
|
71925
72064
|
}
|
|
71926
72065
|
if (buf) {
|
|
71927
|
-
|
|
71928
|
-
|
|
72066
|
+
const clean = buf.replace(ANSI_RE, "").trim();
|
|
72067
|
+
if (writer && clean && isLogWorthy(clean))
|
|
72068
|
+
writer.write(clean + `
|
|
71929
72069
|
`);
|
|
71930
72070
|
onWorkerOutput?.(changeName, label === "err" ? `! ${buf}` : buf);
|
|
71931
72071
|
}
|
|
@@ -72011,6 +72151,14 @@ PR: ${prUrl}` : ""
|
|
|
72011
72151
|
},
|
|
72012
72152
|
...onWorkerPhase && {
|
|
72013
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
|
+
}
|
|
72014
72162
|
}
|
|
72015
72163
|
});
|
|
72016
72164
|
cwdByChange.delete(changeName);
|
|
@@ -72165,21 +72313,21 @@ function prLabel(prUrl) {
|
|
|
72165
72313
|
const m = prUrl.match(/\/pull\/(\d+)/);
|
|
72166
72314
|
return m ? `#${m[1]}` : "PR";
|
|
72167
72315
|
}
|
|
72168
|
-
var HYPERLINKS_SUPPORTED =
|
|
72169
|
-
|
|
72170
|
-
|
|
72171
|
-
|
|
72172
|
-
|
|
72173
|
-
|
|
72174
|
-
|
|
72175
|
-
|
|
72176
|
-
|
|
72177
|
-
|
|
72178
|
-
|
|
72179
|
-
|
|
72180
|
-
|
|
72181
|
-
|
|
72182
|
-
|
|
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);
|
|
72183
72331
|
}
|
|
72184
72332
|
var ANSI_STRIP_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
72185
72333
|
var BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
@@ -72284,10 +72432,16 @@ function displayTailLines(activeCount) {
|
|
|
72284
72432
|
}
|
|
72285
72433
|
var AGENT_LOG_PATH = join16(homedir3(), ".ralph", "agent-mode.log");
|
|
72286
72434
|
var SESSION_START = new Date().toISOString();
|
|
72435
|
+
mkdir4(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
72436
|
+
return;
|
|
72437
|
+
});
|
|
72287
72438
|
function writeAgentLog(text) {
|
|
72288
|
-
const
|
|
72439
|
+
const clean = text.replace(ANSI_STRIP_RE, "").trim();
|
|
72440
|
+
if (!clean)
|
|
72441
|
+
return;
|
|
72442
|
+
const line = `[${new Date().toISOString()}] ${clean}
|
|
72289
72443
|
`;
|
|
72290
|
-
|
|
72444
|
+
appendFile(AGENT_LOG_PATH, line).catch(() => {
|
|
72291
72445
|
return;
|
|
72292
72446
|
});
|
|
72293
72447
|
}
|
|
@@ -72491,9 +72645,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72491
72645
|
setFocusedIdx(n - 1);
|
|
72492
72646
|
}
|
|
72493
72647
|
}, { isActive: isRawModeSupported && activeCount > 1 });
|
|
72494
|
-
const FIXED_OVERHEAD = 22;
|
|
72495
72648
|
const nonFocusedCount = Math.max(0, activeCount - 1);
|
|
72496
|
-
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);
|
|
72497
72652
|
const compactTailLines = displayTailLines(activeCount);
|
|
72498
72653
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
72499
72654
|
flexDirection: "column",
|
|
@@ -72993,10 +73148,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72993
73148
|
dimColor: true,
|
|
72994
73149
|
children: "\u2197 LINEAR"
|
|
72995
73150
|
}, undefined, false, undefined, this),
|
|
72996
|
-
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(
|
|
72997
|
-
|
|
72998
|
-
|
|
72999
|
-
|
|
73151
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
|
|
73152
|
+
url: w.issue.url,
|
|
73153
|
+
label: w.issueIdentifier,
|
|
73154
|
+
color: "blue"
|
|
73000
73155
|
}, undefined, false, undefined, this)
|
|
73001
73156
|
]
|
|
73002
73157
|
}, undefined, true, undefined, this),
|
|
@@ -73007,10 +73162,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
73007
73162
|
dimColor: true,
|
|
73008
73163
|
children: "\u2197 PR"
|
|
73009
73164
|
}, undefined, false, undefined, this),
|
|
73010
|
-
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(
|
|
73011
|
-
|
|
73012
|
-
|
|
73013
|
-
|
|
73165
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
|
|
73166
|
+
url: prUrl,
|
|
73167
|
+
label: prLabel(prUrl),
|
|
73168
|
+
color: "green"
|
|
73014
73169
|
}, undefined, false, undefined, this)
|
|
73015
73170
|
]
|
|
73016
73171
|
}, undefined, true, undefined, this)
|
|
@@ -73119,11 +73274,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
73119
73274
|
}
|
|
73120
73275
|
|
|
73121
73276
|
// packages/openspec/src/openspec-change-store.ts
|
|
73122
|
-
import { join as join17, dirname as
|
|
73123
|
-
import { readdir, mkdir as
|
|
73277
|
+
import { join as join17, dirname as dirname5 } from "path";
|
|
73278
|
+
import { readdir, mkdir as mkdir5 } from "fs/promises";
|
|
73124
73279
|
function resolveOpenspecBin() {
|
|
73125
73280
|
const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
|
|
73126
|
-
return join17(
|
|
73281
|
+
return join17(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
73127
73282
|
}
|
|
73128
73283
|
function runOpenspec(args, options = {}) {
|
|
73129
73284
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -73181,7 +73336,7 @@ class OpenSpecChangeStore {
|
|
|
73181
73336
|
}
|
|
73182
73337
|
async writeTaskList(name, content) {
|
|
73183
73338
|
const path = join17("openspec", "changes", name, "tasks.md");
|
|
73184
|
-
await
|
|
73339
|
+
await mkdir5(dirname5(path), { recursive: true });
|
|
73185
73340
|
await Bun.write(path, content);
|
|
73186
73341
|
}
|
|
73187
73342
|
async appendSteering(name, message) {
|
|
@@ -73192,7 +73347,7 @@ class OpenSpecChangeStore {
|
|
|
73192
73347
|
|
|
73193
73348
|
${existing.trimStart()}` : `${message}
|
|
73194
73349
|
`;
|
|
73195
|
-
await
|
|
73350
|
+
await mkdir5(dirname5(path), { recursive: true });
|
|
73196
73351
|
await Bun.write(path, updated);
|
|
73197
73352
|
}
|
|
73198
73353
|
async readSection(name, artifact, heading) {
|
|
@@ -73403,8 +73558,8 @@ try {
|
|
|
73403
73558
|
const statesDir = layout.statesDir;
|
|
73404
73559
|
const tasksDir = layout.tasksDir;
|
|
73405
73560
|
if (args.mode === "init") {
|
|
73406
|
-
await
|
|
73407
|
-
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");
|
|
73408
73563
|
Bun.spawnSync({
|
|
73409
73564
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
73410
73565
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -73471,13 +73626,13 @@ try {
|
|
|
73471
73626
|
process.exit(0);
|
|
73472
73627
|
}
|
|
73473
73628
|
if (args.mode === "task" && args.name) {
|
|
73474
|
-
await
|
|
73475
|
-
await
|
|
73629
|
+
await mkdir6(join19(statesDir, args.name), { recursive: true });
|
|
73630
|
+
await mkdir6(join19(tasksDir, args.name), { recursive: true });
|
|
73476
73631
|
}
|
|
73477
73632
|
if (args.mode === "agent") {
|
|
73478
|
-
await
|
|
73479
|
-
await
|
|
73480
|
-
await
|
|
73633
|
+
await mkdir6(statesDir, { recursive: true });
|
|
73634
|
+
await mkdir6(tasksDir, { recursive: true });
|
|
73635
|
+
await mkdir6(join19(projectRoot, ".ralph"), { recursive: true });
|
|
73481
73636
|
}
|
|
73482
73637
|
await runWithContext(createDefaultContext(), async () => {
|
|
73483
73638
|
const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
|