@locusai/sdk 0.10.6 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) 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/task-executor.d.ts.map +1 -1
  4. package/dist/agent/worker-cli.d.ts.map +1 -1
  5. package/dist/agent/worker-types.d.ts +0 -10
  6. package/dist/agent/worker-types.d.ts.map +1 -1
  7. package/dist/agent/worker.d.ts +8 -8
  8. package/dist/agent/worker.d.ts.map +1 -1
  9. package/dist/agent/worker.js +257 -804
  10. package/dist/ai/codex-runner.d.ts +1 -1
  11. package/dist/ai/codex-runner.d.ts.map +1 -1
  12. package/dist/core/config.d.ts +1 -1
  13. package/dist/core/config.d.ts.map +1 -1
  14. package/dist/index-node.d.ts +0 -1
  15. package/dist/index-node.d.ts.map +1 -1
  16. package/dist/index-node.js +809 -1536
  17. package/dist/orchestrator/agent-pool.d.ts +1 -58
  18. package/dist/orchestrator/agent-pool.d.ts.map +1 -1
  19. package/dist/orchestrator/execution.d.ts +1 -54
  20. package/dist/orchestrator/execution.d.ts.map +1 -1
  21. package/dist/orchestrator/index.d.ts +25 -31
  22. package/dist/orchestrator/index.d.ts.map +1 -1
  23. package/dist/orchestrator/tier-merge.d.ts +1 -49
  24. package/dist/orchestrator/tier-merge.d.ts.map +1 -1
  25. package/dist/orchestrator/types.d.ts +0 -11
  26. package/dist/orchestrator/types.d.ts.map +1 -1
  27. package/dist/planning/agents/architect.d.ts.map +1 -1
  28. package/dist/planning/agents/cross-task-reviewer.d.ts +2 -6
  29. package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
  30. package/dist/planning/agents/sprint-organizer.d.ts +1 -1
  31. package/dist/planning/agents/sprint-organizer.d.ts.map +1 -1
  32. package/dist/planning/agents/tech-lead.d.ts.map +1 -1
  33. package/dist/planning/sprint-plan.d.ts +0 -2
  34. package/dist/planning/sprint-plan.d.ts.map +1 -1
  35. package/dist/worktree/index.d.ts +1 -2
  36. package/dist/worktree/index.d.ts.map +1 -1
  37. package/dist/worktree/worktree-config.d.ts +1 -59
  38. package/dist/worktree/worktree-config.d.ts.map +1 -1
  39. package/dist/worktree/worktree-manager.d.ts +1 -115
  40. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/dist/agent/__tests__/orchestrator.cleanup.test.d.ts +0 -2
  43. package/dist/agent/__tests__/orchestrator.cleanup.test.d.ts.map +0 -1
  44. package/dist/agent/__tests__/worker.no-changes.test.d.ts +0 -2
  45. 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
  "",
@@ -1333,14 +1330,13 @@ class CodexRunner {
1333
1330
  type: "tool_use",
1334
1331
  tool: line.replace(/^[→•✓]\s*/, "")
1335
1332
  });
1336
- } else {
1337
- enqueueChunk({ type: "text_delta", content: `${line}
1338
- ` });
1339
1333
  }
1340
1334
  }
1341
1335
  };
1342
1336
  codex.stdout.on("data", processOutput);
