@locusai/sdk 0.10.5 → 0.11.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/agent/git-workflow.d.ts +37 -23
- package/dist/agent/git-workflow.d.ts.map +1 -1
- package/dist/agent/worker-cli.d.ts.map +1 -1
- package/dist/agent/worker-types.d.ts +0 -10
- package/dist/agent/worker-types.d.ts.map +1 -1
- package/dist/agent/worker.d.ts +8 -8
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +255 -802
- package/dist/ai/codex-runner.d.ts.map +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/index-node.d.ts +0 -1
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +783 -1532
- package/dist/orchestrator/agent-pool.d.ts +1 -58
- package/dist/orchestrator/agent-pool.d.ts.map +1 -1
- package/dist/orchestrator/execution.d.ts +1 -54
- package/dist/orchestrator/execution.d.ts.map +1 -1
- package/dist/orchestrator/index.d.ts +25 -31
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/tier-merge.d.ts +1 -49
- package/dist/orchestrator/tier-merge.d.ts.map +1 -1
- package/dist/orchestrator/types.d.ts +0 -11
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/planning/agents/architect.d.ts.map +1 -1
- package/dist/planning/agents/cross-task-reviewer.d.ts +2 -6
- package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
- package/dist/planning/agents/sprint-organizer.d.ts +1 -1
- package/dist/planning/agents/sprint-organizer.d.ts.map +1 -1
- package/dist/planning/agents/tech-lead.d.ts.map +1 -1
- package/dist/planning/sprint-plan.d.ts +0 -2
- package/dist/planning/sprint-plan.d.ts.map +1 -1
- package/dist/worktree/index.d.ts +1 -2
- package/dist/worktree/index.d.ts.map +1 -1
- package/dist/worktree/worktree-config.d.ts +1 -59
- package/dist/worktree/worktree-config.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts +1 -115
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/agent/__tests__/orchestrator.cleanup.test.d.ts +0 -2
- package/dist/agent/__tests__/orchestrator.cleanup.test.d.ts.map +0 -1
- package/dist/agent/__tests__/worker.no-changes.test.d.ts +0 -2
- package/dist/agent/__tests__/worker.no-changes.test.d.ts.map +0 -1
package/dist/agent/worker.js
CHANGED
|
@@ -574,9 +574,6 @@ var init_config = __esm(() => {
|
|
|
574
574
|
"# Locus AI - Plans (generated per task)",
|
|
575
575
|
".locus/plans/",
|
|
576
576
|
"",
|
|
577
|
-
"# Locus AI - Agent worktrees (parallel execution)",
|
|
578
|
-
".locus-worktrees/",
|
|
579
|
-
"",
|
|
580
577
|
"# Locus AI - Settings (contains API key, telegram config, etc.)",
|
|
581
578
|
".locus/settings.json",
|
|
582
579
|
"",
|
|
@@ -1333,7 +1330,7 @@ class CodexRunner {
|
|
|
1333
1330
|
type: "tool_use",
|
|
1334
1331
|
tool: line.replace(/^[→•✓]\s*/, "")
|
|
1335
1332
|
});
|
|
1336
|
-
} else
|
|
1333
|
+
} else {
|
|
1337
1334
|
enqueueChunk({ type: "text_delta", content: `${line}
|
|
1338
1335
|
` });
|
|
1339
1336
|
}
|
|
@@ -1473,18 +1470,11 @@ class CodexRunner {
|
|
|
1473
1470
|
}
|
|
1474
1471
|
buildArgs(outputPath) {
|
|
1475
1472
|
const args = [
|
|
1476
|
-
"--ask-for-approval",
|
|
1477
|
-
"never",
|
|
1478
1473
|
"exec",
|
|
1479
|
-
"--
|
|
1480
|
-
"workspace-write",
|
|
1474
|
+
"--full-auto",
|
|
1481
1475
|
"--skip-git-repo-check",
|
|
1482
1476
|
"--output-last-message",
|
|
1483
|
-
outputPath
|
|
1484
|
-
"-c",
|
|
1485
|
-
"sandbox_workspace_write.network_access=true",
|
|
1486
|
-
"-c",
|
|
1487
|
-
'sandbox.excludedCommands=["git", "gh"]'
|
|
1477
|
+
outputPath
|
|
1488
1478
|
];
|
|
1489
1479
|
if (this.model) {
|
|
1490
1480
|
args.push("--model", this.model);
|
|
@@ -1760,583 +1750,247 @@ var init_knowledge_base = __esm(() => {
|
|
|
1760
1750
|
import_node_path5 = require("node:path");
|
|
1761
1751
|
});
|
|
1762
1752
|
|
|
1763
|
-
// src/git
|
|
1764
|
-
class
|
|
1765
|
-
|
|
1753
|
+
// src/agent/git-workflow.ts
|
|
1754
|
+
class GitWorkflow {
|
|
1755
|
+
config;
|
|
1766
1756
|
log;
|
|
1767
|
-
|
|
1768
|
-
|
|
1757
|
+
projectPath;
|
|
1758
|
+
branchName = null;
|
|
1759
|
+
baseBranch = null;
|
|
1760
|
+
ghUsername;
|
|
1761
|
+
constructor(config, log) {
|
|
1762
|
+
this.config = config;
|
|
1769
1763
|
this.log = log;
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
branch,
|
|
1775
|
-
baseBranch: requestedBaseBranch,
|
|
1776
|
-
agentId,
|
|
1777
|
-
summary
|
|
1778
|
-
} = options;
|
|
1779
|
-
const provider = detectRemoteProvider(this.projectPath);
|
|
1780
|
-
if (provider !== "github") {
|
|
1781
|
-
throw new Error(`PR creation is only supported for GitHub repositories (detected: ${provider})`);
|
|
1782
|
-
}
|
|
1783
|
-
if (!isGhAvailable(this.projectPath)) {
|
|
1784
|
-
throw new Error("GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/");
|
|
1785
|
-
}
|
|
1786
|
-
const title = `[Locus] ${task.title}`;
|
|
1787
|
-
const body = this.buildPrBody(task, agentId, summary);
|
|
1788
|
-
const baseBranch = requestedBaseBranch ?? getDefaultBranch(this.projectPath);
|
|
1789
|
-
this.validateCreatePrInputs(baseBranch, branch);
|
|
1790
|
-
this.log(`Creating PR: ${title} (${branch} → ${baseBranch})`, "info");
|
|
1791
|
-
const output = import_node_child_process4.execFileSync("gh", [
|
|
1792
|
-
"pr",
|
|
1793
|
-
"create",
|
|
1794
|
-
"--title",
|
|
1795
|
-
title,
|
|
1796
|
-
"--body",
|
|
1797
|
-
body,
|
|
1798
|
-
"--base",
|
|
1799
|
-
baseBranch,
|
|
1800
|
-
"--head",
|
|
1801
|
-
branch
|
|
1802
|
-
], {
|
|
1803
|
-
cwd: this.projectPath,
|
|
1804
|
-
encoding: "utf-8",
|
|
1805
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1806
|
-
}).trim();
|
|
1807
|
-
const url = output;
|
|
1808
|
-
const prNumber = this.extractPrNumber(url);
|
|
1809
|
-
this.log(`PR created: ${url}`, "success");
|
|
1810
|
-
return { url, number: prNumber };
|
|
1811
|
-
}
|
|
1812
|
-
validateCreatePrInputs(baseBranch, headBranch) {
|
|
1813
|
-
if (!this.hasRemoteBranch(baseBranch)) {
|
|
1814
|
-
throw new Error(`Base branch "${baseBranch}" does not exist on origin. Push/fetch refs and retry.`);
|
|
1815
|
-
}
|
|
1816
|
-
if (!this.hasRemoteBranch(headBranch)) {
|
|
1817
|
-
throw new Error(`Head branch "${headBranch}" is not available on origin. Ensure it is pushed before PR creation.`);
|
|
1818
|
-
}
|
|
1819
|
-
const baseRef = this.resolveBranchRef(baseBranch);
|
|
1820
|
-
const headRef = this.resolveBranchRef(headBranch);
|
|
1821
|
-
if (!baseRef) {
|
|
1822
|
-
throw new Error(`Could not resolve base branch "${baseBranch}" locally.`);
|
|
1823
|
-
}
|
|
1824
|
-
if (!headRef) {
|
|
1825
|
-
throw new Error(`Could not resolve head branch "${headBranch}" locally.`);
|
|
1826
|
-
}
|
|
1827
|
-
const commitsAhead = this.countCommitsAhead(baseRef, headRef);
|
|
1828
|
-
if (commitsAhead <= 0) {
|
|
1829
|
-
throw new Error(`No commits between "${baseBranch}" and "${headBranch}". Skipping PR creation.`);
|
|
1764
|
+
this.projectPath = config.projectPath || process.cwd();
|
|
1765
|
+
this.ghUsername = getGhUsername();
|
|
1766
|
+
if (this.ghUsername) {
|
|
1767
|
+
this.log(`GitHub user: ${this.ghUsername}`, "info");
|
|
1830
1768
|
}
|
|
1831
1769
|
}
|
|
1832
|
-
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
encoding: "utf-8",
|
|
1836
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1837
|
-
}).trim();
|
|
1838
|
-
const value = Number.parseInt(output || "0", 10);
|
|
1839
|
-
return Number.isNaN(value) ? 0 : value;
|
|
1840
|
-
}
|
|
1841
|
-
resolveBranchRef(branch) {
|
|
1842
|
-
if (this.hasLocalBranch(branch)) {
|
|
1843
|
-
return branch;
|
|
1844
|
-
}
|
|
1845
|
-
if (this.hasRemoteTrackingBranch(branch)) {
|
|
1846
|
-
return `origin/${branch}`;
|
|
1847
|
-
}
|
|
1848
|
-
return null;
|
|
1849
|
-
}
|
|
1850
|
-
hasLocalBranch(branch) {
|
|
1770
|
+
createBranch(sprintId) {
|
|
1771
|
+
const defaultBranch = getDefaultBranch(this.projectPath);
|
|
1772
|
+
this.baseBranch = defaultBranch;
|
|
1851
1773
|
try {
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1855
|
-
});
|
|
1856
|
-
return true;
|
|
1774
|
+
this.gitExec(["checkout", defaultBranch]);
|
|
1775
|
+
this.gitExec(["pull", "origin", defaultBranch]);
|
|
1857
1776
|
} catch {
|
|
1858
|
-
|
|
1777
|
+
this.log(`Could not pull latest from ${defaultBranch}, continuing with current state`, "warn");
|
|
1859
1778
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1779
|
+
const suffix = sprintId ? sprintId.slice(0, 8) : Date.now().toString(36);
|
|
1780
|
+
this.branchName = `locus/${suffix}`;
|
|
1862
1781
|
try {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
} catch {
|
|
1869
|
-
return false;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
hasRemoteBranch(branch) {
|
|
1873
|
-
try {
|
|
1874
|
-
import_node_child_process4.execFileSync("git", ["ls-remote", "--exit-code", "--heads", "origin", branch], {
|
|
1875
|
-
cwd: this.projectPath,
|
|
1876
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1877
|
-
});
|
|
1878
|
-
return true;
|
|
1879
|
-
} catch {
|
|
1880
|
-
return false;
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
getPrDiff(branch) {
|
|
1884
|
-
return import_node_child_process4.execFileSync("gh", ["pr", "diff", branch], {
|
|
1885
|
-
cwd: this.projectPath,
|
|
1886
|
-
encoding: "utf-8",
|
|
1887
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1888
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1889
|
-
});
|
|
1890
|
-
}
|
|
1891
|
-
submitReview(prIdentifier, body, event) {
|
|
1892
|
-
try {
|
|
1893
|
-
import_node_child_process4.execFileSync("gh", [
|
|
1894
|
-
"pr",
|
|
1895
|
-
"review",
|
|
1896
|
-
prIdentifier,
|
|
1897
|
-
"--body",
|
|
1898
|
-
body,
|
|
1899
|
-
`--${event.toLowerCase().replace("_", "-")}`
|
|
1900
|
-
], {
|
|
1901
|
-
cwd: this.projectPath,
|
|
1902
|
-
encoding: "utf-8",
|
|
1903
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1904
|
-
});
|
|
1905
|
-
} catch (err) {
|
|
1906
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1907
|
-
if (event === "REQUEST_CHANGES" && msg.includes("own pull request")) {
|
|
1908
|
-
import_node_child_process4.execFileSync("gh", ["pr", "review", prIdentifier, "--body", body, "--comment"], {
|
|
1909
|
-
cwd: this.projectPath,
|
|
1910
|
-
encoding: "utf-8",
|
|
1911
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1912
|
-
});
|
|
1913
|
-
return;
|
|
1914
|
-
}
|
|
1915
|
-
throw err;
|
|
1916
|
-
}
|
|
1782
|
+
this.gitExec(["branch", "-D", this.branchName]);
|
|
1783
|
+
} catch {}
|
|
1784
|
+
this.gitExec(["checkout", "-b", this.branchName]);
|
|
1785
|
+
this.log(`Created branch: ${this.branchName} (from ${defaultBranch})`, "success");
|
|
1786
|
+
return this.branchName;
|
|
1917
1787
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
"list",
|
|
1923
|
-
"--search",
|
|
1924
|
-
"[Locus] in:title",
|
|
1925
|
-
"--state",
|
|
1926
|
-
"open",
|
|
1927
|
-
"--json",
|
|
1928
|
-
"number,title,url,headRefName"
|
|
1929
|
-
], {
|
|
1930
|
-
cwd: this.projectPath,
|
|
1931
|
-
encoding: "utf-8",
|
|
1932
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1933
|
-
}).trim();
|
|
1934
|
-
const prs = JSON.parse(output || "[]");
|
|
1935
|
-
return prs.map((pr) => ({
|
|
1936
|
-
number: pr.number,
|
|
1937
|
-
title: pr.title,
|
|
1938
|
-
url: pr.url,
|
|
1939
|
-
branch: pr.headRefName
|
|
1940
|
-
}));
|
|
1941
|
-
} catch {
|
|
1942
|
-
this.log("Failed to list Locus PRs", "warn");
|
|
1943
|
-
return [];
|
|
1788
|
+
commitAndPush(task) {
|
|
1789
|
+
if (!this.branchName) {
|
|
1790
|
+
this.log("No branch created yet, skipping commit", "warn");
|
|
1791
|
+
return { branch: null, pushed: false, pushFailed: false };
|
|
1944
1792
|
}
|
|
1945
|
-
}
|
|
1946
|
-
hasLocusReview(prNumber) {
|
|
1947
1793
|
try {
|
|
1948
|
-
const
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
return data.reviews?.some((r) => r.body?.includes("## Locus Agent Review")) ?? false;
|
|
1955
|
-
} catch {
|
|
1956
|
-
return false;
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
listUnreviewedLocusPrs() {
|
|
1960
|
-
const allPrs = this.listLocusPrs();
|
|
1961
|
-
return allPrs.filter((pr) => !this.hasLocusReview(String(pr.number)));
|
|
1962
|
-
}
|
|
1963
|
-
buildPrBody(task, agentId, summary) {
|
|
1964
|
-
const sections = [];
|
|
1965
|
-
sections.push(`## Task: ${task.title}`);
|
|
1966
|
-
sections.push("");
|
|
1967
|
-
if (task.description) {
|
|
1968
|
-
sections.push(task.description);
|
|
1969
|
-
sections.push("");
|
|
1970
|
-
}
|
|
1971
|
-
if (task.acceptanceChecklist?.length > 0) {
|
|
1972
|
-
sections.push("## Acceptance Criteria");
|
|
1973
|
-
for (const item of task.acceptanceChecklist) {
|
|
1974
|
-
sections.push(`- [ ] ${item.text}`);
|
|
1975
|
-
}
|
|
1976
|
-
sections.push("");
|
|
1977
|
-
}
|
|
1978
|
-
if (summary) {
|
|
1979
|
-
sections.push("## Agent Summary");
|
|
1980
|
-
sections.push(summary);
|
|
1981
|
-
sections.push("");
|
|
1982
|
-
}
|
|
1983
|
-
sections.push("---");
|
|
1984
|
-
sections.push(`*Created by Locus Agent \`${agentId.slice(-8)}\`* | Task ID: \`${task.id}\``);
|
|
1985
|
-
return sections.join(`
|
|
1986
|
-
`);
|
|
1987
|
-
}
|
|
1988
|
-
extractPrNumber(url) {
|
|
1989
|
-
const match = url.match(/\/pull\/(\d+)/);
|
|
1990
|
-
return match ? Number.parseInt(match[1], 10) : 0;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
var import_node_child_process4;
|
|
1994
|
-
var init_pr_service = __esm(() => {
|
|
1995
|
-
init_git_utils();
|
|
1996
|
-
import_node_child_process4 = require("node:child_process");
|
|
1997
|
-
});
|
|
1998
|
-
|
|
1999
|
-
// src/worktree/worktree-config.ts
|
|
2000
|
-
var WORKTREE_ROOT_DIR = ".locus-worktrees", WORKTREE_BRANCH_PREFIX = "agent", DEFAULT_WORKTREE_CONFIG;
|
|
2001
|
-
var init_worktree_config = __esm(() => {
|
|
2002
|
-
DEFAULT_WORKTREE_CONFIG = {
|
|
2003
|
-
rootDir: WORKTREE_ROOT_DIR,
|
|
2004
|
-
branchPrefix: WORKTREE_BRANCH_PREFIX,
|
|
2005
|
-
cleanupPolicy: "retain-on-failure"
|
|
2006
|
-
};
|
|
2007
|
-
});
|
|
2008
|
-
|
|
2009
|
-
// src/worktree/worktree-manager.ts
|
|
2010
|
-
class WorktreeManager {
|
|
2011
|
-
config;
|
|
2012
|
-
projectPath;
|
|
2013
|
-
log;
|
|
2014
|
-
constructor(projectPath, config, log) {
|
|
2015
|
-
this.projectPath = import_node_path6.resolve(projectPath);
|
|
2016
|
-
this.config = { ...DEFAULT_WORKTREE_CONFIG, ...config };
|
|
2017
|
-
this.log = log ?? ((_msg) => {
|
|
2018
|
-
return;
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
get rootPath() {
|
|
2022
|
-
return import_node_path6.join(this.projectPath, this.config.rootDir);
|
|
2023
|
-
}
|
|
2024
|
-
buildBranchName(taskId, taskSlug) {
|
|
2025
|
-
const sanitized = taskSlug.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
2026
|
-
return `${this.config.branchPrefix}/${taskId}-${sanitized}`;
|
|
2027
|
-
}
|
|
2028
|
-
create(options) {
|
|
2029
|
-
const branch = this.buildBranchName(options.taskId, options.taskSlug);
|
|
2030
|
-
const worktreeDir = `${options.agentId}-${options.taskId}`;
|
|
2031
|
-
const worktreePath = import_node_path6.join(this.rootPath, worktreeDir);
|
|
2032
|
-
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
2033
|
-
const baseBranch = options.baseBranch ?? this.config.baseBranch ?? this.getCurrentBranch();
|
|
2034
|
-
if (!this.branchExists(baseBranch)) {
|
|
2035
|
-
this.log(`Base branch "${baseBranch}" not found locally, fetching from origin`, "info");
|
|
2036
|
-
try {
|
|
2037
|
-
this.gitExec(["fetch", "origin", baseBranch], this.projectPath);
|
|
2038
|
-
this.gitExec(["branch", baseBranch, `origin/${baseBranch}`], this.projectPath);
|
|
2039
|
-
} catch {
|
|
2040
|
-
this.log(`Could not fetch/create local branch for "${baseBranch}", falling back to current branch`, "warn");
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
this.log(`Creating worktree: ${worktreeDir} (branch: ${branch}, base: ${baseBranch})`, "info");
|
|
2044
|
-
if (import_node_fs4.existsSync(worktreePath)) {
|
|
2045
|
-
this.log(`Removing stale worktree directory: ${worktreePath}`, "warn");
|
|
2046
|
-
try {
|
|
2047
|
-
this.git(`worktree remove "${worktreePath}" --force`, this.projectPath);
|
|
2048
|
-
} catch {
|
|
2049
|
-
import_node_fs4.rmSync(worktreePath, { recursive: true, force: true });
|
|
2050
|
-
this.git("worktree prune", this.projectPath);
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
if (this.branchExists(branch)) {
|
|
2054
|
-
this.log(`Deleting existing branch: ${branch}`, "warn");
|
|
2055
|
-
const branchWorktrees = this.list().filter((wt) => wt.branch === branch);
|
|
2056
|
-
for (const wt of branchWorktrees) {
|
|
2057
|
-
const worktreePath2 = import_node_path6.resolve(wt.path);
|
|
2058
|
-
if (wt.isMain || !this.isManagedWorktreePath(worktreePath2)) {
|
|
2059
|
-
throw new Error(`Branch "${branch}" is checked out at "${worktreePath2}". Remove or detach that worktree before retrying.`);
|
|
1794
|
+
const status = this.gitExec(["status", "--porcelain"]).trim();
|
|
1795
|
+
if (!status) {
|
|
1796
|
+
const baseBranchCommit = this.getBaseCommit();
|
|
1797
|
+
const headCommit = this.gitExec(["rev-parse", "HEAD"]).trim();
|
|
1798
|
+
if (baseBranchCommit && headCommit !== baseBranchCommit) {
|
|
1799
|
+
return this.pushBranch();
|
|
2060
1800
|
}
|
|
2061
|
-
this.log(
|
|
2062
|
-
|
|
1801
|
+
this.log("No changes to commit for this task", "info");
|
|
1802
|
+
return {
|
|
1803
|
+
branch: this.branchName,
|
|
1804
|
+
pushed: false,
|
|
1805
|
+
pushFailed: false,
|
|
1806
|
+
noChanges: true,
|
|
1807
|
+
skipReason: "No changes were made for this task."
|
|
1808
|
+
};
|
|
2063
1809
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
this.
|
|
2068
|
-
|
|
1810
|
+
this.gitExec(["add", "-A"]);
|
|
1811
|
+
const staged = this.gitExec(["diff", "--cached", "--name-only"]).trim();
|
|
1812
|
+
if (!staged) {
|
|
1813
|
+
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
1814
|
+
return {
|
|
1815
|
+
branch: this.branchName,
|
|
1816
|
+
pushed: false,
|
|
1817
|
+
pushFailed: false,
|
|
1818
|
+
noChanges: true,
|
|
1819
|
+
skipReason: "All changes were ignored by .gitignore."
|
|
1820
|
+
};
|
|
2069
1821
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
1822
|
+
this.log(`Staging ${staged.split(`
|
|
1823
|
+
`).length} file(s) for commit`, "info");
|
|
1824
|
+
const trailers = [
|
|
1825
|
+
`Task-ID: ${task.id}`,
|
|
1826
|
+
`Agent: ${this.config.agentId}`,
|
|
1827
|
+
"Co-authored-by: LocusAI <agent@locusai.team>"
|
|
1828
|
+
];
|
|
1829
|
+
if (this.ghUsername) {
|
|
1830
|
+
trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
|
|
2077
1831
|
}
|
|
2078
|
-
|
|
2079
|
-
this.cleanupFailedWorktree(worktreePath, branch);
|
|
2080
|
-
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
2081
|
-
addWorktree();
|
|
2082
|
-
}
|
|
2083
|
-
const baseCommitHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
2084
|
-
this.log(`Worktree created at ${worktreePath} (base: ${baseCommitHash.slice(0, 8)})`, "success");
|
|
2085
|
-
return { worktreePath, branch, baseBranch, baseCommitHash };
|
|
2086
|
-
}
|
|
2087
|
-
list() {
|
|
2088
|
-
const output = this.git("worktree list --porcelain", this.projectPath);
|
|
2089
|
-
const worktrees = [];
|
|
2090
|
-
const blocks = output.trim().split(`
|
|
1832
|
+
const commitMessage = `feat(agent): ${task.title}
|
|
2091
1833
|
|
|
2092
|
-
`
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
head = line.slice("HEAD ".length);
|
|
2108
|
-
} else if (line.startsWith("branch ")) {
|
|
2109
|
-
branch = line.slice("branch ".length).replace("refs/heads/", "");
|
|
2110
|
-
} else if (line === "bare" || path === this.projectPath) {
|
|
2111
|
-
isMain = true;
|
|
2112
|
-
} else if (line === "prunable") {
|
|
2113
|
-
isPrunable = true;
|
|
2114
|
-
} else if (line === "detached") {
|
|
2115
|
-
branch = "(detached)";
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
if (import_node_path6.resolve(path) === this.projectPath) {
|
|
2119
|
-
isMain = true;
|
|
2120
|
-
}
|
|
2121
|
-
if (path) {
|
|
2122
|
-
worktrees.push({ path, branch, head, isMain, isPrunable });
|
|
2123
|
-
}
|
|
1834
|
+
${trailers.join(`
|
|
1835
|
+
`)}`;
|
|
1836
|
+
this.gitExec(["commit", "-m", commitMessage]);
|
|
1837
|
+
const hash = this.gitExec(["rev-parse", "HEAD"]).trim();
|
|
1838
|
+
this.log(`Committed: ${hash.slice(0, 8)}`, "success");
|
|
1839
|
+
return this.pushBranch();
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1842
|
+
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
1843
|
+
return {
|
|
1844
|
+
branch: this.branchName,
|
|
1845
|
+
pushed: false,
|
|
1846
|
+
pushFailed: true,
|
|
1847
|
+
pushError: `Git commit/push failed: ${errorMessage}`
|
|
1848
|
+
};
|
|
2124
1849
|
}
|
|
2125
|
-
return worktrees;
|
|
2126
|
-
}
|
|
2127
|
-
listAgentWorktrees() {
|
|
2128
|
-
return this.list().filter((wt) => !wt.isMain);
|
|
2129
1850
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
const worktree = worktrees.find((wt) => import_node_path6.resolve(wt.path) === absolutePath);
|
|
2134
|
-
const branchToDelete = worktree?.branch;
|
|
2135
|
-
this.log(`Removing worktree: ${absolutePath}`, "info");
|
|
2136
|
-
try {
|
|
2137
|
-
this.git(`worktree remove "${absolutePath}" --force`, this.projectPath);
|
|
2138
|
-
} catch {
|
|
2139
|
-
if (import_node_fs4.existsSync(absolutePath)) {
|
|
2140
|
-
import_node_fs4.rmSync(absolutePath, { recursive: true, force: true });
|
|
2141
|
-
}
|
|
2142
|
-
this.git("worktree prune", this.projectPath);
|
|
1851
|
+
pushBranch() {
|
|
1852
|
+
if (!this.branchName) {
|
|
1853
|
+
return { branch: null, pushed: false, pushFailed: false };
|
|
2143
1854
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
1855
|
+
try {
|
|
1856
|
+
this.gitExec(["push", "-u", "origin", this.branchName]);
|
|
1857
|
+
this.log(`Pushed ${this.branchName} to origin`, "success");
|
|
1858
|
+
return { branch: this.branchName, pushed: true, pushFailed: false };
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1861
|
+
if (msg.includes("non-fast-forward") || msg.includes("[rejected]") || msg.includes("fetch first")) {
|
|
1862
|
+
this.log(`Push rejected (non-fast-forward). Retrying with --force-with-lease.`, "warn");
|
|
1863
|
+
try {
|
|
1864
|
+
this.gitExec([
|
|
1865
|
+
"push",
|
|
1866
|
+
"--force-with-lease",
|
|
1867
|
+
"-u",
|
|
1868
|
+
"origin",
|
|
1869
|
+
this.branchName
|
|
1870
|
+
]);
|
|
1871
|
+
this.log(`Pushed ${this.branchName} to origin with --force-with-lease`, "success");
|
|
1872
|
+
return { branch: this.branchName, pushed: true, pushFailed: false };
|
|
1873
|
+
} catch (retryErr) {
|
|
1874
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
1875
|
+
this.log(`Git push retry failed: ${retryMsg}`, "error");
|
|
1876
|
+
return {
|
|
1877
|
+
branch: this.branchName,
|
|
1878
|
+
pushed: false,
|
|
1879
|
+
pushFailed: true,
|
|
1880
|
+
pushError: retryMsg
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
2150
1883
|
}
|
|
1884
|
+
this.log(`Git push failed: ${msg}`, "error");
|
|
1885
|
+
return {
|
|
1886
|
+
branch: this.branchName,
|
|
1887
|
+
pushed: false,
|
|
1888
|
+
pushFailed: true,
|
|
1889
|
+
pushError: msg
|
|
1890
|
+
};
|
|
2151
1891
|
}
|
|
2152
|
-
this.log("Worktree removed", "success");
|
|
2153
1892
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
const after = this.listAgentWorktrees().filter((wt) => wt.isPrunable).length;
|
|
2158
|
-
const pruned = before - after;
|
|
2159
|
-
if (pruned > 0) {
|
|
2160
|
-
this.log(`Pruned ${pruned} stale worktree(s)`, "success");
|
|
1893
|
+
createPullRequest(completedTasks, summaries) {
|
|
1894
|
+
if (!this.branchName || !this.baseBranch) {
|
|
1895
|
+
return { url: null, error: "No branch or base branch available." };
|
|
2161
1896
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
try {
|
|
2169
|
-
this.remove(wt.path, true);
|
|
2170
|
-
removed++;
|
|
2171
|
-
} catch {
|
|
2172
|
-
this.log(`Failed to remove worktree: ${wt.path}`, "warn");
|
|
2173
|
-
}
|
|
1897
|
+
const provider = detectRemoteProvider(this.projectPath);
|
|
1898
|
+
if (provider !== "github") {
|
|
1899
|
+
return {
|
|
1900
|
+
url: null,
|
|
1901
|
+
error: `PR creation is only supported for GitHub repositories (detected: ${provider})`
|
|
1902
|
+
};
|
|
2174
1903
|
}
|
|
2175
|
-
if (
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
1904
|
+
if (!isGhAvailable(this.projectPath)) {
|
|
1905
|
+
return {
|
|
1906
|
+
url: null,
|
|
1907
|
+
error: "GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/"
|
|
1908
|
+
};
|
|
2179
1909
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
const status = this.git("status --porcelain", worktreePath).trim();
|
|
2184
|
-
return status.length > 0;
|
|
2185
|
-
}
|
|
2186
|
-
hasCommitsAhead(worktreePath, baseBranch) {
|
|
1910
|
+
const title = `[Locus] Sprint tasks (${completedTasks.length} task${completedTasks.length !== 1 ? "s" : ""})`;
|
|
1911
|
+
const body = this.buildPrBody(completedTasks, summaries);
|
|
1912
|
+
this.log(`Creating PR: ${title} (${this.branchName} → ${this.baseBranch})`, "info");
|
|
2187
1913
|
try {
|
|
2188
|
-
const
|
|
2189
|
-
|
|
1914
|
+
const output = import_node_child_process4.execFileSync("gh", [
|
|
1915
|
+
"pr",
|
|
1916
|
+
"create",
|
|
1917
|
+
"--title",
|
|
1918
|
+
title,
|
|
1919
|
+
"--body",
|
|
1920
|
+
body,
|
|
1921
|
+
"--base",
|
|
1922
|
+
this.baseBranch,
|
|
1923
|
+
"--head",
|
|
1924
|
+
this.branchName
|
|
1925
|
+
], {
|
|
1926
|
+
cwd: this.projectPath,
|
|
1927
|
+
encoding: "utf-8",
|
|
1928
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1929
|
+
}).trim();
|
|
1930
|
+
this.log(`PR created: ${output}`, "success");
|
|
1931
|
+
return { url: output };
|
|
2190
1932
|
} catch (err) {
|
|
2191
|
-
|
|
2192
|
-
|
|
1933
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1934
|
+
this.log(`PR creation failed: ${errorMessage}`, "error");
|
|
1935
|
+
return { url: null, error: errorMessage };
|
|
2193
1936
|
}
|
|
2194
1937
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
return headHash !== baseHash;
|
|
2199
|
-
} catch {
|
|
2200
|
-
return false;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
commitChanges(worktreePath, message, baseBranch, baseCommitHash) {
|
|
2204
|
-
const hasUncommittedChanges = this.hasChanges(worktreePath);
|
|
2205
|
-
if (hasUncommittedChanges) {
|
|
2206
|
-
const statusOutput = this.git("status --porcelain", worktreePath).trim();
|
|
2207
|
-
this.log(`Detected uncommitted changes:
|
|
2208
|
-
${statusOutput.split(`
|
|
2209
|
-
`).slice(0, 10).join(`
|
|
2210
|
-
`)}${statusOutput.split(`
|
|
2211
|
-
`).length > 10 ? `
|
|
2212
|
-
... and ${statusOutput.split(`
|
|
2213
|
-
`).length - 10} more` : ""}`, "info");
|
|
2214
|
-
}
|
|
2215
|
-
if (!hasUncommittedChanges) {
|
|
2216
|
-
if (baseBranch && this.hasCommitsAhead(worktreePath, baseBranch)) {
|
|
2217
|
-
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
2218
|
-
this.log(`Agent already committed changes (${hash2.slice(0, 8)}); skipping additional commit`, "info");
|
|
2219
|
-
return hash2;
|
|
2220
|
-
}
|
|
2221
|
-
if (baseCommitHash && this.hasCommitsAheadOfHash(worktreePath, baseCommitHash)) {
|
|
2222
|
-
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
2223
|
-
this.log(`Agent already committed changes (${hash2.slice(0, 8)}, detected via base commit hash); skipping additional commit`, "info");
|
|
2224
|
-
return hash2;
|
|
2225
|
-
}
|
|
2226
|
-
const branch = this.getBranch(worktreePath);
|
|
2227
|
-
this.log(`No changes detected in worktree (branch: ${branch}, baseBranch: ${baseBranch ?? "none"}, baseCommitHash: ${baseCommitHash?.slice(0, 8) ?? "none"})`, "warn");
|
|
2228
|
-
return null;
|
|
2229
|
-
}
|
|
2230
|
-
this.git("add -A", worktreePath);
|
|
2231
|
-
const staged = this.git("diff --cached --name-only", worktreePath).trim();
|
|
2232
|
-
if (!staged) {
|
|
2233
|
-
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
2234
|
-
return null;
|
|
2235
|
-
}
|
|
2236
|
-
this.log(`Staging ${staged.split(`
|
|
2237
|
-
`).length} file(s) for commit`, "info");
|
|
2238
|
-
this.gitExec(["commit", "-m", message], worktreePath);
|
|
2239
|
-
const hash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
2240
|
-
this.log(`Committed: ${hash.slice(0, 8)}`, "success");
|
|
2241
|
-
return hash;
|
|
2242
|
-
}
|
|
2243
|
-
pushBranch(worktreePath, remote = "origin") {
|
|
2244
|
-
const branch = this.getBranch(worktreePath);
|
|
2245
|
-
this.log(`Pushing branch ${branch} to ${remote}`, "info");
|
|
1938
|
+
checkoutBaseBranch() {
|
|
1939
|
+
if (!this.baseBranch)
|
|
1940
|
+
return;
|
|
2246
1941
|
try {
|
|
2247
|
-
this.gitExec(["
|
|
2248
|
-
this.log(`
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
if (!this.isNonFastForwardPushError(error)) {
|
|
2252
|
-
throw error;
|
|
2253
|
-
}
|
|
2254
|
-
this.log(`Push rejected for ${branch} (non-fast-forward). Retrying with --force-with-lease.`, "warn");
|
|
2255
|
-
try {
|
|
2256
|
-
this.gitExec(["fetch", remote, branch], worktreePath);
|
|
2257
|
-
} catch {}
|
|
2258
|
-
this.gitExec(["push", "--force-with-lease", "-u", remote, branch], worktreePath);
|
|
2259
|
-
this.log(`Pushed ${branch} to ${remote} with --force-with-lease`, "success");
|
|
2260
|
-
return branch;
|
|
1942
|
+
this.gitExec(["checkout", this.baseBranch]);
|
|
1943
|
+
this.log(`Checked out base branch: ${this.baseBranch}`, "info");
|
|
1944
|
+
} catch (err) {
|
|
1945
|
+
this.log(`Could not checkout base branch: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
2261
1946
|
}
|
|
2262
1947
|
}
|
|
2263
|
-
|
|
2264
|
-
return this.
|
|
1948
|
+
getBranchName() {
|
|
1949
|
+
return this.branchName;
|
|
2265
1950
|
}
|
|
2266
|
-
|
|
2267
|
-
return this.
|
|
1951
|
+
getBaseBranch() {
|
|
1952
|
+
return this.baseBranch;
|
|
2268
1953
|
}
|
|
2269
|
-
|
|
1954
|
+
getBaseCommit() {
|
|
1955
|
+
if (!this.baseBranch)
|
|
1956
|
+
return null;
|
|
2270
1957
|
try {
|
|
2271
|
-
this.
|
|
2272
|
-
return true;
|
|
1958
|
+
return this.gitExec(["rev-parse", this.baseBranch]).trim();
|
|
2273
1959
|
} catch {
|
|
2274
|
-
return
|
|
1960
|
+
return null;
|
|
2275
1961
|
}
|
|
2276
1962
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
if (!import_node_fs4.statSync(dirPath).isDirectory()) {
|
|
2289
|
-
throw new Error(`${label} exists but is not a directory: ${dirPath}`);
|
|
1963
|
+
buildPrBody(completedTasks, summaries) {
|
|
1964
|
+
const sections = [];
|
|
1965
|
+
sections.push("## Completed Tasks");
|
|
1966
|
+
sections.push("");
|
|
1967
|
+
for (let i = 0;i < completedTasks.length; i++) {
|
|
1968
|
+
const task = completedTasks[i];
|
|
1969
|
+
sections.push(`### ${i + 1}. ${task.title}`);
|
|
1970
|
+
sections.push(`Task ID: \`${task.id}\``);
|
|
1971
|
+
if (summaries[i]) {
|
|
1972
|
+
sections.push("");
|
|
1973
|
+
sections.push(summaries[i]);
|
|
2290
1974
|
}
|
|
2291
|
-
|
|
2292
|
-
}
|
|
2293
|
-
import_node_fs4.mkdirSync(dirPath, { recursive: true });
|
|
2294
|
-
}
|
|
2295
|
-
isMissingDirectoryError(error) {
|
|
2296
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2297
|
-
return message.includes("cannot create directory") || message.includes("No such file or directory");
|
|
2298
|
-
}
|
|
2299
|
-
cleanupFailedWorktree(worktreePath, branch) {
|
|
2300
|
-
try {
|
|
2301
|
-
this.git(`worktree remove "${worktreePath}" --force`, this.projectPath);
|
|
2302
|
-
} catch {}
|
|
2303
|
-
if (import_node_fs4.existsSync(worktreePath)) {
|
|
2304
|
-
import_node_fs4.rmSync(worktreePath, { recursive: true, force: true });
|
|
2305
|
-
}
|
|
2306
|
-
try {
|
|
2307
|
-
this.git("worktree prune", this.projectPath);
|
|
2308
|
-
} catch {}
|
|
2309
|
-
if (this.branchExists(branch)) {
|
|
2310
|
-
try {
|
|
2311
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
2312
|
-
} catch {}
|
|
1975
|
+
sections.push("");
|
|
2313
1976
|
}
|
|
1977
|
+
sections.push("---");
|
|
1978
|
+
sections.push(`*Created by Locus Agent \`${this.config.agentId.slice(-8)}\`*`);
|
|
1979
|
+
return sections.join(`
|
|
1980
|
+
`);
|
|
2314
1981
|
}
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
}
|
|
2319
|
-
git(args, cwd) {
|
|
2320
|
-
return import_node_child_process5.execSync(`git ${args}`, {
|
|
2321
|
-
cwd,
|
|
2322
|
-
encoding: "utf-8",
|
|
2323
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2324
|
-
});
|
|
2325
|
-
}
|
|
2326
|
-
gitExec(args, cwd) {
|
|
2327
|
-
return import_node_child_process5.execFileSync("git", args, {
|
|
2328
|
-
cwd,
|
|
1982
|
+
gitExec(args) {
|
|
1983
|
+
return import_node_child_process4.execFileSync("git", args, {
|
|
1984
|
+
cwd: this.projectPath,
|
|
2329
1985
|
encoding: "utf-8",
|
|
2330
1986
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2331
1987
|
});
|
|
2332
1988
|
}
|
|
2333
1989
|
}
|
|
2334
|
-
var
|
|
2335
|
-
var
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
import_node_fs4 = require("node:fs");
|
|
2339
|
-
import_node_path6 = require("node:path");
|
|
1990
|
+
var import_node_child_process4;
|
|
1991
|
+
var init_git_workflow = __esm(() => {
|
|
1992
|
+
init_git_utils();
|
|
1993
|
+
import_node_child_process4 = require("node:child_process");
|
|
2340
1994
|
});
|
|
2341
1995
|
|
|
2342
1996
|
// src/core/prompt-builder.ts
|
|
@@ -2380,9 +2034,9 @@ ${task.description || "No description provided."}
|
|
|
2380
2034
|
}
|
|
2381
2035
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2382
2036
|
let hasLocalContext = false;
|
|
2383
|
-
if (
|
|
2037
|
+
if (import_node_fs4.existsSync(contextPath)) {
|
|
2384
2038
|
try {
|
|
2385
|
-
const context =
|
|
2039
|
+
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
2386
2040
|
if (context.trim().length > 20) {
|
|
2387
2041
|
prompt += `## Project Context (Local)
|
|
2388
2042
|
${context}
|
|
@@ -2436,7 +2090,7 @@ ${serverContext.context}
|
|
|
2436
2090
|
|
|
2437
2091
|
`;
|
|
2438
2092
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
2439
|
-
if (
|
|
2093
|
+
if (import_node_fs4.existsSync(indexPath)) {
|
|
2440
2094
|
prompt += `## Codebase Overview
|
|
2441
2095
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
2442
2096
|
|
|
@@ -2513,9 +2167,9 @@ ${query}
|
|
|
2513
2167
|
}
|
|
2514
2168
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2515
2169
|
let hasLocalContext = false;
|
|
2516
|
-
if (
|
|
2170
|
+
if (import_node_fs4.existsSync(contextPath)) {
|
|
2517
2171
|
try {
|
|
2518
|
-
const context =
|
|
2172
|
+
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
2519
2173
|
if (context.trim().length > 20) {
|
|
2520
2174
|
prompt += `## Project Context (Local)
|
|
2521
2175
|
${context}
|
|
@@ -2549,7 +2203,7 @@ ${fallback}
|
|
|
2549
2203
|
|
|
2550
2204
|
`;
|
|
2551
2205
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
2552
|
-
if (
|
|
2206
|
+
if (import_node_fs4.existsSync(indexPath)) {
|
|
2553
2207
|
prompt += `## Codebase Overview
|
|
2554
2208
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
2555
2209
|
|
|
@@ -2564,9 +2218,9 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2564
2218
|
}
|
|
2565
2219
|
getProjectConfig() {
|
|
2566
2220
|
const configPath = getLocusPath(this.projectPath, "configFile");
|
|
2567
|
-
if (
|
|
2221
|
+
if (import_node_fs4.existsSync(configPath)) {
|
|
2568
2222
|
try {
|
|
2569
|
-
return JSON.parse(
|
|
2223
|
+
return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
|
|
2570
2224
|
} catch {
|
|
2571
2225
|
return null;
|
|
2572
2226
|
}
|
|
@@ -2574,10 +2228,10 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2574
2228
|
return null;
|
|
2575
2229
|
}
|
|
2576
2230
|
getFallbackContext() {
|
|
2577
|
-
const readmePath =
|
|
2578
|
-
if (
|
|
2231
|
+
const readmePath = import_node_path6.join(this.projectPath, "README.md");
|
|
2232
|
+
if (import_node_fs4.existsSync(readmePath)) {
|
|
2579
2233
|
try {
|
|
2580
|
-
const content =
|
|
2234
|
+
const content = import_node_fs4.readFileSync(readmePath, "utf-8");
|
|
2581
2235
|
const limit = 1000;
|
|
2582
2236
|
return content.slice(0, limit) + (content.length > limit ? `
|
|
2583
2237
|
...(truncated)...` : "");
|
|
@@ -2589,12 +2243,12 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2589
2243
|
}
|
|
2590
2244
|
getProjectStructure() {
|
|
2591
2245
|
try {
|
|
2592
|
-
const entries =
|
|
2246
|
+
const entries = import_node_fs4.readdirSync(this.projectPath);
|
|
2593
2247
|
const folders = entries.filter((e) => {
|
|
2594
2248
|
if (e.startsWith(".") || e === "node_modules")
|
|
2595
2249
|
return false;
|
|
2596
2250
|
try {
|
|
2597
|
-
return
|
|
2251
|
+
return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
|
|
2598
2252
|
} catch {
|
|
2599
2253
|
return false;
|
|
2600
2254
|
}
|
|
@@ -2635,11 +2289,11 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2635
2289
|
}
|
|
2636
2290
|
}
|
|
2637
2291
|
}
|
|
2638
|
-
var
|
|
2292
|
+
var import_node_fs4, import_node_path6, import_shared2;
|
|
2639
2293
|
var init_prompt_builder = __esm(() => {
|
|
2640
2294
|
init_config();
|
|
2641
|
-
|
|
2642
|
-
|
|
2295
|
+
import_node_fs4 = require("node:fs");
|
|
2296
|
+
import_node_path6 = require("node:path");
|
|
2643
2297
|
import_shared2 = require("@locusai/shared");
|
|
2644
2298
|
});
|
|
2645
2299
|
|
|
@@ -2682,162 +2336,6 @@ var init_task_executor = __esm(() => {
|
|
|
2682
2336
|
init_prompt_builder();
|
|
2683
2337
|
});
|
|
2684
2338
|
|
|
2685
|
-
// src/agent/git-workflow.ts
|
|
2686
|
-
class GitWorkflow {
|
|
2687
|
-
config;
|
|
2688
|
-
log;
|
|
2689
|
-
ghUsername;
|
|
2690
|
-
worktreeManager;
|
|
2691
|
-
prService;
|
|
2692
|
-
constructor(config, log, ghUsername) {
|
|
2693
|
-
this.config = config;
|
|
2694
|
-
this.log = log;
|
|
2695
|
-
this.ghUsername = ghUsername;
|
|
2696
|
-
const projectPath = config.projectPath || process.cwd();
|
|
2697
|
-
this.worktreeManager = config.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }, log) : null;
|
|
2698
|
-
this.prService = config.autoPush ? new PrService(projectPath, log) : null;
|
|
2699
|
-
}
|
|
2700
|
-
createTaskWorktree(task, defaultExecutor) {
|
|
2701
|
-
if (!this.worktreeManager) {
|
|
2702
|
-
return {
|
|
2703
|
-
worktreePath: null,
|
|
2704
|
-
baseBranch: null,
|
|
2705
|
-
baseCommitHash: null,
|
|
2706
|
-
executor: defaultExecutor
|
|
2707
|
-
};
|
|
2708
|
-
}
|
|
2709
|
-
const slug = task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
2710
|
-
const result = this.worktreeManager.create({
|
|
2711
|
-
taskId: task.id,
|
|
2712
|
-
taskSlug: slug,
|
|
2713
|
-
agentId: this.config.agentId,
|
|
2714
|
-
baseBranch: this.config.baseBranch
|
|
2715
|
-
});
|
|
2716
|
-
this.log(`Worktree created: ${result.worktreePath} (${result.branch})`, "info");
|
|
2717
|
-
const provider = this.config.provider ?? PROVIDER.CLAUDE;
|
|
2718
|
-
const taskAiRunner = createAiRunner(provider, {
|
|
2719
|
-
projectPath: result.worktreePath,
|
|
2720
|
-
model: this.config.model,
|
|
2721
|
-
log: this.log
|
|
2722
|
-
});
|
|
2723
|
-
const taskExecutor = new TaskExecutor({
|
|
2724
|
-
aiRunner: taskAiRunner,
|
|
2725
|
-
projectPath: result.worktreePath,
|
|
2726
|
-
log: this.log
|
|
2727
|
-
});
|
|
2728
|
-
return {
|
|
2729
|
-
worktreePath: result.worktreePath,
|
|
2730
|
-
baseBranch: result.baseBranch,
|
|
2731
|
-
baseCommitHash: result.baseCommitHash,
|
|
2732
|
-
executor: taskExecutor
|
|
2733
|
-
};
|
|
2734
|
-
}
|
|
2735
|
-
commitAndPush(worktreePath, task, baseBranch, baseCommitHash) {
|
|
2736
|
-
if (!this.worktreeManager) {
|
|
2737
|
-
return { branch: null, pushed: false, pushFailed: false };
|
|
2738
|
-
}
|
|
2739
|
-
try {
|
|
2740
|
-
const trailers = [
|
|
2741
|
-
`Task-ID: ${task.id}`,
|
|
2742
|
-
`Agent: ${this.config.agentId}`,
|
|
2743
|
-
"Co-authored-by: LocusAI <agent@locusai.team>"
|
|
2744
|
-
];
|
|
2745
|
-
if (this.ghUsername) {
|
|
2746
|
-
trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
|
|
2747
|
-
}
|
|
2748
|
-
const commitMessage = `feat(agent): ${task.title}
|
|
2749
|
-
|
|
2750
|
-
${trailers.join(`
|
|
2751
|
-
`)}`;
|
|
2752
|
-
const hash = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch, baseCommitHash);
|
|
2753
|
-
if (!hash) {
|
|
2754
|
-
this.log("No changes to commit for this task", "info");
|
|
2755
|
-
return {
|
|
2756
|
-
branch: null,
|
|
2757
|
-
pushed: false,
|
|
2758
|
-
pushFailed: false,
|
|
2759
|
-
noChanges: true,
|
|
2760
|
-
skipReason: "No changes were committed, so no branch was pushed."
|
|
2761
|
-
};
|
|
2762
|
-
}
|
|
2763
|
-
const localBranch = this.worktreeManager.getBranch(worktreePath);
|
|
2764
|
-
if (this.config.autoPush) {
|
|
2765
|
-
try {
|
|
2766
|
-
return {
|
|
2767
|
-
branch: this.worktreeManager.pushBranch(worktreePath),
|
|
2768
|
-
pushed: true,
|
|
2769
|
-
pushFailed: false
|
|
2770
|
-
};
|
|
2771
|
-
} catch (err) {
|
|
2772
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2773
|
-
this.log(`Git push failed: ${errorMessage}`, "error");
|
|
2774
|
-
return {
|
|
2775
|
-
branch: localBranch,
|
|
2776
|
-
pushed: false,
|
|
2777
|
-
pushFailed: true,
|
|
2778
|
-
pushError: errorMessage
|
|
2779
|
-
};
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
this.log("Auto-push disabled; skipping branch push", "info");
|
|
2783
|
-
return {
|
|
2784
|
-
branch: localBranch,
|
|
2785
|
-
pushed: false,
|
|
2786
|
-
pushFailed: false,
|
|
2787
|
-
skipReason: "Auto-push is disabled, so PR creation was skipped."
|
|
2788
|
-
};
|
|
2789
|
-
} catch (err) {
|
|
2790
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2791
|
-
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
2792
|
-
return {
|
|
2793
|
-
branch: null,
|
|
2794
|
-
pushed: false,
|
|
2795
|
-
pushFailed: true,
|
|
2796
|
-
pushError: `Git commit/push failed: ${errorMessage}`
|
|
2797
|
-
};
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
createPullRequest(task, branch, summary, baseBranch) {
|
|
2801
|
-
if (!this.prService) {
|
|
2802
|
-
const errorMessage = "PR service is not initialized. Enable auto-push to allow PR creation.";
|
|
2803
|
-
this.log(`PR creation skipped: ${errorMessage}`, "warn");
|
|
2804
|
-
return { url: null, error: errorMessage };
|
|
2805
|
-
}
|
|
2806
|
-
this.log(`Attempting PR creation from branch: ${branch}`, "info");
|
|
2807
|
-
try {
|
|
2808
|
-
const result = this.prService.createPr({
|
|
2809
|
-
task,
|
|
2810
|
-
branch,
|
|
2811
|
-
baseBranch,
|
|
2812
|
-
agentId: this.config.agentId,
|
|
2813
|
-
summary
|
|
2814
|
-
});
|
|
2815
|
-
return { url: result.url };
|
|
2816
|
-
} catch (err) {
|
|
2817
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2818
|
-
this.log(`PR creation failed: ${errorMessage}`, "error");
|
|
2819
|
-
return { url: null, error: errorMessage };
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
cleanupWorktree(worktreePath, keepBranch) {
|
|
2823
|
-
if (!this.worktreeManager || !worktreePath)
|
|
2824
|
-
return;
|
|
2825
|
-
try {
|
|
2826
|
-
this.worktreeManager.remove(worktreePath, !keepBranch);
|
|
2827
|
-
this.log(keepBranch ? "Worktree cleaned up (branch preserved)" : "Worktree cleaned up", "info");
|
|
2828
|
-
} catch {
|
|
2829
|
-
this.log(`Could not clean up worktree: ${worktreePath}`, "warn");
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
var init_git_workflow = __esm(() => {
|
|
2834
|
-
init_factory();
|
|
2835
|
-
init_config();
|
|
2836
|
-
init_pr_service();
|
|
2837
|
-
init_worktree_manager();
|
|
2838
|
-
init_task_executor();
|
|
2839
|
-
});
|
|
2840
|
-
|
|
2841
2339
|
// src/agent/worker-cli.ts
|
|
2842
2340
|
var exports_worker_cli = {};
|
|
2843
2341
|
__export(exports_worker_cli, {
|
|
@@ -2870,16 +2368,8 @@ function parseWorkerArgs(argv) {
|
|
|
2870
2368
|
config.apiKey = args[++i];
|
|
2871
2369
|
else if (arg === "--project-path")
|
|
2872
2370
|
config.projectPath = args[++i];
|
|
2873
|
-
else if (arg === "--main-project-path")
|
|
2874
|
-
config.mainProjectPath = args[++i];
|
|
2875
2371
|
else if (arg === "--model")
|
|
2876
2372
|
config.model = args[++i];
|
|
2877
|
-
else if (arg === "--use-worktrees")
|
|
2878
|
-
config.useWorktrees = true;
|
|
2879
|
-
else if (arg === "--auto-push")
|
|
2880
|
-
config.autoPush = true;
|
|
2881
|
-
else if (arg === "--base-branch")
|
|
2882
|
-
config.baseBranch = args[++i];
|
|
2883
2373
|
else if (arg === "--provider") {
|
|
2884
2374
|
const value = args[i + 1];
|
|
2885
2375
|
if (value && !value.startsWith("--"))
|
|
@@ -2927,8 +2417,8 @@ class AgentWorker {
|
|
|
2927
2417
|
tasksCompleted = 0;
|
|
2928
2418
|
heartbeatInterval = null;
|
|
2929
2419
|
currentTaskId = null;
|
|
2930
|
-
|
|
2931
|
-
|
|
2420
|
+
completedTaskList = [];
|
|
2421
|
+
taskSummaries = [];
|
|
2932
2422
|
constructor(config) {
|
|
2933
2423
|
this.config = config;
|
|
2934
2424
|
const projectPath = config.projectPath || process.cwd();
|
|
@@ -2943,17 +2433,12 @@ class AgentWorker {
|
|
|
2943
2433
|
}
|
|
2944
2434
|
});
|
|
2945
2435
|
const log = this.log.bind(this);
|
|
2946
|
-
if (
|
|
2947
|
-
this.log("git is not installed —
|
|
2948
|
-
config.useWorktrees = false;
|
|
2436
|
+
if (!isGitAvailable()) {
|
|
2437
|
+
this.log("git is not installed — branch management will not work", "error");
|
|
2949
2438
|
}
|
|
2950
|
-
if (
|
|
2439
|
+
if (!isGhAvailable(projectPath)) {
|
|
2951
2440
|
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");
|
|
2952
2441
|
}
|
|
2953
|
-
const ghUsername = config.autoPush ? getGhUsername() : null;
|
|
2954
|
-
if (ghUsername) {
|
|
2955
|
-
this.log(`GitHub user: ${ghUsername}`, "info");
|
|
2956
|
-
}
|
|
2957
2442
|
const provider = config.provider ?? PROVIDER.CLAUDE;
|
|
2958
2443
|
this.aiRunner = createAiRunner(provider, {
|
|
2959
2444
|
projectPath,
|
|
@@ -2966,18 +2451,9 @@ class AgentWorker {
|
|
|
2966
2451
|
log
|
|
2967
2452
|
});
|
|
2968
2453
|
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
2969
|
-
this.gitWorkflow = new GitWorkflow(config, log
|
|
2454
|
+
this.gitWorkflow = new GitWorkflow(config, log);
|
|
2970
2455
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
2971
2456
|
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
2972
|
-
if (config.useWorktrees) {
|
|
2973
|
-
this.log("Per-task worktree isolation enabled", "info");
|
|
2974
|
-
if (config.baseBranch) {
|
|
2975
|
-
this.log(`Base branch for worktrees: ${config.baseBranch}`, "info");
|
|
2976
|
-
}
|
|
2977
|
-
if (config.autoPush) {
|
|
2978
|
-
this.log("Auto-push enabled: branches will be pushed to remote", "info");
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
2457
|
}
|
|
2982
2458
|
log(message, level = "info") {
|
|
2983
2459
|
const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
@@ -3026,56 +2502,26 @@ class AgentWorker {
|
|
|
3026
2502
|
}
|
|
3027
2503
|
async executeTask(task) {
|
|
3028
2504
|
const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
|
|
3029
|
-
const { worktreePath, baseBranch, baseCommitHash, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
3030
|
-
this.currentWorktreePath = worktreePath;
|
|
3031
|
-
let branchPushed = false;
|
|
3032
|
-
let keepBranch = false;
|
|
3033
|
-
let preserveWorktree = false;
|
|
3034
2505
|
try {
|
|
3035
|
-
const result = await
|
|
3036
|
-
let taskBranch = null;
|
|
3037
|
-
let prUrl = null;
|
|
3038
|
-
let prError = null;
|
|
2506
|
+
const result = await this.taskExecutor.execute(fullTask);
|
|
3039
2507
|
let noChanges = false;
|
|
3040
|
-
|
|
3041
|
-
|
|
2508
|
+
let taskBranch = null;
|
|
2509
|
+
if (result.success) {
|
|
2510
|
+
const commitResult = this.gitWorkflow.commitAndPush(fullTask);
|
|
3042
2511
|
taskBranch = commitResult.branch;
|
|
3043
|
-
branchPushed = commitResult.pushed;
|
|
3044
|
-
keepBranch = taskBranch !== null;
|
|
3045
2512
|
noChanges = Boolean(commitResult.noChanges);
|
|
3046
|
-
if (commitResult.pushFailed) {
|
|
3047
|
-
preserveWorktree = true;
|
|
3048
|
-
prError = commitResult.pushError ?? "Git push failed before PR creation. Please retry manually.";
|
|
3049
|
-
this.log(`Preserving worktree after push failure: ${worktreePath}`, "warn");
|
|
3050
|
-
}
|
|
3051
|
-
if (branchPushed && taskBranch) {
|
|
3052
|
-
const prResult = this.gitWorkflow.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
|
|
3053
|
-
prUrl = prResult.url;
|
|
3054
|
-
prError = prResult.error ?? null;
|
|
3055
|
-
if (!prUrl) {
|
|
3056
|
-
preserveWorktree = true;
|
|
3057
|
-
this.log(`Preserving worktree for manual follow-up: ${worktreePath}`, "warn");
|
|
3058
|
-
}
|
|
3059
|
-
} else if (commitResult.skipReason) {
|
|
3060
|
-
this.log(`Skipping PR creation: ${commitResult.skipReason}`, "info");
|
|
3061
|
-
}
|
|
3062
|
-
} else if (result.success && !worktreePath) {
|
|
3063
|
-
this.log("Skipping commit/push/PR flow because no task worktree is active.", "warn");
|
|
3064
2513
|
}
|
|
3065
2514
|
return {
|
|
3066
2515
|
...result,
|
|
3067
2516
|
branch: taskBranch ?? undefined,
|
|
3068
|
-
prUrl: prUrl ?? undefined,
|
|
3069
|
-
prError: prError ?? undefined,
|
|
3070
2517
|
noChanges: noChanges || undefined
|
|
3071
2518
|
};
|
|
3072
|
-
}
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
}
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2521
|
+
return {
|
|
2522
|
+
success: false,
|
|
2523
|
+
summary: `Execution error: ${msg}`
|
|
2524
|
+
};
|
|
3079
2525
|
}
|
|
3080
2526
|
}
|
|
3081
2527
|
updateProgress(task, summary) {
|
|
@@ -3108,19 +2554,13 @@ class AgentWorker {
|
|
|
3108
2554
|
this.log(`Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
3109
2555
|
});
|
|
3110
2556
|
}
|
|
3111
|
-
async delayAfterCleanup() {
|
|
3112
|
-
if (!this.config.useWorktrees || this.postCleanupDelayMs <= 0)
|
|
3113
|
-
return;
|
|
3114
|
-
this.log(`Waiting ${Math.floor(this.postCleanupDelayMs / 1000)}s after worktree cleanup before next dispatch`, "info");
|
|
3115
|
-
await new Promise((resolve3) => setTimeout(resolve3, this.postCleanupDelayMs));
|
|
3116
|
-
}
|
|
3117
2557
|
async run() {
|
|
3118
2558
|
this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
|
|
3119
2559
|
const handleShutdown = () => {
|
|
3120
2560
|
this.log("Received shutdown signal. Aborting...", "warn");
|
|
3121
2561
|
this.aiRunner.abort();
|
|
3122
2562
|
this.stopHeartbeat();
|
|
3123
|
-
this.gitWorkflow.
|
|
2563
|
+
this.gitWorkflow.checkoutBaseBranch();
|
|
3124
2564
|
process.exit(1);
|
|
3125
2565
|
};
|
|
3126
2566
|
process.on("SIGTERM", handleShutdown);
|
|
@@ -3132,10 +2572,12 @@ class AgentWorker {
|
|
|
3132
2572
|
} else {
|
|
3133
2573
|
this.log("No active sprint found.", "warn");
|
|
3134
2574
|
}
|
|
2575
|
+
const branchName = this.gitWorkflow.createBranch(this.config.sprintId);
|
|
2576
|
+
this.log(`Working on branch: ${branchName}`, "info");
|
|
3135
2577
|
while (this.tasksCompleted < this.maxTasks) {
|
|
3136
2578
|
const task = await this.getNextTask();
|
|
3137
2579
|
if (!task) {
|
|
3138
|
-
this.log("No more tasks to process.
|
|
2580
|
+
this.log("No more tasks to process.", "info");
|
|
3139
2581
|
break;
|
|
3140
2582
|
}
|
|
3141
2583
|
this.log(`Claimed: ${task.title}`, "success");
|
|
@@ -3151,7 +2593,7 @@ class AgentWorker {
|
|
|
3151
2593
|
});
|
|
3152
2594
|
await this.client.tasks.addComment(task.id, this.config.workspaceId, {
|
|
3153
2595
|
author: this.config.agentId,
|
|
3154
|
-
text: `⚠️ Agent execution finished with no file changes, so no commit
|
|
2596
|
+
text: `⚠️ Agent execution finished with no file changes, so no commit was created.
|
|
3155
2597
|
|
|
3156
2598
|
${result.summary}`
|
|
3157
2599
|
});
|
|
@@ -3160,22 +2602,17 @@ ${result.summary}`
|
|
|
3160
2602
|
const updatePayload = {
|
|
3161
2603
|
status: import_shared3.TaskStatus.IN_REVIEW
|
|
3162
2604
|
};
|
|
3163
|
-
if (result.prUrl) {
|
|
3164
|
-
updatePayload.prUrl = result.prUrl;
|
|
3165
|
-
}
|
|
3166
2605
|
await this.client.tasks.update(task.id, this.config.workspaceId, updatePayload);
|
|
3167
2606
|
const branchInfo = result.branch ? `
|
|
3168
2607
|
|
|
3169
2608
|
Branch: \`${result.branch}\`` : "";
|
|
3170
|
-
const prInfo = result.prUrl ? `
|
|
3171
|
-
PR: ${result.prUrl}` : "";
|
|
3172
|
-
const prErrorInfo = result.prError ? `
|
|
3173
|
-
PR automation error: ${result.prError}` : "";
|
|
3174
2609
|
await this.client.tasks.addComment(task.id, this.config.workspaceId, {
|
|
3175
2610
|
author: this.config.agentId,
|
|
3176
|
-
text: `✅ ${result.summary}${branchInfo}
|
|
2611
|
+
text: `✅ ${result.summary}${branchInfo}`
|
|
3177
2612
|
});
|
|
3178
2613
|
this.tasksCompleted++;
|
|
2614
|
+
this.completedTaskList.push({ title: task.title, id: task.id });
|
|
2615
|
+
this.taskSummaries.push(result.summary);
|
|
3179
2616
|
this.updateProgress(task, result.summary);
|
|
3180
2617
|
}
|
|
3181
2618
|
} else {
|
|
@@ -3191,8 +2628,24 @@ PR automation error: ${result.prError}` : "";
|
|
|
3191
2628
|
}
|
|
3192
2629
|
this.currentTaskId = null;
|
|
3193
2630
|
this.sendHeartbeat();
|
|
3194
|
-
await this.delayAfterCleanup();
|
|
3195
2631
|
}
|
|
2632
|
+
if (this.completedTaskList.length > 0) {
|
|
2633
|
+
this.log("All tasks done. Creating pull request...", "info");
|
|
2634
|
+
const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
|
|
2635
|
+
if (prResult.url) {
|
|
2636
|
+
this.log(`PR created: ${prResult.url}`, "success");
|
|
2637
|
+
for (const task of this.completedTaskList) {
|
|
2638
|
+
try {
|
|
2639
|
+
await this.client.tasks.update(task.id, this.config.workspaceId, {
|
|
2640
|
+
prUrl: prResult.url
|
|
2641
|
+
});
|
|
2642
|
+
} catch {}
|
|
2643
|
+
}
|
|
2644
|
+
} else if (prResult.error) {
|
|
2645
|
+
this.log(`PR creation failed: ${prResult.error}`, "error");
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
this.gitWorkflow.checkoutBaseBranch();
|
|
3196
2649
|
this.currentTaskId = null;
|
|
3197
2650
|
this.stopHeartbeat();
|
|
3198
2651
|
this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
|