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