@taskless-app/swarm 0.6.18 → 0.6.19

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 (3) hide show
  1. package/dist/cli.js +195 -14
  2. package/dist/overmind.js +267 -46
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -472,6 +472,92 @@ var init_mergeGate = __esm({
472
472
  }
473
473
  });
474
474
 
475
+ // ../swarm-core/src/mergeQueue.ts
476
+ function orderMergeQueue(ready) {
477
+ return [...ready].sort((a, b) => a.prNumber - b.prNumber || a.runId - b.runId);
478
+ }
479
+ function label(e) {
480
+ return e.slug ? `#${e.prNumber} (${e.slug})` : `#${e.prNumber}`;
481
+ }
482
+ function computeMergeQueue(input) {
483
+ const queue = orderMergeQueue(input.ready);
484
+ const size = queue.length;
485
+ const idx = queue.findIndex((e) => e.runId === input.entry.runId && e.prNumber === input.entry.prNumber);
486
+ const position = idx + 1;
487
+ const head = queue[0];
488
+ if (head && idx > 0) {
489
+ return {
490
+ allowed: false,
491
+ code: "queue_wait",
492
+ reason: `\u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 PR \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443: \u0441\u0435\u0439\u0447\u0430\u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u044C PR ${label(head)}, \u044D\u0442\u043E\u0442 \u2014 ${position}-\u0439 \u0438\u0437 ${size}. \u041C\u0435\u0440\u0436\u0438\u0442\u044C \u0435\u0433\u043E \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u043B\u044C\u0437\u044F \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C, \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0434 \u043D\u0438\u043C \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F (TASWM-472)`,
493
+ position,
494
+ size
495
+ };
496
+ }
497
+ if (input.behindBy == null) {
498
+ return {
499
+ allowed: false,
500
+ code: "base_unknown",
501
+ reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0437\u043D\u0430\u0442\u044C, \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043B\u0438 \u0432\u0435\u0442\u043A\u0430 PR \u0442\u0435\u043A\u0443\u0449\u0438\u0439 main \u2014 \u0430 \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0437\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0435 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E \u043F\u0440\u043E main \u043F\u043E\u0441\u043B\u0435 \u0432\u043B\u0438\u0432\u0430\u043D\u0438\u044F. \u042D\u0442\u043E \u0442\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u043E (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B): \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443 (TASWM-472)",
502
+ position,
503
+ size
504
+ };
505
+ }
506
+ if (input.behindBy > 0) {
507
+ return {
508
+ allowed: false,
509
+ code: "base_stale",
510
+ reason: `\u0431\u0430\u0437\u0430 \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u043D\u0430 ${input.behindBy} \u043A\u043E\u043C\u043C\u0438\u0442(\u043E\u0432) \u0441 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u2014 \u0437\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u0439 \u0431\u0430\u0437\u0435 \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E main \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C. \u0412\u043B\u0435\u0439 main \u0432 \u0432\u0435\u0442\u043A\u0443 PR \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 (TASWM-472)`,
511
+ position,
512
+ size
513
+ };
514
+ }
515
+ if (input.checks === null) {
516
+ return {
517
+ allowed: false,
518
+ code: "checks_unknown",
519
+ reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 PR \u2014 \u0430 \u043D\u0430 \u0441\u0432\u0435\u0436\u0435\u0439 \u0431\u0430\u0437\u0435 \u0438\u043C\u0435\u043D\u043D\u043E \u043E\u043D\u0438 \u0438 \u043E\u0442\u0432\u0435\u0447\u0430\u044E\u0442, \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043B\u0438 main \u0437\u0435\u043B\u0451\u043D\u044B\u043C. \u042D\u0442\u043E \u0442\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u043E (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B): \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443 (TASWM-472)",
520
+ position,
521
+ size
522
+ };
523
+ }
524
+ if (input.checks === "fail") {
525
+ return {
526
+ allowed: false,
527
+ code: "checks_red",
528
+ reason: "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u041E\u0411\u041D\u041E\u0412\u041B\u0401\u041D\u041D\u041E\u0419 \u0431\u0430\u0437\u0435 \u043A\u0440\u0430\u0441\u043D\u044B\u0435 \u2014 \u044D\u0442\u043E\u0442 PR \u043B\u043E\u043C\u0430\u0435\u0442 main \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C \u0431\u0430\u0437\u044B. \u041C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F: \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430 (TASWM-472)",
529
+ position,
530
+ size
531
+ };
532
+ }
533
+ if (input.checks === "pending") {
534
+ return {
535
+ allowed: false,
536
+ code: "checks_pending",
537
+ reason: "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 \u0435\u0449\u0451 \u0438\u0434\u0443\u0442 \u2014 \u0438\u0445 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0438 \u0435\u0441\u0442\u044C \u043E\u0442\u0432\u0435\u0442 \xAB\u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043B\u0438 main \u0437\u0435\u043B\u0451\u043D\u044B\u043C\xBB. \u0414\u043E\u0436\u0434\u0438\u0441\u044C (TASWM-472)",
538
+ position,
539
+ size
540
+ };
541
+ }
542
+ return {
543
+ allowed: true,
544
+ code: "queue_head",
545
+ reason: "\u0433\u043E\u043B\u043E\u0432\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430, \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0441\u0432\u0435\u0436\u0430\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043D\u0435\u0439 \u043D\u0435 \u043A\u0440\u0430\u0441\u043D\u044B\u0435 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E",
546
+ position,
547
+ size
548
+ };
549
+ }
550
+ function computeMergeDecision(gate, queue) {
551
+ if (!gate.allowed || gate.code === "unknown_pr" || !queue) return { ...gate, queue: queue ?? null };
552
+ if (!queue.allowed) return { allowed: false, code: queue.code, reason: queue.reason, queue };
553
+ return { ...gate, reason: `${gate.reason}; ${queue.reason}`, queue };
554
+ }
555
+ var init_mergeQueue = __esm({
556
+ "../swarm-core/src/mergeQueue.ts"() {
557
+ "use strict";
558
+ }
559
+ });
560
+
475
561
  // ../swarm-core/src/modelPolicy.ts
