automata-cli 0.1.0-feature-030-do-work.212 → 0.1.0-feature-030-do-work.214

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.
@@ -9,7 +9,7 @@ import {
9
9
  readConfig,
10
10
  readRawConfig,
11
11
  writeConfig
12
- } from "./chunk-UA32XYYL.js";
12
+ } from "./chunk-5ZRROOCC.js";
13
13
 
14
14
  // src/config/ConfigWizard.tsx
15
15
  import { useState } from "react";
@@ -13,7 +13,7 @@ var DEFAULT_DO_WORK = {
13
13
  maxRunsPerTick: 0,
14
14
  lockStaleMinutes: 120
15
15
  };
16
- var DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT = "You are the agent named in the context below, working on a GitHub issue together with the people allowed to instruct you. Answer the messages marked NEW; the earlier messages are context only.\n\nDo not modify, create or delete any file, and do not create a branch or a pull request, UNLESS a message marked NEW explicitly asks you to implement the work. If it does: create a branch off the base branch named below, implement the change following the project's existing conventions, run the tests and the linter, and open a pull request whose body contains `Closes #<issue number>`.\n\nOtherwise do not touch the code at all: reply on the issue with the specification, the plan, or the open questions you need answered. Keep the reply short and concrete, and always post a reply \u2014 silence looks like a crash.";
16
+ var DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT = "You are the agent named in the context below, working on a GitHub issue together with the people allowed to instruct you. Answer the messages marked NEW; the earlier messages are context only.\n\nDo not modify, create or delete any file, and do not create a branch or a pull request, UNLESS a message marked NEW explicitly asks you to implement the work. If it does: create a branch off the base branch named below, implement the change following the project's existing conventions, run the tests and the linter, and open a pull request whose body contains `Closes #<issue number>`.\n\nOtherwise do not touch the code at all: reply on the issue with the specification, the plan, or the open questions you need answered. Keep the reply short and concrete.\n\nEither way, always post a reply on the issue before you finish \u2014 including when you implemented and opened a pull request. Silence is indistinguishable from a crash, and the run will be reported as having produced no answer.";
17
17
  var DEFAULT_DO_WORK_PR_WORK_PROMPT = "You are the agent named in the context below, working on the pull request for a GitHub issue together with the people allowed to instruct you. Work on the branch named below, which is already checked out and up to date.\n\nAddress every message marked NEW and every unresolved review thread listed. Follow the project's existing conventions, run the tests and the linter, then commit and push to that branch. Do not merge the pull request and do not push to the base branch.\n\nReply on the pull request with a short summary of what you changed, or reply in the review thread when your answer belongs to a specific comment. Always post a reply \u2014 silence looks like a crash.";
18
18
  var CONFIG_DIR = ".automata";
19
19
  var CONFIG_FILE = "config.json";
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  readConfig,
11
11
  readRawConfig,
12
12
  writeConfig
13
- } from "./chunk-UA32XYYL.js";
13
+ } from "./chunk-5ZRROOCC.js";
14
14
 
15
15
  // src/index.ts
16
16
  import { Command as Command7 } from "commander";
