@taskless-app/swarm 0.6.18 → 0.6.20

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 +201 -16
  2. package/dist/overmind.js +558 -57
  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, SWARM_WORKTREE_REAP_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";
@@ -2129,6 +2216,7 @@ var init_config = __esm({
2129
2216
  SWARM_PROD_HEARTBEAT_MS = envNum("SWARM_PROD_HEARTBEAT_MS", 15e3);
2130
2217
  SWARM_VERSION_CHECK_MS = envNum("SWARM_VERSION_CHECK_MS", 5 * 6e4);
2131
2218
  SWARM_SWEEP_INTERVAL_MS = envNum("SWARM_SWEEP_INTERVAL_MS", 10 * 6e4);
2219
+ SWARM_WORKTREE_REAP_INTERVAL_MS = envNum("SWARM_WORKTREE_REAP_INTERVAL_MS", 5 * 6e4);
2132
2220
  DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
2133
2221
  DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
2134
2222
  DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
@@ -2153,6 +2241,7 @@ var init_config = __esm({
2153
2241
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
2154
2242
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2155
2243
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2244
+ SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2156
2245
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join4(SWARM_HOME, "review-cutoff.json");
2157
2246
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2158
2247
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -2284,7 +2373,7 @@ var init_package = __esm({
2284
2373
  "package.json"() {
2285
2374
  package_default = {
2286
2375
  name: "@taskless-app/swarm",
2287
- version: "0.6.18",
2376
+ version: "0.6.20",
2288
2377
  type: "module",
2289
2378
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2290
2379
  keywords: [
@@ -2334,7 +2423,7 @@ var init_package = __esm({
2334
2423
  execa: "^10.0.0",
2335
2424
  zod: "3.25.76"
2336
2425
  },
2337
- gitHead: "9531f684d8a7c6c4c3595f0c57e4aebce0038ac1"
2426
+ gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
2338
2427
  };
2339
2428
  }
2340
2429
  });
@@ -2501,8 +2590,8 @@ function describeError(e, maxDepth = 4) {
2501
2590
  const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2502
2591
  const code = cur.code;
2503
2592
  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);
2593
+ const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2594
+ if (label2 && !parts.includes(label2)) parts.push(label2);
2506
2595
  cur = cur.cause;
2507
2596
  }
2508
2597
  return parts.join(" \u2190 ") || String(e);
@@ -5172,18 +5261,18 @@ function isTransientResourceError(e) {
5172
5261
  const code = e?.code;
5173
5262
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
5174
5263
  }
5175
- function logTransientSpawnError(label, e) {
5264
+ function logTransientSpawnError(label2, e) {
5176
5265
  if (isTransientResourceError(e)) {
5177
5266
  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`);
5267
+ 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
5268
  } 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)}`);
5269
+ 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
5270
  }
5182
5271
  }
5183
5272
  function parseVmStatAvailableBytes(vmStatOut) {
5184
5273
  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+)`));
5274
+ const pages = (label2) => {
5275
+ const m = vmStatOut.match(new RegExp(`Pages ${label2}:\\s+(\\d+)`));
5187
5276
  return m ? Number(m[1]) : 0;
5188
5277
  };
5189
5278
  return (pages("free") + pages("inactive") + pages("speculative") + pages("purgeable")) * page;
@@ -8469,6 +8558,11 @@ init_config();
8469
8558
 
8470
8559
  // src/mergeGate.ts
8471
8560
  init_src();
8561
+
8562
+ // src/mergeQueue.ts
8563
+ init_src();
8564
+
8565
+ // src/prNumber.ts
8472
8566
  function prNumberOf(ref) {
8473
8567
  const s = (ref || "").trim();
8474
8568
  if (!s) return null;
@@ -8477,6 +8571,76 @@ function prNumberOf(ref) {
8477
8571
  const bare = s.match(/^#?(\d+)$/);
8478
8572
  return bare ? Number(bare[1]) : null;
8479
8573
  }
8574
+
8575
+ // src/mergeQueue.ts
8576
+ var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
8577
+ function isMergeCandidate(r) {
8578
+ 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;
8579
+ }
8580
+ function mergeQueueCandidates(runs, repo) {
8581
+ const byPr = /* @__PURE__ */ new Map();
8582
+ for (const r of runs) {
8583
+ if (r.repo !== repo || !isMergeCandidate(r)) continue;
8584
+ const num = prNumberOf(r.pr_url);
8585
+ const seen = byPr.get(num);
8586
+ if (!seen || r.id > seen.id) byPr.set(num, r);
8587
+ }
8588
+ return [...byPr.values()];
8589
+ }
8590
+ var BASE_TTL_MS = 2e4;
8591
+ function makeMergeQueueResolver(deps) {
8592
+ const memo = /* @__PURE__ */ new Map();
8593
+ const evaluate = async (e, prUrl, ready) => {
8594
+ const now = Date.now();
8595
+ let cached = memo.get(prUrl);
8596
+ const ttl = deps.ttlMs ?? BASE_TTL_MS;
8597
+ if (!cached || now - cached.at >= ttl) {
8598
+ const base3 = await deps.baseSync(prUrl).catch(() => null);
8599
+ let checks = "none";
8600
+ if (base3?.behindBy === 0 && deps.checks) {
8601
+ const ci = await deps.checks(prUrl).catch(() => null);
8602
+ checks = ci ? ci.bucket : null;
8603
+ }
8604
+ cached = { at: now, base: base3, checks };
8605
+ memo.set(prUrl, cached);
8606
+ }
8607
+ return computeMergeQueue({ entry: e, ready, behindBy: cached.base ? cached.base.behindBy : null, checks: cached.checks });
8608
+ };
8609
+ return async (run) => {
8610
+ if (!deps.baseSync || !run.pr_url) return null;
8611
+ const num = prNumberOf(run.pr_url);
8612
+ if (num == null) return null;
8613
+ const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
8614
+ const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
8615
+ if (!mine) return null;
8616
+ const ready = [];
8617
+ const urlOf = /* @__PURE__ */ new Map();
8618
+ for (const c of candidates) {
8619
+ if (!await deps.gateAllows(c).catch(() => false)) continue;
8620
+ ready.push(entryOf(c));
8621
+ urlOf.set(c.id, c.pr_url);
8622
+ }
8623
+ const entry = entryOf(mine);
8624
+ if (!ready.some((e) => e.runId === entry.runId)) return null;
8625
+ let queue = orderMergeQueue(ready);
8626
+ while (queue.length > 1 && queue[0].runId !== entry.runId) {
8627
+ const head = queue[0];
8628
+ const headUrl = urlOf.get(head.runId);
8629
+ const decision = await evaluate(head, headUrl, queue);
8630
+ if (decision.code !== "checks_red") break;
8631
+ queue = queue.slice(1);
8632
+ }
8633
+ if (queue[0].runId !== entry.runId) {
8634
+ return computeMergeQueue({ entry, ready: queue, behindBy: null, checks: "none" });
8635
+ }
8636
+ return evaluate(entry, mine.pr_url, queue);
8637
+ };
8638
+ }
8639
+ function entryOf(r) {
8640
+ return { runId: r.id, prNumber: prNumberOf(r.pr_url), slug: r.task_slug };
8641
+ }
8642
+
8643
+ // src/mergeGate.ts
8480
8644
  function findRunByPr(runs, ref) {
8481
8645
  const num = prNumberOf(ref);
8482
8646
  if (num == null) return null;
@@ -8488,17 +8652,34 @@ function findRunByPr(runs, ref) {
8488
8652
  return best;
8489
8653
  }
8490
8654
  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 };
8655
+ return makeMergeGateParts(deps).gate;
8656
+ }
8657
+ function makeMergeGateParts(deps) {
8658
+ const gateFor = async (run) => {
8494
8659
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
8495
- const decision = computeMergeGate({
8660
+ return computeMergeGate({
8496
8661
  hasRun: true,
8497
8662
  autoReview,
8498
8663
  reviewVerdict: run.review_verdict,
8499
8664
  autoVerify: deps.autoVerify === true,
8500
8665
  verifyVerdict: run.verify_verdict ?? null
8501
8666
  });
8667
+ };
8668
+ const queueFor = makeMergeQueueResolver({
8669
+ listRuns: deps.listRuns,
8670
+ gateAllows: async (r) => (await gateFor(r)).allowed,
8671
+ ...deps.baseSync ? { baseSync: deps.baseSync } : {},
8672
+ ...deps.checks ? { checks: deps.checks } : {},
8673
+ ...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
8674
+ });
8675
+ const gate = async (ref) => {
8676
+ const run = findRunByPr(deps.listRuns(), ref);
8677
+ if (!run) {
8678
+ return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
8679
+ }
8680
+ const accept = await gateFor(run);
8681
+ const queue = accept.allowed && accept.code !== "unknown_pr" ? await queueFor(run) : null;
8682
+ const decision = computeMergeDecision(accept, queue);
8502
8683
  return {
8503
8684
  ...decision,
8504
8685
  run: {
@@ -8511,6 +8692,7 @@ function makeMergeGateResolver(deps) {
8511
8692
  }
8512
8693
  };
8513
8694
  };
8695
+ return { gate, queue: queueFor };
8514
8696
  }
8515
8697
 
8516
8698
  // src/web.ts
@@ -10841,12 +11023,15 @@ async function previousHooksPath(worktree) {
10841
11023
  }
10842
11024
  async function removeWorktree(repo, path) {
10843
11025
  const branch = (await $5({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
11026
+ await releaseWorktree(repo, path);
11027
+ if (branch) await $5({ reject: false })`git -C ${repo} branch -D ${branch}`;
11028
+ }
11029
+ async function releaseWorktree(repo, path) {
10844
11030
  await cleanupWorktreeProcesses(path);
10845
11031
  await detachWorktree(repo, path);
10846
- if (branch) await $5({ reject: false })`git -C ${repo} branch -D ${branch}`;
10847
11032
  }
10848
11033
  async function worktreeDirty(path) {
10849
- const st = await $5({ reject: false })`git -C ${path} status --porcelain`;
11034
+ const st = await $5({ reject: false })`git -C ${path} status --porcelain --untracked-files=all`;
10850
11035
  if (st.exitCode !== 0) return true;
10851
11036
  const status = st.stdout.trim();
10852
11037
  if (status) return true;