476
562
  function findProvider(id) {
477
563
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1471,6 +1557,7 @@ var init_src = __esm({
1471
1557
  init_timeline();
1472
1558
  init_reviewPhase();
1473
1559
  init_mergeGate();
1560
+ init_mergeQueue();
1474
1561
  init_verifyVerdict();
1475
1562
  init_modelPolicy();
1476
1563
  init_status();
@@ -2080,7 +2167,7 @@ function tokenFromFile() {
2080
2167
  return "";
2081
2168
  }
2082
2169
  }
2083
- var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2170
+ var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2084
2171
  var init_config = __esm({
2085
2172
  "src/config.ts"() {
2086
2173
  "use strict";
@@ -2153,6 +2240,7 @@ var init_config = __esm({
2153
2240
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
2154
2241
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2155
2242
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2243
+ SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2156
2244
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join4(SWARM_HOME, "review-cutoff.json");
2157
2245
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2158
2246
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -2284,7 +2372,7 @@ var init_package = __esm({
2284
2372
  "package.json"() {
2285
2373
  package_default = {
2286
2374
  name: "@taskless-app/swarm",
2287
- version: "0.6.18",
2375
+ version: "0.6.19",
2288
2376
  type: "module",
2289
2377
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2290
2378
  keywords: [
@@ -2334,7 +2422,7 @@ var init_package = __esm({
2334
2422
  execa: "^10.0.0",
2335
2423
  zod: "3.25.76"
2336
2424
  },
2337
- gitHead: "9531f684d8a7c6c4c3595f0c57e4aebce0038ac1"
2425
+ gitHead: "a61c26b5e254ef5383d918f14a9f6e8e8a09904a"
2338
2426
  };
2339
2427
  }
2340
2428
  });
@@ -2501,8 +2589,8 @@ function describeError(e, maxDepth = 4) {
2501
2589
  const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2502
2590
  const code = cur.code;
2503
2591
  const codeText = code === void 0 || code === null ? "" : String(code);
2504
- const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2505
- if (label && !parts.includes(label)) parts.push(label);
2592
+ const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2593
+ if (label2 && !parts.includes(label2)) parts.push(label2);
2506
2594
  cur = cur.cause;
2507
2595
  }
2508
2596
  return parts.join(" \u2190 ") || String(e);
@@ -5172,18 +5260,18 @@ function isTransientResourceError(e) {
5172
5260
  const code = e?.code;
5173
5261
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
5174
5262
  }
5175
- function logTransientSpawnError(label, e) {
5263
+ function logTransientSpawnError(label2, e) {
5176
5264
  if (isTransientResourceError(e)) {
5177
5265
  const code = e.code;
5178
- console.warn(`[overmind] ${label}: \u041E\u0421 \u043D\u0435 \u0434\u0430\u0451\u0442 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B (${code}) \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0438\u0433\u043D\u0430\u043B \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043A\u0430, \u043D\u0435 \u043F\u0430\u0434\u0430\u044E`);
5266
+ console.warn(`[overmind] ${label2}: \u041E\u0421 \u043D\u0435 \u0434\u0430\u0451\u0442 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B (${code}) \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0438\u0433\u043D\u0430\u043B \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043A\u0430, \u043D\u0435 \u043F\u0430\u0434\u0430\u044E`);
5179
5267
  } else {
5180
- console.warn(`[overmind] ${label} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F (\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u0437\u0430 \u044D\u0442\u043E\u0442 \u0442\u0438\u043A): ${String(e)}`);
5268
+ console.warn(`[overmind] ${label2} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F (\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u0437\u0430 \u044D\u0442\u043E\u0442 \u0442\u0438\u043A): ${String(e)}`);
5181
5269
  }
5182
5270
  }
5183
5271
  function parseVmStatAvailableBytes(vmStatOut) {
5184
5272
  const page = Number(vmStatOut.match(/page size of (\d+) bytes/)?.[1]) || 4096;
5185
- const pages = (label) => {
5186
- const m = vmStatOut.match(new RegExp(`Pages ${label}:\\s+(\\d+)`));
5273
+ const pages = (label2) => {
5274
+ const m = vmStatOut.match(new RegExp(`Pages ${label2}:\\s+(\\d+)`));
5187
5275
  return m ? Number(m[1]) : 0;
5188
5276
  };
5189
5277
  return (pages("free") + pages("inactive") + pages("speculative") + pages("purgeable")) * page;
@@ -8469,6 +8557,11 @@ init_config();
8469
8557
 
8470
8558
  // src/mergeGate.ts
8471
8559
  init_src();
8560
+
8561
+ // src/mergeQueue.ts
8562
+ init_src();
8563
+
8564
+ // src/prNumber.ts
8472
8565
  function prNumberOf(ref) {
8473
8566
  const s = (ref || "").trim();
8474
8567
  if (!s) return null;
@@ -8477,6 +8570,76 @@ function prNumberOf(ref) {
8477
8570
  const bare = s.match(/^#?(\d+)$/);
8478
8571
  return bare ? Number(bare[1]) : null;
8479
8572
  }
8573
+
8574
+ // src/mergeQueue.ts
8575
+ var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
8576
+ function isMergeCandidate(r) {
8577
+ return !!r.pr_url && r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
8578
+ }
8579
+ function mergeQueueCandidates(runs, repo) {
8580
+ const byPr = /* @__PURE__ */ new Map();
8581
+ for (const r of runs) {
8582
+ if (r.repo !== repo || !isMergeCandidate(r)) continue;
8583
+ const num = prNumberOf(r.pr_url);
8584
+ const seen = byPr.get(num);
8585
+ if (!seen || r.id > seen.id) byPr.set(num, r);
8586
+ }
8587
+ return [...byPr.values()];
8588
+ }
8589
+ var BASE_TTL_MS = 2e4;
8590
+ function makeMergeQueueResolver(deps) {
8591
+ const memo = /* @__PURE__ */ new Map();
8592
+ const evaluate = async (e, prUrl, ready) => {
8593
+ const now = Date.now();
8594
+ let cached = memo.get(prUrl);
8595
+ const ttl = deps.ttlMs ?? BASE_TTL_MS;
8596
+ if (!cached || now - cached.at >= ttl) {
8597
+ const base3 = await deps.baseSync(prUrl).catch(() => null);
8598
+ let checks = "none";
8599
+ if (base3?.behindBy === 0 && deps.checks) {
8600
+ const ci = await deps.checks(prUrl).catch(() => null);
8601
+ checks = ci ? ci.bucket : null;
8602
+ }
8603
+ cached = { at: now, base: base3, checks };
8604
+ memo.set(prUrl, cached);
8605
+ }
8606
+ return computeMergeQueue({ entry: e, ready, behindBy: cached.base ? cached.base.behindBy : null, checks: cached.checks });
8607
+ };
8608
+ return async (run) => {
8609
+ if (!deps.baseSync || !run.pr_url) return null;
8610
+ const num = prNumberOf(run.pr_url);
8611
+ if (num == null) return null;
8612
+ const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
8613
+ const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
8614
+ if (!mine) return null;
8615
+ const ready = [];
8616
+ const urlOf = /* @__PURE__ */ new Map();
8617
+ for (const c of candidates) {
8618
+ if (!await deps.gateAllows(c).catch(() => false)) continue;
8619
+ ready.push(entryOf(c));
8620
+ urlOf.set(c.id, c.pr_url);
8621
+ }
8622
+ const entry = entryOf(mine);
8623
+ if (!ready.some((e) => e.runId === entry.runId)) return null;
8624
+ let queue = orderMergeQueue(ready);
8625
+ while (queue.length > 1 && queue[0].runId !== entry.runId) {
8626
+ const head = queue[0];
8627
+ const headUrl = urlOf.get(head.runId);
8628
+ const decision = await evaluate(head, headUrl, queue);
8629
+ if (decision.code !== "checks_red") break;
8630
+ queue = queue.slice(1);
8631
+ }
8632
+ if (queue[0].runId !== entry.runId) {
8633
+ return computeMergeQueue({ entry, ready: queue, behindBy: null, checks: "none" });
8634
+ }
8635
+ return evaluate(entry, mine.pr_url, queue);
8636
+ };
8637
+ }
8638
+ function entryOf(r) {
8639
+ return { runId: r.id, prNumber: prNumberOf(r.pr_url), slug: r.task_slug };
8640
+ }
8641
+
8642
+ // src/mergeGate.ts
8480
8643
  function findRunByPr(runs, ref) {
8481
8644
  const num = prNumberOf(ref);
8482
8645
  if (num == null) return null;
@@ -8488,17 +8651,34 @@ function findRunByPr(runs, ref) {
8488
8651
  return best;
8489
8652
  }
8490
8653
  function makeMergeGateResolver(deps) {
8491
- return async (ref) => {
8492
- const run = findRunByPr(deps.listRuns(), ref);
8493
- if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
8654
+ return makeMergeGateParts(deps).gate;
8655
+ }
8656
+ function makeMergeGateParts(deps) {
8657
+ const gateFor = async (run) => {
8494
8658
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
8495
- const decision = computeMergeGate({
8659
+ return computeMergeGate({
8496
8660
  hasRun: true,
8497
8661
  autoReview,
8498
8662
  reviewVerdict: run.review_verdict,
8499
8663
  autoVerify: deps.autoVerify === true,
8500
8664
  verifyVerdict: run.verify_verdict ?? null
8501
8665
  });
8666
+ };
8667
+ const queueFor = makeMergeQueueResolver({
8668
+ listRuns: deps.listRuns,
8669
+ gateAllows: async (r) => (await gateFor(r)).allowed,
8670
+ ...deps.baseSync ? { baseSync: deps.baseSync } : {},
8671
+ ...deps.checks ? { checks: deps.checks } : {},
8672
+ ...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
8673
+ });
8674
+ const gate = async (ref) => {
8675
+ const run = findRunByPr(deps.listRuns(), ref);
8676
+ if (!run) {
8677
+ return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
8678
+ }
8679
+ const accept = await gateFor(run);
8680
+ const queue = accept.allowed && accept.code !== "unknown_pr" ? await queueFor(run) : null;
8681
+ const decision = computeMergeDecision(accept, queue);
8502
8682
  return {
8503
8683
  ...decision,
8504
8684
  run: {
@@ -8511,6 +8691,7 @@ function makeMergeGateResolver(deps) {
8511
8691
  }
8512
8692
  };
8513
8693
  };
8694
+ return { gate, queue: queueFor };
8514
8695
  }
8515
8696
 
8516
8697
  // src/web.ts
package/dist/overmind.js CHANGED
@@ -311,8 +311,8 @@ function computeReviewPhase(input) {
311
311
  }
312
312
  if (ciRed) {
313
313
  const jobs = (input.ciFailing ?? []).filter((j) => j && j !== "?");
314
- const label2 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
315
- return { kind: "ci_fixing", label: label2, ready: false };
314
+ const label3 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
315
+ return { kind: "ci_fixing", label: label3, ready: false };
316
316
  }
317
317
  if (reviewVerdict === "changes") {
318
318
  return { kind: "changes", label: REVIEW_PHASE_TEXT.changes, ready: false };
@@ -327,8 +327,8 @@ function computeReviewPhase(input) {
327
327
  if (p && p.total > 0 && p.pass >= p.total) {
328
328
  return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
329
329
  }
330
- const label = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
331
- return { kind: "checks", label, ready: false };
330
+ const label2 = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
331
+ return { kind: "checks", label: label2, ready: false };
332
332
  }
333
333
  var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
334
334
  var init_reviewPhase = __esm({
@@ -439,6 +439,92 @@ var init_mergeGate = __esm({
439
439
  }
440
440
  });
441
441
 
442
+ // ../swarm-core/src/mergeQueue.ts
443
+ function orderMergeQueue(ready) {
444
+ return [...ready].sort((a, b) => a.prNumber - b.prNumber || a.runId - b.runId);
445
+ }
446
+ function label(e) {
447
+ return e.slug ? `#${e.prNumber} (${e.slug})` : `#${e.prNumber}`;
448
+ }
449
+ function computeMergeQueue(input) {
450
+ const queue = orderMergeQueue(input.ready);
451
+ const size = queue.length;
452
+ const idx = queue.findIndex((e) => e.runId === input.entry.runId && e.prNumber === input.entry.prNumber);
453
+ const position = idx + 1;
454
+ const head = queue[0];
455
+ if (head && idx > 0) {
456
+ return {
457
+ allowed: false,
458
+ code: "queue_wait",
459
+ reason: `\u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 PR \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443: \u0441\u0435\u0439\u0447\u0430\u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u044C PR ${label(head)}, \u044D\u0442\u043E\u0442 \u2014 ${position}-\u0439 \u0438\u0437 ${size}. \u041C\u0435\u0440\u0436\u0438\u0442\u044C \u0435\u0433\u043E \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u043B\u044C\u0437\u044F \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C, \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0434 \u043D\u0438\u043C \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F (TASWM-472)`,
460
+ position,
461
+ size
462
+ };
463
+ }
464
+ if (input.behindBy == null) {
465
+ return {
466
+ allowed: false,
467
+ code: "base_unknown",
468
+ reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0437\u043D\u0430\u0442\u044C, \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043B\u0438 \u0432\u0435\u0442\u043A\u0430 PR \u0442\u0435\u043A\u0443\u0449\u0438\u0439 main \u2014 \u0430 \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0437\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0435 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E \u043F\u0440\u043E main \u043F\u043E\u0441\u043B\u0435 \u0432\u043B\u0438\u0432\u0430\u043D\u0438\u044F. \u042D\u0442\u043E \u0442\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u043E (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B): \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443 (TASWM-472)",
469
+ position,
470
+ size
471
+ };
472
+ }
473
+ if (input.behindBy > 0) {
474
+ return {
475
+ allowed: false,
476
+ code: "base_stale",
477
+ reason: `\u0431\u0430\u0437\u0430 \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u043D\u0430 ${input.behindBy} \u043A\u043E\u043C\u043C\u0438\u0442(\u043E\u0432) \u0441 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u2014 \u0437\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u0439 \u0431\u0430\u0437\u0435 \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E main \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C. \u0412\u043B\u0435\u0439 main \u0432 \u0432\u0435\u0442\u043A\u0443 PR \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 (TASWM-472)`,
478
+ position,
479
+ size
480
+ };
481
+ }
482
+ if (input.checks === null) {
483
+ return {
484
+ allowed: false,
485
+ code: "checks_unknown",
486
+ reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 PR \u2014 \u0430 \u043D\u0430 \u0441\u0432\u0435\u0436\u0435\u0439 \u0431\u0430\u0437\u0435 \u0438\u043C\u0435\u043D\u043D\u043E \u043E\u043D\u0438 \u0438 \u043E\u0442\u0432\u0435\u0447\u0430\u044E\u0442, \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043B\u0438 main \u0437\u0435\u043B\u0451\u043D\u044B\u043C. \u042D\u0442\u043E \u0442\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u043E (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B): \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443 (TASWM-472)",
487
+ position,
488
+ size
489
+ };
490
+ }
491
+ if (input.checks === "fail") {
492
+ return {
493
+ allowed: false,
494
+ code: "checks_red",
495
+ reason: "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u041E\u0411\u041D\u041E\u0412\u041B\u0401\u041D\u041D\u041E\u0419 \u0431\u0430\u0437\u0435 \u043A\u0440\u0430\u0441\u043D\u044B\u0435 \u2014 \u044D\u0442\u043E\u0442 PR \u043B\u043E\u043C\u0430\u0435\u0442 main \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C \u0431\u0430\u0437\u044B. \u041C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F: \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430 (TASWM-472)",
496
+ position,
497
+ size
498
+ };
499
+ }
500
+ if (input.checks === "pending") {
501
+ return {
502
+ allowed: false,
503
+ code: "checks_pending",
504
+ reason: "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 \u0435\u0449\u0451 \u0438\u0434\u0443\u0442 \u2014 \u0438\u0445 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0438 \u0435\u0441\u0442\u044C \u043E\u0442\u0432\u0435\u0442 \xAB\u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043B\u0438 main \u0437\u0435\u043B\u0451\u043D\u044B\u043C\xBB. \u0414\u043E\u0436\u0434\u0438\u0441\u044C (TASWM-472)",
505
+ position,
506
+ size
507
+ };
508
+ }
509
+ return {
510
+ allowed: true,
511
+ code: "queue_head",
512
+ reason: "\u0433\u043E\u043B\u043E\u0432\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430, \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0441\u0432\u0435\u0436\u0430\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043D\u0435\u0439 \u043D\u0435 \u043A\u0440\u0430\u0441\u043D\u044B\u0435 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E",
513
+ position,
514
+ size
515
+ };
516
+ }
517
+ function computeMergeDecision(gate, queue) {
518
+ if (!gate.allowed || gate.code === "unknown_pr" || !queue) return { ...gate, queue: queue ?? null };
519
+ if (!queue.allowed) return { allowed: false, code: queue.code, reason: queue.reason, queue };
520
+ return { ...gate, reason: `${gate.reason}; ${queue.reason}`, queue };
521
+ }
522
+ var init_mergeQueue = __esm({
523
+ "../swarm-core/src/mergeQueue.ts"() {
524
+ "use strict";
525
+ }
526
+ });
527
+
442
528
  // ../swarm-core/src/modelPolicy.ts
443
529
  function findProvider(id) {
444
530
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1545,6 +1631,7 @@ var init_src = __esm({
1545
1631
  init_timeline();
1546
1632
  init_reviewPhase();
1547
1633
  init_mergeGate();
1634
+ init_mergeQueue();
1548
1635
  init_verifyVerdict();
1549
1636
  init_modelPolicy();
1550
1637
  init_status();
@@ -2161,6 +2248,7 @@ __export(config_exports, {
2161
2248
  SWARM_MAX_FD_LOAD_FACTOR: () => SWARM_MAX_FD_LOAD_FACTOR,
2162
2249
  SWARM_MAX_IN_FLIGHT: () => SWARM_MAX_IN_FLIGHT,
2163
2250
  SWARM_MAX_LOAD_FACTOR: () => SWARM_MAX_LOAD_FACTOR,
2251
+ SWARM_MERGE_QUEUE: () => SWARM_MERGE_QUEUE,
2164
2252
  SWARM_MIN_FREE_GB: () => SWARM_MIN_FREE_GB,
2165
2253
  SWARM_OUTBOX: () => SWARM_OUTBOX,
2166
2254
  SWARM_PROD_HEARTBEAT_MS: () => SWARM_PROD_HEARTBEAT_MS,
@@ -2283,7 +2371,7 @@ function reviewCutoffAt() {
2283
2371
  }
2284
2372
  return at;
2285
2373
  }
2286
- var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2374
+ var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2287
2375
  var init_config = __esm({
2288
2376
  "src/config.ts"() {
2289
2377
  "use strict";
@@ -2356,6 +2444,7 @@ var init_config = __esm({
2356
2444
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
2357
2445
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2358
2446
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2447
+ SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2359
2448
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
2360
2449
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2361
2450
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -2401,12 +2490,12 @@ function isTransientResourceError(e) {
2401
2490
  const code = e?.code;
2402
2491
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
2403
2492
  }
2404
- function logTransientSpawnError(label, e) {
2493
+ function logTransientSpawnError(label2, e) {
2405
2494
  if (isTransientResourceError(e)) {
2406
2495
  const code = e.code;
2407
- console.warn(`[overmind] ${label}: \u041E\u0421 \u043D\u0435 \u0434\u0430\u0451\u0442 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B (${code}) \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0438\u0433\u043D\u0430\u043B \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043A\u0430, \u043D\u0435 \u043F\u0430\u0434\u0430\u044E`);
2496
+ console.warn(`[overmind] ${label2}: \u041E\u0421 \u043D\u0435 \u0434\u0430\u0451\u0442 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B (${code}) \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0438\u0433\u043D\u0430\u043B \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043A\u0430, \u043D\u0435 \u043F\u0430\u0434\u0430\u044E`);
2408
2497
  } else {
2409
- console.warn(`[overmind] ${label} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F (\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u0437\u0430 \u044D\u0442\u043E\u0442 \u0442\u0438\u043A): ${String(e)}`);
2498
+ console.warn(`[overmind] ${label2} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F (\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u0437\u0430 \u044D\u0442\u043E\u0442 \u0442\u0438\u043A): ${String(e)}`);
2410
2499
  }
2411
2500
  }
2412
2501
  function resourcesOk(load1, cpus, freeGb, maxLoadFactor = SWARM_MAX_LOAD_FACTOR, minFreeGb = SWARM_MIN_FREE_GB, fdLoadRatio = null, maxFdLoadFactor = SWARM_MAX_FD_LOAD_FACTOR, cpuBusy = null, maxCpuBusy = SWARM_MAX_CPU_BUSY) {
@@ -2417,8 +2506,8 @@ function resourcesOk(load1, cpus, freeGb, maxLoadFactor = SWARM_MAX_LOAD_FACTOR,
2417
2506
  }
2418
2507
  function parseVmStatAvailableBytes(vmStatOut) {
2419
2508
  const page = Number(vmStatOut.match(/page size of (\d+) bytes/)?.[1]) || 4096;
2420
- const pages = (label) => {
2421
- const m = vmStatOut.match(new RegExp(`Pages ${label}:\\s+(\\d+)`));
2509
+ const pages = (label2) => {
2510
+ const m = vmStatOut.match(new RegExp(`Pages ${label2}:\\s+(\\d+)`));
2422
2511
  return m ? Number(m[1]) : 0;
2423
2512
  };
2424
2513
  return (pages("free") + pages("inactive") + pages("speculative") + pages("purgeable")) * page;
@@ -2870,8 +2959,8 @@ function describeError(e, maxDepth = 4) {
2870
2959
  const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2871
2960
  const code = cur.code;
2872
2961
  const codeText = code === void 0 || code === null ? "" : String(code);
2873
- const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2874
- if (label && !parts.includes(label)) parts.push(label);
2962
+ const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2963
+ if (label2 && !parts.includes(label2)) parts.push(label2);
2875
2964
  cur = cur.cause;
2876
2965
  }
2877
2966
  return parts.join(" \u2190 ") || String(e);
@@ -3992,37 +4081,37 @@ async function materializeAttachments(opts) {
3992
4081
  let index = 0;
3993
4082
  for (const ref of opts.refs) {
3994
4083
  index++;
3995
- const label = ref.caption || ref.url;
4084
+ const label2 = ref.caption || ref.url;
3996
4085
  const cls = classifyAttachment(ref);
3997
4086
  if ("skip" in cls) {
3998
- result.skipped.push(`${label} \u2014 ${cls.skip}`);
4087
+ result.skipped.push(`${label2} \u2014 ${cls.skip}`);
3999
4088
  continue;
4000
4089
  }
4001
4090
  if (result.files.length >= maxFiles) {
4002
- result.skipped.push(`${label} \u2014 \u0441\u0432\u0435\u0440\u0445 \u043B\u0438\u043C\u0438\u0442\u0430 \u0432 ${maxFiles} \u0444\u0430\u0439\u043B\u043E\u0432`);
4091
+ result.skipped.push(`${label2} \u2014 \u0441\u0432\u0435\u0440\u0445 \u043B\u0438\u043C\u0438\u0442\u0430 \u0432 ${maxFiles} \u0444\u0430\u0439\u043B\u043E\u0432`);
4003
4092
  continue;
4004
4093
  }
4005
4094
  if (ref.size != null && ref.size > maxFile) {
4006
- result.skipped.push(`${label} \u2014 ${mb(ref.size)} > \u043B\u0438\u043C\u0438\u0442\u0430 ${mb(maxFile)} \u043D\u0430 \u0444\u0430\u0439\u043B`);
4095
+ result.skipped.push(`${label2} \u2014 ${mb(ref.size)} > \u043B\u0438\u043C\u0438\u0442\u0430 ${mb(maxFile)} \u043D\u0430 \u0444\u0430\u0439\u043B`);
4007
4096
  continue;
4008
4097
  }
4009
4098
  let file = null;
4010
4099
  try {
4011
4100
  file = await opts.fetchFile(ref.url);
4012
4101
  } catch (e) {
4013
- result.skipped.push(`${label} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${errText(e)})`);
4102
+ result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${errText(e)})`);
4014
4103
  continue;
4015
4104
  }
4016
4105
  if (!file) {
4017
- result.skipped.push(`${label} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0438\u043B\u0438 \u0444\u0430\u0439\u043B \u0443\u0434\u0430\u043B\u0451\u043D)`);
4106
+ result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0438\u043B\u0438 \u0444\u0430\u0439\u043B \u0443\u0434\u0430\u043B\u0451\u043D)`);
4018
4107
  continue;
4019
4108
  }
4020
4109
  if (file.bytes.byteLength > maxFile) {
4021
- result.skipped.push(`${label} \u2014 ${mb(file.bytes.byteLength)} > \u043B\u0438\u043C\u0438\u0442\u0430 ${mb(maxFile)} \u043D\u0430 \u0444\u0430\u0439\u043B`);
4110
+ result.skipped.push(`${label2} \u2014 ${mb(file.bytes.byteLength)} > \u043B\u0438\u043C\u0438\u0442\u0430 ${mb(maxFile)} \u043D\u0430 \u0444\u0430\u0439\u043B`);
4022
4111
  continue;
4023
4112
  }
4024
4113
  if (total + file.bytes.byteLength > maxTotal) {
4025
- result.skipped.push(`${label} \u2014 \u043D\u0435 \u0432\u043B\u0435\u0437\u043B\u043E \u0432 \u043E\u0431\u0449\u0438\u0439 \u043B\u0438\u043C\u0438\u0442 ${mb(maxTotal)}`);
4114
+ result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0432\u043B\u0435\u0437\u043B\u043E \u0432 \u043E\u0431\u0449\u0438\u0439 \u043B\u0438\u043C\u0438\u0442 ${mb(maxTotal)}`);
4026
4115
  continue;
4027
4116
  }
4028
4117
  const name = attachmentFileName(index, ref.caption, attachmentExt(ref, file.contentType));
@@ -4033,7 +4122,7 @@ async function materializeAttachments(opts) {
4033
4122
  }
4034
4123
  await io.writeFile(join20(dir, name), file.bytes);
4035
4124
  } catch (e) {
4036
- result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
4125
+ result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
4037
4126
  continue;
4038
4127
  }
4039
4128
  total += file.bytes.byteLength;
@@ -4681,7 +4770,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4681
4770
  return await safeCall("update_task", { id, assigneeId: userId }) != null;
4682
4771
  },
4683
4772
  uploadShot: (file) => uploadShot(safeCall, file, fetchImpl),
4684
- addComment: async (idOrSlug, body, label, attachments) => {
4773
+ addComment: async (idOrSlug, body, label2, attachments) => {
4685
4774
  const id = await resolveId(idOrSlug);
4686
4775
  if (!id) {
4687
4776
  console.error(`[taskless] add_comment \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D: \u0437\u0430\u0434\u0430\u0447\u0430 ${idOrSlug} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 (resolveId \u2192 null)`);
@@ -4700,7 +4789,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4700
4789
  body: parts[i],
4701
4790
  // Метки роя — на КАЖДОЙ части: без них хвост отчёта выглядит в ленте задачи
4702
4791
  // чужой репликой без автора (swarmKind/swarmDroneName рисуются чипами).
4703
- ...label ?? {},
4792
+ ...label2 ?? {},
4704
4793
  // TASWM-294: скрины — к ПЕРВОЙ части, к шапке отчёта. Разносить их по
4705
4794
  // частям треда нельзя: человек открывает задачу и должен увидеть, как
4706
4795
  // это выглядит, в том же комменте, что и «что сделано».
@@ -10472,6 +10561,22 @@ async function prHeadBranch(prUrl, run = realRunner) {
10472
10561
  return null;
10473
10562
  }
10474
10563
  }
10564
+ async function prBaseSync(prUrl, run = realRunner) {
10565
+ const m = prUrl.trim().match(/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/\d+/i);
10566
+ if (!isGithubPrUrl(prUrl) || !m) return null;
10567
+ const [, owner, repo] = m;
10568
+ try {
10569
+ const j = JSON.parse(await run(["pr", "view", prUrl, "--json", "baseRefName,headRefOid"]));
10570
+ const base3 = j.baseRefName;
10571
+ const head = j.headRefOid;
10572
+ if (!base3 || !head) return null;
10573
+ const out = await run(["api", `repos/${owner}/${repo}/compare/${base3}...${head}?per_page=1`, "--jq", ".behind_by"]);
10574
+ const behindBy = Number(out.trim());
10575
+ return Number.isFinite(behindBy) && behindBy >= 0 ? { behindBy, base: base3 } : null;
10576
+ } catch {
10577
+ return null;
10578
+ }
10579
+ }
10475
10580
  async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
10476
10581
  if (!branch || !repoDir) return [];
10477
10582
  try {
@@ -10704,17 +10809,17 @@ function normalizeZoneLabel(zone) {
10704
10809
  return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
10705
10810
  }
10706
10811
  function renderZoneMemoryBlock(zone, notes) {
10707
- const label = normalizeZoneLabel(zone);
10708
- if (!label) return "";
10812
+ const label2 = normalizeZoneLabel(zone);
10813
+ if (!label2) return "";
10709
10814
  const head = notes.length ? [
10710
- `\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
10815
+ `\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
10711
10816
  ...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
10712
10817
  "\u042D\u0442\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438, \u0430 \u043D\u0435 \u0438\u0441\u0442\u0438\u043D\u0430 \u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0446\u0438\u0438: \u0440\u0430\u0437\u043E\u0448\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C \u2014 \u0432\u0435\u0440\u044C \u043A\u043E\u0434\u0443 \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0441\u0434\u0430\u0447\u0435.",
10713
10818
  `\u041F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u0432 done: \`zone_notes_used: ["${notes[0].id}"]\` (id \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435 \u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u042D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0436\u0438\u0432\u0451\u0442: \u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430, \u0438 \u0432\u044B\u0442\u0435\u0441\u043D\u044F\u0435\u0442\u0441\u044F \u0442\u0430, \u0432 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0440\u043E\u0439 \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0445 \u043D\u0435 \u0443\u0431\u0435\u0436\u0434\u0430\u043B\u0441\u044F. \u041D\u0435 \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u043E\u0441\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E, \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434.`
10714
- ] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
10819
+ ] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
10715
10820
  return [
10716
10821
  ...head,
10717
- `\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`
10822
+ `\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label2}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`
10718
10823
  ].join("\n");
10719
10824
  }
10720
10825
  function collectZoneNoteConfirmations(report) {
@@ -12508,6 +12613,11 @@ function makeDroneCapReader(opts) {
12508
12613
 
12509
12614
  // src/mergeGate.ts
12510
12615
  init_src();
12616
+
12617
+ // src/mergeQueue.ts
12618
+ init_src();
12619
+
12620
+ // src/prNumber.ts
12511
12621
  function prNumberOf(ref) {
12512
12622
  const s = (ref || "").trim();
12513
12623
  if (!s) return null;
@@ -12516,6 +12626,79 @@ function prNumberOf(ref) {
12516
12626
  const bare = s.match(/^#?(\d+)$/);
12517
12627
  return bare ? Number(bare[1]) : null;
12518
12628
  }
12629
+
12630
+ // src/mergeQueue.ts
12631
+ var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
12632
+ function isMergeCandidate(r) {
12633
+ return !!r.pr_url && r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
12634
+ }
12635
+ function mergeQueueCandidates(runs, repo) {
12636
+ const byPr = /* @__PURE__ */ new Map();
12637
+ for (const r of runs) {
12638
+ if (r.repo !== repo || !isMergeCandidate(r)) continue;
12639
+ const num = prNumberOf(r.pr_url);
12640
+ const seen = byPr.get(num);
12641
+ if (!seen || r.id > seen.id) byPr.set(num, r);
12642
+ }
12643
+ return [...byPr.values()];
12644
+ }
12645
+ var BASE_TTL_MS = 2e4;
12646
+ function makeMergeQueueResolver(deps) {
12647
+ const memo = /* @__PURE__ */ new Map();
12648
+ const evaluate = async (e, prUrl, ready) => {
12649
+ const now = Date.now();
12650
+ let cached = memo.get(prUrl);
12651
+ const ttl = deps.ttlMs ?? BASE_TTL_MS;
12652
+ if (!cached || now - cached.at >= ttl) {
12653
+ const base3 = await deps.baseSync(prUrl).catch(() => null);
12654
+ let checks = "none";
12655
+ if (base3?.behindBy === 0 && deps.checks) {
12656
+ const ci = await deps.checks(prUrl).catch(() => null);
12657
+ checks = ci ? ci.bucket : null;
12658
+ }
12659
+ cached = { at: now, base: base3, checks };
12660
+ memo.set(prUrl, cached);
12661
+ }
12662
+ return computeMergeQueue({ entry: e, ready, behindBy: cached.base ? cached.base.behindBy : null, checks: cached.checks });
12663
+ };
12664
+ return async (run) => {
12665
+ if (!deps.baseSync || !run.pr_url) return null;
12666
+ const num = prNumberOf(run.pr_url);
12667
+ if (num == null) return null;
12668
+ const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
12669
+ const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
12670
+ if (!mine) return null;
12671
+ const ready = [];
12672
+ const urlOf = /* @__PURE__ */ new Map();
12673
+ for (const c of candidates) {
12674
+ if (!await deps.gateAllows(c).catch(() => false)) continue;
12675
+ ready.push(entryOf(c));
12676
+ urlOf.set(c.id, c.pr_url);
12677
+ }
12678
+ const entry = entryOf(mine);
12679
+ if (!ready.some((e) => e.runId === entry.runId)) return null;
12680
+ let queue = orderMergeQueue(ready);
12681
+ while (queue.length > 1 && queue[0].runId !== entry.runId) {
12682
+ const head = queue[0];
12683
+ const headUrl = urlOf.get(head.runId);
12684
+ const decision = await evaluate(head, headUrl, queue);
12685
+ if (decision.code !== "checks_red") break;
12686
+ queue = queue.slice(1);
12687
+ }
12688
+ if (queue[0].runId !== entry.runId) {
12689
+ return computeMergeQueue({ entry, ready: queue, behindBy: null, checks: "none" });
12690
+ }
12691
+ return evaluate(entry, mine.pr_url, queue);
12692
+ };
12693
+ }
12694
+ function entryOf(r) {
12695
+ return { runId: r.id, prNumber: prNumberOf(r.pr_url), slug: r.task_slug };
12696
+ }
12697
+ function needsBaseRefresh(d) {
12698
+ return d?.code === "base_stale";
12699
+ }
12700
+
12701
+ // src/mergeGate.ts
12519
12702
  function findRunByPr(runs, ref) {
12520
12703
  const num = prNumberOf(ref);
12521
12704
  if (num == null) return null;
@@ -12527,17 +12710,34 @@ function findRunByPr(runs, ref) {
12527
12710
  return best;
12528
12711
  }
12529
12712
  function makeMergeGateResolver(deps) {
12530
- return async (ref) => {
12531
- const run = findRunByPr(deps.listRuns(), ref);
12532
- if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
12713
+ return makeMergeGateParts(deps).gate;
12714
+ }
12715
+ function makeMergeGateParts(deps) {
12716
+ const gateFor = async (run) => {
12533
12717
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
12534
- const decision = computeMergeGate({
12718
+ return computeMergeGate({
12535
12719
  hasRun: true,
12536
12720
  autoReview,
12537
12721
  reviewVerdict: run.review_verdict,
12538
12722
  autoVerify: deps.autoVerify === true,
12539
12723
  verifyVerdict: run.verify_verdict ?? null
12540
12724
  });
12725
+ };
12726
+ const queueFor = makeMergeQueueResolver({
12727
+ listRuns: deps.listRuns,
12728
+ gateAllows: async (r) => (await gateFor(r)).allowed,
12729
+ ...deps.baseSync ? { baseSync: deps.baseSync } : {},
12730
+ ...deps.checks ? { checks: deps.checks } : {},
12731
+ ...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
12732
+ });
12733
+ const gate = async (ref) => {
12734
+ const run = findRunByPr(deps.listRuns(), ref);
12735
+ if (!run) {
12736
+ return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
12737
+ }
12738
+ const accept = await gateFor(run);
12739
+ const queue = accept.allowed && accept.code !== "unknown_pr" ? await queueFor(run) : null;
12740
+ const decision = computeMergeDecision(accept, queue);
12541
12741
  return {
12542
12742
  ...decision,
12543
12743
  run: {
@@ -12550,6 +12750,7 @@ function makeMergeGateResolver(deps) {
12550
12750
  }
12551
12751
  };
12552
12752
  };
12753
+ return { gate, queue: queueFor };
12553
12754
  }
12554
12755
 
12555
12756
  // src/envGate.ts
@@ -12990,7 +13191,7 @@ async function changedPaths(worktree, opts = {}) {
12990
13191
  // package.json
12991
13192
  var package_default = {
12992
13193
  name: "@taskless-app/swarm",
12993
- version: "0.6.18",
13194
+ version: "0.6.19",
12994
13195
  type: "module",
12995
13196
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
12996
13197
  keywords: [
@@ -13040,7 +13241,7 @@ var package_default = {
13040
13241
  execa: "^10.0.0",
13041
13242
  zod: "3.25.76"
13042
13243
  },
13043
- gitHead: "9531f684d8a7c6c4c3595f0c57e4aebce0038ac1"
13244
+ gitHead: "a61c26b5e254ef5383d918f14a9f6e8e8a09904a"
13044
13245
  };
13045
13246
 
13046
13247
  // src/version.ts
@@ -14743,8 +14944,8 @@ async function executeHostCommands(opts) {
14743
14944
  }
14744
14945
  async function executeOne(cmd, opts) {
14745
14946
  const { effects, ack, statePath } = opts;
14746
- const label = hostCommandLabel(cmd);
14747
- effects.narrate(`\u{1F4DF} \u041A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u0437 Taskless: ${label}`);
14947
+ const label2 = hostCommandLabel(cmd);
14948
+ effects.narrate(`\u{1F4DF} \u041A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u0437 Taskless: ${label2}`);
14748
14949
  try {
14749
14950
  switch (cmd.kind) {
14750
14951
  case "pause":
@@ -15627,6 +15828,14 @@ function lastConflictAttemptMs(db, runId) {
15627
15828
  if (!evs.length) return null;
15628
15829
  return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
15629
15830
  }
15831
+ async function needsBaseRefreshNow(deps, run, stormActive, now) {
15832
+ if (!deps.mergeQueue || !deps.resolveConflict || stormActive) return false;
15833
+ if (deps.db.activeResolverFor(run.id)) return false;
15834
+ if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) return false;
15835
+ const last = lastConflictAttemptMs(deps.db, run.id);
15836
+ if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) return false;
15837
+ return needsBaseRefresh(await deps.mergeQueue(run).catch(() => null));
15838
+ }
15630
15839
  function lastCiCheckMs(db, runId) {
15631
15840
  const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked");
15632
15841
  if (!evs.length) return null;
@@ -17653,7 +17862,8 @@ async function tickOnce(deps) {
17653
17862
  continue;
17654
17863
  }
17655
17864
  await retireOutdatedReviewers(deps, run);
17656
- if (st.conflicting && deps.resolveConflict && !stormActive) {
17865
+ const baseStale = !st.conflicting && await needsBaseRefreshNow(deps, run, stormActive, now);
17866
+ if ((st.conflicting || baseStale) && deps.resolveConflict && !stormActive) {
17657
17867
  if (deps.resourcesOk && !deps.resourcesOk()) continue;
17658
17868
  if (countActiveDrones(deps.db) >= droneCap) continue;
17659
17869
  if (!await projectQuotaGateAllows(deps, run)) continue;
@@ -17666,7 +17876,11 @@ async function tickOnce(deps) {
17666
17876
  }
17667
17877
  const last = lastConflictAttemptMs(deps.db, run.id);
17668
17878
  if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) continue;
17669
- deps.db.addEvent(run.id, "conflict", "PR \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 main \u2014 \u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 main (merge-main-in)");
17879
+ deps.db.addEvent(
17880
+ run.id,
17881
+ "conflict",
17882
+ st.conflicting ? "PR \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 main \u2014 \u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 main (merge-main-in)" : "\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043C\u0435\u0440\u0436\u0430: \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u2014 \u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 main \u0438 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0435\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 (TASWM-472)"
17883
+ );
17670
17884
  try {
17671
17885
  await deps.resolveConflict(run);
17672
17886
  } catch (e) {
@@ -18989,8 +19203,21 @@ async function runDaemon(opts) {
18989
19203
  };
18990
19204
  control.set = setPaused;
18991
19205
  const restart = initialRestartState();
19206
+ const mergeParts = makeMergeGateParts({
19207
+ listRuns: () => db.listRuns(),
19208
+ ...autoReviewProject ? { autoReview: autoReviewProject } : {},
19209
+ fallbackAutoReview: SWARM_AUTO_REVIEW,
19210
+ autoVerify: SWARM_AUTO_VERIFY,
19211
+ // TASWM-344: вторая ось приёмки — вердикт верификатора
19212
+ // TASWM-472: третья ось — ОЧЕРЕДЬ. Вливаем по одному и только на свежей базе; ридер
19213
+ // свежести ходит в gh (`compare/base...head` → behind_by). Ключа нет при
19214
+ // SWARM_MERGE_QUEUE=0 — тогда гейт отвечает ровно как до TASWM-472.
19215
+ ...SWARM_MERGE_QUEUE ? { baseSync: (url) => prBaseSync(url), checks: (url) => prCiStatus(url) } : {}
19216
+ });
18992
19217
  const deps = {
18993
19218
  db,
19219
+ // TASWM-472: реконсайлу от очереди нужен ровно исход `base_stale` (см. needsBaseRefreshNow).
19220
+ ...SWARM_MERGE_QUEUE ? { mergeQueue: mergeParts.queue } : {},
18994
19221
  narrate: narrator.narrate,
18995
19222
  control,
18996
19223
  spawn: async (run) => {
@@ -19072,7 +19299,7 @@ async function runDaemon(opts) {
19072
19299
  // TasklessClient.addComment + gh/git хелперы. Без tl (нет токена) ключи не задаём —
19073
19300
  // postAdoptionReport/postScrapedVerdictReport становятся no-op, как раньше.
19074
19301
  ...tl ? {
19075
- reportComment: (slug, body, label, ws) => tl.forWorkspace(ws).addComment(slug, body, label)
19302
+ reportComment: (slug, body, label2, ws) => tl.forWorkspace(ws).addComment(slug, body, label2)
19076
19303
  } : {},
19077
19304
  prCommitTitles: (prUrl) => prCommitTitles(prUrl),
19078
19305
  prHeadBranch: (prUrl) => prHeadBranch(prUrl),
@@ -19548,13 +19775,7 @@ async function runDaemon(opts) {
19548
19775
  // TASWM-396: тот же ридер решает и подъём ревьюера, не только мерж — иначе проектная
19549
19776
  // настройка «ревью не нужно» ничего не выключала, а машина продолжала платить.
19550
19777
  ...autoReviewProject ? { autoReviewForRepo: autoReviewProject } : {},
19551
- mergeGate: makeMergeGateResolver({
19552
- listRuns: () => db.listRuns(),
19553
- ...autoReviewProject ? { autoReview: autoReviewProject } : {},
19554
- fallbackAutoReview: SWARM_AUTO_REVIEW,
19555
- autoVerify: SWARM_AUTO_VERIFY
19556
- // TASWM-344: вторая ось приёмки — вердикт верификатора
19557
- }),
19778
+ mergeGate: mergeParts.gate,
19558
19779
  // Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
19559
19780
  // narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
19560
19781
  control,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.18",
3
+ "version": "0.6.19",
4
4
  "type": "module",
5
5
  "description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6
6
  "keywords": [
@@ -38,7 +38,7 @@
38
38
  "execa": "^10.0.0",
39
39
  "zod": "3.25.76"
40
40
  },
41
- "gitHead": "9531f684d8a7c6c4c3595f0c57e4aebce0038ac1",
41
+ "gitHead": "a61c26b5e254ef5383d918f14a9f6e8e8a09904a",
42
42
  "scripts": {
43
43
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
44
44
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",