automata-cli 0.1.0-feature-030-do-work.206 → 0.1.0-feature-030-do-work.210

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.
Files changed (2) hide show
  1. package/dist/index.js +108 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,8 +34,9 @@ function writeDoWork(patch) {
34
34
  writeConfig({ ...current, doWork: { ...current.doWork, ...patch } });
35
35
  }
36
36
  function parseNonNegativeInt(value, label) {
37
- const parsed = Number.parseInt(value, 10);
38
- if (Number.isNaN(parsed) || parsed < 0 || String(parsed) !== value.trim()) {
37
+ const trimmed = value.trim();
38
+ const parsed = Number.parseInt(trimmed, 10);
39
+ if (Number.isNaN(parsed) || parsed < 0 || String(parsed) !== trimmed || !Number.isSafeInteger(parsed)) {
39
40
  process.stderr.write(`Error: ${label} must be a non-negative integer (got "${value}").
40
41
  `);
41
42
  process.exit(1);
@@ -722,7 +723,7 @@ function createTrackingBranch(branch) {
722
723
  return gitCommand(["checkout", "-b", branch, `origin/${branch}`]);
723
724
  }
724
725
  function fetchBranch(branch) {
725
- return gitCommand(["fetch", "origin", branch]);
726
+ return gitCommand(["fetch", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
726
727
  }
727
728
  function pullFastForwardOnly(branch) {
728
729
  return gitCommand(branch === void 0 ? ["pull", "--ff-only"] : ["pull", "--ff-only", "origin", branch]);
@@ -2494,6 +2495,7 @@ query($owner:String!,$repo:String!,$prNumber:Int!,$cursor:String){
2494
2495
  nodes{
2495
2496
  isResolved isOutdated path line
2496
2497
  comments(last:100){
2498
+ pageInfo{ hasPreviousPage }
2497
2499
  nodes{ author{login} body createdAt }
2498
2500
  }
2499
2501
  }
@@ -2531,6 +2533,11 @@ function getReviewThreads(prNumber) {
2531
2533
  );
2532
2534
  const connection = response.data.repository.pullRequest.reviewThreads;
2533
2535
  for (const node of connection.nodes) {
2536
+ if (node.comments.pageInfo?.hasPreviousPage) {
2537
+ throw new Error(
2538
+ `Review thread on ${node.path} in pull request #${String(prNumber)} has more than 100 comments, so the earliest ones were not read. Refusing rather than risk suppressing a maintainer's request.`
2539
+ );
2540
+ }
2534
2541
  threads.push({
2535
2542
  path: node.path,
2536
2543
  line: node.line ?? null,
@@ -2833,7 +2840,7 @@ function composePrompt(input) {
2833
2840
  }
2834
2841
 
2835
2842
  // src/run/runLock.ts
2836
- import { writeFileSync, readFileSync as readFileSync3, unlinkSync, mkdirSync, renameSync } from "fs";
2843
+ import { writeFileSync, readFileSync as readFileSync3, unlinkSync, mkdirSync, renameSync, linkSync } from "fs";
2837
2844
  import { randomUUID } from "crypto";
2838
2845
  import { hostname } from "os";
2839
2846
  import { join as join2 } from "path";
@@ -2885,8 +2892,26 @@ function makeHandle(path, token) {
2885
2892
  if (current !== null && current.token !== token) {
2886
2893
  return;
2887
2894
  }
2895
+ const takenAway = `${path}.releasing.${token}`;
2896
+ try {
2897
+ renameSync(path, takenAway);
2898
+ } catch {
2899
+ return;
2900
+ }
2901
+ const owner = readOwner(takenAway);
2902
+ if (owner === null || owner.token === token) {
2903
+ try {
2904
+ unlinkSync(takenAway);
2905
+ } catch {
2906
+ }
2907
+ return;
2908
+ }
2888
2909
  try {
2889
- unlinkSync(path);
2910
+ linkSync(takenAway, path);
2911
+ } catch {
2912
+ }
2913
+ try {
2914
+ unlinkSync(takenAway);
2890
2915
  } catch {
2891
2916
  }
2892
2917
  }
@@ -3043,8 +3068,8 @@ function resolveSettings(options) {
3043
3068
  if (agentUser.length === 0) {
3044
3069
  fail("No agent user configured. Run `automata config set agent-user <login>`.");
3045
3070
  }
3071
+ validateDoWorkConfig(config.doWork);
3046
3072
  const doWork = config.doWork ?? {};
3047
- validateDoWorkConfig(doWork);
3048
3073
  let executor = doWork.executor ?? DEFAULT_DO_WORK.executor;
3049
3074
  if (options.with !== void 0) {
3050
3075
  const requested = options.with.toLowerCase();
@@ -3136,33 +3161,49 @@ function describePlannedRun(item, settings, run5) {
3136
3161
  ];
3137
3162
  return lines.join("\n") + "\n";
3138
3163
  }
3139
- function validateDoWorkConfig(doWork) {
3140
- if (doWork.executor !== void 0 && doWork.executor !== "claude" && doWork.executor !== "codex") {
3141
- fail(`doWork.executor must be 'claude' or 'codex', got '${String(doWork.executor)}'.`);
3164
+ function isPlainObject(value) {
3165
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3166
+ }
3167
+ function validateOptionalString(container, key, path) {
3168
+ const value = container[key];
3169
+ if (value === void 0 || value === null) return;
3170
+ if (typeof value !== "string" || value.trim().length === 0) {
3171
+ fail(`${path} must be a non-empty string.`);
3142
3172
  }
3143
- if (doWork.baseBranch?.trim().length === 0) {
3144
- fail("doWork.baseBranch must not be empty.");
3173
+ }
3174
+ function validateOptionalInt(container, key, path, min, hint) {
3175
+ const value = container[key];
3176
+ if (value === void 0 || value === null) return;
3177
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min) {
3178
+ fail(`${path} must be ${hint}, got ${JSON.stringify(value)}.`);
3145
3179
  }
3146
- if (doWork.maxRunsPerTick !== void 0) {
3147
- if (!Number.isSafeInteger(doWork.maxRunsPerTick) || doWork.maxRunsPerTick < 0) {
3148
- fail(
3149
- `doWork.maxRunsPerTick must be a non-negative integer (0 = unlimited), got ${String(doWork.maxRunsPerTick)}.`
3150
- );
3151
- }
3180
+ }
3181
+ function validateDoWorkConfig(section) {
3182
+ if (section === void 0 || section === null) return;
3183
+ if (!isPlainObject(section)) {
3184
+ fail(`doWork must be an object, got ${JSON.stringify(section)}.`);
3152
3185
  }
3153
- if (doWork.lockStaleMinutes !== void 0) {
3154
- if (!Number.isSafeInteger(doWork.lockStaleMinutes) || doWork.lockStaleMinutes <= 0) {
3155
- fail(`doWork.lockStaleMinutes must be a positive integer, got ${String(doWork.lockStaleMinutes)}.`);
3156
- }
3186
+ const executor = section["executor"];
3187
+ if (executor !== void 0 && executor !== "claude" && executor !== "codex") {
3188
+ fail(`doWork.executor must be 'claude' or 'codex', got ${JSON.stringify(executor)}.`);
3157
3189
  }
3158
- for (const [key, value] of [
3159
- ["doWork.models.claude", doWork.models?.claude],
3160
- ["doWork.models.codex", doWork.models?.codex],
3161
- ["doWork.prompts.issueDiscuss", doWork.prompts?.issueDiscuss],
3162
- ["doWork.prompts.prWork", doWork.prompts?.prWork]
3163
- ]) {
3164
- if (value !== void 0 && (typeof value !== "string" || value.trim().length === 0)) {
3165
- fail(`${key} must be a non-empty string.`);
3190
+ validateOptionalString(section, "baseBranch", "doWork.baseBranch");
3191
+ validateOptionalInt(section, "maxRunsPerTick", "doWork.maxRunsPerTick", 0, "a non-negative integer (0 = unlimited)");
3192
+ validateOptionalInt(section, "lockStaleMinutes", "doWork.lockStaleMinutes", 1, "a positive integer");
3193
+ for (const container of ["models", "prompts"]) {
3194
+ const value = section[container];
3195
+ if (value === void 0 || value === null) continue;
3196
+ if (!isPlainObject(value)) {
3197
+ fail(`doWork.${container} must be an object, got ${JSON.stringify(value)}.`);
3198
+ }
3199
+ const keys = container === "models" ? ["claude", "codex"] : ["issueDiscuss", "prWork"];
3200
+ for (const key of keys) {
3201
+ validateOptionalString(value, key, `doWork.${container}.${key}`);
3202
+ }
3203
+ for (const key of Object.keys(value)) {
3204
+ if (!keys.includes(key)) {
3205
+ fail(`doWork.${container}.${key} is not a recognised setting; expected one of: ${keys.join(", ")}.`);
3206
+ }
3166
3207
  }
3167
3208
  }
3168
3209
  }
@@ -3360,6 +3401,21 @@ async function processItem(planned, settings, silent) {
3360
3401
  `);
3361
3402
  }
3362
3403
  }
3404
+ if (item.turn === "pr-work" && item.pr && item.issueAnalysis.hasNewMessage) {
3405
+ try {
3406
+ postMarker(
3407
+ "issue",
3408
+ item.issue.number,
3409
+ `automata do-work: picked this up on pull request #${String(item.pr.number)} \u2014 ${item.pr.url}`
3410
+ );
3411
+ progress(` noted on issue #${String(item.issue.number)} that the work is on pull request #${String(item.pr.number)}.
3412
+ `);
3413
+ } catch (err) {
3414
+ progress(` skipped: could not note the pickup on issue #${String(item.issue.number)} \u2014 ${err.message}
3415
+ `);
3416
+ return { ...base, outcome: "skipped", detail: `issue pickup note failed: ${err.message}` };
3417
+ }
3418
+ }
3363
3419
  let marker;
3364
3420
  const markerSurface = item.turn === "pr-work" && item.pr ? item.pr.number : item.issue.number;
3365
3421
  try {
@@ -3410,10 +3466,15 @@ var doWorkCommand = new Command6("do-work").description(
3410
3466
  const settings = resolveSettings(options);
3411
3467
  const lock = acquireRunLock("do-work", settings.lockStaleMinutes);
3412
3468
  if (!lock.ok) {
3413
- out(
3414
- `Another automata instance is already running here (pid ${String(lock.heldBy.pid)} on ${lock.heldBy.host}, started ${lock.heldBy.startedAt}, command ${lock.heldBy.command}). Doing nothing.
3415
- `
3416
- );
3469
+ const held = lock.heldBy;
3470
+ const sentence = `Another automata instance is already running here (pid ${String(held.pid)} on ${held.host}, started ${held.startedAt}, command ${held.command}). Doing nothing.
3471
+ `;
3472
+ if (options.json === true) {
3473
+ progress(sentence);
3474
+ out(JSON.stringify({ lockHeld: true, heldBy: held, plan: [], items: [], exitCode: 0 }, null, 2) + "\n");
3475
+ } else {
3476
+ out(sentence);
3477
+ }
3417
3478
  return;
3418
3479
  }
3419
3480
  const handle = lock.handle;
@@ -3473,7 +3534,20 @@ ${describePlan(decisions)}`;
3473
3534
  deferred.push(item);
3474
3535
  continue;
3475
3536
  }
3476
- const report = await processItem(item, settings, options.silent === true);
3537
+ let report;
3538
+ try {
3539
+ report = await processItem(item, settings, options.silent === true);
3540
+ } catch (err) {
3541
+ progress(` failed: ${err.message}
3542
+ `);
3543
+ report = {
3544
+ issue: item.issue.number,
3545
+ title: item.issue.title,
3546
+ turn: item.turn,
3547
+ outcome: "failed",
3548
+ detail: err.message
3549
+ };
3550
+ }
3477
3551
  if (report.outcome !== "skipped") runsUsed++;
3478
3552
  reports.push(report);
3479
3553
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.1.0-feature-030-do-work.206",
3
+ "version": "0.1.0-feature-030-do-work.210",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "bin": {