1343
- codex.stderr.on("data", processOutput);
1337
+ codex.stderr.on("data", (data) => {
1338
+ finalOutput += data.toString();
1339
+ });
1344
1340
  codex.on("error", (err) => {
1345
1341
  errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
1346
1342
  this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
@@ -1476,6 +1472,7 @@ class CodexRunner {
1476
1472
  "exec",
1477
1473
  "--full-auto",
1478
1474
  "--skip-git-repo-check",
1475
+ "--quiet",
1479
1476
  "--output-last-message",
1480
1477
  outputPath
1481
1478
  ];
@@ -1496,12 +1493,7 @@ class CodexRunner {
1496
1493
  }
1497
1494
  }
1498
1495
  shouldDisplay(line) {
1499
- return [
1500
- /^thinking\b/,
1501
- /^\*\*/,
1502
- /^Plan update\b/,
1503
- /^[→•✓]/
1504
- ].some((pattern) => pattern.test(line));
1496
+ return /^Plan update\b/.test(line);
1505
1497
  }
1506
1498
  readOutput(outputPath, fallback) {
1507
1499
  if (import_node_fs2.existsSync(outputPath)) {
@@ -1753,583 +1745,247 @@ var init_knowledge_base = __esm(() => {
1753
1745
  import_node_path5 = require("node:path");
1754
1746
  });
1755
1747
 
1756
- // src/git/pr-service.ts
1757
- class PrService {
1758
- projectPath;
1748
+ // src/agent/git-workflow.ts
1749
+ class GitWorkflow {
1750
+ config;
1759
1751
  log;
1760
- constructor(projectPath, log) {
1761
- this.projectPath = projectPath;
1752
+ projectPath;
1753
+ branchName = null;
1754
+ baseBranch = null;
1755
+ ghUsername;
1756
+ constructor(config, log) {
1757
+ this.config = config;
1762
1758
  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.`);
1823
- }
1824
- }
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) {
1844
- 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;
1850
- } catch {
1851
- return false;
1852
- }
1853
- }
1854
- hasRemoteTrackingBranch(branch) {
1855
- 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;
1759
+ this.projectPath = config.projectPath || process.cwd();
1760
+ this.ghUsername = getGhUsername();
1761
+ if (this.ghUsername) {
1762
+ this.log(`GitHub user: ${this.ghUsername}`, "info");
1863
1763
  }
1864
1764
  }
1865
- hasRemoteBranch(branch) {
1765
+ createBranch(sprintId) {
1766
+ const defaultBranch = getDefaultBranch(this.projectPath);
1767
+ this.baseBranch = defaultBranch;
1866
1768
  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;
1769
+ this.gitExec(["checkout", defaultBranch]);
1770
+ this.gitExec(["pull", "origin", defaultBranch]);
1872
1771
  } catch {
1873
- return false;
1772
+ this.log(`Could not pull latest from ${defaultBranch}, continuing with current state`, "warn");
1874
1773
  }
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) {
1774
+ const suffix = sprintId ? sprintId.slice(0, 8) : Date.now().toString(36);
1775
+ this.branchName = `locus/${suffix}`;
1885
1776
  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
- }
1777
+ this.gitExec(["branch", "-D", this.branchName]);
1778
+ } catch {}
1779
+ this.gitExec(["checkout", "-b", this.branchName]);
1780
+ this.log(`Created branch: ${this.branchName} (from ${defaultBranch})`, "success");
1781
+ return this.branchName;
1910
1782
  }
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 [];
1783
+ commitAndPush(task) {
1784
+ if (!this.branchName) {
1785
+ this.log("No branch created yet, skipping commit", "warn");
1786
+ return { branch: null, pushed: false, pushFailed: false };
1937
1787
  }
1938
- }
1939
- hasLocusReview(prNumber) {
1940
1788
  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.`);
1789
+ const status = this.gitExec(["status", "--porcelain"]).trim();
1790
+ if (!status) {
1791
+ const baseBranchCommit = this.getBaseCommit();
1792
+ const headCommit = this.gitExec(["rev-parse", "HEAD"]).trim();
1793
+ if (baseBranchCommit && headCommit !== baseBranchCommit) {
1794
+ return this.pushBranch();
2053
1795
  }
2054
- this.log(`Removing existing worktree for branch: ${branch} (${worktreePath2})`, "warn");
2055
- this.remove(worktreePath2, false);
1796
+ this.log("No changes to commit for this task", "info");
1797
+ return {
1798
+ branch: this.branchName,
1799
+ pushed: false,
1800
+ pushFailed: false,
1801
+ noChanges: true,
1802
+ skipReason: "No changes were made for this task."
1803
+ };
2056
1804
  }
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);
1805
+ this.gitExec(["add", "-A"]);
1806
+ const staged = this.gitExec(["diff", "--cached", "--name-only"]).trim();
1807
+ if (!staged) {
1808
+ this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
1809
+ return {
1810
+ branch: this.branchName,
1811
+ pushed: false,
1812
+ pushFailed: false,
1813
+ noChanges: true,
1814
+ skipReason: "All changes were ignored by .gitignore."
1815
+ };
2062
1816
  }
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;
1817
+ this.log(`Staging ${staged.split(`
1818
+ `).length} file(s) for commit`, "info");
1819
+ const trailers = [
1820
+ `Task-ID: ${task.id}`,
1821
+ `Agent: ${this.config.agentId}`,
1822
+ "Co-authored-by: LocusAI <agent@locusai.team>"
1823
+ ];
1824
+ if (this.ghUsername) {
1825
+ trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
2070
1826
  }
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(`
1827
+ const commitMessage = `feat(agent): ${task.title}
2084
1828
 
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
- }
1829
+ ${trailers.join(`
1830
+ `)}`;
1831
+ this.gitExec(["commit", "-m", commitMessage]);
1832
+ const hash = this.gitExec(["rev-parse", "HEAD"]).trim();
1833
+ this.log(`Committed: ${hash.slice(0, 8)}`, "success");
1834
+ return this.pushBranch();
1835
+ } catch (err) {
1836
+ const errorMessage = err instanceof Error ? err.message : String(err);
1837
+ this.log(`Git commit failed: ${errorMessage}`, "error");
1838
+ return {
1839
+ branch: this.branchName,
1840
+ pushed: false,
1841
+ pushFailed: true,
1842
+ pushError: `Git commit/push failed: ${errorMessage}`
1843
+ };
2117
1844
  }
2118
- return worktrees;
2119
- }
2120
- listAgentWorktrees() {
2121
- return this.list().filter((wt) => !wt.isMain);
2122
1845
  }
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);
1846
+ pushBranch() {
1847
+ if (!this.branchName) {
1848
+ return { branch: null, pushed: false, pushFailed: false };
2136
1849
  }
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");
1850
+ try {
1851
+ this.gitExec(["push", "-u", "origin", this.branchName]);
1852
+ this.log(`Pushed ${this.branchName} to origin`, "success");
1853
+ return { branch: this.branchName, pushed: true, pushFailed: false };
1854
+ } catch (error) {
1855
+ const msg = error instanceof Error ? error.message : String(error);
1856
+ if (msg.includes("non-fast-forward") || msg.includes("[rejected]") || msg.includes("fetch first")) {
1857
+ this.log(`Push rejected (non-fast-forward). Retrying with --force-with-lease.`, "warn");
1858
+ try {
1859
+ this.gitExec([
1860
+ "push",
1861
+ "--force-with-lease",
1862
+ "-u",
1863
+ "origin",
1864
+ this.branchName
1865
+ ]);
1866
+ this.log(`Pushed ${this.branchName} to origin with --force-with-lease`, "success");
1867
+ return { branch: this.branchName, pushed: true, pushFailed: false };
1868
+ } catch (retryErr) {
1869
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
1870
+ this.log(`Git push retry failed: ${retryMsg}`, "error");
1871
+ return {
1872
+ branch: this.branchName,
1873
+ pushed: false,
1874
+ pushFailed: true,
1875
+ pushError: retryMsg
1876
+ };
1877
+ }
2143
1878
  }
1879
+ this.log(`Git push failed: ${msg}`, "error");
1880
+ return {
1881
+ branch: this.branchName,
1882
+ pushed: false,
1883
+ pushFailed: true,
1884
+ pushError: msg
1885
+ };
2144
1886
  }
2145
- this.log("Worktree removed", "success");
2146
1887
  }
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");
1888
+ createPullRequest(completedTasks, summaries) {
1889
+ if (!this.branchName || !this.baseBranch) {
1890
+ return { url: null, error: "No branch or base branch available." };
2154
1891
  }
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
- }
1892
+ const provider = detectRemoteProvider(this.projectPath);
1893
+ if (provider !== "github") {
1894
+ return {
1895
+ url: null,
1896
+ error: `PR creation is only supported for GitHub repositories (detected: ${provider})`
1897
+ };
2167
1898
  }
2168
- if (import_node_fs4.existsSync(this.rootPath)) {
2169
- try {
2170
- import_node_fs4.rmSync(this.rootPath, { recursive: true, force: true });
2171
- } catch {}
1899
+ if (!isGhAvailable(this.projectPath)) {
1900
+ return {
1901
+ url: null,
1902
+ error: "GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/"
1903
+ };
2172
1904
  }
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) {
1905
+ const title = `[Locus] Sprint tasks (${completedTasks.length} task${completedTasks.length !== 1 ? "s" : ""})`;
1906
+ const body = this.buildPrBody(completedTasks, summaries);
1907
+ this.log(`Creating PR: ${title} (${this.branchName} → ${this.baseBranch})`, "info");
2180
1908
  try {
2181
- const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
2182
- return Number.parseInt(count, 10) > 0;
1909
+ const output = import_node_child_process4.execFileSync("gh", [
1910
+ "pr",
1911
+ "create",
1912
+ "--title",
1913
+ title,
1914
+ "--body",
1915
+ body,
1916
+ "--base",
1917
+ this.baseBranch,
1918
+ "--head",
1919
+ this.branchName
1920
+ ], {
1921
+ cwd: this.projectPath,
1922
+ encoding: "utf-8",
1923
+ stdio: ["pipe", "pipe", "pipe"]
1924
+ }).trim();
1925
+ this.log(`PR created: ${output}`, "success");
1926
+ return { url: output };
2183
1927
  } catch (err) {
2184
- this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
2185
- return false;
1928
+ const errorMessage = err instanceof Error ? err.message : String(err);
1929
+ this.log(`PR creation failed: ${errorMessage}`, "error");
1930
+ return { url: null, error: errorMessage };
2186
1931
  }
2187
1932
  }
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");
1933
+ checkoutBaseBranch() {
1934
+ if (!this.baseBranch)
1935
+ return;
2239
1936
  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;
1937
+ this.gitExec(["checkout", this.baseBranch]);
1938
+ this.log(`Checked out base branch: ${this.baseBranch}`, "info");
1939
+ } catch (err) {
1940
+ this.log(`Could not checkout base branch: ${err instanceof Error ? err.message : String(err)}`, "warn");
2254
1941
  }
2255
1942
  }
2256
- getBranch(worktreePath) {
2257
- return this.git("rev-parse --abbrev-ref HEAD", worktreePath).trim();
1943
+ getBranchName() {
1944
+ return this.branchName;
2258
1945
  }
2259
- hasWorktreeForTask(taskId) {
2260
- return this.listAgentWorktrees().some((wt) => wt.branch.includes(taskId) || wt.path.includes(taskId));
1946
+ getBaseBranch() {
1947
+ return this.baseBranch;
2261
1948
  }
2262
- branchExists(branchName) {
1949
+ getBaseCommit() {
1950
+ if (!this.baseBranch)
1951
+ return null;
2263
1952
  try {
2264
- this.git(`rev-parse --verify "refs/heads/${branchName}"`, this.projectPath);
2265
- return true;
1953
+ return this.gitExec(["rev-parse", this.baseBranch]).trim();
2266
1954
  } catch {
2267
- return false;
1955
+ return null;
2268
1956
  }
2269
1957
  }
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}`);
1958
+ buildPrBody(completedTasks, summaries) {
1959
+ const sections = [];
1960
+ sections.push("## Completed Tasks");
1961
+ sections.push("");
1962
+ for (let i = 0;i < completedTasks.length; i++) {
1963
+ const task = completedTasks[i];
1964
+ sections.push(`### ${i + 1}. ${task.title}`);
1965
+ sections.push(`Task ID: \`${task.id}\``);
1966
+ if (summaries[i]) {
1967
+ sections.push("");
1968
+ sections.push(summaries[i]);
2283
1969
  }
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 {}
1970
+ sections.push("");
2306
1971
  }
1972
+ sections.push("---");
1973
+ sections.push(`*Created by Locus Agent \`${this.config.agentId.slice(-8)}\`*`);
1974
+ return sections.join(`
1975
+ `);
2307
1976
  }
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,
1977
+ gitExec(args) {
1978
+ return import_node_child_process4.execFileSync("git", args, {
1979
+ cwd: this.projectPath,
2322
1980
  encoding: "utf-8",
2323
1981
  stdio: ["pipe", "pipe", "pipe"]
2324
1982
  });
2325
1983
  }
2326
1984
  }
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");
1985
+ var import_node_child_process4;
1986
+ var init_git_workflow = __esm(() => {
1987
+ init_git_utils();
1988
+ import_node_child_process4 = require("node:child_process");
2333
1989
  });
2334
1990
 
2335
1991
  // src/core/prompt-builder.ts
@@ -2373,9 +2029,9 @@ ${task.description || "No description provided."}
2373
2029
  }
2374
2030
  const contextPath = getLocusPath(this.projectPath, "contextFile");
2375
2031
  let hasLocalContext = false;
2376
- if (import_node_fs5.existsSync(contextPath)) {
2032
+ if (import_node_fs4.existsSync(contextPath)) {
2377
2033
  try {
2378
- const context = import_node_fs5.readFileSync(contextPath, "utf-8");
2034
+ const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2379
2035
  if (context.trim().length > 20) {
2380
2036
  prompt += `## Project Context (Local)
2381
2037
  ${context}
@@ -2429,7 +2085,7 @@ ${serverContext.context}
2429
2085
 
2430
2086
  `;
2431
2087
  const indexPath = getLocusPath(this.projectPath, "indexFile");
2432
- if (import_node_fs5.existsSync(indexPath)) {
2088
+ if (import_node_fs4.existsSync(indexPath)) {
2433
2089
  prompt += `## Codebase Overview
2434
2090
  There is an index file in the .locus/codebase-index.json and if you need you can check it.
2435
2091
 
@@ -2506,9 +2162,9 @@ ${query}
2506
2162
  }
2507
2163
  const contextPath = getLocusPath(this.projectPath, "contextFile");
2508
2164
  let hasLocalContext = false;
2509
- if (import_node_fs5.existsSync(contextPath)) {
2165
+ if (import_node_fs4.existsSync(contextPath)) {
2510
2166
  try {
2511
- const context = import_node_fs5.readFileSync(contextPath, "utf-8");
2167
+ const context = import_node_fs4.readFileSync(contextPath, "utf-8");
2512
2168
  if (context.trim().length > 20) {
2513
2169
  prompt += `## Project Context (Local)
2514
2170
  ${context}
@@ -2542,7 +2198,7 @@ ${fallback}
2542
2198
 
2543
2199
  `;
2544
2200
  const indexPath = getLocusPath(this.projectPath, "indexFile");
2545
- if (import_node_fs5.existsSync(indexPath)) {
2201
+ if (import_node_fs4.existsSync(indexPath)) {
2546
2202
  prompt += `## Codebase Overview
2547
2203
  There is an index file in the .locus/codebase-index.json and if you need you can check it.
2548
2204
 
@@ -2557,9 +2213,9 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2557
2213
  }
2558
2214
  getProjectConfig() {
2559
2215
  const configPath = getLocusPath(this.projectPath, "configFile");
2560
- if (import_node_fs5.existsSync(configPath)) {
2216
+ if (import_node_fs4.existsSync(configPath)) {
2561
2217
  try {
2562
- return JSON.parse(import_node_fs5.readFileSync(configPath, "utf-8"));
2218
+ return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
2563
2219
  } catch {
2564
2220
  return null;
2565
2221
  }
@@ -2567,10 +2223,10 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2567
2223
  return null;
2568
2224
  }
2569
2225
  getFallbackContext() {
2570
- const readmePath = import_node_path7.join(this.projectPath, "README.md");
2571
- if (import_node_fs5.existsSync(readmePath)) {
2226
+ const readmePath = import_node_path6.join(this.projectPath, "README.md");
2227
+ if (import_node_fs4.existsSync(readmePath)) {
2572
2228
  try {
2573
- const content = import_node_fs5.readFileSync(readmePath, "utf-8");
2229
+ const content = import_node_fs4.readFileSync(readmePath, "utf-8");
2574
2230
  const limit = 1000;
2575
2231
  return content.slice(0, limit) + (content.length > limit ? `
2576
2232
  ...(truncated)...` : "");
@@ -2582,12 +2238,12 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2582
2238
  }
2583
2239
  getProjectStructure() {
2584
2240
  try {
2585
- const entries = import_node_fs5.readdirSync(this.projectPath);
2241
+ const entries = import_node_fs4.readdirSync(this.projectPath);
2586
2242
  const folders = entries.filter((e) => {
2587
2243
  if (e.startsWith(".") || e === "node_modules")
2588
2244
  return false;
2589
2245
  try {
2590
- return import_node_fs5.statSync(import_node_path7.join(this.projectPath, e)).isDirectory();
2246
+ return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
2591
2247
  } catch {
2592
2248
  return false;
2593
2249
  }
@@ -2628,11 +2284,11 @@ There is an index file in the .locus/codebase-index.json and if you need you can
2628
2284
  }
2629
2285
  }
2630
2286
  }
2631
- var import_node_fs5, import_node_path7, import_shared2;
2287
+ var import_node_fs4, import_node_path6, import_shared2;
2632
2288
  var init_prompt_builder = __esm(() => {
2633
2289
  init_config();
2634
- import_node_fs5 = require("node:fs");
2635
- import_node_path7 = require("node:path");
2290
+ import_node_fs4 = require("node:fs");
2291
+ import_node_path6 = require("node:path");
2636
2292
  import_shared2 = require("@locusai/shared");
2637
2293
  });
2638
2294
 
@@ -2648,7 +2304,6 @@ class TaskExecutor {
2648
2304
  this.deps.log(`Executing: ${task.title}`, "info");
2649
2305
  const basePrompt = await this.promptBuilder.build(task);
2650
2306
  try {
2651
- this.deps.log("Starting Execution...", "info");
2652
2307
  const output = await this.deps.aiRunner.run(basePrompt);
2653
2308
  const summary = this.extractSummary(output);
2654
2309
  return { success: true, summary };
@@ -2675,162 +2330,6 @@ var init_task_executor = __esm(() => {
2675
2330
  init_prompt_builder();
2676
2331
  });
2677
2332
 
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
2333
  // src/agent/worker-cli.ts
2835
2334
  var exports_worker_cli = {};
2836
2335
  __export(exports_worker_cli, {
@@ -2863,16 +2362,8 @@ function parseWorkerArgs(argv) {
2863
2362
  config.apiKey = args[++i];
2864
2363
  else if (arg === "--project-path")
2865
2364
  config.projectPath = args[++i];
2866
- else if (arg === "--main-project-path")
2867
- config.mainProjectPath = args[++i];
2868
2365
  else if (arg === "--model")
2869
2366
  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
2367
  else if (arg === "--provider") {
2877
2368
  const value = args[i + 1];
2878
2369
  if (value && !value.startsWith("--"))
@@ -2920,8 +2411,8 @@ class AgentWorker {
2920
2411
  tasksCompleted = 0;
2921
2412
  heartbeatInterval = null;
2922
2413
  currentTaskId = null;
2923
- currentWorktreePath = null;
2924
- postCleanupDelayMs = 5000;
2414
+ completedTaskList = [];
2415
+ taskSummaries = [];
2925
2416
  constructor(config) {
2926
2417
  this.config = config;
2927
2418
  const projectPath = config.projectPath || process.cwd();
@@ -2936,17 +2427,12 @@ class AgentWorker {
2936
2427
  }
2937
2428
  });
2938
2429
  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;
2430
+ if (!isGitAvailable()) {
2431
+ this.log("git is not installed — branch management will not work", "error");
2942
2432
  }
2943
- if (config.autoPush && !isGhAvailable(projectPath)) {
2433
+ if (!isGhAvailable(projectPath)) {
2944
2434
  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
2435
  }
2946
- const ghUsername = config.autoPush ? getGhUsername() : null;
2947
- if (ghUsername) {
2948
- this.log(`GitHub user: ${ghUsername}`, "info");
2949
- }
2950
2436
  const provider = config.provider ?? PROVIDER.CLAUDE;
2951
2437
  this.aiRunner = createAiRunner(provider, {
2952
2438
  projectPath,
@@ -2959,18 +2445,9 @@ class AgentWorker {
2959
2445
  log
2960
2446
  });
2961
2447
  this.knowledgeBase = new KnowledgeBase(projectPath);
2962
- this.gitWorkflow = new GitWorkflow(config, log, ghUsername);
2448
+ this.gitWorkflow = new GitWorkflow(config, log);
2963
2449
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
2964
2450
  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
2451
  }
2975
2452
  log(message, level = "info") {
2976
2453
  const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
@@ -3019,56 +2496,26 @@ class AgentWorker {
3019
2496
  }
3020
2497
  async executeTask(task) {
3021
2498
  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
2499
  try {
3028
- const result = await executor.execute(fullTask);
3029
- let taskBranch = null;
3030
- let prUrl = null;
3031
- let prError = null;
2500
+ const result = await this.taskExecutor.execute(fullTask);
3032
2501
  let noChanges = false;
3033
- if (result.success && worktreePath) {
3034
- const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined, baseCommitHash ?? undefined);
2502
+ let taskBranch = null;
2503
+ if (result.success) {
2504
+ const commitResult = this.gitWorkflow.commitAndPush(fullTask);
3035
2505
  taskBranch = commitResult.branch;
3036
- branchPushed = commitResult.pushed;
3037
- keepBranch = taskBranch !== null;
3038
2506
  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
2507
  }
3058
2508
  return {
3059
2509
  ...result,
3060
2510
  branch: taskBranch ?? undefined,
3061
- prUrl: prUrl ?? undefined,
3062
- prError: prError ?? undefined,
3063
2511
  noChanges: noChanges || undefined
3064
2512
  };
3065
- } finally {
3066
- if (preserveWorktree || keepBranch) {
3067
- this.currentWorktreePath = null;
3068
- } else {
3069
- this.gitWorkflow.cleanupWorktree(worktreePath, keepBranch);
3070
- this.currentWorktreePath = null;
3071
- }
2513
+ } catch (err) {
2514
+ const msg = err instanceof Error ? err.message : String(err);
2515
+ return {
2516
+ success: false,
2517
+ summary: `Execution error: ${msg}`
2518
+ };
3072
2519
  }
3073
2520
  }
3074
2521
  updateProgress(task, summary) {
@@ -3081,7 +2528,6 @@ class AgentWorker {
3081
2528
  role: "assistant",
3082
2529
  content: summary
3083
2530
  });
3084
- this.log(`Updated progress.md: ${task.title}`, "info");
3085
2531
  } catch (err) {
3086
2532
  this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
3087
2533
  }
@@ -3101,19 +2547,13 @@ class AgentWorker {
3101
2547
  this.log(`Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, "warn");
3102
2548
  });
3103
2549
  }
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
2550
  async run() {
3111
2551
  this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
3112
2552
  const handleShutdown = () => {
3113
2553
  this.log("Received shutdown signal. Aborting...", "warn");
3114
2554
  this.aiRunner.abort();
3115
2555
  this.stopHeartbeat();
3116
- this.gitWorkflow.cleanupWorktree(this.currentWorktreePath, false);
2556
+ this.gitWorkflow.checkoutBaseBranch();
3117
2557
  process.exit(1);
3118
2558
  };
3119
2559
  process.on("SIGTERM", handleShutdown);
@@ -3125,10 +2565,12 @@ class AgentWorker {
3125
2565
  } else {
3126
2566
  this.log("No active sprint found.", "warn");
3127
2567
  }
2568
+ const branchName = this.gitWorkflow.createBranch(this.config.sprintId);
2569
+ this.log(`Working on branch: ${branchName}`, "info");
3128
2570
  while (this.tasksCompleted < this.maxTasks) {
3129
2571
  const task = await this.getNextTask();
3130
2572
  if (!task) {
3131
- this.log("No more tasks to process. Exiting.", "info");
2573
+ this.log("No more tasks to process.", "info");
3132
2574
  break;
3133
2575
  }
3134
2576
  this.log(`Claimed: ${task.title}`, "success");
@@ -3144,7 +2586,7 @@ class AgentWorker {
3144
2586
  });
3145
2587
  await this.client.tasks.addComment(task.id, this.config.workspaceId, {
3146
2588
  author: this.config.agentId,
3147
- text: `⚠️ Agent execution finished with no file changes, so no commit/branch/PR was created.
2589
+ text: `⚠️ Agent execution finished with no file changes, so no commit was created.
3148
2590
 
3149
2591
  ${result.summary}`
3150
2592
  });
@@ -3153,22 +2595,17 @@ ${result.summary}`
3153
2595
  const updatePayload = {
3154
2596
  status: import_shared3.TaskStatus.IN_REVIEW
3155
2597
  };
3156
- if (result.prUrl) {
3157
- updatePayload.prUrl = result.prUrl;
3158
- }
3159
2598
  await this.client.tasks.update(task.id, this.config.workspaceId, updatePayload);
3160
2599
  const branchInfo = result.branch ? `
3161
2600
 
3162
2601
  Branch: \`${result.branch}\`` : "";
3163
- const prInfo = result.prUrl ? `
3164
- PR: ${result.prUrl}` : "";
3165
- const prErrorInfo = result.prError ? `
3166
- PR automation error: ${result.prError}` : "";
3167
2602
  await this.client.tasks.addComment(task.id, this.config.workspaceId, {
3168
2603
  author: this.config.agentId,
3169
- text: `✅ ${result.summary}${branchInfo}${prInfo}${prErrorInfo}`
2604
+ text: `✅ ${result.summary}${branchInfo}`
3170
2605
  });
3171
2606
  this.tasksCompleted++;
2607
+ this.completedTaskList.push({ title: task.title, id: task.id });
2608
+ this.taskSummaries.push(result.summary);
3172
2609
  this.updateProgress(task, result.summary);
3173
2610
  }
3174
2611
  } else {
@@ -3184,8 +2621,24 @@ PR automation error: ${result.prError}` : "";
3184
2621
  }
3185
2622
  this.currentTaskId = null;
3186
2623
  this.sendHeartbeat();
3187
- await this.delayAfterCleanup();
3188
2624
  }
2625
+ if (this.completedTaskList.length > 0) {
2626
+ this.log("All tasks done. Creating pull request...", "info");
2627
+ const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
2628
+ if (prResult.url) {
2629
+ this.log(`PR created: ${prResult.url}`, "success");
2630
+ for (const task of this.completedTaskList) {
2631
+ try {
2632
+ await this.client.tasks.update(task.id, this.config.workspaceId, {
2633
+ prUrl: prResult.url
2634
+ });
2635
+ } catch {}
2636
+ }
2637
+ } else if (prResult.error) {
2638
+ this.log(`PR creation failed: ${prResult.error}`, "error");
2639
+ }
2640
+ }
2641
+ this.gitWorkflow.checkoutBaseBranch();
3189
2642
  this.currentTaskId = null;
3190
2643
  this.stopHeartbeat();
3191
2644
  this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});