@autoclawd/autoclawd 1.1.5 → 1.1.7
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 +91 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,8 @@ var LinearConfigSchema = z.object({
|
|
|
31
31
|
statuses: z.array(z.string()).default(["Todo"]),
|
|
32
32
|
assignToMe: z.boolean().default(true),
|
|
33
33
|
doneStatus: z.string().default("Done"),
|
|
34
|
-
inProgressStatus: z.string().default("In Progress")
|
|
34
|
+
inProgressStatus: z.string().default("In Progress"),
|
|
35
|
+
inReviewStatus: z.string().default("In Review")
|
|
35
36
|
});
|
|
36
37
|
var GitHubConfigSchema = z.object({
|
|
37
38
|
token: z.string(),
|
|
@@ -328,6 +329,20 @@ async function fetchTicket(client, identifier) {
|
|
|
328
329
|
url: issue.url
|
|
329
330
|
};
|
|
330
331
|
}
|
|
332
|
+
async function fetchTicketStatus(client, identifier) {
|
|
333
|
+
const match = identifier.match(/^([A-Za-z]+)-(\d+)$/);
|
|
334
|
+
if (!match) return void 0;
|
|
335
|
+
const [, teamKey, numStr] = match;
|
|
336
|
+
const team = await client.team(teamKey.toUpperCase());
|
|
337
|
+
const result = await team.issues({
|
|
338
|
+
filter: { number: { eq: parseInt(numStr) } },
|
|
339
|
+
first: 1
|
|
340
|
+
});
|
|
341
|
+
const issue = result.nodes[0];
|
|
342
|
+
if (!issue) return void 0;
|
|
343
|
+
const state = await issue.state;
|
|
344
|
+
return state?.name;
|
|
345
|
+
}
|
|
331
346
|
async function claimTicket(client, config, ticketId) {
|
|
332
347
|
log.info(`Claiming ticket, moving to "${config.linear.inProgressStatus}"`);
|
|
333
348
|
await retry(async () => {
|
|
@@ -342,17 +357,23 @@ async function claimTicket(client, config, ticketId) {
|
|
|
342
357
|
await client.updateIssue(ticketId, { stateId: inProgress.id });
|
|
343
358
|
}, { label: "Linear claim", retryIf: isTransientError });
|
|
344
359
|
}
|
|
345
|
-
async function
|
|
360
|
+
async function reviewTicket(client, config, ticketId, comment) {
|
|
346
361
|
await retry(async () => {
|
|
347
362
|
await client.createComment({ issueId: ticketId, body: comment });
|
|
348
363
|
const team = await client.team(config.linear.teamId);
|
|
349
364
|
const states = await team.states();
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
await client.updateIssue(ticketId, { stateId:
|
|
365
|
+
const target = config.linear.inReviewStatus.toLowerCase();
|
|
366
|
+
const review = states.nodes.find((s) => s.name.toLowerCase() === target);
|
|
367
|
+
if (review) {
|
|
368
|
+
await client.updateIssue(ticketId, { stateId: review.id });
|
|
369
|
+
} else {
|
|
370
|
+
const doneTarget = config.linear.doneStatus.toLowerCase();
|
|
371
|
+
const done = states.nodes.find((s) => s.name.toLowerCase() === doneTarget);
|
|
372
|
+
if (done) {
|
|
373
|
+
await client.updateIssue(ticketId, { stateId: done.id });
|
|
374
|
+
}
|
|
354
375
|
}
|
|
355
|
-
}, { label: "Linear
|
|
376
|
+
}, { label: "Linear review", retryIf: isTransientError });
|
|
356
377
|
}
|
|
357
378
|
async function addBranchLabel(client, ticketId, branchName) {
|
|
358
379
|
try {
|
|
@@ -482,20 +503,29 @@ async function ensureRepoExists(octokit, opts) {
|
|
|
482
503
|
if (status !== 404) throw err;
|
|
483
504
|
}
|
|
484
505
|
log.info(`Repo ${opts.owner}/${opts.repo} does not exist, creating...`);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
506
|
+
try {
|
|
507
|
+
const { data: authUser } = await octokit.rest.users.getAuthenticated();
|
|
508
|
+
if (authUser.login === opts.owner) {
|
|
509
|
+
await octokit.rest.repos.createForAuthenticatedUser({
|
|
510
|
+
name: opts.repo,
|
|
511
|
+
description: opts.description,
|
|
512
|
+
auto_init: false
|
|
513
|
+
});
|
|
514
|
+
} else {
|
|
515
|
+
await octokit.rest.repos.createInOrg({
|
|
516
|
+
org: opts.owner,
|
|
517
|
+
name: opts.repo,
|
|
518
|
+
description: opts.description,
|
|
519
|
+
auto_init: false
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
} catch (err) {
|
|
523
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
524
|
+
if (msg.includes("name already exists")) {
|
|
525
|
+
log.info(`Repo ${opts.owner}/${opts.repo} was created by another ticket, continuing`);
|
|
526
|
+
return { created: false };
|
|
527
|
+
}
|
|
528
|
+
throw err;
|
|
499
529
|
}
|
|
500
530
|
return { created: true };
|
|
501
531
|
}
|
|
@@ -1581,8 +1611,28 @@ async function executeTicket(opts) {
|
|
|
1581
1611
|
let workDir;
|
|
1582
1612
|
let container;
|
|
1583
1613
|
try {
|
|
1584
|
-
|
|
1585
|
-
|
|
1614
|
+
if (ticket.baseBranch) {
|
|
1615
|
+
const parentMatch = ticket.baseBranch.match(/^autoclawd\/([A-Z]+-\d+)/i);
|
|
1616
|
+
if (parentMatch) {
|
|
1617
|
+
const parentId = parentMatch[1].toUpperCase();
|
|
1618
|
+
const parentStatus = await fetchTicketStatus(linearClient, parentId);
|
|
1619
|
+
if (parentStatus) {
|
|
1620
|
+
const statusLower = parentStatus.toLowerCase();
|
|
1621
|
+
const reviewTarget = config.linear.inReviewStatus.toLowerCase();
|
|
1622
|
+
const doneTarget = config.linear.doneStatus.toLowerCase();
|
|
1623
|
+
if (statusLower !== reviewTarget && statusLower !== doneTarget) {
|
|
1624
|
+
log.ticket(ticket.identifier, `Parent ${parentId} is "${parentStatus}" \u2014 waiting for "${config.linear.inReviewStatus}" or "${config.linear.doneStatus}"`);
|
|
1625
|
+
return {
|
|
1626
|
+
ticketId: ticket.identifier,
|
|
1627
|
+
success: false,
|
|
1628
|
+
error: `REQUEUE: parent ${parentId} is "${parentStatus}", not yet in review or done`,
|
|
1629
|
+
iterations: 0
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
log.ticket(ticket.identifier, `Parent ${parentId} is "${parentStatus}" \u2014 proceeding`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1586
1636
|
const { owner: repoOwner, repo: repoName } = parseRepoUrl(ticket.repoUrl);
|
|
1587
1637
|
const { created: repoCreated } = await ensureRepoExists(octokit, {
|
|
1588
1638
|
owner: repoOwner,
|
|
@@ -1600,7 +1650,21 @@ async function executeTicket(opts) {
|
|
|
1600
1650
|
}
|
|
1601
1651
|
const detectedBase = repoCreated ? "main" : detectDefaultBranch2(ticket.repoUrl, config.github.token);
|
|
1602
1652
|
log.ticket(ticket.identifier, `Default branch: ${detectedBase}`);
|
|
1603
|
-
|
|
1653
|
+
try {
|
|
1654
|
+
workDir = await cloneToTemp(ticket.repoUrl, detectedBase, config.github.token);
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1657
|
+
if (msg.includes("not found in upstream") || msg.includes("Could not find remote branch") || msg.includes("Remote branch")) {
|
|
1658
|
+
log.ticket(ticket.identifier, `Branch "${detectedBase}" not ready \u2014 will retry later`);
|
|
1659
|
+
return {
|
|
1660
|
+
ticketId: ticket.identifier,
|
|
1661
|
+
success: false,
|
|
1662
|
+
error: `REQUEUE: branch "${detectedBase}" does not exist yet`,
|
|
1663
|
+
iterations: 0
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
throw err;
|
|
1667
|
+
}
|
|
1604
1668
|
log.ticket(ticket.identifier, "Cloned repo");
|
|
1605
1669
|
const repoLocal = loadRepoLocalConfig(workDir);
|
|
1606
1670
|
const actualBase = ticket.baseBranch ?? repoLocal?.base ?? detectedBase;
|
|
@@ -1630,6 +1694,8 @@ async function executeTicket(opts) {
|
|
|
1630
1694
|
}
|
|
1631
1695
|
}
|
|
1632
1696
|
execSync3(`git checkout -B ${branchName} --`, { cwd: workDir, stdio: "pipe" });
|
|
1697
|
+
await claimTicket(linearClient, config, ticket.id);
|
|
1698
|
+
log.ticket(ticket.identifier, `Claimed \u2014 ${ticket.repoUrl}`);
|
|
1633
1699
|
container = await createContainer({
|
|
1634
1700
|
dockerConfig: docker2,
|
|
1635
1701
|
workspacePath: workDir,
|
|
@@ -1769,7 +1835,7 @@ async function executeTicket(opts) {
|
|
|
1769
1835
|
}
|
|
1770
1836
|
}
|
|
1771
1837
|
}
|
|
1772
|
-
await
|
|
1838
|
+
await reviewTicket(linearClient, config, ticket.id, `PR opened: ${pr.url}`);
|
|
1773
1839
|
await addBranchLabel(linearClient, ticket.id, branchName);
|
|
1774
1840
|
log.success(`${ticket.identifier} done \u2014 ${pr.url}`);
|
|
1775
1841
|
const result = {
|