@@ -184,7 +184,7 @@ var configCommand = new Command("config").description("Configure automata settin
184
184
  const [{ render }, React, { ConfigWizard }] = await Promise.all([
185
185
  import("ink"),
186
186
  import("react"),
187
- import("./ConfigWizard-OYNUMRTO.js")
187
+ import("./ConfigWizard-5MOWNA7O.js")
188
188
  ]);
189
189
  const { waitUntilExit } = render(React.createElement(ConfigWizard));
190
190
  await waitUntilExit();
@@ -2428,6 +2428,7 @@ function getIssueSurface(issueNumber) {
2428
2428
  var LINK_MAP_QUERY = `
2429
2429
  query($owner:String!,$repo:String!,$cursor:String){
2430
2430
  repository(owner:$owner,name:$repo){
2431
+ defaultBranchRef{ name }
2431
2432
  pullRequests(states:OPEN, first:100, after:$cursor, orderBy:{field:UPDATED_AT, direction:DESC}){
2432
2433
  pageInfo{ hasNextPage endCursor }
2433
2434
  nodes{
@@ -2471,17 +2472,19 @@ function indexPullRequest(map, node, nameWithOwner) {
2471
2472
  function getOpenPrLinkMap() {
2472
2473
  const { owner, repo } = getRepoSlug();
2473
2474
  const map = /* @__PURE__ */ new Map();
2475
+ let defaultBranch = null;
2474
2476
  let cursor = null;
2475
2477
  for (let page = 0; page < MAX_LINK_MAP_PAGES; page++) {
2476
2478
  const args = ["api", "graphql", "-f", `query=${LINK_MAP_QUERY}`, "-f", `owner=${owner}`, "-f", `repo=${repo}`];
2477
2479
  if (cursor !== null) args.push("-f", `cursor=${cursor}`);
2478
2480
  const response = ghJson(args, "query open pull requests");
2481
+ defaultBranch = response.data.repository.defaultBranchRef?.name ?? defaultBranch;
2479
2482
  const connection = response.data.repository.pullRequests;
2480
2483
  for (const node of connection.nodes) {
2481
2484
  indexPullRequest(map, node, `${owner}/${repo}`);
2482
2485
  }
2483
2486
  if (!connection.pageInfo?.hasNextPage || connection.pageInfo.endCursor === null) {
2484
- return map;
2487
+ return { byIssue: map, defaultBranch };
2485
2488
  }
2486
2489
  cursor = connection.pageInfo.endCursor;
2487
2490
  }
@@ -2499,7 +2502,7 @@ query($owner:String!,$repo:String!,$prNumber:Int!,$cursor:String){
2499
2502
  isResolved isOutdated path line
2500
2503
  comments(last:100){
2501
2504
  pageInfo{ hasPreviousPage }
2502
- nodes{ author{login} body createdAt }
2505
+ nodes{ author{login} body createdAt url }
2503
2506
  }
2504
2507
  }
2505
2508
  }
@@ -2545,6 +2548,7 @@ function getReviewThreads(prNumber) {
2545
2548
  path: node.path,
2546
2549
  line: node.line ?? null,
2547
2550
  isResolved: node.isResolved,
2551
+ url: node.comments.nodes.at(-1)?.url ?? null,
2548
2552
  comments: node.comments.nodes.map((comment) => ({
2549
2553
  kind: "thread-comment",
2550
2554
  author: login(comment.author),
@@ -2667,12 +2671,16 @@ function isAssignedToAgent(assignees, agentUser) {
2667
2671
  const agent = agentUser.toLowerCase();
2668
2672
  return assignees.some((name) => name.toLowerCase() === agent);
2669
2673
  }
2670
- function findActionableThreads(threads, p) {
2674
+ function findActionableThreads(threads, p, prLastAgentAt) {
2671
2675
  const actionable = [];
2672
2676
  for (const thread of threads) {
2673
2677
  if (thread.isResolved) continue;
2674
2678
  const comments = thread.comments.filter((comment) => classifyForThread(comment.author, p) !== "other");
2675
2679
  if (lastAuthorClass(comments, p) !== "authorized") continue;
2680
+ const newestAuthorized = comments.at(-1)?.createdAt;
2681
+ if (newestAuthorized !== void 0 && prLastAgentAt !== null && prLastAgentAt > newestAuthorized) {
2682
+ continue;
2683
+ }
2676
2684
  actionable.push({ ...thread, comments });
2677
2685
  }
2678
2686
  return actionable;
@@ -2688,7 +2696,7 @@ function newestPr(prs) {
2688
2696
  function plural(count, noun) {
2689
2697
  return `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
2690
2698
  }
2691
- function decideWork(state, p, baseBranch) {
2699
+ function decideWork(state, p, baseBranch, defaultBranch = null) {
2692
2700
  const { issueSurface, prSurface } = state;
2693
2701
  const issue = issueSurface.issue;
2694
2702
  if (issueSurface.state === "CLOSED") {
@@ -2732,17 +2740,20 @@ function decideWork(state, p, baseBranch) {
2732
2740
  detail: `pull request #${String(surface.pr.number)} comes from a fork; its head branch is not in this repository`
2733
2741
  };
2734
2742
  }
2735
- if (surface.pr.headRefName === baseBranch) {
2743
+ const protectedHead = [baseBranch, defaultBranch].filter(
2744
+ (branch) => branch !== null && branch.length > 0
2745
+ );
2746
+ if (protectedHead.includes(surface.pr.headRefName)) {
2736
2747
  return {
2737
2748
  kind: "skip",
2738
2749
  issue,
2739
2750
  reason: "unsafe-pr-branch",
2740
- detail: `pull request #${String(surface.pr.number)} has the base branch (${baseBranch}) as its head`
2751
+ detail: `pull request #${String(surface.pr.number)} has a protected branch (${surface.pr.headRefName}) as its head, so a turn would have to push to it`
2741
2752
  };
2742
2753
  }
2743
2754
  const agentThreadMessages = surface.threads.flatMap((thread) => thread.comments).filter((comment) => comment.author.toLowerCase() === p.agentUser.toLowerCase());
2744
2755
  const prAnalysis = analyzeSurface([...surface.messages, ...agentThreadMessages], p);
2745
- const actionableThreads = findActionableThreads(surface.threads, p);
2756
+ const actionableThreads = findActionableThreads(surface.threads, p, prAnalysis.lastAgentAt);
2746
2757
  const hasPrWork = prAnalysis.hasNewMessage || actionableThreads.length > 0;
2747
2758
  if (!hasPrWork && !issueAnalysis.hasNewMessage) {
2748
2759
  return {
@@ -2784,12 +2795,6 @@ function selectLinkedPr(linkedPrs) {
2784
2795
  const open = linkedPrs.filter((pr) => pr.state === "OPEN");
2785
2796
  return open.length === 0 ? null : newestPr(open);
2786
2797
  }
2787
- function agentAnsweredAfter(messages, agentUser, marker) {
2788
- const agent = agentUser.toLowerCase();
2789
- return messages.some(
2790
- (message) => message.author.toLowerCase() === agent && message.kind !== "issue-body" && message.createdAt > marker.createdAt
2791
- );
2792
- }
2793
2798
 
2794
2799
  // src/github/workPrompt.ts
2795
2800
  function formatThreads(threads) {
@@ -2798,7 +2803,9 @@ function formatThreads(threads) {
2798
2803
  const newest = thread.comments.at(-1);
2799
2804
  const author = newest?.author ?? "unknown";
2800
2805
  const body = newest?.body ?? "";
2801
- return `[${author}] ${location}
2806
+ const link = thread.url === null ? "" : `
2807
+ ${thread.url}`;
2808
+ return `[${author}] ${location}${link}
2802
2809
  ${body}`;
2803
2810
  }).join("\n\n");
2804
2811
  }
@@ -2861,6 +2868,36 @@ function composePrompt(input) {
2861
2868
  return lines.join("\n");
2862
2869
  }
2863
2870
 
2871
+ // src/github/markerReconciliation.ts
2872
+ function isAuthorized(author, p) {
2873
+ const login2 = author.toLowerCase();
2874
+ return login2 !== p.agentUser.toLowerCase() && p.allowedUsers.some((user) => user.toLowerCase() === login2);
2875
+ }
2876
+ function promptWatermark(analysed) {
2877
+ let watermark = null;
2878
+ for (const surface of analysed) {
2879
+ for (const message of surface.messages) {
2880
+ if (watermark === null || message.createdAt > watermark) watermark = message.createdAt;
2881
+ }
2882
+ }
2883
+ return watermark;
2884
+ }
2885
+ function analyseAnswer(messages, p, marker, watermark) {
2886
+ let answeredAt = null;
2887
+ for (const message of messages) {
2888
+ if (message.kind === "issue-body") continue;
2889
+ if (message.author.toLowerCase() !== p.agentUser.toLowerCase()) continue;
2890
+ if (message.createdAt <= marker.createdAt) continue;
2891
+ if (answeredAt === null || message.createdAt > answeredAt) answeredAt = message.createdAt;
2892
+ }
2893
+ const since = watermark ?? marker.createdAt;
2894
+ const toReport = messages.filter(
2895
+ (message) => isAuthorized(message.author, p) && message.createdAt > since
2896
+ );
2897
+ const missed = answeredAt === null ? [] : toReport.filter((message) => message.createdAt < answeredAt);
2898
+ return { answeredAt, missed, toReport };
2899
+ }
2900
+
2864
2901
  // src/run/runLock.ts
2865
2902
  import { writeFileSync, readFileSync as readFileSync3, unlinkSync, mkdirSync, renameSync, linkSync } from "fs";
2866
2903
  import { randomUUID } from "crypto";
@@ -3022,20 +3059,60 @@ function claimStaleLock(path, token, expected) {
3022
3059
  }
3023
3060
  return true;
3024
3061
  }
3025
- function reclaim(path, command, token, expected) {
3026
- if (!claimStaleLock(path, token, expected)) {
3027
- return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
3028
- }
3029
- const claimed = `${path}.stale.${token}`;
3062
+ var CLAIM_STALE_MS = 6e4;
3063
+ function acquireClaim(path) {
3064
+ const claimPath = `${path}.claim`;
3030
3065
  try {
3031
- write(path, command, token, "wx");
3032
- return { ok: true, handle: makeHandle(path, token) };
3066
+ writeFileSync(claimPath, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }), {
3067
+ encoding: "utf8",
3068
+ flag: "wx"
3069
+ });
3070
+ return claimPath;
3033
3071
  } catch (err) {
3034
3072
  if (err.code !== "EEXIST") throw err;
3073
+ }
3074
+ try {
3075
+ const raw = JSON.parse(readFileSync3(claimPath, "utf8"));
3076
+ const at = raw.at === void 0 ? Number.NaN : Date.parse(raw.at);
3077
+ if (!Number.isNaN(at) && Date.now() - at < CLAIM_STALE_MS) return null;
3078
+ unlinkSync(claimPath);
3079
+ } catch {
3080
+ }
3081
+ try {
3082
+ writeFileSync(claimPath, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }), {
3083
+ encoding: "utf8",
3084
+ flag: "wx"
3085
+ });
3086
+ return claimPath;
3087
+ } catch {
3088
+ return null;
3089
+ }
3090
+ }
3091
+ function reclaim(path, command, token, expected) {
3092
+ const claimPath = acquireClaim(path);
3093
+ if (claimPath === null) {
3035
3094
  return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
3095
+ }
3096
+ try {
3097
+ if (!claimStaleLock(path, token, expected)) {
3098
+ return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
3099
+ }
3100
+ const claimed = `${path}.stale.${token}`;
3101
+ try {
3102
+ write(path, command, token, "wx");
3103
+ return { ok: true, handle: makeHandle(path, token) };
3104
+ } catch (err) {
3105
+ if (err.code !== "EEXIST") throw err;
3106
+ return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
3107
+ } finally {
3108
+ try {
3109
+ unlinkSync(claimed);
3110
+ } catch {
3111
+ }
3112
+ }
3036
3113
  } finally {
3037
3114
  try {
3038
- unlinkSync(claimed);
3115
+ unlinkSync(claimPath);
3039
3116
  } catch {
3040
3117
  }
3041
3118
  }
@@ -3306,7 +3383,7 @@ function issueMatchesFilter(surface, settings) {
3306
3383
  }
3307
3384
  function buildIssueState(issue, linkMap) {
3308
3385
  const issueSurface = getIssueSurface(issue.number);
3309
- const linkedPrs = linkMap.get(issue.number) ?? [];
3386
+ const linkedPrs = linkMap.byIssue.get(issue.number) ?? [];
3310
3387
  const selected = selectLinkedPr(linkedPrs);
3311
3388
  return {
3312
3389
  issueSurface,
@@ -3364,17 +3441,10 @@ function refreshItem(item, settings) {
3364
3441
  return decideWork(
3365
3442
  buildIssueState(item.issue, linkMap),
3366
3443
  settings.participants,
3367
- settings.baseBranch
3444
+ settings.baseBranch,
3445
+ linkMap.defaultBranch
3368
3446
  );
3369
3447
  }
3370
- function authorizedMessagesSince(messages, participants, marker) {
3371
- const allowed = participants.allowedUsers.map((user) => user.toLowerCase());
3372
- const agent = participants.agentUser.toLowerCase();
3373
- return messages.filter((message) => {
3374
- const author = message.author.toLowerCase();
3375
- return author !== agent && allowed.includes(author) && message.createdAt > marker.createdAt;
3376
- });
3377
- }
3378
3448
  function readAnsweringSurface(item) {
3379
3449
  if (item.turn === "issue-discuss" || item.pr === null) {
3380
3450
  return getIssueSurface(item.issue.number).messages;
@@ -3385,62 +3455,71 @@ function readAnsweringSurface(item) {
3385
3455
  function markerSurfaceLabel(item) {
3386
3456
  return item.turn === "pr-work" && item.pr ? `pull request #${String(item.pr.number)}` : `issue #${String(item.issue.number)}`;
3387
3457
  }
3388
- function reconcileMarker(item, marker, participants, runError) {
3389
- const agentUser = participants.agentUser;
3390
- let surfaceMessages = [];
3391
- let answered;
3458
+ function markerSurfaceTarget(item) {
3459
+ return item.turn === "pr-work" && item.pr ? { surface: "pr", number: item.pr.number } : { surface: "issue", number: item.issue.number };
3460
+ }
3461
+ function reportOvertakenMessages(item, analysis) {
3462
+ if (analysis.missed.length === 0) return;
3463
+ const authors = [...new Set(analysis.toReport.map((message) => message.author))].join(", ");
3464
+ const count = analysis.toReport.length;
3465
+ const target = markerSurfaceTarget(item);
3392
3466
  try {
3393
- surfaceMessages = readAnsweringSurface(item);
3394
- answered = agentAnsweredAfter(surfaceMessages, agentUser, marker);
3467
+ postMarker(
3468
+ target.surface,
3469
+ target.number,
3470
+ `automata do-work: ${authors} posted here while this run was already in progress, so ${count === 1 ? "that message was" : "those messages were"} not included in it. Please post again to have them acted on.`
3471
+ );
3472
+ } catch (err) {
3473
+ progress(` warning: could not report the overtaken message(s): ${err.message}
3474
+ `);
3475
+ }
3476
+ }
3477
+ function reconcileMarker(item, marker, participants, watermark, runError) {
3478
+ let analysis;
3479
+ try {
3480
+ analysis = analyseAnswer(readAnsweringSurface(item), participants, marker, watermark);
3395
3481
  } catch (err) {
3396
3482
  progress(
3397
3483
  ` warning: could not re-read issue #${String(item.issue.number)} to check for an answer: ${err.message}
3398
3484
  `
3399
3485
  );
3400
- answered = "unknown";
3401
- }
3402
- if (answered === "unknown") {
3403
- const surface2 = markerSurfaceLabel(item);
3404
- try {
3405
- updateMarker(
3406
- marker,
3407
- `automata do-work: the agent run finished, but automata could not read ${surface2} afterwards to confirm whether an answer was posted. Check this thread and the branch before assuming either. Reply here to have another attempt made.`
3408
- );
3409
- } catch (updateErr) {
3410
- progress(` warning: could not update the marker comment: ${updateErr.message}
3411
- `);
3412
- }
3413
- return { outcome: "answered-no-reply", detail: "could not verify whether an answer was posted" };
3486
+ return reportUnverified(item, marker);
3414
3487
  }
3415
- if (answered) {
3416
- const interleaved = authorizedMessagesSince(surfaceMessages, participants, marker);
3417
- if (interleaved.length > 0) {
3418
- const authors = [...new Set(interleaved.map((message) => message.author))].join(", ");
3419
- try {
3420
- postMarker(
3421
- item.turn === "pr-work" ? "pr" : "issue",
3422
- item.turn === "pr-work" && item.pr ? item.pr.number : item.issue.number,
3423
- `automata do-work: ${authors} posted here while this run was already in progress, so ${interleaved.length === 1 ? "that message was" : "those messages were"} not included in it. Please post again to have them acted on.`
3424
- );
3425
- } catch (err) {
3426
- progress(` warning: could not report the interleaved message(s): ${err.message}
3427
- `);
3428
- }
3429
- }
3488
+ if (analysis.answeredAt !== null) {
3489
+ reportOvertakenMessages(item, analysis);
3430
3490
  try {
3431
3491
  deleteMarker(marker);
3432
3492
  } catch (err) {
3433
3493
  progress(` warning: could not delete the marker comment: ${err.message}
3434
3494
  `);
3435
3495
  }
3436
- const noted = interleaved.length > 0 ? ` (${String(interleaved.length)} message(s) arrived mid-run and were flagged)` : "";
3437
- if (interleaved.length > 0) {
3438
- return { outcome: "answered-no-reply", detail: `answered${noted}` };
3496
+ if (analysis.missed.length > 0) {
3497
+ return {
3498
+ outcome: "answered-no-reply",
3499
+ detail: `answered (${String(analysis.toReport.length)} message(s) arrived mid-run and were flagged)`
3500
+ };
3439
3501
  }
3440
3502
  return runError === null ? { outcome: "answered", detail: "answered" } : { outcome: "answered", detail: `answered, but the run reported: ${runError.message}` };
3441
3503
  }
3504
+ return reportNoAnswer(item, marker, runError);
3505
+ }
3506
+ function reportUnverified(item, marker) {
3442
3507
  const surface = markerSurfaceLabel(item);
3443
- const explanation = runError === null ? `automata do-work: the agent run finished without posting an answer on ${surface}. It may still have changed the branch \`${item.branch}\` \u2014 check it before assuming otherwise. Reply on ${surface} to have another attempt made.` : `automata do-work: the agent run failed before posting an answer (${runError.message}). It may have left partial changes on the branch \`${item.branch}\`. Reply on ${surface} to have another attempt made.`;
3508
+ try {
3509
+ updateMarker(
3510
+ marker,
3511
+ `automata do-work: the agent run finished, but automata could not read ${surface} afterwards to confirm whether an answer was posted. Check this thread and the branch before assuming either. Reply here to have another attempt made.`
3512
+ );
3513
+ } catch (err) {
3514
+ progress(` warning: could not update the marker comment: ${err.message}
3515
+ `);
3516
+ }
3517
+ return { outcome: "answered-no-reply", detail: "could not verify whether an answer was posted" };
3518
+ }
3519
+ function reportNoAnswer(item, marker, runError) {
3520
+ const surface = markerSurfaceLabel(item);
3521
+ const sideEffects = item.turn === "issue-discuss" ? "It may still have created a branch or opened a pull request \u2014 check before assuming otherwise." : `It may still have changed the branch \`${item.branch}\` \u2014 check it before assuming otherwise.`;
3522
+ const explanation = runError === null ? `automata do-work: the agent run finished without posting an answer on ${surface}. ${sideEffects} Reply on ${surface} to have another attempt made.` : `automata do-work: the agent run failed before posting an answer (${runError.message}). ${sideEffects} Reply on ${surface} to have another attempt made.`;
3444
3523
  try {
3445
3524
  updateMarker(marker, explanation);
3446
3525
  } catch (err) {
@@ -3465,26 +3544,28 @@ function repairIssueLink(item, baseBranch) {
3465
3544
  if (branch === baseBranch) {
3466
3545
  progress(` issue #${String(item.issue.number)} is still in discussion (no branch was created).
3467
3546
  `);
3468
- return;
3547
+ return false;
3469
3548
  }
3470
3549
  const pr = getCurrentBranchPr();
3471
3550
  if (!pr) {
3472
3551
  progress(` issue #${String(item.issue.number)} is still in discussion (no pull request).
3473
3552
  `);
3474
- return;
3553
+ return false;
3475
3554
  }
3476
3555
  const closesRef = new RegExp(String.raw`\bcloses\s+#` + String(item.issue.number) + String.raw`\b`, "i");
3477
3556
  if (closesRef.test(pr.body)) {
3478
3557
  progress(` pull request #${String(pr.number)} already closes issue #${String(item.issue.number)}.
3479
3558
  `);
3480
- return;
3559
+ return true;
3481
3560
  }
3482
3561
  addClosesRefToPr(pr.number, item.issue.number);
3483
3562
  progress(` linked pull request #${String(pr.number)} to issue #${String(item.issue.number)}.
3484
3563
  `);
3564
+ return true;
3485
3565
  } catch (err) {
3486
3566
  progress(` warning: could not link a pull request to issue #${String(item.issue.number)}: ${err.message}
3487
3567
  `);
3568
+ return false;
3488
3569
  }
3489
3570
  }
3490
3571
  async function processItem(planned, settings, silent) {
@@ -3534,6 +3615,10 @@ async function processItem(planned, settings, silent) {
3534
3615
  inFlightMarker = null;
3535
3616
  return { ...base, outcome: "skipped", detail: "issue pickup note failed" };
3536
3617
  }
3618
+ const watermark = promptWatermark([
3619
+ item.issueAnalysis,
3620
+ ...item.prAnalysis === null ? [] : [item.prAnalysis]
3621
+ ]);
3537
3622
  const prompt = composePrompt({
3538
3623
  item,
3539
3624
  repo: getRepoSlug(),
@@ -3567,13 +3652,18 @@ async function processItem(planned, settings, silent) {
3567
3652
  }
3568
3653
  const ranExecutor = true;
3569
3654
  inFlightMarker = null;
3570
- const reconciled = reconcileMarker(item, marker, settings.participants, runError);
3655
+ const reconciled = reconcileMarker(item, marker, settings.participants, watermark, runError);
3571
3656
  progress(` ${reconciled.detail}
3572
3657
  `);
3658
+ let outcome = reconciled;
3573
3659
  if (item.turn === "issue-discuss") {
3574
- repairIssueLink(item, settings.baseBranch);
3660
+ const linked = repairIssueLink(item, settings.baseBranch);
3661
+ if (linked && outcome.outcome === "answered-no-reply") {
3662
+ outcome = { outcome: "answered", detail: "opened a pull request (no issue comment)" };
3663
+ progress(" a pull request was opened, so the turn is counted as answered.\n");
3664
+ }
3575
3665
  }
3576
- return { ...base, outcome: reconciled.outcome, detail: reconciled.detail, ranExecutor };
3666
+ return { ...base, outcome: outcome.outcome, detail: outcome.detail, ranExecutor };
3577
3667
  }
3578
3668
  function summarize(reports) {
3579
3669
  out("\nTick summary:\n");
@@ -3670,7 +3760,14 @@ var doWorkCommand = new Command6("do-work").description(
3670
3760
  async function runTick(settings, options) {
3671
3761
  const issues = discoverIssues(settings);
3672
3762
  const linkMap = getOpenPrLinkMap();
3673
- const decisions = issues.map((issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, settings.baseBranch));
3763
+ const decisions = issues.map(
3764
+ (issue) => decideWork(
3765
+ buildIssueState(issue, linkMap),
3766
+ settings.participants,
3767
+ settings.baseBranch,
3768
+ linkMap.defaultBranch
3769
+ )
3770
+ );
3674
3771
  const items = decisions.flatMap((decision) => decision.kind === "work" ? [decision.item] : []);
3675
3772
  const planText = `Work plan (${String(items.length)} of ${String(issues.length)} issues need an answer):
3676
3773
  ${describePlan(decisions)}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.1.0-feature-030-do-work.212",
3
+ "version": "0.1.0-feature-030-do-work.214",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "bin": {