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