@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.`);
1764
+ this.projectPath = config.projectPath || process.cwd();
1765
+ this.ghUsername = getGhUsername();
1766
+ if (this.ghUsername) {
1767
+ this.log(`GitHub user: ${this.ghUsername}`, "info");
1816
1768
  }
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
1769
  }
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
  }
1779
+ const suffix = sprintId ? sprintId.slice(0, 8) : Date.now().toString(36);
1780
+ this.branchName = `locus/${suffix}`;
1781
+ try {
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;
1853
1787
  }
1854
- hasRemoteTrackingBranch(branch) {
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 };
1792
+ }
1855
1793
  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;
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();
1800
+ }
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
+ };
1809
+ }
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
+ };
1821
+ }
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>`);
1831
+ }
1832
+ const commitMessage = `feat(agent): ${task.title}
1833
+
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
+ };
1863
1849
  }
1864
1850
  }
1865
- hasRemoteBranch(branch) {
1851
+ pushBranch() {
1852
+ if (!this.branchName) {
1853
+ return { branch: null, pushed: false, pushFailed: false };
1854
+ }
1866
1855
  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;
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
+ }
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
+ };
1874
1891
  }
1875
1892
  }
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) {
1893
+ createPullRequest(completedTasks, summaries) {
1894
+ if (!this.branchName || !this.baseBranch) {
1895
+ return { url: null, error: "No branch or base branch available." };
1896
+ }
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
+ };
1903
+ }
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
+ };
1909
+ }
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");
1885
1913
  try {
1886
- import_node_child_process4.execFileSync("gh", [
1914
+ const output = import_node_child_process4.execFileSync("gh", [
1887
1915
  "pr",
1888
- "review",
1889
- prIdentifier,
1916
+ "create",
1917
+ "--title",
1918
+ title,
1890
1919
  "--body",
1891
1920
  body,
1892
- `--${event.toLowerCase().replace("_", "-")}`
1921
+ "--base",
1922
+ this.baseBranch,
1923
+ "--head",
1924
+ this.branchName
1893
1925
  ], {
1894
1926
  cwd: this.projectPath,
1895
1927
  encoding: "utf-8",
1896
1928
  stdio: ["pipe", "pipe", "pipe"]
1897
- });
1929
+ }).trim();
1930
+ this.log(`PR created: ${output}`, "success");
1931
+ return { url: output };
1898
1932
  } 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;
1933
+ const errorMessage = err instanceof Error ? err.message : String(err);
1934
+ this.log(`PR creation failed: ${errorMessage}`, "error");
1935
+ return { url: null, error: errorMessage };
1909
1936
  }
1910
1937
  }
1911
- listLocusPrs() {
1938
+ checkoutBaseBranch() {
1939
+ if (!this.baseBranch)
1940
+ return;
1912
1941
  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 [];
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");
1937
1946
  }
1938
1947
  }
1939
- hasLocusReview(prNumber) {
1948
+ getBranchName() {
1949
+ return this.branchName;
1950
+ }
1951
+ getBaseBranch() {
1952
+ return this.baseBranch;
1953
+ }
1954
+ getBaseCommit() {
1955
+ if (!this.baseBranch)
1956
+ return null;
1940
1957
  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;
1958
+ return this.gitExec(["rev-parse", this.baseBranch]).trim();
1948
1959
  } catch {
1949
- return false;
1960
+ return null;
1950
1961
  }
1951
1962
  }
1952
- listUnreviewedLocusPrs() {
1953
- const allPrs = this.listLocusPrs();
1954
- return allPrs.filter((pr) => !this.hasLocusReview(String(pr.number)));
1955
- }
1956
- buildPrBody(task, agentId, summary) {
1963
+ buildPrBody(completedTasks, summaries) {
1957
1964
  const sections = [];
1958
- sections.push(`## Task: ${task.title}`);
1965
+ sections.push("## Completed Tasks");
1959
1966
  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}`);
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]);
1968
1974
  }
1969
1975
  sections.push("");
1970
1976
  }
1971
- if (summary) {
1972
- sections.push("## Agent Summary");
1973
- sections.push(summary);
1974
- sections.push("");
1975
- }
1976
1977
  sections.push("---");
1977
- sections.push(`*Created by Locus Agent \`${agentId.slice(-8)}\`* | Task ID: \`${task.id}\``);
1978
+ sections.push(`*Created by Locus Agent \`${this.config.agentId.slice(-8)}\`*`);
1978
1979
  return sections.join(`
1979
1980
  `);
1980
1981
  }
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.`);
2053
- }
2054
- this.log(`Removing existing worktree for branch: ${branch} (${worktreePath2})`, "warn");
2055
- this.remove(worktreePath2, false);
2056
- }
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);
2062
- }
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;
2070
- }
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(`
2084
-
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
- }
2117
- }
2118
- return worktrees;
2119
- }
2120
- listAgentWorktrees() {
2121
- return this.list().filter((wt) => !wt.isMain);
2122
- }
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);
2136
- }
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");
2143
- }
2144
- }
2145
- this.log("Worktree removed", "success");
2146
- }
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");
2154
- }
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
- }
2167
- }
2168
- if (import_node_fs4.existsSync(this.rootPath)) {
2169
- try {
2170
- import_node_fs4.rmSync(this.rootPath, { recursive: true, force: true });
2171
- } catch {}
2172
- }
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) {
2180
- try {
2181
- const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
2182
- return Number.parseInt(count, 10) > 0;
2183
- } catch (err) {
2184
- this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
2185
- return false;
2186
- }
2187
- }
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");
2239
- 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;
2254
- }
2255
- }
2256
- getBranch(worktreePath) {
2257
- return this.git("rev-parse --abbrev-ref HEAD", worktreePath).trim();
2258
- }
2259
- hasWorktreeForTask(taskId) {
2260
- return this.listAgentWorktrees().some((wt) => wt.branch.includes(taskId) || wt.path.includes(taskId));
2261
- }
2262
- branchExists(branchName) {
2263
- try {
2264
- this.git(`rev-parse --verify "refs/heads/${branchName}"`, this.projectPath);
2265
- return true;
2266
- } catch {
2267
- return false;
2268
- }
2269
- }
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}`);
2283
- }
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 {}
2306
- }
2307
- }
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(() => {});
@@ -3232,10 +2692,7 @@ __export(exports_index_node, {
3232
2692
  detectRemoteProvider: () => detectRemoteProvider,
3233
2693
  createAiRunner: () => createAiRunner,
3234
2694
  c: () => c,
3235
- WorktreeManager: () => WorktreeManager,
3236
2695
  WorkspacesModule: () => WorkspacesModule,
3237
- WORKTREE_ROOT_DIR: () => WORKTREE_ROOT_DIR,
3238
- WORKTREE_BRANCH_PREFIX: () => WORKTREE_BRANCH_PREFIX,
3239
2696
  TasksModule: () => TasksModule,
3240
2697
  TaskExecutor: () => TaskExecutor,
3241
2698
  SprintsModule: () => SprintsModule,
@@ -3263,7 +2720,6 @@ __export(exports_index_node, {
3263
2720
  ExecEventEmitter: () => ExecEventEmitter,
3264
2721
  DocumentFetcher: () => DocumentFetcher,
3265
2722
  DocsModule: () => DocsModule,
3266
- DEFAULT_WORKTREE_CONFIG: () => DEFAULT_WORKTREE_CONFIG,
3267
2723
  DEFAULT_MODEL: () => DEFAULT_MODEL,
3268
2724
  ContextTracker: () => ContextTracker,
3269
2725
  CodexRunner: () => CodexRunner,
@@ -3279,8 +2735,8 @@ module.exports = __toCommonJS(exports_index_node);
3279
2735
 
3280
2736
  // src/core/indexer.ts
3281
2737
  var import_node_crypto2 = require("node:crypto");
3282
- var import_node_fs6 = require("node:fs");
3283
- var import_node_path8 = require("node:path");
2738
+ var import_node_fs5 = require("node:fs");
2739
+ var import_node_path7 = require("node:path");
3284
2740
  var import_globby = require("globby");
3285
2741
 
3286
2742
  class CodebaseIndexer {
@@ -3289,7 +2745,7 @@ class CodebaseIndexer {
3289
2745
  fullReindexRatioThreshold = 0.2;
3290
2746
  constructor(projectPath) {
3291
2747
  this.projectPath = projectPath;
3292
- this.indexPath = import_node_path8.join(projectPath, ".locus", "codebase-index.json");
2748
+ this.indexPath = import_node_path7.join(projectPath, ".locus", "codebase-index.json");
3293
2749
  }
3294
2750
  async index(onProgress, treeSummarizer, force = false) {
3295
2751
  if (!treeSummarizer) {
@@ -3345,11 +2801,11 @@ class CodebaseIndexer {
3345
2801
  }
3346
2802
  }
3347
2803
  async getFileTree() {
3348
- const gitmodulesPath = import_node_path8.join(this.projectPath, ".gitmodules");
2804
+ const gitmodulesPath = import_node_path7.join(this.projectPath, ".gitmodules");
3349
2805
  const submoduleIgnores = [];
3350
- if (import_node_fs6.existsSync(gitmodulesPath)) {
2806
+ if (import_node_fs5.existsSync(gitmodulesPath)) {
3351
2807
  try {
3352
- const content = import_node_fs6.readFileSync(gitmodulesPath, "utf-8");
2808
+ const content = import_node_fs5.readFileSync(gitmodulesPath, "utf-8");
3353
2809
  const lines = content.split(`
3354
2810
  `);
3355
2811
  for (const line of lines) {
@@ -3405,9 +2861,9 @@ class CodebaseIndexer {
3405
2861
  });
3406
2862
  }
3407
2863
  loadIndex() {
3408
- if (import_node_fs6.existsSync(this.indexPath)) {
2864
+ if (import_node_fs5.existsSync(this.indexPath)) {
3409
2865
  try {
3410
- return JSON.parse(import_node_fs6.readFileSync(this.indexPath, "utf-8"));
2866
+ return JSON.parse(import_node_fs5.readFileSync(this.indexPath, "utf-8"));
3411
2867
  } catch {
3412
2868
  return null;
3413
2869
  }
@@ -3415,11 +2871,11 @@ class CodebaseIndexer {
3415
2871
  return null;
3416
2872
  }
3417
2873
  saveIndex(index) {
3418
- const dir = import_node_path8.dirname(this.indexPath);
3419
- if (!import_node_fs6.existsSync(dir)) {
3420
- import_node_fs6.mkdirSync(dir, { recursive: true });
2874
+ const dir = import_node_path7.dirname(this.indexPath);
2875
+ if (!import_node_fs5.existsSync(dir)) {
2876
+ import_node_fs5.mkdirSync(dir, { recursive: true });
3421
2877
  }
3422
- import_node_fs6.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
2878
+ import_node_fs5.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
3423
2879
  }
3424
2880
  cloneIndex(index) {
3425
2881
  return JSON.parse(JSON.stringify(index));
@@ -3435,7 +2891,7 @@ class CodebaseIndexer {
3435
2891
  }
3436
2892
  hashFile(filePath) {
3437
2893
  try {
3438
- const content = import_node_fs6.readFileSync(import_node_path8.join(this.projectPath, filePath), "utf-8");
2894
+ const content = import_node_fs5.readFileSync(import_node_path7.join(this.projectPath, filePath), "utf-8");
3439
2895
  return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
3440
2896
  } catch {
3441
2897
  return null;
@@ -3586,8 +3042,8 @@ Return ONLY valid JSON, no markdown formatting.`;
3586
3042
  }
3587
3043
  // src/agent/document-fetcher.ts
3588
3044
  init_config();
