@autoclawd/autoclawd 1.1.6 → 1.1.8

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 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 completeTicket(client, config, ticketId, comment) {
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 doneTarget = config.linear.doneStatus.toLowerCase();
351
- const done = states.nodes.find((s) => s.name.toLowerCase() === doneTarget);
352
- if (done) {
353
- await client.updateIssue(ticketId, { stateId: done.id });
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 complete", retryIf: isTransientError });
376
+ }, { label: "Linear review", retryIf: isTransientError });
356
377
  }
357
378
  async function addBranchLabel(client, ticketId, branchName) {
358
379
  try {
@@ -1590,8 +1611,28 @@ async function executeTicket(opts) {
1590
1611
  let workDir;
1591
1612
  let container;
1592
1613
  try {
1593
- await claimTicket(linearClient, config, ticket.id);
1594
- log.ticket(ticket.identifier, `Claimed \u2014 ${ticket.repoUrl}`);
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
+ }
1595
1636
  const { owner: repoOwner, repo: repoName } = parseRepoUrl(ticket.repoUrl);
1596
1637
  const { created: repoCreated } = await ensureRepoExists(octokit, {
1597
1638
  owner: repoOwner,
@@ -1653,6 +1694,8 @@ async function executeTicket(opts) {
1653
1694
  }
1654
1695
  }
1655
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}`);
1656
1699
  container = await createContainer({
1657
1700
  dockerConfig: docker2,
1658
1701
  workspacePath: workDir,
@@ -1792,7 +1835,7 @@ async function executeTicket(opts) {
1792
1835
  }
1793
1836
  }
1794
1837
  }
1795
- await completeTicket(linearClient, config, ticket.id, `PR opened: ${pr.url}`);
1838
+ await reviewTicket(linearClient, config, ticket.id, `PR opened: ${pr.url}`);
1796
1839
  await addBranchLabel(linearClient, ticket.id, branchName);
1797
1840
  log.success(`${ticket.identifier} done \u2014 ${pr.url}`);
1798
1841
  const result = {
@@ -2462,12 +2505,8 @@ function checkDeps() {
2462
2505
  name: "Claude credentials",
2463
2506
  installed: hasClaudeAuth(),
2464
2507
  instructions: 'export ANTHROPIC_API_KEY="sk-ant-..." (from console.anthropic.com)\n Or run: claude (and complete the login flow)'
2465
- },
2466
- {
2467
- name: "cloudflared",
2468
- installed: commandExists("cloudflared"),
2469
- instructions: process.platform === "darwin" ? "brew install cloudflared" : "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"
2470
2508
  }
2509
+ // cloudflared is only needed for `autoclawd serve` — not checked here
2471
2510
  ];
2472
2511
  }
2473
2512
  function hasClaudeAuth() {
@@ -2562,6 +2601,15 @@ program.command("serve").description("Start webhook server with auto-tunnel").op
2562
2601
  if (opts.verbose) setLogLevel("debug");
2563
2602
  enableFileLogging();
2564
2603
  log.info(`autoclawd v${pkg.version}`);
2604
+ if (opts.tunnel !== false) {
2605
+ try {
2606
+ execSync5("which cloudflared", { stdio: "pipe" });
2607
+ } catch {
2608
+ throw new Error(
2609
+ "cloudflared is required for `autoclawd serve`.\n" + (process.platform === "darwin" ? "Install: brew install cloudflared\n" : "Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/\n") + "Or use: autoclawd watch (polling mode, no tunnel needed)"
2610
+ );
2611
+ }
2612
+ }
2565
2613
  await checkDockerAvailable();
2566
2614
  if (!checkClaudeCredentials()) {
2567
2615
  throw new Error(