@locusai/cli 0.10.6 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent/worker.js +251 -797
- package/bin/locus.js +22446 -23332
- package/package.json +2 -2
package/bin/agent/worker.js
CHANGED
|
@@ -14611,8 +14611,7 @@ var init_task = __esm(() => {
|
|
|
14611
14611
|
comments: exports_external.array(CommentSchema).optional(),
|
|
14612
14612
|
activityLog: exports_external.array(EventSchema).optional(),
|
|
14613
14613
|
docs: exports_external.array(DocSchema),
|
|
14614
|
-
order: exports_external.number().optional()
|
|
14615
|
-
tier: exports_external.number().int().min(0).optional()
|
|
14614
|
+
order: exports_external.number().optional()
|
|
14616
14615
|
});
|
|
14617
14616
|
CreateTaskSchema = exports_external.object({
|
|
14618
14617
|
title: exports_external.string().min(1, "Title is required").max(200),
|
|
@@ -14627,8 +14626,7 @@ var init_task = __esm(() => {
|
|
|
14627
14626
|
sprintId: exports_external.string().nullable().optional(),
|
|
14628
14627
|
acceptanceChecklist: exports_external.array(AcceptanceItemSchema).optional(),
|
|
14629
14628
|
docIds: exports_external.array(exports_external.string()).optional(),
|
|
14630
|
-
order: exports_external.number().optional()
|
|
14631
|
-
tier: exports_external.number().int().min(0).optional()
|
|
14629
|
+
order: exports_external.number().optional()
|
|
14632
14630
|
});
|
|
14633
14631
|
UpdateTaskSchema = TaskSchema.partial().omit({
|
|
14634
14632
|
id: true,
|
|
@@ -15532,14 +15530,13 @@ class CodexRunner {
|
|
|
15532
15530
|
type: "tool_use",
|
|
15533
15531
|
tool: line.replace(/^[→•✓]\s*/, "")
|
|
15534
15532
|
});
|
|
15535
|
-
} else {
|
|
15536
|
-
enqueueChunk({ type: "text_delta", content: `${line}
|
|
15537
|
-
` });
|
|
15538
15533
|
}
|
|
15539
15534
|
}
|
|
15540
15535
|
};
|
|
15541
15536
|
codex.stdout.on("data", processOutput);
|
|
15542
|
-
codex.stderr.on("data",
|
|
15537
|
+
codex.stderr.on("data", (data) => {
|
|
15538
|
+
finalOutput += data.toString();
|
|
15539
|
+
});
|
|
15543
15540
|
codex.on("error", (err) => {
|
|
15544
15541
|
errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
|
|
15545
15542
|
this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
|
|
@@ -15675,6 +15672,7 @@ class CodexRunner {
|
|
|
15675
15672
|
"exec",
|
|
15676
15673
|
"--full-auto",
|
|
15677
15674
|
"--skip-git-repo-check",
|
|
15675
|
+
"--quiet",
|
|
15678
15676
|
"--output-last-message",
|
|
15679
15677
|
outputPath
|
|
15680
15678
|
];
|
|
@@ -15695,12 +15693,7 @@ class CodexRunner {
|
|
|
15695
15693
|
}
|
|
15696
15694
|
}
|
|
15697
15695
|
shouldDisplay(line) {
|
|
15698
|
-
return
|
|
15699
|
-
/^thinking\b/,
|
|
15700
|
-
/^\*\*/,
|
|
15701
|
-
/^Plan update\b/,
|
|
15702
|
-
/^[→•✓]/
|
|
15703
|
-
].some((pattern) => pattern.test(line));
|
|
15696
|
+
return /^Plan update\b/.test(line);
|
|
15704
15697
|
}
|
|
15705
15698
|
readOutput(outputPath, fallback) {
|
|
15706
15699
|
if (existsSync2(outputPath)) {
|
|
@@ -31378,588 +31371,252 @@ var init_knowledge_base = __esm(() => {
|
|
|
31378
31371
|
init_config();
|
|
31379
31372
|
});
|
|
31380
31373
|
|
|
31381
|
-
// ../sdk/src/git
|
|
31374
|
+
// ../sdk/src/agent/git-workflow.ts
|
|
31382
31375
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
31383
31376
|
|
|
31384
|
-
class
|
|
31385
|
-
|
|
31377
|
+
class GitWorkflow {
|
|
31378
|
+
config;
|
|
31386
31379
|
log;
|
|
31387
|
-
|
|
31388
|
-
|
|
31380
|
+
projectPath;
|
|
31381
|
+
branchName = null;
|
|
31382
|
+
baseBranch = null;
|
|
31383
|
+
ghUsername;
|
|
31384
|
+
constructor(config2, log) {
|
|
31385
|
+
this.config = config2;
|
|
31389
31386
|
this.log = log;
|
|
31390
|
-
|
|
31391
|
-
|
|
31392
|
-
|
|
31393
|
-
|
|
31394
|
-
branch,
|
|
31395
|
-
baseBranch: requestedBaseBranch,
|
|
31396
|
-
agentId,
|
|
31397
|
-
summary
|
|
31398
|
-
} = options;
|
|
31399
|
-
const provider = detectRemoteProvider(this.projectPath);
|
|
31400
|
-
if (provider !== "github") {
|
|
31401
|
-
throw new Error(`PR creation is only supported for GitHub repositories (detected: ${provider})`);
|
|
31402
|
-
}
|
|
31403
|
-
if (!isGhAvailable(this.projectPath)) {
|
|
31404
|
-
throw new Error("GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/");
|
|
31405
|
-
}
|
|
31406
|
-
const title = `[Locus] ${task2.title}`;
|
|
31407
|
-
const body = this.buildPrBody(task2, agentId, summary);
|
|
31408
|
-
const baseBranch = requestedBaseBranch ?? getDefaultBranch(this.projectPath);
|
|
31409
|
-
this.validateCreatePrInputs(baseBranch, branch);
|
|
31410
|
-
this.log(`Creating PR: ${title} (${branch} → ${baseBranch})`, "info");
|
|
31411
|
-
const output = execFileSync2("gh", [
|
|
31412
|
-
"pr",
|
|
31413
|
-
"create",
|
|
31414
|
-
"--title",
|
|
31415
|
-
title,
|
|
31416
|
-
"--body",
|
|
31417
|
-
body,
|
|
31418
|
-
"--base",
|
|
31419
|
-
baseBranch,
|
|
31420
|
-
"--head",
|
|
31421
|
-
branch
|
|
31422
|
-
], {
|
|
31423
|
-
cwd: this.projectPath,
|
|
31424
|
-
encoding: "utf-8",
|
|
31425
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31426
|
-
}).trim();
|
|
31427
|
-
const url3 = output;
|
|
31428
|
-
const prNumber = this.extractPrNumber(url3);
|
|
31429
|
-
this.log(`PR created: ${url3}`, "success");
|
|
31430
|
-
return { url: url3, number: prNumber };
|
|
31431
|
-
}
|
|
31432
|
-
validateCreatePrInputs(baseBranch, headBranch) {
|
|
31433
|
-
if (!this.hasRemoteBranch(baseBranch)) {
|
|
31434
|
-
throw new Error(`Base branch "${baseBranch}" does not exist on origin. Push/fetch refs and retry.`);
|
|
31435
|
-
}
|
|
31436
|
-
if (!this.hasRemoteBranch(headBranch)) {
|
|
31437
|
-
throw new Error(`Head branch "${headBranch}" is not available on origin. Ensure it is pushed before PR creation.`);
|
|
31438
|
-
}
|
|
31439
|
-
const baseRef = this.resolveBranchRef(baseBranch);
|
|
31440
|
-
const headRef = this.resolveBranchRef(headBranch);
|
|
31441
|
-
if (!baseRef) {
|
|
31442
|
-
throw new Error(`Could not resolve base branch "${baseBranch}" locally.`);
|
|
31387
|
+
this.projectPath = config2.projectPath || process.cwd();
|
|
31388
|
+
this.ghUsername = getGhUsername();
|
|
31389
|
+
if (this.ghUsername) {
|
|
31390
|
+
this.log(`GitHub user: ${this.ghUsername}`, "info");
|
|
31443
31391
|
}
|
|
31444
|
-
if (!headRef) {
|
|
31445
|
-
throw new Error(`Could not resolve head branch "${headBranch}" locally.`);
|
|
31446
|
-
}
|
|
31447
|
-
const commitsAhead = this.countCommitsAhead(baseRef, headRef);
|
|
31448
|
-
if (commitsAhead <= 0) {
|
|
31449
|
-
throw new Error(`No commits between "${baseBranch}" and "${headBranch}". Skipping PR creation.`);
|
|
31450
|
-
}
|
|
31451
|
-
}
|
|
31452
|
-
countCommitsAhead(baseRef, headRef) {
|
|
31453
|
-
const output = execFileSync2("git", ["rev-list", "--count", `${baseRef}..${headRef}`], {
|
|
31454
|
-
cwd: this.projectPath,
|
|
31455
|
-
encoding: "utf-8",
|
|
31456
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31457
|
-
}).trim();
|
|
31458
|
-
const value = Number.parseInt(output || "0", 10);
|
|
31459
|
-
return Number.isNaN(value) ? 0 : value;
|
|
31460
|
-
}
|
|
31461
|
-
resolveBranchRef(branch) {
|
|
31462
|
-
if (this.hasLocalBranch(branch)) {
|
|
31463
|
-
return branch;
|
|
31464
|
-
}
|
|
31465
|
-
if (this.hasRemoteTrackingBranch(branch)) {
|
|
31466
|
-
return `origin/${branch}`;
|
|
31467
|
-
}
|
|
31468
|
-
return null;
|
|
31469
31392
|
}
|
|
31470
|
-
|
|
31393
|
+
createBranch(sprintId) {
|
|
31394
|
+
const defaultBranch = getDefaultBranch(this.projectPath);
|
|
31395
|
+
this.baseBranch = defaultBranch;
|
|
31471
31396
|
try {
|
|
31472
|
-
|
|
31473
|
-
|
|
31474
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31475
|
-
});
|
|
31476
|
-
return true;
|
|
31397
|
+
this.gitExec(["checkout", defaultBranch]);
|
|
31398
|
+
this.gitExec(["pull", "origin", defaultBranch]);
|
|
31477
31399
|
} catch {
|
|
31478
|
-
|
|
31400
|
+
this.log(`Could not pull latest from ${defaultBranch}, continuing with current state`, "warn");
|
|
31479
31401
|
}
|
|
31402
|
+
const suffix = sprintId ? sprintId.slice(0, 8) : Date.now().toString(36);
|
|
31403
|
+
this.branchName = `locus/${suffix}`;
|
|
31404
|
+
try {
|
|
31405
|
+
this.gitExec(["branch", "-D", this.branchName]);
|
|
31406
|
+
} catch {}
|
|
31407
|
+
this.gitExec(["checkout", "-b", this.branchName]);
|
|
31408
|
+
this.log(`Created branch: ${this.branchName} (from ${defaultBranch})`, "success");
|
|
31409
|
+
return this.branchName;
|
|
31480
31410
|
}
|
|
31481
|
-
|
|
31411
|
+
commitAndPush(task2) {
|
|
31412
|
+
if (!this.branchName) {
|
|
31413
|
+
this.log("No branch created yet, skipping commit", "warn");
|
|
31414
|
+
return { branch: null, pushed: false, pushFailed: false };
|
|
31415
|
+
}
|
|
31482
31416
|
try {
|
|
31483
|
-
|
|
31484
|
-
|
|
31485
|
-
|
|
31486
|
-
|
|
31487
|
-
|
|
31488
|
-
|
|
31489
|
-
|
|
31417
|
+
const status = this.gitExec(["status", "--porcelain"]).trim();
|
|
31418
|
+
if (!status) {
|
|
31419
|
+
const baseBranchCommit = this.getBaseCommit();
|
|
31420
|
+
const headCommit = this.gitExec(["rev-parse", "HEAD"]).trim();
|
|
31421
|
+
if (baseBranchCommit && headCommit !== baseBranchCommit) {
|
|
31422
|
+
return this.pushBranch();
|
|
31423
|
+
}
|
|
31424
|
+
this.log("No changes to commit for this task", "info");
|
|
31425
|
+
return {
|
|
31426
|
+
branch: this.branchName,
|
|
31427
|
+
pushed: false,
|
|
31428
|
+
pushFailed: false,
|
|
31429
|
+
noChanges: true,
|
|
31430
|
+
skipReason: "No changes were made for this task."
|
|
31431
|
+
};
|
|
31432
|
+
}
|
|
31433
|
+
this.gitExec(["add", "-A"]);
|
|
31434
|
+
const staged = this.gitExec(["diff", "--cached", "--name-only"]).trim();
|
|
31435
|
+
if (!staged) {
|
|
31436
|
+
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
31437
|
+
return {
|
|
31438
|
+
branch: this.branchName,
|
|
31439
|
+
pushed: false,
|
|
31440
|
+
pushFailed: false,
|
|
31441
|
+
noChanges: true,
|
|
31442
|
+
skipReason: "All changes were ignored by .gitignore."
|
|
31443
|
+
};
|
|
31444
|
+
}
|
|
31445
|
+
this.log(`Staging ${staged.split(`
|
|
31446
|
+
`).length} file(s) for commit`, "info");
|
|
31447
|
+
const trailers = [
|
|
31448
|
+
`Task-ID: ${task2.id}`,
|
|
31449
|
+
`Agent: ${this.config.agentId}`,
|
|
31450
|
+
"Co-authored-by: LocusAI <agent@locusai.team>"
|
|
31451
|
+
];
|
|
31452
|
+
if (this.ghUsername) {
|
|
31453
|
+
trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
|
|
31454
|
+
}
|
|
31455
|
+
const commitMessage = `feat(agent): ${task2.title}
|
|
31456
|
+
|
|
31457
|
+
${trailers.join(`
|
|
31458
|
+
`)}`;
|
|
31459
|
+
this.gitExec(["commit", "-m", commitMessage]);
|
|
31460
|
+
const hash2 = this.gitExec(["rev-parse", "HEAD"]).trim();
|
|
31461
|
+
this.log(`Committed: ${hash2.slice(0, 8)}`, "success");
|
|
31462
|
+
return this.pushBranch();
|
|
31463
|
+
} catch (err) {
|
|
31464
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
31465
|
+
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
31466
|
+
return {
|
|
31467
|
+
branch: this.branchName,
|
|
31468
|
+
pushed: false,
|
|
31469
|
+
pushFailed: true,
|
|
31470
|
+
pushError: `Git commit/push failed: ${errorMessage}`
|
|
31471
|
+
};
|
|
31490
31472
|
}
|
|
31491
31473
|
}
|
|
31492
|
-
|
|
31474
|
+
pushBranch() {
|
|
31475
|
+
if (!this.branchName) {
|
|
31476
|
+
return { branch: null, pushed: false, pushFailed: false };
|
|
31477
|
+
}
|
|
31493
31478
|
try {
|
|
31494
|
-
|
|
31495
|
-
|
|
31496
|
-
|
|
31497
|
-
|
|
31498
|
-
|
|
31499
|
-
|
|
31500
|
-
|
|
31479
|
+
this.gitExec(["push", "-u", "origin", this.branchName]);
|
|
31480
|
+
this.log(`Pushed ${this.branchName} to origin`, "success");
|
|
31481
|
+
return { branch: this.branchName, pushed: true, pushFailed: false };
|
|
31482
|
+
} catch (error48) {
|
|
31483
|
+
const msg = error48 instanceof Error ? error48.message : String(error48);
|
|
31484
|
+
if (msg.includes("non-fast-forward") || msg.includes("[rejected]") || msg.includes("fetch first")) {
|
|
31485
|
+
this.log(`Push rejected (non-fast-forward). Retrying with --force-with-lease.`, "warn");
|
|
31486
|
+
try {
|
|
31487
|
+
this.gitExec([
|
|
31488
|
+
"push",
|
|
31489
|
+
"--force-with-lease",
|
|
31490
|
+
"-u",
|
|
31491
|
+
"origin",
|
|
31492
|
+
this.branchName
|
|
31493
|
+
]);
|
|
31494
|
+
this.log(`Pushed ${this.branchName} to origin with --force-with-lease`, "success");
|
|
31495
|
+
return { branch: this.branchName, pushed: true, pushFailed: false };
|
|
31496
|
+
} catch (retryErr) {
|
|
31497
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
31498
|
+
this.log(`Git push retry failed: ${retryMsg}`, "error");
|
|
31499
|
+
return {
|
|
31500
|
+
branch: this.branchName,
|
|
31501
|
+
pushed: false,
|
|
31502
|
+
pushFailed: true,
|
|
31503
|
+
pushError: retryMsg
|
|
31504
|
+
};
|
|
31505
|
+
}
|
|
31506
|
+
}
|
|
31507
|
+
this.log(`Git push failed: ${msg}`, "error");
|
|
31508
|
+
return {
|
|
31509
|
+
branch: this.branchName,
|
|
31510
|
+
pushed: false,
|
|
31511
|
+
pushFailed: true,
|
|
31512
|
+
pushError: msg
|
|
31513
|
+
};
|
|
31501
31514
|
}
|
|
31502
31515
|
}
|
|
31503
|
-
|
|
31504
|
-
|
|
31505
|
-
|
|
31506
|
-
|
|
31507
|
-
|
|
31508
|
-
|
|
31509
|
-
|
|
31510
|
-
|
|
31511
|
-
|
|
31516
|
+
createPullRequest(completedTasks, summaries) {
|
|
31517
|
+
if (!this.branchName || !this.baseBranch) {
|
|
31518
|
+
return { url: null, error: "No branch or base branch available." };
|
|
31519
|
+
}
|
|
31520
|
+
const provider = detectRemoteProvider(this.projectPath);
|
|
31521
|
+
if (provider !== "github") {
|
|
31522
|
+
return {
|
|
31523
|
+
url: null,
|
|
31524
|
+
error: `PR creation is only supported for GitHub repositories (detected: ${provider})`
|
|
31525
|
+
};
|
|
31526
|
+
}
|
|
31527
|
+
if (!isGhAvailable(this.projectPath)) {
|
|
31528
|
+
return {
|
|
31529
|
+
url: null,
|
|
31530
|
+
error: "GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/"
|
|
31531
|
+
};
|
|
31532
|
+
}
|
|
31533
|
+
const title = `[Locus] Sprint tasks (${completedTasks.length} task${completedTasks.length !== 1 ? "s" : ""})`;
|
|
31534
|
+
const body = this.buildPrBody(completedTasks, summaries);
|
|
31535
|
+
this.log(`Creating PR: ${title} (${this.branchName} → ${this.baseBranch})`, "info");
|
|
31512
31536
|
try {
|
|
31513
|
-
execFileSync2("gh", [
|
|
31537
|
+
const output = execFileSync2("gh", [
|
|
31514
31538
|
"pr",
|
|
31515
|
-
"
|
|
31516
|
-
|
|
31539
|
+
"create",
|
|
31540
|
+
"--title",
|
|
31541
|
+
title,
|
|
31517
31542
|
"--body",
|
|
31518
31543
|
body,
|
|
31519
|
-
|
|
31544
|
+
"--base",
|
|
31545
|
+
this.baseBranch,
|
|
31546
|
+
"--head",
|
|
31547
|
+
this.branchName
|
|
31520
31548
|
], {
|
|
31521
31549
|
cwd: this.projectPath,
|
|
31522
31550
|
encoding: "utf-8",
|
|
31523
31551
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31524
|
-
});
|
|
31552
|
+
}).trim();
|
|
31553
|
+
this.log(`PR created: ${output}`, "success");
|
|
31554
|
+
return { url: output };
|
|
31525
31555
|
} catch (err) {
|
|
31526
|
-
const
|
|
31527
|
-
|
|
31528
|
-
|
|
31529
|
-
cwd: this.projectPath,
|
|
31530
|
-
encoding: "utf-8",
|
|
31531
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31532
|
-
});
|
|
31533
|
-
return;
|
|
31534
|
-
}
|
|
31535
|
-
throw err;
|
|
31556
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
31557
|
+
this.log(`PR creation failed: ${errorMessage}`, "error");
|
|
31558
|
+
return { url: null, error: errorMessage };
|
|
31536
31559
|
}
|
|
31537
31560
|
}
|
|
31538
|
-
|
|
31561
|
+
checkoutBaseBranch() {
|
|
31562
|
+
if (!this.baseBranch)
|
|
31563
|
+
return;
|
|
31539
31564
|
try {
|
|
31540
|
-
|
|
31541
|
-
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
"[Locus] in:title",
|
|
31545
|
-
"--state",
|
|
31546
|
-
"open",
|
|
31547
|
-
"--json",
|
|
31548
|
-
"number,title,url,headRefName"
|
|
31549
|
-
], {
|
|
31550
|
-
cwd: this.projectPath,
|
|
31551
|
-
encoding: "utf-8",
|
|
31552
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31553
|
-
}).trim();
|
|
31554
|
-
const prs = JSON.parse(output || "[]");
|
|
31555
|
-
return prs.map((pr) => ({
|
|
31556
|
-
number: pr.number,
|
|
31557
|
-
title: pr.title,
|
|
31558
|
-
url: pr.url,
|
|
31559
|
-
branch: pr.headRefName
|
|
31560
|
-
}));
|
|
31561
|
-
} catch {
|
|
31562
|
-
this.log("Failed to list Locus PRs", "warn");
|
|
31563
|
-
return [];
|
|
31565
|
+
this.gitExec(["checkout", this.baseBranch]);
|
|
31566
|
+
this.log(`Checked out base branch: ${this.baseBranch}`, "info");
|
|
31567
|
+
} catch (err) {
|
|
31568
|
+
this.log(`Could not checkout base branch: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
31564
31569
|
}
|
|
31565
31570
|
}
|
|
31566
|
-
|
|
31571
|
+
getBranchName() {
|
|
31572
|
+
return this.branchName;
|
|
31573
|
+
}
|
|
31574
|
+
getBaseBranch() {
|
|
31575
|
+
return this.baseBranch;
|
|
31576
|
+
}
|
|
31577
|
+
getBaseCommit() {
|
|
31578
|
+
if (!this.baseBranch)
|
|
31579
|
+
return null;
|
|
31567
31580
|
try {
|
|
31568
|
-
|
|
31569
|
-
cwd: this.projectPath,
|
|
31570
|
-
encoding: "utf-8",
|
|
31571
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31572
|
-
}).trim();
|
|
31573
|
-
const data = JSON.parse(output || "{}");
|
|
31574
|
-
return data.reviews?.some((r) => r.body?.includes("## Locus Agent Review")) ?? false;
|
|
31581
|
+
return this.gitExec(["rev-parse", this.baseBranch]).trim();
|
|
31575
31582
|
} catch {
|
|
31576
|
-
return
|
|
31583
|
+
return null;
|
|
31577
31584
|
}
|
|
31578
31585
|
}
|
|
31579
|
-
|
|
31580
|
-
const allPrs = this.listLocusPrs();
|
|
31581
|
-
return allPrs.filter((pr) => !this.hasLocusReview(String(pr.number)));
|
|
31582
|
-
}
|
|
31583
|
-
buildPrBody(task2, agentId, summary) {
|
|
31586
|
+
buildPrBody(completedTasks, summaries) {
|
|
31584
31587
|
const sections = [];
|
|
31585
|
-
sections.push(
|
|
31588
|
+
sections.push("## Completed Tasks");
|
|
31586
31589
|
sections.push("");
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
sections.push(
|
|
31590
|
-
|
|
31591
|
-
|
|
31592
|
-
|
|
31593
|
-
|
|
31594
|
-
sections.push(`- [ ] ${item.text}`);
|
|
31590
|
+
for (let i = 0;i < completedTasks.length; i++) {
|
|
31591
|
+
const task2 = completedTasks[i];
|
|
31592
|
+
sections.push(`### ${i + 1}. ${task2.title}`);
|
|
31593
|
+
sections.push(`Task ID: \`${task2.id}\``);
|
|
31594
|
+
if (summaries[i]) {
|
|
31595
|
+
sections.push("");
|
|
31596
|
+
sections.push(summaries[i]);
|
|
31595
31597
|
}
|
|
31596
31598
|
sections.push("");
|
|
31597
31599
|
}
|
|
31598
|
-
if (summary) {
|
|
31599
|
-
sections.push("## Agent Summary");
|
|
31600
|
-
sections.push(summary);
|
|
31601
|
-
sections.push("");
|
|
31602
|
-
}
|
|
31603
31600
|
sections.push("---");
|
|
31604
|
-
sections.push(`*Created by Locus Agent \`${agentId.slice(-8)}
|
|
31601
|
+
sections.push(`*Created by Locus Agent \`${this.config.agentId.slice(-8)}\`*`);
|
|
31605
31602
|
return sections.join(`
|
|
31606
31603
|
`);
|
|
31607
31604
|
}
|
|
31608
|
-
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
}
|
|
31612
|
-
}
|
|
31613
|
-
var init_pr_service = __esm(() => {
|
|
31614
|
-
init_git_utils();
|
|
31615
|
-
});
|
|
31616
|
-
|
|
31617
|
-
// ../sdk/src/worktree/worktree-config.ts
|
|
31618
|
-
var WORKTREE_ROOT_DIR = ".locus-worktrees", WORKTREE_BRANCH_PREFIX = "agent", DEFAULT_WORKTREE_CONFIG;
|
|
31619
|
-
var init_worktree_config = __esm(() => {
|
|
31620
|
-
DEFAULT_WORKTREE_CONFIG = {
|
|
31621
|
-
rootDir: WORKTREE_ROOT_DIR,
|
|
31622
|
-
branchPrefix: WORKTREE_BRANCH_PREFIX,
|
|
31623
|
-
cleanupPolicy: "retain-on-failure"
|
|
31624
|
-
};
|
|
31625
|
-
});
|
|
31626
|
-
|
|
31627
|
-
// ../sdk/src/worktree/worktree-manager.ts
|
|
31628
|
-
import { execFileSync as execFileSync3, execSync } from "node:child_process";
|
|
31629
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, rmSync, statSync } from "node:fs";
|
|
31630
|
-
import { join as join4, resolve as resolve2, sep } from "node:path";
|
|
31631
|
-
|
|
31632
|
-
class WorktreeManager {
|
|
31633
|
-
config;
|
|
31634
|
-
projectPath;
|
|
31635
|
-
log;
|
|
31636
|
-
constructor(projectPath, config2, log) {
|
|
31637
|
-
this.projectPath = resolve2(projectPath);
|
|
31638
|
-
this.config = { ...DEFAULT_WORKTREE_CONFIG, ...config2 };
|
|
31639
|
-
this.log = log ?? ((_msg) => {
|
|
31640
|
-
return;
|
|
31641
|
-
});
|
|
31642
|
-
}
|
|
31643
|
-
get rootPath() {
|
|
31644
|
-
return join4(this.projectPath, this.config.rootDir);
|
|
31645
|
-
}
|
|
31646
|
-
buildBranchName(taskId, taskSlug) {
|
|
31647
|
-
const sanitized = taskSlug.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
31648
|
-
return `${this.config.branchPrefix}/${taskId}-${sanitized}`;
|
|
31649
|
-
}
|
|
31650
|
-
create(options) {
|
|
31651
|
-
const branch = this.buildBranchName(options.taskId, options.taskSlug);
|
|
31652
|
-
const worktreeDir = `${options.agentId}-${options.taskId}`;
|
|
31653
|
-
const worktreePath = join4(this.rootPath, worktreeDir);
|
|
31654
|
-
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
31655
|
-
const baseBranch = options.baseBranch ?? this.config.baseBranch ?? this.getCurrentBranch();
|
|
31656
|
-
if (!this.branchExists(baseBranch)) {
|
|
31657
|
-
this.log(`Base branch "${baseBranch}" not found locally, fetching from origin`, "info");
|
|
31658
|
-
try {
|
|
31659
|
-
this.gitExec(["fetch", "origin", baseBranch], this.projectPath);
|
|
31660
|
-
this.gitExec(["branch", baseBranch, `origin/${baseBranch}`], this.projectPath);
|
|
31661
|
-
} catch {
|
|
31662
|
-
this.log(`Could not fetch/create local branch for "${baseBranch}", falling back to current branch`, "warn");
|
|
31663
|
-
}
|
|
31664
|
-
}
|
|
31665
|
-
this.log(`Creating worktree: ${worktreeDir} (branch: ${branch}, base: ${baseBranch})`, "info");
|
|
31666
|
-
if (existsSync4(worktreePath)) {
|
|
31667
|
-
this.log(`Removing stale worktree directory: ${worktreePath}`, "warn");
|
|
31668
|
-
try {
|
|
31669
|
-
this.git(`worktree remove "${worktreePath}" --force`, this.projectPath);
|
|
31670
|
-
} catch {
|
|
31671
|
-
rmSync(worktreePath, { recursive: true, force: true });
|
|
31672
|
-
this.git("worktree prune", this.projectPath);
|
|
31673
|
-
}
|
|
31674
|
-
}
|
|
31675
|
-
if (this.branchExists(branch)) {
|
|
31676
|
-
this.log(`Deleting existing branch: ${branch}`, "warn");
|
|
31677
|
-
const branchWorktrees = this.list().filter((wt) => wt.branch === branch);
|
|
31678
|
-
for (const wt of branchWorktrees) {
|
|
31679
|
-
const worktreePath2 = resolve2(wt.path);
|
|
31680
|
-
if (wt.isMain || !this.isManagedWorktreePath(worktreePath2)) {
|
|
31681
|
-
throw new Error(`Branch "${branch}" is checked out at "${worktreePath2}". Remove or detach that worktree before retrying.`);
|
|
31682
|
-
}
|
|
31683
|
-
this.log(`Removing existing worktree for branch: ${branch} (${worktreePath2})`, "warn");
|
|
31684
|
-
this.remove(worktreePath2, false);
|
|
31685
|
-
}
|
|
31686
|
-
try {
|
|
31687
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
31688
|
-
} catch {
|
|
31689
|
-
this.git("worktree prune", this.projectPath);
|
|
31690
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
31691
|
-
}
|
|
31692
|
-
}
|
|
31693
|
-
const addWorktree = () => this.git(`worktree add "${worktreePath}" -b "${branch}" "${baseBranch}"`, this.projectPath);
|
|
31694
|
-
try {
|
|
31695
|
-
addWorktree();
|
|
31696
|
-
} catch (error48) {
|
|
31697
|
-
if (!this.isMissingDirectoryError(error48)) {
|
|
31698
|
-
throw error48;
|
|
31699
|
-
}
|
|
31700
|
-
this.log(`Worktree creation failed due to missing directories. Retrying after cleanup: ${worktreePath}`, "warn");
|
|
31701
|
-
this.cleanupFailedWorktree(worktreePath, branch);
|
|
31702
|
-
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
31703
|
-
addWorktree();
|
|
31704
|
-
}
|
|
31705
|
-
const baseCommitHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31706
|
-
this.log(`Worktree created at ${worktreePath} (base: ${baseCommitHash.slice(0, 8)})`, "success");
|
|
31707
|
-
return { worktreePath, branch, baseBranch, baseCommitHash };
|
|
31708
|
-
}
|
|
31709
|
-
list() {
|
|
31710
|
-
const output = this.git("worktree list --porcelain", this.projectPath);
|
|
31711
|
-
const worktrees = [];
|
|
31712
|
-
const blocks = output.trim().split(`
|
|
31713
|
-
|
|
31714
|
-
`);
|
|
31715
|
-
for (const block of blocks) {
|
|
31716
|
-
if (!block.trim())
|
|
31717
|
-
continue;
|
|
31718
|
-
const lines = block.trim().split(`
|
|
31719
|
-
`);
|
|
31720
|
-
let path = "";
|
|
31721
|
-
let head = "";
|
|
31722
|
-
let branch = "";
|
|
31723
|
-
let isMain = false;
|
|
31724
|
-
let isPrunable = false;
|
|
31725
|
-
for (const line of lines) {
|
|
31726
|
-
if (line.startsWith("worktree ")) {
|
|
31727
|
-
path = line.slice("worktree ".length);
|
|
31728
|
-
} else if (line.startsWith("HEAD ")) {
|
|
31729
|
-
head = line.slice("HEAD ".length);
|
|
31730
|
-
} else if (line.startsWith("branch ")) {
|
|
31731
|
-
branch = line.slice("branch ".length).replace("refs/heads/", "");
|
|
31732
|
-
} else if (line === "bare" || path === this.projectPath) {
|
|
31733
|
-
isMain = true;
|
|
31734
|
-
} else if (line === "prunable") {
|
|
31735
|
-
isPrunable = true;
|
|
31736
|
-
} else if (line === "detached") {
|
|
31737
|
-
branch = "(detached)";
|
|
31738
|
-
}
|
|
31739
|
-
}
|
|
31740
|
-
if (resolve2(path) === this.projectPath) {
|
|
31741
|
-
isMain = true;
|
|
31742
|
-
}
|
|
31743
|
-
if (path) {
|
|
31744
|
-
worktrees.push({ path, branch, head, isMain, isPrunable });
|
|
31745
|
-
}
|
|
31746
|
-
}
|
|
31747
|
-
return worktrees;
|
|
31748
|
-
}
|
|
31749
|
-
listAgentWorktrees() {
|
|
31750
|
-
return this.list().filter((wt) => !wt.isMain);
|
|
31751
|
-
}
|
|
31752
|
-
remove(worktreePath, deleteBranch = true) {
|
|
31753
|
-
const absolutePath = resolve2(worktreePath);
|
|
31754
|
-
const worktrees = this.list();
|
|
31755
|
-
const worktree = worktrees.find((wt) => resolve2(wt.path) === absolutePath);
|
|
31756
|
-
const branchToDelete = worktree?.branch;
|
|
31757
|
-
this.log(`Removing worktree: ${absolutePath}`, "info");
|
|
31758
|
-
try {
|
|
31759
|
-
this.git(`worktree remove "${absolutePath}" --force`, this.projectPath);
|
|
31760
|
-
} catch {
|
|
31761
|
-
if (existsSync4(absolutePath)) {
|
|
31762
|
-
rmSync(absolutePath, { recursive: true, force: true });
|
|
31763
|
-
}
|
|
31764
|
-
this.git("worktree prune", this.projectPath);
|
|
31765
|
-
}
|
|
31766
|
-
if (deleteBranch && branchToDelete && !branchToDelete.startsWith("(")) {
|
|
31767
|
-
try {
|
|
31768
|
-
this.git(`branch -D "${branchToDelete}"`, this.projectPath);
|
|
31769
|
-
this.log(`Deleted branch: ${branchToDelete}`, "success");
|
|
31770
|
-
} catch {
|
|
31771
|
-
this.log(`Could not delete branch: ${branchToDelete} (may already be deleted)`, "warn");
|
|
31772
|
-
}
|
|
31773
|
-
}
|
|
31774
|
-
this.log("Worktree removed", "success");
|
|
31775
|
-
}
|
|
31776
|
-
prune() {
|
|
31777
|
-
const before = this.listAgentWorktrees().filter((wt) => wt.isPrunable).length;
|
|
31778
|
-
this.git("worktree prune", this.projectPath);
|
|
31779
|
-
const after = this.listAgentWorktrees().filter((wt) => wt.isPrunable).length;
|
|
31780
|
-
const pruned = before - after;
|
|
31781
|
-
if (pruned > 0) {
|
|
31782
|
-
this.log(`Pruned ${pruned} stale worktree(s)`, "success");
|
|
31783
|
-
}
|
|
31784
|
-
return pruned;
|
|
31785
|
-
}
|
|
31786
|
-
removeAll() {
|
|
31787
|
-
const agentWorktrees = this.listAgentWorktrees();
|
|
31788
|
-
let removed = 0;
|
|
31789
|
-
for (const wt of agentWorktrees) {
|
|
31790
|
-
try {
|
|
31791
|
-
this.remove(wt.path, true);
|
|
31792
|
-
removed++;
|
|
31793
|
-
} catch {
|
|
31794
|
-
this.log(`Failed to remove worktree: ${wt.path}`, "warn");
|
|
31795
|
-
}
|
|
31796
|
-
}
|
|
31797
|
-
if (existsSync4(this.rootPath)) {
|
|
31798
|
-
try {
|
|
31799
|
-
rmSync(this.rootPath, { recursive: true, force: true });
|
|
31800
|
-
} catch {}
|
|
31801
|
-
}
|
|
31802
|
-
return removed;
|
|
31803
|
-
}
|
|
31804
|
-
hasChanges(worktreePath) {
|
|
31805
|
-
const status = this.git("status --porcelain", worktreePath).trim();
|
|
31806
|
-
return status.length > 0;
|
|
31807
|
-
}
|
|
31808
|
-
hasCommitsAhead(worktreePath, baseBranch) {
|
|
31809
|
-
try {
|
|
31810
|
-
const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
|
|
31811
|
-
return Number.parseInt(count, 10) > 0;
|
|
31812
|
-
} catch (err) {
|
|
31813
|
-
this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
31814
|
-
return false;
|
|
31815
|
-
}
|
|
31816
|
-
}
|
|
31817
|
-
hasCommitsAheadOfHash(worktreePath, baseHash) {
|
|
31818
|
-
try {
|
|
31819
|
-
const headHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31820
|
-
return headHash !== baseHash;
|
|
31821
|
-
} catch {
|
|
31822
|
-
return false;
|
|
31823
|
-
}
|
|
31824
|
-
}
|
|
31825
|
-
commitChanges(worktreePath, message, baseBranch, baseCommitHash) {
|
|
31826
|
-
const hasUncommittedChanges = this.hasChanges(worktreePath);
|
|
31827
|
-
if (hasUncommittedChanges) {
|
|
31828
|
-
const statusOutput = this.git("status --porcelain", worktreePath).trim();
|
|
31829
|
-
this.log(`Detected uncommitted changes:
|
|
31830
|
-
${statusOutput.split(`
|
|
31831
|
-
`).slice(0, 10).join(`
|
|
31832
|
-
`)}${statusOutput.split(`
|
|
31833
|
-
`).length > 10 ? `
|
|
31834
|
-
... and ${statusOutput.split(`
|
|
31835
|
-
`).length - 10} more` : ""}`, "info");
|
|
31836
|
-
}
|
|
31837
|
-
if (!hasUncommittedChanges) {
|
|
31838
|
-
if (baseBranch && this.hasCommitsAhead(worktreePath, baseBranch)) {
|
|
31839
|
-
const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31840
|
-
this.log(`Agent already committed changes (${hash3.slice(0, 8)}); skipping additional commit`, "info");
|
|
31841
|
-
return hash3;
|
|
31842
|
-
}
|
|
31843
|
-
if (baseCommitHash && this.hasCommitsAheadOfHash(worktreePath, baseCommitHash)) {
|
|
31844
|
-
const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31845
|
-
this.log(`Agent already committed changes (${hash3.slice(0, 8)}, detected via base commit hash); skipping additional commit`, "info");
|
|
31846
|
-
return hash3;
|
|
31847
|
-
}
|
|
31848
|
-
const branch = this.getBranch(worktreePath);
|
|
31849
|
-
this.log(`No changes detected in worktree (branch: ${branch}, baseBranch: ${baseBranch ?? "none"}, baseCommitHash: ${baseCommitHash?.slice(0, 8) ?? "none"})`, "warn");
|
|
31850
|
-
return null;
|
|
31851
|
-
}
|
|
31852
|
-
this.git("add -A", worktreePath);
|
|
31853
|
-
const staged = this.git("diff --cached --name-only", worktreePath).trim();
|
|
31854
|
-
if (!staged) {
|
|
31855
|
-
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
31856
|
-
return null;
|
|
31857
|
-
}
|
|
31858
|
-
this.log(`Staging ${staged.split(`
|
|
31859
|
-
`).length} file(s) for commit`, "info");
|
|
31860
|
-
this.gitExec(["commit", "-m", message], worktreePath);
|
|
31861
|
-
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31862
|
-
this.log(`Committed: ${hash2.slice(0, 8)}`, "success");
|
|
31863
|
-
return hash2;
|
|
31864
|
-
}
|
|
31865
|
-
pushBranch(worktreePath, remote = "origin") {
|
|
31866
|
-
const branch = this.getBranch(worktreePath);
|
|
31867
|
-
this.log(`Pushing branch ${branch} to ${remote}`, "info");
|
|
31868
|
-
try {
|
|
31869
|
-
this.gitExec(["push", "-u", remote, branch], worktreePath);
|
|
31870
|
-
this.log(`Pushed ${branch} to ${remote}`, "success");
|
|
31871
|
-
return branch;
|
|
31872
|
-
} catch (error48) {
|
|
31873
|
-
if (!this.isNonFastForwardPushError(error48)) {
|
|
31874
|
-
throw error48;
|
|
31875
|
-
}
|
|
31876
|
-
this.log(`Push rejected for ${branch} (non-fast-forward). Retrying with --force-with-lease.`, "warn");
|
|
31877
|
-
try {
|
|
31878
|
-
this.gitExec(["fetch", remote, branch], worktreePath);
|
|
31879
|
-
} catch {}
|
|
31880
|
-
this.gitExec(["push", "--force-with-lease", "-u", remote, branch], worktreePath);
|
|
31881
|
-
this.log(`Pushed ${branch} to ${remote} with --force-with-lease`, "success");
|
|
31882
|
-
return branch;
|
|
31883
|
-
}
|
|
31884
|
-
}
|
|
31885
|
-
getBranch(worktreePath) {
|
|
31886
|
-
return this.git("rev-parse --abbrev-ref HEAD", worktreePath).trim();
|
|
31887
|
-
}
|
|
31888
|
-
hasWorktreeForTask(taskId) {
|
|
31889
|
-
return this.listAgentWorktrees().some((wt) => wt.branch.includes(taskId) || wt.path.includes(taskId));
|
|
31890
|
-
}
|
|
31891
|
-
branchExists(branchName) {
|
|
31892
|
-
try {
|
|
31893
|
-
this.git(`rev-parse --verify "refs/heads/${branchName}"`, this.projectPath);
|
|
31894
|
-
return true;
|
|
31895
|
-
} catch {
|
|
31896
|
-
return false;
|
|
31897
|
-
}
|
|
31898
|
-
}
|
|
31899
|
-
getCurrentBranch() {
|
|
31900
|
-
return this.git("rev-parse --abbrev-ref HEAD", this.projectPath).trim();
|
|
31901
|
-
}
|
|
31902
|
-
isManagedWorktreePath(worktreePath) {
|
|
31903
|
-
const rootPath = resolve2(this.rootPath);
|
|
31904
|
-
const candidate = resolve2(worktreePath);
|
|
31905
|
-
const rootWithSep = rootPath.endsWith(sep) ? rootPath : `${rootPath}${sep}`;
|
|
31906
|
-
return candidate.startsWith(rootWithSep);
|
|
31907
|
-
}
|
|
31908
|
-
ensureDirectory(dirPath, label) {
|
|
31909
|
-
if (existsSync4(dirPath)) {
|
|
31910
|
-
if (!statSync(dirPath).isDirectory()) {
|
|
31911
|
-
throw new Error(`${label} exists but is not a directory: ${dirPath}`);
|
|
31912
|
-
}
|
|
31913
|
-
return;
|
|
31914
|
-
}
|
|
31915
|
-
mkdirSync2(dirPath, { recursive: true });
|
|
31916
|
-
}
|
|
31917
|
-
isMissingDirectoryError(error48) {
|
|
31918
|
-
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
31919
|
-
return message.includes("cannot create directory") || message.includes("No such file or directory");
|
|
31920
|
-
}
|
|
31921
|
-
cleanupFailedWorktree(worktreePath, branch) {
|
|
31922
|
-
try {
|
|
31923
|
-
this.git(`worktree remove "${worktreePath}" --force`, this.projectPath);
|
|
31924
|
-
} catch {}
|
|
31925
|
-
if (existsSync4(worktreePath)) {
|
|
31926
|
-
rmSync(worktreePath, { recursive: true, force: true });
|
|
31927
|
-
}
|
|
31928
|
-
try {
|
|
31929
|
-
this.git("worktree prune", this.projectPath);
|
|
31930
|
-
} catch {}
|
|
31931
|
-
if (this.branchExists(branch)) {
|
|
31932
|
-
try {
|
|
31933
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
31934
|
-
} catch {}
|
|
31935
|
-
}
|
|
31936
|
-
}
|
|
31937
|
-
isNonFastForwardPushError(error48) {
|
|
31938
|
-
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
31939
|
-
return message.includes("non-fast-forward") || message.includes("[rejected]") || message.includes("fetch first");
|
|
31940
|
-
}
|
|
31941
|
-
git(args, cwd) {
|
|
31942
|
-
return execSync(`git ${args}`, {
|
|
31943
|
-
cwd,
|
|
31944
|
-
encoding: "utf-8",
|
|
31945
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31946
|
-
});
|
|
31947
|
-
}
|
|
31948
|
-
gitExec(args, cwd) {
|
|
31949
|
-
return execFileSync3("git", args, {
|
|
31950
|
-
cwd,
|
|
31605
|
+
gitExec(args) {
|
|
31606
|
+
return execFileSync2("git", args, {
|
|
31607
|
+
cwd: this.projectPath,
|
|
31951
31608
|
encoding: "utf-8",
|
|
31952
31609
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31953
31610
|
});
|
|
31954
31611
|
}
|
|
31955
31612
|
}
|
|
31956
|
-
var
|
|
31957
|
-
|
|
31613
|
+
var init_git_workflow = __esm(() => {
|
|
31614
|
+
init_git_utils();
|
|
31958
31615
|
});
|
|
31959
31616
|
|
|
31960
31617
|
// ../sdk/src/core/prompt-builder.ts
|
|
31961
|
-
import { existsSync as
|
|
31962
|
-
import { join as
|
|
31618
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
31619
|
+
import { join as join4 } from "node:path";
|
|
31963
31620
|
|
|
31964
31621
|
class PromptBuilder {
|
|
31965
31622
|
projectPath;
|
|
@@ -32001,7 +31658,7 @@ ${task2.description || "No description provided."}
|
|
|
32001
31658
|
}
|
|
32002
31659
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
32003
31660
|
let hasLocalContext = false;
|
|
32004
|
-
if (
|
|
31661
|
+
if (existsSync4(contextPath)) {
|
|
32005
31662
|
try {
|
|
32006
31663
|
const context = readFileSync4(contextPath, "utf-8");
|
|
32007
31664
|
if (context.trim().length > 20) {
|
|
@@ -32057,7 +31714,7 @@ ${serverContext.context}
|
|
|
32057
31714
|
|
|
32058
31715
|
`;
|
|
32059
31716
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
32060
|
-
if (
|
|
31717
|
+
if (existsSync4(indexPath)) {
|
|
32061
31718
|
prompt += `## Codebase Overview
|
|
32062
31719
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
32063
31720
|
|
|
@@ -32134,7 +31791,7 @@ ${query}
|
|
|
32134
31791
|
}
|
|
32135
31792
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
32136
31793
|
let hasLocalContext = false;
|
|
32137
|
-
if (
|
|
31794
|
+
if (existsSync4(contextPath)) {
|
|
32138
31795
|
try {
|
|
32139
31796
|
const context = readFileSync4(contextPath, "utf-8");
|
|
32140
31797
|
if (context.trim().length > 20) {
|
|
@@ -32170,7 +31827,7 @@ ${fallback}
|
|
|
32170
31827
|
|
|
32171
31828
|
`;
|
|
32172
31829
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
32173
|
-
if (
|
|
31830
|
+
if (existsSync4(indexPath)) {
|
|
32174
31831
|
prompt += `## Codebase Overview
|
|
32175
31832
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
32176
31833
|
|
|
@@ -32185,7 +31842,7 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
32185
31842
|
}
|
|
32186
31843
|
getProjectConfig() {
|
|
32187
31844
|
const configPath = getLocusPath(this.projectPath, "configFile");
|
|
32188
|
-
if (
|
|
31845
|
+
if (existsSync4(configPath)) {
|
|
32189
31846
|
try {
|
|
32190
31847
|
return JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
32191
31848
|
} catch {
|
|
@@ -32195,8 +31852,8 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
32195
31852
|
return null;
|
|
32196
31853
|
}
|
|
32197
31854
|
getFallbackContext() {
|
|
32198
|
-
const readmePath =
|
|
32199
|
-
if (
|
|
31855
|
+
const readmePath = join4(this.projectPath, "README.md");
|
|
31856
|
+
if (existsSync4(readmePath)) {
|
|
32200
31857
|
try {
|
|
32201
31858
|
const content = readFileSync4(readmePath, "utf-8");
|
|
32202
31859
|
const limit = 1000;
|
|
@@ -32215,7 +31872,7 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
32215
31872
|
if (e.startsWith(".") || e === "node_modules")
|
|
32216
31873
|
return false;
|
|
32217
31874
|
try {
|
|
32218
|
-
return
|
|
31875
|
+
return statSync(join4(this.projectPath, e)).isDirectory();
|
|
32219
31876
|
} catch {
|
|
32220
31877
|
return false;
|
|
32221
31878
|
}
|
|
@@ -32273,7 +31930,6 @@ class TaskExecutor {
|
|
|
32273
31930
|
this.deps.log(`Executing: ${task2.title}`, "info");
|
|
32274
31931
|
const basePrompt = await this.promptBuilder.build(task2);
|
|
32275
31932
|
try {
|
|
32276
|
-
this.deps.log("Starting Execution...", "info");
|
|
32277
31933
|
const output = await this.deps.aiRunner.run(basePrompt);
|
|
32278
31934
|
const summary = this.extractSummary(output);
|
|
32279
31935
|
return { success: true, summary };
|
|
@@ -32300,162 +31956,6 @@ var init_task_executor = __esm(() => {
|
|
|
32300
31956
|
init_prompt_builder();
|
|
32301
31957
|
});
|
|
32302
31958
|
|
|
32303
|
-
// ../sdk/src/agent/git-workflow.ts
|
|
32304
|
-
class GitWorkflow {
|
|
32305
|
-
config;
|
|
32306
|
-
log;
|
|
32307
|
-
ghUsername;
|
|
32308
|
-
worktreeManager;
|
|
32309
|
-
prService;
|
|
32310
|
-
constructor(config2, log, ghUsername) {
|
|
32311
|
-
this.config = config2;
|
|
32312
|
-
this.log = log;
|
|
32313
|
-
this.ghUsername = ghUsername;
|
|
32314
|
-
const projectPath = config2.projectPath || process.cwd();
|
|
32315
|
-
this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }, log) : null;
|
|
32316
|
-
this.prService = config2.autoPush ? new PrService(projectPath, log) : null;
|
|
32317
|
-
}
|
|
32318
|
-
createTaskWorktree(task2, defaultExecutor) {
|
|
32319
|
-
if (!this.worktreeManager) {
|
|
32320
|
-
return {
|
|
32321
|
-
worktreePath: null,
|
|
32322
|
-
baseBranch: null,
|
|
32323
|
-
baseCommitHash: null,
|
|
32324
|
-
executor: defaultExecutor
|
|
32325
|
-
};
|
|
32326
|
-
}
|
|
32327
|
-
const slug = task2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
32328
|
-
const result = this.worktreeManager.create({
|
|
32329
|
-
taskId: task2.id,
|
|
32330
|
-
taskSlug: slug,
|
|
32331
|
-
agentId: this.config.agentId,
|
|
32332
|
-
baseBranch: this.config.baseBranch
|
|
32333
|
-
});
|
|
32334
|
-
this.log(`Worktree created: ${result.worktreePath} (${result.branch})`, "info");
|
|
32335
|
-
const provider = this.config.provider ?? PROVIDER.CLAUDE;
|
|
32336
|
-
const taskAiRunner = createAiRunner(provider, {
|
|
32337
|
-
projectPath: result.worktreePath,
|
|
32338
|
-
model: this.config.model,
|
|
32339
|
-
log: this.log
|
|
32340
|
-
});
|
|
32341
|
-
const taskExecutor = new TaskExecutor({
|
|
32342
|
-
aiRunner: taskAiRunner,
|
|
32343
|
-
projectPath: result.worktreePath,
|
|
32344
|
-
log: this.log
|
|
32345
|
-
});
|
|
32346
|
-
return {
|
|
32347
|
-
worktreePath: result.worktreePath,
|
|
32348
|
-
baseBranch: result.baseBranch,
|
|
32349
|
-
baseCommitHash: result.baseCommitHash,
|
|
32350
|
-
executor: taskExecutor
|
|
32351
|
-
};
|
|
32352
|
-
}
|
|
32353
|
-
commitAndPush(worktreePath, task2, baseBranch, baseCommitHash) {
|
|
32354
|
-
if (!this.worktreeManager) {
|
|
32355
|
-
return { branch: null, pushed: false, pushFailed: false };
|
|
32356
|
-
}
|
|
32357
|
-
try {
|
|
32358
|
-
const trailers = [
|
|
32359
|
-
`Task-ID: ${task2.id}`,
|
|
32360
|
-
`Agent: ${this.config.agentId}`,
|
|
32361
|
-
"Co-authored-by: LocusAI <agent@locusai.team>"
|
|
32362
|
-
];
|
|
32363
|
-
if (this.ghUsername) {
|
|
32364
|
-
trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
|
|
32365
|
-
}
|
|
32366
|
-
const commitMessage = `feat(agent): ${task2.title}
|
|
32367
|
-
|
|
32368
|
-
${trailers.join(`
|
|
32369
|
-
`)}`;
|
|
32370
|
-
const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch, baseCommitHash);
|
|
32371
|
-
if (!hash2) {
|
|
32372
|
-
this.log("No changes to commit for this task", "info");
|
|
32373
|
-
return {
|
|
32374
|
-
branch: null,
|
|
32375
|
-
pushed: false,
|
|
32376
|
-
pushFailed: false,
|
|
32377
|
-
noChanges: true,
|
|
32378
|
-
skipReason: "No changes were committed, so no branch was pushed."
|
|
32379
|
-
};
|
|
32380
|
-
}
|
|
32381
|
-
const localBranch = this.worktreeManager.getBranch(worktreePath);
|
|
32382
|
-
if (this.config.autoPush) {
|
|
32383
|
-
try {
|
|
32384
|
-
return {
|
|
32385
|
-
branch: this.worktreeManager.pushBranch(worktreePath),
|
|
32386
|
-
pushed: true,
|
|
32387
|
-
pushFailed: false
|
|
32388
|
-
};
|
|
32389
|
-
} catch (err) {
|
|
32390
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32391
|
-
this.log(`Git push failed: ${errorMessage}`, "error");
|
|
32392
|
-
return {
|
|
32393
|
-
branch: localBranch,
|
|
32394
|
-
pushed: false,
|
|
32395
|
-
pushFailed: true,
|
|
32396
|
-
pushError: errorMessage
|
|
32397
|
-
};
|
|
32398
|
-
}
|
|
32399
|
-
}
|
|
32400
|
-
this.log("Auto-push disabled; skipping branch push", "info");
|
|
32401
|
-
return {
|
|
32402
|
-
branch: localBranch,
|
|
32403
|
-
pushed: false,
|
|
32404
|
-
pushFailed: false,
|
|
32405
|
-
skipReason: "Auto-push is disabled, so PR creation was skipped."
|
|
32406
|
-
};
|
|
32407
|
-
} catch (err) {
|
|
32408
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32409
|
-
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
32410
|
-
return {
|
|
32411
|
-
branch: null,
|
|
32412
|
-
pushed: false,
|
|
32413
|
-
pushFailed: true,
|
|
32414
|
-
pushError: `Git commit/push failed: ${errorMessage}`
|
|
32415
|
-
};
|
|
32416
|
-
}
|
|
32417
|
-
}
|
|
32418
|
-
createPullRequest(task2, branch, summary, baseBranch) {
|
|
32419
|
-
if (!this.prService) {
|
|
32420
|
-
const errorMessage = "PR service is not initialized. Enable auto-push to allow PR creation.";
|
|
32421
|
-
this.log(`PR creation skipped: ${errorMessage}`, "warn");
|
|
32422
|
-
return { url: null, error: errorMessage };
|
|
32423
|
-
}
|
|
32424
|
-
this.log(`Attempting PR creation from branch: ${branch}`, "info");
|
|
32425
|
-
try {
|
|
32426
|
-
const result = this.prService.createPr({
|
|
32427
|
-
task: task2,
|
|
32428
|
-
branch,
|
|
32429
|
-
baseBranch,
|
|
32430
|
-
agentId: this.config.agentId,
|
|
32431
|
-
summary
|
|
32432
|
-
});
|
|
32433
|
-
return { url: result.url };
|
|
32434
|
-
} catch (err) {
|
|
32435
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32436
|
-
this.log(`PR creation failed: ${errorMessage}`, "error");
|
|
32437
|
-
return { url: null, error: errorMessage };
|
|
32438
|
-
}
|
|
32439
|
-
}
|
|
32440
|
-
cleanupWorktree(worktreePath, keepBranch) {
|
|
32441
|
-
if (!this.worktreeManager || !worktreePath)
|
|
32442
|
-
return;
|
|
32443
|
-
try {
|
|
32444
|
-
this.worktreeManager.remove(worktreePath, !keepBranch);
|
|
32445
|
-
this.log(keepBranch ? "Worktree cleaned up (branch preserved)" : "Worktree cleaned up", "info");
|
|
32446
|
-
} catch {
|
|
32447
|
-
this.log(`Could not clean up worktree: ${worktreePath}`, "warn");
|
|
32448
|
-
}
|
|
32449
|
-
}
|
|
32450
|
-
}
|
|
32451
|
-
var init_git_workflow = __esm(() => {
|
|
32452
|
-
init_factory();
|
|
32453
|
-
init_config();
|
|
32454
|
-
init_pr_service();
|
|
32455
|
-
init_worktree_manager();
|
|
32456
|
-
init_task_executor();
|
|
32457
|
-
});
|
|
32458
|
-
|
|
32459
31959
|
// ../sdk/src/agent/worker-cli.ts
|
|
32460
31960
|
var exports_worker_cli = {};
|
|
32461
31961
|
__export(exports_worker_cli, {
|
|
@@ -32488,16 +31988,8 @@ function parseWorkerArgs(argv) {
|
|
|
32488
31988
|
config2.apiKey = args[++i];
|
|
32489
31989
|
else if (arg === "--project-path")
|
|
32490
31990
|
config2.projectPath = args[++i];
|
|
32491
|
-
else if (arg === "--main-project-path")
|
|
32492
|
-
config2.mainProjectPath = args[++i];
|
|
32493
31991
|
else if (arg === "--model")
|
|
32494
31992
|
config2.model = args[++i];
|
|
32495
|
-
else if (arg === "--use-worktrees")
|
|
32496
|
-
config2.useWorktrees = true;
|
|
32497
|
-
else if (arg === "--auto-push")
|
|
32498
|
-
config2.autoPush = true;
|
|
32499
|
-
else if (arg === "--base-branch")
|
|
32500
|
-
config2.baseBranch = args[++i];
|
|
32501
31993
|
else if (arg === "--provider") {
|
|
32502
31994
|
const value = args[i + 1];
|
|
32503
31995
|
if (value && !value.startsWith("--"))
|
|
@@ -32539,8 +32031,8 @@ class AgentWorker {
|
|
|
32539
32031
|
tasksCompleted = 0;
|
|
32540
32032
|
heartbeatInterval = null;
|
|
32541
32033
|
currentTaskId = null;
|
|
32542
|
-
|
|
32543
|
-
|
|
32034
|
+
completedTaskList = [];
|
|
32035
|
+
taskSummaries = [];
|
|
32544
32036
|
constructor(config2) {
|
|
32545
32037
|
this.config = config2;
|
|
32546
32038
|
const projectPath = config2.projectPath || process.cwd();
|
|
@@ -32555,17 +32047,12 @@ class AgentWorker {
|
|
|
32555
32047
|
}
|
|
32556
32048
|
});
|
|
32557
32049
|
const log = this.log.bind(this);
|
|
32558
|
-
if (
|
|
32559
|
-
this.log("git is not installed —
|
|
32560
|
-
config2.useWorktrees = false;
|
|
32050
|
+
if (!isGitAvailable()) {
|
|
32051
|
+
this.log("git is not installed — branch management will not work", "error");
|
|
32561
32052
|
}
|
|
32562
|
-
if (
|
|
32053
|
+
if (!isGhAvailable(projectPath)) {
|
|
32563
32054
|
this.log("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/", "warn");
|
|
32564
32055
|
}
|
|
32565
|
-
const ghUsername = config2.autoPush ? getGhUsername() : null;
|
|
32566
|
-
if (ghUsername) {
|
|
32567
|
-
this.log(`GitHub user: ${ghUsername}`, "info");
|
|
32568
|
-
}
|
|
32569
32056
|
const provider = config2.provider ?? PROVIDER.CLAUDE;
|
|
32570
32057
|
this.aiRunner = createAiRunner(provider, {
|
|
32571
32058
|
projectPath,
|
|
@@ -32578,18 +32065,9 @@ class AgentWorker {
|
|
|
32578
32065
|
log
|
|
32579
32066
|
});
|
|
32580
32067
|
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
32581
|
-
this.gitWorkflow = new GitWorkflow(config2, log
|
|
32068
|
+
this.gitWorkflow = new GitWorkflow(config2, log);
|
|
32582
32069
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
32583
32070
|
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
32584
|
-
if (config2.useWorktrees) {
|
|
32585
|
-
this.log("Per-task worktree isolation enabled", "info");
|
|
32586
|
-
if (config2.baseBranch) {
|
|
32587
|
-
this.log(`Base branch for worktrees: ${config2.baseBranch}`, "info");
|
|
32588
|
-
}
|
|
32589
|
-
if (config2.autoPush) {
|
|
32590
|
-
this.log("Auto-push enabled: branches will be pushed to remote", "info");
|
|
32591
|
-
}
|
|
32592
|
-
}
|
|
32593
32071
|
}
|
|
32594
32072
|
log(message, level = "info") {
|
|
32595
32073
|
const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
@@ -32638,56 +32116,26 @@ class AgentWorker {
|
|
|
32638
32116
|
}
|
|
32639
32117
|
async executeTask(task2) {
|
|
32640
32118
|
const fullTask = await this.client.tasks.getById(task2.id, this.config.workspaceId);
|
|
32641
|
-
const { worktreePath, baseBranch, baseCommitHash, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
32642
|
-
this.currentWorktreePath = worktreePath;
|
|
32643
|
-
let branchPushed = false;
|
|
32644
|
-
let keepBranch = false;
|
|
32645
|
-
let preserveWorktree = false;
|
|
32646
32119
|
try {
|
|
32647
|
-
const result = await
|
|
32648
|
-
let taskBranch = null;
|
|
32649
|
-
let prUrl = null;
|
|
32650
|
-
let prError = null;
|
|
32120
|
+
const result = await this.taskExecutor.execute(fullTask);
|
|
32651
32121
|
let noChanges = false;
|
|
32652
|
-
|
|
32653
|
-
|
|
32122
|
+
let taskBranch = null;
|
|
32123
|
+
if (result.success) {
|
|
32124
|
+
const commitResult = this.gitWorkflow.commitAndPush(fullTask);
|
|
32654
32125
|
taskBranch = commitResult.branch;
|
|
32655
|
-
branchPushed = commitResult.pushed;
|
|
32656
|
-
keepBranch = taskBranch !== null;
|
|
32657
32126
|
noChanges = Boolean(commitResult.noChanges);
|
|
32658
|
-
if (commitResult.pushFailed) {
|
|
32659
|
-
preserveWorktree = true;
|
|
32660
|
-
prError = commitResult.pushError ?? "Git push failed before PR creation. Please retry manually.";
|
|
32661
|
-
this.log(`Preserving worktree after push failure: ${worktreePath}`, "warn");
|
|
32662
|
-
}
|
|
32663
|
-
if (branchPushed && taskBranch) {
|
|
32664
|
-
const prResult = this.gitWorkflow.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
|
|
32665
|
-
prUrl = prResult.url;
|
|
32666
|
-
prError = prResult.error ?? null;
|
|
32667
|
-
if (!prUrl) {
|
|
32668
|
-
preserveWorktree = true;
|
|
32669
|
-
this.log(`Preserving worktree for manual follow-up: ${worktreePath}`, "warn");
|
|
32670
|
-
}
|
|
32671
|
-
} else if (commitResult.skipReason) {
|
|
32672
|
-
this.log(`Skipping PR creation: ${commitResult.skipReason}`, "info");
|
|
32673
|
-
}
|
|
32674
|
-
} else if (result.success && !worktreePath) {
|
|
32675
|
-
this.log("Skipping commit/push/PR flow because no task worktree is active.", "warn");
|
|
32676
32127
|
}
|
|
32677
32128
|
return {
|
|
32678
32129
|
...result,
|
|
32679
32130
|
branch: taskBranch ?? undefined,
|
|
32680
|
-
prUrl: prUrl ?? undefined,
|
|
32681
|
-
prError: prError ?? undefined,
|
|
32682
32131
|
noChanges: noChanges || undefined
|
|
32683
32132
|
};
|
|
32684
|
-
}
|
|
32685
|
-
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
}
|
|
32133
|
+
} catch (err) {
|
|
32134
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32135
|
+
return {
|
|
32136
|
+
success: false,
|
|
32137
|
+
summary: `Execution error: ${msg}`
|
|
32138
|
+
};
|
|
32691
32139
|
}
|
|
32692
32140
|
}
|
|
32693
32141
|
updateProgress(task2, summary) {
|
|
@@ -32700,7 +32148,6 @@ class AgentWorker {
|
|
|
32700
32148
|
role: "assistant",
|
|
32701
32149
|
content: summary
|
|
32702
32150
|
});
|
|
32703
|
-
this.log(`Updated progress.md: ${task2.title}`, "info");
|
|
32704
32151
|
} catch (err) {
|
|
32705
32152
|
this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
32706
32153
|
}
|
|
@@ -32720,19 +32167,13 @@ class AgentWorker {
|
|
|
32720
32167
|
this.log(`Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
32721
32168
|
});
|
|
32722
32169
|
}
|
|
32723
|
-
async delayAfterCleanup() {
|
|
32724
|
-
if (!this.config.useWorktrees || this.postCleanupDelayMs <= 0)
|
|
32725
|
-
return;
|
|
32726
|
-
this.log(`Waiting ${Math.floor(this.postCleanupDelayMs / 1000)}s after worktree cleanup before next dispatch`, "info");
|
|
32727
|
-
await new Promise((resolve3) => setTimeout(resolve3, this.postCleanupDelayMs));
|
|
32728
|
-
}
|
|
32729
32170
|
async run() {
|
|
32730
32171
|
this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
|
|
32731
32172
|
const handleShutdown = () => {
|
|
32732
32173
|
this.log("Received shutdown signal. Aborting...", "warn");
|
|
32733
32174
|
this.aiRunner.abort();
|
|
32734
32175
|
this.stopHeartbeat();
|
|
32735
|
-
this.gitWorkflow.
|
|
32176
|
+
this.gitWorkflow.checkoutBaseBranch();
|
|
32736
32177
|
process.exit(1);
|
|
32737
32178
|
};
|
|
32738
32179
|
process.on("SIGTERM", handleShutdown);
|
|
@@ -32744,10 +32185,12 @@ class AgentWorker {
|
|
|
32744
32185
|
} else {
|
|
32745
32186
|
this.log("No active sprint found.", "warn");
|
|
32746
32187
|
}
|
|
32188
|
+
const branchName = this.gitWorkflow.createBranch(this.config.sprintId);
|
|
32189
|
+
this.log(`Working on branch: ${branchName}`, "info");
|
|
32747
32190
|
while (this.tasksCompleted < this.maxTasks) {
|
|
32748
32191
|
const task2 = await this.getNextTask();
|
|
32749
32192
|
if (!task2) {
|
|
32750
|
-
this.log("No more tasks to process.
|
|
32193
|
+
this.log("No more tasks to process.", "info");
|
|
32751
32194
|
break;
|
|
32752
32195
|
}
|
|
32753
32196
|
this.log(`Claimed: ${task2.title}`, "success");
|
|
@@ -32763,7 +32206,7 @@ class AgentWorker {
|
|
|
32763
32206
|
});
|
|
32764
32207
|
await this.client.tasks.addComment(task2.id, this.config.workspaceId, {
|
|
32765
32208
|
author: this.config.agentId,
|
|
32766
|
-
text: `⚠️ Agent execution finished with no file changes, so no commit
|
|
32209
|
+
text: `⚠️ Agent execution finished with no file changes, so no commit was created.
|
|
32767
32210
|
|
|
32768
32211
|
${result.summary}`
|
|
32769
32212
|
});
|
|
@@ -32772,22 +32215,17 @@ ${result.summary}`
|
|
|
32772
32215
|
const updatePayload = {
|
|
32773
32216
|
status: "IN_REVIEW" /* IN_REVIEW */
|
|
32774
32217
|
};
|
|
32775
|
-
if (result.prUrl) {
|
|
32776
|
-
updatePayload.prUrl = result.prUrl;
|
|
32777
|
-
}
|
|
32778
32218
|
await this.client.tasks.update(task2.id, this.config.workspaceId, updatePayload);
|
|
32779
32219
|
const branchInfo = result.branch ? `
|
|
32780
32220
|
|
|
32781
32221
|
Branch: \`${result.branch}\`` : "";
|
|
32782
|
-
const prInfo = result.prUrl ? `
|
|
32783
|
-
PR: ${result.prUrl}` : "";
|
|
32784
|
-
const prErrorInfo = result.prError ? `
|
|
32785
|
-
PR automation error: ${result.prError}` : "";
|
|
32786
32222
|
await this.client.tasks.addComment(task2.id, this.config.workspaceId, {
|
|
32787
32223
|
author: this.config.agentId,
|
|
32788
|
-
text: `✅ ${result.summary}${branchInfo}
|
|
32224
|
+
text: `✅ ${result.summary}${branchInfo}`
|
|
32789
32225
|
});
|
|
32790
32226
|
this.tasksCompleted++;
|
|
32227
|
+
this.completedTaskList.push({ title: task2.title, id: task2.id });
|
|
32228
|
+
this.taskSummaries.push(result.summary);
|
|
32791
32229
|
this.updateProgress(task2, result.summary);
|
|
32792
32230
|
}
|
|
32793
32231
|
} else {
|
|
@@ -32803,8 +32241,24 @@ PR automation error: ${result.prError}` : "";
|
|
|
32803
32241
|
}
|
|
32804
32242
|
this.currentTaskId = null;
|
|
32805
32243
|
this.sendHeartbeat();
|
|
32806
|
-
await this.delayAfterCleanup();
|
|
32807
32244
|
}
|
|
32245
|
+
if (this.completedTaskList.length > 0) {
|
|
32246
|
+
this.log("All tasks done. Creating pull request...", "info");
|
|
32247
|
+
const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
|
|
32248
|
+
if (prResult.url) {
|
|
32249
|
+
this.log(`PR created: ${prResult.url}`, "success");
|
|
32250
|
+
for (const task2 of this.completedTaskList) {
|
|
32251
|
+
try {
|
|
32252
|
+
await this.client.tasks.update(task2.id, this.config.workspaceId, {
|
|
32253
|
+
prUrl: prResult.url
|
|
32254
|
+
});
|
|
32255
|
+
} catch {}
|
|
32256
|
+
}
|
|
32257
|
+
} else if (prResult.error) {
|
|
32258
|
+
this.log(`PR creation failed: ${prResult.error}`, "error");
|
|
32259
|
+
}
|
|
32260
|
+
}
|
|
32261
|
+
this.gitWorkflow.checkoutBaseBranch();
|
|
32808
32262
|
this.currentTaskId = null;
|
|
32809
32263
|
this.stopHeartbeat();
|
|
32810
32264
|
this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
|