3589
- var import_node_fs7 = require("node:fs");
3590
- var import_node_path9 = require("node:path");
3045
+ var import_node_fs6 = require("node:fs");
3046
+ var import_node_path8 = require("node:path");
3591
3047
 
3592
3048
  class DocumentFetcher {
3593
3049
  deps;
@@ -3596,8 +3052,8 @@ class DocumentFetcher {
3596
3052
  }
3597
3053
  async fetch() {
3598
3054
  const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
3599
- if (!import_node_fs7.existsSync(documentsDir)) {
3600
- import_node_fs7.mkdirSync(documentsDir, { recursive: true });
3055
+ if (!import_node_fs6.existsSync(documentsDir)) {
3056
+ import_node_fs6.mkdirSync(documentsDir, { recursive: true });
3601
3057
  }
3602
3058
  try {
3603
3059
  const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
@@ -3610,14 +3066,14 @@ class DocumentFetcher {
3610
3066
  continue;
3611
3067
  }
3612
3068
  const groupName = groupMap.get(doc.groupId || "") || "General";
3613
- const groupDir = import_node_path9.join(documentsDir, groupName);
3614
- if (!import_node_fs7.existsSync(groupDir)) {
3615
- import_node_fs7.mkdirSync(groupDir, { recursive: true });
3069
+ const groupDir = import_node_path8.join(documentsDir, groupName);
3070
+ if (!import_node_fs6.existsSync(groupDir)) {
3071
+ import_node_fs6.mkdirSync(groupDir, { recursive: true });
3616
3072
  }
3617
3073
  const fileName = `${doc.title}.md`;
3618
- const filePath = import_node_path9.join(groupDir, fileName);
3619
- if (!import_node_fs7.existsSync(filePath) || import_node_fs7.readFileSync(filePath, "utf-8") !== doc.content) {
3620
- import_node_fs7.writeFileSync(filePath, doc.content || "");
3074
+ const filePath = import_node_path8.join(groupDir, fileName);
3075
+ if (!import_node_fs6.existsSync(filePath) || import_node_fs6.readFileSync(filePath, "utf-8") !== doc.content) {
3076
+ import_node_fs6.writeFileSync(filePath, doc.content || "");
3621
3077
  fetchedCount++;
3622
3078
  }
3623
3079
  }
@@ -3635,7 +3091,7 @@ class DocumentFetcher {
3635
3091
  init_git_workflow();
3636
3092
 
3637
3093
  // src/agent/review-service.ts
3638
- var import_node_child_process6 = require("node:child_process");
3094
+ var import_node_child_process5 = require("node:child_process");
3639
3095
 
3640
3096
  class ReviewService {
3641
3097
  deps;
@@ -3645,7 +3101,7 @@ class ReviewService {
3645
3101
  async reviewStagedChanges(sprint) {
3646
3102
  const { projectPath, log } = this.deps;
3647
3103
  try {
3648
- import_node_child_process6.execSync("git add -A", { cwd: projectPath, stdio: "pipe" });
3104
+ import_node_child_process5.execSync("git add -A", { cwd: projectPath, stdio: "pipe" });
3649
3105
  log("Staged all changes for review.", "info");
3650
3106
  } catch (err) {
3651
3107
  log(`Failed to stage changes: ${err instanceof Error ? err.message : String(err)}`, "error");
@@ -3653,7 +3109,7 @@ class ReviewService {
3653
3109
  }
3654
3110
  let diff;
3655
3111
  try {
3656
- diff = import_node_child_process6.execSync("git diff --cached --stat && echo '---' && git diff --cached", {
3112
+ diff = import_node_child_process5.execSync("git diff --cached --stat && echo '---' && git diff --cached", {
3657
3113
  cwd: projectPath,
3658
3114
  maxBuffer: 10 * 1024 * 1024
3659
3115
  }).toString();
@@ -3696,11 +3152,246 @@ Keep the review concise but thorough. Focus on substance over style.`;
3696
3152
  init_factory();
3697
3153
  init_config();
3698
3154
  init_git_utils();
3699
- init_pr_service();
3700
- init_src();
3701
- init_knowledge_base();
3702
- init_colors();
3703
- function resolveProvider2(value) {
3155
+
3156
+ // src/git/pr-service.ts
3157
+ init_git_utils();
3158
+ var import_node_child_process6 = require("node:child_process");
3159
+
3160
+ class PrService {
3161
+ projectPath;
3162
+ log;
3163
+ constructor(projectPath, log) {
3164
+ this.projectPath = projectPath;
3165
+ this.log = log;
3166
+ }
3167
+ createPr(options) {
3168
+ const {
3169
+ task,
3170
+ branch,
3171
+ baseBranch: requestedBaseBranch,
3172
+ agentId,
3173
+ summary
3174
+ } = options;
3175
+ const provider = detectRemoteProvider(this.projectPath);
3176
+ if (provider !== "github") {
3177
+ throw new Error(`PR creation is only supported for GitHub repositories (detected: ${provider})`);
3178
+ }
3179
+ if (!isGhAvailable(this.projectPath)) {
3180
+ throw new Error("GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/");
3181
+ }
3182
+ const title = `[Locus] ${task.title}`;
3183
+ const body = this.buildPrBody(task, agentId, summary);
3184
+ const baseBranch = requestedBaseBranch ?? getDefaultBranch(this.projectPath);
3185
+ this.validateCreatePrInputs(baseBranch, branch);
3186
+ this.log(`Creating PR: ${title} (${branch} → ${baseBranch})`, "info");
3187
+ const output = import_node_child_process6.execFileSync("gh", [
3188
+ "pr",
3189
+ "create",
3190
+ "--title",
3191
+ title,
3192
+ "--body",
3193
+ body,
3194
+ "--base",
3195
+ baseBranch,
3196
+ "--head",
3197
+ branch
3198
+ ], {
3199
+ cwd: this.projectPath,
3200
+ encoding: "utf-8",
3201
+ stdio: ["pipe", "pipe", "pipe"]
3202
+ }).trim();
3203
+ const url = output;
3204
+ const prNumber = this.extractPrNumber(url);
3205
+ this.log(`PR created: ${url}`, "success");
3206
+ return { url, number: prNumber };
3207
+ }
3208
+ validateCreatePrInputs(baseBranch, headBranch) {
3209
+ if (!this.hasRemoteBranch(baseBranch)) {
3210
+ throw new Error(`Base branch "${baseBranch}" does not exist on origin. Push/fetch refs and retry.`);
3211
+ }
3212
+ if (!this.hasRemoteBranch(headBranch)) {
3213
+ throw new Error(`Head branch "${headBranch}" is not available on origin. Ensure it is pushed before PR creation.`);
3214
+ }
3215
+ const baseRef = this.resolveBranchRef(baseBranch);
3216
+ const headRef = this.resolveBranchRef(headBranch);
3217
+ if (!baseRef) {
3218
+ throw new Error(`Could not resolve base branch "${baseBranch}" locally.`);
3219
+ }
3220
+ if (!headRef) {
3221
+ throw new Error(`Could not resolve head branch "${headBranch}" locally.`);
3222
+ }
3223
+ const commitsAhead = this.countCommitsAhead(baseRef, headRef);
3224
+ if (commitsAhead <= 0) {
3225
+ throw new Error(`No commits between "${baseBranch}" and "${headBranch}". Skipping PR creation.`);
3226
+ }
3227
+ }
3228
+ countCommitsAhead(baseRef, headRef) {
3229
+ const output = import_node_child_process6.execFileSync("git", ["rev-list", "--count", `${baseRef}..${headRef}`], {
3230
+ cwd: this.projectPath,
3231
+ encoding: "utf-8",
3232
+ stdio: ["pipe", "pipe", "pipe"]
3233
+ }).trim();
3234
+ const value = Number.parseInt(output || "0", 10);
3235
+ return Number.isNaN(value) ? 0 : value;
3236
+ }
3237
+ resolveBranchRef(branch) {
3238
+ if (this.hasLocalBranch(branch)) {
3239
+ return branch;
3240
+ }
3241
+ if (this.hasRemoteTrackingBranch(branch)) {
3242
+ return `origin/${branch}`;
3243
+ }
3244
+ return null;
3245
+ }
3246
+ hasLocalBranch(branch) {
3247
+ try {
3248
+ import_node_child_process6.execFileSync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
3249
+ cwd: this.projectPath,
3250
+ stdio: ["pipe", "pipe", "pipe"]
3251
+ });
3252
+ return true;
3253
+ } catch {
3254
+ return false;
3255
+ }
3256
+ }
3257
+ hasRemoteTrackingBranch(branch) {
3258
+ try {
3259
+ import_node_child_process6.execFileSync("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`], {
3260
+ cwd: this.projectPath,
3261
+ stdio: ["pipe", "pipe", "pipe"]
3262
+ });
3263
+ return true;
3264
+ } catch {
3265
+ return false;
3266
+ }
3267
+ }
3268
+ hasRemoteBranch(branch) {
3269
+ try {
3270
+ import_node_child_process6.execFileSync("git", ["ls-remote", "--exit-code", "--heads", "origin", branch], {
3271
+ cwd: this.projectPath,
3272
+ stdio: ["pipe", "pipe", "pipe"]
3273
+ });
3274
+ return true;
3275
+ } catch {
3276
+ return false;
3277
+ }
3278
+ }
3279
+ getPrDiff(branch) {
3280
+ return import_node_child_process6.execFileSync("gh", ["pr", "diff", branch], {
3281
+ cwd: this.projectPath,
3282
+ encoding: "utf-8",
3283
+ stdio: ["pipe", "pipe", "pipe"],
3284
+ maxBuffer: 10 * 1024 * 1024
3285
+ });
3286
+ }
3287
+ submitReview(prIdentifier, body, event) {
3288
+ try {
3289
+ import_node_child_process6.execFileSync("gh", [
3290
+ "pr",
3291
+ "review",
3292
+ prIdentifier,
3293
+ "--body",
3294
+ body,
3295
+ `--${event.toLowerCase().replace("_", "-")}`
3296
+ ], {
3297
+ cwd: this.projectPath,
3298
+ encoding: "utf-8",
3299
+ stdio: ["pipe", "pipe", "pipe"]
3300
+ });
3301
+ } catch (err) {
3302
+ const msg = err instanceof Error ? err.message : String(err);
3303
+ if (event === "REQUEST_CHANGES" && msg.includes("own pull request")) {
3304
+ import_node_child_process6.execFileSync("gh", ["pr", "review", prIdentifier, "--body", body, "--comment"], {
3305
+ cwd: this.projectPath,
3306
+ encoding: "utf-8",
3307
+ stdio: ["pipe", "pipe", "pipe"]
3308
+ });
3309
+ return;
3310
+ }
3311
+ throw err;
3312
+ }
3313
+ }
3314
+ listLocusPrs() {
3315
+ try {
3316
+ const output = import_node_child_process6.execFileSync("gh", [
3317
+ "pr",
3318
+ "list",
3319
+ "--search",
3320
+ "[Locus] in:title",
3321
+ "--state",
3322
+ "open",
3323
+ "--json",
3324
+ "number,title,url,headRefName"
3325
+ ], {
3326
+ cwd: this.projectPath,
3327
+ encoding: "utf-8",
3328
+ stdio: ["pipe", "pipe", "pipe"]
3329
+ }).trim();
3330
+ const prs = JSON.parse(output || "[]");
3331
+ return prs.map((pr) => ({
3332
+ number: pr.number,
3333
+ title: pr.title,
3334
+ url: pr.url,
3335
+ branch: pr.headRefName
3336
+ }));
3337
+ } catch {
3338
+ this.log("Failed to list Locus PRs", "warn");
3339
+ return [];
3340
+ }
3341
+ }
3342
+ hasLocusReview(prNumber) {
3343
+ try {
3344
+ const output = import_node_child_process6.execFileSync("gh", ["pr", "view", prNumber, "--json", "reviews"], {
3345
+ cwd: this.projectPath,
3346
+ encoding: "utf-8",
3347
+ stdio: ["pipe", "pipe", "pipe"]
3348
+ }).trim();
3349
+ const data = JSON.parse(output || "{}");
3350
+ return data.reviews?.some((r) => r.body?.includes("## Locus Agent Review")) ?? false;
3351
+ } catch {
3352
+ return false;
3353
+ }
3354
+ }
3355
+ listUnreviewedLocusPrs() {
3356
+ const allPrs = this.listLocusPrs();
3357
+ return allPrs.filter((pr) => !this.hasLocusReview(String(pr.number)));
3358
+ }
3359
+ buildPrBody(task, agentId, summary) {
3360
+ const sections = [];
3361
+ sections.push(`## Task: ${task.title}`);
3362
+ sections.push("");
3363
+ if (task.description) {
3364
+ sections.push(task.description);
3365
+ sections.push("");
3366
+ }
3367
+ if (task.acceptanceChecklist?.length > 0) {
3368
+ sections.push("## Acceptance Criteria");
3369
+ for (const item of task.acceptanceChecklist) {
3370
+ sections.push(`- [ ] ${item.text}`);
3371
+ }
3372
+ sections.push("");
3373
+ }
3374
+ if (summary) {
3375
+ sections.push("## Agent Summary");
3376
+ sections.push(summary);
3377
+ sections.push("");
3378
+ }
3379
+ sections.push("---");
3380
+ sections.push(`*Created by Locus Agent \`${agentId.slice(-8)}\`* | Task ID: \`${task.id}\``);
3381
+ return sections.join(`
3382
+ `);
3383
+ }
3384
+ extractPrNumber(url) {
3385
+ const match = url.match(/\/pull\/(\d+)/);
3386
+ return match ? Number.parseInt(match[1], 10) : 0;
3387
+ }
3388
+ }
3389
+
3390
+ // src/agent/reviewer-worker.ts
3391
+ init_src();
3392
+ init_knowledge_base();
3393
+ init_colors();
3394
+ function resolveProvider2(value) {
3704
3395
  if (!value || value.startsWith("--"))
3705
3396
  return PROVIDER.CLAUDE;
3706
3397
  if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
@@ -4344,8 +4035,8 @@ class ExecEventEmitter {
4344
4035
  }
4345
4036
  // src/exec/history-manager.ts
4346
4037
  init_config();
4347
- var import_node_fs8 = require("node:fs");
4348
- var import_node_path10 = require("node:path");
4038
+ var import_node_fs7 = require("node:fs");
4039
+ var import_node_path9 = require("node:path");
4349
4040
  var DEFAULT_MAX_SESSIONS = 30;
4350
4041
  function generateSessionId2() {
4351
4042
  const timestamp = Date.now().toString(36);
@@ -4357,30 +4048,30 @@ class HistoryManager {
4357
4048
  historyDir;
4358
4049
  maxSessions;
4359
4050
  constructor(projectPath, options) {
4360
- this.historyDir = options?.historyDir ?? import_node_path10.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
4051
+ this.historyDir = options?.historyDir ?? import_node_path9.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
4361
4052
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
4362
4053
  this.ensureHistoryDir();
4363
4054
  }
4364
4055
  ensureHistoryDir() {
4365
- if (!import_node_fs8.existsSync(this.historyDir)) {
4366
- import_node_fs8.mkdirSync(this.historyDir, { recursive: true });
4056
+ if (!import_node_fs7.existsSync(this.historyDir)) {
4057
+ import_node_fs7.mkdirSync(this.historyDir, { recursive: true });
4367
4058
  }
4368
4059
  }
4369
4060
  getSessionPath(sessionId) {
4370
- return import_node_path10.join(this.historyDir, `${sessionId}.json`);
4061
+ return import_node_path9.join(this.historyDir, `${sessionId}.json`);
4371
4062
  }
4372
4063
  saveSession(session) {
4373
4064
  const filePath = this.getSessionPath(session.id);
4374
4065
  session.updatedAt = Date.now();
4375
- import_node_fs8.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
4066
+ import_node_fs7.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
4376
4067
  }
4377
4068
  loadSession(sessionId) {
4378
4069
  const filePath = this.getSessionPath(sessionId);
4379
- if (!import_node_fs8.existsSync(filePath)) {
4070
+ if (!import_node_fs7.existsSync(filePath)) {
4380
4071
  return null;
4381
4072
  }
4382
4073
  try {
4383
- const content = import_node_fs8.readFileSync(filePath, "utf-8");
4074
+ const content = import_node_fs7.readFileSync(filePath, "utf-8");
4384
4075
  return JSON.parse(content);
4385
4076
  } catch {
4386
4077
  return null;
@@ -4388,18 +4079,18 @@ class HistoryManager {
4388
4079
  }
4389
4080
  deleteSession(sessionId) {
4390
4081
  const filePath = this.getSessionPath(sessionId);
4391
- if (!import_node_fs8.existsSync(filePath)) {
4082
+ if (!import_node_fs7.existsSync(filePath)) {
4392
4083
  return false;
4393
4084
  }
4394
4085
  try {
4395
- import_node_fs8.rmSync(filePath);
4086
+ import_node_fs7.rmSync(filePath);
4396
4087
  return true;
4397
4088
  } catch {
4398
4089
  return false;
4399
4090
  }
4400
4091
  }
4401
4092
  listSessions(options) {
4402
- const files = import_node_fs8.readdirSync(this.historyDir);
4093
+ const files = import_node_fs7.readdirSync(this.historyDir);
4403
4094
  let sessions = [];
4404
4095
  for (const file of files) {
4405
4096
  if (file.endsWith(".json")) {
@@ -4472,11 +4163,11 @@ class HistoryManager {
4472
4163
  return deleted;
4473
4164
  }
4474
4165
  getSessionCount() {
4475
- const files = import_node_fs8.readdirSync(this.historyDir);
4166
+ const files = import_node_fs7.readdirSync(this.historyDir);
4476
4167
  return files.filter((f) => f.endsWith(".json")).length;
4477
4168
  }
4478
4169
  sessionExists(sessionId) {
4479
- return import_node_fs8.existsSync(this.getSessionPath(sessionId));
4170
+ return import_node_fs7.existsSync(this.getSessionPath(sessionId));
4480
4171
  }
4481
4172
  findSessionByPartialId(partialId) {
4482
4173
  const sessions = this.listSessions();
@@ -4490,12 +4181,12 @@ class HistoryManager {
4490
4181
  return this.historyDir;
4491
4182
  }
4492
4183
  clearAllSessions() {
4493
- const files = import_node_fs8.readdirSync(this.historyDir);
4184
+ const files = import_node_fs7.readdirSync(this.historyDir);
4494
4185
  let deleted = 0;
4495
4186
  for (const file of files) {
4496
4187
  if (file.endsWith(".json")) {
4497
4188
  try {
4498
- import_node_fs8.rmSync(import_node_path10.join(this.historyDir, file));
4189
+ import_node_fs7.rmSync(import_node_path9.join(this.historyDir, file));
4499
4190
  deleted++;
4500
4191
  } catch {}
4501
4192
  }
@@ -4761,7 +4452,6 @@ ${currentPrompt}`);
4761
4452
  }
4762
4453
  // src/git/index.ts
4763
4454
  init_git_utils();
4764
- init_pr_service();
4765
4455
 
4766
4456
  // src/index-node.ts
4767
4457
  init_src();
@@ -4770,59 +4460,128 @@ init_src();
4770
4460
  init_git_utils();
4771
4461
  init_src();
4772
4462
  init_colors();
4773
- init_worktree_manager();
4774
- var import_shared6 = require("@locusai/shared");
4775
- var import_events5 = require("events");
4776
-
4777
- // src/orchestrator/agent-pool.ts
4778
- init_colors();
4779
4463
  init_resolve_bin();
4780
4464
  var import_node_child_process7 = require("node:child_process");
4781
- var import_node_fs9 = require("node:fs");
4782
- var import_node_path11 = require("node:path");
4465
+ var import_node_fs8 = require("node:fs");
4466
+ var import_node_path10 = require("node:path");
4783
4467
  var import_node_url = require("node:url");
4784
4468
  var import_shared4 = require("@locusai/shared");
4785
4469
  var import_events4 = require("events");
4786
- var MAX_AGENTS = 5;
4787
4470
 
4788
- class AgentPool extends import_events4.EventEmitter {
4471
+ class AgentOrchestrator extends import_events4.EventEmitter {
4472
+ client;
4789
4473
  config;
4790
- agents = new Map;
4474
+ isRunning = false;
4475
+ processedTasks = new Set;
4476
+ resolvedSprintId = null;
4477
+ agentState = null;
4791
4478
  heartbeatInterval = null;
4792
4479
  constructor(config) {
4793
4480
  super();
4794
4481
  this.config = config;
4482
+ this.client = new LocusClient({
4483
+ baseUrl: config.apiBase,
4484
+ token: config.apiKey
4485
+ });
4795
4486
  }
4796
- get size() {
4797
- return this.agents.size;
4798
- }
4799
- get effectiveAgentCount() {
4800
- return Math.min(Math.max(this.config.agentCount ?? 1, 1), MAX_AGENTS);
4801
- }
4802
- getAll() {
4803
- return Array.from(this.agents.values());
4804
- }
4805
- get(agentId) {
4806
- return this.agents.get(agentId);
4807
- }
4808
- async spawn(index, resolvedSprintId, baseBranch) {
4809
- const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
4810
- const agentState = {
4811
- id: agentId,
4812
- status: "IDLE",
4813
- currentTaskId: null,
4814
- tasksCompleted: 0,
4487
+ async resolveSprintId() {
4488
+ if (this.config.sprintId) {
4489
+ return this.config.sprintId;
4490
+ }
4491
+ try {
4492
+ const sprint = await this.client.sprints.getActive(this.config.workspaceId);
4493
+ if (sprint?.id) {
4494
+ console.log(c.info(`\uD83D\uDCCB Using active sprint: ${sprint.name}`));
4495
+ return sprint.id;
4496
+ }
4497
+ } catch {}
4498
+ console.log(c.dim("ℹ No sprint specified, working with all workspace tasks"));
4499
+ return "";
4500
+ }
4501
+ async start() {
4502
+ if (this.isRunning) {
4503
+ throw new Error("Orchestrator is already running");
4504
+ }
4505
+ this.isRunning = true;
4506
+ this.processedTasks.clear();
4507
+ try {
4508
+ await this.orchestrationLoop();
4509
+ } catch (error) {
4510
+ this.emit("error", error);
4511
+ throw error;
4512
+ } finally {
4513
+ await this.cleanup();
4514
+ }
4515
+ }
4516
+ async orchestrationLoop() {
4517
+ this.resolvedSprintId = await this.resolveSprintId();
4518
+ this.emit("started", {
4519
+ timestamp: new Date,
4520
+ config: this.config,
4521
+ sprintId: this.resolvedSprintId
4522
+ });
4523
+ this.printBanner();
4524
+ const tasks2 = await this.getAvailableTasks();
4525
+ if (tasks2.length === 0) {
4526
+ console.log(c.dim("ℹ No available tasks found in the backlog."));
4527
+ return;
4528
+ }
4529
+ if (!this.preflightChecks())
4530
+ return;
4531
+ this.startHeartbeatMonitor();
4532
+ await this.spawnAgent();
4533
+ await this.waitForAgent();
4534
+ console.log(`
4535
+ ${c.success("✅ Orchestrator finished")}`);
4536
+ }
4537
+ printBanner() {
4538
+ console.log(`
4539
+ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
4540
+ console.log(c.dim("----------------------------------------------"));
4541
+ console.log(`${c.bold("Workspace:")} ${this.config.workspaceId}`);
4542
+ if (this.resolvedSprintId) {
4543
+ console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
4544
+ }
4545
+ console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
4546
+ console.log(c.dim(`----------------------------------------------
4547
+ `));
4548
+ }
4549
+ preflightChecks() {
4550
+ if (!isGitAvailable()) {
4551
+ console.log(c.error("git is not installed. Install from https://git-scm.com/"));
4552
+ return false;
4553
+ }
4554
+ if (!isGhAvailable(this.config.projectPath)) {
4555
+ console.log(c.warning("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/"));
4556
+ }
4557
+ return true;
4558
+ }
4559
+ async getAvailableTasks() {
4560
+ try {
4561
+ const tasks2 = await this.client.tasks.getAvailable(this.config.workspaceId, this.resolvedSprintId || undefined);
4562
+ return tasks2.filter((task) => !this.processedTasks.has(task.id));
4563
+ } catch (error) {
4564
+ this.emit("error", error);
4565
+ return [];
4566
+ }
4567
+ }
4568
+ async spawnAgent() {
4569
+ const agentId = `agent-0-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
4570
+ this.agentState = {
4571
+ id: agentId,
4572
+ status: "IDLE",
4573
+ currentTaskId: null,
4574
+ tasksCompleted: 0,
4815
4575
  tasksFailed: 0,
4816
4576
  lastHeartbeat: new Date
4817
4577
  };
4818
- this.agents.set(agentId, agentState);
4819
4578
  console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
4820
4579
  `);
4821
4580
  const workerPath = this.resolveWorkerPath();
4822
4581
  if (!workerPath) {
4823
4582
  throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
4824
4583
  }
4825
- const workerArgs = this.buildWorkerArgs(agentId, resolvedSprintId, baseBranch);
4584
+ const workerArgs = this.buildWorkerArgs(agentId);
4826
4585
  const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
4827
4586
  stdio: ["pipe", "pipe", "pipe"],
4828
4587
  detached: true,
@@ -4833,60 +4592,64 @@ class AgentPool extends import_events4.EventEmitter {
4833
4592
  LOCUS_WORKSPACE: this.config.workspaceId
4834
4593
  })
4835
4594
  });
4836
- agentState.process = agentProcess;
4837
- this.attachProcessHandlers(agentId, agentState, agentProcess);
4595
+ this.agentState.process = agentProcess;
4596
+ this.attachProcessHandlers(agentId, this.agentState, agentProcess);
4838
4597
  this.emit("agent:spawned", { agentId });
4839
4598
  }
4840
- async waitForAll(isRunning) {
4841
- while (this.agents.size > 0 && isRunning()) {
4599
+ async waitForAgent() {
4600
+ while (this.agentState && this.isRunning) {
4842
4601
  await sleep(2000);
4843
4602
  }
4844
4603
  }
4845
4604
  startHeartbeatMonitor() {
4846
4605
  this.heartbeatInterval = setInterval(() => {
4606
+ if (!this.agentState)
4607
+ return;
4847
4608
  const now = Date.now();
4848
- for (const [agentId, agent] of this.agents.entries()) {
4849
- if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
4850
- console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
4851
- if (agent.process && !agent.process.killed) {
4852
- killProcessTree(agent.process);
4853
- }
4854
- this.emit("agent:stale", { agentId });
4609
+ if (this.agentState.status === "WORKING" && now - this.agentState.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
4610
+ console.log(c.error(`Agent ${this.agentState.id} is stale (no heartbeat for 10 minutes). Killing.`));
4611
+ if (this.agentState.process && !this.agentState.process.killed) {
4612
+ killProcessTree(this.agentState.process);
4855
4613
  }
4614
+ this.emit("agent:stale", { agentId: this.agentState.id });
4856
4615
  }
4857
4616
  }, 60000);
4858
4617
  }
4618
+ async stop() {
4619
+ this.isRunning = false;
4620
+ await this.cleanup();
4621
+ this.emit("stopped", { timestamp: new Date });
4622
+ }
4859
4623
  stopAgent(agentId) {
4860
- const agent = this.agents.get(agentId);
4861
- if (!agent)
4624
+ if (!this.agentState || this.agentState.id !== agentId)
4862
4625
  return false;
4863
- if (agent.process && !agent.process.killed) {
4864
- killProcessTree(agent.process);
4626
+ if (this.agentState.process && !this.agentState.process.killed) {
4627
+ killProcessTree(this.agentState.process);
4865
4628
  }
4866
4629
  return true;
4867
4630
  }
4868
- shutdown() {
4631
+ async cleanup() {
4869
4632
  if (this.heartbeatInterval) {
4870
4633
  clearInterval(this.heartbeatInterval);
4871
4634
  this.heartbeatInterval = null;
4872
4635
  }
4873
- for (const [agentId, agent] of this.agents.entries()) {
4874
- if (agent.process && !agent.process.killed) {
4875
- console.log(`Killing agent: ${agentId}`);
4876
- killProcessTree(agent.process);
4877
- }
4636
+ if (this.agentState?.process && !this.agentState.process.killed) {
4637
+ console.log(`Killing agent: ${this.agentState.id}`);
4638
+ killProcessTree(this.agentState.process);
4878
4639
  }
4879
- this.agents.clear();
4880
4640
  }
4881
4641
  getStats() {
4882
4642
  return {
4883
- activeAgents: this.agents.size,
4884
- agentCount: this.effectiveAgentCount,
4885
- totalTasksCompleted: this.getAll().reduce((sum, a) => sum + a.tasksCompleted, 0),
4886
- totalTasksFailed: this.getAll().reduce((sum, a) => sum + a.tasksFailed, 0)
4643
+ activeAgents: this.agentState ? 1 : 0,
4644
+ totalTasksCompleted: this.agentState?.tasksCompleted ?? 0,
4645
+ totalTasksFailed: this.agentState?.tasksFailed ?? 0,
4646
+ processedTasks: this.processedTasks.size
4887
4647
  };
4888
4648
  }
4889
- buildWorkerArgs(agentId, resolvedSprintId, baseBranch) {
4649
+ getAgentStates() {
4650
+ return this.agentState ? [this.agentState] : [];
4651
+ }
4652
+ buildWorkerArgs(agentId) {
4890
4653
  const args = [
4891
4654
  "--agent-id",
4892
4655
  agentId,
@@ -4905,17 +4668,8 @@ class AgentPool extends import_events4.EventEmitter {
4905
4668
  if (this.config.provider) {
4906
4669
  args.push("--provider", this.config.provider);
4907
4670
  }
4908
- if (resolvedSprintId) {
4909
- args.push("--sprint-id", resolvedSprintId);
4910
- }
4911
- if (this.config.useWorktrees ?? true) {
4912
- args.push("--use-worktrees");
4913
- }
4914
- if (this.config.autoPush) {
4915
- args.push("--auto-push");
4916
- }
4917
- if (baseBranch) {
4918
- args.push("--base-branch", baseBranch);
4671
+ if (this.resolvedSprintId) {
4672
+ args.push("--sprint-id", this.resolvedSprintId);
4919
4673
  }
4920
4674
  return args;
4921
4675
  }
@@ -4938,29 +4692,28 @@ class AgentPool extends import_events4.EventEmitter {
4938
4692
  proc.on("exit", (code) => {
4939
4693
  console.log(`
4940
4694
  ${agentId} finished (exit code: ${code})`);
4941
- const agent = this.agents.get(agentId);
4942
- if (agent) {
4943
- agent.status = code === 0 ? "COMPLETED" : "FAILED";
4695
+ if (this.agentState) {
4696
+ this.agentState.status = code === 0 ? "COMPLETED" : "FAILED";
4944
4697
  this.emit("agent:completed", {
4945
4698
  agentId,
4946
- status: agent.status,
4947
- tasksCompleted: agent.tasksCompleted,
4948
- tasksFailed: agent.tasksFailed
4699
+ status: this.agentState.status,
4700
+ tasksCompleted: this.agentState.tasksCompleted,
4701
+ tasksFailed: this.agentState.tasksFailed
4949
4702
  });
4950
- this.agents.delete(agentId);
4703
+ this.agentState = null;
4951
4704
  }
4952
4705
  });
4953
4706
  }
4954
4707
  resolveWorkerPath() {
4955
- const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/agent-pool.ts");
4956
- const currentModuleDir = import_node_path11.dirname(currentModulePath);
4708
+ const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
4709
+ const currentModuleDir = import_node_path10.dirname(currentModulePath);
4957
4710
  const potentialPaths = [
4958
- import_node_path11.join(currentModuleDir, "..", "agent", "worker.js"),
4959
- import_node_path11.join(currentModuleDir, "agent", "worker.js"),
4960
- import_node_path11.join(currentModuleDir, "worker.js"),
4961
- import_node_path11.join(currentModuleDir, "..", "agent", "worker.ts")
4711
+ import_node_path10.join(currentModuleDir, "..", "agent", "worker.js"),
4712
+ import_node_path10.join(currentModuleDir, "agent", "worker.js"),
4713
+ import_node_path10.join(currentModuleDir, "worker.js"),
4714
+ import_node_path10.join(currentModuleDir, "..", "agent", "worker.ts")
4962
4715
  ];
4963
- return potentialPaths.find((p) => import_node_fs9.existsSync(p));
4716
+ return potentialPaths.find((p) => import_node_fs8.existsSync(p));
4964
4717
  }
4965
4718
  }
4966
4719
  function killProcessTree(proc) {
@@ -4975,460 +4728,16 @@ function killProcessTree(proc) {
4975
4728
  }
4976
4729
  }
4977
4730
  function sleep(ms) {
4978
- return new Promise((resolve3) => setTimeout(resolve3, ms));
4979
- }
4980
-
4981
- // src/orchestrator/execution.ts
4982
- init_git_utils();
4983
- init_colors();
4984
- var import_shared5 = require("@locusai/shared");
4985
- var SPAWN_DELAY_MS = 5000;
4986
-
4987
- class ExecutionStrategy {
4988
- config;
4989
- pool;
4990
- tierMerge;
4991
- resolvedSprintId;
4992
- isRunning;
4993
- constructor(config, pool, tierMerge, resolvedSprintId, isRunning) {
4994
- this.config = config;
4995
- this.pool = pool;
4996
- this.tierMerge = tierMerge;
4997
- this.resolvedSprintId = resolvedSprintId;
4998
- this.isRunning = isRunning;
4999
- }
5000
- async execute(tasks2) {
5001
- const hasTiers = tasks2.some((t) => t.tier !== null && t.tier !== undefined);
5002
- const useWorktrees = this.config.useWorktrees ?? true;
5003
- if (hasTiers && useWorktrees) {
5004
- await this.tierBasedExecution(tasks2);
5005
- } else {
5006
- await this.legacyExecution(tasks2);
5007
- }
5008
- }
5009
- async tierBasedExecution(allTasks) {
5010
- const tierMap = groupByTier(allTasks);
5011
- const tiers = Array.from(tierMap.keys()).sort((a, b) => a - b);
5012
- const defaultBranch = getDefaultBranch(this.config.projectPath);
5013
- console.log(c.primary(`\uD83D\uDCCA Tier-based execution: ${tiers.length} tier(s) detected [${tiers.join(", ")}]`));
5014
- let currentBaseBranch = defaultBranch;
5015
- for (const tier of tiers) {
5016
- if (!this.isRunning())
5017
- break;
5018
- const tierTasks = tierMap.get(tier) ?? [];
5019
- const dispatchable = tierTasks.filter(isDispatchable);
5020
- if (dispatchable.length === 0) {
5021
- console.log(c.dim(`ℹ Tier ${tier}: all ${tierTasks.length} task(s) already completed, skipping`));
5022
- const tierBranch = this.tierMerge.tierBranchName(tier);
5023
- if (this.tierMerge.remoteBranchExists(tierBranch)) {
5024
- currentBaseBranch = tierBranch;
5025
- }
5026
- continue;
5027
- }
5028
- console.log(`
5029
- ${c.primary(`\uD83C\uDFD7️ Tier ${tier}:`)} ${dispatchable.length} task(s) | base: ${c.bold(currentBaseBranch)}`);
5030
- await this.spawnAgentsForTasks(dispatchable.length, currentBaseBranch);
5031
- await this.pool.waitForAll(this.isRunning);
5032
- console.log(c.success(`✓ Tier ${tier} complete`));
5033
- if (this.config.autoPush && tiers.indexOf(tier) < tiers.length - 1) {
5034
- const mergeBranch = this.tierMerge.createMergeBranch(tier, currentBaseBranch);
5035
- if (mergeBranch) {
5036
- currentBaseBranch = mergeBranch;
5037
- console.log(c.success(`\uD83D\uDCCC Created merge branch: ${mergeBranch} (base for tier ${tier + 1})`));
5038
- }
5039
- }
5040
- }
5041
- }
5042
- async legacyExecution(tasks2) {
5043
- const defaultBranch = getDefaultBranch(this.config.projectPath);
5044
- await this.spawnAgentsForTasks(tasks2.length, defaultBranch);
5045
- await this.pool.waitForAll(this.isRunning);
5046
- }
5047
- async spawnAgentsForTasks(taskCount, baseBranch) {
5048
- const agentsToSpawn = Math.min(this.pool.effectiveAgentCount, taskCount);
5049
- const spawnPromises = [];
5050
- for (let i = 0;i < agentsToSpawn; i++) {
5051
- if (i > 0) {
5052
- await sleep2(SPAWN_DELAY_MS);
5053
- }
5054
- spawnPromises.push(this.pool.spawn(i, this.resolvedSprintId, baseBranch));
5055
- }
5056
- await Promise.all(spawnPromises);
5057
- }
5058
- }
5059
- function groupByTier(tasks2) {
5060
- const tierMap = new Map;
5061
- for (const task of tasks2) {
5062
- const tier = task.tier ?? 0;
5063
- const existing = tierMap.get(tier);
5064
- if (existing) {
5065
- existing.push(task);
5066
- } else {
5067
- tierMap.set(tier, [task]);
5068
- }
5069
- }
5070
- return tierMap;
5071
- }
5072
- function isDispatchable(task) {
5073
- return task.status === import_shared5.TaskStatus.BACKLOG || task.status === import_shared5.TaskStatus.IN_PROGRESS && !task.assignedTo;
5074
- }
5075
- function sleep2(ms) {
5076
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5077
- }
5078
-
5079
- // src/orchestrator/tier-merge.ts
5080
- init_colors();
5081
- var import_node_child_process8 = require("node:child_process");
5082
- var TIER_BRANCH_PREFIX = "locus/tier";
5083
-
5084
- class TierMergeService {
5085
- projectPath;
5086
- sprintId;
5087
- tierTaskIds = new Map;
5088
- constructor(projectPath, sprintId) {
5089
- this.projectPath = projectPath;
5090
- this.sprintId = sprintId;
5091
- }
5092
- registerTierTasks(tasks2) {
5093
- for (const task of tasks2) {
5094
- const tier = task.tier ?? 0;
5095
- const existing = this.tierTaskIds.get(tier);
5096
- if (existing) {
5097
- existing.push(task.id);
5098
- } else {
5099
- this.tierTaskIds.set(tier, [task.id]);
5100
- }
5101
- }
5102
- }
5103
- tierBranchName(tier) {
5104
- const suffix = this.sprintId ? `-${this.sprintId.slice(0, 8)}` : "";
5105
- return `${TIER_BRANCH_PREFIX}-${tier}${suffix}`;
5106
- }
5107
- remoteBranchExists(branch) {
5108
- try {
5109
- import_node_child_process8.execFileSync("git", ["ls-remote", "--exit-code", "--heads", "origin", branch], {
5110
- cwd: this.projectPath,
5111
- encoding: "utf-8",
5112
- stdio: ["pipe", "pipe", "pipe"]
5113
- });
5114
- return true;
5115
- } catch {
5116
- return false;
5117
- }
5118
- }
5119
- createMergeBranch(tier, baseBranch) {
5120
- const mergeBranchName = this.tierBranchName(tier);
5121
- try {
5122
- this.gitExec(["fetch", "origin"]);
5123
- const tierTaskBranches = this.findTierTaskBranches(tier);
5124
- if (tierTaskBranches.length === 0) {
5125
- console.log(c.dim(` Tier ${tier}: no pushed task branches found, skipping merge branch creation`));
5126
- return null;
5127
- }
5128
- console.log(c.dim(` Merging ${tierTaskBranches.length} branch(es) into ${mergeBranchName}: ${tierTaskBranches.join(", ")}`));
5129
- try {
5130
- this.gitExec(["branch", "-D", mergeBranchName]);
5131
- } catch {}
5132
- this.gitExec(["checkout", "-b", mergeBranchName, `origin/${baseBranch}`]);
5133
- for (const branch of tierTaskBranches) {
5134
- try {
5135
- this.gitExec(["merge", `origin/${branch}`, "--no-edit"]);
5136
- } catch (err) {
5137
- const msg = err instanceof Error ? err.message : String(err);
5138
- console.log(c.error(` Merge conflict merging ${branch} into ${mergeBranchName}: ${msg}`));
5139
- try {
5140
- this.gitExec(["merge", "--abort"]);
5141
- } catch {}
5142
- }
5143
- }
5144
- this.gitExec(["push", "-u", "origin", mergeBranchName, "--force"]);
5145
- this.gitExec(["checkout", baseBranch]);
5146
- return mergeBranchName;
5147
- } catch (err) {
5148
- const msg = err instanceof Error ? err.message : String(err);
5149
- console.log(c.error(`Failed to create tier merge branch: ${msg}`));
5150
- try {
5151
- this.gitExec(["checkout", baseBranch]);
5152
- } catch {}
5153
- return null;
5154
- }
5155
- }
5156
- findTierTaskBranches(tier) {
5157
- const tierTaskIds = this.tierTaskIds.get(tier);
5158
- if (!tierTaskIds || tierTaskIds.length === 0)
5159
- return [];
5160
- try {
5161
- const output = import_node_child_process8.execSync('git branch -r --list "origin/agent/*" --format="%(refname:short)"', { cwd: this.projectPath, encoding: "utf-8" }).trim();
5162
- if (!output)
5163
- return [];
5164
- const remoteBranches = output.split(`
5165
- `).map((b) => b.replace("origin/", ""));
5166
- return remoteBranches.filter((branch) => {
5167
- const branchSuffix = branch.replace(/^agent\//, "");
5168
- if (!branchSuffix)
5169
- return false;
5170
- return tierTaskIds.some((id) => branchSuffix.startsWith(`${id}-`) || branchSuffix === id || branchSuffix.startsWith(id));
5171
- });
5172
- } catch (err) {
5173
- console.log(c.dim(` Could not list remote branches for tier ${tier}: ${err instanceof Error ? err.message : String(err)}`));
5174
- return [];
5175
- }
5176
- }
5177
- gitExec(args) {
5178
- return import_node_child_process8.execFileSync("git", args, {
5179
- cwd: this.projectPath,
5180
- encoding: "utf-8",
5181
- stdio: ["pipe", "pipe", "pipe"]
5182
- });
5183
- }
5184
- }
5185
-
5186
- // src/orchestrator/index.ts
5187
- class AgentOrchestrator extends import_events5.EventEmitter {
5188
- client;
5189
- config;
5190
- pool;
5191
- isRunning = false;
5192
- processedTasks = new Set;
5193
- resolvedSprintId = null;
5194
- worktreeManager = null;
5195
- constructor(config) {
5196
- super();
5197
- this.config = config;
5198
- this.client = new LocusClient({
5199
- baseUrl: config.apiBase,
5200
- token: config.apiKey
5201
- });
5202
- this.pool = new AgentPool(config);
5203
- this.pool.on("agent:spawned", (data) => this.emit("agent:spawned", data));
5204
- this.pool.on("agent:completed", (data) => this.emit("agent:completed", data));
5205
- this.pool.on("agent:stale", (data) => this.emit("agent:stale", data));
5206
- }
5207
- get useWorktrees() {
5208
- return this.config.useWorktrees ?? true;
5209
- }
5210
- get worktreeCleanupPolicy() {
5211
- return this.config.worktreeCleanupPolicy ?? "retain-on-failure";
5212
- }
5213
- async resolveSprintId() {
5214
- if (this.config.sprintId) {
5215
- return this.config.sprintId;
5216
- }
5217
- try {
5218
- const sprint = await this.client.sprints.getActive(this.config.workspaceId);
5219
- if (sprint?.id) {
5220
- console.log(c.info(`\uD83D\uDCCB Using active sprint: ${sprint.name}`));
5221
- return sprint.id;
5222
- }
5223
- } catch {}
5224
- console.log(c.dim("ℹ No sprint specified, working with all workspace tasks"));
5225
- return "";
5226
- }
5227
- async start() {
5228
- if (this.isRunning) {
5229
- throw new Error("Orchestrator is already running");
5230
- }
5231
- this.isRunning = true;
5232
- this.processedTasks.clear();
5233
- try {
5234
- await this.orchestrationLoop();
5235
- } catch (error) {
5236
- this.emit("error", error);
5237
- throw error;
5238
- } finally {
5239
- await this.cleanup();
5240
- }
5241
- }
5242
- async orchestrationLoop() {
5243
- this.resolvedSprintId = await this.resolveSprintId();
5244
- this.emit("started", {
5245
- timestamp: new Date,
5246
- config: this.config,
5247
- sprintId: this.resolvedSprintId
5248
- });
5249
- this.printBanner();
5250
- const tasks2 = await this.getAvailableTasks();
5251
- if (tasks2.length === 0) {
5252
- console.log(c.dim("ℹ No available tasks found in the backlog."));
5253
- return;
5254
- }
5255
- if (!this.preflightChecks(tasks2))
5256
- return;
5257
- if (this.useWorktrees) {
5258
- this.worktreeManager = new WorktreeManager(this.config.projectPath, {
5259
- cleanupPolicy: this.worktreeCleanupPolicy
5260
- });
5261
- }
5262
- this.pool.startHeartbeatMonitor();
5263
- const tierMerge = new TierMergeService(this.config.projectPath, this.resolvedSprintId);
5264
- tierMerge.registerTierTasks(tasks2);
5265
- const execution = new ExecutionStrategy(this.config, this.pool, tierMerge, this.resolvedSprintId, () => this.isRunning);
5266
- await execution.execute(tasks2);
5267
- console.log(`
5268
- ${c.success("✅ Orchestrator finished")}`);
5269
- }
5270
- printBanner() {
5271
- console.log(`
5272
- ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
5273
- console.log(c.dim("----------------------------------------------"));
5274
- console.log(`${c.bold("Workspace:")} ${this.config.workspaceId}`);
5275
- if (this.resolvedSprintId) {
5276
- console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
5277
- }
5278
- console.log(`${c.bold("Agents:")} ${this.pool.effectiveAgentCount}`);
5279
- console.log(`${c.bold("Worktrees:")} ${this.useWorktrees ? "enabled" : "disabled"}`);
5280
- if (this.useWorktrees) {
5281
- console.log(`${c.bold("Cleanup policy:")} ${this.worktreeCleanupPolicy}`);
5282
- console.log(`${c.bold("Auto-push:")} ${this.config.autoPush ? "enabled" : "disabled"}`);
5283
- }
5284
- console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
5285
- console.log(c.dim(`----------------------------------------------
5286
- `));
5287
- }
5288
- preflightChecks(_tasks) {
5289
- if (this.useWorktrees && !isGitAvailable()) {
5290
- console.log(c.error("git is not installed. Worktree isolation requires git. Install from https://git-scm.com/"));
5291
- return false;
5292
- }
5293
- if (this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
5294
- console.log(c.warning("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/"));
5295
- }
5296
- return true;
5297
- }
5298
- async getAvailableTasks() {
5299
- try {
5300
- const tasks2 = await this.client.tasks.getAvailable(this.config.workspaceId, this.resolvedSprintId || undefined);
5301
- return tasks2.filter((task) => !this.processedTasks.has(task.id));
5302
- } catch (error) {
5303
- this.emit("error", error);
5304
- return [];
5305
- }
5306
- }
5307
- async assignTaskToAgent(agentId) {
5308
- const agent = this.pool.get(agentId);
5309
- if (!agent)
5310
- return null;
5311
- try {
5312
- const tasks2 = await this.getAvailableTasks();
5313
- const priorityOrder = [
5314
- import_shared6.TaskPriority.CRITICAL,
5315
- import_shared6.TaskPriority.HIGH,
5316
- import_shared6.TaskPriority.MEDIUM,
5317
- import_shared6.TaskPriority.LOW
5318
- ];
5319
- let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
5320
- if (!task && tasks2.length > 0) {
5321
- task = tasks2[0];
5322
- }
5323
- if (!task)
5324
- return null;
5325
- agent.currentTaskId = task.id;
5326
- agent.status = "WORKING";
5327
- this.emit("task:assigned", {
5328
- agentId,
5329
- taskId: task.id,
5330
- title: task.title
5331
- });
5332
- return task;
5333
- } catch (error) {
5334
- this.emit("error", error);
5335
- return null;
5336
- }
5337
- }
5338
- async completeTask(taskId, agentId, summary) {
5339
- try {
5340
- await this.client.tasks.update(taskId, this.config.workspaceId, {
5341
- status: import_shared6.TaskStatus.IN_REVIEW
5342
- });
5343
- if (summary) {
5344
- await this.client.tasks.addComment(taskId, this.config.workspaceId, {
5345
- author: agentId,
5346
- text: `✅ Task completed
5347
-
5348
- ${summary}`
5349
- });
5350
- }
5351
- this.processedTasks.add(taskId);
5352
- const agent = this.pool.get(agentId);
5353
- if (agent) {
5354
- agent.tasksCompleted += 1;
5355
- agent.currentTaskId = null;
5356
- agent.status = "IDLE";
5357
- }
5358
- this.emit("task:completed", { agentId, taskId });
5359
- } catch (error) {
5360
- this.emit("error", error);
5361
- }
5362
- }
5363
- async failTask(taskId, agentId, error) {
5364
- try {
5365
- await this.client.tasks.update(taskId, this.config.workspaceId, {
5366
- status: import_shared6.TaskStatus.BACKLOG,
5367
- assignedTo: null
5368
- });
5369
- await this.client.tasks.addComment(taskId, this.config.workspaceId, {
5370
- author: agentId,
5371
- text: `❌ Agent failed: ${error}`
5372
- });
5373
- const agent = this.pool.get(agentId);
5374
- if (agent) {
5375
- agent.tasksFailed += 1;
5376
- agent.currentTaskId = null;
5377
- agent.status = "IDLE";
5378
- }
5379
- this.emit("task:failed", { agentId, taskId, error });
5380
- } catch (error2) {
5381
- this.emit("error", error2);
5382
- }
5383
- }
5384
- async stop() {
5385
- this.isRunning = false;
5386
- await this.cleanup();
5387
- this.emit("stopped", { timestamp: new Date });
5388
- }
5389
- stopAgent(agentId) {
5390
- return this.pool.stopAgent(agentId);
5391
- }
5392
- async cleanup() {
5393
- this.pool.shutdown();
5394
- if (this.worktreeManager) {
5395
- try {
5396
- if (this.worktreeCleanupPolicy === "auto") {
5397
- const removed = this.worktreeManager.removeAll();
5398
- if (removed > 0) {
5399
- console.log(c.dim(`Cleaned up ${removed} worktree(s)`));
5400
- }
5401
- } else if (this.worktreeCleanupPolicy === "retain-on-failure") {
5402
- this.worktreeManager.prune();
5403
- console.log(c.dim("Retaining worktrees for failure analysis (cleanup policy: retain-on-failure)"));
5404
- } else {
5405
- console.log(c.dim("Skipping worktree cleanup (cleanup policy: manual)"));
5406
- }
5407
- } catch {
5408
- console.log(c.dim("Could not clean up some worktrees"));
5409
- }
5410
- }
5411
- }
5412
- getStats() {
5413
- const poolStats = this.pool.getStats();
5414
- return {
5415
- ...poolStats,
5416
- useWorktrees: this.useWorktrees,
5417
- processedTasks: this.processedTasks.size
5418
- };
5419
- }
5420
- getAgentStates() {
5421
- return this.pool.getAll();
5422
- }
4731
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
5423
4732
  }
5424
4733
  // src/planning/plan-manager.ts
5425
4734
  init_config();
5426
4735
  init_knowledge_base();
5427
- var import_node_fs10 = require("node:fs");
5428
- var import_node_path12 = require("node:path");
4736
+ var import_node_fs9 = require("node:fs");
4737
+ var import_node_path11 = require("node:path");
5429
4738
 
5430
4739
  // src/planning/sprint-plan.ts
5431
- var import_shared7 = require("@locusai/shared");
4740
+ var import_shared5 = require("@locusai/shared");
5432
4741
  function sprintPlanToMarkdown(plan) {
5433
4742
  const lines = [];
5434
4743
  lines.push(`# Sprint Plan: ${plan.name}`);
@@ -5450,32 +4759,25 @@ function sprintPlanToMarkdown(plan) {
5450
4759
  }
5451
4760
  lines.push(`## Tasks (${plan.tasks.length})`);
5452
4761
  lines.push("");
5453
- const maxTier = Math.max(0, ...plan.tasks.map((t) => t.tier ?? 0));
5454
- for (let tier = 0;tier <= maxTier; tier++) {
5455
- const tierTasks = plan.tasks.filter((t) => (t.tier ?? 0) === tier);
5456
- if (tierTasks.length === 0)
5457
- continue;
5458
- lines.push(`### Tier ${tier}${tier === 0 ? " (Foundation)" : ""}`);
5459
- lines.push(tier === 0 ? "_These tasks run first and must complete before higher tiers start._" : `_These tasks run in parallel after Tier ${tier - 1} completes._`);
4762
+ lines.push("_Tasks are executed sequentially in the order listed below._");
4763
+ lines.push("");
4764
+ for (const task of plan.tasks) {
4765
+ lines.push(`### ${task.index}. ${task.title}`);
4766
+ lines.push(`- **Role:** ${task.assigneeRole}`);
4767
+ lines.push(`- **Priority:** ${task.priority}`);
4768
+ lines.push(`- **Complexity:** ${"█".repeat(task.complexity)}${"░".repeat(5 - task.complexity)} (${task.complexity}/5)`);
4769
+ if (task.labels.length > 0) {
4770
+ lines.push(`- **Labels:** ${task.labels.join(", ")}`);
4771
+ }
5460
4772
  lines.push("");
5461
- for (const task of tierTasks) {
5462
- lines.push(`#### ${task.index}. ${task.title}`);
5463
- lines.push(`- **Role:** ${task.assigneeRole}`);
5464
- lines.push(`- **Priority:** ${task.priority}`);
5465
- lines.push(`- **Complexity:** ${"█".repeat(task.complexity)}${"░".repeat(5 - task.complexity)} (${task.complexity}/5)`);
5466
- if (task.labels.length > 0) {
5467
- lines.push(`- **Labels:** ${task.labels.join(", ")}`);
4773
+ lines.push(task.description);
4774
+ lines.push("");
4775
+ if (task.acceptanceCriteria.length > 0) {
4776
+ lines.push(`**Acceptance Criteria:**`);
4777
+ for (const ac of task.acceptanceCriteria) {
4778
+ lines.push(`- [ ] ${ac}`);
5468
4779
  }
5469
4780
  lines.push("");
5470
- lines.push(task.description);
5471
- lines.push("");
5472
- if (task.acceptanceCriteria.length > 0) {
5473
- lines.push(`**Acceptance Criteria:**`);
5474
- for (const ac of task.acceptanceCriteria) {
5475
- lines.push(`- [ ] ${ac}`);
5476
- }
5477
- lines.push("");
5478
- }
5479
4781
  }
5480
4782
  }
5481
4783
  if (plan.risks.length > 0) {
@@ -5496,13 +4798,12 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
5496
4798
  return plan.tasks.map((task) => ({
5497
4799
  title: task.title,
5498
4800
  description: task.description,
5499
- status: import_shared7.TaskStatus.BACKLOG,
4801
+ status: import_shared5.TaskStatus.BACKLOG,
5500
4802
  assigneeRole: task.assigneeRole,
5501
4803
  priority: task.priority,
5502
4804
  labels: task.labels,
5503
4805
  sprintId,
5504
4806
  order: task.index * 10,
5505
- tier: task.tier,
5506
4807
  acceptanceChecklist: task.acceptanceCriteria.map((text, i) => ({
5507
4808
  id: `ac-${i + 1}`,
5508
4809
  text,
@@ -5526,8 +4827,7 @@ function parseSprintPlanFromAI(raw, directive) {
5526
4827
  priority: t.priority || "MEDIUM",
5527
4828
  complexity: t.complexity || 3,
5528
4829
  acceptanceCriteria: t.acceptanceCriteria || [],
5529
- labels: t.labels || [],
5530
- tier: typeof t.tier === "number" ? t.tier : 0
4830
+ labels: t.labels || []
5531
4831
  }));
5532
4832
  return {
5533
4833
  id,
@@ -5558,19 +4858,19 @@ class PlanManager {
5558
4858
  save(plan) {
5559
4859
  this.ensurePlansDir();
5560
4860
  const slug = this.slugify(plan.name);
5561
- const jsonPath = import_node_path12.join(this.plansDir, `${slug}.json`);
5562
- const mdPath = import_node_path12.join(this.plansDir, `sprint-${slug}.md`);
5563
- import_node_fs10.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
5564
- import_node_fs10.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
4861
+ const jsonPath = import_node_path11.join(this.plansDir, `${slug}.json`);
4862
+ const mdPath = import_node_path11.join(this.plansDir, `sprint-${slug}.md`);
4863
+ import_node_fs9.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
4864
+ import_node_fs9.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
5565
4865
  return plan.id;
5566
4866
  }
5567
4867
  load(idOrSlug) {
5568
4868
  this.ensurePlansDir();
5569
- const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4869
+ const files = import_node_fs9.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
5570
4870
  for (const file of files) {
5571
- const filePath = import_node_path12.join(this.plansDir, file);
4871
+ const filePath = import_node_path11.join(this.plansDir, file);
5572
4872
  try {
5573
- const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
4873
+ const plan = JSON.parse(import_node_fs9.readFileSync(filePath, "utf-8"));
5574
4874
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
5575
4875
  return plan;
5576
4876
  }
@@ -5580,11 +4880,11 @@ class PlanManager {
5580
4880
  }
5581
4881
  list(status) {
5582
4882
  this.ensurePlansDir();
5583
- const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
4883
+ const files = import_node_fs9.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
5584
4884
  const plans = [];
5585
4885
  for (const file of files) {
5586
4886
  try {
5587
- const plan = JSON.parse(import_node_fs10.readFileSync(import_node_path12.join(this.plansDir, file), "utf-8"));
4887
+ const plan = JSON.parse(import_node_fs9.readFileSync(import_node_path11.join(this.plansDir, file), "utf-8"));
5588
4888
  if (!status || plan.status === status) {
5589
4889
  plans.push(plan);
5590
4890
  }
@@ -5650,18 +4950,18 @@ class PlanManager {
5650
4950
  }
5651
4951
  delete(idOrSlug) {
5652
4952
  this.ensurePlansDir();
5653
- const files = import_node_fs10.readdirSync(this.plansDir);
4953
+ const files = import_node_fs9.readdirSync(this.plansDir);
5654
4954
  for (const file of files) {
5655
- const filePath = import_node_path12.join(this.plansDir, file);
4955
+ const filePath = import_node_path11.join(this.plansDir, file);
5656
4956
  if (!file.endsWith(".json"))
5657
4957
  continue;
5658
4958
  try {
5659
- const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
4959
+ const plan = JSON.parse(import_node_fs9.readFileSync(filePath, "utf-8"));
5660
4960
  if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
5661
- import_node_fs10.unlinkSync(filePath);
5662
- const mdPath = import_node_path12.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
5663
- if (import_node_fs10.existsSync(mdPath)) {
5664
- import_node_fs10.unlinkSync(mdPath);
4961
+ import_node_fs9.unlinkSync(filePath);
4962
+ const mdPath = import_node_path11.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
4963
+ if (import_node_fs9.existsSync(mdPath)) {
4964
+ import_node_fs9.unlinkSync(mdPath);
5665
4965
  }
5666
4966
  return;
5667
4967
  }
@@ -5675,8 +4975,8 @@ class PlanManager {
5675
4975
  return sprintPlanToMarkdown(plan);
5676
4976
  }
5677
4977
  ensurePlansDir() {
5678
- if (!import_node_fs10.existsSync(this.plansDir)) {
5679
- import_node_fs10.mkdirSync(this.plansDir, { recursive: true });
4978
+ if (!import_node_fs9.existsSync(this.plansDir)) {
4979
+ import_node_fs9.mkdirSync(this.plansDir, { recursive: true });
5680
4980
  }
5681
4981
  }
5682
4982
  slugify(name) {
@@ -5686,7 +4986,7 @@ class PlanManager {
5686
4986
  // src/planning/planning-meeting.ts
5687
4987
  init_config();
5688
4988
  init_knowledge_base();
5689
- var import_node_fs11 = require("node:fs");
4989
+ var import_node_fs10 = require("node:fs");
5690
4990
 
5691
4991
  // src/planning/agents/architect.ts
5692
4992
  function buildArchitectPrompt(input) {
@@ -5723,21 +5023,14 @@ Review and refine the Tech Lead's breakdown:
5723
5023
  5. **Missing Tasks** — Add any tasks the Tech Lead missed (database migrations, configuration, testing, etc.).
5724
5024
  6. **Description Quality** — Review and improve each task description to be a clear, actionable implementation guide. Each description must tell the executing agent exactly what to do, where to do it (specific files/modules), how to do it (patterns, utilities, data flow), and what is NOT in scope. Vague descriptions like "Add authentication" must be rewritten with specific file paths, implementation approach, and boundaries.
5725
5025
 
5726
- ## CRITICAL: Task Isolation & Overlap Detection
5026
+ ## CRITICAL: Task Ordering & Dependencies
5727
5027
 
5728
- Tasks are executed by INDEPENDENT agents on SEPARATE git branches that get merged together. Each agent has NO knowledge of what other agents are doing. You MUST enforce these rules:
5028
+ Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in array order. Each completed task's changes are available to subsequent tasks. You MUST enforce these rules:
5729
5029
 
5730
- 1. **Detect overlapping file modifications.** For each task, mentally list the files it will touch. If two tasks modify the same file (especially config files like app.module.ts, configuration.ts, package.json, or shared modules), they WILL cause merge conflicts. You must either:
5731
- - **Merge them** into a single task, OR
5732
- - **Move the shared file changes** into one foundational task that runs and merges first
5733
-
5734
- 2. **Detect duplicated work.** If two tasks both introduce the same environment variable, config field, dependency, helper function, or module registration — that is duplicated work. Consolidate it into ONE task.
5735
-
5736
- 3. **Do NOT split tasks that share code changes.** Even if a task is large, do NOT split it if the subtasks would both need to modify the same files. A single larger self-contained task is far better than two smaller conflicting tasks. Only split tasks when the parts are truly independent (touch completely different files and modules).
5737
-
5738
- 4. **Validate self-containment.** Each task must include ALL changes it needs to function: config, schema, module registration, implementation, and tests. A task must NOT assume another concurrent task will provide something it needs.
5739
-
5740
- 5. **Flag high-conflict zones.** In your risk assessment, specifically call out any remaining cases where tasks might touch the same files, and explain why it's unavoidable and how merge conflicts can be minimized.
5030
+ 1. **Order tasks by dependency.** Foundation tasks (schemas, config, shared code) must come first. Tasks that build on earlier work must appear later in the list.
5031
+ 2. **Each task must be self-contained for its scope.** A task can depend on earlier tasks (they run sequentially), but must include all changes needed for its own goal.
5032
+ 3. **Split tasks at logical boundaries.** Since tasks run sequentially on the same branch, splitting is safe — later tasks see earlier changes. Split when it improves clarity and reviewability.
5033
+ 4. **Flag risks.** In your risk assessment, call out tasks that are complex or have unknowns.
5741
5034
 
5742
5035
  ## Output Format
5743
5036
 
@@ -5771,20 +5064,20 @@ Your entire response must be a single JSON object — no text before it, no text
5771
5064
  function buildCrossTaskReviewerPrompt(input) {
5772
5065
  let prompt = `# Role: Cross-Task Reviewer (Architect + Engineer + Planner)
5773
5066
 
5774
- You are a combined Architect, Senior Engineer, and Sprint Planner performing a FINAL review of a sprint plan. Your sole focus is ensuring that tasks are fully isolated and will not conflict when executed by independent agents on separate git branches.
5067
+ You are a combined Architect, Senior Engineer, and Sprint Planner performing a FINAL review of a sprint plan. Your focus is ensuring that tasks are correctly ordered, well-scoped, and will execute successfully in sequence.
5775
5068
 
5776
5069
  ## Context
5777
5070
 
5778
- In this system, tasks are organized into execution **tiers** (0, 1, 2, ...):
5779
- - All tasks within the same tier run IN PARALLEL on separate git branches (worktrees)
5780
- - Agents within a tier have NO knowledge of what other agents in that tier are doing
5781
- - After ALL tasks in tier N complete and merge, tier N+1 starts
5782
- - Tier N+1 branches are created FROM the merged tier N result, so they can see tier N's work
5071
+ In this system, tasks are executed SEQUENTIALLY by a single agent on ONE branch:
5072
+ - Tasks run one at a time, in the order they appear in the array
5073
+ - Each task's changes are committed before the next task starts
5074
+ - Later tasks can see and build on earlier tasks' work
5075
+ - The final result is a single branch with all changes, which becomes a pull request
5783
5076
 
5784
5077
  This means:
5785
- - Two tasks in the SAME tier that modify the same file WILL cause merge conflicts
5786
- - Two tasks in DIFFERENT tiers are safe the later tier sees the earlier tier's merged output
5787
- - Tier assignment is critical: foundational work must be in tier 0, dependent work in higher tiers
5078
+ - Task ordering is critical a task must NOT depend on a later task's output
5079
+ - Foundation work (config, schemas, shared code) must come first
5080
+ - Each task should be a focused, logical unit of work
5788
5081
 
5789
5082
  ## CEO Directive
5790
5083
  > ${input.directive}
@@ -5806,41 +5099,21 @@ ${input.sprintOrganizerOutput}
5806
5099
 
5807
5100
  ## Your Review Checklist
5808
5101
 
5809
- Go through EACH pair of tasks and check for:
5810
-
5811
- ### 1. File Overlap Analysis (WITHIN the same tier)
5812
- For each task, list the files it will likely modify. Then check:
5813
- - Do any two tasks **in the same tier** modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
5814
- - If yes: MERGE those tasks, move them to different tiers, or move shared changes to a foundational task in a lower tier
5815
- - Note: tasks in different tiers are safe because higher tiers branch from merged lower-tier results
5102
+ Go through EACH task and check for:
5816
5103
 
5817
- ### 2. Duplicated Work Detection (WITHIN the same tier)
5818
- Check if multiple tasks **in the same tier**:
5819
- - Add the same environment variable or config field
5820
- - Install or configure the same dependency
5821
- - Register the same module or provider
5822
- - Create the same helper function, guard, interceptor, or middleware
5823
- - Add the same import to a shared file
5824
- If yes: consolidate into ONE task or move the shared work to a lower tier
5104
+ ### 1. Ordering & Dependency Analysis
5105
+ For each task, verify:
5106
+ - Does it depend on any task that appears LATER in the list? If so, reorder.
5107
+ - Are foundational tasks (config, schemas, shared code) at the beginning?
5108
+ - Is the overall execution order logical?
5825
5109
 
5826
- ### 3. Self-Containment Validation
5110
+ ### 2. Scope & Completeness
5827
5111
  For each task, verify:
5828
- - Does it include ALL config/env changes it needs?
5829
- - Does it include ALL module registrations it needs?
5830
- - Does it include ALL dependency installations it needs?
5831
- - Can it be completed without ANY output from tasks in the SAME tier? (It CAN depend on lower-tier tasks that are already merged)
5832
-
5833
- ### 4. Tier Assignment Validation
5834
- Verify tier assignments are correct:
5835
- - Foundational tasks (schemas, config, shared code) MUST be in tier 0
5836
- - Tasks that depend on another task's output must be in a HIGHER tier
5837
- - Tasks in the same tier must be truly independent of each other
5838
- - No circular dependencies between tiers
5839
-
5840
- ### 5. Merge Conflict Risk Zones
5841
- Identify the highest-risk files (files that multiple same-tier tasks might touch) and ensure only ONE task per tier modifies each.
5842
-
5843
- ### 6. Description Quality Validation
5112
+ - Is the task well-scoped? Not too large, not too trivial?
5113
+ - Does it include ALL changes needed for its goal (given earlier tasks are done)?
5114
+ - Are there any missing tasks that should be added?
5115
+
5116
+ ### 3. Description Quality Validation
5844
5117
  For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
5845
5118
  - **What to do** — the specific goal and expected behavior/outcome
5846
5119
  - **Where to do it** — specific files, modules, or directories to modify or create
@@ -5849,6 +5122,10 @@ For each task, verify the description is a clear, actionable implementation guid
5849
5122
 
5850
5123
  If any description is vague (e.g., "Add authentication", "Update the API", "Fix the frontend"), rewrite it with concrete implementation details. The executing agent receives ONLY the task title, description, and acceptance criteria as its instructions.
5851
5124
 
5125
+ ### 4. Risk Assessment
5126
+ - Are there tasks that might fail or have unknowns?
5127
+ - Is the sprint scope realistic for sequential execution?
5128
+
5852
5129
  ## Output Format
5853
5130
 
5854
5131
  Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
@@ -5857,10 +5134,10 @@ Your entire response must be a single JSON object — no text before it, no text
5857
5134
  "hasIssues": true | false,
5858
5135
  "issues": [
5859
5136
  {
5860
- "type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk" | "wrong_tier" | "vague_description",
5137
+ "type": "wrong_order" | "missing_task" | "scope_issue" | "vague_description",
5861
5138
  "description": "string describing the specific issue",
5862
5139
  "affectedTasks": ["Task Title 1", "Task Title 2"],
5863
- "resolution": "string describing how to fix it (merge, move to different tier, consolidate)"
5140
+ "resolution": "string describing how to fix it"
5864
5141
  }
5865
5142
  ],
5866
5143
  "revisedPlan": {
@@ -5875,8 +5152,7 @@ Your entire response must be a single JSON object — no text before it, no text
5875
5152
  "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5876
5153
  "labels": ["string"],
5877
5154
  "acceptanceCriteria": ["string"],
5878
- "complexity": 3,
5879
- "tier": 0
5155
+ "complexity": 3
5880
5156
  }
5881
5157
  ],
5882
5158
  "risks": [
@@ -5890,15 +5166,11 @@ Your entire response must be a single JSON object — no text before it, no text
5890
5166
  }
5891
5167
 
5892
5168
  IMPORTANT:
5893
- - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (tasks merged, duplicated work consolidated, tier assignments fixed, etc.)
5169
+ - If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (reordered, descriptions rewritten, missing tasks added, etc.)
5894
5170
  - If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
5895
5171
  - The revisedPlan is ALWAYS required — it becomes the final plan
5896
- - When merging tasks, combine their acceptance criteria and update descriptions to cover all consolidated work
5897
5172
  - Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
5898
- - Prefer fewer, larger, self-contained tasks over many small conflicting ones
5899
- - Every task MUST have a "tier" field (integer >= 0)
5900
- - tier 0 = foundational (runs first), tier 1 = depends on tier 0, tier 2 = depends on tier 1, etc.
5901
- - Tasks within the same tier run in parallel — they MUST NOT conflict with each other`;
5173
+ - Tasks execute sequentially the array order IS the execution order`;
5902
5174
  return prompt;
5903
5175
  }
5904
5176
 
@@ -5929,30 +5201,27 @@ Produce the final sprint plan:
5929
5201
 
5930
5202
  1. **Sprint Name** — A concise, memorable name for this sprint (e.g., "User Authentication", "Payment Integration")
5931
5203
  2. **Sprint Goal** — One paragraph describing what this sprint delivers
5932
- 3. **Task Ordering** — Final ordering so that foundational work comes first. The position in the array IS the execution order — task at index 0 runs first, index 1 runs second, etc.
5933
- 4. **Tier Assignment** — Assign each task an execution tier (integer, starting at 0). Tasks within the same tier run IN PARALLEL on separate git branches. Tasks in tier N+1 only start AFTER all tier N tasks are complete and merged. Tier 0 = foundational tasks (config, schemas, shared code). Higher tiers build on lower tier outputs.
5934
- 5. **Duration Estimate** — How many days this sprint will take with 2-3 agents working in parallel
5935
- 6. **Final Task List** — Each task with all fields filled in, ordered by execution priority
5936
- 7. **Description Quality Check** — Ensure every task description is a clear, actionable implementation guide. Each description must specify: what to do, where to do it (specific files/modules/directories), how to do it (implementation approach, patterns to follow, existing utilities to use), and what is NOT in scope. If any description is vague or generic, rewrite it with specifics. Remember: an independent agent will receive ONLY the task title, description, and acceptance criteria — the description is its primary instruction.
5204
+ 3. **Task Ordering** — Final ordering so that foundational work comes first. The position in the array IS the execution order — task at index 0 runs first, index 1 runs second, etc. Tasks are executed SEQUENTIALLY by a single agent on one branch.
5205
+ 4. **Duration Estimate** — How many days this sprint will take with a single agent working sequentially
5206
+ 5. **Final Task List** — Each task with all fields filled in, ordered by execution priority
5207
+ 6. **Description Quality Check** — Ensure every task description is a clear, actionable implementation guide. Each description must specify: what to do, where to do it (specific files/modules/directories), how to do it (implementation approach, patterns to follow, existing utilities to use), and what is NOT in scope. If any description is vague or generic, rewrite it with specifics. Remember: an independent agent will receive ONLY the task title, description, and acceptance criteria — the description is its primary instruction.
5937
5208
 
5938
5209
  Guidelines:
5939
5210
  - The order of tasks in the array determines execution order. Tasks are dispatched sequentially from first to last.
5940
- - Foundation tasks (schemas, config, shared code) must appear before tasks that build on them AND must be in tier 0
5941
- - Tasks within the same tier MUST be truly independent no shared file modifications, no dependencies between them
5942
- - Tasks that depend on outputs from other tasks must be in a higher tier than those dependencies
5943
- - Group related independent tasks in the same tier for maximum parallelism
5211
+ - Foundation tasks (schemas, config, shared code) must appear before tasks that build on them
5212
+ - Since tasks execute sequentially on one branch, later tasks can depend on earlier tasks' outputs
5944
5213
  - Ensure acceptance criteria are specific and testable
5945
5214
  - Keep the sprint focused — if it's too large (>12 tasks), consider reducing scope
5946
5215
  - Ensure every task description reads as a standalone implementation brief — not a summary
5947
5216
 
5948
- ## CRITICAL: Task Isolation Validation
5217
+ ## CRITICAL: Task Ordering Validation
5949
5218
 
5950
- Before finalizing, validate that EVERY task is fully self-contained and conflict-free:
5219
+ Before finalizing, validate that tasks are in the correct execution order:
5951
5220
 
5952
- 1. **No two tasks should modify the same file.** If they do, merge them or restructure so shared changes live in one foundational task.
5953
- 2. **No duplicated work.** Each env var, config field, dependency, module import, or helper function must be introduced by exactly ONE task.
5954
- 3. **Each task is independently executable within its tier.** An agent working on a task must be able to complete it without knowing what other tasks in the same tier are doing. Tasks CAN depend on lower-tier tasks since those are merged before the current tier starts.
5955
- 4. **Prefer fewer, larger self-contained tasks over many small overlapping ones.** Do not split a task if the parts would conflict with each other.
5221
+ 1. **No forward dependencies.** A task must NOT depend on a task that appears later in the list.
5222
+ 2. **Foundation first.** Config, schemas, and shared code must come before implementation tasks.
5223
+ 3. **Each task is independently executable given prior tasks.** An agent working on task N must be able to complete it assuming tasks 1 through N-1 are already done.
5224
+ 4. **Prefer focused, well-scoped tasks.** Each task should do one logical unit of work.
5956
5225
 
5957
5226
  ## Output Format
5958
5227
 
@@ -5970,8 +5239,7 @@ Your entire response must be a single JSON object — no text before it, no text
5970
5239
  "priority": "CRITICAL | HIGH | MEDIUM | LOW",
5971
5240
  "labels": ["string"],
5972
5241
  "acceptanceCriteria": ["string"],
5973
- "complexity": 3,
5974
- "tier": 0
5242
+ "complexity": 3
5975
5243
  }
5976
5244
  ],
5977
5245
  "risks": [
@@ -5983,12 +5251,7 @@ Your entire response must be a single JSON object — no text before it, no text
5983
5251
  ]
5984
5252
  }
5985
5253
 
5986
- IMPORTANT about tiers:
5987
- - tier 0 = foundational tasks (run first, merged before anything else)
5988
- - tier 1 = tasks that depend on tier 0 outputs (run in parallel after tier 0 merges)
5989
- - tier 2 = tasks that depend on tier 1 outputs (run in parallel after tier 1 merges)
5990
- - Tasks within the same tier run in parallel on separate branches — they MUST NOT conflict
5991
- - Every task MUST have a "tier" field (integer >= 0)`;
5254
+ IMPORTANT: Tasks are executed sequentially by a single agent. The array order IS the execution order — ensure foundational work comes first and dependent tasks come after their prerequisites.`;
5992
5255
  return prompt;
5993
5256
  }
5994
5257
 
@@ -6046,15 +5309,14 @@ Each description MUST include:
6046
5309
  Bad example: "Add authentication to the API."
6047
5310
  Good example: "Implement JWT-based authentication middleware in src/middleware/auth.ts. Create a verifyToken middleware that extracts the Bearer token from the Authorization header, validates it using the existing JWT_SECRET from env config, and attaches the decoded user payload to req.user. Apply this middleware to all routes in src/routes/protected/. Add a POST /auth/login endpoint in src/routes/auth.ts that accepts {email, password}, validates credentials against the users table, and returns a signed JWT. This task does NOT include user registration or password reset — those are handled separately."
6048
5311
 
6049
- ## CRITICAL: Task Isolation Rules
5312
+ ## CRITICAL: Task Ordering Rules
6050
5313
 
6051
- Tasks will be executed by INDEPENDENT agents on SEPARATE git branches that get merged together. Each agent has NO knowledge of what other agents are doing. Therefore:
5314
+ Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in the order they appear in the array. Therefore:
6052
5315
 
6053
- 1. **No shared work across tasks.** If two tasks both need the same config variable, helper function, database migration, or module setup, that shared work MUST be consolidated into ONE task (or placed into a dedicated foundational task that runs first and is merged before others start).
6054
- 2. **Each task must be fully self-contained.** A task must include ALL the code changes it needs to work — from config to implementation to tests. It should NOT assume that another task in the same sprint will create something it depends on.
6055
- 3. **Do NOT split tasks if they share code changes.** If implementing feature A and feature B both require modifying the same file or adding the same dependency/config, they should be ONE task — even if that makes the task larger. A bigger self-contained task is better than two smaller conflicting tasks.
6056
- 4. **Think about file-level conflicts.** Two tasks modifying the same file (e.g., app.module.ts, configuration.ts, package.json) will cause git merge conflicts. Minimize this by bundling related changes together.
6057
- 5. **Environment variables, configs, and shared modules are high-conflict zones.** If a task introduces a new env var, config schema field, or module import, NO other task should touch that same file unless absolutely necessary.
5316
+ 1. **Foundation first.** Place foundational tasks (schemas, config, shared code) at the beginning of the list. Later tasks can build on earlier ones since they run in sequence on the same branch.
5317
+ 2. **Each task must be self-contained.** A task must include ALL the code changes it needs to work — from config to implementation to tests. A task CAN depend on earlier tasks in the list since they will have already been completed.
5318
+ 3. **Logical ordering matters.** Tasks are dispatched in the order they appear. Ensure dependent tasks come after their prerequisites.
5319
+ 4. **Keep tasks focused.** Each task should do one logical unit of work. Since there are no parallel execution conflicts, tasks can be more granular but avoid tasks that are too small or trivial.
6058
5320
 
6059
5321
  ## Output Format
6060
5322
 
@@ -6146,11 +5408,11 @@ class PlanningMeeting {
6146
5408
  }
6147
5409
  getCodebaseIndex() {
6148
5410
  const indexPath = getLocusPath(this.projectPath, "indexFile");
6149
- if (!import_node_fs11.existsSync(indexPath)) {
5411
+ if (!import_node_fs10.existsSync(indexPath)) {
6150
5412
  return "";
6151
5413
  }
6152
5414
  try {
6153
- const raw = import_node_fs11.readFileSync(indexPath, "utf-8");
5415
+ const raw = import_node_fs10.readFileSync(indexPath, "utf-8");
6154
5416
  const index = JSON.parse(raw);
6155
5417
  const parts = [];
6156
5418
  if (index.responsibilities) {
@@ -6173,7 +5435,3 @@ class PlanningMeeting {
6173
5435
  // src/index-node.ts
6174
5436
  init_knowledge_base();
6175
5437
  init_colors();
6176
-
6177
- // src/worktree/index.ts
6178
- init_worktree_config();
6179
- init_worktree_manager();