@autoclawd/autoclawd 1.1.22 → 1.1.24
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/index.js +293 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,6 +56,10 @@ var SafetyConfigSchema = z.object({
|
|
|
56
56
|
branchPrefix: z.string().default("autoclawd/"),
|
|
57
57
|
maxFileChanges: z.number().min(1).optional()
|
|
58
58
|
});
|
|
59
|
+
var IntegrationConfigSchema = z.object({
|
|
60
|
+
crossBranchValidate: z.boolean().default(true),
|
|
61
|
+
maxCrossBranchIterations: z.number().min(1).max(10).default(3)
|
|
62
|
+
});
|
|
59
63
|
var ConfigSchema = z.object({
|
|
60
64
|
linear: LinearConfigSchema,
|
|
61
65
|
github: GitHubConfigSchema,
|
|
@@ -63,6 +67,7 @@ var ConfigSchema = z.object({
|
|
|
63
67
|
agent: AgentConfigSchema.default({}),
|
|
64
68
|
webhook: WebhookConfigSchema.default({}),
|
|
65
69
|
safety: SafetyConfigSchema.default({}),
|
|
70
|
+
integration: IntegrationConfigSchema.default({}),
|
|
66
71
|
maxConcurrent: z.number().default(1),
|
|
67
72
|
validate: z.array(z.string()).optional()
|
|
68
73
|
// Global default validation commands
|
|
@@ -72,8 +77,9 @@ var RepoLocalConfigSchema = z.object({
|
|
|
72
77
|
base: z.string().default("main"),
|
|
73
78
|
agent: AgentConfigSchema.partial().optional(),
|
|
74
79
|
docker: DockerConfigSchema.partial().optional(),
|
|
75
|
-
validate: z.array(z.string()).optional()
|
|
80
|
+
validate: z.array(z.string()).optional(),
|
|
76
81
|
// Validation commands to run before PR
|
|
82
|
+
integration: IntegrationConfigSchema.partial().optional()
|
|
77
83
|
});
|
|
78
84
|
function resolveEnvVars(obj) {
|
|
79
85
|
if (typeof obj === "string") {
|
|
@@ -135,8 +141,12 @@ function mergeConfigs(host, local) {
|
|
|
135
141
|
...host.docker,
|
|
136
142
|
...local?.docker
|
|
137
143
|
});
|
|
144
|
+
const integration = IntegrationConfigSchema.parse({
|
|
145
|
+
...host.integration,
|
|
146
|
+
...local?.integration
|
|
147
|
+
});
|
|
138
148
|
const validate = local?.validate ?? host.validate;
|
|
139
|
-
return { agent, docker: docker2, prompt: local?.prompt, validate };
|
|
149
|
+
return { agent, docker: docker2, prompt: local?.prompt, validate, integration };
|
|
140
150
|
}
|
|
141
151
|
function parseRepoFromLabels(labels) {
|
|
142
152
|
for (const label of labels) {
|
|
@@ -411,6 +421,31 @@ async function addBranchLabel(client, ticketId, branchName) {
|
|
|
411
421
|
log.debug(`Could not add branch label: ${err instanceof Error ? err.message : err}`);
|
|
412
422
|
}
|
|
413
423
|
}
|
|
424
|
+
async function findTicketsWithLabel(client, teamId, labelName) {
|
|
425
|
+
const team = await client.team(teamId);
|
|
426
|
+
const teamLabels = await team.labels();
|
|
427
|
+
const label = teamLabels.nodes.find((l) => l.name === labelName);
|
|
428
|
+
if (!label) return [];
|
|
429
|
+
const issues = await label.issues({ first: 50 });
|
|
430
|
+
const tickets = [];
|
|
431
|
+
for (const issue of issues.nodes) {
|
|
432
|
+
const labels = await issue.labels();
|
|
433
|
+
const labelNames = labels.nodes.map((l) => l.name);
|
|
434
|
+
const repoUrl = parseRepoFromLabels(labelNames);
|
|
435
|
+
const baseBranch = parseBaseFromLabels(labelNames);
|
|
436
|
+
tickets.push({
|
|
437
|
+
id: issue.id,
|
|
438
|
+
identifier: issue.identifier,
|
|
439
|
+
title: issue.title,
|
|
440
|
+
description: issue.description ?? "",
|
|
441
|
+
labels: labelNames,
|
|
442
|
+
repoUrl,
|
|
443
|
+
baseBranch,
|
|
444
|
+
url: issue.url
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return tickets;
|
|
448
|
+
}
|
|
414
449
|
async function failTicket(client, ticketId, reason) {
|
|
415
450
|
await client.createComment({
|
|
416
451
|
issueId: ticketId,
|
|
@@ -630,6 +665,21 @@ async function enableAutoMerge(octokit, opts) {
|
|
|
630
665
|
}
|
|
631
666
|
}, { label: "GitHub auto-merge", retryIf: isTransientError });
|
|
632
667
|
}
|
|
668
|
+
async function findSiblingPRs(octokit, opts) {
|
|
669
|
+
const { owner, repo } = parseRepoUrl(opts.repoUrl);
|
|
670
|
+
const { data: prs } = await octokit.rest.pulls.list({
|
|
671
|
+
owner,
|
|
672
|
+
repo,
|
|
673
|
+
base: opts.baseBranch,
|
|
674
|
+
state: "open"
|
|
675
|
+
});
|
|
676
|
+
return prs.filter((pr) => pr.head.ref !== opts.excludeHead && pr.head.ref.startsWith(opts.branchPrefix)).map((pr) => ({
|
|
677
|
+
number: pr.number,
|
|
678
|
+
head: pr.head.ref,
|
|
679
|
+
base: pr.base.ref,
|
|
680
|
+
title: pr.title
|
|
681
|
+
}));
|
|
682
|
+
}
|
|
633
683
|
async function retargetPR(octokit, opts) {
|
|
634
684
|
return retry(async () => {
|
|
635
685
|
const { owner, repo } = parseRepoUrl(opts.repoUrl);
|
|
@@ -1516,7 +1566,7 @@ function countRuns() {
|
|
|
1516
1566
|
}
|
|
1517
1567
|
|
|
1518
1568
|
// src/worker.ts
|
|
1519
|
-
function buildPrompt(ticket, repoPrompt) {
|
|
1569
|
+
function buildPrompt(ticket, repoPrompt, siblings) {
|
|
1520
1570
|
const parts = [];
|
|
1521
1571
|
if (repoPrompt) {
|
|
1522
1572
|
parts.push(repoPrompt);
|
|
@@ -1527,6 +1577,17 @@ function buildPrompt(ticket, repoPrompt) {
|
|
|
1527
1577
|
if (ticket.description) {
|
|
1528
1578
|
parts.push(ticket.description);
|
|
1529
1579
|
}
|
|
1580
|
+
if (siblings && siblings.length > 0) {
|
|
1581
|
+
parts.push("");
|
|
1582
|
+
parts.push("## Related Tickets (being worked on in parallel branches)");
|
|
1583
|
+
parts.push("The following tickets target the same codebase and may be worked on concurrently.");
|
|
1584
|
+
parts.push("Ensure your implementation is compatible \u2014 use consistent type definitions, interfaces, and patterns.");
|
|
1585
|
+
parts.push("");
|
|
1586
|
+
for (const sib of siblings) {
|
|
1587
|
+
const desc = sib.description ? sib.description.length > 300 ? sib.description.slice(0, 300) + "..." : sib.description : "(no description)";
|
|
1588
|
+
parts.push(`- **${sib.identifier}: ${sib.title}** \u2014 ${desc}`);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1530
1591
|
parts.push("");
|
|
1531
1592
|
parts.push("## Instructions");
|
|
1532
1593
|
parts.push("- Start by reading CLAUDE.md if it exists \u2014 it has project-specific guidance");
|
|
@@ -1665,6 +1726,97 @@ function buildValidationFixPrompt(failures) {
|
|
|
1665
1726
|
parts.push("- When done, include [autoclawd:done] in your output");
|
|
1666
1727
|
return parts.join("\n");
|
|
1667
1728
|
}
|
|
1729
|
+
async function runCrossBranchValidation(container, validate, siblingBranches, ticketId) {
|
|
1730
|
+
const results = [];
|
|
1731
|
+
for (const siblingBranch of siblingBranches) {
|
|
1732
|
+
log.ticket(ticketId, `Cross-branch check: trial merge with ${siblingBranch}`);
|
|
1733
|
+
const fetchResult = await exec(container, ["git", "fetch", "origin", siblingBranch], {
|
|
1734
|
+
user: "autoclawd",
|
|
1735
|
+
env: ["HOME=/home/autoclawd"],
|
|
1736
|
+
timeout: 6e4
|
|
1737
|
+
});
|
|
1738
|
+
if (fetchResult.exitCode !== 0) {
|
|
1739
|
+
log.ticket(ticketId, ` Could not fetch ${siblingBranch}, skipping`);
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
const mergeResult = await exec(container, [
|
|
1743
|
+
"git",
|
|
1744
|
+
"merge",
|
|
1745
|
+
"--no-commit",
|
|
1746
|
+
"--no-ff",
|
|
1747
|
+
`origin/${siblingBranch}`
|
|
1748
|
+
], {
|
|
1749
|
+
user: "autoclawd",
|
|
1750
|
+
env: ["HOME=/home/autoclawd"],
|
|
1751
|
+
timeout: 6e4
|
|
1752
|
+
});
|
|
1753
|
+
if (mergeResult.exitCode !== 0) {
|
|
1754
|
+
const mergeOutput = (mergeResult.stdout + mergeResult.stderr).trim();
|
|
1755
|
+
const diffResult = await exec(container, ["git", "diff", "--diff-filter=U"], {
|
|
1756
|
+
user: "autoclawd",
|
|
1757
|
+
env: ["HOME=/home/autoclawd"],
|
|
1758
|
+
timeout: 1e4
|
|
1759
|
+
});
|
|
1760
|
+
const conflictDetail = diffResult.stdout.trim();
|
|
1761
|
+
const output = [
|
|
1762
|
+
mergeOutput,
|
|
1763
|
+
"",
|
|
1764
|
+
"Conflict details (files with conflict markers):",
|
|
1765
|
+
conflictDetail || "(no diff available)"
|
|
1766
|
+
].join("\n").slice(-3e3);
|
|
1767
|
+
results.push({
|
|
1768
|
+
branch: siblingBranch,
|
|
1769
|
+
failures: [{
|
|
1770
|
+
command: `git merge origin/${siblingBranch}`,
|
|
1771
|
+
exitCode: mergeResult.exitCode,
|
|
1772
|
+
output
|
|
1773
|
+
}]
|
|
1774
|
+
});
|
|
1775
|
+
await exec(container, ["git", "merge", "--abort"], {
|
|
1776
|
+
user: "autoclawd",
|
|
1777
|
+
env: ["HOME=/home/autoclawd"],
|
|
1778
|
+
timeout: 1e4
|
|
1779
|
+
});
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
const failures = await runValidation(container, validate, ticketId);
|
|
1783
|
+
results.push({ branch: siblingBranch, failures });
|
|
1784
|
+
await exec(container, ["git", "merge", "--abort"], {
|
|
1785
|
+
user: "autoclawd",
|
|
1786
|
+
env: ["HOME=/home/autoclawd"],
|
|
1787
|
+
timeout: 1e4
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
return results;
|
|
1791
|
+
}
|
|
1792
|
+
function buildCrossBranchFixPrompt(crossResults) {
|
|
1793
|
+
const parts = [];
|
|
1794
|
+
parts.push("## Cross-Branch Compatibility Issues");
|
|
1795
|
+
parts.push("");
|
|
1796
|
+
parts.push("Your branch was trial-merged with sibling branches that target the same codebase.");
|
|
1797
|
+
parts.push("The following issues were found. You must fix YOUR code to be compatible \u2014 do not assume the sibling is wrong.");
|
|
1798
|
+
parts.push("");
|
|
1799
|
+
for (const { branch, failures } of crossResults) {
|
|
1800
|
+
if (failures.length === 0) continue;
|
|
1801
|
+
parts.push(`### Conflicts with \`${branch}\``);
|
|
1802
|
+
for (const f of failures) {
|
|
1803
|
+
parts.push(`#### \`${f.command}\` (exit code ${f.exitCode})`);
|
|
1804
|
+
parts.push("```");
|
|
1805
|
+
parts.push(f.output);
|
|
1806
|
+
parts.push("```");
|
|
1807
|
+
parts.push("");
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
parts.push("## Instructions");
|
|
1811
|
+
parts.push("- Fix YOUR code to be compatible with the sibling branches");
|
|
1812
|
+
parts.push("- Focus on: consistent type definitions, matching interfaces, compatible exports");
|
|
1813
|
+
parts.push("- If there are merge conflicts in a file (e.g. src/index.ts), you MUST include BOTH your changes AND the sibling's changes in that file \u2014 fetch the sibling branch, look at their version of the file, and make yours include both sets of changes");
|
|
1814
|
+
parts.push("- To see what the sibling added: run `git diff main..origin/<sibling-branch> -- <file>`");
|
|
1815
|
+
parts.push("- Run validation commands to verify your fix");
|
|
1816
|
+
parts.push('- Commit your fixes with: git add -A && git commit -m "fix: resolve cross-branch compatibility"');
|
|
1817
|
+
parts.push("- When done, include [autoclawd:done] in your output");
|
|
1818
|
+
return parts.join("\n");
|
|
1819
|
+
}
|
|
1668
1820
|
async function executeTicket(opts) {
|
|
1669
1821
|
const { ticket, config, linearClient, octokit, force } = opts;
|
|
1670
1822
|
const startedAt = /* @__PURE__ */ new Date();
|
|
@@ -1792,7 +1944,7 @@ async function executeTicket(opts) {
|
|
|
1792
1944
|
log.ticket(ticket.identifier, "Cloned repo");
|
|
1793
1945
|
const repoLocal = loadRepoLocalConfig(workDir);
|
|
1794
1946
|
const actualBase = ticket.baseBranch ?? repoLocal?.base ?? detectedBase;
|
|
1795
|
-
const { agent, docker: docker2, prompt, validate } = mergeConfigs(config, repoLocal);
|
|
1947
|
+
const { agent, docker: docker2, prompt, validate, integration } = mergeConfigs(config, repoLocal);
|
|
1796
1948
|
if (repoLocal) {
|
|
1797
1949
|
log.ticket(ticket.identifier, "Loaded .autoclawd.yaml from repo");
|
|
1798
1950
|
}
|
|
@@ -1826,7 +1978,46 @@ async function executeTicket(opts) {
|
|
|
1826
1978
|
name: containerName
|
|
1827
1979
|
});
|
|
1828
1980
|
await setupRepo(container, { branchName });
|
|
1829
|
-
|
|
1981
|
+
let siblings = [];
|
|
1982
|
+
try {
|
|
1983
|
+
const siblingPRs = await findSiblingPRs(octokit, {
|
|
1984
|
+
repoUrl: ticket.repoUrl,
|
|
1985
|
+
baseBranch: actualBase,
|
|
1986
|
+
excludeHead: branchName,
|
|
1987
|
+
branchPrefix: config.safety.branchPrefix
|
|
1988
|
+
});
|
|
1989
|
+
for (const spr of siblingPRs) {
|
|
1990
|
+
const match = spr.head.match(/^(?:autoclawd\/)([A-Za-z]+-\d+)/);
|
|
1991
|
+
if (!match) continue;
|
|
1992
|
+
const sibTicket = await fetchTicket(linearClient, match[1]);
|
|
1993
|
+
if (sibTicket && sibTicket.identifier !== ticket.identifier) {
|
|
1994
|
+
siblings.push({
|
|
1995
|
+
identifier: sibTicket.identifier,
|
|
1996
|
+
title: sibTicket.title,
|
|
1997
|
+
description: sibTicket.description
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
if (ticket.baseBranch && siblings.length === 0) {
|
|
2002
|
+
const baseLabelName = `base:${ticket.baseBranch}`;
|
|
2003
|
+
const siblingTickets = await findTicketsWithLabel(linearClient, config.linear.teamId, baseLabelName);
|
|
2004
|
+
for (const sib of siblingTickets) {
|
|
2005
|
+
if (sib.identifier !== ticket.identifier) {
|
|
2006
|
+
siblings.push({
|
|
2007
|
+
identifier: sib.identifier,
|
|
2008
|
+
title: sib.title,
|
|
2009
|
+
description: sib.description
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
if (siblings.length > 0) {
|
|
2015
|
+
log.ticket(ticket.identifier, `Found ${siblings.length} sibling ticket(s): ${siblings.map((s) => s.identifier).join(", ")}`);
|
|
2016
|
+
}
|
|
2017
|
+
} catch (err) {
|
|
2018
|
+
log.debug(`Could not discover siblings: ${err instanceof Error ? err.message : err}`);
|
|
2019
|
+
}
|
|
2020
|
+
const agentPrompt = buildPrompt(ticket, prompt, siblings.length > 0 ? siblings : void 0);
|
|
1830
2021
|
let agentResult = await runAgentLoop({
|
|
1831
2022
|
container,
|
|
1832
2023
|
agentConfig: agent,
|
|
@@ -1967,6 +2158,78 @@ async function executeTicket(opts) {
|
|
|
1967
2158
|
}
|
|
1968
2159
|
}
|
|
1969
2160
|
}
|
|
2161
|
+
if (integration.crossBranchValidate && validate && validate.length > 0) {
|
|
2162
|
+
try {
|
|
2163
|
+
const siblingPRs = await findSiblingPRs(octokit, {
|
|
2164
|
+
repoUrl: ticket.repoUrl,
|
|
2165
|
+
baseBranch: actualBase,
|
|
2166
|
+
excludeHead: branchName,
|
|
2167
|
+
branchPrefix: config.safety.branchPrefix
|
|
2168
|
+
});
|
|
2169
|
+
const siblingBranches = siblingPRs.map((s) => s.head);
|
|
2170
|
+
if (siblingBranches.length > 0) {
|
|
2171
|
+
log.ticket(ticket.identifier, `Running cross-branch validation against ${siblingBranches.length} sibling(s)`);
|
|
2172
|
+
let crossIterations = 0;
|
|
2173
|
+
const maxCrossIterations = integration.maxCrossBranchIterations;
|
|
2174
|
+
while (crossIterations < maxCrossIterations) {
|
|
2175
|
+
const crossResults = await runCrossBranchValidation(
|
|
2176
|
+
container,
|
|
2177
|
+
validate,
|
|
2178
|
+
siblingBranches,
|
|
2179
|
+
ticket.identifier
|
|
2180
|
+
);
|
|
2181
|
+
const failing = crossResults.filter((r) => r.failures.length > 0);
|
|
2182
|
+
if (failing.length === 0) {
|
|
2183
|
+
log.ticket(ticket.identifier, "Cross-branch validation passed");
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
crossIterations++;
|
|
2187
|
+
log.ticket(ticket.identifier, `Cross-branch issues with ${failing.map((f) => f.branch).join(", ")} (attempt ${crossIterations}/${maxCrossIterations})`);
|
|
2188
|
+
const fixPrompt = buildCrossBranchFixPrompt(failing);
|
|
2189
|
+
const fixResult = await runAgentLoop({
|
|
2190
|
+
container,
|
|
2191
|
+
agentConfig: { ...agent, maxIterations: 1 },
|
|
2192
|
+
prompt: fixPrompt,
|
|
2193
|
+
ticketId: ticket.identifier
|
|
2194
|
+
});
|
|
2195
|
+
agentResult = {
|
|
2196
|
+
iterations: agentResult.iterations + fixResult.iterations,
|
|
2197
|
+
success: fixResult.success,
|
|
2198
|
+
lastOutput: fixResult.lastOutput
|
|
2199
|
+
};
|
|
2200
|
+
await commitAndPush(container, {
|
|
2201
|
+
branchName,
|
|
2202
|
+
ticketId: ticket.identifier,
|
|
2203
|
+
title: ticket.title,
|
|
2204
|
+
repoUrl: ticket.repoUrl,
|
|
2205
|
+
githubToken: config.github.token,
|
|
2206
|
+
branchPrefix: config.safety.branchPrefix,
|
|
2207
|
+
maxFileChanges: config.safety.maxFileChanges
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
const finalCross = await runCrossBranchValidation(
|
|
2211
|
+
container,
|
|
2212
|
+
validate,
|
|
2213
|
+
siblingBranches,
|
|
2214
|
+
ticket.identifier
|
|
2215
|
+
);
|
|
2216
|
+
const stillFailing = finalCross.filter((r) => r.failures.length > 0);
|
|
2217
|
+
if (stillFailing.length > 0) {
|
|
2218
|
+
const allFailures = stillFailing.flatMap(
|
|
2219
|
+
(r) => r.failures.map((f) => ({ ...f, command: `[cross: ${r.branch}] ${f.command}` }))
|
|
2220
|
+
);
|
|
2221
|
+
log.ticket(ticket.identifier, "Cross-branch validation still failing \u2014 adding warnings to PR");
|
|
2222
|
+
await updatePRBody(octokit, {
|
|
2223
|
+
repoUrl: ticket.repoUrl,
|
|
2224
|
+
prNumber: pr.number,
|
|
2225
|
+
body: buildPRBody({ validationWarnings: allFailures, commitCount: gitResult.commitCount })
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
} catch (err) {
|
|
2230
|
+
log.debug(`Cross-branch validation error: ${err instanceof Error ? err.message : err}`);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
1970
2233
|
await reviewTicket(linearClient, config, ticket.id, `PR opened: ${pr.url}`);
|
|
1971
2234
|
await addBranchLabel(linearClient, ticket.id, branchName);
|
|
1972
2235
|
log.success(`${ticket.identifier} done \u2014 ${pr.url}`);
|
|
@@ -2223,6 +2486,7 @@ var WebhookServer = class {
|
|
|
2223
2486
|
} else {
|
|
2224
2487
|
return;
|
|
2225
2488
|
}
|
|
2489
|
+
this.recentlyRequeued.clear();
|
|
2226
2490
|
if (this.activeInMemory.has(data.id) || isTicketActive(data.id)) {
|
|
2227
2491
|
log.debug(`${data.identifier}: already active, ignoring`);
|
|
2228
2492
|
return;
|
|
@@ -2288,6 +2552,7 @@ var WebhookServer = class {
|
|
|
2288
2552
|
log.ticket(ticket.identifier, "Re-queued (waiting for dependency branch)");
|
|
2289
2553
|
finishRun(ticket.id, "failed", result.error);
|
|
2290
2554
|
removeFromProcessed(ticket.id);
|
|
2555
|
+
this.recentlyRequeued.add(ticket.id);
|
|
2291
2556
|
enqueue(ticket);
|
|
2292
2557
|
} else if (result.success) {
|
|
2293
2558
|
finishRun(ticket.id, "success");
|
|
@@ -2303,14 +2568,33 @@ var WebhookServer = class {
|
|
|
2303
2568
|
this.drainQueue();
|
|
2304
2569
|
});
|
|
2305
2570
|
}
|
|
2571
|
+
// Track recently requeued tickets to prevent cycling in the same drain
|
|
2572
|
+
recentlyRequeued = /* @__PURE__ */ new Set();
|
|
2306
2573
|
drainQueue() {
|
|
2307
2574
|
if (this.pausedUntil > Date.now()) return;
|
|
2308
|
-
|
|
2575
|
+
const tickets = [];
|
|
2576
|
+
while (true) {
|
|
2309
2577
|
const next = dequeue();
|
|
2310
2578
|
if (!next) break;
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2579
|
+
tickets.push(next);
|
|
2580
|
+
}
|
|
2581
|
+
tickets.sort((a, b) => {
|
|
2582
|
+
const aStacked = a.baseBranch ? 1 : 0;
|
|
2583
|
+
const bStacked = b.baseBranch ? 1 : 0;
|
|
2584
|
+
return aStacked - bStacked;
|
|
2585
|
+
});
|
|
2586
|
+
for (const ticket of tickets) {
|
|
2587
|
+
if (this.activeInMemory.has(ticket.id)) continue;
|
|
2588
|
+
if (ticket.baseBranch && this.recentlyRequeued.has(ticket.id)) {
|
|
2589
|
+
enqueue(ticket);
|
|
2590
|
+
continue;
|
|
2591
|
+
}
|
|
2592
|
+
if (this.activeInMemory.size >= this.config.maxConcurrent) {
|
|
2593
|
+
enqueue(ticket);
|
|
2594
|
+
continue;
|
|
2595
|
+
}
|
|
2596
|
+
log.ticket(ticket.identifier, `Dequeued (${getQueueLength()} remaining)`);
|
|
2597
|
+
this.dispatch(ticket);
|
|
2314
2598
|
}
|
|
2315
2599
|
}
|
|
2316
2600
|
};
|